v1.29.0: jump host is core + fix fresh-install setup.sh abort (#125)
Two fresh-install fixes and promote the SSH jump host from opt-in to core. setup.sh: fix silent abort after "Minting per-app OpenBao tokens". env_get's grep|cut pipeline returns non-zero under set -euo pipefail when .env exists (created by the root VAULT_TOKEN env_upsert) but an app-token key is absent (the normal first-run state); the unguarded existing assignment from env_get then tripped set -e and killed the script before minting any token. env_get now always returns 0 (|| true). Reproduced + verified under the exact condition. jump host is no longer optional: - docker-compose.yml: drop profiles jump-host from the jump-host service (always started); rename the opt-in test fixture profile jump-host to ldap-test. - setup.sh: SUBMODULES always includes jump-host; build/start/register/summary no longer guarded by JUMP_ENABLED; drop the COMPOSE_PROFILES export. - bootstrap.js: jump provisioning + directory record run unconditionally. - setup.env.example/docs: drop optional/CFG_JUMP_HOST_ENABLED wording. Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,41 @@ orchestration code; see each submodule's own `CHANGELOG.md`
|
|||||||
[sso-manager-node](https://github.com/theta42/sso-manager-node/blob/master/CHANGELOG.md))
|
[sso-manager-node](https://github.com/theta42/sso-manager-node/blob/master/CHANGELOG.md))
|
||||||
for what changed inside the apps it composes.
|
for what changed inside the apps it composes.
|
||||||
|
|
||||||
|
## [v1.29.0] - 2026-08-01
|
||||||
|
|
||||||
|
Two fixes for a fresh `./setup.sh` install, plus the SSH jump host promoted
|
||||||
|
from an opt-in component to a core part of the stack.
|
||||||
|
|
||||||
|
### Fixed (theta-env orchestration)
|
||||||
|
- **`setup.sh`** — fresh installs aborted silently right after `Minting
|
||||||
|
per-app OpenBao tokens`. The `env_get` helper's `grep | cut` pipeline returns
|
||||||
|
non-zero under `set -euo pipefail` when `.env` exists (it's created earlier
|
||||||
|
by the root-`VAULT_TOKEN` `env_upsert`) but a given app-token key is absent —
|
||||||
|
the normal first-run state. The unguarded `existing="$(env_get ...)"` then
|
||||||
|
tripped `set -e` and killed the script before any token was minted. `env_get`
|
||||||
|
now always returns 0 (`|| true`), so "key absent" resolves to empty and the
|
||||||
|
run continues through token minting, the SSO/proxy bring-up, and the jump
|
||||||
|
host. Reproduced + verified the fix under the exact fresh-install condition.
|
||||||
|
- **`setup.sh`** — `JUMP_VAULT_TOKEN` is now always minted (jump host is core;
|
||||||
|
the mint was already unconditional, this just documents it).
|
||||||
|
|
||||||
|
### Changed (theta-env orchestration)
|
||||||
|
- **jump host is no longer optional** — it is built + started on every run,
|
||||||
|
with no `CFG_JUMP_HOST_ENABLED` flag.
|
||||||
|
- `docker-compose.yml`: removed `profiles: ["jump-host"]` from the
|
||||||
|
`jump-host` service so `docker compose up` includes it unconditionally.
|
||||||
|
The test-only `ldap-test-host` downstream fixture keeps an opt-in profile,
|
||||||
|
renamed `jump-host` → `ldap-test` (`docker compose --profile ldap-test up`).
|
||||||
|
- `setup.sh`: `SUBMODULES` always includes `jump-host`; the build/start +
|
||||||
|
host-register + summary lines for the jump host are no longer wrapped in a
|
||||||
|
`JUMP_ENABLED` guard; the `COMPOSE_PROFILES` export is gone.
|
||||||
|
- `bootstrap/bootstrap.js`: jump-host provisioning (mint API token + write
|
||||||
|
`jump-secrets.js` + mirror into OpenBao) and its directory service record
|
||||||
|
now run unconditionally — no `CFG_JUMP_HOST_ENABLED` gate.
|
||||||
|
- `setup.env.example` / `docs/index.md` / `docs/quickstart.md`: dropped the
|
||||||
|
"optional / enable with `CFG_JUMP_HOST_ENABLED=true`" wording; the
|
||||||
|
`CFG_JUMP_HOST` hostname override + `JUMP_SSH_PORT` remain.
|
||||||
|
|
||||||
## [v1.28.0] - 2026-08-01
|
## [v1.28.0] - 2026-08-01
|
||||||
|
|
||||||
OpenBao becomes the central secrets store for the whole stack. Every app now
|
OpenBao becomes the central secrets store for the whole stack. Every app now
|
||||||
|
|||||||
Vendored
+27
-28
@@ -464,9 +464,9 @@ async function seedDirectory(token, clientId, jumpClientId) {
|
|||||||
subType: 'openresty',
|
subType: 'openresty',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Optional SSH jump host service.
|
// SSH jump host service (core component — always registered).
|
||||||
let jumpSvc = null;
|
let jumpSvc = null;
|
||||||
if (/^(1|true|yes)$/i.test(process.env.CFG_JUMP_HOST_ENABLED || '')) {
|
{
|
||||||
const jumpHost = process.env.CFG_JUMP_HOST || (DOMAIN ? `jump.${DOMAIN}` : '');
|
const jumpHost = process.env.CFG_JUMP_HOST || (DOMAIN ? `jump.${DOMAIN}` : '');
|
||||||
jumpSvc = await ensure('service', 'SSH Jump Host', 'jump-host', host.id, {
|
jumpSvc = await ensure('service', 'SSH Jump Host', 'jump-host', host.id, {
|
||||||
address: jumpHost ? `https://${jumpHost}` : '',
|
address: jumpHost ? `https://${jumpHost}` : '',
|
||||||
@@ -523,16 +523,16 @@ function writeProxyCreds(id, secret) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 6. Optional: provision the SSH jump host ────────────────────────────────
|
// ── 6. Provision the SSH jump host ─────────────────────────────────────────
|
||||||
// When CFG_JUMP_HOST_ENABLED=true, the jump host needs: a directory API token
|
// The jump host is a core component (always provisioned). It needs: a directory
|
||||||
// (to resolve which hosts a user may reach), an LDAP bind account that can
|
// API token (to resolve which hosts a user may reach), an LDAP bind account
|
||||||
// WRITE the sshPublicKey attribute (it injects its own key on first use), and
|
// that can WRITE the sshPublicKey attribute (it injects its own key on first
|
||||||
// a config file it reads. We write /config/jump-secrets.js deriving LDAP/site
|
// use), and a config file it reads. We write /config/jump-secrets.js deriving
|
||||||
// from sso-secrets.js + a freshly minted API token. The bundled jump host
|
// LDAP/site from sso-secrets.js + a freshly minted API token. The bundled jump
|
||||||
// binds as cn=admin (already able to write sshPublicKey) — hardened bare-metal
|
// host binds as cn=admin (already able to write sshPublicKey) — hardened
|
||||||
// deployments should use a scoped account + attribute ACL instead (see the
|
// bare-metal deployments should use a scoped account + attribute ACL instead
|
||||||
// jump-host README). Idempotent: skips if the file already has a real token.
|
// (see the jump-host README). Idempotent: skips if the file already has a real
|
||||||
const JUMP_ENABLED = /^(1|true|yes)$/i.test(process.env.CFG_JUMP_HOST_ENABLED || '');
|
// token.
|
||||||
const JUMP_HOST = process.env.CFG_JUMP_HOST || (DOMAIN ? `jump.${DOMAIN}` : '');
|
const JUMP_HOST = process.env.CFG_JUMP_HOST || (DOMAIN ? `jump.${DOMAIN}` : '');
|
||||||
const JUMP_SECRETS = '/config/jump-secrets.js';
|
const JUMP_SECRETS = '/config/jump-secrets.js';
|
||||||
const JUMP_TOKEN_NAME = 'theta-jump-host';
|
const JUMP_TOKEN_NAME = 'theta-jump-host';
|
||||||
@@ -716,23 +716,22 @@ async function provisionJumpHost(token) {
|
|||||||
// generated OAuth creds). Warn-only.
|
// generated OAuth creds). Warn-only.
|
||||||
await baoPut('proxy/conf', freshRequire('/config/proxy-secrets.js'));
|
await baoPut('proxy/conf', freshRequire('/config/proxy-secrets.js'));
|
||||||
|
|
||||||
// Provision the jump host (mint token + write config) when enabled.
|
// Provision the jump host (mint token + write config). Warn-only — never
|
||||||
// Warn-only — never fail the whole bring-up over the optional service.
|
// fail the whole bring-up over it, but it's a core component so always
|
||||||
|
// attempted (no longer gated by CFG_JUMP_HOST_ENABLED).
|
||||||
let jumpClientId = null;
|
let jumpClientId = null;
|
||||||
if (JUMP_ENABLED) {
|
try {
|
||||||
try {
|
jumpClientId = await provisionJumpHost(token);
|
||||||
jumpClientId = await provisionJumpHost(token);
|
out('JUMP_HOST_CONFIGURED', '1');
|
||||||
out('JUMP_HOST_CONFIGURED', '1');
|
// Mirror jump-secrets.js (just written by provisionJumpHost) into
|
||||||
// Mirror jump-secrets.js (just written by provisionJumpHost)
|
// OpenBao so the jump host loads it from there at boot via
|
||||||
// into OpenBao so the jump host loads it from there at boot via
|
// @simpleworkjs/bao-conf. setup.sh's seed may have put a
|
||||||
// @simpleworkjs/bao-conf. setup.sh's seed may have put a
|
// placeholder/stale version here; this replaces it with the
|
||||||
// placeholder/stale version here; this replaces it with the
|
// complete file (LDAP bind, minted API token, OAuth client).
|
||||||
// complete file (LDAP bind, minted API token, OAuth client).
|
// Warn-only.
|
||||||
// Warn-only.
|
await baoPut('jump-host/conf', freshRequire(JUMP_SECRETS));
|
||||||
await baoPut('jump-host/conf', freshRequire(JUMP_SECRETS));
|
} catch (e) {
|
||||||
} catch (e) {
|
log(`WARNING: jump host provisioning failed (${e.message || e}) — continuing`);
|
||||||
log(`WARNING: jump host provisioning failed (${e.message || e}) — continuing`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seed the directory (site/host/services + OAuth client link). Never
|
// Seed the directory (site/host/services + OAuth client link). Never
|
||||||
|
|||||||
+7
-9
@@ -170,12 +170,10 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
start_period: 30s
|
start_period: 30s
|
||||||
|
|
||||||
# Optional SSH jump host. Only started when the `jump-host` compose profile
|
# SSH jump host — a core component, always built + started alongside the
|
||||||
# is active — setup.sh exports COMPOSE_PROFILES=jump-host when
|
# SSO and proxy. Authenticates users against the SSO's OpenLDAP, resolves
|
||||||
# CFG_JUMP_HOST_ENABLED=true. Authenticates users against the SSO's OpenLDAP,
|
# reachable hosts from the directory API, and bridges SSH through.
|
||||||
# resolves reachable hosts from the directory API, and bridges SSH through.
|
|
||||||
jump-host:
|
jump-host:
|
||||||
profiles: ["jump-host"]
|
|
||||||
build:
|
build:
|
||||||
context: ./jump-host
|
context: ./jump-host
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
@@ -220,11 +218,11 @@ services:
|
|||||||
# a container with a manually-dropped public key in authorized_keys never
|
# a container with a manually-dropped public key in authorized_keys never
|
||||||
# exercises the LDAP-key-serving path a real production host does. Built
|
# exercises the LDAP-key-serving path a real production host does. Built
|
||||||
# from the theta42/ldap-client submodule -- see ./config/ldap-test-host.vars
|
# from the theta42/ldap-client submodule -- see ./config/ldap-test-host.vars
|
||||||
# for setup notes. Same jump-host profile, so
|
# for setup notes. Opt-in test fixture: bring it up explicitly with
|
||||||
# `docker compose --profile jump-host up` brings up jump-host and a host it
|
# `docker compose --profile ldap-test up` (jump-host itself now starts
|
||||||
# can actually reach together.
|
# unconditionally, so this only adds a downstream host for it to reach).
|
||||||
ldap-test-host:
|
ldap-test-host:
|
||||||
profiles: ["jump-host"]
|
profiles: ["ldap-test"]
|
||||||
build:
|
build:
|
||||||
context: ./ldap-client
|
context: ./ldap-client
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
|||||||
+5
-5
@@ -15,7 +15,7 @@ LDAP directory) and [Proxy](https://theta42.github.io/proxy/) (an
|
|||||||
OIDC-protected reverse proxy that can also look users up directly in LDAP) —
|
OIDC-protected reverse proxy that can also look users up directly in LDAP) —
|
||||||
and automates the fiddly part: registering the proxy as an OIDC client of the
|
and automates the fiddly part: registering the proxy as an OIDC client of the
|
||||||
SSO and pointing it at the right LDAP directory, with hostnames and secrets
|
SSO and pointing it at the right LDAP directory, with hostnames and secrets
|
||||||
generated from one `setup.env`. An optional third component, the
|
generated from one `setup.env`. A third component, the
|
||||||
[Jump Host](https://theta42.github.io/jump-host/), adds directory-driven SSH
|
[Jump Host](https://theta42.github.io/jump-host/), adds directory-driven SSH
|
||||||
access to your machines through one public entry point.
|
access to your machines through one public entry point.
|
||||||
|
|
||||||
@@ -46,9 +46,9 @@ snapshots state before every rebuild.
|
|||||||
- **Proxy** — add the hosts you want to protect with OIDC login.
|
- **Proxy** — add the hosts you want to protect with OIDC login.
|
||||||
- **LDAPS** for direct binds — Linux hosts (PAM/SSSD, sudo, SSH keys) and
|
- **LDAPS** for direct binds — Linux hosts (PAM/SSSD, sudo, SSH keys) and
|
||||||
LDAP-native apps authenticate against the same directory.
|
LDAP-native apps authenticate against the same directory.
|
||||||
- **SSH Jump Host** *(optional)* — `ssh uid_-_host@jump.<domain>` (WinSCP-friendly)
|
- **SSH Jump Host** — `ssh uid_-_host@jump.<domain>` (WinSCP-friendly)
|
||||||
or an interactive picker; access is driven by directory group membership, with
|
or an interactive picker; access is driven by directory group membership, with
|
||||||
a web UI for audit + metrics. Enable with `CFG_JUMP_HOST_ENABLED=true`.
|
a web UI for audit + metrics.
|
||||||
- **Self-service API tokens** in both apps' UIs, for scripting/CI without a
|
- **Self-service API tokens** in both apps' UIs, for scripting/CI without a
|
||||||
browser session.
|
browser session.
|
||||||
- **Multi-Site Support (Geo-Location Scaling)** — built-in support for N-Way Multi-Master LDAP replication across physical locations.
|
- **Multi-Site Support (Geo-Location Scaling)** — built-in support for N-Way Multi-Master LDAP replication across physical locations.
|
||||||
@@ -74,5 +74,5 @@ architecture, and running each project standalone, see the
|
|||||||
provider + LDAP directory this stack runs.
|
provider + LDAP directory this stack runs.
|
||||||
- **[Proxy](https://theta42.github.io/proxy/)** — the reverse proxy this
|
- **[Proxy](https://theta42.github.io/proxy/)** — the reverse proxy this
|
||||||
stack runs in front of it.
|
stack runs in front of it.
|
||||||
- **[Jump Host](https://theta42.github.io/jump-host/)** — the optional SSH jump
|
- **[Jump Host](https://theta42.github.io/jump-host/)** — the SSH jump
|
||||||
host this stack can bring up (`CFG_JUMP_HOST_ENABLED=true`).
|
host this stack brings up.
|
||||||
|
|||||||
+1
-2
@@ -60,8 +60,7 @@ setups `CFG_DOMAIN` is the only value you set:
|
|||||||
| `CFG_ADMIN_UID` | `admin` | optional, defaults to `admin` |
|
| `CFG_ADMIN_UID` | `admin` | optional, defaults to `admin` |
|
||||||
| `CFG_ADMIN_EMAIL` | `admin@<proxyHost>` | optional |
|
| `CFG_ADMIN_EMAIL` | `admin@<proxyHost>` | optional |
|
||||||
| `CFG_BASE_DN` | `dc=lab,dc=local` | advanced: override the derived LDAP base DN |
|
| `CFG_BASE_DN` | `dc=lab,dc=local` | advanced: override the derived LDAP base DN |
|
||||||
| `CFG_JUMP_HOST_ENABLED` | `true` | optional: bring up the [SSH jump host](https://theta42.github.io/jump-host/) (default off) |
|
| `CFG_JUMP_HOST` | `jump.lab.local` | optional, defaults to `jump.<domain>` (the [SSH jump host](https://theta42.github.io/jump-host/) is installed + started by default) |
|
||||||
| `CFG_JUMP_HOST` | `jump.lab.local` | optional, defaults to `jump.<domain>` |
|
|
||||||
| `JUMP_SSH_PORT` | `2222` | optional: host port for the jump host's SSH (never 22 by default) |
|
| `JUMP_SSH_PORT` | `2222` | optional: host port for the jump host's SSH (never 22 by default) |
|
||||||
|
|
||||||
`setup.env` is used **only on the first run** to generate `./config/`; after
|
`setup.env` is used **only on the first run** to generate `./config/`; after
|
||||||
|
|||||||
+8
-9
@@ -33,15 +33,14 @@ CFG_DOMAIN=example.com
|
|||||||
#CFG_SSO_HOST=sso.example.com
|
#CFG_SSO_HOST=sso.example.com
|
||||||
#CFG_PROXY_HOST=proxy.example.com
|
#CFG_PROXY_HOST=proxy.example.com
|
||||||
|
|
||||||
# ── Optional SSH jump host ───────────────────────────────────────────────────
|
# ── SSH jump host (always installed) ─────────────────────────────────────────
|
||||||
# Enable the theta42/jump-host component: a public SSH jump host that
|
# The theta42/jump-host component is installed and started by default — a
|
||||||
# authenticates users against the directory and bridges them to downstream
|
# public SSH jump host that authenticates users against the directory and
|
||||||
# hosts (ssh uid_-_target@jump, or an interactive picker). Off by default.
|
# bridges them to downstream hosts (ssh uid_-_target@jump, or an interactive
|
||||||
# When true, setup.sh clones/builds the jump-host submodule, the bootstrap
|
# picker). setup.sh clones/builds the jump-host submodule, the bootstrap mints
|
||||||
# mints its directory API token + writes ./config/jump-secrets.js, and it's
|
# its directory API token + writes ./config/jump-secrets.js, and it's registered
|
||||||
# registered in the proxy + directory. See jump-host's README for the LDAP
|
# in the proxy + directory. See jump-host's README for the LDAP write-ACL note
|
||||||
# write-ACL note (the bundled deployment binds as cn=admin).
|
# (the bundled deployment binds as cn=admin).
|
||||||
#CFG_JUMP_HOST_ENABLED=false
|
|
||||||
#CFG_JUMP_HOST=jump.example.com # defaults to jump.<domain>
|
#CFG_JUMP_HOST=jump.example.com # defaults to jump.<domain>
|
||||||
#JUMP_SSH_PORT=2222 # host port mapped to the jump host's SSH (never 22 by default)
|
#JUMP_SSH_PORT=2222 # host port mapped to the jump host's SSH (never 22 by default)
|
||||||
|
|
||||||
|
|||||||
@@ -169,18 +169,14 @@ then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Optional jump host: resolve the enable flag early ─────────────────────────
|
# ── Jump host hostname (always installed) ─────────────────────────────────────
|
||||||
# CFG_JUMP_HOST_ENABLED gates the optional SSH jump host (a third submodule).
|
# The SSH jump host is a core component — always built + started (no longer
|
||||||
# Read it from the environment or ./setup.env now (before the submodule loop
|
# gated by CFG_JUMP_HOST_ENABLED). Read its optional hostname override from
|
||||||
# and the compose steps) so every run knows whether to build/start it. The
|
# ./setup.env now (before the submodule loop and the compose steps) so the
|
||||||
# authoritative CFG_* for secrets are still resolved in ensure_config; this is
|
# later steps can use it. The authoritative CFG_* for secrets are still
|
||||||
# only the on/off switch + its hostname.
|
# resolved in ensure_config; this is only the hostname override.
|
||||||
[[ -f ./setup.env ]] && parse_kv_file ./setup.env
|
[[ -f ./setup.env ]] && parse_kv_file ./setup.env
|
||||||
JUMP_ENABLED=0
|
export CFG_JUMP_HOST
|
||||||
case "${CFG_JUMP_HOST_ENABLED:-}" in 1|true|TRUE|yes|YES) JUMP_ENABLED=1 ;; esac
|
|
||||||
export CFG_JUMP_HOST_ENABLED CFG_JUMP_HOST
|
|
||||||
# When enabled, activate the compose profile so `up`/`ps` include the service.
|
|
||||||
if [[ "$JUMP_ENABLED" == "1" ]]; then export COMPOSE_PROFILES="jump-host"; fi
|
|
||||||
|
|
||||||
# ── Optional outbound HTTP(S) proxy for docker build + the running containers ─
|
# ── Optional outbound HTTP(S) proxy for docker build + the running containers ─
|
||||||
# CFG_HTTP_PROXY / CFG_HTTPS_PROXY / CFG_NO_PROXY (from ./setup.env or the
|
# CFG_HTTP_PROXY / CFG_HTTPS_PROXY / CFG_NO_PROXY (from ./setup.env or the
|
||||||
@@ -213,9 +209,8 @@ if [[ "${SKIP_SUBMODULE_UPDATE:-0}" != "1" ]]; then
|
|||||||
die "git submodule update --init failed. Run manually: git submodule update --init --recursive"
|
die "git submodule update --init failed. Run manually: git submodule update --init --recursive"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# jump-host is optional: only track/build it when enabled.
|
# jump-host is a core component — always tracked + built.
|
||||||
SUBMODULES=(sso-manager-node proxy)
|
SUBMODULES=(sso-manager-node proxy jump-host)
|
||||||
[[ "$JUMP_ENABLED" == "1" ]] && SUBMODULES+=(jump-host)
|
|
||||||
info "Updating submodules to their latest release tag (${SUBMODULES[*]})..."
|
info "Updating submodules to their latest release tag (${SUBMODULES[*]})..."
|
||||||
for sm in "${SUBMODULES[@]}"; do
|
for sm in "${SUBMODULES[@]}"; do
|
||||||
[[ -d "$sm" ]] || continue
|
[[ -d "$sm" ]] || continue
|
||||||
@@ -730,7 +725,15 @@ ensure_policy() {
|
|||||||
env_get() {
|
env_get() {
|
||||||
local key="$1" file=./.env
|
local key="$1" file=./.env
|
||||||
[[ -f "$file" ]] || return 0
|
[[ -f "$file" ]] || return 0
|
||||||
grep -m1 "^${key}=" "$file" 2>/dev/null | cut -d= -f2-
|
# `|| true` is load-bearing: under `set -euo pipefail`, a no-match `grep`
|
||||||
|
# exits 1 and (pipefail) makes the whole pipeline return 1. Callers do
|
||||||
|
# `existing="$(env_get ...)"` as a bare assignment — a non-zero return there
|
||||||
|
# trips `set -e` and silently kills the whole script (this is exactly what
|
||||||
|
# aborted a fresh install right after "Minting per-app OpenBao tokens": the
|
||||||
|
# root VAULT_TOKEN env_upsert had already created .env, but the app-token
|
||||||
|
# keys were absent, so the first env_get returned 1). "Key absent" is the
|
||||||
|
# normal path here, so always return 0 with empty output.
|
||||||
|
grep -m1 "^${key}=" "$file" 2>/dev/null | cut -d= -f2- || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Mint an orphan, renewable token for `policy` and persist it to .env as `key`,
|
# Mint an orphan, renewable token for `policy` and persist it to .env as `key`,
|
||||||
@@ -904,7 +907,6 @@ BOOTSTRAP_OUT=$("${COMPOSE[@]}" exec -T \
|
|||||||
-e STACK_HOST_MAC="$STACK_HOST_MAC" \
|
-e STACK_HOST_MAC="$STACK_HOST_MAC" \
|
||||||
-e STACK_HOST_OS="$STACK_HOST_OS" \
|
-e STACK_HOST_OS="$STACK_HOST_OS" \
|
||||||
-e STACK_HOST_KERNEL="$STACK_HOST_KERNEL" \
|
-e STACK_HOST_KERNEL="$STACK_HOST_KERNEL" \
|
||||||
-e CFG_JUMP_HOST_ENABLED="${CFG_JUMP_HOST_ENABLED:-}" \
|
|
||||||
-e CFG_JUMP_HOST="${CFG_JUMP_HOST:-}" \
|
-e CFG_JUMP_HOST="${CFG_JUMP_HOST:-}" \
|
||||||
-e VAULT_ADDR=http://openbao:8200 \
|
-e VAULT_ADDR=http://openbao:8200 \
|
||||||
-e VAULT_TOKEN="$VAULT_TOKEN" \
|
-e VAULT_TOKEN="$VAULT_TOKEN" \
|
||||||
@@ -984,35 +986,34 @@ NODEEOF
|
|||||||
) || die "Registering hosts with the proxy failed:\n${HOSTS_OUT}"
|
) || die "Registering hosts with the proxy failed:\n${HOSTS_OUT}"
|
||||||
echo "$HOSTS_OUT" | sed 's/^/[setup] /'
|
echo "$HOSTS_OUT" | sed 's/^/[setup] /'
|
||||||
|
|
||||||
# ── 7b. Optional: build + start the SSH jump host ─────────────────────────────
|
# ── 7b. Build + start the SSH jump host ──────────────────────────────────────
|
||||||
# Enabled by CFG_JUMP_HOST_ENABLED. The bootstrap (step 5) already wrote
|
# The jump host is a core component (no longer optional). The bootstrap (step 5)
|
||||||
# ./config/jump-secrets.js (minted API token + LDAP admin bind). Build/start the
|
# already wrote ./config/jump-secrets.js (minted API token + LDAP admin bind) and
|
||||||
# service (compose profile 'jump-host' is active), wait for its web /health, and
|
# mirrored it into OpenBao. Build/start the service, wait for its web /health,
|
||||||
# register its web UI hostname as a proxy Host so https://<JUMP_HOST> routes.
|
# and register its web UI hostname as a proxy Host so https://<JUMP_HOST> routes.
|
||||||
if [[ "$JUMP_ENABLED" == "1" ]]; then
|
JUMP_HOST="${CFG_JUMP_HOST:-jump.${SSO_HOST#sso.}}"
|
||||||
JUMP_HOST="${CFG_JUMP_HOST:-jump.${SSO_HOST#sso.}}"
|
JUMP_GIT_COMMIT="$(git -C jump-host rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
||||||
JUMP_GIT_COMMIT="$(git -C jump-host rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
export JUMP_GIT_COMMIT
|
||||||
export JUMP_GIT_COMMIT
|
env_upsert JUMP_GIT_COMMIT "$JUMP_GIT_COMMIT"
|
||||||
env_upsert JUMP_GIT_COMMIT "$JUMP_GIT_COMMIT"
|
# Seed jump-host/conf from the file bootstrap just wrote (it mints the API
|
||||||
# Seed jump-host/conf from the file bootstrap just wrote (it mints the API
|
# token + OAuth client into /config/jump-secrets.js at step 5). bootstrap also
|
||||||
# token + OAuth client into /config/jump-secrets.js at step 5). bootstrap
|
# writes this to OpenBao directly, so this is a fallback for when bootstrap's
|
||||||
# also writes this to OpenBao directly, so this is a fallback for when
|
# jump provisioning warned-but-continued.
|
||||||
# bootstrap's jump provisioning warned-but-continued.
|
seed_app_conf jump-host/conf /config/jump-secrets.js
|
||||||
seed_app_conf jump-host/conf /config/jump-secrets.js
|
info "Building + starting jump-host..."
|
||||||
info "Building + starting jump-host (optional; enabled via CFG_JUMP_HOST_ENABLED)..."
|
"${COMPOSE[@]}" up -d --build jump-host
|
||||||
"${COMPOSE[@]}" up -d --build jump-host
|
|
||||||
|
|
||||||
info "Waiting for jump-host to be healthy..."
|
info "Waiting for jump-host to be healthy..."
|
||||||
for i in $(seq 1 60); do
|
for i in $(seq 1 60); do
|
||||||
if docker exec jump-host node -e "require('http').get('http://localhost:3002/health',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))" >/dev/null 2>&1; then
|
if docker exec jump-host node -e "require('http').get('http://localhost:3002/health',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))" >/dev/null 2>&1; then
|
||||||
info "jump-host is healthy."; break
|
info "jump-host is healthy."; break
|
||||||
fi
|
fi
|
||||||
if (( i == 60 )); then warn "jump-host did not become healthy in 120s. Check: ${COMPOSE[*]} logs jump-host"; break; fi
|
if (( i == 60 )); then warn "jump-host did not become healthy in 120s. Check: ${COMPOSE[*]} logs jump-host"; break; fi
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
info "Registering ${JUMP_HOST} (jump-host web UI) with the proxy..."
|
info "Registering ${JUMP_HOST} (jump-host web UI) with the proxy..."
|
||||||
JUMP_HOSTS_OUT=$("${COMPOSE[@]}" exec -T proxy node <<NODEEOF || true
|
JUMP_HOSTS_OUT=$("${COMPOSE[@]}" exec -T proxy node <<NODEEOF || true
|
||||||
const {Host} = require('/app/models').models;
|
const {Host} = require('/app/models').models;
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -1027,8 +1028,7 @@ const {Host} = require('/app/models').models;
|
|||||||
})();
|
})();
|
||||||
NODEEOF
|
NODEEOF
|
||||||
)
|
)
|
||||||
echo "$JUMP_HOSTS_OUT" | sed 's/^/[setup] /'
|
echo "$JUMP_HOSTS_OUT" | sed 's/^/[setup] /'
|
||||||
fi
|
|
||||||
|
|
||||||
# ── 8. Summary ───────────────────────────────────────────────────────────────
|
# ── 8. Summary ───────────────────────────────────────────────────────────────
|
||||||
echo
|
echo
|
||||||
@@ -1038,11 +1038,9 @@ echo " SSO Manager UI: https://${SSO_HOST} (fronted by the proxy under TLS
|
|||||||
echo " first-run fallback: http://127.0.0.1:${SSO_PORT:-3001}"
|
echo " first-run fallback: http://127.0.0.1:${SSO_PORT:-3001}"
|
||||||
echo " Proxy mgmt UI: https://${PROXY_HOST}"
|
echo " Proxy mgmt UI: https://${PROXY_HOST}"
|
||||||
echo " first-run fallback: http://127.0.0.1:${MGMT_PORT:-3000}"
|
echo " first-run fallback: http://127.0.0.1:${MGMT_PORT:-3000}"
|
||||||
if [[ "$JUMP_ENABLED" == "1" ]]; then
|
|
||||||
echo " Jump host (SSH): ssh -p ${JUMP_SSH_PORT:-2222} <uid>@${JUMP_HOST:-jump.${SSO_HOST#sso.}} (TUI picker)"
|
echo " Jump host (SSH): ssh -p ${JUMP_SSH_PORT:-2222} <uid>@${JUMP_HOST:-jump.${SSO_HOST#sso.}} (TUI picker)"
|
||||||
echo " ssh -p ${JUMP_SSH_PORT:-2222} <uid>_-_<host>@${JUMP_HOST:-jump.${SSO_HOST#sso.}}"
|
echo " ssh -p ${JUMP_SSH_PORT:-2222} <uid>_-_<host>@${JUMP_HOST:-jump.${SSO_HOST#sso.}}"
|
||||||
echo " Jump host (web): https://${JUMP_HOST:-jump.${SSO_HOST#sso.}} (audit + metrics)"
|
echo " Jump host (web): https://${JUMP_HOST:-jump.${SSO_HOST#sso.}} (audit + metrics)"
|
||||||
fi
|
|
||||||
echo
|
echo
|
||||||
echo " First admin login credentials are in ./config/sso-secrets.js:"
|
echo " First admin login credentials are in ./config/sso-secrets.js:"
|
||||||
echo " user: ${ADMIN_UID}"
|
echo " user: ${ADMIN_UID}"
|
||||||
|
|||||||
Reference in New Issue
Block a user