diff --git a/deploy/systemd/README.md b/deploy/systemd/README.md index de36b60..c64efc5 100644 --- a/deploy/systemd/README.md +++ b/deploy/systemd/README.md @@ -12,6 +12,7 @@ python3 deploy/systemd/install_systemd.py \ --user mt2.jakubkadlec.dev \ --group mt2.jakubkadlec.dev \ --runtime-root /home/mt2.jakubkadlec.dev/metin/runtime/server \ + --env-file /etc/metin/metin.env \ --channel 1 \ --channel 99 \ --restart @@ -30,3 +31,14 @@ python3 deploy/systemd/install_systemd.py \ - `/usr/local/libexec/metin-wait-port` The `metin-db-ready.service` gate waits until the DB socket is actually accepting connections before `auth` and `game` units start. + +## Optional Environment File + +The runtime units support an optional `EnvironmentFile` for host-local overrides: + +- default path: `/etc/metin/metin.env` +- it is loaded with `EnvironmentFile=-...`, so the file may be absent +- recommended ownership: `root:root` +- recommended mode: `0600` + +This is the preferred place for production-only values such as DB credentials and the admin page password. diff --git a/deploy/systemd/install_systemd.py b/deploy/systemd/install_systemd.py index 693a2f6..5abbe5b 100644 --- a/deploy/systemd/install_systemd.py +++ b/deploy/systemd/install_systemd.py @@ -24,6 +24,7 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--runtime-root", required=True, help="Absolute path to the live runtime root") parser.add_argument("--systemd-dir", default="/etc/systemd/system", help="systemd unit destination") parser.add_argument("--libexec-dir", default="/usr/local/libexec", help="Helper script destination") + parser.add_argument("--env-file", default="/etc/metin/metin.env", help="Optional EnvironmentFile path for runtime overrides") parser.add_argument("--wait-host", default="127.0.0.1", help="DB readiness host") parser.add_argument("--wait-port", type=int, default=9000, help="DB readiness port") parser.add_argument("--wait-timeout", type=int, default=30, help="DB readiness timeout in seconds") @@ -118,6 +119,7 @@ def main() -> int: "USER_NAME": args.user, "GROUP_NAME": group_name, "RUNTIME_ROOT": runtime_root, + "ENV_FILE": args.env_file, "WAIT_HOST": args.wait_host, "WAIT_PORT": str(args.wait_port), "WAIT_TIMEOUT": str(args.wait_timeout), diff --git a/deploy/systemd/templates/metin-auth.service.in b/deploy/systemd/templates/metin-auth.service.in index 86b1621..05785b1 100644 --- a/deploy/systemd/templates/metin-auth.service.in +++ b/deploy/systemd/templates/metin-auth.service.in @@ -10,6 +10,7 @@ Before=metin-server.service Type=simple User={{USER_NAME}} Group={{GROUP_NAME}} +EnvironmentFile=-{{ENV_FILE}} WorkingDirectory={{RUNTIME_ROOT}}/channels/auth ExecStart={{RUNTIME_ROOT}}/channels/auth/game_auth Restart=on-failure diff --git a/deploy/systemd/templates/metin-db.service.in b/deploy/systemd/templates/metin-db.service.in index 33109fe..f541102 100644 --- a/deploy/systemd/templates/metin-db.service.in +++ b/deploy/systemd/templates/metin-db.service.in @@ -10,6 +10,7 @@ Before=metin-server.service Type=simple User={{USER_NAME}} Group={{GROUP_NAME}} +EnvironmentFile=-{{ENV_FILE}} WorkingDirectory={{RUNTIME_ROOT}}/channels/db ExecStart={{RUNTIME_ROOT}}/channels/db/db Restart=on-failure diff --git a/deploy/systemd/templates/metin-game@.service.in b/deploy/systemd/templates/metin-game@.service.in index dd15b94..3c5552a 100644 --- a/deploy/systemd/templates/metin-game@.service.in +++ b/deploy/systemd/templates/metin-game@.service.in @@ -10,6 +10,7 @@ Before=metin-server.service Type=simple User={{USER_NAME}} Group={{GROUP_NAME}} +EnvironmentFile=-{{ENV_FILE}} WorkingDirectory={{RUNTIME_ROOT}} ExecStart=/usr/local/libexec/metin-game-instance-start %i Restart=on-failure diff --git a/docs/config-and-secrets.md b/docs/config-and-secrets.md index 7ec9edd..e7ba340 100644 --- a/docs/config-and-secrets.md +++ b/docs/config-and-secrets.md @@ -36,6 +36,7 @@ For the current Debian VPS: - root-only operational wrappers may inject short-lived values locally - headless login healthcheck uses a temporary password via environment, not a command-line literal - the installed wrapper is root-only and not network-facing +- `systemd` units may load a host-local env file from `/etc/metin/metin.env` ## Admin Page Password @@ -54,3 +55,83 @@ The Debian deployment should eventually move to a clearer contract such as: - documented override points Until that is done, keep all real secret rotation and secret overrides on the host, not in commits. + +## Environment Override Contract + +The source/runtime stack now supports these host-local environment overrides: + +- `METIN2_ADMINPAGE_PASSWORD` +- `METIN2_DB_ADDR` +- `METIN2_DB_PORT` +- `METIN2_ACCOUNT_SQL_HOST` +- `METIN2_ACCOUNT_SQL_USER` +- `METIN2_ACCOUNT_SQL_PASSWORD` +- `METIN2_ACCOUNT_SQL_DB` +- `METIN2_ACCOUNT_SQL_PORT` +- `METIN2_PLAYER_SQL_HOST` +- `METIN2_PLAYER_SQL_USER` +- `METIN2_PLAYER_SQL_PASSWORD` +- `METIN2_PLAYER_SQL_DB` +- `METIN2_PLAYER_SQL_PORT` +- `METIN2_COMMON_SQL_HOST` +- `METIN2_COMMON_SQL_USER` +- `METIN2_COMMON_SQL_PASSWORD` +- `METIN2_COMMON_SQL_DB` +- `METIN2_COMMON_SQL_PORT` +- `METIN2_LOG_SQL_HOST` +- `METIN2_LOG_SQL_USER` +- `METIN2_LOG_SQL_PASSWORD` +- `METIN2_LOG_SQL_DB` +- `METIN2_LOG_SQL_PORT` +- `METIN2_HOTBACKUP_SQL_HOST` +- `METIN2_HOTBACKUP_SQL_USER` +- `METIN2_HOTBACKUP_SQL_PASSWORD` +- `METIN2_HOTBACKUP_SQL_DB` +- `METIN2_HOTBACKUP_SQL_PORT` + +`game_auth` and `game` consume the `ACCOUNT/PLAYER/COMMON/LOG` variants. The `db` process consumes `ACCOUNT/PLAYER/COMMON/HOTBACKUP`. + +Recommended deployment model: + +- keep git-tracked `share/conf/*.txt` as bootstrap defaults only +- install `/etc/metin/metin.env` as `root:root` with mode `0600` +- point systemd at that env file via `deploy/systemd/install_systemd.py --env-file /etc/metin/metin.env` + +Example: + +```bash +mkdir -p /etc/metin +chmod 700 /etc/metin +cat >/etc/metin/metin.env <<'EOF' +METIN2_ADMINPAGE_PASSWORD=replace-me +METIN2_DB_ADDR=127.0.0.1 +METIN2_DB_PORT=9000 +METIN2_ACCOUNT_SQL_HOST=127.0.0.1 +METIN2_ACCOUNT_SQL_USER=mt2 +METIN2_ACCOUNT_SQL_PASSWORD=replace-me +METIN2_ACCOUNT_SQL_DB=account +METIN2_ACCOUNT_SQL_PORT=0 +METIN2_PLAYER_SQL_HOST=127.0.0.1 +METIN2_PLAYER_SQL_USER=mt2 +METIN2_PLAYER_SQL_PASSWORD=replace-me +METIN2_PLAYER_SQL_DB=player +METIN2_PLAYER_SQL_PORT=0 +METIN2_COMMON_SQL_HOST=127.0.0.1 +METIN2_COMMON_SQL_USER=mt2 +METIN2_COMMON_SQL_PASSWORD=replace-me +METIN2_COMMON_SQL_DB=common +METIN2_COMMON_SQL_PORT=0 +METIN2_LOG_SQL_HOST=127.0.0.1 +METIN2_LOG_SQL_USER=mt2 +METIN2_LOG_SQL_PASSWORD=replace-me +METIN2_LOG_SQL_DB=log +METIN2_LOG_SQL_PORT=0 +METIN2_HOTBACKUP_SQL_HOST=127.0.0.1 +METIN2_HOTBACKUP_SQL_USER=mt2 +METIN2_HOTBACKUP_SQL_PASSWORD=replace-me +METIN2_HOTBACKUP_SQL_DB=hotbackup +METIN2_HOTBACKUP_SQL_PORT=0 +EOF +chown root:root /etc/metin/metin.env +chmod 600 /etc/metin/metin.env +``` diff --git a/docs/debian-runtime.md b/docs/debian-runtime.md index 1d7b432..775322b 100644 --- a/docs/debian-runtime.md +++ b/docs/debian-runtime.md @@ -94,6 +94,19 @@ systemctl restart metin-server journalctl -u metin-auth.service -n 100 --no-pager ``` +Install or refresh the systemd stack with a host-local env file: + +```bash +python3 deploy/systemd/install_systemd.py \ + --user mt2.jakubkadlec.dev \ + --group mt2.jakubkadlec.dev \ + --runtime-root /home/mt2.jakubkadlec.dev/metin/runtime/server \ + --env-file /etc/metin/metin.env \ + --channel 1 \ + --channel 99 \ + --restart +``` + Rebuild the login smoke utility: ```bash @@ -109,6 +122,8 @@ Current operational stance: - password SSH login is disabled - `root` login is allowed only by SSH key - production helper scripts that touch the DB directly are root-only -- runtime repo and source repo do not store secrets +- git-tracked runtime configs are treated as bootstrap defaults, not as the final secret source of truth Do not store production secrets in markdown, `systemd` templates, or git-tracked shell scripts. + +For production overrides, prefer `/etc/metin/metin.env` with `root:root` ownership and mode `0600`.