Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9cf2d70226 | |||
| e5f3e1714f | |||
| 4fe1c41b82 | |||
| c60e745665 | |||
| 8a6edd4f0e | |||
| c47aa209be | |||
| a56a594ff5 | |||
| 287f821e8c | |||
| 41443f4be9 | |||
| 9fae4b3b75 | |||
| 273577d124 | |||
| b295c3ae7f | |||
| c7276b1e14 | |||
| 7c3f275b71 | |||
| f5c6097924 | |||
| 49ae0f9b96 | |||
| 3bff9af42d | |||
| 83750ff609 | |||
| 1730a5036c | |||
| 6b8cd7e553 | |||
| 4117d7a8ff | |||
| 62ce314be2 | |||
| abdc2c0c2e | |||
| df5e5e1f56 | |||
| 1fab34b6ec | |||
| cc8e83f42f | |||
| af68c73629 | |||
| 69e02cc121 | |||
| ee79b3888b | |||
| fb2d1486c1 | |||
| f7ae7c5ec7 | |||
| a9f9ff00ab | |||
| 6b7488cb0a | |||
| 7e679928ce | |||
| 1bf3f63619 | |||
| 192c5a3c6e | |||
| bc0f7ae6a1 | |||
| 32684d5d35 | |||
| a61ad10262 | |||
| fc9528f4ba | |||
| 57040bd62a | |||
| 189980d862 | |||
| 9ca3b1a113 | |||
| ecdd7ee9bd | |||
| 4f011cbb69 | |||
| 5982d44a4a | |||
| 8e5b9cd05f | |||
| 8f9e68bf5e | |||
| 79051b96e3 | |||
| 3cb549f39c | |||
| 0f7f7e2080 | |||
| c45d030da1 | |||
| 425d92a137 | |||
| 4bd2c5a8be | |||
| 6f5878989d | |||
| 1285a86ce6 | |||
| b43c3d0d1d |
@@ -0,0 +1,55 @@
|
|||||||
|
# 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.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.3...HEAD
|
||||||
|
[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
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 theta42
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -16,6 +16,25 @@ Each project still runs **standalone** (`docker compose up` in its own folder);
|
|||||||
this repo just composes them and automates the first-run glue so they find each
|
this repo just composes them and automates the first-run glue so they find each
|
||||||
other.
|
other.
|
||||||
|
|
||||||
|
**Documentation:** [https://theta42.github.io/theta-env/](https://theta42.github.io/theta-env/)
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
The SSO Manager and the proxy it fronts, both stood up by one `./setup.sh` run:
|
||||||
|
|
||||||
|
| SSO Manager Dashboard | Proxy Hosts |
|
||||||
|
| --- | --- |
|
||||||
|
| [](docs/images/sso-dashboard.png) | [](docs/images/proxy-hosts.png) |
|
||||||
|
|
||||||
|
**Why use this instead of running the two separately?** The two only become
|
||||||
|
useful once the proxy is registered as an OIDC client of the SSO and pointed at
|
||||||
|
the SSO's LDAP directory — and the SSO's domain has to match across half a dozen
|
||||||
|
config fields or logins silently fail with `Invalid Credentials`. Doing that by
|
||||||
|
hand is fiddly and easy to get wrong. `setup.sh` asks for your domain once (in
|
||||||
|
`setup.env`), generates both config files with it filled in everywhere, registers
|
||||||
|
the proxy as an OIDC client, and snapshots state before every rebuild — so you
|
||||||
|
get a working SSO + proxy stack in one command and a safe way to upgrade it.
|
||||||
|
|
||||||
```
|
```
|
||||||
┌──────────────────────────────────────────────┐
|
┌──────────────────────────────────────────────┐
|
||||||
│ your browser / apps │
|
│ your browser / apps │
|
||||||
@@ -55,10 +74,10 @@ real TLS certificates for it via Let's Encrypt. A `.local` or made-up name only
|
|||||||
gets you a self-signed cert (browsers will warn — fine for testing, painful for
|
gets you a self-signed cert (browsers will warn — fine for testing, painful for
|
||||||
daily use).
|
daily use).
|
||||||
|
|
||||||
The domain is the **one** value you set in `setup.env` (as the LDAP base DN,
|
The domain is the **one** value you set in `setup.env` (e.g.
|
||||||
e.g. `CFG_BASE_DN=dc=lab,dc=example,dc=com` for `lab.example.com`) — see
|
`CFG_DOMAIN=lab.example.com`) — see *Quickstart*. The SSO/proxy hostnames
|
||||||
*Quickstart*. The SSO/proxy hostnames default to `sso.<domain>` /
|
default to `sso.<domain>` / `proxy.<domain>`, and the LDAP base DN
|
||||||
`proxy.<domain>`, derived from it.
|
(`dc=lab,dc=example,dc=com`) is built from it automatically.
|
||||||
|
|
||||||
### 2. At least two hostnames, pointing at your public IP
|
### 2. At least two hostnames, pointing at your public IP
|
||||||
|
|
||||||
@@ -114,12 +133,13 @@ standalone (`docker-compose`) both work.
|
|||||||
```bash
|
```bash
|
||||||
git clone --recursive https://github.com/theta42/theta-env.git
|
git clone --recursive https://github.com/theta42/theta-env.git
|
||||||
cd theta-env
|
cd theta-env
|
||||||
cp setup.env.example setup.env # then edit setup.env: set CFG_BASE_DN to your domain
|
cp setup.env.example setup.env # then edit setup.env: set CFG_DOMAIN to your domain
|
||||||
./setup.sh # first run: generates ./config/ from setup.env, builds + bootstraps + starts
|
./setup.sh # first run: generates ./config/ from setup.env, builds + bootstraps + starts
|
||||||
```
|
```
|
||||||
|
|
||||||
Your domain is entered **once**, as the LDAP base DN in `setup.env` (e.g.
|
Your domain is entered **once** in `setup.env` (e.g.
|
||||||
`CFG_BASE_DN=dc=lab,dc=example,dc=com` for the domain `lab.example.com`). The
|
`CFG_DOMAIN=lab.example.com`) — the LDAP base DN (`dc=lab,dc=example,dc=com`)
|
||||||
|
is derived from it, however many labels it has. The
|
||||||
first `./setup.sh` reads `setup.env` and generates `./config/sso-secrets.js` +
|
first `./setup.sh` reads `setup.env` and generates `./config/sso-secrets.js` +
|
||||||
`./config/proxy-secrets.js` with that domain filled in everywhere (hostnames
|
`./config/proxy-secrets.js` with that domain filled in everywhere (hostnames
|
||||||
default to `sso.<domain>` / `proxy.<domain>`) plus random secrets, then builds
|
default to `sso.<domain>` / `proxy.<domain>`) plus random secrets, then builds
|
||||||
@@ -142,7 +162,12 @@ operator-owned and `setup.env` is ignored.
|
|||||||
- registers the proxy as an OIDC client in the SSO and **writes the generated
|
- registers the proxy as an OIDC client in the SSO and **writes the generated
|
||||||
client id + secret back into `./config/proxy-secrets.js`**.
|
client id + secret back into `./config/proxy-secrets.js`**.
|
||||||
4. Builds + starts the proxy container, waits for it to be healthy.
|
4. Builds + starts the proxy container, waits for it to be healthy.
|
||||||
5. Prints your first admin login + the public URLs.
|
5. Registers `<SSO_HOST>` and `<PROXY_HOST>` as Host records in the proxy
|
||||||
|
(directly via its Host model, inside the proxy container) — the proxy
|
||||||
|
routes every hostname it serves off a Host record, including its own
|
||||||
|
management UI and the SSO's UI, so without this step those two URLs
|
||||||
|
would 404. Idempotent; skips a host that already exists.
|
||||||
|
6. Prints your first admin login + the public URLs.
|
||||||
|
|
||||||
### Configuration — `./config/` (no `.env` files)
|
### Configuration — `./config/` (no `.env` files)
|
||||||
|
|
||||||
@@ -412,8 +437,12 @@ exactly in the bootstrap) so the SSO can verify them on bind.
|
|||||||
off-host (see *Backups and restore*).
|
off-host (see *Backups and restore*).
|
||||||
3. **LDAPS uses the SSO's self-signed cert by default.** The proxy binds with
|
3. **LDAPS uses the SSO's self-signed cert by default.** The proxy binds with
|
||||||
`ldap.tlsOptions.rejectUnauthorized=false` (in `proxy-secrets.js`). For strict
|
`ldap.tlsOptions.rejectUnauthorized=false` (in `proxy-secrets.js`). For strict
|
||||||
trust, mount the SSO's cert (`ldap-certs` volume) into the proxy and set
|
trust, this is a **two-step change, not config-only**: (a) edit
|
||||||
`ldap.tlsOptions.ca=<path>` in `./config/proxy-secrets.js`.
|
`docker-compose.yml` to also mount the `ldap-certs` volume into the `proxy`
|
||||||
|
service (it's currently only mounted into `sso-manager`) — see the
|
||||||
|
commented-out boilerplate in the `proxy` service's `volumes:` block — then
|
||||||
|
(b) set `ldap.tlsOptions.ca=<path>` in `./config/proxy-secrets.js` to the
|
||||||
|
mounted cert path and `docker compose up -d proxy` to pick up the new mount.
|
||||||
4. **Re-running `setup.sh` resets the bootstrap admin + service passwords to
|
4. **Re-running `setup.sh` resets the bootstrap admin + service passwords to
|
||||||
the `./config/` values.** If you change a user's password in the SSO UI
|
the `./config/` values.** If you change a user's password in the SSO UI
|
||||||
later, re-running `setup.sh` will reset the bootstrap admin's password back
|
later, re-running `setup.sh` will reset the bootstrap admin's password back
|
||||||
@@ -428,7 +457,7 @@ exactly in the bootstrap) so the SSO can verify them on bind.
|
|||||||
|
|
||||||
```
|
```
|
||||||
theta-env/
|
theta-env/
|
||||||
├── setup.env.example # first-run config template — cp to setup.env, set CFG_BASE_DN
|
├── setup.env.example # first-run config template — cp to setup.env, set CFG_DOMAIN
|
||||||
├── config.example/ # committed annotated config templates (copy to ./config/)
|
├── config.example/ # committed annotated config templates (copy to ./config/)
|
||||||
├── docker-compose.yml # sso-manager + proxy on one bridge net
|
├── docker-compose.yml # sso-manager + proxy on one bridge net
|
||||||
├── setup.sh # one-command idempotent bring-up (manages ./config/ + backups)
|
├── setup.sh # one-command idempotent bring-up (manages ./config/ + backups)
|
||||||
@@ -442,7 +471,14 @@ theta-env/
|
|||||||
gitignored `./config/` (`sso-secrets.js` + `proxy-secrets.js`) and snapshots to
|
gitignored `./config/` (`sso-secrets.js` + `proxy-secrets.js`) and snapshots to
|
||||||
the gitignored `./backups/` before each rebuild.
|
the gitignored `./backups/` before each rebuild.
|
||||||
|
|
||||||
`./setup.sh` updates both submodules to the latest of their tracked remote
|
`./setup.sh` updates both submodules to their latest `vX.Y.Z` release tag
|
||||||
branch before building, so each run builds current upstream — no manual
|
before building — not the tip of `master` — so each run builds the newest
|
||||||
`git submodule update --remote` needed. To lock to the pinned commits (offline
|
tagged release of each app, not whatever's most recently merged upstream. To
|
||||||
rebuild, or a deliberate pin), run `SKIP_SUBMODULE_UPDATE=1 ./setup.sh`.
|
lock to the pinned commits (offline rebuild, or a deliberate pin), run
|
||||||
|
`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).
|
||||||
@@ -29,6 +29,12 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./sso-manager-node
|
context: ./sso-manager-node
|
||||||
dockerfile: Dockerfile.openldap
|
dockerfile: Dockerfile.openldap
|
||||||
|
args:
|
||||||
|
# A submodule's .git is a pointer file, not a real repo — the image
|
||||||
|
# can't resolve its own commit hash from inside the build context.
|
||||||
|
# setup.sh sets this from the host, where the submodule resolves
|
||||||
|
# correctly (git -C sso-manager-node rev-parse --short HEAD).
|
||||||
|
GIT_COMMIT: ${SSO_GIT_COMMIT:-}
|
||||||
container_name: sso-manager
|
container_name: sso-manager
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks: [theta-net]
|
networks: [theta-net]
|
||||||
@@ -74,6 +80,12 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./proxy
|
context: ./proxy
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
# A submodule's .git is a pointer file, not a real repo — the image
|
||||||
|
# can't resolve its own commit hash from inside the build context.
|
||||||
|
# setup.sh sets this from the host, where the submodule resolves
|
||||||
|
# correctly (git -C proxy rev-parse --short HEAD).
|
||||||
|
GIT_COMMIT: ${PROXY_GIT_COMMIT:-}
|
||||||
container_name: proxy
|
container_name: proxy
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks: [theta-net]
|
networks: [theta-net]
|
||||||
@@ -103,6 +115,10 @@ services:
|
|||||||
- proxy-data:/data
|
- proxy-data:/data
|
||||||
- proxy-cache:/var/cache/nginx/proxy
|
- proxy-cache:/var/cache/nginx/proxy
|
||||||
- proxy-logs:/var/log/nginx
|
- proxy-logs:/var/log/nginx
|
||||||
|
# OPTIONAL, for strict LDAPS trust (see README "Security notes"): mount
|
||||||
|
# the SSO's self-signed cert into the proxy read-only, then set
|
||||||
|
# ldap.tlsOptions.ca=<path-below> in ./config/proxy-secrets.js.
|
||||||
|
# - ldap-certs:/etc/ssl/sso-ldap-certs:ro
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-fsS", "http://localhost:3000/health"]
|
test: ["CMD", "curl", "-fsS", "http://localhost:3000/health"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
title: theta-env
|
title: theta-env
|
||||||
description: A unified, one-command SSO Manager + OIDC proxy stack for home labs and small businesses
|
description: A unified, one-command SSO Manager + OIDC proxy stack for home labs and small businesses
|
||||||
theme: jekyll-theme-cayman
|
theme: jekyll-theme-cayman
|
||||||
show_downloads: true
|
show_downloads: false
|
||||||
github:
|
github:
|
||||||
repository_url: https://github.com/theta42/theta-env
|
repository_url: https://github.com/theta42/theta-env
|
||||||
zip_url: https://github.com/theta42/theta-env/archive/refs/heads/master.zip
|
zip_url: https://github.com/theta42/theta-env/archive/refs/heads/master.zip
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ theta-env is a **composition** repo: it builds the two existing projects from
|
|||||||
their git submodules and adds the glue that wires them together. It does not
|
their git submodules and adds the glue that wires them together. It does not
|
||||||
fork or patch them — both projects work unchanged on their own.
|
fork or patch them — both projects work unchanged on their own.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## The three repos
|
## The three repos
|
||||||
|
|
||||||
| Repo | Role |
|
| Repo | Role |
|
||||||
@@ -22,6 +24,8 @@ fork or patch them — both projects work unchanged on their own.
|
|||||||
The two projects are pinned as **git submodules**. `git clone --recursive`
|
The two projects are pinned as **git submodules**. `git clone --recursive`
|
||||||
fetches all three in one step; `git submodule update --remote` bumps them.
|
fetches all three in one step; `git submodule update --remote` bumps them.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## The two containers
|
## The two containers
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -68,6 +72,8 @@ redis instances is the no-source-patch path and is fine at this scale.
|
|||||||
| `3001` (sso) | localhost only | SSO web UI (first-run convenience; fronted by the proxy normally) |
|
| `3001` (sso) | localhost only | SSO web UI (first-run convenience; fronted by the proxy normally) |
|
||||||
| `389` (sso) | **no** | plain LDAP — internal only (app↔slapd over localhost) |
|
| `389` (sso) | **no** | plain LDAP — internal only (app↔slapd over localhost) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## The first-run bootstrap
|
## The first-run bootstrap
|
||||||
|
|
||||||
`./setup.sh` orchestrates first-run wiring; `bootstrap/bootstrap.js` does the
|
`./setup.sh` orchestrates first-run wiring; `bootstrap/bootstrap.js` does the
|
||||||
@@ -98,6 +104,18 @@ inputs from the bind-mounted `./config/sso-secrets.js` + `./config/proxy-secrets
|
|||||||
6. **Build + start the proxy**, wait for `/health`. The proxy entrypoint symlinks
|
6. **Build + start the proxy**, wait for `/health`. The proxy entrypoint symlinks
|
||||||
`./config/proxy-secrets.js` to `/app/conf/secrets.js`, so `@simpleworkjs/conf`
|
`./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.
|
(≥1.1.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
|
||||||
|
than the proxy's own HTTP API, since no authenticated session exists yet at
|
||||||
|
this point in the run. The proxy routes every hostname purely off a Host
|
||||||
|
record (`ops/nginx_conf/proxy.conf` has no default/self route), so without
|
||||||
|
this step neither URL resolves to anything. `<SSO_HOST>` targets
|
||||||
|
`sso-manager:3001` (the Docker service), `<PROXY_HOST>` targets
|
||||||
|
`127.0.0.1:3000` (the proxy's own management app, same container). Both
|
||||||
|
are created with `sso_enabled: false` — each app already gates its own
|
||||||
|
login, and SSO-gating the SSO's own login page would be circular. Skips a
|
||||||
|
host that already exists, so re-running `setup.sh` is a no-op here.
|
||||||
|
|
||||||
`setup.sh` then prints the first-admin login + the public URLs.
|
`setup.sh` then prints the first-admin login + the public URLs.
|
||||||
|
|
||||||
@@ -127,6 +145,8 @@ admin creds from `./config/sso-secrets.js` sidesteps that entirely, and going
|
|||||||
through the HTTP API for the OAuth client validates the whole admin login path
|
through the HTTP API for the OAuth client validates the whole admin login path
|
||||||
end-to-end.
|
end-to-end.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Idempotency
|
## Idempotency
|
||||||
|
|
||||||
Re-running `./setup.sh` converges to `./config/`:
|
Re-running `./setup.sh` converges to `./config/`:
|
||||||
@@ -139,6 +159,8 @@ Re-running `./setup.sh` converges to `./config/`:
|
|||||||
So `setup.sh` is safe to re-run after editing `./config/`, after a `docker
|
So `setup.sh` is safe to re-run after editing `./config/`, after a `docker
|
||||||
compose down`, or after restoring from backup.
|
compose down`, or after restoring from backup.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Backups and restore
|
## Backups and restore
|
||||||
|
|
||||||
`./setup.sh` auto-snapshots `./config/` + LDAP + both Redis to
|
`./setup.sh` auto-snapshots `./config/` + LDAP + both Redis to
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 118 KiB |
+56
-86
@@ -5,101 +5,71 @@ title: Home
|
|||||||
|
|
||||||
# theta-env
|
# theta-env
|
||||||
|
|
||||||
A single repo that runs the whole theta42 identity + access stack —
|
The whole theta42 identity + access stack in one repo, brought up with a
|
||||||
[SSO Manager](https://github.com/theta42/sso-manager-node) (OIDC provider + LDAP)
|
single command — for home labs and small businesses.
|
||||||
and the [theta42/proxy](https://github.com/theta42/proxy) (OIDC-protected reverse
|
|
||||||
proxy) — together, with **one command**, for home labs and small businesses.
|
|
||||||
|
|
||||||
It exists for people whose needs are met by these two projects and who want to
|
It wires together two projects that already work on their own —
|
||||||
run them "very simply." Each project still works **standalone**; this repo just
|
[SSO Manager](https://theta42.github.io/sso-manager-node/) (OIDC provider +
|
||||||
wires them together and automates the first-run glue.
|
LDAP directory) and [Proxy](https://theta42.github.io/proxy/) (an
|
||||||
|
OIDC-protected reverse proxy that can also look users up directly in LDAP) —
|
||||||
|
and automates the fiddly part: registering the proxy as an OIDC client of the
|
||||||
|
SSO and pointing it at the right LDAP directory, with hostnames and secrets
|
||||||
|
generated from one `setup.env`.
|
||||||
|
|
||||||
## Quick start
|
## Screenshots
|
||||||
|
|
||||||
|
The SSO Manager and the proxy it fronts, both stood up by one `./setup.sh` run:
|
||||||
|
|
||||||
|
<a href="images/sso-dashboard.png" target="_blank"><img src="images/sso-dashboard.png" alt="SSO Manager dashboard" width="49%"></a>
|
||||||
|
<a href="images/proxy-hosts.png" target="_blank"><img src="images/proxy-hosts.png" alt="Proxy host list" width="49%"></a>
|
||||||
|
|
||||||
|
*(click either screenshot to view full size)*
|
||||||
|
|
||||||
|
## Why this over running them separately
|
||||||
|
|
||||||
|
Each project works standalone, but they only become useful together once the
|
||||||
|
proxy is registered as an OIDC client of the SSO *and* pointed at the SSO's
|
||||||
|
LDAP directory — and the domain has to match across half a dozen config
|
||||||
|
fields, or logins silently fail. Doing that by hand is fiddly. `setup.sh`
|
||||||
|
asks for your domain once, generates both apps' config with it filled in
|
||||||
|
everywhere, registers the proxy as an OIDC client automatically, and
|
||||||
|
snapshots state before every rebuild.
|
||||||
|
|
||||||
|
## What you get
|
||||||
|
|
||||||
|
- **SSO Manager**, fronted by the proxy under TLS — manage users, groups,
|
||||||
|
and OAuth clients.
|
||||||
|
- **Proxy** — add the hosts you want to protect with OIDC login.
|
||||||
|
- **LDAPS** for legacy apps that bind directly.
|
||||||
|
- **Self-service API tokens** in both apps' UIs, for scripting/CI without a
|
||||||
|
browser session.
|
||||||
|
|
||||||
|
## Get it
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone --recursive https://github.com/theta42/theta-env.git
|
git clone --recursive https://github.com/theta42/theta-env.git
|
||||||
cd theta-env
|
cd theta-env
|
||||||
./setup.sh # generates ./config/ the first time — edit it, then re-run
|
cp setup.env.example setup.env # then edit setup.env: set CFG_DOMAIN to your domain
|
||||||
./setup.sh # builds + bootstraps + starts the stack
|
./setup.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
You need **Docker** + **Docker Compose**. `./setup.sh` is idempotent — re-run any
|
You need **Docker** + **Docker Compose**. `./setup.sh` is idempotent — re-run
|
||||||
time to converge the stack to `./config/`.
|
any time to converge the stack to `./config/`. For the full config reference,
|
||||||
|
architecture, and running each project standalone, see the
|
||||||
|
**[GitHub repository](https://github.com/theta42/theta-env)**.
|
||||||
|
|
||||||
See the [Quickstart Guide](quickstart.html) for a walkthrough of `./config/` and
|
## More docs
|
||||||
what `setup.sh` does, [Architecture](architecture.html) for how the pieces fit
|
|
||||||
together, and [Standalone](standalone.html) for running each project on its own.
|
|
||||||
|
|
||||||
## What you get
|
- **[Quickstart](quickstart.html)** — prerequisites and a step-by-step first run.
|
||||||
|
- **[Architecture](architecture.html)** — how the pieces fit together.
|
||||||
|
- **[Running each project standalone](standalone.html)** — using the SSO
|
||||||
|
Manager or the proxy on their own, without theta-env.
|
||||||
|
- **[Changelog](https://github.com/theta42/theta-env/blob/master/CHANGELOG.md)**
|
||||||
|
— what changed in each release.
|
||||||
|
|
||||||
- **SSO Manager** at `https://<SSO_HOST>` — log in as your first admin to manage
|
## Related projects
|
||||||
users, groups, and OAuth clients. Fronted by the proxy under TLS.
|
|
||||||
- **Proxy** at `https://<PROXY_HOST>` — add the Host records you want to protect
|
|
||||||
with OIDC login.
|
|
||||||
- **LDAPS** at `ldaps://<host>:636` — legacy apps can bind directly (admin or
|
|
||||||
the read-only `cn=ldapclient` service account the bootstrap creates).
|
|
||||||
- **API tokens** — both apps let any logged-in user mint self-service personal
|
|
||||||
access tokens (`Authorization: Bearer sso_…` / `prx_…`) to drive the management
|
|
||||||
API from scripts/CI without a browser session. A token authenticates as its
|
|
||||||
creator (carrying their permissions); mint/rotate/revoke under **API Tokens**
|
|
||||||
in each UI. See each submodule's DEPLOYMENT for the details.
|
|
||||||
|
|
||||||
## The `./config/` values you must set
|
- **[SSO Manager](https://theta42.github.io/sso-manager-node/)** — the OIDC
|
||||||
|
provider + LDAP directory this stack runs.
|
||||||
All config and secrets live in `./config/sso-secrets.js` +
|
- **[Proxy](https://theta42.github.io/proxy/)** — the reverse proxy this
|
||||||
`./config/proxy-secrets.js` (gitignored), generated by the first `./setup.sh`.
|
stack runs in front of it.
|
||||||
There is **no `.env`**. Set at least these in `./config/sso-secrets.js`:
|
|
||||||
|
|
||||||
| Key (in `sso-secrets.js`) | What it is |
|
|
||||||
|-----|------------|
|
|
||||||
| `stack.ldapBaseDn` | Directory base, e.g. `dc=lab,dc=local`. |
|
|
||||||
| `ldap.bindPassword` | LDAP root password (generated). **Back it up.** |
|
|
||||||
| `oauth.jwtSecret` | Signs the SSO's tokens (generated). **Back it up.** |
|
|
||||||
| `stack.ssoHost` | Public hostname the proxy serves the SSO UI at. |
|
|
||||||
| `stack.proxyHost` | Public hostname the proxy serves its own mgmt UI at. |
|
|
||||||
| `bootstrap.adminUid` / `bootstrap.adminPass` | Your first admin login. |
|
|
||||||
|
|
||||||
See `config.example/` for the full annotated shape (SMTP, LDAP cert CN, proxy
|
|
||||||
OIDC/LDAP/auth, …).
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
┌──────────────────────────────────────────────┐
|
|
||||||
│ your browser / apps │
|
|
||||||
└───────────────┬──────────────────────────────┘
|
|
||||||
│ https
|
|
||||||
┌─────────▼─────────┐
|
|
||||||
│ proxy │ OpenResty :80/:443/:4443
|
|
||||||
│ (OIDC + LDAP) │ mgmt app :3000 (localhost)
|
|
||||||
└─────────┬─────────┘ bundled redis
|
|
||||||
┌─────────────┼──────────────────────┐
|
|
||||||
│ ldaps:636 │ http:3001 (internal)│ OIDC token/userinfo
|
|
||||||
▼ ▼ │
|
|
||||||
┌──────────────────────────┐ │
|
|
||||||
│ sso-manager │◄────────────────┘
|
|
||||||
│ OIDC provider + OpenLDAP │ bundled redis
|
|
||||||
│ web UI :3001 (localhost) │
|
|
||||||
│ ldaps :636 (LAN clients) │
|
|
||||||
└───────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
The proxy is **both** an OIDC client of the SSO (for login) **and** a direct LDAP
|
|
||||||
client (for user lookups). See [Architecture](architecture.html) for the full
|
|
||||||
diagram + the first-run bootstrap flow.
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
- [Quickstart Guide](quickstart.html) — full walkthrough of `./config/` + `setup.sh`.
|
|
||||||
- [Architecture](architecture.html) — the 3-repo + submodule + 2-container
|
|
||||||
design, and how the bootstrap wires the proxy into a fresh SSO.
|
|
||||||
- [Standalone](standalone.html) — running SSO Manager or the proxy on its own.
|
|
||||||
|
|
||||||
## Community
|
|
||||||
|
|
||||||
- [GitHub Repository](https://github.com/theta42/theta-env)
|
|
||||||
- [Issue Tracker](https://github.com/theta42/theta-env/issues)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT License — see the repository for details.
|
|
||||||
|
|||||||
+32
-6
@@ -7,6 +7,8 @@ title: Quickstart
|
|||||||
|
|
||||||
[← Back to Home](index.html)
|
[← Back to Home](index.html)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- A Linux host with **Docker** + **Docker Compose** (the v2 plugin `docker
|
- A Linux host with **Docker** + **Docker Compose** (the v2 plugin `docker
|
||||||
@@ -18,6 +20,8 @@ title: Quickstart
|
|||||||
certs; otherwise the proxy serves a self-signed fallback (browsers warn —
|
certs; otherwise the proxy serves a self-signed fallback (browsers warn —
|
||||||
expected for LAN use).
|
expected for LAN use).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 1. Clone
|
## 1. Clone
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -32,24 +36,29 @@ step. If you forgot it:
|
|||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2. Configure `setup.env` (enter your domain once)
|
## 2. Configure `setup.env` (enter your domain once)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp setup.env.example setup.env
|
cp setup.env.example setup.env
|
||||||
$EDITOR setup.env # set CFG_BASE_DN to your domain, as a base DN
|
$EDITOR setup.env # set CFG_DOMAIN to your domain
|
||||||
```
|
```
|
||||||
|
|
||||||
Your domain is entered **once**, as the LDAP base DN. The SSO/proxy hostnames
|
Your domain is entered **once**, as a plain DNS domain. The SSO/proxy
|
||||||
default to `sso.<domain>` / `proxy.<domain>`, derived from it, so for most setups
|
hostnames default to `sso.<domain>` / `proxy.<domain>`, and the LDAP base DN
|
||||||
`CFG_BASE_DN` is the only value you set:
|
is built from it (any number of labels works — a domain like
|
||||||
|
`myhost.duckdns.org` becomes `dc=myhost,dc=duckdns,dc=org`), so for most
|
||||||
|
setups `CFG_DOMAIN` is the only value you set:
|
||||||
|
|
||||||
| `setup.env` key | Example | Notes |
|
| `setup.env` key | Example | Notes |
|
||||||
|-----|---------|-------|
|
|-----|---------|-------|
|
||||||
| `CFG_BASE_DN` | `dc=lab,dc=local` | your directory base — **required** |
|
| `CFG_DOMAIN` | `lab.local` | your domain — **required** |
|
||||||
| `CFG_SSO_HOST` | `sso.lab.local` | optional, defaults to `sso.<domain>` |
|
| `CFG_SSO_HOST` | `sso.lab.local` | optional, defaults to `sso.<domain>` |
|
||||||
| `CFG_PROXY_HOST` | `proxy.lab.local` | optional, defaults to `proxy.<domain>` |
|
| `CFG_PROXY_HOST` | `proxy.lab.local` | optional, defaults to `proxy.<domain>` |
|
||||||
| `CFG_ADMIN_UID` | `admin` | optional, defaults to `admin` |
|
| `CFG_ADMIN_UID` | `admin` | optional, defaults to `admin` |
|
||||||
| `CFG_ADMIN_EMAIL` | `admin@<proxyHost>` | optional |
|
| `CFG_ADMIN_EMAIL` | `admin@<proxyHost>` | optional |
|
||||||
|
| `CFG_BASE_DN` | `dc=lab,dc=local` | advanced: override the derived LDAP base DN |
|
||||||
|
|
||||||
`setup.env` is used **only on the first run** to generate `./config/`; after
|
`setup.env` is used **only on the first run** to generate `./config/`; after
|
||||||
that `./config/*.js` are operator-owned and `setup.env` is ignored. Secrets
|
that `./config/*.js` are operator-owned and `setup.env` is ignored. Secrets
|
||||||
@@ -63,6 +72,8 @@ file shape.
|
|||||||
> exist, `./setup.sh` migrates them into `./config/` preserving your existing
|
> exist, `./setup.sh` migrates them into `./config/` preserving your existing
|
||||||
> secrets — no need to write a `setup.env`.
|
> secrets — no need to write a `setup.env`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 3. Run
|
## 3. Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -81,11 +92,16 @@ What happens:
|
|||||||
service account, your first admin, and the proxy's OAuth client, and writes
|
service account, your first admin, and the proxy's OAuth client, and writes
|
||||||
the generated client id + secret into `./config/proxy-secrets.js`.
|
the generated client id + secret into `./config/proxy-secrets.js`.
|
||||||
4. Builds + starts **proxy**, waits for `/health`.
|
4. Builds + starts **proxy**, waits for `/health`.
|
||||||
5. Prints your first-admin login + the public URLs.
|
5. Registers `<SSO_HOST>` and `<PROXY_HOST>` as Host records in the proxy —
|
||||||
|
every hostname the proxy serves, including its own UI and the SSO's,
|
||||||
|
needs one of these or it 404s. Idempotent.
|
||||||
|
6. Prints your first-admin login + the public URLs.
|
||||||
|
|
||||||
The first run builds two Docker images (a few minutes). Subsequent runs are
|
The first run builds two Docker images (a few minutes). Subsequent runs are
|
||||||
fast.
|
fast.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 4. Point DNS at the host
|
## 4. Point DNS at the host
|
||||||
|
|
||||||
`stack.ssoHost` and `stack.proxyHost` (from `./config/sso-secrets.js`) must
|
`stack.ssoHost` and `stack.proxyHost` (from `./config/sso-secrets.js`) must
|
||||||
@@ -98,6 +114,8 @@ echo "127.0.0.1 sso.lab.local proxy.lab.local" | sudo tee -a /etc/hosts
|
|||||||
(The proxy needs port 80 reachable for Let's Encrypt; on a LAN without that it
|
(The proxy needs port 80 reachable for Let's Encrypt; on a LAN without that it
|
||||||
serves a self-signed cert — browsers will warn, which is fine for home-lab use.)
|
serves a self-signed cert — browsers will warn, which is fine for home-lab use.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 5. Log in
|
## 5. Log in
|
||||||
|
|
||||||
Open `https://<SSO_HOST>` and log in as your bootstrap admin
|
Open `https://<SSO_HOST>` and log in as your bootstrap admin
|
||||||
@@ -110,6 +128,8 @@ it). Add the Host records you want to protect with OIDC.
|
|||||||
First-run fallbacks (if DNS/TLS isn't ready yet): SSO UI at
|
First-run fallbacks (if DNS/TLS isn't ready yet): SSO UI at
|
||||||
`http://127.0.0.1:3001`, proxy UI at `http://127.0.0.1:3000`.
|
`http://127.0.0.1:3001`, proxy UI at `http://127.0.0.1:3000`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Re-running
|
## Re-running
|
||||||
|
|
||||||
`./setup.sh` is **idempotent** — safe to re-run after editing `./config/`, after
|
`./setup.sh` is **idempotent** — safe to re-run after editing `./config/`, after
|
||||||
@@ -118,6 +138,8 @@ then converges the stack to your `./config/` values (LDAP service account + admi
|
|||||||
passwords are reset to the config; the OAuth client is kept if `proxy-secrets.js`
|
passwords are reset to the config; the OAuth client is kept if `proxy-secrets.js`
|
||||||
already holds its creds).
|
already holds its creds).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Direct LDAP for legacy apps
|
## Direct LDAP for legacy apps
|
||||||
|
|
||||||
Legacy apps bind LDAP directly over LDAPS:
|
Legacy apps bind LDAP directly over LDAPS:
|
||||||
@@ -131,6 +153,8 @@ ldapsearch -x -H ldaps://<host>:636 \
|
|||||||
Use the `cn=ldapclient` service account (read-only, the bootstrap created it)
|
Use the `cn=ldapclient` service account (read-only, the bootstrap created it)
|
||||||
or the admin DN. Use LDAPS (636), not plain LDAP.
|
or the admin DN. Use LDAPS (636), not plain LDAP.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Backups and restore
|
## Backups and restore
|
||||||
|
|
||||||
`./setup.sh` auto-snapshots `./config/` + LDAP + both Redis to `./backups/<ts>/`
|
`./setup.sh` auto-snapshots `./config/` + LDAP + both Redis to `./backups/<ts>/`
|
||||||
@@ -145,6 +169,8 @@ docker compose exec sso-manager slapcat -f /etc/openldap/slapd.conf \
|
|||||||
-b "<base>" > backup-$(date +%F).ldif
|
-b "<base>" > backup-$(date +%F).ldif
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
- Add users / groups in the SSO UI.
|
- Add users / groups in the SSO UI.
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ theta-env composes the two projects but doesn't fork them — both work on their
|
|||||||
own. The submodules in this repo are normal clones; you can also clone them
|
own. The submodules in this repo are normal clones; you can also clone them
|
||||||
directly from GitHub.
|
directly from GitHub.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## SSO Manager alone
|
## SSO Manager alone
|
||||||
|
|
||||||
The all-in-one image (`Dockerfile.openldap`) bundles the app + OpenLDAP + Redis:
|
The all-in-one image (`Dockerfile.openldap`) bundles the app + OpenLDAP + Redis:
|
||||||
@@ -46,6 +48,8 @@ sudo systemctl enable --now sso-manager
|
|||||||
Idempotent — re-run to update. See the SSO Manager
|
Idempotent — re-run to update. See the SSO Manager
|
||||||
[deployment guide](https://theta42.github.io/sso-manager-node/deployment.html).
|
[deployment guide](https://theta42.github.io/sso-manager-node/deployment.html).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Proxy alone
|
## Proxy alone
|
||||||
|
|
||||||
The all-in-one image (`Dockerfile`) bundles OpenResty + the Node app + Redis:
|
The all-in-one image (`Dockerfile`) bundles OpenResty + the Node app + Redis:
|
||||||
@@ -70,6 +74,33 @@ so `app_*` would silently override your file.
|
|||||||
Requires `@simpleworkjs/conf` >= 1.1.0. Full reference:
|
Requires `@simpleworkjs/conf` >= 1.1.0. Full reference:
|
||||||
[proxy deployment docs](https://theta42.github.io/proxy/docker.html).
|
[proxy deployment docs](https://theta42.github.io/proxy/docker.html).
|
||||||
|
|
||||||
|
### The `auth.adminUsers` anti-lockout account
|
||||||
|
|
||||||
|
Both `setup.sh` and `config.example/proxy-secrets.js.example` write
|
||||||
|
`auth.adminUsers: ['proxyadmin2']` into `proxy-secrets.js`. This is a
|
||||||
|
**local, config-driven admin bypass** — the proxy grants full admin rights to
|
||||||
|
any logged-in OIDC user whose username (the `preferred_username` claim from
|
||||||
|
the SSO) matches an entry in `auth.adminUsers`, regardless of their LDAP group
|
||||||
|
membership (see `proxy/nodejs/utils/roles.js`, `resolveEffective()`). It exists
|
||||||
|
so an operator can't lock themselves out of the proxy mgmt UI if the SSO's
|
||||||
|
`app_sso_admin` group is ever misconfigured, deleted, or otherwise broken.
|
||||||
|
|
||||||
|
It is **not** derived from any `setup.env` value, and it does **not** create a
|
||||||
|
user by itself — the name is only a username match. To actually use the
|
||||||
|
bypass, create a user with uid `proxyadmin2` in the SSO (it does not need to
|
||||||
|
be in `app_sso_admin` or any other group) and log in through the proxy as that
|
||||||
|
user.
|
||||||
|
|
||||||
|
To change or disable it, edit `auth.adminUsers` directly in
|
||||||
|
`./config/proxy-secrets.js` after the first `./setup.sh` run (re-running
|
||||||
|
`setup.sh` will not overwrite an existing `proxy-secrets.js`):
|
||||||
|
|
||||||
|
- **Rename** it to a less guessable username: `adminUsers: ['your-break-glass-uid']`.
|
||||||
|
- **Add more** anti-lockout accounts: `adminUsers: ['proxyadmin2', 'another-admin']`.
|
||||||
|
- **Disable** it entirely: `adminUsers: []` (global admin then comes only from
|
||||||
|
`auth.adminGroups` membership — make sure at least one real admin group is
|
||||||
|
reachable before doing this).
|
||||||
|
|
||||||
### Bare metal
|
### Bare metal
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -80,6 +111,8 @@ See the proxy
|
|||||||
[Docker guide](https://theta42.github.io/proxy/docker.html) /
|
[Docker guide](https://theta42.github.io/proxy/docker.html) /
|
||||||
[installation guide](https://theta42.github.io/proxy/installation.html).
|
[installation guide](https://theta42.github.io/proxy/installation.html).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Mixing and matching
|
## Mixing and matching
|
||||||
|
|
||||||
theta-env isn't required to use the two together — the four wiring steps are
|
theta-env isn't required to use the two together — the four wiring steps are
|
||||||
|
|||||||
+1
-1
Submodule proxy updated: 3df7d8c5cb...1c29ba7206
+21
-11
@@ -7,24 +7,30 @@
|
|||||||
# (edit them directly; setup.env is ignored on later runs).
|
# (edit them directly; setup.env is ignored on later runs).
|
||||||
#
|
#
|
||||||
# cp setup.env.example setup.env
|
# cp setup.env.example setup.env
|
||||||
# $EDITOR setup.env # set CFG_BASE_DN below to your domain
|
# $EDITOR setup.env # set CFG_DOMAIN below to your domain
|
||||||
# ./setup.sh # generates ./config/ and builds the stack
|
# ./setup.sh # generates ./config/ and builds the stack
|
||||||
#
|
#
|
||||||
# Copying this file to setup.env (gitignored) keeps your domain out of git.
|
# Copying this file to setup.env (gitignored) keeps your domain out of git.
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
# Your domain, as an LDAP base DN. THIS IS THE ONE PLACE THE DOMAIN IS ENTERED.
|
# Your domain. THIS IS THE ONE PLACE THE DOMAIN IS ENTERED. Everything else
|
||||||
# Everything else derives from it: the SSO/proxy hostnames default to
|
# derives from it: the SSO/proxy hostnames default to sso.<domain> /
|
||||||
# sso.<domain> / proxy.<domain>, and the LDAP DNs are cn=admin,<dn>,
|
# proxy.<domain>, and the LDAP base DN is built from it (example.com becomes
|
||||||
# ou=people,<dn>, ou=groups,<dn>. Required — setup.sh refuses to run without it.
|
# dc=example,dc=com; a 3-label domain like myhost.duckdns.org becomes
|
||||||
CFG_BASE_DN=dc=example,dc=com
|
# dc=myhost,dc=duckdns,dc=org — any number of labels works). Required —
|
||||||
|
# setup.sh refuses to run without it.
|
||||||
|
CFG_DOMAIN=example.com
|
||||||
|
|
||||||
# Public hostnames. Optional — default to sso.<domain> / proxy.<domain> derived
|
# Public hostnames. Optional — default to sso.<domain> / proxy.<domain> derived
|
||||||
# from CFG_BASE_DN above. Uncomment and set only if your hostnames differ
|
# from CFG_DOMAIN above. Uncomment and set only if your hostnames differ
|
||||||
# (e.g. a different subdomain, or the domain isn't the bare apex):
|
# (e.g. a different subdomain, or the domain isn't the bare apex):
|
||||||
#CFG_SSO_HOST=sso.example.com
|
#CFG_SSO_HOST=sso.example.com
|
||||||
#CFG_PROXY_HOST=proxy.example.com
|
#CFG_PROXY_HOST=proxy.example.com
|
||||||
|
|
||||||
|
# Advanced: override the derived LDAP base DN directly (e.g. to namespace
|
||||||
|
# under an OU-style prefix). Leave unset to use the DN built from CFG_DOMAIN:
|
||||||
|
#CFG_BASE_DN=dc=example,dc=com
|
||||||
|
|
||||||
# Optional — sensible defaults if left blank:
|
# Optional — sensible defaults if left blank:
|
||||||
#CFG_ORG=SSO Manager # app display name + outbound email org
|
#CFG_ORG=SSO Manager # app display name + outbound email org
|
||||||
#CFG_ADMIN_UID=admin # initial SSO admin username
|
#CFG_ADMIN_UID=admin # initial SSO admin username
|
||||||
@@ -39,7 +45,11 @@ CFG_BASE_DN=dc=example,dc=com
|
|||||||
#CFG_SMTP_FROM=SSO Manager <noreply@example.com>
|
#CFG_SMTP_FROM=SSO Manager <noreply@example.com>
|
||||||
|
|
||||||
# ── DO NOT put secrets here ──────────────────────────────────────────────────
|
# ── DO NOT put secrets here ──────────────────────────────────────────────────
|
||||||
# The LDAP admin password, JWT secret, admin password, and LDAP service-account
|
# The LDAP admin password, JWT secret, admin password, LDAP service-account
|
||||||
# password are GENERATED (random) into ./config/sso-secrets.js on first run.
|
# password, and the proxy's local admin password are all GENERATED (random)
|
||||||
# Change them later by editing ./config/sso-secrets.js directly. Do NOT set
|
# into ./config/sso-secrets.js + ./config/proxy-secrets.js on first run.
|
||||||
# CFG_LDAP_ADMIN_PASS / CFG_JWT_SECRET / CFG_ADMIN_PASS / CFG_SVC_PASS here.
|
# Change them later by editing those files directly (the proxy's local admin
|
||||||
|
# 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.
|
||||||
@@ -3,26 +3,36 @@
|
|||||||
# theta-env setup — one-command bring-up of the unified SSO Manager + Proxy stack.
|
# theta-env setup — one-command bring-up of the unified SSO Manager + Proxy stack.
|
||||||
#
|
#
|
||||||
# git clone --recursive <theta-env> && cd theta-env
|
# git clone --recursive <theta-env> && cd theta-env
|
||||||
# cp setup.env.example setup.env # set CFG_BASE_DN to your domain (once)
|
# cp setup.env.example setup.env # set CFG_DOMAIN to your domain (once)
|
||||||
# ./setup.sh # first run: generates ./config/ from setup.env, builds + bootstraps + starts
|
# ./setup.sh # first run: generates ./config/ from setup.env, builds + bootstraps + starts
|
||||||
# ./setup.sh # later runs: rebuilds + bootstraps + starts (config left untouched)
|
# ./setup.sh # later runs: rebuilds + bootstraps + starts (config left untouched)
|
||||||
#
|
#
|
||||||
# Idempotent: safe to re-run. It manages config in a bind-mounted ./config/
|
# Idempotent: safe to re-run. It pulls its own latest version, updates the two
|
||||||
# directory (sso-secrets.js + proxy-secrets.js — NO .env / proxy.env), snapshots
|
# submodules, manages config in a bind-mounted ./config/ directory
|
||||||
# state before rebuild, (re)starts the SSO Manager, runs the bootstrap (which
|
# (sso-secrets.js + proxy-secrets.js — NO .env / proxy.env), snapshots state
|
||||||
|
# before rebuild, (re)starts the SSO Manager, runs the bootstrap (which
|
||||||
# converges the LDAP service account / first admin / OAuth client to the ./config
|
# converges the LDAP service account / first admin / OAuth client to the ./config
|
||||||
# values and writes the generated OAuth client creds into proxy-secrets.js),
|
# values and writes the generated OAuth client creds into proxy-secrets.js),
|
||||||
# then starts the proxy.
|
# then starts the proxy and registers the SSO's + proxy's own hostnames as
|
||||||
|
# Host records in it (otherwise the proxy has no route for either). A single
|
||||||
|
# `./setup.sh` run is enough to bring an existing deployment fully up to date —
|
||||||
|
# no manual `git pull` needed first.
|
||||||
#
|
#
|
||||||
# What it does, in order:
|
# What it does, in order:
|
||||||
|
# 0. Pull theta-env's own latest commit (fast-forward only) and, if it
|
||||||
|
# moved, re-exec so the rest of this run uses the new script. Never
|
||||||
|
# blocks the run — skips silently with no upstream, warns and continues
|
||||||
|
# on any other pull failure (offline, local changes). Skip with
|
||||||
|
# SKIP_SELF_UPDATE=1.
|
||||||
# 1. Update the git submodules to the latest of their tracked remote branch
|
# 1. Update the git submodules to the latest of their tracked remote branch
|
||||||
# (so each run builds the newest sso-manager-node + proxy). Skip with
|
# (so each run builds the newest sso-manager-node + proxy). Skip with
|
||||||
# SKIP_SUBMODULE_UPDATE=1.
|
# SKIP_SUBMODULE_UPDATE=1.
|
||||||
# 2. ensure_config: create ./config/sso-secrets.js + proxy-secrets.js if
|
# 2. ensure_config: create ./config/sso-secrets.js + proxy-secrets.js if
|
||||||
# missing. On a fresh clone the domain/hosts are read from ./setup.env
|
# missing. On a fresh clone the domain/hosts are read from ./setup.env
|
||||||
# (the one place the domain is entered, as the LDAP base DN) and both
|
# (the one place the domain is entered, as a plain DNS domain — the LDAP
|
||||||
# files are generated with that domain filled in everywhere + random
|
# base DN is derived from it) and both files are generated with that
|
||||||
# secrets, then the run proceeds to build (no edit-and-re-run step). On
|
# domain filled in everywhere + random secrets, then the run proceeds to
|
||||||
|
# build (no edit-and-re-run step). On
|
||||||
# an existing deployment with .env/proxy.env, the secrets are migrated
|
# an existing deployment with .env/proxy.env, the secrets are migrated
|
||||||
# (preserved) into ./config. If ./config already exists it is left
|
# (preserved) into ./config. If ./config already exists it is left
|
||||||
# untouched (the operator owns it; setup.env is ignored).
|
# untouched (the operator owns it; setup.env is ignored).
|
||||||
@@ -35,7 +45,11 @@
|
|||||||
# writes the OAuth client creds into ./config/proxy-secrets.js; prints
|
# writes the OAuth client creds into ./config/proxy-secrets.js; prints
|
||||||
# CLIENT_ID / CLIENT_SECRET / ALREADY_CONFIGURED on stdout.
|
# CLIENT_ID / CLIENT_SECRET / ALREADY_CONFIGURED on stdout.
|
||||||
# 6. docker compose up -d --build proxy; wait for /health.
|
# 6. docker compose up -d --build proxy; wait for /health.
|
||||||
# 7. Print the first-admin login + the public URLs.
|
# 7. Register <SSO_HOST> and <PROXY_HOST> as Host records in the proxy (via
|
||||||
|
# `docker compose exec proxy node`, calling the proxy's Host model
|
||||||
|
# directly) so the proxy actually routes those hostnames somewhere —
|
||||||
|
# nothing else creates them. Idempotent; skips a host that already exists.
|
||||||
|
# 8. Print the first-admin login + the public URLs.
|
||||||
#
|
#
|
||||||
# Requires: git, docker + docker compose (v1 standalone or v2 plugin).
|
# Requires: git, docker + docker compose (v1 standalone or v2 plugin).
|
||||||
|
|
||||||
@@ -106,15 +120,73 @@ parse_kv_file() {
|
|||||||
done < "$file"
|
done < "$file"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── 1. Update submodules to latest, verify build contexts ─────────────────────
|
# ── 0. Self-update: pull theta-env itself, then restart with the new version ──
|
||||||
|
# Step 1 below only refreshes the proxy/sso-manager-node submodules — it never
|
||||||
|
# updates setup.sh or this repo's own files. Pull the current branch's
|
||||||
|
# upstream (fast-forward only) before anything else, and if it moved, re-exec
|
||||||
|
# so the rest of THIS run uses the freshly-pulled script rather than the copy
|
||||||
|
# already read into memory. Never blocks the run: skips silently if this
|
||||||
|
# isn't a git checkout, is on a detached HEAD, or has no upstream configured;
|
||||||
|
# warns (but continues on the current checkout) if the pull fails for any
|
||||||
|
# other reason (offline, local changes that prevent a fast-forward). Skip
|
||||||
|
# entirely with SKIP_SELF_UPDATE=1.
|
||||||
|
if [[ "${SKIP_SELF_UPDATE:-0}" != "1" && "${THETA_ENV_REEXECED:-0}" != "1" ]] \
|
||||||
|
&& command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1 \
|
||||||
|
&& git rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
BEFORE_REV="$(git rev-parse HEAD)"
|
||||||
|
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..."
|
||||||
|
THETA_ENV_REEXECED=1 exec "$0" "$@"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not fast-forward theta-env to the latest upstream (offline, or local changes) — continuing with the current checkout."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 1. Update submodules to their latest release tag, verify build contexts ───
|
||||||
|
# Submodules track release tags (vX.Y.Z), not the tip of master -- so
|
||||||
|
# "update" means "move to the newest tag", not "move to the newest commit".
|
||||||
|
# `git submodule update --init --recursive` (no --remote) only clones a
|
||||||
|
# missing submodule at its currently-pinned commit; it never advances it on
|
||||||
|
# its own, so the per-submodule tag resolution below is what actually moves
|
||||||
|
# proxy/sso-manager-node forward.
|
||||||
if [[ "${SKIP_SUBMODULE_UPDATE:-0}" != "1" ]]; then
|
if [[ "${SKIP_SUBMODULE_UPDATE:-0}" != "1" ]]; then
|
||||||
if ! command -v git >/dev/null 2>&1; then
|
if ! command -v git >/dev/null 2>&1; then
|
||||||
die "git not found. Install git, or set SKIP_SUBMODULE_UPDATE=1 to build the pinned submodule commits."
|
die "git not found. Install git, or set SKIP_SUBMODULE_UPDATE=1 to build the pinned submodule commits."
|
||||||
fi
|
fi
|
||||||
info "Updating submodules to latest (sso-manager-node, proxy)..."
|
if ! git submodule update --init --recursive 2>&1; then
|
||||||
if ! git submodule update --init --remote --recursive 2>&1; then
|
die "git submodule update --init failed. Run manually: git submodule update --init --recursive"
|
||||||
warn "git submodule update failed (offline?) — continuing with the currently checked-out code."
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
info "Updating submodules to their latest release tag (sso-manager-node, proxy)..."
|
||||||
|
for sm in sso-manager-node proxy; do
|
||||||
|
[[ -d "$sm" ]] || continue
|
||||||
|
before_rev="$(git -C "$sm" rev-parse HEAD 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if ! git -C "$sm" fetch --tags -q 2>&1; then
|
||||||
|
warn " ${sm}: could not fetch tags (offline?) — staying on the current pin."
|
||||||
|
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."
|
||||||
|
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."
|
||||||
|
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})"
|
||||||
|
fi
|
||||||
|
done
|
||||||
else
|
else
|
||||||
info "Skipping submodule update (SKIP_SUBMODULE_UPDATE=1)."
|
info "Skipping submodule update (SKIP_SUBMODULE_UPDATE=1)."
|
||||||
fi
|
fi
|
||||||
@@ -125,11 +197,22 @@ fi
|
|||||||
|| die "proxy/Dockerfile missing. Run: git submodule update --init --recursive"
|
|| die "proxy/Dockerfile missing. Run: git submodule update --init --recursive"
|
||||||
|
|
||||||
# ── 2. ensure_config ──────────────────────────────────────────────────────────
|
# ── 2. ensure_config ──────────────────────────────────────────────────────────
|
||||||
# Derive a DNS domain from a base DN (dc=foo,dc=bar -> foo.bar).
|
# Derive a DNS domain from a base DN (dc=foo,dc=bar -> foo.bar). Only used to
|
||||||
|
# read domain back out of a base DN set directly (advanced override, or an
|
||||||
|
# old setup.env / migrated .env) — the normal path is dn_from_domain below.
|
||||||
domain_from_dn() {
|
domain_from_dn() {
|
||||||
echo "$1" | sed 's/^dc=//; s/,dc=/./g'
|
echo "$1" | sed 's/^dc=//; s/,dc=/./g'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Derive an LDAP base DN from a DNS domain (foo.bar -> dc=foo,dc=bar). This is
|
||||||
|
# the normal path: operators enter a plain domain in setup.env (CFG_DOMAIN),
|
||||||
|
# and the base DN is built from it, however many labels it has (a DuckDNS
|
||||||
|
# domain like foo.duckdns.org becomes dc=foo,dc=duckdns,dc=org — LDAP doesn't
|
||||||
|
# care how many dc= components there are).
|
||||||
|
dn_from_domain() {
|
||||||
|
echo "dc=$1" | sed 's/\./,dc=/g'
|
||||||
|
}
|
||||||
|
|
||||||
# Write ./config/sso-secrets.js from the CFG_* shell vars.
|
# Write ./config/sso-secrets.js from the CFG_* shell vars.
|
||||||
write_sso_secrets() {
|
write_sso_secrets() {
|
||||||
local dn="$CFG_BASE_DN" domain="$CFG_DOMAIN"
|
local dn="$CFG_BASE_DN" domain="$CFG_DOMAIN"
|
||||||
@@ -221,6 +304,10 @@ module.exports = {
|
|||||||
adminGroups: ['app_sso_admin'],
|
adminGroups: ['app_sso_admin'],
|
||||||
adminUsers: ['proxyadmin2'],
|
adminUsers: ['proxyadmin2'],
|
||||||
groupRoleMap: {},
|
groupRoleMap: {},
|
||||||
|
// Initial password for the local anti-lockout admin (proxyadmin2) —
|
||||||
|
// only read by the proxy the first time that account is created;
|
||||||
|
// changing it here later has no effect on an already-created account.
|
||||||
|
localAdminPass: $(js_str "$CFG_PROXY_ADMIN_PASS"),
|
||||||
},
|
},
|
||||||
stack: {
|
stack: {
|
||||||
ssoHost: $(js_str "$CFG_SSO_HOST"),
|
ssoHost: $(js_str "$CFG_SSO_HOST"),
|
||||||
@@ -237,8 +324,9 @@ ensure_config() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# First run: read the domain/hosts from ./setup.env — the ONE place the
|
# First run: read the domain/hosts from ./setup.env — the ONE place the
|
||||||
# domain is entered, as the LDAP base DN (e.g. dc=718it,dc=biz). Hostnames
|
# domain is entered (e.g. 718it.biz), as a plain DNS domain; the LDAP base
|
||||||
# default to sso.<domain> / proxy.<domain>, derived from it. setup.env is
|
# DN is derived from it (dc=718it,dc=biz). Hostnames default to
|
||||||
|
# sso.<domain> / proxy.<domain>, also derived from it. setup.env is
|
||||||
# used ONLY on first run; once ./config/*.js exist they are operator-owned
|
# used ONLY on first run; once ./config/*.js exist they are operator-owned
|
||||||
# and setup.env is ignored. Falls back to legacy .env/proxy.env migration
|
# and setup.env is ignored. Falls back to legacy .env/proxy.env migration
|
||||||
# below for existing deployments.
|
# below for existing deployments.
|
||||||
@@ -265,6 +353,7 @@ ensure_config() {
|
|||||||
CFG_JWT_SECRET="${CFG_JWT_SECRET:-}"
|
CFG_JWT_SECRET="${CFG_JWT_SECRET:-}"
|
||||||
CFG_ADMIN_PASS="${CFG_ADMIN_PASS:-}"
|
CFG_ADMIN_PASS="${CFG_ADMIN_PASS:-}"
|
||||||
CFG_SVC_PASS="${CFG_SVC_PASS:-}"
|
CFG_SVC_PASS="${CFG_SVC_PASS:-}"
|
||||||
|
CFG_PROXY_ADMIN_PASS="${CFG_PROXY_ADMIN_PASS:-}"
|
||||||
|
|
||||||
# ── One-time migration from .env / proxy.env (existing deployments) ──
|
# ── One-time migration from .env / proxy.env (existing deployments) ──
|
||||||
# Preserve the operator's existing secrets so the running deployment keeps
|
# Preserve the operator's existing secrets so the running deployment keeps
|
||||||
@@ -304,11 +393,15 @@ ensure_config() {
|
|||||||
migrated=1
|
migrated=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Derive everything from the base DN — the one domain value. No example.com
|
# Derive everything from the domain — the one value operators enter. No
|
||||||
# defaults: a blank base DN means first-run setup hasn't been done yet.
|
# example.com defaults: a blank domain means first-run setup hasn't been
|
||||||
[[ -n "$CFG_BASE_DN" ]] \
|
# done yet. CFG_BASE_DN can still be set directly (setup.env or a migrated
|
||||||
|| die "First run: 'cp setup.env.example setup.env', set CFG_BASE_DN to your domain (e.g. dc=718it,dc=biz), then re-run ./setup.sh"
|
# .env) to override the derived DN or to read the domain back out of an
|
||||||
CFG_DOMAIN="${CFG_DOMAIN:-$(domain_from_dn "$CFG_BASE_DN")}"
|
# old-style DN-first setup.env; if not, it's built from CFG_DOMAIN.
|
||||||
|
CFG_DOMAIN="${CFG_DOMAIN:-$([[ -n "$CFG_BASE_DN" ]] && domain_from_dn "$CFG_BASE_DN" || true)}"
|
||||||
|
[[ -n "$CFG_DOMAIN" ]] \
|
||||||
|
|| die "First run: 'cp setup.env.example setup.env', set CFG_DOMAIN to your domain (e.g. example.com), then re-run ./setup.sh"
|
||||||
|
CFG_BASE_DN="${CFG_BASE_DN:-$(dn_from_domain "$CFG_DOMAIN")}"
|
||||||
CFG_SSO_HOST="${CFG_SSO_HOST:-sso.$CFG_DOMAIN}"
|
CFG_SSO_HOST="${CFG_SSO_HOST:-sso.$CFG_DOMAIN}"
|
||||||
CFG_PROXY_HOST="${CFG_PROXY_HOST:-proxy.$CFG_DOMAIN}"
|
CFG_PROXY_HOST="${CFG_PROXY_HOST:-proxy.$CFG_DOMAIN}"
|
||||||
CFG_ORG="${CFG_ORG:-SSO Manager}"
|
CFG_ORG="${CFG_ORG:-SSO Manager}"
|
||||||
@@ -323,6 +416,7 @@ ensure_config() {
|
|||||||
CFG_JWT_SECRET="${CFG_JWT_SECRET:-$(rand_hex 32)}"
|
CFG_JWT_SECRET="${CFG_JWT_SECRET:-$(rand_hex 32)}"
|
||||||
CFG_ADMIN_PASS="${CFG_ADMIN_PASS:-$(rand_hex 16)}"
|
CFG_ADMIN_PASS="${CFG_ADMIN_PASS:-$(rand_hex 16)}"
|
||||||
CFG_SVC_PASS="${CFG_SVC_PASS:-$(rand_hex 16)}"
|
CFG_SVC_PASS="${CFG_SVC_PASS:-$(rand_hex 16)}"
|
||||||
|
CFG_PROXY_ADMIN_PASS="${CFG_PROXY_ADMIN_PASS:-$(rand_hex 16)}"
|
||||||
|
|
||||||
mkdir -p "$CONFIG_DIR" && chmod 700 "$CONFIG_DIR"
|
mkdir -p "$CONFIG_DIR" && chmod 700 "$CONFIG_DIR"
|
||||||
write_sso_secrets
|
write_sso_secrets
|
||||||
@@ -473,6 +567,12 @@ backup_before_rebuild() {
|
|||||||
backup_before_rebuild
|
backup_before_rebuild
|
||||||
|
|
||||||
# ── 4. Start SSO Manager, wait for health ─────────────────────────────────────
|
# ── 4. Start SSO Manager, wait for health ─────────────────────────────────────
|
||||||
|
# SSO_GIT_COMMIT: sso-manager-node is a git submodule here, so its .git is a
|
||||||
|
# pointer file (not a real repo) -- the image can't resolve its own commit
|
||||||
|
# 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)"
|
||||||
info "Building + starting sso-manager (first run builds the image; this takes a while)..."
|
info "Building + starting sso-manager (first run builds the image; this takes a while)..."
|
||||||
"${COMPOSE[@]}" up -d --build sso-manager
|
"${COMPOSE[@]}" up -d --build sso-manager
|
||||||
|
|
||||||
@@ -493,6 +593,8 @@ done
|
|||||||
read_config_kv() {
|
read_config_kv() {
|
||||||
"${COMPOSE[@]}" exec -T sso-manager node -e '
|
"${COMPOSE[@]}" exec -T sso-manager node -e '
|
||||||
const c = require("/config/sso-secrets.js");
|
const c = require("/config/sso-secrets.js");
|
||||||
|
let p = {};
|
||||||
|
try { p = require("/config/proxy-secrets.js"); } catch (_) {}
|
||||||
const o = {
|
const o = {
|
||||||
SSO_HOST: (c.stack && c.stack.ssoHost) || "",
|
SSO_HOST: (c.stack && c.stack.ssoHost) || "",
|
||||||
PROXY_HOST: (c.stack && c.stack.proxyHost) || "",
|
PROXY_HOST: (c.stack && c.stack.proxyHost) || "",
|
||||||
@@ -500,6 +602,7 @@ read_config_kv() {
|
|||||||
ORG_NAME: c.name || "",
|
ORG_NAME: c.name || "",
|
||||||
ADMIN_UID: (c.bootstrap && c.bootstrap.adminUid) || "",
|
ADMIN_UID: (c.bootstrap && c.bootstrap.adminUid) || "",
|
||||||
ADMIN_PASS: (c.bootstrap && c.bootstrap.adminPass) || "",
|
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]));
|
for (const k in o) console.log(k + "=" + (o[k] == null ? "" : o[k]));
|
||||||
' 2>/dev/null
|
' 2>/dev/null
|
||||||
@@ -510,6 +613,7 @@ SSO_HOST="$(cfgval SSO_HOST)"
|
|||||||
PROXY_HOST="$(cfgval PROXY_HOST)"
|
PROXY_HOST="$(cfgval PROXY_HOST)"
|
||||||
ADMIN_UID="$(cfgval ADMIN_UID)"
|
ADMIN_UID="$(cfgval ADMIN_UID)"
|
||||||
ADMIN_PASS="$(cfgval ADMIN_PASS)"
|
ADMIN_PASS="$(cfgval ADMIN_PASS)"
|
||||||
|
PROXY_LOCAL_ADMIN_PASS="$(cfgval PROXY_LOCAL_ADMIN_PASS)"
|
||||||
|
|
||||||
info "Stack config:"
|
info "Stack config:"
|
||||||
info " SSO host: https://${SSO_HOST}"
|
info " SSO host: https://${SSO_HOST}"
|
||||||
@@ -536,6 +640,8 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# ── 6. Start the proxy, wait for health ───────────────────────────────────────
|
# ── 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)"
|
||||||
info "Building + starting proxy (first run builds the image; this takes a while)..."
|
info "Building + starting proxy (first run builds the image; this takes a while)..."
|
||||||
"${COMPOSE[@]}" up -d --build proxy
|
"${COMPOSE[@]}" up -d --build proxy
|
||||||
|
|
||||||
@@ -548,7 +654,52 @@ for i in $(seq 1 60); do
|
|||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
# ── 7. Summary ───────────────────────────────────────────────────────────────
|
# ── 7. Register the SSO + proxy UIs as Host records in the proxy ──────────────
|
||||||
|
# The proxy routes EVERY hostname it serves — including its own management UI
|
||||||
|
# and the SSO's UI — off a Host record (ops/nginx_conf/proxy.conf has no
|
||||||
|
# default/self route; targetinfo.lua does a lookup for every request, full
|
||||||
|
# stop). Nothing else creates these two, so without this step https://<SSO_HOST>
|
||||||
|
# and https://<PROXY_HOST> 404 on first run. sso_enabled is left false on both:
|
||||||
|
# each app gates its own login already, and SSO-gating the SSO's own login page
|
||||||
|
# would be circular. Idempotent — skips a host that already exists.
|
||||||
|
info "Registering ${SSO_HOST} and ${PROXY_HOST} with the proxy..."
|
||||||
|
HOSTS_OUT=$("${COMPOSE[@]}" exec -T proxy node <<NODEEOF
|
||||||
|
const {Host} = require('/app/models').models;
|
||||||
|
|
||||||
|
async function ensureHost(host, ip, targetPort) {
|
||||||
|
try {
|
||||||
|
await Host.get(host);
|
||||||
|
console.log('SKIP ' + host + ' (already exists)');
|
||||||
|
} catch (error) {
|
||||||
|
if (error.name !== 'EntryNotFound') throw error;
|
||||||
|
await Host.create({
|
||||||
|
host: host,
|
||||||
|
ip: ip,
|
||||||
|
targetPort: targetPort,
|
||||||
|
forcessl: true,
|
||||||
|
targetssl: false,
|
||||||
|
sso_enabled: false,
|
||||||
|
created_by: 'setup.sh',
|
||||||
|
});
|
||||||
|
console.log('CREATED ' + host + ' -> ' + ip + ':' + targetPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
await ensureHost($(js_str "$SSO_HOST"), 'sso-manager', 3001);
|
||||||
|
await ensureHost($(js_str "$PROXY_HOST"), '127.0.0.1', 3000);
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('ERROR', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
NODEEOF
|
||||||
|
) || die "Registering hosts with the proxy failed:\n${HOSTS_OUT}"
|
||||||
|
echo "$HOSTS_OUT" | sed 's/^/[setup] /'
|
||||||
|
|
||||||
|
# ── 8. Summary ───────────────────────────────────────────────────────────────
|
||||||
echo
|
echo
|
||||||
info "\033[1;32mDone. Your SSO + proxy stack is up.\033[0m"
|
info "\033[1;32mDone. Your SSO + proxy stack is up.\033[0m"
|
||||||
echo
|
echo
|
||||||
@@ -561,6 +712,12 @@ echo " First admin login:"
|
|||||||
echo " user: ${ADMIN_UID}"
|
echo " user: ${ADMIN_UID}"
|
||||||
echo " pass: ${ADMIN_PASS}"
|
echo " pass: ${ADMIN_PASS}"
|
||||||
echo
|
echo
|
||||||
|
echo " Proxy local admin (anti-lockout fallback if the SSO is unreachable):"
|
||||||
|
echo " user: proxyadmin2"
|
||||||
|
echo " pass: ${PROXY_LOCAL_ADMIN_PASS}"
|
||||||
|
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
|
||||||
echo " Secrets live in ./config/ (sso-secrets.js + proxy-secrets.js). Back them"
|
echo " Secrets live in ./config/ (sso-secrets.js + proxy-secrets.js). Back them"
|
||||||
echo " up off-host — ./setup.sh snapshots to ./backups/ before each rebuild."
|
echo " up off-host — ./setup.sh snapshots to ./backups/ before each rebuild."
|
||||||
echo
|
echo
|
||||||
|
|||||||
+1
-1
Submodule sso-manager-node updated: 11fb2c0a54...4c4fc34dcf
Reference in New Issue
Block a user