v0.8.20-beta - Prometheus startte niet, en 23 apps erbij
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 12m38s

- apps/prometheus was kapot: het koppelde een lege map op /etc/prometheus en
  startte met --config.file naar een bestand dat er nooit kwam. Er zit nu een
  startconfiguratie bij, met koppelvelden naar node-exporter en cAdvisor
- De meeste apps van deze ronde zijn de ontbrekende helft van wat er al stond:
  node-exporter, cAdvisor en Alertmanager maken Prometheus bruikbaar; Loki en
  Alloy bewaren logs waar Dozzle alleen live meekijkt; Unbound zoekt DNS zelf op
  in plaats van door te vragen aan Google of Cloudflare
- Domeinen die ontbraken: evcc (energie), Healthchecks (cronbewaking),
  Infisical (geheimen voor applicaties), Music Assistant, pgAdmin
- Verder Crafty, Manyfold, ErsatzTV, FitTrackee, ComfyUI, Omada, en de zwaardere
  Zabbix, Graylog, Wazuh, NetBox, Seafile en OpenCloud. Van 218 naar 241 apps
- Nieuw tools/controleer_images.py: controleert of elk image echt bestaat. De
  ad-hoc versie draaide met zestien verzoeken tegelijk en meldde negen
  ontbrekende images die alle negen wel bestonden -- registries knijpen af, en
  dat is niet te onderscheiden van een ontbrekend image. Nu vier tegelijk met
  opnieuw proberen, en als stap in de drie workflows

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q9eqpADJSRs49SoGGr4NAy
This commit is contained in:
Ramon 2026-08-01 04:06:26 +02:00
parent c9b73d8b69
commit 42bd87047a
58 changed files with 2601 additions and 3 deletions

View file

@ -103,6 +103,20 @@ jobs:
fi
echo "Alle compose-bestanden zijn geldig"
- name: Images controleren
run: |
set -euo pipefail
# Een image dat niet meer bestaat merk je anders pas als iemand op
# installeren drukt. Zo verdween Huntarr nadat dat project offline
# ging, en wezen Forgejo, Planka en Baby Buddy naar tags die er niet
# zijn. Draait bewust rustig: registries knijpen af, en een afgeknepen
# verzoek is niet te onderscheiden van een ontbrekend image.
docker run --rm \
-v "$PWD:/w" -w /w \
-e PYTHONDONTWRITEBYTECODE=1 \
python:3.12-slim \
python tools/controleer_images.py
- name: Inloggen op de registry
run: |
set -euo pipefail

View file

@ -91,6 +91,20 @@ jobs:
fi
echo "Alle compose-bestanden zijn geldig"
- name: Images controleren
run: |
set -euo pipefail
# Een image dat niet meer bestaat merk je anders pas als iemand op
# installeren drukt. Zo verdween Huntarr nadat dat project offline
# ging, en wezen Forgejo, Planka en Baby Buddy naar tags die er niet
# zijn. Draait bewust rustig: registries knijpen af, en een afgeknepen
# verzoek is niet te onderscheiden van een ontbrekend image.
docker run --rm \
-v "$PWD:/w" -w /w \
-e PYTHONDONTWRITEBYTECODE=1 \
python:3.12-slim \
python tools/controleer_images.py
- name: Sync code naar deploy-directory
run: |
set -euo pipefail

View file

@ -91,6 +91,20 @@ jobs:
fi
echo "Alle compose-bestanden zijn geldig"
- name: Images controleren
run: |
set -euo pipefail
# Een image dat niet meer bestaat merk je anders pas als iemand op
# installeren drukt. Zo verdween Huntarr nadat dat project offline
# ging, en wezen Forgejo, Planka en Baby Buddy naar tags die er niet
# zijn. Draait bewust rustig: registries knijpen af, en een afgeknepen
# verzoek is niet te onderscheiden van een ontbrekend image.
docker run --rm \
-v "$PWD:/w" -w /w \
-e PYTHONDONTWRITEBYTECODE=1 \
python:3.12-slim \
python tools/controleer_images.py
- name: Sync code naar deploy-directory
run: |
set -euo pipefail

View file

@ -1,3 +1,39 @@
# v0.8.20-beta — Prometheus startte niet, en 23 apps erbij
**Prometheus stond kapot in de catalogus.** Het sjabloon koppelde een lege map op
`/etc/prometheus` en startte met `--config.file=/etc/prometheus/prometheus.yml`
een bestand dat er nooit kwam. Prometheus stopt daar meteen op. En zelfs mét
configuratie viel er niets te meten: er was in de hele catalogus geen
node-exporter en geen cAdvisor. Er zit nu een startconfiguratie bij, met
koppelvelden naar allebei.
Datzelfde patroon zat op meer plekken, en dat is waar de meeste apps van deze
ronde vandaan komen — niet nieuwe categorieën, maar de ontbrekende helft van wat
er al stond:
- **node-exporter**, **cAdvisor** en **Alertmanager** maken Prometheus en Grafana
pas bruikbaar
- **Loki** en **Grafana Alloy** bewaren logs; Dozzle liet alleen zien wat er nú
gebeurt, en na een herstart was de oorzaak weg
- **Unbound** zoekt DNS zelf op. Pi-hole en AdGuard blokkeerden wel reclame, maar
stuurden de rest gewoon door naar Google of Cloudflare
**Domeinen die ontbraken:** evcc (zonnepanelen, thuisbatterij en laadpaal op
elkaar afstemmen), Healthchecks (merkt dat een cronjob níét liep — Uptime Kuma
merkt alleen dat iets uit ligt), Infisical (geheimen voor je applicaties, waar
Vaultwarden voor mensen is), Music Assistant en pgAdmin.
**Verder:** Crafty Controller, Manyfold, ErsatzTV, FitTrackee, ComfyUI, Omada
Controller, en de zwaardere Zabbix, Graylog, Wazuh, NetBox, Seafile en OpenCloud.
Van 218 naar 241 apps. Monitoring gaat van 18 naar 27.
**Een controle die zichzelf tegensprak.** De image-controle draaide met zestien
verzoeken tegelijk en meldde negen ontbrekende lscr.io-images. Alle negen bleken
te bestaan: registries knijpen af, en een afgeknepen verzoek ziet er precies zo
uit als een image dat er niet is. Bijna had ik negen werkende sjablonen
"gerepareerd". Die controle zit nu in `tools/controleer_images.py`, met vier
tegelijk en opnieuw proberen bij twijfel, en draait als stap in CI.
# v0.8.10-beta — Vervangers voor big tech, en sleutels in het juiste formaat
**Een sleutel is niet zomaar een willekeurige string.** De generator maakte

View file

