137 lines
5.3 KiB
JavaScript
Executable File
137 lines
5.3 KiB
JavaScript
Executable File
const fs = require("fs-extra");
|
|
const path = require("path");
|
|
const Odmdb = require("./Odmdb.js");
|
|
|
|
const DATA_PATH = process.env.DATAPATH;
|
|
const TRIBE_NAME = process.env.TOWN;
|
|
const log = process.env.ACTIVELOG?.split(',').includes('Towns');
|
|
|
|
/**
|
|
* Manages towns, which represent server instances in the network.
|
|
*/
|
|
class Towns {
|
|
|
|
/**
|
|
* Changes the owner (mayor) of a town.
|
|
* @param {string} newOwnerAlias - The alias of the new owner.
|
|
* @param {string} requestedByAlias - The alias of the user requesting the change (must be the current owner).
|
|
* @returns {Promise<object>} A response object.
|
|
*/
|
|
static async changeowner(newOwnerAlias, requestedByAlias) {
|
|
try {
|
|
const townsDb = new Odmdb('towns', TRIBE_NAME);
|
|
const pagansDb = new Odmdb('pagans', TRIBE_NAME);
|
|
const confPath = path.join(DATA_PATH, TRIBE_NAME, 'conf.json');
|
|
|
|
// 1. Verify the new owner exists as a pagan
|
|
const newOwnerResult = await pagansDb.read(newOwnerAlias, { xprofils: ['admin'] });
|
|
if (newOwnerResult.status !== 200) {
|
|
return { status: 404, ref: "Towns", msg: "new_owner_alias_not_found", data: { alias: newOwnerAlias } };
|
|
}
|
|
|
|
// 2. Verify the requester is the current mayor
|
|
const townConf = await fs.readJson(confPath);
|
|
if (townConf.mayorId !== requestedByAlias) {
|
|
return { status: 403, ref: "Towns", msg: "permission_denied_not_current_mayor" };
|
|
}
|
|
|
|
// 3. Update the town's data file
|
|
const townId = townConf.townId;
|
|
const townUpdateData = { townId, mayorId: newOwnerAlias };
|
|
const role = { xprofils: ['admin', 'mayor'], xalias: requestedByAlias };
|
|
const updateResult = await townsDb.update( townUpdateData, role);
|
|
|
|
if (updateResult.status !== 200) {
|
|
return updateResult; // Forward the error from Odmdb
|
|
}
|
|
|
|
// 4. Update the local server configuration file
|
|
townConf.mayorId = newOwnerAlias;
|
|
await fs.outputJson(confPath, townConf, { spaces: 2 });
|
|
|
|
if (log) console.log(`[Towns] Town owner changed successfully to '${newOwnerAlias}' by '${requestedByAlias}'.`);
|
|
return { status: 200, ref: "Towns", msg: "owner_change_successful", data: { alias: newOwnerAlias, townId } };
|
|
|
|
} catch (error) {
|
|
console.error(`[Towns] Error changing owner to '${newOwnerAlias}':`, error);
|
|
return { status: 500, ref: "Towns", msg: "internal_server_error", error: error.message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @deprecated The create method contains server setup logic (like nginx config)
|
|
* and should not be part of a data model. This logic should be moved to a
|
|
* dedicated setup or installation script.
|
|
*/
|
|
static create() {
|
|
console.error("[Towns] The 'create' method is deprecated and should not be used. Please use a dedicated setup script.");
|
|
return { status: 501, ref: "Towns", msg: "create_method_is_deprecated" };
|
|
}
|
|
|
|
static async addCrontabEntry(cronExpression, command, identifier) {
|
|
try {
|
|
const { exec } = require('child_process');
|
|
const { promisify } = require('util');
|
|
const execAsync = promisify(exec);
|
|
|
|
const tmpCronFile = path.join(DATA_PATH, TRIBE_NAME, 'tmp', `cron_${Date.now()}.txt`);
|
|
await fs.ensureDir(path.dirname(tmpCronFile));
|
|
|
|
const { stdout: currentCron } = await execAsync('crontab -l 2>/dev/null || true');
|
|
|
|
const cronLines = currentCron.split('\n').filter(line => {
|
|
if (!line.trim()) return false;
|
|
if (identifier && line.includes(identifier)) return false;
|
|
return true;
|
|
});
|
|
|
|
const cronEntry = `${cronExpression} ${command} # ${identifier}`;
|
|
cronLines.push(cronEntry);
|
|
|
|
await fs.writeFile(tmpCronFile, cronLines.join('\n') + '\n');
|
|
|
|
await execAsync(`crontab "${tmpCronFile}"`);
|
|
await fs.remove(tmpCronFile);
|
|
|
|
if (log) console.log(`[Towns] Crontab entry added: ${cronEntry}`);
|
|
return { status: 200, ref: "Towns", msg: "crontab_entry_added", data: { cronEntry } };
|
|
|
|
} catch (error) {
|
|
console.error('[Towns] Error adding crontab entry:', error);
|
|
return { status: 500, ref: "Towns", msg: "crontab_entry_failed", error: error.message };
|
|
}
|
|
}
|
|
|
|
static async removeCrontabEntry(identifier) {
|
|
try {
|
|
const { exec } = require('child_process');
|
|
const { promisify } = require('util');
|
|
const execAsync = promisify(exec);
|
|
|
|
const tmpCronFile = path.join(DATA_PATH, TRIBE_NAME, 'tmp', `cron_${Date.now()}.txt`);
|
|
await fs.ensureDir(path.dirname(tmpCronFile));
|
|
|
|
const { stdout: currentCron } = await execAsync('crontab -l 2>/dev/null || true');
|
|
|
|
const cronLines = currentCron.split('\n').filter(line => {
|
|
if (!line.trim()) return false;
|
|
if (identifier && line.includes(identifier)) return false;
|
|
return true;
|
|
});
|
|
|
|
await fs.writeFile(tmpCronFile, cronLines.join('\n') + '\n');
|
|
|
|
await execAsync(`crontab "${tmpCronFile}"`);
|
|
await fs.remove(tmpCronFile);
|
|
|
|
if (log) console.log(`[Towns] Crontab entries removed for: ${identifier}`);
|
|
return { status: 200, ref: "Towns", msg: "crontab_entry_removed", data: { identifier } };
|
|
|
|
} catch (error) {
|
|
console.error('[Towns] Error removing crontab entry:', error);
|
|
return { status: 500, ref: "Towns", msg: "crontab_remove_failed", error: error.message };
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = Towns; |