mirror of
https://github.com/archr-linux/Arch-R.git
synced 2026-07-12 18:19:42 -07:00
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:
+11
-5
@@ -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
@@ -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-----
|
||||
|
||||
mQINBFLbBPMBEADNB2XChJQplQwbAcl8wkhsPZOozGhxUYO+BVEF5vKjxcNzeR57
|
||||
cjj1veSw4aMmEv03MkBHi9Kyyk2wKUkFHuTx4DA5ZxnTt+2ScEezEFcmEoLsRYid
|
||||
eQ35tYWaFjpjZDLbR4bp0EumCi8zvxwQhXl1y4mRZtBCX8z4otdgXk8dBUSJJsHg
|
||||
JsmRobzNrBDGEr55nNbT88BxVcG39idEb/VOOqS24rogNJvkQUdRwmu8BGbSWI/I
|
||||
aLB0Wc3cMEMYCf5TjEwS9HsSvUOmdWE6RWibpnaQp9/CC2PHjP1QnXby3b1tA4nN
|
||||
m3IkP8HqwpeIrSL3hXSGKMn3r8D/Sil1kfU1U5nll0yk1hFoAfVK4AWEvcXHqy3B
|
||||
wDYa1iyiiqGxmNRyiK68EZlc20uGjG/GsNJx+/tAqWTOW8sJJrp+YGT/07uH2iRP
|
||||
ivjWAetgih6xlmbbkskKm4hQI+sozWFObQzPe9R1lNLfncGXxxOWkkvGlbdZVXYn
|
||||
PSKf6dF+H+lZLjSvhRznTlz+jM/Ou/9bmzf0kvJt6fOI4aR8ZeseXEJfxpA0Bdbx
|
||||
arjPUrPj9XVLu75bFXMzeRIEW3zLd9OiHAnxfLlm/WDwc0zYjJIyU0V/KVhYx7Kq
|
||||
mNlD5zpg4gIh+n53OpSZPsLCWxwa/5y8w8HLOEpZRB/N6ocmzISSiqWUlwARAQAB
|
||||
tDZBcmNoIExpbnV4IEFSTSBCdWlsZCBTeXN0ZW0gPGJ1aWxkZXJAYXJjaGxpbnV4
|
||||
YXJtLm9yZz6JAjcEEwEKACEFAlLbBPMCGwMFCwkIBwMFFQoJCAsFFgIDAQACHgEC
|
||||
F4AACgkQdxk/FSvb5qaJbA//Qv/rT5+HFQIOzDLwMrX+A7v8/ojq/21Gz3Ty8+ER
|
||||
yvZvtg0OaX5CYdj7rVNgspwSNUggYPTKyuC7ooEXo2SwLOBMGku9k6wydzoipXnn
|
||||
QZCYqVPi+m+ajbRvg8Ae7TDAtrauSxjeovHpCovgaWVnBk7Fe62AnSe6nUGLYXgB
|
||||
L7NOYTb9yH8TmKAs7vaULAV3WLtQckiVad+1RwRlEqNoveyepjGEt+1FOftRPezd
|
||||
EH3NCZb0tZOGjPxj1OQWY/TE4gfppvj8lrSY1hEP6U3ogn4v699yxAgVV31Inttg
|
||||
CYccJJPIKpXgEa/5Iktxqp7CiSuBxBqbjsRgjSkz2NB5fcLWk7daCxdWS3jKgGOQ
|
||||
shbwt9lNAeOF0THqm3cIe3JAP76A/cO0fOh0vL0zh1wHlDqIPYG4JXuBz9qL9SoU
|
||||
rueVxuo2oqy7iNqEp2nuPl4Qw4XG4XGF9W0rPPGS/iDcCKJNyTo5OPGUXSPuLBQR
|
||||
Cq6JaKze9iLBdHHXNFSCadZiMDSiVXNnTWFtNaagDTEgUvDbLLgtqlxZnDcERRH5
|
||||
1hWMWRaKrgFp/OEa2MRRwP/XBg7hZiHjBEUSEVwYT2Zzmsy7T0uXXu9LDtu4qnHp
|
||||
+jNJaYPoRIhnlQPo6lBovA4rgz7kOdMUPQZRMPCgj6AW8gndzi52/i2NMRw75BiM
|
||||
NlC5Ag0EUtsE8wEQANNQk8cZFYEarnWi0DONcFpF6rv6MV3I1srtJQNiFWlmlnUW
|
||||
9wWgCdclAAZOolhU0jqcNiWQKqmeT/PIExk69L1bSpR1t5TisHLhcSnk8ajUEngH
|
||||
iGMywwIQwJb8kCRgytUwxQ5l7A2kieh2vFu5ffnvkwmxhPx/vWKHYMvtbPfKC/JU
|
||||
PACseLacTPhDRCg/HVIbv3JIcE6eHtnHlliTYwPZ1wiKqVNM51d4N7AN2K8zUMOd
|
||||
WBDpnnMX89x7nCMgE0F4oD6pq/hs9V/cTRwMLtehecLHHxasY5euu8YOQjNjiMfn
|
||||
HVwdeulACPOfAyHIFmMf44s4wbbr7mZdGXIf6XLeO6IwTCwIJGa2Jl76s/J+5dXP
|
||||
CBH3M1Vw9FYmjVPxS3tVeMkyXKcWOTWINY2D3my6/dEI6NmrKdc3IUjk/KgpmneV
|
||||
MJri9gt7tUy/0UHlNNbryRjyQL9RnlqAUiGqeVINQghpXZavq0ZIybqSLKCk20to
|
||||
B8mqiTXO7Q4gEeAkoQjzZcFqXsmWDUE6bMrjqlnua8HoTaKtaXmrxyKZfpg8u9s3
|
||||
0JEDKA2mnudVu7Hf3mS3pVQzH8oMbSdHCAvidTs2J4qjEZVyMlb1DyZ9uZWN2S1I
|
||||
4v6gRs9GctTO7lsKMgTxrIwPihah+wRTFT4gYB2taSGu3bFelbjM39uKFEw/ABEB
|
||||
AAGJAh8EGAEKAAkFAlLbBPMCGwwACgkQdxk/FSvb5qZX/g//epqrtufsS+aUcDta
|
||||
767SMf+P7KAnZitRCkxbUv99jwk+EpYlBcjwYmpxKHIZfr7YtSemctKC8DC3M7Lk
|
||||
OayfnUAK+GJdwQFaW7zY3Y6i79Bj9fOvcmGyUnfQrznDaN1Is1urjh2BMoCHKmm+
|
||||
aLjU58dPa1624Gpz+mk2t1ecAYR1P68wGOBcBxTq5n2GCJbmkmdwVDktBwanODJ1
|
||||
7HF5qVxB+D8uxp+S27hcSvMZK91M9zT6e28WcER0kYjhlNzb6hh91VsFYMzbtGV8
|
||||
su8sXv8R3meKRCDpU+3J32B6OP4BO4zojgpiFgSRe4kkSIxy5/ZqyGucLjZ6Q22C
|
||||
NPNMuq+xVjgfLvU49VVMG9dpa7216MxvV4BAwuV1GxC+xLJ5SJjdEEE9hHGOutgA
|
||||
nXEqAarW/6EpNm0pF3gcykDZrPT3/NQgIT67czN6Ne6AjPbTlJla6h3LxKIEkEaA
|
||||
y/byaZLoPuR42Bf0k00ImfxT4b5aa7U/1wFZ9SCThub41ti+RXSLuMuRAm4Tmgt7
|
||||
Z+GORDCJiA4hnrYVYbrVWdcQg4UDI+j7TEWhzPu3rAo/kleJEV3uIvis6POfVtA6
|
||||
O28ZVDXxgwEzesoqcm3jpUoa3sIkvLWRDlx+m4tFqXb/jRHj0lD2iTvcIACjAMDn
|
||||
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."
|
||||
|
||||
@@ -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`.
|
||||
Executable
+126
@@ -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}\"/*"
|
||||
Reference in New Issue
Block a user