Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a4fc2427e | ||
|
|
c219d237ab | ||
|
|
8b6c6c6b21 | ||
|
|
a6b547c4c6 | ||
|
|
235cf51d6e | ||
|
|
17c1cd9e71 | ||
|
|
276c8f2144 |
@@ -1,5 +1,9 @@
|
||||
# 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.
|
||||
|
||||
## 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 +11,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.
|
||||
|
||||
+27
-34
@@ -1,5 +1,9 @@
|
||||
# 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.
|
||||
|
||||
## beta.280 — JEI embarqué et profil serveur
|
||||
|
||||
JEI rejoint le JAR Sanctuary ; `mods/sanctuary.pw.toml` reste le seul artefact
|
||||
@@ -16,6 +20,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
|
||||
@@ -2127,45 +2144,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
|
||||
|
||||
|
||||
+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