Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb20160eff | ||
|
|
2a4fc2427e | ||
|
|
c219d237ab | ||
|
|
8b6c6c6b21 | ||
|
|
a6b547c4c6 | ||
|
|
235cf51d6e | ||
|
|
17c1cd9e71 | ||
|
|
276c8f2144 |
@@ -2,6 +2,9 @@
|
||||
|
||||
Ce dépôt contient le pack Sanctuary et ses mods. Lire `README.md`, puis le ticket concerné.
|
||||
La vision est dans `docs/vision.md` ; elle décrit aussi des fonctionnalités futures.
|
||||
Pour développer/livrer le site, packwiz, la Beta dédiée et Prism local, lire
|
||||
`docs/updates.md`. La compétence personnelle `sanctuary-updates` mémorise les
|
||||
chemins et accès propres au poste ; ne pas déduire une publication d'un simple push.
|
||||
|
||||
- Avant une modification, lire l'état Git et préserver les changements existants.
|
||||
- Un ticket correspond à un résultat jouable ou vérifiable et à une branche `codex/<sujet>`.
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Sanctuary
|
||||
|
||||
Le [téléchargement direct Sanctuary Beta](docs/beta-download.md) fournit une
|
||||
instance Prism prête : importer le ZIP, puis lancer pour télécharger le pack.
|
||||
Les publications du canal restent commandées par le créateur via Codex.
|
||||
|
||||
[Procédure de développement et de mise à jour complète](docs/updates.md) :
|
||||
site, artefacts immuables, canal packwiz, serveur Beta et même instance Prism locale.
|
||||
|
||||
## beta.280 — JEI réintégré et packwiz serveur
|
||||
|
||||
[Un seul JAR Sanctuary avec le fork JEI et un profil packwiz serveur](docs/jei-server-beta280.md).
|
||||
@@ -7,8 +14,9 @@ Client et serveur partagent le même artefact ; le profil serveur ne contient
|
||||
que Sanctuary et Fabric API. Le [kit Linux](docs/server-pack.md) prépare les
|
||||
mises à jour et la connexion aux services Web existants. Build, 13 GameTests,
|
||||
sept contrôles de l'outil de mise à jour, installation packwiz et démarrage du
|
||||
JAR de production vérifiés. Une nouvelle CT pour 20 joueurs est en préparation
|
||||
sur `gpu`, séparément de l'Alpha.
|
||||
JAR de production vérifiés. La nouvelle CT 350 sur `gpu` accueille le serveur
|
||||
de 20 places, relié à l'API, à la communauté et à la console privée du site.
|
||||
[Release beta.280](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.280).
|
||||
|
||||
Les **quatre expansions validées sont disponibles dans Sanctuary normal** :
|
||||
Nord glacial, Sud des canyons, Ouest océanique et Est volcanique.
|
||||
|
||||
@@ -21,6 +21,12 @@ tasks.register('verifyServerUpdater', Exec) {
|
||||
}
|
||||
tasks.named('check') { dependsOn('verifyServerUpdater') }
|
||||
|
||||
tasks.register('verifyWebDistribution', Exec) {
|
||||
group = 'verification'
|
||||
commandLine('python3', 'scripts/test_publish_web_pack.py')
|
||||
}
|
||||
tasks.named('check') { dependsOn('verifyWebDistribution') }
|
||||
|
||||
tasks.register('verifyPack', Exec) {
|
||||
group = 'verification'
|
||||
description = 'Verify pinned pack dependencies, versions and index hashes.'
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# DIST-WEB — téléchargement direct d'une instance Prism prête
|
||||
|
||||
Branche `codex/beta-download`, serveur dédié CT 350 sur `gpu`. Cette livraison
|
||||
conserve les binaires immuables beta.280 et ne modifie ni monde ni format de
|
||||
sauvegarde. L'Alpha reste hors cible.
|
||||
|
||||
## Résultat pour le joueur
|
||||
|
||||
Le bouton **Télécharger** de [Sanctuary Web](https://sanctuary-minecraft.net)
|
||||
donne directement le fichier **Sanctuary-Beta.zip** :
|
||||
|
||||
```text
|
||||
https://sanctuary-minecraft.net/downloads/sanctuary-prism.zip
|
||||
```
|
||||
|
||||
Le joueur importe ce ZIP dans Prism (**Ajouter une instance → Importer depuis
|
||||
un ZIP**), puis lance **Sanctuary Beta**. Packwiz télécharge le pack complet au
|
||||
premier lancement et suit le même canal aux lancements suivants. La commande
|
||||
avant lancement et le bootstrap sont déjà inclus ; aucune configuration manuelle
|
||||
ou téléchargement séparé d'outil n'est demandé.
|
||||
|
||||
Le format ZIP d'instance Prism conserve ces réglages, contrairement à un MRpack
|
||||
standard. Le MRpack beta.280 reste accessible par son ancienne adresse pour
|
||||
compatibilité des liens, sans devenir un deuxième bouton du site. Le parcours
|
||||
manuel de configuration et son lien dans le pied de page sont retirés.
|
||||
|
||||
L'archive légère contient uniquement `instance.cfg`, `mmc-pack.json`, l'icône et
|
||||
`minecraft/packwiz-installer-bootstrap.jar`. Elle démarre avec zéro mod local.
|
||||
L'instance est nommée **Sanctuary Beta**, avec `ManagedPack=false`,
|
||||
`OverrideCommands=true`, la sélection automatique de Java et le canal stable :
|
||||
|
||||
```text
|
||||
https://git.botsu.net/koka/sanctuary-beta/raw/branch/packwiz/pack.toml
|
||||
```
|
||||
|
||||
Le nom du bouton et le téléchargement direct sont conservés. Le pied de page et
|
||||
le survol indiquent la version réellement publiée. Les réponses de téléchargement
|
||||
utilisent `Cache-Control: no-store` pour garder la même adresse à chaque livraison.
|
||||
|
||||
## Publication demandée via Codex
|
||||
|
||||
Aucune tâche périodique ne décide de publier une version. À la demande du créateur :
|
||||
|
||||
1. Vérifier et publier les nouveaux binaires immuables, puis le canal packwiz
|
||||
selon [le protocole de distribution](packwiz.md).
|
||||
2. Générer l'instance prête avec `python3 scripts/pack.py prism <url-du-canal>`.
|
||||
Joindre `build/Sanctuary-Prism-beta.xxx.zip` à la release, avec le MRpack et
|
||||
le kit serveur. L'export Prism est reproductible et conserve ses commandes.
|
||||
3. Mettre à jour la CT dédiée selon [le contrat serveur](server-pack.md), en
|
||||
conservant sauvegardes, réglages et secrets, puis vérifier son redémarrage.
|
||||
4. Publier le téléchargement Web après contrôle de cette CT et des deux archives :
|
||||
|
||||
```sh
|
||||
python3 scripts/publish_web_pack.py \
|
||||
--web-ct 320 --server-ct 350 --version beta.280 \
|
||||
--mrpack-url https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.280/Sanctuary-beta.280.mrpack \
|
||||
--prism-url https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.280/Sanctuary-Prism-beta.280.zip \
|
||||
--pack-url https://git.botsu.net/koka/sanctuary-beta/raw/branch/packwiz/pack.toml
|
||||
```
|
||||
|
||||
Cette dernière commande s'exécute sur `gpu`, où `pct` est disponible. `--dry-run`
|
||||
vérifie sans publier. Le script contrôle les index, les versions, les hashes des
|
||||
dépendances et le JAR Sanctuary de la CT, du canal et du MRpack. Il refuse une
|
||||
archive Prism sans commande active, visant un autre canal ou une autre plateforme,
|
||||
avec un bootstrap non vérifié ou des fichiers étrangers.
|
||||
|
||||
Les fichiers Web restent dans
|
||||
`/var/www/sanctuary/storage/app/distribution/releases/beta.xxx/prism/`.
|
||||
Chaque publication est immuable. Sous verrou, `current` bascule atomiquement après
|
||||
écriture complète. Les anciennes versions restent disponibles pour une reprise.
|
||||
Le script ne redémarre aucun serveur et ne change pas le canal lui-même.
|
||||
|
||||
## Vérifications
|
||||
|
||||
Huit tests Python couvrent l'alignement CT/canal/MRpack, le refus d'un serveur
|
||||
arrêté ou divergent, le contrat d'import Prism prêt, les fichiers interdits et la
|
||||
publication atomique avec conservation de la précédente en cas de conflit.
|
||||
Ils font partie de `./gradlew check`.
|
||||
|
||||
Les quatre tests de téléchargement et les trois tests des métriques Laravel
|
||||
passent, ainsi que Pint : le bouton sert un ZIP anonyme directement, le nom est
|
||||
`Sanctuary-Beta.zip`, les publications incomplètes restent indisponibles, et le
|
||||
changement de version ne demande pas de reconstruire le cache de configuration.
|
||||
Le MRpack reste téléchargeable par son ancien lien.
|
||||
|
||||
Les tests natifs utilisent une extraction de l'archive prête dans un chemin
|
||||
contenant des espaces, avec les variables de sa propre commande avant lancement.
|
||||
Ils exécutent packwiz sans interface ; aucun Minecraft ni monde personnel n'est
|
||||
ouvert. Les fichiers témoins de réglages, favoris et sauvegardes sont comparés
|
||||
avant et après synchronisation. Les journaux sont conservés sous
|
||||
`build/prism-ready-validation*/`, hors de Git.
|
||||
|
||||
`./gradlew check build assemblePack -PsanctuaryFocusedTests=recipes,progression
|
||||
-PsanctuaryAtlasOnly=true` réussit. L'archive prête mesure **92 417 octets**, SHA-256
|
||||
`30dd37f3a6e1d79f8b443f6312918258276c85d5b6a5e7fbdc9c3fea7562dbf2`.
|
||||
Une première tentative native reçoit des HTTP 502 de Gitea ; une nouvelle
|
||||
extraction entièrement vide réussit ensuite ses deux lancements de synchronisation.
|
||||
Le reçu est `build/prism-ready-validation-retry/validation.json`.
|
||||
|
||||
L'archive est ajoutée à la release beta.280 sans changer son tag ni ses artefacts
|
||||
existants. La CT Web publie ensuite le ZIP et le MRpack vérifiés dans la même
|
||||
bascule atomique. Le code Web est livré au commit
|
||||
`46d784ecbb8ba3d0f20199b85bcf486831ba3750` ; les changements préexistants sont
|
||||
conservés et les empreintes des comptes et permissions restent identiques.
|
||||
Sauvegarde privée :
|
||||
`/root/sanctuary-beta-maintenance/web-prism-ready-20261025-1792943502/`.
|
||||
|
||||
Le parcours navigateur **accueil → Télécharger → Sanctuary-Beta.zip reçu** est
|
||||
vérifié à 1280 × 900 et 390 × 844 : HTTP 200 direct, pas de changement de page,
|
||||
nom et SHA-256 identiques à l'archive testée, aucune erreur d'application ni
|
||||
débordement horizontal. Le lien de guide manuel est absent. Le MRpack historique
|
||||
répond toujours HTTP 200. Le navigateur utilisé est Playwright, le plugin
|
||||
Browser étant absent ; reçu et captures sous `/tmp/sanctuary-prism-ready-*`.
|
||||
Reçus opérationnels ignorés : `build/prism-ready-publication.json`,
|
||||
`build/prism-ready-web-publication.json`, `build/prism-ready-web-deployment.log`
|
||||
et `build/prism-ready-build.log`.
|
||||
+128
-2
@@ -59,5 +59,131 @@ L'Alpha (CT 300), les CT 330 et 340 et les mondes existants restent hors cible.
|
||||
Le nouveau monde utilisera `sanctuary:sanctuary` (réglages initiaux
|
||||
`sanctuary:island231_medium`), graine `28020261025`, version beta.280.
|
||||
Aucune expansion n'est activée par le déploiement. Le site est dans la CT 320 ;
|
||||
son API et son schéma communautaire 3 sont présents. La mise en service du
|
||||
nouveau serveur et le raccordement aux services sont en cours.
|
||||
son API et son schéma communautaire 3 sont présents.
|
||||
|
||||
## Livraison et mise en service
|
||||
|
||||
La [release beta.280](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.280)
|
||||
est publiée depuis le commit `23b295356b499404e0056affbb5d3b3811c1b01f`.
|
||||
Le canal packwiz est avancé au commit `338e8a62387048c7e703ef036fda759ef57b4130`,
|
||||
avec les profils client et serveur dans le même commit. Aucun JEI séparé n'est
|
||||
joint. Le MRpack contient le seul JAR Sanctuary en override, et trois dépendances
|
||||
téléchargées (Fabric API, Sodium et Sound Physics).
|
||||
|
||||
| Artefact | SHA-256 |
|
||||
| --- | --- |
|
||||
| `sanctuary-beta.280.jar` | `489dade38218d34d2a4502fa4ffe71f0bc13035948dc1c52b633b528e9855c7e` |
|
||||
| `Sanctuary-Server-beta.280.zip` | `54ed73e75b53305a9f0cc5505f3d64624d23598f906ac69d121030255ced0d5a` |
|
||||
| `Sanctuary-beta.280.mrpack` | `b23ba2d9cf72d27da2595e3f4a32a36656f71abf2d2cb4d6cefd7f9d29e4cd01` |
|
||||
|
||||
Le kit téléchargé dans la nouvelle CT correspond à cet artefact immuable.
|
||||
Java 25, Python 3.13 et le service `sanctuary-beta.service` sont installés ;
|
||||
le service est activé au démarrage. Le premier monde Sanctuary a été créé à
|
||||
la graine annoncée, avec le générateur 231, taille Medium, en Survie et
|
||||
`online-mode=true`. La réponse réseau Minecraft annonce 26.3, protocole 777,
|
||||
**20 places**, et le JEI embarqué est chargé côté serveur.
|
||||
|
||||
La synchronisation packwiz a été exécutée après arrêt propre du nouveau serveur :
|
||||
**61 fichiers de monde, de configuration et de secrets gardent leurs hashes**.
|
||||
Le même monde redémarre ensuite. L'outil maintient son verrou jusqu'à la fin
|
||||
de la sauvegarde Java, y compris lorsque systemd transmet SIGTERM.
|
||||
|
||||
L'API du site accepte l'authentification et refuse un corps de vérification
|
||||
vide avec HTTP 422 : aucun code d'habitant n'est consommé. L'accueil serveur
|
||||
confirme que les codes sont requis. Un compte MariaDB propre à Beta a été créé,
|
||||
restreint à son hôte et aux sept tables nécessaires, avec TLS obligatoire ;
|
||||
Connector/J utilise `sslMode=verify-full`. Le schéma existant 3 n'est pas migré.
|
||||
La ligne de statistiques du nouveau serveur est alimentée toutes les 30 secondes.
|
||||
|
||||
La console du site était non configurée. Seules ses trois variables RCON ont
|
||||
été renseignées pour Beta, après sauvegarde privée ; le cache Laravel est
|
||||
reconstruit sans redémarrer le site. La commande de lecture `list`, appelée
|
||||
par le vrai service PHP, répond « 0 of a max of 20 ». Le port RCON est filtré
|
||||
dans la CT Beta : accès du site et de loopback uniquement, refus depuis un autre
|
||||
client LAN. Aucune redirection publique RCON n'est créée.
|
||||
|
||||
Les secrets restent dans `/etc/sanctuary/beta.env`, le `server.properties`
|
||||
privé et l'environnement Web, jamais dans Git ou packwiz. Les unités
|
||||
`sanctuary-beta-rcon-firewall.service` et le complément systemd associé
|
||||
appartiennent à cette installation, hors de l'index de mods.
|
||||
|
||||
Le domaine demandé est `beta.sanctuary-minecraft.net`. Les deux enregistrements
|
||||
OVH sont publiés : A pour Beta et SRV `_minecraft._tcp.beta` vers le port 25567,
|
||||
TTL 300. Les quatorze enregistrements DNS préexistants sont conservés. Une
|
||||
réservation DHCP lie l'adresse de la CT à sa MAC ; elle est activée après test
|
||||
de syntaxe et redémarrage de dnsmasq, sans changer ses deux fichiers existants.
|
||||
Aucun serveur Minecraft ou proxy existant n'est redémarré par cette opération.
|
||||
|
||||
Une redirection UPnP Beta seule a servi d'amorçage. Le 25 octobre, le créateur
|
||||
réenregistre la règle Bbox permanente « Sanctuary beta 20 » : **port externe
|
||||
25567 → 192.168.1.122:25565**. L'API de la Bbox déclare encore `protocol=all`
|
||||
(TCP/UDP) ; l'accès Minecraft TCP est vérifié.
|
||||
|
||||
La redirection UPnP appartenant à Beta est ensuite supprimée. Son timer est
|
||||
désactivé, le service est arrêté, puis les trois fichiers du maintien sont
|
||||
retirés de la configuration active et archivés dans la CT sous
|
||||
`/root/sanctuary-beta-maintenance/upnp-retired-20261025-1792900244/`.
|
||||
Les sept autres règles NAT permanentes et les quatre autres redirections UPnP
|
||||
sont conservées. Le processus Minecraft reste le même : aucun redémarrage ni
|
||||
modification d'une sauvegarde n'est nécessaire à ce retrait.
|
||||
|
||||
Après suppression du secours, `beta.sanctuary-minecraft.net:25567` répond bien
|
||||
Minecraft **26.3**, protocole **777**, **0/20**, avec le MOTD du nouveau Beta.
|
||||
Le SRV public permet de saisir seulement `beta.sanctuary-minecraft.net`.
|
||||
La vérification utilise le chemin public depuis le LAN (hairpin) ; une validation
|
||||
depuis un réseau Internet extérieur reste à effectuer.
|
||||
|
||||
Le [MRpack client publié](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.280/Sanctuary-beta.280.mrpack)
|
||||
est retéléchargé et vérifié : 16 589 058 octets, hash identique au fichier local,
|
||||
ZIP intègre, version beta.280, Minecraft 26.3 et un seul override Sanctuary avec
|
||||
JEI intégré.
|
||||
|
||||
Aucune charge réelle avec vingt joueurs, nouvelle connexion OAuth Discord ou
|
||||
nouvelle qualification graphique n'est revendiquée.
|
||||
|
||||
Les sources sont isolées de Map Island Zoom. La prochaine livraison doit
|
||||
reprendre ce profil serveur et utiliser un compteur supérieur à beta.280.
|
||||
L'instance Prism personnelle n'a pas été synchronisée par cette mise en service.
|
||||
|
||||
Preuves locales supplémentaires : `build/server280-publication.log`,
|
||||
`build/server280-deployment.log`, `build/server280-gpu-update-validation.log`,
|
||||
`build/server280-rcon-setup.log`, `build/server280-dhcp-reservation.log`,
|
||||
`build/server280-upnp-retirement.log` et
|
||||
les inventaires sous `/root/beta-publication-20261025/` sur PVE. Ces fichiers
|
||||
restent hors de Git.
|
||||
|
||||
## Raccordement Sanctuary Web corrigé le 25 octobre 2026
|
||||
|
||||
Le statut et l'Intendance de [Sanctuary Web](https://sanctuary-minecraft.net)
|
||||
lisaient encore l'ancienne base distante par la connexion Laravel `game`, alors
|
||||
que Beta écrivait déjà dans la base `sanctuary` de la CT Web 320. La vérification
|
||||
initiale des écritures serveur ne couvrait donc pas toutes les lectures du site.
|
||||
Hermes confirme l'identité de cette instance Web ; les anciens sites de release
|
||||
ne sont pas remis en service.
|
||||
|
||||
La connexion `game` utilise maintenant la même base locale que Beta, en gardant
|
||||
`COMMUNITY_DB_CONNECTION=game` pour les services et les ressources
|
||||
d'administration. Le cache de configuration Laravel est reconstruit. Les sources
|
||||
Web déployées, qui contiennent des changements préexistants, restent inchangées.
|
||||
La connexion Discord fonctionne selon le créateur et n'est pas modifiée.
|
||||
|
||||
La nouvelle communauté ne contient aucune publication : Gazette, Tableau et
|
||||
Intendance affichent désormais cet état, sans suppression dans l'ancienne base.
|
||||
Les trois comptes, dont deux administrateurs, le groupe d'équipe, ses deux
|
||||
adhésions et les vingt-deux permissions sont conservés. Les empreintes des
|
||||
identités, des droits administrateurs et des tables d'équipe sont identiques
|
||||
avant et après l'opération.
|
||||
|
||||
Les services publics et les requêtes d'administration Gazette/Tableau lisent
|
||||
zéro publication. La page d'accueil et les deux listes publiques répondent HTTP
|
||||
200. Les métriques sont fraîches, le serveur est déclaré en fonctionnement, et
|
||||
son temps cumulé continue d'augmenter. Le compteur reste à zéro jour tant que
|
||||
Beta n'a pas cumulé vingt-quatre heures ; les anciens décès ne sont plus affichés.
|
||||
|
||||
La configuration précédente est sauvegardée dans la CT Web, avec accès root
|
||||
seul, sous
|
||||
`/root/sanctuary-beta-maintenance/web-game-connection-20261025-1792901058/`.
|
||||
Le reçu `build/sanctuary-web-beta-alignment.log` et la capture
|
||||
`build/sanctuary-web-beta-after.png` restent hors de Git. Cette correction de
|
||||
configuration et de documentation ne change aucun binaire beta.280 ni aucune
|
||||
sauvegarde Minecraft.
|
||||
|
||||
+36
-39
@@ -1,5 +1,13 @@
|
||||
# Distribution packwiz et Prism
|
||||
|
||||
Le [téléchargement direct de Sanctuary Web](beta-download.md) donne une instance
|
||||
Prism déjà configurée. Le premier lancement télécharge les mods ; les suivants
|
||||
suivent les publications explicitement demandées via Codex.
|
||||
|
||||
La [procédure de reprise et de publication complète](updates.md) relie aussi
|
||||
le serveur dédié, le téléchargement du site et l'instance locale. Les reçus
|
||||
des anciennes versions ci-dessous restent historiques.
|
||||
|
||||
## beta.280 — JEI embarqué et profil serveur
|
||||
|
||||
JEI rejoint le JAR Sanctuary ; `mods/sanctuary.pw.toml` reste le seul artefact
|
||||
@@ -16,6 +24,19 @@ et les commandes de synchronisation/démarrage. [Mode d'emploi](server-pack.md),
|
||||
MariaDB et les secrets restent hors de l'index. La préparation locale ne publie
|
||||
pas le canal et ne met pas à jour une installation personnelle.
|
||||
|
||||
Livraison du 25 octobre : [release beta.280](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.280)
|
||||
publiée, canal `338e8a62387048c7e703ef036fda759ef57b4130`, puis nouveau serveur
|
||||
CT 350 sur `gpu` installé avec le kit vérifié. Le monde neuf, les paramètres
|
||||
et les services Web sont vérifiés dans le [reçu](jei-server-beta280.md).
|
||||
Une synchronisation du serveur conserve 61 fichiers suivis. L'Alpha et
|
||||
l'instance Prism personnelle restent hors de cette mise en service.
|
||||
|
||||
La règle Bbox permanente est ensuite vérifiée et le secours UPnP Beta retiré.
|
||||
Le domaine Minecraft `beta.sanctuary-minecraft.net` répond sans ce secours,
|
||||
avec 20 places. [MRpack client beta.280](https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.280/Sanctuary-beta.280.mrpack)
|
||||
vérifié et disponible ; le [reçu](jei-server-beta280.md) conserve les empreintes
|
||||
et la limite de validation réseau.
|
||||
|
||||
## beta.257 — Mods client séparés
|
||||
|
||||
JEI Sanctuary, Sound Physics Remastered et Sodium sont facultatifs et séparés
|
||||
@@ -2094,18 +2115,18 @@ La procédure complète, également utilisable manuellement :
|
||||
1. Incrémenter ensemble les versions de `gradle.properties` et `packwiz/pack.toml`
|
||||
selon la [convention `beta.xxx`](versioning.md).
|
||||
2. Exécuter `./gradlew check build assemblePack` et les vérifications du ticket.
|
||||
3. Préparer les métadonnées de la livraison, avec les deux URL exactes :
|
||||
3. Pour préparer les métadonnées manuellement depuis beta.280, utiliser la
|
||||
seule URL du JAR Sanctuary contenant JEI :
|
||||
|
||||
```sh
|
||||
python3 scripts/pack.py release \
|
||||
https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.257/sanctuary-beta.257.jar \
|
||||
https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.257/jei-sanctuary-26.3-30.32.0-sanctuary.4.jar
|
||||
https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.xxx/sanctuary-beta.xxx.jar
|
||||
```
|
||||
|
||||
4. Pousser le commit source vérifié. Créer une release Gitea correspondant à ce
|
||||
commit et joindre les JAR Sanctuary et JEI construits, sans remplacer les artefacts d'une release
|
||||
commit et joindre le JAR Sanctuary construit, sans remplacer les artefacts d'une release
|
||||
antérieure. Vérifier que le téléchargement public possède le SHA-256 indiqué
|
||||
dans `build/packwiz-release/mods/sanctuary.pw.toml` et `jei-sanctuary.pw.toml`.
|
||||
dans `build/packwiz-release/mods/sanctuary.pw.toml`, identique au profil serveur.
|
||||
5. Publier le contenu de `build/packwiz-release/` à la racine de la branche
|
||||
`packwiz` dans un seul commit, puis vérifier les fichiers et hashes depuis
|
||||
l'URL publique. Préserver l'historique de ce canal et ne jamais y copier le
|
||||
@@ -2127,45 +2148,21 @@ python3 scripts/pack.py prism https://git.botsu.net/koka/sanctuary-beta/raw/bran
|
||||
|
||||
Il contient les versions exactes Minecraft/Fabric, la commande de lancement et
|
||||
`packwiz-installer-bootstrap` v0.0.3. Le script vérifie le SHA-256 du bootstrap
|
||||
officiel avant d'écrire `build/Sanctuary-Prism-auto-update.zip`. Ce ZIP est réservé
|
||||
officiel avant d'écrire `build/Sanctuary-Prism-beta.xxx.zip`. Ce ZIP est réservé
|
||||
aux nouvelles installations ; les instances déjà branchées sur packwiz suivent
|
||||
le canal sans être réimportées.
|
||||
|
||||
## Raccorder l'instance Prism existante
|
||||
## Importer l'instance Prism prête
|
||||
|
||||
Fermer Minecraft avant de modifier ses mods. Fermer également Prism avant de
|
||||
modifier directement `instance.cfg`, pour éviter que son état en mémoire écrase
|
||||
les changements. Dans l'instance Sanctuary Beta :
|
||||
Le bouton **Télécharger** de [Sanctuary Web](https://sanctuary-minecraft.net)
|
||||
télécharge directement [Sanctuary-Beta.zip](https://sanctuary-minecraft.net/downloads/sanctuary-prism.zip).
|
||||
Dans Prism : **Ajouter une instance → Importer depuis un ZIP**, choisir ce fichier,
|
||||
puis lancer **Sanctuary Beta**. Packwiz télécharge automatiquement les mods.
|
||||
La commande avant lancement et l'outil sont déjà dans l'archive.
|
||||
|
||||
1. Sauvegarder `instance.cfg` et les anciens JAR Sanctuary dans un dossier de
|
||||
sauvegarde hors de `mods/`. Vérifier que l'instance utilise Minecraft
|
||||
`26.3-pre-2` et Fabric `0.19.5` ; ne pas convertir une instance 26.2.
|
||||
2. Mettre le bootstrap issu du ZIP dans son dossier `minecraft/` (ou `.minecraft/`
|
||||
selon l'installation).
|
||||
3. Dans **Modifier → Paramètres → Commandes personnalisées**, activer les
|
||||
commandes et renseigner la commande avant lancement ci-dessous.
|
||||
4. Pour une instance issue d'un ancien `.mrpack`, déplacer seulement l'ancien
|
||||
JAR Sanctuary non géré dans la sauvegarde préparée, puis faire la première
|
||||
synchronisation packwiz. Conserver les autres mods et données personnelles.
|
||||
Si la synchronisation échoue, remettre ce JAR et les réglages sauvegardés.
|
||||
5. Vérifier qu'il reste un seul mod Sanctuary actif et que `packwiz.json` est
|
||||
présent dans le dossier Minecraft. Les prochaines mises à jour sont suivies
|
||||
par ce manifeste local : ne pas le supprimer entre les versions.
|
||||
|
||||
Commande à coller dans l'interface Prism :
|
||||
|
||||
```text
|
||||
"$INST_JAVA" -jar "$INST_MC_DIR/packwiz-installer-bootstrap.jar" --bootstrap-main-jar "$INST_MC_DIR/packwiz-installer.jar" --pack-folder "$INST_MC_DIR" --multimc-folder "$INST_DIR" https://git.botsu.net/koka/sanctuary-beta/raw/branch/packwiz/pack.toml
|
||||
```
|
||||
|
||||
Une modification directe de `instance.cfg` doit échapper les guillemets au format
|
||||
Qt INI (`\"`) et activer `OverrideCommands=true`. Ces clés et
|
||||
`PreLaunchCommand` doivent être dans la section **`[General]`**, jamais ajoutées
|
||||
après la section `[UI]`. Le ZIP préparé le fait déjà. Rouvrir les commandes
|
||||
personnalisées dans Prism pour vérifier que la commande est effectivement lue.
|
||||
Une instance importée d'un `.mrpack` peut être marquée `ManagedPack=true` :
|
||||
dès que packwiz en assure les mises à jour, utiliser `ManagedPack=false` pour
|
||||
éviter deux mécanismes de gestion sur le même pack.
|
||||
Conserver cette même instance pour les versions suivantes : une publication
|
||||
explicitement demandée via Codex est suivie au lancement suivant. Une instance
|
||||
déjà branchée sur le canal stable n'a pas besoin d'être réimportée.
|
||||
|
||||
## Données locales et mondes
|
||||
|
||||
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
# UPDATE-OPS — développer et publier Sanctuary
|
||||
|
||||
Procédure de reprise demandée par Koka le **26 octobre 2026**, branche
|
||||
`codex/update-workflow`. Elle relie les sources du jeu, packwiz, le serveur Beta,
|
||||
Sanctuary Web et l'instance Prism locale. Les anciennes livraisons restent dans
|
||||
[packwiz.md](packwiz.md) ; le parcours de téléchargement actuel est décrit dans
|
||||
[beta-download.md](beta-download.md).
|
||||
|
||||
## Reprendre un chantier
|
||||
|
||||
Lire `AGENTS.md`, `README.md`, le ticket et l'état Git des dépôts concernés.
|
||||
Choisir la branche source qui contient les dernières livraisons voulues, puis
|
||||
travailler sur `codex/<sujet>`. Les branches des collègues peuvent avancer : ne
|
||||
pas prendre automatiquement `main`, le plus grand compteur ou un autre worktree
|
||||
comme base. Comparer les commits et les changements avant toute intégration.
|
||||
|
||||
| Élément | Référence à retrouver |
|
||||
| --- | --- |
|
||||
| Sources du jeu | `https://git.botsu.net/koka/sanctuary-beta.git` |
|
||||
| Sources du site | `https://git.botsu.net/chris/sanctuary-web.git` |
|
||||
| Canal client | `https://git.botsu.net/koka/sanctuary-beta/raw/branch/packwiz/pack.toml` |
|
||||
| Canal serveur | Même adresse, avec `packwiz/server/pack.toml` |
|
||||
| Site actif | `https://sanctuary-minecraft.net` — Sanctuary Web, pas un ancien site de release |
|
||||
| Entrée Minecraft | `beta.sanctuary-minecraft.net` — SRV vers le port public 25567 |
|
||||
| Téléchargement unique du site | `/downloads/sanctuary-prism.zip`, nom reçu `Sanctuary-Beta.zip` |
|
||||
|
||||
Développer/tester localement ne publie pas. Un simple `commit et push` pousse
|
||||
les sources ; il ne redémarre pas les services. Quand Koka demande une livraison
|
||||
complète ou « mets tout à jour », dérouler les cibles demandées jusqu'à leur
|
||||
vérification sans redemander les choix déjà établis. Cette note mémorise la
|
||||
procédure ; elle ne lance aucune publication périodique.
|
||||
|
||||
## Préparer une livraison du jeu
|
||||
|
||||
Pour chaque nouveau binaire Sanctuary, prendre le prochain numéro **libre**
|
||||
`beta.xxx` après examen des branches/releases. Aligner `mod_version` et
|
||||
`pack_version` dans `gradle.properties`, puis `version` dans `packwiz/pack.toml`.
|
||||
Une modification documentaire ou du site seule ne change pas ce compteur.
|
||||
|
||||
Utiliser **Java 25** et **Python 3.11+**. Sur le Mac actuel, `python3` peut encore
|
||||
être Python 3.9 : utiliser `python3.14` pour les outils opérationnels. Aucun
|
||||
changement de Minecraft/Fabric ou de format de sauvegarde n'est implicite.
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack assembleServerPack
|
||||
python3.14 scripts/pack.py prism https://git.botsu.net/koka/sanctuary-beta/raw/branch/packwiz/pack.toml
|
||||
```
|
||||
|
||||
Ajouter les vérifications utiles au ticket ; les essais graphiques du jeu
|
||||
ciblent **Vulkan uniquement**. Le profil ciblé employé pour beta.280 était
|
||||
`-PsanctuaryFocusedTests=recipes,progression -PsanctuaryAtlasOnly=true` : ce reçu
|
||||
historique ne remplace pas les tests d'un futur changement touchant un autre
|
||||
comportement. Consigner les limites, ne pas présenter un contrôle exclu comme
|
||||
réussi.
|
||||
|
||||
Les sorties attendues pour la version choisie sont :
|
||||
|
||||
- `mods/sanctuary/build/libs/sanctuary-beta.xxx.jar` : un JAR Sanctuary contenant JEI ;
|
||||
- `build/Sanctuary-beta.xxx.mrpack` : format conservé pour les liens existants ;
|
||||
- `build/Sanctuary-Prism-beta.xxx.zip` : instance prête pour le bouton du site ;
|
||||
- `build/Sanctuary-Server-beta.xxx.zip` : kit Linux ;
|
||||
- les métadonnées client/serveur de `build/packwiz-release/` après publication.
|
||||
|
||||
Vérifier les versions, SHA-256, index et contenus avant publication. Ne pas
|
||||
versionner les binaires, dépendances, mondes ni secrets. Le ZIP Prism contient
|
||||
seulement sa configuration, son icône et le bootstrap ; le premier lancement
|
||||
télécharge les mods. Ne pas lui ajouter des réglages ou sauvegardes personnels.
|
||||
|
||||
## Publier les artefacts et le canal
|
||||
|
||||
Committer et pousser la branche source vérifiée, puis exécuter depuis cette
|
||||
même branche propre, en remplaçant `beta.xxx` et le chemin des notes :
|
||||
|
||||
```sh
|
||||
python3.14 scripts/publish_pack.py \
|
||||
--notes-file docs/notes-du-ticket.md \
|
||||
--asset build/Sanctuary-beta.xxx.mrpack \
|
||||
--asset build/Sanctuary-Prism-beta.xxx.zip \
|
||||
--asset build/Sanctuary-Server-beta.xxx.zip
|
||||
```
|
||||
|
||||
Cette commande crée/vérifie la release, joint les artefacts, publie ensemble
|
||||
les profils client et serveur et vérifie leurs téléchargements publics.
|
||||
Elle utilise les identifiants du gestionnaire Git en mémoire. Le tag est
|
||||
exactement `beta.xxx`, sans `v`. Une release déjà publiée reste immuable : ne
|
||||
pas déplacer le tag ou remplacer une pièce jointe différente. Une reprise se
|
||||
fait depuis le commit source exact de la release, pas depuis un commit
|
||||
documentaire ultérieur portant le même compteur.
|
||||
|
||||
`publish_pack.py` avance déjà le canal : ne pas faire un deuxième push manuel
|
||||
du pack. Il ne met à jour ni le serveur, ni Prism, ni le téléchargement Web.
|
||||
Tester deux synchronisations avec le véritable installateur dans un dossier
|
||||
isolé ; vérifier l'absence de doublon Sanctuary et la stabilité du second
|
||||
passage. Conserver le reçu et les hashes dans `build/`, hors de Git.
|
||||
|
||||
## Mettre à jour le serveur Beta dédié
|
||||
|
||||
La cible est la **CT 350 sur le nœud `gpu`**, dossier
|
||||
`/srv/minecraft/sanctuary-beta`, service `sanctuary-beta.service`, utilisateur
|
||||
`minecraft`. La CT 300 Alpha et les autres serveurs sont hors cible.
|
||||
L'accès et les chemins propres au poste sont dans la compétence locale
|
||||
`sanctuary-updates`, référence `references/local-environment.md`.
|
||||
|
||||
Quand la livraison comprend le serveur, après les builds et avant d'avancer
|
||||
le canal, arrêter proprement la Beta. Attendre la fin de Java et de la
|
||||
sauvegarde, puis sauvegarder son dossier entier et son environnement privé
|
||||
dans un emplacement root privé hors du dossier géré par packwiz. Noter le
|
||||
commit du canal et les hashes précédents. Cela évite que des clients à jour
|
||||
rejoignent brièvement l'ancien serveur.
|
||||
|
||||
Après publication du canal, ces commandes s'exécutent **dans la CT 350** :
|
||||
|
||||
```sh
|
||||
systemctl stop sanctuary-beta.service
|
||||
runuser -u minecraft -- /srv/minecraft/sanctuary-beta/server.sh update
|
||||
systemctl start sanctuary-beta.service
|
||||
systemctl is-active sanctuary-beta.service
|
||||
journalctl -u sanctuary-beta.service -n 100 --no-pager
|
||||
```
|
||||
|
||||
La sauvegarde précède `update` ; le premier `stop` ci-dessus reste nécessaire
|
||||
si cette étape est exécutée séparément. Ne redémarrer qu'après une mise à jour
|
||||
réussie. L'outil refuse un changement implicite de Minecraft/Fabric et une
|
||||
mise à jour concurrente au lancement. Il ne charge pas automatiquement
|
||||
`/etc/sanctuary/beta.env` : reprendre seulement les variables non secrètes
|
||||
nécessaires si le Java ou le canal diffèrent de ceux du kit. Le service charge
|
||||
l'environnement privé au démarrage.
|
||||
|
||||
Conserver le monde, `packwiz.json`, `config/`, `server.properties`,
|
||||
`eula.txt`, les listes de joueurs, opérateurs, permissions et reçus d'admission.
|
||||
Garder l'UUID communautaire existant. Ne pas recréer une CT, réextraire un kit
|
||||
sur tout le serveur ou réinitialiser Gazette/Tableau/Intendance pour une mise
|
||||
à jour courante. [Contrat complet du serveur](server-pack.md).
|
||||
|
||||
Contrôler le JAR effectivement chargé, Fabric API, l'admission sans consommer
|
||||
un vrai code, les logs, le ping Minecraft et la fraîcheur des métriques Web.
|
||||
Un ping depuis le LAN via l'IP publique ne prouve pas un accès depuis un réseau
|
||||
extérieur. Ne pas remettre l'ancien secours UPnP : la règle Bbox permanente
|
||||
existe déjà.
|
||||
|
||||
## Actualiser le téléchargement et la version du site
|
||||
|
||||
Après vérification de la CT Beta, transférer le script versionné
|
||||
`scripts/publish_web_pack.py` sur **`gpu`** dans un dossier temporaire privé et
|
||||
vérifier que sa copie a le même SHA-256. Lancer sur `gpu`, où `pct` existe :
|
||||
|
||||
```sh
|
||||
python3 /chemin/temporaire/publish_web_pack.py \
|
||||
--web-ct 320 --server-ct 350 --version beta.xxx \
|
||||
--mrpack-url https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.xxx/Sanctuary-beta.xxx.mrpack \
|
||||
--prism-url https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.xxx/Sanctuary-Prism-beta.xxx.zip \
|
||||
--pack-url https://git.botsu.net/koka/sanctuary-beta/raw/branch/packwiz/pack.toml \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
Si le contrôle passe, reprendre la même commande sans `--dry-run`. Ce script
|
||||
vérifie le serveur en marche, les deux profils, les archives et leurs hashes,
|
||||
puis bascule atomiquement `storage/app/distribution/current` dans la CT Web.
|
||||
Il ne modifie pas le code du site et ne redémarre pas Minecraft.
|
||||
|
||||
Le footer et le survol du bouton lisent cette publication via
|
||||
`App\Services\PackDistribution`. Ne pas remplacer leurs versions en dur ni
|
||||
reconfigurer le bouton à chaque livraison. Le bouton **Télécharger** donne
|
||||
directement le ZIP prêt ; le joueur l'importe une fois dans Prism puis lance
|
||||
pour télécharger le pack. Aucun guide manuel ni téléchargement séparé ne
|
||||
remplace ce parcours. L'ancien `/downloads/sanctuary-beta.mrpack` reste valide.
|
||||
|
||||
Vérifier le ZIP reçu anonymement (HTTP 200, nom `Sanctuary-Beta.zip`, SHA-256),
|
||||
le footer et le survol, puis le lien MRpack historique. Une bascule de version
|
||||
ne nécessite pas de reconstruire le cache Laravel.
|
||||
|
||||
## Livrer une modification du site
|
||||
|
||||
Travailler dans les sources **Sanctuary Web** et lire leur `AGENTS.md` ainsi
|
||||
que les règles/compétences applicables. Tester les comportements modifiés,
|
||||
formater le PHP avec `vendor/bin/pint --dirty --format agent`, construire les
|
||||
assets concernés et vérifier le rendu navigateur. Les contrôles existants
|
||||
pour cette chaîne sont `tests/Feature/DownloadTest.php` et
|
||||
`tests/Feature/ServerMetricsTest.php`.
|
||||
|
||||
**La CT 320 est un checkout de production avec des changements préexistants.**
|
||||
Ne pas y faire `git pull`, `reset --hard`, `clean`, ou une copie avec suppression
|
||||
depuis `origin/main`. Inspecter les fichiers concernés et leurs hashes, préparer
|
||||
un patch limité conservant les autres différences, sauvegarder les fichiers
|
||||
et caches affectés dans `/root/sanctuary-beta-maintenance/`, puis appliquer
|
||||
avec refus si les fichiers ont changé depuis l'inspection. Conserver
|
||||
propriétaires/modes et écrire les assets avant les vues qui les référencent.
|
||||
|
||||
Ne vider/reconstruire que les caches concernés : `view:clear` pour Blade ;
|
||||
`config:cache` si la configuration change ; `route:cache` si des routes changent
|
||||
et étaient déjà mises en cache. Exécuter Artisan comme `www-data` depuis
|
||||
`/var/www/sanctuary`. Aucun changement de base, réinitialisation de données ou
|
||||
migration ne découle d'une simple livraison de présentation.
|
||||
|
||||
Contrôler le site public, les métriques et la distribution. Les comptes admins,
|
||||
groupes staff et permissions restent identiques ; comparer leurs empreintes si
|
||||
l'intervention touche leur contexte. Les scripts ponctuels dans `build/`
|
||||
restent des reçus historiques, à ne pas rejouer avec leurs anciennes hypothèses.
|
||||
La procédure complémentaire du dépôt Web est `docs/updates.md`.
|
||||
|
||||
## Synchroniser l'instance locale existante
|
||||
|
||||
Retrouver l'instance **Sanctuary Beta** et lire sa configuration. Son dossier
|
||||
historique peut encore s'appeler `Sanctuary-0.1.0-alpha.1` ; ce nom n'indique
|
||||
pas sa version courante. Ne pas créer une nouvelle instance à chaque livraison,
|
||||
ne pas réimporter le ZIP et ne pas toucher aux anciennes instances 26.2.
|
||||
|
||||
Si la demande inclut la mise à jour locale : attendre la fermeture du jeu,
|
||||
sauvegarder les fichiers gérés et les métadonnées de l'instance hors de `mods/`,
|
||||
relever les hashes des données personnelles, puis exécuter sa commande packwiz
|
||||
avant lancement avec ses vrais `INST_JAVA`, `INST_MC_DIR`, `INST_DIR`.
|
||||
Pour une synchronisation sans lancer Minecraft, ajouter `-g` à l'installateur.
|
||||
Ne jamais lancer `instance.cfg` comme du shell ni évaluer une chaîne de commande
|
||||
non vérifiée. Les chemins avec espaces doivent rester des arguments séparés.
|
||||
|
||||
Faire deux passages et comparer : un seul JAR Sanctuary attendu, mêmes fichiers
|
||||
gérés au second passage, mêmes données personnelles avant/après. Conserver
|
||||
`saves/`, captures, favoris, réglages, packs graphiques et configurations tierces.
|
||||
Ne pas ouvrir un monde personnel pour prouver une simple synchronisation.
|
||||
|
||||
## Reprise et reçu de livraison
|
||||
|
||||
Pour un échec, conserver l'état/les logs, revenir au dernier artefact immuable
|
||||
et à la sauvegarde compatible. Un retour du canal doit former un nouveau commit
|
||||
de métadonnées sans réécrire l'historique. Une reprise du téléchargement Web
|
||||
rebascule son lien `current` vers une publication complète conservée ; elle ne
|
||||
change pas à elle seule le serveur ou le canal. Ne pas démarrer un ancien JAR
|
||||
sur des données migrées sans vérifier le contrat de sauvegarde.
|
||||
|
||||
Consigner pour chaque livraison : commit source, version/tag, commit du canal,
|
||||
hashes et URL des artefacts, CT/service et version chargée, publication Web,
|
||||
version locale effectivement synchronisée, sauvegardes, vérifications et limites.
|
||||
Mettre à jour la référence d'environnement de la compétence lorsque les cibles
|
||||
ou chemins changent. La mémorisation de cette procédure ne prétend pas que
|
||||
toutes les installations sont déjà synchronisées.
|
||||
+10
-6
@@ -247,7 +247,7 @@ def prism(pack_url):
|
||||
# Prism instance.cfg uses Qt INI escaping for quotation marks.
|
||||
config = '\n'.join([
|
||||
'[General]', 'ConfigVersion=1.3', 'InstanceType=OneSix',
|
||||
'name=Sanctuary - ' + values['pack_version'], 'iconKey=sanctuary-beta', 'AutomaticJava=true',
|
||||
'name=Sanctuary Beta', 'iconKey=sanctuary-beta', 'AutomaticJava=true',
|
||||
'OverrideJavaLocation=false', 'IgnoreJavaCompatibility=false',
|
||||
'OverrideMemory=true', 'MinMemAlloc=512', 'MaxMemAlloc=4096',
|
||||
'ManagedPack=false', 'OverrideCommands=true', 'LogPrePostOutput=true',
|
||||
@@ -261,13 +261,17 @@ def prism(pack_url):
|
||||
{'uid': 'net.fabricmc.fabric-loader', 'version': values['loader_version']},
|
||||
],
|
||||
}
|
||||
destination = ROOT / 'build/Sanctuary-Prism-auto-update.zip'
|
||||
destination = ROOT / 'build' / ('Sanctuary-Prism-' + values['pack_version'] + '.zip')
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(destination, 'w', zipfile.ZIP_DEFLATED) as archive:
|
||||
archive.writestr('instance.cfg', config)
|
||||
archive.write(SOURCE / 'icon.png', 'sanctuary-beta.png')
|
||||
archive.writestr('mmc-pack.json', json.dumps(components, indent=2) + '\n')
|
||||
archive.writestr('minecraft/packwiz-installer-bootstrap.jar', bootstrap)
|
||||
files = {'instance.cfg': config.encode(), 'sanctuary-beta.png': (SOURCE / 'icon.png').read_bytes(),
|
||||
'mmc-pack.json': (json.dumps(components, indent=2) + '\n').encode(),
|
||||
'minecraft/packwiz-installer-bootstrap.jar': bootstrap}
|
||||
for name, data in files.items():
|
||||
entry = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0))
|
||||
entry.compress_type = zipfile.ZIP_DEFLATED
|
||||
entry.external_attr = 0o100644 << 16
|
||||
archive.writestr(entry, data)
|
||||
print('Prism auto-update template: ' + str(destination))
|
||||
print('Pre-launch command: ' + command)
|
||||
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Publish verified Prism/MRpack archives matching a CT, when Codex invokes it.
|
||||
|
||||
Run on the Proxmox node hosting both CTs. This does not restart Minecraft,
|
||||
advance packwiz, change a world, or copy server configuration into the client.
|
||||
"""
|
||||
import argparse
|
||||
import base64
|
||||
import configparser
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
from pathlib import PurePosixPath
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tomllib
|
||||
from urllib.parse import urljoin, urlsplit
|
||||
from urllib.request import urlopen
|
||||
import zipfile
|
||||
|
||||
BOOTSTRAP_SHA256 = 'a8fbb24dc604278e97f4688e82d3d91a318b98efc08d5dbfcbcbcab6443d116c'
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise SystemExit(message)
|
||||
|
||||
|
||||
def digest(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def fetch(url):
|
||||
require(urlsplit(url).scheme == 'https', 'Distribution URLs must use HTTPS')
|
||||
with urlopen(url, timeout=60) as response:
|
||||
data = response.read(64 * 1024 * 1024 + 1)
|
||||
require(len(data) <= 64 * 1024 * 1024, 'Distribution file exceeds 64 MiB')
|
||||
return data
|
||||
|
||||
|
||||
def channel(url):
|
||||
manifest = tomllib.loads(fetch(url).decode())
|
||||
index_data = fetch(urljoin(url, manifest['index']['file']))
|
||||
require(manifest['index']['hash-format'] == 'sha256'
|
||||
and digest(index_data) == manifest['index']['hash'], 'Pack index hash mismatch')
|
||||
index = tomllib.loads(index_data.decode())
|
||||
require(index['hash-format'] == 'sha256', 'Unsupported index hashes')
|
||||
mods = {}
|
||||
icon_hash = None
|
||||
for entry in index['files']:
|
||||
path = PurePosixPath(entry['file'])
|
||||
require(not path.is_absolute() and '..' not in path.parts, 'Unsafe index path')
|
||||
data = fetch(urljoin(url, str(path)))
|
||||
require(digest(data) == entry['hash'], 'Indexed file hash mismatch: ' + str(path))
|
||||
if str(path) == 'icon.png':
|
||||
icon_hash = digest(data)
|
||||
if entry.get('metafile'):
|
||||
mod = tomllib.loads(data.decode())
|
||||
require(mod['filename'] not in mods, 'Duplicate mod filename')
|
||||
mods[mod['filename']] = mod
|
||||
return {'manifest': manifest, 'mods': mods, 'icon_sha256': icon_hash}
|
||||
|
||||
|
||||
def verify_prism(data, client, pack_url):
|
||||
with zipfile.ZipFile(io.BytesIO(data)) as archive:
|
||||
require(archive.testzip() is None, 'Corrupt Prism archive')
|
||||
require(set(archive.namelist()) == {'instance.cfg', 'mmc-pack.json', 'sanctuary-beta.png',
|
||||
'minecraft/packwiz-installer-bootstrap.jar'}, 'Unexpected Prism archive contents')
|
||||
require(digest(archive.read('minecraft/packwiz-installer-bootstrap.jar')) == BOOTSTRAP_SHA256,
|
||||
'Prism bootstrap hash mismatch')
|
||||
require(digest(archive.read('sanctuary-beta.png')) == client['icon_sha256'], 'Prism icon drift')
|
||||
config = configparser.ConfigParser(interpolation=None)
|
||||
config.read_string(archive.read('instance.cfg').decode())
|
||||
general = config['General']
|
||||
require(general['ManagedPack'] == 'false' and general['OverrideCommands'] == 'true'
|
||||
and general['InstanceType'] == 'OneSix' and general['name'] == 'Sanctuary Beta',
|
||||
'Prism instance is not configured for automatic updates')
|
||||
command = ('"$INST_JAVA" -jar "$INST_MC_DIR/packwiz-installer-bootstrap.jar" '
|
||||
'--bootstrap-main-jar "$INST_MC_DIR/packwiz-installer.jar" '
|
||||
'--pack-folder "$INST_MC_DIR" --multimc-folder "$INST_DIR" ' + pack_url)
|
||||
require(general['PreLaunchCommand'].replace('\\"', '"') == command, 'Prism pre-launch command drift')
|
||||
components = json.loads(archive.read('mmc-pack.json'))['components']
|
||||
require({c['uid']: c['version'] for c in components} ==
|
||||
{'net.minecraft': client['manifest']['versions']['minecraft'],
|
||||
'net.fabricmc.fabric-loader': client['manifest']['versions']['fabric']}
|
||||
and len(components) == 2, 'Prism platform drift')
|
||||
return {'prism_size': len(data), 'prism_sha256': digest(data)}
|
||||
|
||||
|
||||
def verify(mrpack, client, server, installed, version):
|
||||
require(re.fullmatch(r'beta\.(?!000$)[0-9]{3}', version), 'Invalid Beta version')
|
||||
require(installed['running'], 'Dedicated Beta server must be running')
|
||||
for profile in (client, server):
|
||||
require(profile['manifest']['version'] == version, 'Channel version differs from dedicated CT')
|
||||
require(client['manifest']['versions'] == server['manifest']['versions'], 'Client/server platform drift')
|
||||
filename = 'sanctuary-' + version + '.jar'
|
||||
require(set(server['mods']) == {filename, installed['fabric_api_filename']}, 'Unexpected server mods')
|
||||
require(client['mods'][filename] == server['mods'][filename], 'Client/server Sanctuary artifact drift')
|
||||
download = server['mods'][filename]['download']
|
||||
require(download['hash-format'] == 'sha256' and download['hash'] == installed['sanctuary_sha256'],
|
||||
'Sanctuary channel does not match the dedicated CT')
|
||||
api = server['mods'][installed['fabric_api_filename']]['download']
|
||||
require(installed['fabric_api_hashes'].get(api['hash-format']) == api['hash'],
|
||||
'Fabric API channel does not match the dedicated CT')
|
||||
with zipfile.ZipFile(io.BytesIO(mrpack)) as archive:
|
||||
require(archive.testzip() is None, 'Corrupt MRpack')
|
||||
index = json.loads(archive.read('modrinth.index.json'))
|
||||
require(index['game'] == 'minecraft' and index['formatVersion'] == 1, 'Unsupported MRpack')
|
||||
require(index['versionId'] == version, 'MRpack version differs from dedicated CT')
|
||||
require(index['dependencies'] == {'minecraft': client['manifest']['versions']['minecraft'],
|
||||
'fabric-loader': client['manifest']['versions']['fabric']}, 'MRpack platform drift')
|
||||
jar = archive.read('overrides/mods/' + filename)
|
||||
require(digest(jar) == installed['sanctuary_sha256'], 'MRpack Sanctuary differs from dedicated CT')
|
||||
require({name for name in archive.namelist() if not name.endswith('/')} ==
|
||||
{'modrinth.index.json', 'overrides/icon.png', 'overrides/mods/' + filename},
|
||||
'Unexpected MRpack overrides; review client contents before publication')
|
||||
files = {entry['path']: entry for entry in index['files']}
|
||||
require(len(files) == len(index['files']), 'Duplicate MRpack entry')
|
||||
require(set(files) == {'mods/' + name for name in client['mods'] if name != filename}, 'MRpack mod list drift')
|
||||
for name, mod in client['mods'].items():
|
||||
if name == filename:
|
||||
continue
|
||||
entry = files['mods/' + name]
|
||||
require(entry['hashes'].get(mod['download']['hash-format']) == mod['download']['hash'], 'MRpack dependency hash drift')
|
||||
require(entry['downloads'] == [mod['download']['url']], 'MRpack dependency URL drift')
|
||||
return {'version': version, 'minecraft': index['dependencies']['minecraft'],
|
||||
'fabric': index['dependencies']['fabric-loader'], 'mrpack_sha256': digest(mrpack),
|
||||
'mrpack_size': len(mrpack), 'sanctuary_sha256': installed['sanctuary_sha256']}
|
||||
|
||||
|
||||
INSTALL = r'''
|
||||
import base64,fcntl,json,os,sys
|
||||
from pathlib import Path
|
||||
p=json.load(sys.stdin);root=Path(p['root'])/'storage/app/distribution';root.mkdir(parents=True,exist_ok=True)
|
||||
lock=(root/'.publish.lock').open('a');fcntl.flock(lock,fcntl.LOCK_EX)
|
||||
release=root/'releases'/p['metadata']['version']/'prism';release.mkdir(parents=True,exist_ok=True)
|
||||
files={'Sanctuary-Beta.mrpack':base64.b64decode(p['mrpack']),
|
||||
'Sanctuary-Beta.zip':base64.b64decode(p['prism']),
|
||||
'distribution.json':(json.dumps(p['metadata'],indent=2)+'\n').encode()}
|
||||
for name,data in files.items():
|
||||
f=release/name
|
||||
if f.exists() and f.read_bytes()!=data:raise SystemExit('Immutable Web release conflict: '+name)
|
||||
for name,data in files.items():
|
||||
f=release/name
|
||||
if not f.exists():
|
||||
tmp=f.with_suffix(f.suffix+'.new');tmp.write_bytes(data);tmp.chmod(0o644);os.replace(tmp,f)
|
||||
current=root/'current'
|
||||
if current.exists() and not current.is_symlink():raise SystemExit('Existing distribution/current is not a symlink')
|
||||
previous=os.readlink(current) if current.is_symlink() else None
|
||||
tmp=root/('current-'+str(os.getpid()));tmp.symlink_to('releases/'+p['metadata']['version']+'/prism');os.replace(tmp,current)
|
||||
print(json.dumps({'published':p['metadata'],'previous':previous,'current':str(current)}))
|
||||
'''
|
||||
|
||||
|
||||
def ct(number, *args, input=None):
|
||||
result = subprocess.run(['pct', 'exec', str(number), '--', *args], input=input,
|
||||
text=True, capture_output=True, timeout=120)
|
||||
require(result.returncode == 0, 'CT operation failed: ' + result.stderr[:300])
|
||||
return result.stdout
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--web-ct', type=int, required=True)
|
||||
parser.add_argument('--server-ct', type=int, required=True)
|
||||
parser.add_argument('--version', required=True)
|
||||
parser.add_argument('--mrpack-url', required=True)
|
||||
parser.add_argument('--prism-url', required=True)
|
||||
parser.add_argument('--pack-url', required=True)
|
||||
parser.add_argument('--web-root', default='/var/www/sanctuary')
|
||||
parser.add_argument('--server-root', default='/srv/minecraft/sanctuary-beta')
|
||||
parser.add_argument('--dry-run', action='store_true')
|
||||
args = parser.parse_args()
|
||||
require(args.web_ct != args.server_ct, 'Web and Minecraft CT must be distinct')
|
||||
inventory = r'''
|
||||
import hashlib,json,subprocess,sys
|
||||
from pathlib import Path
|
||||
p=Path(sys.argv[1]);jars=list((p/'mods').glob('sanctuary-*.jar'));apis=list((p/'mods').glob('fabric-api-*.jar'))
|
||||
assert len(jars)==len(apis)==1,'Ambiguous installed mods'
|
||||
print(json.dumps({'running':subprocess.run(['systemctl','is-active','--quiet','sanctuary-beta.service']).returncode==0,
|
||||
'sanctuary_sha256':hashlib.sha256(jars[0].read_bytes()).hexdigest(),
|
||||
'fabric_api_filename':apis[0].name,'fabric_api_hashes':{a:hashlib.new(a,apis[0].read_bytes()).hexdigest() for a in ('sha256','sha512')}}))
|
||||
'''
|
||||
installed = json.loads(ct(args.server_ct, 'python3', '-c', inventory, args.server_root))
|
||||
client = channel(args.pack_url)
|
||||
server = channel(urljoin(args.pack_url, 'server/pack.toml'))
|
||||
mrpack = fetch(args.mrpack_url)
|
||||
metadata = verify(mrpack, client, server, installed, args.version)
|
||||
prism = fetch(args.prism_url)
|
||||
metadata.update(verify_prism(prism, client, args.pack_url))
|
||||
metadata.update({'packwiz_url': args.pack_url, 'source_url': args.mrpack_url,
|
||||
'prism_source_url': args.prism_url})
|
||||
if args.dry_run:
|
||||
print(json.dumps({'verified': metadata, 'web_ct': args.web_ct, 'server_ct': args.server_ct}, indent=2))
|
||||
else:
|
||||
payload = json.dumps({'root': args.web_root, 'metadata': metadata,
|
||||
'mrpack': base64.b64encode(mrpack).decode(),
|
||||
'prism': base64.b64encode(prism).decode()})
|
||||
print(ct(args.web_ct, 'python3', '-c', INSTALL, input=payload))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,119 @@
|
||||
"""Check CT/channel alignment and atomic publication without a live PVE."""
|
||||
import base64
|
||||
import copy
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
import zipfile
|
||||
from unittest.mock import patch
|
||||
|
||||
import pack
|
||||
import publish_web_pack as web
|
||||
|
||||
|
||||
class WebDistributionTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.jar = b'sanctuary-runtime'
|
||||
self.version = 'beta.280'
|
||||
self.filename = 'sanctuary-beta.280.jar'
|
||||
self.sanctuary = {'filename': self.filename, 'download': {'hash-format': 'sha256', 'hash': web.digest(self.jar)}}
|
||||
self.api = {'filename': 'fabric-api.jar', 'download': {'url': 'https://example.test/api.jar', 'hash-format': 'sha512', 'hash': hashlib.sha512(b'api').hexdigest()}}
|
||||
self.profile = {'manifest': {'version': self.version, 'versions': {'minecraft': '26.3', 'fabric': '0.19.5'}},
|
||||
'mods': {self.filename: self.sanctuary, 'fabric-api.jar': self.api}}
|
||||
self.installed = {'running': True, 'sanctuary_sha256': web.digest(self.jar), 'fabric_api_filename': 'fabric-api.jar',
|
||||
'fabric_api_hashes': {'sha512': hashlib.sha512(b'api').hexdigest()}}
|
||||
|
||||
def mrpack(self, jar=None, version='beta.280', extra=None):
|
||||
index = {'formatVersion': 1, 'game': 'minecraft', 'versionId': version,
|
||||
'dependencies': {'minecraft': '26.3', 'fabric-loader': '0.19.5'},
|
||||
'files': [{'path': 'mods/fabric-api.jar', 'hashes': {'sha512': self.api['download']['hash']}, 'downloads': [self.api['download']['url']]}]}
|
||||
out = io.BytesIO()
|
||||
with zipfile.ZipFile(out, 'w') as z:
|
||||
z.writestr('modrinth.index.json', json.dumps(index))
|
||||
z.writestr('overrides/icon.png', b'icon')
|
||||
z.writestr('overrides/mods/' + self.filename, self.jar if jar is None else jar)
|
||||
if extra:
|
||||
z.writestr(extra, 'private-server-data')
|
||||
return out.getvalue()
|
||||
|
||||
def test_matching_runtime_produces_verified_download_metadata(self):
|
||||
data = self.mrpack()
|
||||
result = web.verify(data, self.profile, self.profile, self.installed, self.version)
|
||||
self.assertEqual(result['version'], 'beta.280')
|
||||
self.assertEqual(result['sanctuary_sha256'], web.digest(b'sanctuary-runtime'))
|
||||
self.assertEqual(result['mrpack_sha256'], web.digest(data))
|
||||
self.assertEqual(result['mrpack_size'], len(data))
|
||||
|
||||
def test_stopped_or_different_ct_is_rejected(self):
|
||||
for change in ({'running': False}, {'sanctuary_sha256': '0' * 64}, {'fabric_api_hashes': {'sha512': '0' * 128}}):
|
||||
with self.subTest(change=change), self.assertRaises(SystemExit):
|
||||
web.verify(self.mrpack(), self.profile, self.profile, self.installed | change, self.version)
|
||||
|
||||
def test_different_channel_or_mrpack_is_rejected(self):
|
||||
changed = copy.deepcopy(self.profile)
|
||||
changed['manifest']['version'] = 'beta.281'
|
||||
with self.assertRaisesRegex(SystemExit, 'Channel version'):
|
||||
web.verify(self.mrpack(), changed, self.profile, self.installed, self.version)
|
||||
for data in (self.mrpack(jar=b'another-build'), self.mrpack(version='beta.281')):
|
||||
with self.subTest(), self.assertRaises(SystemExit):
|
||||
web.verify(data, self.profile, self.profile, self.installed, self.version)
|
||||
|
||||
def test_server_files_cannot_enter_the_client_download(self):
|
||||
with self.assertRaisesRegex(SystemExit, 'Unexpected MRpack overrides'):
|
||||
web.verify(self.mrpack(extra='overrides/server.properties'), self.profile, self.profile, self.installed, self.version)
|
||||
|
||||
def prism(self, override='true', url='https://pack.example/pack.toml', extra=None):
|
||||
command = ('"$INST_JAVA" -jar "$INST_MC_DIR/packwiz-installer-bootstrap.jar" '
|
||||
'--bootstrap-main-jar "$INST_MC_DIR/packwiz-installer.jar" '
|
||||
'--pack-folder "$INST_MC_DIR" --multimc-folder "$INST_DIR" ' + url)
|
||||
config = '[General]\nname=Sanctuary Beta\nInstanceType=OneSix\nManagedPack=false\nOverrideCommands='+override+'\nPreLaunchCommand='+command.replace('"', '\\"')+'\n'
|
||||
out = io.BytesIO()
|
||||
with zipfile.ZipFile(out, 'w') as z:
|
||||
z.writestr('instance.cfg', config)
|
||||
z.writestr('sanctuary-beta.png', b'icon')
|
||||
z.writestr('minecraft/packwiz-installer-bootstrap.jar', b'bootstrap')
|
||||
z.writestr('mmc-pack.json', json.dumps({'components': [{'uid':'net.minecraft','version':'26.3'}, {'uid':'net.fabricmc.fabric-loader','version':'0.19.5'}]}))
|
||||
if extra:
|
||||
z.writestr(extra, 'unwanted')
|
||||
return out.getvalue()
|
||||
|
||||
def test_prism_is_ready_to_import_with_automatic_updates(self):
|
||||
with patch.object(web, 'BOOTSTRAP_SHA256', web.digest(b'bootstrap')):
|
||||
result = web.verify_prism(self.prism(), self.profile | {'icon_sha256': web.digest(b'icon')}, 'https://pack.example/pack.toml')
|
||||
self.assertEqual(result['prism_sha256'], web.digest(self.prism()))
|
||||
|
||||
def test_prism_without_prelaunch_or_with_another_channel_is_rejected(self):
|
||||
with patch.object(web, 'BOOTSTRAP_SHA256', web.digest(b'bootstrap')):
|
||||
for data in (self.prism(override='false'), self.prism(url='https://other.example/pack.toml')):
|
||||
with self.subTest(), self.assertRaises(SystemExit):
|
||||
web.verify_prism(data, self.profile | {'icon_sha256': web.digest(b'icon')}, 'https://pack.example/pack.toml')
|
||||
|
||||
def test_prism_cannot_include_worlds_or_unverified_tools(self):
|
||||
with self.assertRaisesRegex(SystemExit, 'bootstrap hash mismatch'):
|
||||
web.verify_prism(self.prism(), self.profile, 'https://pack.example/pack.toml')
|
||||
with self.assertRaisesRegex(SystemExit, 'Unexpected Prism archive contents'):
|
||||
web.verify_prism(self.prism(extra='minecraft/saves/world.dat'), self.profile, 'https://pack.example/pack.toml')
|
||||
|
||||
def test_atomic_publication_preserves_old_release_and_rejects_mutation(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
def publish(version, data):
|
||||
payload = {'root': directory, 'metadata': {'version': version}, 'mrpack': base64.b64encode(data).decode(), 'prism': base64.b64encode(b'prism-ready').decode()}
|
||||
return subprocess.run([sys.executable, '-c', web.INSTALL], input=json.dumps(payload), text=True, capture_output=True)
|
||||
self.assertEqual(publish('beta.280', b'first').returncode, 0)
|
||||
self.assertEqual(publish('beta.281', b'second').returncode, 0)
|
||||
current = root / 'storage/app/distribution/current/Sanctuary-Beta.mrpack'
|
||||
self.assertEqual(current.read_bytes(), b'second')
|
||||
self.assertEqual((root / 'storage/app/distribution/releases/beta.280/prism/Sanctuary-Beta.mrpack').read_bytes(), b'first')
|
||||
self.assertNotEqual(publish('beta.281', b'conflicting').returncode, 0)
|
||||
self.assertEqual(current.read_bytes(), b'second')
|
||||
self.assertEqual(publish('beta.281', b'second').returncode, 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user