fp-dotfiles-manager 0.2.5

Minimal, zero-dependency Chezmoi-based dotfiles manager
# fp-dotfiles-manager

A minimal, zero-dependency, ultra-compact Rust-based manager for Chezmoi. This project provides a memory-safe, exceptionally lightweight, and unified control suite for your personal environments.

## Features

- **Unified Sync Pipeline:** Automated Chezmoi states, recursive adds, and Git staging under the hood.
- **Dry-Run & Preview:** Preview state and tracking modifications using `--dry-run` or `-n`.
- **Encrypted Profile Backups:** Archive tracked files relative to HOME with integrated `--age-pass` decryption/encryption.
- **Premium Git Logs:** Beautiful single-line colored commits and diff statistics (`logs`).
- **Robust Path Globs:** O(N) path resolution supporting complex match patterns and exclusions.
- **Atomic Locking:** Self-healing directory lock that reclaims itself when a crashed or killed run leaves it behind.
- **Pre/Post Sync Hooks:** Custom script pipelines executed before and after sync operations.
- **Secret Leak Auditing:** Finds plaintext secrets in managed files and promotes them to encrypted tracking.
- **Automatic Config Regeneration:** Regenerates the chezmoi config when its template changes, so chezmoi's nag never reaches you.
- **Zero-Dependency Footprint:** Written in pure Rust. UPX-compressed binary under **70 KB** (dynamic) / **207 KB** (static LTO).

## Configuration

Settings are resolved in the following priority order:
1. Environment variables
2. Configuration file at `~/.config/fp-dotfiles-manager/config.yml`
3. Internal defaults

### Options & Overrides

| Config Property | Env Variable | Default Value | Description |
|---|---|---|---|
| `tracked_file` | `DOTFILES_TRACKED` | `~/.config/fp-dotfiles-manager/tracked` | File listing standard tracked home paths |
| `tracked_enc_file` | `DOTFILES_TRACKED_ENC` | `~/.config/fp-dotfiles-manager/tracked_encrypted` | File listing encrypted home paths |
| `bootstrap_dir` | `DOTFILES_BOOTSTRAP_DIR` | `~/.config/fp-dotfiles-manager/bootstrap.d` | Directory of executable post-setup scripts |
| `ssh_key_path` | `DOTFILES_SSH_KEY` | `~/.config/fp-dotfiles-manager/ssh_gitlab` | Private SSH key path for repository Git operations |
| `age_pass` | `DOTFILES_AGE_PASS` | `""` | Passphrase used to decrypt your age key non-interactively |
| `repo_path` | `DOTFILES_REPO_PATH` | `fptbb/dotfiles` | Repository identifier on git host |
| `repo_url` | `DOTFILES_REPO_URL` | `git@gitlab.com:fptbb/dotfiles.git` | Target SSH remote repository address |
| `pre_sync_hook` | `DOTFILES_PRE_SYNC` | `~/.config/fp-dotfiles-manager/hooks/pre-sync.sh` | Hook script run before synchronization starts |
| `post_sync_hook` | `DOTFILES_POST_SYNC` | `~/.config/fp-dotfiles-manager/hooks/post-sync.sh` | Hook script run after synchronization finishes |
| `secret_scan` | `DOTFILES_SECRET_SCAN` | `enforce` | Secret-leak scanning coverage: `off`, `warn`, or `enforce` |

Other system variables:
- `SKIP_BOOTSTRAP`: Set to `1` or `true` to skip running bootstrap hooks.
- `NO_COLOR`: Set to any value to disable ANSI terminal colors.

### Concurrency

`sync` and `audit-secrets --fix` hold an exclusive lock in `$XDG_RUNTIME_DIR/dotfiles-sync.lock.dir`, so a second run skips rather than interleaving writes with the first.

The lock records the owning PID, so a lock left behind by a crashed run, a `SIGKILL`, or a failed `git push` is detected and reclaimed on the next invocation instead of blocking sync permanently. A lock is only honoured while its owner is alive; as a backstop against PID reuse, anything older than 6 hours is treated as stale. If you ever need to clear it by hand:

```bash
rm -rf "${XDG_RUNTIME_DIR:-$HOME/.cache}/dotfiles-sync.lock.dir"
```


To generate the default configuration template, run the initialization command:
```bash
fp-dotfiles-manager init-config
```

---

## Setup & Usage Quick Tutorial

To start managing your dotfiles with `fp-dotfiles-manager`, follow these simple steps:

### 1. Bootstrapping / Initializing your Repository
You can use the `setup` command to either clone your existing configuration or bootstrap a brand new repository from scratch:

* **Scenario A: Bootstrap from an existing GitLab/GitHub Repository**
  If you already have a dotfiles repository, the setup command will safely check for required tools, verify your SSH private key, clone your repository, decrypt your age keys, and apply your dotfiles:
  ```bash
  fp-dotfiles-manager setup --repo-url git@gitlab.com:yourusername/dotfiles.git
  ```

