CLAUDE.md
Repository Overview
This is Profpatsch's personal development repository containing experimental projects and utilities.
NEVER use delegation to subagents unless I explicitely ask for it.
IMPORTANT: Instructions
When the user tells you to use INSTRUCTION, you must ALWAYS run the instructions between :
<GIT_COMMIT> Read the file ./.claude/CLAUDE-git EVEN IF YOU READ IT BEFORE, and create a git commit </GIT_COMMIT>
<USE_RELATIVE_TMP> If you need to write temporary files, put them into ./tmp from where you are currently working. Don’t use /tmp </USE_RELATIVE_TMP>
ALWAYS read the manpage first
Many projects here document themselves in a manpage next to the source, not in
a README: mailweb.1, source-forge.1, timetrack.1, buildGo.7 and a dozen
others. They are the authoritative description of what the thing does and, more
importantly, why — including the constraints and caveats that are not visible
from the code.
Before changing a project, read its manpage if it has one, even if you have read the source:
ls users/Profpatsch/<project>/*.[1-9] # is there one?
man ./users/Profpatsch/<project>/<project>.1 # read it
Reading only the code means rediscovering by experiment what a paragraph would have told you, and re-introducing bugs the caveats section already warns about.
When you change behaviour the manpage describes, update it in the same
change. A manpage that documents a flag that no longer exists is worse than
none. Check your work with mandoc -T lint <page> (available via
nix-shell -p mandoc).
Active Projects:
The following projects are active projects I’m working on:
./users/Profpatsch/git-blimey :: A git-blame TUI written in go ./users/Profpatsch/timetrack :: A freelancing timetracker for multiple clients
Development Environment
Primary Build System: Nix
- You can build packages with
nix build .#<package>from the repo root. - When you are running in this repo, you will be inside a
nix-shellthat uses the environment defined in./shell.nix. If you change the environment, you will have to ask the user to restart your nix-shell before changes come into effect.
Where packages come from
Two rules govern the package tree (see ./README.md for the long version):
<dir>/default.nix— this directory is a package, a function of{ depot, pkgs, lib, ... }.<dir>/packages.nix— this directory is a namespace; it names its contents explicitly, oneimportper entry.
A directory that no packages.nix names is not in the tree at all. There is no
filesystem traversal and nothing is injected implicitly: to find where
depot.users.Profpatsch.foo comes from, follow the imports from
./default.nix.
Adding a package = write <name>/default.nix + one line in the parent
packages.nix. Forgetting the second step is why a new package "does not
exist".
module.nix is a NixOS module (imported by path from machines/), never a
package. Adding a package to flake.nix is a separate, deliberate step; that
list is explicit on purpose (see the comment there).
Installing Services with systemd
For projects that have .service files (systemd user services):
# 1. List packages installed in the user profile
nix profile list
# 1.2.a Upgrade package if installed
nix profile upgrade <package-name>
# 1.2.a Install from flake if not installed
nix profile install .#<package-name>
# 2 only if service does not exist yet, else 3
# 2.1 Copy the service file to systemd user directory
cp ~/kot/Profpatsch/users/Profpatsch/<service>/<service>.service ~/.config/systemd/user/
# 2.2 Reload systemd user daemon
systemctl --user daemon-reload
# 2.3 Enable and start the service
systemctl --user enable <service>.service
systemctl --user start <service>.service
# 3 Restart service if it was already running
systemctl --user restart <service>.service
NEVER start long-running services in the background
Never launch a server, daemon or other long-running process with &,
nohup, disown or similar. Always install it and drive it through its
systemd user service, as above.
If a service must run with different flags for a test, edit the unit, restart it, and put it back afterwards — do not spawn a second copy alongside it. If you absolutely must have a second copy (e.g. to compare old versions), you may create a temporary second service (but remember to clean it again).
Note also that pkill -f <pattern> matches the shell running the command
itself and will kill it mid-command, which looks like "pkill never works".
Select the process explicitly instead:
ps -eo pid,args | grep '[m]ailweb --imap-host' # bracket avoids self-match
File Operations Rules
NEVER use cd commands - Always use paths relative to the project in commands instead:
# BAD:
cd /home/philip/depot/users/Profpatsch && git add ./foo/bar
# GOOD:
git add users/Profpatsch/foo/bar
- Use tools like
Read,Edit,Writewith repository-relative paths - For bash commands, specify full paths instead of changing directories
Special workflows
Git Operations → ALWAYS read .claude/CLAUDE-git.md before any git commit
If the user specifies "for ", only commit files related to the given project
(in users/Profpatsch/<project>)
Deploying machines → Machine configs live in ./machines/profpatsch/ and
are wired up as nixosConfigurations in flake.nix. Deploy straight from your
local checkout (no push to any remote is required first):
# Deploy to any machine (runs nixos-rebuild switch on the remote)
deploy <machine> # e.g. deploy legosi, deploy haku
# Or directly with nixos-rebuild from a local checkout:
sudo nixos-rebuild switch --flake ~/kot/Profpatsch#<machine>
The deploy binary (at users/Profpatsch/deploy/) builds the system closure
locally, copies it to the target via nix copy, then activates it remotely.
NixOS machines: rolery (workstation), legosi, haku, leguin (servers)
Git remotes → The default push target is legosi-sources
(ssh://source-forge@legosi:7001/var/lib/source-forge/Profpatsch.git;
canon tracks it and remote.pushDefault is set to it), so a bare
git push on canon pushes there and a post-receive hook re-ingests the tree
into the source-forge source browser at sources.profpatsch.de. The website
is a separate target and must be pushed by name (see below) — a bare
git push does NOT publish the site.
source-forge can host any number of projects, each its own bare repo
directly under /var/lib/source-forge/ on legosi (e.g. Profpatsch.git,
netencode.git), published at sources.profpatsch.de/<project>/. A repo's
post-receive hook is wired in generically (via core.hooksPath, not per-repo
— see users/Profpatsch/source-forge/nixos-module.nix), but a project must
still be declared before anything can be pushed to it — this is what
decides, and locks in, which branch gets published:
ssh -p 7001 root@legosi 'sudo -u source-forge source-forge project add \
--db /var/lib/source-forge/db/source-forge.db \
--repos /var/lib/source-forge --project <name> --branch <branch>'
Pushing to an undeclared repo, or to a branch other than the one declared, is
rejected (or simply ignored, respectively) — see source-forge.1 for the
full project subcommand reference.
Deploying the website (profpatsch.de) → The website is NOT deployed via
nixos-rebuild/deploy, and a bare git push (which goes to legosi-sources)
does NOT publish it. It is served from a separate git remote on legosi: push
the canon branch to the legosi-web remote by name
(ssh://root@legosi:7001/var/www/web.git); a post-receive hook rebuilds and
publishes the site:
git push legosi-web canon
The site sources live in users/Profpatsch/web/ (rendered HTML) and
users/Profpatsch/blog/ (markdown source). Verify a change went live with the
users/Profpatsch/link-check tool:
nix-build . -A users.Profpatsch.link-check
./result/bin/link-check https://profpatsch.de https://sfttime.softwaregardening.org
link-check is a recursive link checker: it crawls the live site, recurses
into in-scope hosts, liveness-checks outbound links, and exits non-zero on dead
links. NOTE: the site host is profpatsch.de (there is no www. record).
Interactive Development with Tmux + GHCi
For rapid iteration with long-running REPL sessions (starting sessions,
capturing large context, multi-line input, and the no-sleep anti-pattern),
see .claude/CLAUDE-tmux.md.