furtka/pyproject.toml

47 lines
872 B
TOML
Raw Permalink Normal View History

[project]
name = "furtka"
fix(https): make HTTPS opt-in to stop the BAD_SIGNATURE trap on fresh installs Every Furtka since 26.5 shipped a Caddyfile with a `__FURTKA_HOSTNAME__.local { tls internal }` site block, so every first boot auto-generated a fresh self-signed CA + intermediate + leaf. That worked for the first-ever Furtka user, but every reinstall (or second box on the same LAN) produced a new CA whose intermediate shared the fixed CN `Caddy Local Authority - ECC Intermediate` with the previous one. Firefox caches intermediates by CN across profiles — even private windows share cert9.db — so any visitor who had trusted an older Furtka's CA got a cached intermediate with mismatched keys when they hit the new box, producing `SEC_ERROR_BAD_SIGNATURE`. Unlike UNKNOWN_ISSUER, Firefox has NO "Advanced → Accept Risk" bypass for BAD_SIGNATURE, so fresh-install boxes were effectively unreachable over HTTPS in any browser that had ever seen a previous Furtka. Validated live on the .46 test VM: fresh 26.14 ISO install → Firefox hits BAD_SIGNATURE on https://furtka.local/ (even in private mode). Chromium bypasses it via mDNS failure but the issue is the same. openssl verify on the box confirms the chain is internally valid — this is purely client-side cache pollution across boxes. Fix: - assets/Caddyfile: removed the hostname site block. Default install serves :80 only — https://furtka.local connection-refuses, which is a normal error every browser handles instead of the unbypassable crypto fault. Added top-level import of /etc/caddy/furtka-https.d/*.caddyfile so the /settings HTTPS toggle can drop a listener snippet there when a user explicitly opts in. - furtka/https.py: set_force_https now writes TWO snippets atomically — the top-level hostname + tls internal block (enables :443) and the :80-scoped redirect (forces HTTP→HTTPS). Disable removes both. Reload failure rolls both back. Added _read_hostname + _https_snippet_content helpers with `/etc/hostname` → 'furtka' fallback so a missing hostname file doesn't produce an empty site block Caddy rejects. - furtka/https.py::status: force_https now reads the listener snippet (was reading the redirect snippet). A redirect without a listener isn't actually HTTPS being served, so the listener is the authoritative "HTTPS is on" signal. - furtka/updater.py: new _maybe_migrate_preserve_https hook runs inside _refresh_caddyfile on the 26.14 → 26.15 transition. If the box had the redirect snippet on disk (user had opted into HTTPS under the old regime), it writes the new listener snippet too so HTTPS keeps working after the Caddyfile swap removes the hostname block. - webinstaller/app.py: post-install creates /etc/caddy/furtka-https.d/ alongside /etc/caddy/furtka.d/ so the glob import can't trip an older Caddy on a missing path during the first reload. Live-tested on .46: set_force_https(True) writes both snippets, Caddy reloads, :443 listener comes up with fresh CA, curl -k returns 302, HTTP 301-redirects. set_force_https(False) removes both snippets atomically, :443 goes back to connection-refused. Tests: test_https.py expanded from 13 to 15 cases. Toggle-on asserts both snippets written + hostname substituted. Toggle-off asserts both removed. Rollback cases verify BOTH snippets restore on reload failure. New test_https_snippet_content_has_tls_internal_and_routes locks the exact shape of the listener block. test_webinstaller_assets.py: updated two old asserts that assumed hostname block was in Caddyfile; new test_post_install_creates_https_snippet_dir guards the new directory. 276 tests pass, ruff check + format clean. Known remaining wart (documented in CHANGELOG): a browser that trusted a prior Furtka CA still hits BAD_SIGNATURE on this box's HTTPS after enabling it, because the fixed intermediate CN is a Caddy-side limitation. Workaround: clear cert9.db or visit in a fresh profile. Won't affect end users with one Furtka box ever. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 19:30:04 +02:00
version = "26.15-alpha"
description = "Open-source home server OS — simple enough for everyone."
requires-python = ">=3.11"
readme = "README.md"
license = { text = "AGPL-3.0-or-later" }
authors = [
{ name = "Daniel Syrnicki" },
{ name = "Robert Syrnicki" },
]
dependencies = [
"flask>=3.0",
]
[project.optional-dependencies]
dev = [
"ruff>=0.6",
"pytest>=8.0",
]
[tool.ruff]
line-length = 100
target-version = "py311"
extend-exclude = [".venv", "*.venv"]
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"F", # pyflakes
"I", # isort
"W", # pycodestyle warnings
"B", # flake8-bugbear
"UP", # pyupgrade
]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["webinstaller", "."]
[project.scripts]
furtka = "furtka.cli:main"
[tool.setuptools]
packages = ["furtka"]