Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 71 additions & 35 deletions .github/workflows/infrastructure-download-external.yml
Original file line number Diff line number Diff line change
Expand Up @@ -764,8 +764,6 @@ jobs:
mkdir -p "$APTLY_ROOT"
printf '{ "rootDir": "%s" }\n' "$APTLY_ROOT" > "$APTLY_CFG"

echo "::debug::APTLY_ROOT=$APTLY_ROOT"
echo "::debug::APTLY_CFG=$APTLY_CFG"
echo "::debug::Config file contents:"
cat "$APTLY_CFG" >&2

Expand All @@ -789,7 +787,6 @@ jobs:
MIRROR="${{ matrix.name }}-${{ matrix.release }}-${{ matrix.arch }}"

echo "::debug::MIRROR_NAME=$MIRROR"
echo "::debug::Original KEY='${KEY:-}'"

# KEY may be:
# - "unstable contrib non-free" (suite + components)
Expand All @@ -798,9 +795,6 @@ jobs:
read -r DIST REST <<<"${KEY:-}"
COMPONENTS="$REST"

echo "::debug::DIST='$DIST'"
echo "::debug::REST='$REST'"
echo "::debug::COMPONENTS='$COMPONENTS'"

# Special/flat cases: do not pass components
case "${KEY:-}" in
Expand All @@ -823,69 +817,111 @@ jobs:
URL="${URL/http:\/\//https:\/\/}"

echo "::debug::URL='$URL'"
echo "::debug::FILTER_ARGS='${FILTER_ARGS[*]}'"
echo "::debug::ADDITIONAL_FILTER='$ADDITIONAL_FILTER'"
echo "::debug::ARCH='${{ matrix.arch }}'"

