Project deep-dive

mtune-mirror

Tuned Arch package mirrors · for the hardware you refuse to retire.

Rebuilds selected Arch packages — tuned for a specific target CPU, patched with your own PKGBUILDs, or just offloaded from a slower machine — and serves them as pacman-compatible overlay repos over HTTP, with a web UI to monitor and drive the whole build pipeline. Clients keep using the official repos and only pull the rebuilt packages from a higher-priority local repo, falling back cleanly to upstream for everything else.

Overview

Two machines kicked this off: a 2009 Acer Aspire One netbook (Intel Atom N270, i686) and a 2011 Acer Aspire 725 (AMD C-60, "Bobcat"). Both run current Arch just fine, but both are genuinely slow — every package rebuild on-device takes ages, and every package the distro ships is compiled generic, leaving real performance on the table on hardware this constrained.

mtune-mirror rebuilds the packages that matter — full essentials groups, individual picks, or fully custom sources like a hand-tuned kernel — with per-CPU -march/-mtune flags, in an isolated build chroot, on hardware fast enough to actually do the work. The result is served as a normal pacman repo that slots in above the official mirrors — clients get the tuned build where one exists and fall back to upstream for everything else, with zero change to their day-to-day pacman -Syu.

A third arch, manjaro, demonstrates the same machinery solving a different problem: no CPU tuning at all, just offloading slow AUR/odd-repo builds from a weaker workstation onto a bigger box, bootstrapped against Manjaro's own repos instead of vanilla Arch.

The primary deployment is a Proxmox VE helper that provisions and bootstraps the whole thing — build chroots, web UI, systemd timers — in one command against a fresh LXC.

3 Tuned arches out of the box — atom, btver1, manjaro
85+ Packages tracked across the live deployment
-14% vmlinuz size, tuned+slimmed kernel vs. stock

Screenshots

Dashboard — idle, with per-arch timer countdowns and recent build sweeps Live build — currently-building panel with progress bar and streaming console Packages — per-arch table with override badges, versions, and per-package build history Build history — every build sweep, paged, with per-package results Settings — package groups and per-arch enabled groups + chroot status Help — dashboard guide and client pacman.conf setup instructions
Dashboard — idle, with per-arch timer countdowns and recent build sweeps (click to expand)
Dashboard — idle, with per-arch timer countdowns and recent build sweeps Live build — currently-building panel with progress bar and streaming console Packages — per-arch table with override badges, versions, and per-package build history Build history — every build sweep, paged, with per-package results Settings — package groups and per-arch enabled groups + chroot status Help — dashboard guide and client pacman.conf setup instructions

Architecture

A Go web service drives config-as-TOML shell scripts that own every build chroot, mirrored behind nginx — one per-arch chroot, isolated and parallel.

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, ui-sans-serif, sans-serif', 'lineColor': '#a5b4fc', 'primaryTextColor': '#e2e4ea', 'primaryColor': '#1e2330', 'primaryBorderColor': '#6366f1', 'secondaryColor': '#252b3b', 'tertiaryColor': '#181c26', 'background': '#0d0f14', 'clusterBkg': '#181c26', 'clusterBorder': '#374151', 'titleColor': '#e2e4ea'}}}%%
flowchart TD
    A(["config/*.toml\narches · groups · packages"])

    subgraph web["pkgmirror-web (Go)"]
        B["pkgconfig\nreads TOML natively"]
        C["REST API +\nSSE log streaming"]
        D["Embedded SPA\ndashboard · packages\nbuilds · settings"]
        B --> C --> D
    end

    subgraph build["bin/*.sh — owns every write"]
        E["build.sh\nper-arch chroot\nmemory-aware -j cap"]
        F["group.sh\ncontrol.sh\nupdate-check.sh"]
    end

    subgraph chroots["Per-arch build chroots"]
        G["atom (i686)\n-march/-mtune=atom"]
        H["btver1 (x86_64)\n-march/-mtune=btver1"]
        I["manjaro (x86_64)\nuntuned, offload-only"]
    end

    J(["nginx\nrepos static + UI proxy"])
    K(["pacman clients\noverlay repo"])

    A --> B
    C -->|systemd-run\nUser=pkgmirror| E
    E --> F
    E --> G
    E --> H
    E --> I
    G --> J
    H --> J
    I --> J
    J --> K
        

Design decisions

