Skip to content

Architecture

Trevin edited this page Sep 28, 2026 · 6 revisions

Architecture

Aphotic's repo mirrors what actually gets installed, plus the machinery that decides what that is. There's no separate "build system" — install.sh resolves a plan from declarative TOML data and copies files into place.

Aphotic was previously known as Noctis-Hypr; the CLI, install script, and every path below are the current, renamed project.

Repo layout

Aphotic-Hypr/
├── install.sh / uninstall.sh   Thin orchestrators: guided setup, wizard or flags in, resolved plan out
├── aphotic.toml                 Generated on first install: the resolved source of truth
├── lib/
│   ├── install/                 Guided setup + wizard prompts, AUR helper bootstrap, backups, config
│   │                              deploy, greeter, snapshot recovery, failure reports
│   ├── toml/                    Profile + layer merge logic (merge.py)
│   └── ai/                      The Aphotic Assistant's prompt template
├── profiles/
│   ├── base/                    minimal.toml, full.toml
│   └── layers/                  gaming.toml, dev.toml, ai.toml, exploit.toml (meta) + exploit-*.toml sublayers
├── themes/                      Swappable theme presets (THEME_SPEC.md documents the contract)
└── Configs/                     Mirrors ~/.config: the configs that land on disk
    ├── systemd/user/              aphotic-shell.service (Restart=on-failure supervision for qs),
    │                                aphotic-recovery.service, aphotic-agent-usage.service/.timer,
    │                                aphotic-greeter-sync + aphotic-sddm-sync .service/.timer,
    │                                aphotic-package-check.service/.timer, kdeconnectd.service
    ├── greetd/                    The Aphotic greeter (Quickshell login surface) and its compositor config
    ├── quickshell/aphotic/        Hand-vendored Quickshell shell (see below)
    │   ├── config/                Tokens/Config/GlobalConfig singletons (hand-written, no native plugin)
    │   ├── services/               Colours (reads the palette snapshot), Audio, Hypr, Players, Notifs,
    │   │   │                         PluginRegistry, RuntimeContext, Surfaces, ResourcePosture, ...
    │   │   ├── ai/                   AiConfig/AiKeys/AiProviders (AI Chat + Settings → AI), AgentEvents,
    │   │   │                         InferenceMode, the local-inference claimants (Ollama, llama-swap, LM Studio)
    │   │   └── profile/              ProfileEngine (profile lifecycle state machine), ResourceEngine
    │   │                             (cross-domain claim arbitration), DevProfile, SecurityProfile,
    │   │                             WorkloadPassports, ActionReceipts, StateSnapshot
    │   ├── components/             Shared UI primitives (StyledText, MaterialIcon, StateLayer,
    │   │                            SettingsRow/SettingsGroup/SettingsToggleRow/SettingsPresetRow, Logo, ...)
    │   ├── modules/                bar/ (+ real popouts), notch/, launcher/, switcher/ (Alt+Tab),
    │   │                            dashboard/ (Command Center), flow/, settings/ (Control Center, 16 panes),
    │   │                            notifications/, notificationcenter/, osd/, lock/, session/,
    │   │                            intelligence/, negotiation/, wallpaperpicker/, background/,
    │   │                            workspace/, overlay/, areapicker/, colorpicker/, keybinds/, pkginstall/
    │   │                            -- plugin surfaces (Agent Graph and the rest) link in under
    │   │                            modules/plugins/ at install time, see below
    │   └── RUNTIME.md              The shell runtime contracts: RuntimeContext, Surfaces, ResourcePosture
    ├── hypr/                     hyprland.lua, keybinds.lua, custom.lua (never overwritten, see below)
    └── .local/
        ├── bin/aphotic            aphotic CLI entry point, symlinked onto PATH by install.sh
        └── lib/aphotic/           CLI internals: commands/ (one cmd_*.sh per subcommand),
                                     globalcontrol.sh, agent_hook.py/.sh (Claude Code hook worker),
                                     agent_usage.py (token-usage aggregation)

Installation system

install.sh is a thin orchestrator, not a script full of hardcoded package arrays. On the stable channel it first checks out the newest release tag. Run with no flags on a terminal, it walks through a four-step guided setup and one summary to accept; --opt-in is just the layer picker, and flags like --profile/--with/--theme skip the corresponding prompts. Either way it:

  • Resolves a package plan at runtime by merging profiles/base/<profile>.toml with each selected profiles/layers/<layer>.toml and profiles/custom_apps.lst, deduplicating as it goes (lib/toml/merge.py).
  • Detects an AUR helper (yay or paru), and builds yay if neither is present (lib/install/aur.sh).
  • Snapshots your current configs to a timestamped backup before touching anything (lib/install/backup.sh), unless --no-backup is passed.
  • Writes the resolved choice to aphotic.toml at the repo root, the source of truth every re-run reads back.
  • Copies Configs/ over ~/.config/, symlinks the parts that must track the checkout (most of hypr/, wallust/, matugen/), installs the greeter as the login screen, and restarts the shell.

install.sh never hardcodes a package list — everything downstream (backups, AUR helper choice, config copying) reads from that one resolved plan. --dry-run prints the whole plan and exits before any sudo prompt, package install, or filesystem write happens.

