Phase 4: service registry — index.json + 4 service manifests

This commit is contained in:
2026-05-09 09:53:05 -04:00
commit cab94f135b
6 changed files with 104 additions and 0 deletions
+25
View File
@@ -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
+10
View File
@@ -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"}
]
}
+16
View File
@@ -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]
}
+20
View File
@@ -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]
}
+16
View File
@@ -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]
}
+17
View File
@@ -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"]
}