8000 GitHub - gig3m/covey: A local PHP development environment for Linux. Make a directory, get a working HTTPS site. · GitHub
[go: up one dir, main page]

Skip to content
gig3mPublic

About

A local PHP development environment for Linux. Make a directory, get a working HTTPS site.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

covey

Make a directory, get a working HTTPS site — a Linux answer to Laravel Herd.

$ mkdir ~/Covey/shop
$ curl https://shop.localhost

No 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.

A new directory in ~/Covey showing up in covey sites, with its HTTPS URL and the installed PHP versions

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.


Features

  • A directory in ~/Covey is served at https://<name>.localhost.
  • Local HTTPS that browsers actually trust (covey trust installs the CA into the system store and the NSS store Chrome and Firefox read).
  • *.localhost is resolved by systemd-resolved. There is no dnsmasq and no /etc/hosts rewrite.
  • PHP version per site, from .covey, then composer.json require.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 .env already has. PostgreSQL listens on 5433 so it can sit beside Supabase. Optional S3 via RustFS.
  • covey doctor checks 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 doctor and covey status.
  • Runs as your user. No root daemons. covey does not modify files inside a project.

Contents


Requirements

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 *.localhost resolves to loopback.
  • nss (certutil), so covey trust can 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_start once.

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

Install

From a checkout

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 pacman

AUR (not yet)

covey 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.

Finish setup

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 --system

Leave 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 up

For the database stack:

sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"   # then log in again

covey up warns and still serves sites when Docker is absent. MariaDB, PostgreSQL, Redis, and Mailpit stay down until it is available.

Verify:

covey doctor

Restart the browser after covey trust. Chrome and Firefox read the NSS store at startup.

Quick start

mkdir ~/Covey/hello
printf '<?php echo "hello from covey\n";\n' > ~/Covey/hello/index.php
curl https://hello.localhost

covey 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.localhost

MySQL 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 shop

PostgreSQL is the same credentials on port 5433.

Comparison

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.

Commands

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

How it works

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.

PHP versions

A site's PHP version resolves in this order:

  1. .covey in the site directory — an explicit pin
  2. composer.json require.php — the constraint is resolved against installed providers (^, ~, >=, X.Y.*, ||)
  3. 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 = myapp

Providers 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.

Extensions

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.

php.ini settings

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 default

Values 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.

Services

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.

Optional services

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.

Up and down

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 down

covey 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 it

covey 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.

Per-site up and down

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.localhost

A 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 doctor stops 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 sync stops 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.

Solo

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 restored

restore 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.

Sharing a site

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 --all

It 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 whatever Host arrives, and tells PHP the request is HTTPS so the app builds https:// 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 a public/ directory, composer.json or artisan is served from public/ only — if public/index.php disappears 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, under covey.target. Whether a site is shared is read from systemd, so covey down ends every share, and nothing comes back by itself at covey up or 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.

Certificates

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

$ 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.

Status bar

covey ships an omarchy-shell bar widget. covey install links it; enable it with:

omarchy plugin enable covey

One 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) and copy (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

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 .env lines 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.

Agents

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.

Scope

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.

Uninstall

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 down

Drop 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 -v

Remove 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 it

Contributing

Issues 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.

License

MIT — see LICENSE. Copyright (c) 2026 Kyle Arrington.

About

A local PHP development environment for Linux. Make a directory, get a working HTTPS site.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

0