diff --git a/README.md b/README.md index 9237cd0..bb58fc7 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,19 @@ can no longer merge. `KEEPORG_MASTER_KEY` takes the base64 key directly if your secret store injects variables instead of files. See [packaging/docker/compose.yaml](packaging/docker/compose.yaml). +Server environment variables: + +| Variable | Meaning | +| --- | --- | +| `KEEPORG_MASTER_KEY` | the master key, base64; overrides the other two sources | +| `KEEPORG_MASTER_KEY_FILE` | path to a file holding the master key (base64 or 32 raw bytes); never created, and if it is missing the server runs but cannot create or merge vaults | +| `KEEPORG_EXTERNAL_URL` | the public base URL (`external_url`); `-url` overrides it | +| `KEEPORG_ADMIN_PASSWORD` | the first `admin` password, read only on an empty server and by `-reset-admin` | + +Everything else is configured in `server.json` or by flag. The details, +including the standard proxy and CA variables, are in +[docs/HANDOVER.md](docs/HANDOVER.md#environment-variables). + ## Documentation - [docs/HANDOVER.md](docs/HANDOVER.md) — architecture, the decisions behind diff --git a/cmd/keeporgd/main.go b/cmd/keeporgd/main.go index 377885e..f0d0f32 100644 --- a/cmd/keeporgd/main.go +++ b/cmd/keeporgd/main.go @@ -17,6 +17,7 @@ import ( "os" "os/signal" "path/filepath" + "strings" "syscall" "time" @@ -26,6 +27,9 @@ import ( "cc.tiredsysadmin.keeporg/internal/store" ) +// externalURLEnv sets external_url, between the configuration file and -url. +const externalURLEnv = "KEEPORG_EXTERNAL_URL" + // Version is stamped at build time from versions.mk. var Version = "dev" @@ -82,7 +86,7 @@ func main() { configPath = flag.String("config", "", "path to a JSON configuration file") addr = flag.String("addr", "", "listen address (overrides the config file)") dataDir = flag.String("data", "", "data directory (overrides the config file)") - extURL = flag.String("url", "", "externally reachable base URL (overrides the config file)") + extURL = flag.String("url", "", "externally reachable base URL (overrides $"+externalURLEnv+" and the config file)") masterKey = flag.String("master-key-file", "", "file holding the master key that wraps the vault keys, created if "+ "absent; $"+secret.MasterKeyEnv+" and $"+secret.MasterKeyFileEnv+ @@ -121,6 +125,12 @@ func main() { } } cfg := fc.toConfig() + // The public URL is the one setting a deployment usually knows only at + // deploy time, so it can come from the environment as well. A flag + // still wins. + if v := strings.TrimSpace(os.Getenv(externalURLEnv)); v != "" { + cfg.ExternalURL = v + } if *addr != "" { cfg.Addr = *addr } diff --git a/config/variables/server.values.yaml b/config/variables/server.values.yaml index e6c1ab7..6009e5a 100644 --- a/config/variables/server.values.yaml +++ b/config/variables/server.values.yaml @@ -3,6 +3,8 @@ namespace: "apps" environment: - name: "KEEPORG_MASTER_KEY_FILE" value: "/etc/keeporg/config.txt" +- name: "KEEPORG_EXTERNAL_URL" + value: "https://keeporg.tiredsysadmin.cc" image: name: "repo.rcmd.space/keeporg" version: "v0.1.0" diff --git a/docs/HANDOVER.md b/docs/HANDOVER.md index b4b4272..ba52b71 100644 --- a/docs/HANDOVER.md +++ b/docs/HANDOVER.md @@ -599,9 +599,51 @@ things. ``` `external_url` must be what a browser sees: invite links, device approval -pages and the OIDC redirect URI are built from it. Terminate TLS in front of +pages and the OIDC redirect URI are built from it. `KEEPORG_EXTERNAL_URL` +sets it too, for a deployment that knows the address only at deploy time. Terminate TLS in front of the server, or set `tls_cert` and `tls_key`. +### Environment variables + +The server reads four variables of its own: the public URL, and secrets or +first-run material. Everything else — address, data directory, OIDC, +limits — is set only in `server.json` or by flag, with flags winning; there +is no environment equivalent for those. An empty or whitespace-only value +counts as unset. + +| Variable | Read by | Meaning | +| --- | --- | --- | +| `KEEPORG_MASTER_KEY` | every start | The master key itself, base64 of 32 bytes. Takes precedence over everything below. Never written anywhere. A value that does not decode has the same effect as a missing key file. | +| `KEEPORG_MASTER_KEY_FILE` | every start, when `KEEPORG_MASTER_KEY` is unset | Path to a file holding the key, as base64 text or exactly 32 raw bytes. The file must exist and is never created, since a new key would open none of the existing vaults. If it is missing or malformed the server logs `no master key, so this server cannot create or merge vaults` and runs without one: key-less strategies keep working, vault creation and merging are refused. Takes precedence over `master_key_file` / `-master-key-file`. | +| `KEEPORG_EXTERNAL_URL` | every start | The base URL a browser reaches the server at, as `external_url`. Overrides the file; `-url` overrides it. Invite links, the device-approval page every client sign-in uses, and the OIDC redirect URI are built from it, and without any of the three it is `http://localhost`, which is wrong behind any proxy. | +| `KEEPORG_ADMIN_PASSWORD` | first start on an empty state, and `-reset-admin` | The password for the built-in `admin` account instead of a generated one, which is then not logged. Ignored once any account exists, so leaving it set is harmless but pointless; unset it after the first start. | + +With neither master-key variable set, the key comes from `master_key_file` +(default `/etc/keeporg/master.key`), which *is* created if missing. See +[Key custody](#key-custody-and-encryption-at-rest) for why the three sources +behave differently. + +The server also honours the Go runtime's standard variables, which matter +only for its outbound calls to an OIDC provider: + +| Variable | Effect | +| --- | --- | +| `HTTPS_PROXY`, `HTTP_PROXY`, `NO_PROXY` | proxy for the discovery, token and userinfo requests | +| `SSL_CERT_FILE`, `SSL_CERT_DIR` | CA bundle to trust instead of the system's (Linux and BSD), for an identity provider behind a private CA; in the container the bundle is `/etc/ssl/certs/ca-certificates.crt` | + +`packaging/docker/compose.yaml` additionally uses `KEEPORG_VERSION` to pick +the image tag and build argument. That is read by compose, not by the +server. + +In the deployment under `config/variables/server.values.yaml`, the master +key arrives as the Kubernetes secret `keeporg-encryption` mounted on +`/etc/keeporg`, and `KEEPORG_MASTER_KEY_FILE=/etc/keeporg/config.txt` points +at it. `KEEPORG_EXTERNAL_URL=https://keeporg.tiredsysadmin.cc` matches the +nginx `server_name`; the image's command passes neither `-config` nor +`-url`, so this variable is the only thing that sets it there. +`KEEPORG_ADMIN_PASSWORD` is not set there, so the first start logs a +generated password once. + Back up `/var/lib/keeporg` **and** `/etc/keeporg/master.key`, separately. The data directory is the whole server: `state.json` plus the blobs. The master key is what turns the wrapped vault keys in it back into vault diff --git a/packaging/deb/server/changelog b/packaging/deb/server/changelog index 225b585..a1182f8 100644 --- a/packaging/deb/server/changelog +++ b/packaging/deb/server/changelog @@ -1,3 +1,11 @@ +keeporgd (0.8.0) unstable; urgency=medium + + * The public base URL can be set with the KEEPORG_EXTERNAL_URL + environment variable. It overrides external_url in the configuration + file; -url overrides it. + + -- Azer Abdullaev Mon, 02 Nov 2026 12:37:29 +0100 + keeporgd (0.7.0) unstable; urgency=medium * Deleting a database now requires the caller's own account password diff --git a/versions.mk b/versions.mk index b229659..6cbd18b 100644 --- a/versions.mk +++ b/versions.mk @@ -1,6 +1,6 @@ # Single source of truth for all platform versions (semver). # Bump with: make bump PLATFORM= PART= -VERSION_SERVER := 0.7.0 +VERSION_SERVER := 0.8.0 VERSION_LINUX := 0.5.0 VERSION_MACOS := 0.5.0 VERSION_WINDOWS := 0.5.0