Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c8e5be0c5 | |||
| 4b613c7ca1 | |||
| a118f7ad4e | |||
| 87c8c0fc02 | |||
| 43cdf1dbbb | |||
| 9a737dd178 | |||
| fea1237c46 | |||
| 0b55535aa9 | |||
| 2baf8acd64 | |||
| 3943ed02c5 | |||
| 7f43eee36e | |||
| 19ea7e012a | |||
| 94b357e915 | |||
| f5d8cdd09d | |||
| 96b3aec5eb | |||
| 5aff5349a8 | |||
| 3b0f8f1f9a | |||
| 28016376ad | |||
| daa48dd154 | |||
| a226ff9d00 | |||
| 65a4c1d839 | |||
| 005c66d3f4 | |||
| e46768bb68 | |||
| 8def1f6340 | |||
| 49cd134fb3 | |||
| 30146e9588 | |||
| 481602ae60 | |||
| 26b853e1d6 | |||
| 04a9c557e8 | |||
| d1136a98a6 | |||
| 33f603dcc0 | |||
| f3a5194104 | |||
| d21dfefc37 | |||
| 0861b6a075 | |||
| fee1baaaf9 | |||
| 5f5df7d2bf | |||
| 03838e46d9 | |||
| a8373fa413 | |||
| 40e4c9f9ef | |||
| 3bece9beeb | |||
| f5312fa528 | |||
| 765664aa51 | |||
| b4055d3579 | |||
| 5a9767e9e8 | |||
| 8bc6e9c0e0 | |||
| dbae26f095 | |||
| 502aba3da6 | |||
| c44527984d | |||
| f2ede4c018 | |||
| c4fb8b8a7c | |||
| 56ba01ad18 | |||
| 9c10b3b1d1 | |||
| 7a65841d2d | |||
| 7fd2b828ab | |||
| c7b90b7e7c | |||
| a6653cfb96 | |||
| 9cf2d70226 | |||
| e5f3e1714f | |||
| 4fe1c41b82 | |||
| c60e745665 |
+13
-4
@@ -14,7 +14,8 @@
|
||||
LDAP_BASE_DN=dc=example,dc=com
|
||||
# DNS domain (dc=foo,dc=bar -> foo.bar). Leave blank to derive from LDAP_BASE_DN.
|
||||
LDAP_DOMAIN=
|
||||
LDAP_ADMIN_PASS=change-me-ldap-admin-password
|
||||
# LDAP admin password. MUST be changed. Leave blank and setup.sh will generate one.
|
||||
LDAP_ADMIN_PASS=CHANGE-ME
|
||||
ORG_NAME="My Org"
|
||||
|
||||
# ── Public hostnames (REQUIRED) ───────────────────────────────────────────────
|
||||
@@ -30,13 +31,15 @@ PROXY_HOST=proxy.example.com
|
||||
# app_sso_oauth_admin, and logs in as them to register the proxy OAuth client.
|
||||
# Re-running setup.sh resets this password to BOOTSTRAP_ADMIN_PASS.
|
||||
BOOTSTRAP_ADMIN_UID=admin
|
||||
BOOTSTRAP_ADMIN_PASS=change-me-admin-password
|
||||
# First admin password. MUST be changed. Leave blank and setup.sh will generate one.
|
||||
BOOTSTRAP_ADMIN_PASS=CHANGE-ME
|
||||
BOOTSTRAP_ADMIN_EMAIL=admin@example.com
|
||||
|
||||
# ── Proxy LDAP service account (created by the bootstrap) ────────────────────
|
||||
# The proxy binds to LDAP as cn=ldapclient,ou=people,<base> with this password.
|
||||
# Re-running setup.sh resets it to LDAP_SERVICE_PASS.
|
||||
LDAP_SERVICE_PASS=change-me-ldap-service-password
|
||||
# LDAP service-account password. MUST be changed. Leave blank and setup.sh will generate one.
|
||||
LDAP_SERVICE_PASS=CHANGE-ME
|
||||
|
||||
# ── OAuth JWT secret (REQUIRED — persist it) ────────────────────────────────
|
||||
# Signs the SSO's access/refresh tokens. Generate with: openssl rand -hex 32
|
||||
@@ -73,4 +76,10 @@ MGMT_BIND=0.0.0.0
|
||||
# Defaults to LDAP_DOMAIN. Set to the hostname the proxy connects via
|
||||
# (sso-manager inside the docker net uses the service name, which is in the
|
||||
# cert's SAN, so the default is usually fine).
|
||||
LDAP_CERT_CN=
|
||||
LDAP_CERT_CN=
|
||||
|
||||
# ── Optional: LDAPS hostname shown on the SSO /integrations page ────────────────
|
||||
# Leave blank to derive from the public SSO host (SSO_HOST). Set an internal-only
|
||||
# name like 'ldap.internal.example.com' or 'sso-manager' so direct-LDAP clients
|
||||
# don't need a public 636 port forward. See docs/ldap.md for network layouts.
|
||||
LDAPS_HOST=
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Lint
|
||||
|
||||
# theta-env has no app code of its own to unit-test (it orchestrates the
|
||||
# proxy/sso-manager-node submodules) -- this checks the one thing that can
|
||||
# actually break silently: setup.sh and bootstrap.js.
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
push:
|
||||
branches-ignore:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
shellcheck:
|
||||
name: Shellcheck setup.sh
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Syntax check
|
||||
run: bash -n setup.sh
|
||||
|
||||
- name: Shellcheck
|
||||
run: shellcheck -S warning setup.sh
|
||||
|
||||
bootstrap-syntax:
|
||||
name: Syntax check bootstrap.js
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22.x
|
||||
|
||||
- name: Syntax check
|
||||
run: node --check bootstrap/bootstrap.js
|
||||
+4
-1
@@ -20,4 +20,7 @@ setup.env
|
||||
*.ldif
|
||||
|
||||
# Docker Compose runtime artifacts
|
||||
*.log
|
||||
*.log
|
||||
|
||||
# Jekyll build output (docs/ site) — generated, not committed.
|
||||
docs/_site
|
||||
+385
@@ -0,0 +1,385 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project are documented here. Format loosely
|
||||
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions
|
||||
correspond to git tags (`vX.Y.Z`). Entries here cover theta-env's own
|
||||
orchestration code; see each submodule's own `CHANGELOG.md`
|
||||
([proxy](https://github.com/theta42/proxy/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.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.3.3] - 2026-07-23
|
||||
|
||||
### Bumped
|
||||
- sso-manager-node -> [v1.3.0](https://github.com/theta42/sso-manager-node/releases/tag/v1.3.0) (from v1.1.18; includes the intermediate v1.2.1 release)
|
||||
|
||||
sso-manager-node 1.3.0:
|
||||
|
||||
### Added
|
||||
- **OAuth client management API** at `/api/oauth/client` (group `app_sso_oauth_admin`): list, create, update, delete, and rotate-secret for OAuth clients, backed by the Resource model. Accepts form-style string inputs (newline-separated `redirect_uris`/`allowed_groups`, space-separated `scopes`).
|
||||
- **Dockerized test suite**: `docker-compose -f docker-compose.test.yml up --build` spins up OpenLDAP + Redis + a test-runner that seeds the test user and runs the full jest suite (174 tests) against them. `tests/globalSetup.js` honors `REDIS_URL`.
|
||||
|
||||
### Fixed
|
||||
- Completed the model-redis → `@simpleworkjs/orm` port that shipped half-finished in 1.2.1:
|
||||
- `OtpToken.issue`/`verify` called nonexistent `find()`/`listDetail()` — every OTP login 500'd.
|
||||
- Impersonation create/revoke called nonexistent `ImpersonationToken.listDetail()` — both endpoints 500'd.
|
||||
- `OAuthClient` read `is_valid` from the Resource model, which has no such column — every client evaluated as disabled and **all `/oauth/authorize` requests were rejected with 400**. Client validity now lives in `metadata` (absent = valid).
|
||||
- `OAuthClient.add` didn't set the required-unique `Resource.slug`; clients now get a slug derived from the client name.
|
||||
- `GET /api/token/:name/:token` returned `{results: null}` with 200 for unknown tokens (orm `get()` returns null instead of throwing); now 404s.
|
||||
- `User.login` returns a clean 401 instead of crashing when neither `uid` nor `username` is supplied.
|
||||
- Depend on published `@simpleworkjs/orm` ^0.2.8 and `model-redis` ^1.6.0 instead of a local `file:` link that broke `npm ci` in docker builds.
|
||||
|
||||
### Changed
|
||||
- Removed the Mobile Phone field from the user create/edit form.
|
||||
|
||||
sso-manager-node 1.2.1:
|
||||
|
||||
### Added
|
||||
- **Actionable Metrics**: New real-time metrics tracking for failed logins, top IPs, and service usage per user.
|
||||
- **LDAP Monitor**: Background service to parse OpenLDAP binds over port 389 and track metrics for legacy apps.
|
||||
- **UI Updates**: Executive dashboard now displays actionable metrics cards instead of raw logs. User profiles show individual service usage stats to admins.
|
||||
- **Directory Management**: Integrated site/host/service abstractions into directory UI and allowed associating OAuth apps directly to services.
|
||||
|
||||
## [1.3.2] - 2026-07-21
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.2.2](https://github.com/theta42/proxy/releases/tag/v1.2.2)
|
||||
|
||||
proxy:
|
||||
|
||||
### Fixed
|
||||
- Multi-target load balancing (added in 1.2.0) crashed every request to a load-balanced host: `ops/nginx_conf/targetinfo.lua` required a nonexistent `resty.balancer.round_robin` module. The `lua-resty-balancer` rock actually installed provides `resty.roundrobin` instead, with a different constructor API. Fixed `targetinfo.lua` to use the real module — verified end-to-end that requests now round-robin across targets with no Lua errors.
|
||||
|
||||
## [1.3.1] - 2026-07-21
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.2.1](https://github.com/theta42/proxy/releases/tag/v1.2.1)
|
||||
- sso-manager-node -> [v1.1.18](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.18)
|
||||
|
||||
proxy:
|
||||
|
||||
### Fixed
|
||||
- The bootstrap anti-lockout admin account was always created as `proxyadmin2` regardless of `conf.auth.adminUsers`, while `migrations/permission_bootstrap.js` grants the global-admin permission to `conf.auth.adminUsers[0]`. If an operator customized `adminUsers` away from the default, the bootstrapped account and the permissioned account were two different (non-matching) usernames, so the anti-lockout account ended up with no admin access. `models/user_redis.js` now derives the bootstrap username from `conf.auth.adminUsers[0]` (falling back to `proxyadmin2`), matching `permission_bootstrap.js`.
|
||||
- Corrected a `secrets.js.example` comment that claimed the bootstrap admin's password "defaults to the username itself" — it actually generates a random password printed to the container log on first boot.
|
||||
|
||||
### Changed
|
||||
- Refreshed all README screenshots (hosts, per-host SSO auth, per-host basic auth) against the current UI, and added a new load-balancing screenshot for the multi-target feature.
|
||||
|
||||
sso-manager-node:
|
||||
|
||||
### Added
|
||||
- N-Way Multi-Master LDAP replication: `LDAP_SERVER_ID` + `LDAP_REPLICATION_HOSTS` configure `syncrepl` peers in the bundled OpenLDAP, and a new `/sites` page (nav: **Sites**) shows each configured peer's LDAP URL and live reachability.
|
||||
- A `location` property on users, editable from the profile and user-edit forms.
|
||||
|
||||
### Fixed
|
||||
- `/sites` (added above) 500'd on every load: `views/sites.ejs` included nonexistent partials `header`/`footer` instead of this app's actual `top`/`bottom`. Fixed to match every other view.
|
||||
|
||||
### Changed
|
||||
- Refreshed all README screenshots (dashboard, users, groups, OAuth apps) against the current UI, and added a new Sites & Replication screenshot.
|
||||
|
||||
### theta-env own changes
|
||||
- Refreshed `docs/images/sso-dashboard.png` and `docs/images/proxy-hosts.png` to match the submodules' updated screenshots.
|
||||
|
||||
## [1.1.20] - 2026-07-20
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.17](https://github.com/theta42/proxy/releases/tag/v1.1.17)
|
||||
|
||||
proxy:
|
||||
|
||||
### Fixed
|
||||
- An existing single-label subdomain host (e.g. `sso.nl.wgnode.com`) could not be attached to a wildcard cert added later (e.g. `*.nl.wgnode.com`): `Host.lookUpWildcardParent()` only checked the wildcard-as-child position (the wildcard's own base domain) and missed the far more common wildcard-as-sibling case, so the edit form's "Parent Wildcard" option stayed permanently greyed out. It now checks both positions, and a regression test covers the sibling case.
|
||||
|
||||
## [1.1.19] - 2026-07-18
|
||||
|
||||
### Bumped
|
||||
- sso-manager-node -> [v1.1.17](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.17)
|
||||
|
||||
sso-manager-node:
|
||||
|
||||
### Added
|
||||
- `conf.ldap.ldapsHost` and `conf.ldap.ldapsPort` config options for advertising a separate, internal-only LDAPS hostname on the `/integrations` page. Falls back to the public OAuth issuer host when unset.
|
||||
- Contextual help panel on `/integrations` → LDAP explaining why LDAPS needs a hostname, why port 636 should not be forwarded publicly, and the recommended internal-DNS / Docker-internal alternatives.
|
||||
- Tests for the `/integrations` route's LDAPS URL derivation and `ldapsHost` override.
|
||||
|
||||
### Changed
|
||||
- `nodejs/package.json` / `package-lock.json` version bumped to `1.1.17`.
|
||||
- `routes/index.js` now derives the displayed LDAPS URL from `conf.ldap.ldapsHost`/`ldapsPort` with fallback to the OAuth issuer host.
|
||||
- `docs/configuration.md`, `docs/ldap.md`, `DEPLOYMENT.md`, and `secrets.js.example` document the new `ldapsHost`/`ldapsPort` options and recommended network layouts.
|
||||
|
||||
### theta-env own changes
|
||||
- `setup.env.example` adds optional `CFG_LDAPS_HOST` for the internal LDAPS hostname.
|
||||
- `setup.sh` passes `CFG_LDAPS_HOST` into the generated `./config/sso-secrets.js` as `ldap.ldapsHost`.
|
||||
- `config.example/sso-secrets.js.example` documents `ldap.ldapsHost` / `ldap.ldapsPort`.
|
||||
- `.env.example` adds `LDAPS_HOST` for legacy `.env` migrations.
|
||||
- `docker-compose.yml` comments warn against forwarding 636 to the public internet.
|
||||
- `README.md` explains the `CFG_LDAPS_HOST` recommendation in the port-forwarding section.
|
||||
|
||||
## [1.1.18] - 2026-07-18
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.16](https://github.com/theta42/proxy/releases/tag/v1.1.16)
|
||||
- sso-manager-node -> [v1.1.16](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.16)
|
||||
|
||||
proxy:
|
||||
|
||||
### Changed
|
||||
- Public-release packaging: removed `"private": true` from `nodejs/package.json`, corrected the repository URL to `https://github.com/theta42/proxy.git`, and fixed the MIT `LICENSE` copyright line.
|
||||
- Genericized committed config defaults in `conf/base.js` and `conf/development.js` (`example.com` / `localhost` instead of theta42 infrastructure).
|
||||
- The bootstrap `proxyadmin2` account now gets a random, one-time password when `auth.localAdminPass` is unset, instead of the well-known default.
|
||||
|
||||
### Security
|
||||
- Sanitized rendered docs HTML with `xss` in `routes/docs.js`.
|
||||
- The Unix socket JSON-RPC socket is now created with mode `660` instead of world-writable `777`.
|
||||
|
||||
### Fixed
|
||||
- The global error handler no longer leaks `err.keys`, stack traces, or internal details in JSON responses.
|
||||
- `DEPLOYMENT.md` and `docs/docker.md` now correctly describe the `CONF_SECRETS` env-var mechanism.
|
||||
|
||||
sso-manager-node:
|
||||
|
||||
### Security
|
||||
- Hardened LDAP filter and DN construction against injection in `models/group_ldap.js` and `models/user_ldap.js`.
|
||||
- Replaced `Math.random()`-based token/UUID/OTP generation with `crypto.randomUUID()` / `crypto.randomInt()` in `models/token.js`, `models/oauth_code.js`, and `models/oauth_client.js`.
|
||||
- Refused startup when `oauth.jwtSecret` is missing or placeholder.
|
||||
- Sanitized rendered docs/Terms-of-Service HTML with `xss` to block malicious markdown output.
|
||||
- Removed full-object `console.log` of new-user data and reduced login error logging to `name`/`message` only.
|
||||
|
||||
### Changed
|
||||
- Public-release packaging: removed `"private": true` from `nodejs/package.json` and bumped version to `1.1.16`.
|
||||
|
||||
### Fixed
|
||||
- `models/email.js`: fixed from-address template rendering bug.
|
||||
|
||||
### theta-env own changes
|
||||
- `CHANGELOG.md` now embeds the full app-level release notes for each submodule bump, not just links.
|
||||
- `.env.example` no longer ships realistic-looking default passwords; values are clearly placeholders.
|
||||
- `config.example/*.js.example` comments now describe the actual `CONF_SECRETS` env-var loading mechanism.
|
||||
- `setup.sh` summary no longer prints generated passwords to stdout; it points to `./config/*.js`.
|
||||
- `bootstrap/bootstrap.js` fails hard instead of falling back to weak default passwords when config is missing.
|
||||
|
||||
## [1.1.17] - 2026-07-18
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.15](https://github.com/theta42/proxy/releases/tag/v1.1.15)
|
||||
- sso-manager-node -> [v1.1.15](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.15)
|
||||
|
||||
Both apps' bare-metal `install.sh` now installs to `/opt/theta42/<app>` and seeds `/etc/<app>/secrets.js` on first run, matching a `wget -O - .../install.sh | sudo bash` one-line install for both (previously proxy-only); re-running it prints the version it's updating from/to. sso-manager-node's installer was rewritten from a flag-driven, copy-based script into the same idempotent git-clone pattern proxy already used, and now bootstraps OpenLDAP itself on first run instead of requiring the repo to already be checked out locally. None of this affects the Docker/unified-stack deployment this repo orchestrates — bare-metal-only.
|
||||
|
||||
## [1.1.16] - 2026-07-18
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.14](https://github.com/theta42/proxy/releases/tag/v1.1.14)
|
||||
- sso-manager-node -> [v1.1.14](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.14)
|
||||
|
||||
Both: bumped `@simpleworkjs/conf` to 1.2.0 and `jq-repeat` to 2.2.0.
|
||||
|
||||
### Changed
|
||||
- `./config/sso-secrets.js` and `./config/proxy-secrets.js` are now loaded via each app's `CONF_SECRETS` env var (set by the entrypoint) instead of being symlinked into `/app/conf/secrets.js` — neither container needs write access to its own `conf/` directory anymore. No change to the config file format or bind mounts; existing `./config/` directories keep working as-is.
|
||||
|
||||
## [1.1.15] - 2026-07-17
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.13](https://github.com/theta42/proxy/releases/tag/v1.1.13)
|
||||
- sso-manager-node -> [v1.1.13](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.13)
|
||||
|
||||
proxy:
|
||||
|
||||
### Fixed
|
||||
- The host edit form's "Parent Wildcard" option stayed greyed out even when a valid wildcard actually existed for that host, so an already-created host could never be switched onto one from the edit modal (only brand-new hosts, via the field's `keyup` handler, ever saw it become available). The underlying `/host/lookup/:item` check also had the same self-match issue as the recently-fixed backend bug: it resolved an already-existing host to its own record instead of a sibling wildcard. Added a dedicated `/host/wildcard-parent/:item` endpoint that checks both directions, and the edit form now actually runs the check when it opens.
|
||||
- Fixed an nginx startup warning: `the "listen ... http2" directive is deprecated, use the "http2" directive instead`. Migrated to the standalone `http2 on;` directive (nginx 1.25.1+).
|
||||
|
||||
### Added
|
||||
- Four new plain-language docs aimed at less technical readers, replacing the system-design-level Architecture/Installation docs as the target of most card help links: **Hosts & HTTPS**, **DNS Providers**, **Users, Groups & Permissions**, and **API Tokens**. Each links onward to the deeper technical reference for readers who want it; the technical docs link back the other way too. The personal-access-token card (previously missed entirely) now has a help link.
|
||||
|
||||
### Fixed
|
||||
- The in-app docs viewer rendered every `docs/*.md` page with a garbled heading and a stray horizontal rule at the top — Jekyll front matter (meant only for the GitHub Pages build) was never stripped before being handed to the markdown renderer. Also fixed: cross-doc links never resolved in-app, since this viewer serves docs at `/docs/<slug>` with no `.html` suffix — they're now rewritten to the correct in-app URL (by registered slug, falling back to the doc's real filename), the same way image paths already were.
|
||||
|
||||
sso-manager-node:
|
||||
|
||||
### Added
|
||||
- Three new plain-language docs aimed at less technical readers, replacing the schema-level LDAP/OAuth/API docs as the target of most card help links: **Accounts, Groups & Managers**, **Connecting Apps (SSO)**, and **API Tokens**. Each links onward to the deeper technical reference for readers who want it; the technical docs link back the other way too. The personal-access-token card (previously missed) now links to its own doc.
|
||||
|
||||
### Fixed
|
||||
- The in-app docs viewer rendered every `docs/*.md` page with a garbled heading and a stray horizontal rule at the top — Jekyll front matter (meant only for the GitHub Pages build) was never stripped before being handed to the markdown renderer. Also fixed: cross-doc links (`ldap.html`, `index.html`, etc.) never resolved in-app, since this viewer serves docs at `/docs/<slug>` with no `.html` suffix — they're now rewritten to the correct in-app URL, the same way image paths already were.
|
||||
- The new concept docs' cross-links (`concepts-accounts.html` etc.) are the correct, working URL on the Jekyll/GitHub Pages build (where the page's URL is its filename stem) but didn't resolve in the in-app docs viewer, which serves docs at a separate short slug (`/docs/accounts`). The in-app renderer now also resolves a doc's real filename as a fallback, so one link written in a doc works on both targets.
|
||||
|
||||
## [1.1.14] - 2026-07-17
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.11](https://github.com/theta42/proxy/releases/tag/v1.1.11)
|
||||
- sso-manager-node -> [v1.1.11](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.11)
|
||||
|
||||
Both: moved the help (❓) link out of the global header and onto each relevant card individually, so it deep-links straight to the doc that actually covers that card instead of one generic per-page guess.
|
||||
|
||||
## [1.1.13] - 2026-07-17
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.10](https://github.com/theta42/proxy/releases/tag/v1.1.10)
|
||||
- sso-manager-node -> [v1.1.10](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.10)
|
||||
|
||||
Both: added a help icon (❓) in the top-right header that deep-links to the doc most relevant to the current page, and made the in-app docs viewer (`/docs`) searchable (a simple line-substring search over the local doc set, no new dependency, still works with no internet access).
|
||||
|
||||
## [1.1.12] - 2026-07-17
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.9](https://github.com/theta42/proxy/releases/tag/v1.1.9)
|
||||
|
||||
proxy:
|
||||
|
||||
### Added
|
||||
- The host list now shows who created each host, and when.
|
||||
- Plain (non-wildcard) hosts can now be renamed after creation — the hostname field is no longer permanently locked. Wildcard hosts, wildcard children, and auto-created subdomain cache entries stay locked, since other records reference them by name.
|
||||
- More inline help text on the host create/edit form (Target SSL, wildcard matching behavior).
|
||||
|
||||
### Fixed
|
||||
- The host create/edit modal's tabs could overflow awkwardly on narrow (mobile) screens — they now scroll horizontally instead.
|
||||
- Fixed a bug in the vendored `model-redis` library's record-rename path: renaming a record's primary key while another `always`-type field (e.g. `updated_on`) is defined earlier in the schema left a stray, incomplete hash behind under the old key, making that name permanently unavailable for reuse. Worked around in `Host.prototype.update()`.
|
||||
|
||||
## [1.1.11] - 2026-07-17
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.8](https://github.com/theta42/proxy/releases/tag/v1.1.8)
|
||||
|
||||
proxy:
|
||||
|
||||
### Fixed
|
||||
- **Couldn't attach an existing host to a parent wildcard.** The host edit form's "Parent Wildcard" option submitted correctly, but `Host.prototype.update()` had no `challengeType` handling at all (only `Host.create()` did) — selecting it and saving silently did nothing. Added the same wildcard-parent lookup to `update()`.
|
||||
- **Couldn't register a wildcard's own base domain as a host.** A wildcard cert's `altNames` already cover both the base domain and `*.base domain`, but the lookup tree stores the wildcard one level below its base domain, and a lookup for the bare base domain landed on that empty parent node and found nothing — even though the already-issued cert covers it. `buildLookUpObj()` now also stamps the parent node so this resolves correctly, without re-issuing or duplicating the cert.
|
||||
|
||||
## [1.1.10] - 2026-07-17
|
||||
|
||||
### Bumped
|
||||
- sso-manager-node -> [v1.1.9](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.9)
|
||||
|
||||
sso-manager-node:
|
||||
|
||||
### Added
|
||||
- Every account's personal Unix group (its primary GID holder) can now have supplementary members managed from the account's profile page ("Members of `<uid>`'s group", admin-only) — e.g. to share write access to files owned by that group. Uses the standard `memberUid` attribute (RFC 2307 `posixGroup`).
|
||||
|
||||
## [1.1.9] - 2026-07-17
|
||||
|
||||
### Bumped
|
||||
- sso-manager-node -> [v1.1.8](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.8)
|
||||
|
||||
sso-manager-node:
|
||||
|
||||
### Added
|
||||
- Group membership is now editable directly from a user's profile page ("My groups" -- add via a group-name picker, remove with a button per row), instead of only from each group's own card on the Groups page. Admin-only, using the existing per-group member add/remove endpoints.
|
||||
|
||||
### Fixed
|
||||
- The Edit Profile form's Mobile Phone field had a stray `validate=":9"` making it effectively required (submission was blocked with "Please fix the form errors" if left blank) -- it was always meant to be optional, matching the "Add user" form. Removed.
|
||||
- A service account's profile always showed `Name: Service Account` -- every service account has the same literal filler given/last name (a schema-satisfying placeholder, not meant to be shown), making them indistinguishable by name. The Name line is now hidden for service accounts.
|
||||
- The Users page's Service Accounts tab, and a freshly-created service account's own profile, could appear empty/not-a-service-account for up to 5 minutes right after creation. Creating a user caches it via `User.get()` *before* the route handler marks it as a service account (group membership), so the cached copy had `isServiceAccount` stuck wrong until the cache TTL expired. Now cleared and re-fetched immediately after marking.
|
||||
- A user belonging to exactly one LDAP group had their `memberOf` attribute returned as a bare string instead of a one-element array (ldapts's normal behavior for single-valued attributes) -- client-side permission checks (`for(let group of user.memberOf)`) would then iterate the DN character-by-character instead of once, causing pages gated on that group (e.g. Groups) to incorrectly show "You do not have permission to be here." Normalized `memberOf` to always be an array, same fix already applied to `manager`.
|
||||
|
||||
## [1.1.8] - 2026-07-17
|
||||
|
||||
### Bumped
|
||||
- sso-manager-node -> [v1.1.7](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.7)
|
||||
|
||||
sso-manager-node:
|
||||
|
||||
### Changed
|
||||
- **Service accounts unified to one kind.** Removed the LDAP bind-only service account type (the Integrations → LDAP "Service Accounts" card, and its `/api/service-account` routes) -- every service account is now a real Unix/POSIX account with a UID, created from the new **Users → Service Accounts** tab. Email and password are both optional for service accounts; a blank password means no `userPassword` is set at all (the account simply can't bind).
|
||||
- **Added a `manager` field to every account.** Multi-valued (a list of usernames), defaults to whoever created the account (the admin who added it, or whoever sent the invite), and reassignable from the account's Edit form. Anyone listed as a manager can edit that account -- same fields an admin can (mobile, description, SSH key, date of birth, home directory, login shell, manager list) -- without needing `app_sso_admin`.
|
||||
- `homeDirectory` and `loginShell` are now editable from the Edit Profile form (previously view-only).
|
||||
|
||||
## [1.1.7] - 2026-07-16
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.7](https://github.com/theta42/proxy/releases/tag/v1.1.7)
|
||||
- sso-manager-node -> [v1.1.6](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.6)
|
||||
|
||||
Both: redesigned the GitHub Pages docs site to match each app's own look (dark navbar/footer, Bootstrap 5, Font Awesome) instead of the generic `jekyll-theme-cayman` theme, added a real cross-page nav, SEO (`jekyll-seo-tag` + `jekyll-sitemap`), and mobile-responsive layout. theta-env's own docs site got the same treatment in this release too (see below).
|
||||
|
||||
### Changed
|
||||
- theta-env's own docs site redesigned the same way -- dark navbar/footer using the shared theta42 logo (this repo has no app UI of its own), cross-page nav, SEO, mobile-responsive. `docs/index.md`'s "More docs" section removed (redundant with the new nav).
|
||||
- Added `docs/_site` to `.gitignore` (missing entirely before).
|
||||
|
||||
## [1.1.6] - 2026-07-16
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.6](https://github.com/theta42/proxy/releases/tag/v1.1.6)
|
||||
|
||||
proxy: Hosts admin UI's Authentication tab radios (Off / Basic / SSO) had no shared `name`, so clicking one didn't uncheck the others. Added `name="auth_mode"` to restore standard exclusive radio-group behavior.
|
||||
|
||||
## [1.1.5] - 2026-07-16
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.5](https://github.com/theta42/proxy/releases/tag/v1.1.5)
|
||||
- sso-manager-node -> [v1.1.5](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.5)
|
||||
|
||||
Both: bumped `jq-repeat` 2.0.1 -> 2.1.0. proxy fixed real breakage from the removed `__setPut`/`__setTake` API (insert/remove row hooks in the admin UI); sso-manager-node fixed a stale-data flash in the edit-profile flow caused by `update()`'s new throttling.
|
||||
|
||||
## [1.1.4] - 2026-07-16
|
||||
|
||||
### Added
|
||||
- White-label support in both proxy and sso-manager-node -- `<title>`, navbar brand, and logo are now conf-driven (`conf.name`/`conf.logo`) instead of hardcoded. Closes [proxy#45](https://github.com/theta42/proxy/issues/45) and [sso-manager-node#6](https://github.com/theta42/sso-manager-node/issues/6).
|
||||
|
||||
### Fixed
|
||||
- sso-manager-node: the bundled default LDAP ppolicy had `pwdLockout: FALSE`, silently making "deactivate user" not actually block login. Fixed, with a drift-correction path for already-deployed instances.
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.4](https://github.com/theta42/proxy/releases/tag/v1.1.4)
|
||||
- sso-manager-node -> [v1.1.4](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.4)
|
||||
|
||||
## [1.1.3] - 2026-07-16
|
||||
|
||||
### Added
|
||||
- `CHANGELOG.md` (this file). Closes [#43](https://github.com/theta42/theta-env/issues/43).
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.3](https://github.com/theta42/proxy/releases/tag/v1.1.3)
|
||||
- sso-manager-node -> [v1.1.3](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.3)
|
||||
|
||||
## [1.1.2] - 2026-07-16
|
||||
|
||||
### Changed
|
||||
- `docs/index.md` (the published site's home page) never linked to `architecture.md`, `quickstart.md`, or `standalone.md` — added a "More docs" section so they're reachable from the site instead of only by direct URL.
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.2](https://github.com/theta42/proxy/releases/tag/v1.1.2)
|
||||
- sso-manager-node -> [v1.1.2](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.2)
|
||||
|
||||
## [1.1.1] - 2026-07-16
|
||||
|
||||
### Changed
|
||||
- `setup.sh` now pins `proxy` and `sso-manager-node` to their latest release tag (`vX.Y.Z`) instead of the tip of `master`. A rebuild now always lands on a tagged, versioned release of each app rather than whatever was most recently merged upstream.
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.1](https://github.com/theta42/proxy/releases/tag/v1.1.1)
|
||||
- sso-manager-node -> [v1.1.1](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.1)
|
||||
|
||||
## [1.1.0] - 2026-07-16
|
||||
|
||||
First tagged release. Establishes the `vX.Y.Z` tag convention going forward.
|
||||
|
||||
### Added
|
||||
- `setup.sh` now reports which submodules actually moved to a newer commit during an update, instead of updating silently.
|
||||
|
||||
### Bumped
|
||||
- proxy -> [v1.1.0](https://github.com/theta42/proxy/releases/tag/v1.1.0)
|
||||
- sso-manager-node -> [v1.1.0](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.0)
|
||||
|
||||
[Unreleased]: https://github.com/theta42/theta-env/compare/v1.1.20...HEAD
|
||||
[1.1.17]: https://github.com/theta42/theta-env/compare/v1.1.16...v1.1.17
|
||||
[1.1.16]: https://github.com/theta42/theta-env/compare/v1.1.15...v1.1.16
|
||||
[1.1.15]: https://github.com/theta42/theta-env/compare/v1.1.14...v1.1.15
|
||||
[1.1.14]: https://github.com/theta42/theta-env/compare/v1.1.13...v1.1.14
|
||||
[1.1.13]: https://github.com/theta42/theta-env/compare/v1.1.12...v1.1.13
|
||||
[1.1.12]: https://github.com/theta42/theta-env/compare/v1.1.11...v1.1.12
|
||||
[1.1.11]: https://github.com/theta42/theta-env/compare/v1.1.10...v1.1.11
|
||||
[1.1.10]: https://github.com/theta42/theta-env/compare/v1.1.9...v1.1.10
|
||||
[1.1.9]: https://github.com/theta42/theta-env/compare/v1.1.8...v1.1.9
|
||||
[1.1.8]: https://github.com/theta42/theta-env/compare/v1.1.7...v1.1.8
|
||||
[1.1.7]: https://github.com/theta42/theta-env/compare/v1.1.6...v1.1.7
|
||||
[1.1.6]: https://github.com/theta42/theta-env/compare/v1.1.5...v1.1.6
|
||||
[1.1.5]: https://github.com/theta42/theta-env/compare/v1.1.4...v1.1.5
|
||||
[1.1.4]: https://github.com/theta42/theta-env/compare/v1.1.3...v1.1.4
|
||||
[1.1.3]: https://github.com/theta42/theta-env/compare/v1.1.2...v1.1.3
|
||||
[1.1.2]: https://github.com/theta42/theta-env/compare/v1.1.1...v1.1.2
|
||||
[1.1.1]: https://github.com/theta42/theta-env/compare/v1.1.0...v1.1.1
|
||||
[1.1.0]: https://github.com/theta42/theta-env/releases/tag/v1.1.0
|
||||
@@ -60,6 +60,10 @@ It is **both** an OIDC client of the SSO (for login) **and** a direct LDAP
|
||||
client (for user lookups). Legacy apps can still bind to LDAPS on the SSO
|
||||
directly.
|
||||
|
||||
- **Self-service API tokens** in both apps' UIs, for scripting/CI without a browser session.
|
||||
- **Multi-Site Support (Geo-Location Scaling)** — built-in support for N-Way Multi-Master LDAP replication across physical locations.
|
||||
- **Multi-target load balancing** — built-in proxy support for round-robin load balancing across multiple application servers.
|
||||
|
||||
---
|
||||
|
||||
## Before you begin
|
||||
@@ -120,6 +124,10 @@ Optional extra ports (only if you need them):
|
||||
- **636** (LDAPS) — only if a legacy app on another machine binds to LDAP
|
||||
directly over the network. The proxy itself reaches LDAP over the internal
|
||||
Docker network, so you do **not** need to expose 636 for the stack to work.
|
||||
**Do not forward 636 to the public internet.** If you need LAN clients to bind
|
||||
LDAP, set `CFG_LDAPS_HOST=ldap.internal.example.com` (or `sso-manager` for
|
||||
same-host Docker clients) in `setup.env` and use an internal DNS record / cert
|
||||
SAN. The default shows the public SSO hostname, which implies a public route.
|
||||
|
||||
### 4. Docker + Docker Compose
|
||||
|
||||
@@ -172,7 +180,8 @@ operator-owned and `setup.env` is ignored.
|
||||
### Configuration — `./config/` (no `.env` files)
|
||||
|
||||
All config and secrets live in a bind-mounted `./config/` directory (gitignored),
|
||||
read by each app's `@simpleworkjs/conf` from a symlinked `secrets.js`:
|
||||
read by each app's `@simpleworkjs/conf` via the `CONF_SECRETS` env var, which
|
||||
the entrypoint points at the mounted file:
|
||||
|
||||
- **`./config/sso-secrets.js`** — SSO config: `ldap` (base, admin password,
|
||||
user/group bases), `oauth` (issuer, `jwtSecret`), `smtp`, `name`, plus
|
||||
@@ -475,4 +484,10 @@ the gitignored `./backups/` before each rebuild.
|
||||
before building — not the tip of `master` — so each run builds the newest
|
||||
tagged release of each app, not whatever's most recently merged upstream. To
|
||||
lock to the pinned commits (offline rebuild, or a deliberate pin), run
|
||||
`SKIP_SUBMODULE_UPDATE=1 ./setup.sh`.
|
||||
`SKIP_SUBMODULE_UPDATE=1 ./setup.sh`.
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for what changed in each theta-env release
|
||||
(and each submodule's own `CHANGELOG.md` —
|
||||
[proxy](https://github.com/theta42/proxy/blob/master/CHANGELOG.md),
|
||||
[sso-manager-node](https://github.com/theta42/sso-manager-node/blob/master/CHANGELOG.md)
|
||||
— for what changed inside the apps themselves).
|
||||
Vendored
+11
-4
@@ -44,8 +44,15 @@ const fs = require('fs');
|
||||
const sso = require('/config/sso-secrets.js');
|
||||
const proxy = require('/config/proxy-secrets.js');
|
||||
|
||||
const BASE_DN = (sso.stack && sso.stack.ldapBaseDn) || 'dc=example,dc=com';
|
||||
const ADMIN_PASS = (sso.ldap && sso.ldap.bindPassword) || 'admin';
|
||||
function requireConf(value, name) {
|
||||
if (value === undefined || value === null || value === '' || value === 'CHANGE-ME') {
|
||||
throw new Error(`${name} is not configured in /config/sso-secrets.js`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const BASE_DN = requireConf((sso.stack && sso.stack.ldapBaseDn), 'stack.ldapBaseDn');
|
||||
const ADMIN_PASS = requireConf((sso.ldap && sso.ldap.bindPassword), 'ldap.bindPassword');
|
||||
const BIND_DN = `cn=admin,${BASE_DN}`;
|
||||
const LDAP_URL = 'ldap://localhost:389';
|
||||
|
||||
@@ -53,9 +60,9 @@ const ADMIN_UID = (sso.bootstrap && sso.bootstrap.adminUid) || 'admin';
|
||||
// The first admin *user's* password (cn=<uid>,ou=people,<base>). Distinct from
|
||||
// ADMIN_PASS above, which is the LDAP *root* (cn=admin,<base>) bind password —
|
||||
// two different accounts, two different secrets.
|
||||
const ADMIN_USER_PASS = (sso.bootstrap && sso.bootstrap.adminPass) || 'admin';
|
||||
const ADMIN_USER_PASS = requireConf((sso.bootstrap && sso.bootstrap.adminPass), 'bootstrap.adminPass');
|
||||
const ADMIN_EMAIL = (sso.bootstrap && sso.bootstrap.adminEmail) || '';
|
||||
const SVC_PASS = sso.serviceAccountPass || 'service';
|
||||
const SVC_PASS = requireConf(sso.serviceAccountPass, 'serviceAccountPass');
|
||||
|
||||
const SSO_HOST = (sso.stack && sso.stack.ssoHost) || 'sso.example.com';
|
||||
const PROXY_HOST = (sso.stack && sso.stack.proxyHost) || 'proxy.example.com';
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
// bootstrap writes the OAuth client clientId/clientSecret back into it; this
|
||||
// file documents the shape for manual editing / reference.
|
||||
//
|
||||
// The proxy app reads this via @simpleworkjs/conf (docker-entrypoint.sh
|
||||
// symlinks it to /app/conf/secrets.js). Never commit ./config/.
|
||||
// The proxy app reads this via @simpleworkjs/conf (docker-entrypoint.sh sets
|
||||
// CONF_SECRETS to point at it). Never commit ./config/.
|
||||
|
||||
module.exports = {
|
||||
oidc: {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
// `./setup.sh` generates ./config/sso-secrets.js for you on first run; this file
|
||||
// documents the shape for manual editing / reference.
|
||||
//
|
||||
// The SSO app reads this via @simpleworkjs/conf (docker-entrypoint.sh symlinks
|
||||
// it to /app/conf/secrets.js). The app ignores the extra stack/bootstrap/
|
||||
// The SSO app reads this via @simpleworkjs/conf (docker-entrypoint.sh sets
|
||||
// CONF_SECRETS to point at it). The app ignores the extra stack/bootstrap/
|
||||
// serviceAccountPass keys (read by the orchestrator). Back this up off-host —
|
||||
// it holds all SSO secrets. Never commit ./config/.
|
||||
|
||||
@@ -17,6 +17,9 @@ module.exports = {
|
||||
bindPassword: 'CHANGE-ME', // slapd root + app bind password
|
||||
userBase: 'ou=people,dc=example,dc=com',
|
||||
groupBase: 'ou=groups,dc=example,dc=com',
|
||||
// ldapsHost: 'ldap.internal.example.com', // optional: internal-only hostname
|
||||
// shown on /integrations for direct LDAPS binds. Empty -> derive from issuer.
|
||||
// ldapsPort: 636,
|
||||
},
|
||||
smtp: { // optional; leave host '' to skip
|
||||
host: '', port: 587, secure: false,
|
||||
|
||||
+12
-7
@@ -13,11 +13,12 @@
|
||||
# Config + secrets live in bind-mounted ./config/ (gitignored):
|
||||
# ./config/sso-secrets.js — SSO app + orchestrator config
|
||||
# ./config/proxy-secrets.js — proxy OIDC/LDAP/auth config
|
||||
# Each app's entrypoint symlinks its file into /app/conf/secrets.js so
|
||||
# @simpleworkjs/conf reads it. No app_* env is passed (app_* env would override
|
||||
# secrets.js). The sso-manager mounts ./config read-write so the bootstrap can
|
||||
# write the generated OAuth client creds back into proxy-secrets.js; the proxy
|
||||
# mounts it read-only.
|
||||
# Each app's entrypoint points CONF_SECRETS at its file so @simpleworkjs/conf
|
||||
# (>= 1.2.0) reads it directly -- no app_* env is passed (app_* env would
|
||||
# override secrets.js), and no write access to /app/conf is needed. The
|
||||
# sso-manager mounts ./config read-write so the bootstrap can write the
|
||||
# generated OAuth client creds back into proxy-secrets.js; the proxy mounts
|
||||
# it read-only.
|
||||
#
|
||||
# Compose only interpolates the port defaults below — there is no .env file.
|
||||
# First-run wiring (LDAP service account, first admin, OAuth client) is
|
||||
@@ -45,6 +46,8 @@ services:
|
||||
- "${SSO_BIND:-0.0.0.0}:${SSO_PORT:-3001}:3001"
|
||||
# LDAPS for EXTERNAL direct-LDAP clients (legacy apps). The proxy itself
|
||||
# reaches LDAPS over theta-net (sso-manager:636) without this host mapping.
|
||||
# Prefer an internal-only hostname (set CFG_LDAPS_HOST in setup.env / ldapsHost
|
||||
# in sso-secrets.js) and do NOT forward 636 to the public internet.
|
||||
- "${LDAPS_PORT:-636}:636"
|
||||
# Plain LDAP (389) is NOT mapped — direct-LDAP clients should use LDAPS.
|
||||
environment:
|
||||
@@ -53,10 +56,12 @@ services:
|
||||
# reads that are not part of its conf tree.
|
||||
- NODE_ENV=production
|
||||
- NODE_PORT=3001
|
||||
- LDAP_SERVER_ID=${LDAP_SERVER_ID:-}
|
||||
- LDAP_REPLICATION_HOSTS=${LDAP_REPLICATION_HOSTS:-}
|
||||
volumes:
|
||||
# Operator-edited SSO secrets (sso-secrets.js). Read-WRITE so the bootstrap
|
||||
# can write the generated OAuth client creds into proxy-secrets.js. The
|
||||
# entrypoint symlinks /config/sso-secrets.js -> /app/conf/secrets.js.
|
||||
# entrypoint points CONF_SECRETS at /config/sso-secrets.js.
|
||||
- ./config:/config
|
||||
# Persist the LDAP database across container recreation.
|
||||
- ldap-data:/var/lib/ldap
|
||||
@@ -108,7 +113,7 @@ services:
|
||||
volumes:
|
||||
# Operator-edited proxy secrets (proxy-secrets.js). READ-ONLY — the proxy
|
||||
# only reads it; the sso-manager bootstrap writes the OAuth creds. The
|
||||
# entrypoint symlinks /config/proxy-secrets.js -> /app/conf/secrets.js.
|
||||
# entrypoint points CONF_SECRETS at /config/proxy-secrets.js.
|
||||
- ./config:/config:ro
|
||||
# Persist Redis (AOF + RDB) so Host records, permissions, DNS creds, local
|
||||
# users, AND the auto-ssl Let's Encrypt certs survive container recreation.
|
||||
|
||||
+36
-4
@@ -1,9 +1,41 @@
|
||||
title: theta-env
|
||||
description: A unified, one-command SSO Manager + OIDC proxy stack for home labs and small businesses
|
||||
theme: jekyll-theme-cayman
|
||||
show_downloads: false
|
||||
description: A unified, one-command SSO Manager + OIDC proxy stack for home labs and small businesses.
|
||||
url: "https://theta42.github.io"
|
||||
baseurl: "/theta-env"
|
||||
logo: /assets/img/theta42.svg
|
||||
lang: en_US
|
||||
|
||||
plugins:
|
||||
- jekyll-seo-tag
|
||||
- jekyll-sitemap
|
||||
|
||||
github:
|
||||
repository_url: https://github.com/theta42/theta-env
|
||||
zip_url: https://github.com/theta42/theta-env/archive/refs/heads/master.zip
|
||||
tar_url: https://github.com/theta42/theta-env/archive/refs/heads/master.tar.gz
|
||||
repository_name: theta42/theta-env
|
||||
repository_name: theta42/theta-env
|
||||
|
||||
nav:
|
||||
- title: Home
|
||||
page: /
|
||||
icon: fa-house
|
||||
- title: Quickstart
|
||||
page: /quickstart.html
|
||||
icon: fa-rocket
|
||||
- title: Architecture
|
||||
page: /architecture.html
|
||||
icon: fa-sitemap
|
||||
- title: Standalone
|
||||
page: /standalone.html
|
||||
icon: fa-puzzle-piece
|
||||
- title: Changelog
|
||||
url: https://github.com/theta42/theta-env/blob/master/CHANGELOG.md
|
||||
icon: fa-list
|
||||
|
||||
defaults:
|
||||
- scope:
|
||||
path: ""
|
||||
type: "pages"
|
||||
values:
|
||||
layout: default
|
||||
image: /assets/img/theta42.svg
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link rel="icon" type="image/svg+xml" href="{{ '/assets/img/theta42.svg' | relative_url }}">
|
||||
|
||||
{% seo title=false %}
|
||||
<title>{% if page.title %}{{ page.title }} · {% endif %}{{ site.title }}</title>
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
|
||||
<link rel="stylesheet" href="{{ '/assets/css/style.css' | relative_url }}">
|
||||
</head>
|
||||
<body class="d-flex flex-column min-vh-100">
|
||||
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
|
||||
<div class="container-fluid px-3">
|
||||
<a class="navbar-brand d-flex align-items-center" href="{{ '/' | relative_url }}">
|
||||
<img src="{{ '/assets/img/theta42.svg' | relative_url }}" height="28" class="me-2" alt="">
|
||||
{{ site.title }}
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navMain" aria-controls="navMain" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse justify-content-end" id="navMain">
|
||||
<ul class="navbar-nav">
|
||||
{% for item in site.nav %}
|
||||
<li class="nav-item">
|
||||
{% if item.page %}
|
||||
<a class="nav-link{% if page.url == item.page %} active{% endif %}" href="{{ item.page | relative_url }}">
|
||||
{% if item.icon %}<i class="fa-solid {{ item.icon }}"></i>{% endif %} {{ item.title }}
|
||||
</a>
|
||||
{% else %}
|
||||
<a class="nav-link" href="{{ item.url }}" target="_blank" rel="noopener">
|
||||
{% if item.icon %}<i class="fa-solid {{ item.icon }}"></i>{% endif %} {{ item.title }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="flex-grow-1" style="margin-top: 4.5rem;">
|
||||
<div class="container-fluid py-4 py-md-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-10 col-xl-8">
|
||||
<div class="card shadow-lg">
|
||||
<div class="card-body p-4 p-md-5 site-content">
|
||||
{{ content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="py-3 bg-dark text-light mt-auto">
|
||||
<div class="container-fluid d-flex flex-wrap justify-content-between align-items-center small gap-2 px-3">
|
||||
<span class="d-flex align-items-center gap-2">
|
||||
<a href="https://theta42.com" target="_blank" rel="noopener">
|
||||
<img width="40" src="{{ '/assets/img/theta42.svg' | relative_url }}" alt="theta42">
|
||||
</a>
|
||||
© {{ 'now' | date: '%Y' }} theta42 ·
|
||||
<a href="{{ site.github.repository_url }}/blob/master/LICENSE" target="_blank" rel="noopener" class="text-light">MIT License</a>
|
||||
</span>
|
||||
<span class="d-flex align-items-center gap-3">
|
||||
<a href="{{ site.github.repository_url }}" target="_blank" rel="noopener" class="text-light text-decoration-none">
|
||||
<i class="fa-brands fa-github"></i> GitHub
|
||||
</a>
|
||||
<a href="{{ site.github.repository_url }}/blob/master/CHANGELOG.md" target="_blank" rel="noopener" class="text-light text-decoration-none">
|
||||
<i class="fa-solid fa-list"></i> Changelog
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+14
-12
@@ -1,6 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
title: Architecture
|
||||
description: How theta-env composes the SSO Manager and proxy submodules — the OIDC/LDAP wiring setup.sh generates from one domain.
|
||||
---
|
||||
|
||||
# Architecture
|
||||
@@ -101,9 +102,9 @@ inputs from the bind-mounted `./config/sso-secrets.js` + `./config/proxy-secrets
|
||||
read-only). If `proxy-secrets.js` already holds a `clientId`+`clientSecret`
|
||||
matching an existing client, they are kept; if the client exists but the file
|
||||
has no usable secret, the secret is rotated and written back.
|
||||
6. **Build + start the proxy**, wait for `/health`. The proxy entrypoint symlinks
|
||||
`./config/proxy-secrets.js` to `/app/conf/secrets.js`, so `@simpleworkjs/conf`
|
||||
(≥1.1.0) reads the OAuth creds + LDAP bind creds from the file.
|
||||
6. **Build + start the proxy**, wait for `/health`. The proxy entrypoint points
|
||||
`CONF_SECRETS` at `./config/proxy-secrets.js`, so `@simpleworkjs/conf`
|
||||
(≥1.2.0) reads the OAuth creds + LDAP bind creds from the file.
|
||||
7. **Register `<SSO_HOST>` and `<PROXY_HOST>` as Host records in the proxy** —
|
||||
`setup.sh` runs a short script inside the proxy container that calls its
|
||||
Host model directly (`Host.create({host, ip, targetPort, ...})`), rather
|
||||
@@ -122,19 +123,20 @@ inputs from the bind-mounted `./config/sso-secrets.js` + `./config/proxy-secrets
|
||||
### How config reaches the apps (no `.env`)
|
||||
|
||||
All config and secrets live in `./config/` (gitignored, bind-mounted). Each
|
||||
entrypoint symlinks its file to `/app/conf/secrets.js` early, before the app
|
||||
starts:
|
||||
entrypoint points the `CONF_SECRETS` env var (`@simpleworkjs/conf` >= 1.2.0)
|
||||
at its file early, before the app starts:
|
||||
|
||||
```
|
||||
./config/sso-secrets.js -> sso-manager:/app/conf/secrets.js (./config RW)
|
||||
./config/proxy-secrets.js -> proxy:/app/conf/secrets.js (./config RO)
|
||||
CONF_SECRETS=/config/sso-secrets.js (sso-manager, ./config RW)
|
||||
CONF_SECRETS=/config/proxy-secrets.js (proxy, ./config RO)
|
||||
```
|
||||
|
||||
`@simpleworkjs/conf` loads `conf/base.js → <env>.js → conf/secrets.js → app_*
|
||||
env`, where **env beats `secrets.js`**. So compose passes **no `app_*` env vars**
|
||||
(only `NODE_ENV`, `NODE_PORT`) — that makes `secrets.js` authoritative. The SSO
|
||||
entrypoint reads the few values it needs at startup (LDAP base DN, admin
|
||||
password, JWT secret, cert CN) from `secrets.js` via an in-container `node` call.
|
||||
`@simpleworkjs/conf` loads `conf/base.js → <env>.js → secrets file → app_*
|
||||
env`, where **env beats the secrets file**. So compose passes **no `app_*` env
|
||||
vars** (only `NODE_ENV`, `NODE_PORT`) — that makes the secrets file
|
||||
authoritative. The SSO entrypoint reads the few values it needs at startup
|
||||
(LDAP base DN, admin password, JWT secret, cert CN) from `sso-secrets.js` via
|
||||
an in-container `node` call.
|
||||
|
||||
### Why not `require` the SSO's internal models?
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/* theta42 docs site — shares the in-app dark navbar/footer + card look
|
||||
(Bootstrap 5 + Font Awesome, same as the running apps) rather than a
|
||||
generic Jekyll theme. */
|
||||
|
||||
body {
|
||||
background-color: #f4f5f6;
|
||||
}
|
||||
|
||||
.navbar-brand img {
|
||||
filter: drop-shadow(0 0 2px rgba(0, 0, 0, .4));
|
||||
}
|
||||
|
||||
.navbar-nav .nav-link.active {
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Markdown content typography, scoped to the card body so it doesn't leak
|
||||
into the nav/footer. */
|
||||
.site-content h1:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.site-content h1,
|
||||
.site-content h2,
|
||||
.site-content h3 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.site-content h2 {
|
||||
margin-top: 2.5rem;
|
||||
padding-bottom: .4rem;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.site-content h3 {
|
||||
margin-top: 1.75rem;
|
||||
}
|
||||
|
||||
.site-content a {
|
||||
color: #a3671f;
|
||||
text-decoration-color: rgba(163, 103, 31, .35);
|
||||
}
|
||||
|
||||
.site-content a:hover {
|
||||
color: #8a5a16;
|
||||
}
|
||||
|
||||
.site-content pre {
|
||||
background-color: #212529;
|
||||
color: #f8f9fa;
|
||||
padding: 1rem 1.25rem;
|
||||
border-radius: .375rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.site-content code {
|
||||
color: #a3671f;
|
||||
background-color: #f4f0e8;
|
||||
padding: .15em .4em;
|
||||
border-radius: .25rem;
|
||||
font-size: .875em;
|
||||
}
|
||||
|
||||
.site-content pre code {
|
||||
color: inherit;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.site-content table {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1.25rem 0;
|
||||
}
|
||||
|
||||
.site-content table th,
|
||||
.site-content table td {
|
||||
border: 1px solid #dee2e6;
|
||||
padding: .5rem .75rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.site-content table th {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.site-content blockquote {
|
||||
border-left: 4px solid #C59341;
|
||||
padding: .5rem 1rem;
|
||||
margin: 1.25rem 0;
|
||||
background-color: #f8f6f1;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.site-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Screenshot grids in the markdown use width="49%" inline attrs for a
|
||||
two-up desktop layout -- stack them on narrow screens instead of
|
||||
squeezing to illegibility. */
|
||||
@media (max-width: 576px) {
|
||||
.site-content img[width] {
|
||||
width: 100% !important;
|
||||
margin-bottom: .75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.site-content hr {
|
||||
margin: 2rem 0;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="100%" height="100%">
|
||||
<defs>
|
||||
<linearGradient id="gold-grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#C59341" />
|
||||
<stop offset="20%" stop-color="#E4B869" />
|
||||
<stop offset="40%" stop-color="#FBF0B9" />
|
||||
<stop offset="60%" stop-color="#DFB260" />
|
||||
<stop offset="80%" stop-color="#BC8837" />
|
||||
<stop offset="100%" stop-color="#A36F28" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="text-grad" x1="0%" y1="100%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#FFFFFF" />
|
||||
<stop offset="40%" stop-color="#F5E3B5" />
|
||||
<stop offset="70%" stop-color="#D4A343" />
|
||||
<stop offset="100%" stop-color="#8A5A16" />
|
||||
</linearGradient>
|
||||
|
||||
<filter id="drop-shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="8" stdDeviation="6" flood-color="#000000" flood-opacity="0.4"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<g filter="url(#drop-shadow)">
|
||||
<g fill="url(#gold-grad)">
|
||||
<path d="M 200,40
|
||||
C 290,40 350,110 350,200
|
||||
C 350,290 290,360 200,360
|
||||
C 110,360 50,290 50,200
|
||||
C 50,110 110,40 200,40 Z
|
||||
M 200,75
|
||||
C 130,75 88,130 88,200
|
||||
C 88,270 130,325 200,325
|
||||
C 270,325 312,270 312,200
|
||||
C 312,130 270,75 200,75 Z"
|
||||
fill-rule="evenodd" />
|
||||
|
||||
<path d="M 88,190 L 140,190 C 140,190 142,210 140,210 L 88,210 Z" />
|
||||
|
||||
<path d="M 260,190 L 312,190 C 312,190 310,210 260,210 Z" />
|
||||
</g>
|
||||
|
||||
<text x="200" y="222"
|
||||
font-family="system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
|
||||
font-size="78"
|
||||
font-weight="900"
|
||||
fill="url(#text-grad)"
|
||||
text-anchor="middle"
|
||||
letter-spacing="-2">42</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 394 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 232 KiB |
@@ -1,6 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
title: Home
|
||||
description: A unified, one-command SSO Manager + OIDC proxy stack for home labs and small businesses. Wires together a self-hosted identity provider and a reverse proxy with one setup.sh.
|
||||
---
|
||||
|
||||
# theta-env
|
||||
@@ -43,6 +44,8 @@ snapshots state before every rebuild.
|
||||
- **LDAPS** for legacy apps that bind directly.
|
||||
- **Self-service API tokens** in both apps' UIs, for scripting/CI without a
|
||||
browser session.
|
||||
- **Multi-Site Support (Geo-Location Scaling)** — built-in support for N-Way Multi-Master LDAP replication across physical locations.
|
||||
- **Multi-target load balancing** — built-in proxy support for round-robin load balancing across multiple application servers.
|
||||
|
||||
## Get it
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
title: Quickstart
|
||||
description: Step-by-step first run for theta-env — prerequisites, setup.env, and bringing up the stack with ./setup.sh.
|
||||
---
|
||||
|
||||
# Quickstart Guide
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://theta42.github.io/theta-env/sitemap.xml
|
||||
+9
-8
@@ -1,6 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
title: Standalone
|
||||
description: Running the SSO Manager or the proxy on their own, without theta-env's orchestration.
|
||||
---
|
||||
|
||||
# Running each project standalone
|
||||
@@ -24,18 +25,18 @@ mkdir -p config && cp secrets.js.example config/sso-secrets.js # edit it
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
The entrypoint symlinks `config/sso-secrets.js` to `nodejs/conf/secrets.js` so
|
||||
The entrypoint points the `CONF_SECRETS` env var at `config/sso-secrets.js` so
|
||||
`@simpleworkjs/conf` reads it. Set `ldap.bindPassword`, `oauth.jwtSecret`, and
|
||||
the `stack`/`bootstrap` keys (the app ignores the ones it doesn't use). Pass
|
||||
**no `app_*` env** — env beats `secrets.js`, so `app_*` would silently override
|
||||
your file.
|
||||
**no `app_*` env** — env beats the secrets file, so `app_*` would silently
|
||||
override your file.
|
||||
|
||||
- Web UI: `http://localhost:3001`
|
||||
- Health: `http://localhost:3001/health`
|
||||
- OIDC discovery: `http://localhost:3001/.well-known/openid-configuration`
|
||||
- LDAPS: `ldaps://<host>:636`
|
||||
|
||||
Requires `@simpleworkjs/conf` >= 1.1.0. Full reference:
|
||||
Requires `@simpleworkjs/conf` >= 1.2.0. Full reference:
|
||||
[SSO Manager deployment docs](https://theta42.github.io/sso-manager-node/deployment.html).
|
||||
|
||||
### Bare metal
|
||||
@@ -61,11 +62,11 @@ mkdir -p config && cp secrets.js.example config/proxy-secrets.js # edit it
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
The entrypoint symlinks `config/proxy-secrets.js` to `nodejs/conf/secrets.js` so
|
||||
`@simpleworkjs/conf` reads it. Fill in `oidc` (your SSO's endpoints +
|
||||
The entrypoint points the `CONF_SECRETS` env var at `config/proxy-secrets.js`
|
||||
so `@simpleworkjs/conf` reads it. Fill in `oidc` (your SSO's endpoints +
|
||||
`clientId`/`clientSecret`/`redirectUri`), `ldap` (bind creds + search base), and
|
||||
`auth` (admin groups/users). Pass **no `app_*` env** — env beats `secrets.js`,
|
||||
so `app_*` would silently override your file.
|
||||
`auth` (admin groups/users). Pass **no `app_*` env** — env beats the secrets
|
||||
file, so `app_*` would silently override your file.
|
||||
|
||||
- Proxy (public, auto-SSL): `https://<host>/`
|
||||
- Mgmt UI / API: `http://127.0.0.1:3000/`
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
https://github.com/theta42/theta-env/pull/75
|
||||
+1
-1
Submodule proxy updated: 5a685e9dd7...60dbfe5b9b
+19
-1
@@ -36,6 +36,12 @@ CFG_DOMAIN=example.com
|
||||
#CFG_ADMIN_UID=admin # initial SSO admin username
|
||||
#CFG_ADMIN_EMAIL=admin@proxy.example.com # defaults to admin@<proxyHost>
|
||||
#CFG_LDAP_CERT_CN= # LDAP TLS cert CN; empty -> defaults to the domain
|
||||
#
|
||||
# Hostname advertised on the SSO /integrations page for direct LDAPS binds.
|
||||
# Leave blank to derive it from the public SSO host (same as oauth.issuer).
|
||||
# Recommended: set an internal-only name like 'ldap.internal.example.com' or
|
||||
# 'sso-manager' so clients don't need a public 636 port forward. See docs.
|
||||
#CFG_LDAPS_HOST=
|
||||
|
||||
# Optional SMTP (outbound email from the SSO app). Leave blank to disable:
|
||||
#CFG_SMTP_HOST=smtp.example.com
|
||||
@@ -52,4 +58,16 @@ CFG_DOMAIN=example.com
|
||||
# password is the exception — see ./config/proxy-secrets.js's auth.localAdminPass
|
||||
# comment for how to actually change it after the account exists). Do NOT set
|
||||
# CFG_LDAP_ADMIN_PASS / CFG_JWT_SECRET / CFG_ADMIN_PASS / CFG_SVC_PASS /
|
||||
# CFG_PROXY_ADMIN_PASS here.
|
||||
# CFG_PROXY_ADMIN_PASS here.
|
||||
|
||||
# ── Geo-Location Scaling (N-Way Multi-Master LDAP) ───────────────────────────
|
||||
# If deploying this stack across multiple physical sites to provide local HA
|
||||
# for directory services, you can enable N-Way Multi-Master OpenLDAP replication.
|
||||
# This requires assigning a unique ID to each site and listing the LDAPS URLs
|
||||
# of all OTHER sites in the cluster.
|
||||
#
|
||||
# Each site MUST have a unique LDAP_SERVER_ID (e.g. 1, 2, 3).
|
||||
# LDAP_REPLICATION_HOSTS is a space-separated list of the other sites' LDAP URLs.
|
||||
# Example for Site 1:
|
||||
#LDAP_SERVER_ID=1
|
||||
#LDAP_REPLICATION_HOSTS="ldaps://sso.site2.com:636 ldaps://sso.site3.com:636"
|
||||
@@ -135,10 +135,12 @@ if [[ "${SKIP_SELF_UPDATE:-0}" != "1" && "${THETA_ENV_REEXECED:-0}" != "1" ]] \
|
||||
&& git rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1
|
||||
then
|
||||
BEFORE_REV="$(git rev-parse HEAD)"
|
||||
BEFORE_VER="$(git describe --tags "$BEFORE_REV" 2>/dev/null || echo "${BEFORE_REV:0:12}")"
|
||||
if git pull --ff-only -q; then
|
||||
AFTER_REV="$(git rev-parse HEAD)"
|
||||
if [[ "$BEFORE_REV" != "$AFTER_REV" ]]; then
|
||||
info "Updated theta-env (${BEFORE_REV:0:12} -> ${AFTER_REV:0:12}) — restarting setup.sh with the new version..."
|
||||
AFTER_VER="$(git describe --tags "$AFTER_REV" 2>/dev/null || echo "${AFTER_REV:0:12}")"
|
||||
info "Updated theta-env (${BEFORE_VER} -> ${AFTER_VER}) — restarting setup.sh with the new version..."
|
||||
THETA_ENV_REEXECED=1 exec "$0" "$@"
|
||||
fi
|
||||
else
|
||||
@@ -165,26 +167,32 @@ if [[ "${SKIP_SUBMODULE_UPDATE:-0}" != "1" ]]; then
|
||||
for sm in sso-manager-node proxy; do
|
||||
[[ -d "$sm" ]] || continue
|
||||
before_rev="$(git -C "$sm" rev-parse HEAD 2>/dev/null || true)"
|
||||
# Prefer the exact tag the submodule is currently pinned to; fall back
|
||||
# to a short commit hash if it's on an untagged commit (shouldn't
|
||||
# normally happen -- this repo only ever pins tagged releases).
|
||||
before_tag="$(git -C "$sm" describe --tags --exact-match "$before_rev" 2>/dev/null || echo "${before_rev:0:12}")"
|
||||
|
||||
if ! git -C "$sm" fetch --tags -q 2>&1; then
|
||||
warn " ${sm}: could not fetch tags (offline?) — staying on the current pin."
|
||||
warn " ${sm}: could not fetch tags (offline?) — staying on ${before_tag}."
|
||||
continue
|
||||
fi
|
||||
|
||||
latest_tag="$(git -C "$sm" tag --list 'v*' --sort=-v:refname | head -n1)"
|
||||
if [[ -z "$latest_tag" ]]; then
|
||||
warn " ${sm}: no vX.Y.Z release tags found — staying on the current pin."
|
||||
warn " ${sm}: no vX.Y.Z release tags found — staying on ${before_tag}."
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! git -C "$sm" checkout -q "$latest_tag" 2>&1; then
|
||||
warn " ${sm}: could not check out ${latest_tag} — staying on the current pin."
|
||||
warn " ${sm}: could not check out ${latest_tag} — staying on ${before_tag}."
|
||||
continue
|
||||
fi
|
||||
|
||||
after_rev="$(git -C "$sm" rev-parse HEAD 2>/dev/null || true)"
|
||||
if [[ "$before_rev" != "$after_rev" ]]; then
|
||||
info " ${sm}: updated to ${latest_tag} (${before_rev:0:12} -> ${after_rev:0:12})"
|
||||
info " ${sm}: updated ${before_tag} -> ${latest_tag}"
|
||||
else
|
||||
info " ${sm}: already up to date (${latest_tag})"
|
||||
fi
|
||||
done
|
||||
else
|
||||
@@ -220,7 +228,7 @@ write_sso_secrets() {
|
||||
cat > "$CONFIG_DIR/sso-secrets.js" <<SSOEOF
|
||||
'use strict';
|
||||
// Generated by setup.sh. Edit freely; re-run ./setup.sh to apply.
|
||||
// The SSO app reads this via @simpleworkjs/conf (symlinked to conf/secrets.js).
|
||||
// The SSO app reads this via @simpleworkjs/conf (CONF_SECRETS env var).
|
||||
// The app ignores the extra stack/bootstrap/serviceAccountPass keys (read by
|
||||
// the orchestrator). Back this file up off-host — it holds all SSO secrets.
|
||||
|
||||
@@ -232,6 +240,8 @@ module.exports = {
|
||||
bindPassword: $(js_str "$CFG_LDAP_ADMIN_PASS"),
|
||||
userBase: $(js_str "ou=people,${dn}"),
|
||||
groupBase: $(js_str "ou=groups,${dn}"),
|
||||
ldapsHost: $(js_str "${CFG_LDAPS_HOST:-}"),
|
||||
ldapsPort: 636,
|
||||
},
|
||||
smtp: {
|
||||
host: $(js_str "${CFG_SMTP_HOST:-}"),
|
||||
@@ -271,8 +281,8 @@ write_proxy_secrets() {
|
||||
local dn="$CFG_BASE_DN"
|
||||
cat > "$CONFIG_DIR/proxy-secrets.js" <<PROXYEOF
|
||||
'use strict';
|
||||
// Generated by setup.sh. The proxy reads this via @simpleworkjs/conf (symlinked
|
||||
// to conf/secrets.js). clientId/clientSecret are filled in by the bootstrap
|
||||
// Generated by setup.sh. The proxy reads this via @simpleworkjs/conf (CONF_SECRETS
|
||||
// env var). clientId/clientSecret are filled in by the bootstrap
|
||||
// (run by ./setup.sh) — leave them as-is. ldap.bindPassword MUST equal
|
||||
// serviceAccountPass in sso-secrets.js (the proxy binds as that account).
|
||||
|
||||
@@ -347,6 +357,7 @@ ensure_config() {
|
||||
CFG_ADMIN_UID="${CFG_ADMIN_UID:-}"
|
||||
CFG_ADMIN_EMAIL="${CFG_ADMIN_EMAIL:-}"
|
||||
CFG_LDAP_CERT_CN="${CFG_LDAP_CERT_CN:-}"
|
||||
CFG_LDAPS_HOST="${CFG_LDAPS_HOST:-}"
|
||||
CFG_CLIENT_ID="${CFG_CLIENT_ID:-}"
|
||||
CFG_CLIENT_SECRET="${CFG_CLIENT_SECRET:-}"
|
||||
CFG_LDAP_ADMIN_PASS="${CFG_LDAP_ADMIN_PASS:-}"
|
||||
@@ -375,6 +386,8 @@ ensure_config() {
|
||||
CFG_ADMIN_PASS="${BOOTSTRAP_ADMIN_PASS:-$CFG_ADMIN_PASS}"
|
||||
CFG_SVC_PASS="${LDAP_SERVICE_PASS:-$CFG_SVC_PASS}"
|
||||
CFG_LDAP_CERT_CN="${LDAP_CERT_CN:-$CFG_LDAP_CERT_CN}"
|
||||
# .env has no legacy LDAPS_HOST key; this stays as set in setup.env/env.
|
||||
CFG_LDAPS_HOST="${CFG_LDAPS_HOST:-}"
|
||||
CFG_SMTP_HOST="${SMTP_HOST:-${CFG_SMTP_HOST:-}}"
|
||||
CFG_SMTP_PORT="${SMTP_PORT:-${CFG_SMTP_PORT:-}}"
|
||||
CFG_SMTP_USER="${SMTP_USER:-${CFG_SMTP_USER:-}}"
|
||||
@@ -408,6 +421,7 @@ ensure_config() {
|
||||
CFG_ADMIN_UID="${CFG_ADMIN_UID:-admin}"
|
||||
CFG_ADMIN_EMAIL="${CFG_ADMIN_EMAIL:-admin@$CFG_PROXY_HOST}"
|
||||
CFG_LDAP_CERT_CN="${CFG_LDAP_CERT_CN:-}"
|
||||
CFG_LDAPS_HOST="${CFG_LDAPS_HOST:-}"
|
||||
CFG_CLIENT_ID="${CFG_CLIENT_ID:-}"
|
||||
CFG_CLIENT_SECRET="${CFG_CLIENT_SECRET:-}"
|
||||
# Random secrets (generated fresh unless sourced/migrated above). These do
|
||||
@@ -558,7 +572,7 @@ backup_before_rebuild() {
|
||||
# Only prune real backup dirs — skip symlinks (a stray symlink could
|
||||
# point rm at an arbitrary tree) and non-dir entries.
|
||||
[[ -d "$BACKUP_DIR/$old" && ! -L "$BACKUP_DIR/$old" ]] || continue
|
||||
rm -rf "$BACKUP_DIR/$old" || true
|
||||
rm -rf "${BACKUP_DIR:?}/$old" || true
|
||||
removed=$((removed + 1))
|
||||
done < <(ls -1 "$BACKUP_DIR" 2>/dev/null | sort -r | tail -n +$((keep + 1)))
|
||||
[[ "$removed" -gt 0 ]] && info " pruned $removed old backup(s) (keeping $keep)."
|
||||
@@ -572,7 +586,8 @@ backup_before_rebuild
|
||||
# hash from inside the Docker build context. Resolve it on the host (where
|
||||
# the submodule DOES resolve correctly) and pass it in as a build arg; see
|
||||
# docker-compose.yml and sso-manager-node's Dockerfile.openldap.
|
||||
export SSO_GIT_COMMIT="$(git -C sso-manager-node rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
||||
SSO_GIT_COMMIT="$(git -C sso-manager-node rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
||||
export SSO_GIT_COMMIT
|
||||
info "Building + starting sso-manager (first run builds the image; this takes a while)..."
|
||||
"${COMPOSE[@]}" up -d --build sso-manager
|
||||
|
||||
@@ -601,8 +616,6 @@ read_config_kv() {
|
||||
LDAP_BASE_DN: (c.stack && c.stack.ldapBaseDn) || "",
|
||||
ORG_NAME: c.name || "",
|
||||
ADMIN_UID: (c.bootstrap && c.bootstrap.adminUid) || "",
|
||||
ADMIN_PASS: (c.bootstrap && c.bootstrap.adminPass) || "",
|
||||
PROXY_LOCAL_ADMIN_PASS: (p.auth && p.auth.localAdminPass) || "",
|
||||
};
|
||||
for (const k in o) console.log(k + "=" + (o[k] == null ? "" : o[k]));
|
||||
' 2>/dev/null
|
||||
@@ -612,8 +625,6 @@ cfgval() { echo "$CFG_OUT" | grep -m1 "^$1=" | cut -d= -f2-; }
|
||||
SSO_HOST="$(cfgval SSO_HOST)"
|
||||
PROXY_HOST="$(cfgval PROXY_HOST)"
|
||||
ADMIN_UID="$(cfgval ADMIN_UID)"
|
||||
ADMIN_PASS="$(cfgval ADMIN_PASS)"
|
||||
PROXY_LOCAL_ADMIN_PASS="$(cfgval PROXY_LOCAL_ADMIN_PASS)"
|
||||
|
||||
info "Stack config:"
|
||||
info " SSO host: https://${SSO_HOST}"
|
||||
@@ -629,7 +640,6 @@ BOOTSTRAP_OUT=$("${COMPOSE[@]}" exec -T sso-manager node /bootstrap/bootstrap.js
|
||||
|
||||
getval() { echo "$BOOTSTRAP_OUT" | grep -m1 "^$1=" | cut -d= -f2-; }
|
||||
CLIENT_ID=$(getval CLIENT_ID)
|
||||
CLIENT_SECRET=$(getval CLIENT_SECRET)
|
||||
ALREADY_CONFIGURED=$(getval ALREADY_CONFIGURED)
|
||||
[[ -n "$CLIENT_ID" ]] || die "bootstrap did not return CLIENT_ID:\n${BOOTSTRAP_OUT}"
|
||||
|
||||
@@ -641,7 +651,8 @@ fi
|
||||
|
||||
# ── 6. Start the proxy, wait for health ───────────────────────────────────────
|
||||
# PROXY_GIT_COMMIT: same reasoning as SSO_GIT_COMMIT above.
|
||||
export PROXY_GIT_COMMIT="$(git -C proxy rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
||||
PROXY_GIT_COMMIT="$(git -C proxy rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
||||
export PROXY_GIT_COMMIT
|
||||
info "Building + starting proxy (first run builds the image; this takes a while)..."
|
||||
"${COMPOSE[@]}" up -d --build proxy
|
||||
|
||||
@@ -708,13 +719,13 @@ echo " first-run fallback: http://127.0.0.1:${SSO_PORT:-300
|
||||
echo " Proxy mgmt UI: https://${PROXY_HOST}"
|
||||
echo " first-run fallback: http://127.0.0.1:${MGMT_PORT:-3000}"
|
||||
echo
|
||||
echo " First admin login:"
|
||||
echo " First admin login credentials are in ./config/sso-secrets.js:"
|
||||
echo " user: ${ADMIN_UID}"
|
||||
echo " pass: ${ADMIN_PASS}"
|
||||
echo " pass: bootstrap.adminPass"
|
||||
echo
|
||||
echo " Proxy local admin (anti-lockout fallback if the SSO is unreachable):"
|
||||
echo " user: proxyadmin2"
|
||||
echo " pass: ${PROXY_LOCAL_ADMIN_PASS}"
|
||||
echo " pass: auth.localAdminPass in ./config/proxy-secrets.js"
|
||||
echo " (only shown when the account is first created; edit ./config/proxy-secrets.js"
|
||||
echo " or use the proxy UI to change it afterward)"
|
||||
echo
|
||||
|
||||
+1
-1
Submodule sso-manager-node updated: 65e43a5677...ec76054e41
Reference in New Issue
Block a user