UNPKG

a3ensite

Version:

Apache/Nginx/Config Manager, JSON based

345 lines (329 loc) 9.62 kB
#!/usr/bin/env node 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; } }