From 4a49a822a93eac5a10f7a6b68ef7cca5efa74a1a Mon Sep 17 00:00:00 2001 From: DeepZone Date: Wed, 20 May 2026 19:36:42 +0100 Subject: [PATCH] v0.5.5-beta: fix SQLite volume permissions for non-root runtime --- README.md | 8 ++++++-- RELEASE_NOTES.md | 14 ++++++++++++++ backend/app/api/routes_health.py | 2 +- backend/app/api/routes_system.py | 2 +- backend/app/core/system_status.py | 2 +- backend/app/main.py | 2 +- backend/pyproject.toml | 2 +- backend/tests/test_api_smoke.py | 2 +- docs/operations/troubleshooting.md | 20 ++++++++++++++++++++ frontend/package-lock.json | 4 ++-- frontend/package.json | 2 +- frontend/src/App.tsx | 6 +++--- frontend/src/components/Layout.tsx | 2 +- 13 files changed, 53 insertions(+), 15 deletions(-) create mode 100644 docs/operations/troubleshooting.md diff --git a/README.md b/README.md index 577d528..37cc4a4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ RouteForge Logo

- Version + Version License Status Selfhosted @@ -43,7 +43,7 @@ Routing changes often require fast but traceable checks across multiple external ## Current Alpha Status -RouteForge is a **functional beta** release with production-like workflows for read-only validation and demo usage. Current release target: **v0.5.4-beta**. +RouteForge is a **functional beta** release with production-like workflows for read-only validation and demo usage. Current release target: **v0.5.5-beta**. ## Quickstart with Docker Compose @@ -215,9 +215,13 @@ In the standard setup, RouteForge does **not** require a hardcoded host IP in th - SQLite/dev mode keeps lightweight startup initialization (`create_all`) for local/demo compatibility. - Run migrations manually before production upgrades (`alembic upgrade head`). +### SQLite permission note +- For SQLite selfhosting setups, the backend entrypoint ensures `/app/data` is writable by the non-root runtime user `routeforge` at container startup. + ### Operations docs - Backup/Restore: `docs/operations/backup-restore.md` - Reverse Proxy: `docs/operations/reverse-proxy.md` +- Troubleshooting: `docs/operations/troubleshooting.md` - Logging: `docs/operations/logging.md` - Upgrades: `docs/operations/upgrades.md` diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7321812..aba5164 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,19 @@ # Release Notes +## v0.5.5-beta + +**SQLite Volume Permission Hotfix** + +### Highlights + +- Fixes SQLite readonly database errors after non-root container hardening. +- Backend entrypoint now prepares `/app/data` permissions for the `routeforge` runtime user. +- Runtime remains non-root. +- Troubleshooting documentation added. + +--- + + ## v0.5.4-beta **Production Polish & Security Baseline** diff --git a/backend/app/api/routes_health.py b/backend/app/api/routes_health.py index 66fbd54..5d9281b 100644 --- a/backend/app/api/routes_health.py +++ b/backend/app/api/routes_health.py @@ -8,4 +8,4 @@ @router.get('/health') def health() -> dict: - return {"status": "ok", "version": "v0.5.4-beta", "database": get_database_status(engine).get("status", "unknown")} + return {"status": "ok", "version": "v0.5.5-beta", "database": get_database_status(engine).get("status", "unknown")} diff --git a/backend/app/api/routes_system.py b/backend/app/api/routes_system.py index de8514d..940de25 100644 --- a/backend/app/api/routes_system.py +++ b/backend/app/api/routes_system.py @@ -11,7 +11,7 @@ def system_info(): return { 'name': 'RouteForge', - 'version': 'v0.5.4-beta', + 'version': 'v0.5.5-beta', 'demo_mode': settings.demo_mode, 'read_only': True, 'data_sources': ['RIPEstat', 'RIPEstat Whois/Registry'], diff --git a/backend/app/core/system_status.py b/backend/app/core/system_status.py index 38e3460..9abf794 100644 --- a/backend/app/core/system_status.py +++ b/backend/app/core/system_status.py @@ -113,7 +113,7 @@ def build_system_status(engine: Engine | None) -> dict: return { "status": "ok", "name": settings.app_name, - "version": "v0.5.4-beta", + "version": "v0.5.5-beta", "read_only": True, "mode": "demo" if settings.demo_mode else "live", "demo_mode": settings.demo_mode, diff --git a/backend/app/main.py b/backend/app/main.py index 46c4e77..22177ff 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -15,7 +15,7 @@ logging.basicConfig(level=getattr(logging, settings.log_level.upper(), logging.INFO)) logger = logging.getLogger("routeforge") -app = FastAPI(title="RouteForge", version="0.5.4") +app = FastAPI(title="RouteForge", version="0.5.5") app.add_middleware( CORSMiddleware, diff --git a/backend/pyproject.toml b/backend/pyproject.toml index b6d164b..38206f7 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "routeforge-backend" -version = "0.5.4" +version = "0.5.5" description = "RouteForge backend" license = "AGPL-3.0-or-later" requires-python = ">=3.12" diff --git a/backend/tests/test_api_smoke.py b/backend/tests/test_api_smoke.py index 11c623b..eeb28ad 100644 --- a/backend/tests/test_api_smoke.py +++ b/backend/tests/test_api_smoke.py @@ -157,7 +157,7 @@ def test_system_status_endpoint() -> None: response = client.get('/api/system/status') assert response.status_code == 200 payload = response.json() - assert payload.get('version') == 'v0.5.4-beta' + assert payload.get('version') == 'v0.5.5-beta' assert payload.get('read_only') is True assert payload.get('database', {}).get('status') assert payload.get('ripestat', {}).get('cache_ttl_seconds') is not None diff --git a/docs/operations/troubleshooting.md b/docs/operations/troubleshooting.md new file mode 100644 index 0000000..4530d7c --- /dev/null +++ b/docs/operations/troubleshooting.md @@ -0,0 +1,20 @@ +# Troubleshooting + +## sqlite3.OperationalError: attempt to write a readonly database + +### Ursache + +Bei SQLite-Deployments liegt die Datenbank häufig unter `/app/data/routeforge.db`. +Wenn das Docker-Volume auf `/app/data` root-owned ist, kann der non-root Runtime-User `routeforge` nicht in die SQLite-Datei schreiben. Dadurch schlagen Check-Speicherungen mit `sqlite3.OperationalError: attempt to write a readonly database` fehl. + +### Fix ab v0.5.5-beta + +Ab `v0.5.5-beta` setzt der Backend-Entrypoint beim Containerstart die Ownership für `/app/data` auf `routeforge:routeforge` und startet danach den Prozess weiterhin als non-root User `routeforge`. + +### Workaround für ältere Versionen + +```bash +docker compose down +docker compose run --rm --user root backend sh -c "mkdir -p /app/data && chown -R routeforge:routeforge /app/data && chmod -R u+rwX /app/data" +docker compose up -d --build +``` diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8080088..4036f3b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "routeforge-frontend", - "version": "0.5.4", + "version": "0.5.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "routeforge-frontend", - "version": "0.5.4", + "version": "0.5.5", "license": "AGPL-3.0-or-later", "dependencies": { "react": "^18.3.1", diff --git a/frontend/package.json b/frontend/package.json index 7a4c616..592c2e8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "routeforge-frontend", - "version": "0.5.4", + "version": "0.5.5", "private": true, "license": "AGPL-3.0-or-later", "type": "module", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5375fff..6a21d62 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -22,14 +22,14 @@ export default function App() { getSystemStatus().then((payload) => { setSystemStatus(payload); setSystemStatusError('') }).catch(() => setSystemStatusError('System status could not be loaded.')) }, []) - const systemLine = useMemo(() => system ? `${system.name} ${system.version} · mode=${system.demo_mode ? 'DEMO' : 'LIVE'} · read_only=${String(system.read_only)}` : 'RouteForge v0.5.4-beta · read-only preflight checks', [system]) + const systemLine = useMemo(() => system ? `${system.name} ${system.version} · mode=${system.demo_mode ? 'DEMO' : 'LIVE'} · read_only=${String(system.read_only)}` : 'RouteForge v0.5.5-beta · read-only preflight checks', [system]) const title = { dashboard: 'Dashboard', asn: 'ASN Check', prefix: 'Prefix Check', preflight: 'Preflight Check', reports: 'Reports', system: 'System Status', about: 'About RouteForge' }[active] const proxyStatus = systemStatusError ? 'ERROR' : 'OK' const migrationStatus = systemStatus?.database?.migration_status || 'unknown' return {active === 'dashboard' &&

