Make a directory, get a working HTTPS site — a Linux answer to Laravel Herd.
$ mkdir ~/Covey/shop
$ curl https://shop.localhostNo park, no link, no per-project config file. A directory in ~/Covey is a
site. That is the whole interface. When a site is broken, covey doctor says
why, and gives the command that fixes it.
TODO: add a short GIF of the omarchy-shell flyout and the settings window next to this terminal shot. The QML UI was not captured for this change.
- A directory in
~/Coveyis served athttps://<name>.localhost. - Local HTTPS that browsers actually trust (
covey trustinstalls the CA into the system store and the NSS store Chrome and Firefox read). *.localhostis resolved bysystemd-resolved. There is no dnsmasq and no/etc/hostsrewrite.- PHP version per site, from
.covey, thencomposer.jsonrequire.php, then the default. PHP 8.5 and 8.3 from Arch packages, side by side. - One shared MariaDB, PostgreSQL, Redis, and Mailpit, on loopback, with credentials a stock Laravel
.envalready has. PostgreSQL listens on5433so it can sit beside Supabase. Optional S3 via RustFS. covey doctorchecks the platform and every site. Each failure has a stable cause and, when something can fix it, the command.- Take one site down, or serve only one (
covey site solo), without the doctor staying red for a project you are not working on. - Put a site on a public URL with
covey share(cloudflared quick tunnels; no account). - Optional omarchy-shell status bar and settings window, written in QML. The window is a renderer over
covey doctorandcovey status. - Runs as your user. No root daemons. covey does not modify files inside a project.
- Requirements
- Install
- Quick start
- Comparison
- Commands
- How it works
- PHP versions
- Services
- Up and down
- Per-site up and down
- Sharing a site
- Certificates
covey doctor- Status bar
- Agents
- Scope
- Uninstall
- Contributing
- License
Supported today: Arch Linux and Arch derivatives that install caddy, php, php-fpm, and php-legacy from the Arch extra repository. That includes EndeavourOS, Manjaro, CachyOS, and Omarchy. The CLI shells out to pacman, and share/providers.tsv names those packages and their paths (/usr/bin/php, /etc/php).
Debian, Ubuntu, Fedora, and NixOS are not supported. A port is a new provider row and a package-manager branch, not a configuration flag.
Also required:
- A systemd user session. Everything runs as you. The stack can start at login; it does not run as a system service.
systemd-resolved, so*.localhostresolves to loopback.nss(certutil), socovey trustcan put the local CA where Chrome and Firefox will read it.- Permission to bind ports 80 and 443 from a user service. The install steps lower
net.ipv4.ip_unprivileged_port_startonce.
Optional, and detected rather than assumed:
| Piece | Used for |
|---|---|
| Docker and the Compose plugin | MariaDB, PostgreSQL, Redis, Mailpit, optional S3 |
composer |
composer check-platform-reqs inside covey doctor |
cloudflared |
covey share |
omarchy-shell |
Status bar and settings window |
This is the way to install covey today. covey install points
~/.local/share/covey at the checkout, so edits apply immediately and
moving the checkout breaks the running environment.
git clone https://github.com/gig3m/covey.git ~/Projects/covey
ln -s ~/Projects/covey/bin/covey ~/.local/bin/covey
covey php install 85 # php and php-fpm from pacmancovey is not on the AUR. The AUR has paused new account registration, so
the package cannot be submitted until it reopens. The PKGBUILD is ready in
packaging/aur/. When it is published it will depend on
caddy, php, php-fpm, and nss, with everything in the table above as
optional dependencies.
User services cannot bind ports below 1024 until the floor is lowered. Once:
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-covey.conf
sudo sysctl --systemLeave the distro caddy.service disabled. covey runs its own user unit, and the two of them want the same ports.
Arch enables almost no PHP extensions. covey php configure installs the ones a Laravel app needs and writes a single managed conf.d/covey.ini. Then trust the local CA and start:
covey install # config, user units, agent skill
covey php configure 85
covey trust # system store and the browser (NSS) store
covey upFor the database stack:
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER" # then log in againcovey up warns and still serves sites when Docker is absent. MariaDB, PostgreSQL, Redis, and Mailpit stay down until it is available.
Verify:
covey doctorRestart the browser after covey trust. Chrome and Firefox read the NSS store at startup.
mkdir ~/Covey/hello
printf '<?php echo "hello from covey\n";\n' > ~/Covey/hello/index.php
curl https://hello.localhostcovey sync runs when ~/Covey changes, so the directory is the only step.
A project with public/index.php is served from public/; anything else is
served from the directory itself.
A new Laravel app defaults to SQLite, which covey php configure has enabled:
composer create-project laravel/laravel ~/Covey/shop
curl -I https://shop.localhostMySQL matches a stock .env (127.0.0.1, user root, empty password, port
3306). Create the database yourself — covey will not do it for you:
covey db create shopPostgreSQL is the same credentials on port 5433.
covey is aimed at one property Herd made pleasant: a directory is a site, and you find out why it isn't. The other tools below are better at other jobs.
| covey | Herd | Valet | Lando | |
|---|---|---|---|---|
| Where it runs | Arch-based Linux | macOS, Windows | macOS; Linux ports exist | macOS, Windows, Linux |
| A site appears when | you mkdir in ~/Covey |
a directory is parked | you park or link |
you init and start that app |
| Names | *.localhost, via systemd-resolved |
*.test, via dnsmasq |
*.test, via dnsmasq |
its own proxy hostnames |
| PHP | Arch packages, shared FPM pools | bundled binaries | Homebrew or distro PHP | a container per app |
| Databases | one shared Compose stack, loopback only | Herd Pro | bring your own | declared per app |
| Files in the project | none, unless you pin a version in .covey |
none for the basic case | none for the basic case | a recipe |
Herd is the more finished product on the systems it supports: a real GUI, and
PHP builds that never touch the system interpreter. covey is for the machine
Herd does not ship on. The php on your PATH is the PHP that serves the site.
Valet is the idea this is descended from. Valet Linux Plus is the closest
Linux port, and a good fit if you want park / link and .test. It takes
ownership of nginx and dnsmasq, which fights systemd-resolved and Tailscale
MagicDNS. covey leaves DNS alone and serves *.localhost instead.
Lando is the right tool when each project needs its own stack and a file that describes it. That travels between machines. DDEV makes the same trade (a config step, containers per project) and is equally serious about it. covey keeps one shared stack and does not write a config file into your repository.
| Command | Does |
|---|---|
covey install |
Render config, link systemd units and the agent skill |
covey up / down / restart |
Bring the whole stack up or down (start/stop are aliases) |
covey autostart [on|off] |
Whether the stack starts at login |
covey status [--json] |
Stack state, memory, services, PHP versions and settings |
covey doctor [--json] |
Check the platform and every site |
covey sites |
List sites, their PHP version and URLs |
covey site up|down <name> |
Take one site in or out of service |
covey site solo <name> / restore |
Serve only one site; put the rest back |
covey share [<name>] / covey unshare <name>|--all |
Put a site on a public URL; list or end shares |
covey sync |
Regenerate site config from ~/Covey, reload |
covey php list |
Show PHP providers |
covey php install <tag> |
Install a PHP provider |
covey php configure <tag> |
Enable the extension set for a provider |
covey php settings / set <key> <value> / unset <key> |
php.ini values for served sites |
covey services [up|down|status|logs] |
The MySQL/PostgreSQL/Redis/Mailpit stack |
covey services enable|disable <name> |
Add an optional service (s3) to the stack, or take it out |
covey db [list|create|drop|shell] |
Databases |
covey trust |
Trust the local CA (system + browser stores) |
covey logs [caddy|php] |
Tail logs |
systemd-resolved already resolves every *.localhost name to loopback.
covey does not run dnsmasq and does not edit /etc/hosts, so a network
change has nothing of its own to repair.
Everything is a user systemd unit under one target, so the environment starts, stops, and reports as a single thing:
covey.target
├── covey-caddy.service Caddy on :80/:443
├── covey-fpm@<ver>.service one FPM pool per PHP version, started on demand
├── covey-sync.path watches ~/Covey, regenerates config
└── covey-services.service docker compose: mysql, postgres, redis, mailpit (+ enabled extras)
Running as your user rather than http is deliberate: the FPM pool reads
~/Covey with no ACLs or permission workarounds.
Site config is generated, not wildcard-matched. covey sync writes one
Caddy block per site and a path unit runs it when ~/Covey changes, so mkdir
is normally all you do. Generation is not an implementation detail — a wildcard
*.localhost certificate cannot work, because TLS clients reject a wildcard
with only one label after it (*.localhost is treated like *.com). Each site
therefore needs its own named block and its own certificate. Usefully, each
generated block also carries that site's FPM socket, which is what makes
per-site PHP versions fall out for free.
Document root is chosen per request shape: if <site>/public/index.php exists
it is used (framework layout), otherwise the directory itself is served.
A site's PHP version resolves in this order:
.coveyin the site directory — an explicit pincomposer.jsonrequire.php— the constraint is resolved against installed providers (^,~,>=,X.Y.*,||)- The default
If the default already satisfies the constraint, it wins — most sites share one
pool. .covey is the escape hatch for "this project stays on 8.3 even though
composer would allow newer", which is the case composer cannot express.
# ~/Covey/myapp/.covey
php = 8.3
database = myappProviders live in share/providers.tsv; adding a version is one row.
Out of the box: 8.5 (php) and 8.3 (php-legacy), which coexist cleanly.
Arch ships most PHP extensions as .so files and enables almost none, and
the php-redis/php-igbinary packages ship their .ini with the
extension= line commented out. Stock Arch PHP therefore has no pdo_mysql
and no sqlite at all — a Laravel app cannot reach a database.
covey php configure <tag> installs the packages and writes one managed
conf.d/covey.ini enabling:
iconv bcmath gmp exif intl sockets mysqli pdo_mysql sqlite3 pdo_sqlite
pdo_pgsql pgsql gd sodium igbinary redis
The single managed file also fixes load order (igbinary must precede redis).
Because that list is fixed, it cannot know what a project needs. So
covey doctor additionally runs composer check-platform-reqs per site and
reports anything missing.
Served sites get covey's defaults, chosen so a fresh local app does not fall over on PHP's stock 2M uploads:
| Setting | covey default |
|---|---|
memory_limit |
512M |
upload_max_filesize |
64M |
post_max_size |
64M |
max_execution_time |
60 |
max_input_vars |
5000 |
$ covey php set memory_limit 1G
covey: memory_limit = 1G (served sites; pools reloaded)
$ covey php set date.timezone America/Chicago # any setting PHP knows
$ covey php settings # what is in effect
$ covey php unset memory_limit # back to covey's defaultValues are limited to letters, digits, spaces and . / : _ - + ~ & | ^ !
(enough for sizes, times, timezones and E_ALL & ~E_DEPRECATED), because they
are written into the FPM pool config; a hand-edited line outside that is ignored
and reported by covey doctor, and a pool config php-fpm -t rejects is never
installed.
Changes live in ~/.config/covey/php.ini and apply to every PHP version's
pool, rendered as php_value so an app can still ini_set() over them. Pools
are reloaded, not restarted (php-fpm re-reads its config on USR2 and keeps
its socket), so a change never drops a request. Names PHP does not recognise are
rejected rather than silently ignored, and covey doctor flags
upload_max_filesize larger than post_max_size, which makes uploads vanish
without an error.
These reach sites covey serves. The php command line (artisan, composer)
reads /etc/php as usual.
MySQL (MariaDB), PostgreSQL, Redis and Mailpit run as one Docker Compose stack,
bound to loopback only. Credentials are chosen so a stock Laravel .env works
unchanged (PostgreSQL needs DB_PORT=5433; 5432 is left for Supabase):
| Service | Address | Credentials |
|---|---|---|
| MySQL | 127.0.0.1:3306 |
root, empty password |
| PostgreSQL | 127.0.0.1:5433 |
root, empty password (trust auth) |
| Redis | 127.0.0.1:6379 |
none |
| Mailpit | SMTP 127.0.0.1:1025, UI https://mail.localhost |
none |
Databases are created explicitly with covey db create <name> — covey does not
provision them behind your back.
mail.localhost belongs to covey, so a project directory called ~/Covey/mail
is not served; covey doctor reports it as reserved_name. The same goes for
~/Covey/s3.
Some services are not part of the stack until you add them. Adding one changes what the stack is, not whether a service is running: the stack still starts and stops as a whole.
$ covey services enable s3
covey: s3 is part of the stack and running
$ covey services disable s3
covey: s3 is no longer part of the stack (its data volume is kept)| Service | Address | Credentials |
|---|---|---|
| S3 (RustFS) | API 127.0.0.1:9000, console https://s3.localhost |
key covey, secret coveysecret |
For Laravel's s3 disk (composer require league/flysystem-aws-s3-v3):
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=covey
AWS_SECRET_ACCESS_KEY=coveysecret
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=<bucket>
AWS_ENDPOINT=http://127.0.0.1:9000
AWS_USE_PATH_STYLE_ENDPOINT=true
Create the bucket in the console. As with databases, covey does not make one for you.
The choice is stored as COMPOSE_PROFILES=s3 in ~/.config/covey/services.env.
covey-services.service starts and stops the stack through covey, which reads
that file under the same lock enable and disable take, so the unit and covey
always agree on what the stack contains. Enabling while the services are running
starts the new service right away; if it fails to start, the command fails, and
running it again retries. Disabling stops and removes its container, and leaves
the service in the stack if that fails. s3.localhost
belongs to covey whether S3 is enabled or not. While it is disabled, the page
says how to enable it.
covey status lists the same table with live state, and covey status --json
carries each service's address, credentials and the exact .env lines — the
inventory the bar's config pane reads.
The whole stack — Caddy, an FPM pool per PHP version in use, and four containers — is a few hundred megabytes resident. On a day that is not a PHP day, hand it back:
$ covey status
STACK running (437 MiB)
UNIT STATE MEMORY
covey-caddy.service active 84 MiB
covey-fpm@85.service active 43 MiB
covey-fpm@83.service active 41 MiB
covey-sync.path active -
covey-services.service active -
CONTAINER MEMORY
covey-mysql-1 171 MiB
covey-postgres-1 50 MiB
covey-redis-1 28 MiB
covey-mailpit-1 21 MiB
SERVICE STATE ADDRESS OPEN
mysql up 127.0.0.1:3306 covey db shell
postgres up 127.0.0.1:5433
redis up 127.0.0.1:6379
mailpit up 127.0.0.1:1025 https://mail.localhost
PHP VERSION POOL SITES
8.5 8.5.10 running 7
8.3 8.3.33 stopped -
$ covey down
covey is downcovey up brings it back. Both are one step because every unit is PartOf=
covey.target; stopping the target stops the web server, every pool, and the
compose services together.
A stopped stack is not a broken one. covey doctor derives its state
(up, degraded, down) from the target, and while covey is down it runs only
the static checks — what is installed and configured — and skips the runtime
ones. It does not report eight failures, each with a fix telling you to start
what you just stopped:
$ covey doctor
STACK stopped
PLATFORM
ok php-8.5 8.5.9
ok extensions-8.5
ok browser-trust local CA in NSS store
...
covey is stopped - run `covey up` to start itcovey down is session only: it stops what is running now and leaves the
login behaviour alone. covey autostart off is the separate, persistent choice.
Container memory costs a ~2s docker stats sample, so it is measured only where
you asked for it — covey status and covey doctor --json --resources — never
on the path the bar widget polls.
covey down is all or nothing. To take a single site out of service and leave
the rest running:
$ covey site down baseline
covey: baseline is down (covey site up baseline to bring it back)
$ curl -s https://baseline.localhost
covey: baseline is disabled
bring it back: covey site up baseline
$ covey site up baseline
covey: baseline is up - https://baseline.localhostA disabled site keeps a Caddy block on purpose, so it answers with a 503 that explains itself. Dropping the block would give a bare TLS error instead — the kind of unexplained failure covey exists to remove.
What this is for is quiet, not memory. FPM pools are per PHP version and shared by every site on it, so switching off one of a dozen sites on 8.5 frees nothing. What it does do:
covey doctorstops counting it. A disabled site emits no check that can fail, so doctor going green — and the bar staying calm — means "everything I care about today is fine", instead of being permanently red because of a half-finished project you are not working on. Same rule as a stopped stack: off on purpose is a state, not a failure.- It does free a pool when it is the last site on a version.
covey syncstops any FPM pool no enabled site asks for (~25 MiB each).
The list of what is off lives in ~/.config/covey/disabled, one name per line.
It is never written into the project — covey does not touch your files.
With many projects checked out, the common case is "just this one today":
$ covey site solo shop
covey: only shop is up - https://shop.localhost (covey site restore to end)
$ covey site restore
covey: solo ended, sites restoredrestore puts back the list that was off before solo, not "everything on" —
a half-finished project you had switched off stays off. Soloing a second site
while already in solo keeps that original list. covey doctor shows a SOLO
line while it is in effect, and the saved list lives in ~/.config/covey/solo.
Put a site on a public URL — to show a client, or to test on a phone that is not on your network:
$ covey share shop
covey: shop is shared at https://words-like-these.trycloudflare.com
covey: anyone with the URL can reach this site. With APP_DEBUG=true an
error page shows your .env - turn it off, or share for as long as you need.
end it with: covey unshare shop
$ covey share # what is shared
$ covey unshare --allIt uses cloudflared quick tunnels
(sudo pacman -S cloudflared): no account, no token, a new random URL each
time. The flyout's share / unshare / link actions and the settings
window's Sharing section do the same thing.
How it fits together:
- covey gives a shared site a loopback listener on
127.0.0.1:41xxx(the port comes from the site name) that serves it whateverHostarrives, and tells PHP the request is HTTPS so the app buildshttps://links. Because it is public, it is stricter than the local one: any dot-path (.env,.git/,.well-known/) is refused, and a project with apublic/directory,composer.jsonorartisanis served frompublic/only — ifpublic/index.phpdisappears mid-checkout the share returns 404 rather than falling back to the project root. - The tunnel is one row of data, in
share/tunnels.tsv: the binary, its arguments, and a regex that finds the public URL in its output. Another tool (ngrok, a named Cloudflare tunnel) is another row moved to the top — covey is not tied to Cloudflare. - A share is a user unit,
covey-share@<site>.service, undercovey.target. Whether a site is shared is read from systemd, socovey downends every share, and nothing comes back by itself atcovey upor login. Taking a site down (or soloing another) ends its share too.
Limits: quick tunnels are best effort (no uptime guarantee, rate limited), so
they suit a demo, not a webhook you depend on. The URL is held back until
public DNS has it, but if your machine looked it up too early and cached the
miss, resolvectl flush-caches. covey does not touch the app, so if it
generates links from APP_URL they will still say .localhost — doctor's
share check shows the public URL; fixing the app's config is yours.
covey trust installs the local CA into both the system trust store and the
browser (NSS) store at ~/.pki/nssdb.
Both halves matter. caddy
8000
trust writes only the system store, which satisfies
curl — while Chrome and Firefox on Linux read NSS and reject the certificate.
That failure is invisible to command-line testing, so covey doctor checks it
explicitly. Browsers read NSS at startup: restart your browser after running
covey trust.
$ covey doctor
PLATFORM
ok caddy
ok php-8.5 8.5.9
ok extensions-8.5
ok browser-trust local CA in NSS store
ok mysql 127.0.0.1:3306
myapp https://myapp.localhost
ok php 8.3.33 (from composer.json)
FAIL database database 'myapp' does not exist
fix: covey db create myapp--json emits the same model for scripts and agents; --cached [ttl] serves a
recent result for polling. Exit code is 0 when everything passes, 1
otherwise.
Every failing check carries a stable problem code and either a runnable
fix.cmd or — when nothing can fix it automatically — a hint:
{ "check": "extensions-8.3", "ok": false,
"problem": "extensions_missing",
"detail": "pdo_mysql gd",
"fix": { "cmd": "covey php configure 83", "needs_root": true } }Branch on problem, never on detail. A hint is prose — never execute it.
Full code table: agents/skills/covey/troubleshooting.md.
covey ships an omarchy-shell bar widget. covey install
links it; enable it with:
omarchy plugin enable coveyOne icon: normal while every check passes, red when any fails, dimmed while the stack is deliberately down. Clicking opens a flyout listing every site under management with its state — PHP version when healthy, a short problem label when not — and a row that takes the stack up or down.
- Click a site's name to open it in the browser.
- Its switch runs
covey site up|down— the quick way to silence a site that is red because you are not working on it. - Hover a row for
solo,term(a terminal in the project directory) andcopy(the URL). - Sites that are off fold into an Off (n) group; in solo mode a banner offers Restore.
SITES php 8.5, 8.3
● shop 8.5 [■]
● stack 8.5 [■]
● needsdb no database [■]
▸ Off (2)
Stack running Stop →
All checks passed Full report →
omarchy-shell covey up and ... down do the same thing without a mouse.
Settings → in the flyout (or omarchy-shell covey settings) opens covey's
settings window, the equivalent of Herd's:
- Sites — every site with its switch, URL and path, Open / Terminal / Copy URL / Solo, which PHP it runs and why (and which file to edit to change it), and its checks, each failing one with its fix and a Run button.
- Services — MySQL, PostgreSQL, Redis and Mailpit: state, address,
credentials, image, memory, the
.envlines with a Copy button, MySQL's databases, Mailpit's UI, and one start/stop for the lot. - PHP — the php.ini settings (edit a value and press Enter; Reset / Remove; add any other setting), then each provider's version, pool state, the sites using it and its extensions, with Install / Enable extensions when something is missing.
- General — stack start/stop, start at login, the sites folder, browser trust, the :80/:443 sysctl, and the logs.
Keys: 1–4 or Tab switch tabs, ↑/↓ move through sites, Esc closes.
omarchy-shell shell summon covey '{"tab":"services"}' opens a given tab.
The window renders covey status --json and covey doctor --json, and every
button runs a covey command; anything needing root opens in a terminal so sudo
can ask. It never edits a project: changing a site's PHP version means editing
that project's .covey or composer.json, and the window says which.
It is a renderer over covey doctor --json, not a second source of truth.
covey is agent-accessible by design, following the pattern omarchy uses: the
tool owns its documentation and symlinks it into the agent's skill directory.
covey install links agents/skills/covey into ~/.claude/skills/covey.
That works because the check layer built for the status bar is exactly what an
agent needs — one JSON model, three renderers (terminal, bar, agent). An agent
can read covey doctor --json, branch on problem, and run fix.cmd.
The scope boundary below doubles as the safety boundary: covey exposes no way to modify files inside a project, so an agent driving it can install a PHP version or start a service but cannot wander into your source.
covey manages the platform. It never modifies files inside a project.
In: serving, TLS, PHP versions and extensions, MySQL/PostgreSQL/Redis/Mailpit, databases on request, and checks for all of the above.
Out: .env, APP_KEY, storage/ permissions, composer install,
vendor/, npm and asset builds, queue workers, schedulers.
If covey doctor passes and your app still misbehaves, it is app-level — and
covey will tell you that rather than pretend otherwise.
There is no covey uninstall command. The steps below remove what covey
wrote. They do not remove ~/Covey. That directory is your projects.
Stop the stack first, while the program is still on disk:
covey unshare --all
covey autostart off
covey downDrop the database containers and their volumes if you used them and want the data gone. Skip this when Docker was never set up. Do it before removing the package or the checkout; the Compose file lives in the covey tree.
docker compose -f ~/.local/share/covey/share/compose/covey.yaml --profile '*' down -vRemove the user units and covey's own config:
systemctl --user disable --now covey.target 2>/dev/null || true
rm -f ~/.config/systemd/user/covey.target \
~/.config/systemd/user/covey-caddy.service \
~/.config/systemd/user/covey-fpm@.service \
~/.config/systemd/user/covey-share@.service \
~/.config/systemd/user/covey-sync.service \
~/.config/systemd/user/covey-sync.path \
~/.config/systemd/user/covey-services.service \
~/.claude/skills/covey \
~/.config/omarchy/plugins/covey \
~/.local/share/covey
rm -rf ~/.config/systemd/user/covey.target.wants \
~/.config/systemd/user/default.target.wants/covey.target
systemctl --user daemon-reload
rm -rf ~/.config/covey~/.local/share/covey is a symlink to your checkout. Removing the symlink
does not remove the checkout. Remove the ~/.local/bin/covey symlink too.
Root-owned leftovers, only if you want them gone:
sudo rm -f /etc/sysctl.d/99-covey.conf /etc/php/conf.d/covey.ini /etc/php-legacy/conf.d/covey.ini
sudo sysctl --system
sudo caddy untrust
certutil -d "sql:$HOME/.pki/nssdb" -D -n "Caddy Local Authority - 2026 ECC Root"The NSS nickname includes the year Caddy used when it minted the CA. If
-D says the nickname is missing, certutil -d "sql:$HOME/.pki/nssdb" -L
lists the one to delete. caddy untrust removes it from the system store.
Then remove the program:
rm -rf ~/Projects/covey # once nothing points at itIssues and pull requests are welcome. The layout, the checks to run, and the two invariants that matter are in CONTRIBUTING.md. Read docs/HACKING.md before changing behaviour, and docs/DESIGN.md before proposing a different shape.
MIT — see LICENSE. Copyright (c) 2026 Kyle Arrington.