Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,16 +277,16 @@ uses `bin/compose.yml` with pre-built GHCR images.

Sorted by what a command does to state that cannot be recreated.

**Read freely.** `status`, `show-config`, `tailscale-peers`, `list-certs`,
**Read freely.** `status`, `show-config`, `tailscale-peers`, `certs list`, `certs describe`,
`logs`, `self-test`.

**Run deliberately.** `start*`, `restart`, `stop-metrics`, `stop-tailscale`,
`upgrade`, `self-update`, `configure-dns`, `generate-mkcert`,
`upgrade`, `self-update`, `configure-dns`, `certs generate`,
`tailscale-peers --refresh`. Recoverable, but they restart containers, rewrite
system DNS, or install a certificate authority.

**Ask first.** `clean` and `destroy` (both remove volumes, so both take
monitoring data; `destroy` also removes images), `remove-cert`,
monitoring data; `destroy` also removes images), `certs delete`,
`docker compose down -v`, `git push --force`, and any write to
`~/.local/spark/http-proxy/state` — that directory is a trust input rather than
a cache, since its contents decide whose traffic is forwarded where.
Expand Down Expand Up @@ -335,8 +335,11 @@ The CLI is one Bash script plus libraries in `bin/lib/`, sourced at startup. Fol
2. `case` dispatch block (before the `*` catch-all)
3. `show_usage` help text
4. `generate_completion` commands string
- Commands that do not need Docker (e.g. pure git or config ops) must be added to the
prerequisite skip list near line 326
- Commands that do not need Docker (e.g. pure git or config ops) must be added to
`needs_prerequisites`
- The test suite sources the script up to the first top-level `case "$1" in`, the
dispatch, so a top-level `case` placed before it hides every later function from
the tests; keep such logic inside a function

