CFG_HTTP_PROXY / CFG_HTTPS_PROXY / CFG_NO_PROXY in setup.env (all
optional, unset by default) get wired into every service's docker build
(npm/apt) and running container (SMTP, ACME/Let's Encrypt, DNS provider
calls, the jump-host directory API client) as HTTP_PROXY/HTTPS_PROXY/
NO_PROXY. Useful for isolated/offline/corporate-network test hosts that
only reach the internet through an upstream proxy — distinct from the
theta42 "proxy" app itself. CFG_NO_PROXY defaults to the stack's own
internal service names so container-to-container traffic never routes
through the proxy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds jump-host as a third, opt-in submodule, wired behind
CFG_JUMP_HOST_ENABLED (default off — existing installs unaffected):
- .gitmodules + jump-host submodule pinned to v1.0.0
- setup.sh: resolves the enable flag early, adds jump-host to the
submodule tag-update loop and activates the `jump-host` compose
profile when enabled; builds/starts the service after the proxy,
waits for its /health, and registers its web UI as a proxy Host;
passes CFG_JUMP_HOST_ENABLED/CFG_JUMP_HOST to the bootstrap
- docker-compose.yml: jump-host service with profiles:["jump-host"],
depends_on sso-manager healthy, ports 2222 (SSH) + 3002 (web),
./config:ro + jump-data volume
- bootstrap.js: when enabled, mints a directory API token and writes
./config/jump-secrets.js (binds as cn=admin so it can write the
sshPublicKey attribute for key injection), and seeds a directory
service entry for the jump host. Warn-only, idempotent.
- setup.env.example: CFG_JUMP_HOST_ENABLED / CFG_JUMP_HOST / JUMP_SSH_PORT
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CFG_SITE_NAME in setup.env (below CFG_DOMAIN, default "local") names
the directory site; slug site_<name> matches ldap-client's parentSlug
convention so joined Linux hosts land under the same site. Wired
through sso-secrets.js stack.siteName.
- setup.sh collects host facts ON THE HOST (hostname, IP, default-route
MAC, OS pretty-name, kernel — same collection as ldap-client/index.sh)
and passes them into the bootstrap exec env; the stack host is now
registered as host_<hostname> with that metadata (subType linux).
- Services carry their internal port and git repo in metadata
(sso-manager 3001, proxy 3000, openldap 389/ext 636, openresty 443),
using the metadata keys the directory UI natively displays.
- ensure() now adopts resources from the earlier seed layout (alt slugs
'stack-host' / domain-slug site) and back-fills missing seed metadata
via a metadata-only PUT — operator-set values are never overwritten.
Verified against a live app: old-layout resources are adopted and
back-filled (no duplicates), fresh seed creates the full graph, and a
second pass changes nothing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pass optional CFG_LDAPS_HOST from setup.env through setup.sh into the
generated ./config/sso-secrets.js as ldap.ldapsHost. This lets operators
advertise an internal-only LDAPS hostname (e.g. ldap.internal.example.com
or sso-manager) on the SSO /integrations page instead of the public
OAuth issuer, avoiding a public 636 port forward.
- setup.env.example: add CFG_LDAPS_HOST
- setup.sh: read/forward CFG_LDAPS_HOST into sso-secrets.js
- config.example/sso-secrets.js.example: document ldapsHost/ldapsPort
- .env.example: add LDAPS_HOST for legacy .env migrations
- docker-compose.yml: comment warning against public 636 forwarding
- README.md: explain CFG_LDAPS_HOST recommendation
- CHANGELOG.md + bump version to 1.1.19
Co-authored-by: Claude <noreply@anthropic.com>
Two related fixes found while testing the Docker build:
1. Print the proxy's local anti-lockout admin (proxyadmin2) password
in the summary. Previously this account was always created with
username == password == "proxyadmin2" (a hardcoded proxy default —
see theta42/proxy#133), and setup.sh had no way to know or surface
whatever password ended up in use. Now generates a random
CFG_PROXY_ADMIN_PASS the same way it already does for the SSO
admin, writes it into proxy-secrets.js's auth.localAdminPass (read
by the proxy once, on first creation of that account), and prints
it in the final summary. read_config_kv() reads it back from
proxy-secrets.js so this works correctly on re-runs too (config
already exists -> ensure_config's early-return path never sets
CFG_PROXY_ADMIN_PASS in that run's shell, same reasoning as the
existing SSO_HOST/PROXY_HOST/ADMIN_PASS readback).
2. Pass GIT_COMMIT build-args so the proxy/sso-manager images bake in
their real commit hash instead of "unknown". Both submodules' .git
is a pointer file, not a real repo, so the images can never resolve
their own commit from inside the Docker build context no matter
what (see theta42/proxy#133 and theta42/sso-manager-node#43) --
only the host, where the submodule resolves correctly, can compute
it. setup.sh does that with `git -C <submodule> rev-parse --short
HEAD` right before each build and exports it for docker-compose.yml
to pick up.
Verified end to end against a real ./setup.sh run (not just docker
build in isolation):
- Local admin password printed on first run, logs in successfully;
the DEFAULT ("proxyadmin2"/"proxyadmin2") correctly does NOT.
- Re-running prints the SAME password (confirms the readback path
works on re-runs, not just first-run).
- `docker exec proxy cat /app/.build_commit` and the equivalent for
sso-manager both match `git -C <submodule> rev-parse --short HEAD`
on the host — footer now shows the real hash instead of "unknown".
Entering the base DN directly (CFG_BASE_DN=dc=foo,dc=bar) is fragile —
a missing comma between labels silently produces a malformed domain
(e.g. "theta42dc=duckdns.org" instead of "theta42.duckdns.org") with
no validation to catch it. Flip the direction: operators now set
CFG_DOMAIN to a plain domain (any number of labels — a DuckDNS domain
like foo.duckdns.org works the same as a normal one), and setup.sh
derives the base DN from it via the new dn_from_domain().
CFG_BASE_DN is still supported as an explicit override (e.g. to
namespace under an OU-style prefix) and is how migrated .env/proxy.env
deployments keep working, since domain_from_dn() still reads the
domain back out of an existing DN either way.
The first-run flow generated ./config/*.js with example.com/dc=example,dc=com
defaults and then exit 0'd, telling the operator to hand-edit. The domain/base
DN was repeated across ~9 fields in each secrets file; on the deploy host only
the stack block was updated (to dc=718it,dc=biz) while ldap.* DNs stayed at
dc=example,dc=com, so slapd's root DN didn't match the app's bindDN and
bootstrap failed with 401 Invalid Credentials.
Enter the domain once: a new setup.env (gitignored; setup.env.example is the
committed template) holds the essential, non-repeating first-run info — the
domain as an LDAP base DN (CFG_BASE_DN). setup.sh reads it ONLY on first run
(when ./config/*.js don't exist), derives hostnames (sso.<domain> /
proxy.<domain>) and all LDAP DNs from it, generates both secrets files with
the real domain filled in everywhere + random secrets, and proceeds to build
in the same run (no edit-and-re-run step). After first run the secrets files
are operator-owned and setup.env is ignored — the apps and secrets files are
unchanged.
- setup.sh ensure_config: source setup.env -> bind CFG_* to empty (set -u
safe) -> unchanged .env/proxy.env legacy migration -> derive from base DN
(no example.com defaults; die with a helpful msg if CFG_BASE_DN blank) ->
generate + proceed. Header comments updated.
- setup.env.example: committed template; secrets stay out (generated into
./config/*.js).
- .gitignore: ignore setup.env (per-deployment).
- README.md + docs/quickstart.md: Quickstart now cp setup.env.example ->
set CFG_BASE_DN -> ./setup.sh; domain-asked-once note in "Before you begin".
- Bump sso-manager-node gitlink to 11fb2c0 (docs PR #37: base-DN-is-the-one-
domain-value note in sso-manager README/DEPLOYMENT/secrets.js.example).
Co-authored-by: Claude <noreply@anthropic.com>