diff --git a/.sops.yaml b/.sops.yaml index 58b43ea8..bb513abb 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -12,6 +12,13 @@ keys: - &umbriel-bfs age1jxntjca8q2vxvf2jaal4xyvm2ae6sh62fhv897694kuzawfrk5asj00zdt creation_rules: + - path_regex: sus/neuro-minecraft.yaml$ + key_groups: + - age: + - age1r25zdeqq8nac6dgca9en28r57ffyz9u9d8z5yc25gc8xqz747vaqmdtk0h + - age1ak7heljpr0pjr4m0rcwxgn3sp0jjxw03lxyf33r8lcemqh2u2sgqx0aplq + - age1ev53mzse6rg4ffwtcwtq4e93c7x7s4d0eyu89jrsahrke8r4yamsseu8h4 + - path_regex: sus/home.xray.yaml$ key_groups: - age: diff --git a/docs/minecraft-prism.md b/docs/minecraft-prism.md new file mode 100755 index 00000000..beccc8fa --- /dev/null +++ b/docs/minecraft-prism.md @@ -0,0 +1,133 @@ +# WorldOfSosal: Prism automatic updates + +The published client entry point is: +https://store.hectic-lab.com/minecraft/world-of-sosal/ + +Players import `WorldOfSosal-Prism.zip` into Prism once and approve its pre-launch +command. Before each launch, packwiz-installer reconciles the client with the +published pack: it adds, replaces, and removes managed files, checking hashes. +`options.txt` is seeded once and preserved. Pack configuration files are managed +and can be replaced. Upstream mods do not update independently of your release. +Minecraft 1.21.1, NeoForge 21.1.250, Java 21; the instance reserves up to 8 GiB. + +The original `.mrpack` alone does not provide this automatic update mechanism. +Official workflow: https://packwiz.infra.link/tutorials/installing/packwiz-installer/ + +## Publishing a tested update + +Keep the authoritative `.mrpack` in Storage Box at +`minecraft/pack/WorldOfSosal.mrpack`. For a server update, replace that archive, +set its new SHA-256 in `nixos/system/neuro/minecraft/world-of-sosal.nix`, +and rebuild/switch neuro before publishing the corresponding client export. The server importer and the +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 +``` + +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 +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 +are included, matching the current server importer's optional-mod behavior. + +Publishing uploads an immutable directory, checks it if it already exists, and +atomically switches `current`. Previous directories remain available for rollback. +Do not remove a release while clients may still be reading it. Hash checks cause +an overlapping update to fail safely rather than silently accept mixed contents; +retry the launch if a publication overlapped a download. + +The files live under `/var/www/store/minecraft/world-of-sosal` on `hectic-lab`, +served by the existing `store.hectic-lab.com` nginx virtual host. No nginx reload +is needed for pack updates. Keep `current/pack.toml` as the stable client URL. +The index must be alongside pack.toml: putting a release prefix in `[index].file` +also prefixes client installation paths with that directory in packwiz-installer. + +If Minecraft/NeoForge versions change, update and test both the server pin and +client pack. packwiz-installer 0.5.14 understands NeoForge components in Prism's +`mmc-pack.json`; a launcher restart/relaunch may be necessary after changing them. + +## Verification on 2026-09-18 + +- Source archive SHA-256: + `f8c18acb9208e4592725632ae50dab4f9c308483b34fd43a6507c74fdbf8169f`. +- Public HTTPS installation into a clean Prism-format instance passed: all 141 + client mods and all overrides match the original archive. A second launch + performed no downloads and preserved personal options. +- Direct probes of neuro public ports 25565, 25567, and 25568 timed out; + the configured relay now provides the public entry point. +- Live WoW server reached `Done` with all 135 server mod SHA-512 hashes + matching the same archive used for the Prism client. +- Public `store.hectic-lab.com:25568` status/ping succeeded (about 111 ms); + a login handshake reached the online authentication encryption request. + An authenticated in-game session has not been tested. +- Server and tunnel are enabled at boot; relay and both NixOS configurations + are deployed. No failed systemd units remain on neuro. +- Loader package `neoforge-1.21.1-21.1.250` built successfully in Nix. +- Automatic updater add/remove/config-update and options-preservation behavior + tested with an actual packwiz-installer run against a controlled update fixture. + +## WoW server and public entry point + +The WoW map and WorldOfSosal mods share the `wowMineMap` server on neuro, +listening on 25567. There is no separate WorldOfSosal world/server on 25568. +The client pack and server both pin Minecraft 1.21.1 / NeoForge 21.1.250. +Map import runs before mod import, and both finish before Minecraft starts. + +The public entry point is `store.hectic-lab.com:25568`: + +``` +Prism -> hectic-lab:25568 -> loopback:25577 -> SSH tunnel -> neuro:25567 +``` + +`minecraft-wow-proxy.socket` and its socket-proxyd service run on hectic-lab. +`minecraft-wow-tunnel.service` on neuro establishes a reverse SSH forward and +reconnects after failures. A dedicated SSH identity may listen only on +127.0.0.1:25577 at the relay; it has no interactive shell or other forwarding. +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 +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 +are eventually released. Minecraft retains +`online-mode=true`, requiring normal authenticated Minecraft accounts. + +For a temporary direct local tunnel, use: + +```sh +ssh -NTL 0.0.0.0:25568:127.0.0.1:25567 \ + -o ExitOnForwardFailure=yes \ + -o ServerAliveInterval=30 -o ServerAliveCountMax=3 neuro +``` + +That command exposes the local 25568 listener on all interfaces, as requested. +Use 127.0.0.1 instead of the first 0.0.0.0 if only this computer should use it. + +Credentials are encrypted in `sus/neuro-minecraft.yaml` with the actual neuro +host identity and owner keys. The existing `sus/neuro.yaml` is unchanged. +The source WoW archive remains untouched in Storage Box. Import is idempotent: +an existing world with level.dat is preserved. Never delete the world to update +mods; publish/deploy a matching modpack release instead. + +Useful checks: + +```sh +ssh neuro systemctl status minecraft-world-import-wowMineMap \ + minecraft-modpack-import-worldOfSosal minecraft-server-wowMineMap \ + minecraft-wow-tunnel --no-pager +ssh hectic-lab systemctl status minecraft-wow-proxy.socket --no-pager +ssh neuro journalctl -u minecraft-server-wowMineMap -n 80 --no-pager +``` + +The initial isolated server compatibility test reached `Done` and answered the +Minecraft status/ping protocol. Its logs also contain nonfatal recipe and class +function errors from the supplied modpack; successful startup does not imply that +every recipe or RPG class feature works correctly. + +The imported map metadata is `wow mine`, DataVersion 3953 (Minecraft 1.21), +spawn 0 / 68 / -32; extracted size is approximately 11.7 GiB. The archive +SHA-256 was verified before extraction. diff --git a/nixos/system/hectic-lab/hectic-lab.nix b/nixos/system/hectic-lab/hectic-lab.nix index 975fb258..f68728e9 100644 --- a/nixos/system/hectic-lab/hectic-lab.nix +++ b/nixos/system/hectic-lab/hectic-lab.nix @@ -64,6 +64,7 @@ in { (import ./attic.nix { inherit flake self inputs domain; }) (import ./containers.nix { inherit flake self inputs; }) ./experimental-sshd.nix + ./minecraft-wow-proxy.nix (import ./ente.nix { inherit domain; }) (import ./immich.nix { inherit domain; }) (import ./mechabellum.nix { inherit flake self inputs domain; }) @@ -106,7 +107,7 @@ in { serverName = "servertest"; serverPropertiesFile = /var/lib/project-zomboid/server-password.ini; serverProperties = { - Map = "map_distanciado;Muldraugh, KY"; + Map = "vehicle_interior_arcadia75;Muldraugh, KY"; DoLuaChecksum = false; Public = true; AntiCheatSafety = 4; @@ -140,18 +141,12 @@ in { }; }; workshopItems = [ + "3773972040" # Arcadia RV Interiors B42.20 "2210760610" # Cryogenic Winter +Easy/Hard Modes "3676456221" # Lua Digital Watch Framework "3600401184" # Realistic Temperature Mod "3387824513" # Material Weight Reducer - "3387539308" # Auto Mechanics - "3402491515" # Tsar's Common Library B42 - "3403490889" # Standardized Vehicle Upgrades 3 - Core - "3520758551" # More Car Features + Spawn Zones Expansion - "3110911330" # '87 Ford B700/F700 Trucks "3413150945" # More Damaged Objects - "3554424111" # U.S. M998 Humvee - "2705406713" # Military Tool Kit "3512708849" # Shotgun Trajectory "3401576145" # Firearm Models: Redux "3401134276" # Vanilla Gear Expanded @@ -162,19 +157,14 @@ in { "2812326159" # Spongie's Open Jackets ]; mods = [ - "\\Military_Tool_Kit" + "\\ArcadiaRVInterior_B42_MP" + "\\ArcadiaRVInterior_B42_Vanilla" "\\CryogenicWinter2NormalMode" "\\LuaDigitalWatchUI" "\\RC_RealisticColdMod" "\\Material Weight Reducer" "\\Ammunition Weight Reducer" - "\\AutoMechanics" - "\\tsarslib" - "\\StandardizedVehicleUpgrades3Core" - "\\WayMoreCars" - "\\87fordB700" "\\MoreDamagedObjects" - "\\U.S. M998 Humvee by Papa_Chad" "\\ShotgunTrajectory" "\\FMR" "\\VanillaGearExpanded" diff --git a/nixos/system/hectic-lab/minecraft-wow-proxy.nix b/nixos/system/hectic-lab/minecraft-wow-proxy.nix new file mode 100644 index 00000000..d40198b4 --- /dev/null +++ b/nixos/system/hectic-lab/minecraft-wow-proxy.nix @@ -0,0 +1,43 @@ +{ 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; + }; + }; +} diff --git a/nixos/system/hectic-lab/static/world-of-sosal/index.html b/nixos/system/hectic-lab/static/world-of-sosal/index.html index ed8173f8..13a1e95e 100644 --- a/nixos/system/hectic-lab/static/world-of-sosal/index.html +++ b/nixos/system/hectic-lab/static/world-of-sosal/index.html @@ -8,6 +8,7 @@

