feat(vikunja): add Vikunja; retire Focalboard rather than swap it

Focalboard is the one audit finding with no successor to follow.
Mattermost ended support in 2023, the community repo is openly asking
for maintainers, and the image has not been rebuilt in 1042 days.

Adds Vikunja as the replacement for NEW installs: lists, kanban, table
and gantt — the same job Focalboard did — from a project rebuilt 14 days
ago. One container on SQLite, no database sidecar, following the
catalogue conventions (tag sentinels throughout, category/title +
backup.db/backup.files labels, traefik block, gluetun markers).

VIKUNJA_SERVICE_PUBLICURL is wired to the existing APP_URL_TAG rather
than a hand-built URL. It is not optional for this app — get it wrong
and creating the first account fails with a bare "unauthorized" — and
APP_URL_TAG already resolves to https://<domain> behind Traefik or
http://<host>:<assigned-port> otherwise, so the port is never guessed.
(First attempt invented a PORT_DATA_1 tag that does not exist; checking
what the processors actually emit found the real mechanism, which
bookstack already uses.)

Focalboard is RETIRED, not deleted. Replacing an app in place would stand
still for anyone already running it — their data does not move to Vikunja
— so it keeps working, and instead:
  * the description says plainly that it is unmaintained, why, and what
    to use instead
  * UPDATE_TYPE drops to manual, because there is nothing to update TO
    and auto-pulling a 2.8-year-old tag is pure churn

Icon is a drawn placeholder, not the upstream trademark.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
librelad 2026-08-18 04:04:23 +01:00
parent 298ccf4868
commit cdf76040f1
5 changed files with 143 additions and 3 deletions

View File

@ -13,7 +13,7 @@
CFG_FOCALBOARD_APP_NAME=focalboard
CFG_FOCALBOARD_BACKUP=true
CFG_FOCALBOARD_BACKUP_STRATEGY=auto
CFG_FOCALBOARD_UPDATE_TYPE=auto
CFG_FOCALBOARD_UPDATE_TYPE=manual
CFG_FOCALBOARD_COMPOSE_FILE=default
CFG_FOCALBOARD_HEALTHCHECK=true
CFG_FOCALBOARD_AUTHELIA=false
@ -31,8 +31,8 @@ CFG_FOCALBOARD_HEADSCALE=false
#
CFG_FOCALBOARD_CATEGORY="productivity"
CFG_FOCALBOARD_TITLE="Focalboard"
CFG_FOCALBOARD_DESCRIPTION="Project Management"
CFG_FOCALBOARD_LONG_DESCRIPTION="Focalboard is an open source, self-hosted alternative to Trello, Notion, and Asana that helps organize projects and tasks"
CFG_FOCALBOARD_DESCRIPTION="Project Management (unmaintained)"
CFG_FOCALBOARD_LONG_DESCRIPTION="NO LONGER MAINTAINED. Mattermost ended support in 2023 and the community repository is asking for maintainers; the image has not been rebuilt since October 2023, so it receives no security updates. Existing installs keep working and your data is untouched, but for a new board use Vikunja instead. Kept in the catalogue only so current users are not stranded"
CFG_FOCALBOARD_URL="https://github.com/mattermost/focalboard"
CFG_FOCALBOARD_ACTIONS="configure|install|restart|shutdown|uninstall"

View File

