# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## What this project is STP (Sistema de Transferencia Personal) is a personal machine-setup automation tool. Running it on a fresh Ubuntu LTS install configures the machine to match the owner's environment: packages, PPAs, SSH keys, dotfiles, audio (PipeWire), video drivers, systemd services, and application configurations. Files (documents, media) are handled separately by Nextcloud and are out of scope. ## Key commands ```bash # First-time setup on a new machine bash <(curl -fsSL https://gitea.mateosaldain.uy/mateo/stp/raw/branch/main/bootstrap.sh) # Run all modules bash stp.sh # Run a single module bash stp.sh --module registry # Skip specific modules bash stp.sh --skip ssh # List modules in execution order bash stp.sh --list ``` ## Architecture **Execution flow:** `bootstrap.sh` → clone repo from Gitea → `stp.sh` → modules in the order defined by `config/modules` **`stp.sh`** reads module order from `config/modules` (plain text, one name per line, `#` comments — no `yq` required at this level), then runs each `modules/.sh` as a subprocess inheriting the exported `stpRoot` variable. **Modules** (`modules/.sh`) source `lib/log.sh`, `lib/utils.sh`, and optionally `lib/yaml.sh`. All modules are idempotent — they check current state before acting. Current modules in execution order: - `bootstrap` — installs `yq` and `age` (required by all other modules) - `ssh` — decrypts `secrets/sshKeys.tar.gz.age` with `age` and installs keys into `~/.ssh` - `registry` — reads `config/registry.yaml` and applies every entry (packages, dotfiles, services, drivers, audio) - `thunderbird` — installs Thunderbird (snap) and restores profile from `secrets/thunderbirdProfile.zip` - `claudeCode` — configures the Anthropic apt repository and installs `claude-code` - `easyEffects` — restores EasyEffects config and presets from `secrets/easyEffectsConfig.zip` - `wireplumber` — restores WirePlumber state (default audio device, Bluetooth profiles) from `secrets/wireplumberState.zip` - `cups` — restores CUPS printer definitions and PPD drivers from `secrets/cupsConfig.zip` **`modules/registry.sh`** — the main workhorse. Reads all entries from `config/registry.yaml` and dispatches each one to a handler based on its `type`. Supported types: `ppa`, `apt`, `snap`, `flatpak`, `dotfile`, `service`, `pipewire`, `video`, `docker`. **`config/registry.yaml`** — the single list of everything to configure on the machine. Adding a new entry is all that's needed to have STP apply it on the next run. Entries are idempotent — each handler checks current system state before acting. **`config/settings.yaml`** — machine-independent settings: Gitea host/user/repo coordinates, user identity (`name`, `email`, `username`), and dotfiles `backup` flag. **`lib/`** — shared helpers sourced by modules: - `log.sh` — `log::step`, `log::info`, `log::ok`, `log::warn`, `log::error`; internal emit via `log::emit` - `utils.sh` — `util::cmdExists`, `util::isAptInstalled`, `util::isSnapInstalled`, `util::isFlatpakInstalled`, `util::isYamlNull`, `util::confirm`, `util::requireSudo`, `util::keepSudoAlive`, `util::aptUpdateOnce` - `yaml.sh` — thin wrapper around `yq` v4: `yaml::get`, `yaml::getArray`, `yaml::has` **`dotfiles/`** — mirrors `$HOME/` structure. Files referenced by `type: dotfile` entries in the registry become symlinks in the real home directory. If a real file already exists at the destination, it is renamed to `${destination}.stpbackup` before the symlink is created. **`docker/`** — stores Docker configurations (Compose files, Dockerfiles, etc.) organized by service: `docker//`. Files are symlinked to the configured `destination` (default `~/docker/`). If `autostart: true`, `docker compose up -d` is run after deploying. **`secrets/`** — encrypted and configuration backups, all safe to commit: - `sshKeys.tar.gz.age` — `age --passphrase`-encrypted tar of `~/.ssh` - `thunderbirdProfile.zip` — Thunderbird profile (accounts, filters, extensions; excludes emails and cache) - `easyEffectsConfig.zip` — EasyEffects settings and output presets - `wireplumberState.zip` — WirePlumber state (default audio sink, Bluetooth device profiles, per-app volumes) - `cupsConfig.zip` — CUPS `printers.conf` and PPD driver files for all configured printers **`scripts/`** — capture scripts to run on the current machine before pushing: - `encryptSsh.sh` — encrypts `~/.ssh` into `secrets/sshKeys.tar.gz.age` - `thunderbird/capture.sh` — captures Thunderbird Snap profile into `secrets/thunderbirdProfile.zip` - `easyEffects/capture.sh` — captures EasyEffects Flatpak config into `secrets/easyEffectsConfig.zip` - `wireplumber/capture.sh` — captures WirePlumber state into `secrets/wireplumberState.zip` - `cups/capture.sh` — captures CUPS printer config into `secrets/cupsConfig.zip` (requires sudo) **`config/keys/`** — GPG public keys for third-party apt repositories: - `claude-code.asc` — Anthropic signing key for the Claude Code apt repository **`.claude/`** — Claude Code project configuration: - `settings.json` — allowed bash commands for this project - `style.md` — style guide (camelCase, Clean Code, no dry-run) - `commands/new-module.md` → `/new-module ` — scaffolds a new module - `commands/addEntry.md` → `/addEntry ` — adds an entry to the registry - `commands/encrypt-ssh.md` → `/encrypt-ssh` — guides SSH key encryption ## Currently saved configurations ### Packages (via registry) | ID | Type | Package | |---|---|---| | curl, wget, git, vim, htop, tree, unzip | apt | same as ID | | buildEssential | apt | build-essential | | virtualbox | apt | virtualbox | | dbeaverCe | snap | dbeaver-ce | | vscodium | flatpak | com.vscodium.codium | | filezilla | flatpak | org.filezillaproject.Filezilla | | angryIpScanner | flatpak | org.angryip.ipscan | | anydesk | flatpak | com.anydesk.Anydesk | | bitwarden | flatpak | com.bitwarden.desktop | | bottles | flatpak | com.usebottles.bottles | | bruno | flatpak | com.usebruno.Bruno | | nextcloudDesktop | flatpak | com.nextcloud.desktopclient.nextcloud | | easyEffects | flatpak | com.github.wwmm.easyeffects | | flatseal | flatpak | com.github.tchx84.Flatseal | | warehouse | flatpak | io.github.flattool.Warehouse | | freecad | flatpak | org.freecad.FreeCAD | | hidamari | flatpak | io.github.jeffshee.Hidamari | | inkscape | flatpak | org.inkscape.Inkscape | | libreoffice | flatpak | org.libreoffice.LibreOffice | | logseq | flatpak | com.logseq.Logseq | | obsStudio | flatpak | com.obsproject.Studio | | openshot | flatpak | org.openshot.OpenShot | ### Application configurations (via modules) | Module | Secret | What is restored | |---|---|---| | `ssh` | `secrets/sshKeys.tar.gz.age` | SSH keys with correct permissions | | `thunderbird` | `secrets/thunderbirdProfile.zip` | Accounts, filters, extensions (Snap; excludes emails) | | `claudeCode` | `config/keys/claude-code.asc` | Anthropic apt repo + claude-code package | | `easyEffects` | `secrets/easyEffectsConfig.zip` | Settings DB and output presets (HB-Flat, HB-Lite, HB-Mid, Heavy Bass) | | `wireplumber` | `secrets/wireplumberState.zip` | Default audio sink (Bluetooth headphones), A2DP profile, per-app volumes | | `cups` | `secrets/cupsConfig.zip` | 5 printers: Epson L8050 WiFi, Canon MP230, Samsung M2020, POS80, Cups PDF | ## Adding a new package Add an entry to `config/registry.yaml`. The registry handles: `ppa`, `apt`, `snap`, `flatpak`, `dotfile`, `service`, `pipewire`, `video`, `docker`. Use `/addEntry ` for guided entry creation. For complex setups that require steps beyond what the registry types support (GPG key import, profile restoration, custom installers), create a dedicated module with `/new-module `. ## Adding a new application configuration backup 1. Create `scripts//capture.sh` — reads from the app's data directory and writes a ZIP to `secrets/` 2. Create `modules/.sh` — checks if app is installed, checks if config already exists, restores from ZIP 3. Add `` to `config/modules` after `registry` 4. Run `bash scripts//capture.sh` on the current machine 5. Commit the generated file in `secrets/` Restoration is automatic on the next `stp.sh` run. All modules are idempotent — if the config already exists on the target machine, the restore is skipped. ## Updating a saved configuration Re-run the corresponding capture script and commit the updated file: ```bash bash scripts/thunderbird/capture.sh && git add secrets/thunderbirdProfile.zip bash scripts/easyEffects/capture.sh && git add secrets/easyEffectsConfig.zip bash scripts/wireplumber/capture.sh && git add secrets/wireplumberState.zip bash scripts/cups/capture.sh && git add secrets/cupsConfig.zip bash scripts/encryptSsh.sh && git add secrets/sshKeys.tar.gz.age ``` ## Restoring on a new machine ```bash # 1. Bootstrap (clones repo and runs all modules) bash <(curl -fsSL https://gitea.mateosaldain.uy/mateo/stp/raw/branch/main/bootstrap.sh) ``` The bootstrap runs all modules automatically. Each module skips gracefully if its secret is missing or if the configuration is already in place. **After WirePlumber restore**, restart the session manager to apply the Bluetooth device as default: ```bash systemctl --user restart wireplumber ``` ## Environment variables | Variable | Default | Effect | |---|---|---| | `stpDir` | `~/.stp` | Clone destination used by `bootstrap.sh` | | `stpLogFile` | `/tmp/stp-.log` | Path for the full execution log | ## Style guide (enforced on all scripts) See `.claude/style.md` for the full guide. Key rules: - **camelCase everywhere**: variables, functions, file names — no underscores, no hyphens in identifiers - **No abbreviations**: `packageName` not `pkg`, `serviceName` not `svc`, `sourceFile` not `src`; `temporaryDirectory` not `tempDir`, `sourceDirectory` not `sourceDir` - **Small functions with one responsibility**: extract any named block into a function - **Boolean functions named as questions**: `printersAreConfigured()`, `wireplumberStateExists()` - **No `--dry-run`**: the system is idempotent — it checks state before acting - **No `util::run` wrapper**: call commands directly - **Comments only for WHY**, never for WHAT - **Pre-increment `((++n))`** not post-increment `((n++))` — post-increment of 0 exits with code 1 under `set -e`