madOS is an Arch Linux distribution built using archiso. It targets modern hardware with GPU acceleration and integrates OpenCode as an AI assistant. The project produces a custom live/installer ISO with a GTK graphical installer and pre-configured Hyprland desktop environment.
../theme-imperative-dotsis the canonical working repository used to iterate on imperative-dots theme fixes.- The same theme is consumed by madOS during build/install under
/usr/share/mados/themes/imperative-dots. - Development flow: edit and test behavior live from
theme-imperative-dotsagainst the local madOS environment, validate fixes in-session, then commit changes back totheme-imperative-dots. - When investigating visual mismatches, compare all theme files between the local runtime copy and
theme-imperative-dotsbefore fixing issues.
SKWD Launchersource of truth is../skwd(repomadkoding/skwd), notskwd-wall.- Launcher implementation paths in
skwd:qml/launcher/AppLauncher.qmlqml/launcher/AppLauncherService.qmlscripts/python/build-app-cache
- In madOS live runtime, launcher UI may still be consumed from the installed theme copy under
~/.config/quickshell/widgets/launcherand/usr/share/mados/themes/imperative-dots/scripts/quickshell/widgets/launcher, but upstream fixes for launcher behavior should be applied inskwd. skwd-wall(madkoding/skwd-wall) is wallpaper selector/daemon only. It is installed to/usr/local/share/skwd-walland does not contain the app launcher module.- Theme repo (
../theme-imperative-dots) remains the source of truth for top bar/main shell composition and quickshell startup wiring:scripts/quickshell/TopBar.qmlscripts/quickshell/Main.qmlscripts/start/start.sh
# Build standard ISO (syslinux BIOS + systemd-boot UEFI)
sudo mkarchiso -v -w work/ -o out/ .
# Build with timestamped workdir
sudo mkarchiso -v -w work-$(date +%Y%m%d-%H%M%S)/ -o out/ .
# Serve ISO via HTTP (default port 8000)
./serve-iso.sh
./serve-iso.sh 8080 # custom port
./serve-iso.sh 9000 out/ # custom port and directory
# Boot ISO in QEMU
./run-qemu.sh- ~10GB free disk space
- archiso, xorriso, qemu-system-x86_64 installed
- sudo privileges
work-*/— Working directory (safe to delete after build)out/— Final ISO outputout/madOS-dev-x86_64.iso— Base ISO with syslinux/systemd-boot
# Install ruff and shellcheck
pip install ruff
pacman -S shellcheck # Arch Linux
# Run ruff linter (auto-fix issues where possible)
ruff check .
# Run ruff with fixes
ruff check --fix .
# Run ruff formatter
ruff format .
# Run both lint and format
ruff check --fix . && ruff format .# Install shellcheck (Arch Linux)
sudo pacman -S shellcheck
# Run shellcheck on all executable scripts
find airootfs/usr/local/bin -type f -executable | while read -r script; do
if head -1 "$script" | grep -qE "(bash|sh)$"; then
shellcheck -s bash --severity=error "$script"
fi
done
# Run shellcheck on installer scripts
if [ -d airootfs/usr/local/lib/mados_installer/scripts ]; then
find airootfs/usr/local/lib/mados_installer/scripts -type f -name "*.sh" | while read -r script; do
shellcheck -s bash --severity=error "$script"
done
fi# Install pre-commit
pip install pre-commit
# Install hooks
pre-commit install
# Run manually
pre-commit run --all-files[tool.ruff]
line-length = 100
target-version = "py313"
[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP", "B", "C4"]
ignore = ["E501", "E402", "F401"]Per-file ignores (defined in pyproject.toml):
tests/*: B011, F841, I001, F811, N806, E741, UP015, W293, W291, B905, B007, N802, UP024airootfs/usr/local/lib/mados_*/**: E402, F401, I001, B904, UP024, W293, W291, UP041, UP015, B905, UP032, B007, F541, F841airootfs/usr/local/bin/*: I001
# Run all unit tests
python3 -m pytest tests/ -v
# Run a single test file
python3 -m pytest tests/test_boot_scripts.py -v
# Run a single test function
python3 -m pytest tests/test_boot_scripts.py::TestBootScriptSyntax::test_all_boot_scripts_valid_syntax -v
# Run tests matching a pattern
python3 -m pytest -k "boot_scripts" -v
# Run with verbose output
python3 -m pytest tests/ -vv
# Run with less output
python3 -m pytest tests/ -q# USB Persistence tests
docker run --privileged --rm -v $(pwd):/build archlinux:latest bash /build/tests/test-liveusb-persistence.sh
# First-boot simulation
docker run --privileged --rm -v $(pwd):/build archlinux:latest bash /build/tests/test-first-boot-simulation.sh
# Installer Python validation
docker run --privileged --rm -v $(pwd):/build archlinux:latest bash /build/tests/test-installer-python-validation.sh- Shebang:
#!/usr/bin/env python3 - Formatter: ruff with double quotes, 100 char line length
- Target version: Python 3.13
- Imports: Group in order: stdlib, third-party, local; each group separated by blank line
import sys import types from pathlib import Path from tests.test_helpers import install_gtk_mocks
- Types: Use type hints where beneficial; prefer explicit over implicit
- Error handling: Use specific exceptions; avoid bare
except:# Good except FileNotFoundError as e: log.error(f"Config missing: {e}") # Bad except: pass
- Naming: snake_case for functions/variables, PascalCase for classes, UPPER_SNAKE for constants
- Strings: Use double quotes consistently
- Docstrings: Use triple double quotes, present tense, imperative mood
- Shebang:
#!/usr/bin/env bash - Linter: shellcheck (follow shellcheck guidelines)
- Strict mode: Use
set -euo pipefailfor all scripts - Exception: Setup scripts that run as systemd services may skip strict mode if they have their own graceful error handling
- Uses GTK3 with Nord color scheme (polar night background, frost accents)
- Some standalone tools (
mados-help,mados-power) use GTK4 for better theming - Check existing scripts to determine which GTK version to use:
- GTK3:
gi.require_version("Gtk", "3.0")— Installer and older tools - GTK4:
gi.require_version("Gtk", "4.0")— Standalone UI tools (help, power menu)
- GTK3:
- Both versions require
python-gobjectandgtk3/gtk4packages respectively - For headless testing, use mocks from
tests/test_helpers.py:from tests.test_helpers import install_gtk_mocks install_gtk_mocks()
profiledef.sh— ISO metadata (name, publisher, version) and file permissionspackages.x86_64— Package list for the ISO (one package per line)pacman.conf— Pacman configuration for the build
grub/— GRUB bootloader config (UEFI) - deprecated, using systemd-bootsyslinux/— Syslinux config (BIOS)efiboot/— EFI boot loader configuration
airootfs/etc/— System configuration (sysctl, systemd, skel configs)airootfs/usr/local/bin/— Custom scripts and executablesairootfs/usr/local/lib/— Python library modules for toolsairootfs/root/customize_airootfs.d/— Post-install customization modules
serve-iso.sh— HTTP server for ISO distributionrun-qemu.sh— QEMU launcher with GTK display, UEFI, serial log
airootfs/root/customize_airootfs.d/03-apps.sh— App installer (clones from GitHub)airootfs/usr/local/bin/mados-installer-autostart— Launcher for external installerairootfs/usr/local/bin/setup-persistence.sh— Persistent storage setupairootfs/usr/local/lib/mados_installer/— Installer Python modules
- Unit tests:
tests/test_*.py(Python, pytest with unittest conventions) - Integration tests:
tests/test-*.sh(Shell scripts) - Test helpers:
tests/test_helpers.py(GTK mocks for headless testing)
docs/— Project documentation and GitHub Pages sitedocs/HARDWARE_QUIRKS.md— Hardware-specific configurationsdocs/SEQUENCE_DIAGRAMS.md— System sequence diagrams
The 03-apps.sh script installs madOS native applications during ISO build:
# Key variables
BUILD_DIR="/root/build_tmp" # Working directory for git clones
INSTALL_DIR="/opt/mados" # Where apps are installed
BIN_DIR="/usr/local/bin" # Wrapper scripts location- Clone repo to
/root/build_tmp/{module_name}_$$/ - Move to
/opt/mados/{module_name}/ - Create wrapper script in
/usr/local/bin/{app-name} - Copy
.desktopfile to/usr/share/applications/
Most apps use python3 -m {module_name}:
#!/bin/bash
export PYTHONPATH="/opt/mados:${PYTHONPATH:-}"
cd "/opt/mados/{module_name}"
exec python3 -m "{module_name}" "$@"Exception: mados-installer uses python3 __main__.py:
#!/bin/bash
cd "/opt/mados/mados_installer"
exec python3 __main__.py "$@"All apps follow a standardized structure with app.py as entry point:
| App | Repo | Module Name | Special Handling |
|---|---|---|---|
| mados-audio-player | madoslinux | mados_audio_player | Standard python3 -m |
| mados-equalizer | madoslinux | mados_equalizer | Standard python3 -m |
| mados-launcher | madoslinux | mados_launcher | Standard python3 -m |
| mados-pdf-viewer | madoslinux | mados_pdf_viewer | Standard python3 -m |
| mados-photo-viewer | madoslinux | mados_photo_viewer | Standard python3 -m |
| mados-video-player | madoslinux | mados_video_player | Standard python3 -m |
| mados-wallpaper | madoslinux | mados_wallpaper | Has daemon/ subpackage, uses python3 -m daemon |
| mados-installer | madoslinux | mados_installer | Uses python3 __main__.py |
| mados-updater | madkoding | mados_updater | Standard python3 -m |
{app_name}/
├── app.py # Main application code
├── __main__.py # Entry point for python3 -m
├── {app_name}.desktop # Desktop entry file
└── ...other files
mados_wallpaper/
├── app.py
├── __main__.py
├── daemon/
│ ├── __init__.py
│ └── __main__.py # daemon entry point
└── ...
- Script stops on first failure (no
|| true) - 3 retry attempts with 2 second delays for git clone
- Builds in
/root/build_tmpinstead of/tmp(more stable in chroot)
- Keep
python,python-gobject, andgtk3inpackages.x86_64(required for the installer) - RAM optimizations target 1.9GB systems — avoid adding heavy packages without justification
- When adding new scripts to
airootfs/usr/local/bin/, updateprofiledef.shwith proper permissions (0:0:755) - When modifying the installer, update tests in
tests/test_installer_config.py - Desktop configs live in
airootfs/etc/skel/.config/(Sway, Waybar, Foot, Wofi) - System name: "madOS" (all lowercase in filenames, styled in display text)
- File permissions: New executable scripts must be added to
profiledef.shfile_permissionsarray - Package additions: Add one package per line to
packages.x86_64 - ZRAM and kernel tuning configs are in
airootfs/etc/sysctl.d/andairootfs/etc/systemd/
The CI/CD pipeline (.github/workflows/ci-cd.yml) runs in stages:
- Stage 1 — Unit tests (pytest, parallel matrix) and integration tests run in parallel
- Stage 2 — Installer validation in Arch container (requires Stage 1)
- Stage 3 — ISO build with mkarchiso (only on tags or manual trigger)
- Stage 4 — Upload to Internet Archive
- Stage 5 — GitHub Release and website update (stable releases only)
GitHub Pages deployment is in .github/workflows/pages.yml.
- If SDL fails, use
-display gtkinstead - If
-soundpcspkfails, remove it (not supported in newer QEMU versions) - Ensure
/dev/kvmis accessible for KVM acceleration
- Check network connectivity in chroot environment
- Use
ping github.comto verify - 03-apps.sh now has retry logic (3 attempts)
- Check
out/mados-serial.logfor boot errors - Use
run-qemu.shwith serial logging enabled