2.1: pacman as real update mechanism (client side)

Decision: pacman repo lives on GitHub Releases at
archr-linux/archr-repo. No VPS, no repo.arch-r.io. Google Drive stays
out of runtime (backup/archive only). Cloudflare R2 fallback deferred.
Detail in docs/release-policy.md.

archr-update rewritten end-to-end:
- 188 lines of dead POST-to-update.arch-r.io code replaced by an
  87-line wrapper around pacman -Syu. The POST endpoint never existed
  so there is no legacy compat to preserve.
- subcommands: check, update, info. system.cfg "updates.branch" maps
  to repo-stable / repo-next / repo-dev tags on archr-linux/archr-repo;
  the mirrorlist is rewritten on every invocation so channel switches
  are immediate.

pacman.conf rebuilt around a single [archr] repo (Arch Linux ARM
upstream removed). SigLevel starts at "Required DatabaseOptional"
during bootstrap; will tighten to "Required" once the production
master key signs the .db.

pacman-init reachability test points at github.com and the keyring
populate target is "archr" instead of "archlinuxarm".

archr-keyring repurposed: the three archlinuxarm keyring files are
removed; package.mk now expects archr.gpg / archr-trusted /
archr-revoked under keys/ and ships empty placeholders until the real
ArchR master key is generated.

scripts/repo/gen-pacman-repo + README: take the build.*-RK3326 tree
that "make docker-RK3326" leaves behind, package each install_pkg/<x>
as a .pkg.tar.zst with synthesized .PKGINFO, run repo-add to build
archr.db, optionally GPG-sign everything. Emits a `gh release create`
command for the publish step. CI hook left as a follow-up.

