konradmalik/dotfiles
konradmalik/dotfiles has 29 GitHub stars and 1 forks. GitHot measured 0 stars in 24 hours. Last checked 2026-11-14 18:39 UTC. Last push 2026-11-13.
Measured star growth
- in 24h
- 0
- in 7 days
- Collecting history
- in 15 days
- Collecting history
- in 1 month
- Collecting history
Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.
About
Dotfiles for Linux and MacOS. Currently on Nix.
ProductivityREADME.md
OriginalDotfiles
My NixOS and Nix-Darwin configurations.
Layout
hosts- one directory per machine, holding everything specific to it:default.nix, andhardware.nix/home.nix/disko.nixwhere there is onesystem- shared system-level (NixOS/nix-darwin) configurationhome- shared home-manager configurationsecrets- sops-encrypted secrets,system.yamlandhome.yamlpkgs- own packages (fonts,scripts), available aspkgs.custom.*in configs and as flakepackagesfiles- static assets (wallpapers)templates- flake templatesrouter- home network and MikroTik router config, see router/readme.md
Naming
Inside system and home:
- modules - something that's imported on-demand and does not support explicitly enabling
- options - something that's always imported but requires explicit enable. It's also configurable via some abstraction.
- profiles - prepared modules for a system kind or use-case: nixos, darwin, server, desktop, laptop
- user - per-user system-level config (
systemonly)
Hosts
| Host | Kind | Notes |
|---|---|---|
framework |
NixOS | desktop, Ryzen AI Max+ 395 |
x1c6 |
NixOS | laptop, ThinkPad X1 Carbon 6th gen |
rpi4-1 |
NixOS | server, aarch64 |
rpi4-2 |
NixOS | server, aarch64 |
m4 |
nix-darwin | macbook air m4 |
Commands
[!NOTE] in all commands flake location can be one of the following:
# github repo
github:konradmalik/dotfiles#<target>
# local current dir
.#<target>
I'll use the local version for brevity.
[!NOTE] Checkouts live under the XDG projects dir (
~/Code) as<host>/<user>/<repo>. Repos the config refers to by path are declared inkonrad.repos(home/options/repos.nix) and cloned on activation if missing. On workstations the dotfiles checkout is also linked as/etc/nixos(NixOS) or/etc/nix-darwin(darwin), so rebuilds need no--flake.
Formatting and linting
Formatting goes through treefmt (treefmt.nix). The style lives in
.editorconfig in this repo, so the devshell's formatters respect the same style.
$ nix fmt
$ nix flake check
The check covers formatting, shellcheck and lua lint. On Linux it's qml (quickshell) lint and NixOS VM tests too. To run just one, build it directly:
$ nix build .#checks.aarch64-darwin.lint-lua
NixOS:
Build and enable config locally:
$ sudo nixos-rebuild switch
# or
$ sudo nixos-rebuild boot
nixos-rebuild --sudo switch works too: it evaluates and builds as the user and uses sudo only
for activation. On machines without /etc/nixos add --flake ..
To just build (for example for a test):
$ nix build .#nixosConfigurations.framework.config.system.build.toplevel
Deploy to an rpi from another machine
This can be faster than building on a raspberry pi:
$ nixos-rebuild --sudo switch --flake .#rpi4-1 --target-host rpi4-1
Firmware updates
fwupd runs on every NixOS host except the rpis and pulls firmware (UEFI, NVMe, docks, some peripherals) from LVFS:
$ fwupdmgr refresh
$ fwupdmgr get-updates
$ fwupdmgr update
The rpi4 bootloader EEPROM isn't on LVFS, so there it goes through Raspberry Pi's
rpi-eeprom-update. The package is unfree and the script stages the update on the boot
partition, which the sd-image doesn't mount, so on the rpi:
$ sudo mkdir -p /boot/firmware
$ sudo mount /dev/disk/by-label/FIRMWARE /boot/firmware
$ NIXPKGS_ALLOW_UNFREE=1 nix shell --impure nixpkgs#raspberrypi-eeprom
# shows current vs latest version
$ sudo BOOTFS=/boot/firmware "$(command -v rpi-eeprom-update)"
# stages the latest one, flashed by the rpi itself on the next boot
$ sudo BOOTFS=/boot/firmware "$(command -v rpi-eeprom-update)" -a
$ sudo umount /boot/firmware
$ sudo reboot
Build sd-image:
$ nix build .#rpi4-2-sd-image
The image lands in result/sd-image/nixos-sd-image-<version>-aarch64-linux.img.zst.
Unpack and flash it to the card:
$ unzstd -c result/sd-image/*.img.zst | sudo dd of=/dev/sdX bs=4M conv=fsync status=progress
[!NOTE] The filesystem won't be complete, it will miss
etcand more. NixOS will populate those dirs on first boot.So if you need to modify something on the card (like read host keys) then the steps are:
- boot rpi with the newly flashed card once
- wait a minute or two
- poweroff rpi and mount the card on your PC
- filesystem will be complete
Install/reinstall/rebuild NixOS from ISO
In NixOS ISO:
Clone this repo (a throwaway checkout):
$ git clone https://github.com/konradmalik/dotfiles
Enter shell
$ nix-shell
Use disko to format and mount:
$ sudo disko --mode destroy,format,mount ./hosts/x1c6/disko.nix
Generate hardware configuration and put it in the host's dir:
$ sudo nixos-generate-config --no-filesystems --root /mnt
$ cp /mnt/etc/nixos/hardware-configuration.nix ./hosts/x1c6/hardware.nix
Generate/add sops keys (if required for the configuration). Do this later only if no critical services rely on them (like user passwords).
Host ones will be picked up automatically. Add user ones to /home/USER/.config/sops/age/keys.txt.
For details refer to sops-nix section.
Commit and push the new host files, then install:
$ sudo nixos-install --flake github:konradmalik/dotfiles#x1c6 --root /mnt
Installing from github: means the throwaway checkout can go, first activation clones the real one.
For an existing host there is nothing to generate, so skip the clone and install straight away.
Fix something on NixOS from ISO
In NixOS ISO:
Clone this repo
$ git clone https://github.com/konradmalik/dotfiles
Enter shell
$ nix-shell
Use disko to mount:
$ sudo disko --mode mount ./hosts/x1c6/disko.nix
Enter your system
$ cd /mnt
$ nixos-enter
nix-darwin:
You need to install nix. nix-darwin manual suggests using lix as the bootstrapper.
Next install homebrew.
Finally, build and enable config, first activation clones this repo into ~/Code and links it as
/etc/nix-darwin:
$ sudo nix run nix-darwin -- switch --flake github:konradmalik/dotfiles#m4
From then on:
$ sudo darwin-rebuild switch
Once that succeeds, uninstall the bootstrapper. nix.package owns nix from here on, but the installer
leaves itself in root's default profile, which stays on PATH next to the real one. Two nix
implementations sharing ~/.cache/nix do not agree on the narHash of every git input, which surfaces
later as mismatch in field 'narHash' of input ... in flakes and direnv:
$ sudo nix-env --profile /nix/var/nix/profiles/default --uninstall lix
$ sudo nix-env --profile /nix/var/nix/profiles/default --delete-generations old
nix-env -q --profile /nix/var/nix/profiles/default should then list only nss-cacert.
To just build a darwin host (for example for a test):
$ nix build .#darwinConfigurations.m4.config.system.build.toplevel
# or shortened by nix-darwin
$ nix build .#darwinConfigurations.m4.system
Linux builder
It is useful to have a Linux builder on a macOS machine to build linux-specific stuff.
nix-darwin supports it as an option. It is a NixOS VM run via Apple's
Virtualization.framework (linux-builder-vz) and it serves both aarch64-linux
and x86_64-linux - the latter through Rosetta, which needs
softwareupdate --install-rosetta on the host once.
The launchd daemon is started on demand with the
linux-builder-ctl helper that ships with that module.
Docker on Darwin
Podman via home-manager's services.podman. The machine is created on activation but
not started at login: podman machine start / podman machine stop.
Tools that need the docker socket (testcontainers etc.) want a one-time
sudo podman-mac-helper install.
sops-nix
system-wide (Linux only)
Strategy with keys:
- none of the keys block new machines. If they're missing, they'll just fail to decrypt on runtime.
agederived from host ssh key for host-wide secretsagederived from personal ssh key for personal secrets- one global
agekey per person that is kept secret and not directly on any machine. Serves as a backup to decrypt in case of 'tragedy'
Host keys
To get age key for the machine, use:
$ cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age
Add this key to .sops.yaml and propagate re-encryption to all secrets:
$ for file in $(grep -lr "^sops:$"); do sops updatekeys -y $file; done
User keys
Create age directory for sops:
$ mkdir -p "$XDG_CONFIG_HOME/sops/age"
$ touch "$XDG_CONFIG_HOME/sops/age/keys.txt"
$ chmod 700 "$XDG_CONFIG_HOME/sops/age"
$ chmod 600 "$XDG_CONFIG_HOME/sops/age/keys.txt"
Create age key from your personal ssh key:
Why do this when decryption keys are also derived from host ssh keys?
- Redundancy, 2. Personal (user-specific) secrets, 3. Keys generated here can also be used in the home-manager module below
$ ssh-to-age -private-key -i ~/.ssh/personal > "$XDG_CONFIG_HOME/sops/age/keys.txt"
Add this key to .sops.yaml and propagate re-encryption to all secrets:
$ for file in $(grep -lr "^sops:$"); do sops updatekeys -y $file; done
home-manager
For user-specific secrets, a home-manager modules of sops-nix is used.
We similarly use age. The key is reused from system-wide config (the one derived from personal ssh).
See how sops is configured in the home-manager (it just points at the keys.txt file).
Hardware-backed ssh keys
Keys live in the machine's security chip: non-exportable, impossible to back up, one per
machine. ~/.ssh/personal stays as the offline fallback.
NixOS (TPM)
ssh-tpm-agent serves the sealed keys and proxies to the plain ssh-agent, so ordinary
key files keep working alongside them.
$ ssh-tpm-keygen -C konrad@$(hostname) -f ~/.ssh/hardware
That writes ~/.ssh/hardware.tpm and its .pub. The agent loads every sealed key it
finds in ~/.ssh on start, so any number of them can coexist:
$ systemctl --user restart ssh-tpm-agent.service
$ ssh-add -l
The .tpm blob is the key, encrypted to a hierarchy seed that never leaves the chip.
There is no per-key state inside the tpm, so removing a key is removing its files - while
clearing the tpm regenerates that seed and destroys every key at once:
$ rm ~/.ssh/hardware.tpm ~/.ssh/hardware.pub
$ systemctl --user restart ssh-tpm-agent.service
nix-darwin (Secure Enclave)
Requires macOS Tahoe. Apple's middleware is already wired up in the ssh config and in
SSH_SK_PROVIDER, so ssh, ssh-add and ssh-keygen find it on their own.
Create the identity - -l is the label, -t bio asks for Touch ID on every use while
-t none never asks:
$ sc_auth create-ctk-identity -l ssh -k p-256-ne -t bio
Then download the key handle. -K writes one file pair per identity into the current
directory, named after the label, and they can be renamed afterwards:
$ cd ~/.ssh
$ SSH_ASKPASS_REQUIRE=force SSH_ASKPASS=true ssh-keygen -w /usr/lib/ssh-keychain.dylib -K
$ ssh-keygen -lf hardware.pub
List and remove identities:
$ sc_auth list-ctk-identities
$ sc_auth delete-ctk-identity -h <hash>
Making ssh use them
ssh-egress.nix sets IdentitiesOnly, so only listed keys are offered. See that file for names or add a new one.
Bitwarden
Non-hardware keys are in Bitwarden, only their .pub stays on disk - ssh resolves an IdentityFile through its
.pub and lets the agent sign. The vault is a last resort by ordering:
ssh-egress offers the hardware key first and never asks bitwarden when it is accepted.
Linux chains it behind the plain agent, darwin points IdentityAgent at it since the
enclave key needs no agent at all. Both need the desktop app running and unlocked.
Normal keys
$ ssh -i ~/.ssh/somekey user@host # ad-hoc, works despite IdentitiesOnly
$ ssh-add ~/.ssh/somekey # linux, proxied to the plain agent
$ ssh-add --apple-use-keychain ~/.ssh/somekey # darwin, needs IdentityAgent overridden
Per host, add a block to ssh-egress or a drop-in to the already-included config.d:
$ cat >> ~/.ssh/config.d/somehost <<'EOF'
Host somehost
IdentitiesOnly yes
IdentityFile ~/.ssh/somekey
EOF
Backups
restic, via home/options/restic. Every host backs up to backblaze b2 (home/modules/base/restic.nix),
desktops also to local (system/modules/local-backup.nix), an external disk mounted at /mnt/backup
and shared by whichever machine has it attached.
Each repository gets a baker-<name> command, e.g. baker-local snapshots.
The local repository is created once, on any host with the disk attached. The others just use it:
$ findmnt /mnt/backup # must be mounted, or init lands on the root disk
$ sudo install -d -o konrad -g users -m 700 /mnt/backup/restic
$ baker-local init
m4 also backs up to onedrive, restic through rclone, set up once by hand (hosts/m4/home.nix).
Credits
Misterio77 - big inspiration for hyprland and nix files structure.