@ -16,7 +16,7 @@ IP-adres in je netwerk geven.
| | |
|---|---|
| **App-catalogus** | 218 kant-en-klare apps, ingedeeld in categorieën met kleur. Zoeken, filteren en installeren met een formulier in plaats van YAML. |
| **App-catalogus** | 241 kant-en-klare apps, ingedeeld in categorieën met kleur. Zoeken, filteren en installeren met een formulier in plaats van YAML. |
| **Stacks beheren** | Starten, stoppen, bijwerken en herstarten — per stack of per losse container, met logs en live CPU-/geheugengebruik. |
| **Apps koppelen** | Stacks komen op een gedeeld netwerk, zodat ze elkaar op naam bereiken. Een veld dat naar een andere app verwijst toont een keuzelijst met wat je al draait. |
| **Eigen IP per stack** | Met macvlan of ipvlan krijgt een app een eigen adres in je LAN in plaats van poorten op de host. |

View file

@ -1 +1 @@
0.8.10-beta
0.8.20-beta

View file

@ -0,0 +1,13 @@
# Startconfiguratie. Zonder dit bestand weigert Alertmanager te starten.
route:
receiver: standaard
group_by: [alertname]
group_wait: 30s
repeat_interval: 4h
receivers:
- name: standaard
<%- if webhook_url %>
webhook_configs:
- url: "<< webhook_url >>"
<%- endif %>

View file

@ -0,0 +1,13 @@
services:
<< service_name >>:
image: quay.io/prometheus/alertmanager:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
volumes:
- << appdata_dir >>/<< service_name >>:/etc/alertmanager
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
command: --config.file=/etc/alertmanager/alertmanager.yml --storage.path=/alertmanager
ports:
- "<< port >>:9093"
restart: unless-stopped

View file

@ -0,0 +1,77 @@
{
"kind": "compose",
"metadata": {
"name": "Alertmanager",
"description": "Neemt de alarmen van Prometheus aan, bundelt ze en stuurt ze door naar e-mail, ntfy of Discord. Onderdrukt herhalingen.",
"tags": [
"monitoring",
"meldingen",
"prometheus"
],
"icon": {
"provider": "selfhst",
"id": "prometheus"
},
"version": {
"name": "latest"
},
"categories": [
"monitoring",
"communicatie"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "alertmanager",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 9093,
"required": true
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
},
{
"title": "Instellingen",
"items": [
{
"name": "webhook_url",
"type": "str",
"title": "Webhook",
"default": "",
"required": false,
"connect": {
"app": "ntfy",
"scheme": "http",
"port": 80
},
"help": "Waar de alarmen naartoe gaan. Wijs dit naar ntfy of Gotify, of laat het leeg en stel het later in het bestand in."
}
]
}
]
}

View file

@ -0,0 +1,14 @@
services:
<< service_name >>:
image: grafana/alloy:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
volumes:
- << appdata_dir >>/<< service_name >>:/var/lib/alloy/data
- ./config.alloy:/etc/alloy/config.alloy:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
command: run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
ports:
- "<< port >>:12345"
restart: unless-stopped

View file

@ -0,0 +1,16 @@
// Leest de logs van elke draaiende container en stuurt ze naar Loki.
discovery.docker "containers" {
host = "unix:///var/run/docker.sock"
}
loki.source.docker "containers" {
host = "unix:///var/run/docker.sock"
targets = discovery.docker.containers.targets
forward_to = [loki.write.opslag.receiver]
}
loki.write "opslag" {
endpoint {
url = "<< loki_url >>/loki/api/v1/push"
}
}

76
apps/alloy/template.json Normal file
View file

@ -0,0 +1,76 @@
{
"kind": "compose",
"metadata": {
"name": "Grafana Alloy",
"description": "Verzamelt de logs van je containers en stuurt ze naar Loki. De opvolger van Promtail.",
"tags": [
"logs",
"monitoring",
"grafana"
],
"icon": {
"provider": "selfhst",
"id": "grafana"
},
"version": {
"name": "latest"
},
"categories": [
"monitoring"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "alloy",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 12345,
"required": true
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
},
{
"title": "Instellingen",
"items": [
{
"name": "loki_url",
"type": "str",
"title": "Adres van Loki",
"default": "http://loki:3100",
"required": true,
"connect": {
"app": "loki",
"scheme": "http",
"port": 3100
},
"help": "Waar Alloy de logs naartoe stuurt. Draaien ze op hetzelfde gedeelde netwerk, dan is de containernaam genoeg."
}
]
}
]
}

View file

@ -0,0 +1,18 @@
services:
<< service_name >>:
image: gcr.io/cadvisor/cadvisor:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
ports:
- "<< port >>:8080"
devices:
- /dev/kmsg
privileged: true
restart: unless-stopped

View file

@ -0,0 +1,53 @@
{
"kind": "compose",
"metadata": {
"name": "cAdvisor",
"description": "Meet per container hoeveel processor, geheugen en netwerk er verbruikt wordt. Vult node-exporter aan, dat alleen de host als geheel ziet. Let op: Draait met verhoogde rechten omdat het bij de cgroups van elke container moet kunnen.",
"tags": [
"monitoring",
"metrics",
"containers",
"prometheus"
],
"icon": {
"provider": "selfhst",
"id": "cadvisor"
},
"version": {
"name": "latest"
},
"categories": [
"monitoring",
"beheer"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "cadvisor",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 8202,
"required": true,
"description": "Standaard 8080; hier verplaatst omdat die poort al bezet is"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
}
]
}

View file

@ -0,0 +1,12 @@
services:
<< service_name >>:
image: ghcr.io/ai-dock/comfyui:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
volumes:
- << appdata_dir >>/<< service_name >>:/opt/ComfyUI/models
- << appdata_dir >>/<< service_name >>-output:/opt/ComfyUI/output
ports:
- "<< port >>:8188"
restart: unless-stopped

View file

@ -0,0 +1,59 @@
{
"kind": "compose",
"metadata": {
"name": "ComfyUI",
"description": "Beeldgeneratie met modellen die op je eigen machine draaien, opgebouwd als een schema dat je zelf aan elkaar klikt. Let op: Zonder videokaart die doorgegeven wordt aan de container draait dit op de processor, en dan duurt één afbeelding minuten in plaats van seconden.",
"tags": [
"ai",
"beeld",
"generatie"
],
"icon": {
"provider": "selfhst",
"id": "comfyui"
},
"version": {
"name": "latest"
},
"categories": [
"ai"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "comfyui",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 8210,
"required": true,
"description": "Standaard 8188; hier verplaatst omdat die poort al bezet is"
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
}
]
}

View file

@ -0,0 +1,14 @@
services:
<< service_name >>:
image: registry.gitlab.com/crafty-controller/crafty-4:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
volumes:
- << appdata_dir >>/<< service_name >>:/crafty/app/config
- << appdata_dir >>/<< service_name >>-servers:/crafty/servers
- << appdata_dir >>/<< service_name >>-backups:/crafty/backups
ports:
- "<< port >>:8443"
- "<< port_spel >>:25565"
restart: unless-stopped

68
apps/crafty/template.json Normal file
View file

