From ed198a9c46b12fd854c30c67977fb404481fcc2f Mon Sep 17 00:00:00 2001 From: hampuslinden <62944155+hampuslinden@users.noreply.github.com> Date: Sun, 21 Jun 2026 13:36:42 +0100 Subject: [PATCH 1/2] feat: improved application usage --- .gitattributes | 1 + .github/workflows/pr.yaml | 6 +- CLAUDE.md | 2 +- README.md | 19 ++++- docs/API.md | 15 ++++ ssltui/api.py | 169 ++++++++++++++++++++++++++++++++++++-- ssltui/images/favicon.ico | 3 + ssltui/images/favicon.png | 3 + ssltui/tui.py | 52 +++++++++++- tools/convert_favicon.sh | 12 +++ 10 files changed, 271 insertions(+), 11 deletions(-) create mode 100644 ssltui/images/favicon.ico create mode 100644 ssltui/images/favicon.png create mode 100644 tools/convert_favicon.sh 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..a323826 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -131,7 +131,7 @@ 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. 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..7e71bfe 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