Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .chezmoi.toml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,14 @@
{{- $tags := $profileData.tags -}}
{{- $wantsDecrypt := or (get $profileData "decrypt") false -}}
{{- $work := or (get $profileData "work") false -}}
{{- $backup := or (get $profileData "backup") false -}}
{{- $de := "" -}}
{{- if hasKey $profileData "de" -}}{{- $de = $profileData.de -}}
{{- end -}}

{{- /* Enable encryption when the profile declares it. The run_before decrypt
script ensures the key exists (via env var, 1Password, or manual placement)
before chezmoi processes any encrypted files. */ -}}
{{- /* Enable encryption when the profile declares it. run_before_01-decrypt ensures the
age key exists (via env var, 1Password, or manual placement) before chezmoi
processes any encrypted files. */ -}}

{{ if $wantsDecrypt -}}
encryption = "age"
Expand All @@ -63,6 +64,7 @@ encryption = "age"
tags = "{{ $tags }}"
work = {{ $work }}
decrypt = {{ $wantsDecrypt }}
backup = {{ $backup }}
de = "{{ $de }}"
{{ if $wantsDecrypt }}
[age]
Expand Down
13 changes: 13 additions & 0 deletions .chezmoidata/packages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,19 @@ packages:
desc: "Multipurpose relay for bidirectional data transfer"
brew: false

# restic/resticprofile install ONLY on profiles with `backup: true` (cascade-filter
# backup gate) — keeps servers/containers/Macs (Time Machine) free of the backup stack.
restic:
tags: [core]
desc: "Fast, secure, deduplicating backup program"
backup: true

resticprofile:
tags: [core]
desc: "Configuration profiles and scheduler for restic"
os: linux
backup: true

# ===========================================================================
# macOS GNU Tools
# ===========================================================================
Expand Down
2 changes: 2 additions & 0 deletions .chezmoidata/profiles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# brew/pacman/apt/dnf/rpm_ostree/flatpak/appimage: which package managers to enable
# work: work machine (system SSH agent, corporate configs)
# decrypt: enable age decryption of private configs
# backup: personal machine — install+schedule restic backups (default false; servers/containers stay off)
#
# Profile is selected interactively during `chezmoi init`, or via:
# DOTFILES_PROFILE=arch chezmoi init
Expand Down Expand Up @@ -40,6 +41,7 @@ profiles:
flatpak: true
appimage: true
decrypt: true
backup: true # personal machine — runs restic backups to the homelab rest-server

# --- Debian/Ubuntu ---
debian-server:
Expand Down
8 changes: 8 additions & 0 deletions .chezmoiignore.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ tests/
**/*.age
{{ end }}

{{ if not .backup }}
# restic backups only run on personal machines (profile backup: true).
# Skipping the whole config dir keeps the decrypt() in the profile template from
# even running on servers/containers (which lack the age key).
.config/resticprofile
.config/resticprofile/**
Comment on lines +30 to +31
{{ end }}

{{ if ne .profile "devpod" }}
# Devpod-only tooling
dot_local/bin/executable_devpod-linuxbrew-fetch
Expand Down
9 changes: 5 additions & 4 deletions .chezmoitemplates/cascade-filter
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
absent = no restriction (installs on all profiles)
string = only on profiles where de matches (e.g., hyprland-omarchy)

work/decrypt fields are tri-state:
work/decrypt/backup fields are tri-state:
absent = no restriction (installs on all profiles)
true = only on profiles where work/decrypt is true
false = only on profiles where work/decrypt is false
true = only on profiles where work/decrypt/backup is true
false = only on profiles where work/decrypt/backup is false

Parameters (via dict):
.root - root template context ($)
Expand All @@ -50,6 +50,7 @@
{{- $de_ok := or (not (hasKey $pkg "de")) (eq (default "" $pkg.de) .root.de) -}}
{{- $work_ok := or (not (hasKey $pkg "work")) (eq $pkg.work .root.work) -}}
{{- $decrypt_ok := or (not (hasKey $pkg "decrypt")) (eq $pkg.decrypt .root.decrypt) -}}
{{- $backup_ok := or (not (hasKey $pkg "backup")) (eq $pkg.backup .root.backup) -}}

{{- /* Opt-in managers: require their field to exist and be truthy */ -}}
{{- $field_ok := true -}}
Expand Down Expand Up @@ -81,7 +82,7 @@
{{- else if $avail_appimage -}}{{- $winner = "appimage" -}}
{{- end -}}

