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
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<img src="frontend/public/routeforge.png" alt="RouteForge Logo" width="420">
</p>
<p align="center">
<img src="https://img.shields.io/badge/version-v0.5.4--beta-blue" alt="Version">
<img src="https://img.shields.io/badge/version-v0.5.5--beta-blue" alt="Version">
<img src="https://img.shields.io/badge/license-AGPL--3.0--or--later-orange" alt="License">
<img src="https://img.shields.io/badge/status-beta-yellow" alt="Status">
<img src="https://img.shields.io/badge/selfhosted-ready-success" alt="Selfhosted">
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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`

Expand Down
14 changes: 14 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -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**
Expand Down
2 changes: 1 addition & 1 deletion backend/app/api/routes_health.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")}
2 changes: 1 addition & 1 deletion backend/app/api/routes_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
2 changes: 1 addition & 1 deletion backend/app/core/system_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
2 changes: 1 addition & 1 deletion backend/tests/test_api_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions docs/operations/troubleshooting.md
Original file line number Diff line number Diff line change
@@ -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
```
4 changes: 2 additions & 2 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Layout active={active} onNav={setActive} systemLine={systemLine} title={title} demoMode={Boolean(system?.demo_mode)}>
{active === 'dashboard' && <section className='space-y-4'>
<article className='rf-card p-6'><h1 className='text-2xl font-bold'>RouteForge v0.5.4-beta</h1><p className='mt-2 text-slate-600'>Modernes read-only Operator-Tool für Preflight Checks von ASN, Prefix, RPKI und Registry/IRR.</p></article>
<article className='rf-card p-6'><h1 className='text-2xl font-bold'>RouteForge v0.5.5-beta</h1><p className='mt-2 text-slate-600'>Modernes read-only Operator-Tool für Preflight Checks von ASN, Prefix, RPKI und Registry/IRR.</p></article>
<article className='rf-card p-4'><h3 className='font-semibold'>System Health</h3>{systemStatusError ? <p className='text-sm text-rose-700 mt-2'>{systemStatusError}</p> : <div className='mt-2 grid gap-2 text-sm md:grid-cols-4'><div>Status: <b>{systemStatus?.status || 'unknown'}</b></div><div>Mode: <b>{systemStatus?.mode || 'unknown'}</b></div><div>Database: <b>{systemStatus?.database?.status || 'unknown'}</b></div><div>Version: <b>{systemStatus?.version || 'unknown'}</b></div></div>}</article>
</section>}
{active === 'asn' && <AsnCheckForm />}
Expand All @@ -49,6 +49,6 @@ export default function App() {
<article className='rf-card p-4 text-sm'><h3 className='font-semibold mb-2'>Features</h3><pre>{JSON.stringify(systemStatus.features, null, 2)}</pre></article>
</>}
</section>}
{active === 'about' && <section className='rf-card p-5 space-y-2 text-sm text-slate-700'><p>RouteForge liefert nachvollziehbare Routing-Preflightchecks für technische Operator-Workflows.</p><p><b>Version:</b> v0.5.4-beta</p></section>}
{active === 'about' && <section className='rf-card p-5 space-y-2 text-sm text-slate-700'><p>RouteForge liefert nachvollziehbare Routing-Preflightchecks für technische Operator-Workflows.</p><p><b>Version:</b> v0.5.5-beta</p></section>}
</Layout>
}
2 changes: 1 addition & 1 deletion frontend/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function Layout({ children, active, onNav, systemLine, title, demoMode }:
<div className='flex flex-wrap gap-2 text-xs font-semibold'>
<span className={`rounded-full border px-3 py-1 ${demoMode ? 'border-amber-300 bg-amber-50 text-amber-700' : 'border-emerald-300 bg-emerald-50 text-emerald-700'}`}>{demoMode ? 'DEMO' : 'LIVE'}</span>
<span className='rounded-full border border-blue-200 bg-blue-50 px-3 py-1 text-blue-700'>READ-ONLY</span>
<span className='rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-slate-700'>v0.5.4-beta</span>
<span className='rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-slate-700'>v0.5.5-beta</span>
</div>
</header>
<main className='space-y-4'>{children}</main>
Expand Down
Loading