@ -0,0 +1,68 @@
{
"kind": "compose",
"metadata": {
"name": "Crafty Controller",
"description": "Beheerpaneel voor gameservers: meerdere Minecraft-werelden naast elkaar, met back-ups, planning en rechten per gebruiker.",
"tags": [
"spellen",
"minecraft",
"gameserver"
],
"icon": {
"provider": "selfhst",
"id": "crafty"
},
"version": {
"name": "latest"
},
"categories": [
"spellen",
"beheer"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "crafty",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 8206,
"required": true,
"description": "Standaard 8443; hier verplaatst omdat die poort al bezet is"
},
{
"name": "port_spel",
"type": "int",
"title": "Speelpoort",
"default": 25566,
"required": true,
"description": "Voor de eerste server; volgende krijgen 25566 en verder"
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
}
]
}

View file

@ -0,0 +1,12 @@
services:
<< service_name >>:
image: jasongdove/ersatztv:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
volumes:
- << appdata_dir >>/<< service_name >>:/config
- << data_root >>/media:/data/media:ro
ports:
- "<< port >>:8409"
restart: unless-stopped

View file

@ -0,0 +1,68 @@
{
"kind": "compose",
"metadata": {
"name": "ErsatzTV",
"description": "Maakt echte tv-kanalen van je eigen bibliotheek, met een programmagids. Je zapt langs je eigen films en series in plaats van te kiezen.",
"tags": [
"media",
"iptv",
"streaming"
],
"icon": {
"provider": "selfhst",
"id": "ersatztv"
},
"version": {
"name": "latest"
},
"categories": [
"media"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "ersatztv",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 8208,
"required": true,
"description": "Standaard 8409; hier verplaatst omdat die poort al bezet is"
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "data_root",
"type": "str",
"title": "Gedeelde datamap",
"default": "/mnt/data",
"required": true,
"description": "Je mediabibliotheek, alleen-lezen gekoppeld",
"help": "Media en downloads horen onder één map te staan, die als /data in elke container komt. Alleen dan kan een voltooide download met een hardlink naar je bibliotheek. Eronder: media/films, media/series, torrents/ en usenet/."
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
}
]
}

View file

@ -0,0 +1,12 @@
services:
<< service_name >>:
image: evcc/evcc:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
volumes:
- << appdata_dir >>/<< service_name >>:/root/.evcc
ports:
- "<< port >>:7070"
network_mode: host
restart: unless-stopped

60
apps/evcc/template.json Normal file
View file

@ -0,0 +1,60 @@
{
"kind": "compose",
"metadata": {
"name": "evcc",
"description": "Stemt je zonnepanelen, thuisbatterij en laadpaal op elkaar af. Laadt de auto als de zon schijnt of de stroomprijs laag is. Let op: Draait op het netwerk van de host, want het moet je omvormer en laadpaal in je LAN kunnen vinden. Bereikbaar op poort 7070. De instellingen zet je in evcc.yaml in de appdata-map.",
"tags": [
"energie",
"zonnepanelen",
"laadpaal",
"auto"
],
"icon": {
"provider": "selfhst",
"id": "evcc"
},
"version": {
"name": "latest"
},
"categories": [
"domotica",
"gezin"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "evcc",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 7070,
"required": true
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
}
]
}

View file

@ -0,0 +1,36 @@
services:
<< service_name >>:
image: fittrackee/fittrackee:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
- APP_SECRET_KEY=<< secret_key >>
- DATABASE_URL=postgresql://fittrackee:<< db_password >>@<< service_name >>-db:5432/fittrackee
- UI_URL=<< app_url >>
- APP_SETTINGS=fittrackee.config.ProductionConfig
- REDIS_URL=redis://<< service_name >>-redis:6379
volumes:
- << appdata_dir >>/<< service_name >>:/usr/src/app/uploads
ports:
- "<< port >>:5000"
depends_on:
- << service_name >>-db
- << service_name >>-redis
restart: unless-stopped
<< service_name >>-db:
image: postgres:16-alpine
container_name: << service_name >>-db
environment:
- POSTGRES_DB=fittrackee
- POSTGRES_USER=fittrackee
- POSTGRES_PASSWORD=<< db_password >>
volumes:
- << appdata_dir >>/<< service_name >>-db:/var/lib/postgresql/data
restart: unless-stopped
<< service_name >>-redis:
image: valkey/valkey:8-alpine
container_name: << service_name >>-redis
command: valkey-server --save 60 1
volumes:
- << appdata_dir >>/<< service_name >>-redis:/data
restart: unless-stopped

View file

@ -0,0 +1,95 @@
{
"kind": "compose",
"metadata": {
"name": "FitTrackee",
"description": "Houdt je hardloop-, fiets- en wandeltochten bij uit gpx-bestanden, met kaarten en statistieken per periode.",
"tags": [
"sport",
"gezondheid",
"gps"
],
"icon": {
"provider": "selfhst",
"id": "fittrackee"
},
"version": {
"name": "latest"
},
"categories": [
"gezin"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "fittrackee",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 8209,
"required": true,
"description": "Standaard 5000; hier verplaatst omdat die poort al bezet is"
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
},
{
"title": "Instellingen",
"items": [
{
"name": "app_url",
"type": "str",
"title": "Publiek adres",
"default": "http://localhost:8209",
"required": true
}
]
},
{
"title": "Toegang",
"items": [
{
"name": "db_password",
"type": "str",
"title": "Databasewachtwoord",
"default": "",
"required": true,
"secret": true,
"generate": "auto",
"description": "Wordt automatisch ingevuld en in .env bewaard"
},
{
"name": "secret_key",
"type": "str",
"title": "Geheime sleutel",
"default": "",
"required": true,
"secret": true,
"generate": "auto"
}
]
}
]
}

View file

@ -0,0 +1,41 @@
services:
<< service_name >>:
image: graylog/graylog:6.1
container_name: << service_name >>
environment:
- TZ=<< timezone >>
- GRAYLOG_PASSWORD_SECRET=<< password_secret >>
- GRAYLOG_ROOT_PASSWORD_SHA2=<< root_password_sha2 >>
- GRAYLOG_HTTP_EXTERNAL_URI=<< app_url >>/
- GRAYLOG_MONGODB_URI=mongodb://<< service_name >>-mongo:27017/graylog
- GRAYLOG_ELASTICSEARCH_HOSTS=http://<< service_name >>-opensearch:9200
volumes:
- << appdata_dir >>/<< service_name >>:/usr/share/graylog/data/data
- << appdata_dir >>/<< service_name >>-journal:/usr/share/graylog/data/journal
ports:
- "<< port >>:9000"
- "<< port_syslog >>:1514"
- "<< port_syslog >>:1514/udp"
- "<< port_gelf >>:12201"
- "<< port_gelf >>:12201/udp"
depends_on:
- << service_name >>-mongo
- << service_name >>-opensearch
restart: unless-stopped
<< service_name >>-mongo:
image: mongo:6
container_name: << service_name >>-mongo
volumes:
- << appdata_dir >>/<< service_name >>-mongo:/data/db
restart: unless-stopped
<< service_name >>-opensearch:
image: opensearchproject/opensearch:2
container_name: << service_name >>-opensearch
environment:
- discovery.type=single-node
- plugins.security.disabled=true
- OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g
- bootstrap.memory_lock=true
volumes:
- << appdata_dir >>/<< service_name >>-opensearch:/usr/share/opensearch/data
restart: unless-stopped