@ -0,0 +1,60 @@
networks:
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
external: true
services:
# Vikunja — tasks, lists, kanban, gantt. One container: the API and the
# frontend were merged upstream, and SQLite is a first-class backend, so
# this needs no database sidecar.
vikunja-service: #LIBREPORTAL|SERVICE_TAG_1|vikunja-service
container_name: vikunja-service
image: vikunja/vikunja:latest #LIBREPORTAL|VIKUNJA_VERSION_TAG|latest
restart: unless-stopped
hostname: vikunja
# GLUETUN_OFF_BEGIN
ports:
- "PORTS_DATA_1" #LIBREPORTAL|PORTS_TAG_1|PORTS_DATA_1
# GLUETUN_OFF_END
volumes:
- SOCKET_DATA #LIBREPORTAL|SOCKET_TAG|SOCKET_DATA
# The image presets VIKUNJA_DATABASE_PATH=/db/vikunja.db, so the
# database lives in /db and uploaded attachments in files/.
- ./db:/db
- ./files:/app/vikunja/files
environment:
- TZ=TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA
# Vikunja builds absolute URLs from this, and it is NOT optional:
# get it wrong and registering the first account fails with a bare
# "unauthorized". APP_URL_TAG is the existing mechanism for exactly
# this (bookstack uses it too) — it resolves to https://<domain>
# when the app is public and Traefik is installed, and to
# http://<host>:<assigned-port> otherwise, so the port never has to
# be guessed or hardcoded here.
- VIKUNJA_SERVICE_PUBLICURL=APP_URL_DATA #LIBREPORTAL|APP_URL_TAG|APP_URL_DATA
- VIKUNJA_DATABASE_TYPE=sqlite
- VIKUNJA_DATABASE_PATH=/db/vikunja.db
labels:
libreportal.category: "CATEGORY_DATA" #LIBREPORTAL|CATEGORY_TAG|CATEGORY_DATA
libreportal.title: "TITLE_DATA" #LIBREPORTAL|TITLE_TAG|TITLE_DATA
libreportal.backup.db: "sqlite:::db/vikunja.db"
libreportal.backup.files: "vikunja-service:/app/vikunja/files:files"
traefik.enable: TRAEFIK_ENABLE_DATA #LIBREPORTAL|TRAEFIK_ENABLE_TAG|TRAEFIK_ENABLE_DATA
# TRAEFIK_PORT_1_BEGIN
traefik.http.routers.vikunja-service.entrypoints: web,websecure
traefik.http.routers.vikunja-service.rule: Host(`DOMAINSUBNAME_DATA_1`) #LIBREPORTAL|DOMAINSUBNAME_TAG_1|DOMAINSUBNAME_DATA_1
traefik.http.routers.vikunja-service.tls: true
traefik.http.routers.vikunja-service.tls.certresolver: production
traefik.http.services.vikunja-service.loadbalancer.server.port: PORT_INTERNAL_DATA_1 #LIBREPORTAL|PORT_INTERNAL_TAG_1|PORT_INTERNAL_DATA_1
traefik.http.routers.vikunja-service.middlewares: MIDDLEWARE_DATA_1 #LIBREPORTAL|MIDDLEWARE_TAG_1|MIDDLEWARE_DATA_1
# TRAEFIK_PORT_1_END
traefik.docker.network: DOCKER_NETWORK_DATA #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
healthcheck:
disable: HEALTHCHECK_DATA #LIBREPORTAL|HEALTHCHECK_TAG|HEALTHCHECK_DATA
# GLUETUN_OFF_BEGIN
networks:
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
ipv4_address: IP_DATA_1 #LIBREPORTAL|IP_TAG_1|IP_DATA_1
# GLUETUN_OFF_END
# GLUETUN_ON_BEGIN
# network_mode: "container:gluetun-service"
# GLUETUN_ON_END

View File