{{- if and $enabled $tags_ok $os_ok $de_ok $work_ok $decrypt_ok $field_ok (eq $winner $mgr) -}}
{{- if and $enabled $tags_ok $os_ok $de_ok $work_ok $decrypt_ok $backup_ok $field_ok (eq $winner $mgr) -}}
{{- $resolved := $display -}}
{{- if and (eq $mgr "brew") (hasKey $pkg "brew") -}}
{{- $resolved = index $pkg "brew" -}}
Expand Down
12 changes: 12 additions & 0 deletions .chezmoitemplates/op-cached-secret
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{{- /* Fetch a secret from 1Password ONCE, then serve it from an on-disk cache. Generic —
usable for any cached op secret, not just restic. Returns the cache file's contents when it
exists and is non-empty, otherwise reads from 1Password (the caller writes the result to the
same path, so the next render is an offline cache hit; op is only hit when the cache is missing).
Args (list, positional):
0 cache path — absolute, or relative to $HOME (the partial prepends $HOME)
1 full op:// reference */ -}}
{{- $p := index . 0 -}}
{{- $dest := $p -}}
{{- if not (hasPrefix "/" $p) -}}{{- $dest = printf "%s/%s" (env "HOME") $p -}}{{- end -}}
{{- $s := stat $dest -}}
{{- if and $s (gt $s.size 0) -}}{{- output "cat" $dest -}}{{- else -}}{{- onepasswordRead (index . 1) -}}{{- end -}}
31 changes: 22 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,19 @@ Each profile (defined in `.chezmoidata/profiles.yaml`) fully specifies:
- **Package managers**: `brew`, `pacman`, `apt`, `dnf`, `rpm_ostree`, `flatpak`
- **`work`**: work machine (system SSH agent, corporate configs)
- **`decrypt`**: enable age decryption of private configs
- **`backup`**: personal machine — install the restic stack and schedule backups (default off; servers/containers/Macs stay off, Macs use Time Machine)

If no profile is specified, auto-detects from distro (macOS → `macos-work`, Arch → `arch-desktop`, etc.).

**Available profiles:**

| Profile | Tags | Pkg Managers | Work | Decrypt | Description |
|---|---|---|---|---|---|
| `macos-work` | core, container, dev, ai, ui, ui-extra | brew | yes | yes | Work Mac — corporate dev + GUI |
| `arch-desktop` | core, container, dev, ai, hardware, ui, ui-extra, gaming | pacman, flatpak, appimage | | yes | Arch Linux desktop |
| `debian-server` | core | apt | | | Debian/Ubuntu server — CLI only |
| `devpod` | core | brew | yes | no | Debian/Ubuntu dev |
| `silverblue` | core, container, dev, ai, hardware, ui, ui-extra, gaming | brew, rpm_ostree, flatpak, appimage | | yes | Silverblue/Bazzite immutable |
| Profile | Tags | Pkg Managers | Work | Decrypt | Backup | Description |
|---|---|---|---|---|---|---|
| `macos-work` | core, container, dev, ai, ui, ui-extra | brew | yes | yes | | Work Mac — corporate dev + GUI (Time Machine) |
| `arch-desktop` | core, container, dev, ai, hardware, ui, ui-extra, gaming | pacman, flatpak, appimage | | yes | yes | Arch Linux desktop |
| `debian-server` | core | apt | | | | Debian/Ubuntu server — CLI only |
| `devpod` | core | brew | yes | no | | Debian/Ubuntu dev |
| `silverblue` | core, container, dev, ai, hardware, ui, ui-extra, gaming | brew, rpm_ostree, flatpak, appimage | | yes | | Silverblue/Bazzite immutable |

