| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Two breakages observed on first linux-hardened boot:
1. `podman run` failed because linux-hardened sets
kernel.unprivileged_userns_clone=0 by default (stock linux: 1).
Rootless podman requires unprivileged user namespaces. Restoring
the stock-kernel default via sysctl — this is a documented hardened
knob meant to be flipped back if you actually use rootless
containers. No-op on stock kernel.
2. "kernel does not support overlay fs: 'overlay' is not supported over
btrfs". Kernel overlayfs cannot use a btrfs subvolume as lowerdir;
podman needs fuse-overlayfs as the user-mode shim. ~10-30% slower
I/O than native overlay but works correctly and is the upstream
recommendation for btrfs-backed rootless storage.
|
| |
|
|
|
|
|
|
| |
Pair each default UKI entry with its fallback so the boot order list
mirrors the four UKIs mkinitcpio produces. Fallback entries are
optional — UEFI firmware menus can usually pick UKIs from
/EFI/Linux/ directly — but having named entries lets you reorder /
--bootnext them without dropping into the firmware menu.
|
| |
|
|
|
|
|
|
|
| |
Keeping the fallback after all — leaves the door open to dropping the
stock 'linux' package entirely once linux-hardened is proven as a
daily driver. Without hardened-fallback, that future single-kernel
config would have zero autodetect recovery path.
This reverts commit c0c9183.
|
| |
|
|
|
|
|
|
|
| |
Stock linux-fallback already covers the 'autodetect missed a module'
recovery scenario, regardless of which kernel you tried to boot.
hardened being opt-in means a hardened-default failure naturally
falls back to stock — no need for hardened-fallback as a second
safety net. Saves ESP space and mkinitcpio regen time on each
linux-hardened update.
|
| |
|
|
|
|
| |
The hardened kernel ships as a parallel UKI; document its efibootmgr
registration alongside the stock one. Stock stays default-boot;
hardened is selected on demand (efibootmgr --bootnext or firmware menu).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
These three tools are the native (non-flatpak) network parsers in the
install set — every other internet-facing app is already flatpak'd. The
threat model is a RCE in a subtitle/extractor/muxer that walks $HOME
looking for SSH keys, GPG keyring, pass store, cloud tokens, etc.
Approach (defence in depth, not full sandboxing):
- bwrap --bind / / keeps Wayland, PipeWire, DBus, GPU, hwaccel and all
config files working transparently.
- --tmpfs over known-sensitive dirs (.ssh, .gnupg, .password-store,
.config/gh, .config/op, .aws, .local/share/keyrings) blanks them
from the sandbox view; a compromised parser literally cannot see them.
- inner PATH stripped of ~/.local/bin so streamlink's spawn of `mpv`
resolves to /usr/bin/mpv and does not re-enter the sandbox.
- --die-with-parent + --new-session for tidy lifecycle.
- Escape hatch: SANDBOX=0 mpv ... bypasses for one invocation.
- Graceful degradation if bwrap is missing (warns and execs anyway).
bubblewrap added explicitly to meta/base.txt (was implicit via flatpak).
Wrappers in ~/.local/bin shadow /usr/bin via dot_zprofile:15 PATH order.
Not symlinked into the Ubuntu VM (nix/vm.nix does not touch ~/.local/bin),
which is fine: those tools on the headless VM don't need sandboxing.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Installs linux-hardened + linux-hardened-headers alongside the stock
linux kernel. Stock kernel remains the default; linux-hardened is opt-in
via efibootmgr --bootnext after the EFI entry is registered (one-time
host-side step, documented in the preset).
After first 'just pkg-apply', mkinitcpio auto-builds
/boot/EFI/Linux/arch-linux-hardened.efi from the new preset (sharing
etc/kernel/cmdline.tmpl with the stock UKI — same LUKS root, no
kernel-specific cmdline knobs).
Host-side EFI entry registration:
sudo efibootmgr --create --disk /dev/nvme0n1 --part 1 \
--label 'Arch Hardened' --loader '\\EFI\\Linux\\arch-linux-hardened.efi'
Roll back any time by removing both packages and the preset file; the
stock kernel and its UKI are untouched.
|
| |
|
|
|
|
|
|
|
|
|
| |
AddressRandomization=network: iwd generates a deterministic per-SSID
random MAC. Hardware MAC is never exposed on Wi-Fi; reconnects to the
same network reuse the same MAC, so DHCP leases, WPA-EAP creds and
captive portals stay stable.
EnableNetworkConfiguration=false keeps systemd-networkd authoritative
for IP — the existing 30-wifi-bond0.network setup is unaffected and
the wlan interface still gets enslaved into bond0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Two narrow defence-in-depth rules:
- 52-systemd-local-only: org.freedesktop.systemd1.* requires both
subject.local and subject.active. Wheel-via-sudo-rs is on a different
path (sudoers) and is not affected. Stops a non-active or remote
polkit caller from start/stop/restart of system units.
- 53-udisks-system-mount: filesystem-mount-system and modify-system
require subject.active. The everyday USB auto-mount path uses
filesystem-mount (no -system suffix) and is unaffected.
Audited against current workflow (virt-manager, networkctl, USB mount,
bluetoothctl, fwupdmgr) — none of these break.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Adds standard KSPP-style sysctl hardening that does not interfere with
the existing dev workflow:
- kptr_restrict=2, unprivileged_bpf_disabled=1, bpf_jit_harden=2
- kexec_load_disabled=1 (no kexec in use)
- fs.suid_dumpable=0
- ICMP broadcast/bogus-error ignores
- tcp_timestamps=0 (BBR+cake do not need RFC1323 timestamps)
- IPv6 RA disabled at kernel layer (systemd-networkd is authoritative)
- explicit tcp_syncookies=1
Drops 'kernel.yama.ptrace_scope = 0' so the kernel default 1 (parent
only) applies. Debugging own builds via 'gdb ./a.out', 'lldb -- ./bin',
'rust-gdb' still works; only attach-by-PID now needs sudo, accepted
trade-off.
Intentionally kept dev-permissive:
kernel.sysrq=1, kernel.dmesg_restrict=0, kernel.perf_event_paranoid=-1
|
| | |
|
| |
|
|
|
|
|
|
| |
Upstream tuicr commit 5b19712 migrated from the legacy
`defaultPackage.<system>` flake output to the standard
`packages.<system>.default`, which broke `nix-update` with:
error: attribute 'defaultPackage' missing
|
| |
|
|
|
|
|
|
|
|
| |
Mirror the libvirt pattern by accepting DHCP+DNS on waydroid0 so the
Android container's DhcpClient can lease an IP from dnsmasq.
Remove the manual ip nat MASQUERADE table: waydroid-container installs
its own MASQUERADE rule via iptables-nft compat, so the explicit table
is redundant (and was clobbering anything else in ip nat via the
destroy table).
|
| | |
|
| |
|
|
|
|
|
| |
waydroid-container ships only the iptables-legacy code path for adding
its POSTROUTING MASQUERADE; on a host with pure nftables the rule
never lands and the Android container has no outbound NAT. Declare it
explicitly in our nftables.conf for determinism.
|
| |
|
|
| |
This reverts commit eca1a71fc486690489f7aef671d7beccc2ec3f25.
|
| |
|
|
|
|
|
| |
waydroid (and libvirt with finicky guests) need the host to route
between their NAT bridge and the upstream NIC. libvirtd usually
enables this on demand but it doesn't persist, so the container has
no internet on a fresh boot until something else flips the bit.
|
| |
|
|
|
|
|
|
|
|
| |
Name= negation list was failing in practice — veth/waydroid interfaces
were still being enslaved into bond0, taking down host networking.
Switch to positive matching: Path=pci-*|platform-* AND Name=en* AND
Type=ether. Virtual interfaces (veth, virbr, waydroid0, docker0, ...)
have no udev ID_PATH and never start with 'en', so they're cleanly
excluded by the AND of all three keys.
|
| |
|
|
|
|
|
|
|
| |
systemd-networkd's Type=ether matcher was enslaving waydroid0 into
bond0 the moment 'waydroid session start' ran, taking down the host's
default route. Mirror the libvirt/docker negation pattern.
Also mirror the existing virbr0 forward accepts for waydroid0 so the
Android container can actually reach the internet through MASQUERADE.
|
| | |
|
| |
|
|
|
|
| |
Add dot_config/tuicr/config.toml with theme = "gruvbox-dark".
Symlinked from nix/vm.nix per the symlink invariant so the same
config applies on both host (via chezmoi) and VM (via home-manager).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Previously every new login retargeted ~/.ssh/agent.sock to its own
per-connection forwarded socket. That broke a multi-connection setup
when the most-recent connection (which 'won' the symlink) dropped:
all surviving connections' panes would point at a dead socket until a
fresh login from a surviving connection re-ran zprofile.
zprofile: only retarget when the existing symlink target is dead
(sshd unlinks the per-connection socket on disconnect, so [[ -S ]] on
the resolved path is a reliable liveness probe). First connection
seeds the symlink, subsequent logins keep using it.
ssh-agent-refresh: scan /tmp/ssh-*/agent.* for any live forwarded
socket and retarget to the first that responds to ssh-add. Lets the
surviving connection recover without waiting for a new login shell.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Switching to nix's zsh on the Arch host left two functional gaps the
Arch zsh package used to fill:
1. /usr/share/zsh/site-functions in fpath: pacman, paru, systemctl,
journalctl, flatpak, docker, kubectl, makepkg etc. drop their
completions there. nix zsh's compiled-in fpath doesn't include
/usr/share so we lose all of them silently. Added that path (and
vendor-completions for the VM's apt-installed completions) to the
existing fpath loop, guarded by [[ -d ]].
2. HELPDIR for the run-help / help-alias machinery: needed so 'help cd'
etc. find the per-builtin help docs. Pick the first existing version
dir, preferring nix-profile so it matches the running zsh version.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
tuicr's upstream flake uses the legacy 'defaultPackage.<system>' output
schema, not 'packages.<system>.default' — fixes the home-manager switch
error 'attribute packages missing' at nix/flake.nix:28.
zsh: removing the system zsh package took /etc/zsh/zprofile with it,
which used to 'source /etc/profile' and pull in /etc/profile.d/*.sh
(flatpak.sh, nix.sh, etc.). Reconstruct XDG_DATA_DIRS in dot_zprofile
defensively, including per-user + system flatpak exports + nix-profile
share, so 'flatpak update' stops warning and desktop entries from
flatpak/nix-installed apps work in launchers (fuzzel).
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Waybar (and other user services) was inheriting the bare pre-login
PATH from systemd --user, missing ~/.nix-profile/bin and ~/.local/bin.
Modules that call nix-provisioned binaries (pass, python3, ncat from
common.nix) silently picked up system copies instead — symptom was
waybar showing different output from the same script when invoked
manually (thunderbird tb-unread.sh, wifi-status.sh).
Also propagate GNUPGHOME and GPG_TTY so pinentry / pass-otp inside
user services behave the same as in the interactive shell.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Forwarded SSH_AUTH_SOCK lives at /tmp/ssh-XXX/agent.NNN — a
per-connection path that disappears on disconnect, leaving every
long-running zellij pane (and its children: claude-code, nvim, …)
pointing at a dead socket. Reattaching after reconnect doesn't help:
the env was captured when zellij first started.
Fix: maintain ~/.ssh/agent.sock as a symlink, re-aimed at the live
forwarded socket on every login (zprofile). Export the stable path so
processes inherit a value that survives reconnects — git fetch /
commit signing keep working in re-attached zellij panes with zero
per-pane re-export.
Adds 'ssh-agent-refresh' helper for transitional panes still holding
the dead per-connection path: re-exports SSH_AUTH_SOCK to the stable
symlink and validates with ssh-add -l. Already-running children
(claude-code) must still be restarted since env is inherited, not
observed.
|
| |
|
|
|
|
|
|
| |
Make it explicit that adding, removing, or renaming any chezmoi-deployed
config requires a matching update to nix/vm.nix's xdg.configFile /
home.file blocks when the corresponding binary is in common.nix.
Drives the audit performed in the previous commit.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The VM doesn't run chezmoi, so every config the host gets via chezmoi
must reach the VM via a nix symlink. Audit found gaps for tools whose
binary IS in common.nix but whose dot_config tree was unlinked:
bat, lsd, yazi, ripgrep, fd, wget, npm, ipython,
gdb, clangd, ccache
Plus the new tuicr claude-code skill (under ~/.claude/skills/tuicr/,
NOT ~/.config — uses home.file instead of xdg.configFile).
Reorganises the block by category and adds an INVARIANT comment
pointing at the rule in .github/copilot-instructions.md.
GUI/wayland-only tools (sway/mako/waybar/fuzzel/mpv/zathura/etc) stay
unlinked: the VM is headless.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Adapts upstream agavra/tuicr's tmux-based wrapper to zellij:
- Detects $ZELLIJ instead of $TMUX
- Spawns child via 'zellij action new-pane --floating' (or --direction
with TUICR_PANE_MODE=split / TUICR_SPLIT_DIR=down)
- Replaces tmux 'wait-for' with a mkfifo / printf done sentinel
- Uses 'pgrep -x tuicr' for the cross-pane already-running probe
(zellij has no list-panes equivalent)
SKILL.md updated for zellij keybinds and a nix install path note.
|
| |
|
|
|
|
| |
tuicr (TUI git-change reviewer) isn't packaged in nixpkgs, so pull it
as a flake input with an overlay exposing pkgs.tuicr. The companion
claude-code skill lives in dot_claude/skills/tuicr/ (separate commit).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
zsh now lives in nix/common.nix instead of meta/base.txt. Removing the
pacman zsh package leaves /etc/passwd dangling at /usr/bin/zsh, so new
login terminals die with 'shell not found'.
Mirror the chsh logic from nix/bootstrap.sh (which only runs on the VM
during first-time provisioning) into the nix-switch recipe so every
`just sync` / `just init` re-asserts the login shell — and the
host gets the same treatment as the VM.
Idempotent: skips when the shell already matches, skips when
~/.nix-profile/bin/zsh is missing (pre-bootstrap state).
|
| |
|
|
|
|
|
|
|
| |
Now that meta/*.txt is conventionally required to list installed
package names (not virtual providers — see preceding commit dropping
ttf-font-awesome in favour of the already-declared otf-font-awesome),
the intersection with pacman -Qq is unnecessary. Failing loudly on a
virtual-provider entry is actually useful: it surfaces a data-entry
mistake instead of silently masking it.
|
| |
|
|
|
|
|
| |
ttf-font-awesome is a virtual provided by otf-font-awesome (already
declared on the line above) — paru resolves the former to the latter,
so listing both adds nothing and confuses the mark-explicit step in
pkg-apply.
|
| |
|
|
|
|
|
|
|
|
| |
paru may resolve a declared name to a provider with a different package
name (e.g. ttf-font-awesome -> otf-font-awesome). Calling
`pacman -D --asexplicit` on the declared name then fails with
'could not find or read package' and aborts the recipe.
Intersect the declared list with `pacman -Qq` before bumping reasons;
names not present in the local DB are silently skipped.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`paru -S --needed` skips packages already on disk, so anything pulled
in transitively first and *later* added to meta/*.txt stays marked as
'installed as a dependency' in the local pacman DB — and keeps showing
up under `pacopt`.
After each install pass, force the declared set to 'explicitly
installed' via `pacman -D --asexplicit`. This treats meta/*.txt as
the source of truth for install reason: anything listed there is
explicit, anything else is a transitive dep.
Idempotent on already-explicit packages (pacman just prints 'install
reason is already explicit', which we discard).
|
| |
|
|
|
|
|
|
| |
Root filesystem is btrfs; the userspace tools are needed for routine
maintenance (scrub, balance, subvolume management) and inspection
(`btrfs filesystem usage` — the only honest reporter on btrfs since
plain `df` doesn't account for metadata/profiles/unallocated). Also
used by the mkinitcpio btrfs hook at boot.
|
| |
|
|
|
|
| |
Interactive python REPL. Uses python3Packages.ipython so only the
`ipython` binary lands on PATH — no stray system `python`/`python3`,
preserving the 'tools managed by uv per-project' policy in common.nix.
|
| |
|
|
|
|
|
|
|
| |
linux: previously installed only as an Optional Dep of base. Promote to
an explicit declaration so it stops showing up under pacopt.
dosfstools: required by udisks2 (and libblockdev-fs) for mounting FAT
volumes — USB sticks, the EFI system partition, etc. Universally useful
on any desktop install.
|
| |
|
|
|
|
|
|
|
|
|
| |
Promote pacopt from a plain alias to a function. In addition to listing
packages that remain installed solely as someone's optional dependency,
each package is now annotated with its parent(s) and the upstream
reason text from the parent's Optional Deps field.
Implementation is pacman-only (no expac): one awk pass over 'pacman -Qi'
builds a reverse index of every (parent, optdep, reason) edge in the
local DB, then per leaf package the index is filtered for matching deps.
|
| |
|
|
| |
Stops the 'X news items unread' banner on every home-manager switch.
|
| |
|
|
|
|
|
|
| |
Delta's hyperlink resolution shells out to git rev-parse for every
blob to build clickable file links, which is fine on bare metal but
death-by-fork on a slow VM — 'git diff' could take many seconds
while 'git diff | cat' returns instantly. Turn hyperlinks off; we
get clickable nothing but visible-everything-fast diffs.
|
| |
|
|
|
|
|
|
|
|
|
| |
Mirror the drop-in already in place for protonmail-bridge. The
default ~/.password-store doesn't exist on this system (store lives
under $XDG_DATA_HOME); without this, pass-secret-service throws
PassNotInitialized when the service is started outside a context
that already exported PASSWORD_STORE_DIR (e.g. D-Bus activation
before sway has imported env). That cascades into protonmail-bridge
failing to persist its IMAP creds and Thunderbird seeing ephemeral
passwords with 'no such user' errors.
|
| |
|
|
|
|
| |
Unused; the pass entry doesn't exist on most machines, so login emitted
'Error: copilot/firecrawl-api-key is not in the password store' on every
shell start. Easier to drop than to gate.
|
| |
|
|
|
|
| |
Just's formatter inserts a blank line so only the immediately-adjacent
comment serves as the recipe's doc string. Pre-existing drift, surfaced
when CI added 'just check-fmt' to the pipeline.
|
| |
|
|
|
|
|
|
| |
Older shellcheck (Ubuntu's in CI) flags '[ test ] && cmd || true'
as SC2015 because, despite the intent, A && B || C is not
equivalent to if-then-else (C runs when A is true but B fails).
Replace with explicit 'if … fi' or split into two 'A || continue'
guards. Functionally identical, lint-clean across versions.
|
| |
|
|
|
|
|
| |
The doctor recipe checks for basedpyright (used by 'just lint' for
Python type-checking), but the CI workflow wasn't installing it,
causing every run since the recipe was added to fail at the doctor
step. Install it via pipx like ruff.
|
| |
|
|
|
|
| |
host.nix reads HOME/USER via builtins.getEnv, which returns "" under
pure-eval (nix run's default). just-nix-switch already passes --impure;
match it here.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
The standalone 'home-manager' command defaults to the legacy
~/.config/home-manager/home.nix and errors out for flake users. The
wrapper auto-selects the host/vm profile by /etc/os-release ID and
points at whichever dotfiles checkout exists on the machine.
Usage:
hm news
hm switch
hm generations
|
| | |
|
| |
|
|
|
|
| |
Refreshes flake.lock (nixpkgs + home-manager) and re-activates the
profile. Plugged into the existing 'just update' aggregate so a weekly
'just update' bumps pacman/AUR + flatpak + nix in one go.
|