Rebrand to volundr; add deploy artifacts

- User-facing brand "bike-admin" -> "volundr" (page titles, top bar,
  login header, FastAPI title, README/AGENTS). Directory, remote,
  BIKE_ADMIN_* env prefix and bike_admin.db filename are unchanged.
- Add deploy/: systemd unit (bike-admin.service) and a production
  .env template (env.production.example).
This commit is contained in:
2026-09-10 11:29:49 +01:00
parent 12494bf30c
commit 2d63006bdc
12 changed files with 64 additions and 12 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ Guidance for working in this repo.
## What this is
`bike-admin` is a small single-user web app for logging bike service/repair
**volundr** (packaged as `bike_admin`) is a small single-user web app for logging bike service/repair
"events" (type, date, mileage, cost, notes) and seeing per-bike hints: a
**service-due** status derived from the newest mileage and the most recent
`service` event, plus **MOT / insurance renewal** statuses derived from the most
+1 -1
View File
@@ -1,4 +1,4 @@
# bike-admin
# volundr
A small, secure, single-user web app for logging bike service and repair
"events" (type, date, mileage, cost, notes) and getting a clear **service-due**
+1 -1
View File
@@ -1 +1 @@
"""bike-admin package."""
"""volundr package."""
+1 -1
View File
@@ -215,7 +215,7 @@ async def lifespan(_: FastAPI):
yield
app = FastAPI(title="bike-admin", lifespan=lifespan)
app = FastAPI(title="volundr", lifespan=lifespan)
@app.exception_handler(FastAPIUnauthorized)
+2 -2
View File
@@ -3,14 +3,14 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}bike-admin{% endblock %}</title>
<title>{% block title %}volundr{% endblock %}</title>
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
<link rel="stylesheet" href="/static/main.css">
<script src="/static/htmx.min.js" defer></script>
</head>
<body>
<header class="topbar">
<a class="brand" href="/">bike-admin</a>
<a class="brand" href="/">volundr</a>
<nav class="nav">
<a href="/bikes/new" class="btn btn-primary">+ Bike</a>
<a href="/logout"
+1 -1
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}{{ bike.name }} - bike-admin{% endblock %}
{% block title %}{{ bike.name }} - volundr{% endblock %}
{% block content %}
<div class="page-head">
<h1>{{ bike.name }}</h1>
+1 -1
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Edit bike - bike-admin{% endblock %}
{% block title %}Edit bike - volundr{% endblock %}
{% block content %}
<div class="page-head"><h1>Edit {{ bike.name }}</h1></div>
<div class="card">
+1 -1
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}New bike - bike-admin{% endblock %}
{% block title %}New bike - volundr{% endblock %}
{% block content %}
<div class="page-head"><h1>New bike</h1></div>
<div class="card">
+1 -1
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Dashboard - bike-admin{% endblock %}
{% block title %}Dashboard - volundr{% endblock %}
{% block content %}
<div class="page-head">
<h1>Your bikes</h1>
+2 -2
View File
@@ -3,13 +3,13 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Log in - bike-admin</title>
<title>Log in - volundr</title>
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
<link rel="stylesheet" href="/static/main.css">
</head>
<body class="auth">
<main class="container auth-card">
<h1 style="margin-top:0">bike-admin</h1>
<h1 style="margin-top:0">volundr</h1>
<p class="muted">Log in to manage your bikes.</p>
{% if error %}
+27
View File
@@ -0,0 +1,27 @@
[Unit]
Description=volundr (bike service-due + MOT/insurance tracker)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=bike
Group=bike
WorkingDirectory=/opt/bike_admin
# Leading `-` = optional: the app still boots (and prompts for a password)
# if the .env has not been created yet.
EnvironmentFile=-/opt/bike_admin/.env
ExecStart=/opt/bike_admin/.venv/bin/uvicorn app.main:app \
--host 0.0.0.0 --port 8000 \
--forwarded-allow-ips 192.168.0.0/16 --proxy-headers
Restart=on-failure
RestartSec=2
# Hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/opt/bike_admin/data
[Install]
WantedBy=multi-user.target
+25
View File
@@ -0,0 +1,25 @@
# bike_admin production environment (copied to /opt/bike_admin/.env on the server)
# All values are BIKE_ADMIN_-prefixed. This file is NOT committed (see .gitignore).
# --- Required -------------------------------------------------
BIKE_ADMIN_USERNAME=jake
BIKE_ADMIN_PASSWORD=change-me-to-something-strong
# --- Required in production -----------------------------------
# Secret used to derive the CSRF / session tokens. Generate with:
# python3 -c "import secrets;print(secrets.token_urlsafe(32))"
BIKE_ADMIN_SECRET_KEY=
# Adds the Secure flag to the session cookie (we're served over HTTPS via Caddy).
BIKE_ADMIN_IS_PRODUCTION=true
# Bind to all interfaces so the hermodr reverse proxy (on another host) can reach it.
# The app trusts X-Forwarded-* headers from the proxy (BIKE_ADMIN_TRUST_PROXY defaults true).
BIKE_ADMIN_BIND_HOST=0.0.0.0
BIKE_ADMIN_BIND_PORT=8000
# --- Optional fine-tuning (defaults shown) ---------------------
# BIKE_ADMIN_SERVICE_DUE_SOON_MILES=200 # service "book soon" lead time
# BIKE_ADMIN_RENEWAL_DUE_SOON_DAYS=60 # MOT/insurance "due soon" lead time
# BIKE_ADMIN_RENEWAL_MONTHS=12 # MOT/insurance validity (months)
# BIKE_ADMIN_DATA_DIR=/opt/bike_admin/data