Config resolution order

  1. Base profile (profiles/base/<profile>.toml)
  2. Selected layers (profiles/layers/<layer>.toml)
  3. Custom apps (profiles/custom_apps.lst, still readable at the repo root as a symlink)

All three merge into one deduplicated plan at install time. See Profiles & Layers for what each profile/layer actually contains.

Backup system

Every install.sh run snapshots your current configuration first:

  • Stored under ~/.config-backup/ as timestamped directories.
  • --keep-backups <N> controls how many are retained before pruning (default 5).
  • Restored automatically by ./uninstall.sh, which reverses the most recent snapshot on request. Pass --purge-packages to also remove what your profile installed.

The separate aphotic backup CLI keeps its own manual snapshots under ~/.local/state/aphotic/backups/. See Installation → Backup System.

ai layer wiring

Selecting the ai layer enables the aphotic-agent-usage.timer systemd user unit (token-usage aggregation for the bar's agent icon) and installs Ollama, llmfit and Claude Code. It does not wire any harness's agent hooks anymore — as of the modular plugin architecture (v2.0.0), Claude Code/Codex/OpenCode hook wiring and Agent Graph are each their own opt-in plugin (claude-hooks/codex-hooks/opencode-hooks/agent-graph, installed via aphotic plugin install <name>), not something install.sh touches. See Agentic Workflows for those plugins' own contract, and Plugin System for how the plugin mechanism itself works.

Config sync only

--config-only skips the wizard, package installs, and aphotic.toml entirely — it just backs up, copies Configs/ over ~/.config/, and restarts the shell. Most day-to-day updates (Quickshell QML, Hyprland config, keybinds) have no package churn behind them, so this is the fast path. aphotic sync (and Settings → About's Sync button) runs it after a git pull; aphotic update does not, see Installation → Updating.

Configuration files

Configs/hypr/

  • hyprland.lua — main Hyprland configuration that loads the other Lua files.
  • keybinds.lua — all keybindings, grouped logically.
  • custom.lua — never overwritten by the installer once it exists. Put your own Hyprland tweaks here and a re-run or aphotic update won't clobber them. See Advanced Usage for the Lua bind syntax.

Configs/quickshell/aphotic/

The Quickshell shell is hand-vendored — it isn't an installed dependency, the QML lives directly in the repo, and no native C++ plugin is required to run it:

  • config/ — Tokens, Config, GlobalConfig singletons (hand-written, no native plugin).
  • services/ — Colours (reads the palette snapshot both colour engines write), Audio, Hypr, Players, Notifs, AgentProviders, InstallProfile, PluginRegistry, plus the ai/ subfolder backing AI Chat, Settings → AI and inference mode, and the profile/ subfolder (ProfileEngine, ResourceEngine, DevProfile, SecurityProfile, WorkloadPassports, StateSnapshot), among many others. The Gaming profile is a plugin, not core.
  • components/ — shared UI primitives (StyledText, MaterialIcon, StateLayer, the SettingsRow/SettingsGroup family, Logo, ...).
  • modules/ — one directory per surface: bar/, notch/, launcher/, switcher/, dashboard/ (Command Center), flow/, settings/ (Control Center), notifications/, lock/, session/ and more. Plugin surfaces link in under modules/plugins/<name> when a plugin installs.

Colours.qml reads the palette snapshot at ~/.local/state/aphotic/palette.json (written by wallust, or matugen for a theme that pins it); Quickshell doesn't bring its own theming engine. See Theming for the full pipeline.

Key design principles

Declarative over hardcoded

Package sets live as data (profiles/*.toml) rather than bash arrays buried in an install script. Changing what ships means editing a TOML file, not doing surgery on install.sh.

Composable, not monolithic

A base profile (minimal or full) plus any combination of layers (gaming, dev, ai, exploit and its sublayers) resolve into one merged package list at install time. Adding a layer doesn't require touching the base, and layers dedupe against each other automatically.

Safe to run twice

Every install.sh run snapshots configs before making changes. Re-running the installer detects your saved aphotic.toml and re-resolves profile/layers against upstream changes instead of asking the same questions again.

Reversible

./uninstall.sh restores your most recent backup on request — trying Aphotic doesn't require burning your current setup down first.

Protected customization

~/.config/hypr/custom.lua is the one file the installer never touches once it exists.

CLI integration

The aphotic CLI (Configs/.local/bin/aphotic, symlinked onto PATH by install.sh) is a single entry point over Configs/.local/lib/aphotic/commands/, one cmd_*.sh file per subcommand — the list is auto-discovered, so aphotic --help can't drift out of sync with what's actually implemented. Highlights:

  • Config: theme, scheme, wallpaper, bar, context, displaymanager, greeter, sddm, plugin, vpn, config
  • Core: agent, diff, doctor, perf, reload, runtime, shell, status
  • Lifecycle: backup, packages, reconcile, recovery, report, restore, rollback, safemode, sync, update, whatsnew
  • AI: ai
  • Fun: play

See CLI Reference for the full command list, and Theming for the theme/scheme/wallpaper subcommands specifically.

See also

Clone this wiki locally