AMWI — Arch Mirror Web Interface
A self-hosted Arch Linux mirror with a web dashboard for sync management, traffic monitoring, package search, and upstream mirror benchmarking.
Version: b1.0.0
Features
- Rsync-based mirror sync from a configurable upstream mirror
- Web dashboard with CPU/RAM/disk/network stats and live traffic pulse
- Package search across
core,extra, andmultilib - Mirror benchmark tool to pick a fast upstream from country mirrorlists
- Scheduled sync via systemd timer (configurable interval)
- Discord notifications for sync start/success/failure (optional)
- HTTP mirror serving at
/archlinux/for pacman clients
Requirements
- Arch Linux (or compatible) host with root access
- Python 3 with
venv rsync- Disk space for a full mirror (~80–100 GB+ depending on repos synced)
- Network access to an upstream rsync mirror
Quick Install
git clone https://opengit.nakildias.com/Nakildias/AMWI.git
cd AMWI
sudo ./install.sh
The installer:
- Creates
/opt/archmirrorwith a Python virtualenv - Copies application files and systemd units
- Enables
arch-web.service(web UI + mirror HTTP) andarch-sync-manager.timer(periodic sync)
After install, open http://localhost:8000, register the first admin account, and configure your upstream mirror from the dashboard.
Directory Layout
| Path | Purpose |
|---|---|
/opt/archmirror/ |
Application install root |
/opt/archmirror/app.py |
Flask web app and mirror HTTP handler |
/opt/archmirror/sync_manager.py |
Rsync sync logic |
/opt/archmirror/settings.json |
Runtime configuration (created on first use) |
/opt/archmirror/database.db |
SQLite DB for users and traffic stats |
/srv/http/archlinux/ |
Mirror data served to pacman clients |
Services
| Unit | Role |
|---|---|
arch-web.service |
Gunicorn + Flask on port 8000 |
arch-sync-manager.timer |
Triggers sync checks every 5 minutes |
arch-sync-manager.service |
One-shot rsync job (invoked by timer or dashboard) |
Useful commands:
sudo systemctl status arch-web.service
sudo systemctl restart arch-web.service
sudo journalctl -u arch-web.service -f
sudo ./monitor.sh # tail web service logs
Manual sync:
sudo /opt/archmirror/venv/bin/python /opt/archmirror/sync_manager.py --force
Client Configuration (pacman)
Point pacman at your mirror. Example /etc/pacman.d/mirrorlist entry:
Server = http://YOUR_HOST:8000/archlinux/$repo/os/$arch
Replace YOUR_HOST with your server's hostname or IP. Clients will fetch packages from /archlinux/... on port 8000.
Architecture
pacman client
│
▼
Gunicorn (:8000)
│
├── /archlinux/* → send_from_directory(/srv/http/archlinux/)
├── /dashboard, /api/*, /search → Flask routes
│
sync_manager.py ──rsync──► upstream mirror ──► /srv/http/archlinux/
Mirror files and the admin UI share a single Gunicorn worker (--workers 1). Every package download is handled by the same Python process that serves the dashboard.
Slow-download timeouts (known issue)
Symptom
Clients on slow connections lose downloads after roughly 15–30 seconds, especially for large packages (hundreds of MB). pacman may report a partial download or connection reset.
Root cause
Package files are served through Gunicorn, not a dedicated static file server. Gunicorn's default worker timeout is 30 seconds. When a client reads slowly:
- Flask's
send_from_directory()blocks on TCP writes while sending the file. - The worker cannot respond to Gunicorn's master process heartbeat in time.
- Gunicorn sends SIGKILL to the worker and drops the client connection.
This is visible in logs as worker timeout / WORKER TIMEOUT entries from Gunicorn.
Relevant code:
- Mirror route:
app.py→serve_mirror()→send_from_directory(ARCHLINUX_ROOT, filename) - Process launcher:
launcher.sh→ Gunicorn (previously without--timeout)
Fix applied in this repo
launcher.sh now starts Gunicorn with --timeout 0, which disables the worker timeout so slow downloads can complete.
After pulling this change, restart the service:
sudo systemctl restart arch-web.service
Recommended production setup
For a public mirror, serving large binaries through Gunicorn is not ideal (single worker, Python overhead, SQLite logging on every hit). Prefer offloading static files to nginx or Apache:
nginx example — serve mirror files directly, proxy only the admin UI:
server {
listen 80;
server_name mirror.example.com;
# Mirror files (fast, no timeout issues)
location /archlinux/ {
alias /srv/http/archlinux/;
autoindex off;
}
# Admin UI and API
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 300s;
}
}
Then point pacman at http://mirror.example.com/archlinux/$repo/os/$arch instead of port 8000.
If you keep serving through Gunicorn only, --timeout 0 is required for slow clients. Very large packages on very slow links can still take a long time; consider increasing workers only for the admin UI if you split static serving as above.
Configuration
Settings are stored in /opt/archmirror/settings.json and editable from the dashboard:
| Key | Description |
|---|---|
enabled |
Enable scheduled sync |
frequency_hours |
Minimum hours between syncs |
upstream_mirror |
Rsync URL of upstream (e.g. rsync://mirror.example.com/archlinux/) |
benchmark_country |
Default country for mirror benchmark |
search_per_page |
Package search page size |
discord_enabled |
Enable Discord webhooks |
discord_webhook_url |
Discord webhook URL |
trusted_proxies |
IPs/CIDRs of reverse proxies (homelab nginx). When the direct connection is from one of these, AMWI uses X-Real-IP / X-Forwarded-For for client traffic stats |
Homelab nginx should pass client IPs (already in nginx/archmirror.nakildias.com.conf):
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
Security: Change app.secret_key in app.py (or move it to an environment variable) before exposing the dashboard to the internet. Only trust proxy headers from known homelab IPs — do not expose port :8000 directly to the internet if you use LAN-wide trusted ranges.
Mirrorlists
Country mirrorlists for benchmarking live in mirrorlists/ (e.g. United_States.txt, Canada.txt). These are parsed to find rsync upstream candidates.
Development
Run locally from the repo (not the /opt/archmirror install):
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python app.py
Gunicorn (production-like):
gunicorn --workers 1 --bind 0.0.0.0:8000 --timeout 0 app:app
Troubleshooting
| Problem | What to check |
|---|---|
| Downloads cut off ~30s | Gunicorn timeout; ensure launcher.sh uses --timeout 0 and restart arch-web.service |
| Sync stuck / "ghost" | Dashboard → clear sync lock; check journalctl -u arch-sync-manager.service |
| Empty package search | Mirror not synced yet; verify /srv/http/archlinux/pool/packages/ has content |
WORKER TIMEOUT in logs |
Same as slow-download issue; restart after timeout fix |
| Port 8000 in use | ss -tlnp \| grep 8000 and adjust bind in launcher.sh if needed |
License
See repository for license terms.