sso-manager's Directory data had no persistent volume at all: @simpleworkjs/orm
falls back to './config/inventory.sqlite' (relative to /app) when conf.orm isn't
set, which lives in the container's ephemeral layer -- every container recreate
silently wiped every site/host/service/oauth-client resource and their
relationships. setup.sh's generated sso-secrets.js now sets
orm.storage=/data/inventory.sqlite, co-located with the already-persisted
sso-data volume (same one Redis uses). Verified live: migrated the running
stack's existing Directory data to /data, force-recreated the container, and
confirmed all 8 resources survived.
Also bumps sso-manager-node to v1.8.0 (resource modal standardization: tabs,
footer with created/updated-by metadata, linkable /directory/:slug URLs, a new
Children tab, and site-slug-prefixed auto-created LDAP groups).
Companion to theta42/jump-host#18: that PR turns on Redis persistence
(AOF + RDB) at /data, but without a volume mount, persistence-within-the-
container is pointless -- docker rm -f (which every real rebuild uses,
per this repo's own documented docker-compose-v1 recreate quirk) throws
the container's whole filesystem away regardless. Adds jump-redis-data:/data,
matching proxy-data's existing role for the same package (lua-resty-auto-ssl
also relies on Redis persistence, via its own volume).
Verified: minted a jump-host API token, force-recreated the container,
confirmed the token still worked afterward.
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
seedDirectory() only ever linked ONE OAuth client -- whatever id was
passed in, which was always the proxy's (resolvedClientId). jump-host's
own OAuth client (minted by provisionJumpHost) was never passed through,
so it was created but never got a ResourceEdge to the "SSH Jump Host"
service resource -- it just showed up in the Directory with no parent.
provisionJumpHost now returns the jump client's id (looking it up even
on the "already configured" early-return path, so existing deployments
self-heal on the next setup.sh run instead of needing this fixed only for
fresh installs), and seedDirectory takes it as a third argument, linking
it under the jump-host service the same way the proxy's client is linked
under the proxy service.
Also fixed live on the affected deployment via the directory-admin API
(created the missing edge directly) rather than waiting for a rebuild.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Regression guard for bootstrap.js's jump-secrets.js template: its ldap
block must use ldaps:// (implicit TLS, :636), not ldap:// (:389), as long
as tlsOptions is set alongside it. ldapts treats a non-empty tlsOptions as
"use implicit TLS" regardless of URL scheme, and jump-host's LDAP client
always sets tlsOptions -- so this exact combination broke every SSH login
to jump-host (any account, any password) before being root-caused
against a real deployment.
Static (parses bootstrap.js as text), not a require()+exec of it --
bootstrap.js is a self-running provisioning script with real side effects
(LDAP writes, live API calls), not a library, so there's nothing safe to
import and call in CI.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
jump-secrets.js's ldap.url was 'ldap://sso-manager:389' with tlsOptions
set. ldapts treats a non-empty tlsOptions as "use implicit TLS" regardless
of URL scheme, so every LDAP connection from jump-host opened a raw TLS
handshake against sso-manager's plaintext-LDAP port — slapd dropped the
connection before any LDAP message parsed (visible in slapd.log as
"connection lost" right after ACCEPT, no BIND ever logged). Every SSH
login failed with a generic "Permission denied" for any account, any
password — indistinguishable from a wrong credential.
Root-caused by building a local theta-env stack, restarting jump-host
with edited config, and calling userLdap.getUser/checkPassword directly
inside the container: got "Client network socket disconnected before
secure TLS connection was established" instead of a vague auth failure.
Switching to ldaps://sso-manager:636 (already exposed by the same
container, already what tlsOptions was meant for) fixes it — verified
getUser/checkPassword succeed and a real SSH login authenticates.
Companion defensive fix: simpleworkjs/ldap#1 (rejects this exact
ldap://+tlsOptions combination going forward, for proxy/sso too).
Existing deployments must edit their own ./config/jump-secrets.js (this
template only affects fresh bootstraps) — see the PR description.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Fixes the sshPublicKey ObjectClassViolationError (both in sso-manager-node's
API and jump-host's key-injection path) and the blank OAuth parent
dropdown; adds a host list to jump-host's dashboard.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
sso-dashboard.png and proxy-hosts.png still showed the pre-unification
nav; refresh with the current shared shell and add a jump-host dashboard
screenshot alongside them.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
sso-manager-node v1.5.0, proxy v1.4.0, jump-host v1.3.0 — the three apps
now share one byte-identical front-end shell (top.ejs, bottom.ejs,
app-base.js) with per-app values in each repo's utils/ui.js, one nav-gating
model driven by /api/user/me, and jQuery 4 / EJS 3 across the board.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
sso-manager-node -> v1.4.0, proxy -> v1.3.0, jump-host -> v1.2.0. The three
apps now share @simpleworkjs/{oidc-client,directory-schema,ldap,app-stack}
(1.0.0, published under the simpleworkjs org) instead of byte-identical forks,
and the SSO directory discovery API no longer leaks client_secret_hash and
returns the {results} envelope (fixing jump-host bridging). No setup.sh change:
the new deps resolve from npm in each app's image build (npm ci stays clean).
CHANGELOG embeds the full per-app release notes. UI chrome unification is
deferred to a browser-verified session (see UI_UNIFICATION_HANDOFF.md).
Co-Authored-By: Claude <noreply@anthropic.com>
The jump host's web UI now authenticates via OIDC + a local admin
(jump-host v1.1.0). Wire that in the bundle:
- bootstrap mints a dedicated 'theta-jump' OAuth client (redirect
https://<JUMP_HOST>/api/auth/oidc/callback) and writes a full oidc
block + generated local admin password into config/jump-secrets.js,
mirroring the proxy's OIDC provisioning
- an existing pre-OIDC jump-secrets.js (API token but no OIDC client) is
regenerated so upgraders get SSO login
- bump jump-host submodule v1.0.x -> v1.1.0
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes the OAuth-client-API client_id serialization bug that broke this
stack's bootstrap (rotate -> 500 -> 'bootstrap failed'). See CHANGELOG.
Co-Authored-By: Claude Opus 4.8 <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>
Directory documentation surfaced in-app; direct-LDAP-binds reframing.
theta-env's own directory seeding (site/host/services + facts, ports,
repos) ships in this release — see CHANGELOG.md.
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>
Linux hosts authenticate against the directory (PAM/SSSD, sudoRole,
sshPublicKey) — reframe the OpenLDAP seed comment and changelog entry
accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The stack runs two more real services than the last seed captured:
- OpenLDAP: independently consumed via direct LDAPS binds (the SSO's
/integrations page advertises it). Seeded with the ldaps:// endpoint,
honoring ldap.ldapsHost when the operator set one.
- OpenResty: the proxy container's data plane (80/443) that every
hostname in the stack actually flows through — distinct from the
'proxy' entry, which is the node management UI. Seeded with a
wildcard https://*.<domain> address (same wildcard convention the
proxy's Host records use).
Both use metadata.subType so the directory UI badges them as
service (openldap) / service (openresty). Same idempotency: existing
slugs are operator-owned and untouched. Re-verified against a live app:
pass 1 creates all six resources + oauth edge, pass 2 changes nothing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Directory page started empty even though setup.sh knows exactly what
it deployed. The bootstrap now seeds (via /api/directory-admin, as the
logged-in admin): a site from the configured domain, a "Stack host", the
SSO Manager + Proxy services with their public URLs in metadata, and
links the proxy's auto-registered OAuth client under its service.
Idempotent: resources whose slug already exists are operator-owned and
never touched. A seed failure only warns — never fails a bring-up (e.g.
against an older sso-manager image without /api/directory-admin).
Verified against a live app (sso-manager-node test stack): first pass
creates site/host/2 services + oauth edge; second pass changes nothing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the ORM port (OTP/impersonation 500s, OAuth authorize 400s),
adds the OAuth client management API and dockerized test suite. See
CHANGELOG.md for the embedded submodule changelogs (v1.2.1 + v1.3.0).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The release commits for proxy v1.2.1 and sso-manager-node v1.1.18
landed as merge commits when their PRs were merged, not the commits
originally pinned here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both bumps are bug-fix releases: proxy fixes a bootstrap admin lockout
bug, sso-manager-node fixes a crash on the new Sites & Replication
page. See CHANGELOG.md for the embedded submodule changelogs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Pins the proxy submodule to v1.1.17 (wildcard sibling-parent fix: an
existing single-label subdomain host can now be attached to a wildcard
cert added afterward). Embeds proxy's v1.1.17 changelog in the theta-env
release notes.
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>
- After the setup summary stopped printing generated passwords,
ADMIN_PASS and PROXY_LOCAL_ADMIN_PASS were assigned but never used,
causing shellcheck SC2034 warnings in CI. Drop them from the summary.
Co-Authored-By: Claude <noreply@anthropic.com>
- Add XSS/PII-logging security notes for proxy and sso-manager-node v1.1.16.
- Fix comparison links to point to v1.1.18/v1.1.16.
Co-Authored-By: Claude <noreply@anthropic.com>
- CHANGELOG.md now embeds full app-level release notes for submodule bumps.
- .env.example uses explicit CHANGE-ME placeholders instead of realistic-looking defaults.
- config.example comments describe the actual CONF_SECRETS mechanism.
- setup.sh summary no longer prints generated passwords to stdout.
- bootstrap.js fails hard instead of falling back to weak default passwords.
Note: submodule pins will be updated to v1.1.16 after the app PRs merge.
Co-Authored-By: Claude <noreply@anthropic.com>
- proxy -> v1.1.15
- sso-manager-node -> v1.1.15
Both apps' bare-metal install.sh now installs to /opt/theta42/<app>
and seeds /etc/<app>/secrets.js on first run.
Also: setup.sh now prints the version each submodule is updating
from/to (or "already up to date") when re-run, instead of only
printing on an actual change with commit hashes -- and the
self-update step shows theta-env's own tag, not just a hash, when
one resolves.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- proxy -> v1.1.14
- sso-manager-node -> v1.1.14
Both bump @simpleworkjs/conf to 1.2.0 and jq-repeat to 2.2.0, and use
the new CONF_SECRETS env var instead of symlinking the mounted secrets
file into /app/conf/secrets.js. Updated theta-env's own docs/setup.sh/
docker-compose.yml comments to match -- no change to the config file
format or bind mounts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CHANGELOG.md had fallen behind theta-env's own tags -- entries
stopped at 1.1.3 despite v1.1.4/v1.1.5/v1.1.6 already being tagged
and released (each with real notes on GitHub, just never copied back
into this file). Backfilled 1.1.4-1.1.6 from the actual release
notes, then added 1.1.7 for this bump + the docs site redesign.
- proxy -> v1.1.7
- sso-manager-node -> v1.1.6