diff --git a/CHANGELOG.md b/CHANGELOG.md index ae1d1f3..be31e4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +# v0.8.17-beta — Elf nieuwe apps voor beheer, automatisering en monitoring + +De app-store bevatte nog weinig lichte infrastructuurapps die zowel op een +reguliere x86-server als op een 64-bits Raspberry Pi werken. Daarom zijn elf +nieuwe templates toegevoegd met expliciete AMD64- en ARM64-ondersteuning. + +- **Remote beheer:** RustDesk Server, MeshCentral en Apache Guacamole. Guacamole + genereert bij de eerste start automatisch het officiële PostgreSQL-schema. +- **Identiteit en discovery:** LLDAP en Consul bieden respectievelijk een lichte + LDAP-directory en service discovery met DNS en healthchecks. +- **Monitoring:** VictoriaMetrics en OpenObserve voegen efficiënte opslag toe + voor metrics en voor logs, metrics en traces. +- **Automatisering:** Activepieces en Windmill bieden visuele workflows, + scripts en interne apps. De Windmill-worker krijgt standaard geen toegang tot + de Docker-socket. +- **Bestandsoverdracht:** PairDrop maakt lokale bestandsoverdracht vanuit de + browser mogelijk. +- **Nomad als veilig control-plane:** de Nomad-container draait alleen als + server met webinterface. Workload-clients horen rechtstreeks op de hosts te + worden geïnstalleerd, omdat HashiCorp Nomad-clients in Docker niet officieel + ondersteunt. +- **Cataloguscontrole:** gerichte tests bewaken dat alle elf apps beschikbaar + blijven voor AMD64 en ARM64, dat Nomad geen containerized client wordt en dat + Guacamole zijn database vóór de webapp initialiseert. + # v0.8.16-beta — De startwizard blijft gesloten **De knop Sluiten verborg de optionele startwizard alleen tijdelijk.** De diff --git a/VERSION b/VERSION index 542db42..e359986 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.8.16-beta +0.8.17-beta diff --git a/apps/activepieces/files/compose.yaml b/apps/activepieces/files/compose.yaml new file mode 100644 index 0000000..5c72437 --- /dev/null +++ b/apps/activepieces/files/compose.yaml @@ -0,0 +1,13 @@ +services: + << service_name >>: + image: activepieces/activepieces:latest + container_name: << service_name >> + ports: + - "<< port >>:80" + environment: + - AP_REDIS_TYPE=MEMORY + - AP_DB_TYPE=PGLITE + - AP_FRONTEND_URL=<< frontend_url >> + volumes: + - << appdata_dir >>/<< service_name >>:/root/.activepieces + restart: unless-stopped diff --git a/apps/activepieces/template.json b/apps/activepieces/template.json new file mode 100644 index 0000000..c772eb9 --- /dev/null +++ b/apps/activepieces/template.json @@ -0,0 +1,24 @@ +{ + "kind": "compose", + "metadata": { + "name": "Activepieces", + "description": "Visuele workflow-automatisering als toegankelijk alternatief voor n8n. Deze lichte één-containeropzet gebruikt PGLite en een geheugenqueue en is bedoeld voor persoonlijk gebruik op één server.", + "tags": ["automatisering", "workflows", "integraties", "no-code"], + "icon": {"provider": "selfhst", "id": "activepieces"}, + "version": {"name": "latest"}, + "categories": ["productiviteit", "ontwikkeling"], + "architectures": ["amd64", "arm64"], + "architecture_note": "De officiële Activepieces-image ondersteunt AMD64 en ARM64." + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + {"name": "service_name", "type": "str", "title": "Servicenaam", "default": "activepieces", "required": true}, + {"name": "port", "type": "int", "title": "Webinterface", "default": 14003, "required": true}, + {"name": "appdata_dir", "type": "str", "title": "Appdata-map", "default": "/opt/serverup/appdata", "required": true}, + {"name": "frontend_url", "type": "str", "title": "Publiek adres", "default": "http://localhost:14003", "required": true, "description": "Gebruik voor webhooks het HTTPS-adres van je reverse proxy."} + ] + } + ] +} diff --git a/apps/consul/files/compose.yaml b/apps/consul/files/compose.yaml new file mode 100644 index 0000000..1e5ed38 --- /dev/null +++ b/apps/consul/files/compose.yaml @@ -0,0 +1,33 @@ +services: + << service_name >>: + image: hashicorp/consul:latest + container_name: << service_name >> + command: + - agent + - -server + - -bootstrap-expect=1 + - -ui + - -client=0.0.0.0 + - -advertise=<< advertise_ip >> + - -http-port=<< port_http >> + - -server-port=<< port_server >> + - -serf-lan-port=<< port_lan >> + - -serf-wan-port=<< port_wan >> + - -dns-port=<< port_dns >> + - -node=<< service_name >> + environment: + - CONSUL_BIND_INTERFACE=eth0 + ports: + - "<< port_http >>:<< port_http >>" + - "<< port_server >>:<< port_server >>" + - "<< port_lan >>:<< port_lan >>/tcp" + - "<< port_lan >>:<< port_lan >>/udp" + - "<< port_wan >>:<< port_wan >>/tcp" + - "<< port_wan >>:<< port_wan >>/udp" + - "<< port_dns >>:<< port_dns >>/tcp" + - "<< port_dns >>:<< port_dns >>/udp" + volumes: + - << appdata_dir >>/<< service_name >>:/consul/data + cap_add: + - IPC_LOCK + restart: unless-stopped diff --git a/apps/consul/template.json b/apps/consul/template.json new file mode 100644 index 0000000..7fd7207 --- /dev/null +++ b/apps/consul/template.json @@ -0,0 +1,33 @@ +{ + "kind": "compose", + "metadata": { + "name": "Consul", + "description": "Service discovery, DNS en healthchecks met een webinterface. Deze eenvoudige single-serveropzet is geschikt voor een thuislab; gebruik voor productie een oneven cluster van meerdere servers.", + "tags": ["service discovery", "dns", "healthchecks", "hashicorp"], + "icon": {"provider": "selfhst", "id": "consul"}, + "version": {"name": "latest"}, + "categories": ["netwerk", "beheer", "ontwikkeling"], + "architectures": ["amd64", "arm64"], + "architecture_note": "HashiCorp publiceert officiële Consul-images voor AMD64 en ARM64." + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + {"name": "service_name", "type": "str", "title": "Servicenaam", "default": "consul", "required": true}, + {"name": "appdata_dir", "type": "str", "title": "Appdata-map", "default": "/opt/serverup/appdata", "required": true}, + {"name": "advertise_ip", "type": "str", "title": "LAN-IP van deze server", "default": "", "required": true, "description": "Het bereikbare hostadres dat Consul aan agents doorgeeft, bijvoorbeeld 192.168.1.10."} + ] + }, + { + "title": "Poorten", + "items": [ + {"name": "port_http", "type": "int", "title": "HTTP API en webinterface", "default": 8500, "required": true}, + {"name": "port_server", "type": "int", "title": "Server-RPC", "default": 8300, "required": true}, + {"name": "port_lan", "type": "int", "title": "LAN-gossip", "default": 8301, "required": true}, + {"name": "port_wan", "type": "int", "title": "WAN-gossip", "default": 8302, "required": true}, + {"name": "port_dns", "type": "int", "title": "DNS", "default": 8600, "required": true} + ] + } + ] +} diff --git a/apps/guacamole/files/compose.yaml b/apps/guacamole/files/compose.yaml new file mode 100644 index 0000000..2e9350a --- /dev/null +++ b/apps/guacamole/files/compose.yaml @@ -0,0 +1,58 @@ +services: + << service_name >>-init: + image: guacamole/guacamole:1.6.0 + container_name: << service_name >>-init + user: "0:0" + entrypoint: ["/bin/sh", "-c"] + command: + - if [ ! -s /init/001-initdb.sql ]; then /opt/guacamole/bin/initdb.sh --postgresql > /init/001-initdb.sql; fi + volumes: + - guacamole-init:/init + restart: "no" + + << service_name >>-db: + image: postgres:16-alpine + container_name: << service_name >>-db + environment: + - POSTGRES_DB=guacamole + - POSTGRES_USER=guacamole + - POSTGRES_PASSWORD=<< db_password >> + volumes: + - << appdata_dir >>/<< service_name >>/db:/var/lib/postgresql/data + - guacamole-init:/docker-entrypoint-initdb.d:ro + depends_on: + << service_name >>-init: + condition: service_completed_successfully + healthcheck: + test: ["CMD-SHELL", "pg_isready -U guacamole -d guacamole"] + interval: 10s + timeout: 5s + retries: 10 + restart: unless-stopped + + << service_name >>-guacd: + image: guacamole/guacd:1.6.0 + container_name: << service_name >>-guacd + restart: unless-stopped + + << service_name >>: + image: guacamole/guacamole:1.6.0 + container_name: << service_name >> + ports: + - "<< port >>:8080" + environment: + - WEBAPP_CONTEXT=ROOT + - GUACD_HOSTNAME=<< service_name >>-guacd + - POSTGRESQL_HOSTNAME=<< service_name >>-db + - POSTGRESQL_DATABASE=guacamole + - POSTGRESQL_USERNAME=guacamole + - POSTGRESQL_PASSWORD=<< db_password >> + depends_on: + << service_name >>-db: + condition: service_healthy + << service_name >>-guacd: + condition: service_started + restart: unless-stopped + +volumes: + guacamole-init: diff --git a/apps/guacamole/template.json b/apps/guacamole/template.json new file mode 100644 index 0000000..e86f6c6 --- /dev/null +++ b/apps/guacamole/template.json @@ -0,0 +1,29 @@ +{ + "kind": "compose", + "metadata": { + "name": "Apache Guacamole", + "description": "Clientloze webgateway voor RDP, VNC en SSH. De PostgreSQL-database wordt bij de eerste start automatisch met het officiële Guacamole-schema ingericht.", + "tags": ["remote desktop", "rdp", "vnc", "ssh"], + "icon": {"provider": "selfhst", "id": "apache-guacamole"}, + "version": {"name": "1.6.0"}, + "categories": ["beheer", "netwerk"], + "architectures": ["amd64", "arm64"], + "architecture_note": "De officiële Guacamole-, guacd- en PostgreSQL-images ondersteunen AMD64 en ARM64." + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + {"name": "service_name", "type": "str", "title": "Servicenaam", "default": "guacamole", "required": true}, + {"name": "port", "type": "int", "title": "Webinterface", "default": 14005, "required": true}, + {"name": "appdata_dir", "type": "str", "title": "Appdata-map", "default": "/opt/serverup/appdata", "required": true} + ] + }, + { + "title": "Database", + "items": [ + {"name": "db_password", "type": "str", "title": "Databasewachtwoord", "default": "", "required": true, "secret": true, "generate": "auto"} + ] + } + ] +} diff --git a/apps/lldap/files/compose.yaml b/apps/lldap/files/compose.yaml new file mode 100644 index 0000000..01f3631 --- /dev/null +++ b/apps/lldap/files/compose.yaml @@ -0,0 +1,19 @@ +services: + << service_name >>: + image: lldap/lldap:stable + container_name: << service_name >> + ports: + - "<< port_web >>:17170" + - "<< port_ldap >>:3890" + environment: + - UID=<< puid >> + - GID=<< pgid >> + - TZ=<< timezone >> + - LLDAP_JWT_SECRET=<< jwt_secret >> + - LLDAP_KEY_SEED=<< key_seed >> + - LLDAP_LDAP_BASE_DN=<< base_dn >> + - LLDAP_LDAP_USER_PASS=<< ldap_user_pass >> + - LLDAP_HTTP_URL=<< http_url >> + volumes: + - << appdata_dir >>/<< service_name >>:/data + restart: unless-stopped diff --git a/apps/lldap/template.json b/apps/lldap/template.json new file mode 100644 index 0000000..d41bb94 --- /dev/null +++ b/apps/lldap/template.json @@ -0,0 +1,42 @@ +{ + "kind": "compose", + "metadata": { + "name": "LLDAP", + "description": "Lichtgewicht centrale gebruikersmap met LDAP en een eenvoudige webinterface. Bedoeld als accountbron voor onder meer Authelia, Authentik, Keycloak en Nextcloud. Stel LDAP niet rechtstreeks bloot aan internet.", + "tags": ["ldap", "accounts", "sso", "identiteit"], + "icon": {"provider": "selfhst", "id": "lldap"}, + "version": {"name": "stable"}, + "categories": ["beveiliging", "beheer"], + "architectures": ["amd64", "arm64"], + "architecture_note": "De officiële image ondersteunt AMD64, ARM64 en ARM/v7." + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + {"name": "service_name", "type": "str", "title": "Servicenaam", "default": "lldap", "required": true}, + {"name": "port_web", "type": "int", "title": "Webinterface", "default": 17170, "required": true}, + {"name": "port_ldap", "type": "int", "title": "LDAP-poort", "default": 3890, "required": true, "description": "Alleen op het vertrouwde LAN gebruiken; voor internet is LDAPS of een VPN nodig."}, + {"name": "appdata_dir", "type": "str", "title": "Appdata-map", "default": "/opt/serverup/appdata", "required": true}, + {"name": "timezone", "type": "str", "title": "Tijdzone", "default": "Europe/Amsterdam", "required": true}, + {"name": "puid", "type": "int", "title": "PUID", "default": 1000, "required": true}, + {"name": "pgid", "type": "int", "title": "PGID", "default": 1000, "required": true} + ] + }, + { + "title": "Directory", + "items": [ + {"name": "base_dn", "type": "str", "title": "Basis-DN", "default": "dc=example,dc=com", "required": true, "description": "Pas example.com aan je eigen interne domein aan."}, + {"name": "http_url", "type": "str", "title": "Publiek webadres", "default": "http://localhost:17170", "required": true} + ] + }, + { + "title": "Geheimen", + "items": [ + {"name": "ldap_user_pass", "type": "str", "title": "Adminwachtwoord", "default": "", "required": true, "secret": true, "generate": true, "credential": "password", "description": "Inloggen gaat met gebruiker admin en dit wachtwoord."}, + {"name": "jwt_secret", "type": "str", "title": "JWT-sleutel", "default": "", "required": true, "secret": true, "generate": "auto", "secret_bytes": 32}, + {"name": "key_seed", "type": "str", "title": "Versleutelingsseed", "default": "", "required": true, "secret": true, "generate": "auto", "secret_bytes": 32} + ] + } + ] +} diff --git a/apps/meshcentral/files/compose.yaml b/apps/meshcentral/files/compose.yaml new file mode 100644 index 0000000..e7fae47 --- /dev/null +++ b/apps/meshcentral/files/compose.yaml @@ -0,0 +1,13 @@ +services: + << service_name >>: + image: ghcr.io/ylianst/meshcentral:latest + container_name: << service_name >> + ports: + - "<< port >>:443" + volumes: + - << appdata_dir >>/<< service_name >>/data:/opt/meshcentral/meshcentral-data + - ./config.json:/opt/meshcentral/meshcentral-data/config.json:ro + - << appdata_dir >>/<< service_name >>/files:/opt/meshcentral/meshcentral-files + - << appdata_dir >>/<< service_name >>/backups:/opt/meshcentral/meshcentral-backups + - << appdata_dir >>/<< service_name >>/web:/opt/meshcentral/meshcentral-web + restart: unless-stopped diff --git a/apps/meshcentral/files/config.json b/apps/meshcentral/files/config.json new file mode 100644 index 0000000..79417ad --- /dev/null +++ b/apps/meshcentral/files/config.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://raw.githubusercontent.com/Ylianst/MeshCentral/master/meshcentral-config-schema.json", + "settings": { + "cert": "<< hostname >>", + "port": 443, + "WANonly": true + }, + "domains": { + "": { + "title": "MeshCentral", + "newAccounts": true + } + } +} diff --git a/apps/meshcentral/template.json b/apps/meshcentral/template.json new file mode 100644 index 0000000..593aa52 --- /dev/null +++ b/apps/meshcentral/template.json @@ -0,0 +1,24 @@ +{ + "kind": "compose", + "metadata": { + "name": "MeshCentral", + "description": "Beheer computers op afstand via de browser: bureaublad, terminal, bestanden en inventaris. Gebruikt ingebouwde opslag en een zelfondertekend certificaat. Het eerste account wordt beheerder; schakel daarna nieuwe registraties uit in config.json.", + "tags": ["remote beheer", "support", "inventaris", "desktop"], + "icon": {"provider": "selfhst", "id": "meshcentral"}, + "version": {"name": "latest"}, + "categories": ["beheer", "netwerk"], + "architectures": ["amd64", "arm64"], + "architecture_note": "De officiële image publiceert manifests voor AMD64 en ARM64." + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + {"name": "service_name", "type": "str", "title": "Servicenaam", "default": "meshcentral", "required": true}, + {"name": "port", "type": "int", "title": "HTTPS-webinterface", "default": 14001, "required": true}, + {"name": "hostname", "type": "str", "title": "Hostnaam of LAN-IP", "default": "", "required": true, "description": "De naam of het adres waarmee apparaten deze server bereiken, bijvoorbeeld mesh.example.nl of 192.168.1.10."}, + {"name": "appdata_dir", "type": "str", "title": "Appdata-map", "default": "/opt/serverup/appdata", "required": true} + ] + } + ] +} diff --git a/apps/nomad-server/files/compose.yaml b/apps/nomad-server/files/compose.yaml new file mode 100644 index 0000000..4eefa4e --- /dev/null +++ b/apps/nomad-server/files/compose.yaml @@ -0,0 +1,18 @@ +services: + << service_name >>: + image: hashicorp/nomad:latest + container_name: << service_name >> + command: + - agent + - -config=/nomad/config/server.hcl + ports: + - "<< port_http >>:<< port_http >>" + - "<< port_rpc >>:<< port_rpc >>" + - "<< port_gossip >>:<< port_gossip >>/tcp" + - "<< port_gossip >>:<< port_gossip >>/udp" + volumes: + - << appdata_dir >>/<< service_name >>:/nomad/data + - ./server.hcl:/nomad/config/server.hcl:ro + cap_add: + - IPC_LOCK + restart: unless-stopped diff --git a/apps/nomad-server/files/server.hcl b/apps/nomad-server/files/server.hcl new file mode 100644 index 0000000..1911d1a --- /dev/null +++ b/apps/nomad-server/files/server.hcl @@ -0,0 +1,28 @@ +data_dir = "/nomad/data" +bind_addr = "0.0.0.0" + +addresses { + http = "0.0.0.0" + rpc = "0.0.0.0" + serf = "0.0.0.0" +} + +ports { + http = << port_http >> + rpc = << port_rpc >> + serf = << port_gossip >> +} + +advertise { + http = "<< advertise_ip >>:<< port_http >>" + rpc = "<< advertise_ip >>:<< port_rpc >>" + serf = "<< advertise_ip >>:<< port_gossip >>" +} + +server { + enabled = true + bootstrap_expect = 1 +} + +client { + enabled = false diff --git a/apps/nomad-server/template.json b/apps/nomad-server/template.json new file mode 100644 index 0000000..dc6596c --- /dev/null +++ b/apps/nomad-server/template.json @@ -0,0 +1,31 @@ +{ + "kind": "compose", + "metadata": { + "name": "Nomad Server", + "description": "Nomad control-plane met webinterface voor een klein lab. Deze container draait bewust alleen als server; installeer Nomad-clients rechtstreeks op de hosts om workloads betrouwbaar te starten.", + "tags": ["orchestratie", "scheduler", "cluster", "hashicorp"], + "icon": {"provider": "selfhst", "id": "nomad"}, + "version": {"name": "latest"}, + "categories": ["beheer", "ontwikkeling"], + "architectures": ["amd64", "arm64"], + "architecture_note": "HashiCorp publiceert officiële Nomad-images voor AMD64 en ARM64. Een Nomad-client in Docker wordt niet officieel ondersteund." + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + {"name": "service_name", "type": "str", "title": "Servicenaam", "default": "nomad", "required": true}, + {"name": "appdata_dir", "type": "str", "title": "Appdata-map", "default": "/opt/serverup/appdata", "required": true}, + {"name": "advertise_ip", "type": "str", "title": "LAN-IP van deze server", "default": "", "required": true, "description": "Het bereikbare hostadres dat Nomad aan clients doorgeeft, bijvoorbeeld 192.168.1.10."} + ] + }, + { + "title": "Poorten", + "items": [ + {"name": "port_http", "type": "int", "title": "HTTP API en webinterface", "default": 4646, "required": true}, + {"name": "port_rpc", "type": "int", "title": "Server-RPC", "default": 4647, "required": true}, + {"name": "port_gossip", "type": "int", "title": "Serf-gossip", "default": 4648, "required": true} + ] + } + ] +} diff --git a/apps/openobserve/files/compose.yaml b/apps/openobserve/files/compose.yaml new file mode 100644 index 0000000..b92884a --- /dev/null +++ b/apps/openobserve/files/compose.yaml @@ -0,0 +1,15 @@ +services: + << service_name >>: + image: public.ecr.aws/zinclabs/openobserve:latest + container_name: << service_name >> + ports: + - "<< port >>:5080" + environment: + - ZO_DATA_DIR=/data + - ZO_LOCAL_MODE=true + - ZO_LOCAL_MODE_STORAGE=disk + - ZO_ROOT_USER_EMAIL=<< root_email >> + - ZO_ROOT_USER_PASSWORD=<< root_password >> + volumes: + - << appdata_dir >>/<< service_name >>:/data + restart: unless-stopped diff --git a/apps/openobserve/template.json b/apps/openobserve/template.json new file mode 100644 index 0000000..30c7b87 --- /dev/null +++ b/apps/openobserve/template.json @@ -0,0 +1,30 @@ +{ + "kind": "compose", + "metadata": { + "name": "OpenObserve", + "description": "Eén interface voor logs, metrics en traces met zoeken, dashboards en waarschuwingen. Deze template gebruikt de open-source single-nodeopzet met lokale schijfopslag.", + "tags": ["logs", "metrics", "traces", "opentelemetry"], + "icon": {"provider": "selfhst", "id": "openobserve"}, + "version": {"name": "latest"}, + "categories": ["monitoring", "analytics"], + "architectures": ["amd64", "arm64"], + "architecture_note": "De officiële OpenObserve OSS-image publiceert AMD64- en ARM64-manifests." + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + {"name": "service_name", "type": "str", "title": "Servicenaam", "default": "openobserve", "required": true}, + {"name": "port", "type": "int", "title": "Web- en API-poort", "default": 14006, "required": true}, + {"name": "appdata_dir", "type": "str", "title": "Appdata-map", "default": "/opt/serverup/appdata", "required": true} + ] + }, + { + "title": "Beheerder", + "items": [ + {"name": "root_email", "type": "str", "title": "Beheerder e-mail", "default": "admin@example.com", "required": true, "credential": "username"}, + {"name": "root_password", "type": "str", "title": "Beheerderwachtwoord", "default": "", "required": true, "secret": true, "generate": true, "credential": "password"} + ] + } + ] +} diff --git a/apps/pairdrop/files/compose.yaml b/apps/pairdrop/files/compose.yaml new file mode 100644 index 0000000..c69f44a --- /dev/null +++ b/apps/pairdrop/files/compose.yaml @@ -0,0 +1,14 @@ +services: + << service_name >>: + image: lscr.io/linuxserver/pairdrop:latest + container_name: << service_name >> + ports: + - "<< port >>:3000" + environment: + - PUID=<< puid >> + - PGID=<< pgid >> + - TZ=<< timezone >> + - RATE_LIMIT=true + - WS_FALLBACK=<< ws_fallback >> + - DEBUG_MODE=false + restart: unless-stopped diff --git a/apps/pairdrop/template.json b/apps/pairdrop/template.json new file mode 100644 index 0000000..bfb56d6 --- /dev/null +++ b/apps/pairdrop/template.json @@ -0,0 +1,26 @@ +{ + "kind": "compose", + "metadata": { + "name": "PairDrop", + "description": "Stuur bestanden en tekst rechtstreeks tussen browsers op ieder platform, zonder account. WebRTC versleutelt de overdracht; zet voor publiek gebruik HTTPS via een reverse proxy aan.", + "tags": ["bestanden", "delen", "airdrop", "webrtc"], + "icon": {"provider": "selfhst", "id": "pairdrop"}, + "version": {"name": "latest"}, + "categories": ["productiviteit", "opslag"], + "architectures": ["amd64", "arm64"], + "architecture_note": "De LinuxServer-image ondersteunt AMD64 en ARM64." + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + {"name": "service_name", "type": "str", "title": "Servicenaam", "default": "pairdrop", "required": true}, + {"name": "port", "type": "int", "title": "Webinterface", "default": 14007, "required": true}, + {"name": "timezone", "type": "str", "title": "Tijdzone", "default": "Europe/Amsterdam", "required": true}, + {"name": "puid", "type": "int", "title": "PUID", "default": 1000, "required": true}, + {"name": "pgid", "type": "int", "title": "PGID", "default": 1000, "required": true}, + {"name": "ws_fallback", "type": "bool", "title": "WebSocket-fallback", "default": false, "required": false, "description": "Routeert bestanden via de server als WebRTC niet werkt; de server kan die gegevens dan verwerken."} + ] + } + ] +} diff --git a/apps/rustdesk-server/files/compose.yaml b/apps/rustdesk-server/files/compose.yaml new file mode 100644 index 0000000..cb4d264 --- /dev/null +++ b/apps/rustdesk-server/files/compose.yaml @@ -0,0 +1,19 @@ +services: + << service_name >>-hbbs: + image: rustdesk/rustdesk-server:latest + container_name: << service_name >>-hbbs + command: hbbs + network_mode: host + volumes: + - << appdata_dir >>/<< service_name >>:/root + depends_on: + - << service_name >>-hbbr + restart: unless-stopped + << service_name >>-hbbr: + image: rustdesk/rustdesk-server:latest + container_name: << service_name >>-hbbr + command: hbbr + network_mode: host + volumes: + - << appdata_dir >>/<< service_name >>:/root + restart: unless-stopped diff --git a/apps/rustdesk-server/template.json b/apps/rustdesk-server/template.json new file mode 100644 index 0000000..9f67b1b --- /dev/null +++ b/apps/rustdesk-server/template.json @@ -0,0 +1,22 @@ +{ + "kind": "compose", + "metadata": { + "name": "RustDesk Server", + "description": "Eigen rendezvous- en relayserver voor RustDesk. Werkt ook op een Raspberry Pi. Gebruikt hostnetwerk en de TCP-poorten 21115-21119 plus UDP 21116; maak die alleen bereikbaar waar nodig.", + "tags": ["remote desktop", "beheer", "support", "rustdesk"], + "icon": {"provider": "selfhst", "id": "rustdesk"}, + "version": {"name": "latest"}, + "categories": ["beheer", "netwerk"], + "architectures": ["amd64", "arm64"], + "architecture_note": "De officiële image ondersteunt AMD64 en ARM64, waaronder een 64-bits Raspberry Pi." + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + {"name": "service_name", "type": "str", "title": "Servicenaam", "default": "rustdesk", "required": true}, + {"name": "appdata_dir", "type": "str", "title": "Appdata-map", "default": "/opt/serverup/appdata", "required": true} + ] + } + ] +} diff --git a/apps/victoriametrics/files/compose.yaml b/apps/victoriametrics/files/compose.yaml new file mode 100644 index 0000000..4656601 --- /dev/null +++ b/apps/victoriametrics/files/compose.yaml @@ -0,0 +1,12 @@ +services: + << service_name >>: + image: victoriametrics/victoria-metrics:latest + container_name: << service_name >> + command: + - -storageDataPath=/storage + - -retentionPeriod=<< retention_months >> + ports: + - "<< port >>:8428" + volumes: + - << appdata_dir >>/<< service_name >>:/storage + restart: unless-stopped diff --git a/apps/victoriametrics/template.json b/apps/victoriametrics/template.json new file mode 100644 index 0000000..33339ae --- /dev/null +++ b/apps/victoriametrics/template.json @@ -0,0 +1,24 @@ +{ + "kind": "compose", + "metadata": { + "name": "VictoriaMetrics", + "description": "Snelle en zuinige tijdreeksdatabase voor Prometheus-metrics. Kan Prometheus remote_write ontvangen en Grafana als databron bedienen; geschikt voor een enkele server of Raspberry Pi.", + "tags": ["metrics", "prometheus", "tijdreeksen", "grafana"], + "icon": {"provider": "selfhst", "id": "victoriametrics"}, + "version": {"name": "latest"}, + "categories": ["monitoring", "analytics"], + "architectures": ["amd64", "arm64"], + "architecture_note": "VictoriaMetrics publiceert officiële AMD64- en ARM64-images en documenteert Raspberry Pi-ondersteuning." + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + {"name": "service_name", "type": "str", "title": "Servicenaam", "default": "victoriametrics", "required": true}, + {"name": "port", "type": "int", "title": "Web- en API-poort", "default": 8428, "required": true}, + {"name": "appdata_dir", "type": "str", "title": "Appdata-map", "default": "/opt/serverup/appdata", "required": true}, + {"name": "retention_months", "type": "int", "title": "Bewaartermijn in maanden", "default": 12, "required": true} + ] + } + ] +} diff --git a/apps/windmill/files/compose.yaml b/apps/windmill/files/compose.yaml new file mode 100644 index 0000000..fe261c6 --- /dev/null +++ b/apps/windmill/files/compose.yaml @@ -0,0 +1,46 @@ +services: + << service_name >>: + image: ghcr.io/windmill-labs/windmill:main + container_name: << service_name >> + ports: + - "<< port >>:8000" + environment: + - MODE=server + - DATABASE_URL=postgres://postgres:<< db_password >>@<< service_name >>-db/windmill?sslmode=disable + volumes: + - << appdata_dir >>/<< service_name >>/logs:/tmp/windmill/logs + depends_on: + << service_name >>-db: + condition: service_healthy + restart: unless-stopped + + << service_name >>-worker: + image: ghcr.io/windmill-labs/windmill:main + container_name: << service_name >>-worker + environment: + - MODE=worker + - WORKER_GROUP=default + - DATABASE_URL=postgres://postgres:<< db_password >>@<< service_name >>-db/windmill?sslmode=disable + volumes: + - << appdata_dir >>/<< service_name >>/worker-cache:/tmp/windmill/cache + - << appdata_dir >>/<< service_name >>/logs:/tmp/windmill/logs + depends_on: + << service_name >>-db: + condition: service_healthy + restart: unless-stopped + + << service_name >>-db: + image: postgres:16-alpine + container_name: << service_name >>-db + environment: + - POSTGRES_DB=windmill + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=<< db_password >> + volumes: + - << appdata_dir >>/<< service_name >>/db:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d windmill"] + interval: 10s + timeout: 5s + retries: 10 + restart: unless-stopped diff --git a/apps/windmill/template.json b/apps/windmill/template.json new file mode 100644 index 0000000..b673826 --- /dev/null +++ b/apps/windmill/template.json @@ -0,0 +1,29 @@ +{ + "kind": "compose", + "metadata": { + "name": "Windmill", + "description": "Open-source platform voor scripts, interne apps en workflows. De template start een server, een worker en PostgreSQL zonder toegang tot de Docker-socket.", + "tags": ["automatisering", "workflows", "scripts", "low-code"], + "icon": {"provider": "selfhst", "id": "windmill"}, + "version": {"name": "latest"}, + "categories": ["ontwikkeling", "productiviteit"], + "architectures": ["amd64", "arm64"], + "architecture_note": "De officiële Windmill- en PostgreSQL-images ondersteunen AMD64 en ARM64." + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + {"name": "service_name", "type": "str", "title": "Servicenaam", "default": "windmill", "required": true}, + {"name": "port", "type": "int", "title": "Webinterface", "default": 14004, "required": true}, + {"name": "appdata_dir", "type": "str", "title": "Appdata-map", "default": "/opt/serverup/appdata", "required": true} + ] + }, + { + "title": "Database", + "items": [ + {"name": "db_password", "type": "str", "title": "Databasewachtwoord", "default": "", "required": true, "secret": true, "generate": "auto"} + ] + } + ] +} diff --git a/tests/test_catalogus_nuttige_apps.py b/tests/test_catalogus_nuttige_apps.py new file mode 100644 index 0000000..9d77f85 --- /dev/null +++ b/tests/test_catalogus_nuttige_apps.py @@ -0,0 +1,91 @@ +"""Regressietests voor de infrastructuur- en beheerapps uit v0.8.17.""" +import json +from pathlib import Path + +import pytest + + +WORTEL = Path(__file__).resolve().parent.parent +APPS = WORTEL / "apps" + +NIEUWE_APPS = { + "activepieces", + "consul", + "guacamole", + "lldap", + "meshcentral", + "nomad-server", + "openobserve", + "pairdrop", + "rustdesk-server", + "victoriametrics", + "windmill", +} + + +def _template(app: str) -> dict: + return json.loads((APPS / app / "template.json").read_text(encoding="utf-8")) + + +def test_alle_geselecteerde_apps_staan_in_de_catalogus(): + aanwezig = {pad.name for pad in APPS.iterdir() if pad.is_dir()} + assert NIEUWE_APPS <= aanwezig + + +@pytest.mark.parametrize("app", sorted(NIEUWE_APPS)) +def test_nieuwe_apps_ondersteunen_pi_en_x86(app): + architecturen = set(_template(app)["metadata"]["architectures"]) + assert {"amd64", "arm64"} <= architecturen + + +def test_nomad_container_is_alleen_control_plane(): + compose = (APPS / "nomad-server" / "files" / "compose.yaml").read_text() + config = (APPS / "nomad-server" / "files" / "server.hcl").read_text() + assert "-config=/nomad/config/server.hcl" in compose + assert "enabled = false" in config + assert "enabled = true" in config + regels = {" ".join(regel.split()) for regel in config.splitlines()} + for adres in ("http", "rpc", "serf"): + assert any(regel.startswith(f'{adres} = "<< advertise_ip >>:') for regel in regels) + assert "/var/run/docker.sock" not in compose + assert "privileged:" not in compose + + +def test_clusterapps_adverteren_het_bereikbare_hostadres(): + for app in ("nomad-server", "consul"): + velden = { + item["name"]: item + for groep in _template(app)["variables"] + for item in groep["items"] + } + assert velden["advertise_ip"]["required"] is True + assert velden["advertise_ip"]["default"] == "" + if app == "nomad-server": + bron = (APPS / app / "files" / "server.hcl").read_text() + else: + bron = (APPS / app / "files" / "compose.yaml").read_text() + assert "<< advertise_ip >>" in bron + + +def test_interne_databasewachtwoorden_worden_automatisch_gemaakt(): + for app in ("guacamole", "windmill"): + velden = [item for groep in _template(app)["variables"] for item in groep["items"]] + assert next(item for item in velden if item["name"] == "db_password")["generate"] == "auto" + + +def test_guacamole_initialiseert_database_voor_de_webapp_start(): + compose = (APPS / "guacamole" / "files" / "compose.yaml").read_text() + assert "initdb.sh --postgresql" in compose + assert "condition: service_completed_successfully" in compose + assert "condition: service_healthy" in compose + + +def test_meshcentral_krijgt_een_bereikbare_https_configuratie(): + config = (APPS / "meshcentral" / "files" / "config.json").read_text() + config = json.loads(config.replace("<< hostname >>", "mesh.example.nl")) + assert config["settings"]["cert"] == "mesh.example.nl" + assert config["settings"]["port"] == 443 + assert config["domains"][""]["newAccounts"] is True + + compose = (APPS / "meshcentral" / "files" / "compose.yaml").read_text() + assert '"<< port >>:443"' in compose