**Package categories:**
- **core** (all machines): Shell (fish, atuin, zoxide), modern CLI tools (eza, bat, fd, ripgrep, etc.), git, neovim, tmux, essential utils
Expand Down Expand Up @@ -123,12 +124,13 @@ Scripts use category-based numeric prefixes with gaps for future expansion:
40-49 Custom binaries (e.g. THPM bootstrap)
50-59 Tool configuration (e.g. hyprpm plugin sync)
60-69 Shell configuration
70+ Future custom
70-79 Backup & data services (e.g. restic/resticprofile timers)
80+ Future custom
```

| Script | Description |
|---|---|
| `run_before_00-decrypt.sh.tmpl` | Ensures age key exists (1Password or manual) |
| `run_before_01-decrypt.sh.tmpl` | Ensures the age key exists (env/1Password/manual) before chezmoi decrypts; restic secrets are self-caching templates (`op-cached-secret` partial) |
| `run_onchange_00-setup-directories.sh` | Creates required dirs (~/.ssh/sockets, etc.) |
| `run_onchange_10-install-packages-homebrew.sh.tmpl` | Homebrew formulas (+ Homebrew install on Linux) |
| `run_onchange_11-install-packages-cask.sh.tmpl` | Homebrew casks (macOS only) |
Expand All @@ -142,6 +144,17 @@ Scripts use category-based numeric prefixes with gaps for future expansion:
| `run_onchange_50-configure-hyprpm.sh.tmpl` | Adds hyprpm repos and enables declared plugins; omarchy-only |
| `run_onchange_60-install-fisher.sh.tmpl` | Installs Fisher and Fish plugins |
| `run_onchange_61-configure-tide.sh.tmpl` | Configures Tide prompt |
| `run_onchange_70-configure-restic.sh.tmpl` | Registers resticprofile systemd timers (user `default` + root `system`/etc); `backup` profiles only |

### Backups (restic + resticprofile)

`backup: true` profiles run [resticprofile](https://creativeprojects.github.io/resticprofile/) against a REST server on the homelab NAS (`--append-only`, per-host repo `ty/<host>`). Config: `dot_config/resticprofile/private_profiles.yaml.tmpl`.

- **Two profiles**: `default` (user `$HOME` + a staged manifest of system state → user-scoped `user_logged_on` timers) and `system` (`/etc` as root → a root timer; needs one sudo escalation, see below).
- **Secrets** (never committed): repo encryption key + REST transport key, fetched from `op://dev-keys/restic-<host>/*` once via the `op-cached-secret` partial, then served from on-disk cache (`~/.config/resticprofile/{password,rest-pass}`).
- **Manifest**: `default` stages package/toolchain lists (pacman, AUR, flatpak, AppImage, brew, cargo, npm, pipx, uv, mise, go) and enabled systemd units into `~/.local/state/restic-system/`, captured by the `$HOME` backup — a record of system state without a root timer.
- **Excludes** keep the repo lean (verified ~39 GiB of a 4.9 TB home): caches, container/VM images, toolchain registries, build outputs, Steam installs (saves kept), local `~/Backups`. No `retention` block — pruning is server-side (clients can't forget against `--append-only`).
- **Root `system` timer**: a non-root `chezmoi apply` cannot create system timers. `run_onchange_70` registers it via `sudo -n` if passwordless sudo is available, otherwise it prints the one `sudo … --name system schedule` command to run by hand. The user `default` timers always register without sudo.

## Tests

Expand Down
23 changes: 10 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,15 @@ This will:

## Profiles

Each profile fully specifies a machine setup: tier, package managers, work mode, and decryption.

| Profile | Tier | Description |
|---------|------|-------------|
| `macos-personal` | 3 | Personal Mac — full dev + GUI apps |
| `macos-work` | 3 | Work Mac — corporate dev + GUI apps |
| `arch` | 3 | Arch Linux desktop — paru + flatpak |
| `debian-server` | 1 | Debian/Ubuntu server — CLI only |
| `debian-dev` | 2 | Debian/Ubuntu dev — apt |
| `debian-brew` | 2 | Debian/Ubuntu dev — Homebrew + flatpak |
| `devpod` | 2 | Work devpod — Homebrew |
| `fedora` | 3 | Fedora Workstation — dnf + flatpak |
Each profile fully specifies a machine setup: category tags, package managers, work mode, decryption, and backups. See `.chezmoidata/profiles.yaml` for the source of truth.

| Profile | Pkg Managers | Work | Decrypt | Backup | Description |
|---------|--------------|------|---------|--------|-------------|
| `macos-work` | brew | yes | yes | | Work Mac — corporate dev + GUI (Time Machine) |
| `arch-desktop` | pacman, flatpak, appimage | | yes | yes | Arch Linux desktop — paru + flatpak |
| `debian-server` | apt | | | | Debian/Ubuntu server — CLI only |
| `devpod` | brew | yes | | | Debian/Ubuntu dev — Homebrew |
| `silverblue` | brew, rpm-ostree, flatpak, appimage | | yes | | Fedora Silverblue/Bazzite — immutable |
| `silverblue` | 3 | Silverblue/Bazzite — Homebrew + flatpak |

**Package tiers:**
Expand Down Expand Up @@ -152,7 +149,7 @@ dotfiles/
├── .chezmoi.toml.tmpl # Chezmoi config template
├── .chezmoiignore.tmpl # Ignore rules (templated)
├── .age-public-key # Age encryption public key
├── run_before_00-decrypt.sh.tmpl # Age key setup (if decrypt enabled)
├── run_before_01-decrypt.sh.tmpl # Age key from 1Password/env if missing (restic secrets use self-caching templates)
├── run_onchange_01-install-packages.sh.tmpl # brew bundle
├── run_onchange_02-install-fisher.sh.tmpl # Fisher plugins
├── run_onchange_03-configure-tide.sh.tmpl # Tide prompt config
Expand Down
34 changes: 0 additions & 34 deletions dot_config/hypr/looknfeel.conf
Original file line number Diff line number Diff line change
Expand Up @@ -75,38 +75,4 @@ plugin {
scale = 0.6
workspace_gap = 100
}

# hyprglass defaults: default_theme=dark, default_preset=default,
# tint_color=0x8899aa22, dark:brightness=0.82, layers.enabled=false.
hyprglass {
default_preset = clear

brightness = 0.9
light:adaptive_boost = 0.5

preset = name:clear, glass_opacity:0.8, blur_strength:1.5
preset = name:clear:dark, brightness:0.7
preset = name:clear:light, brightness:1.2

preset = name:contrasted, inherits:high_contrast, contrast:1.2, adaptive_dim:1.5
preset = name:contrasted:dark, tint_color:0x02142aa9

layers {
enabled = 1
namespaces = waybar, swaync, quickshell:bezel
exclude_namespaces = debug-panel
preset = subtle
namespace_presets = quickshell:bezel:ui
namespace_mask_thresholds = waybar=0.05, quickshell:bezel=0.3
}
}

# dynamic-cursors: only the shaperules diverge from defaults. Top-level
# (enabled=true, mode=tilt, threshold=2) and every subblock (rotate, tilt,
# stretch, shake, hyprcursor) match upstream documented defaults exactly.
dynamic-cursors {
shaperule = text, rotate:offset: 90
shaperule = grab, stretch, stretch:limit: 2000
shaperule = clientside, none
}
}
5 changes: 5 additions & 0 deletions dot_config/resticprofile/private_password.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{- /* restic repo password — fetched from 1Password once, then cached at this target path.
Consumed by restic at runtime via `password-file` (no chezmoi-render dependency). */ -}}
{{- includeTemplate "op-cached-secret" (list
".config/resticprofile/password"
(printf "op://dev-keys/restic-%s/encryption-key" .chezmoi.hostname)) | trim -}}
Loading
Loading