113
apps/graylog/template.json Normal file
View file

@ -0,0 +1,113 @@
{
"kind": "compose",
"metadata": {
"name": "Graylog",
"description": "Verzamelt logs van je hele netwerk, doorzoekt ze en slaat alarm bij patronen die je zelf instelt. Zwaarder dan Loki, maar met meer in huis. Let op: Reken op minstens 4 GB geheugen: OpenSearch alleen al pakt er 1.",
"tags": [
"logs",
"monitoring",
"beveiliging"
],
"icon": {
"provider": "selfhst",
"id": "graylog"
},
"version": {
"name": "latest"
},
"categories": [
"monitoring",
"beveiliging"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "graylog",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 8214,
"required": true,
"description": "Standaard 9000; hier verplaatst omdat die poort al bezet is"
},
{
"name": "port_syslog",
"type": "int",
"title": "Syslog-poort",
"default": 1514,
"required": true,
"description": "Hier sturen je apparaten hun logs naartoe"
},
{
"name": "port_gelf",
"type": "int",
"title": "GELF-poort",
"default": 12201,
"required": true,
"description": "Voor logs uit Docker"
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
},
{
"title": "Instellingen",
"items": [
{
"name": "app_url",
"type": "str",
"title": "Publiek adres",
"default": "http://localhost:8214",
"required": true
}
]
},
{
"title": "Toegang",
"items": [
{
"name": "password_secret",
"type": "str",
"title": "Geheime sleutel",
"default": "",
"required": true,
"secret": true,
"generate": "auto",
"description": "Minimaal 16 tekens; versleutelt de opgeslagen wachtwoorden"
},
{
"name": "root_password_sha2",
"type": "str",
"title": "SHA-256 van je beheerderswachtwoord",
"default": "",
"required": true,
"secret": true,
"eigen_invoer": true,
"description": "Maak hem met: echo -n 'jouwwachtwoord' | sha256sum"
}
]
}
]
}

View file

@ -0,0 +1,19 @@
services:
<< service_name >>:
image: healthchecks/healthchecks:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
- SECRET_KEY=<< secret_key >>
- SITE_ROOT=<< app_url >>
- DB=sqlite
- DB_NAME=/data/hc.sqlite
- SUPERUSER_EMAIL=<< admin_email >>
- SUPERUSER_PASSWORD=<< admin_password >>
- ALLOWED_HOSTS=*
- REGISTRATION_OPEN=False
volumes:
- << appdata_dir >>/<< service_name >>:/data
ports:
- "<< port >>:8000"
restart: unless-stopped

View file

@ -0,0 +1,101 @@
{
"kind": "compose",
"metadata": {
"name": "Healthchecks",
"description": "Merkt het als een cronjob níét draait. Elke taak pingt na afloop; blijft die ping uit, dan krijg je bericht.",
"tags": [
"cron",
"monitoring",
"meldingen"
],
"icon": {
"provider": "selfhst",
"id": "healthchecks"
},
"version": {
"name": "latest"
},
"categories": [
"monitoring"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "healthchecks",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 8203,
"required": true,
"description": "Standaard 8000; hier verplaatst omdat die poort al bezet is"
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
},
{
"title": "Instellingen",
"items": [
{
"name": "app_url",
"type": "str",
"title": "Publiek adres",
"default": "http://localhost:8203",
"required": true
},
{
"name": "admin_email",
"type": "str",
"title": "E-mailadres beheerder",
"default": "admin@example.com",
"required": true
}
]
},
{
"title": "Toegang",
"items": [
{
"name": "secret_key",
"type": "str",
"title": "Django-sleutel",
"default": "",
"required": true,
"secret": true,
"generate": "auto"
},
{
"name": "admin_password",
"type": "str",
"title": "Beheerderswachtwoord",
"default": "",
"required": true,
"secret": true,
"eigen_invoer": true
}
]
}
]
}

View file

@ -0,0 +1,35 @@
services:
<< service_name >>:
image: infisical/infisical:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
- NODE_ENV=production
- ENCRYPTION_KEY=<< encryption_key >>
- AUTH_SECRET=<< auth_secret >>
- DB_CONNECTION_URI=postgres://infisical:<< db_password >>@<< service_name >>-db:5432/infisical
- REDIS_URL=redis://<< service_name >>-redis:6379
- SITE_URL=<< app_url >>
ports:
- "<< port >>:8080"
depends_on:
- << service_name >>-db
- << service_name >>-redis
restart: unless-stopped
<< service_name >>-db:
image: postgres:16-alpine
container_name: << service_name >>-db
environment:
- POSTGRES_DB=infisical
- POSTGRES_USER=infisical
- POSTGRES_PASSWORD=<< db_password >>
volumes:
- << appdata_dir >>/<< service_name >>-db:/var/lib/postgresql/data
restart: unless-stopped
<< service_name >>-redis:
image: valkey/valkey:8-alpine
container_name: << service_name >>-redis
command: valkey-server --save 60 1
volumes:
- << appdata_dir >>/<< service_name >>-redis:/data
restart: unless-stopped

View file

@ -0,0 +1,107 @@
{
"kind": "compose",
"metadata": {
"name": "Infisical",
"description": "Bewaart de geheimen van je eigen applicaties en deelt ze uit per omgeving. Vaultwarden is voor mensen, dit is voor servers.",
"tags": [
"geheimen",
"ontwikkeling",
"beveiliging"
],
"icon": {
"provider": "selfhst",
"id": "infisical"
},
"version": {
"name": "latest"
},
"categories": [
"beveiliging",
"ontwikkeling"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "infisical",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 8204,
"required": true,
"description": "Standaard 8080; hier verplaatst omdat die poort al bezet is"
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
},
{
"title": "Instellingen",
"items": [
{
"name": "app_url",
"type": "str",
"title": "Publiek adres",
"default": "http://localhost:8204",
"required": true
}
]
},
{
"title": "Toegang",
"items": [
{
"name": "db_password",
"type": "str",
"title": "Databasewachtwoord",
"default": "",
"required": true,
"secret": true,
"generate": "auto",
"description": "Wordt automatisch ingevuld en in .env bewaard"
},
{
"name": "encryption_key",
"type": "str",
"title": "Versleutelingssleutel",
"default": "",
"required": true,
"secret": true,
"generate": "auto",
"secret_format": "hex",
"secret_bytes": 16
},
{
"name": "auth_secret",
"type": "str",
"title": "Sleutel voor de inlogsessies",
"default": "",
"required": true,
"secret": true,
"generate": "auto"
}
]
}
]
}

View file

@ -0,0 +1,12 @@
services:
<< service_name >>:
image: grafana/loki:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
volumes:
- << appdata_dir >>/<< service_name >>:/loki
command: -config.file=/etc/loki/local-config.yaml
ports:
- "<< port >>:3100"
restart: unless-stopped

