a3ensite
Version:
Apache/Nginx/Config Manager, JSON based
345 lines (329 loc) • 9.62 kB
text/typescript
import fs from "fs";
import url from "url";
import path from "path";
import minimist, { ParsedArgs } from "minimist";
//{ accessSync, constants }
const default_argv = {
template: "./a3ensite.conf",
config: "./a3ensite.json",
};
function print_error(...args: [any]) {
console.warn(...args);
console.warn("-----------------------------\n");
process.exit(0);
}
function print_log(...args: [any]) {
console.log(...args);
}
print_log("-> Still Lovin You");
const app$argv: ParsedArgs = minimist(process.argv.slice(2), {
default: default_argv,
});
const app$config: AppConfig = setup_config();
const tasks: { [key: string]: { [key: string]: any } } = {
make: {
hosts: $make_hosts,
sites: $make_sites,
},
unmake: {
hosts: null,
sites: null,
},
status: $sites_status,
};
let task: any = tasks;
for (let i = 0; i < app$argv._.length; i++) {
const taskname = app$argv._[i];
if (!task) break;
task = task[taskname];
}
if (typeof task != "function")
print_error(
`error: '${app$argv._.join(
"->",
)}' ~ Invalid task \n Help \n--config a3ensite.json \n--template a3ensite.conf`,
);
else {
task();
print_log(`success = '${app$argv._.join("->")}'`);
}
print_log("\n");
function $make_hosts() {
const start_tag = "#a3ensite-start";
const end_tag = "#a3ensite-end";
if (!access$check(app$config.options.hosts.EnabledLoc)) {
print_error("Hosts file not writable config: options.hosts.EnabledLoc");
}
const curr_hosts = fs.readFileSync(
app$config.options.hosts.EnabledLoc,
"utf8",
);
const start_pos = curr_hosts.indexOf(start_tag);
const end_pos = curr_hosts.indexOf(end_tag);
let new_hosts = curr_hosts;
if (start_pos > -1 && end_pos > -1) {
new_hosts = `${curr_hosts.substring(0, start_pos)}${curr_hosts.substring(
end_pos + end_tag.length,
curr_hosts.length,
)}`;
}
const local_hosts: string[] = [];
local_hosts.push(`${start_tag} #### DO NOT EDIT`);
for (let i = 0; i < app$config.sites.length; i++) {
const site = app$config.sites[i];
let $ip: string =
site[app$config.options.hosts.HostIpKey] ||
app$config.defaults[app$config.options.hosts.HostIpKey];
let $name: string =
site[app$config.options.hosts.HostNameKey] ??
app$config.defaults[app$config.options.hosts.HostNameKey];
local_hosts.push(`${$ip} ${$name}`);
}
local_hosts.push(end_tag);
new_hosts = new_hosts + local_hosts.join("\n");
fs.writeFileSync(app$config.options.hosts.EnabledLoc, new_hosts);
}
type HostOptions = {
HostNameKey: string;
HostIpKey: string;
EnabledLoc: string;
};
interface AppConfig {
options: {
hosts: HostOptions;
};
defaults: {
EnabledLoc: string;
[key: string]: string;
};
server: {
EnabledLoc: string;
OutputFile: string;
[key: string]: string;
};
sites: { [key: string]: string }[];
$changed_time: number;
$config_file: string;
}
type VarList = {
name: string;
pos: number;
value: string[];
};
type Graph = {
line: string;
varlist: VarList[];
};
type VarBuild = {
graph: Graph[];
count: number;
};
function $build() {
const body = setup_template(app$argv);
if (!Array.isArray(app$config.sites))
print_error("[Array] config.sites is required");
const sites = app$config.sites;
const var_builds: VarBuild[] = [];
const defaults = app$config.defaults;
for (let site of sites) {
const var_build: VarBuild = {
graph: [],
count: 1,
};
for (let input of body) {
const varlist = input.varlist;
let line: string | null = input.line;
const _varlist: VarList[] = [];
for (const var_ of varlist) {
let _value: string[] = [];
if (site[var_.name] !== undefined) {
if (var_.typed != "display") {
if (Array.isArray(site[var_.name])) _value.push(...site[var_.name]);
else _value.push(site[var_.name]);
}
} else if (app$config.defaults[var_.name] !== undefined) {
if (var_.typed != "display") {
if (Array.isArray(defaults[var_.name]))
_value.push(...defaults[var_.name]);
else _value.push(defaults[var_.name]);
}
//_value.push(app$config.defaults[var_.name]);
} else {
if (var_.typed == "optional" || var_.typed == "display") {
line = null;
break;
} else {
print_error(`Missing ${var_.name}`);
}
}
if (_value.length == 0) continue;
//if there array, they should all be the same size
if (_value.length == 1) {
} else if (var_build.count == 1) {
var_build.count = _value.length;
} else if (var_build.count != 1 && _value.length != 1) {
var_build.count = _value.length;
} else if (_value.length != var_build.count) {
print_error(
`${input.template_file}: ${input.numberline} Expected ${var_build.count} values, Found ${_value.length} values`,
);
}
_varlist.push({
name: var_.name,
pos: var_.pos,
value: _value,
});
}
if (line === null) continue;
var_build.graph.push({
line: line,
varlist: [..._varlist],
});
}
var_builds.push(var_build);
}
const output = [];
for (let var_build of var_builds) {
for (let i = 0; i < var_build.count; i++) {
for (let build of var_build.graph) {
const varlist = [...build.varlist];
let line = build.line;
while (varlist.length) {
const var_: VarList = varlist.pop() as VarList;
let _value = var_.value.length != 1 ? var_.value[i] : var_.value[0];
line = line.replace(`~//${var_.pos}//~`, _value);
}
output.push(line);
}
}
}
return output.join("\n");
}
function enabled$loc() {
const loc = app$config.server.EnabledLoc;
const output_file = app$config.server.OutputFile;
if (!access$check(loc))
print_error(`config.server.EnabledLoc ${loc} is unaccessable.`);
return path.join(loc, output_file);
}
function $make_sites() {
const output = $build();
const loc = enabled$loc();
fs.writeFileSync(loc, output);
print_log("-> a3ensite applied");
$sites_status();
}
function $disable() {
const loc = enabled$loc();
const state = {
enabled: fs.existsSync(loc),
};
if (!state.enabled) {
print_log("-> a3ensite already disabled");
} else {
print_log("-> a3ensite disabling...");
fs.writeFileSync(loc, "");
print_log("-> a3ensite disabled");
}
$sites_status();
}
function $sites_status() {
const loc = enabled$loc();
const state = {
enabled: fs.existsSync(loc),
changed: false,
};
if (state.enabled) {
const latest = fs.statSync(loc);
state.changed = latest.mtimeMs < app$config.$changed_time;
}
print_log(`Sites: ${app$config.sites.length}`);
print_log(`Sites EnabledLoc: ${app$config.server.EnabledLoc}`);
print_log(`Sites OutputFile: ${app$config.server.OutputFile}`);
print_log(`Sites Status: ${state.enabled ? "Enabled" : "Disabled"}`);
print_log(`Hosts EnabledLoc: ${app$config.options.hosts.EnabledLoc}`);
if (state.changed) {
print_log(`Config: Config File Changed ~ ${app$config.$config_file}`);
}
}
function setup_config() {
const config_file = app$argv.config;
if (!fs.existsSync(config_file))
print_error(`--config file not found ${config_file}`);
const config = JSON.parse(fs.readFileSync(config_file, "utf8"));
const _stat = fs.statSync(config_file);
const options = {
...config.options,
hosts: {
OutputFile: "a3ensite.live.conf",
EnabledLoc: "/etc/hosts",
...config?.options?.hosts,
},
};
const server = {
OutputFile: "a3ensite.live.conf",
...config.server,
};
let _config: AppConfig = {
options: options,
server: server,
defaults: config.defaults || {},
sites: config.sites,
$changed_time: _stat.mtimeMs,
$config_file: config_file,
};
if (!_config.server?.EnabledLoc)
print_error(`config.server.EnabledLoc is required`);
return _config;
}
function setup_template(app$argv: ParsedArgs) {
const template_file = app$argv.template;
if (!fs.existsSync(template_file))
print_error(`--template file not found ${template_file}`);
const template = fs.readFileSync(template_file, "utf8");
const lines = template.split("\n");
const output = [];
let numberline = 0;
for (let line of lines) {
let pos = 0;
const varlist = [];
let var_ = line.match(/\$\??\!?\[([^\s]+)\]\$/);
while (var_ && var_["index"]) {
let typed = "required";
const _flag = var_[0];
const flag = var_[1];
const _i = var_["index"];
const before = line.slice(0, _i);
const after = line.slice(_i + _flag.length);
if (_flag.indexOf("$?") == 0) {
typed = "optional";
line = `${before}~//${pos}//~${after}`;
} else if (_flag.indexOf("$!") == 0) {
typed = "display";
line = `${before}${after}`;
} else {
line = `${before}~//${pos}//~${after}`;
}
varlist.push({
name: flag,
pos,
typed,
});
pos++;
var_ = line.match(/\$\??\!?\[([^\s]+)\]\$/);
}
output.push({ template_file, numberline, varlist, line });
numberline++;
}
return output;
}
function access$check(loc: string) {
if (!loc) return false;
try {
fs.accessSync(loc, fs.constants.R_OK | fs.constants.W_OK);
return true;
} catch (err) {
return false;
}
}