From cab94f135b6bacb6d5cfc0a1c849a0ea239bb1d1 Mon Sep 17 00:00:00 2001 From: roof Date: Sat, 9 May 2026 09:53:05 -0400 Subject: [PATCH] =?UTF-8?q?Phase=204:=20service=20registry=20=E2=80=94=20i?= =?UTF-8?q?ndex.json=20+=204=20service=20manifests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/validate.yml | 25 +++++++++++++++++++++++++ index.json | 10 ++++++++++ services/calendar/manifest.json | 16 ++++++++++++++++ services/email/manifest.json | 20 ++++++++++++++++++++ services/files/manifest.json | 16 ++++++++++++++++ services/webmail/manifest.json | 17 +++++++++++++++++ 6 files changed, 104 insertions(+) create mode 100644 .gitea/workflows/validate.yml create mode 100644 index.json create mode 100644 services/calendar/manifest.json create mode 100644 services/email/manifest.json create mode 100644 services/files/manifest.json create mode 100644 services/webmail/manifest.json diff --git a/.gitea/workflows/validate.yml b/.gitea/workflows/validate.yml new file mode 100644 index 0000000..d0dfc61 --- /dev/null +++ b/.gitea/workflows/validate.yml @@ -0,0 +1,25 @@ +name: Validate Manifests +on: + push: + branches: ['**'] +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Validate index.json + run: python3 -c "import json; json.load(open('index.json'))" + - name: Validate all manifests + run: | + for f in services/*/manifest.json; do + echo "Validating $f" + python3 -c " +import json, sys +m = json.load(open('$f')) +required = ['id','name','version','author','image','container_name'] +missing = [k for k in required if k not in m] +if missing: + print(f'MISSING: {missing}'); sys.exit(1) +print('OK') +" + done diff --git a/index.json b/index.json new file mode 100644 index 0000000..b7a2f28 --- /dev/null +++ b/index.json @@ -0,0 +1,10 @@ +{ + "version": "1", + "updated": "2026-05-09", + "services": [ + {"id": "calendar", "name": "Calendar & Contacts", "version": "1.0.0", "description": "CalDAV/CardDAV server (Radicale)", "author": "roof"}, + {"id": "files", "name": "File Storage", "version": "1.0.0", "description": "WebDAV file storage", "author": "roof"}, + {"id": "email", "name": "Email Server", "version": "1.0.0", "description": "Full email server (Postfix + Dovecot)", "author": "roof"}, + {"id": "webmail", "name": "Webmail", "version": "1.0.0", "description": "Rainloop webmail UI", "author": "roof"} + ] +} diff --git a/services/calendar/manifest.json b/services/calendar/manifest.json new file mode 100644 index 0000000..45a8131 --- /dev/null +++ b/services/calendar/manifest.json @@ -0,0 +1,16 @@ +{ + "id": "calendar", + "name": "Calendar & Contacts", + "description": "CalDAV/CardDAV server based on Radicale. Provides calendar and contacts sync for all your devices.", + "version": "1.0.0", + "author": "roof", + "image": "tomsquest/docker-radicale:latest", + "volumes": [{"name": "radicale-data", "mount": "/data"}], + "env": [], + "caddy_route": {"subdomain": "calendar", "upstream": "cell-radicale:5232"}, + "dns_entry": {"subdomain": "calendar"}, + "iptables_rules": [{"type": "ACCEPT", "dest_ip": "${SERVICE_IP}", "dest_port": 5232, "proto": "tcp"}], + "container_name": "cell-radicale", + "network_ip_var": "RADICALE_IP", + "ports": [5232] +} diff --git a/services/email/manifest.json b/services/email/manifest.json new file mode 100644 index 0000000..6d9e621 --- /dev/null +++ b/services/email/manifest.json @@ -0,0 +1,20 @@ +{ + "id": "email", + "name": "Email Server", + "description": "Full email server: Postfix (SMTP) + Dovecot (IMAP). Requires a domain and proper DNS MX records.", + "version": "1.0.0", + "author": "roof", + "image": "mailserver/docker-mailserver:latest", + "volumes": [{"name": "mail-data", "mount": "/var/mail"}, {"name": "mail-state", "mount": "/var/mail-state"}], + "env": [{"key": "DOMAINNAME", "value": "${CELL_DOMAIN}"}, {"key": "HOSTNAME", "value": "mail"}], + "caddy_route": null, + "dns_entry": {"subdomain": "mail"}, + "iptables_rules": [ + {"type": "ACCEPT", "dest_ip": "${SERVICE_IP}", "dest_port": 25, "proto": "tcp"}, + {"type": "ACCEPT", "dest_ip": "${SERVICE_IP}", "dest_port": 587, "proto": "tcp"}, + {"type": "ACCEPT", "dest_ip": "${SERVICE_IP}", "dest_port": 993, "proto": "tcp"} + ], + "container_name": "cell-mail", + "network_ip_var": "MAIL_IP", + "ports": [25, 587, 993] +} diff --git a/services/files/manifest.json b/services/files/manifest.json new file mode 100644 index 0000000..95ef0f0 --- /dev/null +++ b/services/files/manifest.json @@ -0,0 +1,16 @@ +{ + "id": "files", + "name": "File Storage", + "description": "WebDAV file storage. Mount as a network drive or use the web interface.", + "version": "1.0.0", + "author": "roof", + "image": "bytemark/webdav:latest", + "volumes": [{"name": "webdav-data", "mount": "/var/lib/dav"}], + "env": [{"key": "AUTH_TYPE", "value": "Basic"}, {"key": "USERNAME", "value": "admin"}, {"key": "PASSWORD", "value": "${WEBDAV_PASSWORD}"}], + "caddy_route": {"subdomain": "files", "upstream": "cell-webdav:80"}, + "dns_entry": {"subdomain": "files"}, + "iptables_rules": [{"type": "ACCEPT", "dest_ip": "${SERVICE_IP}", "dest_port": 80, "proto": "tcp"}], + "container_name": "cell-webdav", + "network_ip_var": "WEBDAV_IP", + "ports": [80] +} diff --git a/services/webmail/manifest.json b/services/webmail/manifest.json new file mode 100644 index 0000000..5c84abb --- /dev/null +++ b/services/webmail/manifest.json @@ -0,0 +1,17 @@ +{ + "id": "webmail", + "name": "Webmail", + "description": "Rainloop webmail UI. Requires email service to be installed first.", + "version": "1.0.0", + "author": "roof", + "image": "hardware/rainloop", + "volumes": [{"name": "rainloop-data", "mount": "/rainloop/data"}], + "env": [], + "caddy_route": {"subdomain": "webmail", "upstream": "cell-rainloop:8888"}, + "dns_entry": {"subdomain": "webmail"}, + "iptables_rules": [{"type": "ACCEPT", "dest_ip": "${SERVICE_IP}", "dest_port": 8888, "proto": "tcp"}], + "container_name": "cell-rainloop", + "network_ip_var": "RAINLOOP_IP", + "ports": [8888], + "requires": ["email"] +}