Compare commits
2 Commits
09ee1d3507
...
8f3cb8fb09
Author | SHA1 | Date | |
---|---|---|---|
|
8f3cb8fb09 | ||
|
b68d3fc144 |
@ -10,6 +10,7 @@
|
|||||||
interface: 'public'
|
interface: 'public'
|
||||||
port: 1
|
port: 1
|
||||||
domain: 'db.foo.org'
|
domain: 'db.foo.org'
|
||||||
|
ip: '8.8.8.8'
|
||||||
ssl: false
|
ssl: false
|
||||||
postgres:
|
postgres:
|
||||||
username: 'postgres'
|
username: 'postgres'
|
||||||
|
@ -7,8 +7,7 @@
|
|||||||
"author": "Orion Kindel <cakekindel@gmail.com>",
|
"author": "Orion Kindel <cakekindel@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"fmt:check": "prettier --check src/**/*.js test/**/*.js",
|
"fmt": "prettier --write src/*.js test/*.js src/**/*.js test/**/*.js",
|
||||||
"fmt": "prettier --write src/**/*.js test/**/*.js",
|
|
||||||
"test": "ava"
|
"test": "ava"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -4,10 +4,10 @@ const cp = require('child_process')
|
|||||||
|
|
||||||
const System = require('./system.js')
|
const System = require('./system.js')
|
||||||
|
|
||||||
const run = script =>
|
const run = (script, { stdio = undefined } = {}) =>
|
||||||
pipe(
|
pipe(
|
||||||
Either.tryCatch(
|
Either.tryCatch(
|
||||||
() => cp.spawnSync(script, { encoding: 'utf8', shell: true }),
|
() => cp.spawnSync(script, { encoding: 'utf8', shell: true, stdio }),
|
||||||
id,
|
id,
|
||||||
),
|
),
|
||||||
Either.flatMap(o => (o.error ? Either.left(o.error) : Either.right(o))),
|
Either.flatMap(o => (o.error ? Either.left(o.error) : Either.right(o))),
|
||||||
|
@ -12,9 +12,7 @@ const parseSegmentLinuxUser = flow(
|
|||||||
'linux_user' in o
|
'linux_user' in o
|
||||||
? Either.right(o.linux_user)
|
? Either.right(o.linux_user)
|
||||||
: Either.left(
|
: Either.left(
|
||||||
new Error(
|
new Error(['linux_user required', yaml.stringify(o)].join('\n')),
|
||||||
['linux_user required', `in: ${yaml.stringify(o)}`].join('\n'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Either.flatMap(lx =>
|
Either.flatMap(lx =>
|
||||||
'username' in lx
|
'username' in lx
|
||||||
@ -26,9 +24,7 @@ const parseSegmentLinuxUser = flow(
|
|||||||
})
|
})
|
||||||
: Either.left(
|
: Either.left(
|
||||||
new Error(
|
new Error(
|
||||||
['linux_user.username required', `in: ${yaml.stringify(lx)}`].join(
|
['linux_user.username required', yaml.stringify(lx)].join('\n'),
|
||||||
'\n',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -47,7 +43,7 @@ const parseSegmentLinuxUser = flow(
|
|||||||
new Error(
|
new Error(
|
||||||
[
|
[
|
||||||
`all paths in linux_user.persist must be subpaths of ${lx.homeDir}`,
|
`all paths in linux_user.persist must be subpaths of ${lx.homeDir}`,
|
||||||
`in: ${yaml.stringify(lx)}`,
|
yaml.stringify(lx),
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -61,13 +57,12 @@ const parseSegmentLinuxUser = flow(
|
|||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|
||||||
const parseSegmentPostgres = (cfg, linuxUser) => pipe(
|
const parseSegmentPostgres = (cfg, linuxUser) =>
|
||||||
|
pipe(
|
||||||
'postgres' in cfg
|
'postgres' in cfg
|
||||||
? Either.right(cfg.postgres)
|
? Either.right(cfg.postgres)
|
||||||
: Either.left(
|
: Either.left(
|
||||||
new Error(
|
new Error(['postgres required', yaml.stringify(cfg)].join('\n')),
|
||||||
['postgres required', `in: ${yaml.stringify(cfg)}`].join('\n'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Either.map(pg => ({
|
Either.map(pg => ({
|
||||||
...pg,
|
...pg,
|
||||||
@ -86,7 +81,7 @@ const parseSegmentPostgres = (cfg, linuxUser) => pipe(
|
|||||||
new Error(
|
new Error(
|
||||||
[
|
[
|
||||||
`postgres.data_dir must be a subpath of ${linuxUser.homeDir}`,
|
`postgres.data_dir must be a subpath of ${linuxUser.homeDir}`,
|
||||||
`in: ${yaml.stringify(pg)}`,
|
yaml.stringify(pg),
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -96,9 +91,7 @@ const parseSegmentPostgres = (cfg, linuxUser) => pipe(
|
|||||||
? Either.right(pg)
|
? Either.right(pg)
|
||||||
: Either.left(
|
: Either.left(
|
||||||
new Error(
|
new Error(
|
||||||
['postgres.username required', `in: ${yaml.stringify(pg)}`].join(
|
['postgres.username required', yaml.stringify(pg)].join('\n'),
|
||||||
'\n',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -107,9 +100,7 @@ const parseSegmentPostgres = (cfg, linuxUser) => pipe(
|
|||||||
? Either.right(pg)
|
? Either.right(pg)
|
||||||
: Either.left(
|
: Either.left(
|
||||||
new Error(
|
new Error(
|
||||||
['postgres.password required', `in: ${yaml.stringify(pg)}`].join(
|
['postgres.password required', yaml.stringify(pg)].join('\n'),
|
||||||
'\n',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -120,14 +111,14 @@ const parseSegmentPostgres = (cfg, linuxUser) => pipe(
|
|||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|
||||||
const parseSegmentNetwork = flow(
|
const parseSegmentNetwork = ({ http = true }, svc) =>
|
||||||
|
pipe(
|
||||||
|
svc,
|
||||||
o =>
|
o =>
|
||||||
'network' in o
|
'network' in o
|
||||||
? Either.right(o.network)
|
? Either.right(o.network)
|
||||||
: Either.left(
|
: Either.left(
|
||||||
new Error(
|
new Error(['network required', yaml.stringify(o)].join('\n')),
|
||||||
['network required', `in: ${yaml.stringify(o)}`].join('\n'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Either.flatMap(n =>
|
Either.flatMap(n =>
|
||||||
('interface' in n && n.interface === 'public') || n.interface === 'local'
|
('interface' in n && n.interface === 'public') || n.interface === 'local'
|
||||||
@ -136,7 +127,7 @@ const parseSegmentNetwork = flow(
|
|||||||
new Error(
|
new Error(
|
||||||
[
|
[
|
||||||
"network.interface required, and must be 'public' or 'local'",
|
"network.interface required, and must be 'public' or 'local'",
|
||||||
`in: ${yaml.stringify(n)}`,
|
yaml.stringify(n),
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -148,35 +139,49 @@ const parseSegmentNetwork = flow(
|
|||||||
new Error(
|
new Error(
|
||||||
[
|
[
|
||||||
'network.port required and must be an integer',
|
'network.port required and must be an integer',
|
||||||
`in: ${yaml.stringify(n)}`,
|
yaml.stringify(n),
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Either.flatMap(n =>
|
Either.flatMap(n =>
|
||||||
n.interface === 'public' || (n.interface === 'local' && !n.domain)
|
n.interface === 'public' ||
|
||||||
|
(n.interface === 'local' && !n.domain && !n.ip)
|
||||||
? Either.right(n)
|
? Either.right(n)
|
||||||
: Either.left(
|
: Either.left(
|
||||||
new Error(
|
new Error(
|
||||||
[
|
[
|
||||||
"network.interface is 'local', network.domain must not be present.",
|
"network.interface is 'local', network.ip and network.domain must not be present.",
|
||||||
`in: ${yaml.stringify(n)}`,
|
yaml.stringify(n),
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Either.flatMap(n =>
|
Either.flatMap(n =>
|
||||||
n.interface === 'local' ||
|
n.interface === 'local' ||
|
||||||
(n.interface === 'public' &&
|
(http &&
|
||||||
|
n.interface === 'public' &&
|
||||||
'domain' in n &&
|
'domain' in n &&
|
||||||
typeof n.domain === 'string' &&
|
typeof n.domain === 'string' &&
|
||||||
n.domain.length > 0)
|
n.domain.length > 0) ||
|
||||||
|
(!http &&
|
||||||
|
n.interface === 'public' &&
|
||||||
|
'domain' in n &&
|
||||||
|
typeof n.domain === 'string' &&
|
||||||
|
n.domain.length > 0 &&
|
||||||
|
'ip' in n &&
|
||||||
|
typeof n.ip === 'string' &&
|
||||||
|
n.ip.length > 0)
|
||||||
? Either.right(n)
|
? Either.right(n)
|
||||||
: Either.left(
|
: Either.left(
|
||||||
new Error(
|
new Error(
|
||||||
[
|
[
|
||||||
"network.interface is 'public', network.domain be set to a value.",
|
`network.interface is 'public' and service uses ${
|
||||||
`in: ${yaml.stringify(n)}`,
|
http ? 'http' : 'tcp'
|
||||||
|
}; ${
|
||||||
|
http ? 'network.domain' : 'network.domain and network.ip'
|
||||||
|
} must be set.`,
|
||||||
|
yaml.stringify(n),
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -188,7 +193,7 @@ const parseSegmentNetwork = flow(
|
|||||||
new Error(
|
new Error(
|
||||||
[
|
[
|
||||||
'network.ssl required and must be a bool',
|
'network.ssl required and must be a bool',
|
||||||
`in: ${yaml.stringify(n)}`,
|
yaml.stringify(n),
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -196,6 +201,7 @@ const parseSegmentNetwork = flow(
|
|||||||
Either.map(n => ({
|
Either.map(n => ({
|
||||||
ssl: n.ssl,
|
ssl: n.ssl,
|
||||||
domain: (n.domain || '').trim(),
|
domain: (n.domain || '').trim(),
|
||||||
|
ip: (n.ip || '').trim(),
|
||||||
interface: n.interface,
|
interface: n.interface,
|
||||||
interfaceIp:
|
interfaceIp:
|
||||||
n.interface === 'local' ? Net.localInterfaceIp : Net.publicInterfaceIp,
|
n.interface === 'local' ? Net.localInterfaceIp : Net.publicInterfaceIp,
|
||||||
@ -208,8 +214,10 @@ const parseServiceDb = svc =>
|
|||||||
Either.Do,
|
Either.Do,
|
||||||
Either.let('serviceType', () => 'db'),
|
Either.let('serviceType', () => 'db'),
|
||||||
Either.bind('linuxUser', () => parseSegmentLinuxUser(svc)),
|
Either.bind('linuxUser', () => parseSegmentLinuxUser(svc)),
|
||||||
Either.bind('network', () => parseSegmentNetwork(svc)),
|
Either.bind('network', () => parseSegmentNetwork({ http: false }, svc)),
|
||||||
Either.bind('postgres', ({linuxUser}) => parseSegmentPostgres(svc, linuxUser)),
|
Either.bind('postgres', ({ linuxUser }) =>
|
||||||
|
parseSegmentPostgres(svc, linuxUser),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const parseServiceApi = svc =>
|
const parseServiceApi = svc =>
|
||||||
@ -217,7 +225,7 @@ const parseServiceApi = svc =>
|
|||||||
Either.Do,
|
Either.Do,
|
||||||
Either.let('serviceType', () => 'api'),
|
Either.let('serviceType', () => 'api'),
|
||||||
Either.bind('linuxUser', () => parseSegmentLinuxUser(svc)),
|
Either.bind('linuxUser', () => parseSegmentLinuxUser(svc)),
|
||||||
Either.bind('network', () => parseSegmentNetwork(svc)),
|
Either.bind('network', () => parseSegmentNetwork({ http: true }, svc)),
|
||||||
)
|
)
|
||||||
|
|
||||||
const parseServiceUi = svc =>
|
const parseServiceUi = svc =>
|
||||||
@ -225,14 +233,14 @@ const parseServiceUi = svc =>
|
|||||||
Either.Do,
|
Either.Do,
|
||||||
Either.let('serviceType', () => 'ui'),
|
Either.let('serviceType', () => 'ui'),
|
||||||
Either.bind('linuxUser', () => parseSegmentLinuxUser(svc)),
|
Either.bind('linuxUser', () => parseSegmentLinuxUser(svc)),
|
||||||
Either.bind('network', () => parseSegmentNetwork(svc)),
|
Either.bind('network', () => parseSegmentNetwork({ http: true }, svc)),
|
||||||
)
|
)
|
||||||
|
|
||||||
const badService = svc =>
|
const badService = svc =>
|
||||||
new Error(
|
new Error(
|
||||||
[
|
[
|
||||||
`top-level array elements must be records with a key named "db", "ui", or "api".`,
|
`top-level array elements must be records with a key named "db", "ui", or "api".`,
|
||||||
`in: ${yaml.stringify(svc)}`,
|
yaml.stringify(svc),
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
34
src/index.js
34
src/index.js
@ -10,6 +10,11 @@ const DbService = require('./service/db.js')
|
|||||||
const Config = require('./config.js')
|
const Config = require('./config.js')
|
||||||
const Cmd = require('./cmd.js')
|
const Cmd = require('./cmd.js')
|
||||||
|
|
||||||
|
const execSteps = (res, step) => {
|
||||||
|
console.log(step.label)
|
||||||
|
return Either.flatMap(step.work)(res)
|
||||||
|
}
|
||||||
|
|
||||||
pipe(
|
pipe(
|
||||||
Cmd.run('cat ./config.yml'),
|
Cmd.run('cat ./config.yml'),
|
||||||
Either.map(({ stdout }) => stdout),
|
Either.map(({ stdout }) => stdout),
|
||||||
@ -18,24 +23,25 @@ pipe(
|
|||||||
svcs.reduce(
|
svcs.reduce(
|
||||||
(steps, svc) =>
|
(steps, svc) =>
|
||||||
svc.serviceType === 'db' ? [...steps, ...DbService.steps(svc)] : steps,
|
svc.serviceType === 'db' ? [...steps, ...DbService.steps(svc)] : steps,
|
||||||
[System.step],
|
System.steps(svcs),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Either.flatMap(steps =>
|
Either.flatMap(steps =>
|
||||||
Array_.reverse(steps).reduce((a, step) => {
|
Array_.reverse(steps)
|
||||||
if (step.down) {
|
.filter(step => step.on === 'down')
|
||||||
console.log(step.down.label)
|
.reduce(execSteps, Either.right(steps)),
|
||||||
return Either.flatMap(step.down.work)(a)
|
|
||||||
} else {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
}, Either.right(steps)),
|
|
||||||
),
|
),
|
||||||
Either.flatMap(steps =>
|
Either.flatMap(steps =>
|
||||||
steps.reduce((a, step) => {
|
steps
|
||||||
console.log(step.up.label)
|
.filter(step => step.on === 'up')
|
||||||
return Either.flatMap(step.up.work)(a)
|
.reduce(execSteps, Either.right(steps)),
|
||||||
}, Either.right(steps)),
|
|
||||||
),
|
),
|
||||||
Either.getOrElse(e => {throw e;})
|
Either.flatMap(steps =>
|
||||||
|
steps
|
||||||
|
.filter(step => step.on === 'finalize')
|
||||||
|
.reduce(execSteps, Either.right(steps)),
|
||||||
|
),
|
||||||
|
Either.getOrElse(e => {
|
||||||
|
throw e
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
@ -3,4 +3,7 @@ const Cmd = require('./cmd.js')
|
|||||||
const inaddrAny = '0.0.0.0'
|
const inaddrAny = '0.0.0.0'
|
||||||
const inaddrLoopback = '127.0.0.1'
|
const inaddrLoopback = '127.0.0.1'
|
||||||
|
|
||||||
module.exports = {publicInterfaceIp: inaddrAny, localInterfaceIp: inaddrLoopback}
|
module.exports = {
|
||||||
|
publicInterfaceIp: inaddrAny,
|
||||||
|
localInterfaceIp: inaddrLoopback,
|
||||||
|
}
|
||||||
|
252
src/nginx.js
Normal file
252
src/nginx.js
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
const net = require('net')
|
||||||
|
const { pipe, flow } = require('fp-ts/function')
|
||||||
|
const Either = require('fp-ts/Either')
|
||||||
|
|
||||||
|
const Cmd = () => require('./cmd.js')
|
||||||
|
|
||||||
|
const rootConf = [
|
||||||
|
'worker_processes 1;',
|
||||||
|
'',
|
||||||
|
'error_log logs/error.log debug;',
|
||||||
|
`log_format basic '$remote_addr [$time_local] '`,
|
||||||
|
` '$protocol $status $bytes_sent $bytes_received '`,
|
||||||
|
` '$session_time';`,
|
||||||
|
'',
|
||||||
|
'access_log logs/access.log basic buffer=32k;',
|
||||||
|
'',
|
||||||
|
'events {',
|
||||||
|
' worker_connections 1024;',
|
||||||
|
'}',
|
||||||
|
'',
|
||||||
|
'stream {',
|
||||||
|
' include /etc/nginx/streams-enabled/*;',
|
||||||
|
'}',
|
||||||
|
'',
|
||||||
|
'http {',
|
||||||
|
' include mime.types;',
|
||||||
|
' default_type application/octet-stream;',
|
||||||
|
'',
|
||||||
|
' include /etc/nginx/sites-enabled/*;',
|
||||||
|
'',
|
||||||
|
' sendfile on;',
|
||||||
|
' keepalive_timeout 65;',
|
||||||
|
'}',
|
||||||
|
].join('\n')
|
||||||
|
|
||||||
|
const systemdUnit = [
|
||||||
|
'[Unit]',
|
||||||
|
'Description=The NGINX HTTP and reverse proxy server',
|
||||||
|
'After=syslog.target network-online.target remote-fs.target nss-lookup.target',
|
||||||
|
'Wants=network-online.target',
|
||||||
|
'',
|
||||||
|
'[Service]',
|
||||||
|
'Type=forking',
|
||||||
|
'PIDFile=/usr/local/nginx/nginx.pid',
|
||||||
|
'ExecStartPre=/usr/local/nginx/nginx -t',
|
||||||
|
'ExecStart=/usr/local/nginx/nginx',
|
||||||
|
'ExecReload=/usr/local/nginx/nginx -s reload',
|
||||||
|
'ExecStop=/bin/kill -s QUIT $MAINPID',
|
||||||
|
'PrivateTmp=true',
|
||||||
|
'',
|
||||||
|
'[Install]',
|
||||||
|
'WantedBy=multi-user.target',
|
||||||
|
].join('\n')
|
||||||
|
|
||||||
|
const confs = svcs =>
|
||||||
|
svcs
|
||||||
|
.filter(cfg => cfg.network.interface === 'public')
|
||||||
|
.map(cfg => ({
|
||||||
|
path: `/etc/nginx/${
|
||||||
|
cfg.serviceType === 'db' ? 'streams' : 'sites'
|
||||||
|
}-available/${cfg.network.domain}`,
|
||||||
|
symlinks: [
|
||||||
|
`/etc/nginx/${cfg.serviceType === 'db' ? 'streams' : 'sites'}-enabled/${
|
||||||
|
cfg.network.domain
|
||||||
|
}`,
|
||||||
|
],
|
||||||
|
contents:
|
||||||
|
cfg.serviceType === 'db'
|
||||||
|
? [
|
||||||
|
`upstream ${cfg.linuxUser.username}_pg {`,
|
||||||
|
` server localhost:${cfg.network.port};`,
|
||||||
|
'}',
|
||||||
|
'',
|
||||||
|
'server {',
|
||||||
|
` listen ${
|
||||||
|
net.isIP(cfg.network.ip) === 6
|
||||||
|
? '[' + cfg.network.ip + ']'
|
||||||
|
: cfg.network.ip
|
||||||
|
}:5432;`,
|
||||||
|
` proxy_pass ${cfg.linuxUser.username}_pg;`,
|
||||||
|
'}',
|
||||||
|
].join('\n')
|
||||||
|
: [
|
||||||
|
'server {',
|
||||||
|
` listen 80;`,
|
||||||
|
` server_name ${cfg.network.domain};`,
|
||||||
|
'',
|
||||||
|
' location / {',
|
||||||
|
' client_max_body_size 512M;',
|
||||||
|
` proxy_pass http://localhost:${cfg.network.port};`,
|
||||||
|
' proxy_set_header Host $host;',
|
||||||
|
' proxy_set_header X-Real-IP $remote_addr;',
|
||||||
|
' proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;',
|
||||||
|
' proxy_set_header X-Forwarded-Proto $scheme;',
|
||||||
|
' }',
|
||||||
|
'}',
|
||||||
|
].join('\n'),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const installed = () =>
|
||||||
|
pipe(
|
||||||
|
Cmd().run('nginx -V 2>&1'),
|
||||||
|
Either.map(e => ({ shouldInstall: !e.stdout.includes('--with-stream') })),
|
||||||
|
Either.orElse(e => {
|
||||||
|
try {
|
||||||
|
const { stdout } = JSON.parse(e.message)
|
||||||
|
return stdout.includes('not found')
|
||||||
|
? Either.right({ shouldInstall: true })
|
||||||
|
: Either.left(e)
|
||||||
|
} catch (e) {
|
||||||
|
return Either.left(e)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Either.flatMap(({ shouldInstall }) =>
|
||||||
|
!shouldInstall
|
||||||
|
? Either.right()
|
||||||
|
: Cmd().run(
|
||||||
|
[
|
||||||
|
'set -x',
|
||||||
|
'echo "nginx needs the stream module, building from source... (this will take a while)"',
|
||||||
|
'sleep 2',
|
||||||
|
'rm *.tar.gz || true',
|
||||||
|
// pcre
|
||||||
|
'wget -q github.com/PCRE2Project/pcre2/releases/download/pcre2-10.42/pcre2-10.42.tar.gz',
|
||||||
|
'tar -zxf pcre2-10.42.tar.gz',
|
||||||
|
'cd pcre2-10.42',
|
||||||
|
'./configure',
|
||||||
|
'make 1>/dev/null',
|
||||||
|
'make install 1>/dev/null',
|
||||||
|
'cd ..',
|
||||||
|
// openssl
|
||||||
|
'wget -q https://www.openssl.org/source/openssl-1.1.1u.tar.gz',
|
||||||
|
'tar -zxf openssl-1.1.1u.tar.gz',
|
||||||
|
'cd openssl-1.1.1u',
|
||||||
|
'./config --prefix=/usr 1>/dev/null',
|
||||||
|
'make 1>/dev/null',
|
||||||
|
'make install 1>/dev/null',
|
||||||
|
'cd ..',
|
||||||
|
// zlib
|
||||||
|
'wget -q http://zlib.net/zlib-1.2.13.tar.gz',
|
||||||
|
'tar -zxf zlib-1.2.13.tar.gz',
|
||||||
|
'cd zlib-1.2.13',
|
||||||
|
'./configure 1>/dev/null',
|
||||||
|
'make 1>/dev/null',
|
||||||
|
'make install 1>/dev/null',
|
||||||
|
'cd ..',
|
||||||
|
// nginx
|
||||||
|
'wget https://nginx.org/download/nginx-1.25.1.tar.gz',
|
||||||
|
'tar -zxf nginx-1.25.1.tar.gz',
|
||||||
|
'cd nginx-1.25.1',
|
||||||
|
[
|
||||||
|
'./configure',
|
||||||
|
'--sbin-path=/usr/local/nginx/nginx',
|
||||||
|
'--conf-path=/usr/local/nginx/nginx.conf',
|
||||||
|
'--pid-path=/usr/local/nginx/nginx.pid',
|
||||||
|
'--with-pcre=../pcre2-10.42',
|
||||||
|
'--with-zlib=../zlib-1.2.13',
|
||||||
|
'--with-stream',
|
||||||
|
'--with-threads',
|
||||||
|
'--with-http_ssl_module',
|
||||||
|
'--with-pcre-jit',
|
||||||
|
'--with-http_stub_status_module',
|
||||||
|
'--with-http_realip_module',
|
||||||
|
'--with-http_auth_request_module',
|
||||||
|
'--with-http_v2_module',
|
||||||
|
'--with-http_dav_module',
|
||||||
|
'--with-http_slice_module',
|
||||||
|
'--with-http_addition_module',
|
||||||
|
'--with-http_gunzip_module',
|
||||||
|
'--with-http_gzip_static_module',
|
||||||
|
'--with-http_sub_module',
|
||||||
|
'--with-debug',
|
||||||
|
'1>/dev/null',
|
||||||
|
].join(' '),
|
||||||
|
'make 1>/dev/null',
|
||||||
|
'make install 1>/dev/null',
|
||||||
|
// nginx.conf
|
||||||
|
'cat <<"NGINXCONF" > /usr/local/nginx/nginx.conf',
|
||||||
|
rootConf,
|
||||||
|
'NGINXCONF',
|
||||||
|
// systemd
|
||||||
|
'cat <<"NGINXUNIT" > /lib/systemd/system/nginx.service',
|
||||||
|
systemdUnit,
|
||||||
|
'NGINXUNIT',
|
||||||
|
'systemctl enable nginx',
|
||||||
|
'rm /usr/sbin/nginx || true',
|
||||||
|
'ln /usr/local/nginx/nginx /usr/sbin/nginx',
|
||||||
|
].join('\n'),
|
||||||
|
{ stdio: 'inherit' },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const confsWritten = svcs =>
|
||||||
|
confs(svcs).reduce(
|
||||||
|
(res, { path, symlinks, contents }) =>
|
||||||
|
Either.flatMap(() =>
|
||||||
|
Cmd().run(
|
||||||
|
[
|
||||||
|
`cat <<"EOCONF" > ${path}`,
|
||||||
|
contents,
|
||||||
|
'EOCONF',
|
||||||
|
...symlinks.map(ln => `ln -s ${path} ${ln}`),
|
||||||
|
].join('\n'),
|
||||||
|
),
|
||||||
|
)(res),
|
||||||
|
Either.right(undefined),
|
||||||
|
)
|
||||||
|
|
||||||
|
const confsDeleted = svcs =>
|
||||||
|
confs(svcs).reduce(
|
||||||
|
(res, { path, symlinks }) =>
|
||||||
|
Either.flatMap(() =>
|
||||||
|
Cmd().run(
|
||||||
|
[
|
||||||
|
`rm ${path} || true`,
|
||||||
|
...symlinks.map(ln => `rm ${ln} || true`),
|
||||||
|
].join('\n'),
|
||||||
|
),
|
||||||
|
)(res),
|
||||||
|
Either.right(undefined),
|
||||||
|
)
|
||||||
|
|
||||||
|
const started = () => Cmd().run('systemctl restart nginx')
|
||||||
|
|
||||||
|
const steps = svcs => [
|
||||||
|
{
|
||||||
|
on: 'up',
|
||||||
|
label: 'nginx: setup',
|
||||||
|
work: flow(
|
||||||
|
Either.right,
|
||||||
|
Either.tap(installed),
|
||||||
|
Either.tap(() => confsWritten(svcs)),
|
||||||
|
Either.tap(started),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
on: 'down',
|
||||||
|
label: 'nginx: teardown',
|
||||||
|
work: flow(
|
||||||
|
Either.right,
|
||||||
|
Either.tap(() => confsDeleted(svcs)),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
on: 'finalize',
|
||||||
|
label: 'nginx: cleanup',
|
||||||
|
work: flow(Either.right, Either.tap(started)),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
module.exports = { steps }
|
@ -13,8 +13,8 @@ const dockerCompose = cfg =>
|
|||||||
// restart: always will start the container on system startup / reboot if
|
// restart: always will start the container on system startup / reboot if
|
||||||
// docker.service enabled (see ./linux-user.js)
|
// docker.service enabled (see ./linux-user.js)
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
volumes: [`${cfg.postgres.dataDir}:/var/lib/postgres/data`],
|
volumes: [`${cfg.postgres.dataDir}:/var/lib/postgresql/data`],
|
||||||
ports: [`${cfg.network.interfaceIp}:${cfg.network.port}:5432`],
|
ports: [`${cfg.network.port}:5432`],
|
||||||
environment: {
|
environment: {
|
||||||
POSTGRES_USER: cfg.postgres.username,
|
POSTGRES_USER: cfg.postgres.username,
|
||||||
POSTGRES_PASSWORD: cfg.postgres.password,
|
POSTGRES_PASSWORD: cfg.postgres.password,
|
||||||
@ -24,9 +24,9 @@ const dockerCompose = cfg =>
|
|||||||
})
|
})
|
||||||
|
|
||||||
const steps = cfg => [
|
const steps = cfg => [
|
||||||
LinuxUser.step(cfg),
|
...LinuxUser.steps(cfg),
|
||||||
{
|
{
|
||||||
up: {
|
on: 'up',
|
||||||
label: `${cfg.linuxUser.username}: create db on ${cfg.network.interfaceIp}:${cfg.network.port}`,
|
label: `${cfg.linuxUser.username}: create db on ${cfg.network.interfaceIp}:${cfg.network.port}`,
|
||||||
work: a =>
|
work: a =>
|
||||||
pipe(
|
pipe(
|
||||||
@ -43,8 +43,6 @@ const steps = cfg => [
|
|||||||
Either.map(() => a),
|
Either.map(() => a),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
// down handled by LinuxUser killing user processes
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
module.exports = { steps }
|
module.exports = { steps }
|
||||||
|
@ -17,11 +17,12 @@ const userSessionsLinger = cfg =>
|
|||||||
const persistedStateRestored = cfg =>
|
const persistedStateRestored = cfg =>
|
||||||
Cmd.run(
|
Cmd.run(
|
||||||
[
|
[
|
||||||
|
'set -x',
|
||||||
// if /tmp/home/foo exists, copy /tmp/home/foo/* to /home/foo
|
// if /tmp/home/foo exists, copy /tmp/home/foo/* to /home/foo
|
||||||
`if [[ -d ${path.join(
|
`if [ -d ${path.join(
|
||||||
cfg.linuxUser.persistDir,
|
cfg.linuxUser.persistDir,
|
||||||
cfg.linuxUser.homeDir,
|
cfg.linuxUser.homeDir,
|
||||||
)} ]]; then`,
|
)} ]; then`,
|
||||||
` cp -R ${path.join(
|
` cp -R ${path.join(
|
||||||
cfg.linuxUser.persistDir,
|
cfg.linuxUser.persistDir,
|
||||||
cfg.linuxUser.homeDir,
|
cfg.linuxUser.homeDir,
|
||||||
@ -81,6 +82,7 @@ const statePersisted = cfg =>
|
|||||||
pipe(
|
pipe(
|
||||||
Cmd.run(
|
Cmd.run(
|
||||||
[
|
[
|
||||||
|
'set -x',
|
||||||
`mkdir -p ${path.join(
|
`mkdir -p ${path.join(
|
||||||
cfg.linuxUser.persistDir,
|
cfg.linuxUser.persistDir,
|
||||||
cfg.linuxUser.homeDir,
|
cfg.linuxUser.homeDir,
|
||||||
@ -102,8 +104,9 @@ const userDeleted = cfg =>
|
|||||||
Either.flatMap(() => Cmd.run(`rm -r ${cfg.linuxUser.homeDir} || true`)),
|
Either.flatMap(() => Cmd.run(`rm -r ${cfg.linuxUser.homeDir} || true`)),
|
||||||
)
|
)
|
||||||
|
|
||||||
const step = cfg => ({
|
const steps = cfg => [
|
||||||
up: {
|
{
|
||||||
|
on: 'up',
|
||||||
label: `${cfg.linuxUser.username}: create user`,
|
label: `${cfg.linuxUser.username}: create user`,
|
||||||
work: a =>
|
work: a =>
|
||||||
pipe(
|
pipe(
|
||||||
@ -118,7 +121,8 @@ const step = cfg => ({
|
|||||||
Either.map(() => a),
|
Either.map(() => a),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
down: {
|
{
|
||||||
|
on: 'down',
|
||||||
label: `${cfg.linuxUser.username}: remove user`,
|
label: `${cfg.linuxUser.username}: remove user`,
|
||||||
work: a =>
|
work: a =>
|
||||||
pipe(
|
pipe(
|
||||||
@ -131,6 +135,6 @@ const step = cfg => ({
|
|||||||
Either.map(() => a),
|
Either.map(() => a),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
})
|
]
|
||||||
|
|
||||||
module.exports = { step }
|
module.exports = { steps }
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
const Either = require('fp-ts/Either')
|
const Either = require('fp-ts/Either')
|
||||||
const { flow } = require('fp-ts/function')
|
const { pipe, flow } = require('fp-ts/function')
|
||||||
|
|
||||||
const Cmd = () => require('./cmd.js')
|
const Cmd = () => require('./cmd.js')
|
||||||
|
const Nginx = require('./nginx.js')
|
||||||
|
|
||||||
const localSshKeyPath = '/root/.ssh/id_ed25519'
|
const localSshKeyPath = '/root/.ssh/id_ed25519'
|
||||||
const localSshPubKeyPath = '/root/.ssh/id_ed25519.pub'
|
const localSshPubKeyPath = '/root/.ssh/id_ed25519.pub'
|
||||||
|
|
||||||
const pkgs = [
|
const pkgs = [
|
||||||
|
'openssl',
|
||||||
'man',
|
'man',
|
||||||
'nginx',
|
|
||||||
'neovim',
|
'neovim',
|
||||||
'ca-certificates',
|
'ca-certificates',
|
||||||
'gnupg',
|
'gnupg',
|
||||||
@ -25,16 +26,16 @@ const pkgs = [
|
|||||||
'slirp4netns',
|
'slirp4netns',
|
||||||
]
|
]
|
||||||
|
|
||||||
const apt = () =>
|
const packagesInstalled = () =>
|
||||||
Cmd().run(
|
Cmd().run(
|
||||||
[
|
[
|
||||||
'apt update -y',
|
'apt-get update -y',
|
||||||
'apt upgrade -y',
|
'apt-get upgrade -y',
|
||||||
`apt-get install -fy ${pkgs.join(' ')}`,
|
`apt-get install -fy ${pkgs.join(' ')}`,
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
)
|
)
|
||||||
|
|
||||||
const docker = () =>
|
const dockerInstalled = () =>
|
||||||
Cmd().run(
|
Cmd().run(
|
||||||
[
|
[
|
||||||
'install -m 0755 -d /etc/apt/keyrings',
|
'install -m 0755 -d /etc/apt/keyrings',
|
||||||
@ -55,20 +56,45 @@ const localSshKeyCreated = () =>
|
|||||||
|
|
||||||
const localSshKeyDeleted = () => Cmd().run(`rm ${localSshKeyPath} || true`)
|
const localSshKeyDeleted = () => Cmd().run(`rm ${localSshKeyPath} || true`)
|
||||||
|
|
||||||
const step = {
|
const firewallUp = svcs =>
|
||||||
up: {
|
Cmd().run(
|
||||||
|
[
|
||||||
|
'ufw default deny incoming',
|
||||||
|
'ufw default allow outgoing',
|
||||||
|
'ufw status verbose',
|
||||||
|
'ufw allow ssh',
|
||||||
|
`ufw allow 'Nginx Full'`,
|
||||||
|
...svcs
|
||||||
|
.filter(
|
||||||
|
svc => svc.serviceType === 'db' && svc.network.interface === 'public',
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
svc =>
|
||||||
|
`ufw allow in to '${svc.network.ip}' port 5432 proto tcp comment ${svc.linuxUser.username}`,
|
||||||
|
),
|
||||||
|
'ufw --force enable',
|
||||||
|
].join('\n'),
|
||||||
|
)
|
||||||
|
|
||||||
|
const steps = svcs => [
|
||||||
|
{
|
||||||
|
on: 'up',
|
||||||
label: 'system: setup',
|
label: 'system: setup',
|
||||||
work: flow(
|
work: flow(
|
||||||
Either.right,
|
Either.right,
|
||||||
Either.tap(apt),
|
Either.tap(packagesInstalled),
|
||||||
Either.tap(docker),
|
Either.tap(dockerInstalled),
|
||||||
|
Either.tap(() => firewallUp(svcs)),
|
||||||
Either.tap(localSshKeyCreated),
|
Either.tap(localSshKeyCreated),
|
||||||
|
// Either.tap(sslCertsInstalled),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
down: {
|
{
|
||||||
|
on: 'down',
|
||||||
label: 'system: teardown',
|
label: 'system: teardown',
|
||||||
work: flow(Either.right, Either.tap(localSshKeyDeleted)),
|
work: flow(Either.right, Either.tap(localSshKeyDeleted)),
|
||||||
},
|
},
|
||||||
}
|
...Nginx.steps(svcs),
|
||||||
|
]
|
||||||
|
|
||||||
module.exports = { step, localSshKeyPath, localSshPubKeyPath }
|
module.exports = { steps, localSshKeyPath, localSshPubKeyPath }
|
||||||
|
Loading…
Reference in New Issue
Block a user