@geekmidas/cli 10.0.0-alpha.113 → 10.0.0-alpha.115
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{GoDaddyProvider-DWrDIXzQ.cjs → GoDaddyProvider-BDvIMTO-.cjs} +2 -2
- package/dist/{GoDaddyProvider-DWrDIXzQ.cjs.map → GoDaddyProvider-BDvIMTO-.cjs.map} +1 -1
- package/dist/{GoDaddyProvider-CPabc3XW.mjs → GoDaddyProvider-eW1mre1g.mjs} +2 -2
- package/dist/{GoDaddyProvider-CPabc3XW.mjs.map → GoDaddyProvider-eW1mre1g.mjs.map} +1 -1
- package/dist/{LegacyStateStore-BdXoAlYY.mjs → LegacyStateStore-Ba1_x0cr.mjs} +3 -3
- package/dist/{LegacyStateStore-BdXoAlYY.mjs.map → LegacyStateStore-Ba1_x0cr.mjs.map} +1 -1
- package/dist/{LegacyStateStore-fsyPGBWu.cjs → LegacyStateStore-DFeykTd8.cjs} +3 -3
- package/dist/{LegacyStateStore-fsyPGBWu.cjs.map → LegacyStateStore-DFeykTd8.cjs.map} +1 -1
- package/dist/{LocalStateStore-BrAX5yy7.mjs → LocalStateStore-C1rUUvBr.mjs} +2 -2
- package/dist/{LocalStateStore-BrAX5yy7.mjs.map → LocalStateStore-C1rUUvBr.mjs.map} +1 -1
- package/dist/{LocalStateStore-AtviUBky.cjs → LocalStateStore-CV-04jpI.cjs} +2 -2
- package/dist/{LocalStateStore-AtviUBky.cjs.map → LocalStateStore-CV-04jpI.cjs.map} +1 -1
- package/dist/{S3StateStore-tn5g80Sx.cjs → S3StateStore-CqYi-1sb.cjs} +2 -2
- package/dist/{S3StateStore-tn5g80Sx.cjs.map → S3StateStore-CqYi-1sb.cjs.map} +1 -1
- package/dist/{S3StateStore-Cx7njkBz.mjs → S3StateStore-Dszt4fN5.mjs} +2 -2
- package/dist/{S3StateStore-Cx7njkBz.mjs.map → S3StateStore-Dszt4fN5.mjs.map} +1 -1
- package/dist/{SSMStateStore-sqZ3oDmn.mjs → SSMStateStore-BYRUyMkE.mjs} +2 -2
- package/dist/{SSMStateStore-sqZ3oDmn.mjs.map → SSMStateStore-BYRUyMkE.mjs.map} +1 -1
- package/dist/{SSMStateStore-CAFde-Fq.cjs → SSMStateStore-zpHjDCAW.cjs} +2 -2
- package/dist/{SSMStateStore-CAFde-Fq.cjs.map → SSMStateStore-zpHjDCAW.cjs.map} +1 -1
- package/dist/{add-Cs8yT5iH.cjs → add-0XI8_HOf.cjs} +3 -3
- package/dist/{add-Cs8yT5iH.cjs.map → add-0XI8_HOf.cjs.map} +1 -1
- package/dist/{add-DJE7zqJ4.mjs → add-DXdJjp0b.mjs} +3 -3
- package/dist/{add-DJE7zqJ4.mjs.map → add-DXdJjp0b.mjs.map} +1 -1
- package/dist/{bundler-DrySVbXF.mjs → bundler-B2ysiCTa.mjs} +5 -5
- package/dist/{bundler-DrySVbXF.mjs.map → bundler-B2ysiCTa.mjs.map} +1 -1
- package/dist/{bundler-NrtItVBp.cjs → bundler-hvCgi0EG.cjs} +5 -5
- package/dist/{bundler-NrtItVBp.cjs.map → bundler-hvCgi0EG.cjs.map} +1 -1
- package/dist/{cli-CHzpFBh-.cjs → cli-5k6kV_i4.cjs} +2 -2
- package/dist/{cli-CHzpFBh-.cjs.map → cli-5k6kV_i4.cjs.map} +1 -1
- package/dist/{cli-DDY3jfTt.mjs → cli-H0uB_znq.mjs} +2 -2
- package/dist/{cli-DDY3jfTt.mjs.map → cli-H0uB_znq.mjs.map} +1 -1
- package/dist/{compose-CDC2bCme.mjs → compose-BEiJWrB7.mjs} +3 -3
- package/dist/{compose-CDC2bCme.mjs.map → compose-BEiJWrB7.mjs.map} +1 -1
- package/dist/{compose-ByIbIrzs.cjs → compose-CYeMW2_o.cjs} +3 -3
- package/dist/{compose-ByIbIrzs.cjs.map → compose-CYeMW2_o.cjs.map} +1 -1
- package/dist/deploy/public.cjs +6 -6
- package/dist/deploy/public.mjs +6 -6
- package/dist/{deploy-DLq9-m6H.mjs → deploy-Cyx5fSb7.mjs} +5 -5
- package/dist/{deploy-DLq9-m6H.mjs.map → deploy-Cyx5fSb7.mjs.map} +1 -1
- package/dist/{deploy-Dl_NavYE.cjs → deploy-Q5qo1OP-.cjs} +5 -5
- package/dist/{deploy-Dl_NavYE.cjs.map → deploy-Q5qo1OP-.cjs.map} +1 -1
- package/dist/{dns-pzftumxg.mjs → dns-CsApfzLc.mjs} +2 -2
- package/dist/{dns-pzftumxg.mjs.map → dns-CsApfzLc.mjs.map} +1 -1
- package/dist/{dns-C2TGXUF4.cjs → dns-v9viladZ.cjs} +2 -2
- package/dist/{dns-C2TGXUF4.cjs.map → dns-v9viladZ.cjs.map} +1 -1
- package/dist/{dokploy-B7-OwY2s.mjs → dokploy-DfUo_QRw.mjs} +2 -2
- package/dist/{dokploy-B7-OwY2s.mjs.map → dokploy-DfUo_QRw.mjs.map} +1 -1
- package/dist/{dokploy-OqBh8G4g.cjs → dokploy-Di7rC2z0.cjs} +2 -2
- package/dist/{dokploy-OqBh8G4g.cjs.map → dokploy-Di7rC2z0.cjs.map} +1 -1
- package/dist/{eas-Cne743nQ.cjs → eas-CIvNT6qa.cjs} +2 -2
- package/dist/{eas-Cne743nQ.cjs.map → eas-CIvNT6qa.cjs.map} +1 -1
- package/dist/{eas-DEpwISrQ.mjs → eas-CTfRmndW.mjs} +2 -2
- package/dist/{eas-DEpwISrQ.mjs.map → eas-CTfRmndW.mjs.map} +1 -1
- package/dist/{encryption-DoVnVU82.cjs → encryption--U4NsK7N.cjs} +2 -2
- package/dist/{encryption-DoVnVU82.cjs.map → encryption--U4NsK7N.cjs.map} +1 -1
- package/dist/{encryption-DJv_jEXi.mjs → encryption-BpTPmd7h.mjs} +2 -2
- package/dist/{encryption-DJv_jEXi.mjs.map → encryption-BpTPmd7h.mjs.map} +1 -1
- package/dist/{engine-Db71-OZL.mjs → engine-BFmHfek1.mjs} +3 -3
- package/dist/{engine-Db71-OZL.mjs.map → engine-BFmHfek1.mjs.map} +1 -1
- package/dist/{engine-CJhQb50j.cjs → engine-Ovl1PB6i.cjs} +3 -3
- package/dist/{engine-CJhQb50j.cjs.map → engine-Ovl1PB6i.cjs.map} +1 -1
- package/dist/{github-C3gblUvr.cjs → github-CTlPQ5Ra.cjs} +4 -4
- package/dist/{github-C3gblUvr.cjs.map → github-CTlPQ5Ra.cjs.map} +1 -1
- package/dist/{github-BMakI-k3.mjs → github-tWAGuR9i.mjs} +4 -4
- package/dist/{github-BMakI-k3.mjs.map → github-tWAGuR9i.mjs.map} +1 -1
- package/dist/{googlePlay-B6uiHi65.cjs → googlePlay-BNH729It.cjs} +3 -3
- package/dist/{googlePlay-B6uiHi65.cjs.map → googlePlay-BNH729It.cjs.map} +1 -1
- package/dist/{googlePlay-BYZ-J5sC.mjs → googlePlay-m1De-qIJ.mjs} +3 -3
- package/dist/{googlePlay-BYZ-J5sC.mjs.map → googlePlay-m1De-qIJ.mjs.map} +1 -1
- package/dist/index.cjs +104 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +104 -68
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-CQUGbptv.mjs → migrate-4B4RgAjU.mjs} +2 -2
- package/dist/{migrate-CQUGbptv.mjs.map → migrate-4B4RgAjU.mjs.map} +1 -1
- package/dist/{migrate-bDQzFvBN.cjs → migrate-BYEmpoIg.cjs} +2 -2
- package/dist/{migrate-bDQzFvBN.cjs.map → migrate-BYEmpoIg.cjs.map} +1 -1
- package/dist/{migrate-GLBZlrlJ.cjs → migrate-DZ2--SaO.cjs} +2 -2
- package/dist/{migrate-GLBZlrlJ.cjs.map → migrate-DZ2--SaO.cjs.map} +1 -1
- package/dist/{migrate-xHYaeBkd.mjs → migrate-X0Y7fz3N.mjs} +2 -2
- package/dist/{migrate-xHYaeBkd.mjs.map → migrate-X0Y7fz3N.mjs.map} +1 -1
- package/dist/{publish-SDG1WfYn.cjs → publish-BUvYjiZH.cjs} +2 -2
- package/dist/{publish-SDG1WfYn.cjs.map → publish-BUvYjiZH.cjs.map} +1 -1
- package/dist/{publish-CSctCNf4.mjs → publish-hhgraEhO.mjs} +2 -2
- package/dist/{publish-CSctCNf4.mjs.map → publish-hhgraEhO.mjs.map} +1 -1
- package/dist/{resolve-wG49Woex.cjs → resolve-DdFKTx8l.cjs} +3 -3
- package/dist/{resolve-wG49Woex.cjs.map → resolve-DdFKTx8l.cjs.map} +1 -1
- package/dist/{resolve-CxDqDmCD.mjs → resolve-DotGHvtX.mjs} +3 -3
- package/dist/{resolve-CxDqDmCD.mjs.map → resolve-DotGHvtX.mjs.map} +1 -1
- package/dist/{rollback-BQgEdr_p.mjs → rollback-9t9G3c_u.mjs} +4 -4
- package/dist/{rollback-BQgEdr_p.mjs.map → rollback-9t9G3c_u.mjs.map} +1 -1
- package/dist/{rollback-Cw-Z0mWn.cjs → rollback-D4xeUQvG.cjs} +4 -4
- package/dist/{rollback-Cw-Z0mWn.cjs.map → rollback-D4xeUQvG.cjs.map} +1 -1
- package/dist/{seeds-CIk0l4SC.mjs → seeds-CRkKeyW0.mjs} +19 -6
- package/dist/seeds-CRkKeyW0.mjs.map +1 -0
- package/dist/{seeds-Dk8YkLkM.cjs → seeds-DbKNcs3o.cjs} +19 -6
- package/dist/seeds-DbKNcs3o.cjs.map +1 -0
- package/dist/{serverSetup-BCbD8ksJ.cjs → serverSetup-CcWgNtiC.cjs} +93 -21
- package/dist/serverSetup-CcWgNtiC.cjs.map +1 -0
- package/dist/{serverSetup--O61SRvg.mjs → serverSetup-DlRKRfTi.mjs} +93 -21
- package/dist/serverSetup-DlRKRfTi.mjs.map +1 -0
- package/dist/{sniffer-2aK7cB2u.cjs → sniffer-B6T6za4n.cjs} +6 -6
- package/dist/{sniffer-2aK7cB2u.cjs.map → sniffer-B6T6za4n.cjs.map} +1 -1
- package/dist/{sniffer-CABMO-Bh.mjs → sniffer-CakkKKqQ.mjs} +6 -6
- package/dist/{sniffer-CABMO-Bh.mjs.map → sniffer-CakkKKqQ.mjs.map} +1 -1
- package/dist/{stage-ClKTHQoM.mjs → stage-Mp6qQIoD.mjs} +4 -4
- package/dist/{stage-ClKTHQoM.mjs.map → stage-Mp6qQIoD.mjs.map} +1 -1
- package/dist/{stage-DrCS0vKD.cjs → stage-W3PUt7ar.cjs} +4 -4
- package/dist/{stage-DrCS0vKD.cjs.map → stage-W3PUt7ar.cjs.map} +1 -1
- package/dist/{stages-DJrOYLZ-.mjs → stages-Cb9rqRbN.mjs} +16 -2
- package/dist/stages-Cb9rqRbN.mjs.map +1 -0
- package/dist/{stages-_3RVNG83.cjs → stages-D4SnG5yl.cjs} +16 -2
- package/dist/stages-D4SnG5yl.cjs.map +1 -0
- package/dist/target/public.cjs +1 -1
- package/dist/target/public.mjs +1 -1
- package/package.json +15 -15
- package/dist/seeds-CIk0l4SC.mjs.map +0 -1
- package/dist/seeds-Dk8YkLkM.cjs.map +0 -1
- package/dist/serverSetup--O61SRvg.mjs.map +0 -1
- package/dist/serverSetup-BCbD8ksJ.cjs.map +0 -1
- package/dist/stages-DJrOYLZ-.mjs.map +0 -1
- package/dist/stages-_3RVNG83.cjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"serverSetup--O61SRvg.mjs","names":[],"sources":["../src/compose/serverScript.ts","../src/compose/serverSetup.ts"],"sourcesContent":["/**\n * What `gkm compose:server` runs on the server, as root (through the admin\n * user's sudo). Plain bash, so `--dry-run` can print exactly what will run,\n * and safe to run again: every step changes only what is missing or wrong.\n *\n * Arguments, in order: the admin user (the one running sudo), the deploy\n * user, the SSH port, the deploy key's public line, and its comment.\n *\n * The last line it prints is `HOSTKEY=<fingerprint>` — the server's own\n * ed25519 host key, which the command checks the network's against before\n * pinning it for CI.\n */\nexport const SERVER_SCRIPT = String.raw`set -euo pipefail\nexport DEBIAN_FRONTEND=noninteractive\nADMIN=\"$1\"; DEPLOY=\"$2\"; SSH_PORT=\"$3\"; PUB=\"$4\"; COMMENT=\"$5\"\nsay() { echo \" $*\"; }\nfail() { echo \" ✗ $*\" >&2; exit 1; }\n\n. /etc/os-release\ncase \"$ID\" in\n\tubuntu | debian) ;;\n\t*) fail \"gkm sets up Ubuntu and Debian servers; this one is $ID.\" ;;\nesac\n\n# ── SSH: keys only, no root ────────────────────────────────────────────────\n# Before password logins are turned off, the admin must have a key to come\n# back with — otherwise this would lock them out.\nADMIN_HOME=\"$(getent passwd \"$ADMIN\" | cut -d: -f6)\"\n[ -s \"$ADMIN_HOME/.ssh/authorized_keys\" ] ||\n\tfail \"$ADMIN has no key in $ADMIN_HOME/.ssh/authorized_keys; turning off password logins would lock them out.\"\n# First match wins in sshd_config, and 00- sorts before what a provider ships\n# (cloud-init's 50-cloud-init.conf turns password logins back on).\ncat > /etc/ssh/sshd_config.d/00-gkm-hardening.conf <<'EOF'\n# Written by gkm compose:server.\nPermitRootLogin no\nPasswordAuthentication no\nKbdInteractiveAuthentication no\nEOF\n# The deploy migrates through an SSH tunnel to the server's loopback.\ncat > /etc/ssh/sshd_config.d/60-gkm-deploy.conf <<EOF\n# Written by gkm compose:server.\nMatch User $DEPLOY\n AllowTcpForwarding local\nEOF\nsshd -t\nsystemctl reload ssh 2>/dev/null || systemctl reload sshd\n# What sshd actually does now, whatever else is in its config.\nEFFECTIVE=\"$(sshd -T)\"\nfor setting in 'permitrootlogin no' 'passwordauthentication no' 'kbdinteractiveauthentication no'; do\n\tgrep -qx \"$setting\" <<<\"$EFFECTIVE\" || fail \"sshd still does not say '$setting'; another file in /etc/ssh overrides it.\"\ndone\nFORWARD=\"$(sshd -T -C \"user=$DEPLOY,host=localhost,addr=127.0.0.1\" | sed -n 's/^allowtcpforwarding //p')\"\ncase \"$FORWARD\" in\n\tyes | all | local) ;;\n\t*) fail \"sshd does not let $DEPLOY forward ports (allowtcpforwarding $FORWARD).\" ;;\nesac\nsay \"sshd: keys only, no root login; $DEPLOY may open local tunnels\"\n\n# ── Firewall: SSH, HTTP, HTTPS ─────────────────────────────────────────────\ncommand -v ufw >/dev/null || { apt-get update -qq && apt-get install -y -qq ufw >/dev/null; }\nufw default deny incoming >/dev/null\nufw default allow outgoing >/dev/null\nufw allow \"$SSH_PORT/tcp\" >/dev/null\nufw allow 80/tcp >/dev/null\nufw allow 443/tcp >/dev/null\nufw --force enable >/dev/null\nsay \"firewall: $SSH_PORT, 80, 443 in; everything out\"\nEXTRA=\"$(ufw status | awk 'NR > 4 && $1 != \"\" { print $1 }' | grep -vxE \"(($SSH_PORT|80|443)(/tcp)?|OpenSSH)\" | sort -u || true)\"\nif [ -n \"$EXTRA\" ]; then\n\tsay \"⚠ also open: $(echo $EXTRA). gkm uses none of them; close each with: sudo ufw delete allow <port>\"\nfi\n\n# ── Updates and bans ───────────────────────────────────────────────────────\napt-get update -qq\napt-get install -y -qq unattended-upgrades fail2ban >/dev/null\ncat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF'\nAPT::Periodic::Update-Package-Lists \"1\";\nAPT::Periodic::Unattended-Upgrade \"1\";\nEOF\ncat > /etc/fail2ban/jail.d/gkm.local <<EOF\n# Written by gkm compose:server.\n[sshd]\nenabled = true\nport = $SSH_PORT\nEOF\nsystemctl enable --now unattended-upgrades fail2ban >/dev/null 2>&1\nsystemctl restart fail2ban\nsay \"security updates install daily; fail2ban watches sshd\"\n\n# ── Docker, from Docker's own repository ───────────────────────────────────\nif ! command -v docker >/dev/null || ! docker compose version >/dev/null 2>&1; then\n\tsay \"installing Docker…\"\n\tapt-get install -y -qq ca-certificates curl >/dev/null\n\tinstall -m 0755 -d /etc/apt/keyrings\n\tcurl -fsSL \"https://download.docker.com/linux/$ID/gpg\" -o /etc/apt/keyrings/docker.asc\n\tchmod a+r /etc/apt/keyrings/docker.asc\n\techo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$ID $VERSION_CODENAME stable\" \\\n\t\t> /etc/apt/sources.list.d/docker.list\n\tapt-get update -qq\n\tapt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin >/dev/null\nfi\nsystemctl enable --now docker >/dev/null 2>&1\nsay \"docker $(docker version --format '{{.Server.Version}}')\"\n\n# ── The deploy user: no password, no sudo, in the docker group ─────────────\nid \"$DEPLOY\" >/dev/null 2>&1 || adduser --disabled-password --gecos \"\" \"$DEPLOY\" >/dev/null\nusermod -aG docker \"$DEPLOY\"\nDEPLOY_HOME=\"$(getent passwd \"$DEPLOY\" | cut -d: -f6)\"\ninstall -d -m 700 -o \"$DEPLOY\" -g \"$DEPLOY\" \"$DEPLOY_HOME/.ssh\"\nAUTH=\"$DEPLOY_HOME/.ssh/authorized_keys\"\ntouch \"$AUTH\"\n# One managed line: this key (or an earlier one by the same name) replaced,\n# with nothing allowed but the commands and the tunnel the deploy uses.\nKEYBODY=\"$(awk '{ print $2 }' <<<\"$PUB\")\"\ngrep -v -F -e \"$KEYBODY\" -e \" $COMMENT\" \"$AUTH\" > \"$AUTH.new\" || true\necho \"restrict,port-forwarding $PUB\" >> \"$AUTH.new\"\nmv \"$AUTH.new\" \"$AUTH\"\nchown \"$DEPLOY:$DEPLOY\" \"$AUTH\"\nchmod 600 \"$AUTH\"\nsay \"user $DEPLOY (docker group), deploy key authorized with restrict,port-forwarding\"\nOTHERS=\"$(grep -v -F -e \"$KEYBODY\" \"$AUTH\" | grep -c . || true)\"\nif [ \"$OTHERS\" -gt 0 ]; then\n\tsay \"⚠ $AUTH holds $OTHERS other key(s). Review: sudo cat $AUTH\"\nfi\n\necho \"HOSTKEY=$(ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub | awk '{ print $2 }')\"\n`;\n\n/**\n * What runs as root to give a fresh server its admin — the guide's first\n * step, for a server only root can log in to yet: a sudo user, with root's\n * keys so the same key logs in. `adduser` asks for its password, which sudo\n * will ask for.\n */\nexport const ADMIN_SCRIPT = `set -euo pipefail\nADMIN=\"$1\"\nif ! id \"$ADMIN\" >/dev/null 2>&1; then\n\techo \" creating $ADMIN — choose the password sudo will ask for:\"\n\tadduser --gecos \"\" \"$ADMIN\"\nfi\nusermod -aG sudo \"$ADMIN\"\nHOME_DIR=\"$(getent passwd \"$ADMIN\" | cut -d: -f6)\"\ninstall -d -m 700 -o \"$ADMIN\" -g \"$ADMIN\" \"$HOME_DIR/.ssh\"\nAUTH=\"$HOME_DIR/.ssh/authorized_keys\"\ntouch \"$AUTH\"\n# The keys alone: a provider's options on root's lines (a command=\"…\" that\n# says \"log in as ubuntu\") would stop the admin's login too.\n{ grep -oE '(ssh|ecdsa|sk)-[^ ]+ [A-Za-z0-9+/=]+( .*)?$' /root/.ssh/authorized_keys || true; } |\n\twhile IFS= read -r key; do\n\t\tgrep -qxF \"$key\" \"$AUTH\" || echo \"$key\" >> \"$AUTH\"\n\tdone\nchown \"$ADMIN:$ADMIN\" \"$AUTH\"\nchmod 600 \"$AUTH\"\necho \" $ADMIN is a sudo user, with root's SSH keys\"\n`;\n","/**\n * `gkm compose:server` — make a VPS ready for a stage's compose deploys, from\n * this machine, in one go.\n *\n * It logs in as the server's admin — a sudo user, never root — and, through\n * sudo, sets the server up the way a production VPS should be: SSH by key\n * only with root's login off, a firewall open on SSH, 80 and 443 alone,\n * security updates installed daily, fail2ban on sshd, and Docker from\n * Docker's own repository. It then creates the stage's deploy user (in the\n * docker group, no sudo, no password) and authorizes a deploy key on it that\n * may run commands and open the tunnel migrations use, nothing more.\n *\n * Off the server: it makes the deploy key pair here, pins the server's host\n * key — checked against the fingerprint the server reports of itself — and\n * gives both to the stage's GitHub environment as `DEPLOY_SSH_KEY` and\n * `DEPLOY_KNOWN_HOSTS`, which the generated deploy workflow reads. Last, it\n * logs in exactly as CI will, and checks Docker and the tunnel answer.\n *\n * A fresh server only root can log in to gets its admin first: with root's\n * key it creates the user, or with the admin's password it copies a key in.\n * Safe to run again: each step changes only what is missing or wrong.\n */\n\nimport { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve } from 'node:path';\nimport { loadWorkspaceConfig } from '../config';\nimport { type Gh, ghCli } from '../deploy/github';\nimport { GkmError } from '../errors';\nimport { prompt } from '../prompt';\nimport { secretsStoreFor } from '../secrets/store.js';\nimport { assertDeployedStage } from '../workspace/stages.js';\nimport { type ComposeServer, composeServer } from './server';\nimport { ADMIN_SCRIPT, SERVER_SCRIPT } from './serverScript';\n\nexport interface ComposeServerSetupOptions {\n\t/** The deployed stage whose server this is. */\n\tstage: string;\n\t/** The server's sudo user. Asked for when absent. */\n\tadmin?: string;\n\t/** The deploy key pair. Default `~/.ssh/gkm-<project>-<stage>`. */\n\tkeyFile?: string;\n\t/** `owner/name`; defaults to the repository `gh` sees here. */\n\trepo?: string;\n\t/** `--no-github`: leave the GitHub environment alone. */\n\tgithub?: boolean;\n\t/** Print the plan and the script; connect to nothing. */\n\tdryRun?: boolean;\n\t/** Apply without asking first. */\n\tyes?: boolean;\n\tcwd?: string;\n}\n\n/** What a finished command ran: output kept, its exit code. */\nexport interface Ran {\n\tcode: number;\n\tstdout: string;\n\tstderr: string;\n}\n\n/**\n * Runs a program. `interactive` gives it this terminal's stdin and stderr —\n * for a password sudo or adduser asks — while stdout is still kept, and\n * echoed as it comes.\n */\nexport type Runner = (\n\tcommand: string,\n\targs: readonly string[],\n\toptions?: { input?: string; interactive?: boolean },\n) => Promise<Ran>;\n\nexport interface ComposeServerSetupDeps {\n\trun: Runner;\n\tgh: Gh;\n\t/** Ask the person at the terminal. */\n\task: (question: string) => Promise<string>;\n}\n\nexport interface ComposeServerSetupResult {\n\tserver: ComposeServer;\n\tadmin: string;\n\tkeyFile: string;\n\t/** The pinned host key line, as `known_hosts` holds it. Absent on a dry run. */\n\tknownHosts?: string;\n\t/** The repository whose environment was given the key. */\n\trepo?: string;\n}\n\n/** The admin named is root, whose login this command turns off. */\nexport class ComposeServerAdminIsRoot extends GkmError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t\"The admin can't be root: setting up the server turns root's SSH login \" +\n\t\t\t\t'off. Name the sudo user you log in as (--admin <user>). A fresh ' +\n\t\t\t\t'server with only root gets one: run this again with the name it ' +\n\t\t\t\t\"should have, and gkm offers to create it with root's key.\",\n\t\t);\n\t\tthis.name = 'ComposeServerAdminIsRoot';\n\t}\n}\n\n/** The admin named is the deploy user, which has no sudo. */\nexport class ComposeServerAdminIsDeployUser extends GkmError {\n\tconstructor(readonly user: string) {\n\t\tsuper(\n\t\t\t`'${user}' is the stage's deploy user (deploy.compose.server), which ` +\n\t\t\t\t'gkm creates without sudo. Name the sudo user you log in as with --admin.',\n\t\t);\n\t\tthis.name = 'ComposeServerAdminIsDeployUser';\n\t}\n}\n\n/** No key logs the admin in, and nothing was done to make one. */\nexport class ComposeServerAdminUnreachable extends GkmError {\n\tconstructor(\n\t\treadonly target: string,\n\t\treadonly stderr: string,\n\t) {\n\t\tsuper(\n\t\t\t`Could not log in as ${target} with a key (ssh said: ${stderr.trim() || 'nothing'}). ` +\n\t\t\t\t`Check \\`ssh ${target}\\` works without a password — copy your key with ` +\n\t\t\t\t`\\`ssh-copy-id ${target}\\` — then run this again.`,\n\t\t);\n\t\tthis.name = 'ComposeServerAdminUnreachable';\n\t}\n}\n\n/** `ssh-keygen` could not make the deploy key pair. */\nexport class DeployKeyNotMade extends GkmError {\n\tconstructor(\n\t\treadonly keyFile: string,\n\t\treadonly stderr: string,\n\t) {\n\t\tsuper(\n\t\t\t`Could not make the deploy key ${keyFile} (ssh-keygen said: ${stderr.trim() || 'nothing'}). ` +\n\t\t\t\t'Pass another path with --key-file.',\n\t\t);\n\t\tthis.name = 'DeployKeyNotMade';\n\t}\n}\n\n/** The script on the server stopped before it finished. */\nexport class ComposeServerSetupFailed extends GkmError {\n\tconstructor(\n\t\treadonly target: string,\n\t\treadonly code: number,\n\t) {\n\t\tsuper(\n\t\t\t`Setting up ${target} stopped (exit ${code}); its output is above. ` +\n\t\t\t\t'Fix what it names and run this again — every step it already ' +\n\t\t\t\t'finished is left as it is.',\n\t\t);\n\t\tthis.name = 'ComposeServerSetupFailed';\n\t}\n}\n\n/** The host key the network shows is not the one the server holds. */\nexport class ComposeServerHostKeyMismatch extends GkmError {\n\tconstructor(\n\t\treadonly host: string,\n\t\treadonly server: string,\n\t\treadonly scanned: string,\n\t) {\n\t\tsuper(\n\t\t\t`${host}'s host key is ${server} on the server, but ${scanned || 'nothing'} ` +\n\t\t\t\t'over the network. Something between here and the server answered for ' +\n\t\t\t\t'it, or the server is not the one you logged in to. Nothing was given ' +\n\t\t\t\t'to GitHub; find out why before running this again.',\n\t\t);\n\t\tthis.name = 'ComposeServerHostKeyMismatch';\n\t}\n}\n\n/** The deploy key logged in, but cannot do what a deploy does. */\nexport class ComposeServerCheckFailed extends GkmError {\n\tconstructor(\n\t\treadonly target: string,\n\t\treadonly check: 'docker' | 'tunnel',\n\t\treadonly output: string,\n\t) {\n\t\tsuper(\n\t\t\tcheck === 'docker'\n\t\t\t\t? `The deploy key can't run docker as ${target} (ssh said: ${output.trim() || 'nothing'}).`\n\t\t\t\t: `The deploy key can't open a tunnel as ${target} (got: ${output.trim() || 'nothing'}). ` +\n\t\t\t\t\t\t\"Check its line in the deploy user's authorized_keys says \" +\n\t\t\t\t\t\t'restrict,port-forwarding, and that sshd allows it forwarding.',\n\t\t);\n\t\tthis.name = 'ComposeServerCheckFailed';\n\t}\n}\n\n/** `'…'`, for a value in a command line the remote shell reads. */\nfunction shellQuote(value: string): string {\n\treturn `'${value.replaceAll(\"'\", `'\\\\''`)}'`;\n}\n\n/**\n * The remote command that runs `script` with `args`: written to a temporary\n * file (so the script keeps the terminal for any password it asks), run,\n * and removed. `sudo` for everything but root.\n */\nexport function remoteCommand(\n\tscript: string,\n\targs: readonly string[],\n\tsudo: boolean,\n): string {\n\tconst encoded = Buffer.from(script).toString('base64');\n\treturn [\n\t\t`f=\"$(mktemp)\"`,\n\t\t`echo ${encoded} | base64 -d > \"$f\"`,\n\t\t`${sudo ? 'sudo ' : ''}bash \"$f\" ${args.map(shellQuote).join(' ')}; s=$?`,\n\t\t`rm -f \"$f\"`,\n\t\t'exit $s',\n\t].join('; ');\n}\n\nfunction sshOptions(server: ComposeServer): string[] {\n\treturn ['-o', 'ConnectTimeout=20', '-p', String(server.port)];\n}\n\n/** `user@host`, as `ssh` takes it. */\nfunction at(user: string, host: string): string {\n\treturn `${user}@${host}`;\n}\n\n/** The real runner: the system's programs. */\nexport const systemRunner: Runner = (command, args, options = {}) =>\n\tnew Promise((resolve, reject) => {\n\t\tconst child = spawn(command, args, {\n\t\t\tstdio: [\n\t\t\t\toptions.interactive ? 'inherit' : 'pipe',\n\t\t\t\t'pipe',\n\t\t\t\toptions.interactive ? 'inherit' : 'pipe',\n\t\t\t],\n\t\t\ttimeout: options.interactive ? undefined : 60_000,\n\t\t});\n\t\tlet stdout = '';\n\t\tlet stderr = '';\n\t\tchild.stdout?.on('data', (chunk: Buffer) => {\n\t\t\tstdout += chunk.toString();\n\t\t\tif (options.interactive) process.stdout.write(chunk);\n\t\t});\n\t\tchild.stderr?.on('data', (chunk: Buffer) => {\n\t\t\tstderr += chunk.toString();\n\t\t});\n\t\tchild.on('error', reject);\n\t\tchild.on('close', (code) => resolve({ code: code ?? 1, stdout, stderr }));\n\t\tif (child.stdin) child.stdin.end(options.input ?? '');\n\t});\n\n/** Whether `answer` is a yes, `fallback` when nothing was typed. */\nfunction isYes(answer: string, fallback: boolean): boolean {\n\tconst value = answer.trim().toLowerCase();\n\treturn value === '' ? fallback : value === 'y' || value === 'yes';\n}\n\n/** The SHA256 fingerprint of each key in `keys`, by `ssh-keygen -lf -`. */\nasync function fingerprints(run: Runner, keys: string): Promise<string[]> {\n\tconst { stdout } = await run('ssh-keygen', ['-lf', '-'], { input: keys });\n\treturn stdout\n\t\t.split('\\n')\n\t\t.map((line) => line.split(' ')[1])\n\t\t.filter((value): value is string => Boolean(value));\n}\n\n/** The default deploy key pair for a stage. */\nexport function defaultKeyFile(project: string, stage: string): string {\n\treturn join(homedir(), '.ssh', `gkm-${project}-${stage}`);\n}\n\n/** `gkm compose:server`. */\nexport async function composeServerCommand(\n\toptions: ComposeServerSetupOptions,\n\tdeps: Partial<ComposeServerSetupDeps> = {},\n): Promise<ComposeServerSetupResult> {\n\tconst run = deps.run ?? systemRunner;\n\tconst gh = deps.gh ?? ghCli;\n\tconst ask =\n\t\tdeps.ask ??\n\t\t((question: string) =>\n\t\t\tprompt(question, {\n\t\t\t\tinstead: 'Name the server user with --admin and pass --yes.',\n\t\t\t}));\n\tconst github = options.github !== false;\n\tconst { stage } = options;\n\n\tconst cwd = resolve(options.cwd ?? process.cwd());\n\tconst { workspace } = await loadWorkspaceConfig(cwd);\n\tassertDeployedStage(workspace.stages, stage);\n\tconst server = composeServer({\n\t\tstage,\n\t\tlocal: false,\n\t\tconfig: workspace.deploy?.compose?.server,\n\t\tcustom: (await (await secretsStoreFor(workspace, stage)).read(stage))\n\t\t\t?.custom,\n\t})!;\n\n\tconst admin = (\n\t\toptions.admin ??\n\t\t(await ask(`The sudo user you log in to ${server.host} as (not root): `))\n\t).trim();\n\tif (!admin || admin === 'root') throw new ComposeServerAdminIsRoot();\n\tif (admin === server.user) throw new ComposeServerAdminIsDeployUser(admin);\n\n\tconst keyFile = resolve(\n\t\toptions.keyFile ?? defaultKeyFile(workspace.name, stage),\n\t);\n\tconst comment = `gkm-${workspace.name}-${stage}-deploy`;\n\tconst repo = github\n\t\t? (options.repo ??\n\t\t\tgh(['repo', 'view', '--json', 'nameWithOwner', '-q', '.nameWithOwner']))\n\t\t: undefined;\n\tconst adminTarget = at(admin, server.host);\n\tconst deployTarget = at(server.user, server.host);\n\n\tconsole.log(`\\n🖥 Server for stage \"${stage}\"\\n`);\n\tconsole.log(` Server: ${server.host} (ssh port ${server.port})`);\n\tconsole.log(` Admin: ${admin} — logs in with your key, runs sudo`);\n\tconsole.log(\n\t\t` Deploy user: ${server.user} — docker group, no sudo, no password`,\n\t);\n\tconsole.log(\n\t\t` Deploy key: ${keyFile}${existsSync(keyFile) ? '' : ' (new, made here)'}`,\n\t);\n\tconsole.log(\n\t\t` GitHub: ${repo ? `environment \"${stage}\" of ${repo}: DEPLOY_SSH_KEY, DEPLOY_KNOWN_HOSTS` : 'left alone (--no-github)'}`,\n\t);\n\tconsole.log('\\n On the server, through sudo:');\n\tfor (const step of [\n\t\t\"SSH: keys only, root's login off (and nothing else may turn passwords back on)\",\n\t\t`firewall: ${server.port}, 80 and 443 in, everything out`,\n\t\t'security updates installed daily (unattended-upgrades); fail2ban on sshd',\n\t\t\"Docker and its compose plugin, from Docker's repository\",\n\t\t`user ${server.user}, the deploy key authorized with restrict,port-forwarding`,\n\t]) {\n\t\tconsole.log(` · ${step}`);\n\t}\n\n\tif (options.dryRun) {\n\t\tconsole.log(\n\t\t\t`\\n The script, run as: sudo bash setup.sh ${[admin, server.user, String(server.port), '<deploy key>', comment].map(shellQuote).join(' ')}\\n`,\n\t\t);\n\t\tconsole.log(SERVER_SCRIPT.replace(/^/gm, ' '));\n\t\tconsole.log(' --dry-run: nothing changed.\\n');\n\t\treturn { server, admin, keyFile, ...(repo ? { repo } : {}) };\n\t}\n\n\tif (\n\t\t!options.yes &&\n\t\t!isYes(await ask(`\\n Set up ${server.host}? [y/N] `), false)\n\t) {\n\t\tconsole.log(' Nothing changed.\\n');\n\t\treturn { server, admin, keyFile, ...(repo ? { repo } : {}) };\n\t}\n\n\t// ── The deploy key pair, here ──────────────────────────────────────────\n\tif (!existsSync(keyFile)) {\n\t\tawait mkdir(dirname(keyFile), { recursive: true, mode: 0o700 });\n\t\tconst made = await run('ssh-keygen', [\n\t\t\t'-t',\n\t\t\t'ed25519',\n\t\t\t'-N',\n\t\t\t'',\n\t\t\t'-C',\n\t\t\tcomment,\n\t\t\t'-f',\n\t\t\tkeyFile,\n\t\t]);\n\t\tif (made.code !== 0) throw new DeployKeyNotMade(keyFile, made.stderr);\n\t\tconsole.log(`\\n ✓ Made the deploy key ${keyFile}`);\n\t}\n\tconst publicKey = (await readFile(`${keyFile}.pub`, 'utf-8')).trim();\n\n\t// ── The admin's login ──────────────────────────────────────────────────\n\t// Key only, and the host key accepted on first sight — as the first\n\t// `ssh` to a new server does. CI's is pinned and checked below.\n\tconst probe = (user: string) =>\n\t\trun('ssh', [\n\t\t\t'-o',\n\t\t\t'BatchMode=yes',\n\t\t\t'-o',\n\t\t\t'StrictHostKeyChecking=accept-new',\n\t\t\t...sshOptions(server),\n\t\t\tat(user, server.host),\n\t\t\t'true',\n\t\t]);\n\tlet login = await probe(admin);\n\tif (login.code !== 0) {\n\t\tconst root = await probe('root');\n\t\tif (root.code === 0) {\n\t\t\tif (\n\t\t\t\tisYes(\n\t\t\t\t\tawait ask(\n\t\t\t\t\t\t`\\n ${admin} can't log in with a key, but root can. Create ${admin} as a sudo user with root's keys? [Y/n] `,\n\t\t\t\t\t),\n\t\t\t\t\ttrue,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tawait run(\n\t\t\t\t\t'ssh',\n\t\t\t\t\t[\n\t\t\t\t\t\t'-t',\n\t\t\t\t\t\t...sshOptions(server),\n\t\t\t\t\t\tat('root', server.host),\n\t\t\t\t\t\tremoteCommand(ADMIN_SCRIPT, [admin], false),\n\t\t\t\t\t],\n\t\t\t\t\t{ interactive: true },\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (\n\t\t\tisYes(\n\t\t\t\tawait ask(\n\t\t\t\t\t`\\n ${admin} can't log in with a key. Copy yours there with ssh-copy-id (asks ${admin}'s password)? [Y/n] `,\n\t\t\t\t),\n\t\t\t\ttrue,\n\t\t\t)\n\t\t) {\n\t\t\tawait run('ssh-copy-id', ['-p', String(server.port), adminTarget], {\n\t\t\t\tinteractive: true,\n\t\t\t});\n\t\t}\n\t\tlogin = await probe(admin);\n\t\tif (login.code !== 0) {\n\t\t\tthrow new ComposeServerAdminUnreachable(adminTarget, login.stderr);\n\t\t}\n\t}\n\tconsole.log(` ✓ ${adminTarget} logs in with a key\\n`);\n\n\t// ── The server ─────────────────────────────────────────────────────────\n\tconst setup = await run(\n\t\t'ssh',\n\t\t[\n\t\t\t'-t',\n\t\t\t...sshOptions(server),\n\t\t\tadminTarget,\n\t\t\tremoteCommand(\n\t\t\t\tSERVER_SCRIPT,\n\t\t\t\t[admin, server.user, String(server.port), publicKey, comment],\n\t\t\t\ttrue,\n\t\t\t),\n\t\t],\n\t\t{ interactive: true },\n\t);\n\tif (setup.code !== 0) {\n\t\tthrow new ComposeServerSetupFailed(adminTarget, setup.code);\n\t}\n\tconst serverFingerprint = setup.stdout\n\t\t.replaceAll('\\r', '')\n\t\t.match(/^HOSTKEY=(\\S+)$/m)?.[1];\n\tif (!serverFingerprint) {\n\t\tthrow new ComposeServerSetupFailed(adminTarget, setup.code);\n\t}\n\n\t// ── The host key, pinned ───────────────────────────────────────────────\n\tconst scan = await run('ssh-keyscan', [\n\t\t'-t',\n\t\t'ed25519',\n\t\t'-p',\n\t\tString(server.port),\n\t\tserver.host,\n\t]);\n\tconst knownHosts = scan.stdout\n\t\t.split('\\n')\n\t\t.filter((line) => line.trim() && !line.startsWith('#'))\n\t\t.join('\\n');\n\tconst [scanned = ''] = await fingerprints(run, knownHosts);\n\tif (scanned !== serverFingerprint) {\n\t\tthrow new ComposeServerHostKeyMismatch(\n\t\t\tserver.host,\n\t\t\tserverFingerprint,\n\t\t\tscanned,\n\t\t);\n\t}\n\tconsole.log(`\\n ✓ Host key ${scanned}, as the server reports it`);\n\n\tif (repo) {\n\t\tgh(['api', '--method', 'PUT', `repos/${repo}/environments/${stage}`]);\n\t\tgh([\n\t\t\t'variable',\n\t\t\t'set',\n\t\t\t'DEPLOY_KNOWN_HOSTS',\n\t\t\t'--env',\n\t\t\tstage,\n\t\t\t'--repo',\n\t\t\trepo,\n\t\t\t'--body',\n\t\t\tknownHosts,\n\t\t]);\n\t\t// Through stdin, so the key is never an argument a process list shows.\n\t\tgh(\n\t\t\t['secret', 'set', 'DEPLOY_SSH_KEY', '--env', stage, '--repo', repo],\n\t\t\tawait readFile(keyFile, 'utf-8'),\n\t\t);\n\t\tconsole.log(\n\t\t\t` ✓ GitHub: environment \"${stage}\", DEPLOY_SSH_KEY and DEPLOY_KNOWN_HOSTS`,\n\t\t);\n\t}\n\n\t// ── Logged in as CI will be ────────────────────────────────────────────\n\tconst known = join(dirname(keyFile), `.gkm-known-${workspace.name}-${stage}`);\n\tawait writeFile(known, `${knownHosts}\\n`);\n\ttry {\n\t\tconst asCi = [\n\t\t\t'-i',\n\t\t\tkeyFile,\n\t\t\t'-o',\n\t\t\t'IdentitiesOnly=yes',\n\t\t\t'-o',\n\t\t\t'BatchMode=yes',\n\t\t\t'-o',\n\t\t\t`UserKnownHostsFile=${known}`,\n\t\t\t'-o',\n\t\t\t'StrictHostKeyChecking=yes',\n\t\t\t...sshOptions(server),\n\t\t];\n\t\tconst docker = await run('ssh', [\n\t\t\t...asCi,\n\t\t\tdeployTarget,\n\t\t\t'docker',\n\t\t\t'version',\n\t\t\t'--format',\n\t\t\t'{{.Server.Version}}',\n\t\t]);\n\t\tif (docker.code !== 0) {\n\t\t\tthrow new ComposeServerCheckFailed(deployTarget, 'docker', docker.stderr);\n\t\t}\n\t\tconsole.log(\n\t\t\t` ✓ ${deployTarget} runs docker (engine ${docker.stdout.trim()})`,\n\t\t);\n\t\t// The server's own sshd, through a tunnel: what a migration does to\n\t\t// reach Postgres on the server's loopback.\n\t\tconst tunnel = await run('ssh', [\n\t\t\t...asCi,\n\t\t\t'-W',\n\t\t\t`127.0.0.1:${server.port}`,\n\t\t\tdeployTarget,\n\t\t]);\n\t\tif (!tunnel.stdout.startsWith('SSH-2.0')) {\n\t\t\tthrow new ComposeServerCheckFailed(\n\t\t\t\tdeployTarget,\n\t\t\t\t'tunnel',\n\t\t\t\ttunnel.stdout.slice(0, 80) || tunnel.stderr,\n\t\t\t);\n\t\t}\n\t\tconsole.log(` ✓ ${deployTarget} opens tunnels (migrations)`);\n\t} finally {\n\t\tawait rm(known, { force: true });\n\t}\n\n\tconsole.log(\n\t\t`\\n✅ ${server.host} is ready for \"${stage}\".${repo ? ' CI deploys it with the key it was given.' : ''} ` +\n\t\t\t`To deploy from here, load the key first: ssh-add ${keyFile}\\n`,\n\t);\n\treturn {\n\t\tserver,\n\t\tadmin,\n\t\tkeyFile,\n\t\tknownHosts,\n\t\t...(repo ? { repo } : {}),\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"serverSetup-BCbD8ksJ.cjs","names":["GkmError","spawn","join","homedir","ghCli","prompt","resolve","loadWorkspaceConfig","composeServer","secretsStoreFor","existsSync","mkdir","dirname","readFile","writeFile","rm"],"sources":["../src/compose/serverScript.ts","../src/compose/serverSetup.ts"],"sourcesContent":["/**\n * What `gkm compose:server` runs on the server, as root (through the admin\n * user's sudo). Plain bash, so `--dry-run` can print exactly what will run,\n * and safe to run again: every step changes only what is missing or wrong.\n *\n * Arguments, in order: the admin user (the one running sudo), the deploy\n * user, the SSH port, the deploy key's public line, and its comment.\n *\n * The last line it prints is `HOSTKEY=<fingerprint>` — the server's own\n * ed25519 host key, which the command checks the network's against before\n * pinning it for CI.\n */\nexport const SERVER_SCRIPT = String.raw`set -euo pipefail\nexport DEBIAN_FRONTEND=noninteractive\nADMIN=\"$1\"; DEPLOY=\"$2\"; SSH_PORT=\"$3\"; PUB=\"$4\"; COMMENT=\"$5\"\nsay() { echo \" $*\"; }\nfail() { echo \" ✗ $*\" >&2; exit 1; }\n\n. /etc/os-release\ncase \"$ID\" in\n\tubuntu | debian) ;;\n\t*) fail \"gkm sets up Ubuntu and Debian servers; this one is $ID.\" ;;\nesac\n\n# ── SSH: keys only, no root ────────────────────────────────────────────────\n# Before password logins are turned off, the admin must have a key to come\n# back with — otherwise this would lock them out.\nADMIN_HOME=\"$(getent passwd \"$ADMIN\" | cut -d: -f6)\"\n[ -s \"$ADMIN_HOME/.ssh/authorized_keys\" ] ||\n\tfail \"$ADMIN has no key in $ADMIN_HOME/.ssh/authorized_keys; turning off password logins would lock them out.\"\n# First match wins in sshd_config, and 00- sorts before what a provider ships\n# (cloud-init's 50-cloud-init.conf turns password logins back on).\ncat > /etc/ssh/sshd_config.d/00-gkm-hardening.conf <<'EOF'\n# Written by gkm compose:server.\nPermitRootLogin no\nPasswordAuthentication no\nKbdInteractiveAuthentication no\nEOF\n# The deploy migrates through an SSH tunnel to the server's loopback.\ncat > /etc/ssh/sshd_config.d/60-gkm-deploy.conf <<EOF\n# Written by gkm compose:server.\nMatch User $DEPLOY\n AllowTcpForwarding local\nEOF\nsshd -t\nsystemctl reload ssh 2>/dev/null || systemctl reload sshd\n# What sshd actually does now, whatever else is in its config.\nEFFECTIVE=\"$(sshd -T)\"\nfor setting in 'permitrootlogin no' 'passwordauthentication no' 'kbdinteractiveauthentication no'; do\n\tgrep -qx \"$setting\" <<<\"$EFFECTIVE\" || fail \"sshd still does not say '$setting'; another file in /etc/ssh overrides it.\"\ndone\nFORWARD=\"$(sshd -T -C \"user=$DEPLOY,host=localhost,addr=127.0.0.1\" | sed -n 's/^allowtcpforwarding //p')\"\ncase \"$FORWARD\" in\n\tyes | all | local) ;;\n\t*) fail \"sshd does not let $DEPLOY forward ports (allowtcpforwarding $FORWARD).\" ;;\nesac\nsay \"sshd: keys only, no root login; $DEPLOY may open local tunnels\"\n\n# ── Firewall: SSH, HTTP, HTTPS ─────────────────────────────────────────────\ncommand -v ufw >/dev/null || { apt-get update -qq && apt-get install -y -qq ufw >/dev/null; }\nufw default deny incoming >/dev/null\nufw default allow outgoing >/dev/null\nufw allow \"$SSH_PORT/tcp\" >/dev/null\nufw allow 80/tcp >/dev/null\nufw allow 443/tcp >/dev/null\nufw --force enable >/dev/null\nsay \"firewall: $SSH_PORT, 80, 443 in; everything out\"\nEXTRA=\"$(ufw status | awk 'NR > 4 && $1 != \"\" { print $1 }' | grep -vxE \"(($SSH_PORT|80|443)(/tcp)?|OpenSSH)\" | sort -u || true)\"\nif [ -n \"$EXTRA\" ]; then\n\tsay \"⚠ also open: $(echo $EXTRA). gkm uses none of them; close each with: sudo ufw delete allow <port>\"\nfi\n\n# ── Updates and bans ───────────────────────────────────────────────────────\napt-get update -qq\napt-get install -y -qq unattended-upgrades fail2ban >/dev/null\ncat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF'\nAPT::Periodic::Update-Package-Lists \"1\";\nAPT::Periodic::Unattended-Upgrade \"1\";\nEOF\ncat > /etc/fail2ban/jail.d/gkm.local <<EOF\n# Written by gkm compose:server.\n[sshd]\nenabled = true\nport = $SSH_PORT\nEOF\nsystemctl enable --now unattended-upgrades fail2ban >/dev/null 2>&1\nsystemctl restart fail2ban\nsay \"security updates install daily; fail2ban watches sshd\"\n\n# ── Docker, from Docker's own repository ───────────────────────────────────\nif ! command -v docker >/dev/null || ! docker compose version >/dev/null 2>&1; then\n\tsay \"installing Docker…\"\n\tapt-get install -y -qq ca-certificates curl >/dev/null\n\tinstall -m 0755 -d /etc/apt/keyrings\n\tcurl -fsSL \"https://download.docker.com/linux/$ID/gpg\" -o /etc/apt/keyrings/docker.asc\n\tchmod a+r /etc/apt/keyrings/docker.asc\n\techo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$ID $VERSION_CODENAME stable\" \\\n\t\t> /etc/apt/sources.list.d/docker.list\n\tapt-get update -qq\n\tapt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin >/dev/null\nfi\nsystemctl enable --now docker >/dev/null 2>&1\nsay \"docker $(docker version --format '{{.Server.Version}}')\"\n\n# ── The deploy user: no password, no sudo, in the docker group ─────────────\nid \"$DEPLOY\" >/dev/null 2>&1 || adduser --disabled-password --gecos \"\" \"$DEPLOY\" >/dev/null\nusermod -aG docker \"$DEPLOY\"\nDEPLOY_HOME=\"$(getent passwd \"$DEPLOY\" | cut -d: -f6)\"\ninstall -d -m 700 -o \"$DEPLOY\" -g \"$DEPLOY\" \"$DEPLOY_HOME/.ssh\"\nAUTH=\"$DEPLOY_HOME/.ssh/authorized_keys\"\ntouch \"$AUTH\"\n# One managed line: this key (or an earlier one by the same name) replaced,\n# with nothing allowed but the commands and the tunnel the deploy uses.\nKEYBODY=\"$(awk '{ print $2 }' <<<\"$PUB\")\"\ngrep -v -F -e \"$KEYBODY\" -e \" $COMMENT\" \"$AUTH\" > \"$AUTH.new\" || true\necho \"restrict,port-forwarding $PUB\" >> \"$AUTH.new\"\nmv \"$AUTH.new\" \"$AUTH\"\nchown \"$DEPLOY:$DEPLOY\" \"$AUTH\"\nchmod 600 \"$AUTH\"\nsay \"user $DEPLOY (docker group), deploy key authorized with restrict,port-forwarding\"\nOTHERS=\"$(grep -v -F -e \"$KEYBODY\" \"$AUTH\" | grep -c . || true)\"\nif [ \"$OTHERS\" -gt 0 ]; then\n\tsay \"⚠ $AUTH holds $OTHERS other key(s). Review: sudo cat $AUTH\"\nfi\n\necho \"HOSTKEY=$(ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub | awk '{ print $2 }')\"\n`;\n\n/**\n * What runs as root to give a fresh server its admin — the guide's first\n * step, for a server only root can log in to yet: a sudo user, with root's\n * keys so the same key logs in. `adduser` asks for its password, which sudo\n * will ask for.\n */\nexport const ADMIN_SCRIPT = `set -euo pipefail\nADMIN=\"$1\"\nif ! id \"$ADMIN\" >/dev/null 2>&1; then\n\techo \" creating $ADMIN — choose the password sudo will ask for:\"\n\tadduser --gecos \"\" \"$ADMIN\"\nfi\nusermod -aG sudo \"$ADMIN\"\nHOME_DIR=\"$(getent passwd \"$ADMIN\" | cut -d: -f6)\"\ninstall -d -m 700 -o \"$ADMIN\" -g \"$ADMIN\" \"$HOME_DIR/.ssh\"\nAUTH=\"$HOME_DIR/.ssh/authorized_keys\"\ntouch \"$AUTH\"\n# The keys alone: a provider's options on root's lines (a command=\"…\" that\n# says \"log in as ubuntu\") would stop the admin's login too.\n{ grep -oE '(ssh|ecdsa|sk)-[^ ]+ [A-Za-z0-9+/=]+( .*)?$' /root/.ssh/authorized_keys || true; } |\n\twhile IFS= read -r key; do\n\t\tgrep -qxF \"$key\" \"$AUTH\" || echo \"$key\" >> \"$AUTH\"\n\tdone\nchown \"$ADMIN:$ADMIN\" \"$AUTH\"\nchmod 600 \"$AUTH\"\necho \" $ADMIN is a sudo user, with root's SSH keys\"\n`;\n","/**\n * `gkm compose:server` — make a VPS ready for a stage's compose deploys, from\n * this machine, in one go.\n *\n * It logs in as the server's admin — a sudo user, never root — and, through\n * sudo, sets the server up the way a production VPS should be: SSH by key\n * only with root's login off, a firewall open on SSH, 80 and 443 alone,\n * security updates installed daily, fail2ban on sshd, and Docker from\n * Docker's own repository. It then creates the stage's deploy user (in the\n * docker group, no sudo, no password) and authorizes a deploy key on it that\n * may run commands and open the tunnel migrations use, nothing more.\n *\n * Off the server: it makes the deploy key pair here, pins the server's host\n * key — checked against the fingerprint the server reports of itself — and\n * gives both to the stage's GitHub environment as `DEPLOY_SSH_KEY` and\n * `DEPLOY_KNOWN_HOSTS`, which the generated deploy workflow reads. Last, it\n * logs in exactly as CI will, and checks Docker and the tunnel answer.\n *\n * A fresh server only root can log in to gets its admin first: with root's\n * key it creates the user, or with the admin's password it copies a key in.\n * Safe to run again: each step changes only what is missing or wrong.\n */\n\nimport { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve } from 'node:path';\nimport { loadWorkspaceConfig } from '../config';\nimport { type Gh, ghCli } from '../deploy/github';\nimport { GkmError } from '../errors';\nimport { prompt } from '../prompt';\nimport { secretsStoreFor } from '../secrets/store.js';\nimport { assertDeployedStage } from '../workspace/stages.js';\nimport { type ComposeServer, composeServer } from './server';\nimport { ADMIN_SCRIPT, SERVER_SCRIPT } from './serverScript';\n\nexport interface ComposeServerSetupOptions {\n\t/** The deployed stage whose server this is. */\n\tstage: string;\n\t/** The server's sudo user. Asked for when absent. */\n\tadmin?: string;\n\t/** The deploy key pair. Default `~/.ssh/gkm-<project>-<stage>`. */\n\tkeyFile?: string;\n\t/** `owner/name`; defaults to the repository `gh` sees here. */\n\trepo?: string;\n\t/** `--no-github`: leave the GitHub environment alone. */\n\tgithub?: boolean;\n\t/** Print the plan and the script; connect to nothing. */\n\tdryRun?: boolean;\n\t/** Apply without asking first. */\n\tyes?: boolean;\n\tcwd?: string;\n}\n\n/** What a finished command ran: output kept, its exit code. */\nexport interface Ran {\n\tcode: number;\n\tstdout: string;\n\tstderr: string;\n}\n\n/**\n * Runs a program. `interactive` gives it this terminal's stdin and stderr —\n * for a password sudo or adduser asks — while stdout is still kept, and\n * echoed as it comes.\n */\nexport type Runner = (\n\tcommand: string,\n\targs: readonly string[],\n\toptions?: { input?: string; interactive?: boolean },\n) => Promise<Ran>;\n\nexport interface ComposeServerSetupDeps {\n\trun: Runner;\n\tgh: Gh;\n\t/** Ask the person at the terminal. */\n\task: (question: string) => Promise<string>;\n}\n\nexport interface ComposeServerSetupResult {\n\tserver: ComposeServer;\n\tadmin: string;\n\tkeyFile: string;\n\t/** The pinned host key line, as `known_hosts` holds it. Absent on a dry run. */\n\tknownHosts?: string;\n\t/** The repository whose environment was given the key. */\n\trepo?: string;\n}\n\n/** The admin named is root, whose login this command turns off. */\nexport class ComposeServerAdminIsRoot extends GkmError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t\"The admin can't be root: setting up the server turns root's SSH login \" +\n\t\t\t\t'off. Name the sudo user you log in as (--admin <user>). A fresh ' +\n\t\t\t\t'server with only root gets one: run this again with the name it ' +\n\t\t\t\t\"should have, and gkm offers to create it with root's key.\",\n\t\t);\n\t\tthis.name = 'ComposeServerAdminIsRoot';\n\t}\n}\n\n/** The admin named is the deploy user, which has no sudo. */\nexport class ComposeServerAdminIsDeployUser extends GkmError {\n\tconstructor(readonly user: string) {\n\t\tsuper(\n\t\t\t`'${user}' is the stage's deploy user (deploy.compose.server), which ` +\n\t\t\t\t'gkm creates without sudo. Name the sudo user you log in as with --admin.',\n\t\t);\n\t\tthis.name = 'ComposeServerAdminIsDeployUser';\n\t}\n}\n\n/** No key logs the admin in, and nothing was done to make one. */\nexport class ComposeServerAdminUnreachable extends GkmError {\n\tconstructor(\n\t\treadonly target: string,\n\t\treadonly stderr: string,\n\t) {\n\t\tsuper(\n\t\t\t`Could not log in as ${target} with a key (ssh said: ${stderr.trim() || 'nothing'}). ` +\n\t\t\t\t`Check \\`ssh ${target}\\` works without a password — copy your key with ` +\n\t\t\t\t`\\`ssh-copy-id ${target}\\` — then run this again.`,\n\t\t);\n\t\tthis.name = 'ComposeServerAdminUnreachable';\n\t}\n}\n\n/** `ssh-keygen` could not make the deploy key pair. */\nexport class DeployKeyNotMade extends GkmError {\n\tconstructor(\n\t\treadonly keyFile: string,\n\t\treadonly stderr: string,\n\t) {\n\t\tsuper(\n\t\t\t`Could not make the deploy key ${keyFile} (ssh-keygen said: ${stderr.trim() || 'nothing'}). ` +\n\t\t\t\t'Pass another path with --key-file.',\n\t\t);\n\t\tthis.name = 'DeployKeyNotMade';\n\t}\n}\n\n/** The script on the server stopped before it finished. */\nexport class ComposeServerSetupFailed extends GkmError {\n\tconstructor(\n\t\treadonly target: string,\n\t\treadonly code: number,\n\t) {\n\t\tsuper(\n\t\t\t`Setting up ${target} stopped (exit ${code}); its output is above. ` +\n\t\t\t\t'Fix what it names and run this again — every step it already ' +\n\t\t\t\t'finished is left as it is.',\n\t\t);\n\t\tthis.name = 'ComposeServerSetupFailed';\n\t}\n}\n\n/** The host key the network shows is not the one the server holds. */\nexport class ComposeServerHostKeyMismatch extends GkmError {\n\tconstructor(\n\t\treadonly host: string,\n\t\treadonly server: string,\n\t\treadonly scanned: string,\n\t) {\n\t\tsuper(\n\t\t\t`${host}'s host key is ${server} on the server, but ${scanned || 'nothing'} ` +\n\t\t\t\t'over the network. Something between here and the server answered for ' +\n\t\t\t\t'it, or the server is not the one you logged in to. Nothing was given ' +\n\t\t\t\t'to GitHub; find out why before running this again.',\n\t\t);\n\t\tthis.name = 'ComposeServerHostKeyMismatch';\n\t}\n}\n\n/** The deploy key logged in, but cannot do what a deploy does. */\nexport class ComposeServerCheckFailed extends GkmError {\n\tconstructor(\n\t\treadonly target: string,\n\t\treadonly check: 'docker' | 'tunnel',\n\t\treadonly output: string,\n\t) {\n\t\tsuper(\n\t\t\tcheck === 'docker'\n\t\t\t\t? `The deploy key can't run docker as ${target} (ssh said: ${output.trim() || 'nothing'}).`\n\t\t\t\t: `The deploy key can't open a tunnel as ${target} (got: ${output.trim() || 'nothing'}). ` +\n\t\t\t\t\t\t\"Check its line in the deploy user's authorized_keys says \" +\n\t\t\t\t\t\t'restrict,port-forwarding, and that sshd allows it forwarding.',\n\t\t);\n\t\tthis.name = 'ComposeServerCheckFailed';\n\t}\n}\n\n/** `'…'`, for a value in a command line the remote shell reads. */\nfunction shellQuote(value: string): string {\n\treturn `'${value.replaceAll(\"'\", `'\\\\''`)}'`;\n}\n\n/**\n * The remote command that runs `script` with `args`: written to a temporary\n * file (so the script keeps the terminal for any password it asks), run,\n * and removed. `sudo` for everything but root.\n */\nexport function remoteCommand(\n\tscript: string,\n\targs: readonly string[],\n\tsudo: boolean,\n): string {\n\tconst encoded = Buffer.from(script).toString('base64');\n\treturn [\n\t\t`f=\"$(mktemp)\"`,\n\t\t`echo ${encoded} | base64 -d > \"$f\"`,\n\t\t`${sudo ? 'sudo ' : ''}bash \"$f\" ${args.map(shellQuote).join(' ')}; s=$?`,\n\t\t`rm -f \"$f\"`,\n\t\t'exit $s',\n\t].join('; ');\n}\n\nfunction sshOptions(server: ComposeServer): string[] {\n\treturn ['-o', 'ConnectTimeout=20', '-p', String(server.port)];\n}\n\n/** `user@host`, as `ssh` takes it. */\nfunction at(user: string, host: string): string {\n\treturn `${user}@${host}`;\n}\n\n/** The real runner: the system's programs. */\nexport const systemRunner: Runner = (command, args, options = {}) =>\n\tnew Promise((resolve, reject) => {\n\t\tconst child = spawn(command, args, {\n\t\t\tstdio: [\n\t\t\t\toptions.interactive ? 'inherit' : 'pipe',\n\t\t\t\t'pipe',\n\t\t\t\toptions.interactive ? 'inherit' : 'pipe',\n\t\t\t],\n\t\t\ttimeout: options.interactive ? undefined : 60_000,\n\t\t});\n\t\tlet stdout = '';\n\t\tlet stderr = '';\n\t\tchild.stdout?.on('data', (chunk: Buffer) => {\n\t\t\tstdout += chunk.toString();\n\t\t\tif (options.interactive) process.stdout.write(chunk);\n\t\t});\n\t\tchild.stderr?.on('data', (chunk: Buffer) => {\n\t\t\tstderr += chunk.toString();\n\t\t});\n\t\tchild.on('error', reject);\n\t\tchild.on('close', (code) => resolve({ code: code ?? 1, stdout, stderr }));\n\t\tif (child.stdin) child.stdin.end(options.input ?? '');\n\t});\n\n/** Whether `answer` is a yes, `fallback` when nothing was typed. */\nfunction isYes(answer: string, fallback: boolean): boolean {\n\tconst value = answer.trim().toLowerCase();\n\treturn value === '' ? fallback : value === 'y' || value === 'yes';\n}\n\n/** The SHA256 fingerprint of each key in `keys`, by `ssh-keygen -lf -`. */\nasync function fingerprints(run: Runner, keys: string): Promise<string[]> {\n\tconst { stdout } = await run('ssh-keygen', ['-lf', '-'], { input: keys });\n\treturn stdout\n\t\t.split('\\n')\n\t\t.map((line) => line.split(' ')[1])\n\t\t.filter((value): value is string => Boolean(value));\n}\n\n/** The default deploy key pair for a stage. */\nexport function defaultKeyFile(project: string, stage: string): string {\n\treturn join(homedir(), '.ssh', `gkm-${project}-${stage}`);\n}\n\n/** `gkm compose:server`. */\nexport async function composeServerCommand(\n\toptions: ComposeServerSetupOptions,\n\tdeps: Partial<ComposeServerSetupDeps> = {},\n): Promise<ComposeServerSetupResult> {\n\tconst run = deps.run ?? systemRunner;\n\tconst gh = deps.gh ?? ghCli;\n\tconst ask =\n\t\tdeps.ask ??\n\t\t((question: string) =>\n\t\t\tprompt(question, {\n\t\t\t\tinstead: 'Name the server user with --admin and pass --yes.',\n\t\t\t}));\n\tconst github = options.github !== false;\n\tconst { stage } = options;\n\n\tconst cwd = resolve(options.cwd ?? process.cwd());\n\tconst { workspace } = await loadWorkspaceConfig(cwd);\n\tassertDeployedStage(workspace.stages, stage);\n\tconst server = composeServer({\n\t\tstage,\n\t\tlocal: false,\n\t\tconfig: workspace.deploy?.compose?.server,\n\t\tcustom: (await (await secretsStoreFor(workspace, stage)).read(stage))\n\t\t\t?.custom,\n\t})!;\n\n\tconst admin = (\n\t\toptions.admin ??\n\t\t(await ask(`The sudo user you log in to ${server.host} as (not root): `))\n\t).trim();\n\tif (!admin || admin === 'root') throw new ComposeServerAdminIsRoot();\n\tif (admin === server.user) throw new ComposeServerAdminIsDeployUser(admin);\n\n\tconst keyFile = resolve(\n\t\toptions.keyFile ?? defaultKeyFile(workspace.name, stage),\n\t);\n\tconst comment = `gkm-${workspace.name}-${stage}-deploy`;\n\tconst repo = github\n\t\t? (options.repo ??\n\t\t\tgh(['repo', 'view', '--json', 'nameWithOwner', '-q', '.nameWithOwner']))\n\t\t: undefined;\n\tconst adminTarget = at(admin, server.host);\n\tconst deployTarget = at(server.user, server.host);\n\n\tconsole.log(`\\n🖥 Server for stage \"${stage}\"\\n`);\n\tconsole.log(` Server: ${server.host} (ssh port ${server.port})`);\n\tconsole.log(` Admin: ${admin} — logs in with your key, runs sudo`);\n\tconsole.log(\n\t\t` Deploy user: ${server.user} — docker group, no sudo, no password`,\n\t);\n\tconsole.log(\n\t\t` Deploy key: ${keyFile}${existsSync(keyFile) ? '' : ' (new, made here)'}`,\n\t);\n\tconsole.log(\n\t\t` GitHub: ${repo ? `environment \"${stage}\" of ${repo}: DEPLOY_SSH_KEY, DEPLOY_KNOWN_HOSTS` : 'left alone (--no-github)'}`,\n\t);\n\tconsole.log('\\n On the server, through sudo:');\n\tfor (const step of [\n\t\t\"SSH: keys only, root's login off (and nothing else may turn passwords back on)\",\n\t\t`firewall: ${server.port}, 80 and 443 in, everything out`,\n\t\t'security updates installed daily (unattended-upgrades); fail2ban on sshd',\n\t\t\"Docker and its compose plugin, from Docker's repository\",\n\t\t`user ${server.user}, the deploy key authorized with restrict,port-forwarding`,\n\t]) {\n\t\tconsole.log(` · ${step}`);\n\t}\n\n\tif (options.dryRun) {\n\t\tconsole.log(\n\t\t\t`\\n The script, run as: sudo bash setup.sh ${[admin, server.user, String(server.port), '<deploy key>', comment].map(shellQuote).join(' ')}\\n`,\n\t\t);\n\t\tconsole.log(SERVER_SCRIPT.replace(/^/gm, ' '));\n\t\tconsole.log(' --dry-run: nothing changed.\\n');\n\t\treturn { server, admin, keyFile, ...(repo ? { repo } : {}) };\n\t}\n\n\tif (\n\t\t!options.yes &&\n\t\t!isYes(await ask(`\\n Set up ${server.host}? [y/N] `), false)\n\t) {\n\t\tconsole.log(' Nothing changed.\\n');\n\t\treturn { server, admin, keyFile, ...(repo ? { repo } : {}) };\n\t}\n\n\t// ── The deploy key pair, here ──────────────────────────────────────────\n\tif (!existsSync(keyFile)) {\n\t\tawait mkdir(dirname(keyFile), { recursive: true, mode: 0o700 });\n\t\tconst made = await run('ssh-keygen', [\n\t\t\t'-t',\n\t\t\t'ed25519',\n\t\t\t'-N',\n\t\t\t'',\n\t\t\t'-C',\n\t\t\tcomment,\n\t\t\t'-f',\n\t\t\tkeyFile,\n\t\t]);\n\t\tif (made.code !== 0) throw new DeployKeyNotMade(keyFile, made.stderr);\n\t\tconsole.log(`\\n ✓ Made the deploy key ${keyFile}`);\n\t}\n\tconst publicKey = (await readFile(`${keyFile}.pub`, 'utf-8')).trim();\n\n\t// ── The admin's login ──────────────────────────────────────────────────\n\t// Key only, and the host key accepted on first sight — as the first\n\t// `ssh` to a new server does. CI's is pinned and checked below.\n\tconst probe = (user: string) =>\n\t\trun('ssh', [\n\t\t\t'-o',\n\t\t\t'BatchMode=yes',\n\t\t\t'-o',\n\t\t\t'StrictHostKeyChecking=accept-new',\n\t\t\t...sshOptions(server),\n\t\t\tat(user, server.host),\n\t\t\t'true',\n\t\t]);\n\tlet login = await probe(admin);\n\tif (login.code !== 0) {\n\t\tconst root = await probe('root');\n\t\tif (root.code === 0) {\n\t\t\tif (\n\t\t\t\tisYes(\n\t\t\t\t\tawait ask(\n\t\t\t\t\t\t`\\n ${admin} can't log in with a key, but root can. Create ${admin} as a sudo user with root's keys? [Y/n] `,\n\t\t\t\t\t),\n\t\t\t\t\ttrue,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tawait run(\n\t\t\t\t\t'ssh',\n\t\t\t\t\t[\n\t\t\t\t\t\t'-t',\n\t\t\t\t\t\t...sshOptions(server),\n\t\t\t\t\t\tat('root', server.host),\n\t\t\t\t\t\tremoteCommand(ADMIN_SCRIPT, [admin], false),\n\t\t\t\t\t],\n\t\t\t\t\t{ interactive: true },\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (\n\t\t\tisYes(\n\t\t\t\tawait ask(\n\t\t\t\t\t`\\n ${admin} can't log in with a key. Copy yours there with ssh-copy-id (asks ${admin}'s password)? [Y/n] `,\n\t\t\t\t),\n\t\t\t\ttrue,\n\t\t\t)\n\t\t) {\n\t\t\tawait run('ssh-copy-id', ['-p', String(server.port), adminTarget], {\n\t\t\t\tinteractive: true,\n\t\t\t});\n\t\t}\n\t\tlogin = await probe(admin);\n\t\tif (login.code !== 0) {\n\t\t\tthrow new ComposeServerAdminUnreachable(adminTarget, login.stderr);\n\t\t}\n\t}\n\tconsole.log(` ✓ ${adminTarget} logs in with a key\\n`);\n\n\t// ── The server ─────────────────────────────────────────────────────────\n\tconst setup = await run(\n\t\t'ssh',\n\t\t[\n\t\t\t'-t',\n\t\t\t...sshOptions(server),\n\t\t\tadminTarget,\n\t\t\tremoteCommand(\n\t\t\t\tSERVER_SCRIPT,\n\t\t\t\t[admin, server.user, String(server.port), publicKey, comment],\n\t\t\t\ttrue,\n\t\t\t),\n\t\t],\n\t\t{ interactive: true },\n\t);\n\tif (setup.code !== 0) {\n\t\tthrow new ComposeServerSetupFailed(adminTarget, setup.code);\n\t}\n\tconst serverFingerprint = setup.stdout\n\t\t.replaceAll('\\r', '')\n\t\t.match(/^HOSTKEY=(\\S+)$/m)?.[1];\n\tif (!serverFingerprint) {\n\t\tthrow new ComposeServerSetupFailed(adminTarget, setup.code);\n\t}\n\n\t// ── The host key, pinned ───────────────────────────────────────────────\n\tconst scan = await run('ssh-keyscan', [\n\t\t'-t',\n\t\t'ed25519',\n\t\t'-p',\n\t\tString(server.port),\n\t\tserver.host,\n\t]);\n\tconst knownHosts = scan.stdout\n\t\t.split('\\n')\n\t\t.filter((line) => line.trim() && !line.startsWith('#'))\n\t\t.join('\\n');\n\tconst [scanned = ''] = await fingerprints(run, knownHosts);\n\tif (scanned !== serverFingerprint) {\n\t\tthrow new ComposeServerHostKeyMismatch(\n\t\t\tserver.host,\n\t\t\tserverFingerprint,\n\t\t\tscanned,\n\t\t);\n\t}\n\tconsole.log(`\\n ✓ Host key ${scanned}, as the server reports it`);\n\n\tif (repo) {\n\t\tgh(['api', '--method', 'PUT', `repos/${repo}/environments/${stage}`]);\n\t\tgh([\n\t\t\t'variable',\n\t\t\t'set',\n\t\t\t'DEPLOY_KNOWN_HOSTS',\n\t\t\t'--env',\n\t\t\tstage,\n\t\t\t'--repo',\n\t\t\trepo,\n\t\t\t'--body',\n\t\t\tknownHosts,\n\t\t]);\n\t\t// Through stdin, so the key is never an argument a process list shows.\n\t\tgh(\n\t\t\t['secret', 'set', 'DEPLOY_SSH_KEY', '--env', stage, '--repo', repo],\n\t\t\tawait readFile(keyFile, 'utf-8'),\n\t\t);\n\t\tconsole.log(\n\t\t\t` ✓ GitHub: environment \"${stage}\", DEPLOY_SSH_KEY and DEPLOY_KNOWN_HOSTS`,\n\t\t);\n\t}\n\n\t// ── Logged in as CI will be ────────────────────────────────────────────\n\tconst known = join(dirname(keyFile), `.gkm-known-${workspace.name}-${stage}`);\n\tawait writeFile(known, `${knownHosts}\\n`);\n\ttry {\n\t\tconst asCi = [\n\t\t\t'-i',\n\t\t\tkeyFile,\n\t\t\t'-o',\n\t\t\t'IdentitiesOnly=yes',\n\t\t\t'-o',\n\t\t\t'BatchMode=yes',\n\t\t\t'-o',\n\t\t\t`UserKnownHostsFile=${known}`,\n\t\t\t'-o',\n\t\t\t'StrictHostKeyChecking=yes',\n\t\t\t...sshOptions(server),\n\t\t];\n\t\tconst docker = await run('ssh', [\n\t\t\t...asCi,\n\t\t\tdeployTarget,\n\t\t\t'docker',\n\t\t\t'version',\n\t\t\t'--format',\n\t\t\t'{{.Server.Version}}',\n\t\t]);\n\t\tif (docker.code !== 0) {\n\t\t\tthrow new ComposeServerCheckFailed(deployTarget, 'docker', docker.stderr);\n\t\t}\n\t\tconsole.log(\n\t\t\t` ✓ ${deployTarget} runs docker (engine ${docker.stdout.trim()})`,\n\t\t);\n\t\t// The server's own sshd, through a tunnel: what a migration does to\n\t\t// reach Postgres on the server's loopback.\n\t\tconst tunnel = await run('ssh', [\n\t\t\t...asCi,\n\t\t\t'-W',\n\t\t\t`127.0.0.1:${server.port}`,\n\t\t\tdeployTarget,\n\t\t]);\n\t\tif (!tunnel.stdout.startsWith('SSH-2.0')) {\n\t\t\tthrow new ComposeServerCheckFailed(\n\t\t\t\tdeployTarget,\n\t\t\t\t'tunnel',\n\t\t\t\ttunnel.stdout.slice(0, 80) || tunnel.stderr,\n\t\t\t);\n\t\t}\n\t\tconsole.log(` ✓ ${deployTarget} opens tunnels (migrations)`);\n\t} finally {\n\t\tawait rm(known, { force: true });\n\t}\n\n\tconsole.log(\n\t\t`\\n✅ ${server.host} is ready for \"${stage}\".${repo ? ' CI deploys it with the key it was given.' : ''} ` +\n\t\t\t`To deploy from here, load the key first: ssh-add ${keyFile}\\n`,\n\t);\n\treturn {\n\t\tserver,\n\t\tadmin,\n\t\tkeyFile,\n\t\tknownHosts,\n\t\t...(repo ? { repo } : {}),\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stages-DJrOYLZ-.mjs","names":[],"sources":["../src/stages/plan.ts","../src/stages/index.ts"],"sourcesContent":["/**\n * Which stages a workflow run builds and deploys, from the event that started\n * it and the project's `stages`.\n *\n * The rules live here, in tested code, rather than in a workflow's YAML or in\n * the action that runs `gkm stages --github-output`: a project's deploy\n * workflow names no stage, and what it does for each event is decided by the\n * CLI the project installed.\n *\n * | event | build | deploy |\n * | ------------------- | ------------------ | ------------------------------- |\n * | `push` | every deployed | deployed, less the protected |\n * | `release` | none | the protected |\n * | `workflow_dispatch` | none | the one stage named |\n * | anything else | none | none |\n *\n * A push builds every deployed stage, protected ones included, so a release\n * deploys images built from the very commit its tag names; a release and a\n * manual run deploy what a push already built.\n */\n\nimport { GkmError } from '../errors';\nimport { UndeclaredStage } from '../workspace/stages.js';\nimport type { StagesConfig } from '../workspace/types.js';\n\nexport interface StagePlan {\n\t/** The stages whose images this run builds and pushes. */\n\tbuild: string[];\n\t/** The stages this run deploys. */\n\tdeploy: string[];\n}\n\nexport interface PlanInput {\n\t/** The GitHub event name: `push`, `release`, `workflow_dispatch`, … */\n\tevent: string;\n\t/** The stage a `workflow_dispatch` names. */\n\tstage?: string;\n}\n\nexport function planStages(stages: StagesConfig, input: PlanInput): StagePlan {\n\tconst kept = stages.protected ?? [];\n\n\tswitch (input.event) {\n\t\tcase 'push':\n\t\t\treturn {\n\t\t\t\tbuild: [...stages.deployed],\n\t\t\t\tdeploy: stages.deployed.filter((stage) => !kept.includes(stage)),\n\t\t\t};\n\t\tcase 'release':\n\t\t\treturn { build: [], deploy: [...kept] };\n\t\tcase 'workflow_dispatch': {\n\t\t\tconst stage = input.stage?.trim();\n\t\t\tif (!stage) throw new DispatchNamesNoStage(stages.deployed);\n\t\t\tif (!stages.deployed.includes(stage)) {\n\t\t\t\tthrow new UndeclaredStage(stage, stages.deployed);\n\t\t\t}\n\t\t\treturn { build: [], deploy: [stage] };\n\t\t}\n\t\tdefault:\n\t\t\treturn { build: [], deploy: [] };\n\t}\n}\n\n/**\n * A manual run with no stage. Refused rather than read as \"nothing to do\", so\n * a run started with the field left empty fails where it can be seen.\n */\nexport class DispatchNamesNoStage extends GkmError {\n\tconstructor(readonly deployed: readonly string[]) {\n\t\tsuper(\n\t\t\t`A manual run deploys one stage, and none was given. Run the workflow again with stage set to one of: ${deployed.join(', ') || 'nothing yet — gkm.config.ts deploys to no stage'}.`,\n\t\t);\n\t\tthis.name = 'DispatchNamesNoStage';\n\t}\n}\n","/**\n * `gkm stages` — the workspace's stages, as gkm.config.ts declares them.\n *\n * ```bash\n * gkm stages # a table\n * gkm stages --json # {\"local\":\"dev\",\"deployed\":[\"prod\"],\"protected\":[\"prod\"]}\n * gkm stages --github-output # a workflow run's build and deploy matrices\n * ```\n *\n * The config is read by the normal loader (and sandbox), so a workflow never\n * parses TypeScript to learn which stages exist. `--github-output` is what the\n * `geekmidas/toolbox/actions/stages` action runs: it writes the stages, and\n * which of them this run builds and deploys, to `$GITHUB_OUTPUT`.\n */\n\nimport { appendFile } from 'node:fs/promises';\nimport { loadWorkspaceSettings } from '../config.js';\nimport { GkmError } from '../errors';\nimport { s3SecretsLocation } from '../secrets/providers.js';\nimport type { NormalizedWorkspace, StagesConfig } from '../workspace/types.js';\nimport { planStages } from './plan.js';\n\nexport { DispatchNamesNoStage, type PlanInput, planStages } from './plan.js';\n\nexport interface StagesOptions {\n\t/** Print the stages as JSON. */\n\tjson?: boolean;\n\t/** Write the outputs a deploy workflow reads to `$GITHUB_OUTPUT`. */\n\tgithubOutput?: boolean;\n\t/** The event to plan for; defaults to `GITHUB_EVENT_NAME`. */\n\tevent?: string;\n\t/** The stage a `workflow_dispatch` names. */\n\tstage?: string;\n}\n\nexport interface StagesDeps {\n\tcwd?: string;\n\tenv?: NodeJS.ProcessEnv;\n\t/** Where lines go; stdout by default. */\n\tlog?: (line: string) => void;\n}\n\n/** The stages, as `gkm stages --json` prints them. */\nexport interface StagesJson {\n\tlocal: string;\n\tdeployed: string[];\n\tprotected: string[];\n}\n\nexport function stagesJson(stages: StagesConfig): StagesJson {\n\treturn {\n\t\tlocal: stages.local,\n\t\tdeployed: [...stages.deployed],\n\t\tprotected: [...(stages.protected ?? [])],\n\t};\n}\n\n/**\n * The region of the workspace's AWS secrets store — `s3`, `ssm` or\n * `secrets-manager` — or `''` when its deployed stages keep their secrets\n * anywhere else. A job assumes the stage's AWS role only when this is set.\n */\nexport function awsSecretsRegion(workspace: NormalizedWorkspace): string {\n\tconst store = workspace.secrets?.store;\n\tif (typeof store !== 'object' || store === null) return '';\n\tif (store.provider === 's3') {\n\t\treturn s3SecretsLocation(store, workspace.state).region;\n\t}\n\tif (store.provider === 'ssm' || store.provider === 'secrets-manager') {\n\t\treturn store.region;\n\t}\n\treturn '';\n}\n\n/**\n * Every output the stages action sets, each a string `fromJSON()` reads, for\n * one workflow run.\n */\nexport function githubOutputs(\n\tworkspace: NormalizedWorkspace,\n\tinput: { event: string; stage?: string },\n): Record<string, string> {\n\tconst stages = stagesJson(workspace.stages);\n\tconst plan = planStages(workspace.stages, input);\n\n\treturn {\n\t\tlocal: JSON.stringify(stages.local),\n\t\tdeployed: JSON.stringify(stages.deployed),\n\t\tprotected: JSON.stringify(stages.protected),\n\t\tbuild: JSON.stringify(plan.build),\n\t\tdeploy: JSON.stringify(plan.deploy),\n\t\t// A matrix over an empty list is an error on GitHub, so a job over one\n\t\t// is skipped on these instead.\n\t\t'has-build': String(plan.build.length > 0),\n\t\t'has-deploy': String(plan.deploy.length > 0),\n\t\t'aws-region': awsSecretsRegion(workspace),\n\t};\n}\n\n/** `name=value` lines, as `$GITHUB_OUTPUT` takes single-line values. */\nexport function formatGithubOutputs(outputs: Record<string, string>): string {\n\treturn Object.entries(outputs)\n\t\t.map(([name, value]) => `${name}=${value}\\n`)\n\t\t.join('');\n}\n\n/** A message as a workflow command's data: GitHub reads `%`, CR and LF escaped. */\nfunction workflowCommandData(message: string): string {\n\treturn message\n\t\t.replaceAll('%', '%25')\n\t\t.replaceAll('\\r', '%0D')\n\t\t.replaceAll('\\n', '%0A');\n}\n\nfunction table(stages: StagesJson): string {\n\tconst rows: [string, string][] = [\n\t\t['STAGE', 'KIND'],\n\t\t[stages.local, 'local'],\n\t\t...stages.deployed.map((name): [string, string] => [\n\t\t\tname,\n\t\t\tstages.protected.includes(name) ? 'deployed, protected' : 'deployed',\n\t\t]),\n\t];\n\tconst width = Math.max(...rows.map(([name]) => name.length));\n\treturn rows\n\t\t.map(([name, kind]) => `${name.padEnd(width)} ${kind}`)\n\t\t.join('\\n');\n}\n\nexport async function stagesCommand(\n\toptions: StagesOptions,\n\tdeps: StagesDeps = {},\n): Promise<void> {\n\tconst env = deps.env ?? process.env;\n\tconst log = deps.log ?? ((line: string) => console.log(line));\n\n\tif (!options.githubOutput) {\n\t\tconst workspace = await loadWorkspaceSettings(deps.cwd);\n\t\tconst stages = stagesJson(workspace.stages);\n\t\tlog(options.json ? JSON.stringify(stages) : table(stages));\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst file = env.GITHUB_OUTPUT;\n\t\tif (!file) throw new GithubOutputNotSet();\n\n\t\tconst workspace = await loadWorkspaceSettings(deps.cwd);\n\t\tconst event = options.event || env.GITHUB_EVENT_NAME;\n\t\tif (!event) throw new GithubEventNotSet();\n\n\t\tconst outputs = githubOutputs(workspace, {\n\t\t\tevent,\n\t\t\t...(options.stage ? { stage: options.stage } : {}),\n\t\t});\n\t\tawait appendFile(file, formatGithubOutputs(outputs));\n\n\t\tlog(`${event}: build ${outputs.build}, deploy ${outputs.deploy}`);\n\t} catch (error) {\n\t\t// Annotated on the run, where whoever started it looks first.\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tlog(`::error title=gkm stages::${workflowCommandData(message)}`);\n\t\tthrow error;\n\t}\n}\n\n/** `--github-output` outside a GitHub Actions step, which sets the file. */\nexport class GithubOutputNotSet extends GkmError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'gkm stages --github-output writes to the file GITHUB_OUTPUT names, and it is not set. Run it in a GitHub Actions step, or set GITHUB_OUTPUT to a file to write to.',\n\t\t);\n\t\tthis.name = 'GithubOutputNotSet';\n\t}\n}\n\n/** No `--event`, and no `GITHUB_EVENT_NAME` to take it from. */\nexport class GithubEventNotSet extends GkmError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'gkm stages --github-output plans for an event, and none was given. Pass --event (push, release, workflow_dispatch), or run it in a GitHub Actions step, which sets GITHUB_EVENT_NAME.',\n\t\t);\n\t\tthis.name = 'GithubEventNotSet';\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stages-_3RVNG83.cjs","names":["UndeclaredStage","GkmError","s3SecretsLocation","loadWorkspaceSettings","appendFile","GkmError"],"sources":["../src/stages/plan.ts","../src/stages/index.ts"],"sourcesContent":["/**\n * Which stages a workflow run builds and deploys, from the event that started\n * it and the project's `stages`.\n *\n * The rules live here, in tested code, rather than in a workflow's YAML or in\n * the action that runs `gkm stages --github-output`: a project's deploy\n * workflow names no stage, and what it does for each event is decided by the\n * CLI the project installed.\n *\n * | event | build | deploy |\n * | ------------------- | ------------------ | ------------------------------- |\n * | `push` | every deployed | deployed, less the protected |\n * | `release` | none | the protected |\n * | `workflow_dispatch` | none | the one stage named |\n * | anything else | none | none |\n *\n * A push builds every deployed stage, protected ones included, so a release\n * deploys images built from the very commit its tag names; a release and a\n * manual run deploy what a push already built.\n */\n\nimport { GkmError } from '../errors';\nimport { UndeclaredStage } from '../workspace/stages.js';\nimport type { StagesConfig } from '../workspace/types.js';\n\nexport interface StagePlan {\n\t/** The stages whose images this run builds and pushes. */\n\tbuild: string[];\n\t/** The stages this run deploys. */\n\tdeploy: string[];\n}\n\nexport interface PlanInput {\n\t/** The GitHub event name: `push`, `release`, `workflow_dispatch`, … */\n\tevent: string;\n\t/** The stage a `workflow_dispatch` names. */\n\tstage?: string;\n}\n\nexport function planStages(stages: StagesConfig, input: PlanInput): StagePlan {\n\tconst kept = stages.protected ?? [];\n\n\tswitch (input.event) {\n\t\tcase 'push':\n\t\t\treturn {\n\t\t\t\tbuild: [...stages.deployed],\n\t\t\t\tdeploy: stages.deployed.filter((stage) => !kept.includes(stage)),\n\t\t\t};\n\t\tcase 'release':\n\t\t\treturn { build: [], deploy: [...kept] };\n\t\tcase 'workflow_dispatch': {\n\t\t\tconst stage = input.stage?.trim();\n\t\t\tif (!stage) throw new DispatchNamesNoStage(stages.deployed);\n\t\t\tif (!stages.deployed.includes(stage)) {\n\t\t\t\tthrow new UndeclaredStage(stage, stages.deployed);\n\t\t\t}\n\t\t\treturn { build: [], deploy: [stage] };\n\t\t}\n\t\tdefault:\n\t\t\treturn { build: [], deploy: [] };\n\t}\n}\n\n/**\n * A manual run with no stage. Refused rather than read as \"nothing to do\", so\n * a run started with the field left empty fails where it can be seen.\n */\nexport class DispatchNamesNoStage extends GkmError {\n\tconstructor(readonly deployed: readonly string[]) {\n\t\tsuper(\n\t\t\t`A manual run deploys one stage, and none was given. Run the workflow again with stage set to one of: ${deployed.join(', ') || 'nothing yet — gkm.config.ts deploys to no stage'}.`,\n\t\t);\n\t\tthis.name = 'DispatchNamesNoStage';\n\t}\n}\n","/**\n * `gkm stages` — the workspace's stages, as gkm.config.ts declares them.\n *\n * ```bash\n * gkm stages # a table\n * gkm stages --json # {\"local\":\"dev\",\"deployed\":[\"prod\"],\"protected\":[\"prod\"]}\n * gkm stages --github-output # a workflow run's build and deploy matrices\n * ```\n *\n * The config is read by the normal loader (and sandbox), so a workflow never\n * parses TypeScript to learn which stages exist. `--github-output` is what the\n * `geekmidas/toolbox/actions/stages` action runs: it writes the stages, and\n * which of them this run builds and deploys, to `$GITHUB_OUTPUT`.\n */\n\nimport { appendFile } from 'node:fs/promises';\nimport { loadWorkspaceSettings } from '../config.js';\nimport { GkmError } from '../errors';\nimport { s3SecretsLocation } from '../secrets/providers.js';\nimport type { NormalizedWorkspace, StagesConfig } from '../workspace/types.js';\nimport { planStages } from './plan.js';\n\nexport { DispatchNamesNoStage, type PlanInput, planStages } from './plan.js';\n\nexport interface StagesOptions {\n\t/** Print the stages as JSON. */\n\tjson?: boolean;\n\t/** Write the outputs a deploy workflow reads to `$GITHUB_OUTPUT`. */\n\tgithubOutput?: boolean;\n\t/** The event to plan for; defaults to `GITHUB_EVENT_NAME`. */\n\tevent?: string;\n\t/** The stage a `workflow_dispatch` names. */\n\tstage?: string;\n}\n\nexport interface StagesDeps {\n\tcwd?: string;\n\tenv?: NodeJS.ProcessEnv;\n\t/** Where lines go; stdout by default. */\n\tlog?: (line: string) => void;\n}\n\n/** The stages, as `gkm stages --json` prints them. */\nexport interface StagesJson {\n\tlocal: string;\n\tdeployed: string[];\n\tprotected: string[];\n}\n\nexport function stagesJson(stages: StagesConfig): StagesJson {\n\treturn {\n\t\tlocal: stages.local,\n\t\tdeployed: [...stages.deployed],\n\t\tprotected: [...(stages.protected ?? [])],\n\t};\n}\n\n/**\n * The region of the workspace's AWS secrets store — `s3`, `ssm` or\n * `secrets-manager` — or `''` when its deployed stages keep their secrets\n * anywhere else. A job assumes the stage's AWS role only when this is set.\n */\nexport function awsSecretsRegion(workspace: NormalizedWorkspace): string {\n\tconst store = workspace.secrets?.store;\n\tif (typeof store !== 'object' || store === null) return '';\n\tif (store.provider === 's3') {\n\t\treturn s3SecretsLocation(store, workspace.state).region;\n\t}\n\tif (store.provider === 'ssm' || store.provider === 'secrets-manager') {\n\t\treturn store.region;\n\t}\n\treturn '';\n}\n\n/**\n * Every output the stages action sets, each a string `fromJSON()` reads, for\n * one workflow run.\n */\nexport function githubOutputs(\n\tworkspace: NormalizedWorkspace,\n\tinput: { event: string; stage?: string },\n): Record<string, string> {\n\tconst stages = stagesJson(workspace.stages);\n\tconst plan = planStages(workspace.stages, input);\n\n\treturn {\n\t\tlocal: JSON.stringify(stages.local),\n\t\tdeployed: JSON.stringify(stages.deployed),\n\t\tprotected: JSON.stringify(stages.protected),\n\t\tbuild: JSON.stringify(plan.build),\n\t\tdeploy: JSON.stringify(plan.deploy),\n\t\t// A matrix over an empty list is an error on GitHub, so a job over one\n\t\t// is skipped on these instead.\n\t\t'has-build': String(plan.build.length > 0),\n\t\t'has-deploy': String(plan.deploy.length > 0),\n\t\t'aws-region': awsSecretsRegion(workspace),\n\t};\n}\n\n/** `name=value` lines, as `$GITHUB_OUTPUT` takes single-line values. */\nexport function formatGithubOutputs(outputs: Record<string, string>): string {\n\treturn Object.entries(outputs)\n\t\t.map(([name, value]) => `${name}=${value}\\n`)\n\t\t.join('');\n}\n\n/** A message as a workflow command's data: GitHub reads `%`, CR and LF escaped. */\nfunction workflowCommandData(message: string): string {\n\treturn message\n\t\t.replaceAll('%', '%25')\n\t\t.replaceAll('\\r', '%0D')\n\t\t.replaceAll('\\n', '%0A');\n}\n\nfunction table(stages: StagesJson): string {\n\tconst rows: [string, string][] = [\n\t\t['STAGE', 'KIND'],\n\t\t[stages.local, 'local'],\n\t\t...stages.deployed.map((name): [string, string] => [\n\t\t\tname,\n\t\t\tstages.protected.includes(name) ? 'deployed, protected' : 'deployed',\n\t\t]),\n\t];\n\tconst width = Math.max(...rows.map(([name]) => name.length));\n\treturn rows\n\t\t.map(([name, kind]) => `${name.padEnd(width)} ${kind}`)\n\t\t.join('\\n');\n}\n\nexport async function stagesCommand(\n\toptions: StagesOptions,\n\tdeps: StagesDeps = {},\n): Promise<void> {\n\tconst env = deps.env ?? process.env;\n\tconst log = deps.log ?? ((line: string) => console.log(line));\n\n\tif (!options.githubOutput) {\n\t\tconst workspace = await loadWorkspaceSettings(deps.cwd);\n\t\tconst stages = stagesJson(workspace.stages);\n\t\tlog(options.json ? JSON.stringify(stages) : table(stages));\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst file = env.GITHUB_OUTPUT;\n\t\tif (!file) throw new GithubOutputNotSet();\n\n\t\tconst workspace = await loadWorkspaceSettings(deps.cwd);\n\t\tconst event = options.event || env.GITHUB_EVENT_NAME;\n\t\tif (!event) throw new GithubEventNotSet();\n\n\t\tconst outputs = githubOutputs(workspace, {\n\t\t\tevent,\n\t\t\t...(options.stage ? { stage: options.stage } : {}),\n\t\t});\n\t\tawait appendFile(file, formatGithubOutputs(outputs));\n\n\t\tlog(`${event}: build ${outputs.build}, deploy ${outputs.deploy}`);\n\t} catch (error) {\n\t\t// Annotated on the run, where whoever started it looks first.\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tlog(`::error title=gkm stages::${workflowCommandData(message)}`);\n\t\tthrow error;\n\t}\n}\n\n/** `--github-output` outside a GitHub Actions step, which sets the file. */\nexport class GithubOutputNotSet extends GkmError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'gkm stages --github-output writes to the file GITHUB_OUTPUT names, and it is not set. Run it in a GitHub Actions step, or set GITHUB_OUTPUT to a file to write to.',\n\t\t);\n\t\tthis.name = 'GithubOutputNotSet';\n\t}\n}\n\n/** No `--event`, and no `GITHUB_EVENT_NAME` to take it from. */\nexport class GithubEventNotSet extends GkmError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'gkm stages --github-output plans for an event, and none was given. Pass --event (push, release, workflow_dispatch), or run it in a GitHub Actions step, which sets GITHUB_EVENT_NAME.',\n\t\t);\n\t\tthis.name = 'GithubEventNotSet';\n\t}\n}\n"],"mappings":"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"}
|