# Some upstreams advertise every historical build: ~50 versions of
# "code" pass GLOB, 7.9 GiB, of which the prune below keeps 210 MiB.
# aptly queues from the filtered index, so pin the filter to the newest
# version of each name first -- same index aptly is about to read.
# Engages only where the index actually holds duplicates.
PINNED_FILTER=""
pin_newest() {
local comp stem body name ver entries=0
local -A newest=()
local -a comps=()
if [[ -n "$COMPONENTS" ]]; then
read -r -a comps <<< "$COMPONENTS"
else
comps=("")
fi
for comp in "${comps[@]}"; do
if [[ -n "$comp" ]]; then
stem="${URL%/}/dists/${DIST}/${comp}/binary-${{ matrix.arch }}/Packages"
else
stem="${URL%/}/Packages"
fi
body=""
body="$(curl -fsSL --max-time 60 "${stem}.gz" 2>/dev/null | gzip -dc 2>/dev/null)" || true
[[ -n "$body" ]] || body="$(curl -fsSL --max-time 60 "${stem}.xz" 2>/dev/null | xz -dc 2>/dev/null)" || true
[[ -n "$body" ]] || body="$(curl -fsSL --max-time 60 "${stem}" 2>/dev/null)" || true
# 2 = index unreadable; caller must not run unpinned
[[ -n "$body" ]] || return 2
while read -r name ver; do
[[ -n "$name" && -n "$ver" ]] || continue
entries=$((entries + 1))
if [[ -z "${newest[$name]:-}" ]] || dpkg --compare-versions "$ver" gt "${newest[$name]}"; then
newest["$name"]="$ver"
fi
done < <(awk '/^Package: /{p=$2} /^Version: /{if (p != "") {print p, $2; p=""}}' <<< "$body")
done
# nothing to gain when the index already holds one version per name,
# and a filter naming hundreds of packages is not worth building
(( entries > ${#newest[@]} )) || return 1
(( ${#newest[@]} > 0 && ${#newest[@]} <= 500 )) || return 1
local q=""
for name in "${!newest[@]}"; do
q+="${q:+ | }${name} (= ${newest[$name]})"
done
PINNED_FILTER="$q"
echo "::notice::pinning ${#newest[@]} package(s) to newest of ${entries} versions"
}
pin_newest || { [[ $? == 2 ]] && warn_skip "index unreadable: $URL $DIST"; true; }

# Drop mirror if it already exists from previous run
echo "::debug::Checking if mirror exists..."
if aptly -config="$APTLY_CFG" mirror show "$MIRROR" &>/dev/null; then
echo "::notice::Dropping existing mirror: $MIRROR"
aptly -config="$APTLY_CFG" mirror drop "$MIRROR" || true
else
echo "::debug::Mirror does not exist yet"
fi

# Create mirror (distribution + optional components).
# `|| warn_skip` catches a missing/dead Release file at
# the URL — common cause of "broken source" — and skips
# the slot instead of letting `set -euo pipefail` fail
# the whole job.
echo "::debug::Creating mirror..."
if [[ -n "$COMPONENTS" ]]; then
echo "::debug::aptly -config="$APTLY_CFG" -ignore-signatures ${FILTER_ARGS[*]} ${ADDITIONAL_FILTER} -architectures="${{ matrix.arch }}" mirror create "$MIRROR" "$URL" "$DIST" $COMPONENTS"
# $COMPONENTS unquoted on purpose: empty means "no components".
mk_mirror() {
# shellcheck disable=SC2086
aptly -config="$APTLY_CFG" -ignore-signatures "${FILTER_ARGS[@]}" $ADDITIONAL_FILTER -architectures="${{ matrix.arch }}" mirror create "$MIRROR" "$URL" "$DIST" $COMPONENTS \
aptly -config="$APTLY_CFG" -ignore-signatures "${FILTER_ARGS[@]}" $ADDITIONAL_FILTER \
-architectures="${{ matrix.arch }}" mirror create "$MIRROR" "$URL" "$DIST" $COMPONENTS \
|| warn_skip "aptly mirror create failed (URL='$URL' DIST='$DIST' COMPONENTS='$COMPONENTS')"
else
echo "::debug::aptly -config="$APTLY_CFG" -ignore-signatures ${FILTER_ARGS[*]} ${ADDITIONAL_FILTER} -architectures="${{ matrix.arch }}" mirror create "$MIRROR" "$URL" "$DIST""
# shellcheck disable=SC2086
aptly -config="$APTLY_CFG" -ignore-signatures "${FILTER_ARGS[@]}" $ADDITIONAL_FILTER -architectures="${{ matrix.arch }}" mirror create "$MIRROR" "$URL" "$DIST" \
|| warn_skip "aptly mirror create failed (URL='$URL' DIST='$DIST')"
}

# Swap in the pinned filter.
PINNED_APPLIED=0
if [[ -n "$PINNED_FILTER" ]]; then
[[ -n "${GLOB:-}" ]] \
&& FILTER_ARGS=(-filter="( ${GLOB} ), ( ${PINNED_FILTER} )") \
|| FILTER_ARGS=(-filter="${PINNED_FILTER}")
PINNED_APPLIED=1
fi
echo "::debug::Mirror created successfully"

echo "::debug::Creating mirror..."
mk_mirror

# Update mirror with retry logic for EOF errors
echo "::debug::Updating mirror..."
MAX_RETRIES=3
RETRY_COUNT=0
UPDATE_SUCCESS=false

while [[ $RETRY_COUNT -lt $MAX_RETRIES && "$UPDATE_SUCCESS" == "false" ]]; do
if aptly -config="$APTLY_CFG" -max-tries=20 -ignore-signatures mirror update "$MIRROR"; then
echo "::debug::Mirror updated successfully"
UPDATE_SUCCESS=true
else
RETRY_COUNT=$((RETRY_COUNT + 1))
if [[ $RETRY_COUNT -lt $MAX_RETRIES ]]; then
echo "::warning::Mirror update failed (attempt $RETRY_COUNT/$MAX_RETRIES), retrying..."
sleep 2
# Recreate mirror if it got corrupted
echo "::debug::Recreating mirror after failure..."
aptly -config="$APTLY_CFG" mirror drop "$MIRROR" || true
if [[ -n "$COMPONENTS" ]]; then
# shellcheck disable=SC2086
aptly -config="$APTLY_CFG" -ignore-signatures "${FILTER_ARGS[@]}" $ADDITIONAL_FILTER -architectures="${{ matrix.arch }}" mirror create "$MIRROR" "$URL" "$DIST" $COMPONENTS
else
# shellcheck disable=SC2086
aptly -config="$APTLY_CFG" -ignore-signatures "${FILTER_ARGS[@]}" $ADDITIONAL_FILTER -architectures="${{ matrix.arch }}" mirror create "$MIRROR" "$URL" "$DIST"
fi
mk_mirror
else
warn_skip "aptly mirror update failed after $MAX_RETRIES attempts (URL='$URL' DIST='$DIST')"
fi
fi
done

# A pinned filter matching nothing would publish an empty repository.
if [[ "$PINNED_APPLIED" == "1" ]] && [[ "$(aptly -config="$APTLY_CFG" mirror show "$MIRROR" \
2>/dev/null | awk -F': *' '/^Number of packages/{print $2;exit}')" == "0" ]]; then
warn_skip "newest-version pin matched no packages (URL='$URL' DIST='$DIST')"
fi

# Snapshot. Failure here is rare (local aptly state op,
# not a remote fetch) but `set -e` would still kill the
# job, so route through warn_skip for consistency.
Expand Down
14 changes: 9 additions & 5 deletions .github/workflows/maintenance-update-readme.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
#
# Secrets (already present on this repo):
# ANTHROPIC_API_KEY - Claude API key (same one reporting-release-summary uses)
# ACCESS_TOKEN - PAT with `repo` scope on the target repos (the builtin
# GITHUB_TOKEN cannot push branches / open PRs cross-repo)
# ACCESS_TOKEN_ARMBIANWORKER
# - armbianworker PAT with `repo` scope on the target repos
# (the builtin GITHUB_TOKEN cannot push branches / open PRs
# cross-repo). Must be the worker account, not a person:
# reporting-release-summary.yml filters the release digest
# by PR author, and a human author lands these in the notes.
name: "Maintenance: Update README (AI)"

on:
Expand Down Expand Up @@ -70,7 +74,7 @@ jobs:
uses: actions/checkout@v7
with:
repository: ${{ matrix.repo }}
token: ${{ secrets.ACCESS_TOKEN }}
token: ${{ secrets.ACCESS_TOKEN_ARMBIANWORKER }}
path: target
fetch-depth: 0

Expand All @@ -89,7 +93,7 @@ jobs:
# closed: any gh/API error aborts the run rather than proceeding feedback-less.
- name: Collect reviewer feedback from the open README PR
env:
GH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
GH_TOKEN: ${{ secrets.ACCESS_TOKEN_ARMBIANWORKER }}
REPO: ${{ matrix.repo }}
run: |
set -euo pipefail
Expand Down Expand Up @@ -121,7 +125,7 @@ jobs:
- name: Open pull request
uses: peter-evans/create-pull-request@v8
with:
token: ${{ secrets.ACCESS_TOKEN }}
token: ${{ secrets.ACCESS_TOKEN_ARMBIANWORKER }}
path: target
branch: chore/update-readme
delete-branch: true
Expand Down
73 changes: 63 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,74 @@
<br><br>
</h2>

### Purpose of This Repository
# armbian.github.io

This repository acts as a central **automation and orchestration hub** for the Armbian project. It coordinates CI workflows, maintains metadata, syncs external data, and generates machine-readable output to power [armbian.com](https://www.armbian.com), [docs.armbian.com](https://docs.armbian.com), and related services.
## Purpose of This Repository

It also produces [data exchange files](https://github.armbian.com/) used for automation, reporting, and content delivery across the Armbian infrastructure.
This repository is Armbian's **automation and orchestration hub**. It hosts the CI workflows, scripts, and reference assets (board photos, vendor logos, release-target configuration) that generate the machine-readable data files driving [armbian.com](https://www.armbian.com), [docs.armbian.com](https://docs.armbian.com), the download index, the Raspberry Pi Imager list, mirror/redirector configuration, and other Armbian infrastructure.

Generated artefacts are published to the [`data` branch](https://github.com/armbian/armbian.github.io/tree/data) and exposed via [github.armbian.com](https://github.armbian.com/).

### Workflow Status & Monitoring
---

**[GitHub actions dashboard](https://actions.armbian.com/?repo=armbian.github.io)**
## Repository Layout

Monitor all automation workflows with real-time status tracking:
```text
.
├── board-images/ PNG photos of supported single-board computers
├── board-vendor-logos/ Logos for board vendors / SoC families
├── release-targets/ Inputs & generated YAML for the CI build matrix
│ (see release-targets/README.md)
├── scripts/ Python / shell / Node scripts invoked by workflows
├── templates/ Templates used by generators
├── .github/ Dependabot, labels, issue/PR automation, workflows
├── CNAME
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE GNU GPL v2
└── README.md
```

- **Execution history** — Complete log of past workflow runs with timestamps and outcomes
- **Performance metrics** — Runtime duration, resource usage, and success/failure rates
- **Live status** — Current state of running CI/CD pipelines and scheduled tasks
- **Debugging tools** — Detailed logs and error traces for failed workflows
### Key subsystems

| Area | Where | What it does |
|---|---|---|
| Build-target generation | [`release-targets/`](release-targets/) + `scripts/generate_targets.py` | Reads `image-info.json` (the per-board build inventory) plus the config files in `release-targets/` and emits the YAML files that drive Armbian's CI/CD pipeline matrix (`targets-release-apps.yaml`, `targets-release-standard-support.yaml`, `targets-release-nightly.yaml`, `targets-release-community-maintained.yaml`) and the website's `exposed.map`. See [`release-targets/README.md`](release-targets/README.md). |
| Board & vendor artwork | `board-images/`, `board-vendor-logos/` | Source PNG/SVG assets. CI generates multi-width thumbnails from these and publishes them for the website/download pages. |
| Data generation scripts | `scripts/` | Python, shell and Node scripts invoked by the workflows to build JSON/HTML/YAML artefacts (download index, RPi imager JSON, base-files package info, Jira excerpts, partners & maintainers, keyring downloads, MOTD, server inventory from NetBox, actions reports, kernel descriptions, …). |

---

## How It Works

- Scheduled and dispatched workflows in `.github/workflows/` run scripts from `scripts/` against source data from sibling Armbian repositories ([`armbian/build`](https://github.com/armbian/build), [`armbian/os`](https://github.com/armbian/os), …) and external services (Zoho Bigin, Atlassian Jira, NetBox, Ubuntu/Debian package archives, mirror rsync endpoints).
- Generated artefacts are committed to the `data` branch under `data/` and served publicly via [github.armbian.com](https://github.armbian.com/).
- Workflows chain via `repository_dispatch` events (e.g. new build inventory → regenerated build lists → refreshed website directory listing → redirector config update).

The workflow files themselves are YAML; their `run:` steps use Bash, and they invoke Python 3 scripts (with dependencies such as `requests`, `lxml`) and Node.js tooling (`fast-glob`, `js-yaml`) as needed. Image processing steps use GraphicsMagick and `pngquant`.

## Workflow Status & Monitoring

**[GitHub actions dashboard for this repo](https://actions.armbian.com/?repo=armbian.github.io)**

The dashboard aggregates every workflow in this repository with:

- **Execution history** — past runs with timestamps and outcomes
- **Performance metrics** — runtime, resource usage, success/failure rates
- **Live status** — current state of running pipelines and scheduled tasks
- **Debugging tools** — logs and error traces for failed runs

## Contributing

Contributions are welcome — bug reports, feature discussion, PRs, and documentation help. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the development workflow and other ways to get involved (board maintenance, staff applications, forum help). Please also read the [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md).

Related links:

- [Armbian documentation](https://docs.armbian.com)
- [Board maintainer procedures](https://docs.armbian.com/Board_Maintainers_Procedures_and_Guidelines/)
- [Armbian forum](https://forum.armbian.com/)
- [Issues](https://github.com/armbian/armbian.github.io/issues)

## License

Released under the GNU General Public License v2 — see [`LICENSE`](LICENSE).
Binary file added board-images/boardcon-sbc3568.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added board-images/mba62xx-tqma62xx.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added board-images/mba67xx-tqma67xx.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added board-images/tanix-tx6s.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added board-images/ztl-a568.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added board-vendor-logos/boardcon-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added board-vendor-logos/ztl-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 9 additions & 2 deletions release-targets/targets-extensions.map
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,12 @@ khadas-vim3:::ENABLE_EXTENSIONS="image-output-oowow"
khadas-vim3l:::ENABLE_EXTENSIONS="image-output-oowow"
khadas-vim4:::ENABLE_EXTENSIONS="image-output-oowow"
uefi-x86:::ENABLE_EXTENSIONS="nvidia"
musepipro:::ENABLE_EXTENSIONS: "v4l2loopback-dkms"
bananapif3:::ENABLE_EXTENSIONS: "v4l2loopback-dkms"
musepipro:::ENABLE_EXTENSIONS="v4l2loopback-dkms"
bananapif3:::ENABLE_EXTENSIONS="v4l2loopback-dkms"

# Seeed boards: vendor-branch only (Seeed extension depends on the
# vendor U-Boot chain; not for the mainline edge branch).
# Requires the seeed-extension wrapper extension in armbian/build.
recomputer-rk3576-devkit:vendor::ENABLE_EXTENSIONS="seeed-extension"
recomputer-rk3576-module-devkit:vendor::ENABLE_EXTENSIONS="seeed-extension"
recomputer-rk3588-devkit:vendor::ENABLE_EXTENSIONS="seeed-extension"
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ mekotronics-r58hd
mekotronics-r58-4x4
retroidpocket-rpmini
retroidpocket-rp5

# Blacklisted until the build/compilation issues are resolved
xiaomi-sheng
3 changes: 2 additions & 1 deletion release-targets/targets-release-nightly.blacklist
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ lepotato
lime-a64
lubancat-5io
luckfox-rk3308b-nova
mba62xx-tqma62xx
mba67xx-tqma67xx
mekotronics-r58s2
mksklipad50
musebook
Expand All @@ -70,7 +72,6 @@ nanopik1plus
nanopik2-s905
nanopim4v2
odroidc1
odroidc2
odroidc4
odroidhc4
odroidm1
Expand Down
6 changes: 6 additions & 0 deletions scripts/generate_targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,12 @@ def is_fast_hardware(entry):
if board_family == 'imx93':
return False

# MBa62xx (TI AM625/PowerVR AXE-1-16M): GNOME/Mutter's cross-GPU EGLImage
# sharing isn't supported, screen stays stuck on fbcon. XFCE works fine.
# Board-specific, not family-wide - MBa67xx (bigger GPU) has no issue.
if board == 'mba62xx-tqma62xx':
return False

# Nexell S5P6818 (NanoPi M3 / NanoPC-T3+ / NanoPi Fire3) has a Mali-400
# GPU that is GLES2-only — no OpenGL ES 3.0, which GNOME/mutter requires,
# so the shell falls back to software rendering (and it is 1 GiB). Classify
Expand Down
Loading