58
apps/loki/template.json Normal file
View file

@ -0,0 +1,58 @@
{
"kind": "compose",
"metadata": {
"name": "Loki",
"description": "Bewaart logregels zodat je ze later nog kunt doorzoeken. Dozzle laat zien wat er nú gebeurt; Loki weet nog wat er vannacht misging.",
"tags": [
"logs",
"monitoring",
"grafana"
],
"icon": {
"provider": "selfhst",
"id": "loki"
},
"version": {
"name": "latest"
},
"categories": [
"monitoring"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "loki",
"required": true
},
{
"name": "port",
"type": "int",
"title": "API-poort",
"default": 3100,
"required": true
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
}
]
}

View file

@ -0,0 +1,36 @@
services:
<< service_name >>:
image: ghcr.io/manyfold3d/manyfold:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
- SECRET_KEY_BASE=<< secret_key_base >>
- DATABASE_URL=postgresql://manyfold:<< db_password >>@<< service_name >>-db/manyfold
- REDIS_URL=redis://<< service_name >>-redis:6379/1
- PUID=<< puid >>
- PGID=<< pgid >>
volumes:
- << appdata_dir >>/<< service_name >>:/libraries
ports:
- "<< port >>:3214"
depends_on:
- << service_name >>-db
- << service_name >>-redis
restart: unless-stopped
<< service_name >>-db:
image: postgres:16-alpine
container_name: << service_name >>-db
environment:
- POSTGRES_DB=manyfold
- POSTGRES_USER=manyfold
- POSTGRES_PASSWORD=<< db_password >>
volumes:
- << appdata_dir >>/<< service_name >>-db:/var/lib/postgresql/data
restart: unless-stopped
<< service_name >>-redis:
image: valkey/valkey:8-alpine
container_name: << service_name >>-redis
command: valkey-server --save 60 1
volumes:
- << appdata_dir >>/<< service_name >>-redis:/data
restart: unless-stopped

104
apps/manyfold/template.json Normal file
View file

@ -0,0 +1,104 @@
{
"kind": "compose",
"metadata": {
"name": "Manyfold",
"description": "Bibliotheek voor je 3D-modellen: doorzoekbaar, met voorbeeldweergave, tags en licentie per model.",
"tags": [
"3d-printen",
"modellen",
"bibliotheek"
],
"icon": {
"provider": "selfhst",
"id": "manyfold"
},
"version": {
"name": "latest"
},
"categories": [
"3d-printen",
"documenten"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "manyfold",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 8207,
"required": true,
"description": "Standaard 3214; hier verplaatst omdat die poort al bezet is"
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
},
{
"title": "Gebruiker",
"items": [
{
"name": "puid",
"type": "int",
"title": "Gebruikers-ID (PUID)",
"default": 1000,
"required": true,
"description": "`id -u` op je server"
},
{
"name": "pgid",
"type": "int",
"title": "Groeps-ID (PGID)",
"default": 1000,
"required": true
}
]
},
{
"title": "Toegang",
"items": [
{
"name": "db_password",
"type": "str",
"title": "Databasewachtwoord",
"default": "",
"required": true,
"secret": true,
"generate": "auto",
"description": "Wordt automatisch ingevuld en in .env bewaard"
},
{
"name": "secret_key_base",
"type": "str",
"title": "Rails-sleutel",
"default": "",
"required": true,
"secret": true,
"generate": "auto"
}
]
}
]
}

View file

@ -0,0 +1,10 @@
services:
<< service_name >>:
image: ghcr.io/music-assistant/server:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
volumes:
- << appdata_dir >>/<< service_name >>:/data
network_mode: host
restart: unless-stopped

View file

@ -0,0 +1,52 @@
{
"kind": "compose",
"metadata": {
"name": "Music Assistant",
"description": "Brengt Spotify, je eigen bestanden en je speakers samen onder één afspeellijst. Werkt samen met Home Assistant. Let op: Draait op het netwerk van de host, want anders vindt het je Chromecasts en Sonos-speakers niet. Bereikbaar op poort 8095.",
"tags": [
"muziek",
"domotica",
"streaming"
],
"icon": {
"provider": "selfhst",
"id": "music-assistant"
},
"version": {
"name": "latest"
},
"categories": [
"domotica",
"media"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "musicassistant",
"required": true
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
}
]
}

View file

@ -0,0 +1,44 @@
services:
<< service_name >>:
image: netboxcommunity/netbox:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
- DB_HOST=<< service_name >>-db
- DB_NAME=netbox
- DB_USER=netbox
- DB_PASSWORD=<< db_password >>
- REDIS_HOST=<< service_name >>-redis
- REDIS_PORT=6379
- REDIS_CACHE_HOST=<< service_name >>-redis
- REDIS_CACHE_DATABASE=1
- SECRET_KEY=<< secret_key >>
- SUPERUSER_NAME=<< admin_user >>
- SUPERUSER_EMAIL=<< admin_email >>
- SUPERUSER_PASSWORD=<< admin_password >>
- ALLOWED_HOSTS=*
volumes:
- << appdata_dir >>/<< service_name >>:/opt/netbox/netbox/media
ports:
- "<< port >>:8080"
depends_on:
- << service_name >>-db
- << service_name >>-redis
restart: unless-stopped
<< service_name >>-db:
image: postgres:16-alpine
container_name: << service_name >>-db
environment:
- POSTGRES_DB=netbox
- POSTGRES_USER=netbox
- POSTGRES_PASSWORD=<< db_password >>
volumes:
- << appdata_dir >>/<< service_name >>-db:/var/lib/postgresql/data
restart: unless-stopped
<< service_name >>-redis:
image: valkey/valkey:8-alpine
container_name: << service_name >>-redis
command: valkey-server --save 60 1
volumes:
- << appdata_dir >>/<< service_name >>-redis:/data
restart: unless-stopped

115
apps/netbox/template.json Normal file
View file

@ -0,0 +1,115 @@
{
"kind": "compose",
"metadata": {
"name": "NetBox",
"description": "Documenteert je netwerk zoals het hoort te zijn: racks, kabels, IP-adressen en VLAN's, als bron van waarheid voor je automatisering.",
"tags": [
"netwerk",
"documentatie",
"ipam"
],
"icon": {
"provider": "selfhst",
"id": "netbox"
},
"version": {
"name": "latest"
},
"categories": [
"netwerk",
"zakelijk"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "netbox",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 8216,
"required": true,
"description": "Standaard 8080; hier verplaatst omdat die poort al bezet is"
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
},
{
"title": "Instellingen",
"items": [
{
"name": "admin_user",
"type": "str",
"title": "Beheerdersnaam",
"default": "admin",
"required": true
},
{
"name": "admin_email",
"type": "str",
"title": "E-mailadres beheerder",
"default": "admin@example.com",
"required": true
}
]
},
{
"title": "Toegang",
"items": [
{
"name": "db_password",
"type": "str",
"title": "Databasewachtwoord",
"default": "",
"required": true,
"secret": true,
"generate": "auto",
"description": "Wordt automatisch ingevuld en in .env bewaard"
},
{
"name": "secret_key",
"type": "str",
"title": "Django-sleutel",
"default": "",
"required": true,
"secret": true,
"generate": "auto",
"secret_format": "hex",
"secret_bytes": 32,
"description": "Minimaal 50 tekens"
},
{
"name": "admin_password",
"type": "str",
"title": "Beheerderswachtwoord",
"default": "",
"required": true,
"secret": true,
"eigen_invoer": true
}
]
}
]
}

