commit cab94f135b6bacb6d5cfc0a1c849a0ea239bb1d1 Author: roof Date: Sat May 9 09:53:05 2026 -0400 Phase 4: service registry — index.json + 4 service manifests 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"] +}