commit 9200d10ad885bf77e601570fbcb0d3b91141c40a Author: videogame hacker Date: Wed Aug 9 07:56:34 2023 +0100 Initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3838095 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = true diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e40716f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "deno.enable": true, + "deno.lint": true, + "deno.unstable": true +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..5d13ab6 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ngineer + +Opinionated nginx config generation in Deno. diff --git a/ngineer.ts b/ngineer.ts new file mode 100644 index 0000000..c2371ee --- /dev/null +++ b/ngineer.ts @@ -0,0 +1,96 @@ +type ConfigNode = ConfigStatement | ConfigBlock | ConfigBreak | ConfigFile; + +class ConfigBlock { + value: string; + children: ConfigNode[]; + + constructor(value: string, children: ConfigNode[]) { + this.value = value; + this.children = children; + } + + build(): string { + let output = this.value; + output += " {\n "; + output += this.children + .map((child) => child.build().split("\n").join("\n ")) + .join("\n "); + output += "\n}"; + return output; + } +} + +class ConfigStatement { + value: string; + constructor(value: string) { + this.value = value; + } + + build(): string { + return this.value + ";"; + } +} + +class ConfigBreak { + build(): string { + return ""; + } +} + +export const br = new ConfigBreak(); + +class ConfigFile { + nodes: ConfigNode[]; + constructor(nodes: ConfigNode[]) { + this.nodes = nodes; + } + + build(): string { + return this.nodes.map((n) => n.build()).join("\n"); + } +} + +type LooseConfigNode = ConfigNode | string; + +function conformNode(looseNode: LooseConfigNode): ConfigNode { + if (typeof looseNode === "string") { + return new ConfigStatement(looseNode); + } + return looseNode; +} + +function conformNodes(looseNodes: LooseConfigNode[]): ConfigNode[] { + return looseNodes.map((node) => conformNode(node)); +} + +export function ng(value?: string, children?: LooseConfigNode[]): ConfigNode { + const hasValue = value !== undefined && value !== ""; + const hasChildren = children !== undefined; + + if (!hasValue && !hasChildren) { + return new ConfigBreak(); + } else if (hasValue && !hasChildren) { + return new ConfigStatement(value); + } else if (hasValue && hasChildren) { + return new ConfigBlock(value, conformNodes(children)); + } else if (!hasValue && hasChildren) { + return new ConfigFile(conformNodes(children)); + } + + throw new Error("unreachable"); +} + +export const listenv4v6 = (...extras: string[]) => + conformNodes([ + `listen 443 ${["ssl", "http2", ...extras].join(" ")}`, + `listen [::]:443 ${["ssl", "http2", ...extras].join(" ")}`, + ]); + +export const letsEncrypt = ( + domain: string, + liveDir = "/etc/letsencrypt/live" +) => + conformNodes([ + `ssl_certificate ${liveDir}/${domain}/fullchain.pem`, + `ssl_certificate_key ${liveDir}/${domain}/privkey.pem`, + ]); diff --git a/test.ts b/test.ts new file mode 100644 index 0000000..b4cc1d2 --- /dev/null +++ b/test.ts @@ -0,0 +1,13 @@ +import { letsEncrypt, listenv4v6, br, ng } from "./ngineer.ts"; + +const conf = ng("server", [ + ...listenv4v6(), + br, + "server_name mydomain.com", + br, + ...letsEncrypt("mydomain.com"), + br, + ng("location /", ["root /srv/http/mydomain.com"]), +]); + +console.log(conf.build());