docs/improvements.md 2.1 marked partial: client and build script are
ready; what blocks the first usable release is generating the GPG
master key, creating archr-linux/archr-repo on GitHub, and one manual
publish for the repo-dev tag to validate the end-to-end loop.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Douglas Teles
2026-06-23 17:03:35 -03:00
parent 3beb86f3d4
commit b6ceea9347
12 changed files with 360 additions and 288 deletions
+11 -5
View File
@@ -89,11 +89,17 @@ Identidade Arch vem dos 7 itens desta seção (pacman, PKGBUILD, FHS, rolling, K
### 2.1. Wirar pacman como mecanismo de update real
- [ ] **Subir mirror `repo.arch-r.io`** L. Hospedar `archr-core` (sistema base) e `archr-extra` (emuladores, temas, scrapers) como repos pacman. Sync via CI quando build de release sai.
- [ ] **Construir packages `.pkg.tar.zst` no pipeline de release** M. Hoje cuspimos `.img.gz`; passar a também produzir um pacote pacman por componente quando o build do package.mk concluir. Permite atualização granular.
- [ ] **Substituir `archr-update` por wrapper de `pacman -Syu`** M. Apenas para upgrades incrementais. Reservar image-based para upgrades grandes (kernel, mudança de major version) com migração assistida.
- [ ] **Suporte a `pacman -S <emulador>`** M. Quando 2.1.1 e 2.1.2 estiverem ativos, o usuário pode instalar emuladores opcionais sem reflash. Reduz tamanho da imagem base.
- [ ] **Rollback via pacman cache** S. `/storage/.pacman/cache` já está reservado no package.mk; só falta UI no settings menu para "voltar para a release anterior".
**Status: pipeline cliente pronto (2026-06-23).** Falta gerar a primeira release no `archr-linux/archr-repo` para o sistema ter o que sincronizar.
- [x] **Decisão de infra: GitHub Releases como mirror único.** Sem VPS, sem `repo.arch-r.io`. Detalhes em `docs/release-policy.md`. Google Drive como backup; Cloudflare R2 fica como fallback futuro se aparecer problema concreto.
- [x] **`archr-update` reescrito** como wrapper fino de `pacman -Syu`. 87 linhas (eram 188). Comandos: `archr-update [check|update|info]`. Channel lido de `system.cfg "updates.branch"` e refletido no mirrorlist via rewrite on-the-fly.
- [x] **`pacman.conf` apontando para o repo `archr`** (não mais Arch Linux ARM repos). `SigLevel = Required DatabaseOptional` durante bootstrap; aperta para `Required` quando o keyring de produção entrar.
- [x] **`pacman-init` adaptado**: testa conectividade via `github.com` em vez de `mirror.archlinuxarm.org`, popula keyring `archr` (não mais `archlinuxarm`).
- [x] **`archr-keyring` reformulado**: ship empty placeholders por enquanto, hooks prontos para receber o keyring real (`archr.gpg`, `archr-trusted`, `archr-revoked`) quando a chave master for gerada.
- [x] **`scripts/repo/gen-pacman-repo`**: empacota `build.*/install_pkg/<pkg>/` em `.pkg.tar.zst`, gera `.PKGINFO`, monta `archr.db` via `repo-add`. README ao lado documenta o workflow `gh release create repo-<canal>`.
- [ ] **Gerar a chave master GPG**: chave ArchR offline + subchave de signer. Atualizar `archr-keyring/keys/` com `archr.gpg`, `archr-trusted`, `archr-revoked`. Bloqueia o primeiro release assinado.
- [ ] **Primeira release no `archr-linux/archr-repo`**: criar o repo no GitHub, rodar `gen-pacman-repo` em build local, publicar `repo-dev` para teste interno antes de promover para `repo-next` e `repo-stable`.
- [ ] **CI no `archr-linux/Arch-R`** que invoque `gen-pacman-repo` após `make docker-RK3326` e publique no `archr-repo` automaticamente. GitHub Actions free tier basta.
### 2.2. Aproximar do FHS
+30 -6
View File
@@ -71,12 +71,36 @@ Tooling consumidor (Flasher, archr-update) pode parsear essa tag e mostrar banne
3. Permanece em `next` por no mínimo 7 dias com feedback dos testers.
4. Promove para `stable`: anúncio com tag apropriada (Action Required se houver migração).
## Compatibilidade com `archr-update` existente
## Infra de distribuição
`archr-update` atual já aceita `BRANCH` como variável de canal e faz POST para `update.arch-r.io`. Nenhuma mudança no cliente é necessária. O servidor precisa entender:
**Decisão registrada em 2026-06-23.**
- `BRANCH=stable` (default): retorna último `YYYY.MM.PATCH` validado.
- `BRANCH=next`: retorna último build da branch principal.
- `BRANCH=dev`: retorna último build noturno (com warning de instabilidade).
**Mirror único: GitHub Releases.** Tudo que ArchR distribui (imagem `.img.gz` e pacotes `.pkg.tar.zst` quando 2.1 estiver pronto) sai pelo GitHub. Justificativa:
Quando 2.1 (pacman como update real) estiver implementado, os canais migram naturalmente para repos pacman separados (`archr-core-stable`, `archr-core-next`, `archr-core-dev`).
- Custo zero. GitHub serve a CDN, TLS e storage como bônus do repo público.
- Throughput global. CDN distribuída cobre o usuário onde estiver.
- Limites suficientes. 2 GB por asset cobre o maior pacote (kernel + initramfs). Sem limite total por release nem por conta.
- Já é o canal vigente. Reduzimos uma variável (não há "novo provedor para o usuário aprender").
**Google Drive (10 TB do mantenedor):** ficou de fora do runtime. Usos legítimos discutidos e aprovados:
- Backup das releases publicadas (via rclone após upload no GitHub).
- Archive histórico de versões antigas que saíram do GitHub.
- Assets pesados internos (BIOS de teste, gameplay para QA, scraper packs).
- Não serve como mirror pacman: URLs com hash, virus-scan warning em arquivos >25 MB, throttling, range requests inconsistentes.
**Cloudflare R2 ou Backblaze B2 como fallback:** avaliados, descartados por enquanto. Reavaliar se aparecer um problema concreto de disponibilidade do GitHub.
## Plano de update sem legacy
`archr-update` original faz POST para `update.arch-r.io`. Esse endpoint **nunca existiu**: o script nunca entregou update funcional. Significa que hoje o único caminho de update é reflashar a microSD com nova imagem baixada do GitHub Releases manualmente, e que **não há compatibilidade legada a preservar**.
A implementação de 2.1 (pacman como update real) é portanto clean slate:
1. Repo pacman `archr-linux/archr-repo` no GitHub.
2. Cada release nesse repo é uma tag tipo `repo-YYYY.MM.PATCH`, com todos os `.pkg.tar.zst` da snapshot + `archr-core.db` assinado.
3. Tags rolling `repo-stable`, `repo-next`, `repo-dev` mantêm sempre o snapshot mais recente do canal e ficam como alvo do `mirrorlist`.
4. `/etc/pacman.d/mirrorlist` aponta para `https://github.com/archr-linux/archr-repo/releases/download/repo-<canal>`.
5. `archr-update` reescrito como wrapper de `pacman -Syu` (algumas dezenas de linhas).
6. Reflashar continua sendo o caminho de upgrade para major bumps (kernel ABI, FHS, etc.); pacman cobre incrementais.
Primeira release ArchR a ter update via pacman será marco histórico. Pode ser RC5 ou v2.0 final, conforme prontidão da infra GPG e CI.
@@ -1,188 +1,87 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026 ArchR (https://github.com/archr-linux/Arch-R)
#
# archr-update: wrapper around pacman to surface the same CLI ArchR
# users were promised before, while the actual update mechanism is
# pacman -Syu against the repo published on GitHub Releases.
#
# Release channel is read from system.cfg "updates.branch" and maps to
# the matching mirror tag on github.com/archr-linux/archr-repo:
# stable -> repo-stable
# next -> repo-next
# dev -> repo-dev
#
# The mirrorlist that points to those tags is installed by the pacman
# package (/etc/pacman.d/mirrorlist).
. /etc/profile
BRANCH="$(get_setting updates.branch)"
FORCE="$(get_setting updates.force)"
BRANCH="$(get_setting updates.branch 2>/dev/null)"
BRANCH="${BRANCH:-stable}"
ENDPOINT_URL="https://update.arch-r.io"
MACHINEIDFILE=/storage/.cache/systemd-machine-id
USERID=$(cat ${MACHINEIDFILE})
# Function to check for an update. It returns a URL if an update is available.
check_update() {
custom_force="${1:-$FORCE}"
update_url=$(curl -s -S -X POST \
-F "OS=${OS_NAME}" \
-F "ARCH=${HW_ARCH}" \
-F "SOC=${HW_DEVICE}" \
-F "VERSION=${OS_VERSION}" \
-F "UID=${USERID:(-12)}" \
-F "BUILD=${OS_BUILD}" \
-F "DEV=${QUIRK_DEVICE}" \
-F "FORCE=${custom_force}" \
-F "BRANCH=${BRANCH}" \
"$ENDPOINT_URL")
echo "$update_url"
return $?
# Channel is reflected by a single line in mirrorlist; rewrite it on
# the fly so the user can switch channels by changing the setting.
write_mirrorlist() {
cat >/etc/pacman.d/mirrorlist <<EOF
# Generated by archr-update from system.cfg updates.branch=${BRANCH}
Server = https://github.com/archr-linux/archr-repo/releases/download/repo-${BRANCH}
EOF
}
# Function to get list of releases
get_releases() {
releases=$(curl -s -S -X POST \
-F "OS=${OS_NAME}" \
-F "ARCH=${HW_ARCH}" \
-F "SOC=${HW_DEVICE}" \
-F "BRANCH=${BRANCH}" \
-F "LIST=1" \
"$ENDPOINT_URL")
echo "$releases"
return $?
network_ok() {
ping -c 1 -W 3 8.8.8.8 >/dev/null 2>&1
}
# Returns 0 if there is enough available free space at target_path in megabytes.
check_disk_space() {
target_path=$1
required_mB=$2
available_mB=$(df -m "$target_path" | awk 'NR==2 {print $4}')
if [ "$available_mB" -ge "$required_mB" ]; then
return 0
else
usage() {
cat <<EOF
Usage: ${0##*/} [check|update|info]
check Sync repo metadata and report whether updates are available.
update Sync metadata and apply pending updates (pacman -Syu).
info Show current OS version, channel, and configured mirror.
Channel is configured via system.cfg "updates.branch" (stable|next|dev).
EOF
}
cmd_check() {
write_mirrorlist
pacman -Sy --noconfirm >/dev/null 2>&1 || {
echo "Failed to sync repo metadata. Check network."
return 1
}
pending=$(pacman -Qu | wc -l)
if [ "${pending}" -eq 0 ]; then
echo "No updates available."
return 1
fi
echo "Updates available: ${pending} package(s)."
pacman -Qu
}
# Download a file from url to destination and show progress.
download_file() {
url=$1
destination=$2
curl --progress-bar -S -L "$url" -o "$destination"
return $?
cmd_update() {
write_mirrorlist
pacman -Syu --noconfirm
}
# Main update logic.
main_update() {
custom_force="${1:-$FORCE}"
update_url=$(check_update "$custom_force")
# Check if the returned update_url looks like a URL (e.g., starts with http)
case "$update_url" in
http://*|https://*)
echo "Update available at: $update_url" ;;
*)
echo "No update available or invalid update URL."
exit 1
;;
esac
# Check that there is at least 2GB of available free space on /storage.
if ! check_disk_space "/storage" "2048"; then
echo "Not enough free space on /storage (need at least 2GB)."
exit 1
fi
# Ensure that the /var/cache/archr/update folder exists.
if [ ! -d "/var/cache/archr/update" ]; then
mkdir -p /var/cache/archr/update || { echo "Failed to create /var/cache/archr/update directory."; exit 1; }
fi
# Extract the file name from the update URL.
filename=$(basename "$update_url")
# Define download destinations inside /var/cache/archr/update folder using the original file names.
update_file="/var/cache/archr/update/${filename}"
checksum_file="/var/cache/archr/update/${filename}.sha256"
# Download the update file.
echo "Downloading update file..."
download_file "$update_url" "$update_file"
if [ $? -ne 0 ]; then
echo "Failed to download update file."
exit 1
fi
# Download the checksum file (append .sha256 to the update URL).
checksum_url="${update_url}.sha256"
echo "Downloading checksum file..."
download_file "$checksum_url" "$checksum_file"
if [ $? -ne 0 ]; then
echo "Failed to download checksum file."
rm -f "$update_file"
exit 1
fi
# Verify checksum.
echo "Verifying the downloaded update..."
expected_checksum=$(awk '{print $1}' "$checksum_file")
actual_checksum=$(sha256sum "$update_file" | awk '{print $1}')
if [ "$expected_checksum" = "$actual_checksum" ]; then
echo "Checksum verified successfully. Reboot to apply the update."
set_setting updates.force 0
sync
else
echo "Checksum verification failed. Removing downloaded files."
rm -f "$update_file" "$checksum_file"
exit 1
fi
cmd_info() {
echo "OS: ${OS_NAME:-ArchR} ${OS_VERSION:-?}"
echo "Build: ${OS_BUILD:-?}"
echo "Channel: ${BRANCH}"
echo "Mirror: https://github.com/archr-linux/archr-repo/releases/download/repo-${BRANCH}"
echo "Pacman: $(pacman --version | head -1 | awk '{print $3}')"
}
# Check network connectivity (ping DNS server)
if ! ping -c 1 8.8.8.8 >/dev/null 2>&1; then
if ! network_ok; then
echo "Network connection unavailable."
exit 1
fi
# Read the first argument to determine the mode.
mode=$1
# Check if the mode is a number (e.g., 20250101), which indicates forcing a specific version
if echo "$mode" | grep -q '^[0-9]\{8\}$'; then
# If mode is a date (8 digits), treat it as a forced version
echo "Forcing update to version $mode..."
main_update "$mode"
exit 0
fi
case "$mode" in
check)
update_url=$(check_update)
case "$update_url" in
http://*|https://*)
# Extract the filename from the URL.
filename=$(basename "$update_url")
# Use shell parameter expansion to remove everything up to the last dash, then remove the .tar suffix.
version=${filename##*-}
version=${version%.tar}
echo "$version"
exit 0
;;
*)
echo "No update available."
exit 1
;;
esac
;;
releases)
# Get the list of available releases
echo "Checking available releases..."
releases=$(get_releases)
echo "$releases"
exit 0
;;
update|"")
# If mode is "update" or no argument is provided, proceed with the update.
main_update
;;
*)
echo "Usage: $0 [check|update|releases|<version>]"
echo " check - Check for available updates and print version"
echo " update - Update to the latest version"
echo " releases - Show available releases"
echo " <version> - Force update to specific version (e.g., 20250101)"
exit 1
;;
case "${1:-update}" in
check) cmd_check ;;
update) cmd_update ;;
info) cmd_info ;;
-h|--help|help) usage ;;
*) usage; exit 2 ;;
esac
@@ -1 +0,0 @@
68B3537F39A313B3E574D06777193F152BDBE6A6:4:
@@ -1,51 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
fx6y8+A0kBiaAmcY01U/upYXeHs=
=AIgD
-----END PGP PUBLIC KEY BLOCK-----
@@ -2,12 +2,12 @@
# Copyright (C) 2026 ArchR (https://github.com/archr-linux)
PKG_NAME="archr-keyring"
PKG_VERSION="20240419"
PKG_VERSION="20260623"
PKG_LICENSE="GPL"
PKG_SITE="https://archlinuxarm.org"
PKG_SITE="https://arch-r.io"
PKG_URL=""
PKG_DEPENDS_TARGET="toolchain gnupg"
PKG_LONGDESC="ArchR keyring: Arch Linux ARM GPG keys for pacman package verification."
PKG_LONGDESC="ArchR pacman keyring: master + signer public keys for package verification."
PKG_TOOLCHAIN="manual"
make_target() {
@@ -15,9 +15,22 @@ make_target() {
}
makeinstall_target() {
# Install Arch Linux ARM keyring for pacman-key --populate
mkdir -p ${INSTALL}/usr/share/pacman/keyrings
cp ${PKG_DIR}/keys/archlinuxarm.gpg ${INSTALL}/usr/share/pacman/keyrings/
cp ${PKG_DIR}/keys/archlinuxarm-trusted ${INSTALL}/usr/share/pacman/keyrings/
cp ${PKG_DIR}/keys/archlinuxarm-revoked ${INSTALL}/usr/share/pacman/keyrings/
# ArchR master + signer keys. When a real keyring is generated, ship:
# keys/archr.gpg - public keyring file
# keys/archr-trusted - trusted key fingerprints, one per line,
# format: <fingerprint>:<trust-level>:<owner>
# keys/archr-revoked - revoked fingerprints, one per line
# The placeholder release ships an empty keyring; pacman runs with
# SigLevel = Required DatabaseOptional and accepts unsigned packages
# from the archr repo because the .db remains optional. Tighten to
# 'Required' once the real master key is generated and signs the .db.
for f in archr.gpg archr-trusted archr-revoked; do
if [ -f "${PKG_DIR}/keys/${f}" ]; then
cp "${PKG_DIR}/keys/${f}" ${INSTALL}/usr/share/pacman/keyrings/
else
: >${INSTALL}/usr/share/pacman/keyrings/${f}
fi
done
}
@@ -1,20 +1,15 @@
#
# Arch Linux ARM mirror list
# Generated for ArchR distribution
# ArchR pacman mirror list
#
# Auto-rewritten by archr-update based on system.cfg "updates.branch".
# The line below is the stable-channel default; running
# archr-update <command>
# regenerates the file from the current channel setting.
#
# Channels available:
# repo-stable - monthly validated snapshot, default for end users
# repo-next - validated next-channel build, for testers
# repo-dev - nightly, may break, for maintainers
#
## Arch Linux ARM - Primary
Server = http://mirror.archlinuxarm.org/$arch/$repo
## Arch Linux ARM - US mirrors
# Server = http://ca.us.mirror.archlinuxarm.org/$arch/$repo
# Server = http://fl.us.mirror.archlinuxarm.org/$arch/$repo
# Server = http://il.us.mirror.archlinuxarm.org/$arch/$repo
# Server = http://nj.us.mirror.archlinuxarm.org/$arch/$repo
## Arch Linux ARM - EU mirrors
# Server = http://de.mirror.archlinuxarm.org/$arch/$repo
# Server = http://dk.mirror.archlinuxarm.org/$arch/$repo
# Server = http://eu.mirror.archlinuxarm.org/$arch/$repo
# Server = http://hu.mirror.archlinuxarm.org/$arch/$repo
# Server = http://sg.mirror.archlinuxarm.org/$arch/$repo
Server = https://github.com/archr-linux/archr-repo/releases/download/repo-stable
@@ -5,7 +5,8 @@
#
[options]
# Paths adapted for ArchR (mutable state on /storage partition)
# Paths follow Arch convention; on ArchR the dirs are bridge symlinks
# pointing into the /storage rw overlay (see archr meta-package).
RootDir = /
DBPath = /var/lib/pacman/
CacheDir = /var/cache/pacman/pkg/
@@ -13,31 +14,27 @@ LogFile = /var/log/pacman.log
GPGDir = /etc/pacman.d/gnupg/
HookDir = /etc/pacman.d/hooks/
HoldPkg = pacman glibc bash
HoldPkg = pacman glibc bash linux archr-keyring
Architecture = aarch64
# Signature checking - relaxed for initial setup
# Tighten to 'Required DatabaseOptional' after running pacman-key --init
SigLevel = Optional TrustAll
# Signature checking. Required is the target once archr-keyring ships
# the production master key; DatabaseOptional during bootstrap because
# the .db files in the first releases may not be signed yet.
SigLevel = Required DatabaseOptional
LocalFileSigLevel = Optional
# Misc options
CheckSpace
ParallelDownloads = 3
Color
#
# Arch Linux ARM repositories
# ArchR official repository
#
# Hosted as GitHub Releases under archr-linux/archr-repo. The release
# tag is per channel (repo-stable, repo-next, repo-dev); archr-update
# rewrites /etc/pacman.d/mirrorlist with the right tag based on the
# system.cfg "updates.branch" setting before invoking pacman.
#
[core]
Include = /etc/pacman.d/mirrorlist
[extra]
Include = /etc/pacman.d/mirrorlist
[alarm]
Include = /etc/pacman.d/mirrorlist
[aur]
[archr]
Include = /etc/pacman.d/mirrorlist
@@ -2,9 +2,15 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2026 ArchR (https://github.com/archr-linux)
#
# pacman-init: Initialize pacman on first boot
# Creates directory structure and initializes GPG keyring
# Works offline using embedded Arch Linux ARM keys
# pacman-init: bring pacman state up on first boot.
#
# - Materialize the dir structure the package config expects on
# /storage (the bridge symlinks /var/lib/pacman, /var/cache/pacman/pkg
# already point here).
# - Initialize the GPG keyring and trust the embedded ArchR master key
# (populated by the archr-keyring package).
# - Sync repo metadata if we have network. Best-effort; archr-update
# will sync again on demand.
set -e
@@ -12,7 +18,6 @@ PACMAN_ROOT="/storage/.pacman"
echo "ArchR: Initializing pacman..."
# Create pacman directory structure on /storage
mkdir -p "${PACMAN_ROOT}/db/local"
mkdir -p "${PACMAN_ROOT}/db/sync"
mkdir -p "${PACMAN_ROOT}/cache"
@@ -23,31 +28,31 @@ mkdir -p "${PACMAN_ROOT}/logs"
echo "ArchR: Pacman directory structure created."
# Initialize pacman GPG keyring
if [ ! -d /etc/pacman.d/gnupg/trustdb.gpg ] || [ ! -s /etc/pacman.d/gnupg/trustdb.gpg ]; then
# Initialize and populate the GPG keyring with the ArchR master key
# (offline; archr-keyring ships the keyring file).
if [ ! -s /etc/pacman.d/gnupg/trustdb.gpg ]; then
echo "ArchR: Initializing pacman keyring..."
pacman-key --init 2>/dev/null || true
# Populate with embedded Arch Linux ARM keys (offline, no network needed)
if [ -f /usr/share/pacman/keyrings/archlinuxarm.gpg ]; then
echo "ArchR: Populating keyring with Arch Linux ARM keys (offline)..."
pacman-key --populate archlinuxarm 2>/dev/null || true
echo "ArchR: Keyring populated successfully."
if [ -f /usr/share/pacman/keyrings/archr.gpg ]; then
echo "ArchR: Populating keyring with ArchR master key..."
pacman-key --populate archr 2>/dev/null || true
echo "ArchR: Keyring populated."
else
echo "ArchR: WARNING: archlinuxarm keyring not found at /usr/share/pacman/keyrings/"
echo "ArchR: Package signature verification may fail."
echo "ArchR: WARNING: archr keyring not found at /usr/share/pacman/keyrings/archr.gpg"
echo "ArchR: Package signature verification will fail until archr-keyring is reinstalled."
fi
fi
# Sync package databases if network is available
if ping -c 1 -W 3 mirror.archlinuxarm.org >/dev/null 2>&1; then
echo "ArchR: Network available, syncing package databases..."
pacman -Sy --noconfirm 2>/dev/null || true
# Best-effort metadata sync against GitHub. Silent failure: pacman -Sy
# (and archr-update check / update) will retry interactively later.
if curl -s -o /dev/null --max-time 4 https://github.com; then
echo "ArchR: Network available, syncing repo metadata..."
pacman -Sy --noconfirm >/dev/null 2>&1 || true
else
echo "ArchR: Network unavailable, skipping database sync."
echo "ArchR: Run 'pacman -Sy' when online to sync databases."
echo "ArchR: Network unavailable, skipping initial metadata sync."
echo "ArchR: Run 'archr-update check' when online."
fi
# Mark as initialized
touch "${PACMAN_ROOT}/.initialized"
echo "ArchR: Pacman initialization complete."
+59
View File
@@ -0,0 +1,59 @@
# scripts/repo
Tooling for the GitHub-Releases-as-pacman-mirror workflow described in
`docs/release-policy.md`. The end result is a tag like `repo-stable`
on `archr-linux/archr-repo` with the assembled `.pkg.tar.zst` files
and signed metadata.
## gen-pacman-repo
Reads the finished build tree (`build.*-RK3326/install_pkg/<pkg>/`)
that `make docker-RK3326` leaves behind, packs each `<pkg>` directory
into a `<pkg>-<version>-1-aarch64.pkg.tar.zst`, generates the
`.PKGINFO` sidecar that pacman needs, and runs `repo-add` to build
`archr.db` and `archr.files`.
Run after a successful build:
```sh
BUILD_DIR=build.ArchR-RK3326.aarch64-2026.06-devel \
OUT_DIR=/tmp/repo-out \
CHANNEL=dev \
SIGNER= \
scripts/repo/gen-pacman-repo
```
Then upload:
```sh
gh release create repo-dev \
--repo archr-linux/archr-repo \
--title "ArchR repo dev $(date +%Y%m%d)" \
/tmp/repo-out/*
```
## GPG signing
For production releases, set `SIGNER=<fingerprint>` and have the
agent (or a YubiKey-backed agent) ready. `repo-add --sign` will sign
the .db as it walks the package list, and each .pkg.tar.zst gets a
.sig as well.
The master key whose public half is bundled into the `archr-keyring`
package must include `<fingerprint>` either directly or as a signed
subkey, otherwise `pacman-key --populate archr` on the client will
not extend trust to packages signed by it.
## Channels and tag mapping
`archr-update` rewrites `/etc/pacman.d/mirrorlist` based on
`system.cfg "updates.branch"`:
| Channel value | Mirror tag on archr-linux/archr-repo |
|---------------|--------------------------------------|
| `stable` (default) | `repo-stable` |
| `next` | `repo-next` |
| `dev` | `repo-dev` |
The promotion workflow (dev -> next -> stable) is documented in
`docs/release-policy.md`.
+126
View File
@@ -0,0 +1,126 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026 ArchR (https://github.com/archr-linux/Arch-R)
#
# gen-pacman-repo: build a pacman repo (archr-core.db + .pkg.tar.zst per
# package) out of a finished ArchR build tree.
#
# Inputs:
# BUILD_DIR - the build.* dir that has install_pkg/<pkg>/ trees
# (defaults to the most recent build.*-RK3326 under cwd)
# OUT_DIR - where the .pkg.tar.zst and .db get written
# (defaults to ${BUILD_DIR}/../repo-out)
# CHANNEL - stable|next|dev. Goes into PKGINFO and the .db name
# (defaults to dev)
# SIGNER - GPG key id to sign packages and the .db with.
# If empty, packages are not signed (good for local CI).
#
# Output:
# ${OUT_DIR}/archr.db (+.sig if signed)
# ${OUT_DIR}/archr.files
# ${OUT_DIR}/<pkg>-<ver>-<rel>-aarch64.pkg.tar.zst (+.sig)
#
# This is the GitHub-Releases-as-mirror path: after gen-pacman-repo,
# `gh release create repo-${CHANNEL} ${OUT_DIR}/*` uploads everything
# and pacman clients pick it up via the mirrorlist URL.
set -euo pipefail
BUILD_DIR="${BUILD_DIR:-$(ls -1dt build.*-RK3326 2>/dev/null | head -1)}"
[ -z "${BUILD_DIR:-}" ] && { echo "BUILD_DIR not set and no build.*-RK3326 found"; exit 2; }
[ -d "${BUILD_DIR}/install_pkg" ] || { echo "no install_pkg/ under ${BUILD_DIR}"; exit 2; }
OUT_DIR="${OUT_DIR:-${BUILD_DIR}/../repo-out}"
CHANNEL="${CHANNEL:-dev}"
SIGNER="${SIGNER:-}"
REPO_NAME="archr"
ARCH="aarch64"
mkdir -p "${OUT_DIR}"
echo "gen-pacman-repo: build=${BUILD_DIR} out=${OUT_DIR} channel=${CHANNEL} signer=${SIGNER:-none}"
# Per-package metadata. Today the package.mk format does not carry a
# clean semver, so we synthesize: PKG_VERSION if numeric, else the
# build date. Real PKGBUILDs replace this when Arch-ification 2.3 lands.
build_one_pkg() {
local src="$1"
local name
name="$(basename "${src}")"
# Skip anything that is not an actually built package tree.
[ -d "${src}" ] || return 0
# Pull version from the package.mk if present; fallback to date.
local pkgmk version
pkgmk="projects/ArchR/packages/*/${name}/package.mk packages/*/${name}/package.mk"
version=""
for f in $(ls ${pkgmk} 2>/dev/null); do
version="$(awk -F'"' '/^PKG_VERSION/ {print $2; exit}' "${f}")"
[ -n "${version}" ] && break
done
version="${version:-$(date +%Y%m%d)}"
# pacman version must match [a-zA-Z0-9.+_]+; drop unsupported chars.
version="$(echo "${version}" | tr -c 'a-zA-Z0-9.+_' '.')"
local pkgrel=1
local outfile="${OUT_DIR}/${name}-${version}-${pkgrel}-${ARCH}.pkg.tar.zst"
# .PKGINFO sidecar that pacman reads on install.
local pkginfo="${src}/.PKGINFO"
cat >"${pkginfo}" <<EOF
# Generated by gen-pacman-repo
pkgname = ${name}
pkgver = ${version}-${pkgrel}
pkgdesc = ArchR ${name} (channel: ${CHANNEL})
url = https://arch-r.io
builddate = $(date +%s)
packager = ArchR Build <build@arch-r.io>
size = $(du -sb "${src}" | cut -f1)
arch = ${ARCH}
license = GPL
EOF
(cd "${src}" && tar --zstd -cf "${outfile}" --owner=0 --group=0 . )
rm -f "${pkginfo}"
if [ -n "${SIGNER}" ]; then
gpg --batch --yes --detach-sign --use-agent --local-user "${SIGNER}" "${outfile}"
fi
echo " + ${name} ${version}-${pkgrel}"
}
echo "gen-pacman-repo: packaging install_pkg/* into .pkg.tar.zst..."
for src in "${BUILD_DIR}/install_pkg/"*; do
build_one_pkg "${src}"
done
# repo-add walks the .pkg.tar.zst files and produces archr.db.tar.gz
# (which pacman renames to archr.db at fetch time) plus archr.files.
echo "gen-pacman-repo: building repo db..."
local_db="${OUT_DIR}/${REPO_NAME}.db.tar.gz"
local_files="${OUT_DIR}/${REPO_NAME}.files.tar.gz"
rm -f "${local_db}" "${local_files}"
if [ -n "${SIGNER}" ]; then
repo-add --sign --key "${SIGNER}" "${local_db}" "${OUT_DIR}"/*.pkg.tar.zst
else
repo-add "${local_db}" "${OUT_DIR}"/*.pkg.tar.zst
fi
# Pacman fetches archr.db (no .tar.gz); the conventional way is to ship
# the .tar.gz and a .db symlink alongside.
( cd "${OUT_DIR}" && \
ln -sf "${REPO_NAME}.db.tar.gz" "${REPO_NAME}.db" && \
ln -sf "${REPO_NAME}.files.tar.gz" "${REPO_NAME}.files" )
echo ""
echo "gen-pacman-repo: ready at ${OUT_DIR}"
ls -1sh "${OUT_DIR}" | head -20
echo ""
echo "Next: gh release create repo-${CHANNEL} \\"
echo " --repo archr-linux/archr-repo \\"
echo " --title \"ArchR repo ${CHANNEL} $(date +%Y%m%d)\" \\"
echo " \"${OUT_DIR}\"/*"