23 Commits

Author SHA1 Message Date
wmantly 86026e90e7 fix: skip host self-registration when sso_token empty; roll up ldap-client v1.25.0 (v1.40.0) (#166)
CI/CD / build-theta-agent (push) Successful in 42s
CI/CD / docker-push (push) Failing after 18s
ldap-client no longer POSTs an empty Bearer to /api/directory-admin/resources
(the misleading 'Invalid Credentials, login failed' during setup). Gitlink ->
ldap-client 68fcdb5 (v1.25.0).
2026-08-05 01:34:32 -04:00
wmantly 73e1cc807a fix: setup.sh ldap.vars re-run abort + drop app_super_admin; roll up sso v1.26.1 (v1.36.1) (#162)
CI/CD / build-theta-agent (push) Successful in 45s
CI/CD / docker-push (push) Failing after 19s
- setup.sh: ldap.vars generation read CFG_* first-run vars (unset on re-run);
  now reads real values from sso-secrets.js, so LDAP enrollment works on re-runs
- generated ldap_access_groups now references god_admin (app_super_admin gone)
- gitlink: sso-manager-node 8db00f0 (v1.26.1)
2026-08-04 19:33:32 -04:00
wmantly a77aa8d2df feat: seed god_admin + docker plugin, fix ldap-client enrollment, roll up sso v1.26.0 + theta-agent v1.3.0 (v1.36.0) (#161)
CI/CD / build-theta-agent (push) Successful in 44s
CI/CD / docker-push (push) Failing after 16s
- bootstrap: seed god_admin into the admin's groups; seed a docker-local discovery plugin
- setup.sh: generate ldap-client/ldap.vars from the stack config so LDAP enrollment works
- docs: GROUPS.md site-slug convention (verbatim, kind in resource slug)
- gitlinks: sso-manager-node 8a9de94 (v1.26.0), theta-agent 52379c2 (v1.3.0)
2026-08-04 19:10:32 -04:00
wmantly 72606bfc13 feat: seed theta-proxy + theta-jump as managed host resources (v1.35.16)
The bootstrap now creates theta-proxy and theta-jump as managed host-kind
resources in the Directory (matching the OAuth client identities), alongside
the existing stack host and its service entries, so a fresh install shows them
as first-class hosts.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 13:27:28 -04:00
wmantly e6b318e28e release(theta-suite): v1.35.1 - Submodule updates, Directory, Conf layout & Jump host target filter
Lint / Shellcheck setup.sh (push) Failing after 9s
Lint / Syntax check bootstrap.js (push) Successful in 14s
2026-08-03 14:02:13 -04:00
wmantly 3b769cf24a chore: release v1.34.3 (updates submodules, docs, bootstrap)
Lint / Shellcheck setup.sh (push) Failing after 9s
Lint / Syntax check bootstrap.js (push) Successful in 13s
2026-08-02 19:08:14 -04:00
wmantly 9541c47470 fix: resolve jump-host naming bug and setup.sh secrets list bug 2026-08-02 12:56:55 -04:00
wmantly 3287777b9b v1.30.0: rename theta-env -> theta-suite + docs rewrite + sso v1.16.1 (#126)
Rename the project to theta-suite (it is now an integrated suite of four
apps around a shared OpenBao secrets store, not a two-project env).
- theta-env -> theta-suite across the superproject: _config.yml (title +
  baseurl /theta-suite + repo URLs), README, setup.sh (incl. the
  THETA_SUITE_REEXECED self-update sentinel), docker-compose.yml,
  bootstrap.js, lint.yml, config.example/*, docs/robots.txt, all docs,
  this changelog.
- architecture.md rewritten: real 4-service + ldap-client topology, OpenBao
  secrets section, OpenBao-aware config flow; removed "two containers" /
  "three repos" / LDAP-"legacy" framing.
- index.md: integrated-suite framing + secrets/OpenBao + ldap-client.
- standalone.md + README: standalone reframed as advanced opt-in.
- sso-manager-node submodule -> v1.16.1 (401 fix on /conf and /vault).

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-01 18:46:10 -04:00
wmantly 5ef3e3fa8c 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>
2026-08-01 13:48:28 -04:00
wmantly f7df04c2f0 v1.28.0: OpenBao as the central secrets store for the stack
theta-env orchestration:
- setup.sh: idempotent OpenBao policies (sso-broker, sso-admin, proxy,
  jump-host), sso-broker token role (allowed_policies_glob user-*/app-*,
  24h), mint scoped SSO/PROXY/JUMP_VAULT_TOKEN (orphan, .env reuse),
  seed_app_conf seeds secret/{sso-manager,proxy,jump-host}/conf. Bootstrap
  exec passes root VAULT_ADDR/VAULT_TOKEN for seeding. Root token never
  reaches a service container. shellcheck -S warning clean.
- docker-compose.yml: VAULT_ADDR + VAULT_TOKEN env for sso/proxy/jump;
  proxy/jump depends_on openbao service_started.
- bootstrap/bootstrap.js: baoPut() writes generated OAuth creds to
  secret/proxy/conf + secret/jump-host/conf (OpenBao authoritative).
- docs/secrets.md (new): full secrets architecture. README + nav updated.

Submodule bumps:
- sso-manager-node -> v1.16.0 (OpenBao broker + vault UI + remediation)
- proxy -> v1.13.1 (via v1.13.0: OpenBao boot)
- jump-host -> v1.14.1 (via v1.14.0: OpenBao boot)
- ldap-client unchanged

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 12:54:56 -04:00
wmantly ab9d9301f0 Fix: theta-jump OAuth client had no parent in the directory
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>
2026-07-28 12:53:51 -04:00
wmantly f90d319eeb Fix jump-host SSH login: point it at ldaps://, not ldap://
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>
2026-07-27 20:10:58 -04:00
wmantly 67374dc914 feat: provision jump-host web-UI SSO login (OIDC client); bump to v1.1.0
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>
2026-07-23 21:01:36 -04:00
wmantly 1f8f4c70be feat: optional SSH jump host component (theta42/jump-host)
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>
2026-07-23 15:55:40 -04:00
wmantly 3e85e37b63 feat: site name config, host facts, and service ports/repos in the directory seed
- 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>
2026-07-23 03:18:20 -04:00
wmantly faf67d8ffc docs: direct LDAP binds are first-class, not "legacy"
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>
2026-07-23 02:56:17 -04:00
wmantly 4f61eeb1a7 feat: also seed OpenLDAP and OpenResty services in the directory
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>
2026-07-23 02:44:19 -04:00
wmantly 1c96c75118 feat: seed the SSO directory with the stack's own resources at bootstrap
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>
2026-07-23 02:39:41 -04:00
wmantly e46768bb68 chore(release): public-release readiness fixes for 1.1.18
- 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>
2026-07-18 22:11:13 -04:00
wmantly b5f24d40fc Persist Redis + config in bind-mounted ./config/ (no .env); add backup/restore (#8)
Part A — lossless upgrades:
- Persist both bundled Redis stores via AOF+RDB on named volumes (sso-data,
  proxy-data) so OAuth clients, Host records, perms, DNS creds, and auto-ssl
  Let's Encrypt certs survive rebuilds.
- setup.sh: backup_before_rebuild() snapshots ./config/ + LDAP (slapcat) +
  both Redis (BGSAVE + compose cp) to ./backups/<ts>/ before each rebuild,
  keeps last BACKUP_KEEP (default 5). First run is a no-op.
- Restore runbook (README + docs): full / Redis-only / LDAP-only, with the
  AOF-vs-RDB note (delete the AOF before restoring an RDB).

Part B — eliminate .env / proxy.env:
- All config + secrets live in bind-mounted ./config/ (gitignored), read by each
  app's @simpleworkjs/conf from a symlinked secrets.js. Compose passes only
  NODE_ENV + NODE_PORT (no app_* env, which would override secrets.js).
- ./config/sso-secrets.js: app secrets + orchestrator-only stack/bootstrap/
  serviceAccountPass keys (app ignores the ones it doesn't use).
- ./config/proxy-secrets.js: oidc (clientId/clientSecret filled in by the
  bootstrap), ldap (bind creds), auth (admin groups/users).
- setup.sh ensure_config(): generates ./config/ with random secrets on first
  run (then exits for editing); one-time migration from .env/proxy.env
  preserving existing secrets (LDAP admin pass, JWT, OAuth client, service
  pass) so a running deployment keeps its directory + tokens + OAuth client.
- bootstrap/bootstrap.js: reads /config/*.js (not process.env), registers the
  proxy as an OIDC client, and writes the SSO-generated client id+secret back
  into ./config/proxy-secrets.js (sso mounts ./config RW, proxy RO).
- config.example/ holds committed annotated templates for manual reference.
- .gitignore: add config/, backups/, *.rdb, *.ldif.

Bump both gitlinks to the merged submodule tips:
- sso-manager-node -> 6920a9f (PR #34)
- proxy -> 8e78604 (PR #118)

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-12 13:16:17 -04:00
wmantly 5ec951a683 Fixed issues with password 2026-07-11 18:46:20 -04:00
wmantly f7edc5b672 Fixed env template 2026-07-11 18:42:05 -04:00
wmantly 9fb240ff45 theta-env: unified SSO Manager + Proxy stack with one-command setup
Composes theta42/sso-manager-node and theta42/proxy (as git submodules) on a
single Docker network and automates first-run wiring.

- docker-compose.yml: sso-manager (build ./sso-manager-node/Dockerfile.openldap)
  + proxy (build ./proxy/Dockerfile) on theta-net; SSO UI + mgmt port bound to
  localhost, LDAPS published, proxy 80/443/4443 published.
- setup.sh: idempotent one-command bring-up — validates .env, starts SSO, runs
  the bootstrap, writes ./proxy.env, starts the proxy, prints admin login.
- bootstrap/bootstrap.js: runs inside the sso-manager container (self-contained,
  Node built-ins + fetch only) — creates the LDAP service account, first admin
  (+ app_sso_admin/app_sso_oauth_admin membership), registers the proxy as an
  OIDC client via the SSO HTTP API, emits CLIENT_ID/CLIENT_SECRET.
- .env.example: all tunables (LDAP_BASE_DN, LDAP_ADMIN_PASS, JWT_SECRET,
  SSO_HOST, PROXY_HOST, BOOTSTRAP_ADMIN_*, LDAP_SERVICE_PASS, SMTP_*, ports).
- README.md + docs/ (Jekyll site for GitHub Pages): quickstart, architecture,
  standalone usage.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-11 17:04:36 -04:00