View file

@ -0,0 +1,13 @@
services:
<< service_name >>:
image: quay.io/prometheus/node-exporter:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
volumes:
- /:/host:ro,rslave
command: --path.rootfs=/host --collector.filesystem.mount-points-exclude="^/(sys|proc|dev|host|etc)($$|/)"
ports:
- "<< port >>:9100"
pid: host
restart: unless-stopped

View file

@ -0,0 +1,50 @@
{
"kind": "compose",
"metadata": {
"name": "node-exporter",
"description": "Levert de cijfers van de host aan Prometheus: processor, geheugen, schijven, netwerk en temperatuur. Let op: Meet de host, dus koppelt het hele bestandssysteem alleen-lezen mee. Zonder Prometheus ernaast doet dit niets.",
"tags": [
"monitoring",
"metrics",
"prometheus"
],
"icon": {
"provider": "selfhst",
"id": "prometheus"
},
"version": {
"name": "latest"
},
"categories": [
"monitoring"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "nodeexporter",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Metrics-poort",
"default": 9100,
"required": true
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
}
]
}

View file

@ -0,0 +1,19 @@
services:
<< service_name >>:
image: mbentley/omada-controller:latest
container_name: << service_name >>
environment:
- MANAGE_HTTP_PORT=8088
- MANAGE_HTTPS_PORT=8043
- PORT_ADOPT_V1=29812
- PORT_APP_DISCOVERY=27001
- SHOW_SERVER_LOGS=true
- TZ=<< timezone >>
volumes:
- << appdata_dir >>/<< service_name >>:/opt/tplink/EAPController/data
- << appdata_dir >>/<< service_name >>-logs:/opt/tplink/EAPController/logs
ports:
- "<< port >>:8043"
- "<< port_http >>:8088"
- "<< port_beheer >>:29814"
restart: unless-stopped

74
apps/omada/template.json Normal file
View file

@ -0,0 +1,74 @@
{
"kind": "compose",
"metadata": {
"name": "Omada Controller",
"description": "Beheert TP-Link Omada-accesspoints, switches en routers vanaf één plek, met een netwerkkaart en gastportaal. Let op: Accesspoints vinden de controller via uitzendingen op je LAN. Zit hij in een Docker-bridge, dan moet je ze handmatig aanmelden of de controller een eigen IP-adres geven.",
"tags": [
"netwerk",
"wifi",
"accesspoints"
],
"icon": {
"provider": "selfhst",
"id": "omada"
},
"version": {
"name": "latest"
},
"categories": [
"netwerk"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "omada",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 8211,
"required": true,
"description": "Standaard 8043; hier verplaatst omdat die poort al bezet is"
},
{
"name": "port_http",
"type": "int",
"title": "HTTP-poort",
"default": 8212,
"required": true
},
{
"name": "port_beheer",
"type": "int",
"title": "Beheerpoort",
"default": 29814,
"required": true,
"description": "Waarmee de apparaten zichzelf aanmelden"
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
}
]
}

View file

@ -0,0 +1,17 @@
services:
<< service_name >>:
image: opencloudeu/opencloud:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
- OC_URL=<< app_url >>
- OC_INSECURE=true
- PROXY_HTTP_ADDR=0.0.0.0:9200
- IDM_ADMIN_PASSWORD=<< admin_password >>
volumes:
- << appdata_dir >>/<< service_name >>:/etc/opencloud
- << appdata_dir >>/<< service_name >>-data:/var/lib/opencloud
command: sh -c "opencloud init || true && opencloud server"
ports:
- "<< port >>:9200"
restart: unless-stopped

View file

@ -0,0 +1,86 @@
{
"kind": "compose",
"metadata": {
"name": "OpenCloud",
"description": "Bestandsopslag met delen en samenwerken, gebouwd op de ownCloud-basis maar volledig opnieuw opgezet in Go.",
"tags": [
"bestanden",
"sync",
"opslag",
"samenwerken"
],
"icon": {
"provider": "selfhst",
"id": "opencloud"
},
"version": {
"name": "latest"
},
"categories": [
"opslag",
"productiviteit"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "opencloud",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 9200,
"required": true
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
},
{
"title": "Instellingen",
"items": [
{
"name": "app_url",
"type": "str",
"title": "Publiek adres",
"default": "https://cloud.example.com",
"required": true
}
]
},
{
"title": "Toegang",
"items": [
{
"name": "admin_password",
"type": "str",
"title": "Beheerderswachtwoord",
"default": "",
"required": true,
"secret": true,
"eigen_invoer": true
}
]
}
]
}

View file

@ -0,0 +1,14 @@
services:
<< service_name >>:
image: dpage/pgadmin4:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
- PGADMIN_DEFAULT_EMAIL=<< admin_email >>
- PGADMIN_DEFAULT_PASSWORD=<< admin_password >>
- PGADMIN_CONFIG_SERVER_MODE=True
volumes:
- << appdata_dir >>/<< service_name >>:/var/lib/pgadmin
ports:
- "<< port >>:80"
restart: unless-stopped

View file

@ -0,0 +1,85 @@
{
"kind": "compose",
"metadata": {
"name": "pgAdmin",
"description": "Beheerprogramma voor PostgreSQL: query's, schema's, gebruikers en back-ups. Uitgebreider dan Adminer, maar alleen voor PostgreSQL.",
"tags": [
"database",
"postgresql",
"beheer"
],
"icon": {
"provider": "selfhst",
"id": "pgadmin"
},
"version": {
"name": "latest"
},
"categories": [
"ontwikkeling"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "pgadmin",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 8205,
"required": true,
"description": "Standaard 80; hier verplaatst omdat die poort al bezet is"
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
},
{
"title": "Instellingen",
"items": [
{
"name": "admin_email",
"type": "str",
"title": "E-mailadres beheerder",
"default": "admin@example.com",
"required": true
}
]
},
{
"title": "Toegang",
"items": [
{
"name": "admin_password",
"type": "str",
"title": "Beheerderswachtwoord",
"default": "",
"required": true,
"secret": true,
"eigen_invoer": true
}
]
}
]
}

View file

@ -10,7 +10,10 @@ services:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.retention.time=<< retention >>
volumes:
- << appdata_dir >>/<< service_name >>/config:/etc/prometheus
# Het configuratiebestand komt uit dit sjabloon; zonder dat weigert
# Prometheus te starten. Eigen alertregels zet je in de rules-map.
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- << appdata_dir >>/<< service_name >>/rules:/etc/prometheus/rules
- << service_name >>_data:/prometheus
restart: unless-stopped

View file

