Set the server's public URL from KEEPORG_EXTERNAL_URL
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful

The image passes neither -config nor -url, so a deployment had no way to
set external_url and the server built invite links, device-approval pages
and the OIDC redirect URI on http://localhost:8765. The variable overrides
the configuration file and -url overrides it. The deployment values set it
to https://keeporg.tiredsysadmin.cc.

Also document every environment variable the server reads, and the
standard proxy and CA variables it honours for OIDC.

Server version 0.8.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Reactorcoremeltdown
2026-11-02 12:40:09 +01:00
co-authored by Claude Opus 5.5
parent ead82dc777
commit ef6598fa15
6 changed files with 78 additions and 3 deletions
+13
View File
@@ -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 your secret store injects variables instead of files. See
[packaging/docker/compose.yaml](packaging/docker/compose.yaml). [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 ## Documentation
- [docs/HANDOVER.md](docs/HANDOVER.md) — architecture, the decisions behind - [docs/HANDOVER.md](docs/HANDOVER.md) — architecture, the decisions behind
+11 -1
View File
@@ -17,6 +17,7 @@ import (
"os" "os"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"strings"
"syscall" "syscall"
"time" "time"
@@ -26,6 +27,9 @@ import (
"cc.tiredsysadmin.keeporg/internal/store" "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. // Version is stamped at build time from versions.mk.
var Version = "dev" var Version = "dev"
@@ -82,7 +86,7 @@ func main() {
configPath = flag.String("config", "", "path to a JSON configuration file") configPath = flag.String("config", "", "path to a JSON configuration file")
addr = flag.String("addr", "", "listen address (overrides the config file)") addr = flag.String("addr", "", "listen address (overrides the config file)")
dataDir = flag.String("data", "", "data directory (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", "", masterKey = flag.String("master-key-file", "",
"file holding the master key that wraps the vault keys, created if "+ "file holding the master key that wraps the vault keys, created if "+
"absent; $"+secret.MasterKeyEnv+" and $"+secret.MasterKeyFileEnv+ "absent; $"+secret.MasterKeyEnv+" and $"+secret.MasterKeyFileEnv+
@@ -121,6 +125,12 @@ func main() {
} }
} }
cfg := fc.toConfig() 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 != "" { if *addr != "" {
cfg.Addr = *addr cfg.Addr = *addr
} }
+2
View File
@@ -3,6 +3,8 @@ namespace: "apps"
environment: environment:
- name: "KEEPORG_MASTER_KEY_FILE" - name: "KEEPORG_MASTER_KEY_FILE"
value: "/etc/keeporg/config.txt" value: "/etc/keeporg/config.txt"
- name: "KEEPORG_EXTERNAL_URL"
value: "https://keeporg.tiredsysadmin.cc"
image: image:
name: "repo.rcmd.space/keeporg" name: "repo.rcmd.space/keeporg"
version: "v0.1.0" version: "v0.1.0"
+43 -1
View File
@@ -599,9 +599,51 @@ things.
``` ```
`external_url` must be what a browser sees: invite links, device approval `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`. 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<addr>`, 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. Back up `/var/lib/keeporg` **and** `/etc/keeporg/master.key`, separately.
The data directory is the whole server: `state.json` plus the blobs. The 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 master key is what turns the wrapped vault keys in it back into vault
+8
View File
@@ -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 <github@reactorcoremeltdown.anonaddy.com> Mon, 02 Nov 2026 12:37:29 +0100
keeporgd (0.7.0) unstable; urgency=medium keeporgd (0.7.0) unstable; urgency=medium
* Deleting a database now requires the caller's own account password * Deleting a database now requires the caller's own account password
+1 -1
View File
@@ -1,6 +1,6 @@
# Single source of truth for all platform versions (semver). # Single source of truth for all platform versions (semver).
# Bump with: make bump PLATFORM=<server|linux|macos|windows|bsd|android> PART=<major|minor|patch> # Bump with: make bump PLATFORM=<server|linux|macos|windows|bsd|android> PART=<major|minor|patch>
VERSION_SERVER := 0.7.0 VERSION_SERVER := 0.8.0
VERSION_LINUX := 0.5.0 VERSION_LINUX := 0.5.0
VERSION_MACOS := 0.5.0 VERSION_MACOS := 0.5.0
VERSION_WINDOWS := 0.5.0 VERSION_WINDOWS := 0.5.0