fix: wow-minecraft: mirror on bfs.band
runner nix smoke / nix label and flake smoke (push) Failing after 22s

This commit is contained in:
2026-09-19 08:27:33 +00:00
parent 3a52023082
commit a09f247290
8 changed files with 211 additions and 66 deletions
+39 -7
View File
@@ -1,7 +1,11 @@
# WorldOfSosal: Prism automatic updates
The published client entry point is:
https://store.hectic-lab.com/minecraft/world-of-sosal/
The published client entry points are:
- https://bfs.band/minecraft/ (BFS / Element host)
- 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
server address. Both installs use the same Minecraft world and modpack release.
Players import `WorldOfSosal-Prism.zip` into Prism once and approve its pre-launch
command. Before each launch, packwiz-installer reconciles the client with the
@@ -23,14 +27,12 @@ client export must consume the same archive; publishing only the client can make
it incompatible with the running server.
```sh
python3 script/build-prism-pack.py WorldOfSosal.mrpack /tmp/world-of-sosal-release \
--server store.hectic-lab.com:25568
# Test the client and deploy the matching server release first.
python3 script/publish-prism-pack.py /tmp/world-of-sosal-release hectic-lab
python3 script/publish-prism-mirrors.py WorldOfSosal.mrpack
```
Use a new output directory for each build. Omit `--server` until the destination
server is chosen. The builder downloads a SHA-256-pinned bootstrap from the
The mirror publisher creates temporary build directories and sets each server
address and update URL automatically. The builder downloads a SHA-256-pinned bootstrap from the
packwiz project's release, or accepts it via `--bootstrap /path/to/file.jar`.
External mods retain their original URLs and SHA-512 checksums. Embedded mods and
configuration are hosted with the release. Both required and optional client mods
@@ -151,3 +153,33 @@ SHA-256 was verified before extraction.
again automatically deleted it. Existing files were reused from cache, and
options.txt retained its checksum. The production pack contents were unchanged.
- Restored the instance's regular current/pack.toml update URL.
## Independent BFS entry point (2026-09-19)
- Server: `bfs.band:25568`; downloads: https://bfs.band/minecraft/.
- 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
not transit hectic-lab; both tunnels have independent reconnecting services.
- Shared proxy implementation: `nixos/module/generic/minecraft-public-relay.nix`.
Host settings remain in `minecraft-wow-proxy.nix` (hectic-lab) and
`minecraft-wow.nix` (BFS). Only `/minecraft/` is added to the existing BFS
nginx virtual host; Element/Matrix routes remain intact.
- Downloaded BFS ZIP seeds `bfs.band:25568` and uses the stable manifest
`https://bfs.band/minecraft/world-of-sosal/current/pack.toml`. It does not
redirect installation metadata to hectic-lab. Upstream mod and Java/loader
downloads still use their original providers (e.g. Modrinth, GitHub, Mojang).
- Existing hectic-lab instances can be migrated without reinstalling mods:
in Edit / Settings / Custom commands, replace only the manifest URL in
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.
- `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
archive has the same SHA-256. Each host's switch is atomic; publication across
two hosts is sequential, so rerun the command if it exits unsuccessfully.
- Both configurations were deployed; public Minecraft status/ping succeeds
on BFS (~125 ms), HTTPS serves the pack, and Element/Matrix HTTP checks pass.
Clean installation through the BFS manifest passed: all 141 client mods and
all overrides match the source archive. A second updater run performed no
downloads and preserved options.txt. The public BFS login protocol reached
online authentication; the earlier full GUI login used hectic-lab.
@@ -0,0 +1,56 @@
{ ... }:
{ config, lib, pkgs, ... }:
let
cfg = config.services.minecraft-public-relay;
in {
options.services.minecraft-public-relay = {
enable = lib.mkEnableOption "restricted SSH relay for Minecraft";
publicPort = lib.mkOption { type = lib.types.port; default = 25568; };
tunnelPort = lib.mkOption { type = lib.types.port; default = 25577; };
publicKey = lib.mkOption {
type = lib.types.str;
description = "Public SSH key of the Minecraft tunnel client";
};
};
config = lib.mkIf cfg.enable {
networking.firewall.allowedTCPPorts = [ cfg.publicPort ];
users.groups.mc-wow-relay = { };
users.users.mc-wow-relay = {
isSystemUser = true;
group = "mc-wow-relay";
openssh.authorizedKeys.keys = [
"restrict,port-forwarding,permitlisten=\"127.0.0.1:${toString cfg.tunnelPort}\" ${cfg.publicKey}"
];
};
services.openssh.extraConfig = ''
Match User mc-wow-relay
ClientAliveInterval 15
ClientAliveCountMax 3
AllowTcpForwarding remote
PermitListen 127.0.0.1:${toString cfg.tunnelPort}
AllowAgentForwarding no
X11Forwarding no
PermitTTY no
ForceCommand ${pkgs.coreutils}/bin/false
Match all
'';
systemd.sockets.minecraft-wow-proxy = {
description = "WorldOfSosal WoW public Minecraft port";
wantedBy = [ "sockets.target" ];
listenStreams = [ "0.0.0.0:${toString cfg.publicPort}" ];
};
systemd.services.minecraft-wow-proxy = {
description = "Forward Minecraft to the neuro reverse tunnel";
requires = [ "minecraft-wow-proxy.socket" ];
after = [ "minecraft-wow-proxy.socket" ];
serviceConfig = {
ExecStart = "${pkgs.systemd}/lib/systemd/systemd-socket-proxyd 127.0.0.1:${toString cfg.tunnelPort}";
DynamicUser = true;
NoNewPrivileges = true;
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
};
};
};
}
@@ -16,6 +16,7 @@
matrixClusterSopsFile = flake + "/sus/matrix-cluster.yaml";
in {
imports = [
./minecraft-wow.nix
self.nixosModules.xray-system
self.nixosModules.matrix-cluster
self.nixosModules.matrix-cluster-users
@@ -0,0 +1,21 @@
{ ... }:
{
imports = [ (import ../../module/generic/minecraft-public-relay.nix { }) ];
services.minecraft-public-relay = {
enable = true;
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKNWWegOVTOF3EOmam32iP7sMybULMTxsXuC+cEGITQ8 minecraft-wow-relay";
};
systemd.tmpfiles.rules = [ "d /var/www/store/minecraft/world-of-sosal 0755 root root -" ];
services.nginx.virtualHosts."bfs.band".locations = {
"= /minecraft".return = "302 /minecraft/world-of-sosal/";
"= /minecraft/".return = "302 /minecraft/world-of-sosal/";
"^~ /minecraft/" = {
root = "/var/www/store";
extraConfig = ''
autoindex off;
add_header Cache-Control "no-cache";
try_files $uri $uri/ =404;
'';
};
};
}
@@ -1,43 +1,8 @@
{ pkgs, ... }:
{ ... }:
{
# Public entry point; the backend arrives through a restricted reverse tunnel.
networking.firewall.allowedTCPPorts = [ 25568 ];
users.groups.mc-wow-relay = { };
users.users.mc-wow-relay = {
isSystemUser = true;
group = "mc-wow-relay";
openssh.authorizedKeys.keys = [
"restrict,port-forwarding,permitlisten=\"127.0.0.1:25577\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKNWWegOVTOF3EOmam32iP7sMybULMTxsXuC+cEGITQ8 minecraft-wow-relay"
];
};
services.openssh.extraConfig = ''
Match User mc-wow-relay
ClientAliveInterval 15
ClientAliveCountMax 3
AllowTcpForwarding remote
PermitListen 127.0.0.1:25577
AllowAgentForwarding no
X11Forwarding no
PermitTTY no
ForceCommand ${pkgs.coreutils}/bin/false
Match all
'';
systemd.sockets.minecraft-wow-proxy = {
description = "WorldOfSosal WoW public Minecraft port";
wantedBy = [ "sockets.target" ];
listenStreams = [ "0.0.0.0:25568" ];
};
systemd.services.minecraft-wow-proxy = {
description = "Forward Minecraft to the neuro reverse tunnel";
requires = [ "minecraft-wow-proxy.socket" ];
after = [ "minecraft-wow-proxy.socket" ];
serviceConfig = {
ExecStart = "${pkgs.systemd}/lib/systemd/systemd-socket-proxyd 127.0.0.1:25577";
DynamicUser = true;
NoNewPrivileges = true;
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
};
imports = [ (import ../../module/generic/minecraft-public-relay.nix { }) ];
services.minecraft-public-relay = {
enable = true;
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKNWWegOVTOF3EOmam32iP7sMybULMTxsXuC+cEGITQ8 minecraft-wow-relay";
};
}
+30 -17
View File
@@ -1,5 +1,24 @@
{ config, pkgs, ... }:
{
let
mkTunnel = relay: {
description = "WorldOfSosal WoW reverse tunnel to ${relay.name}";
startLimitIntervalSec = 0;
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
serviceConfig = {
User = "mc-wow-tunnel";
Group = "mc-wow-tunnel";
ExecStart = "${pkgs.openssh}/bin/ssh -NT -i ${config.sops.secrets."minecraft/wow-tunnel-key".path} -o IPQoS=none -o Ciphers=aes256-ctr -o MACs=hmac-sha2-256-etm@openssh.com -o KexAlgorithms=curve25519-sha256 -o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=/etc/ssh/ssh_known_hosts -o ExitOnForwardFailure=yes -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -R 127.0.0.1:25577:127.0.0.1:25567 mc-wow-relay@${relay.address}";
Restart = "always";
RestartSec = 10;
NoNewPrivileges = true;
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
};
};
in {
users.groups.mc-wow-tunnel = { };
users.users.mc-wow-tunnel = {
isSystemUser = true;
@@ -15,22 +34,16 @@
hostNames = [ "128.140.75.58" ];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAFpr4DPSaJt0xeuGIfcZBJD3LsJHTdIRIs2Tt9HF+CT";
};
systemd.services.minecraft-wow-tunnel = {
description = "WorldOfSosal WoW reverse tunnel to hectic-lab";
startLimitIntervalSec = 0;
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
serviceConfig = {
User = "mc-wow-tunnel";
Group = "mc-wow-tunnel";
ExecStart = "${pkgs.openssh}/bin/ssh -NT -i ${config.sops.secrets."minecraft/wow-tunnel-key".path} -o IPQoS=none -o Ciphers=aes256-ctr -o MACs=hmac-sha2-256-etm@openssh.com -o KexAlgorithms=curve25519-sha256 -o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=/etc/ssh/ssh_known_hosts -o ExitOnForwardFailure=yes -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -R 127.0.0.1:25577:127.0.0.1:25567 mc-wow-relay@128.140.75.58";
Restart = "always";
RestartSec = 10;
NoNewPrivileges = true;
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
programs.ssh.knownHosts.minecraft-wow-relay-bfs = {
hostNames = [ "91.198.166.181" ];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICcCn57nlWY5QyEz17kxuAbIX9PkjPwtlGzdJyhy+SQQ";
};
systemd.services.minecraft-wow-tunnel = mkTunnel {
name = "hectic-lab";
address = "128.140.75.58";
};
systemd.services.minecraft-wow-tunnel-bfs = mkTunnel {
name = "bfs.band";
address = "91.198.166.181";
};
}
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
"""Build and publish the same tested server pack on both independent entry points."""
import argparse
import hashlib
from pathlib import Path
import subprocess
import sys
import tempfile
import zipfile
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument('mrpack', type=Path)
ap.add_argument('--bootstrap', type=Path)
args = ap.parse_args()
archive = args.mrpack.resolve()
expected = hashlib.sha256(archive.read_bytes()).hexdigest()
# Publishing a client before deploying its server can prevent players joining.
deployed = subprocess.run([
'ssh', '-o', 'BatchMode=yes', 'neuro',
'systemctl is-active --quiet minecraft-server-wowMineMap && '
'sha256sum /var/lib/minecraft-modpacks/worldOfSosal/WorldOfSosal.mrpack',
], capture_output=True, text=True, check=True).stdout.split()[0]
if deployed != expected:
ap.error('Deploy this mrpack on neuro first: the server archive hash differs')
scripts = Path(__file__).resolve().parent
mirrors = [
('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'),
]
with tempfile.TemporaryDirectory(prefix='prism-mirrors-') as temporary:
root = Path(temporary)
bootstrap = args.bootstrap.resolve() if args.bootstrap else None
builds = []
for host, url, server in mirrors:
output = root / host
command = [sys.executable, str(scripts / 'build-prism-pack.py'),
str(archive), str(output), '--base-url', url, '--server', server]
if bootstrap:
command += ['--bootstrap', str(bootstrap)]
subprocess.run(command, check=True)
if bootstrap is None:
bootstrap = root / 'packwiz-installer-bootstrap.jar'
with zipfile.ZipFile(output / 'WorldOfSosal-Prism.zip') as z:
bootstrap.write_bytes(z.read('.minecraft/packwiz-installer-bootstrap.jar'))
builds.append((host, url, output))
# Each switch is atomic on its host. A failed publication exits nonzero;
# rerunning safely verifies existing releases and retries both mirrors.
for host, url, output in builds:
subprocess.run([sys.executable, str(scripts / 'publish-prism-pack.py'),
str(output), host], check=True)
print(url, flush=True)
if __name__ == '__main__':
main()
+1 -1
View File
@@ -46,4 +46,4 @@ mv -Tf "$stage/current" "$base/current"
echo "Published $release"
'''.replace('RELEASE', release)
subprocess.run(['ssh', '-o', 'BatchMode=yes', host, 'sh', '-s'], input=script, text=True, check=True)
print('https://store.hectic-lab.com/minecraft/world-of-sosal/')
print(f'Published client files on {host}:/var/www/store/minecraft/world-of-sosal')