Gmails refresh-token lå i klartekst i databasen. Det er ikke filen på serveren, der er problemet, men kopierne af den: sikkerhedskopier kan hentes ned gennem fladen, så hver eneste kopi, der er taget indtil nu, bar et levende token. Derfor ligger nøglen uden for databasen — i miljøet, eller i en fil ved siden af dataene med tilstand 0600. VAPID-parret er præcedens for en hemmelighed, instansen selv laver og gemmer i basen, men det mønster dur ikke her: en nøgle i samme fil som det, den låser, er ingen lås. Backup er VACUUM INTO af selve databasen, så filen følger ikke med. Kun de navngivne felter forsegles — refresh_token, access_token, password, client_secret. Resten af klatten er det, man læser, når en postkasse driller: hvilken mappe, hvilken etiket, hvilken adresse. At kryptere det med ville ikke købe noget og ville koste muligheden for at kigge. Ingen migration, og ingen der skal forbinde forfra: en værdi uden mærket læses som sig selv, og næste skrivning lægger den forseglet på plads. Kolonnen konverterer sig selv efterhånden som den bruges, i stedet for i én omgang, der skal have ret om hver eneste række på én gang. Der er en prøve for netop det. Fladen siger det på siden, hvor man henter kopien — for den dag nøglen betyder noget, er den dag, man gendanner et andet sted og undrer sig over, at postkassen er tavs. Runen er på v4; en ny variabel uden et versionsspring er usynlig. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
160 lines
6.1 KiB
YAML
160 lines
6.1 KiB
YAML
gameskill:
|
|
id: verdande
|
|
name: "verdande"
|
|
category: "Apps"
|
|
description: >
|
|
Self-hosted tasks and projects, shared with other people. Natural-language
|
|
quick add in Danish and English, sub-tasks, labels, saved filters, board and
|
|
calendar views, recurring tasks, reminders, comments and attachments. One
|
|
binary with SQLite inside it — no database to set up. Set BASE_URL to the
|
|
address you reach it on, point the SMTP settings at your mail server for
|
|
invites and reminders, then start it and open the URL to create the first
|
|
admin account.
|
|
author: "kristianwind"
|
|
# Bumped whenever the variables below change. The panel keys its catalogue on
|
|
# this: a manifest with new fields and an unchanged version is a manifest the
|
|
# panel has no reason to re-read, so the fields never appear and the failure is
|
|
# silent — the server's settings page simply does not offer them.
|
|
version: 4
|
|
icon: "app"
|
|
|
|
# The image is distroless — no shell — so keep_entrypoint runs the binary the
|
|
# image already declares as its ENTRYPOINT, and startup.exec passes it argv
|
|
# directly rather than through /bin/sh, which does not exist in there.
|
|
docker:
|
|
image: "ghcr.io/kristianwind/verdande:latest"
|
|
data_path: /data
|
|
keep_entrypoint: true
|
|
|
|
variables:
|
|
# verdande cannot work out its own public address: the host port is not chosen
|
|
# until the server is created. {{PUBLIC_URL}} is what the panel knows and this
|
|
# is what invite links, password resets and ICS feeds are built from — get it
|
|
# wrong and the links in your email point somewhere that does not answer.
|
|
- key: VERDANDE_BASE_URL
|
|
name: "Public address (used in invite and reset links)"
|
|
type: string
|
|
default: "{{PUBLIC_URL}}"
|
|
required: true
|
|
|
|
- key: VERDANDE_SMTP_HOST
|
|
name: "SMTP host (blank = show invite links in the app instead of mailing them)"
|
|
type: string
|
|
default: ""
|
|
- key: VERDANDE_SMTP_PORT
|
|
name: "SMTP port"
|
|
type: int
|
|
default: 587
|
|
min: 1
|
|
max: 65535
|
|
- key: VERDANDE_SMTP_USER
|
|
name: "SMTP username"
|
|
type: string
|
|
default: ""
|
|
- key: VERDANDE_SMTP_PASS
|
|
name: "SMTP password"
|
|
type: string
|
|
default: ""
|
|
secret: true
|
|
- key: VERDANDE_SMTP_FROM
|
|
name: "Sender address"
|
|
type: string
|
|
default: ""
|
|
pattern: '^$|^[^@\s]+@[^@\s]+\.[^@\s]+$'
|
|
hint: "must be an email address like verdande@example.dk, or blank to leave it unset"
|
|
- key: VERDANDE_SMTP_STARTTLS
|
|
name: "Use STARTTLS (turn off only for port 465, which is TLS from the start)"
|
|
type: bool
|
|
default: true
|
|
|
|
- key: VERDANDE_SESSION_TTL
|
|
name: "How long a login lasts (Go duration, e.g. 720h for 30 days)"
|
|
type: string
|
|
default: "720h"
|
|
pattern: '^[0-9]+(ns|us|ms|s|m|h)$'
|
|
hint: "must be a duration like 720h, 30m or 45s"
|
|
|
|
# Off by default because it is an outbound request nobody asked for. It sends
|
|
# no identifier and reads one public URL — GitHub's releases API — but that is
|
|
# a choice to make rather than a default to inherit.
|
|
- key: VERDANDE_UPDATE_CHECK
|
|
name: "Look for new releases (asks GitHub once a day; sends nothing about you)"
|
|
type: bool
|
|
default: false
|
|
|
|
# Restarting from verdande's own settings, which is also how it picks up a new
|
|
# version: a container cannot replace its own image, so the panel recreates it
|
|
# and pulls :latest on the way. All three are needed together; the page names
|
|
# whichever are missing.
|
|
- key: VERDANDE_PANEL_URL
|
|
name: "Panel address, so verdande can ask to be restarted from its own settings"
|
|
type: string
|
|
default: ""
|
|
- key: VERDANDE_PANEL_TOKEN
|
|
name: "Panel API token — from an account with control of this server"
|
|
type: string
|
|
secret: true
|
|
default: ""
|
|
- key: VERDANDE_PANEL_SERVER_ID
|
|
name: "This server's id in the panel (the one in its URL)"
|
|
type: string
|
|
default: ""
|
|
- key: VERDANDE_SECRET_KEY
|
|
name: "Key for the tokens and mailbox passwords in the database — 32 bytes of base64, from: openssl rand -base64 32. Leave empty to keep one in a file beside the data. Change it and every mailbox must be connected again."
|
|
type: string
|
|
secret: true
|
|
default: ""
|
|
|
|
install:
|
|
# The image has no shell, so the install step borrows one. It only has to make
|
|
# the directory layout exist; verdande creates and migrates its own database on
|
|
# first start.
|
|
image: "busybox:latest"
|
|
script: |
|
|
set -eu
|
|
mkdir -p /data/files /data/backups
|
|
echo "verdande data directory ready at /data"
|
|
|
|
startup:
|
|
# No command: with keep_entrypoint the image's own ENTRYPOINT (/verdande) runs,
|
|
# and everything is configured through the environment above.
|
|
done_regex: '"msg":"listening"'
|
|
|
|
ports:
|
|
- { name: web, default: 8080, protocol: tcp }
|
|
|
|
# Everything worth keeping is under /data. The -wal and -shm files are included
|
|
# deliberately: a WAL-mode SQLite database copied without them can be missing the
|
|
# most recent writes.
|
|
backup:
|
|
include: ["."]
|
|
|
|
# The container logs JSON — VERDANDE_DEV is off in production — so these patterns
|
|
# match the JSON form, not the human-readable one seen when developing locally.
|
|
watchers:
|
|
- name: "Database errors"
|
|
pattern: 'database is locked|disk I/O error|database disk image is malformed'
|
|
threshold: 1
|
|
window_secs: 300
|
|
action: kvasir
|
|
- name: "Failed logins"
|
|
pattern: '"path":"/api/v1/auth/login".*"status":401'
|
|
threshold: 10
|
|
window_secs: 300
|
|
action: notify
|
|
- name: "Server errors"
|
|
pattern: '"status":5[0-9][0-9]'
|
|
threshold: 5
|
|
window_secs: 300
|
|
action: kvasir
|
|
|
|
events:
|
|
# Capture group 1 is the client address, which the panel rolls up per source —
|
|
# so a brute-force run shows up as one row per address per hour.
|
|
- key: login_failed
|
|
label: "Failed login"
|
|
match: '"path":"/api/v1/auth/login".*"status":401.*"remote":"([^"]+)"'
|
|
- key: http_5xx
|
|
label: "HTTP 5xx"
|
|
match: '"status":5[0-9][0-9].*"remote":"([^"]+)"'
|