Local overrides always win over upstream Drop a patched PKGBUILD in pkgbuilds/<arch>/<pkg>/ and it's never clobbered by a sync — the same mechanism covers fully custom packages with no upstream at all, like a hand-tuned kernel.
Every write goes through bin/*.sh, never the web service pkgmirror-web reads config natively via internal/pkgconfig for speed, but delegates every mutation — builds, chroot bootstraps, config edits — to the same shell scripts a human would run by hand. One code path, one source of truth, no drift between UI-triggered and manual builds.
Builds run as a dedicated non-root pkgmirror user makepkg refuses to run as root outright, and the web service triggers builds via systemd-run --uid=pkgmirror rather than shelling out as whatever user it happens to run as — keeps build-tree ownership consistent regardless of who or what kicked the build off.
Memory-aware concurrency cap Configurable per-arch build concurrency, each in its own chroot copy, plus a memory-aware cap on make -j so heavy builds (a kernel, a browser) don't get OOM-killed on constrained build hardware.
An arch doesn't need CPU tuning to be useful The manjaro arch ships with no -march/-mtune at all — chroot.mirror/chroot.keyring config fields let an x86_64 arch bootstrap against a distro other than vanilla Arch, purely to offload slow builds from a weaker machine.
Live SSE console, independent of the UI tab that started the build A build kicked off from the dashboard, a daily systemd timer, or another browser tab is all the same underlying systemd-run unit — any tab can reattach an SSE stream to it by unit name, so the console isn't tied to whoever happened to click "Build".

Tech Stack

Web service

  • Go
  • Embedded SPA (vanilla JS)
  • Server-Sent Events

Build pipeline

  • makechrootpkg / devtools
  • systemd-run (per-build isolation)
  • dasel (TOML config)

Deployment

  • Proxmox VE (pct, pveam)
  • systemd (per-arch timers)
  • nginx

Also ships

  • Docker sandbox (local iteration)
  • GitHub Actions CI
  • linux-atom / linux-btver1 (companion kernel repos)

Proof it works: two tuned kernels

mtune-mirror's git package source exists specifically for cases like this — a fully custom package with no upstream, built straight from its own repo every time. Two companion repos put that to real use, retuning and slimming the kernel for the exact machines this project started with.

Not a random pair of test machines — the Aspire One was my first laptop, bought in the final year of my bachelor's, and it carried me through most of my master's too. I still pull it out as a dumb terminal for remote desktop into my other machines; there's something about it that brings back simpler times, and I intend to keep it alive for as long as I can. The Aspire 725 was its successor — bought mainly because the Aspire One's webcam finally gave out — another cheap beater I used to an inch of its life. It carried me to the end of my master's thesis, and when my wife started her own master's, she took it over and presented her thesis on it too.

The Acer Aspire One and its successor, the Acer Aspire 725, side by side on a desk

linux-atom

Intel Atom N270 (i686), CONFIG_MATOM + localmodconfig slimming. Benchmarked against stock on the real machine: mostly a wash on raw throughput (expected — userspace binaries are unaffected by kernel choice), but a real ~20% win under oversubscribed thread contention, and an 17.5% smaller vmlinuz.

github.com/N0t4R0b0t/linux-atom — a standalone PKGBUILD, packaged the same way as any AUR entry: git clone + makepkg -si builds and installs it directly, no mtune-mirror required.

linux-btver1

AMD C-60 "Bobcat" (x86_64), -march=btver1 -mtune=btver1 at the compiler level (no per-CPU-model Kconfig option exists on x86_64) + the same slimming approach. Benchmarked the same way: the honest result is a wash on raw throughput and contention, but a consistent, real 14% smaller vmlinuz and 18% less unreclaimable kernel memory from slimming alone.

github.com/N0t4R0b0t/linux-btver1 — same deal: a standalone PKGBUILD, packaged like any AUR entry, buildable on its own with makepkg -si.

Quick Start

On a Proxmox VE node, as root — provisions the container, bootstraps both build chroots, and brings up the repos + web UI in one shot.

Install

bash -c "$(curl -fsSL https://raw.githubusercontent.com/N0t4R0b0t/mtune-mirror/main/install.sh)"

# pin a tagged release instead of tracking main
REPO_REF=v1.0.0 bash -c "$(curl -fsSL https://raw.githubusercontent.com/N0t4R0b0t/mtune-mirror/v1.0.0/install.sh)"

Point a client at it

# /etc/pacman.conf — above the official repos
[btver1-local]
Server = http://<container-ip>/repos/btver1
SigLevel = Optional TrustAll

sudo pacman -Sy && sudo pacman -Syu

The dashboard's Help page generates this block for you, matched to whichever arch you need.

Back to portfolio