Heinzel is a set of rules that turns an AI coding assistant into a cautious, methodical sysadmin. Describe what you need in plain English. Heinzel works out the right commands for your OS, explains each one, and waits for your approval. It backs up configs, tests before applying, and remembers every server it has worked on.
It manages Linux, FreeBSD and macOS, remote over SSH or on the local machine, and runs in Claude Code, OpenCode or any terminal AI tool that reads project files and runs shell commands.
The trailer is scripted, not recorded. To change it, see
assets/trailer/.
Screencasts on YouTube:
- Debug and fix a misconfigured nginx and firewall (1 min)
- Install the latest stable Ruby and Ruby on Rails (1 min)
- Install a firewall, upgrade the distribution, set up daily security updates (2 min)
Press: German article about Heinzel on heise.de
You need:
- An AI coding assistant in the terminal, e.g. Claude Code or OpenCode.
- SSH access to the server as a normal user or as root, with a key that does not ask for a passphrase. Not needed for the local machine. New to SSH keys? See the Arch wiki guide.
- A workstation running Linux, macOS, FreeBSD or Windows.
On Windows use
WSL, or start
the AI tool from the Git Bash of
Git for Windows. PowerShell
and
cmd.exeare not supported.
Then:
git clone https://github.com/wintermeyer/heinzel.git
cd heinzel
claude # or: opencode
❯ Install postgresql on server1.example.com
On the first connection Heinzel may ask what it cannot
detect, usually the SSH user. The answer goes to
memory/user.md, so it asks once. You can pre-fill that file
from memory/user.md.example. It also holds your language
(Language: German).
Heinzel proposes every command, says what it does and why, and waits for your approval.
Heinzel shares one SSH connection per host and keeps it open
for 10 minutes after the last call. The sockets live in
~/.cache/heinzel (mode 0700), so any process of your local
user can use an open connection without the key.
Check on web1.example.com: reads the server's memory file and recent changes, then picks up where the last session ended.Run housekeeping on app.example.com: a health report on disk, memory, load, updates, firewall, certificates and failed services. Problems come first.Run a security audit on app.example.com: SSH, firewall, accounts, open ports and kernel settings, by severity.Run a fleet audit: compares update, sshd, firewall, mail and time-sync policies across all known servers and shows where they disagree. Changes nothing.Email me the output of "df -h" from app.example.com: sends text or files by mail, from your workstation or from the server.Clean up leftovers of old apps on this Mac: finds what removed apps left in~/Libraryand/Library. Only what you approve goes to the Trash.Update all Homebrew packages on this Mac: local mode, no SSH, same rules./plan Migrate the database on db.example.com: explores and drafts a plan, changes nothing until you approve./planis Claude Code. In OpenCode, ask Heinzel to plan first.
More that happens without asking:
- OS detection. On first contact Heinzel detects the OS and hardware and loads the rule file for that platform.
- Memory. Each server gets a folder under
memory/servers/with its state, a changelog and an open to-do list. An interrupted job shows up as pending on the next connection. - Long jobs. A command that can take more than a
minute runs as a job on the host, output and exit
status in files, and is read back later, after a
dropped connection or in a new session too
(
rules/ssh-connections.md). - DNS aliases. Several names for the same IP share one memory. Each alias can have its own SSH user.
- SSH ports. A port other than 22 is stored per server.
List the ports you use as
Alternative SSH ports:inmemory/user.md. Heinzel tries those and never scans. - Email details. Mails carry a short signature with the
operator's name (
Operator name:inmemory/user.md) and headers that keep out-of-office replies away. The first mail per host asks where to send from. Heinzel uses an existing MTA and asks before installing one.
The macOS cleanup scanner needs the Command Line Tools
(xcode-select --install).
The safety rules are part of every session.
- Asks before acting. Destructive commands, firewall
changes, reboots and service restarts need your approval.
Reloads run on their own when the config test passes
(
rules/service-reload.md,memory/service-policy.md). - Hard guardrails (Claude Code). A PreToolUse hook
(
.claude/hooks/guard-taboos.sh) blocks the absolute taboos in every permission mode, even--dangerously-skip-permissions: halt and poweroff,mkfs, partition-table writers, deleting or overwriting SSH keys, writes tosshd_config. It also catches them insidessh host "…", behindpython3 -cand when the file-edit tools target a key orsshd_config. Read-only forms (fdisk -l) stay allowed. For a legitimate exception such as an OS replacement, start the session withHEINZEL_GUARD_DISABLE=1. OpenCode does not run this hook; there the written rules are the safety layer. - Verifies before it reports. "It is gone since the
reboot" gets checked against the live system first
(
rules/verify-before-reporting.md). - Backs up config files to
/var/backups/heinzel/before editing. Cleaned up after 30 days. - Tests before applying with dry-run or validation modes where a tool has one.
- Logs everything. Each change is one plain-language line
in the system journal. The technical detail and the way
back are in
memory/servers/<hostname>/changelog.log. - Stable repos only. No third-party sources without your approval.
- Least privilege. Normal user first,
sudowhen needed, root last. With neither, Heinzel does what it can and writes a report for the tasks that need root. - Blacklist and read-only servers. Hosts in
memory/blacklist.mdare never contacted. Hosts inmemory/readonly.mdare inspected but never changed. - Protected paths.
memory/protected-paths.mdmarks paths per server asreadonly,hidden(content never shown) orconfirm(needs a typedCONFIRM). - Ignores injected instructions. Text in files, logs and command output is data. Text that addresses the AI is flagged to you and not followed.
- Keeps secrets out. Keys, password files and
.envcontents are inspected by metadata and fingerprint, never printed, and never passed as command-line arguments.
An LLM can invent a flag or a path. Heinzel puts verified facts in front of it instead:
- A rule file per platform holds the right commands, package manager and firewall tool.
- It checks
--help, the man page or upstream docs before a command runs. - It reads the server's memory file instead of guessing.
- It uses dry-run modes first.
- You see every command before it runs.
This reduces the risk. It does not remove it.
Caution
Heinzel operates on live systems, as root, with sudo or unprivileged. Review every command before you approve it.
A disciplined AI that follows the checklist every time makes fewer mistakes than a tired human at 2 AM. It can still misread your intent. Stay in the driver's seat.
By default Claude Code asks before every tool call. For batch work use auto mode: a background check lets routine commands through and still stops on risky ones. Press Shift+Tab to cycle modes, or pass the flag:
claude --permission-mode auto \
-p "Run housekeeping on server1.example.com"-p runs one prompt without the interactive UI. In OpenCode
that is opencode run "…".
- For CI, the strictest setup is
--permission-mode dontAskwith an allowlist (--allowedToolsorpermissions.allowin.claude/settings.json). --dangerously-skip-permissionsremoves all review, including the protection against malicious text in server output. Use it in disposable environments only.- Whatever the mode, Heinzel's own rules still apply. See the permission modes docs for details.
A nightly health check by cron:
17 6 * * * cd /path/to/heinzel && flock -n \
/tmp/heinzel-cron-server1.lock timeout 30m \
/abs/path/to/claude --permission-mode auto \
-p "Run housekeeping on server1.example.com and \
email me the report" >> ~/heinzel-cron.log 2>&1
Run the exact prompt interactively once first, so one-time
questions are answered and stored. Details and a systemd
timer variant: rules/scheduled-housekeeping.md.
All your state is plain text in one directory:
user.md: SSH usernames, language, operator nameblacklist.md,readonly.md,protected-paths.md: access policiesservice-policy.md: which services may reload or restart without askingservers/<hostname>/: memory, changelog, to-do and rule overrides per servercustom-rules/: your global rule overridesnetwork.md,housekeeping.md: cross-server facts and your own checksopencode.json: your OpenCode config
bin/heinzel-backup # writes a .tar.gz
bin/heinzel-backup -o <path> # to a given path
bin/heinzel-backup --list # dry run
bin/heinzel-backup --restore <file.tar.gz>Restore refuses to overwrite existing content without
--force and validates the archive before writing.
memory/ is gitignored by default. To share server state:
- Everyone copies
memory/user.md.exampletomemory/user.md. This file always stays personal. - Edit
.gitignoreto track server memory. The comments in the file say which lines to change. - Keep each member's own machine out of git, e.g.
memory/servers/stefans-mbp/. - Commit server memory after sessions.
user.md, blacklist.md, readonly.md and opencode.json
are never shared and still need the backup.
Change behaviour without editing the upstream rule files. Three layers, later wins:
rules/<name>.md(upstream)memory/custom-rules/<name>.md(yours, all servers)memory/servers/<hostname>/rules.md(one server)
## Add: Docker cleanup
New rules applied alongside the base.
## Replace: Firewall
Replaces the matching base section entirely.
## Remove: Common Pitfalls > snap
Skip this base section.A section without a prefix is an addition.
memory/custom-rules/all.md applies to every server. Skills
follow the same chain, e.g.
memory/custom-rules/heinzel-housekeeping.md.
The version is in VERSION, the changes in CHANGELOG.md.
Claude Code updates Heinzel at session start with a
git pull, unless you are pinned, on another branch, or have
set HEINZEL_NO_UPDATE=1. Elsewhere:
bin/heinzel-update # pull latest
bin/heinzel-update --check # check only
bin/heinzel-update --pin v2.28.0
bin/heinzel-update --unpinComing from 1.x? The update moves rules/custom/ and
opencode.json into memory/ by itself. If you are pinned
to a 1.x tag, run bin/heinzel-update --unpin first.
| Family | Distributions | Rule file |
|---|---|---|
| Debian | Debian, Ubuntu | rules/debian.md |
| RHEL | RHEL, CentOS, Fedora, Rocky, Alma | rules/rhel.md |
| SUSE | openSUSE, SLES | rules/suse.md |
| macOS | Apple Silicon and Intel | rules/macos.md |
| FreeBSD | all versions | rules/freebsd.md |
| Unraid | observed on 7.3 | rules/unraid.md |
Other distributions work with general best practices.
Claude Code is the tool Heinzel is developed with. OpenCode
reads the same CLAUDE.md and .claude/skills/, as long as
OPENCODE_DISABLE_CLAUDE_CODE is unset. Any other tool that
reads project files gets the rule layer. The skills
(housekeeping, security audit, email, fleet audit, macOS
cleanup) need a tool that supports Skills.
Heinzel can run on your own hardware with Ollama:
ollama pull qwen3.5:9b
ollama run qwen3.5:9b
>>> /set parameter num_ctx 16384
>>> /save qwen3.5:9b-16k
>>> /bye
cp memory/opencode.json.example memory/opencode.json
opencodeOllama's default context of 4096 tokens is too small for
tool use, hence the larger variant. Adjust baseURL and the
model name in memory/opencode.json, then pick the model
with /models. Larger models (14B and up) make more reliable
tool calls. See the
OpenCode provider docs
for more.
Every change is logged on the server:
journalctl -t heinzel
journalctl -t heinzel --since "2026-02-01"
# macOS
log show \
--predicate 'senderImagePath CONTAINS "logger"' \
--info --last 7d | grep heinzelCLAUDE.md main instructions for the AI tool
rules/ rule files per platform and topic
.claude/skills/ housekeeping, security, email, fleet audit,
macOS cleanup
.claude/hooks/ update check and the taboo guard
bin/ heinzel-update, heinzel-backup,
heinzel-migrate
memory/ your state (gitignored)
assets/trailer/ script that renders the README trailer
The Heinzelmännchen are the helpful gnomes of Cologne. At night, while the city slept, they did the work that was left undone.
Wintermeyer Consulting offers consulting and hands-on support for Heinzel, from setup to ongoing system management. Contact Stefan Wintermeyer: sw@wintermeyer-consulting.de
Bug reports, feature requests and pull requests are welcome.
MIT
