Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60ba9ba989 |
@@ -1,41 +0,0 @@
|
||||
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
|
||||
+1
-4
@@ -20,7 +20,4 @@ setup.env
|
||||
*.ldif
|
||||
|
||||
# Docker Compose runtime artifacts
|
||||
*.log
|
||||
|
||||
# Jekyll build output (docs/ site) — generated, not committed.
|
||||
docs/_site
|
||||
*.log
|
||||
@@ -1,98 +0,0 @@
|
||||
# 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.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.7...HEAD
|
||||
[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,16 +16,6 @@ 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
|
||||
@@ -74,10 +64,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` (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.
|
||||
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.
|
||||
|
||||
### 2. At least two hostnames, pointing at your public IP
|
||||
|
||||
@@ -133,13 +123,12 @@ 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_DOMAIN to your domain
|
||||
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
|
||||
```
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
@@ -162,12 +151,7 @@ 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. 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.
|
||||
5. Prints your first admin login + the public URLs.
|
||||
|
||||
### Configuration — `./config/` (no `.env` files)
|
||||
|
||||
@@ -457,7 +441,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_DOMAIN
|
||||
├── setup.env.example # first-run config template — cp to setup.env, set CFG_BASE_DN
|
||||
├── 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)
|
||||
@@ -471,14 +455,7 @@ 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 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).
|
||||
`./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`.
|
||||
@@ -29,12 +29,6 @@ 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]
|
||||
@@ -80,12 +74,6 @@ 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]
|
||||
|
||||
+4
-36
@@ -1,41 +1,9 @@
|
||||
title: theta-env
|
||||
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
|
||||
|
||||
description: A unified, one-command SSO Manager + OIDC proxy stack for home labs and small businesses
|
||||
theme: jekyll-theme-cayman
|
||||
show_downloads: true
|
||||
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
|
||||
|
||||
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
|
||||
repository_name: theta42/theta-env
|
||||
@@ -1,82 +0,0 @@
|
||||
<!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,7 +1,6 @@
|
||||
---
|
||||
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
|
||||
@@ -105,18 +104,6 @@ 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.
|
||||
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 126 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 118 KiB |
+101
-49
@@ -1,67 +1,119 @@
|
||||
---
|
||||
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
|
||||
|
||||
The whole theta42 identity + access stack in one repo, brought up with a
|
||||
single command — for home labs and small businesses.
|
||||
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.
|
||||
|
||||
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`.
|
||||
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.
|
||||
|
||||
## 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
|
||||
## Quick start
|
||||
|
||||
```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_DOMAIN to your domain
|
||||
./setup.sh
|
||||
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
|
||||
```
|
||||
|
||||
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)**.
|
||||
You need **Docker** + **Docker Compose**. `./setup.sh` is idempotent — re-run any
|
||||
time to converge the stack to `./config/`.
|
||||
|
||||
## Related projects
|
||||
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.
|
||||
|
||||
- **[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.
|
||||
---
|
||||
|
||||
## 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.
|
||||
+6
-13
@@ -1,7 +1,6 @@
|
||||
---
|
||||
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
|
||||
@@ -43,23 +42,20 @@ git submodule update --init --recursive
|
||||
|
||||
```bash
|
||||
cp setup.env.example setup.env
|
||||
$EDITOR setup.env # set CFG_DOMAIN to your domain
|
||||
$EDITOR setup.env # set CFG_BASE_DN to your domain, as a base DN
|
||||
```
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
| `setup.env` key | Example | Notes |
|
||||
|-----|---------|-------|
|
||||
| `CFG_DOMAIN` | `lab.local` | your domain — **required** |
|
||||
| `CFG_BASE_DN` | `dc=lab,dc=local` | your directory base — **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
|
||||
@@ -93,10 +89,7 @@ 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. 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.
|
||||
5. Prints your first-admin login + the public URLs.
|
||||
|
||||
The first run builds two Docker images (a few minutes). Subsequent runs are
|
||||
fast.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://theta42.github.io/theta-env/sitemap.xml
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
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: 11f44176c0...3df7d8c5cb
+11
-21
@@ -7,30 +7,24 @@
|
||||
# (edit them directly; setup.env is ignored on later runs).
|
||||
#
|
||||
# cp setup.env.example setup.env
|
||||
# $EDITOR setup.env # set CFG_DOMAIN below to your domain
|
||||
# $EDITOR setup.env # set CFG_BASE_DN 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. 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
|
||||
# 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
|
||||
|
||||
# Public hostnames. Optional — default to sso.<domain> / proxy.<domain> derived
|
||||
# from CFG_DOMAIN above. Uncomment and set only if your hostnames differ
|
||||
# from CFG_BASE_DN 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
|
||||
@@ -45,11 +39,7 @@ CFG_DOMAIN=example.com
|
||||
#CFG_SMTP_FROM=SSO Manager <noreply@example.com>
|
||||
|
||||
# ── DO NOT put secrets 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.
|
||||
# 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.
|
||||
@@ -3,36 +3,26 @@
|
||||
# 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_DOMAIN to your domain (once)
|
||||
# cp setup.env.example setup.env # set CFG_BASE_DN 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 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
|
||||
# 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
|
||||
# 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 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.
|
||||
# then starts the proxy.
|
||||
#
|
||||
# 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 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
|
||||
# (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
|
||||
# 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).
|
||||
@@ -45,11 +35,7 @@
|
||||
# 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. 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.
|
||||
# 7. Print the first-admin login + the public URLs.
|
||||
#
|
||||
# Requires: git, docker + docker compose (v1 standalone or v2 plugin).
|
||||
|
||||
@@ -120,73 +106,15 @@ parse_kv_file() {
|
||||
done < "$file"
|
||||
}
|
||||
|
||||
# ── 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.
|
||||
# ── 1. Update submodules to latest, verify build contexts ─────────────────────
|
||||
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
|
||||
if ! git submodule update --init --recursive 2>&1; then
|
||||
die "git submodule update --init failed. Run manually: git submodule update --init --recursive"
|
||||
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."
|
||||
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
|
||||
@@ -197,22 +125,11 @@ 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). 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.
|
||||
# Derive a DNS domain from a base DN (dc=foo,dc=bar -> foo.bar).
|
||||
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"
|
||||
@@ -304,10 +221,6 @@ 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"),
|
||||
@@ -324,9 +237,8 @@ ensure_config() {
|
||||
fi
|
||||
|
||||
# First run: read the domain/hosts from ./setup.env — the ONE place the
|
||||
# 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
|
||||
# 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
|
||||
# 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.
|
||||
@@ -353,7 +265,6 @@ 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
|
||||
@@ -393,15 +304,11 @@ ensure_config() {
|
||||
migrated=1
|
||||
fi
|
||||
|
||||
# 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")}"
|
||||
# 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")}"
|
||||
CFG_SSO_HOST="${CFG_SSO_HOST:-sso.$CFG_DOMAIN}"
|
||||
CFG_PROXY_HOST="${CFG_PROXY_HOST:-proxy.$CFG_DOMAIN}"
|
||||
CFG_ORG="${CFG_ORG:-SSO Manager}"
|
||||
@@ -416,7 +323,6 @@ 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
|
||||
@@ -558,7 +464,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)."
|
||||
@@ -567,13 +473,6 @@ 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
|
||||
|
||||
@@ -594,8 +493,6 @@ 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) || "",
|
||||
@@ -603,7 +500,6 @@ 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
|
||||
@@ -614,7 +510,6 @@ 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}"
|
||||
@@ -630,6 +525,7 @@ 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}"
|
||||
|
||||
@@ -640,9 +536,6 @@ 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
|
||||
|
||||
@@ -655,52 +548,7 @@ for i in $(seq 1 60); do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# ── 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 ───────────────────────────────────────────────────────────────
|
||||
# ── 7. Summary ───────────────────────────────────────────────────────────────
|
||||
echo
|
||||
info "\033[1;32mDone. Your SSO + proxy stack is up.\033[0m"
|
||||
echo
|
||||
@@ -713,12 +561,6 @@ 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: 5fc65d6fb3...11fb2c0a54
Reference in New Issue
Block a user