A system maintenance CLI for Arch Linux — checks for failed services, runs health checks, keeps your mirrorlist fresh, and tracks which maintenance tasks are due, all from one command.
archcare automates the handful of maintenance chores every Arch install eventually needs (failed-unit checks, mirrorlist refreshes, disk/memory/CPU health checks) and tracks their schedule so nothing quietly falls behind. It aims to be a user-friendly tool that makes maintenance easy, painless, and reliable. If you're reading the code, the Architecture section below is for you.
failed-services— lists failed systemd units, with descriptions, status, and recent logs for each; supports an ignore-list for known-flaky unitshealth-check— disk, memory, CPU, filesystem, and pacman-database health in one passmirrorlist-update— refreshes/etc/pacman.d/mirrorlistviareflector, with automatic backup and rollback on failuremaintenance-check— the scheduler-aware "what's due" task; reports overdue/at-risk maintenance across all other tasks by severity- Per-task scheduling with configurable frequency, tracked run history, and
a
task statusview showing what's due and what's overdue - Optional desktop notifications on task completion
- Systemd timer integration for fully automated, unattended runs
- Verbose, per-task-formatted terminal output for every task result
Requires Python 3.13+, Poetry, and (for the
tasks that need them) systemd, reflector, and pacman — all standard on
an Arch install.
git clone https://github.com/shayanmoosavi/archcare.git
cd archcare
poetry installReflector isn't installed by default on most systems:
sudo pacman -S reflector# CLI reference
archcare --help
# First-time setup: creates ~/.config/archcare/{tasks,settings,ignored-services}.toml
archcare setup config
# See what's registered
archcare task list
# Run one task
archcare task run failed-services
# Check what's due
archcare task statusCommands are grouped by area; run archcare --help or archcare <group> --help for the full, always-up-to-date reference. The summary below covers
the common cases.
archcare task run <task-name> [--force] [--verbose]
archcare task status [<task-name>] [--due]
archcare task list [--type automated|manual]--forceruns a task even if it isn't due yet--verboseshows the task's full structured details (not just the summary line) — e.g. every failed service's status and recent logs, or every maintenance item needing attention--dueontask statusshows only tasks that are currently due
$ archcare task run health-check --verbose
Running Task: health-check
──────────────────────────
╭──── Task Result: health-check ─────╮
│ Status: ✓ SUCCESS │
│ Message: All health checks passed │
│ Duration: 35.62s │
│ │
│ Details: │
│ │
│ System Summary: │
│ Disk Usage: 28.1% │
│ Memory Usage: 50.9% │
│ CPU Usage: 4.1% │
│ Pacman Database: Healthy │
│ Installed Package Files: Healthy │
│ System Uptime: 3 days, 3 hours │
╰────────────────────────────────────╯archcare setup config # create/repair config files (non-destructive)
archcare setup timers # install systemd timers for automated tasks (needs sudo)archcare debug test-notification --severity warning # test desktop notification deliveryarchcare logs # view recent archcare log outputConfig lives under ~/.config/archcare/ (or the target user's home when run
via a systemd timer as root — see Architecture).
tasks.toml — defines every task's schedule and type:
[failed-services]
type = "manual"
frequency = 30
description = "Check for failed systemd services"
enabled = true
[maintenance-check]
type = "automated"
frequency = 1
description = "Check for due system maintenance tasks"
enabled = truesettings.toml — global and per-task behavior:
log_level = "INFO"
log_retention_days = 30
require_confirmation = true
[mirrorlist]
country = "Germany"
protocol = "https"
sort = "rate"
number_of_mirrors = 5
[maintenance_check]
critical_threshold_days = 7
warning_threshold_days = 0
output_mode = "terminal"
require_acknowledgment = trueignored-services.toml — systemd units to exclude from
failed-services, e.g. units known to fail harmlessly on your system:
services = ["known-flaky.service", "getty@tty7.service"]Entries must be valid systemd unit names (any recognized unit type, not
just .service — the failed-unit check itself isn't type-restricted).
archcare is organized in strict layers, each only depending on the ones below it:
cli/ Typer commands, presenters, terminal rendering
services/ Business logic, orchestrates core + config for each command
core/ Task execution, scheduling, task/formatter registry
config/ Pydantic models, TOML/JSON loading and persistence
utils/ subprocess wrappers, system/hardware queries, notifications
A few things worth knowing if you're extending it:
- Dependency injection throughout.
AppContextbuilds aTaskExecutoronce per invocation and threads it down throughctx.obj; nothing reaches for global state. - Ports for anything environment-specific.
TaskInteraction(confirm/ notify) andTaskDetailFormatter(render a task's details) are both duck-typed protocols defined incore/, with CLI-specific implementations living incli/. This is what will let a future GUI frontend reusecore/andconfig/unmodified — it just supplies its own interaction and formatter implementations. - One static registry.
TaskRegistry(core/task_registry.py) maps each task name to both its execution class and its detail formatter class, providing the presenters and execution engine with what they need. - Typed task results.
TaskResult[TDetails]is generic over a per-task details dataclass (FailedServicesDetails,HealthCheckDetails, etc. — seecore/task_details.py), so a task's result carries real, typed, attribute-accessed data end to end instead of a loosely-keyed dict. - A real exception hierarchy. Every layer has its own domain exceptions
rooted in a single shared
ArchcareError(archcare/exceptions.py), withcore/configexceptions that need to pass through Pydantic validators deliberately also subclassingValueErrorwhere required.
poetry run pytest # full suite
poetry run pytest tests/unit # unit tests only
poetry run pytest tests/integration # integration tests only
poetry run ty check # static type checkingThe suite is split by intent, not just by directory:
- Unit tests (
tests/unit/) mirrorsrc/archcare/1:1 and use mocks at precise boundaries — real Pydantic models over bare mocks wherever construction is cheap,mocker.patch.objectover stacked@patchdecorators, and specced mocks (MagicMock(spec=X)) to catch attribute typos. - Integration tests (
tests/integration/) invoke the real CLI via Typer'sCliRunner, build a realAppContext, and do real file I/O undertmp_path. The only things ever mocked are the actual OS/subprocess boundary (utils/system.py'srun_command/run_command_with_sudo) and desktop notifications. This combination has caught real bugs unit tests alone missed — seegit logfor a couple of examples where a mocked unit test passed while the real wiring was broken.
poetry install --with dev
poetry run pytest
poetry run ty checkContributions should keep to the layering above — in particular, core/
and config/ should never import from cli/ or services/. If you're
adding a new task, look at core/task_registry.py's DEFAULT_TASK_REGISTRY
and core/task_details.py for the pattern every existing task follows.
- PySide6/QML GUI frontend, reusing
core//config/as-is via the port boundaries described above - Implementing progress bars, spinners, etc., for long-running tasks.
- Implementing the rest of the tasks in the default
tasks.toml
This project is licensed under the GNU General Public License v3.0. See LICENSE for details.