Lint with `-x` and every file, or shellcheck does not follow the libraries and their
code goes unchecked:
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- `spark-http-proxy certs` groups the certificate commands: `list` is a table of every certificate with its files, `describe` reads one with openssl and says what it covers and whether the proxy serves it, `generate` and `delete` keep their behaviour ([#160](https://github.com/sparkfabrik/http-proxy/issues/160))
- `spark-http-proxy hosts` lists what the proxy serves, which machine serves it, and the directory local containers run from
- HTTPS to a forwarded hostname needs a certificate on the machine reaching it, now documented ([#118](https://github.com/sparkfabrik/http-proxy/issues/118))
- `tailscale-refresh-peers` runs a discovery cycle now instead of waiting for the next one ([#132](https://github.com/sparkfabrik/http-proxy/issues/132))
Expand Down Expand Up @@ -49,6 +50,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `generate-mkcert` and `remove-cert` reject an argument containing a path ([#113](https://github.com/sparkfabrik/http-proxy/issues/113))
- `self-test` verifies end-to-end routing rather than only DNS liveness ([#104](https://github.com/sparkfabrik/http-proxy/issues/104))

### Deprecated

- `generate-mkcert`, `list-certs` and `remove-cert` still run but warn and point at `certs generate`, `certs list` and `certs delete` ([#160](https://github.com/sparkfabrik/http-proxy/issues/160))

### Fixed

- `hosts` reports only what Traefik serves, including containers whose rule names several hostnames
Expand Down
76 changes: 55 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Simply add `VIRTUAL_HOST=myapp.local` to any container or use native Traefik lab
- [Automatic HTTP and HTTPS Routes](#automatic-http-and-https-routes)
- [Self-Signed Certificates](#self-signed-certificates)
- [Trusted Local Certificates with mkcert](#trusted-local-certificates-with-mkcert)
- [Listing and Removing Certificates](#listing-and-removing-certificates)
- [Listing, Describing and Removing Certificates](#listing-describing-and-removing-certificates)
- [Manual Certificate Generation (Alternative)](#manual-certificate-generation-alternative)
- [Start the proxy](#start-the-proxy)
- [How Certificate Matching Works](#how-certificate-matching-works)
Expand Down Expand Up @@ -101,10 +101,10 @@ spark-http-proxy start

# Generate trusted SSL certificates
# Option 1: Wildcard certificate (covers nginx.spark.loc, api.spark.loc, etc.)
spark-http-proxy generate-mkcert "*.spark.loc"
spark-http-proxy certs generate "*.spark.loc"

# Option 2: Specific certificate (covers only nginx.spark.loc)
spark-http-proxy generate-mkcert "nginx.spark.loc"
spark-http-proxy certs generate "nginx.spark.loc"

# Run an nginx container
docker run -d -e VIRTUAL_HOST=nginx.spark.loc nginx
Expand All @@ -119,8 +119,8 @@ curl https://nginx.spark.loc

When generating certificates, you can choose between specific domains or wildcards:

- **Specific certificate**: `spark-http-proxy generate-mkcert "nginx.spark.loc"` - covers only `nginx.spark.loc`
- **Wildcard certificate**: `spark-http-proxy generate-mkcert "*.spark.loc"` - covers `nginx.spark.loc`, `api.spark.loc`, etc.
- **Specific certificate**: `spark-http-proxy certs generate "nginx.spark.loc"` - covers only `nginx.spark.loc`
- **Wildcard certificate**: `spark-http-proxy certs generate "*.spark.loc"` - covers `nginx.spark.loc`, `api.spark.loc`, etc.

**⚠️ Important**: Wildcard certificates have nesting limitations. A certificate for `*.spark.loc` will NOT work for nested domains like `test.foo.spark.loc`. To match nested domains, you need to generate a more specific wildcard like `*.foo.spark.loc`.

Expand Down Expand Up @@ -519,44 +519,78 @@ This is implemented using Traefik's `disable-hsts` middleware applied to the HTT

### Trusted Local Certificates with mkcert

For browser-trusted certificates without warnings, use the `spark-http-proxy generate-mkcert` command. This command automatically handles the entire certificate generation process:
For browser-trusted certificates without warnings, use the `spark-http-proxy certs generate` command. This command automatically handles the entire certificate generation process:

```bash
# Generate wildcard certificate for .loc domains
spark-http-proxy generate-mkcert "*.loc"
spark-http-proxy certs generate "*.loc"

# Generate certificates for specific domains
spark-http-proxy generate-mkcert "myapp.local"
spark-http-proxy certs generate "myapp.local"

# For complex multi-level domains, generate additional certificates:
spark-http-proxy generate-mkcert "*.project.loc"
spark-http-proxy certs generate "*.project.loc"
```

The `generate-mkcert` command automatically:
The `certs generate` command automatically:

- **Installs mkcert** if not already available (using Homebrew on macOS)
- **Creates the certificate directory** (`~/.local/spark/http-proxy/certs`)
- **Generates certificates** with safe filenames for wildcard domains
- **Applies the certificate** to the running proxy, without restarting it and
without dropping connections to anything else it is serving

#### Listing and Removing Certificates
#### Listing, Describing and Removing Certificates

List the certificates currently installed in the certificate directory:
List the certificates currently installed, one row each, with the file that holds the certificate and the one that holds its key:

```bash
spark-http-proxy list-certs
spark-http-proxy certs list
```

```
Certificates in ~/.local/spark/http-proxy/certs

DOMAIN CERTIFICATE KEY
api.spark.loc api.spark.loc.pem api.spark.loc-key.pem
*.spark.loc _wildcard_.spark.loc.pem _wildcard_.spark.loc-key.pem

2 certificates. Remove one with: spark-http-proxy certs delete '*.spark.loc'
```

A wildcard is stored as `_wildcard_`, and a key that is not beside its certificate shows as `missing`. `certs list` needs neither Docker nor the proxy.

Describe one certificate to see what it covers, its validity, who issued it, and whether the running proxy is serving it. Name a hostname instead of a certificate and the command finds the certificate that covers it, or says why none does:

```bash
spark-http-proxy certs describe "*.spark.loc"
spark-http-proxy certs describe "app.spark.loc" # covered by *.spark.loc
spark-http-proxy certs describe "a.b.spark.loc" # not covered: a wildcard matches one label
```

```
*.spark.loc
certificate ~/.local/spark/http-proxy/certs/_wildcard_.spark.loc.pem
private key ~/.local/spark/http-proxy/certs/_wildcard_.spark.loc-key.pem
covers *.spark.loc
valid 2025-07-13 to 2027-10-13
issued by mkcert paolo@workstation
served yes, by the running proxy
```

`certs describe` reads the certificate with `openssl`, which sparkdock installs as `openssl@3`. Without a usable `openssl` it stops with a message saying so; the other `certs` commands do not depend on it.

Remove certificate pairs for one or more domains. This deletes both the `.pem` and `-key.pem` files and applies the change to the running proxy, which stops serving the removed certificates without being restarted:

```bash
spark-http-proxy remove-cert "nginx.spark.loc"
spark-http-proxy remove-cert "*.spark.loc"
spark-http-proxy remove-cert "nginx.spark.loc" "api.spark.loc" "*.old.loc"
spark-http-proxy certs delete "nginx.spark.loc"
spark-http-proxy certs delete "*.spark.loc"
spark-http-proxy certs delete "nginx.spark.loc" "api.spark.loc" "*.old.loc"
```

Pass the same domains you used with `generate-mkcert`, including wildcards. The command lists every match, reports any domain it cannot find, and asks for a single confirmation before deleting.
Pass the same domains you used with `certs generate`, including wildcards. The command lists every match, reports any domain it cannot find, and asks for a single confirmation before deleting.

The former names `generate-mkcert`, `list-certs` and `remove-cert` still work, print a deprecation warning naming their replacement, and are no longer listed in the help or the shell completion.

#### Manual Certificate Generation (Alternative)

Expand All @@ -575,7 +609,7 @@ mkcert -cert-file ~/.local/spark/http-proxy/certs/wildcard.loc.pem \
"*.loc"
```

**Note**: A certificate written by hand is not picked up on its own. It is applied the next time the proxy starts, or immediately with `spark-http-proxy restart`. Certificates made with `spark-http-proxy generate-mkcert` need neither.
**Note**: A certificate written by hand is not picked up on its own. It is applied the next time the proxy starts, or immediately with `spark-http-proxy restart`. Certificates made with `spark-http-proxy certs generate` need neither.

#### Start the proxy

Expand All @@ -587,7 +621,7 @@ spark-http-proxy start

Start it this way rather than with `docker compose` directly. The command creates the directories the proxy bind-mounts before the containers do. Docker creates a missing bind-mount source itself, owned by root, and a certificate directory owned by root cannot be written to afterwards.

If a machine already reached that state, `generate-mkcert` fails with a permission error and peer routing stops with a `chmod` error. Take the directories back:
If a machine already reached that state, `certs generate` fails with a permission error and peer routing stops with a `chmod` error. Take the directories back:

```bash
sudo chown -R "$(id -un)" ~/.local/spark/http-proxy/certs ~/.local/spark/http-proxy/state
Expand Down Expand Up @@ -832,7 +866,7 @@ forwarding any hostname it does not serve locally to `http://<peer>:80` with the
does not itself serve. A wildcard covers one label level, so `*.spark.loc`
covers `app.spark.loc` but not `app.client.spark.loc`, and a forwarded
hostname outside the wildcard you hold produces a browser warning until you
run `spark-http-proxy generate-mkcert` for it. Improving this is tracked in
run `spark-http-proxy certs generate` for it. Improving this is tracked in
[#118](https://github.com/sparkfabrik/http-proxy/issues/118).
- **Only your own machines are used.** A machine is used when the Tailscale
status document says it belongs to the same account as this one. That check
Expand Down Expand Up @@ -1029,7 +1063,7 @@ $ curl https://macos.test.spark.loc/
The fix is the usual command, run on the machine doing the reaching:

```bash
spark-http-proxy generate-mkcert 'macos.test.spark.loc'
spark-http-proxy certs generate 'macos.test.spark.loc'
```

**A wildcard covers exactly one label, which is the part that surprises people.**
Expand Down
Loading
Loading