From 6291b2cb2de00b6a697a14e5b126cff3c9500762 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 31 Mar 2026 18:19:14 -0700 Subject: [PATCH] Migrate services to rspace.online domains and fix Traefik routing - Add separate routers for rspace.online aliases (collab, ipfs, ipfs-api) instead of || syntax to avoid priority conflicts - Fix healthcheck quoting for Node.js v23 TypeScript-style parsing - Update IPFS client defaults from jeffemmett.com to rspace.online - Add rspace.online CORS origins to collab-server Co-Authored-By: Claude Opus 4.6 --- poc/collab-server/docker-compose.yml | 32 ++++++++++++++++++++++------ poc/ipfs-storage/src/ipfs-client.ts | 14 ++++++------ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/poc/collab-server/docker-compose.yml b/poc/collab-server/docker-compose.yml index f305051..78df9c1 100644 --- a/poc/collab-server/docker-compose.yml +++ b/poc/collab-server/docker-compose.yml @@ -3,7 +3,9 @@ # # Deploy: scp to Netcup /opt/apps/collab-server/, docker compose up -d # Requires: Traefik proxy network -# DNS: collab.jeffemmett.com, ipfs.jeffemmett.com, ipfs-api.jeffemmett.com +# DNS: collab.jeffemmett.com / collab.rspace.online +# ipfs.jeffemmett.com / ipfs.rspace.online +# ipfs-api.jeffemmett.com / ipfs-api.rspace.online services: collab-server: @@ -17,7 +19,7 @@ services: NODE_ENV: production MONGODB_URI: mongodb://collab-mongo:27017/collaboration REDIS_ENABLED: "false" - CORS_ORIGINS: "https://rnotes.jeffemmett.com,https://rspace.jeffemmett.com,http://localhost:3000,http://localhost:5173" + CORS_ORIGINS: "https://rnotes.jeffemmett.com,https://rspace.jeffemmett.com,https://collab.rspace.online,https://ipfs.rspace.online,https://collab.jeffemmett.com,http://localhost:3000,http://localhost:5173" SERVER_DID: "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK" RATE_LIMIT_WINDOW_MS: 900000 RATE_LIMIT_MAX: 100 @@ -26,14 +28,21 @@ services: - collab-internal labels: - "traefik.enable=true" + # Primary domain - "traefik.http.routers.collab.rule=Host(`collab.jeffemmett.com`)" - "traefik.http.routers.collab.entrypoints=web" + - "traefik.http.routers.collab.service=collab" + # rspace.online alias + - "traefik.http.routers.collab-rspace.rule=Host(`collab.rspace.online`)" + - "traefik.http.routers.collab-rspace.entrypoints=web" + - "traefik.http.routers.collab-rspace.service=collab" + # Shared service - "traefik.http.services.collab.loadbalancer.server.port=5001" depends_on: collab-mongo: condition: service_started healthcheck: - test: ["CMD", "node", "-e", "fetch('http://localhost:5001/health').then(r => process.exit(r.ok ? 0 : 1))"] + test: ["CMD-SHELL", "node -e \"fetch('http://localhost:5001/health').then(r=>process.exit(r.ok?0:1))\""] interval: 30s timeout: 5s retries: 3 @@ -60,19 +69,30 @@ services: - collab-internal labels: - "traefik.enable=true" - # IPFS Gateway (public, read-only) + # IPFS Gateway — primary domain - "traefik.http.routers.ipfs-gw.rule=Host(`ipfs.jeffemmett.com`)" - "traefik.http.routers.ipfs-gw.entrypoints=web" - "traefik.http.routers.ipfs-gw.service=ipfs-gw" + # IPFS Gateway — rspace.online alias + - "traefik.http.routers.ipfs-gw-rspace.rule=Host(`ipfs.rspace.online`)" + - "traefik.http.routers.ipfs-gw-rspace.entrypoints=web" + - "traefik.http.routers.ipfs-gw-rspace.service=ipfs-gw" + # Shared gateway service - "traefik.http.services.ipfs-gw.loadbalancer.server.port=8080" - # IPFS API (private, Headscale-only access via IP allowlist) + # IPFS API — primary domain - "traefik.http.routers.ipfs-api.rule=Host(`ipfs-api.jeffemmett.com`)" - "traefik.http.routers.ipfs-api.entrypoints=web" - "traefik.http.routers.ipfs-api.service=ipfs-api" + - "traefik.http.routers.ipfs-api.middlewares=ipfs-api-ipallow" + # IPFS API — rspace.online alias + - "traefik.http.routers.ipfs-api-rspace.rule=Host(`ipfs-api.rspace.online`)" + - "traefik.http.routers.ipfs-api-rspace.entrypoints=web" + - "traefik.http.routers.ipfs-api-rspace.service=ipfs-api" + - "traefik.http.routers.ipfs-api-rspace.middlewares=ipfs-api-ipallow" + # Shared API service - "traefik.http.services.ipfs-api.loadbalancer.server.port=5001" # Restrict API to Headscale mesh + Cloudflare tunnel IPs - "traefik.http.middlewares.ipfs-api-ipallow.ipallowlist.sourcerange=100.64.0.0/10,127.0.0.1/32,172.16.0.0/12" - - "traefik.http.routers.ipfs-api.middlewares=ipfs-api-ipallow" healthcheck: test: ["CMD", "ipfs", "id"] interval: 30s diff --git a/poc/ipfs-storage/src/ipfs-client.ts b/poc/ipfs-storage/src/ipfs-client.ts index 01b3d1b..6dbe648 100644 --- a/poc/ipfs-storage/src/ipfs-client.ts +++ b/poc/ipfs-storage/src/ipfs-client.ts @@ -48,7 +48,7 @@ export class PinataBackend implements IPFSBackend { constructor(jwt: string, gateway?: string) { this.jwt = jwt - this.gateway = gateway ?? 'https://ipfs.jeffemmett.com/ipfs' + this.gateway = gateway ?? 'https://ipfs.rspace.online/ipfs' } async upload(data: Uint8Array, filename: string): Promise { @@ -101,8 +101,8 @@ export class KuboBackend implements IPFSBackend { /** Create KuboBackend from environment variables */ static fromEnv(): KuboBackend { - const apiUrl = process.env.IPFS_API_URL || 'https://ipfs-api.jeffemmett.com' - const gatewayUrl = process.env.IPFS_GATEWAY_URL || 'https://ipfs.jeffemmett.com' + const apiUrl = process.env.IPFS_API_URL || 'https://ipfs-api.rspace.online' + const gatewayUrl = process.env.IPFS_GATEWAY_URL || 'https://ipfs.rspace.online' const authToken = process.env.IPFS_AUTH_TOKEN return new KuboBackend(apiUrl, gatewayUrl, authToken) } @@ -213,7 +213,7 @@ export class EncryptedIPFSClient { * Generate an IPFS gateway URL (returns encrypted content — client must decrypt) */ gatewayUrl(cid: string, gateway?: string): string { - return `${gateway ?? 'https://ipfs.jeffemmett.com/ipfs'}/${cid}` + return `${gateway ?? 'https://ipfs.rspace.online/ipfs'}/${cid}` } } @@ -242,7 +242,7 @@ export function createImageAttrs( alt?: string ): IPFSImageAttrs { return { - src: `${gateway ?? 'https://ipfs.jeffemmett.com/ipfs'}/${metadata.cid}`, + src: `${gateway ?? 'https://ipfs.rspace.online/ipfs'}/${metadata.cid}`, cid: metadata.cid, encKey: metadata.encryptionKey, alt: alt ?? metadata.filename, @@ -253,8 +253,8 @@ export function createImageAttrs( /** Create an EncryptedIPFSClient using the self-hosted kubo node */ export function createSelfHostedClient( - apiUrl = 'https://ipfs-api.jeffemmett.com', - gatewayUrl = 'https://ipfs.jeffemmett.com' + apiUrl = 'https://ipfs-api.rspace.online', + gatewayUrl = 'https://ipfs.rspace.online' ): EncryptedIPFSClient { return new EncryptedIPFSClient(new KuboBackend(apiUrl, gatewayUrl)) }