-

RouteForge v0.5.4-beta

Modernes read-only Operator-Tool für Preflight Checks von ASN, Prefix, RPKI und Registry/IRR.

+

RouteForge v0.5.5-beta

Modernes read-only Operator-Tool für Preflight Checks von ASN, Prefix, RPKI und Registry/IRR.

System Health

{systemStatusError ?

{systemStatusError}

:
Status: {systemStatus?.status || 'unknown'}
Mode: {systemStatus?.mode || 'unknown'}
Database: {systemStatus?.database?.status || 'unknown'}
Version: {systemStatus?.version || 'unknown'}
}
} {active === 'asn' && } @@ -49,6 +49,6 @@ export default function App() {

Features

{JSON.stringify(systemStatus.features, null, 2)}
} } - {active === 'about' &&

RouteForge liefert nachvollziehbare Routing-Preflightchecks für technische Operator-Workflows.

Version: v0.5.4-beta

} + {active === 'about' &&

RouteForge liefert nachvollziehbare Routing-Preflightchecks für technische Operator-Workflows.

Version: v0.5.5-beta

} } diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 9d72415..746401e 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -34,7 +34,7 @@ export function Layout({ children, active, onNav, systemLine, title, demoMode }:
{demoMode ? 'DEMO' : 'LIVE'} READ-ONLY - v0.5.4-beta + v0.5.5-beta
{children}