Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0edce57f80 | |||
| 49b868fedb | |||
| f9af81983b | |||
| 5ef2493e17 | |||
| 8535372123 | |||
| ade9a41aed | |||
| ce664f5cb9 | |||
| 647f5b846c | |||
| fe08c2f8c7 | |||
| 43c58e7ed5 | |||
| 13d8a979c7 | |||
| 16cbe46793 | |||
| 40a1e7f64e | |||
| 45715b61ea | |||
| 54ebb83bb1 | |||
| a67d972217 | |||
| 875ea874b4 | |||
| 119f21b821 | |||
| 80275c42e4 | |||
| a30deae866 | |||
| dc9a7ff9c4 | |||
| 7937d6cf92 | |||
| 21333de814 | |||
| b3bdebe1c9 | |||
| e6b318e28e | |||
| 4d0b7f555e | |||
| 67b511f8d7 | |||
| b8a8be9697 | |||
| cd9c81cd92 | |||
| c8c04440db | |||
| d53bdefc2a | |||
| 542e5fd33f | |||
| 848f35fc5e | |||
| 1d14fcee19 | |||
| 30609de3e8 | |||
| 925ac027a6 | |||
| 3b769cf24a |
@@ -8,6 +8,54 @@ orchestration code; see each submodule's own `CHANGELOG.md`
|
||||
[sso-manager-node](https://github.com/theta42/sso-manager-node/blob/master/CHANGELOG.md))
|
||||
for what changed inside the apps it composes.
|
||||
|
||||
## [v1.35.12] - 2026-08-04
|
||||
|
||||
### Fixed
|
||||
- **theta-agent crash-looped (`cannot unmarshal !!bool 'true' into []string`)** — setup.sh's "full control" edit wrote `service_control: true`, but that field is a `[]string` allowlist, so the agent failed to decode the config and restart-loop. Removed the invalid edit; `service_control` now stays as its allowlist (default `[]` = deny all) and the operator can list specific services.
|
||||
|
||||
## [v1.35.11] - 2026-08-04
|
||||
|
||||
### Fixed
|
||||
- **`setup.sh` aborted with `UNSEAL_KEY: unbound variable` on re-runs** — when OpenBao was already unsealed, the unseal block was skipped and `UNSEAL_KEY` was never set, so the later `if [[ -n "$UNSEAL_KEY" ]]` crashed under `set -u`. Guarded with `${UNSEAL_KEY:-}`.
|
||||
|
||||
## [v1.35.10] - 2026-08-04
|
||||
|
||||
### Added
|
||||
- **`--reset-openbao`** — full clean OpenBao reset for clearing stale policies/tokens (re-inits the store, flushes the Redis vault-token cache). Use when the vault UI shows a recurring `403 permission denied` on the secrets list.
|
||||
- **sso v1.21.0** — shared secrets: users publish secrets to `secret/shared/<owner>/<slug>` and grant read access to other users and apps; plus a durable fix for the recurring vault 403 (broker now always reconciles policy content before serving a cached token). Bumped the sso-manager-node submodule gitlink to v1.21.0.
|
||||
|
||||
### Fixed
|
||||
- **theta-agent was never installed** — `setup.sh` tried to `go build` from an incomplete source-file list (omitting `executor.go`/`telemetry.go`), which failed silently and skipped install. It now installs the prebuilt `theta-agent-linux-amd64` binary from the submodule and writes config to `/etc/theta42/agent.yml` (the path the agent actually reads).
|
||||
|
||||
## [v1.35.9] - 2026-08-03
|
||||
|
||||
### Fixed
|
||||
- **sso & proxy version strings now match their release tags** — The v1.20.2 / v1.32.0 release tags were created but their `nodejs/package.json` version fields were left behind (1.20.1 / 1.14.3), so the deployed apps' update-check banner falsely reported a newer version. Bumped submodules to the corrected commits so `buildVersion` matches the deployed tag.
|
||||
|
||||
## [v1.35.2] - 2026-08-03
|
||||
|
||||
### Fixed
|
||||
- **Unbound `CFG_CREATE_ALL_HTTP` variable in `setup.sh`** — Fixed unbound variable error during host registration in `setup.sh` when `ensure_secrets_files()` is skipped on pre-configured installations.
|
||||
|
||||
## [v1.35.1] - 2026-08-03
|
||||
|
||||
### Fixed
|
||||
- **Directory & Configuration UI enhancements** — Live Cytoscape graph update on parent/child edge modifications, improved discovery reconciler host matching, updated configuration sidebar layout, relocated discovery and messaging plugins to Directory and Configuration pages.
|
||||
- **Managed Host Target Filter** — Filter SSH connection targets in Jump Host to managed hosts only.
|
||||
|
||||
## [v1.35.0] - 2026-08-02
|
||||
|
||||
### Added
|
||||
- **Non-interactive theta-agent configuration** — Added three `setup.env` variables
|
||||
to control theta-agent installation and configuration without interactive prompts:
|
||||
- `CFG_THETA_AGENT_ENABLE` (default: 1) — Enable theta-agent installation
|
||||
- `CFG_THETA_AGENT_LDAP_AUTH` (default: 1) — Configure LDAP authentication via ldap-client
|
||||
- `CFG_THETA_AGENT_FULL_CONTROL` (default: 1) — Enable all agent capabilities
|
||||
|
||||
### Changed
|
||||
- **`setup.sh`**: Made theta-agent setup fully non-interactive, driven by `setup.env`
|
||||
variables. Defaults preserve existing behavior (all features enabled).
|
||||
|
||||
## [v1.34.0] - 2026-08-02
|
||||
|
||||
### Added
|
||||
@@ -1546,3 +1594,12 @@ First tagged release. Establishes the `vX.Y.Z` tag convention going forward.
|
||||
[1.1.2]: https://github.com/theta42/theta-suite/compare/v1.1.1...v1.1.2
|
||||
[1.1.1]: https://github.com/theta42/theta-suite/compare/v1.1.0...v1.1.1
|
||||
[1.1.0]: https://github.com/theta42/theta-suite/releases/tag/v1.1.0
|
||||
|
||||
## [1.34.4] - 2026-08-02
|
||||
### Changed
|
||||
- Updated `sso-manager-node` submodule to `v1.19.6` to pull in a fix for the Vault API 403 error on the Secrets List.
|
||||
|
||||
## [1.34.5] - 2026-08-02
|
||||
### Added
|
||||
- Automatically build and install `theta-agent` on the host system as a systemd service during `setup.sh`.
|
||||
- Added `CFG_CREATE_ALL_HTTP` option to `setup.env` to create all default proxy host entries with `forcessl=false`.
|
||||
|
||||
Vendored
+19
@@ -423,15 +423,21 @@ async function seedDirectory(token, clientId, jumpClientId) {
|
||||
const host = await ensure('host', HOST_FACTS.name || 'Stack host', hostSlug, site.id, {
|
||||
subType: 'linux',
|
||||
ip: HOST_FACTS.ip,
|
||||
address: HOST_FACTS.ip,
|
||||
macAddress: HOST_FACTS.mac,
|
||||
os: HOST_FACTS.os,
|
||||
kernel: HOST_FACTS.kernel,
|
||||
sshPort: 22,
|
||||
managed: true,
|
||||
}, ['stack-host']);
|
||||
await ensure('service', 'SSO Manager', 'sso-manager', host.id, {
|
||||
address: `https://${SSO_HOST}`,
|
||||
port: 3001,
|
||||
gitRepo: 'https://github.com/theta42/sso-manager-node',
|
||||
subType: 'web',
|
||||
icon: 'mdi:shield-account',
|
||||
tagline: 'Home-lab identity and access management.',
|
||||
requestable: false,
|
||||
});
|
||||
// Proxy = the node management UI; OpenResty = the data plane every hostname
|
||||
// in the stack actually flows through (80/443). Two faces, two entries.
|
||||
@@ -440,6 +446,9 @@ async function seedDirectory(token, clientId, jumpClientId) {
|
||||
port: 3000,
|
||||
gitRepo: 'https://github.com/theta42/proxy',
|
||||
subType: 'web',
|
||||
icon: 'mdi:server-network',
|
||||
tagline: 'Reverse proxy and API gateway.',
|
||||
requestable: false,
|
||||
});
|
||||
// OpenLDAP is independently consumed — Linux hosts authenticate against it
|
||||
// (PAM/SSSD, sudoRole, sshPublicKey) and LDAP-native apps bind directly
|
||||
@@ -451,8 +460,12 @@ async function seedDirectory(token, clientId, jumpClientId) {
|
||||
address: `ldaps://${LDAPS_HOST}:636`,
|
||||
port: 389,
|
||||
externalPort: 636,
|
||||
portMappings: [{ proto: 'tcp', external: 636, internal: 389, comment: 'LDAPS' }],
|
||||
gitRepo: 'https://github.com/theta42/sso-manager-node',
|
||||
subType: 'openldap',
|
||||
icon: 'mdi:book-open-outline',
|
||||
tagline: 'LDAP directory for identity.',
|
||||
requestable: false,
|
||||
});
|
||||
// Wildcard address: OpenResty fronts every host under the domain (same
|
||||
// */** wildcard convention the proxy's Host records use). Its config lives
|
||||
@@ -462,6 +475,9 @@ async function seedDirectory(token, clientId, jumpClientId) {
|
||||
port: 443,
|
||||
gitRepo: 'https://github.com/theta42/proxy',
|
||||
subType: 'openresty',
|
||||
icon: 'mdi:router-network',
|
||||
tagline: 'Data plane.',
|
||||
requestable: false,
|
||||
});
|
||||
|
||||
// SSH jump host service (core component — always registered).
|
||||
@@ -473,6 +489,9 @@ async function seedDirectory(token, clientId, jumpClientId) {
|
||||
port: 3002,
|
||||
gitRepo: 'https://github.com/theta42/jump-host',
|
||||
subType: 'ssh',
|
||||
icon: 'mdi:ssh',
|
||||
tagline: 'Secure SSH jump host.',
|
||||
requestable: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -140,6 +140,13 @@ then converges the stack to your `./config/` values (LDAP service account + admi
|
||||
passwords are reset to the config; the OAuth client is kept if `proxy-secrets.js`
|
||||
already holds its creds).
|
||||
|
||||
> **Troubleshooting: "A newer version is available" after running setup.sh?**
|
||||
> If the UI shows this warning immediately after you ran `./setup.sh`, the latest
|
||||
> GitHub release tag might not yet be merged into the default tracking branch for
|
||||
> the submodules, or Docker may have cached the `COPY` step if the `package.json`
|
||||
> didn't change. You can force a clean rebuild by running
|
||||
> `docker compose build --no-cache` and then re-running `./setup.sh`.
|
||||
|
||||
---
|
||||
|
||||
## Direct LDAP for LDAP-native clients and Linux hosts
|
||||
|
||||
+1
-1
Submodule jump-host updated: 463111dfd6...8c4ec67282
+1
-1
Submodule ldap-client updated: ca8132f5ff...31d8fa1229
+1
-1
Submodule proxy updated: 25419382bd...8107755307
+21
-8
@@ -73,13 +73,6 @@ CFG_DOMAIN=example.com
|
||||
# 'sso-manager' so clients don't need a public 636 port forward. See docs.
|
||||
#CFG_LDAPS_HOST=
|
||||
|
||||
# Optional SMTP (outbound email from the SSO app). Leave blank to disable:
|
||||
#CFG_SMTP_HOST=smtp.example.com
|
||||
#CFG_SMTP_PORT=587
|
||||
#CFG_SMTP_USER=noreply@example.com
|
||||
#CFG_SMTP_PASS=your-smtp-password
|
||||
#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)
|
||||
@@ -90,6 +83,20 @@ CFG_DOMAIN=example.com
|
||||
# CFG_LDAP_ADMIN_PASS / CFG_JWT_SECRET / CFG_ADMIN_PASS / CFG_SVC_PASS /
|
||||
# CFG_PROXY_ADMIN_PASS here.
|
||||
|
||||
# ── theta-agent Host Integration ─────────────────────────────────────────────
|
||||
# Configure theta-agent integration with the local host. All options default to
|
||||
# enabled (1). Set to 0 to disable.
|
||||
#
|
||||
# Enable theta-agent installation and configuration on this host.
|
||||
#CFG_THETA_AGENT_ENABLE=1
|
||||
#
|
||||
# Configure LDAP authentication for this host via ldap-client (SSSD/PAM).
|
||||
#CFG_THETA_AGENT_LDAP_AUTH=1
|
||||
#
|
||||
# Allow theta-agent full control of this host (arbitrary_bash, service_control,
|
||||
# reboot, configure_ldap capabilities).
|
||||
#CFG_THETA_AGENT_FULL_CONTROL=1
|
||||
|
||||
# ── Geo-Location Scaling (N-Way Multi-Master LDAP) ───────────────────────────
|
||||
# If deploying this stack across multiple physical sites to provide local HA
|
||||
# for directory services, you can enable N-Way Multi-Master OpenLDAP replication.
|
||||
@@ -100,4 +107,10 @@ CFG_DOMAIN=example.com
|
||||
# LDAP_REPLICATION_HOSTS is a space-separated list of the other sites' LDAP URLs.
|
||||
# Example for Site 1:
|
||||
#LDAP_SERVER_ID=1
|
||||
#LDAP_REPLICATION_HOSTS="ldaps://sso.site2.com:636 ldaps://sso.site3.com:636"
|
||||
#LDAP_REPLICATION_HOSTS="ldaps://sso.site2.com:636 ldaps://sso.site3.com:636"
|
||||
# ── Proxy HTTP/HTTPS Defaults ────────────────────────────────────────────────
|
||||
# If you are running the stack behind an external reverse proxy (like Cloudflare
|
||||
# or another ingress) that handles TLS termination, you may want the internal
|
||||
# proxy to serve everything over plain HTTP without forcing redirects to HTTPS.
|
||||
# Set this to 1 to create all default proxy host entries with forcessl=false.
|
||||
#CFG_CREATE_ALL_HTTP=1
|
||||
|
||||
@@ -61,6 +61,7 @@ set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
CFG_ADMIN_PASS="${CFG_ADMIN_PASS:-}"
|
||||
CONFIG_DIR=./config
|
||||
BACKUP_DIR=./backups
|
||||
BACKUP_KEEP="${BACKUP_KEEP:-5}"
|
||||
@@ -71,6 +72,19 @@ warn() { printf '\033[1;33m[setup]\033[0m %s\n' "$*" >&2; }
|
||||
error() { printf '\033[1;31m[setup]\033[0m %s\n' "$*" >&2; }
|
||||
die() { error "$*"; exit 1; }
|
||||
|
||||
# ── Flags ──────────────────────────────────────────────────────────────────────
|
||||
# --reset-openbao: wipe the OpenBao volume + bao-init.json and re-initialize a
|
||||
# fresh store (no prod data to preserve). Use when OpenBao state is suspect
|
||||
# (stale policies/tokens causing vault 403s). The Redis vault-token cache is
|
||||
# flushed once sso-manager is back up (see the OpenBao bootstrap section).
|
||||
RESET_OPENBAO=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--reset-openbao) RESET_OPENBAO=1 ;;
|
||||
*) warn "unknown argument: $arg (ignored)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Escape a value for a single-quoted JS string: \ -> \\, ' -> \', then wrap in '...'.
|
||||
js_str() {
|
||||
local s="$1"
|
||||
@@ -105,6 +119,13 @@ env_upsert() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Read KEY= from ./.env (empty if absent)
|
||||
env_get() {
|
||||
local key="$1" file=./.env
|
||||
[[ -f "$file" ]] || return 0
|
||||
grep -m1 "^${key}=" "$file" 2>/dev/null | cut -d= -f2- || true
|
||||
}
|
||||
|
||||
# Detect docker compose (v2 plugin `docker compose` or v1 standalone `docker-compose`).
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
COMPOSE=(docker compose)
|
||||
@@ -177,6 +198,8 @@ fi
|
||||
# resolved in ensure_config; this is only the hostname override.
|
||||
[[ -f ./setup.env ]] && parse_kv_file ./setup.env
|
||||
export CFG_JUMP_HOST
|
||||
CFG_CREATE_ALL_HTTP="${CFG_CREATE_ALL_HTTP:-0}"
|
||||
export CFG_CREATE_ALL_HTTP
|
||||
|
||||
# ── Optional outbound HTTP(S) proxy for docker build + the running containers ─
|
||||
# CFG_HTTP_PROXY / CFG_HTTPS_PROXY / CFG_NO_PROXY (from ./setup.env or the
|
||||
@@ -442,6 +465,7 @@ BAOEOF
|
||||
CFG_ADMIN_PASS="${CFG_ADMIN_PASS:-}"
|
||||
CFG_SVC_PASS="${CFG_SVC_PASS:-}"
|
||||
CFG_PROXY_ADMIN_PASS="${CFG_PROXY_ADMIN_PASS:-}"
|
||||
CFG_CREATE_ALL_HTTP="${CFG_CREATE_ALL_HTTP:-0}"
|
||||
|
||||
# ── One-time migration from .env / proxy.env (existing deployments) ──
|
||||
# Preserve the operator's existing secrets so the running deployment keeps
|
||||
@@ -660,6 +684,20 @@ backup_before_rebuild() {
|
||||
backup_before_rebuild
|
||||
|
||||
# ── 3b. Setup OpenBao (Vault) ────────────────────────────────────────────────
|
||||
# Full reset (--reset-openbao): stop/remove openbao, drop the data volume, and
|
||||
# delete the init/keys file (incl. any backup copy that setup.sh would otherwise
|
||||
# restore). The normal bootstrap below then initializes a brand-new store, so no
|
||||
# stale policy content or token survives.
|
||||
if [[ "$RESET_OPENBAO" == "1" ]]; then
|
||||
info "── Full OpenBao reset requested (--reset-openbao) ──"
|
||||
"${COMPOSE[@]}" stop openbao >/dev/null 2>&1 || true
|
||||
"${COMPOSE[@]}" rm -f openbao >/dev/null 2>&1 || true
|
||||
docker volume ls -q 2>/dev/null | grep '^openbao' | xargs -r docker volume rm >/dev/null 2>&1 || true
|
||||
rm -f "$CONFIG_DIR/bao-init.json"
|
||||
rm -f ./backups/bao-init.json ./backups/*/bao-init.json 2>/dev/null || true
|
||||
info " openbao volume + bao-init.json cleared; will re-initialize fresh."
|
||||
fi
|
||||
|
||||
info "Starting openbao..."
|
||||
"${COMPOSE[@]}" run --rm --user root openbao chown -R 100:1000 /vault/data
|
||||
"${COMPOSE[@]}" up -d openbao
|
||||
@@ -672,7 +710,17 @@ for i in $(seq 1 30); do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if ! docker exec openbao bao status -format=json 2>/dev/null | grep -q '"initialized": true' || true; then
|
||||
# If config/bao-init.json is missing, search backups for a saved copy
|
||||
if [[ ! -f "$CONFIG_DIR/bao-init.json" ]]; then
|
||||
latest_backup_init=$(find ./backups -name "bao-init.json" 2>/dev/null | sort -r | head -n1 || true)
|
||||
if [[ -n "$latest_backup_init" && -f "$latest_backup_init" ]]; then
|
||||
info "Restoring $CONFIG_DIR/bao-init.json from backup ($latest_backup_init)..."
|
||||
cp "$latest_backup_init" "$CONFIG_DIR/bao-init.json"
|
||||
chmod 600 "$CONFIG_DIR/bao-init.json"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! docker exec openbao bao status -format=json 2>/dev/null | grep -q '"initialized": true'; then
|
||||
status_json=$(docker exec openbao bao status -format=json 2>/dev/null || true)
|
||||
if ! echo "$status_json" | grep -q '"initialized": true'; then
|
||||
info "Initializing openbao for the first time..."
|
||||
@@ -684,13 +732,54 @@ fi
|
||||
|
||||
status_json=$(docker exec openbao bao status -format=json 2>/dev/null || true)
|
||||
if echo "$status_json" | grep -q '"sealed": true'; then
|
||||
info "Unsealing openbao..."
|
||||
UNSEAL_KEY=$(grep -A1 '"unseal_keys_b64":' "$CONFIG_DIR/bao-init.json" | tail -n1 | cut -d'"' -f2)
|
||||
docker exec openbao bao operator unseal "$UNSEAL_KEY" >/dev/null
|
||||
UNSEAL_KEY=""
|
||||
if [[ -f "$CONFIG_DIR/bao-init.json" ]]; then
|
||||
UNSEAL_KEY=$(grep -A1 '"unseal_keys_b64":' "$CONFIG_DIR/bao-init.json" 2>/dev/null | tail -n1 | cut -d'"' -f2 || true)
|
||||
fi
|
||||
if [[ -z "$UNSEAL_KEY" ]]; then
|
||||
UNSEAL_KEY="$(env_get VAULT_UNSEAL_KEY)"
|
||||
fi
|
||||
|
||||
if [[ -n "$UNSEAL_KEY" ]]; then
|
||||
info "Unsealing openbao..."
|
||||
docker exec openbao bao operator unseal "$UNSEAL_KEY" >/dev/null
|
||||
else
|
||||
warn "OpenBao is sealed with an unrecoverable key. Resetting OpenBao volume and re-initializing..."
|
||||
"${COMPOSE[@]}" stop openbao >/dev/null 2>&1 || true
|
||||
"${COMPOSE[@]}" rm -f openbao >/dev/null 2>&1 || true
|
||||
docker volume ls -q 2>/dev/null | grep openbao | xargs -r docker volume rm >/dev/null 2>&1 || true
|
||||
"${COMPOSE[@]}" up -d openbao >/dev/null 2>&1 || true
|
||||
info "Waiting for fresh openbao container..."
|
||||
for i in $(seq 1 30); do
|
||||
if docker exec openbao bao status >/dev/null 2>&1 || [[ $? -eq 2 ]]; then break; fi
|
||||
sleep 2
|
||||
done
|
||||
info "Initializing fresh openbao..."
|
||||
docker exec openbao bao operator init -key-shares=1 -key-threshold=1 -format=json > "$CONFIG_DIR/bao-init.json"
|
||||
chmod 600 "$CONFIG_DIR/bao-init.json"
|
||||
UNSEAL_KEY=$(grep -A1 '"unseal_keys_b64":' "$CONFIG_DIR/bao-init.json" | tail -n1 | cut -d'"' -f2)
|
||||
info "Unsealing fresh openbao..."
|
||||
docker exec openbao bao operator unseal "$UNSEAL_KEY" >/dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
export VAULT_TOKEN
|
||||
VAULT_TOKEN=$(grep '"root_token":' "$CONFIG_DIR/bao-init.json" | cut -d'"' -f4)
|
||||
if [[ -f "$CONFIG_DIR/bao-init.json" ]]; then
|
||||
VAULT_TOKEN=$(grep '"root_token":' "$CONFIG_DIR/bao-init.json" 2>/dev/null | cut -d'"' -f4 || true)
|
||||
fi
|
||||
if [[ -z "$VAULT_TOKEN" ]]; then
|
||||
VAULT_TOKEN="$(env_get VAULT_TOKEN)"
|
||||
fi
|
||||
|
||||
if [[ -z "$VAULT_TOKEN" ]]; then
|
||||
die "Could not determine OpenBao VAULT_TOKEN from $CONFIG_DIR/bao-init.json or .env."
|
||||
fi
|
||||
|
||||
# UNSEAL_KEY is only set when OpenBao needed unsealing this run; on a re-run of
|
||||
# an already-unsealed store it is unset, so guard with ${UNSEAL_KEY:-} (set -u).
|
||||
if [[ -n "${UNSEAL_KEY:-}" ]]; then
|
||||
env_upsert VAULT_UNSEAL_KEY "$UNSEAL_KEY"
|
||||
fi
|
||||
env_upsert VAULT_TOKEN "$VAULT_TOKEN"
|
||||
|
||||
if ! docker exec -e BAO_TOKEN="$VAULT_TOKEN" openbao bao secrets list -format=json 2>/dev/null | grep -q '"secret/":'; then
|
||||
@@ -720,21 +809,6 @@ ensure_policy() {
|
||||
docker exec -i -e BAO_TOKEN="$VAULT_TOKEN" openbao bao policy write "$name" - >/dev/null
|
||||
}
|
||||
|
||||
# Read KEY= from ./.env (empty if absent) — reuse a previously minted token
|
||||
# instead of minting a fresh one on every setup.sh run.
|
||||
env_get() {
|
||||
local key="$1" file=./.env
|
||||
[[ -f "$file" ]] || return 0
|
||||
# `|| true` is load-bearing: under `set -euo pipefail`, a no-match `grep`
|
||||
# exits 1 and (pipefail) makes the whole pipeline return 1. Callers do
|
||||
# `existing="$(env_get ...)"` as a bare assignment — a non-zero return there
|
||||
# trips `set -e` and silently kills the whole script (this is exactly what
|
||||
# aborted a fresh install right after "Minting per-app OpenBao tokens": the
|
||||
# root VAULT_TOKEN env_upsert had already created .env, but the app-token
|
||||
# keys were absent, so the first env_get returned 1). "Key absent" is the
|
||||
# normal path here, so always return 0 with empty output.
|
||||
grep -m1 "^${key}=" "$file" 2>/dev/null | cut -d= -f2- || true
|
||||
}
|
||||
|
||||
# Mint an orphan, renewable token for `policy` and persist it to .env as `key`,
|
||||
# OR reuse the token already in .env if it is still valid (re-mint on expiry).
|
||||
@@ -856,6 +930,18 @@ for i in $(seq 1 60); do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# After a full OpenBao reset, the Redis-cached per-user/admin vault tokens (in
|
||||
# the persisted sso-data volume) reference the old, now-wiped store — drop them
|
||||
# so the broker re-mints fresh tokens against the new instance. Belt-and-
|
||||
# suspenders: the broker also always reconciles policy content before serving a
|
||||
# token, but a token minted by the previous OpenBao instance is simply invalid
|
||||
# there, so a cache flush is required after a reset.
|
||||
if [[ "$RESET_OPENBAO" == "1" ]]; then
|
||||
info " clearing cached vault tokens (old OpenBao instance)..."
|
||||
docker exec sso-manager sh -c "redis-cli EVAL \"for _,k in ipairs(redis.call('keys','vault_token:*')) do redis.call('del',k) end\" 0" \
|
||||
>/dev/null 2>&1 || warn " could not flush Redis vault-token cache (will re-mint on next access)"
|
||||
fi
|
||||
|
||||
info "Seeding app configs into OpenBao (idempotent)..."
|
||||
# sso-manager/conf holds the operator-set LDAP/SMTP/jwtSecret values — sso has
|
||||
# no bootstrap-generated creds, so the file is the complete source of truth.
|
||||
@@ -976,7 +1062,7 @@ async function ensureHost(host, ip, targetPort) {
|
||||
host: host,
|
||||
ip: ip,
|
||||
targetPort: targetPort,
|
||||
forcessl: true,
|
||||
forcessl: $( [[ "${CFG_CREATE_ALL_HTTP:-0}" == "1" ]] && echo false || echo true ),
|
||||
targetssl: false,
|
||||
sso_enabled: false,
|
||||
created_by: 'setup.sh',
|
||||
@@ -1033,7 +1119,7 @@ const {Host} = require('/app/models').models;
|
||||
try { await Host.get($(js_str "$JUMP_HOST")); console.log('SKIP ${JUMP_HOST} (already exists)'); }
|
||||
catch (e) {
|
||||
if (e.name !== 'EntryNotFound') throw e;
|
||||
await Host.create({ host: $(js_str "$JUMP_HOST"), ip: 'jump-host', targetPort: 3002, forcessl: true, targetssl: false, sso_enabled: false, created_by: 'setup.sh' });
|
||||
await Host.create({ host: $(js_str "$JUMP_HOST"), ip: 'jump-host', targetPort: 3002, forcessl: $( [[ "${CFG_CREATE_ALL_HTTP:-0}" == "1" ]] && echo false || echo true ), targetssl: false, sso_enabled: false, created_by: 'setup.sh' });
|
||||
console.log('CREATED ${JUMP_HOST} -> jump-host:3002');
|
||||
}
|
||||
process.exit(0);
|
||||
@@ -1043,9 +1129,114 @@ NODEEOF
|
||||
)
|
||||
echo "$JUMP_HOSTS_OUT" | sed 's/^/[setup] /'
|
||||
|
||||
# ── 7c. Install theta-agent on the host ──────────────────────────────────────
|
||||
# Controlled by CFG_THETA_AGENT_ENABLE (default: 1 = enabled)
|
||||
CFG_THETA_AGENT_ENABLE="${CFG_THETA_AGENT_ENABLE:-1}"
|
||||
if [[ "$CFG_THETA_AGENT_ENABLE" == "1" ]]; then
|
||||
info "Setting up theta-agent on the host..."
|
||||
(
|
||||
cd theta-agent || exit 0
|
||||
# Install the prebuilt binary that ships in the theta-agent submodule (the
|
||||
# repo's own install.sh uses the same release binary). We do NOT build from
|
||||
# source here: a previous `go build -o theta-agent main.go websocket.go
|
||||
# config.go` omitted executor.go/telemetry.go, failed to compile, and was
|
||||
# silently skipped, so the agent was never installed.
|
||||
if [[ ! -f "theta-agent-linux-amd64" ]]; then
|
||||
warn "Prebuilt theta-agent-linux-amd64 missing from the theta-agent submodule. Skipping theta-agent installation."
|
||||
else
|
||||
info " Installing prebuilt theta-agent binary..."
|
||||
if [[ -x "theta-agent-linux-amd64" ]]; then
|
||||
# The agent binary reads /etc/theta42/agent.yml (theta-agent/main.go).
|
||||
sudo mkdir -p /etc/theta42
|
||||
if [[ ! -f /etc/theta42/agent.yml ]]; then
|
||||
sudo cp agent.yml.example /etc/theta42/agent.yml
|
||||
AGENT_TOKEN="$(rand_hex 16)"
|
||||
sudo sed -i "s/REPLACE_WITH_AGENT_TOKEN/$AGENT_TOKEN/" /etc/theta42/agent.yml
|
||||
# We want to connect to either https or http depending on CFG_CREATE_ALL_HTTP
|
||||
if [[ "${CFG_CREATE_ALL_HTTP:-0}" == "1" ]]; then
|
||||
sudo sed -i "s|https://sso.example.com|http://${SSO_HOST}|" /etc/theta42/agent.yml
|
||||
else
|
||||
sudo sed -i "s|https://sso.example.com|https://${SSO_HOST}|" /etc/theta42/agent.yml
|
||||
fi
|
||||
fi
|
||||
sudo cp theta-agent-linux-amd64 /usr/local/bin/theta-agent
|
||||
sudo chmod +x /usr/local/bin/theta-agent
|
||||
|
||||
sudo bash -c "cat <<'EOF' > /etc/systemd/system/theta-agent.service
|
||||
[Unit]
|
||||
Description=Theta Agent
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/theta-agent
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF"
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now theta-agent.service
|
||||
info " theta-agent installed and started."
|
||||
fi
|
||||
fi
|
||||
)
|
||||
else
|
||||
info "theta-agent installation skipped (CFG_THETA_AGENT_ENABLE=0)."
|
||||
fi
|
||||
# ── 7d. Configure theta-agent integration with this host ─────────────────────
|
||||
# Non-interactive configuration driven by setup.env variables:
|
||||
# CFG_THETA_AGENT_ENABLE (default: 1) - Install/configure theta-agent
|
||||
# CFG_THETA_AGENT_LDAP_AUTH (default: 1) - Configure LDAP authentication via ldap-client
|
||||
# CFG_THETA_AGENT_FULL_CONTROL (default: 1) - Enable all agent capabilities
|
||||
# Only runs if theta-agent was installed (section 7c) or already exists.
|
||||
if [[ "$CFG_THETA_AGENT_ENABLE" == "1" ]] && [[ -x /usr/local/bin/theta-agent ]]; then
|
||||
info "Configuring theta-agent integration with this host..."
|
||||
|
||||
# Default to enabled unless explicitly disabled
|
||||
CFG_THETA_AGENT_LDAP_AUTH="${CFG_THETA_AGENT_LDAP_AUTH:-1}"
|
||||
CFG_THETA_AGENT_FULL_CONTROL="${CFG_THETA_AGENT_FULL_CONTROL:-1}"
|
||||
|
||||
if [[ "$CFG_THETA_AGENT_LDAP_AUTH" == "1" ]]; then
|
||||
info " Configuring LDAP authentication for this host..."
|
||||
(
|
||||
cd ldap-client || exit 0
|
||||
if [[ -x "index.sh" ]]; then
|
||||
bash index.sh --non-interactive 2>/dev/null || warn " ldap-client enrollment failed (continuing)..."
|
||||
fi
|
||||
)
|
||||
else
|
||||
info " LDAP authentication configuration skipped (CFG_THETA_AGENT_LDAP_AUTH=0)."
|
||||
fi
|
||||
|
||||
if [[ "$CFG_THETA_AGENT_FULL_CONTROL" == "1" ]]; then
|
||||
info " Configuring theta-agent with full host control capabilities..."
|
||||
if [[ -f /etc/theta42/agent.yml ]]; then
|
||||
sudo sed -i 's/arbitrary_bash: false/arbitrary_bash: true/' /etc/theta42/agent.yml
|
||||
# service_control is a []string allowlist (NOT a bool) — setting it to
|
||||
# `true` makes the agent fail YAML decode and crash-loop. There is no
|
||||
# wildcard; leave the operator's list (or the [] default = deny all)
|
||||
# alone and document how to enable specific services.
|
||||
# sudo sed -i 's/service_control: .*/service_control: true/' ...
|
||||
sudo sed -i 's/reboot: false/reboot: true/' /etc/theta42/agent.yml
|
||||
sudo sed -i 's/configure_ldap: false/configure_ldap: true/' /etc/theta42/agent.yml
|
||||
info " (service_control left as its allowlist; set e.g. service_control: [\"nginx\"] in /etc/theta42/agent.yml to permit managing specific services)"
|
||||
info " theta-agent full control enabled. Restarting service..."
|
||||
sudo systemctl restart theta-agent.service
|
||||
else
|
||||
warn " /etc/theta42/agent.yml not found. Full control not configured."
|
||||
fi
|
||||
else
|
||||
info " theta-agent running with limited capabilities (CFG_THETA_AGENT_FULL_CONTROL=0)."
|
||||
fi
|
||||
else
|
||||
info " theta-agent configuration skipped (agent not installed or CFG_THETA_AGENT_ENABLE=0)."
|
||||
fi
|
||||
|
||||
# ── 8. Summary ───────────────────────────────────────────────────────────────
|
||||
echo
|
||||
echo -e "\033[1;34m[setup]\033[0m \033[1;32mDone. Your SSO + proxy stack is up.\033[0m"
|
||||
printf '\033[1;34m[setup]\033[0m \033[1;32mDone. Your SSO + proxy stack is up.\033[0m\n'
|
||||
echo
|
||||
echo " SSO Manager UI: https://${SSO_HOST} (fronted by the proxy under TLS)"
|
||||
echo " first-run fallback: http://127.0.0.1:${SSO_PORT:-3001}"
|
||||
|
||||
+1
-1
Submodule sso-manager-node updated: 15b3a424bc...5aad6c13bf
+1
-1
Submodule theta-agent updated: 8a97bc7fbd...6500fadafb
Reference in New Issue
Block a user