Compare commits
6 Commits
a09f247290
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| c439c1b948 | |||
| 0fe85c67d9 | |||
| 09eecf5969 | |||
| e444ea5936 | |||
| c917e4908c | |||
| ef849b085f |
+1
-1
@@ -6,7 +6,7 @@ keys:
|
|||||||
- &bfs-server age15yzgmsvl3ku2w863h6gw2vpmw37m9aruv6xrj4fue6n2jpm7pyuqk9xjmj
|
- &bfs-server age15yzgmsvl3ku2w863h6gw2vpmw37m9aruv6xrj4fue6n2jpm7pyuqk9xjmj
|
||||||
- &bfs-pol-server age1fpytf05sg9n6ywpwkmn09lhpfvgtud9h75h76jhxha475zpnasqq952rpu
|
- &bfs-pol-server age1fpytf05sg9n6ywpwkmn09lhpfvgtud9h75h76jhxha475zpnasqq952rpu
|
||||||
- &bfs-new-server age17yx98qk9gzgcf2q6zhhp05p6mmtrkgz66dvyk9gqclypvlr8rersxjy5v7
|
- &bfs-new-server age17yx98qk9gzgcf2q6zhhp05p6mmtrkgz66dvyk9gqclypvlr8rersxjy5v7
|
||||||
- &neuro-server age15yzgmsvl3ku2w863h6gw2vpmw37m9aruv6xrj4fue6n2jpm7pyuqk9xjmj
|
- &neuro-server age1ak7heljpr0pjr4m0rcwxgn3sp0jjxw03lxyf33r8lcemqh2u2sgqx0aplq
|
||||||
- &games-server age15yzgmsvl3ku2w863h6gw2vpmw37m9aruv6xrj4fue6n2jpm7pyuqk9xjmj
|
- &games-server age15yzgmsvl3ku2w863h6gw2vpmw37m9aruv6xrj4fue6n2jpm7pyuqk9xjmj
|
||||||
- &hectic-lab-server age13h8twnwvgxn04l5ywtru89a6psw5d0uckr2eghxsjp88a5augvsstq5ard
|
- &hectic-lab-server age13h8twnwvgxn04l5ywtru89a6psw5d0uckr2eghxsjp88a5augvsstq5ard
|
||||||
- &umbriel-bfs age1jxntjca8q2vxvf2jaal4xyvm2ae6sh62fhv897694kuzawfrk5asj00zdt
|
- &umbriel-bfs age1jxntjca8q2vxvf2jaal4xyvm2ae6sh62fhv897694kuzawfrk5asj00zdt
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# Documentation
|
# Documentation
|
||||||
|
|
||||||
- [Using the `hectic` Attic Cache](./attic-cache.md)
|
- [Using the `hectic` Attic Cache](./attic-cache.md)
|
||||||
|
- [Minecraft incident log](./minecraft-incidents.md)
|
||||||
|
- [Project Zomboid backups](./project-zomboid-backups.md)
|
||||||
|
|
||||||
## Gitea runner labels
|
## Gitea runner labels
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# Minecraft incident log
|
||||||
|
|
||||||
|
This file records only observed evidence, actions, and verification results.
|
||||||
|
An entity appearing in a stack trace is a trigger-path observation, not a
|
||||||
|
proven root cause.
|
||||||
|
|
||||||
|
## 2026-09-19 — WorldOfSosal crashes in Sable block-change handling
|
||||||
|
|
||||||
|
### Impact
|
||||||
|
|
||||||
|
- `minecraft-server-wowMineMap.service` terminates while a player is online.
|
||||||
|
- Public Minecraft endpoint is `store.hectic-lab.com:25568`.
|
||||||
|
- Server is intentionally stopped after the latest crash to prevent repeated
|
||||||
|
crash-save cycles while recovery is investigated.
|
||||||
|
|
||||||
|
### Observed evidence
|
||||||
|
|
||||||
|
All crash reports contain `sable@2.0.5` in
|
||||||
|
`LevelAccelerator.getBlockState`, followed by
|
||||||
|
`ArrayIndexOutOfBoundsException` where the requested section index exceeds
|
||||||
|
the world section array length of `24`.
|
||||||
|
|
||||||
|
| UTC timestamp | Crash report | Observed trigger path | Exception |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 18:47:03 | `crash-2026-09-19_18.47.03-server.txt` | `EnderMan$EndermanTakeBlockGoal.tick` | index `38` / length `24` |
|
||||||
|
| 18:52:17 | `crash-2026-09-19_18.52.17-server.txt` | `GlowSquid.aiStep` → `RedStoneOreBlock.stepOn` | index `33` / length `24` |
|
||||||
|
| 19:14:46 | `crash-2026-09-19_19.14.46-server.txt` | `Skeleton.tick` → `RedStoneOreBlock.stepOn` | index `34` / length `24` |
|
||||||
|
|
||||||
|
Evidence locations on `neuro`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/srv/minecraft/wowMineMap/crash-reports/
|
||||||
|
/srv/minecraft/wowMineMap/logs/latest.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Actions performed
|
||||||
|
|
||||||
|
| UTC timestamp | Action | Result |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 17:51 | Archived current world before recovery | Archive checksum recorded |
|
||||||
|
| 18:08 | Set `randomTickSpeed=0` | Server started, but later crashed from an entity block change |
|
||||||
|
| 18:48 | Set `mobGriefing=false` | Prevented Enderman block pickup only; later crashes still occurred |
|
||||||
|
| 18:54 | Archived post-crash world | Archive checksum recorded |
|
||||||
|
| 19:00 | Moved Boss offline player NBT from `(3299.067, 142.630, 8613.742)` to `(3296, 500, 8608)` in `crafting_azeroth:azeroth` | Only `Pos` and `Dimension` changed; later crash still occurred |
|
||||||
|
| after 19:14 crash | Stopped `minecraft-server-wowMineMap.service` | Prevented further automatic crash/restart saves |
|
||||||
|
|
||||||
|
### Recovery artifacts
|
||||||
|
|
||||||
|
```text
|
||||||
|
/srv/minecraft/backups/wowMineMap-before-sable-recovery-20260919T175139Z.tar.zst
|
||||||
|
/srv/minecraft/backups/wowMineMap-after-sable-crashes-20260919T185445Z.tar.zst
|
||||||
|
/srv/minecraft/wowMineMap/world/playerdata/1c189af5-2713-3fa6-bcc4-893dfadedfa4.dat.before-relocation
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conclusions supported by evidence
|
||||||
|
|
||||||
|
- Public proxy and reverse tunnel are not the failure point: server-list ping
|
||||||
|
succeeded before later in-world crashes.
|
||||||
|
- The failure is not limited to Endermen, random ticks, or one player
|
||||||
|
position.
|
||||||
|
- Sable's block-change callback is present in every captured crash.
|
||||||
|
|
||||||
|
### Not established
|
||||||
|
|
||||||
|
- Exact corrupt chunk, block, or mod data.
|
||||||
|
- Whether world data is corrupt, Sable itself is defective, or another mod is
|
||||||
|
supplying incompatible world state.
|
||||||
|
- Whether deleting any chunk, region, or Sable state would be safe.
|
||||||
|
|
||||||
|
### External research
|
||||||
|
|
||||||
|
No exact upstream match was found for Sable `2.0.5` on NeoForge `1.21.1` with
|
||||||
|
`LevelAccelerator.getBlockState` and a requested section index of `33`, `34`,
|
||||||
|
or `38` against a section array of length `24`.
|
||||||
|
|
||||||
|
Related but non-identical upstream reports:
|
||||||
|
|
||||||
|
- [Sable #776](https://github.com/ryanhcode/sable/issues/776) documents an
|
||||||
|
`ArrayIndexOutOfBoundsException` associated with unusual dimension height
|
||||||
|
bounds. This is relevant to section-coordinate handling, but is an older
|
||||||
|
version and different stack trace.
|
||||||
|
- [Sable #1087](https://github.com/ryanhcode/sable/issues/1087) documents a
|
||||||
|
`LevelAccelerator.getBlockState` recursion during block-shape processing.
|
||||||
|
The failure type differs.
|
||||||
|
- [Sable #820](https://github.com/ryanhcode/sable/issues/820) documents a
|
||||||
|
ticking-entity block-change crash. The reported downgrade to `1.1.3` helped
|
||||||
|
that distinct recursive-update failure; it is not evidence for this crash.
|
||||||
|
- [Sable #1223](https://github.com/ryanhcode/sable/issues/1223) documents a
|
||||||
|
different `ArrayIndexOutOfBoundsException` in voxel-neighborhood handling.
|
||||||
|
Its suggested Lithium setting only reduced crashes for some reporters and is
|
||||||
|
not a verified mitigation here.
|
||||||
|
|
||||||
|
Sable `2.0.4` and `2.0.5` release notes mention other block or contraption
|
||||||
|
crash fixes, but not this exception. No version upgrade or downgrade is
|
||||||
|
currently evidence-backed as a production fix.
|
||||||
|
|
||||||
|
### Next recovery step
|
||||||
|
|
||||||
|
Use a disposable full-world copy to test a supported Sable/physics integration
|
||||||
|
mitigation. Do not restart production, delete region files, or overwrite a
|
||||||
|
backup until that test gives reproducible evidence.
|
||||||
+55
-9
@@ -1,7 +1,7 @@
|
|||||||
# WorldOfSosal: Prism automatic updates
|
# WorldOfSosal: Prism automatic updates
|
||||||
|
|
||||||
The published client entry points are:
|
The published client entry points are:
|
||||||
- https://bfs.band/minecraft/ (BFS / Element host)
|
- https://store.bfs.band/minecraft/ (BFS / Element host)
|
||||||
- https://store.hectic-lab.com/minecraft/world-of-sosal/ (hectic-lab)
|
- https://store.hectic-lab.com/minecraft/world-of-sosal/ (hectic-lab)
|
||||||
|
|
||||||
Each site provides its own Prism ZIP with that site's update URL and matching
|
Each site provides its own Prism ZIP with that site's update URL and matching
|
||||||
@@ -95,8 +95,8 @@ Both services and firewall rules are in Nix and start on boot. The SSH client
|
|||||||
uses an explicit AES-CTR / HMAC-SHA256-ETM / curve25519 transport profile with
|
uses an explicit AES-CTR / HMAC-SHA256-ETM / curve25519 transport profile with
|
||||||
IPQoS=none, tested on the neuro-to-lab route. The default profile stalled after
|
IPQoS=none, tested on the neuro-to-lab route. The default profile stalled after
|
||||||
the handshake on this route. Both ends check peer liveness so stale listeners
|
the handshake on this route. Both ends check peer liveness so stale listeners
|
||||||
are eventually released. Minecraft retains
|
are eventually released. Minecraft initially used `online-mode=true`. It now uses offline mode at the
|
||||||
`online-mode=true`, requiring normal authenticated Minecraft accounts.
|
owner's request; see the RCON and authentication section below.
|
||||||
|
|
||||||
For a temporary direct local tunnel, use:
|
For a temporary direct local tunnel, use:
|
||||||
|
|
||||||
@@ -156,22 +156,22 @@ SHA-256 was verified before extraction.
|
|||||||
|
|
||||||
## Independent BFS entry point (2026-09-19)
|
## Independent BFS entry point (2026-09-19)
|
||||||
|
|
||||||
- Server: `bfs.band:25568`; downloads: https://bfs.band/minecraft/.
|
- Server: `wow.bfs.band`; downloads: https://store.bfs.band/minecraft/.
|
||||||
- BFS is `bfs.poland.xray` (91.198.166.181), the host of Element.
|
- BFS is `bfs.poland.xray` (91.198.166.181), the host of Element.
|
||||||
- `minecraft-wow-tunnel-bfs` connects neuro directly to BFS. The BFS path does
|
- `minecraft-wow-tunnel-bfs` connects neuro directly to BFS. The BFS path does
|
||||||
not transit hectic-lab; both tunnels have independent reconnecting services.
|
not transit hectic-lab; both tunnels have independent reconnecting services.
|
||||||
- Shared proxy implementation: `nixos/module/generic/minecraft-public-relay.nix`.
|
- Shared proxy implementation: `nixos/module/generic/minecraft-public-relay.nix`.
|
||||||
Host settings remain in `minecraft-wow-proxy.nix` (hectic-lab) and
|
Host settings remain in `minecraft-wow-proxy.nix` (hectic-lab) and
|
||||||
`minecraft-wow.nix` (BFS). Only `/minecraft/` is added to the existing BFS
|
`minecraft-wow.nix` (BFS). A dedicated HTTPS virtual host serves `store.bfs.band`. The legacy
|
||||||
nginx virtual host; Element/Matrix routes remain intact.
|
`bfs.band/minecraft/` URLs remain available for already imported instances.
|
||||||
- Downloaded BFS ZIP seeds `bfs.band:25568` and uses the stable manifest
|
- Downloaded BFS ZIP seeds `wow.bfs.band` and uses the stable manifest
|
||||||
`https://bfs.band/minecraft/world-of-sosal/current/pack.toml`. It does not
|
`https://store.bfs.band/minecraft/world-of-sosal/current/pack.toml`. It does not
|
||||||
redirect installation metadata to hectic-lab. Upstream mod and Java/loader
|
redirect installation metadata to hectic-lab. Upstream mod and Java/loader
|
||||||
downloads still use their original providers (e.g. Modrinth, GitHub, Mojang).
|
downloads still use their original providers (e.g. Modrinth, GitHub, Mojang).
|
||||||
- Existing hectic-lab instances can be migrated without reinstalling mods:
|
- Existing hectic-lab instances can be migrated without reinstalling mods:
|
||||||
in Edit / Settings / Custom commands, replace only the manifest URL in
|
in Edit / Settings / Custom commands, replace only the manifest URL in
|
||||||
Pre-launch command with the BFS URL above. Change the multiplayer server
|
Pre-launch command with the BFS URL above. Change the multiplayer server
|
||||||
address to bfs.band:25568. New users should import the ZIP from BFS.
|
address to wow.bfs.band. New users should import the ZIP from BFS.
|
||||||
- `script/publish-prism-mirrors.py` builds host-specific ZIPs from one archive
|
- `script/publish-prism-mirrors.py` builds host-specific ZIPs from one archive
|
||||||
and publishes both mirrors. It checks that the running neuro server's cached
|
and publishes both mirrors. It checks that the running neuro server's cached
|
||||||
archive has the same SHA-256. Each host's switch is atomic; publication across
|
archive has the same SHA-256. Each host's switch is atomic; publication across
|
||||||
@@ -183,3 +183,49 @@ Clean installation through the BFS manifest passed: all 141 client mods and
|
|||||||
all overrides match the source archive. A second updater run performed no
|
all overrides match the source archive. A second updater run performed no
|
||||||
downloads and preserved options.txt. The public BFS login protocol reached
|
downloads and preserved options.txt. The public BFS login protocol reached
|
||||||
online authentication; the earlier full GUI login used hectic-lab.
|
online authentication; the earlier full GUI login used hectic-lab.
|
||||||
|
|
||||||
|
## BFS DNS and dedicated download site (2026-09-19)
|
||||||
|
|
||||||
|
Porkbun DNS, TTL 600:
|
||||||
|
|
||||||
|
| Type | Name | Value |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| A | store.bfs.band | 91.198.166.181 |
|
||||||
|
| A | wow.bfs.band | 91.198.166.181 |
|
||||||
|
| SRV | _minecraft._tcp.wow.bfs.band | 0 0 25568 wow.bfs.band |
|
||||||
|
|
||||||
|
Players enter `wow.bfs.band` without a port in Minecraft Java. In Porkbun,
|
||||||
|
SRV Priority is `0`, and Target is `0 25568 wow.bfs.band` (weight, port, host).
|
||||||
|
The root download URL https://store.bfs.band/ redirects to the WorldOfSosal page.
|
||||||
|
The NixOS virtual host obtains and renews its HTTPS certificate automatically.
|
||||||
|
The publication script now seeds this update URL and the port-free game address.
|
||||||
|
Existing BFS instances retain working legacy update URLs; switching their
|
||||||
|
pre-launch manifest to the new store host is optional. Root bfs.band remains
|
||||||
|
the existing Element entry point.
|
||||||
|
|
||||||
|
## RCON and authentication (2026-09-19)
|
||||||
|
|
||||||
|
The WoW server now has `online-mode=false`. Account authentication is disabled;
|
||||||
|
player names can be impersonated, and offline UUIDs differ from online UUIDs.
|
||||||
|
Existing inventory/permissions may require a separate UUID migration.
|
||||||
|
|
||||||
|
RCON listens on TCP 25575 on neuro; its port is not opened in the firewall or
|
||||||
|
forwarded through the public Minecraft relays. The server-specific automatic
|
||||||
|
firewall is disabled and only game port 25567 is explicitly permitted.
|
||||||
|
A random password is stored in SOPS as `minecraft/rcon-password`, injected into
|
||||||
|
server.properties at startup with mode 0600, and is absent from the Nix store.
|
||||||
|
|
||||||
|
Start a local-only SSH tunnel and leave it running:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ssh -NT -L 127.0.0.1:25575:127.0.0.1:25575 -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 neuro
|
||||||
|
```
|
||||||
|
|
||||||
|
Retrieve the password in another terminal (do not paste it into logs):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ssh neuro cat /run/secrets/minecraft/rcon-password
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure the RCON client with host `127.0.0.1`, port `25575`, and that password.
|
||||||
|
There is no RCON username. These changes apply to wowMineMap only.
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# Project Zomboid backups
|
||||||
|
|
||||||
|
`hectic.services."project-zomboid".backup` creates local backups without stopping
|
||||||
|
or pausing the server. The default schedule is every 30 minutes. Each run:
|
||||||
|
|
||||||
|
1. sends the local RCON `save` command and waits for the configured save grace
|
||||||
|
period;
|
||||||
|
2. rsyncs `Zomboid/Saves/Multiplayer/<serverName>` and non-secret server
|
||||||
|
settings (`SandboxVars`, spawn-points, and spawn-regions) from
|
||||||
|
`Zomboid/Server` into a private staging tree;
|
||||||
|
3. waits five seconds and repeats the rsync to narrow the live-write window;
|
||||||
|
4. publishes a timestamped `tar.zst` archive; and
|
||||||
|
5. deletes local archives older than `backup.retentionDays`.
|
||||||
|
|
||||||
|
The service lock prevents overlapping runs. Missing save or server-config paths
|
||||||
|
skip the run through systemd `ConditionPathExists` checks.
|
||||||
|
|
||||||
|
## Consistency and secrets
|
||||||
|
|
||||||
|
This is a best-effort backup. It does not stop Project Zomboid and does not use
|
||||||
|
an atomic filesystem snapshot. The RCON save command flushes the world before
|
||||||
|
copying, and the second rsync narrows the remaining live-write window, but
|
||||||
|
neither makes the filesystem copy an atomic snapshot.
|
||||||
|
|
||||||
|
Archives do not include the generated server INI, `admin-password`,
|
||||||
|
host-generated password files, or the S3 credentials file. The server INI is
|
||||||
|
generated again during service startup; provision secret-backed values separately
|
||||||
|
after a restore.
|
||||||
|
|
||||||
|
## hectic-lab
|
||||||
|
|
||||||
|
hectic-lab runs the timer every 30 minutes and keeps local archives for 14 days:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/var/lib/project-zomboid/backups/archive/
|
||||||
|
```
|
||||||
|
|
||||||
|
Check it with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
systemctl list-timers project-zomboid-backup.timer
|
||||||
|
systemctl status project-zomboid-backup.service
|
||||||
|
journalctl -u project-zomboid-backup.service
|
||||||
|
```
|
||||||
|
|
||||||
|
RCON is enabled on localhost port `27015`; the firewall does not expose this
|
||||||
|
port. The password is generated at
|
||||||
|
`/var/lib/project-zomboid/rcon-password` with mode `0600`. The server also uses
|
||||||
|
`SaveWorldEveryMinutes=15` as a periodic persistence fallback.
|
||||||
|
|
||||||
|
## Optional S3 upload
|
||||||
|
|
||||||
|
S3 upload is disabled by default. Enabling it requires `bucket`, `endpoint`,
|
||||||
|
`region`, and an absolute runtime `credentialsFile` outside `/nix/store`. The
|
||||||
|
endpoint must use HTTPS. systemd reads the environment file without executing
|
||||||
|
it; this host keeps it owned by `project-zomboid` with mode `0400`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
AWS_ACCESS_KEY_ID=...
|
||||||
|
AWS_SECRET_ACCESS_KEY=...
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `backup.s3.prefix` to choose the object-key prefix and
|
||||||
|
`backup.s3.remoteRetentionDays` to prune old archives from that prefix. Remote
|
||||||
|
deletion runs only after a successful upload and only matches this server's
|
||||||
|
archive name prefix. Configure bucket lifecycle expiration/versioning too when
|
||||||
|
available; it remains the stronger recovery and cleanup control.
|
||||||
|
|
||||||
|
## Restore
|
||||||
|
|
||||||
|
Restoring must be done while the server is stopped so it cannot modify files
|
||||||
|
during extraction:
|
||||||
|
|
||||||
|
The versioned helper creates a fresh current-state backup, stops the timer and
|
||||||
|
server, validates archive paths, restores the save, and starts both services:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo ./docs/project-zomboid-restore.sh \
|
||||||
|
/var/lib/project-zomboid/backups/archive/<archive>.tar.zst
|
||||||
|
```
|
||||||
|
|
||||||
|
It writes a rollback archive named
|
||||||
|
`project-zomboid-<serverName>-pre-restore-<timestamp>.tar.zst` before changing
|
||||||
|
the save.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
systemctl stop project-zomboid.service
|
||||||
|
tar --zstd --no-same-owner --no-same-permissions \
|
||||||
|
-xf /var/lib/project-zomboid/backups/archive/<archive>.tar.zst \
|
||||||
|
-C /var/lib/project-zomboid
|
||||||
|
chown -R project-zomboid:project-zomboid /var/lib/project-zomboid/Zomboid
|
||||||
|
systemctl start project-zomboid.service
|
||||||
|
```
|
||||||
|
|
||||||
|
Re-provision password files and secret-backed INI values before starting.
|
||||||
|
Verify the restored save and server name before allowing players to reconnect.
|
||||||
Executable
+143
@@ -0,0 +1,143 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SERVER_NAME=${SERVER_NAME:-servertest}
|
||||||
|
DATA_DIR=${DATA_DIR:-/var/lib/project-zomboid}
|
||||||
|
ARCHIVE=${1:-}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
printf '%s\n' "Usage: $0 /path/to/project-zomboid-${SERVER_NAME}-<timestamp>.tar.zst"
|
||||||
|
printf '%s\n' "Environment: SERVER_NAME, DATA_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
printf '%s\n' 'Run as root.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$ARCHIVE" ]; then
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -r "$ARCHIVE" ]; then
|
||||||
|
printf 'Backup archive is not readable: %s\n' "$ARCHIVE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ARCHIVE_DIR="$DATA_DIR/backups/archive"
|
||||||
|
SAVE_DIR="$DATA_DIR/Zomboid/Saves/Multiplayer/$SERVER_NAME"
|
||||||
|
SERVER_DIR="$DATA_DIR/Zomboid/Server"
|
||||||
|
TMP_LIST=$(mktemp)
|
||||||
|
ROLLBACK_ARCHIVE=''
|
||||||
|
SERVER_STOPPED=0
|
||||||
|
RESTORE_SUCCEEDED=0
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -f "$TMP_LIST"
|
||||||
|
}
|
||||||
|
|
||||||
|
on_exit() {
|
||||||
|
status=$?
|
||||||
|
if [ "$status" -ne 0 ] && [ "$SERVER_STOPPED" -eq 1 ] \
|
||||||
|
&& [ "$RESTORE_SUCCEEDED" -eq 0 ] && [ -n "$ROLLBACK_ARCHIVE" ]; then
|
||||||
|
set +e
|
||||||
|
rm -rf "$SAVE_DIR"
|
||||||
|
rm -f \
|
||||||
|
"$SERVER_DIR/${SERVER_NAME}_SandboxVars.lua" \
|
||||||
|
"$SERVER_DIR/${SERVER_NAME}_spawnpoints.lua" \
|
||||||
|
"$SERVER_DIR/${SERVER_NAME}_spawnregions.lua"
|
||||||
|
tar --zstd --no-same-owner --no-same-permissions \
|
||||||
|
-xpf "$ROLLBACK_ARCHIVE" -C "$DATA_DIR"
|
||||||
|
chown -R project-zomboid:project-zomboid "$SAVE_DIR" "$SERVER_DIR"
|
||||||
|
systemctl start project-zomboid.service
|
||||||
|
systemctl start project-zomboid-backup.timer
|
||||||
|
printf '%s\n' "Restore failed; current state restored from $ROLLBACK_ARCHIVE" >&2
|
||||||
|
fi
|
||||||
|
cleanup
|
||||||
|
exit "$status"
|
||||||
|
}
|
||||||
|
trap on_exit EXIT
|
||||||
|
|
||||||
|
if ! tar --zstd -tf "$ARCHIVE" >"$TMP_LIST"; then
|
||||||
|
printf 'Archive integrity check failed: %s\n' "$ARCHIVE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r member; do
|
||||||
|
case "$member" in
|
||||||
|
Zomboid/*) ;;
|
||||||
|
*)
|
||||||
|
printf 'Unsafe archive member: %s\n' "$member" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case "$member" in
|
||||||
|
/*|*../*)
|
||||||
|
printf 'Path traversal member: %s\n' "$member" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done <"$TMP_LIST"
|
||||||
|
|
||||||
|
if ! systemctl start project-zomboid-backup.service; then
|
||||||
|
printf '%s\n' 'Could not create fresh backup of current state.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CURRENT_ARCHIVE=$(find "$ARCHIVE_DIR" -maxdepth 1 -type f \
|
||||||
|
-name "project-zomboid-$SERVER_NAME-*.tar.zst" \
|
||||||
|
-printf '%T@ %p\n' | sort -nr | awk 'NR == 1 {sub(/^[^ ]* /, ""); print}')
|
||||||
|
|
||||||
|
if [ -z "$CURRENT_ARCHIVE" ]; then
|
||||||
|
printf '%s\n' 'Fresh current-state backup was not found.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
stamp=$(date -u +%Y%m%dT%H%M%SZ)
|
||||||
|
ROLLBACK_ARCHIVE="$ARCHIVE_DIR/project-zomboid-$SERVER_NAME-pre-restore-$stamp.tar.zst"
|
||||||
|
cp --reflink=auto "$CURRENT_ARCHIVE" "$ROLLBACK_ARCHIVE" 2>/dev/null \
|
||||||
|
|| cp "$CURRENT_ARCHIVE" "$ROLLBACK_ARCHIVE"
|
||||||
|
chmod 0600 "$ROLLBACK_ARCHIVE"
|
||||||
|
chown project-zomboid:project-zomboid "$ROLLBACK_ARCHIVE"
|
||||||
|
|
||||||
|
systemctl stop project-zomboid-backup.timer
|
||||||
|
systemctl stop project-zomboid.service
|
||||||
|
SERVER_STOPPED=1
|
||||||
|
|
||||||
|
if [ "$(systemctl show project-zomboid --property=ActiveState --value)" != inactive ]; then
|
||||||
|
printf '%s\n' 'Project Zomboid did not stop; refusing to restore.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$SAVE_DIR"
|
||||||
|
rm -f \
|
||||||
|
"$SERVER_DIR/${SERVER_NAME}_SandboxVars.lua" \
|
||||||
|
"$SERVER_DIR/${SERVER_NAME}_spawnpoints.lua" \
|
||||||
|
"$SERVER_DIR/${SERVER_NAME}_spawnregions.lua"
|
||||||
|
|
||||||
|
tar --zstd --no-same-owner --no-same-permissions \
|
||||||
|
-xpf "$ARCHIVE" -C "$DATA_DIR"
|
||||||
|
chown -R project-zomboid:project-zomboid "$SAVE_DIR" "$SERVER_DIR"
|
||||||
|
|
||||||
|
systemctl start project-zomboid.service
|
||||||
|
started=0
|
||||||
|
for _ in $(seq 1 90); do
|
||||||
|
if [ "$(systemctl show project-zomboid --property=ActiveState --value)" = active ] \
|
||||||
|
&& [ "$(systemctl show project-zomboid --property=SubState --value)" = running ]; then
|
||||||
|
started=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$started" -ne 1 ]; then
|
||||||
|
printf 'Restore completed, but service did not become healthy. Rollback: %s\n' \
|
||||||
|
"$ROLLBACK_ARCHIVE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
systemctl start project-zomboid-backup.timer
|
||||||
|
RESTORE_SUCCEEDED=1
|
||||||
|
printf 'Restore completed.\n'
|
||||||
|
printf 'Rollback archive: %s\n' "$ROLLBACK_ARCHIVE"
|
||||||
@@ -37,6 +37,162 @@
|
|||||||
) cfg.sandboxProperties;
|
) cfg.sandboxProperties;
|
||||||
zomboidDir = "${cfg.dataDir}/Zomboid";
|
zomboidDir = "${cfg.dataDir}/Zomboid";
|
||||||
adminPasswordFile = "${cfg.dataDir}/admin-password";
|
adminPasswordFile = "${cfg.dataDir}/admin-password";
|
||||||
|
rconPasswordFile = cfg.rcon.passwordFile;
|
||||||
|
backupCfg = cfg.backup;
|
||||||
|
s3CredentialsFile = if backupCfg.s3.credentialsFile == null then "" else backupCfg.s3.credentialsFile;
|
||||||
|
s3Bucket = if backupCfg.s3.bucket == null then "" else backupCfg.s3.bucket;
|
||||||
|
s3Endpoint = if backupCfg.s3.endpoint == null then "" else backupCfg.s3.endpoint;
|
||||||
|
s3Region = if backupCfg.s3.region == null then "" else backupCfg.s3.region;
|
||||||
|
saveDir = "${zomboidDir}/Saves/Multiplayer/${cfg.serverName}";
|
||||||
|
serverConfigDir = "${zomboidDir}/Server";
|
||||||
|
backupScript = pkgs.writeShellScript "project-zomboid-backup" ''
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
staging_dir=${lib.escapeShellArg backupCfg.stagingDir}
|
||||||
|
archive_dir=${lib.escapeShellArg backupCfg.archiveDir}
|
||||||
|
lock_file="$archive_dir/.backup.lock"
|
||||||
|
|
||||||
|
${pkgs.coreutils}/bin/install -d -m 0700 \
|
||||||
|
"$staging_dir/Zomboid/Saves/Multiplayer/${cfg.serverName}" \
|
||||||
|
"$staging_dir/Zomboid/Server" \
|
||||||
|
"$archive_dir"
|
||||||
|
|
||||||
|
exec 9>"$lock_file"
|
||||||
|
if ! ${pkgs.util-linux}/bin/flock -n 9; then
|
||||||
|
${pkgs.coreutils}/bin/printf '%s\n' 'Project Zomboid backup already running; skipping.' >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
${lib.optionalString cfg.rcon.enable ''
|
||||||
|
rcon_password="$(${pkgs.coreutils}/bin/cat ${lib.escapeShellArg rconPasswordFile})"
|
||||||
|
if [ -z "$rcon_password" ]; then
|
||||||
|
${pkgs.coreutils}/bin/printf '%s\n' 'Project Zomboid RCON password file is empty.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
${pkgs.rcon}/bin/rcon \
|
||||||
|
--host 127.0.0.1 \
|
||||||
|
--port ${toString cfg.rcon.port} \
|
||||||
|
--password "$rcon_password" \
|
||||||
|
save
|
||||||
|
${pkgs.coreutils}/bin/sleep ${toString backupCfg.saveWaitSeconds}
|
||||||
|
''}
|
||||||
|
|
||||||
|
sync_staging() {
|
||||||
|
${pkgs.rsync}/bin/rsync -a --delete \
|
||||||
|
${lib.escapeShellArg "${saveDir}/"} \
|
||||||
|
"$staging_dir/Zomboid/Saves/Multiplayer/${cfg.serverName}/"
|
||||||
|
${pkgs.rsync}/bin/rsync -a --delete --delete-excluded \
|
||||||
|
--include=${lib.escapeShellArg "/${cfg.serverName}_SandboxVars.lua"} \
|
||||||
|
--include=${lib.escapeShellArg "/${cfg.serverName}_spawnpoints.lua"} \
|
||||||
|
--include=${lib.escapeShellArg "/${cfg.serverName}_spawnregions.lua"} \
|
||||||
|
--exclude='*' \
|
||||||
|
${lib.escapeShellArg "${serverConfigDir}/"} \
|
||||||
|
"$staging_dir/Zomboid/Server/"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Second pass narrows, but cannot eliminate, live-save inconsistency.
|
||||||
|
sync_staging
|
||||||
|
${pkgs.coreutils}/bin/sleep 5
|
||||||
|
sync_staging
|
||||||
|
|
||||||
|
timestamp="$(${pkgs.coreutils}/bin/date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
archive_name="project-zomboid-${cfg.serverName}-$timestamp.tar.zst"
|
||||||
|
archive_tmp="$archive_dir/.$archive_name.tmp"
|
||||||
|
archive="$archive_dir/$archive_name"
|
||||||
|
trap '${pkgs.coreutils}/bin/rm -f "$archive_tmp"' EXIT
|
||||||
|
${pkgs.gnutar}/bin/tar \
|
||||||
|
--use-compress-program=${lib.escapeShellArg "${pkgs.zstd}/bin/zstd -T0"} \
|
||||||
|
-C "$staging_dir" -cf "$archive_tmp" Zomboid
|
||||||
|
${pkgs.coreutils}/bin/chmod 0600 "$archive_tmp"
|
||||||
|
${pkgs.coreutils}/bin/mv "$archive_tmp" "$archive"
|
||||||
|
trap - EXIT
|
||||||
|
|
||||||
|
${pkgs.findutils}/bin/find "$archive_dir" -maxdepth 1 -type f \
|
||||||
|
-name ${lib.escapeShellArg "project-zomboid-${cfg.serverName}-*.tar.zst"} \
|
||||||
|
-mmin +${toString (backupCfg.retentionDays * 1440)} -delete
|
||||||
|
|
||||||
|
${lib.optionalString backupCfg.s3.enable ''
|
||||||
|
if [ -z "''${AWS_ACCESS_KEY_ID:-}" ] || [ -z "''${AWS_SECRET_ACCESS_KEY:-}" ]; then
|
||||||
|
${pkgs.coreutils}/bin/printf '%s\n' \
|
||||||
|
'AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY missing from Project Zomboid S3 credentials file.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
s3_bucket=${lib.escapeShellArg s3Bucket}
|
||||||
|
s3_prefix=${lib.escapeShellArg backupCfg.s3.prefix}
|
||||||
|
s3_key="''${s3_prefix:+$s3_prefix/}$archive_name"
|
||||||
|
${pkgs.awscli2}/bin/aws s3 cp "$archive" \
|
||||||
|
"s3://$s3_bucket/$s3_key" \
|
||||||
|
--endpoint-url ${lib.escapeShellArg s3Endpoint} \
|
||||||
|
--region ${lib.escapeShellArg s3Region} \
|
||||||
|
--cli-connect-timeout 30 \
|
||||||
|
--cli-read-timeout 300 \
|
||||||
|
--only-show-errors
|
||||||
|
|
||||||
|
remote_prefix="$s3_prefix"
|
||||||
|
if [ -n "$remote_prefix" ]; then
|
||||||
|
remote_prefix="$remote_prefix/"
|
||||||
|
fi
|
||||||
|
archive_prefix=${lib.escapeShellArg "project-zomboid-${cfg.serverName}-"}
|
||||||
|
remote_list="$staging_dir/.remote-objects.json"
|
||||||
|
remote_delete_dir="$staging_dir/.remote-delete"
|
||||||
|
${pkgs.awscli2}/bin/aws s3api list-objects-v2 \
|
||||||
|
--bucket "$s3_bucket" \
|
||||||
|
--prefix "$remote_prefix" \
|
||||||
|
--endpoint-url ${lib.escapeShellArg s3Endpoint} \
|
||||||
|
--region ${lib.escapeShellArg s3Region} \
|
||||||
|
--output json > "$remote_list"
|
||||||
|
${pkgs.python3}/bin/python3 - "$remote_list" "$remote_delete_dir" \
|
||||||
|
"$(( $(${pkgs.coreutils}/bin/date +%s) - ${toString (backupCfg.s3.remoteRetentionDays * 86400)} ))" \
|
||||||
|
"$remote_prefix$archive_prefix" <<'PY'
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
objects_path, delete_dir, cutoff, key_prefix = sys.argv[1:]
|
||||||
|
cutoff = int(cutoff)
|
||||||
|
archive_pattern = re.compile(
|
||||||
|
re.escape(key_prefix) + r"\d{8}T\d{6}Z\.tar\.zst\Z"
|
||||||
|
)
|
||||||
|
with open(objects_path, encoding="utf-8") as stream:
|
||||||
|
objects = json.load(stream).get("Contents", [])
|
||||||
|
|
||||||
|
old_keys = []
|
||||||
|
for item in objects:
|
||||||
|
key = item.get("Key", "")
|
||||||
|
if not archive_pattern.fullmatch(key):
|
||||||
|
continue
|
||||||
|
modified = datetime.datetime.fromisoformat(
|
||||||
|
item["LastModified"].replace("Z", "+00:00")
|
||||||
|
)
|
||||||
|
if int(modified.timestamp()) < cutoff:
|
||||||
|
old_keys.append(key)
|
||||||
|
|
||||||
|
os.makedirs(delete_dir, exist_ok=True)
|
||||||
|
for batch_number in range(0, len(old_keys), 1000):
|
||||||
|
batch = old_keys[batch_number:batch_number + 1000]
|
||||||
|
manifest_path = os.path.join(
|
||||||
|
delete_dir, f"batch-{batch_number // 1000:04d}.json"
|
||||||
|
)
|
||||||
|
with open(manifest_path, "w", encoding="utf-8") as stream:
|
||||||
|
json.dump(
|
||||||
|
{"Objects": [{"Key": key} for key in batch], "Quiet": True},
|
||||||
|
stream,
|
||||||
|
)
|
||||||
|
PY
|
||||||
|
for remote_manifest in "$remote_delete_dir"/*.json; do
|
||||||
|
[ -f "$remote_manifest" ] || continue
|
||||||
|
${pkgs.awscli2}/bin/aws s3api delete-objects \
|
||||||
|
--bucket "$s3_bucket" \
|
||||||
|
--delete "file://$remote_manifest" \
|
||||||
|
--endpoint-url ${lib.escapeShellArg s3Endpoint} \
|
||||||
|
--region ${lib.escapeShellArg s3Region} \
|
||||||
|
--only-show-errors
|
||||||
|
done
|
||||||
|
${pkgs.coreutils}/bin/rm -rf "$remote_list" "$remote_delete_dir"
|
||||||
|
''}
|
||||||
|
'';
|
||||||
startScript = pkgs.writeShellScript "project-zomboid-start" ''
|
startScript = pkgs.writeShellScript "project-zomboid-start" ''
|
||||||
admin_password=$(${pkgs.coreutils}/bin/cat ${lib.escapeShellArg adminPasswordFile})
|
admin_password=$(${pkgs.coreutils}/bin/cat ${lib.escapeShellArg adminPasswordFile})
|
||||||
exec ${pkgs.steam-run}/bin/steam-run \
|
exec ${pkgs.steam-run}/bin/steam-run \
|
||||||
@@ -131,9 +287,149 @@ in {
|
|||||||
default = true;
|
default = true;
|
||||||
description = "Open the Project Zomboid UDP ports in the firewall.";
|
description = "Open the Project Zomboid UDP ports in the firewall.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
rcon = {
|
||||||
|
enable = lib.mkEnableOption "local RCON for Project Zomboid automation";
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 27015;
|
||||||
|
description = "RCON TCP port; not opened in the firewall by this module.";
|
||||||
|
};
|
||||||
|
|
||||||
|
passwordFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
default = "${cfg.dataDir}/rcon-password";
|
||||||
|
description = "Runtime file containing the generated RCON password.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
backup = {
|
||||||
|
enable = lib.mkEnableOption "no-stop Project Zomboid backups";
|
||||||
|
|
||||||
|
onCalendar = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "*:0/30";
|
||||||
|
description = "systemd calendar expression controlling backup frequency.";
|
||||||
|
};
|
||||||
|
|
||||||
|
stagingDir = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
default = "${cfg.dataDir}/backups/staging";
|
||||||
|
description = "Local directory containing the two-pass rsync staging tree.";
|
||||||
|
};
|
||||||
|
|
||||||
|
archiveDir = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
default = "${cfg.dataDir}/backups/archive";
|
||||||
|
description = "Local directory containing timestamped tar.zst archives.";
|
||||||
|
};
|
||||||
|
|
||||||
|
retentionDays = lib.mkOption {
|
||||||
|
type = lib.types.ints.positive;
|
||||||
|
default = 14;
|
||||||
|
description = "Delete local archives older than this many days.";
|
||||||
|
};
|
||||||
|
|
||||||
|
saveWaitSeconds = lib.mkOption {
|
||||||
|
type = lib.types.ints.positive;
|
||||||
|
default = 10;
|
||||||
|
description = "Seconds to wait after the RCON save command before rsync.";
|
||||||
|
};
|
||||||
|
|
||||||
|
s3 = {
|
||||||
|
enable = lib.mkEnableOption "uploading Project Zomboid backups to S3-compatible storage";
|
||||||
|
|
||||||
|
credentialsFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Runtime env file containing AWS_ACCESS_KEY_ID and
|
||||||
|
AWS_SECRET_ACCESS_KEY. Required when S3 upload is enabled.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
bucket = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = "S3 bucket receiving backup archives.";
|
||||||
|
};
|
||||||
|
|
||||||
|
endpoint = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = "S3-compatible endpoint URL.";
|
||||||
|
};
|
||||||
|
|
||||||
|
region = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = "S3 region passed to awscli2.";
|
||||||
|
};
|
||||||
|
|
||||||
|
prefix = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "project-zomboid";
|
||||||
|
description = "Optional object key prefix within the S3 bucket.";
|
||||||
|
};
|
||||||
|
|
||||||
|
remoteRetentionDays = lib.mkOption {
|
||||||
|
type = lib.types.ints.positive;
|
||||||
|
default = 14;
|
||||||
|
description = "Delete uploaded archives older than this many days.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = !cfg.rcon.enable || (
|
||||||
|
lib.hasPrefix "/" cfg.rcon.passwordFile
|
||||||
|
&& !lib.hasPrefix "/nix/store/" cfg.rcon.passwordFile
|
||||||
|
);
|
||||||
|
message = "hectic.services.project-zomboid.rcon.passwordFile must be a runtime path outside /nix/store.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = !backupCfg.s3.enable || backupCfg.enable;
|
||||||
|
message = "hectic.services.project-zomboid.backup must be enabled before S3 upload.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = !backupCfg.s3.enable || backupCfg.s3.credentialsFile != null;
|
||||||
|
message = "hectic.services.project-zomboid.backup.s3.credentialsFile is required when S3 upload is enabled.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = !backupCfg.s3.enable || backupCfg.s3.bucket != null;
|
||||||
|
message = "hectic.services.project-zomboid.backup.s3.bucket is required when S3 upload is enabled.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = !backupCfg.s3.enable || backupCfg.s3.endpoint != null;
|
||||||
|
message = "hectic.services.project-zomboid.backup.s3.endpoint is required when S3 upload is enabled.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = !backupCfg.s3.enable || backupCfg.s3.region != null;
|
||||||
|
message = "hectic.services.project-zomboid.backup.s3.region is required when S3 upload is enabled.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion =
|
||||||
|
!backupCfg.s3.enable
|
||||||
|
|| backupCfg.s3.credentialsFile == null
|
||||||
|
|| (
|
||||||
|
lib.hasPrefix "/" backupCfg.s3.credentialsFile
|
||||||
|
&& !lib.hasPrefix "/nix/store/" backupCfg.s3.credentialsFile
|
||||||
|
);
|
||||||
|
message = "hectic.services.project-zomboid.backup.s3.credentialsFile must be a runtime path outside /nix/store.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion =
|
||||||
|
!backupCfg.s3.enable
|
||||||
|
|| backupCfg.s3.endpoint == null
|
||||||
|
|| lib.hasPrefix "https://" backupCfg.s3.endpoint;
|
||||||
|
message = "hectic.services.project-zomboid.backup.s3.endpoint must use HTTPS.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
users.groups.project-zomboid = { };
|
users.groups.project-zomboid = { };
|
||||||
users.users.project-zomboid = {
|
users.users.project-zomboid = {
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
@@ -145,6 +441,11 @@ in {
|
|||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d ${cfg.dataDir} 0750 project-zomboid project-zomboid - -"
|
"d ${cfg.dataDir} 0750 project-zomboid project-zomboid - -"
|
||||||
"d ${cfg.installDir} 0750 project-zomboid project-zomboid - -"
|
"d ${cfg.installDir} 0750 project-zomboid project-zomboid - -"
|
||||||
|
] ++ lib.optionals backupCfg.enable [
|
||||||
|
"d ${cfg.dataDir}/backups 0700 project-zomboid project-zomboid - -"
|
||||||
|
"Z ${cfg.dataDir}/backups 0700 project-zomboid project-zomboid - -"
|
||||||
|
"d ${backupCfg.stagingDir} 0700 project-zomboid project-zomboid - -"
|
||||||
|
"d ${backupCfg.archiveDir} 0700 project-zomboid project-zomboid - -"
|
||||||
];
|
];
|
||||||
|
|
||||||
systemd.services.project-zomboid = {
|
systemd.services.project-zomboid = {
|
||||||
@@ -160,6 +461,22 @@ in {
|
|||||||
umask 077
|
umask 077
|
||||||
${pkgs.openssl}/bin/openssl rand -base64 32 > ${lib.escapeShellArg adminPasswordFile}
|
${pkgs.openssl}/bin/openssl rand -base64 32 > ${lib.escapeShellArg adminPasswordFile}
|
||||||
fi
|
fi
|
||||||
|
${lib.optionalString cfg.rcon.enable ''
|
||||||
|
if [ ! -s ${lib.escapeShellArg rconPasswordFile} ]; then
|
||||||
|
umask 077
|
||||||
|
${pkgs.openssl}/bin/openssl rand -hex 32 > ${lib.escapeShellArg rconPasswordFile}
|
||||||
|
else
|
||||||
|
rcon_password=$(${pkgs.coreutils}/bin/cat ${lib.escapeShellArg rconPasswordFile})
|
||||||
|
case "$rcon_password" in
|
||||||
|
*[!0123456789abcdefABCDEF]*)
|
||||||
|
umask 077
|
||||||
|
${pkgs.openssl}/bin/openssl rand -hex 32 > ${lib.escapeShellArg rconPasswordFile}
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
${pkgs.coreutils}/bin/chown project-zomboid:project-zomboid ${lib.escapeShellArg rconPasswordFile}
|
||||||
|
${pkgs.coreutils}/bin/chmod 0600 ${lib.escapeShellArg rconPasswordFile}
|
||||||
|
''}
|
||||||
${pkgs.steamcmd}/bin/steamcmd \
|
${pkgs.steamcmd}/bin/steamcmd \
|
||||||
+force_install_dir ${lib.escapeShellArg cfg.installDir} \
|
+force_install_dir ${lib.escapeShellArg cfg.installDir} \
|
||||||
+login anonymous \
|
+login anonymous \
|
||||||
@@ -176,6 +493,12 @@ in {
|
|||||||
) configLines}
|
) configLines}
|
||||||
${lib.optionalString (cfg.serverPropertiesFile != null)
|
${lib.optionalString (cfg.serverPropertiesFile != null)
|
||||||
"${pkgs.coreutils}/bin/cat ${lib.escapeShellArg cfg.serverPropertiesFile};"}
|
"${pkgs.coreutils}/bin/cat ${lib.escapeShellArg cfg.serverPropertiesFile};"}
|
||||||
|
${lib.optionalString cfg.rcon.enable ''
|
||||||
|
${pkgs.coreutils}/bin/printf '%s\n' ${lib.escapeShellArg "RCONPort=${toString cfg.rcon.port}"};
|
||||||
|
${pkgs.coreutils}/bin/printf '%s' 'RCONPassword=';
|
||||||
|
${pkgs.coreutils}/bin/cat ${lib.escapeShellArg rconPasswordFile};
|
||||||
|
${pkgs.coreutils}/bin/printf '\n';
|
||||||
|
''}
|
||||||
} > ${lib.escapeShellArg "${zomboidDir}/Server/${cfg.serverName}.ini"}
|
} > ${lib.escapeShellArg "${zomboidDir}/Server/${cfg.serverName}.ini"}
|
||||||
${lib.optionalString (cfg.sandboxProperties != { }) ''
|
${lib.optionalString (cfg.sandboxProperties != { }) ''
|
||||||
{
|
{
|
||||||
@@ -186,6 +509,10 @@ in {
|
|||||||
${pkgs.coreutils}/bin/printf '%s\n' '};';
|
${pkgs.coreutils}/bin/printf '%s\n' '};';
|
||||||
} > ${lib.escapeShellArg "${zomboidDir}/Server/${cfg.serverName}_SandboxVars.lua"}
|
} > ${lib.escapeShellArg "${zomboidDir}/Server/${cfg.serverName}_SandboxVars.lua"}
|
||||||
''}
|
''}
|
||||||
|
${lib.optionalString (cfg.sandboxProperties == { }) ''
|
||||||
|
${pkgs.coreutils}/bin/rm -f \
|
||||||
|
${lib.escapeShellArg "${zomboidDir}/Server/${cfg.serverName}_SandboxVars.lua"}
|
||||||
|
''}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
@@ -205,6 +532,34 @@ in {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
systemd.services.project-zomboid-backup = lib.mkIf backupCfg.enable {
|
||||||
|
description = "No-stop Project Zomboid backup";
|
||||||
|
after = [ "project-zomboid.service" ];
|
||||||
|
unitConfig.ConditionPathExists = [
|
||||||
|
saveDir
|
||||||
|
serverConfigDir
|
||||||
|
];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = "project-zomboid";
|
||||||
|
Group = "project-zomboid";
|
||||||
|
ExecStart = backupScript;
|
||||||
|
TimeoutStartSec = "30min";
|
||||||
|
UMask = "0077";
|
||||||
|
} // lib.optionalAttrs backupCfg.s3.enable {
|
||||||
|
EnvironmentFile = s3CredentialsFile;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.project-zomboid-backup = lib.mkIf backupCfg.enable {
|
||||||
|
description = "Run Project Zomboid backup";
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = backupCfg.onCalendar;
|
||||||
|
Persistent = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall [
|
networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall [
|
||||||
cfg.port
|
cfg.port
|
||||||
cfg.udpPort
|
cfg.udpPort
|
||||||
|
|||||||
@@ -6,6 +6,20 @@
|
|||||||
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKNWWegOVTOF3EOmam32iP7sMybULMTxsXuC+cEGITQ8 minecraft-wow-relay";
|
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKNWWegOVTOF3EOmam32iP7sMybULMTxsXuC+cEGITQ8 minecraft-wow-relay";
|
||||||
};
|
};
|
||||||
systemd.tmpfiles.rules = [ "d /var/www/store/minecraft/world-of-sosal 0755 root root -" ];
|
systemd.tmpfiles.rules = [ "d /var/www/store/minecraft/world-of-sosal 0755 root root -" ];
|
||||||
|
services.nginx.virtualHosts."store.bfs.band" = {
|
||||||
|
enableACME = true;
|
||||||
|
forceSSL = true;
|
||||||
|
root = "/var/www/store";
|
||||||
|
locations."= /".return = "302 /minecraft/world-of-sosal/";
|
||||||
|
locations."= /minecraft".return = "302 /minecraft/world-of-sosal/";
|
||||||
|
locations."= /minecraft/".return = "302 /minecraft/world-of-sosal/";
|
||||||
|
locations."/".extraConfig = ''
|
||||||
|
autoindex off;
|
||||||
|
add_header Cache-Control "no-cache";
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
# Keep old pack URLs working for already imported Prism instances.
|
||||||
services.nginx.virtualHosts."bfs.band".locations = {
|
services.nginx.virtualHosts."bfs.band".locations = {
|
||||||
"= /minecraft".return = "302 /minecraft/world-of-sosal/";
|
"= /minecraft".return = "302 /minecraft/world-of-sosal/";
|
||||||
"= /minecraft/".return = "302 /minecraft/world-of-sosal/";
|
"= /minecraft/".return = "302 /minecraft/world-of-sosal/";
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ let
|
|||||||
UsePAM yes
|
UsePAM yes
|
||||||
AuthenticationMethods publickey
|
AuthenticationMethods publickey
|
||||||
AuthorizedKeysFile %h/.ssh/authorized_keys /etc/ssh/authorized_keys.d/%u
|
AuthorizedKeysFile %h/.ssh/authorized_keys /etc/ssh/authorized_keys.d/%u
|
||||||
LogLevel DEBUG3
|
LogLevel INFO
|
||||||
|
|
||||||
VersionAddendum none
|
VersionAddendum none
|
||||||
HostKeyAlgorithms rsa-sha2-512,rsa-sha2-256,ssh-ed25519
|
HostKeyAlgorithms rsa-sha2-512,rsa-sha2-256,ssh-ed25519
|
||||||
@@ -65,7 +65,8 @@ in
|
|||||||
Type = "simple";
|
Type = "simple";
|
||||||
StateDirectory = "experimental-sshd";
|
StateDirectory = "experimental-sshd";
|
||||||
RuntimeDirectory = "experimental-sshd";
|
RuntimeDirectory = "experimental-sshd";
|
||||||
ExecStart = "${pkgs.openssh}/bin/sshd -D -e -f /etc/ssh/experimental-sshd_config";
|
ExecStart = "${pkgs.openssh}/bin/sshd -D -f /etc/ssh/experimental-sshd_config";
|
||||||
|
StandardError = "journal";
|
||||||
};
|
};
|
||||||
preStart = ''
|
preStart = ''
|
||||||
${pkgs.openssh}/bin/sshd -t -f /etc/ssh/experimental-sshd_config
|
${pkgs.openssh}/bin/sshd -t -f /etc/ssh/experimental-sshd_config
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ with builtins;
|
|||||||
with lib;
|
with lib;
|
||||||
let
|
let
|
||||||
domain = "hectic-lab.com";
|
domain = "hectic-lab.com";
|
||||||
sshPort = 22;
|
giteaSshPort = 22223;
|
||||||
mailUserNames = [
|
mailUserNames = [
|
||||||
"security"
|
"security"
|
||||||
"founders"
|
"founders"
|
||||||
@@ -103,11 +103,25 @@ in {
|
|||||||
};
|
};
|
||||||
services."project-zomboid" = {
|
services."project-zomboid" = {
|
||||||
enable = true;
|
enable = true;
|
||||||
memory = "3g";
|
memory = "4g";
|
||||||
serverName = "servertest";
|
serverName = "servertest";
|
||||||
serverPropertiesFile = /var/lib/project-zomboid/server-password.ini;
|
serverPropertiesFile = /var/lib/project-zomboid/server-password.ini;
|
||||||
|
rcon.enable = true;
|
||||||
|
backup = {
|
||||||
|
enable = true;
|
||||||
|
onCalendar = "*:0/30";
|
||||||
|
retentionDays = 14;
|
||||||
|
s3 = {
|
||||||
|
enable = true;
|
||||||
|
bucket = "backup-hectic-lab";
|
||||||
|
endpoint = "https://hel1.your-objectstorage.com";
|
||||||
|
region = "hel1";
|
||||||
|
credentialsFile = "/var/lib/project-zomboid/s3-credentials";
|
||||||
|
};
|
||||||
|
};
|
||||||
serverProperties = {
|
serverProperties = {
|
||||||
Map = "vehicle_interior_arcadia75;Muldraugh, KY";
|
Map = "Muldraugh, KY";
|
||||||
|
SaveWorldEveryMinutes = 15;
|
||||||
DoLuaChecksum = false;
|
DoLuaChecksum = false;
|
||||||
Public = true;
|
Public = true;
|
||||||
AntiCheatSafety = 4;
|
AntiCheatSafety = 4;
|
||||||
@@ -127,55 +141,43 @@ in {
|
|||||||
AntiCheatNoClip = 4;
|
AntiCheatNoClip = 4;
|
||||||
AntiCheatServerCustomization = 4;
|
AntiCheatServerCustomization = 4;
|
||||||
};
|
};
|
||||||
sandboxProperties = {
|
|
||||||
StartMonth = 12;
|
|
||||||
StartDay = 1;
|
|
||||||
WaterShut = 3;
|
|
||||||
WaterShutModifier = 60;
|
|
||||||
ElecShut = 3;
|
|
||||||
ElecShutModifier = 60;
|
|
||||||
MinutesPerPage = 0.5;
|
|
||||||
ZombieLore = {
|
|
||||||
Transmission = 4;
|
|
||||||
Mortality = 7;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
workshopItems = [
|
workshopItems = [
|
||||||
"3773972040" # Arcadia RV Interiors B42.20
|
|
||||||
"2210760610" # Cryogenic Winter +Easy/Hard Modes
|
|
||||||
"3676456221" # Lua Digital Watch Framework
|
"3676456221" # Lua Digital Watch Framework
|
||||||
"3600401184" # Realistic Temperature Mod
|
"3600401184" # Realistic Temperature Mod
|
||||||
"3387824513" # Material Weight Reducer
|
|
||||||
"3413150945" # More Damaged Objects
|
|
||||||
"3512708849" # Shotgun Trajectory
|
|
||||||
"3401576145" # Firearm Models: Redux
|
|
||||||
"3401134276" # Vanilla Gear Expanded
|
|
||||||
"2956146279" # Rain Cleans Blood
|
|
||||||
"3693258802" # Tactical Hold
|
|
||||||
"3394588830" # Simple Flashlight on Belt
|
|
||||||
"2684285534" # Spongie's Clothing
|
|
||||||
"2812326159" # Spongie's Open Jackets
|
|
||||||
];
|
];
|
||||||
mods = [
|
mods = [
|
||||||
"\\ArcadiaRVInterior_B42_MP"
|
|
||||||
"\\ArcadiaRVInterior_B42_Vanilla"
|
|
||||||
"\\CryogenicWinter2NormalMode"
|
|
||||||
"\\LuaDigitalWatchUI"
|
"\\LuaDigitalWatchUI"
|
||||||
"\\RC_RealisticColdMod"
|
"\\RC_RealisticColdMod"
|
||||||
"\\Material Weight Reducer"
|
|
||||||
"\\Ammunition Weight Reducer"
|
|
||||||
"\\MoreDamagedObjects"
|
|
||||||
"\\ShotgunTrajectory"
|
|
||||||
"\\FMR"
|
|
||||||
"\\VanillaGearExpanded"
|
|
||||||
"\\RainCleansBlood"
|
|
||||||
"\\TacHold Complete"
|
|
||||||
"\\LightOnBelt"
|
|
||||||
"\\SpnCloth"
|
|
||||||
"\\SpnOpenClothBase"
|
|
||||||
"\\SpnOpenCloth"
|
|
||||||
];
|
];
|
||||||
|
sandboxProperties = {
|
||||||
|
StartMonth = 12;
|
||||||
|
StartDay = 1;
|
||||||
|
WaterShut = 3;
|
||||||
|
WaterShutModifier = 150;
|
||||||
|
ElecShut = 3;
|
||||||
|
ElecShutModifier = 150;
|
||||||
|
MinutesPerPage = 0.5;
|
||||||
|
Zombies = 4;
|
||||||
|
ZombieConfig = {
|
||||||
|
PopulationMultiplier = 1.3;
|
||||||
|
PopulationStartMultiplier = 1.0;
|
||||||
|
PopulationPeakMultiplier = 1.0;
|
||||||
|
RespawnHours = 0.0;
|
||||||
|
RespawnUnseenHours = 0.0;
|
||||||
|
RespawnMultiplier = 0.0;
|
||||||
|
RedistributeHours = 0.0;
|
||||||
|
};
|
||||||
|
ZombieLore = {
|
||||||
|
Transmission = 4;
|
||||||
|
Mortality = 7;
|
||||||
|
Speed = 2;
|
||||||
|
SprinterPercentage = 0;
|
||||||
|
Strength = 2;
|
||||||
|
Cognition = 2;
|
||||||
|
DoorOpeningPercentage = 10;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
services.p4d = {
|
services.p4d = {
|
||||||
enable = true;
|
enable = true;
|
||||||
package = pkgs.p4d;
|
package = pkgs.p4d;
|
||||||
@@ -253,8 +255,8 @@ in {
|
|||||||
key = "init-postgresql";
|
key = "init-postgresql";
|
||||||
};
|
};
|
||||||
"atticd/environment" = {};
|
"atticd/environment" = {};
|
||||||
"immich/storage-box" = {};
|
"immich/storage-box" = {};
|
||||||
"wg-bfs/private-key" = {};
|
"wg-bfs/private-key" = {};
|
||||||
"gitea-runner/org-registration-token" = {
|
"gitea-runner/org-registration-token" = {
|
||||||
sopsFile = flake + "/sus/gitea-runners.yaml";
|
sopsFile = flake + "/sus/gitea-runners.yaml";
|
||||||
key = "gitea/hectic-lab/org-runner-registration-token";
|
key = "gitea/hectic-lab/org-runner-registration-token";
|
||||||
@@ -265,12 +267,37 @@ in {
|
|||||||
"jwt-secret"
|
"jwt-secret"
|
||||||
"s3-access-key"
|
"s3-access-key"
|
||||||
"s3-secret-key"
|
"s3-secret-key"
|
||||||
]);
|
]) // {
|
||||||
|
"project-zomboid/s3-access-key" = {
|
||||||
|
key = "ente/s3-access-key";
|
||||||
|
owner = "project-zomboid";
|
||||||
|
group = "project-zomboid";
|
||||||
|
};
|
||||||
|
"project-zomboid/s3-secret-key" = {
|
||||||
|
key = "ente/s3-secret-key";
|
||||||
|
owner = "project-zomboid";
|
||||||
|
group = "project-zomboid";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.project-zomboid.preStart = lib.mkBefore ''
|
systemd.services.project-zomboid.preStart = lib.mkBefore ''
|
||||||
password_file=${lib.escapeShellArg "/var/lib/project-zomboid/server-password"}
|
password_file=${lib.escapeShellArg "/var/lib/project-zomboid/server-password"}
|
||||||
properties_file=${lib.escapeShellArg "/var/lib/project-zomboid/server-password.ini"}
|
properties_file=${lib.escapeShellArg "/var/lib/project-zomboid/server-password.ini"}
|
||||||
|
s3_credentials_file=${lib.escapeShellArg "/var/lib/project-zomboid/s3-credentials"}
|
||||||
|
s3_credentials_tmp="$(${pkgs.coreutils}/bin/mktemp "''${s3_credentials_file}.XXXXXX")"
|
||||||
|
trap '${pkgs.coreutils}/bin/rm -f "$s3_credentials_tmp"' EXIT
|
||||||
|
|
||||||
|
{
|
||||||
|
${pkgs.coreutils}/bin/printf 'AWS_ACCESS_KEY_ID='
|
||||||
|
${pkgs.coreutils}/bin/cat ${lib.escapeShellArg config.sops.secrets."project-zomboid/s3-access-key".path}
|
||||||
|
${pkgs.coreutils}/bin/printf '\n'
|
||||||
|
${pkgs.coreutils}/bin/printf 'AWS_SECRET_ACCESS_KEY='
|
||||||
|
${pkgs.coreutils}/bin/cat ${lib.escapeShellArg config.sops.secrets."project-zomboid/s3-secret-key".path}
|
||||||
|
${pkgs.coreutils}/bin/printf '\n'
|
||||||
|
} > "$s3_credentials_tmp"
|
||||||
|
${pkgs.coreutils}/bin/chmod 0400 "$s3_credentials_tmp"
|
||||||
|
${pkgs.coreutils}/bin/mv -f "$s3_credentials_tmp" "$s3_credentials_file"
|
||||||
|
|
||||||
if [ ! -s "$password_file" ] || ! ${pkgs.gnugrep}/bin/grep -Eq '^[0-9a-f]{48}$' "$password_file"; then
|
if [ ! -s "$password_file" ] || ! ${pkgs.gnugrep}/bin/grep -Eq '^[0-9a-f]{48}$' "$password_file"; then
|
||||||
umask 077
|
umask 077
|
||||||
@@ -315,7 +342,7 @@ in {
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
services.openssh.ports = [ sshPort ];
|
services.openssh.ports = [ giteaSshPort ];
|
||||||
|
|
||||||
services.mailserver = {
|
services.mailserver = {
|
||||||
enable = true;
|
enable = true;
|
||||||
@@ -337,7 +364,7 @@ in {
|
|||||||
|
|
||||||
networking.firewall = {
|
networking.firewall = {
|
||||||
allowedTCPPorts = [
|
allowedTCPPorts = [
|
||||||
sshPort # ssh
|
giteaSshPort # gitea ssh
|
||||||
80
|
80
|
||||||
443
|
443
|
||||||
3306 # mysql
|
3306 # mysql
|
||||||
@@ -507,6 +534,9 @@ in {
|
|||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
proxy_pass http://127.0.0.1:11011/;
|
proxy_pass http://127.0.0.1:11011/;
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -516,15 +546,21 @@ in {
|
|||||||
gitea = {
|
gitea = {
|
||||||
enable = true;
|
enable = true;
|
||||||
package = pkgs.hectic.gitea-heatmap;
|
package = pkgs.hectic.gitea-heatmap;
|
||||||
settings.service.DISABLE_REGISTRATION = false;
|
# Keep LFS storage limited to accounts provisioned by administrators.
|
||||||
|
settings.service.DISABLE_REGISTRATION = true;
|
||||||
|
settings.session.COOKIE_SECURE = true;
|
||||||
settings.actions.ENABLED = true;
|
settings.actions.ENABLED = true;
|
||||||
# Long CUDA builds must not hit Gitea's default three-hour task watchdog.
|
# Long CUDA builds must not hit Gitea's default three-hour task watchdog.
|
||||||
settings.actions.ENDLESS_TASK_TIMEOUT = "8h";
|
settings.actions.ENDLESS_TASK_TIMEOUT = "8h";
|
||||||
settings.server = {
|
settings.server = {
|
||||||
|
HTTP_ADDR = "127.0.0.1";
|
||||||
HTTP_PORT = 11011;
|
HTTP_PORT = 11011;
|
||||||
SSH_PORT = sshPort;
|
ROOT_URL = "https://gitea.${domain}/";
|
||||||
|
SSH_PORT = giteaSshPort;
|
||||||
SSH_DOMAIN = "hectic-lab.com";
|
SSH_DOMAIN = "hectic-lab.com";
|
||||||
};
|
};
|
||||||
|
lfs.enable = true;
|
||||||
|
settings.lfs.LFS_MAX_FILE_SIZE = 536870912;
|
||||||
database = {
|
database = {
|
||||||
createDatabase = true;
|
createDatabase = true;
|
||||||
type = "postgres";
|
type = "postgres";
|
||||||
|
|||||||
@@ -17,10 +17,12 @@ in self.lib.nixpkgs-lib.nixosSystem {
|
|||||||
];
|
];
|
||||||
config.allowUnfreePredicate = pkg:
|
config.allowUnfreePredicate = pkg:
|
||||||
self.lib.cudaUnfreePredicate pkg || builtins.elem (self.lib.nixpkgs-lib.getName pkg) [
|
self.lib.cudaUnfreePredicate pkg || builtins.elem (self.lib.nixpkgs-lib.getName pkg) [
|
||||||
"minecraft-server"
|
"minecraft-server"
|
||||||
"neoforge"
|
"neoforge"
|
||||||
|
"steamcmd"
|
||||||
|
"steam-unwrapped"
|
||||||
|
|
||||||
"nvidia-x11"
|
"nvidia-x11"
|
||||||
];
|
];
|
||||||
# jitsi-meet depends on libolm which is marked insecure (CVE-2024-4519x)
|
# jitsi-meet depends on libolm which is marked insecure (CVE-2024-4519x)
|
||||||
config.permittedInsecurePackages = [
|
config.permittedInsecurePackages = [
|
||||||
|
|||||||
@@ -13,10 +13,10 @@
|
|||||||
serverName = "wowMineMap";
|
serverName = "wowMineMap";
|
||||||
remoteHost = "u664722.your-storagebox.de";
|
remoteHost = "u664722.your-storagebox.de";
|
||||||
remoteUser = "u664722";
|
remoteUser = "u664722";
|
||||||
remotePath = "minecraft/pack/WorldOfSosal.mrpack";
|
remotePath = "minecraft/pack/WorldOfSosal-v3.mrpack";
|
||||||
archiveName = "WorldOfSosal.mrpack";
|
archiveName = "WorldOfSosal.mrpack";
|
||||||
cacheDir = "/var/lib/minecraft-modpacks/worldOfSosal";
|
cacheDir = "/var/lib/minecraft-modpacks/worldOfSosal";
|
||||||
archiveSha256 = "f8c18acb9208e4592725632ae50dab4f9c308483b34fd43a6507c74fdbf8169f";
|
archiveSha256 = "f97cf251b14f40590e97e7b39e8a8ec43dacfce6da1b02357d15e0eee10d3ade";
|
||||||
expectedDependencies = {
|
expectedDependencies = {
|
||||||
minecraft = "1.21.1";
|
minecraft = "1.21.1";
|
||||||
neoforge = "21.1.250";
|
neoforge = "21.1.250";
|
||||||
|
|||||||
@@ -22,9 +22,28 @@
|
|||||||
hostPublicKey = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA5EB5p/5Hp3hGW1oHok+PIOH9Pbn7cnUiGmUEBrCVjnAw+HrKyN8bYVV0dIGllswYXwkG/+bgiBlE6IVIBAq+JwVWu1Sss3KarHY3OvFJUXZoZyRRg/Gc/+LRCE7lyKpwWQ70dbelGRyyJFH36eNv6ySXoUYtGkwlU5IVaHPApOxe4LHPZa/qhSRbPo2hwoh0orCtgejRebNtW5nlx00DNFgsvn8Svz2cIYLxsPVzKgUxs8Zxsxgn+Q/UvR7uq4AbAhyBMLxv7DjJ1pc7PJocuTno2Rw9uMZi1gkjbnmiOh6TTXIEWbnroyIhwc8555uto9melEUmWNQ+C+PwAK+MPw==";
|
hostPublicKey = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA5EB5p/5Hp3hGW1oHok+PIOH9Pbn7cnUiGmUEBrCVjnAw+HrKyN8bYVV0dIGllswYXwkG/+bgiBlE6IVIBAq+JwVWu1Sss3KarHY3OvFJUXZoZyRRg/Gc/+LRCE7lyKpwWQ70dbelGRyyJFH36eNv6ySXoUYtGkwlU5IVaHPApOxe4LHPZa/qhSRbPo2hwoh0orCtgejRebNtW5nlx00DNFgsvn8Svz2cIYLxsPVzKgUxs8Zxsxgn+Q/UvR7uq4AbAhyBMLxv7DjJ1pc7PJocuTno2Rw9uMZi1gkjbnmiOh6TTXIEWbnroyIhwc8555uto9melEUmWNQ+C+PwAK+MPw==";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sops.secrets."minecraft/rcon-password" = {
|
||||||
|
sopsFile = ../../../../sus/neuro-minecraft.yaml;
|
||||||
|
owner = "minecraft";
|
||||||
|
group = "minecraft";
|
||||||
|
mode = "0400";
|
||||||
|
restartUnits = [ "minecraft-server-wowMineMap.service" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# The module's automatic firewall would also expose RCON.
|
||||||
|
networking.firewall.allowedTCPPorts = [ 25567 ];
|
||||||
services.minecraft-servers.servers.wowMineMap = {
|
services.minecraft-servers.servers.wowMineMap = {
|
||||||
|
openFirewall = false;
|
||||||
|
extraStartPre = ''
|
||||||
|
chmod 600 server.properties
|
||||||
|
{
|
||||||
|
printf '\nrcon.password='
|
||||||
|
cat ${config.sops.secrets."minecraft/rcon-password".path}
|
||||||
|
printf '\n'
|
||||||
|
} >> server.properties
|
||||||
|
'';
|
||||||
enable = true;
|
enable = true;
|
||||||
jvmOpts = "-Xmx8G -Xms2G";
|
jvmOpts = "-Xmx24G -Xms2G";
|
||||||
# WorldOfSosal client and server use the same pinned NeoForge.
|
# WorldOfSosal client and server use the same pinned NeoForge.
|
||||||
package = pkgs.minecraftServers.neoforge-1_21_1.override (
|
package = pkgs.minecraftServers.neoforge-1_21_1.override (
|
||||||
builtins.fromJSON (builtins.readFile ./neoforge-21.1.250.json)
|
builtins.fromJSON (builtins.readFile ./neoforge-21.1.250.json)
|
||||||
@@ -33,7 +52,9 @@
|
|||||||
serverProperties = {
|
serverProperties = {
|
||||||
server-port = 25567;
|
server-port = 25567;
|
||||||
difficulty = "hard";
|
difficulty = "hard";
|
||||||
online-mode = true;
|
online-mode = false;
|
||||||
|
enable-rcon = true;
|
||||||
|
"rcon.port" = 25575;
|
||||||
view-distance = 12;
|
view-distance = 12;
|
||||||
simulation-distance = 8;
|
simulation-distance = 8;
|
||||||
motd = "WorldOfSosal — World of Warcraft";
|
motd = "WorldOfSosal — World of Warcraft";
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ in {
|
|||||||
};
|
};
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
enable = true;
|
enable = false;
|
||||||
virtualHosts."bfs.band" = let
|
virtualHosts."bfs.band" = let
|
||||||
site = pkgs.runCommand "bfs-band-site" {} ''
|
site = pkgs.runCommand "bfs-band-site" {} ''
|
||||||
mkdir -p $out
|
mkdir -p $out
|
||||||
@@ -224,6 +224,70 @@ in {
|
|||||||
archetype.dev.enable = true;
|
archetype.dev.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
hectic.services."project-zomboid" = {
|
||||||
|
enable = true;
|
||||||
|
memory = "8g";
|
||||||
|
serverName = "servertest";
|
||||||
|
serverPropertiesFile = /var/lib/project-zomboid/server-password.ini;
|
||||||
|
serverProperties = {
|
||||||
|
Map = "Muldraugh, KY";
|
||||||
|
DoLuaChecksum = false;
|
||||||
|
Public = true;
|
||||||
|
AntiCheatSafety = 4;
|
||||||
|
AntiCheatMovement = 4;
|
||||||
|
AntiCheatSpeed = 4;
|
||||||
|
AntiCheatHit = 4;
|
||||||
|
AntiCheatPacket = 4;
|
||||||
|
AntiCheatPacketException = 4;
|
||||||
|
AntiCheatPermission = 4;
|
||||||
|
AntiCheatXP = 4;
|
||||||
|
AntiCheatFire = 4;
|
||||||
|
AntiCheatSafeHouse = 4;
|
||||||
|
AntiCheatRecipe = 4;
|
||||||
|
AntiCheatPlayer = 4;
|
||||||
|
AntiCheatChecksum = 4;
|
||||||
|
AntiCheatItem = 4;
|
||||||
|
AntiCheatNoClip = 4;
|
||||||
|
AntiCheatServerCustomization = 4;
|
||||||
|
};
|
||||||
|
workshopItems = [
|
||||||
|
"3676456221" # Lua Digital Watch Framework
|
||||||
|
"3600401184" # Realistic Temperature Mod
|
||||||
|
];
|
||||||
|
mods = [
|
||||||
|
"\\LuaDigitalWatchUI"
|
||||||
|
"\\RC_RealisticColdMod"
|
||||||
|
];
|
||||||
|
sandboxProperties = {
|
||||||
|
Zombies = 6;
|
||||||
|
ZombieConfig = {
|
||||||
|
PopulationMultiplier = 0.0;
|
||||||
|
PopulationStartMultiplier = 0.0;
|
||||||
|
PopulationPeakMultiplier = 0.0;
|
||||||
|
RespawnHours = 0.0;
|
||||||
|
RespawnUnseenHours = 0.0;
|
||||||
|
RespawnMultiplier = 0.0;
|
||||||
|
RedistributeHours = 0.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.project-zomboid.preStart = lib.mkBefore ''
|
||||||
|
password_file=${lib.escapeShellArg "/var/lib/project-zomboid/server-password"}
|
||||||
|
properties_file=${lib.escapeShellArg "/var/lib/project-zomboid/server-password.ini"}
|
||||||
|
|
||||||
|
if [ ! -s "$password_file" ] || ! ${pkgs.gnugrep}/bin/grep -Eq '^[0-9a-f]{48}$' "$password_file"; then
|
||||||
|
umask 077
|
||||||
|
${pkgs.openssl}/bin/openssl rand -hex 24 > "$password_file"
|
||||||
|
fi
|
||||||
|
${pkgs.coreutils}/bin/chmod 0600 "$password_file"
|
||||||
|
|
||||||
|
properties_file_tmp="$( ${pkgs.coreutils}/bin/mktemp "$(dirname "$properties_file")/.server-password.ini.XXXXXX")"
|
||||||
|
${pkgs.coreutils}/bin/printf 'Password=%s\n' "$(<"$password_file")" > "$properties_file_tmp"
|
||||||
|
${pkgs.coreutils}/bin/chmod 0600 "$properties_file_tmp"
|
||||||
|
${pkgs.coreutils}/bin/mv "$properties_file_tmp" "$properties_file"
|
||||||
|
'';
|
||||||
|
|
||||||
sops = {
|
sops = {
|
||||||
gnupg.sshKeyPaths = [ ];
|
gnupg.sshKeyPaths = [ ];
|
||||||
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||||
|
|||||||
@@ -6,6 +6,20 @@ in final: prev: (
|
|||||||
legacyPackages = self.legacyPackages.${prev.stdenv.hostPlatform.system};
|
legacyPackages = self.legacyPackages.${prev.stdenv.hostPlatform.system};
|
||||||
in {
|
in {
|
||||||
hectic = packages // legacyPackages;
|
hectic = packages // legacyPackages;
|
||||||
|
p4d = if final.stdenv.hostPlatform.system == "x86_64-linux" then prev.p4d.overrideAttrs (_: {
|
||||||
|
version = "2023.1/2797325";
|
||||||
|
src = final.fetchurl {
|
||||||
|
url = "https://ftp.perforce.com/pub/perforce/r23.1/bin.linux26x86_64/helix-core-server.tgz";
|
||||||
|
hash = "sha256-O8znAlq2XjrixG0FA4cfkgcI9t/w9QMHV0spUjYKl48=";
|
||||||
|
};
|
||||||
|
}) else prev.p4d;
|
||||||
|
p4 = prev.p4.overrideAttrs (_: {
|
||||||
|
version = "2024.1/3006289";
|
||||||
|
src = final.fetchurl {
|
||||||
|
url = "https://ftp.perforce.com/pub/perforce/r24.1/bin.tools/p4source.tgz";
|
||||||
|
hash = "sha256-z3I3cikbbSrmS7dUMMKi6edPnZk2BYAmdO+pfYRJUVQ=";
|
||||||
|
};
|
||||||
|
});
|
||||||
postgresql_17 = prev.postgresql_17 // {pkgs = prev.postgresql_17.pkgs // {
|
postgresql_17 = prev.postgresql_17 // {pkgs = prev.postgresql_17.pkgs // {
|
||||||
http = packages.pg-17-ext-http;
|
http = packages.pg-17-ext-http;
|
||||||
pg_smtp_client = packages.pg-17-ext-smtp-client;
|
pg_smtp_client = packages.pg-17-ext-smtp-client;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ def main():
|
|||||||
scripts = Path(__file__).resolve().parent
|
scripts = Path(__file__).resolve().parent
|
||||||
mirrors = [
|
mirrors = [
|
||||||
('hectic-lab', 'https://store.hectic-lab.com/minecraft/world-of-sosal/', 'store.hectic-lab.com:25568'),
|
('hectic-lab', 'https://store.hectic-lab.com/minecraft/world-of-sosal/', 'store.hectic-lab.com:25568'),
|
||||||
('bfs.poland.xray', 'https://bfs.band/minecraft/world-of-sosal/', 'bfs.band:25568'),
|
('bfs.poland.xray', 'https://store.bfs.band/minecraft/world-of-sosal/', 'wow.bfs.band'),
|
||||||
]
|
]
|
||||||
with tempfile.TemporaryDirectory(prefix='prism-mirrors-') as temporary:
|
with tempfile.TemporaryDirectory(prefix='prism-mirrors-') as temporary:
|
||||||
root = Path(temporary)
|
root = Path(temporary)
|
||||||
|
|||||||
+21
-20
@@ -1,37 +1,38 @@
|
|||||||
minecraft:
|
minecraft:
|
||||||
storage-box-key: ENC[AES256_GCM,data:+TGUsDgspg+zvPRqnooKK+XpSqZQIxqjW1Se0FHbdFrn1bndW1ImiW7YMAZP01/k6hI5vngKxjq/AiGtFqarNFd/wL18w7Rx9MiIspLHIt9IQmeRG9d3i9Rp0tIRQ4dtgMV3VMd/VVdmFm8Vt2E+yw1KFJMY+vhTN4tpQeTLsMVXpo0eySINgEgY9Fri85wglP87YefRN7LEUQIYofjwh/QH2ll2aYVIb1u1Djkxfljf1OBkfiQOM5dgm4NUIEiv5EiVBE+gFZJDo5+VNW1NkNz8xMyQhydJGUCJitdnb3W8nUiOz+TDkxiCg82t11cIXKiQUThc0KOKCv827zkMn4gPieUd2rmr8gao61e8tpJGEJS7Qv7gPl+Bt7vPLRnNKo3sEtRtUHoqkRam0Jr3eOnn2F8Ubnj3sBn0sYbERKkjV4m1qhorDRjDxxR/J22FR8gZvtdYMq2y0n98DHKBTrtKCLddiwMfyXoxCLi8p8PF+IzZTBD2oKa6hQmTUZB134yYJIadGYzpr2+vM8GY,iv:+ljR+UiWoh+fRLTNNSQwiNo6DDxhWeE3UNUvvVjghMI=,tag:n4an1nQXdBm+eCuUJvnQfw==,type:str]
|
storage-box-key: ENC[AES256_GCM,data:Bf4hPWsFuS9u8DXeAYgvjQA7uCi7OHbq/C1yvhZsBmaKHBV7vfNCZoQrF0rE/Fo/ZAwfiirhxmn4FpNzakL8OzD99P0VPdC+BR/Fl2AA67l56UyTtHnIeir+BiWNlZuOOmhGCI1lrR8m0HW/HuY7BAK3wY9qyHDMyjti8fQBAE79axCnmpFR/cL3Fl+NV65IQwKVddhldTSW2kJhPAithjHHugObMJppn5jMPMGqu/MP9EsufnIc68F9X9tzFJEF4Ry7ukgTI3M8dtEX79jcssROXZAKl6oSiYG2CPmr8DGmbGyvkzy5m3MT9q/ZLrIZbNDPnWmDUVg+FFpqGlv4uLsWn/JIpuXev7t573MckjmKrVuuDldpArpVlKd3fgw2faxYD/iltH0cckfYSSqObndRWQcvN4ioJMUCXBIg/QlU111EcrqhwpaTkQIAXSZUxjUhlNEa+mfndxwp8TvV890zISwKRLXjv6AtrWy6m+9W2gm9ESbGRqDka4Ltzet0gcauY4Iwj6uzNukVfiZS,iv:33onrZNu3cMq5ukxuL8qZM2XeG1Qo+iaRFCy8Gy6U6k=,tag:jgKRJtp6mNZem7NrhDf1AA==,type:str]
|
||||||
storage-box-pack-key: ENC[AES256_GCM,data:rzWpejUCTkdkmdwJlveRRAzr1DhDHgBrjMDF0M21fMS9LEL057P/8yCEwjZQNe81tMNZVC+NfgEvAoxYpMkv7jpKk4FH92Sr1VWmvv4UTgusYzrebvSt2evnzNEjkxhhsi81kpC6Rt386/3Z7SdsQ86RUNj+tJs/qxLo4ekk63vWXVw5i1idpTfr2jMiwG4/mjQaw02kkvUtqFJgQdPiMhHjbXR51uoAQQ7AL0vcOvpGtqYJQJqItcJohaJtTBI7bRj3TqEI88SU3NWIQs9vfWm39HDDyAJyyqNnCSDPZLFp3khbgdQYS8PJGmFCXynVQM27UJhASR+OFL5B5Irx4W6ggkYiJWIYFkNs1hk0l7QpE+yNcRVIkbZEf9cSfUVhOBuUQ6lrUI7bpyD7t+6P/sY4Ok58/6wBt5+9EPyRJsBb0rksf1/wVMuj3oOHDU3X6D1XE+yf1xHK6a0r6UdmUfJXBejNOFHXKg/8uW+4g7iIpu0JxQKkvNyh87UAzXU9U7ORQ48PLS93YGXdd5Dq,iv:a5n3kn/dKoJgaqFam0MUYc5bfOiFQMkWZf6IK+OPvqY=,tag:2enSZwMTeg24OF4il0LyhQ==,type:str]
|
storage-box-pack-key: ENC[AES256_GCM,data:srm0+uu1ejWMdlxsIl4X6Fv2c/KBo4pVKcmtun+jqbsa1yifKBDT0hBul9dD/Vs6CojXZcDKTTs21B7zTfJuVmWX/z67qzY7aO0zNGtptgJsEW2OGwQjPWTXq6XhbIHGAGDutzluvdSpRCcKh9vKg2fPKHnhuntWGnDm/ujJKMgd5DqvapRNKLL4SoGo/6pedEVXJvEV9N37FvSoBIE2kj0KXxlX8AigjOtq7y+7Mk0p25FQY7uL1Ix94IK5Zi1VFt6gCbbntbPNeS08k+UUw+kDJ7QFwJrHIa7KzQ+3sL1eyKr5GbiWtDCzFGKSLth8qP8S2fgUQIII0L4Ak6NOSyJA3v1c1A8tl9kWiqZ/j0CQeFr++2PUi+lRI4tSo8stlV7MGA9lIO8Uwl0XTfdCkiEkBdZXj6UKyoDyS+hjTMGgh9evnCiEj0xETupA84CoRQEWXrb7wQmiw4evSJrYohiu+3iilNikgS9EJxCvXtoXemGTcqKCcXYvKRIPHmouLpeHYtLm2fpjKapvCGfV,iv:AkqaETKUQ7k6uH/nWV4KfxdGoBexH9r4ZiKDUZ/s4+0=,tag:3srUBlg2NvvFRq0BCoVvmg==,type:str]
|
||||||
wow-tunnel-key: ENC[AES256_GCM,data:8aQZZd8c3/DAFkNOoxSTBK+sf1dL2ygAokS94NH/5Dh137Zil9S2MDMiUvEXHHUjvxSPN9YqFeYZfewFu0kaoKy6hwQiJPxMCS4hefzBZtO0uiSIJ7WfLwwMUMwICOvc423ynfK4IuZiu4m9NI3crDl6h2Xfz8PmR9potpssnuLVf88hZeXhZznQhI5NjuZjI7a6RoVNKT+ZaRyNk4T56mhP3pRtGkOS8UU9gElWN+gBUaZWm8NqyBsa7vpLLStdwHalzvQVcj5NDjYcaaH8rCeqODda+8NPJC0+w2XiYWRHXU24c4N1vRkVp9Ioo4P0COpNnG4U1aJcbAF9G255Dy7QNx/251GzG2lK8G3n4YC8XlQB1coEO/WnfkUzPXbI+Un89kDaXg3ZopLypVFv/ApOXhOz9i/vcsD95fCmBnWFzZb/mMx07JZ9L0CeDqAYUMzJzIsxE0cIIKlX0hTJIQpsQ1FZsR4TN4X3XtdeYZkZB1c5m4EURwkc2GhstZmusxlUhvBHxq4UNTvWOq4U82pueMfBf92u3Rgl,iv:4RBfsx51NHcRI+szo+JeJgb30ClnuLSO6Wfql4MTlz4=,tag:V+KH9NAooe3BftWrt22YXQ==,type:str]
|
wow-tunnel-key: ENC[AES256_GCM,data:/2QeyAd+2+9VLeSEdZP5vh9sBqrfakxx0ZqNxzTr0QKT7d0s5jQpMMXOC763cjYFeEnvwQDpE4gL1lkoSSQ543vC0A0kA3a3AUUUtCQxTQqtrESnulferbp2jVXBXZrjYuIyH803YZ1iQD9rFaVEVQEtGR2lqkSqccMMvu+oSnJroXCJyuortN+xCri1xdPb+/tpqoffzpGet7TUUnAXAUatn9XaD18tj4q2ID9HfNLQdzwUjrFiEJx3yOFcn5PC1AE3REx6ZLIHF0ZDdVILfP/Q+uNa5l//Zrj9Xm2/+dgx7h7kGCwkS/Da8ow5EGpvcgAEsbX4cRgVLQvmnr2vaBbbArVyZpriMEyzEcwf68kl/KiZKgfEFuxPjXeawF/eLisMpwbqtFa1owljbzkBBTgBrCIvt9m29jiuj1EsNDdxvUsM95ATVr6eqDL04JgzPrt7niwzO5WeqBfugeOvNFlx3NCzKd7L0mG2qDPXTmRuOvRu3/LfctScW+zT9QCmN9IsxNkqFPLzhsF4JFD1HfPr3JLVsKYALJ5c,iv:k0y53lW8JLoQ8+Botf0EFzqSkTrvzaKB6fBbGTcdaLc=,tag:z4unb+jPbvKSlMZbORHYeA==,type:str]
|
||||||
|
rcon-password: ENC[AES256_GCM,data:wzlMscGtdXF/Cssvi2qeNFgVtYVgCtm9nhX1rCVZ5KcnuyUmejjxsR6N/VAf4iTT,iv:BGdWotcNYNVL+Yi3Wd+5vYHw+l31Gt34MbTXsPxJEuM=,tag:VLWkYWLjjef8mvKYA0JgDA==,type:str]
|
||||||
sops:
|
sops:
|
||||||
age:
|
age:
|
||||||
- recipient: age1r25zdeqq8nac6dgca9en28r57ffyz9u9d8z5yc25gc8xqz747vaqmdtk0h
|
- recipient: age1r25zdeqq8nac6dgca9en28r57ffyz9u9d8z5yc25gc8xqz747vaqmdtk0h
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArTUllbGhrL2F4SGVOMDl5
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4NkhmaG5DeUIyZkZabHls
|
||||||
RkJHZFprY1FIQ2hmRE1PTVkxbGhZWitIYW5ZCk1ueXRYY3REVFdpVXBCY1dXcmFD
|
bWM0Y2dmc2p0MkhtQnR4dUJWWjhkRzB6TnhNCnczdHpUenNtVEdVWit6UUFVQ0k5
|
||||||
QlBEZFVLRWc0MmtiNnpyQWZaMDhFaGcKLS0tIHl0T1pTanIwbG14ZDk0UjdrYVVR
|
cTJhWnJ6Yi9mN0M2K1FiUHpqMjhwRm8KLS0tIFZCOTdybFpCY0huNUxtSVc3ZzQ5
|
||||||
MDRoSzNQdXdsVWk5SjZaYlBkZWJpaDQKkVU1p0KTR2aVyeNDBg9jUfeTFCY+BS7U
|
Ump2dXJyRS94elJYcGo0Y2svWCtkSGcKxnsHCGzvUYbgmEro9YkrvrOGJo7NYRRQ
|
||||||
hqIEwIErcWTg35f7kl3I2Mrw69gEU7jXlc0I0/uKGJuZAwZ3hksmmQ==
|
HzvXpMkoO64LHALsamQq0awmdpOPjXAnJ9UzgcXrLzgbyZPDyVSWng==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1ak7heljpr0pjr4m0rcwxgn3sp0jjxw03lxyf33r8lcemqh2u2sgqx0aplq
|
- recipient: age1ak7heljpr0pjr4m0rcwxgn3sp0jjxw03lxyf33r8lcemqh2u2sgqx0aplq
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhN2FOSlh6VWl1NEJ1dnd6
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2ay9QbytUL3o1SjdPdTRV
|
||||||
cVVTOGM5amREUnZWK2ljZHRyV1dyRlVBS2hrCnhPb2liMEpVSDNLdVlrcjBpeG01
|
aVN0RGlTaWFpeU9Obkd2MkJoQWJFUy9rd2xzCkQ2N3NwYXZwSXdkWUNNbldNSElS
|
||||||
RTdCdGxnSHZFKzUweHdMNTdjNlVQZWMKLS0tIFYwUXZoaUNjYWdUaW1nTFk2TEJG
|
MzZGdG83QWpsd2VBV1lLTXVwWk5tSUkKLS0tIFQvM3dzZVI2L050OXIxVEtFdVF4
|
||||||
NGY5U0tlREpiRUR5QU81cVFRL2NXU1UK6KVnn5WZ+vDMwrS1TkuET3TG5AxYbiPd
|
WWZseFVKV09RZmViQ1JEaDNFaUpTMkkKfUeGHjNCOfIeqxAxxNGUsFZ/yBCyzE8r
|
||||||
MigYj9t0tDtytiiQZ6sbJVqXewqbKwZS6pRaGXuNTu4s2cUTSJ4ABg==
|
+XeGgTZLRyfABCHi7uPkNYFMryMc4veGUdavTcaMNYUcNfEI/g3FtA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1ev53mzse6rg4ffwtcwtq4e93c7x7s4d0eyu89jrsahrke8r4yamsseu8h4
|
- recipient: age1ev53mzse6rg4ffwtcwtq4e93c7x7s4d0eyu89jrsahrke8r4yamsseu8h4
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQTmd5bVlleEtocDZPaHZy
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNY2FTRGZwQ2ZRSjVyYlpa
|
||||||
aUY1ZTdBeHhXZmZsOXFCMTRoWlNraC9OTEFnCkNhV3NMS3lVeWVlSkFnOXdDbUFq
|
WldKdmRKVS9JbHNScEJXbmVKbHRDZFU1NFZBClE5MitXSHc0ZHBHSnVPeHg3V0ZZ
|
||||||
QUxxRWloUDNOQk05QUpVUGwycHRMZUkKLS0tIEdyMDJnaTB3M2VJR1hSYzhReUlz
|
SFFXWWlwS2VMb0czSW93UllpZjhMSzgKLS0tIE5CL0FTOEhoNkhlMVdsS3pQbXNQ
|
||||||
WVlKekVEVm5kRGNCdWNqRjhYMkMyQlEKCBkbxQ2LZ56redETBA1NcL6CTAv/FBdP
|
WWVLZDVCODJja2RqdTdZK3BmQzBXeDAK5Eqxf+HgCMhKLHtW0jRe6nDKHaSsGX/V
|
||||||
x2xBwsbHdxO0PlmEvgC4x/1CtFTFj8NDhNyY7sf21ETMEIyOX9k8oA==
|
nu5r7ceh5XHG1Mq3K9eA1oBa+GFP3305ch5ja6iTICj76ZXjmRnffg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
lastmodified: "2026-09-18T20:09:44Z"
|
lastmodified: "2026-09-19T08:48:54Z"
|
||||||
mac: ENC[AES256_GCM,data:9g87oKlLkbvqS8anS4FD/icqoClklhJENoRbKIFr38YNLPwtLLRmTPLai6Q2Z4KLeLgf0KTB+uS+Z4s0/4UuLZPU9cFXDKittkoRGG7amEY2+n9MDfuoWch47mSCCxem3gP5GV5RNKGrEXuyCESzElCO+Q1ZnsF4WSkOsxsK2MU=,iv:n2nYrw0dHmt3NfRL22Ga0eO23iYn9o/HjyCbQhJQCpc=,tag:zTQRKr0rQD5XYl2tTH7WaA==,type:str]
|
mac: ENC[AES256_GCM,data:vGdJ6avRiDu2PU/115cxCQNE5/Oj2K9QRLAxvPRp9UdYPrvx/sh2Q2cy5rGRL9pn09yZPB8LLtxFA9jDsZuSt8WIktIllkFfMb/ng3lEki/KSyd4YSlbA3wBcQ7wsWdfZ510BuRhbwCLnkIZpZYJ91mnaaU1ykmPzM5MHRj3IfQ=,iv:x/5jXo9ySW+3qqciS+bIDAwXo9O9j/I2JqcJp2BOLJQ=,tag:1yH4ZZReaP/dOuPyW0XBSw==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.10.2
|
version: 3.10.2
|
||||||
|
|||||||
Reference in New Issue
Block a user