Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3bece9beeb | |||
| f5312fa528 | |||
| 765664aa51 | |||
| b4055d3579 | |||
| 5a9767e9e8 | |||
| 8bc6e9c0e0 | |||
| dbae26f095 | |||
| 502aba3da6 | |||
| c44527984d | |||
| f2ede4c018 | |||
| c4fb8b8a7c | |||
| 56ba01ad18 | |||
| 9c10b3b1d1 | |||
| 7a65841d2d | |||
| 7fd2b828ab | |||
| c7b90b7e7c | |||
| a6653cfb96 | |||
| 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,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
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
# 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.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.9...HEAD
|
||||
[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
|
||||
@@ -16,6 +16,16 @@ 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
|
||||
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
|
||||
@@ -64,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
|
||||
daily use).
|
||||
|
||||
The domain is the **one** value you set in `setup.env` (as the LDAP base DN,
|
||||
e.g. `CFG_BASE_DN=dc=lab,dc=example,dc=com` for `lab.example.com`) — see
|
||||
*Quickstart*. The SSO/proxy hostnames default to `sso.<domain>` /
|
||||
`proxy.<domain>`, derived from it.
|
||||
The domain is the **one** value you set in `setup.env` (e.g.
|
||||
`CFG_DOMAIN=lab.example.com`) — see *Quickstart*. The SSO/proxy hostnames
|
||||
default to `sso.<domain>` / `proxy.<domain>`, and the LDAP base DN
|
||||
(`dc=lab,dc=example,dc=com`) is built from it automatically.
|
||||
|
||||
### 2. At least two hostnames, pointing at your public IP
|
||||
|
||||
@@ -123,12 +133,13 @@ standalone (`docker-compose`) both work.
|
||||
```bash
|
||||
git clone --recursive https://github.com/theta42/theta-env.git
|
||||
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
|
||||
```
|
||||
|
||||
Your domain is entered **once**, as the LDAP base DN in `setup.env` (e.g.
|
||||
`CFG_BASE_DN=dc=lab,dc=example,dc=com` for the domain `lab.example.com`). The
|
||||
Your domain is entered **once** in `setup.env` (e.g.
|
||||
`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` +
|
||||
`./config/proxy-secrets.js` with that domain filled in everywhere (hostnames
|
||||
default to `sso.<domain>` / `proxy.<domain>`) plus random secrets, then builds
|
||||
@@ -151,7 +162,12 @@ operator-owned and `setup.env` is ignored.
|
||||
- registers the proxy as an OIDC client in the SSO and **writes the generated
|
||||
client id + secret back into `./config/proxy-secrets.js`**.
|
||||
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)
|
||||
|
||||
@@ -441,7 +457,7 @@ exactly in the bootstrap) so the SSO can verify them on bind.
|
||||
|
||||
```
|
||||
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/)
|
||||
├── docker-compose.yml # sso-manager + proxy on one bridge net
|
||||
├── setup.sh # one-command idempotent bring-up (manages ./config/ + backups)
|
||||
@@ -455,7 +471,14 @@ theta-env/
|
||||
gitignored `./config/` (`sso-secrets.js` + `proxy-secrets.js`) and snapshots to
|
||||
the gitignored `./backups/` before each rebuild.
|
||||
|
||||
`./setup.sh` updates both submodules to the latest of their tracked remote
|
||||
branch before building, so each run builds current upstream — no manual
|
||||
`git submodule update --remote` needed. To lock to the pinned commits (offline
|
||||
rebuild, or a deliberate pin), run `SKIP_SUBMODULE_UPDATE=1 ./setup.sh`.
|
||||
`./setup.sh` updates both submodules to their latest `vX.Y.Z` release tag
|
||||
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`.
|
||||
|
||||
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:
|
||||
context: ./sso-manager-node
|
||||
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
|
||||
restart: unless-stopped
|
||||
networks: [theta-net]
|
||||
@@ -74,6 +80,12 @@ services:
|
||||
build:
|
||||
context: ./proxy
|
||||
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
|
||||
restart: unless-stopped
|
||||
networks: [theta-net]
|
||||
|
||||
+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: true
|
||||
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>
|
||||
@@ -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
|
||||
@@ -104,6 +105,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
|
||||
`./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.
|
||||
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.
|
||||
|
||||
|
||||
@@ -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.
|
After Width: | Height: | Size: 126 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 118 KiB |
+49
-101
@@ -1,119 +1,67 @@
|
||||
---
|
||||
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
|
||||
|
||||
A single repo that runs the whole theta42 identity + access stack —
|
||||
[SSO Manager](https://github.com/theta42/sso-manager-node) (OIDC provider + LDAP)
|
||||
and the [theta42/proxy](https://github.com/theta42/proxy) (OIDC-protected reverse
|
||||
proxy) — together, with **one command**, for home labs and small businesses.
|
||||
The whole theta42 identity + access stack in one repo, brought up with a
|
||||
single command — for home labs and small businesses.
|
||||
|
||||
It exists for people whose needs are met by these two projects and who want to
|
||||
run them "very simply." Each project still works **standalone**; this repo just
|
||||
wires them together and automates the first-run glue.
|
||||
It wires together two projects that already work on their own —
|
||||
[SSO Manager](https://theta42.github.io/sso-manager-node/) (OIDC provider +
|
||||
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`.
|
||||
|
||||
---
|
||||
## Screenshots
|
||||
|
||||
## Quick start
|
||||
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
|
||||
git clone --recursive https://github.com/theta42/theta-env.git
|
||||
cd theta-env
|
||||
cp setup.env.example setup.env # then edit setup.env: set CFG_BASE_DN to your domain
|
||||
./setup.sh # first run: generates ./config/ from setup.env, builds + bootstraps + starts
|
||||
cp setup.env.example setup.env # then edit setup.env: set CFG_DOMAIN to your domain
|
||||
./setup.sh
|
||||
```
|
||||
|
||||
You need **Docker** + **Docker Compose**. `./setup.sh` is idempotent — re-run any
|
||||
time to converge the stack to `./config/`.
|
||||
You need **Docker** + **Docker Compose**. `./setup.sh` is idempotent — re-run
|
||||
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
|
||||
what `setup.sh` does, [Architecture](architecture.html) for how the pieces fit
|
||||
together, and [Standalone](standalone.html) for running each project on its own.
|
||||
## Related projects
|
||||
|
||||
---
|
||||
|
||||
## What you get
|
||||
|
||||
- **SSO Manager** at `https://<SSO_HOST>` — log in as your first admin to manage
|
||||
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
|
||||
|
||||
All config and secrets live in `./config/sso-secrets.js` +
|
||||
`./config/proxy-secrets.js` (gitignored), generated by the first `./setup.sh`.
|
||||
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.
|
||||
- **[SSO Manager](https://theta42.github.io/sso-manager-node/)** — the OIDC
|
||||
provider + LDAP directory this stack runs.
|
||||
- **[Proxy](https://theta42.github.io/proxy/)** — the reverse proxy this
|
||||
stack runs in front of it.
|
||||
|
||||
+13
-6
@@ -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
|
||||
@@ -42,20 +43,23 @@ git submodule update --init --recursive
|
||||
|
||||
```bash
|
||||
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
|
||||
default to `sso.<domain>` / `proxy.<domain>`, derived from it, so for most setups
|
||||
`CFG_BASE_DN` is the only value you set:
|
||||
Your domain is entered **once**, as a plain DNS domain. The SSO/proxy
|
||||
hostnames default to `sso.<domain>` / `proxy.<domain>`, and the LDAP base DN
|
||||
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 |
|
||||
|-----|---------|-------|
|
||||
| `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_PROXY_HOST` | `proxy.lab.local` | optional, defaults to `proxy.<domain>` |
|
||||
| `CFG_ADMIN_UID` | `admin` | optional, defaults to `admin` |
|
||||
| `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
|
||||
that `./config/*.js` are operator-owned and `setup.env` is ignored. Secrets
|
||||
@@ -89,7 +93,10 @@ What happens:
|
||||
service account, your first admin, and the proxy's OAuth client, and writes
|
||||
the generated client id + secret into `./config/proxy-secrets.js`.
|
||||
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
|
||||
fast.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://theta42.github.io/theta-env/sitemap.xml
|
||||
@@ -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
|
||||
|
||||
+1
-1
Submodule proxy updated: 3df7d8c5cb...11f44176c0
+21
-11
@@ -7,24 +7,30 @@
|
||||
# (edit them directly; setup.env is ignored on later runs).
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# 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.
|
||||
# Everything else derives from it: the SSO/proxy hostnames default to
|
||||
# sso.<domain> / proxy.<domain>, and the LDAP DNs are cn=admin,<dn>,
|
||||
# ou=people,<dn>, ou=groups,<dn>. Required — setup.sh refuses to run without it.
|
||||
CFG_BASE_DN=dc=example,dc=com
|
||||
# Your domain. THIS IS THE ONE PLACE THE DOMAIN IS ENTERED. Everything else
|
||||
# derives from it: the SSO/proxy hostnames default to sso.<domain> /
|
||||
# proxy.<domain>, and the LDAP base DN is built from it (example.com becomes
|
||||
# dc=example,dc=com; a 3-label domain like myhost.duckdns.org becomes
|
||||
# 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
|
||||
# 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):
|
||||
#CFG_SSO_HOST=sso.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:
|
||||
#CFG_ORG=SSO Manager # app display name + outbound email org
|
||||
#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>
|
||||
|
||||
# ── DO NOT put secrets here ──────────────────────────────────────────────────
|
||||
# The LDAP admin password, JWT secret, admin password, and LDAP service-account
|
||||
# password are GENERATED (random) into ./config/sso-secrets.js on first run.
|
||||
# Change them later by editing ./config/sso-secrets.js directly. Do NOT set
|
||||
# CFG_LDAP_ADMIN_PASS / CFG_JWT_SECRET / CFG_ADMIN_PASS / CFG_SVC_PASS here.
|
||||
# The LDAP admin password, JWT secret, admin password, LDAP service-account
|
||||
# password, and the proxy's local admin password are all GENERATED (random)
|
||||
# into ./config/sso-secrets.js + ./config/proxy-secrets.js on first run.
|
||||
# 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.
|
||||
#
|
||||
# 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 # later runs: rebuilds + bootstraps + starts (config left untouched)
|
||||
#
|
||||
# Idempotent: safe to re-run. It manages config in a bind-mounted ./config/
|
||||
# directory (sso-secrets.js + proxy-secrets.js — NO .env / proxy.env), snapshots
|
||||
# state before rebuild, (re)starts the SSO Manager, runs the bootstrap (which
|
||||
# Idempotent: safe to re-run. It pulls its own latest version, updates the two
|
||||
# submodules, manages config in a bind-mounted ./config/ directory
|
||||
# (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
|
||||
# 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:
|
||||
# 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
|
||||
# (so each run builds the newest sso-manager-node + proxy). Skip with
|
||||
# SKIP_SUBMODULE_UPDATE=1.
|
||||
# 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
|
||||
# (the one place the domain is entered, as the LDAP base DN) and both
|
||||
# files are generated with that domain filled in everywhere + random
|
||||
# secrets, then the run proceeds to build (no edit-and-re-run step). On
|
||||
# (the one place the domain is entered, as a plain DNS domain — the LDAP
|
||||
# base DN is derived from it) and both files are generated with that
|
||||
# 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
|
||||
# (preserved) into ./config. If ./config already exists it is left
|
||||
# untouched (the operator owns it; setup.env is ignored).
|
||||
@@ -35,7 +45,11 @@
|
||||
# writes the OAuth client creds into ./config/proxy-secrets.js; prints
|
||||
# CLIENT_ID / CLIENT_SECRET / ALREADY_CONFIGURED on stdout.
|
||||
# 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).
|
||||
|
||||
@@ -106,15 +120,73 @@ parse_kv_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 ! 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."
|
||||
fi
|
||||
info "Updating submodules to latest (sso-manager-node, proxy)..."
|
||||
if ! git submodule update --init --remote --recursive 2>&1; then
|
||||
warn "git submodule update failed (offline?) — continuing with the currently checked-out code."
|
||||
if ! git submodule update --init --recursive 2>&1; then
|
||||
die "git submodule update --init failed. Run manually: git submodule update --init --recursive"
|
||||
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
|
||||
info "Skipping submodule update (SKIP_SUBMODULE_UPDATE=1)."
|
||||
fi
|
||||
@@ -125,11 +197,22 @@ fi
|
||||
|| die "proxy/Dockerfile missing. Run: git submodule update --init --recursive"
|
||||
|
||||
# ── 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() {
|
||||
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_sso_secrets() {
|
||||
local dn="$CFG_BASE_DN" domain="$CFG_DOMAIN"
|
||||
@@ -221,6 +304,10 @@ module.exports = {
|
||||
adminGroups: ['app_sso_admin'],
|
||||
adminUsers: ['proxyadmin2'],
|
||||
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: {
|
||||
ssoHost: $(js_str "$CFG_SSO_HOST"),
|
||||
@@ -237,8 +324,9 @@ ensure_config() {
|
||||
fi
|
||||
|
||||
# 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
|
||||
# default to sso.<domain> / proxy.<domain>, derived from it. setup.env is
|
||||
# domain is entered (e.g. 718it.biz), as a plain DNS domain; the LDAP base
|
||||
# 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
|
||||
# and setup.env is ignored. Falls back to legacy .env/proxy.env migration
|
||||
# below for existing deployments.
|
||||
@@ -265,6 +353,7 @@ ensure_config() {
|
||||
CFG_JWT_SECRET="${CFG_JWT_SECRET:-}"
|
||||
CFG_ADMIN_PASS="${CFG_ADMIN_PASS:-}"
|
||||
CFG_SVC_PASS="${CFG_SVC_PASS:-}"
|
||||
CFG_PROXY_ADMIN_PASS="${CFG_PROXY_ADMIN_PASS:-}"
|
||||
|
||||
# ── One-time migration from .env / proxy.env (existing deployments) ──
|
||||
# Preserve the operator's existing secrets so the running deployment keeps
|
||||
@@ -304,11 +393,15 @@ ensure_config() {
|
||||
migrated=1
|
||||
fi
|
||||
|
||||
# Derive everything from the base DN — the one domain value. No example.com
|
||||
# defaults: a blank base DN means first-run setup hasn't been done yet.
|
||||
[[ -n "$CFG_BASE_DN" ]] \
|
||||
|| 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"
|
||||
CFG_DOMAIN="${CFG_DOMAIN:-$(domain_from_dn "$CFG_BASE_DN")}"
|
||||
# Derive everything from the domain — the one value operators enter. No
|
||||
# example.com defaults: a blank domain means first-run setup hasn't been
|
||||
# done yet. CFG_BASE_DN can still be set directly (setup.env or a migrated
|
||||
# .env) to override the derived DN or to read the domain back out of an
|
||||
# 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_PROXY_HOST="${CFG_PROXY_HOST:-proxy.$CFG_DOMAIN}"
|
||||
CFG_ORG="${CFG_ORG:-SSO Manager}"
|
||||
@@ -323,6 +416,7 @@ ensure_config() {
|
||||
CFG_JWT_SECRET="${CFG_JWT_SECRET:-$(rand_hex 32)}"
|
||||
CFG_ADMIN_PASS="${CFG_ADMIN_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"
|
||||
write_sso_secrets
|
||||
@@ -464,7 +558,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)."
|
||||
@@ -473,6 +567,13 @@ backup_before_rebuild() {
|
||||
backup_before_rebuild
|
||||
|
||||
# ── 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.
|
||||
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
|
||||
|
||||
@@ -493,6 +594,8 @@ done
|
||||
read_config_kv() {
|
||||
"${COMPOSE[@]}" exec -T sso-manager node -e '
|
||||
const c = require("/config/sso-secrets.js");
|
||||
let p = {};
|
||||
try { p = require("/config/proxy-secrets.js"); } catch (_) {}
|
||||
const o = {
|
||||
SSO_HOST: (c.stack && c.stack.ssoHost) || "",
|
||||
PROXY_HOST: (c.stack && c.stack.proxyHost) || "",
|
||||
@@ -500,6 +603,7 @@ read_config_kv() {
|
||||
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
|
||||
@@ -510,6 +614,7 @@ 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}"
|
||||
@@ -525,7 +630,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}"
|
||||
|
||||
@@ -536,6 +640,9 @@ else
|
||||
fi
|
||||
|
||||
# ── 6. Start the proxy, wait for health ───────────────────────────────────────
|
||||
# PROXY_GIT_COMMIT: same reasoning as SSO_GIT_COMMIT above.
|
||||
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
|
||||
|
||||
@@ -548,7 +655,52 @@ for i in $(seq 1 60); do
|
||||
sleep 2
|
||||
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
|
||||
info "\033[1;32mDone. Your SSO + proxy stack is up.\033[0m"
|
||||
echo
|
||||
@@ -561,6 +713,12 @@ echo " First admin login:"
|
||||
echo " user: ${ADMIN_UID}"
|
||||
echo " pass: ${ADMIN_PASS}"
|
||||
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 " up off-host — ./setup.sh snapshots to ./backups/ before each rebuild."
|
||||
echo
|
||||
|
||||
+1
-1
Submodule sso-manager-node updated: 11fb2c0a54...65b107d8ff
Reference in New Issue
Block a user