WorldOfSosal

+

Install in Prism with automatic mod updates

Download latest pack

Import latest pack in Prism Launcher

SHA-256 checksums

diff --git a/nixos/system/neuro/ROUTER.md b/nixos/system/neuro/ROUTER.md index 2476ebf0..ee44f0af 100644 --- a/nixos/system/neuro/ROUTER.md +++ b/nixos/system/neuro/ROUTER.md @@ -1,3 +1,17 @@ +# Current Minecraft access (2026-09-18) + +WorldOfSosal on the WoW map uses `store.hectic-lab.com:25568` publicly. +The game server is `neuro:25567`; a restricted persistent reverse SSH tunnel +connects it to the public relay. See `docs/minecraft-prism.md` and the Nix modules +`minecraft/public-tunnel.nix` / `hectic-lab/minecraft-wow-proxy.nix`. + +Verified LAN: neuro is `192.168.88.10`, gateway `192.168.88.1`. +SSH access is `95.31.254.84:34457`. Direct external Minecraft TCP probes timed out, +and no UPnP IGD was discovered. The old TP-Link network and manual port-forward +instructions below describe the previous network, not the active configuration. + +--- + # Router Access (TP-Link) The server `neuro` is behind a NAT router at `192.168.0.1`. diff --git a/nixos/system/neuro/minecraft/default.nix b/nixos/system/neuro/minecraft/default.nix index 35d4200b..8bd35b29 100644 --- a/nixos/system/neuro/minecraft/default.nix +++ b/nixos/system/neuro/minecraft/default.nix @@ -4,6 +4,7 @@ ./create-aeronautics.nix ./wow-mine-map.nix ./world-of-sosal.nix + ./public-tunnel.nix ./world-import.nix ./modpack-import.nix ]; diff --git a/nixos/system/neuro/minecraft/modpack-import.nix b/nixos/system/neuro/minecraft/modpack-import.nix index 52162d27..446de8fc 100644 --- a/nixos/system/neuro/minecraft/modpack-import.nix +++ b/nixos/system/neuro/minecraft/modpack-import.nix @@ -76,7 +76,7 @@ RestrictSUIDSGID = true; LockPersonality = true; CapabilityBoundingSet = [ "" ]; - ReadWritePaths = [ import.cacheDir serverDir ]; + ReadWritePaths = [ import.cacheDir serverDir ]; UMask = "0007"; } // lib.optionalAttrs (stateDirectoryCompatible import.cacheDir) { StateDirectory = cacheStateDirectory; @@ -119,13 +119,15 @@ archive_entries_valid() { lsar -json "$archive" | jq -e ' - (.entries | type == "array") - and (.entries | all(.[]; - (.XADPath | type == "string") - and (.XADPath | startswith("/") | not) - and (.XADPath | contains("\\") | not) - and (.XADPath | test("[[:cntrl:]]") | not) - and ([.XADPath | split("/")[] | select(. == "" or . == "." or . == "..")] | length == 0) + (.lsarContents // .entries) as $entries + | ($entries | type == "array") + and ($entries | all(.[]; + (.XADFileName // .XADPath) as $path + | ($path | type == "string") + and ($path | startswith("/") | not) + and ($path | contains("\\") | not) + and ($path | test("[[:cntrl:]]") | not) + and ([$path | split("/")[] | select(. == "" or . == "." or . == "..")] | length == 0) and ((.XADIsSymbolicLink // false) | not) and ((.XADIsHardLink // false) | not) and ((.XADIsDevice // false) | not) @@ -260,10 +262,26 @@ done >> "$new_managed_paths" fi - existing_symlink=$(find "$server_dir" -type l -print -quit) - if [ -n "$existing_symlink" ]; then - echo "Minecraft server directory contains symlink: $existing_symlink" >&2 - exit 1 + # Nix Minecraft manages eula.txt via a symlink. Only reject symlinks + # in destinations we actually touch, including their parent directories. + safe_target_path() { + safe_relative_path "$1" || return 1 + target="$server_dir/$1" + while [ "$target" != "$server_dir" ]; do + if [ -L "$target" ]; then + echo "Modpack destination contains symlink: $target" >&2 + return 1 + fi + target=$(dirname "$target") + done + } + while IFS= read -r relative; do + safe_target_path "$relative" || exit 1 + done < "$new_managed_paths" + if [ -f "$managed_paths" ]; then + while IFS= read -r relative; do + safe_target_path "$relative" || exit 1 + done < "$managed_paths" fi while IFS= read -r relative; do diff --git a/nixos/system/neuro/minecraft/neoforge-21.1.250.json b/nixos/system/neuro/minecraft/neoforge-21.1.250.json new file mode 100644 index 00000000..98688e67 --- /dev/null +++ b/nixos/system/neuro/minecraft/neoforge-21.1.250.json @@ -0,0 +1,363 @@ +{ + "build": { + "version": "21.1.250", + "src": { + "url": "https://maven.neoforged.net/releases/net/neoforged/neoforge/21.1.250/neoforge-21.1.250-installer.jar", + "hash": "sha256-DkepG6ITmo20v3Ynrwgfe1eJtQi7A57o3qEnK3lpPWA=" + }, + "libraries": [ + "net.neoforged.fancymodloader:earlydisplay:4.0.44", + "net.neoforged.fancymodloader:loader:4.0.44", + "net.neoforged.accesstransformers:at-modlauncher:10.0.1", + "net.neoforged:accesstransformers:10.0.1", + "net.neoforged:bus:8.0.5", + "net.neoforged:coremods:7.0.3", + "cpw.mods:modlauncher:11.0.5", + "net.neoforged:mergetool:2.0.0:api", + "com.electronwill.night-config:toml:3.8.3", + "com.electronwill.night-config:core:3.8.3", + "net.neoforged:JarJarSelector:0.4.1", + "net.neoforged:JarJarMetadata:0.4.1", + "org.apache.maven:maven-artifact:3.8.5", + "net.jodah:typetools:0.6.3", + "net.minecrell:terminalconsoleappender:1.3.0", + "net.fabricmc:sponge-mixin:0.15.2+mixin.0.8.7", + "org.openjdk.nashorn:nashorn-core:15.4", + "org.apache.commons:commons-lang3:3.14.0", + "cpw.mods:bootstraplauncher:2.0.2", + "cpw.mods:securejarhandler:3.0.8", + "org.ow2.asm:asm-commons:9.10.1", + "org.ow2.asm:asm-util:9.10.1", + "org.ow2.asm:asm-analysis:9.10.1", + "org.ow2.asm:asm-tree:9.10.1", + "org.ow2.asm:asm:9.10.1", + "net.neoforged:JarJarFileSystems:0.4.1", + "net.sf.jopt-simple:jopt-simple:5.0.4", + "org.slf4j:slf4j-api:2.0.9", + "org.antlr:antlr4-runtime:4.13.1", + "com.mojang:logging:1.2.7", + "org.apache.logging.log4j:log4j-slf4j2-impl:2.22.1", + "org.apache.logging.log4j:log4j-core:2.22.1", + "org.apache.logging.log4j:log4j-api:2.22.1", + "org.jline:jline-reader:3.20.0", + "org.jline:jline-terminal:3.20.0", + "commons-io:commons-io:2.15.1", + "net.minecraftforge:srgutils:0.4.15", + "com.google.guava:guava:32.1.2-jre", + "com.google.guava:failureaccess:1.0.1", + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.code.findbugs:jsr305:3.0.2", + "org.checkerframework:checker-qual:3.33.0", + "com.google.errorprone:error_prone_annotations:2.18.0", + "com.google.j2objc:j2objc-annotations:2.8", + "com.google.code.gson:gson:2.10.1", + "org.codehaus.plexus:plexus-utils:3.3.0", + "com.machinezoo.noexception:noexception:1.7.1", + "net.neoforged:neoform:1.21.1-20240808.144430@zip", + "net.neoforged.installertools:binarypatcher:2.1.2:fatjar", + "net.neoforged:AutoRenamingTool:2.0.3:all", + "net.neoforged.installertools:installertools:2.1.2", + "net.neoforged:srgutils:1.0.0", + "net.md-5:SpecialSource:1.11.0", + "com.google.code.gson:gson:2.8.9", + "de.siegmar:fastcsv:2.0.0", + "org.ow2.asm:asm-commons:9.3", + "net.neoforged.installertools:cli-utils:2.1.2", + "com.google.guava:guava:20.0", + "com.opencsv:opencsv:4.4", + "org.ow2.asm:asm-analysis:9.3", + "org.ow2.asm:asm-tree:9.3", + "org.ow2.asm:asm:9.3", + "org.apache.commons:commons-text:1.3", + "org.apache.commons:commons-lang3:3.8.1", + "commons-beanutils:commons-beanutils:1.9.3", + "org.apache.commons:commons-collections4:4.2", + "commons-logging:commons-logging:1.2", + "commons-collections:commons-collections:3.2.2", + "net.neoforged.installertools:jarsplitter:2.1.2", + "net.neoforged:neoforge:21.1.250:universal" + ] + }, + "libraryLocks": { + "net.neoforged.fancymodloader:earlydisplay:4.0.44": { + "url": "https://maven.neoforged.net/releases/net/neoforged/fancymodloader/earlydisplay/4.0.44/earlydisplay-4.0.44.jar", + "hash": "sha1-yyr88VZyGA7KHEmxybEgiVHvMi4=" + }, + "net.neoforged.fancymodloader:loader:4.0.44": { + "url": "https://maven.neoforged.net/releases/net/neoforged/fancymodloader/loader/4.0.44/loader-4.0.44.jar", + "hash": "sha1-Y0M2CLaDAqRC49uoeIDlT0LqBYM=" + }, + "net.neoforged.accesstransformers:at-modlauncher:10.0.1": { + "url": "https://maven.neoforged.net/releases/net/neoforged/accesstransformers/at-modlauncher/10.0.1/at-modlauncher-10.0.1.jar", + "hash": "sha1-WrpQICrO6tCGvAn7wnUcnwXLSJA=" + }, + "net.neoforged:accesstransformers:10.0.1": { + "url": "https://maven.neoforged.net/releases/net/neoforged/accesstransformers/10.0.1/accesstransformers-10.0.1.jar", + "hash": "sha1-/YO1cl926ukRXpNV+hxFampEFAA=" + }, + "net.neoforged:bus:8.0.5": { + "url": "https://maven.neoforged.net/releases/net/neoforged/bus/8.0.5/bus-8.0.5.jar", + "hash": "sha1-Wy0zKFq10VVOl5itmMQNbqOGi9U=" + }, + "net.neoforged:coremods:7.0.3": { + "url": "https://maven.neoforged.net/releases/net/neoforged/coremods/7.0.3/coremods-7.0.3.jar", + "hash": "sha1-CRR+b2OLQnKzvV/I+SrTeAJRLGw=" + }, + "cpw.mods:modlauncher:11.0.5": { + "url": "https://maven.neoforged.net/releases/cpw/mods/modlauncher/11.0.5/modlauncher-11.0.5.jar", + "hash": "sha1-uPDUkpT3M/22FzkxsmNVPpQ9yVA=" + }, + "net.neoforged:mergetool:2.0.0:api": { + "url": "https://maven.neoforged.net/releases/net/neoforged/mergetool/2.0.0/mergetool-2.0.0-api.jar", + "hash": "sha1-Uv4ZSb5k4zA6q6qiHjFfVR25yfQ=" + }, + "com.electronwill.night-config:toml:3.8.3": { + "url": "https://maven.neoforged.net/releases/com/electronwill/night-config/toml/3.8.3/toml-3.8.3.jar", + "hash": "sha1-kLL9bvy0p9W5gQz3j52CSZTXF/I=" + }, + "com.electronwill.night-config:core:3.8.3": { + "url": "https://maven.neoforged.net/releases/com/electronwill/night-config/core/3.8.3/core-3.8.3.jar", + "hash": "sha1-tEKpXwnjSZJ/WpRey1lEVYcPz08=" + }, + "net.neoforged:JarJarSelector:0.4.1": { + "url": "https://maven.neoforged.net/releases/net/neoforged/JarJarSelector/0.4.1/JarJarSelector-0.4.1.jar", + "hash": "sha1-+zzHpYryKtKICtuYr21RgSjEfa4=" + }, + "net.neoforged:JarJarMetadata:0.4.1": { + "url": "https://maven.neoforged.net/releases/net/neoforged/JarJarMetadata/0.4.1/JarJarMetadata-0.4.1.jar", + "hash": "sha1-+NoDaD3IFpRVbcPhd8Xju3eub8s=" + }, + "org.apache.maven:maven-artifact:3.8.5": { + "url": "https://maven.neoforged.net/releases/org/apache/maven/maven-artifact/3.8.5/maven-artifact-3.8.5.jar", + "hash": "sha1-RDP1DAfevvrtBVO9AGj09I1EkxM=" + }, + "net.jodah:typetools:0.6.3": { + "url": "https://maven.neoforged.net/releases/net/jodah/typetools/0.6.3/typetools-0.6.3.jar", + "hash": "sha1-oBqqbdrqnsB+xPIJSHt6RqUmKDo=" + }, + "net.minecrell:terminalconsoleappender:1.3.0": { + "url": "https://maven.neoforged.net/releases/net/minecrell/terminalconsoleappender/1.3.0/terminalconsoleappender-1.3.0.jar", + "hash": "sha1-tWLpu2EjXJUg4mKCze5x+PgC0fw=" + }, + "net.fabricmc:sponge-mixin:0.15.2+mixin.0.8.7": { + "url": "https://maven.neoforged.net/releases/net/fabricmc/sponge-mixin/0.15.2+mixin.0.8.7/sponge-mixin-0.15.2+mixin.0.8.7.jar", + "hash": "sha1-KvLwIdjgKgIg3CenpytGZtZtRMo=" + }, + "org.openjdk.nashorn:nashorn-core:15.4": { + "url": "https://maven.neoforged.net/releases/org/openjdk/nashorn/nashorn-core/15.4/nashorn-core-15.4.jar", + "hash": "sha1-9n9f+qX1Ewz2+5sTPaAMffO1MqU=" + }, + "org.apache.commons:commons-lang3:3.14.0": { + "url": "https://libraries.minecraft.net/org/apache/commons/commons-lang3/3.14.0/commons-lang3-3.14.0.jar", + "hash": "sha1-HtRxGUsC8sbLc0oM1vbxB8Zzr64=" + }, + "cpw.mods:bootstraplauncher:2.0.2": { + "url": "https://maven.neoforged.net/releases/cpw/mods/bootstraplauncher/2.0.2/bootstraplauncher-2.0.2.jar", + "hash": "sha1-Gi0HbLwzsFIMus1ZEiRCeyogBH0=" + }, + "cpw.mods:securejarhandler:3.0.8": { + "url": "https://maven.neoforged.net/releases/cpw/mods/securejarhandler/3.0.8/securejarhandler-3.0.8.jar", + "hash": "sha1-wO+Vzs2GmaBEkFOsfZwWB0jZAs0=" + }, + "org.ow2.asm:asm-commons:9.10.1": { + "url": "https://maven.neoforged.net/releases/org/ow2/asm/asm-commons/9.10.1/asm-commons-9.10.1.jar", + "hash": "sha1-QinkxV/Y4Bwj+f6YhAdcxiiqzFA=" + }, + "org.ow2.asm:asm-util:9.10.1": { + "url": "https://maven.neoforged.net/releases/org/ow2/asm/asm-util/9.10.1/asm-util-9.10.1.jar", + "hash": "sha1-e7nUUOjUy/n54ECWxEu/5/uoCxU=" + }, + "org.ow2.asm:asm-analysis:9.10.1": { + "url": "https://maven.neoforged.net/releases/org/ow2/asm/asm-analysis/9.10.1/asm-analysis-9.10.1.jar", + "hash": "sha1-jUnxTVH2Mssdh8iNHOr1DbDYrxs=" + }, + "org.ow2.asm:asm-tree:9.10.1": { + "url": "https://maven.neoforged.net/releases/org/ow2/asm/asm-tree/9.10.1/asm-tree-9.10.1.jar", + "hash": "sha1-4kQzKhdWTB0VckSTmahC3jWIG+I=" + }, + "org.ow2.asm:asm:9.10.1": { + "url": "https://maven.neoforged.net/releases/org/ow2/asm/asm/9.10.1/asm-9.10.1.jar", + "hash": "sha1-raIUHAzFLuj1xIzV+kzg55TyIjY=" + }, + "net.neoforged:JarJarFileSystems:0.4.1": { + "url": "https://maven.neoforged.net/releases/net/neoforged/JarJarFileSystems/0.4.1/JarJarFileSystems-0.4.1.jar", + "hash": "sha1-ePWfid780DLteIsVHKag1ArOeWo=" + }, + "net.sf.jopt-simple:jopt-simple:5.0.4": { + "url": "https://libraries.minecraft.net/net/sf/jopt-simple/jopt-simple/5.0.4/jopt-simple-5.0.4.jar", + "hash": "sha1-T9rC++kt+thqpukwFzb2tDQqP1w=" + }, + "org.slf4j:slf4j-api:2.0.9": { + "url": "https://libraries.minecraft.net/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.jar", + "hash": "sha1-fPJyb9z7yGEPmnH7PtY5hx8xU0A=" + }, + "org.antlr:antlr4-runtime:4.13.1": { + "url": "https://maven.neoforged.net/releases/org/antlr/antlr4-runtime/4.13.1/antlr4-runtime-4.13.1.jar", + "hash": "sha1-FxJbrh2WViTiZe9JVS9kZaK/owc=" + }, + "com.mojang:logging:1.2.7": { + "url": "https://libraries.minecraft.net/com/mojang/logging/1.2.7/logging-1.2.7.jar", + "hash": "sha1-JMuV/7DjQz/W6ETATmgAnlBMocA=" + }, + "org.apache.logging.log4j:log4j-slf4j2-impl:2.22.1": { + "url": "https://libraries.minecraft.net/org/apache/logging/log4j/log4j-slf4j2-impl/2.22.1/log4j-slf4j2-impl-2.22.1.jar", + "hash": "sha1-1+ZpPCYGy35zNQR9e7lt7FLbVmU=" + }, + "org.apache.logging.log4j:log4j-core:2.22.1": { + "url": "https://libraries.minecraft.net/org/apache/logging/log4j/log4j-core/2.22.1/log4j-core-2.22.1.jar", + "hash": "sha1-cYOiVRCgKtAMxqldOz0qfTxajcQ=" + }, + "org.apache.logging.log4j:log4j-api:2.22.1": { + "url": "https://libraries.minecraft.net/org/apache/logging/log4j/log4j-api/2.22.1/log4j-api-2.22.1.jar", + "hash": "sha1-vqb+3mMo+rr9fmg2MWGn6mYFq9E=" + }, + "org.jline:jline-reader:3.20.0": { + "url": "https://maven.neoforged.net/releases/org/jline/jline-reader/3.20.0/jline-reader-3.20.0.jar", + "hash": "sha1-jxVBWwIqJbRz6OFsKK6RMYb/ucQ=" + }, + "org.jline:jline-terminal:3.20.0": { + "url": "https://maven.neoforged.net/releases/org/jline/jline-terminal/3.20.0/jline-terminal-3.20.0.jar", + "hash": "sha1-0N3McI3fUno0VMlBt7kiXMg6Ff8=" + }, + "commons-io:commons-io:2.15.1": { + "url": "https://libraries.minecraft.net/commons-io/commons-io/2.15.1/commons-io-2.15.1.jar", + "hash": "sha1-8RVg2hiatWOlyONRlBQVQw6TBOo=" + }, + "net.minecraftforge:srgutils:0.4.15": { + "url": "https://maven.neoforged.net/releases/net/minecraftforge/srgutils/0.4.15/srgutils-0.4.15.jar", + "hash": "sha1-ykCLExdZR48WTgEPrg1zmX4SX7U=" + }, + "com.google.guava:guava:32.1.2-jre": { + "url": "https://libraries.minecraft.net/com/google/guava/guava/32.1.2-jre/guava-32.1.2-jre.jar", + "hash": "sha1-XmTsfgVkVr7zpLxMb9rvceirYxg=" + }, + "com.google.guava:failureaccess:1.0.1": { + "url": "https://libraries.minecraft.net/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar", + "hash": "sha1-Hc8d44Kgv5Wj2LCElUbIi6wSksk=" + }, + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava": { + "url": "https://libraries.minecraft.net/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar", + "hash": "sha1-tCFSbF8pcpWt7xyIblJGw51Kxik=" + }, + "com.google.code.findbugs:jsr305:3.0.2": { + "url": "https://libraries.minecraft.net/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar", + "hash": "sha1-JeouiwwziodzE71GctP+BW6njw0=" + }, + "org.checkerframework:checker-qual:3.33.0": { + "url": "https://libraries.minecraft.net/org/checkerframework/checker-qual/3.33.0/checker-qual-3.33.0.jar", + "hash": "sha1-3itgti2kh2RPwR9zTnPIsLQxI48=" + }, + "com.google.errorprone:error_prone_annotations:2.18.0": { + "url": "https://libraries.minecraft.net/com/google/errorprone/error_prone_annotations/2.18.0/error_prone_annotations-2.18.0.jar", + "hash": "sha1-ibaEJXCW9Uj6Oaffn9qkCdTU35E=" + }, + "com.google.j2objc:j2objc-annotations:2.8": { + "url": "https://libraries.minecraft.net/com/google/j2objc/j2objc-annotations/2.8/j2objc-annotations-2.8.jar", + "hash": "sha1-yFJw4wfnuCLxCGuTaJEkuJdo4nM=" + }, + "com.google.code.gson:gson:2.10.1": { + "url": "https://libraries.minecraft.net/com/google/code/gson/gson/2.10.1/gson-2.10.1.jar", + "hash": "sha1-s63UeNQ4K3jqILFnE5CoWAAv62w=" + }, + "org.codehaus.plexus:plexus-utils:3.3.0": { + "url": "https://maven.neoforged.net/releases/org/codehaus/plexus/plexus-utils/3.3.0/plexus-utils-3.3.0.jar", + "hash": "sha1-z0O1OR3mI7Nv4GaiESe674LGQCI=" + }, + "com.machinezoo.noexception:noexception:1.7.1": { + "url": "https://maven.neoforged.net/releases/com/machinezoo/noexception/noexception/1.7.1/noexception-1.7.1.jar", + "hash": "sha1-tlMwyY44ofkV+lSm5eykllBePwo=" + }, + "net.neoforged:neoform:1.21.1-20240808.144430@zip": { + "url": "https://maven.neoforged.net/releases/net/neoforged/neoform/1.21.1-20240808.144430/neoform-1.21.1-20240808.144430.zip", + "hash": "sha1-gR4r2G+izaKBLl6OUdcY6ovW0/Q=" + }, + "net.neoforged.installertools:binarypatcher:2.1.2:fatjar": { + "url": "https://maven.neoforged.net/releases/net/neoforged/installertools/binarypatcher/2.1.2/binarypatcher-2.1.2-fatjar.jar", + "hash": "sha1-dZtj7zk+2AQY7B6k0jPNYVLQJjc=" + }, + "net.neoforged:AutoRenamingTool:2.0.3:all": { + "url": "https://maven.neoforged.net/releases/net/neoforged/AutoRenamingTool/2.0.3/AutoRenamingTool-2.0.3-all.jar", + "hash": "sha1-2YkMcbQ2b4hsKxAGeCBDpqaBbrY=" + }, + "net.neoforged.installertools:installertools:2.1.2": { + "url": "https://maven.neoforged.net/releases/net/neoforged/installertools/installertools/2.1.2/installertools-2.1.2.jar", + "hash": "sha1-clJMA2L4EtiqTNtMA+m0Xitxrjs=" + }, + "net.neoforged:srgutils:1.0.0": { + "url": "https://maven.neoforged.net/releases/net/neoforged/srgutils/1.0.0/srgutils-1.0.0.jar", + "hash": "sha1-uf5s2rSUmDIXy8FMxvksjmxhZSY=" + }, + "net.md-5:SpecialSource:1.11.0": { + "url": "https://maven.neoforged.net/releases/net/md-5/SpecialSource/1.11.0/SpecialSource-1.11.0.jar", + "hash": "sha1-Q7hMS7jQHPkKKd/uwclYpLZLr0Y=" + }, + "com.google.code.gson:gson:2.8.9": { + "url": "https://libraries.minecraft.net/com/google/code/gson/gson/2.8.9/gson-2.8.9.jar", + "hash": "sha1-ikMsHWgleB4hoC2y4sM8X94oM7k=" + }, + "de.siegmar:fastcsv:2.0.0": { + "url": "https://maven.neoforged.net/releases/de/siegmar/fastcsv/2.0.0/fastcsv-2.0.0.jar", + "hash": "sha1-thXybAPt6slmYYuTue5PTu1QquE=" + }, + "org.ow2.asm:asm-commons:9.3": { + "url": "https://maven.neoforged.net/releases/org/ow2/asm/asm-commons/9.3/asm-commons-9.3.jar", + "hash": "sha1-HypDLRIS9cNSrmB9e2HcriDCCvU=" + }, + "net.neoforged.installertools:cli-utils:2.1.2": { + "url": "https://maven.neoforged.net/releases/net/neoforged/installertools/cli-utils/2.1.2/cli-utils-2.1.2.jar", + "hash": "sha1-5aMRXrnimRF0cmdZRr6KcuSZCtY=" + }, + "com.google.guava:guava:20.0": { + "url": "https://maven.neoforged.net/releases/com/google/guava/guava/20.0/guava-20.0.jar", + "hash": "sha1-iVB3ASSTiOHtXdz4xB9M4b54Me8=" + }, + "com.opencsv:opencsv:4.4": { + "url": "https://maven.neoforged.net/releases/com/opencsv/opencsv/4.4/opencsv-4.4.jar", + "hash": "sha1-Ulkyoe30bJynWqnIPTHcGAwYaGU=" + }, + "org.ow2.asm:asm-analysis:9.3": { + "url": "https://maven.neoforged.net/releases/org/ow2/asm/asm-analysis/9.3/asm-analysis-9.3.jar", + "hash": "sha1-SwcfIRs3w44On1mYVQGXyFk/atg=" + }, + "org.ow2.asm:asm-tree:9.3": { + "url": "https://maven.neoforged.net/releases/org/ow2/asm/asm-tree/9.3/asm-tree-9.3.jar", + "hash": "sha1-eNLs1hMYtaWM0E+yN2NsDoa3fZc=" + }, + "org.ow2.asm:asm:9.3": { + "url": "https://libraries.minecraft.net/org/ow2/asm/asm/9.3/asm-9.3.jar", + "hash": "sha1-jmMA71HB2AGn7WLQfNIhrKOpBkA=" + }, + "org.apache.commons:commons-text:1.3": { + "url": "https://maven.neoforged.net/releases/org/apache/commons/commons-text/1.3/commons-text-1.3.jar", + "hash": "sha1-mr9hcIpmq15V9haaIA2/xYS1Rtk=" + }, + "org.apache.commons:commons-lang3:3.8.1": { + "url": "https://maven.neoforged.net/releases/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar", + "hash": "sha1-ZQWnKgl9knD3qee/QsQjgoMkd1U=" + }, + "commons-beanutils:commons-beanutils:1.9.3": { + "url": "https://maven.neoforged.net/releases/commons-beanutils/commons-beanutils/1.9.3/commons-beanutils-1.9.3.jar", + "hash": "sha1-yEVwPeM03ca0s80mg1RYyxy6Hz0=" + }, + "org.apache.commons:commons-collections4:4.2": { + "url": "https://maven.neoforged.net/releases/org/apache/commons/commons-collections4/4.2/commons-collections4-4.2.jar", + "hash": "sha1-VOvqCltlPTxoATHnP+gHu494xO0=" + }, + "commons-logging:commons-logging:1.2": { + "url": "https://libraries.minecraft.net/commons-logging/commons-logging/1.2/commons-logging-1.2.jar", + "hash": "sha1-S/wSrf5IQr8HtlfwNpxMtSKVVoY=" + }, + "commons-collections:commons-collections:3.2.2": { + "url": "https://maven.neoforged.net/releases/commons-collections/commons-collections/3.2.2/commons-collections-3.2.2.jar", + "hash": "sha1-itcv45+oyR6q8Sqtsh4MNmH+JtU=" + }, + "net.neoforged.installertools:jarsplitter:2.1.2": { + "url": "https://maven.neoforged.net/releases/net/neoforged/installertools/jarsplitter/2.1.2/jarsplitter-2.1.2.jar", + "hash": "sha1-inkWvgoOWJiXvqt8g5ByYxBn5I4=" + }, + "net.neoforged:neoforge:21.1.250:universal": { + "url": "https://maven.neoforged.net/releases/net/neoforged/neoforge/21.1.250/neoforge-21.1.250-universal.jar", + "hash": "sha1-IUxSk8hZmwqq39v6kkU7ojkYohk=" + } + } +} diff --git a/nixos/system/neuro/minecraft/public-tunnel.nix b/nixos/system/neuro/minecraft/public-tunnel.nix new file mode 100644 index 00000000..12b4385e --- /dev/null +++ b/nixos/system/neuro/minecraft/public-tunnel.nix @@ -0,0 +1,36 @@ +{ config, pkgs, ... }: +{ + users.groups.mc-wow-tunnel = { }; + users.users.mc-wow-tunnel = { + isSystemUser = true; + group = "mc-wow-tunnel"; + }; + sops.secrets."minecraft/wow-tunnel-key" = { + sopsFile = ../../../../sus/neuro-minecraft.yaml; + owner = "mc-wow-tunnel"; + group = "mc-wow-tunnel"; + mode = "0400"; + }; + programs.ssh.knownHosts.minecraft-wow-relay = { + 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; + }; + }; +} diff --git a/nixos/system/neuro/minecraft/world-import.nix b/nixos/system/neuro/minecraft/world-import.nix index e5cd3385..73771001 100644 --- a/nixos/system/neuro/minecraft/world-import.nix +++ b/nixos/system/neuro/minecraft/world-import.nix @@ -93,8 +93,8 @@ mkdir -p "$cache_dir" "$server_dir" chmod 0700 "$cache_dir" - chgrp minecraft "$server_dir" - chmod 0770 "$server_dir" + # The Minecraft module creates server_dir as minecraft:minecraft + # with group write access; this importer must not chmod another user's directory. if [ -d "$world_dir" ]; then if [ -f "$world_dir/level.dat" ]; then diff --git a/nixos/system/neuro/minecraft/world-of-sosal.nix b/nixos/system/neuro/minecraft/world-of-sosal.nix index 7e6e05a7..6c1b70ff 100644 --- a/nixos/system/neuro/minecraft/world-of-sosal.nix +++ b/nixos/system/neuro/minecraft/world-of-sosal.nix @@ -1,8 +1,8 @@ -{ config, pkgs, ... }: +{ config, ... }: { sops.secrets."minecraft/storage-box-pack-key" = { - sopsFile = ../../../../sus/neuro.yaml; + sopsFile = ../../../../sus/neuro-minecraft.yaml; owner = "mc-pack-worldOfSosal"; group = "mc-pack-worldOfSosal"; mode = "0400"; @@ -10,7 +10,7 @@ services.minecraft-modpack-imports.worldOfSosal = { enable = true; - serverName = "worldOfSosal"; + serverName = "wowMineMap"; remoteHost = "u664722.your-storagebox.de"; remoteUser = "u664722"; remotePath = "minecraft/pack/WorldOfSosal.mrpack"; @@ -25,17 +25,9 @@ hostPublicKey = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA5EB5p/5Hp3hGW1oHok+PIOH9Pbn7cnUiGmUEBrCVjnAw+HrKyN8bYVV0dIGllswYXwkG/+bgiBlE6IVIBAq+JwVWu1Sss3KarHY3OvFJUXZoZyRRg/Gc/+LRCE7lyKpwWQ70dbelGRyyJFH36eNv6ySXoUYtGkwlU5IVaHPApOxe4LHPZa/qhSRbPo2hwoh0orCtgejRebNtW5nlx00DNFgsvn8Svz2cIYLxsPVzKgUxs8Zxsxgn+Q/UvR7uq4AbAhyBMLxv7DjJ1pc7PJocuTno2Rw9uMZi1gkjbnmiOh6TTXIEWbnroyIhwc8555uto9melEUmWNQ+C+PwAK+MPw=="; }; - services.minecraft-servers.servers.worldOfSosal = { - enable = true; - jvmOpts = "-Xmx8G -Xms2G"; - package = pkgs.minecraftServers.neoforge-1_21_1; - - serverProperties = { - server-port = 25568; - difficulty = "hard"; - online-mode = true; - view-distance = 20; - pause-when-empty-seconds = 0; - }; + # Import the map before writing modpack configuration into the same server. + systemd.services.minecraft-modpack-import-worldOfSosal = { + after = [ "minecraft-world-import-wowMineMap.service" ]; + requires = [ "minecraft-world-import-wowMineMap.service" ]; }; } diff --git a/nixos/system/neuro/minecraft/wow-mine-map.nix b/nixos/system/neuro/minecraft/wow-mine-map.nix index a01949b8..b8e79220 100644 --- a/nixos/system/neuro/minecraft/wow-mine-map.nix +++ b/nixos/system/neuro/minecraft/wow-mine-map.nix @@ -2,7 +2,7 @@ { sops.secrets."minecraft/storage-box-key" = { - sopsFile = ../../../../sus/neuro.yaml; + sopsFile = ../../../../sus/neuro-minecraft.yaml; owner = "minecraft-map-import-wowMineMap"; group = "minecraft-map-import-wowMineMap"; mode = "0400"; @@ -25,15 +25,18 @@ services.minecraft-servers.servers.wowMineMap = { enable = true; jvmOpts = "-Xmx8G -Xms2G"; - package = pkgs.minecraftServers.neoforge-1_21_1; - - symlinks.mods = import ./mods.nix { inherit pkgs; }; + # WorldOfSosal client and server use the same pinned NeoForge. + package = pkgs.minecraftServers.neoforge-1_21_1.override ( + builtins.fromJSON (builtins.readFile ./neoforge-21.1.250.json) + ); serverProperties = { server-port = 25567; difficulty = "hard"; online-mode = true; - view-distance = 20; + view-distance = 12; + simulation-distance = 8; + motd = "WorldOfSosal — World of Warcraft"; level-name = "world"; pause-when-empty-seconds = 0; }; diff --git a/script/build-prism-pack.py b/script/build-prism-pack.py new file mode 100755 index 00000000..b3f34e8f --- /dev/null +++ b/script/build-prism-pack.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +"""Build a Prism auto-update instance and immutable packwiz release from an mrpack. + +Upload the pack directory to an immutable release, then atomically switch current. +Publish only releases tested with the server, including Minecraft/NeoForge upgrades. +""" +import argparse +import hashlib +import html +import io +import json +from pathlib import Path, PurePosixPath +import re +import struct +import tomllib +import urllib.parse +import urllib.request +import zipfile + +BOOTSTRAP_URL = 'https://github.com/packwiz/packwiz-installer-bootstrap/releases/download/v0.0.3/packwiz-installer-bootstrap.jar' +BOOTSTRAP_SHA256 = 'a8fbb24dc604278e97f4688e82d3d91a318b98efc08d5dbfcbcbcab6443d116c' + +def digest(data): + return hashlib.sha256(data).hexdigest() + +def quote(s): + return json.dumps(s, ensure_ascii=False) + +def safe_path(s): + p = PurePosixPath(s) + if not s or p.is_absolute() or any(x in ('', '.', '..') for x in s.split('/')) or re.search(r'[\\\x00-\x1f:*?"<>|]', s): + raise ValueError(f'Unsafe pack path: {s!r}') + return p + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument('mrpack', type=Path) + ap.add_argument('output', type=Path, help='new output directory (must not exist)') + ap.add_argument('--base-url', default='https://store.hectic-lab.com/minecraft/world-of-sosal/') + ap.add_argument('--server', help='optional initial multiplayer server address') + ap.add_argument('--bootstrap', type=Path) + args = ap.parse_args() + base = args.base_url.rstrip('/') + '/' + if not base.startswith('https://'): + ap.error('--base-url must use HTTPS') + archive = args.mrpack.read_bytes() + release = digest(archive) + bootstrap = args.bootstrap.read_bytes() if args.bootstrap else urllib.request.urlopen(BOOTSTRAP_URL, timeout=60).read() + if digest(bootstrap) != BOOTSTRAP_SHA256: + raise ValueError('Bootstrap checksum mismatch') + args.output.mkdir(parents=True, exist_ok=False) + root = args.output / 'pack' + root.mkdir(parents=True) + entries = {} + destinations = set() + def write(path, data, *, metafile=False, preserve=False): + safe_path(path) + dest = root / path + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_bytes(data) + entries[path] = {'file': path, 'hash': digest(data), 'metafile': metafile, 'preserve': preserve} + with zipfile.ZipFile(io.BytesIO(archive)) as z: + manifest = json.loads(z.read('modrinth.index.json')) + deps = manifest['dependencies'] + if manifest['formatVersion'] != 1 or manifest['game'] != 'minecraft' or set(deps) != {'minecraft', 'neoforge'}: + raise ValueError('Expected a Minecraft NeoForge mrpack v1') + for f in manifest['files']: + path = safe_path(f['path']) + if f.get('env', {}).get('client') == 'unsupported': + continue + if f['path'] in destinations: + raise ValueError('Duplicate destination: ' + f['path']) + destinations.add(f['path']) + url = f['downloads'][0] + sha = f['hashes']['sha512'] + if not url.startswith('https://') or not re.fullmatch('[0-9a-fA-F]{128}', sha): + raise ValueError('Invalid URL/hash: ' + f['path']) + # Include optional mods too, matching the server importer. The published + # manifest, rather than upstream latest versions, controls all updates. + meta = f'name = {quote(path.name)}\nfilename = {quote(path.name)}\nside = "client"\n\n[download]\nurl = {quote(url)}\nhash-format = "sha512"\nhash = {quote(sha)}\n' + write(str(path) + '.pw.toml', meta.encode(), metafile=True) + for prefix in ('overrides/', 'client-overrides/'): + for item in z.infolist(): + if not item.filename.startswith(prefix) or item.is_dir(): + continue + name = item.filename[len(prefix):] + safe_path(name) + if not (name.startswith(('config/', 'mods/', 'resourcepacks/', 'shaderpacks/')) or name == 'options.txt'): + raise ValueError('Review unexpected override: ' + name) + if name in destinations: + raise ValueError('Override duplicates downloaded mod: ' + name) + write(name, z.read(item), preserve=name == 'options.txt') + index = 'hash-format = "sha256"\n' + for entry in sorted(entries.values(), key=lambda e: e['file']): + index += '\n[[files]]\n' + for k, v in entry.items(): + if isinstance(v, bool): + if v: + index += f'{k} = true\n' + else: + index += f'{k} = {quote(v)}\n' + (root / 'index.toml').write_text(index) + pack = f'name = "WorldOfSosal"\npack-format = "packwiz:1.1.0"\nversion = {quote(manifest["versionId"])}\n\n[index]\nfile = "index.toml"\nhash-format = "sha256"\nhash = "{digest(index.encode())}"\n\n[versions]\nminecraft = {quote(deps["minecraft"])}\nneoforge = {quote(deps["neoforge"])}\n' + tomllib.loads(pack) + (root / 'pack.toml').write_text(pack) + (args.output / 'latest.mrpack').write_bytes(archive) + cfg = '\n'.join(['[General]', 'InstanceType=OneSix', 'name=WorldOfSosal Auto Update', 'iconKey=default', 'OverrideCommands=true', 'PreLaunchCommand=' + quote(f'"$INST_JAVA" -jar packwiz-installer-bootstrap.jar {base}current/pack.toml'), 'OverrideMemory=true', 'MinMemAlloc=1024', 'MaxMemAlloc=8192', '']) + mmc = {'formatVersion': 1, 'components': [{'uid':'net.minecraft', 'version':deps['minecraft'], 'important':True}, {'uid':'net.neoforged', 'version':deps['neoforge'], 'important':True}]} + def nbt_string(value): + data = value.encode() + return struct.pack('>H', len(data)) + data + # Initial server list is seeded only during instance import, never overwritten. + servers = b'\x0a\x00\x00\x09' + nbt_string('servers') + b'\x0a\x00\x00\x00\x01' + servers += b'\x08' + nbt_string('name') + nbt_string('WorldOfSosal') + servers += b'\x08' + nbt_string('ip') + nbt_string(args.server or '') + b'\x00\x00' + with zipfile.ZipFile(args.output / 'WorldOfSosal-Prism.zip', 'w', zipfile.ZIP_DEFLATED) as z: + z.writestr('instance.cfg', cfg) + z.writestr('mmc-pack.json', json.dumps(mmc, indent=2)) + z.writestr('.minecraft/packwiz-installer-bootstrap.jar', bootstrap) + if args.server: + z.writestr('.minecraft/servers.dat', servers) + page = f'''WorldOfSosal +