* **Scenario B: Initialize a Brand New Repository from Scratch**
  If you want to start fresh or do not have a repository yet:
  ```bash
  fp-dotfiles-manager setup --repo-url git@gitlab.com:yourusername/dotfiles.git
  ```
  The setup engine will automatically:
  1. Detect that the remote repository does not exist or is empty.
  2. Initialize a clean local dotfiles repository (`chezmoi init`).
  3. Generate a brand new `age` key pair at `~/.config/chezmoi/.age-private-key.txt` (if one doesn't exist).
  4. Create a default `.chezmoi.toml.tmpl` with your new public key to handle encryption automatically.
  5. Generate `~/.config/chezmoi/chezmoi.toml` for immediate local operations.
  6. Bind your SSH `repo_url` as the git remote `origin` and establish secure Git configurations.
  7. Stage and create the initial git commit.
  
  Once complete, your first `fp-dotfiles-manager sync` will be fully prepared to push everything to your remote.

### 2. Configuration File
A configuration file should be created at `~/.config/fp-dotfiles-manager/config.yml`.
You can initialize a default template by running:
```bash
fp-dotfiles-manager init-config
```

### 3. Tracked Files Lists
By default, the lists of files to track are read from:
- **Standard files:** `~/.config/fp-dotfiles-manager/tracked`
- **Sensitive files:** `~/.config/fp-dotfiles-manager/tracked_encrypted`

Place these tracking lists (containing one relative path per line, e.g., `.bashrc` or `.config/git/config`) at those paths, or use `fp-dotfiles-manager add` to automatically append new files to them:
```bash
fp-dotfiles-manager add ~/.bashrc
fp-dotfiles-manager add-secret ~/.ssh/config
```

### 4. Post-Setup Bootstrap Hooks
Any numerical/alphabetical shell scripts (e.g., `01-packages.sh`, `02-symlinks.sh`) placed inside:
```
~/.config/fp-dotfiles-manager/bootstrap.d/
```
will be executed automatically after setup completes. You can run them manually at any time with:
```bash
fp-dotfiles-manager bootstrap
```

### 5. Sync & Backup
- **Sync modifications & discover new files:**
  ```bash
  fp-dotfiles-manager sync
  ```
- **Preview sync changes without committing (Dry-Run):**
  ```bash
  fp-dotfiles-manager sync --dry-run
  ```
- **Archive and encrypt tracked dotfiles:**
  ```bash
  fp-dotfiles-manager backup [destination_path]
  ```

---

## Tracking Wildcards, Exclusions, and Encryption Rules

`fp-dotfiles-manager` features a highly robust path evaluation engine that supports wildcard patterns, granular folder exclusions, and automated encryption fallback rules:

### 1. Wildcard and Glob Matches
- You can use standard shell-native wildcards (`*`, `?`, and `[...]`) inside your tracking files:
  - `*` matches any sequence of characters (e.g., `.config/git/*` tracks all files inside the git config directory).
  - `.*` or `*` at the end of directories will match files recursively.
  - If a directory path is added (e.g., `.config/nvim`), it is automatically treated as `.config/nvim/*` to recursively discover and track all files underneath.

### 2. Exclusions (Adding a folder except specific files)
- To track a folder but exclude certain files inside it, prefix the excluded file pattern with an exclamation mark (`!`) in your tracking lists:
  - **Example** in your `tracked` file:
    ```text
    .config/myfolder
    !.config/myfolder/tmp_*
    !.config/myfolder/local_cache.json
    ```
  - This tracks everything under `.config/myfolder` recursively, except for any files starting with `tmp_` or named `local_cache.json`.

### 3. Folder Staging with Specific Encryption Overrides
- If you add a folder to the standard `tracked` file but want specific sensitive files inside that folder to remain encrypted, simply add those sensitive files to your `tracked_encrypted` file:
  - **Example**:
    - In `tracked`:
      ```text
      .config/myfolder
      ```
    - In `tracked_encrypted`:
      ```text
      .config/myfolder/secret.key
      ```
  - **How it works:** The engine evaluates each discovered file. If a file is matched by any pattern in your encrypted list, it is automatically staged with `--encrypt`, while the rest of the folder remains staged as standard plain text. The encrypted files will always stay secure!

---

## Secret Leak Scanning

Since chezmoi v2.72, `chezmoi add` scans every **unencrypted** file for embedded secrets and reports anything it finds on stderr:

```text
chezmoi: /home/you/.config/app/creds:4: Identified a Slack Bot token, which may compromise bot integrations and communication channel security.
```

Rather than letting those lines leak into the middle of the sync output, `fp-dotfiles-manager` captures them and re-renders them through its own logger:

```text
 󰧑 Adding 3 new file(s)...
  ⚠ Potential secret in /home/you/.config/app/creds:4: Identified a Slack Bot token, which may compromise bot integrations and communication channel security.
  ⚠ 1 potential secret(s) flagged in 1 file(s). Add them to tracked_encrypted to encrypt them.
```

### Coverage levels

| `secret_scan` | Behaviour |
|---|---|
| `off` | Scanning is disabled entirely. Nothing is scanned and no findings are reported. |
| `warn` | Only files being added during the current run are scanned. |
| `enforce` *(default)* | Also re-scans the whole managed set, so leaks that predate the current run are surfaced too. |

Findings are **always advisory** — they are reported but never block a sync, and a flagged file is still committed as it was staged. The knob controls *coverage*, not enforcement.

> `enforce` costs one extra pass over your managed files per sync. If your tracked tree is large and you only care about new additions, set `secret_scan: "warn"`.

### Auditing existing files

`chezmoi add` only ever inspects files it is adding, so leaks that were committed before scanning existed stay invisible to `sync`. The `audit-secrets` command covers those:

```bash
# Report plaintext secrets in already-managed files
fp-dotfiles-manager audit-secrets

# Additionally move the flagged paths into tracked_encrypted and re-encrypt them
fp-dotfiles-manager audit-secrets --fix
```

`--fix` appends the offending paths to `tracked_encrypted` and re-adds them with `--encrypt`, so the plaintext source entry is replaced by an `encrypted_` one. Run `fp-dotfiles-manager sync` afterwards to commit and push the change. To undo, delete the line from `tracked_encrypted`.

Encrypted files are never flagged: chezmoi's scanner keys off the *prospective* add, so scanning a file that is already encrypted would otherwise report it on every single run. Both the `enforce` pass and `audit-secrets` therefore work from `chezmoi managed --exclude=encrypted`.

### Suppressing a false positive

If a finding is not a real secret, the simplest fix is to move that path into `tracked_encrypted`. Alternatively, the scanner honours inline allow comments — adding `betterleaks:allow` or `gitleaks:allow` to a line suppresses findings on it:

```ini
token = "xoxb-not-a-real-token"  # betterleaks:allow
```

### Requirements and degradation

Secret scanning requires **chezmoi >= 2.72**. On older versions the manager probes once, prints a single warning, and carries on without scanning rather than passing an unsupported flag and failing the run.

---

## Automatic Config Regeneration

`fp-dotfiles-manager` generates `~/.config/chezmoi/chezmoi.toml` for you from the `.chezmoi.toml.tmpl` in your repo, so you should never need to edit chezmoi's own files or run chezmoi commands directly.

When the template changes, plain chezmoi reacts by printing this on every state-changing command until you intervene:

```text
chezmoi: warning: config file template has changed, run chezmoi init to regenerate config file
```

Rather than pass that through, the manager resolves it: `sync`, `apply`, `update`, `status`, and `audit-secrets` all detect the drift and regenerate the config automatically.

```text
 󰧑 Chezmoi config template changed; regenerating config file...
 󰄬 Regenerated the chezmoi config from its template.
 󰧑 Previous config backed up to /home/you/.config/chezmoi/chezmoi.toml.fp-manager.bak
```

Your previous config is always backed up to `chezmoi.toml.fp-manager.bak` (owner-only, since it can reference your age identity) before being regenerated. The repair only rewrites the config file — it never touches your home directory, and it never commits or pushes. If regeneration fails, the sync continues against the existing config and says so, because a stale config still works.

> Regenerating re-renders the template, so any hand-edits you made directly to `chezmoi.toml` are replaced. Make such changes in `.chezmoi.toml.tmpl` instead — that is the source of truth.


---

## Command Line Interface

Subcommand names and short descriptions are defined once in `catalog/` and shared
with the TUI. Run the binary for the live help text:

```bash
fp-dotfiles-manager --help
# or
cargo run -p fp-dotfiles-manager -- --help
```

## Compilation and Installation

This repository is a **Cargo workspace** with three members:

| Crate | Role | Binary |
|---|---|---|
| `fp-dotfiles-manager` | CLI (zero external crates.io deps aside from the shared catalog) | `fp-dotfiles-manager` |
| `fp-dotfiles-tui` | Optional ratatui frontend (`tui/`) | `fp-dotfiles-tui` |
| `fp-dotfiles-catalog` | Shared command names / help text for CLI + TUI | (library) |

Both binaries build into the **same** workspace `target/` tree. The project uses
`just` as a command runner for building and size checks.

### Portable Static Build (CLI)
Builds a statically linked release binary utilizing cross-crate Link-Time Optimization (LTO) and panic-abort metadata pruning. UPX is used to perform ultra-brute compression.
```bash
just release
```
*Output Path: `target/release-static/fp-dotfiles-manager`*

### Portable Static Build (TUI)
```bash
just release-tui
```
*Output Path: `target/release-static/fp-dotfiles-tui`*

### Build CLI + TUI together
```bash
just release-all    # static + UPX for both
just build-all      # debug for both
```

### Dynamic Build
Builds a dynamically linked release binary with an embedded rpath resolving to your Rust compiler toolchain's shared libraries.
```bash
just release-dynamic
# or: just release-dynamic-tui / just release-dynamic-all
```
*Output Path: `target/release/fp-dotfiles-manager` (and `fp-dotfiles-tui` when using `*-all`)*

### Installation
Installs the statically linked portable release binary to your local bin directory.
```bash
just install [prefix="~/.bin"]
just install-tui [prefix="~/.bin"]
just install-all [prefix="~/.bin"]
```

### TUI
See [`tui/README.md`](tui/README.md). Quick start:
```bash
just build-all
just run-tui
```

### Clean Artifacts
```bash
just clean
```