@ -0,0 +1,65 @@
#
# =============================================================================
# GENERAL CONFIGURATION
# =============================================================================
# APP_NAME = name of application for use in scripts
# COMPOSE_FILE = default for no app_name in the docker-compose file name, app if there is
# BACKUP = if true, include this application in backup operations
# UPDATE_TYPE = auto: new image builds are applied automatically (a recovery snapshot is taken first), manual: only when you press Update
# HEALTHCHECK = if true, default docker health checks for that container will be enabled
# AUTHELIA = if true, use Authelia authentication, if false turned off.
# HEADSCALE = options : false, local, remote (see general config). e.g false or local,remote
# MONITORING = if true, export this app's metrics to Prometheus + Grafana (needs both apps installed)
#
CFG_VIKUNJA_APP_NAME=vikunja
CFG_VIKUNJA_BACKUP=true
CFG_VIKUNJA_BACKUP_STRATEGY=auto
CFG_VIKUNJA_UPDATE_TYPE=auto
CFG_VIKUNJA_COMPOSE_FILE=default
CFG_VIKUNJA_HEALTHCHECK=true
CFG_VIKUNJA_AUTHELIA=false
CFG_VIKUNJA_HEADSCALE=false
CFG_VIKUNJA_MONITORING=false
#
# =============================================================================
# METADATA
# =============================================================================
# CATEGORY = application category for grouping
# TITLE = display name for the application
# DESCRIPTION = short description of the application
# LONG_DESCRIPTION = detailed description of the application
# URL = source repository or documentation URL
# ACTIONS = available actions for this application
#
CFG_VIKUNJA_CATEGORY="productivity"
CFG_VIKUNJA_TITLE="Vikunja"
CFG_VIKUNJA_DESCRIPTION="Tasks & Project Management"
CFG_VIKUNJA_LONG_DESCRIPTION="Vikunja organises tasks and projects as lists, kanban boards, tables or a gantt timeline — the same job Focalboard did, from a project that is still actively developed. Runs as a single container on SQLite, with no database sidecar to maintain"
CFG_VIKUNJA_URL="https://vikunja.io"
CFG_VIKUNJA_ACTIONS="configure|install|restart|shutdown|uninstall"
#
# =============================================================================
# NETWORK CONFIGURATION
# =============================================================================
# DOMAIN = number of domain from the general config, useful when using multiple domains
# WHITELIST = if true only allow whitelisted ips on traefik, if false allow all
#
CFG_VIKUNJA_DOMAIN=1
CFG_VIKUNJA_WHITELIST=false
CFG_VIKUNJA_NETWORK=default
#
# =============================================================================
# PORT CONFIGURATION
# =============================================================================
# PORT_ = port configuration: app|name|external:internal|access|protocol|login|traefik|webui|description
# - app: application name
# - name: service identifier (webui, dns, ssh, etc.)
# - external:internal: port mapping (external can be 'random' for auto-allocation)
# - access: 'public' (internet accessible), 'private' (local network only), 'disabled' (not running)
# - protocol: 'tcp' or 'udp'
# - login: if true, this port requires basic-auth via Traefik (only meaningful when traefik=true)
# - traefik: if true, Traefik handles this port (reverse proxy)
# - webui: if true, this port serves the main web interface
# - description: human-readable description of the service
#
CFG_VIKUNJA_PORT_1="vikunja-service|webui|random:3456|public|tcp|false|true|true|Web Interface||tasks"

View File

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48" role="img" aria-label="Vikunja">
<!-- Placeholder mark: a checklist on a board. Drawn rather than copied so no
upstream trademark is vendored into the repo. -->
<rect x="7" y="6" width="34" height="36" rx="4" fill="none" stroke="#1973ff" stroke-width="2.5"/>
<path d="M14 16.5l3 3 6-6" fill="none" stroke="#1973ff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 27.5l3 3 6-6" fill="none" stroke="#1973ff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<line x1="27" y1="17" x2="35" y2="17" stroke="#1973ff" stroke-width="2.5" stroke-linecap="round"/>
<line x1="27" y1="28" x2="35" y2="28" stroke="#1973ff" stroke-width="2.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 814 B

View File

