rescriptum · an answer written for this machine
FR
Sur cette page

Construire

./build.sh                    # cette machine, et affiche la taille
./build.sh --all              # toutes les cibles qu'une release livre
./build.sh --no-sqlite        # le plus petit binaire
./build.sh armv7-unknown-linux-gnueabihf
./build.sh --help

build.sh ajoute une cible Rust manquante pour vous et avertit si un build musl est sorti lié dynamiquement — ce que DSM refuserait de lancer, au moment de l’exec sur le NAS plutôt qu’au build sur votre portable.

cargo build tout court fonctionne aussi ; build.sh existe pour le rapport de taille et cet avertissement.

Les cibles de release

CiblePourCroisé
armv7-unknown-linux-gnueabihfle DS416j, la raison d’être du projet — glibc, pas musl, voir plus baszigbuild, plancher 2.17
aarch64-unknown-linux-muslNAS ARM récents, Raspberry Pizigbuild
x86_64-unknown-linux-muslla plupart des autres hôtes Linuxzigbuild
aarch64-apple-darwindéveloppement localnatif
x86_64-apple-darwindéveloppement localnatif

Compilation croisée

cargo-zigbuild utilise Zig comme éditeur de liens, ce qui évite une toolchain croisée complète par cible :

cargo install cargo-zigbuild
cargo zigbuild --release --target armv7-unknown-linux-gnueabihf.2.17

Pourquoi armv7 est la seule cible qui ne soit pas musl

Toutes les autres cibles sont en musl statique. ARMv7 est en glibc, et ce n’est pas une préférence : c’est la seule façon dont la machine qui justifie ce projet exécute le binaire.

Les noyaux ARMv7 de Synology sont des 3.10, et ils répondent EINVAL aux appels système time64 là où on attendrait ENOSYS. musl 1.2 a fait passer time_t à 64 bits sur les architectures 32 bits et tente d’abord clock_gettime64 (ainsi que clock_nanosleep et le futex temporisé), avec un repli sur l’appel 32 bits conditionné à ENOSYS. Sur un noyau qui dit EINVAL, le repli n’arrive jamais et toute demande d’heure échoue. Mesuré sur un DS416j en DSM 7.1, noyau 3.10.108 :

$ ./probe
libc clock_gettime(CLOCK_REALTIME)  -> -1  errno=22 (Invalid argument)
syscall 263 (time32)                -> 0   ok
syscall 403 (time64)                -> -1  errno=22 (Invalid argument)

Le symptôme : un binaire qui répond à --version puis panique dès qu’il veut un horodatage — time.rs:131, Os { code: 22, kind: InvalidInput }. Ce n’est ni un problème d’ABI ni un noyau trop vieux pour les instructions, ce à quoi ça ressemble pourtant.

La glibc, en 32 bits, utilise les appels time32, et DSM fournit la sienne (2.20 sur armada38x). Le build armv7 vise donc un plancher glibc 2.17 — assez bas pour DSM, et comme la glibc est rétrocompatible, le même binaire tourne aussi sur un Linux ARMv7 récent.

Ce qu’il faut vérifier n’est donc plus qu’il est statique, mais qu’il ne réclame aucune glibc plus récente que le plancher. Plus récent échoue au moment de l’exec, sur le NAS, en nommant une version de symbole et rien d’autre :

$ readelf --dyn-syms target/armv7-unknown-linux-gnueabihf/release/rescriptum \
    | grep -o 'GLIBC_[0-9.]*' | sort -uV | tail -1
GLIBC_2.17

La CI l’affirme à chaque push. Les cibles musl, elles, restent vérifiées comme statiques, parce que pour elles c’est la promesse.

Installer Zig sur la machine du mainteneur

Zig n’est pas une installation Homebrew ici : brew install avorte sur cette machine à cause de taps tiers non fiables sans rapport avec Zig. Il vit dans ~/.local/zig, avec un lien symbolique dans ~/.local/bin/zig. Pour le mettre à jour, remplacez ce répertoirebrew upgrade zig ne fait rien.

Toolchain vérifiée : Rust 1.93, cargo-zigbuild 0.23.0, Zig 0.16.0, avec les cibles aarch64-apple-darwin et armv7-unknown-linux-gnueabihf installées.

Le profil release

