Bootstrap Procedure #
Lessons from the last install #
- Do not use Flatpak 1Password, it's built into the actual bootc image now, as a first class citizen.
- No need to worry about browsers, Helium is baked into the image too, because you do not want to use a Chromium browser in a Flatpak, and absolutely no Firefox.
- SSH/YubiKeys are now handled by SOPS secrets, and nushell helper scripts.
- Run
dfefor dotfile edits so changes land in a fresh jj change, and aren't just added to the working copy.
Basic assumptions #
This checklist is assuming an install onto zircadia, my custom bootc image. This image comes with chezmoi, homebrew, Helium Browser, Steam, 1Password, ykman, age, sops, age-plugin-yubikey, and jj preinstalled. They will absolutely be present. If they are not, this list does not apply.
Stage 0: Pre-clone requirements to get to access the rest of the tools #
[ ] brew install helix This gives me my preferred editor.
[ ] curl -L https://sw.kovidgoyal.net/kitty/installer.sh | sh /dev/stdin My preferred terminal.
[ ] Launch kitty
[ ] nu We have to switch to nu, in order to use nushell commands consistently.
[ ] mkdir ~/.ssh
[ ] chmod 700 ~/.ssh
[ ] cd ~/.ssh
[ ] ssh-keygen -K Repeat with exactly one YubiKey inserted at a time, and preserve the identity-handle filenames expected by the encrypted SSH inventory.
[ ] Write a temporary minimal ~/.ssh/config entry for Host tangled.org; all you need is:
IdentityFile <path to the Tangled key downloaded by ssh-keygen -K>IdentitiesOnly trueIdentityAgent none[ ]chmod 600 ~/.ssh/config
Stage 1: Fetching dotfiles from the remote #
[ ] ls ~/.local/share/chezmoi to see if the directory already exists.
[ ] mkdir ~/.local/share/chezmoi if it doesn't.
[ ] cd ~/.local/share/chezmoi
[ ] jj git clone --no-colocate git@tangled.org:threadweaver.dev/dotfiles . This will copy down the files, and the bookmarks, setting the working copy one commit ahead of the trunk.
[ ] mkdir .git This needs to exist as an empty directory so that chezmoi isn't confused. A test clone showed that no .git directory is created as part of the cloning process, and therefore it has to get made manually, so chezmoi init doesn't create a real git repo.
[ ] jj st To check the repo status.
Stage 2: Run the post-clone bootstrap #
The remaining stages are implemented by the standalone repository script bootstrap.nu. It intentionally runs outside chezmoi application and starts Nushell with --no-config-file, so it does not depend on the configuration or packages it is about to install.
[ ] cd ~/.local/share/chezmoi
[ ] nu --no-config-file ./bootstrap.nu --plan Review the ordered operations.
[ ] nu --no-config-file ./bootstrap.nu
The script stops on command errors, displays the chezmoi diff and dry-run before asking permission to apply, pauses for the licensed-font drive, and pauses for exactly one labeled YubiKey at a time. It never commits, moves jj bookmarks, pushes, or reboots. The detailed stages below document what it performs and remain the manual recovery path.
Recovery/diagnostic options:
--font-source <path>: supply the mounted licensed-font directory instead of entering it at the prompt.--skip-fonts: skip the licensed-font drive ceremony.--skip-secrets: skip YubiKey/SOPS/SSH setup.--skip-themes: skip network downloads and public theme installation.--skip-auth-tests: install SSH outputs without the per-YubiKey remote-login confirmations (the automatedssh -Gidentity checks still run).--test-stop-after <stage>: stop after a named stage (chezmoi-review,fonts,packages,apply,secrets,themes). Test-harness checkpoint; not part of the normal bootstrap workflow.--yes: accept confirmation prompts; physical-action instructions still print. Use only for controlled testing or reruns.
Stage 2.5: Restoring licensed fonts #
Proprietary fonts (PragmataPro Mono, Berkeley Mono) cannot live in this public repo, so they are restored from a flash drive before chezmoi apply installs the configs that reference them. Doing this before apply means the terminals and DankMaterialShell resolve the real families on first launch instead of caching a fallback.
[ ] Insert and mount the flash drive holding the fonts.
[ ] let font_source = "<path to the mounted fonts directory>"
[ ] mkdir ~/.local/share/fonts
[ ] let font_files = (glob $"($font_source)/**/*.{otf,ttf}")
[ ] if ($font_files | is-empty) { error make {msg: "No .otf or .ttf files found on the font drive"} }
[ ] $font_files | each {|font| cp $font ~/.local/share/fonts/ }
[ ] glob ~/.local/share/fonts/*.{otf,ttf} | each {|font| chmod 644 $font }
[ ] fc-cache --force ~/.local/share/fonts
[ ] fc-match "PragmataPro Mono" Confirm it names PragmataPro Mono, not a fallback.
[ ] fc-match "Berkeley Mono" Confirm it names Berkeley Mono, not a fallback.
[ ] Unmount the flash drive.
Stage 3: Installing packages and applying the dotfiles #
The tracked Nushell configuration invokes carapace and starship during startup, so bootstrap.nu restores the Brewfile before launching Nushell normally.
[ ] On the first run, chezmoi init prompts for an email address and GitHub username (from home/.chezmoi.toml.tmpl); the answers are stored in ~/.config/chezmoi/chezmoi.toml. The script runs this step in the foreground so the prompts are visible.
[ ] bootstrap.nu trusts the third-party entries declared in home/dot_Brewfile and restores Flatpaks, formulae/casks, Cargo packages, and VS Code extensions in dependency-safe stages.
[ ] bootstrap.nu preserves the temporary Tangled SSH config while packages, fonts, SOPS identities, generated SSH outputs, and themes are prepared.
[ ] bootstrap.nu performs the single real chezmoi apply near the end, only after those prerequisites and generated assets are ready.
[ ] After the script finishes, nu loads the applied configuration and helper scripts.
Stage 4: Applying secrets #
bootstrap.nu performs this interactively, requiring exactly one labeled YubiKey at a time. Because it runs with --no-config-file, it explicitly sets PCSCLITE_CSOCK_NAME=/run/pcscd/pcscd.comm and validates that socket before using age-plugin-yubikey; it does not wait for chezmoi to apply environment.d/pcsclite.conf. These commands remain the manual recovery path.
4.1 - Create the baseline directories #
[ ] mkdir ~/.config/sops/age
[ ] chmod 700 ~/.config/sops
[ ] chmod 700 ~/.config/sops/age
4.2 - YubiKey age identity loop, repeat for each YubiKey #
[ ] age-plugin-yubikey --identity | save -f ~/.config/sops/age/yubikey-<LABEL>.identity.txt
[ ] chmod 600 ~/.config/sops/age/yubikey-<LABEL>.identity.txt
4.3 - Create the keys.txt #
[ ] glob ~/.config/sops/age/yubikey-*.identity.txt | sort | each {|path| open $path } | str join "\n" | save -f ~/.config/sops/age/keys.txt
[ ] chmod 600 ~/.config/sops/age/keys.txt
[ ] Confirm $env.SOPS_AGE_KEY_FILE resolves to ~/.config/sops/age/keys.txt; the tracked Nushell configuration sets it persistently.
4.4 - Testing and generating the results of the secrets #
[ ] dotfiles secrets ssh --check to test that the nushell helper scripts are installed, and that the secrets helpers are working.
[ ] Assuming that the previous step comes back clean dotfiles secrets ssh --apply
[ ] ssh-keygen -lf ~/.ssh/authorized_keys Confirm the expected fleet-key fingerprints are installed without printing the key blobs.
[ ] For each YubiKey, insert only that key and run ssh -G github.com | lines | where {|line| $line | str starts-with "identityfile " }; repeat for tangled.org and yggdrasil and confirm exactly one real identity follows identityfile none.
[ ] Test real authentication with each YubiKey: ssh -T github.com, ssh -T tangled.org, and ssh yggdrasil.
Stage 5: Checking package and dotfile parity #
[ ] dotfiles packages check
[ ] dfs
[ ] dotfiles audit
[ ] chezmoi status
Stage 6: Manual theming assets #
[ ] cd ~/Pictures
[ ] git clone https://github.com/p4rfait/rose-pine-wallpapers.git
[ ] Download the Rose Pine Cursors
[ ] mkdir ~/.local/share/icons
[ ] tar -xvf ~/Downloads/BreezeX-RosePine-Linux.tar.xz -C ~/.local/share/icons The archive contains the required BreezeX-RosePine-Linux/ top-level directory.
[ ] Download Rose Pine gtk themes and icons
[ ] mkdir ~/.themes
[ ] let gtk_staging = (mktemp --directory --tmpdir "rose-pine-gtk.XXXXXX")
[ ] tar -xvf ~/Downloads/gtk3.tar.gz -C $gtk_staging
[ ] cp --recursive ($gtk_staging | path join "gtk3" "rose-pine-gtk") ~/.themes/rose-pine-gtk
[ ] rm --recursive --force $gtk_staging
[ ] mkdir ~/.icons
[ ] let icon_staging = (mktemp --directory --tmpdir "rose-pine-icons.XXXXXX")
[ ] tar -xvf ~/Downloads/rose-pine-icons.tar.gz -C $icon_staging
[ ] cp --recursive ($icon_staging | path join "icons" "rose-pine-icons") ~/.icons/rose-pine-icons
[ ] rm --recursive --force $icon_staging
[ ] Confirm ~/.themes/rose-pine-gtk/index.theme and ~/.icons/rose-pine-icons/index.theme exist.
[ ] After logging out and back in, confirm QT_QPA_PLATFORM=wayland and ELECTRON_OZONE_PLATFORM_HINT=auto from the tracked ~/.config/environment.d/wayland.conf are present in the graphical session.
[ ] Build a distrobox for MEGAsync.
[ ] MAYBE: Set up launch arguments for VSCode.
Stage 7: Final validation #
[ ] Log out and back in so environment.d, fonts, cursor settings, and session applications start from the applied state.
[ ] fc-match "PragmataPro Mono" and fc-match "Berkeley Mono"
[ ] dotfiles packages check
[ ] dfs
[ ] dotfiles audit
[ ] chezmoi status
[ ] Confirm Kitty, Foot, DankMaterialShell, GTK, Qt, cursor, wallpaper, and default application behavior.
[ ] Confirm the required changes are reachable from the intended jj bookmark and pushed before relying on this procedure for another machine.
[ ] Perform a disposable fresh clone and dry-run from the pushed bookmark before declaring bootstrap-ready.