diff --git a/.gitattributes b/.gitattributes index 4a9d8e8..2d1846a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ *.png filter=lfs diff=lfs merge=lfs -text *.gif filter=lfs diff=lfs merge=lfs -text +*.ico filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 3e398c1..804b7ec 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -5,6 +5,10 @@ on: branches: - main +permissions: + contents: read + pull-requests: write + jobs: lint: runs-on: ubuntu-latest @@ -13,7 +17,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v6 with: - python-version: "3.11" + python-version: "3.13" - name: Sync dependencies run: uv sync --all-extras - name: Ruff lint diff --git a/CLAUDE.md b/CLAUDE.md index 01dff92..26d4d4a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -131,10 +131,10 @@ the dashboard and TUI live activity logs. Cron entry (installed by the app): ```bash -0 3 * * * /usr/bin/python3 -m ssltui --renew >> ~/.local/share/ssltui/renewal.log 2>&1 +0 3 * * * /usr/bin/python3 -m ssltui renew >> ~/.local/share/ssltui/renewal.log 2>&1 ``` -Renewal threshold: renew if cert expires within 30 days (configurable). The `--renew` path must be fully non-interactive and safe to run as a cron job with no TTY. +Renewal threshold: renew if cert expires within 30 days (configurable). The `renew` command must be fully non-interactive and safe to run as a cron job with no TTY. ## Infrastructure Requirements diff --git a/README.md b/README.md index f83d674..712151f 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,21 @@ The helper script installs the Flask extra and starts the same command: ./ssltui_web.sh --host 0.0.0.0 --port 8080 ``` +#### Running in the background on a server + +`ssltui serve` (like the TUI) runs in the foreground and stops when you log out. +To keep it running on a remote server after you disconnect, start it inside a +[tmux](https://github.com/tmux/tmux/wiki) session: + +```bash +tmux new -s ssltui # start a session +uv run ssltui serve --host 0.0.0.0 --port 8080 +# detach with Ctrl-b then d — the server keeps running +``` + +Reattach later with `tmux attach -t ssltui`. (`screen` or a systemd user +service work equally well.) + While the server runs, the TUI shows a live access log alongside historical issue events: @@ -264,6 +279,7 @@ directory, so a request can never read files outside it. - `GET /api/v1/certs//cert.pem` downloads the leaf certificate - `GET /api/v1/certs//key.pem` downloads the private key - `GET /api/v1/certs//chain.pem` downloads the chain bundle +- `GET /api/v1/crl.pem` downloads the certificate revocation list Example issue request: @@ -332,7 +348,8 @@ Sign in with your API token. The dashboard: refreshes automatically every 30 seconds - streams a live event log (issued / renewed / revoked, CRL regeneration) - lets you view and download the **certificate** and **chain** PEMs -- provides **root CA certificate** and **CRL** download links in the status bar +- provides **root CA certificate**, **CRL**, and **audit log** (CSV) download + links in the status bar (the audit log requires a signed-in session) For safety, the dashboard never modifies the CA and never serves private keys — key downloads are available only through the API and the TUI. diff --git a/docs/API.md b/docs/API.md index 0d222ee..0f5119d 100644 --- a/docs/API.md +++ b/docs/API.md @@ -217,6 +217,21 @@ curl -s -H "Authorization: Bearer $SSLTUI_API_TOKEN" \ http://127.0.0.1:8080/api/v1/certs/api.test.local/chain.pem ``` +### Download the certificate revocation list (CRL) + +``` +GET /api/v1/crl.pem +``` + +Returns the CA's PEM-encoded certificate revocation list. Responds `404` if no +certificate has been revoked yet (the CRL is generated on the first revocation). + +```bash +curl -s -H "Authorization: Bearer $SSLTUI_API_TOKEN" \ + -o ca.crl \ + http://127.0.0.1:8080/api/v1/crl.pem +``` + ## Error responses All errors return a JSON object with a single `error` field and an appropriate diff --git a/ssltui/api.py b/ssltui/api.py index 2066f31..02af9dc 100644 --- a/ssltui/api.py +++ b/ssltui/api.py @@ -12,12 +12,14 @@ GET /api/v1/certs//cert.pem download leaf cert GET /api/v1/certs//key.pem download private key GET /api/v1/certs//chain.pem download chain (leaf + CA) + GET /api/v1/crl.pem download certificate revocation list Wildcard CN example: GET /api/v1/certs/%2A.local/cert.pem """ from __future__ import annotations +import csv import hashlib import hmac import io @@ -120,6 +122,7 @@ def since(self, after_seq: int) -> tuple[list[dict], int]: "revoke": ("warning", "revoked"), "key_download": ("warning", "key downloaded"), "ca_init": ("error", "CA re-initialised"), + "invalid_request": ("error", "invalid API request"), } @@ -220,6 +223,7 @@ def _safe_version(root: Path) -> int: + ssltui — Login