@ -959,12 +959,14 @@ declare -gA LP_FN_MAP=(
[updaterTagExists]="cli/commands/updater/cli_updater_ladder.sh"
[updaterTagGreater]="webui/data/generators/updater/webui_updater_scan.sh"
[updaterTagIncrement]="cli/commands/updater/cli_updater_ladder.sh"
[updaterTagLastUpdated]="webui/data/generators/updater/webui_updater_scan.sh"
[updaterTagNums]="webui/data/generators/updater/webui_updater_scan.sh"
[updaterTagOf]="webui/data/generators/updater/webui_updater_scan.sh"
[updaterTagShape]="webui/data/generators/updater/webui_updater_scan.sh"
[updaterTagSortKey]="cli/commands/updater/cli_updater_ladder.sh"
[updaterUpgradeApp]="cli/commands/updater/cli_updater_upgrade.sh"
[_updaterUpgradeGenDir]="cli/commands/updater/cli_updater_upgrade.sh"
[_updaterUpgradePruneImages]="cli/commands/updater/cli_updater_upgrade.sh"
[_updaterUpgradeRollbackStep]="cli/commands/updater/cli_updater_upgrade.sh"
[updaterVerifyGeneric]="cli/commands/updater/cli_updater_verify.sh"
[updaterVerifyUpgrade]="cli/commands/updater/cli_updater_verify.sh"
@ -2005,12 +2007,14 @@ declare -gA LP_FN_ROOT=(
[updaterTagExists]="scripts"
[updaterTagGreater]="scripts"
[updaterTagIncrement]="scripts"
[updaterTagLastUpdated]="scripts"
[updaterTagNums]="scripts"
[updaterTagOf]="scripts"
[updaterTagShape]="scripts"
[updaterTagSortKey]="scripts"
[updaterUpgradeApp]="scripts"
[_updaterUpgradeGenDir]="scripts"
[_updaterUpgradePruneImages]="scripts"
[_updaterUpgradeRollbackStep]="scripts"
[updaterVerifyGeneric]="scripts"
[updaterVerifyUpgrade]="scripts"
@ -3085,12 +3089,14 @@ updaterSetAnchorVersion() { unset -f updaterSetAnchorVersion; __lpAutoload "${in
updaterTagExists() { unset -f updaterTagExists; __lpAutoload "${install_scripts_dir}cli/commands/updater/cli_updater_ladder.sh"; updaterTagExists "$@"; }
updaterTagGreater() { unset -f updaterTagGreater; __lpAutoload "${install_scripts_dir}webui/data/generators/updater/webui_updater_scan.sh"; updaterTagGreater "$@"; }
updaterTagIncrement() { unset -f updaterTagIncrement; __lpAutoload "${install_scripts_dir}cli/commands/updater/cli_updater_ladder.sh"; updaterTagIncrement "$@"; }
updaterTagLastUpdated() { unset -f updaterTagLastUpdated; __lpAutoload "${install_scripts_dir}webui/data/generators/updater/webui_updater_scan.sh"; updaterTagLastUpdated "$@"; }
updaterTagNums() { unset -f updaterTagNums; __lpAutoload "${install_scripts_dir}webui/data/generators/updater/webui_updater_scan.sh"; updaterTagNums "$@"; }
updaterTagOf() { unset -f updaterTagOf; __lpAutoload "${install_scripts_dir}webui/data/generators/updater/webui_updater_scan.sh"; updaterTagOf "$@"; }
updaterTagShape() { unset -f updaterTagShape; __lpAutoload "${install_scripts_dir}webui/data/generators/updater/webui_updater_scan.sh"; updaterTagShape "$@"; }
updaterTagSortKey() { unset -f updaterTagSortKey; __lpAutoload "${install_scripts_dir}cli/commands/updater/cli_updater_ladder.sh"; updaterTagSortKey "$@"; }
updaterUpgradeApp() { unset -f updaterUpgradeApp; __lpAutoload "${install_scripts_dir}cli/commands/updater/cli_updater_upgrade.sh"; updaterUpgradeApp "$@"; }
_updaterUpgradeGenDir() { unset -f _updaterUpgradeGenDir; __lpAutoload "${install_scripts_dir}cli/commands/updater/cli_updater_upgrade.sh"; _updaterUpgradeGenDir "$@"; }
_updaterUpgradePruneImages() { unset -f _updaterUpgradePruneImages; __lpAutoload "${install_scripts_dir}cli/commands/updater/cli_updater_upgrade.sh"; _updaterUpgradePruneImages "$@"; }
_updaterUpgradeRollbackStep() { unset -f _updaterUpgradeRollbackStep; __lpAutoload "${install_scripts_dir}cli/commands/updater/cli_updater_upgrade.sh"; _updaterUpgradeRollbackStep "$@"; }
updaterVerifyGeneric() { unset -f updaterVerifyGeneric; __lpAutoload "${install_scripts_dir}cli/commands/updater/cli_updater_verify.sh"; updaterVerifyGeneric "$@"; }
updaterVerifyUpgrade() { unset -f updaterVerifyUpgrade; __lpAutoload "${install_scripts_dir}cli/commands/updater/cli_updater_verify.sh"; updaterVerifyUpgrade "$@"; }