@ -0,0 +1,28 @@
# Startconfiguratie. Prometheus weigert te starten zonder dit bestand — het
# sjabloon koppelde eerder een lege map en verwees naar een prometheus.yml die
# er nooit kwam.
global:
scrape_interval: 30s
evaluation_interval: 30s
rule_files:
- /etc/prometheus/rules/*.yml
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ["localhost:9090"]
<%- if node_exporter %>
# De host zelf: processor, geheugen, schijven, netwerk.
- job_name: node
static_configs:
- targets: ["<< node_exporter >>"]
<%- endif %>
<%- if cadvisor %>
# Per container: verbruik en herstarts.
- job_name: containers
static_configs:
- targets: ["<< cadvisor >>"]
<%- endif %>

View file

@ -59,6 +59,32 @@
"required": true,
"description": "Hoe lang meetwaarden bewaard blijven, bv. 30d of 1y",
"advanced": true
},
{
"name": "node_exporter",
"type": "str",
"title": "node-exporter",
"default": "node-exporter:9100",
"required": false,
"connect": {
"app": "node-exporter",
"scheme": "http",
"port": 9100
},
"help": "Waar Prometheus de cijfers van je host ophaalt: processor, geheugen, schijven en netwerk. Laat leeg als je die niet draait; Prometheus meet dan alleen zichzelf."
},
{
"name": "cadvisor",
"type": "str",
"title": "cAdvisor",
"default": "cadvisor:8080",
"required": false,
"connect": {
"app": "cadvisor",
"scheme": "http",
"port": 8080
},
"help": "Waar Prometheus de cijfers per container ophaalt. Laat leeg als je cAdvisor niet draait."
}
]
}

View file

@ -0,0 +1,30 @@
services:
<< service_name >>:
image: seafileltd/seafile-mc:latest
container_name: << service_name >>
environment:
- DB_HOST=<< service_name >>-db
- DB_ROOT_PASSWD=<< db_password >>
- SEAFILE_ADMIN_EMAIL=<< admin_email >>
- SEAFILE_ADMIN_PASSWORD=<< admin_password >>
- SEAFILE_SERVER_HOSTNAME=<< hostname >>
- SEAFILE_SERVER_LETSENCRYPT=false
- TIME_ZONE=<< timezone >>
volumes:
- << appdata_dir >>/<< service_name >>:/shared
ports:
- "<< port >>:80"
depends_on:
- << service_name >>-db
restart: unless-stopped
<< service_name >>-db:
image: mariadb:11
container_name: << service_name >>-db
environment:
- MARIADB_DATABASE=seafile
- MARIADB_USER=seafile
- MARIADB_PASSWORD=<< db_password >>
- MARIADB_ROOT_PASSWORD=<< db_password >>
volumes:
- << appdata_dir >>/<< service_name >>-db:/var/lib/mysql
restart: unless-stopped

102
apps/seafile/template.json Normal file
View file

@ -0,0 +1,102 @@
{
"kind": "compose",
"metadata": {
"name": "Seafile",
"description": "Bestandssynchronisatie die met grote mappen en veel kleine bestanden beter overweg kan dan Nextcloud, met versiebeheer per bibliotheek.",
"tags": [
"bestanden",
"sync",
"opslag"
],
"icon": {
"provider": "selfhst",
"id": "seafile"
},
"version": {
"name": "latest"
},
"categories": [
"opslag"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "seafile",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Webinterface",
"default": 8217,
"required": true,
"description": "Standaard 80; hier verplaatst omdat die poort al bezet is"
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
},
{
"title": "Instellingen",
"items": [
{
"name": "hostname",
"type": "str",
"title": "Publiek domein",
"default": "seafile.example.com",
"required": true
},
{
"name": "admin_email",
"type": "str",
"title": "E-mailadres beheerder",
"default": "admin@example.com",
"required": true
}
]
},
{
"title": "Toegang",
"items": [
{
"name": "db_password",
"type": "str",
"title": "Databasewachtwoord",
"default": "",
"required": true,
"secret": true,
"generate": "auto",
"description": "Wordt automatisch ingevuld en in .env bewaard"
},
{
"name": "admin_password",
"type": "str",
"title": "Beheerderswachtwoord",
"default": "",
"required": true,
"secret": true,
"eigen_invoer": true
}
]
}
]
}

View file

@ -0,0 +1,12 @@
services:
<< service_name >>:
image: mvance/unbound:latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
volumes:
- << appdata_dir >>/<< service_name >>:/opt/unbound/etc/unbound
ports:
- "<< port >>:53"
- "<< port >>:53/udp"
restart: unless-stopped

View file

@ -0,0 +1,60 @@
{
"kind": "compose",
"metadata": {
"name": "Unbound",
"description": "Zoekt DNS-namen zelf op bij de bron, in plaats van door te vragen aan Google of Cloudflare. Zet hem achter Pi-hole of AdGuard. Let op: Dit vervangt je blokkeerlijst niet — het is de stap erna. Pi-hole bepaalt wát er beantwoord wordt, Unbound waar het antwoord vandaan komt.",
"tags": [
"dns",
"privacy",
"netwerk"
],
"icon": {
"provider": "selfhst",
"id": "unbound"
},
"version": {
"name": "latest"
},
"categories": [
"netwerk",
"beveiliging"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "unbound",
"required": true
},
{
"name": "port",
"type": "int",
"title": "DNS-poort",
"default": 5335,
"required": true,
"description": "Zet dit adres in Pi-hole of AdGuard als upstream"
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
}
]
}

View file

@ -0,0 +1,42 @@
services:
<< service_name >>:
image: wazuh/wazuh-manager:4.9.2
container_name: << service_name >>
environment:
- TZ=<< timezone >>
- INDEXER_URL=https://<< service_name >>-indexer:9200
- INDEXER_USERNAME=admin
- INDEXER_PASSWORD=<< indexer_password >>
- API_USERNAME=wazuh-wui
- API_PASSWORD=<< api_password >>
volumes:
- << appdata_dir >>/<< service_name >>:/var/ossec/data
ports:
- "<< port_agent >>:1515"
- "<< port_api >>:55000"
depends_on:
- << service_name >>-indexer
restart: unless-stopped
<< service_name >>-indexer:
image: wazuh/wazuh-indexer:4.9.2
container_name: << service_name >>-indexer
environment:
- OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g
- bootstrap.memory_lock=true
volumes:
- << appdata_dir >>/<< service_name >>-indexer:/var/lib/wazuh-indexer
restart: unless-stopped
<< service_name >>-dashboard:
image: wazuh/wazuh-dashboard:4.9.2
container_name: << service_name >>-dashboard
environment:
- INDEXER_USERNAME=admin
- INDEXER_PASSWORD=<< indexer_password >>
- WAZUH_API_URL=https://<< service_name >>
- API_USERNAME=wazuh-wui
- API_PASSWORD=<< api_password >>
ports:
- "<< port_web >>:5601"
depends_on:
- << service_name >>-indexer
restart: unless-stopped

97
apps/wazuh/template.json Normal file
View file

@ -0,0 +1,97 @@
{
"kind": "compose",
"metadata": {
"name": "Wazuh",
"description": "Beveiligingsbewaking over je hele park: bestandsintegriteit, kwetsbaarheden, inbraakdetectie en naleving, met agents op elke machine. Let op: Wazuh verwacht normaal zelfgemaakte certificaten tussen zijn onderdelen. Deze opzet draait zonder, dus houd hem binnen je eigen netwerk. Reken op 4 GB geheugen of meer.",
"tags": [
"beveiliging",
"monitoring",
"inbraakdetectie"
],
"icon": {
"provider": "selfhst",
"id": "wazuh"
},
"version": {
"name": "latest"
},
"categories": [
"beveiliging",
"monitoring"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "wazuh",
"required": true
},
{
"name": "port_agent",
"type": "int",
"title": "Agentpoort",
"default": 1515,
"required": true,
"description": "Waarmee de agents zich aanmelden"
},
{
"name": "port_api",
"type": "int",
"title": "API-poort",
"default": 55000,
"required": true
},
{
"name": "port_web",
"type": "int",
"title": "Webinterface",
"default": 8215,
"required": true
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
},
{
"title": "Toegang",
"items": [
{
"name": "indexer_password",
"type": "str",
"title": "Wachtwoord voor de indexer",
"default": "",
"required": true,
"secret": true,
"generate": "auto"
},
{
"name": "api_password",
"type": "str",
"title": "Wachtwoord voor de API",
"default": "",
"required": true,
"secret": true,
"generate": "auto"
}
]
}
]
}

View file

@ -0,0 +1,40 @@
services:
<< service_name >>:
image: zabbix/zabbix-server-pgsql:alpine-7.0-latest
container_name: << service_name >>
environment:
- TZ=<< timezone >>
- DB_SERVER_HOST=<< service_name >>-db
- POSTGRES_USER=zabbix
- POSTGRES_PASSWORD=<< db_password >>
- POSTGRES_DB=zabbix
ports:
- "<< port >>:10051"
depends_on:
- << service_name >>-db
restart: unless-stopped
<< service_name >>-web:
image: zabbix/zabbix-web-nginx-pgsql:alpine-7.0-latest
container_name: << service_name >>-web
environment:
- ZBX_SERVER_HOST=<< service_name >>
- DB_SERVER_HOST=<< service_name >>-db
- POSTGRES_USER=zabbix
- POSTGRES_PASSWORD=<< db_password >>
- POSTGRES_DB=zabbix
- PHP_TZ=<< timezone >>
ports:
- "<< port_web >>:8080"
depends_on:
- << service_name >>
restart: unless-stopped
<< service_name >>-db:
image: postgres:16-alpine
container_name: << service_name >>-db
environment:
- POSTGRES_DB=zabbix
- POSTGRES_USER=zabbix
- POSTGRES_PASSWORD=<< db_password >>
volumes:
- << appdata_dir >>/<< service_name >>-db:/var/lib/postgresql/data
restart: unless-stopped

82
apps/zabbix/template.json Normal file
View file

@ -0,0 +1,82 @@
{
"kind": "compose",
"metadata": {
"name": "Zabbix",
"description": "Bewaakt servers, netwerkapparaten en diensten met agents én zonder, met drempelwaarden per meetwaarde en uitgebreide alarmering. Let op: Het standaardaccount is Admin met wachtwoord zabbix — wijzig dat meteen na de eerste keer inloggen.",
"tags": [
"monitoring",
"netwerk",
"infrastructuur"
],
"icon": {
"provider": "selfhst",
"id": "zabbix"
},
"version": {
"name": "latest"
},
"categories": [
"monitoring",
"netwerk"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "zabbix",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Serverpoort",
"default": 10051,
"required": true,
"description": "Waarmee de agents verbinding maken"
},
{
"name": "port_web",
"type": "int",
"title": "Webinterface",
"default": 8213,
"required": true
},
{
"name": "appdata_dir",
"type": "str",
"title": "Appdata-map",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor de gegevens van deze app"
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
},
{
"title": "Toegang",
"items": [
{
"name": "db_password",
"type": "str",
"title": "Databasewachtwoord",
"default": "",
"required": true,
"secret": true,
"generate": "auto",
"description": "Wordt automatisch ingevuld en in .env bewaard"
}
]
}
]
}

View file

@ -0,0 +1,77 @@
"""Controleer of elk image in de catalogus echt bestaat bij zijn registry.
Aanleiding: `ghcr.io/plexguide/huntarr` stond in de ARR-stack terwijl dat project
offline was gehaald en het image niet meer bestond. Daarna bleken ook Forgejo,
Planka en Baby Buddy naar tags te wijzen die er niet zijn. Zulke fouten merk je
anders pas als iemand op installeren drukt.
python3 tools/controleer_images.py [--json]
**Waarom dit langzaam gaat.** Registries knijpen af bij te veel verzoeken achter
elkaar, en een afgeknepen verzoek ziet er precies zo uit als een image dat niet
bestaat. Eén ronde met zestien tegelijk leverde negen valse meldingen op waarop
je bijna een werkend sjabloon gaat "repareren". Daarom weinig tegelijk, en wat
niet meteen lukt gaat opnieuw met oplopende pauzes.
"""
from __future__ import annotations
import concurrent.futures as cf
import json
import re
import sys
import time
from pathlib import Path
WORTEL = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(WORTEL / "server-up" / "core"))
import registry # noqa: E402
TEGELIJK = 4
POGINGEN = 3
IMAGE_RE = re.compile(r"^\s*image:\s*(\S+)\s*$", re.M)
def images() -> dict[str, list[str]]:
"""Elk image in de catalogus, met de apps die het gebruiken."""
uit: dict[str, list[str]] = {}
for pad in sorted((WORTEL / "apps").glob("*/files/*.yaml")):
for m in IMAGE_RE.finditer(pad.read_text(encoding="utf-8")):
naam = m.group(1)
if "<<" in naam: # nog niet gerenderd
continue
uit.setdefault(naam, []).append(pad.parent.parent.name)
return uit
def bestaat(image: str) -> bool:
for poging in range(POGINGEN):
try:
if registry.manifest_digest(image):
return True
except Exception:
pass
if poging < POGINGEN - 1:
time.sleep(2 ** poging) # 1s, 2s
return False
def main() -> int:
alles = images()
with cf.ThreadPoolExecutor(TEGELIJK) as ex:
uitslag = dict(zip(alles, ex.map(bestaat, alles)))
fout = {i: alles[i] for i, ok in uitslag.items() if not ok}
if "--json" in sys.argv:
print(json.dumps({"gecontroleerd": len(alles), "fout": fout}, indent=2))
else:
print(f"{len(alles)} unieke images gecontroleerd")
for image, apps in sorted(fout.items()):
print(f"{image} ({', '.join(sorted(set(apps)))})")
if not fout:
print(" alle images bestaan")
return 1 if fout else 0
if __name__ == "__main__":
raise SystemExit(main())