{"id":18557710,"url":"https://github.com/prydie/dotfiles","last_synced_at":"2026-04-07T09:32:26.671Z","repository":{"id":23590708,"uuid":"26959211","full_name":"prydie/dotfiles","owner":"prydie","description":"My tool belt at the press of a button","archived":false,"fork":false,"pushed_at":"2026-02-16T23:53:29.000Z","size":521,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-17T01:13:40.126Z","etag":null,"topics":["dotfiles","macos","neovim","ubuntu","zsh"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/prydie.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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}},"created_at":"2014-11-21T12:27:52.000Z","updated_at":"2026-02-16T23:53:32.000Z","dependencies_parsed_at":"2024-07-25T18:03:12.110Z","dependency_job_id":"329301fb-7958-4874-91a1-d3b16967d354","html_url":"https://github.com/prydie/dotfiles","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/prydie/dotfiles","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prydie%2Fdotfiles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prydie%2Fdotfiles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prydie%2Fdotfiles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prydie%2Fdotfiles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/prydie","download_url":"https://codeload.github.com/prydie/dotfiles/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prydie%2Fdotfiles/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31508061,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T03:10:19.677Z","status":"ssl_error","status_checked_at":"2026-04-07T03:10:13.982Z","response_time":105,"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":["dotfiles","macos","neovim","ubuntu","zsh"],"created_at":"2024-11-06T21:37:48.886Z","updated_at":"2026-04-07T09:32:26.661Z","avatar_url":"https://github.com/prydie.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Andrew Pryde's dotfiles\n\nPersonal dotfiles managed with [`rcm`](https://github.com/thoughtbot/rcm).\n\n## Scope\n\nThis repo contains shell/editor/tmux/git config and optional host bootstrap hooks.\n\n- Safe default: `make up` only links dotfiles.\n- Opt-in setup: choose a `PROFILE` to install packages/plugins/tools.\n\n## Requirements\n\n- Linux (Debian/Ubuntu recommended for bootstrap scripts)\n- `git`, `make`, `rcm` (`install.sh` bootstraps these on Debian/Ubuntu)\n- `uv` (installed automatically during package setup)\n\n## Quick start\n\nOne-line bootstrap (fresh Ubuntu):\n\n```bash\nsudo apt-get update \u0026\u0026 sudo apt-get install -y git make \u0026\u0026 git clone https://github.com/prydie/dotfiles.git ~/.dotfiles \u0026\u0026 cd ~/.dotfiles \u0026\u0026 make bootstrap PROFILE=full\n```\n\nIf you prefer SSH auth (recommended when you will push changes):\n\n```bash\nsudo apt-get update \u0026\u0026 sudo apt-get install -y git make \u0026\u0026 git clone git@github.com:prydie/dotfiles.git ~/.dotfiles \u0026\u0026 cd ~/.dotfiles \u0026\u0026 make bootstrap PROFILE=full\n```\n\nIf the repo is already cloned:\n\n```bash\n./install.sh\n```\n\nOr manually:\n\n```bash\nmake up\n```\n\n## Common commands\n\n```bash\nmake help\nmake up\nmake bootstrap\nmake setup\nmake refresh-dev\nmake gnome-prefs-save\nmake gnome-prefs-apply\nmake restic-backup-now\nmake restic-systemd-enable\nmake patching\nmake patching-full\n```\n\n## Home Assistant\n\n`bin/ha` is the general Home Assistant CLI for reading entity state, searching\nentities, calling services, and fetching dashboard config.\n\n`bin/ha-dashboard` updates Lovelace dashboards through the Home Assistant websocket\nAPI instead of editing `.storage` files and restarting Home Assistant.\n\nIt expects:\n\n- `HOME_ASSISTANT_TOKEN` to contain a long-lived access token\n- `HOME_ASSISTANT_URL` to point at your instance\n\nExamples:\n\n```bash\nuv run bin/ha state sensor.battery_soc\nuv run bin/ha attrs media_player.downstairs\nuv run bin/ha search predbat\nuv run bin/ha dashboard get-config --pretty\n```\n\n```bash\nuv run bin/ha-dashboard get-config --pretty\n```\n\n```bash\nuv run bin/ha-dashboard upsert-card \\\n  --view-title Home \\\n  --title \"Home NAS\" \\\n  --after-title \"Predbat\" \\\n  --card-file /tmp/home-nas-card.json\n```\n\n## Setup profiles\n\n`hooks/post-up` is profile-driven. By default, `PROFILE=link`, which does not install host packages.\n\n### Controls\n\n- `PROFILE=link|core|dev|full`\n- `SHELL_DEFAULT=1|0` (default `1`; set `0` to skip login shell update)\n- `NODE_VERSION=\u003cversion\u003e` (default `lts/*`)\n- `GO_MIN_VERSION=\u003cversion\u003e` (optional; default `1.23.0`)\n- `GO_VERSION=\u003cversion\u003e` (optional; pin the exact Go release to install locally, e.g. `1.23.12`)\n- `GOLANGCI_LINT_VERSION=\u003ctag\u003e` (optional; default `v2.8.0`)\n- `GPLS_VERSION=\u003cversion\u003e` (optional; default `latest`)\n\n### What gets configured\n\n- Python tooling via `uv`.\n- Node tooling via `nvm`.\n- `PROFILE=core|dev|full` bootstraps zplug plugins and TPM.\n- `PROFILE=dev|full` installs pinned Neovim from [config/mise/config.toml](/home/andrew/.dotfiles/config/mise/config.toml) and runs headless lazy.nvim sync.\n\n### Profiles\n\n- `link`: no host package/tool installs; dotfiles only.\n- `core`: infra/network baseline (`tailscale`, `cloudflared`, `openconnect`, `wireguard-tools`, `nmap`, `tcpdump`, `dnsutils`, `jq`, `yq`, `traceroute`, `ufw`, `rsync`, `restic`, `rclone`) and Docker Compose v2 (`docker compose`).\n- `core` also installs `mise`, then installs Atuin, Starship, `kubectl`, and `vale` from [config/mise/config.toml](/home/andrew/.dotfiles/config/mise/config.toml), bootstraps `FiraCode Nerd Font` into `~/.local/share/fonts/NerdFonts/FiraCode` (override with `NERD_FONT_NAME` / `NERD_FONT_VERSION`), and configures GNOME Terminal default font to `FiraCode Nerd Font Mono 11` (override with `TERMINAL_FONT_SPEC`).\n- Vale uses the repo-managed global config at [config/vale/vale.ini](/home/andrew/.dotfiles/config/vale/vale.ini), with the `general` profile: `Vale + write-good + alex`.\n- `core` writes Zsh completions for `kubectl` to `${XDG_DATA_HOME:-$HOME/.local/share}/zsh/site-functions/_kubectl`.\n- `PROFILE=dev|full` also installs Neovim, Helm, `kubebuilder`, `doctl`, `awscli`, `esptool`, `black`, `isort`, `mypy`, and `ruff` from [config/mise/config.toml](/home/andrew/.dotfiles/config/mise/config.toml), installs Go developer tools via `go install`, and writes Zsh completions for `kubectl`, `kubebuilder`, and Helm.\n- `dev`: `core` + developer tools such as `ansible`, `go`, `tofu` (OpenTofu), `doctl`, `aws`, `hugo`, `picocom`, Codex CLI (`@openai/codex`), Gemini CLI (`@google/gemini-cli`), and ESP tooling (`esptool` + `idf.py` bootstrap), plus Neovim bootstrap.\n- Go is installed from the official tarball into `~/.local/opt/go` with `~/.local/bin/go` symlinked ahead of system Go, so the repo can enforce a minimum version.\n- `full`: `dev` + heavier extras such as `nerdctl`, `regctl`, `vegeta`, `oci-cli`, `autopep8`, and YubiKey tooling from [hooks/os](/home/andrew/.dotfiles/hooks/os).\n\nUse OpenTofu (`tofu`) instead of Terraform.\n\n### Examples\n\n```bash\n# dotfiles + selected setup profile\nmake setup PROFILE=link\nmake setup PROFILE=core\nmake setup PROFILE=dev\nmake setup PROFILE=full\n\n# bootstrap host dependencies, then run selected setup profile\nmake bootstrap PROFILE=dev\nmake bootstrap PROFILE=full\n\n# override Node channel and shell default behavior\nNODE_VERSION=lts/* SHELL_DEFAULT=0 make setup PROFILE=dev\n```\n\n## GNOME extension preferences\n\nClipboard Indicator and Caffeine preferences can be versioned in this repo:\n\n```bash\nmake gnome-prefs-save\nmake gnome-prefs-apply\n```\n\nThis stores/loads:\n\n- `config/gnome/clipboard-indicator.dconf`\n- `config/gnome/caffeine.dconf`\n\n## Kubernetes installers (no `curl | bash`)\n\nFast-moving CLI tools are managed centrally via [config/mise/config.toml](/home/andrew/.dotfiles/config/mise/config.toml). Update versions there, then run:\n\n```bash\nmise install\n```\n\nManual Kubernetes installers remain available for `k3d` only:\n\n```bash\nsource hooks/kubernetes\nkube::install_k3d\n```\n\n`kubectl` and `vale` are installed automatically by `make setup PROFILE=core|dev|full`.\nHelm, `kubebuilder`, `awscli`, `esptool`, `black`, `isort`, `mypy`, and `ruff` are installed automatically by `make setup PROFILE=dev|full`.\n`oci-cli` and `autopep8` are installed automatically by the `full` profile extras from the same mise config.\nThe repo-managed [config/mise/config.toml](/home/andrew/.dotfiles/config/mise/config.toml) is linked to `~/.config/mise/config.toml`, and the shell/hooks also export `MISE_GLOBAL_CONFIG_FILE` as a fallback when needed.\nVale's global config lives at `~/.config/vale/vale.ini` and setup runs `vale sync` to install the configured style packages.\n\n## Desk light helper (Home Assistant + tray icon)\n\n`$HOME/bin/desk-light-helper` can auto-toggle your desk LED strip when all are true:\n\n- your laptop appears docked (via configurable `lsusb` regex)\n- Home Assistant is reachable\n\nRepo-managed config lives at `config/desk-light-helper/config.toml` and defaults to:\n\n- HA URL: `https://hass.prydie.co.uk/`\n- light entity: `light.office_player_sk6812_light`\n- HA token from env var `HOME_ASSISTANT_TOKEN`\n\nSetup:\n\n```bash\nmake up\nexport HOME_ASSISTANT_TOKEN='your-long-lived-access-token'\n$HOME/bin/desk-light-helper\n```\n\nUse `$HOME/bin/desk-light-helper` for manual runs if `~/bin` is not on `PATH`.\nUse tray menu options to switch between automatic mode and manual force on/off.\nLogs are emitted to stderr/stdout so they are visible in terminal runs and in\n`journalctl` when run via systemd.\n\nRecommended startup via user service:\n\n1. Ensure `HOME_ASSISTANT_TOKEN` is exported in `~/.zshrc.local`\n   (or set it in `~/.config/desk-light-helper/env`).\n2. Enable and start:\n   `systemctl --user daemon-reload`\n   `systemctl --user enable --now desk-light-helper.service`\n3. Follow logs:\n   `journalctl --user -u desk-light-helper.service -f`\n\n## LAN device discovery (router API)\n\n`$HOME/bin/lan-discover` reads device inventory directly from a Sagemcom router API instead of probing the LAN.\n\nThe current implementation is router-first and uses:\n\n- `POST /api/v1/login`\n- `GET /api/v1/hosts`\n- `GET /api/v1/dhcp/clients`\n- optional `GET /api/v1/hosts/arp_table`\n\nKeep credentials out of this repo. Store them in a local config file or environment variables instead:\n\n```toml\n# ~/.config/lan-discover/config.toml\n[router]\nurl = \"https://192.168.1.1\"\nusername = \"admin\"\npassword_env = \"LAN_DISCOVER_ROUTER_PASSWORD\"\n```\n\nThen export the password outside the repo, for example in `~/.zshrc.local`:\n\n```bash\nexport LAN_DISCOVER_ROUTER_PASSWORD='your-router-password'\n```\n\nUsage:\n\n```bash\n$HOME/bin/lan-discover\n$HOME/bin/lan-discover --active-only\n$HOME/bin/lan-discover --include-arp\n$HOME/bin/lan-discover --json\n```\n\nNotes:\n\n- TLS verification is off by default because local router certs are commonly self-signed.\n- The script keeps the router session cookie in memory only.\n- `hosts` is treated as the primary inventory; `dhcp/clients` is merged in for reservation and naming data.\n- DHCP reservations are preserved separately from current live IPs.\n\n## Router Prometheus exporter\n\n`$HOME/bin/router-metrics-exporter` exposes a small Prometheus endpoint for the Sagemcom/YouFibre router.\n\nIt currently exports:\n\n- WAN up/down state\n- WAN RX/TX packets, bytes, errors, discards\n- Per-interface counters and link state from `lan/stats`\n- Session count\n- Network info labels (public IPv4/IPv6, gateways, MAC)\n- Host inventory counts\n- Host inventory grouped by link/type/device type\n- DHCP reservation counts\n\nIt uses the same local-only credentials pattern as `lan-discover`; do not commit secrets.\n\nAuthentication notes:\n\n- The exporter mirrors the router web UI login flow rather than posting the raw password directly.\n- It first calls `/api/v1/login-params`, then derives the challenge response expected by `/api/v1/login`.\n- Login attempts back off exponentially on HTTP `429` so a rate-limited router is not hammered every scrape.\n\nOne-shot test:\n\n```bash\nROUTER_EXPORTER_USERNAME=admin \\\nROUTER_EXPORTER_PASSWORD='your-router-password' \\\n$HOME/bin/router-metrics-exporter --once\n```\n\nServer mode:\n\n```bash\nROUTER_EXPORTER_USERNAME=admin \\\nROUTER_EXPORTER_PASSWORD='your-router-password' \\\n$HOME/bin/router-metrics-exporter\n```\n\nThe default listener is `0.0.0.0:9787` and the default path is `/metrics`.\n\nThe default metric set is intentionally low-cardinality for long-term Prometheus use; it exports host counts and grouped summaries rather than one time series per device.\n\nThe script is also structured to be imported if needed:\n\n- Shared API logic lives in the local `tools/router/router_api/` package and is used by both `lan-discover` and the exporter.\n- The import surface is re-exported from `tools/router/router_api/__init__.py`, with the implementation in `tools/router/router_api/client.py`.\n- `RouterClient` can be reused by other local tools that need authenticated API access\n- `render_metrics()` can be called directly if you want to expose the same metric set from another entrypoint\n- The module has no side effects on import; network access only starts when the client is used\n\nLogging:\n\n- The exporter now uses Python's standard logging module instead of ad-hoc `print()` calls.\n- Set `ROUTER_EXPORTER_LOG_LEVEL` (or pass `--log-level`) to control verbosity. The default is `INFO`.\n\nA minimal container build is included at `tools/router/container/` with:\n\n- `tools/router/container/Dockerfile`\n- `tools/router/container/docker-compose.example.yml`\n\nPrometheus scrape example:\n\n```yaml\n- job_name: 'router'\n  metrics_path: /metrics\n  static_configs:\n    - targets: ['router-metrics-exporter:9787']\n```\n\n## Backups to Synology NAS (Restic)\n\nThis repo now includes a repo-managed `restic` wrapper + user `systemd` timers so both desktop and laptop can use the same backup workflow while keeping NAS credentials local.\n\n### What is versioned vs local\n\n- Versioned in dotfiles:\n  - backup command wrapper: `bin/restic-backup`\n  - verification wrapper: `bin/restic-verify`\n  - workstation bootstrap helper: `bin/restic-workstation-setup`\n  - timer/service units: `config/systemd/user/restic-*.{service,timer}`\n- default include/exclude lists: `config/restic-backup/paths.txt`, `config/restic-backup/excludes.txt`\n- Local per machine (not in git):\n  - `~/.config/restic-backup/env`\n  - `~/.config/restic-backup/password`\n  - optional host/local overrides (`paths.local.txt`, `paths.\u003chostname\u003e.txt`, etc.)\n\n### Setup (each machine)\n\n1. Link dotfiles and ensure `restic` is installed (included in `PROFILE=core|dev|full`).\n2. Bootstrap local workstation config (recommended):\n\n```bash\n./bin/restic-workstation-setup --write-ssh-config\n```\n\nThis generates a dedicated SSH key (`~/.ssh/id_ed25519_home_nas_backup`), creates local `restic` config/password files, and adds a local `home-nas-backup` SSH alias block.\n\nIf you prefer to avoid modifying `~/.ssh/config`, omit `--write-ssh-config` and use the printed SSH snippet/manual setup.\n\n3. (Manual alternative) Create local config files:\n\n```bash\nmkdir -p ~/.config/restic-backup\ncp ~/.config/restic-backup/env.example ~/.config/restic-backup/env\nchmod 600 ~/.config/restic-backup/env\nprintf 'choose-a-strong-restic-password\\n' \u003e ~/.config/restic-backup/password\nchmod 600 ~/.config/restic-backup/password\n```\n\n4. Edit `~/.config/restic-backup/env` and set `RESTIC_REPOSITORY`.\n   Examples:\n   - `sftp:backup@nas.lan:/volume1/restic/workstations` (Synology SSH/SFTP)\n   - `sftp:backup@home-nas-backup:/volume1/Backups/restic/workstations` (recommended with local SSH alias)\n   - `/mnt/nas-backups/restic/workstations` (mounted SMB/NFS share)\n5. Review backup scope in `config/restic-backup/paths.txt` and `config/restic-backup/excludes.txt`.\n   Default is now `~` (whole home) with broad cache/build/VCS excludes.\n6. Initialize the repo once (from one machine):\n\n```bash\n~/bin/restic-backup init\n```\n\n7. Test and enable timers:\n\n```bash\nmake restic-backup-dry-run\nmake restic-backup-now\nmake restic-verify-now\nmake restic-systemd-enable\n```\n\n### Operations\n\n```bash\nmake restic-snapshots\nmake restic-maintenance-now\nmake restic-verify-now\njournalctl --user -u restic-verify.service -f\njournalctl --user -u restic-backup.service -f\n```\n\n### Notes\n\n- The wrapper auto-loads shared + host-specific include/exclude files:\n  - `paths.txt`, `paths.\u003chostname\u003e.txt`, `paths.local.txt`\n  - `excludes.txt`, `excludes.\u003chostname\u003e.txt`, `excludes.local.txt`\n- Missing paths are skipped with a warning (useful when desktop/laptop differ).\n- Defaults back up `~` and exclude `.git` plus common cache/build directories to keep backups focused on durable data/state.\n- Use `excludes.\u003chostname\u003e.txt` or `excludes.local.txt` for machine-specific heavy paths you do not care about (for example VM images, game libraries, media scratch dirs).\n- Verification is intentionally laptop-friendly by default: snapshot freshness warns at 7 days and goes critical at 30 days (`bin/restic-verify`).\n\n### Optional Verification + Home Assistant Webhook\n\nAdd these to `~/.config/restic-backup/env` if you want freshness status pushed to Home Assistant (or any webhook-compatible receiver):\n\n```bash\nRESTIC_VERIFY_WARN_AGE_HOURS=168   # 7 days\nRESTIC_VERIFY_CRIT_AGE_HOURS=720   # 30 days\nRESTIC_HA_WEBHOOK_URL=\"https://homeassistant.example.com/api/webhook/your-webhook-id\"\n```\n\nThe weekly `restic-verify.timer` posts JSON status after each freshness check when `RESTIC_HA_WEBHOOK_URL` is set.\n\n## Safety notes\n\n- Destructive cleanup is guarded and requires `ALLOW_DESTRUCTIVE=1`.\n- Host package/tool installation is opt-in, never implicit.\n- `hooks/os` no longer auto-runs installers when sourced.\n\n## Inspiration\n\n- https://github.com/nicksp/dotfiles\n- https://github.com/thoughtbot/dotfiles\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprydie%2Fdotfiles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprydie%2Fdotfiles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprydie%2Fdotfiles/lists"}