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
[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
+11 -1
View File
@@ -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
}
+2
View File
@@ -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"
+43 -1
View File
@@ -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<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.
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
+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
* 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).
# 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_MACOS := 0.5.0
VERSION_WINDOWS := 0.5.0