Docker hardening + first-run fixes
This commit is contained in:
+9
-4
@@ -30,8 +30,8 @@ WORKDIR /app
|
|||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
# Install tools needed for port detection (netcat)
|
# Install tools needed for port detection and healthchecks
|
||||||
RUN apk add --no-cache netcat-openbsd
|
RUN apk add --no-cache netcat-openbsd wget
|
||||||
|
|
||||||
# Create non-root user
|
# Create non-root user
|
||||||
RUN addgroup --system --gid 1001 nodejs
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
@@ -41,6 +41,11 @@ RUN adduser --system --uid 1001 nextjs
|
|||||||
COPY --from=builder /app/public ./public
|
COPY --from=builder /app/public ./public
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/drizzle ./drizzle
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/drizzle.config.ts ./
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/src/db/schema.ts ./src/db/schema.ts
|
||||||
|
|
||||||
# Copy and set up entrypoint script
|
# Copy and set up entrypoint script
|
||||||
COPY --chown=nextjs:nodejs scripts/docker-entrypoint.sh /usr/local/bin/
|
COPY --chown=nextjs:nodejs scripts/docker-entrypoint.sh /usr/local/bin/
|
||||||
@@ -59,9 +64,9 @@ EXPOSE 3000-3020
|
|||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
ENV HOSTNAME="0.0.0.0"
|
ENV HOSTNAME="0.0.0.0"
|
||||||
|
|
||||||
# Health check (will use the actual PORT at runtime)
|
# Health check (reads dynamic port file when PORT=auto)
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||||
CMD wget -q --spider http://localhost:${PORT:-3000}/api/health || exit 1
|
CMD sh -c 'PORT_FILE=/var/run/synapsis/port; PORT=3000; if [ -f "$PORT_FILE" ]; then PORT=$(cat "$PORT_FILE"); fi; case "$PORT" in ""|*[!0-9]*) PORT=3000;; esac; wget -q --spider "http://127.0.0.1:${PORT}/api/health" || exit 1'
|
||||||
|
|
||||||
# Use entrypoint script for port detection
|
# Use entrypoint script for port detection
|
||||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**Your node. Your network. Your identity.**
|
**Your node. Your network. Your identity.**
|
||||||
|
|
||||||
Synapsis is an open-source, federated social network built for the decentralized web. Run your own node, own your identity with DIDs, and communicate across the Swarm network with end-to-end encryption. No corporations. No lock-in. Just you and the network.
|
Synapsis is an open-source, federated social network built for the decentralized web. Run your own node, own your identity with DIDs, and communicate across the Swarm network with plain text server-stored messaging. No corporations. No lock-in. Just you and the network.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -15,19 +15,22 @@ Run your personal Synapsis node with a single command:
|
|||||||
mkdir -p /opt/synapsis && cd /opt/synapsis
|
mkdir -p /opt/synapsis && cd /opt/synapsis
|
||||||
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker-compose.yml
|
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker-compose.yml
|
||||||
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/Caddyfile
|
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/Caddyfile
|
||||||
|
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/caddy-entrypoint.sh
|
||||||
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/.env.example
|
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/.env.example
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
|
|
||||||
# 2. Edit .env with your domain and secrets (takes 2 minutes)
|
# 2. Edit .env with your domain and secrets (takes 2 minutes)
|
||||||
nano .env
|
nano .env
|
||||||
|
# Use a host-only domain (no scheme), e.g. synapsis.example.com
|
||||||
|
|
||||||
# 3. Start your node
|
# 3. Start your node
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
Done! Your node is live at `https://your-domain.com` with automatic SSL. No build step. No dependencies. No fuss.
|
Done! Your node is live at `https://your-domain.com` with automatic SSL. No build step. No dependencies. No fuss.
|
||||||
|
Database migrations run automatically on startup and during updates.
|
||||||
|
|
||||||
**Updating:**
|
**Updating (migrations run automatically):**
|
||||||
```bash
|
```bash
|
||||||
docker compose pull && docker compose up -d
|
docker compose pull && docker compose up -d
|
||||||
```
|
```
|
||||||
@@ -39,7 +42,7 @@ For detailed Docker setup, see [docker/README.md](docker/README.md).
|
|||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
- **🌐 Swarm Network** — Native peer-to-peer network with automatic node discovery and gossip protocol
|
- **🌐 Swarm Network** — Native peer-to-peer network with automatic node discovery and gossip protocol
|
||||||
- **💬 Swarm Chat** — End-to-end encrypted messaging across the entire network
|
- **💬 Swarm Chat** — Direct messaging across the entire network
|
||||||
- **🔐 Decentralized Identity (DIDs)** — Cryptographic identity you truly own, portable between nodes
|
- **🔐 Decentralized Identity (DIDs)** — Cryptographic identity you truly own, portable between nodes
|
||||||
- **🤖 AI Bots** — Create AI-powered bot accounts with custom personalities
|
- **🤖 AI Bots** — Create AI-powered bot accounts with custom personalities
|
||||||
- **🎨 Modern UI** — Clean, responsive interface inspired by Vercel's design system
|
- **🎨 Modern UI** — Clean, responsive interface inspired by Vercel's design system
|
||||||
@@ -83,7 +86,7 @@ Synapsis operates on the **Swarm** — a native peer-to-peer network designed sp
|
|||||||
|
|
||||||
- **Gossip Protocol** — Nodes discover each other automatically and exchange information
|
- **Gossip Protocol** — Nodes discover each other automatically and exchange information
|
||||||
- **Swarm Timeline** — Aggregated feed of posts from across all Synapsis nodes
|
- **Swarm Timeline** — Aggregated feed of posts from across all Synapsis nodes
|
||||||
- **Swarm Chat** — End-to-end encrypted direct messaging between users on any Synapsis node
|
- **Swarm Chat** — Direct messaging between users on any Synapsis node
|
||||||
- **Handle Registry** — Distributed directory of user handles across the swarm
|
- **Handle Registry** — Distributed directory of user handles across the swarm
|
||||||
- **Instant Interactions** — Likes, reposts, follows, and mentions delivered in real-time
|
- **Instant Interactions** — Likes, reposts, follows, and mentions delivered in real-time
|
||||||
|
|
||||||
@@ -94,7 +97,7 @@ Synapsis operates on the **Swarm** — a native peer-to-peer network designed sp
|
|||||||
| **Identity** | Server-bound (`@user@server`) | DID-based (cryptographic, portable) |
|
| **Identity** | Server-bound (`@user@server`) | DID-based (cryptographic, portable) |
|
||||||
| **Account Migration** | Limited (followers don't auto-migrate) | **Full** — DID-based migration with auto-follow |
|
| **Account Migration** | Limited (followers don't auto-migrate) | **Full** — DID-based migration with auto-follow |
|
||||||
| **Cryptographic Signing** | HTTP Signatures only | Full post signing with user keys |
|
| **Cryptographic Signing** | HTTP Signatures only | Full post signing with user keys |
|
||||||
| **Direct Messages** | Posts with limited visibility | True E2E encrypted chat |
|
| **Direct Messages** | Posts with limited visibility | Direct messaging between users |
|
||||||
| **Network Discovery** | Manual server discovery | Automatic gossip protocol |
|
| **Network Discovery** | Manual server discovery | Automatic gossip protocol |
|
||||||
| **AI Bots** | Not supported | Native bot framework with LLM integration |
|
| **AI Bots** | Not supported | Native bot framework with LLM integration |
|
||||||
| **Interactions** | Queue-based, delayed | Instant delivery via Swarm |
|
| **Interactions** | Queue-based, delayed | Instant delivery via Swarm |
|
||||||
@@ -108,7 +111,7 @@ Want to hack on Synapsis? Here's how to run it locally:
|
|||||||
### Prerequisites
|
### Prerequisites
|
||||||
- Node.js 20+
|
- Node.js 20+
|
||||||
- PostgreSQL 15+
|
- PostgreSQL 15+
|
||||||
- S3-compatible storage (or MinIO for local dev)
|
- S3-compatible storage (AWS S3, Cloudflare R2, Backblaze B2, Wasabi, or Contabo)
|
||||||
|
|
||||||
### Local Setup
|
### Local Setup
|
||||||
|
|
||||||
|
|||||||
+6
-3
@@ -6,7 +6,7 @@
|
|||||||
# mkdir -p /opt/synapsis && cd /opt/synapsis
|
# mkdir -p /opt/synapsis && cd /opt/synapsis
|
||||||
# curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker-compose.yml
|
# curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker-compose.yml
|
||||||
# curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/Caddyfile
|
# curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/Caddyfile
|
||||||
# curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/scripts/caddy-entrypoint.sh
|
# curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/caddy-entrypoint.sh
|
||||||
# curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/.env.example
|
# curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/.env.example
|
||||||
# cp .env.example .env
|
# cp .env.example .env
|
||||||
# 2. Configure (edit with your domain and secrets): nano .env
|
# 2. Configure (edit with your domain and secrets): nano .env
|
||||||
@@ -61,7 +61,10 @@ services:
|
|||||||
AUTH_SECRET: ${AUTH_SECRET}
|
AUTH_SECRET: ${AUTH_SECRET}
|
||||||
|
|
||||||
# Domain configuration
|
# Domain configuration
|
||||||
NEXT_PUBLIC_NODE_DOMAIN: ${DOMAIN:-localhost}
|
DOMAIN: ${DOMAIN:-localhost}
|
||||||
|
NEXT_PUBLIC_NODE_DOMAIN: ${NEXT_PUBLIC_NODE_DOMAIN:-}
|
||||||
|
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-}
|
||||||
|
ALLOW_LOCALHOST: ${ALLOW_LOCALHOST:-}
|
||||||
|
|
||||||
# Admin emails
|
# Admin emails
|
||||||
ADMIN_EMAILS: ${ADMIN_EMAILS}
|
ADMIN_EMAILS: ${ADMIN_EMAILS}
|
||||||
@@ -89,7 +92,7 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
# Increased start period to allow migrations to run
|
# Increased start period to allow migrations to run
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/api/health"]
|
test: ["CMD-SHELL", "PORT=$(cat /var/run/synapsis/port 2>/dev/null || echo 3000); case \"$PORT\" in ''|*[!0-9]*) PORT=3000;; esac; wget -q --spider http://127.0.0.1:${PORT}/api/health || exit 1"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|||||||
+10
-1
@@ -7,9 +7,15 @@
|
|||||||
# REQUIRED: Core Settings
|
# REQUIRED: Core Settings
|
||||||
# ===========================================
|
# ===========================================
|
||||||
|
|
||||||
# Your domain name (e.g., synapsis.example.com)
|
# Your domain name (no scheme, no path). Example: synapsis.example.com
|
||||||
DOMAIN=your-domain.com
|
DOMAIN=your-domain.com
|
||||||
|
|
||||||
|
# Optional: Override node domain (host only, defaults to DOMAIN)
|
||||||
|
# NEXT_PUBLIC_NODE_DOMAIN=your-domain.com
|
||||||
|
|
||||||
|
# Optional: Public app URL for background jobs (auto-derived from node domain if unset)
|
||||||
|
# NEXT_PUBLIC_APP_URL=https://your-domain.com
|
||||||
|
|
||||||
# Admin email addresses (comma-separated for multiple)
|
# Admin email addresses (comma-separated for multiple)
|
||||||
ADMIN_EMAILS=admin@your-domain.com
|
ADMIN_EMAILS=admin@your-domain.com
|
||||||
|
|
||||||
@@ -32,5 +38,8 @@ DB_NAME=synapsis
|
|||||||
# Or set a specific port: PORT=3000
|
# Or set a specific port: PORT=3000
|
||||||
PORT=auto
|
PORT=auto
|
||||||
|
|
||||||
|
# Allow localhost domains in production (set only for local testing)
|
||||||
|
# ALLOW_LOCALHOST=1
|
||||||
|
|
||||||
# Maximum AI bots per user (default: 5)
|
# Maximum AI bots per user (default: 5)
|
||||||
# BOT_MAX_PER_USER=5
|
# BOT_MAX_PER_USER=5
|
||||||
|
|||||||
+3
-4
@@ -43,7 +43,7 @@ RUN npm run build
|
|||||||
# Stage 3: Production Runner
|
# Stage 3: Production Runner
|
||||||
# ============================================
|
# ============================================
|
||||||
FROM node:22-alpine AS runner
|
FROM node:22-alpine AS runner
|
||||||
RUN apk add --no-cache libc6-compat openssl netcat-openbsd
|
RUN apk add --no-cache libc6-compat openssl netcat-openbsd wget
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -87,10 +87,9 @@ USER nextjs
|
|||||||
EXPOSE 3000-3020
|
EXPOSE 3000-3020
|
||||||
|
|
||||||
# Health check - longer start period to allow migrations to run
|
# Health check - longer start period to allow migrations to run
|
||||||
# Uses PORT environment variable (set by entrypoint)
|
# Reads dynamic port file when PORT=auto
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=5 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=5 \
|
||||||
CMD wget -q --spider http://127.0.0.1:${PORT:-3000}/api/health || exit 1
|
CMD sh -c 'PORT_FILE=/var/run/synapsis/port; PORT=3000; if [ -f "$PORT_FILE" ]; then PORT=$(cat "$PORT_FILE"); fi; case "$PORT" in ""|*[!0-9]*) PORT=3000;; esac; wget -q --spider "http://127.0.0.1:${PORT}/api/health" || exit 1'
|
||||||
|
|
||||||
# Set the entrypoint
|
# Set the entrypoint
|
||||||
ENTRYPOINT ["./docker-entrypoint.sh"]
|
ENTRYPOINT ["./docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -10,6 +10,7 @@ Production Docker deployment using pre-built images from GitHub Container Regist
|
|||||||
mkdir -p /opt/synapsis && cd /opt/synapsis
|
mkdir -p /opt/synapsis && cd /opt/synapsis
|
||||||
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker-compose.yml
|
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker-compose.yml
|
||||||
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/Caddyfile
|
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/Caddyfile
|
||||||
|
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/caddy-entrypoint.sh
|
||||||
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/.env.example
|
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/.env.example
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
nano .env # Add your domain and secrets
|
nano .env # Add your domain and secrets
|
||||||
@@ -39,7 +40,7 @@ newgrp docker
|
|||||||
|
|
||||||
## ⚙️ Configuration
|
## ⚙️ Configuration
|
||||||
|
|
||||||
Edit `.env` and set these required values:
|
Edit `.env` and set these required values (domain should be host only, no scheme or path):
|
||||||
|
|
||||||
| Variable | What to put |
|
| Variable | What to put |
|
||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
@@ -48,13 +49,18 @@ Edit `.env` and set these required values:
|
|||||||
| `AUTH_SECRET` | Run: `openssl rand -hex 32` |
|
| `AUTH_SECRET` | Run: `openssl rand -hex 32` |
|
||||||
| `ADMIN_EMAILS` | Your email address |
|
| `ADMIN_EMAILS` | Your email address |
|
||||||
|
|
||||||
|
Optional (advanced):
|
||||||
|
- `NEXT_PUBLIC_NODE_DOMAIN` to override the node domain (defaults to `DOMAIN`)
|
||||||
|
- `NEXT_PUBLIC_APP_URL` to override the public app URL used by background jobs (auto-derived from the node domain)
|
||||||
|
- `ALLOW_LOCALHOST=1` to allow `localhost` in production containers for local testing
|
||||||
|
|
||||||
**Port Configuration:**
|
**Port Configuration:**
|
||||||
- `PORT=auto` (default) — Automatically finds an available port between 3000-3020
|
- `PORT=auto` (default) — Automatically finds an available port between 3000-3020
|
||||||
- `PORT=3000` — Use a specific port instead
|
- `PORT=3000` — Use a specific port instead
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔄 Updates
|
## 🔄 Updates (migrations run automatically)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/synapsis
|
cd /opt/synapsis
|
||||||
|
|||||||
@@ -7,6 +7,24 @@ set -e
|
|||||||
SHARED_PORT_FILE="/var/run/synapsis/port"
|
SHARED_PORT_FILE="/var/run/synapsis/port"
|
||||||
DEFAULT_PORT=3000
|
DEFAULT_PORT=3000
|
||||||
|
|
||||||
|
# Normalize DOMAIN (strip scheme/path) to avoid invalid Caddyfile hostnames
|
||||||
|
sanitize_domain() {
|
||||||
|
echo "$1" | sed -E 's#^[a-zA-Z]+://##; s#/.*$##'
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -n "$DOMAIN" ]; then
|
||||||
|
CLEAN_DOMAIN=$(sanitize_domain "$DOMAIN")
|
||||||
|
if [ "$CLEAN_DOMAIN" != "$DOMAIN" ]; then
|
||||||
|
export DOMAIN="$CLEAN_DOMAIN"
|
||||||
|
echo "🌐 DOMAIN normalized to $DOMAIN"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$DOMAIN" ]; then
|
||||||
|
echo "❌ DOMAIN is not set. Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Wait for the port file to exist (with timeout)
|
# Wait for the port file to exist (with timeout)
|
||||||
echo "⏳ Waiting for Synapsis app to announce its port..."
|
echo "⏳ Waiting for Synapsis app to announce its port..."
|
||||||
TIMEOUT=60
|
TIMEOUT=60
|
||||||
|
|||||||
@@ -57,7 +57,10 @@ services:
|
|||||||
AUTH_SECRET: ${AUTH_SECRET}
|
AUTH_SECRET: ${AUTH_SECRET}
|
||||||
|
|
||||||
# Domain configuration
|
# Domain configuration
|
||||||
NEXT_PUBLIC_NODE_DOMAIN: ${DOMAIN:-localhost}
|
DOMAIN: ${DOMAIN:-localhost}
|
||||||
|
NEXT_PUBLIC_NODE_DOMAIN: ${NEXT_PUBLIC_NODE_DOMAIN:-}
|
||||||
|
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-}
|
||||||
|
ALLOW_LOCALHOST: ${ALLOW_LOCALHOST:-}
|
||||||
|
|
||||||
# Admin emails
|
# Admin emails
|
||||||
ADMIN_EMAILS: ${ADMIN_EMAILS}
|
ADMIN_EMAILS: ${ADMIN_EMAILS}
|
||||||
@@ -89,7 +92,7 @@ services:
|
|||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/health"]
|
test: ["CMD-SHELL", "PORT=$(cat /var/run/synapsis/port 2>/dev/null || echo 3000); case \"$PORT\" in ''|*[!0-9]*) PORT=3000;; esac; wget -q --spider http://127.0.0.1:${PORT}/api/health || exit 1"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|||||||
@@ -82,14 +82,79 @@ echo " Database URL: ${DATABASE_URL%%:*}://***@***"
|
|||||||
echo " Port: $PORT"
|
echo " Port: $PORT"
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
|
|
||||||
|
# Ensure DATABASE_URL is set
|
||||||
|
if [ -z "$DATABASE_URL" ]; then
|
||||||
|
echo "❌ DATABASE_URL is not set. Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Normalize domain inputs (strip scheme/path) to avoid localhost/protocol issues
|
||||||
|
sanitize_domain() {
|
||||||
|
echo "$1" | sed -E 's#^[a-zA-Z]+://##; s#/.*$##'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Normalize DOMAIN if provided
|
||||||
|
if [ -n "$DOMAIN" ]; then
|
||||||
|
CLEAN_DOMAIN=$(sanitize_domain "$DOMAIN")
|
||||||
|
if [ "$CLEAN_DOMAIN" != "$DOMAIN" ]; then
|
||||||
|
export DOMAIN="$CLEAN_DOMAIN"
|
||||||
|
echo "🌐 DOMAIN normalized to $DOMAIN"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Normalize NEXT_PUBLIC_NODE_DOMAIN if provided
|
||||||
|
if [ -n "$NEXT_PUBLIC_NODE_DOMAIN" ]; then
|
||||||
|
CLEAN_NODE_DOMAIN=$(sanitize_domain "$NEXT_PUBLIC_NODE_DOMAIN")
|
||||||
|
if [ "$CLEAN_NODE_DOMAIN" != "$NEXT_PUBLIC_NODE_DOMAIN" ]; then
|
||||||
|
export NEXT_PUBLIC_NODE_DOMAIN="$CLEAN_NODE_DOMAIN"
|
||||||
|
echo "🌐 NEXT_PUBLIC_NODE_DOMAIN normalized to $NEXT_PUBLIC_NODE_DOMAIN"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure NEXT_PUBLIC_NODE_DOMAIN is set (fallback to DOMAIN)
|
||||||
|
if [ -z "$NEXT_PUBLIC_NODE_DOMAIN" ] && [ -n "$DOMAIN" ]; then
|
||||||
|
export NEXT_PUBLIC_NODE_DOMAIN="$DOMAIN"
|
||||||
|
echo "🌐 NEXT_PUBLIC_NODE_DOMAIN set to $NEXT_PUBLIC_NODE_DOMAIN"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fail fast if running in production with localhost domain
|
||||||
|
if [ "$NODE_ENV" = "production" ]; then
|
||||||
|
case "$NEXT_PUBLIC_NODE_DOMAIN" in
|
||||||
|
""|localhost|localhost:*|127.0.0.1|127.0.0.1:*)
|
||||||
|
if [ -z "$ALLOW_LOCALHOST" ]; then
|
||||||
|
echo "❌ NEXT_PUBLIC_NODE_DOMAIN is set to localhost in production."
|
||||||
|
echo " Set DOMAIN to your public domain in .env, or set ALLOW_LOCALHOST=1 to bypass."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure NEXT_PUBLIC_APP_URL is set for background jobs
|
||||||
|
if [ -z "$NEXT_PUBLIC_APP_URL" ] && [ -n "$NEXT_PUBLIC_NODE_DOMAIN" ]; then
|
||||||
|
case "$NEXT_PUBLIC_NODE_DOMAIN" in
|
||||||
|
http://*|https://*)
|
||||||
|
NEXT_PUBLIC_APP_URL="$NEXT_PUBLIC_NODE_DOMAIN"
|
||||||
|
;;
|
||||||
|
localhost*|127.0.0.1*)
|
||||||
|
NEXT_PUBLIC_APP_URL="http://$NEXT_PUBLIC_NODE_DOMAIN"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
NEXT_PUBLIC_APP_URL="https://$NEXT_PUBLIC_NODE_DOMAIN"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
export NEXT_PUBLIC_APP_URL
|
||||||
|
echo "🌐 NEXT_PUBLIC_APP_URL set to $NEXT_PUBLIC_APP_URL"
|
||||||
|
fi
|
||||||
|
|
||||||
# Function to wait for database
|
# Function to wait for database
|
||||||
wait_for_db() {
|
wait_for_db() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "⏳ Waiting for PostgreSQL..."
|
echo "⏳ Waiting for PostgreSQL..."
|
||||||
|
|
||||||
# Extract host and port from DATABASE_URL
|
# Extract host and port from DATABASE_URL
|
||||||
DB_HOST=$(echo "$DATABASE_URL" | sed -n 's/.*@\([^:]*\):.*/\1/p')
|
DB_HOST=$(echo "$DATABASE_URL" | sed -n 's#.*@\([^/:]*\).*#\1#p')
|
||||||
DB_PORT=$(echo "$DATABASE_URL" | sed -n 's/.*:\([0-9]*\)\/.*/\1/p')
|
DB_PORT=$(echo "$DATABASE_URL" | sed -n 's#.*:\([0-9][0-9]*\)/.*#\1#p')
|
||||||
DB_HOST=${DB_HOST:-postgres}
|
DB_HOST=${DB_HOST:-postgres}
|
||||||
DB_PORT=${DB_PORT:-5432}
|
DB_PORT=${DB_PORT:-5432}
|
||||||
|
|
||||||
@@ -119,11 +184,12 @@ run_migrations() {
|
|||||||
|
|
||||||
# Run migrations using npm script
|
# Run migrations using npm script
|
||||||
echo " Executing: npm run db:push"
|
echo " Executing: npm run db:push"
|
||||||
npm run db:push 2>&1 || {
|
if npm run db:push 2>&1; then
|
||||||
echo "⚠️ Migration command exited with error (may be already up to date)"
|
echo "✅ Migration step complete"
|
||||||
}
|
else
|
||||||
|
echo "❌ Migration failed"
|
||||||
echo "✅ Migration step complete"
|
exit 1
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Wait for database
|
# Wait for database
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- Migration: Add storage_public_base_url column to users table
|
||||||
|
ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "storage_public_base_url" text;
|
||||||
@@ -92,6 +92,13 @@
|
|||||||
"when": 1769586699065,
|
"when": 1769586699065,
|
||||||
"tag": "0012_quick_mockingbird",
|
"tag": "0012_quick_mockingbird",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 13,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1769638500000,
|
||||||
|
"tag": "0013_add_storage_public_base_url",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Synapsis Docker Entrypoint Script
|
# Synapsis Docker Entrypoint Script
|
||||||
# Handles automatic port detection when PORT=auto
|
# Handles database migrations, port detection, and application startup
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@@ -66,6 +66,136 @@ echo "📝 Port $PORT written to $SHARED_PORT_FILE"
|
|||||||
# Export HOSTNAME for Next.js
|
# Export HOSTNAME for Next.js
|
||||||
export HOSTNAME="0.0.0.0"
|
export HOSTNAME="0.0.0.0"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Database Migrations
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
|
echo " Synapsis - Starting Application"
|
||||||
|
echo "========================================"
|
||||||
|
echo " Time: $(date)"
|
||||||
|
echo " Working Dir: $(pwd)"
|
||||||
|
echo " Database URL: ${DATABASE_URL%%:*}://***@***"
|
||||||
|
echo " Port: $PORT"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
# Ensure DATABASE_URL is set
|
||||||
|
if [ -z "$DATABASE_URL" ]; then
|
||||||
|
echo "❌ DATABASE_URL is not set. Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Normalize domain inputs (strip scheme/path) to avoid localhost/protocol issues
|
||||||
|
sanitize_domain() {
|
||||||
|
echo "$1" | sed -E 's#^[a-zA-Z]+://##; s#/.*$##'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Normalize DOMAIN if provided
|
||||||
|
if [ -n "$DOMAIN" ]; then
|
||||||
|
CLEAN_DOMAIN=$(sanitize_domain "$DOMAIN")
|
||||||
|
if [ "$CLEAN_DOMAIN" != "$DOMAIN" ]; then
|
||||||
|
export DOMAIN="$CLEAN_DOMAIN"
|
||||||
|
echo "🌐 DOMAIN normalized to $DOMAIN"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Normalize NEXT_PUBLIC_NODE_DOMAIN if provided
|
||||||
|
if [ -n "$NEXT_PUBLIC_NODE_DOMAIN" ]; then
|
||||||
|
CLEAN_NODE_DOMAIN=$(sanitize_domain "$NEXT_PUBLIC_NODE_DOMAIN")
|
||||||
|
if [ "$CLEAN_NODE_DOMAIN" != "$NEXT_PUBLIC_NODE_DOMAIN" ]; then
|
||||||
|
export NEXT_PUBLIC_NODE_DOMAIN="$CLEAN_NODE_DOMAIN"
|
||||||
|
echo "🌐 NEXT_PUBLIC_NODE_DOMAIN normalized to $NEXT_PUBLIC_NODE_DOMAIN"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure NEXT_PUBLIC_NODE_DOMAIN is set (fallback to DOMAIN)
|
||||||
|
if [ -z "$NEXT_PUBLIC_NODE_DOMAIN" ] && [ -n "$DOMAIN" ]; then
|
||||||
|
export NEXT_PUBLIC_NODE_DOMAIN="$DOMAIN"
|
||||||
|
echo "🌐 NEXT_PUBLIC_NODE_DOMAIN set to $NEXT_PUBLIC_NODE_DOMAIN"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fail fast if running in production with localhost domain
|
||||||
|
if [ "$NODE_ENV" = "production" ]; then
|
||||||
|
case "$NEXT_PUBLIC_NODE_DOMAIN" in
|
||||||
|
""|localhost|localhost:*|127.0.0.1|127.0.0.1:*)
|
||||||
|
if [ -z "$ALLOW_LOCALHOST" ]; then
|
||||||
|
echo "❌ NEXT_PUBLIC_NODE_DOMAIN is set to localhost in production."
|
||||||
|
echo " Set DOMAIN to your public domain in .env, or set ALLOW_LOCALHOST=1 to bypass."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure NEXT_PUBLIC_APP_URL is set for background jobs
|
||||||
|
if [ -z "$NEXT_PUBLIC_APP_URL" ] && [ -n "$NEXT_PUBLIC_NODE_DOMAIN" ]; then
|
||||||
|
case "$NEXT_PUBLIC_NODE_DOMAIN" in
|
||||||
|
http://*|https://*)
|
||||||
|
NEXT_PUBLIC_APP_URL="$NEXT_PUBLIC_NODE_DOMAIN"
|
||||||
|
;;
|
||||||
|
localhost*|127.0.0.1*)
|
||||||
|
NEXT_PUBLIC_APP_URL="http://$NEXT_PUBLIC_NODE_DOMAIN"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
NEXT_PUBLIC_APP_URL="https://$NEXT_PUBLIC_NODE_DOMAIN"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
export NEXT_PUBLIC_APP_URL
|
||||||
|
echo "🌐 NEXT_PUBLIC_APP_URL set to $NEXT_PUBLIC_APP_URL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Function to wait for database
|
||||||
|
wait_for_db() {
|
||||||
|
echo ""
|
||||||
|
echo "⏳ Waiting for PostgreSQL..."
|
||||||
|
|
||||||
|
# Extract host and port from DATABASE_URL
|
||||||
|
DB_HOST=$(echo "$DATABASE_URL" | sed -n 's#.*@\([^/:]*\).*#\1#p')
|
||||||
|
DB_PORT=$(echo "$DATABASE_URL" | sed -n 's#.*:\([0-9][0-9]*\)/.*#\1#p')
|
||||||
|
DB_HOST=${DB_HOST:-postgres}
|
||||||
|
DB_PORT=${DB_PORT:-5432}
|
||||||
|
|
||||||
|
max_retries=30
|
||||||
|
retry_count=0
|
||||||
|
|
||||||
|
while ! nc -z "$DB_HOST" "$DB_PORT" 2>/dev/null; do
|
||||||
|
retry_count=$((retry_count + 1))
|
||||||
|
if [ $retry_count -ge $max_retries ]; then
|
||||||
|
echo "❌ Failed to connect to database after $max_retries attempts"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " Attempt $retry_count/$max_retries - waiting for $DB_HOST:$DB_PORT..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "✅ Database is ready!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to run database migrations
|
||||||
|
run_migrations() {
|
||||||
|
echo ""
|
||||||
|
echo "🔄 Running database migrations..."
|
||||||
|
echo " Current directory: $(pwd)"
|
||||||
|
echo " Drizzle directory contents:"
|
||||||
|
ls -la drizzle/ 2>/dev/null || echo " (drizzle dir not found or empty)"
|
||||||
|
|
||||||
|
# Run migrations using npm script
|
||||||
|
echo " Executing: npm run db:push"
|
||||||
|
if npm run db:push 2>&1; then
|
||||||
|
echo "✅ Migration step complete"
|
||||||
|
else
|
||||||
|
echo "❌ Migration failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wait for database
|
||||||
|
wait_for_db
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
run_migrations
|
||||||
|
|
||||||
echo "🚀 Starting Synapsis on port $PORT..."
|
echo "🚀 Starting Synapsis on port $PORT..."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { db, users } from '@/db';
|
import { db, users, isDbAvailable } from '@/db';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
|
if (!isDbAvailable()) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ available: false, error: 'Database not configured' },
|
||||||
|
{ status: 503 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const { searchParams } = new URL(req.url);
|
const { searchParams } = new URL(req.url);
|
||||||
const handle = searchParams.get('handle')?.toLowerCase().trim();
|
const handle = searchParams.get('handle')?.toLowerCase().trim();
|
||||||
|
|
||||||
@@ -15,9 +22,21 @@ export async function GET(req: NextRequest) {
|
|||||||
return NextResponse.json({ available: false, error: 'Invalid characters' });
|
return NextResponse.json({ available: false, error: 'Invalid characters' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingUser = await db.query.users.findFirst({
|
let existingUser = null;
|
||||||
where: eq(users.handle, handle),
|
try {
|
||||||
});
|
existingUser = await db.query.users.findFirst({
|
||||||
|
where: eq(users.handle, handle),
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
// Handle fresh installs where the users table isn't created yet.
|
||||||
|
if (err?.code === '42P01' || /relation .*users.* does not exist/i.test(err?.message || '')) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ available: true, handle, warning: 'Database not initialized' },
|
||||||
|
{ status: 503 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
available: !existingUser,
|
available: !existingUser,
|
||||||
|
|||||||
@@ -1,12 +1,29 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { db, nodes } from '@/db';
|
import { db, nodes } from '@/db';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
export async function GET() {
|
function getRequestBaseUrl(req: NextRequest, fallbackDomain: string): string {
|
||||||
|
const forwardedHost = req.headers.get('x-forwarded-host');
|
||||||
|
const forwardedProto = req.headers.get('x-forwarded-proto');
|
||||||
|
const host = forwardedHost?.split(',')[0]?.trim() || req.headers.get('host');
|
||||||
|
const protocol =
|
||||||
|
forwardedProto?.split(',')[0]?.trim() ||
|
||||||
|
(host && host.includes('localhost') ? 'http' : 'https');
|
||||||
|
|
||||||
|
if (host) {
|
||||||
|
return `${protocol}://${host}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `https://${fallbackDomain}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
if (!db) {
|
if (!db) {
|
||||||
// Redirect to default favicon
|
// Redirect to default favicon
|
||||||
return NextResponse.redirect(new URL('/favicon.png', process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'));
|
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||||
|
const baseUrl = getRequestBaseUrl(req, domain);
|
||||||
|
return NextResponse.redirect(new URL('/favicon.png', baseUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||||
@@ -17,14 +34,20 @@ export async function GET() {
|
|||||||
|
|
||||||
if (node?.faviconUrl) {
|
if (node?.faviconUrl) {
|
||||||
// Redirect to custom favicon
|
// Redirect to custom favicon
|
||||||
return NextResponse.redirect(node.faviconUrl);
|
const baseUrl = getRequestBaseUrl(req, domain);
|
||||||
|
const target = node.faviconUrl.startsWith('/')
|
||||||
|
? new URL(node.faviconUrl, baseUrl)
|
||||||
|
: node.faviconUrl;
|
||||||
|
return NextResponse.redirect(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to default favicon
|
// Redirect to default favicon
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || `https://${domain}`;
|
const baseUrl = getRequestBaseUrl(req, domain);
|
||||||
return NextResponse.redirect(new URL('/favicon.png', baseUrl));
|
return NextResponse.redirect(new URL('/favicon.png', baseUrl));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Favicon error:', error);
|
console.error('Favicon error:', error);
|
||||||
return NextResponse.redirect(new URL('/favicon.png', process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'));
|
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||||
|
const baseUrl = getRequestBaseUrl(req, domain);
|
||||||
|
return NextResponse.redirect(new URL('/favicon.png', baseUrl));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export async function POST(req: NextRequest) {
|
|||||||
accessKeyId: process.env.STORAGE_ACCESS_KEY || '',
|
accessKeyId: process.env.STORAGE_ACCESS_KEY || '',
|
||||||
secretAccessKey: process.env.STORAGE_SECRET_KEY || '',
|
secretAccessKey: process.env.STORAGE_SECRET_KEY || '',
|
||||||
},
|
},
|
||||||
forcePathStyle: true, // Needed for many S3-compatible providers (MinIO, etc.)
|
forcePathStyle: true, // Needed for many S3-compatible providers
|
||||||
});
|
});
|
||||||
|
|
||||||
const bucket = process.env.STORAGE_BUCKET || 'synapsis';
|
const bucket = process.env.STORAGE_BUCKET || 'synapsis';
|
||||||
|
|||||||
@@ -577,8 +577,6 @@ export default function LoginPage() {
|
|||||||
<option value="wasabi">Wasabi</option>
|
<option value="wasabi">Wasabi</option>
|
||||||
<option value="contabo">Contabo S3</option>
|
<option value="contabo">Contabo S3</option>
|
||||||
<option value="s3">AWS S3</option>
|
<option value="s3">AWS S3</option>
|
||||||
<option value="minio">MinIO / Self-hosted</option>
|
|
||||||
<option value="other">Other S3-compatible</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -614,8 +612,8 @@ export default function LoginPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Endpoint URL - only show for providers that need it (R2, B2, Contabo, MinIO, Other) */}
|
{/* Endpoint URL - only show for providers that need it (R2, B2, Contabo) */}
|
||||||
{(storageProvider === 'r2' || storageProvider === 'b2' || storageProvider === 'contabo' || storageProvider === 'minio' || storageProvider === 'other') && (
|
{(storageProvider === 'r2' || storageProvider === 'b2' || storageProvider === 'contabo') && (
|
||||||
<div style={{ marginBottom: '16px' }}>
|
<div style={{ marginBottom: '16px' }}>
|
||||||
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>
|
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>
|
||||||
Endpoint URL
|
Endpoint URL
|
||||||
@@ -635,8 +633,8 @@ export default function LoginPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Public Base URL - only show for providers that need it (R2, B2, Contabo, Other) */}
|
{/* Public Base URL - only show for providers that need it (R2, B2, Contabo) */}
|
||||||
{(storageProvider === 'r2' || storageProvider === 'b2' || storageProvider === 'contabo' || storageProvider === 'other') && (
|
{(storageProvider === 'r2' || storageProvider === 'b2' || storageProvider === 'contabo') && (
|
||||||
<div style={{ marginBottom: '16px' }}>
|
<div style={{ marginBottom: '16px' }}>
|
||||||
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>
|
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>
|
||||||
Public Base URL
|
Public Base URL
|
||||||
|
|||||||
+3
-3
@@ -65,7 +65,7 @@ export const users = pgTable('users', {
|
|||||||
movedFrom: text('moved_from'), // Old actor URL if this account migrated here
|
movedFrom: text('moved_from'), // Old actor URL if this account migrated here
|
||||||
migratedAt: timestamp('migrated_at'), // When the migration occurred
|
migratedAt: timestamp('migrated_at'), // When the migration occurred
|
||||||
// User-owned S3-compatible storage - required for new users
|
// User-owned S3-compatible storage - required for new users
|
||||||
storageProvider: text('storage_provider'), // 's3', 'r2', 'b2', 'wasabi', 'contabo', etc
|
storageProvider: text('storage_provider'), // 's3', 'r2', 'b2', 'wasabi', 'contabo'
|
||||||
storageEndpoint: text('storage_endpoint'), // S3 endpoint URL (optional for AWS)
|
storageEndpoint: text('storage_endpoint'), // S3 endpoint URL (optional for AWS)
|
||||||
storagePublicBaseUrl: text('storage_public_base_url'), // Public URL for viewing files (required for R2, B2, Contabo)
|
storagePublicBaseUrl: text('storage_public_base_url'), // Public URL for viewing files (required for R2, B2, Contabo)
|
||||||
storageRegion: text('storage_region'), // Region (e.g., 'us-east-1')
|
storageRegion: text('storage_region'), // Region (e.g., 'us-east-1')
|
||||||
@@ -919,8 +919,8 @@ export const chatConversationsRelations = relations(chatConversations, ({ one, m
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Individual chat messages within conversations.
|
* Individual chat messages within conversations.
|
||||||
* Messages are encrypted end-to-end using recipient's public key.
|
* Messages are stored as plain text on the server.
|
||||||
* Sender also gets a copy encrypted with their own public key.
|
* Both sender and recipient can view the message content.
|
||||||
*/
|
*/
|
||||||
export const chatMessages = pgTable('chat_messages', {
|
export const chatMessages = pgTable('chat_messages', {
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
||||||
|
|
||||||
interface RuntimeConfig {
|
interface RuntimeConfig {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* User-Owned S3-Compatible Storage Utilities
|
* User-Owned S3-Compatible Storage Utilities
|
||||||
*
|
*
|
||||||
* Supports AWS S3, Cloudflare R2, Backblaze B2, Wasabi, MinIO, etc.
|
* Supports AWS S3, Cloudflare R2, Backblaze B2, Wasabi, and Contabo.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { S3Client, PutObjectCommand, HeadBucketCommand } from '@aws-sdk/client-s3';
|
import { S3Client, PutObjectCommand, HeadBucketCommand } from '@aws-sdk/client-s3';
|
||||||
import { decryptPrivateKey, deserializeEncryptedKey } from '@/lib/crypto/private-key';
|
import { decryptPrivateKey, deserializeEncryptedKey } from '@/lib/crypto/private-key';
|
||||||
|
|
||||||
export type StorageProvider = 's3' | 'r2' | 'b2' | 'wasabi' | 'minio' | 'other';
|
export type StorageProvider = 's3' | 'r2' | 'b2' | 'wasabi' | 'contabo';
|
||||||
|
|
||||||
interface S3Credentials {
|
interface S3Credentials {
|
||||||
endpoint?: string;
|
endpoint?: string;
|
||||||
@@ -105,7 +105,7 @@ export async function uploadToUserStorage(
|
|||||||
if (publicBaseUrl) {
|
if (publicBaseUrl) {
|
||||||
url = `${publicBaseUrl.replace(/\/$/, '')}/${key}`;
|
url = `${publicBaseUrl.replace(/\/$/, '')}/${key}`;
|
||||||
} else if (endpoint) {
|
} else if (endpoint) {
|
||||||
// Custom endpoint (MinIO, etc)
|
// Custom endpoint (R2, B2, Contabo)
|
||||||
url = `${endpoint}/${bucket}/${key}`;
|
url = `${endpoint}/${bucket}/${key}`;
|
||||||
} else {
|
} else {
|
||||||
// AWS S3 standard URL
|
// AWS S3 standard URL
|
||||||
|
|||||||
Reference in New Issue
Block a user