WorldOfSosal — автоматическое обновление

+

Один раз скачайте инстанс для Prism Launcher, затем выберите «Добавить сборку → Импорт из zip». Разрешите команду перед запуском: она устанавливает и обновляет моды через packwiz.

+

Minecraft {html.escape(deps['minecraft'])} · NeoForge {html.escape(deps['neoforge'])} · Java 21 · память 8 ГБ.

+

{'Сервер: ' + html.escape(args.server) + '.' if args.server else 'Адрес игрового сервера будет сообщён отдельно.'}

+

Моды и конфигурация сборки обновляются при каждом запуске. Личные настройки options.txt сохраняются. При смене Minecraft или NeoForge может потребоваться повторный запуск после обновления версии.

+

Обычный mrpack без автообновлений · Контрольные суммы

''' + (args.output / 'index.html').write_text(page) + files = ['WorldOfSosal-Prism.zip', 'latest.mrpack', 'pack/pack.toml'] + (args.output / 'SHA256SUMS').write_text(''.join(f'{digest((args.output/f).read_bytes())} {f.replace("pack/", "current/")}\n' for f in files)) + print(json.dumps({'release':release, 'minecraft':deps['minecraft'], 'neoforge':deps['neoforge'], 'managed_files':len(entries), 'server':args.server})) + +if __name__ == '__main__': + main() diff --git a/script/publish-prism-pack.py b/script/publish-prism-pack.py new file mode 100755 index 00000000..9402bfdb --- /dev/null +++ b/script/publish-prism-pack.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""Publish build-prism-pack.py output; switch the complete pack atomically. + +Usage: python3 script/publish-prism-pack.py OUTPUT_DIRECTORY [SSH_HOST] +Coordinate gameplay updates with deployment of the same release on the server. +""" +import hashlib +from pathlib import Path +import subprocess +import sys +import tarfile +import tempfile + +root = Path(sys.argv[1]).resolve() +host = sys.argv[2] if len(sys.argv) > 2 else 'hectic-lab' +if host.startswith('-'): + raise ValueError('Invalid SSH host') +release = hashlib.sha256((root / 'pack/pack.toml').read_bytes()).hexdigest() +with tempfile.TemporaryDirectory(prefix='prism-publish-') as temporary: + archive = Path(temporary) / 'publish.tar.gz' + with tarfile.open(archive, 'w:gz') as tar: + for name in ['pack', 'WorldOfSosal-Prism.zip', 'latest.mrpack', 'SHA256SUMS', 'index.html']: + tar.add(root / name, arcname=name) + remote = f'/tmp/prism-publish-{release}.tar.gz' + subprocess.run(['scp', '-o', 'BatchMode=yes', str(archive), f'{host}:{remote}'], check=True) + script = '''set -eu +umask 022 +base=/var/www/store/minecraft/world-of-sosal +release=RELEASE +archive=/tmp/prism-publish-$release.tar.gz +mkdir -p "$base/releases" +stage=$(mktemp -d "$base/.publish.XXXXXX") +trap 'rm -rf "$stage"; rm -f "$archive"' EXIT +tar -xzf "$archive" -C "$stage" +chmod -R u=rwX,go=rX "$stage" +if test -d "$base/releases/$release"; then + diff -qr "$stage/pack" "$base/releases/$release" +else + mv "$stage/pack" "$base/releases/$release" +fi +for name in WorldOfSosal-Prism.zip latest.mrpack SHA256SUMS index.html; do + mv "$stage/$name" "$base/$name" +done +ln -s "releases/$release" "$stage/current" +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/') diff --git a/sus/neuro-minecraft.yaml b/sus/neuro-minecraft.yaml new file mode 100644 index 00000000..f4b2ba61 --- /dev/null +++ b/sus/neuro-minecraft.yaml @@ -0,0 +1,37 @@ +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-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] + 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] +sops: + age: + - recipient: age1r25zdeqq8nac6dgca9en28r57ffyz9u9d8z5yc25gc8xqz747vaqmdtk0h + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArTUllbGhrL2F4SGVOMDl5 + RkJHZFprY1FIQ2hmRE1PTVkxbGhZWitIYW5ZCk1ueXRYY3REVFdpVXBCY1dXcmFD + QlBEZFVLRWc0MmtiNnpyQWZaMDhFaGcKLS0tIHl0T1pTanIwbG14ZDk0UjdrYVVR + MDRoSzNQdXdsVWk5SjZaYlBkZWJpaDQKkVU1p0KTR2aVyeNDBg9jUfeTFCY+BS7U + hqIEwIErcWTg35f7kl3I2Mrw69gEU7jXlc0I0/uKGJuZAwZ3hksmmQ== + -----END AGE ENCRYPTED FILE----- + - recipient: age1ak7heljpr0pjr4m0rcwxgn3sp0jjxw03lxyf33r8lcemqh2u2sgqx0aplq + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhN2FOSlh6VWl1NEJ1dnd6 + cVVTOGM5amREUnZWK2ljZHRyV1dyRlVBS2hrCnhPb2liMEpVSDNLdVlrcjBpeG01 + RTdCdGxnSHZFKzUweHdMNTdjNlVQZWMKLS0tIFYwUXZoaUNjYWdUaW1nTFk2TEJG + NGY5U0tlREpiRUR5QU81cVFRL2NXU1UK6KVnn5WZ+vDMwrS1TkuET3TG5AxYbiPd + MigYj9t0tDtytiiQZ6sbJVqXewqbKwZS6pRaGXuNTu4s2cUTSJ4ABg== + -----END AGE ENCRYPTED FILE----- + - recipient: age1ev53mzse6rg4ffwtcwtq4e93c7x7s4d0eyu89jrsahrke8r4yamsseu8h4 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQTmd5bVlleEtocDZPaHZy + aUY1ZTdBeHhXZmZsOXFCMTRoWlNraC9OTEFnCkNhV3NMS3lVeWVlSkFnOXdDbUFq + QUxxRWloUDNOQk05QUpVUGwycHRMZUkKLS0tIEdyMDJnaTB3M2VJR1hSYzhReUlz + WVlKekVEVm5kRGNCdWNqRjhYMkMyQlEKCBkbxQ2LZ56redETBA1NcL6CTAv/FBdP + x2xBwsbHdxO0PlmEvgC4x/1CtFTFj8NDhNyY7sf21ETMEIyOX9k8oA== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-09-18T20:09:44Z" + mac: ENC[AES256_GCM,data:9g87oKlLkbvqS8anS4FD/icqoClklhJENoRbKIFr38YNLPwtLLRmTPLai6Q2Z4KLeLgf0KTB+uS+Z4s0/4UuLZPU9cFXDKittkoRGG7amEY2+n9MDfuoWch47mSCCxem3gP5GV5RNKGrEXuyCESzElCO+Q1ZnsF4WSkOsxsK2MU=,iv:n2nYrw0dHmt3NfRL22Ga0eO23iYn9o/HjyCbQhJQCpc=,tag:zTQRKr0rQD5XYl2tTH7WaA==,type:str] + unencrypted_suffix: _unencrypted + version: 3.10.2