{"id":34947967,"url":"https://github.com/program-the-brain-not-the-heartbeat/dotfiles","last_synced_at":"2026-04-12T11:39:50.822Z","repository":{"id":324552540,"uuid":"1097581582","full_name":"program-the-brain-not-the-heartbeat/dotfiles","owner":"program-the-brain-not-the-heartbeat","description":"🏡dotfiles - Organize the chaos","archived":false,"fork":false,"pushed_at":"2026-03-31T01:43:22.000Z","size":1276,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-31T04:50:56.181Z","etag":null,"topics":["bash","dotfiles","shell","ubuntu"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/program-the-brain-not-the-heartbeat.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-16T13:07:54.000Z","updated_at":"2026-03-31T01:43:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/program-the-brain-not-the-heartbeat/dotfiles","commit_stats":null,"previous_names":["program-the-brain-not-the-heartbeat/dotfiles"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/program-the-brain-not-the-heartbeat/dotfiles","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/program-the-brain-not-the-heartbeat%2Fdotfiles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/program-the-brain-not-the-heartbeat%2Fdotfiles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/program-the-brain-not-the-heartbeat%2Fdotfiles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/program-the-brain-not-the-heartbeat%2Fdotfiles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/program-the-brain-not-the-heartbeat","download_url":"https://codeload.github.com/program-the-brain-not-the-heartbeat/dotfiles/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/program-the-brain-not-the-heartbeat%2Fdotfiles/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31713876,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-12T06:22:27.080Z","status":"ssl_error","status_checked_at":"2026-04-12T06:21:52.710Z","response_time":58,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","dotfiles","shell","ubuntu"],"created_at":"2025-12-26T20:52:01.547Z","updated_at":"2026-04-12T11:39:50.816Z","avatar_url":"https://github.com/program-the-brain-not-the-heartbeat.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Modular Dotfiles System\n\nA robust, pure-Bash, symlink-based dotfiles system designed for modularity and role-based configuration across Linux, macOS, and WSL2.\n\n## Quickstart\n\nTo quickly download and install these dotfiles into a new server, copy/paste the following command:\n\n`bash \u003c(curl -fsSL https://raw.githubusercontent.com/program-the-brain-not-the-heartbeat/dotfiles/main/bootstrap.sh)`\n\nThe bootstrap command will clone the repo and run [install.sh](install.sh).\n\n## Platform setup\n\n### Ubuntu / Debian (bare metal or VM)\n\nPrerequisites: `git` and `curl`.\n\n```bash\nsudo apt update \u0026\u0026 sudo apt install -y git curl\nbash \u003c(curl -fsSL https://raw.githubusercontent.com/program-the-brain-not-the-heartbeat/dotfiles/main/bootstrap.sh)\n```\n\n### macOS\n\nRequires [Homebrew](https://brew.sh). Xcode Command Line Tools (includes `git`) will be prompted automatically.\n\n```bash\n/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\nbash \u003c(curl -fsSL https://raw.githubusercontent.com/program-the-brain-not-the-heartbeat/dotfiles/main/bootstrap.sh)\n```\n\n**Notes:**\n\n- `brew` is used as the package manager (`DOTFILES_PKG_MANAGER=brew`)\n- systemd features (timers, services) are skipped — `has_systemd` returns false\n- GNU coreutils are **not** required; the scripts use POSIX-compatible flags where possible\n\n### Windows 10/11 (WSL2)\n\nThese dotfiles run inside WSL2, not native Windows. Requires Windows 10 version 2004+ or Windows 11.\n\n**1. Install WSL2 with Ubuntu**\n\nOpen PowerShell as Administrator:\n\n```powershell\nwsl --install -d Ubuntu\n```\n\nRestart if prompted, then launch Ubuntu from the Start menu.\n\n**2. Enable systemd in WSL2**\n\nsystemd is required for timers and services (e.g. gmail-backup). Create or edit `/etc/wsl.conf` inside WSL:\n\n```bash\nsudo tee /etc/wsl.conf \u003e /dev/null \u003c\u003c 'EOF'\n[boot]\nsystemd=true\nEOF\n```\n\nThen restart WSL from PowerShell:\n\n```powershell\nwsl --shutdown\n```\n\nReopen Ubuntu and verify:\n\n```bash\nsystemctl --version   # should print the systemd version\n```\n\n**3. Install dotfiles**\n\n```bash\nsudo apt update \u0026\u0026 sudo apt install -y git curl\nbash \u003c(curl -fsSL https://raw.githubusercontent.com/program-the-brain-not-the-heartbeat/dotfiles/main/bootstrap.sh)\n```\n\n**WSL-specific notes:**\n\n- The installer auto-detects WSL and warns if systemd is not enabled\n- [sbin/](sbin/) scripts (disk mount/unmount/scrub) are not applicable in WSL\n- Paths use the Linux filesystem (`/home/user/`), not `/mnt/c/`; keep dotfiles on the Linux side for performance\n- `~/.bashrc.local` is the right place for WSL-specific PATH entries (e.g. adding `/mnt/c/Windows/System32` for interop)\n\n### Windows 10/11 (Native)\n\nFor native Windows machines, [bootstrap-windows.ps1](bootstrap-windows.ps1) automates the entire setup:\n- **Phase 1**: Installs WSL2 with Ubuntu (if not present)\n- **Phase 2**: Installs Windows apps via `winget` and boots the dotfiles installer inside WSL\n\n**1. Open PowerShell (not elevated initially — the script self-elevates)**\n\nThe script will request admin privileges when needed.\n\n**2. Set execution policy and run the bootstrap**\n\nPowerShell execution policies may prevent scripts from running. Download the script first, then bypass the policy for the current session:\n\n```powershell\nSet-ExecutionPolicy Bypass -Scope Process -Force\nirm https://raw.githubusercontent.com/program-the-brain-not-the-heartbeat/dotfiles/main/bootstrap-windows.ps1 -OutFile .\\bootstrap-windows.ps1\n.\\bootstrap-windows.ps1\n```\n\n\u003e **Note:** `-Scope Process` means the execution policy change lasts **only for the current PowerShell session** and doesn't modify system-wide settings. If you close and reopen PowerShell, the default policy is restored.\n\nThe two-step approach (download first, then execute) is safer and more reliable than piping through `iex`.\n\n**Alternative 1: Fully automated (no prompts, auto-reboot if needed)**\n\n```powershell\nSet-ExecutionPolicy Bypass -Scope Process -Force\nirm https://raw.githubusercontent.com/program-the-brain-not-the-heartbeat/dotfiles/main/bootstrap-windows.ps1 -OutFile .\\bootstrap-windows.ps1\n.\\bootstrap-windows.ps1 -AutoConfirm -Role workstation\n```\n\n**Alternative 2: Run in one line from Downloads folder**\n\n```powershell\nSet-ExecutionPolicy Bypass -Scope Process -Force; irm https://raw.githubusercontent.com/program-the-brain-not-the-heartbeat/dotfiles/main/bootstrap-windows.ps1 -OutFile $env:TEMP\\bootstrap-windows.ps1; \u0026 $env:TEMP\\bootstrap-windows.ps1\n```\n\n**Available parameters:**\n\n- `-Phase 1|2` — Run only phase 1 (WSL install) or phase 2 (apps + bootstrap defaults to phase 1)\n- `-SkipWsl` — Skip WSL installation (useful if WSL is already present)\n- `-SkipApps` — Skip winget app installation\n- `-SkipBootstrap` — Don't launch dotfiles bootstrap inside WSL\n- `-AutoConfirm` — Skip all confirmation prompts\n- `-Role base|personal|workstation|development-server|production-server` — Dotfiles role to use in WSL (defaults to `workstation`)\n\n**What happens**\n\n1. On first run, WSL2 + Ubuntu 24.04 are installed, and the machine reboots automatically\n2. After restart, the script resumes (Phase 2) to install Windows apps and optionally bootstrap dotfiles inside WSL\n3. Inside WSL, the standard Linux dotfiles bootstrap runs with your selected role\n\n**Windows execution policy explained**\n\nIf you encounter the error:\n```\nFile .\\bootstrap-windows.ps1 cannot be loaded because running scripts is disabled on this system.\n```\n\nThis is the PowerShell execution policy preventing the script from running. Solutions:\n\n- **For current session only (recommended):** `Set-ExecutionPolicy Bypass -Scope Process -Force` — changes are temporary\n- **Per-user (persistent):** `Set-ExecutionPolicy RemoteSigned -CurrentUser` — allows local scripts + signed remote scripts\n- **System-wide (all users):** `Set-ExecutionPolicy RemoteSigned` — requires admin, affects the entire machine\n\nThe bootstrap commands above use `-Scope Process`, which is safest and doesn't require persistent policy changes.\n\n### Platform compatibility matrix\n\n| Feature                    | Ubuntu/Debian | macOS | WSL2 (systemd on) | WSL2 (systemd off) | Windows native (via bootstrap-windows.ps1) |\n| -------------------------- | ------------- | ----- | ----------------- | ------------------ | ------------------------------------------- |\n| Bash config + aliases      | Yes           | Yes   | Yes               | Yes                | Yes (in WSL2)                               |\n| Git, SSH, tmux, nano, htop | Yes           | Yes   | Yes               | Yes                | Yes (in WSL2)                               |\n| Claude Code config         | Yes           | Yes   | Yes               | Yes                | Yes (in WSL2)                               |\n| systemd timers/services    | Yes           | No    | Yes               | No (skipped)       | Yes (in WSL2)                               |\n| sbin scripts (disk mgmt)   | Yes           | No    | No                | No                 | No (WSL2)                                   |\n| Windows apps (winget)      | —             | —     | —                 | —                  | Yes (Windows side)                          |\n\n## Troubleshooting\n\n### Windows: winget installation fails with 0x80073D02\n\n**Error:**\n```\nError code: Wsl/InstallDistro/Service/RegisterDistro/ERROR_ALREADY_EXISTS\nerror 0x80073D02: The package could not be installed because resources it modifies are currently in use.\n```\n\nThis occurs when `Microsoft.DesktopAppInstaller` is being updated or locked by running processes. **Solution:**\n\n**Option 1: Re-run with automatic retry (recommended)**\n\nThe updated bootstrap script automatically detects this error and retries up to 3 times with process cleanup. Simply run:\n\n```powershell\n.\\bootstrap-windows.ps1 -Phase 2\n```\n\n**Option 2: Manual process cleanup and retry**\n\nIf automatic retry doesn't work, manually close blocking processes:\n\n```powershell\n# Close apps that lock the installer\nStop-Process -Name \"Microsoft.DesktopAppInstaller\" -Force -ErrorAction SilentlyContinue\nStop-Process -Name \"msstore\" -Force -ErrorAction SilentlyContinue\nStop-Process -Name \"winget\" -Force -ErrorAction SilentlyContinue\n\n# Wait for cleanup\nStart-Sleep -Seconds 3\n\n# Retry bootstrap\n.\\bootstrap-windows.ps1 -Phase 2\n```\n\n**Option 3: Register winget manually**\n\nIf retries and process cleanup don't work, register winget directly:\n\n```powershell\nAdd-AppxPackage -RegisterByFamilyName -MainPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\n```\n\n**Option 4: Install via Microsoft Store**\n\nAs a final fallback, install **App Installer** from the [Microsoft Store](https://www.microsoft.com/store/productId/9NBLGGH4NNS1) manually, then re-run the bootstrap.\n\n---\n\n### Windows: PowerShell execution policy error\n\n**Error:**\n```\nFile .\\bootstrap-windows.ps1 cannot be loaded because running scripts is disabled on this system.\n```\n\n**Solution:**\n\nBypass the execution policy for the current session only (safe, temporary):\n\n```powershell\nSet-ExecutionPolicy Bypass -Scope Process -Force\n.\\bootstrap-windows.ps1\n```\n\nThis is already included in the download + run approach — no additional steps needed.\n\n---\n\n### Linux: `curl` or `git` not available\n\n**Error:**\n```\ncurl: command not found\n```\n\n**Solution:**\n\nInstall prerequisites before running the bootstrap:\n\n```bash\n# Ubuntu / Debian\nsudo apt update \u0026\u0026 sudo apt install -y git curl\n\n# CentOS / RHEL\nsudo yum install -y git curl\n\n# Fedora\nsudo dnf install -y git curl\n```\n\n---\n\n## Selecting a Role\n\nEvery machine selects exactly one role. The role controls which packages are installed, which bashrc extensions are loaded, and which systemd units are enabled.\n\nAvailable roles: `base`, `personal`, `workstation`, `development-server`, `production-server`.\n\n### Option 1 — Bootstrap (one-liner, recommended for fresh machines)\n\nPass `--role` directly to the bootstrap command. No interactive prompt will appear.\n\n```bash\nbash \u003c(curl -fsSL https://raw.githubusercontent.com/program-the-brain-not-the-heartbeat/dotfiles/main/bootstrap.sh) --role production-server\n```\n\n### Option 2 — Install flag (repo already cloned)\n\n```bash\nbash install.sh --role production-server\n```\n\n### Option 3 — Environment variable\n\n```bash\nDOTFILES_ROLE=production-server bash install.sh\n```\n\n### Option 4 — Pre-set without running install\n\nTo persist the role on a machine _before_ running the installer (e.g. to export it in an existing shell session or to pre-stage `~/.bashrc.local`), add it to `~/.bashrc.local`:\n\n```bash\necho 'export DOTFILES_ROLE=production-server' \u003e\u003e ~/.bashrc.local\n```\n\n`~/.bashrc.local` is loaded last by `.bashrc` and is gitignored — it is the correct place for any machine-specific overrides. The role set here will be picked up automatically on every subsequent shell start and by the installer if it is run afterwards without a `--role` flag.\n\n\u003e **Note:** There is no standalone \"set role only\" command. Options 1–3 all run the full install. Option 4 is the right approach when you want to stamp the role onto a machine without triggering a full install.\n\n## Post-install checklist\n\nSeveral tools are installed but cannot be fully configured automatically.\nAfter `install.sh` finishes, work through **[docs/post-install.md](docs/post-install.md)**.\n\nQuick summary of what needs manual follow-up:\n\n| Item                                         | All roles | `development-server` | `personal` |\n| -------------------------------------------- | :-------: | :------------------: | :--------: |\n| `tailscale up` (authenticate)                |    Yes    |         Yes          |    Yes     |\n| Distribute SSH public key to remote hosts    |    Yes    |         Yes          |    Yes     |\n| Git identity (`~/.gitconfig.local`)          |    Yes    |         Yes          |    Yes     |\n| Create `~/.bashrc.local` for local overrides |    Yes    |         Yes          |    Yes     |\n| Re-login to activate group memberships       |     —     |         Yes          |     —      |\n| `mysql_secure_installation`                  |     —     |         Yes          |     —      |\n| Nginx vhosts (`host-manage add`)             |     —     |         Yes          |     —      |\n| Certbot certificate provisioning             |     —     |         Yes          |     —      |\n| `stripe login`                               |     —     |         Yes          |     —      |\n| `gh auth login`                              |     —     |         Yes          |    Yes     |\n| Claude Code API key / `claude` auth          |     —     |         Yes          |     —      |\n| Populate `roles/personal/secrets.d/`         |     —     |          —           |    Yes     |\n| Windows manual-download tools (WSL)          |    WSL    |          —           |     —      |\n\nSee [docs/post-install.md](docs/post-install.md) for commands and details.\n\n## Architecture \u0026 Design\n\nThis system is built on the philosophy of **\"No Ansible, No Puppet—just Shell.\"** It prioritizes transparency, speed, and ease of maintenance.\n\n### Pure Bash \u0026 Symlink-based Strategy\n\nThe core of the system relies on standard Bash scripts and symbolic links. Configuration files are symlinked from the [config/](config/) directory to their respective XDG or home locations. This ensures that changes made in the repository are immediately reflected in the system.\n\n### Role-Based Configuration\n\nMachines are classified by a `DOTFILES_ROLE`, allowing for tailored configurations without bloat:\n\n- **base**: Minimum configuration required for all machines (Bash, core utilities).\n- **personal**: Extends base with private secrets and backup timers (e.g., Gmail backup).\n- **workstation**: Optimized for interactive use, including desktop keybindings and WSL integration.\n- **development-server**: Pre-configured with Node (NVM), PHP, and WP-CLI.\n- **production-server**: Hardened for safety with interactive safeguards and restricted permissions.\n\n### Helper-Driven Modularity\n\nThe [lib/install-helpers.sh](lib/install-helpers.sh) library provides a robust API for common tasks such as package management, symlinking, and OS detection. This modularity ensures the main [install.sh](install.sh) remains clean and readable.\n\n### Idempotent Execution\n\nEvery script is designed to be safe to re-run. The system checks for existing configurations, manages file backups with timestamps, and skips redundant steps, making upgrades as simple as running `bash install.sh`.\n\n## Key Features\n\n### Automated Desktop Configuration\n\nFor Ubuntu and Linux Mint environments, the system automatically configures desktop keybindings via `gsettings`. This includes setting `Super+L` for workstation locking and disabling conflicting defaults like Cinnamon's Looking Glass shortcut.\n\n### Git Identity \u0026 Editor Management\n\nThe installer includes an interactive Git configuration helper that sets up `~/.gitconfig.local`. It intelligently selects appropriate editors, defaulting to VS Code (`code --wait`) on desktop roles and native editors on servers, ensuring a consistent developer experience.\n\n### System Tuning\n\n- **Inotify Limits**: Automatically increases `fs.inotify` limits to support large-scale development projects and file watching.\n- **SSHFS Configuration**: Automated installation and setup of `sshfs`, including `SSHFS-Win` integration for WSL2 environments.\n- **Modern Paging**: Configures `batcat` as the default paging engine for `git`, `man` pages, and `systemd` logs where available.\n\n### Security \u0026 Hardening\n\nThe `production-server` role implements several safety measures:\n\n- **Interactive Safe-guards**: Aliases `rm`, `cp`, and `mv` to their interactive (`-i`) counterparts.\n- **Permission Enforcement**: Explicitly blocks `chmod 777` via a shell function wrapper.\n- **Restricted Pathing**: Enforces a strict, minimal `PATH` to prevent execution of unauthorized binaries in the shell.\n- **Secret Management**: Role-specific `secrets.d/` directories combined with `.env` files for secure deployment of non-committed credentials.\n\n### Claude Code Integration\n\nDeep integration for @anthropic-ai/claude-code, with automated installation via `npm`, JSONC-to-JSON rendering for [settings.json](config/claude/settings.json), and symlinked persistence of [keybindings.json](config/claude/keybindings.json).\n\n## Audit \u0026 Authenticity\n\nThis repository undergoes periodic manual audits to ensure it remains a \"trust-nothing\" system:\n\n- **Strict Bash Mode**: Every script uses `set -Eeuo pipefail` to ensure fast failure and predictable behavior.\n- **Dependency Minimalism**: No external runners or heavy runtimes are required; the system relies on standard POSIX tools and the native package manager.\n- **Secure Permissions**: The installer enforces `0700` or `0600` permissions on sensitive directories (like `.ssh` or `secrets.d`) and manages `/opt` structures with appropriate group access.\n- **Fast Failure**: The `die` helper ensures that if a critical step fails (like a failed sudo acquisition), the script terminates immediately rather than proceeding in an undefined state.\n\n## Development\n\n### Contributing\n\nContributions are welcome! Before submitting a PR:\n\n1. **Install code quality tools** (required for validating PRs)\n\n   ```bash\n   # Linux (Ubuntu/Debian)\n   sudo apt-get install -y shellcheck shfmt\n   \n   # macOS\n   brew install shellcheck shfmt\n   \n   # Python tools (all platforms)\n   pip install black flake8 mypy\n   ```\n\n2. **Install Task runner** (optional but recommended)\n\n   ```bash\n   # macOS\n   brew install go-task\n   \n   # Linux\n   curl -1sLf 'https://dl.cloudsmith.io/public/task/task/setup.deb.sh' | sudo -E bash\n   \n   # Windows\n   winget install Task.Task\n   ```\n\n3. **Validate code locally**\n\n   ```bash\n   # Check code quality (ShellCheck, shfmt, Black, flake8, mypy)\n   bash scripts/validate\n   \n   # Or using Task runner\n   task validate\n   \n   # Auto-fix formatting issues\n   bash scripts/validate --fix\n   # Or\n   task validate:fix\n   ```\n\n4. **Useful utility script examples**\n\n   ```bash\n   # Download and install all variants of a Google Font family\n   install-google-font \"Fira Code\"\n\n   # System-wide install (uses sudo)\n   install-google-font \"Inter\" --system\n   ```\n\n5. **Review contribution guidelines**\n\n   See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for detailed recommendations on:\n   - Code style and conventions\n   - Commit message format (conventional commits)\n   - PR process and expectations\n   - Testing recommendations\n\n## Gmail backup (offlineimap + encrypted snapshots)\n\nAutomated Gmail backup via OfflineIMAP sync + encrypted tar snapshots, managed by a systemd user timer. Part of the `personal` role.\n\n### How it works\n\n1. **Hourly**: `gmail-backup.timer` fires `gmail-backup.service`\n2. **Sync**: offlineimap incrementally syncs each account's IMAP to local Maildir\n3. **Snapshot** (daily): tar + AES-256-GCM encrypt the Maildir trees, prune old snapshots\n\n### Path contract\n\nThe service declares systemd managed directories. For user units these resolve to XDG paths:\n\n| Purpose               | Path                                    | Env var                    |\n| --------------------- | --------------------------------------- | -------------------------- |\n| Maildir trees         | `~/.local/share/gmail-backup/mail/`     | `$STATE_DIRECTORY`         |\n| Encrypted snapshots   | `~/.local/share/gmail-backup/archives/` | `$STATE_DIRECTORY`         |\n| Logs                  | `~/.local/state/log/gmail-backup/`      | `$LOGS_DIRECTORY`          |\n| Lock file             | `/run/user/$UID/gmail-backup/lock`      | `$RUNTIME_DIRECTORY`       |\n| Env overrides         | `~/.config/gmail-backup/env`            | `$CONFIGURATION_DIRECTORY` |\n| Encryption passphrase | `~/.config/gmail-backup/passphrase`     | `$CONFIGURATION_DIRECTORY` |\n\n### Setup after install\n\nAfter running `bash install.sh --role personal` (or `DOTFILES_ROLE=personal bash install.sh`), follow these steps:\n\n**1. Create the encryption passphrase**\n\n```bash\n# Generate a strong random passphrase\nopenssl rand -base64 48 \u003e ~/.config/gmail-backup/passphrase\nchmod 600 ~/.config/gmail-backup/passphrase\n```\n\nBack this passphrase up somewhere safe (password manager, offline USB). Without it, encrypted snapshots are unrecoverable.\n\n**2. Configure OfflineIMAP**\n\nCopy the example and edit it with your real account details:\n\n```bash\ncp config/.offlineimaprc.example ~/.offlineimaprc\nchmod 600 ~/.offlineimaprc\n$EDITOR ~/.offlineimaprc\n```\n\n**3. Create the environment file (optional)**\n\n```bash\ncat \u003e ~/.config/gmail-backup/env \u003c\u003c 'EOF'\n# Space-separated account names (must match [Account ...] in .offlineimaprc)\nACCOUNTS_ENV=\"account1 account2\"\nEOF\nchmod 600 ~/.config/gmail-backup/env\n```\n\n**4. Enable the timer**\n\nThe installer enables the timer automatically. Verify with `systemctl --user status gmail-backup.timer`.\n\n### Rotating your Google App Password\n\nofflineimap authenticates with a [Google App Password](https://myaccount.google.com/apppasswords), not your main Google account password. Changing your Google account password revokes all existing App Passwords automatically.\n\n**1. Generate a new App Password**\n\nIn your Google Account: Security → 2-Step Verification → App passwords. Create one named \"offlineimap\" (or similar) and copy the 16-character code.\n\n**2. Update `~/.offlineimaprc`**\n\nFind the `remotepass` line for the affected account and replace it with the new App Password:\n\n```bash\n$EDITOR ~/.offlineimaprc\n```\n\nIf you use `passwordeval` instead (e.g. reading from a secrets file), update that file rather than `.offlineimaprc` directly.\n\n**3. Update the dotfiles secrets copy**\n\nKeep `roles/personal/secrets.d/.offlineimaprc` in sync so the next `install.sh` run deploys the correct value:\n\n```bash\ncp ~/.offlineimaprc roles/personal/secrets.d/.offlineimaprc\n```\n\n**4. Run a manual test**\n\nConfirm offlineimap can authenticate before the next timer fires:\n\n```bash\nsystemctl --user start gmail-backup.service\njournalctl --user -u gmail-backup.service -f\n```\n\nA successful run ends with `=== Run end ===` in the journal.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprogram-the-brain-not-the-heartbeat%2Fdotfiles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprogram-the-brain-not-the-heartbeat%2Fdotfiles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprogram-the-brain-not-the-heartbeat%2Fdotfiles/lists"}