mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-23 03:27:43 +09:00
Document every panel environment variable
About 30 of the ~70 variables the backend reads were documented nowhere: the encryption-key file, extension signing keys, the pip opt-in, the setup-code pin. docs/ENVIRONMENT.md lists them all with defaults, and a guard fails when the backend reads one it doesn't list.
This commit is contained in:
1 parent
f840744341
commit
291b5df844
3 files changed
+212
No files matched your search
@@ -0,0 +1,90 @@
|
||||
"""Every environment variable the backend reads is listed in docs/ENVIRONMENT.md.
|
||||
|
||||
About 30 of the ~70 variables were documented nowhere, so operators found
|
||||
settings like the encryption-key file or the extension signing keys only by
|
||||
reading the source.
|
||||
"""
|
||||
import ast
|
||||
import os
|
||||
import re
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
BACKEND = os.path.dirname(HERE)
|
||||
DOC = os.path.join(os.path.dirname(BACKEND), 'docs', 'ENVIRONMENT.md')
|
||||
|
||||
# Read from the environment but not panel settings.
|
||||
NOT_SETTINGS = {
|
||||
'PATH', # passed through to child processes
|
||||
'APPDATA', # Windows dev fallback for the uptime history file
|
||||
}
|
||||
|
||||
ENV_HELPER = re.compile(r'^_?env_[a-z]+$')
|
||||
|
||||
|
||||
def _sources():
|
||||
yield os.path.join(BACKEND, 'config.py')
|
||||
yield os.path.join(BACKEND, 'run.py')
|
||||
for root, dirs, files in os.walk(os.path.join(BACKEND, 'app')):
|
||||
dirs[:] = [d for d in dirs if d not in ('plugins', '__pycache__')]
|
||||
for name in files:
|
||||
if name.endswith('.py'):
|
||||
yield os.path.join(root, name)
|
||||
|
||||
|
||||
def _string_constants(tree):
|
||||
out = {}
|
||||
for node in tree.body:
|
||||
if isinstance(node, ast.Assign) and isinstance(node.value, ast.Constant) \
|
||||
and isinstance(node.value.value, str):
|
||||
for target in node.targets:
|
||||
if isinstance(target, ast.Name):
|
||||
out[target.id] = node.value.value
|
||||
return out
|
||||
|
||||
|
||||
def _read_names(path):
|
||||
tree = ast.parse(open(path, encoding='utf-8').read())
|
||||
consts = _string_constants(tree)
|
||||
|
||||
def literal(node):
|
||||
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
||||
return node.value
|
||||
if isinstance(node, ast.Name):
|
||||
return consts.get(node.id)
|
||||
return None
|
||||
|
||||
for node in ast.walk(tree):
|
||||
key = None
|
||||
if isinstance(node, ast.Call) and node.args:
|
||||
func = ast.unparse(node.func)
|
||||
short = func.rsplit('.', 1)[-1]
|
||||
if func in ('os.environ.get', 'os.getenv', 'os.environ.setdefault') \
|
||||
or ENV_HELPER.match(short) or short == 'env_bool':
|
||||
key = literal(node.args[0])
|
||||
elif isinstance(node, ast.Subscript) and ast.unparse(node.value) == 'os.environ' \
|
||||
and isinstance(node.ctx, ast.Load):
|
||||
key = literal(node.slice)
|
||||
if key and re.fullmatch(r'[A-Z][A-Z0-9_]+', key):
|
||||
yield key
|
||||
|
||||
|
||||
def test_every_env_var_read_is_documented():
|
||||
documented = set(re.findall(r'^\| `([A-Z][A-Z0-9_]+)`', open(DOC, encoding='utf-8').read(), re.M))
|
||||
read = {}
|
||||
for path in _sources():
|
||||
for key in _read_names(path):
|
||||
read.setdefault(key, os.path.relpath(path, BACKEND))
|
||||
missing = sorted(k for k in read if k not in documented and k not in NOT_SETTINGS)
|
||||
assert not missing, (
|
||||
'Add these to docs/ENVIRONMENT.md: '
|
||||
+ ', '.join(f'{k} ({read[k]})' for k in missing))
|
||||
|
||||
|
||||
def test_the_scan_sees_reads_through_helpers_and_constants():
|
||||
"""The guard is only as good as its scan: config.py reads through
|
||||
_env_bool/_env_int and safe_http names its variable in a constant."""
|
||||
names = set()
|
||||
for path in _sources():
|
||||
names.update(_read_names(path))
|
||||
assert {'TRUST_PROXY_HEADERS', 'AUTH_IP_MAX_ATTEMPTS', 'SERVERKIT_ALLOW_PRIVATE_WEBHOOKS',
|
||||
'SECRET_KEY'} <= names
|
||||
@@ -0,0 +1,119 @@
|
||||
# Environment variables
|
||||
|
||||
Every environment variable the panel backend reads at runtime. Installer
|
||||
options (`SERVERKIT_PROFILE`, `PANEL_DOMAIN`, `SERVERKIT_SKIP_SSL`, ...) are
|
||||
covered in [INSTALLATION.md](INSTALLATION.md#install-options); this page is
|
||||
about the running panel.
|
||||
|
||||
On a host install the values live in `/opt/serverkit/.env`, loaded by the
|
||||
`serverkit-backend` systemd unit. With Docker they come from the compose file
|
||||
or `docker run -e`. Restart the panel after changing one.
|
||||
|
||||
`backend/tests/test_environment_doc.py` fails when the backend reads a
|
||||
variable that is not listed here.
|
||||
|
||||
## Core
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `FLASK_ENV` | `development` | `production` selects the production config, refuses the default secret keys or an empty `CORS_ORIGINS`, and auto-generates `SERVERKIT_ENCRYPTION_KEY` when it is missing. The systemd unit sets it. |
|
||||
| `SECRET_KEY` | insecure dev value | Flask session signing. Must be set in production. |
|
||||
| `JWT_SECRET_KEY` | insecure dev value | Signs access and refresh tokens. Changing it signs everyone out. |
|
||||
| `DATABASE_URL` | `sqlite:////app/instance/serverkit.db` | SQLAlchemy URL (`sqlite:///...` or `postgresql://...`). In Docker see the compose note in INSTALLATION.md. |
|
||||
| `LOG_LEVEL` | `INFO` (`DEBUG` in development) | Python logging level name (`DEBUG`, `INFO`, `WARNING`, ...). |
|
||||
| `PORT` | `5000` | Port for `python run.py`. Gunicorn deployments bind with `-b` instead. |
|
||||
| `SERVERKIT_PROFILE` | `standard` | Install profile (`minimal`, `standard`, `full`) when the panel setting has not overridden it. Written by the installer. |
|
||||
|
||||
## Network and proxy
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `CORS_ORIGINS` | local dev origins | Comma-separated allowed browser origins; required in production. `SERVERKIT_PUBLIC_URL` is added automatically. |
|
||||
| `SERVERKIT_PUBLIC_URL` | unset | The URL browsers and agents use to reach the panel. Needed behind your own proxy or tunnel, or websocket handshakes are rejected on origin. |
|
||||
| `TRUST_PROXY_HEADERS` | `false` | Trust `X-Forwarded-*` from the reverse proxy to find the real client IP. `true` behind the bundled nginx. See SECURITY.md. |
|
||||
| `TRUSTED_PROXY_HOPS` | `1` | How many proxies sit in front of the panel. |
|
||||
| `SERVERKIT_SSL_MODE` | read from `/etc/serverkit/ssl-mode`, else `insecure` | `secure` turns on HSTS. Only set it when the panel really serves end-to-end HTTPS. |
|
||||
| `SITES_BASE_DOMAIN` | empty (`lvh.me` in development) | Managed sites are published at `<slug>.<domain>`. Empty falls back to `localhost:<port>` URLs. The panel setting overrides it. |
|
||||
| `SERVER_PUBLIC_IP` | empty | IP that auto-created DNS records point at. |
|
||||
| `SERVERKIT_ALLOW_PRIVATE_WEBHOOKS` | off | Let outgoing webhooks and event subscriptions reach private/internal addresses (the SSRF guard blocks them by default). |
|
||||
|
||||
## Security and secrets
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `SERVERKIT_ENCRYPTION_KEY` | generated on first production boot | Key for secrets stored in the database. Back it up: losing it makes stored credentials unreadable. |
|
||||
| `SERVERKIT_ENV_FILE` | auto-detected | The `.env` file the generated encryption key is written to. |
|
||||
| `AUTH_IP_MAX_ATTEMPTS` | `10` | Failed logins from one IP before it is blocked. |
|
||||
| `AUTH_IP_WINDOW_MINUTES` | `15` | Window those attempts are counted in. |
|
||||
| `AUTH_IP_BLOCK_MINUTES` | `15` | How long the IP stays blocked. |
|
||||
| `RATELIMIT_STORAGE_URI` | in-memory | Rate-limit storage (e.g. `redis://...`). In-memory is correct for the single-worker panel. |
|
||||
| `SERVERKIT_PASSKEY_RP_ID` | host of `BASE_URL`, else `localhost` | WebAuthn relying-party ID: the panel's domain. |
|
||||
| `SERVERKIT_PASSKEY_ORIGIN` | `BASE_URL`, else `http://localhost` | Origin passkey ceremonies must come from. |
|
||||
| `SERVERKIT_PASSKEY_RP_NAME` | `ServerKit` | Name shown in the authenticator prompt. |
|
||||
| `BASE_URL` | unset | Fallback for the two passkey values above. |
|
||||
| `PROMETHEUS_TOKEN` | unset | Extra bearer token accepted by the Prometheus scrape endpoint, besides the one the panel generates. |
|
||||
| `SERVERKIT_SETUP_CODE` | generated | Pins the one-time code that the first admin registration requires, for automation. At least 12 characters. |
|
||||
| `SERVERKIT_DEMO_MODE` | off | Reject every mutating API request, for a public read-only demo. Wins over the panel setting. |
|
||||
|
||||
## Paths
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `SERVERKIT_INSTALL_DIR` | `/opt/serverkit` | Where the panel is installed. The systemd unit sets it. |
|
||||
| `SERVERKIT_DIR` | `/var/serverkit` | Data root (apps, deployments, imports). Also a lower-priority hint when locating `.env`. |
|
||||
| `SERVERKIT_CONFIG_DIR` | `/etc/serverkit` | Panel configuration files. |
|
||||
| `SERVERKIT_LOG_DIR` | `/var/log/serverkit` | Panel log directory. |
|
||||
| `SERVERKIT_LOG_FILE` | unset | A panel log file to include in support bundles (the systemd unit logs to the journal). |
|
||||
| `SERVERKIT_BACKUP_DIR` | `/var/backups/serverkit` | Update snapshots and backups. Must match `BACKUP_DIR` in `scripts/update.sh`. |
|
||||
| `SERVERKIT_CACHE_DIR` | `/var/cache/serverkit` | Caches, including build-pack workspaces. |
|
||||
| `BUILDPACK_WORKSPACE_DIR` | `$SERVERKIT_CACHE_DIR/buildpacks` | Where build packs stage sources. |
|
||||
| `SERVERKIT_IMPORTS_DIR` | `$SERVERKIT_DIR/imports` | Uploaded site-migration archives. |
|
||||
| `SERVERKIT_QUARANTINE_DIR` | `/var/quarantine` | Files the malware scanner quarantines. |
|
||||
| `SERVERKIT_TMP_DIR` | `/tmp` | Temp directory disk reclaim cleans. |
|
||||
| `SERVERKIT_LOCK_FILE` | `/var/lock/serverkit-update.lock` | Updater lock; slot retention waits while it is held. |
|
||||
| `SERVERKIT_SCRIPTS_DIR` | `<repo>/scripts` | Where the agent installers are served from, for layouts that split the backend from the tree. |
|
||||
| `VMAIL_DIR` | `/var/vmail` | Mailbox storage. |
|
||||
| `NGINX_CONF_DIR` | `/etc/nginx` | nginx configuration root. |
|
||||
| `SERVERKIT_WAF_DIR` | `/etc/nginx/serverkit-conf.d/waf` | Generated WAF rule files. |
|
||||
| `SERVERKIT_OWASP_CRS_PATH` | `/usr/share/modsecurity-crs/owasp-crs.load` | OWASP CRS entrypoint the WAF rules include. |
|
||||
| `SERVERKIT_MODSEC_AUDIT_LOG` | `/var/log/modsec_audit.log` | ModSecurity JSON audit log read for WAF events. |
|
||||
|
||||
## Binaries
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `NGINX_BIN` | `/usr/sbin/nginx` | nginx executable. |
|
||||
| `CERTBOT_BIN` | found on `PATH` | certbot executable. |
|
||||
| `NIXPACKS_BIN` | `nixpacks` | Optional nixpacks executable for opaque builds. |
|
||||
|
||||
## Extensions
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `SERVERKIT_BACKEND_PLUGINS_DIR` | `backend/app/plugins` | Where extension backends are installed. |
|
||||
| `SERVERKIT_FRONTEND_PLUGINS_DIR` | `frontend/src/plugins` | Where extension frontends are installed. Docker panels must set it and mount the folder to install extensions that ship a UI. |
|
||||
| `SERVERKIT_BUILTIN_EXTENSIONS_DIR` | `<repo>/builtin-extensions` | Bundled extensions offered for one-click install. |
|
||||
| `SERVERKIT_GITHUB_TOKEN` | unset | GitHub token for extension downloads: lifts the anonymous rate limit and allows private repositories. Sent only to GitHub hosts over HTTPS. |
|
||||
| `SERVERKIT_ALLOW_PRIVATE_DOWNLOADS` | off | Let extension downloads reach private addresses, for a private-network registry. |
|
||||
| `SERVERKIT_ALLOW_PLUGIN_PIP` | off | Install an extension's `requirements.txt`. Off by default because pip runs with the panel's privileges and a `setup.py` is arbitrary code. |
|
||||
| `SERVERKIT_TRUSTED_EXTENSION_KEYS` | unset | Path to an extra keys file trusted for extension signatures, beside the pinned first-party keys. |
|
||||
|
||||
## Integrations
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `REDIS_URL` | `redis://localhost:6379/0` | Optional Redis for the cache; the panel works without it. |
|
||||
| `SERVERKIT_AGENT_GITHUB_REPO` | the official agent repository | Repository agent releases and installers are fetched from. |
|
||||
| `SERVERKIT_CLOUD_URL` | the hosted service URL | Endpoint `serverkit connect` pairs with. A `--cloud` flag wins. |
|
||||
| `LINKED_PANEL_ALLOWED_WRITE_ROOTS` | `/var/serverkit/apps,/etc/serverkit` | Comma-separated directories a master panel may write to when this panel runs as its linked worker. |
|
||||
| `ANTHROPIC_BASE_URL` | unset | Not a panel setting: when set in the panel's process environment the native Claude driver would inherit it, so Claude connections refuse to run. Remove it, or configure a compatible-gateway connection instead. |
|
||||
|
||||
## Development and testing
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `SERVERKIT_SKIP_BACKGROUND` | off | Do not start background threads (metrics, queues, jobs). The updater sets it while running migrations. |
|
||||
| `SERVERKIT_DEMO_DEPLOYS` | off | Enable scripted demo deployments that exercise the deploy console without Docker. |
|
||||
| `SERVERKIT_PROFILE_REQUESTS` | off | Measure each API request's time and SQL query count, for local or staging profiling. No SQL text or parameters are recorded. |
|
||||
| `SERVERKIT_STAGING` | off | Mark a staging instance; `/system/health` reports it. |
|
||||
| `TEST_DATABASE_URL` | a per-process temp SQLite file | Database for the test suite (set by `tests/conftest.py` when unset). |
|
||||
@@ -64,6 +64,9 @@ On a `minimal` install:
|
||||
| `SERVERKIT_OFFLINE_TARBALL=/path/to/...tar.gz` | Use a local tarball instead of downloading |
|
||||
| `SERVERKIT_MIRROR_URL=https://mirror.example.com/releases` | Fetch releases/checksums from a private mirror |
|
||||
|
||||
These options apply to the installer. Settings the running panel reads from
|
||||
its `.env` are listed in [ENVIRONMENT.md](ENVIRONMENT.md).
|
||||
|
||||
Example with a domain:
|
||||
|
||||
```bash
|
||||
|
||||
Reference in new issue
Block a user