[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
strip = true

panic = "abort" est délibérément absent — voir contraintes. Coût mesuré du maintien du déroulement sur ARMv7 : +2416 octets, +0,8 %.

Taille

BuildARMv7
par défaut2 103 456 octets
--no-default-features (ni SQLite, ni API d’administration)944 928 octets

L’essentiel de la différence est SQLite embarqué, compilé depuis les sources. La CI construit --release --no-default-features à chaque push pour que le petit build ne pourrisse pas sans qu’on le remarque.

Features

FeatureDéfautApporte
sqliteactivéele store SQLite et l’API d’administration
cargo build --no-default-features          # le plus petit
cargo test --all-features                  # ce que lance la CI

Le paquet Synology

Un .spk est un format de release, pas un build : le binaire est fini avant que l’empaquetage commence, il n’y a pas de build spécifique à DSM, et rien dans src/ ne sait que Synology existe.

./build.sh --spk x86_64-unknown-linux-musl   # compiler, puis emballer
packaging/dsm/make-spk.sh armv7              # emballer un build qui existe déjà
packaging/dsm/check-spk.sh                   # contrôle structurel sur dist/*.spk

Le paquet embarque les chargeurs : construisez-les d’abord, sinon il ne passe pas son propre contrôle. make-spk.sh les prend dans packaging/ipxe/out (remplaçable par RESCRIPTUM_LOADERS), et check-spk.sh refuse un paquet qui n’en a pas — un serveur TFTP sans rien à distribuer ne démarre personne. Construire iPXE demande une chaîne C Linux, ce qui sur un Mac veut dire un conteneur :

docker run --rm --platform linux/amd64 -v "$PWD:/w" -w /w debian:bookworm-slim sh -c '
  apt-get update -qq &&
  apt-get install -y --no-install-recommends build-essential liblzma-dev mtools \
    xorriso isolinux gcc-aarch64-linux-gnu git ca-certificates perl &&
  packaging/ipxe/build.sh --out /w/packaging/ipxe/out'

Une fois, pas par paquet : les chargeurs sont les mêmes octets dans le .spk de chaque ABI, puisqu’ils tournent sur les machines démarrées, pas sur le NAS. packaging/ipxe/out est gitignoré — jamais de binaires dans git.

ABIarch dans INFODepuis
x86_64x86_64 — le nom de famille, donc toutes les plateformes Intelx86_64-unknown-linux-musl
armv7armada38x — le raccourci de famille n’atteint pas les plateformes Marvellarmv7-unknown-linux-gnueabihf
aarch64armv8aarch64-unknown-linux-musl, une fois le binaire lancé sur l’une d’elles

La règle pour élargir : revendiquer un ABI une fois le binaire lancé sur son membre au noyau le plus ancien, jamais parce qu’une plateforme est plausible.

make-spk.sh est déterministe — mtimes fixes, propriété 0:0, ustar, gzip -n, liste de fichiers pré-triée — donc les mêmes entrées donnent un .spk identique octet pour octet, ce qui est ce qui donne du sens à la somme publiée.

check-spk.sh tourne dans la CI à chaque push. Il vérifie que l’archive externe est un tar non compressé, que INFO a ses six champs obligatoires et une version tout en segments numériques, que les icônes font exactement 64×64 et 256×256, que les scripts de cycle de vie s’analysent et sont exécutables, et que le --version du binaire empaqueté correspond à INFO — le build x86_64 tourne sur le runner, donc cette dernière assertion est réelle et non une relecture de la même chaîne.

lifecycle-test.sh déroule ensuite les scripts du paquet contre un faux arbre /var/packages — installation, démarrage, /health, les codes de sortie, une mise à jour par-dessus une configuration éditée à la main, une désinstallation par-dessus un canary dans le partage — et tourne lui aussi à chaque push.

packaging/dsm/lifecycle-test.sh

Ce que rien de tout cela ne peut prouver, c’est que DSM acceptera le paquet ; seule une installation le peut. C’est le banc d’essai de packaging/dsm/vm/ : un lanceur QEMU, et un script qui joue les vérifications sur la machine — la VM pendant qu’on itère, le DS416j pour le verdict. Voir tests.

Déployer un build

./deploy.sh admin@nas
./deploy.sh admin@nas /volume1/netboot

Construit, vérifie les réponses et refuse d’expédier si elles ne reviennent pas propres, copie sous un nom temporaire, redémarre, et confirme /health. Voir déploiement.

EnvironnementDéfaut
TARGETarmv7-unknown-linux-gnueabihf
ANSWERS<répertoire-distant>/answers
PORT8000
Mis à jour le Modifier cette page