fix: config example, add parsing
This commit is contained in:
parent
26b956c737
commit
a5dc90db41
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
config.yml
|
||||||
|
config.*.yml
|
||||||
|
!config.example.yml
|
||||||
|
node_modules
|
37
README.md
37
README.md
@ -4,41 +4,10 @@ scaffold one or all of the `dnim` api, database, ui on a bare debian image
|
|||||||
re-runnable and idempotent; changes to configuration does the same work as initial setup without losing state.
|
re-runnable and idempotent; changes to configuration does the same work as initial setup without losing state.
|
||||||
|
|
||||||
## inputs
|
## inputs
|
||||||
script input is read from `./config.yml`:
|
script input is read from `./config.yml`, an example of which can be found
|
||||||
```yaml
|
at [`./config.example.yml`].
|
||||||
db:
|
|
||||||
linux_user:
|
|
||||||
username: "foo_db"
|
|
||||||
allowed_ssh_public_keys: ['ssh-ed25519 <snip> my special ssh key']
|
|
||||||
persist: ["data"] # files not listed will be deleted when script is re-run. relative paths are resolved from /home/<db.linux_user.username>
|
|
||||||
port:
|
|
||||||
local: 5432
|
|
||||||
public: 0 # `0` means not publicly accessible; only local traffic (e.g. ssh sessions) may connect
|
|
||||||
domain: "db.dnim.org"
|
|
||||||
pg_admin:
|
|
||||||
username: "postgres"
|
|
||||||
password: "password"
|
|
||||||
api:
|
|
||||||
linux_user:
|
|
||||||
username: "foo_api"
|
|
||||||
allowed_ssh_public_keys: ['ssh-ed25519 <snip> my special ssh key']
|
|
||||||
persist: ["data"] # files not listed will be deleted when script is re-run. relative paths are resolved from /home/<api.linux_user.username>
|
|
||||||
port:
|
|
||||||
local: 1234
|
|
||||||
public: 1234
|
|
||||||
domain: "api.dnim.org"
|
|
||||||
ui:
|
|
||||||
linux_user:
|
|
||||||
username: "foo_ui"
|
|
||||||
allowed_ssh_public_keys: ['ssh-ed25519 <snip> my special ssh key']
|
|
||||||
persist: ["data"] # files not listed will be deleted when script is re-run. relative paths are resolved from /home/<ui.linux_user.username>
|
|
||||||
port:
|
|
||||||
local: 1234
|
|
||||||
public: 1234
|
|
||||||
domain: "dnim.org"
|
|
||||||
```
|
|
||||||
|
|
||||||
top-level keys `db`, `api`, or `ui` may be omitted to separately deploy instances of each service.
|
top-level keys `db`, `api`, or `ui` may be omitted or repeated.
|
||||||
|
|
||||||
## observable outputs
|
## observable outputs
|
||||||
* linux user `db.linux_user.username` is created
|
* linux user `db.linux_user.username` is created
|
||||||
|
35
config.example.yml
Normal file
35
config.example.yml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
- db:
|
||||||
|
linux_user:
|
||||||
|
username: 'foo_db'
|
||||||
|
allowed_ssh_public_keys: ['ssh-ed25519 <snip> my special ssh key']
|
||||||
|
# files not listed will be deleted when script is re-run.
|
||||||
|
# relative paths are resolved from /home/<db.linux_user.username>
|
||||||
|
persist: ['data']
|
||||||
|
network:
|
||||||
|
interface: '' # valid values: ['public', 'local']
|
||||||
|
port: 5432
|
||||||
|
domain: 'db.foo.org'
|
||||||
|
ssl: false
|
||||||
|
pg_admin:
|
||||||
|
username: 'postgres'
|
||||||
|
password: 'password'
|
||||||
|
- api:
|
||||||
|
linux_user:
|
||||||
|
username: 'foo_api'
|
||||||
|
allowed_ssh_public_keys: ['ssh-ed25519 <snip> my special ssh key']
|
||||||
|
persist: []
|
||||||
|
network:
|
||||||
|
interface: '' # valid values: ['public', 'local']
|
||||||
|
port: 5432
|
||||||
|
domain: 'api.foo.org'
|
||||||
|
ssl: true
|
||||||
|
- ui:
|
||||||
|
linux_user:
|
||||||
|
username: 'foo_ui'
|
||||||
|
allowed_ssh_public_keys: ['ssh-ed25519 <snip> my special ssh key']
|
||||||
|
persist: []
|
||||||
|
network:
|
||||||
|
interface: '' # valid values: ['public', 'local']
|
||||||
|
port: 5432
|
||||||
|
domain: 'foo.org'
|
||||||
|
ssl: true
|
14
package.json
Normal file
14
package.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "srv",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"private": "true",
|
||||||
|
"repository": "git@git.orionkindel.com:dnim/srv.git",
|
||||||
|
"author": "Orion Kindel <cakekindel@gmail.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@matchbook/ts": "^1.0.0",
|
||||||
|
"fp-ts": "^2.16.0",
|
||||||
|
"yaml": "^2.3.1"
|
||||||
|
}
|
||||||
|
}
|
34
src/cmd.js
Normal file
34
src/cmd.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
const Either = require("fp-ts/Either");
|
||||||
|
const { pipe, identity: id } = require("fp-ts/function");
|
||||||
|
const cp = require("child_process");
|
||||||
|
|
||||||
|
const run = (script) =>
|
||||||
|
pipe(
|
||||||
|
Either.tryCatch(
|
||||||
|
() => cp.spawnSync(script, { encoding: "utf8", shell: true }),
|
||||||
|
id
|
||||||
|
),
|
||||||
|
Either.flatMap((o) => (o.error ? Either.left(o.error) : Either.right(o))),
|
||||||
|
Either.flatMap((o) =>
|
||||||
|
o.status > 0
|
||||||
|
? Either.left(
|
||||||
|
new Error(JSON.stringify({ reason: "status_gt_0", ...o }))
|
||||||
|
)
|
||||||
|
: Either.right(o)
|
||||||
|
),
|
||||||
|
Either.map((o) => o.stdout.trim())
|
||||||
|
);
|
||||||
|
|
||||||
|
const ignoringStatus = Either.orElse((e) =>
|
||||||
|
pipe(
|
||||||
|
Either.tryCatch(() => JSON.parse(e.message), id),
|
||||||
|
Either.flatMap((o) =>
|
||||||
|
o.reason && o.reason === "status_gt_0"
|
||||||
|
? Either.right(o.stdout.trim())
|
||||||
|
: Either.left(new Error())
|
||||||
|
),
|
||||||
|
Either.mapLeft(() => e)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
module.exports = { run, ignoringStatus };
|
164
src/config.js
Normal file
164
src/config.js
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
const yaml = require("yaml");
|
||||||
|
const { strike, match, otherwise } = require("@matchbook/ts");
|
||||||
|
const Array_ = require("fp-ts/Array");
|
||||||
|
const Either = require("fp-ts/Either");
|
||||||
|
const { identity: id, flow, pipe } = require("fp-ts/function");
|
||||||
|
|
||||||
|
const parseSegmentLinuxUser = (o) =>
|
||||||
|
"linux_user" in o
|
||||||
|
? "username" in o.linux_user
|
||||||
|
? Either.right({
|
||||||
|
username: o.linux_user.username,
|
||||||
|
allowedSshPublicKeys: o.allowed_ssh_public_keys || [],
|
||||||
|
})
|
||||||
|
: Either.left(new Error("linux_user.username required"))
|
||||||
|
: Either.left(new Error("linux_user required"));
|
||||||
|
|
||||||
|
const parseSegmentPgAdmin = flow(
|
||||||
|
(o) =>
|
||||||
|
"pg_admin" in o
|
||||||
|
? Either.right(o.pg_admin)
|
||||||
|
: Either.left(
|
||||||
|
new Error(
|
||||||
|
["pg_admin required", `in: ${JSON.stringify(o)}`].join("\n")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Either.flatMap((pg) =>
|
||||||
|
"username" in pg
|
||||||
|
? Either.right(pg)
|
||||||
|
: Either.left(
|
||||||
|
new Error(
|
||||||
|
["pg_admin.username required", `in: ${JSON.stringify(pg)}`].join(
|
||||||
|
"\n"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Either.flatMap((pg) =>
|
||||||
|
"password" in pg
|
||||||
|
? Either.right(pg)
|
||||||
|
: Either.left(
|
||||||
|
new Error(
|
||||||
|
["pg_admin.password required", `in: ${JSON.stringify(pg)}`].join(
|
||||||
|
"\n"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Either.map((pg) => ({ username: pg.username, password: pg.password }))
|
||||||
|
);
|
||||||
|
|
||||||
|
const parseSegmentNetwork = flow(
|
||||||
|
(o) =>
|
||||||
|
"network" in o
|
||||||
|
? Either.right(o.network)
|
||||||
|
: Either.left(
|
||||||
|
new Error(["network required", `in: ${JSON.stringify(o)}`].join("\n"))
|
||||||
|
),
|
||||||
|
Either.flatMap((n) =>
|
||||||
|
"interface" in n
|
||||||
|
? Either.right(n)
|
||||||
|
: Either.left(
|
||||||
|
new Error(
|
||||||
|
["network.interface required", `in: ${JSON.stringify(n)}`].join(
|
||||||
|
"\n"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Either.flatMap((n) =>
|
||||||
|
"port" in n
|
||||||
|
? Either.right(n)
|
||||||
|
: Either.left(
|
||||||
|
new Error(
|
||||||
|
["network.port required", `in: ${JSON.stringify(n)}`].join("\n")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Either.flatMap((n) =>
|
||||||
|
"domain" in n
|
||||||
|
? Either.right(n)
|
||||||
|
: Either.left(
|
||||||
|
new Error(
|
||||||
|
[
|
||||||
|
"network.domain required",
|
||||||
|
" (to not listen on a public domain, set this property to empty string '')",
|
||||||
|
`in: ${JSON.stringify(n)}`,
|
||||||
|
].join("\n")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Either.flatMap((n) =>
|
||||||
|
"ssl" in n
|
||||||
|
? Either.right(n)
|
||||||
|
: Either.left(
|
||||||
|
new Error(
|
||||||
|
["network.ssl required", `in: ${JSON.stringify(n)}`].join("\n")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Either.map((n) => ({
|
||||||
|
ssl: n.ssl,
|
||||||
|
domain: n.domain || undefined,
|
||||||
|
interface: n.interface,
|
||||||
|
port: n.port,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const parseServiceDb = (svc) =>
|
||||||
|
pipe(
|
||||||
|
Either.Do,
|
||||||
|
Either.bind("linuxUser", () => parseSegmentLinuxUser(svc)),
|
||||||
|
Either.let("persist", () =>
|
||||||
|
(svc.persist || []).filter((s) => typeof s === "string" && s.length > 0)
|
||||||
|
),
|
||||||
|
Either.bind("network", () => parseSegmentNetwork(svc)),
|
||||||
|
Either.bind("pgAdmin", () => parseSegmentPgAdmin(svc))
|
||||||
|
);
|
||||||
|
|
||||||
|
const parseServiceApi = (svc) =>
|
||||||
|
pipe(
|
||||||
|
Either.Do,
|
||||||
|
Either.bind("linuxUser", () => parseSegmentLinuxUser(svc)),
|
||||||
|
Either.let("persist", () =>
|
||||||
|
(svc.persist || []).filter((s) => typeof s === "string" && s.length > 0)
|
||||||
|
),
|
||||||
|
Either.bind("network", () => parseSegmentNetwork(svc))
|
||||||
|
);
|
||||||
|
|
||||||
|
const parseServiceUi = (svc) =>
|
||||||
|
pipe(
|
||||||
|
Either.Do,
|
||||||
|
Either.bind("linuxUser", () => parseSegmentLinuxUser(svc)),
|
||||||
|
Either.let("persist", () =>
|
||||||
|
(svc.persist || []).filter((s) => typeof s === "string" && s.length > 0)
|
||||||
|
),
|
||||||
|
Either.bind("network", () => parseSegmentNetwork(svc))
|
||||||
|
);
|
||||||
|
|
||||||
|
const parseService = (svc) =>
|
||||||
|
"db" in svc
|
||||||
|
? parseServiceDb(svc.db)
|
||||||
|
: "api" in svc
|
||||||
|
? parseServiceApi(svc.api)
|
||||||
|
: "ui" in svc
|
||||||
|
? parseServiceUi(svc.ui)
|
||||||
|
: Either.left(
|
||||||
|
new Error(
|
||||||
|
[
|
||||||
|
`top-level array elements must be records with a key named "db", "ui", or "api".`,
|
||||||
|
`in: ${JSON.stringify(svc)}`,
|
||||||
|
].join("\n")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const parse = flow(
|
||||||
|
(cfg) => Either.tryCatch(() => yaml.parse(cfg), id),
|
||||||
|
Either.filterOrElse(
|
||||||
|
(p) => p instanceof Array,
|
||||||
|
() => new Error("config must have top-level array elements")
|
||||||
|
),
|
||||||
|
Either.flatMap((svcs) => Either.sequenceArray(svcs.map(parseService)))
|
||||||
|
);
|
||||||
|
|
||||||
|
module.exports = { parse };
|
10
src/index.js
Normal file
10
src/index.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
const Config = require("./config.js");
|
||||||
|
const Cmd = require("./cmd.js");
|
||||||
|
const fs = require("fs");
|
||||||
|
const {pipe} = require("fp-ts/function");
|
||||||
|
const Either = require("fp-ts/Either");
|
||||||
|
|
||||||
|
pipe(
|
||||||
|
Cmd.run('cat ./config.example.yml'),
|
||||||
|
Either.flatMap(Config.parse),
|
||||||
|
);
|
18
yarn.lock
Normal file
18
yarn.lock
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@matchbook/ts@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@matchbook/ts/-/ts-1.0.0.tgz#7338b3fc3fd15f46c5849bf898cde200c6865b48"
|
||||||
|
integrity sha512-0eOE/mJSNa3Z8fxP3fWumBQjVjd+N/oZIqctZ+CVl/l8bZc6rgW/YfsjiHzjZzHJkYdrFOfD2S4u9kqGAcs7iA==
|
||||||
|
|
||||||
|
fp-ts@^2.16.0:
|
||||||
|
version "2.16.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.16.0.tgz#64e03314dfc1c7ce5e975d3496ac14bc3eb7f92e"
|
||||||
|
integrity sha512-bLq+KgbiXdTEoT1zcARrWEpa5z6A/8b7PcDW7Gef3NSisQ+VS7ll2Xbf1E+xsgik0rWub/8u0qP/iTTjj+PhxQ==
|
||||||
|
|
||||||
|
yaml@^2.3.1:
|
||||||
|
version "2.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b"
|
||||||
|
integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==
|
Loading…
Reference in New Issue
Block a user