Container-first, multi-shell dotfiles for Linux power users with a Kubernetes & container workflow focus
- Overview
- Philosophy
- Features
- Quick Start
- Shell Strategy
- Project Structure
- Development Workflow
- Backups
- Bisync
- Design Choices
- Future Plans
This is my personal dotfiles repository, designed to provide a consistent development environment across:
- Linux workstations (Fedora/Universal Blue)
- Container environments (Distrobox, Podman, Docker)
- Future: Kubernetes debug pods (coming soon!)
Target Audience: Linux power users who work with containers, Kubernetes, and need a portable, consistent shell environment anywhere.
Everything is designed to run in userspace and work seamlessly in containerized environments:
- โ No system-level configuration - Works on immutable OSes (Fedora Atomic, etc.)
- โ Portable - Same environment in Distrobox, Podman, or bare metal
- โ Self-contained - All tools managed via mise, no root required
- โ Future-proof - Ready for Kubernetes debug containers
Different shells for different tasks, not just preference:
- ๐ Fish - Primary interactive shell (user-friendly, modern)
- ๐ Bash - Scripting and automation (universal, POSIX)
- ๐ Nushell - Data processing (structured pipelines)
This repo does not manage:
- โ Host NetworkManager configurations
- โ Host-level systemd services (system units)
- โ Server/system administration
- โ Host package installation (no pacman/apt/dnf)
This keeps configs portable and safe for immutable operating systems.
- Editor: Neovim (Lua-based configuration)
- Terminal: Wezterm with sessionizer
- Shell Tools: Starship prompt, Carapace completions, eza, bat, fzf
- Version Management: mise (formerly rtx)
- Git Hooks: hk (high-performance, mise-integrated)
-
Container Runtime: Podman + Distrobox
-
K8s Tools: kubectl, krew plugins
-
Custom kubeconfig manager (Fish shell)
# Automatically merges configs from ~/.kube/clusters/ set-kubeconfig # Load all configs isolate-kubeconfig path # Use single config append-kubeconfig path # Add to current context store-kubeconfig path # Save to clusters dir
- 1Password CLI integration via Chezmoi templates
- Personal use only (not for team/shared secrets)
- CLI Tools: Homebrew (general use), mise (project-specific versions)
- GUI Apps: Flatpak (Linux only)
- Containers: Podman + Distrobox exported apps
- Language Tools: Managed by mise per-project (Python, Node, Go, Rust, etc.)
chezmoi apply on a fresh ublue/immutable Linux system automatically:
- Installs Homebrew (
run_once_before_10) โ Linuxbrew in/home/linuxbrew - Installs Tailscale (
run_once_before_20) โ via official install script, skipped if present or ephemeral - Runs
brew bundle(run_always_after_30) โ CLI tools, fonts; rendered fromBrewfiletemplate - Installs Flatpaks (
run_always_after_35) โ system-wide fromflatpaks.txttemplate; additive only - Installs OrcaSlicer (
run_always_after_36) โ personal machines; version-checked, flatpak bundle - Runs
mise install(run_onchange_after_50) โ installs all tools in~/.config/mise/config.toml - Reloads systemd (
run_always_after_99) โ picks up new/changed user units
All scripts are idempotent and additive โ safe to re-run on every chezmoi apply.
- Niri (Wayland tiling compositor)
- KDE Plasma (full desktop)
# Initialize and apply dotfiles
chezmoi init rwaltr
chezmoi applyOn first apply, chezmoi will automatically install Homebrew, Tailscale, brew bundle (CLI tools + fonts), Flatpaks, and mise tools.
For 1Password on immutable/rpm-ostree hosts, use rwaltrctl-init to layer the native
1password and 1password-cli packages, then reboot before continuing setup.
The init flow installs the 1Password repo key into /etc/pki/rpm-gpg/ and uses a
file:/// repo key reference so it works on immutable hosts without rpm --import.
Flatpak 1Password is no longer part of this setup.
# Self-contained installer (downloads chezmoi)
sh -c "$(curl -fsLS https://raw.githubusercontent.com/rwaltr/dotfiles/master/install.sh)"# Quick ephemeral environment (Docker/Podman)
podman run -it --rm fedora:latest bash -c "
dnf install -y git curl &&
sh -c \"\$(curl -fsLS https://raw.githubusercontent.com/rwaltr/dotfiles/master/install.sh)\"
"
# Persistent Distrobox environment (Linux only)
distrobox create --image ghcr.io/ublue-os/bluefin-cli:latest --name dev
distrobox enter dev
chezmoi init rwaltr && chezmoi applyEach shell serves a specific purpose based on its strengths:
Use for: Daily interactive work, command exploration, quick tasks
Strengths:
- Modern, intuitive syntax
- Excellent tab completion
- Syntax highlighting out-of-the-box
- User-friendly interactive features
Example Config:
# ~/.config/fish/config.fish
# Modular configuration via conf.d/*.fish
# Custom functions in functions/*.fishUse for: Scripts, automation, CI/CD, compatibility
Strengths:
- Available everywhere (including containers, minimal systems)
- POSIX compatible
- Industry standard for scripting
Example Structure:
# Modular configuration in ~/.config/bashrc.d/
# Loaded alphabetically:
# - 0.*.sh (core setup)
# - *.sh (tool configs)Use for: Log analysis, data transformation, structured pipelines
Strengths:
- Structured data (tables, records)
- Type-aware commands
- SQL-like queries
Example Use Case:
# Parse JSON logs with structured queries
cat logs.json | from json | where status == 500 | lengthdotfiles/
โโโ .chezmoiroot # Points source to home/ directory
โโโ install.sh # Bootstrap script (no chezmoi required)
โโโ mise.toml # Development tools & tasks
โโโ hk.pkl # Git hooks configuration
โโโ .markdownlint-cli2.jsonc # Markdown linting rules
โ
โโโ home/ # Chezmoi source directory (becomes ~/)
โ โโโ .chezmoiexternal.yaml # External assets (OrcaSlicer bundle)
โ โโโ .chezmoiignore # Files to skip (personal-only gating)
โ โโโ .chezmoi.yaml.tmpl # Machine flags: personal, work, headless, ephemeral
โ โ
โ โโโ .chezmoiscripts/
โ โ โโโ before/ # Pre-apply: homebrew, tailscale, common dirs
โ โ โโโ after/ # Post-apply: brew bundle, flatpaks, mise, systemd reload
โ โ
โ โโโ .chezmoitemplates/
โ โ โโโ Brewfile # CLI tools + fonts (segmented by flags)
โ โ โโโ flatpaks.txt # Flatpak app IDs (segmented by flags)
โ โ
โ โโโ dot_config/
โ โ โโโ fish/ # Fish shell (primary interactive)
โ โ โโโ bashrc.d/ # Modular Bash configs
โ โ โโโ nushell/ # Nushell data processing
โ โ โโโ nvim/ # Neovim Lua config
โ โ โโโ wezterm/ # Terminal config
โ โ โโโ mise/ # Global mise tool config
โ โ โโโ niri/ # Niri Wayland compositor
โ โ โโโ bisync/ # rclone bisync profiles (LOCAL/REMOTE env pairs)
โ โ โโโ containers/systemd/ # Podman quadlets (resticprofile)
โ โ โโโ systemd/user/ # User systemd units + timers
โ โ โโโ resticprofile/ # Backup profiles (personal only)
โ โ
โ โโโ dot_local/
โ โโโ bin/ # Custom scripts:
โ # auto-bisync, bisync-now, gamingctl,
โ # virtcontainerctl, restic-setup-creds, ssh-multi
โ
โโโ AGENTS.md # Comprehensive context for AI agents
โโโ README.md # This file
mise.toml- Development tools, linting/formatting taskshk.pkl- Git pre-commit/pre-push hooks (delegates to mise)AGENTS.md- Deep dive for AI coding assistants (like pi!)home/dot_config/bashrc.d/- 17 modular Bash scriptshome/dot_config/fish/- 15+ Fish shell modules
Powered by mise tasks and hk git hooks:
# Run all linters
mise run lint
# Run all formatters
mise run format
# Individual linters
mise run lint:shell # shellcheck
mise run lint:fish # fish --no-execute
mise run lint:lua # stylua
mise run lint:yaml # yamllint
mise run lint:toml # taplo
mise run lint:markdown # markdownlint-cli2
# Individual formatters
mise run format:shell # shfmt
mise run format:lua # stylua
mise run format:toml # taploAutomatically runs linters on commit/push:
# Install hooks (one-time)
mise run hk:install
# Manually check staged files
mise run check
# Auto-fix issues in staged files
mise run hk:fix
# Hooks run automatically
git commit -m "feat: something" # โ linters run here
# Skip hooks if needed (emergency only)
HK=0 git commit -m "emergency fix"How it works: hk delegates to mise run lint, ensuring git hooks and manual checks are identical.
138 tests across unit, container image, and VM layers. Unit tests run in a containerized environment for consistency:
# Build the test container (one-time)
mise run test:build
# Run unit tests in container (recommended)
mise run test:unit
# Run unit tests locally (uses host tools, may skip tests)
mise run test:unit:local
# Full suite: lint + unit tests
mise run test
# Everything including container image tests
mise run test:allSee tests/README.md for full details.
# Check what would change
chezmoi diff
# Apply changes
chezmoi apply
# Edit a file (opens in $EDITOR)
chezmoi edit ~/.config/fish/config.fish
# Add a new file to management
chezmoi add ~/.config/newapp/config.toml
# Update from repository
chezmoi update
# See status
chezmoi status# Global tools via Homebrew (available everywhere)
brew install kubectl kubectx jq fzf ripgrep
# Project-specific versions via mise
cd ~/project
mise use node@20 python@3.12
# Or add to project's .mise.toml:
# [tools]
# node = "20"
# python = "3.12"
# Install tools
mise install
# Verify
mise listHome directory backups are managed via resticprofile โ a profile-based wrapper around restic.
- Profile config:
~/.config/resticprofile/profiles.toml(managed by chezmoi, personal machines only) - Repo:
sftp:mouse:backups/<hostname>/homeโ per-host repo on the mouse server over SFTP/Tailscale - SSH auth: 1Password SSH agent (
~/.1password/agent.sock) - Schedule: Hourly via user systemd timer (
resticprofile@home.timer), starts aftertailscale0is up - Retention: 24 hourly, 7 daily, 4 weekly, 12 monthly, 3 yearly snapshots
- Credentials: Per-host password stored at
~/.config/resticprofile/password(mode 600, not in git)
resticprofile runs in a rootless Podman container via a systemd quadlet โ no local install required.
systemd timer โ resticprofile@<profile>.service โ podman run ghcr.io/creativeprojects/resticprofile
The quadlet (~/.config/containers/systemd/resticprofile@.container) mounts:
~โ backup source and SSH keys~/.config/resticprofileโ profile config and credentials~/.1password/agent.sockโ 1Password SSH agent for SFTP auth
Caches, Steam, container storage, build artifacts (node_modules, target, .cargo),
~/src (on GitHub), ~/Downloads, large disk images (*.iso, *.qcow2).
# 1. Set the repo password โ stored at ~/.config/resticprofile/password
restic-setup-creds
# 2. Initialize the restic repo on the remote
systemctl --user start resticprofile@home.service
# (resticprofile initialize = true handles this automatically on first run)
# 3. Enable the timer
systemctl --user enable --now resticprofile@home.timer
# 4. Watch logs
journalctl --user -u resticprofile@home.service -fAdd a new profile to profiles.toml inheriting from base:
[documents]
inherit = "base"
repository = "sftp:mouse:backups/{{ "{{" }} .chezmoi.hostname {{ "}}" }}/documents"
[documents.backup]
source = ["~/Documents"]Then add and enable a timer:
cp ~/.config/systemd/user/resticprofile@home.timer \
~/.config/systemd/user/resticprofile@documents.timer
systemctl --user enable --now resticprofile@documents.timerWhen mouse gets an S3-compatible backend, add a second profile pointing at it โ
the base excludes and retention policy inherit automatically.
Two-way file synchronization between local directories and a remote server using rclone bisync, with profile-based configuration.
- Profiles:
~/.config/bisync/*.envโ each file defines aLOCALandREMOTEpath pair - Transport: SFTP to the
mouseserver over Tailscale with SSH key auth - Tools:
auto-bisync(watch mode) andbisync-now(ad-hoc sync) - Backups: Conflict/overwrite backups stored in
~/.local/share/bisync-backups/
| Profile | Local | Remote |
|---|---|---|
| books | ~/Books |
mouse:/var/tank/home/rwaltr/Books |
| documents | ~/Documents |
mouse:/var/tank/home/rwaltr/Documents |
| games | ~/Games |
mouse:/var/tank/home/rwaltr/Games |
| music | ~/Music |
mouse:/var/tank/home/rwaltr/Music |
| pictures | ~/Pictures |
mouse:/var/tank/home/rwaltr/Pictures |
| videos | ~/Videos |
mouse:/var/tank/home/rwaltr/Videos |
# List available profiles
bisync-now list
# Sync a specific profile
bisync-now documents
# Sync all profiles
bisync-now all
# Watch mode โ auto-sync on file changes (requires inotify-tools)
auto-bisync ~/Documents :sftp,host=mouse,key_file=~/.ssh/id_ed25519:/var/tank/home/rwaltr/Documents watch
# One-shot sync (used by bisync-now internally)
auto-bisync ~/Documents :sftp,host=mouse,key_file=~/.ssh/id_ed25519:/var/tank/home/rwaltr/Documents once
# First-time resync (resolves empty tracking state)
auto-bisync ~/Documents :sftp,host=mouse,key_file=~/.ssh/id_ed25519:/var/tank/home/rwaltr/Documents resyncA templated user service (bisync@.service) runs auto-bisync in watch mode for any profile.
It waits for the Tailscale interface before starting.
# Enable continuous sync for a profile
systemctl --user enable --now bisync@documents.service
# Enable all profiles
for p in books documents games music pictures videos; do
systemctl --user enable --now bisync@${p}.service
done
# Check status
systemctl --user status bisync@documents.service
# View logs
journalctl --user -u bisync@documents.service -frcloneโ installed via Homebrew/miseinotify-toolsโ for watch mode- SSH key referenced by the bisync profile (defaults to
~/.ssh/id_ed25519) with access tomouse - Tailscale โ service waits for
tailscale0interface
rwaltrctl-init will check for the configured bisync SSH key and can generate it if missing.
If Tailscale is already connected, it will also try to install the generated public key on the
remote host automatically. If that fails, you can still install the public key manually.
vs Nix: More complexity than needed for dotfiles alone. Chezmoi hits the sweet spot between:
- Simplicity: Templates, not a whole OS
- Power: Templating, external resources, secrets management
- Portability: Works on any system with a shell
vs Stow/bare git: Need templating for:
- Different configs per machine
- Secret injection from 1Password
- OS-specific sections
- Interactive Focus: 90% of shell time is interactive, not scripting
- Modern UX: Tab completion, syntax highlighting, better defaults
- Less Configuration: Works great out-of-the-box
- Still Use Bash: For scripts where portability matters
For project-specific version management:
- โ Version Management: Replaces asdf, nvm, rbenv, pyenv, etc. per-project
- โ Task Runner: Built-in make alternative
- โ Tool Installer: Downloads and manages CLI tools
- โ
Environment Management: Per-project tool versions via
.mise.toml
vs Homebrew: Homebrew is for global tools used everywhere (kubectl, jq, fzf, etc.), while mise handles per-project versions (Node 18 in project A, Node 20 in project B).
Goal: Run this environment anywhere:
- ๐ฅ๏ธ Workstation: Full setup with GUI tools
- ๐ฆ Container: Lightweight dev environment
- โธ๏ธ K8s Debug Pod: Familiar shell in production (future)
Benefits:
- No system pollution
- Immutable OS friendly
- Portable and reproducible
- Same environment everywhere
- Distrobox Assemble: Rebuild pi AI agent environment on any machine
- Kubernetes debug container: Run dotfiles in
kubectl debugpods - Test suite: 138 bats tests (unit + container image), containerized runner
- Wezterm sessionizer integration with pi agent for long-running tasks
- Container image: Pre-built Docker/Podman image with full setup
- Nushell integration: Deeper data processing workflows
- S3 backup backend: Second restic profile pointing at S3-compatible storage
- AGENTS.md - Comprehensive guide for AI coding assistants
- Full project philosophy
- File-by-file breakdown
- Configuration patterns
- Chezmoi workflows
- Development tasks
This is a personal repository, but feel free to:
- ๐ก Open issues with questions
- ๐ Report bugs or suggest improvements
- โญ Star if you find it useful!
Personal use. Feel free to use as inspiration or starting point for your own dotfiles.
Built with: Chezmoi โข mise โข Fish โข Neovim โข Wezterm