382 lines
14 KiB
Bash
382 lines
14 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# --- Notesnook Sync Server Setup for Proxmox with Cloudflare Wildcard ---
|
|
#
|
|
# This script adapts the Lemmy tutorial for Notesnook sync server to run in a Proxmox LXC container
|
|
# using Podman, a single Cloudflare wildcard domain, and Traefik for routing on port 443.
|
|
# It sets up all required services (MongoDB, MinIO, identity, sync, SSE, monograph) with path-based routing.
|
|
|
|
# Exit on any error
|
|
set -e
|
|
|
|
# Define the main project directory
|
|
PROJECT_DIR="/srv/Files/Notesnook/setup"
|
|
DATA_DIR="/srv/Files/Notesnook"
|
|
|
|
# Define the required configuration files
|
|
REQUIRED_FILES=("docker-compose.yml" "traefik.yml")
|
|
|
|
# --- Step 1: Check and install prerequisites ---
|
|
echo "--- Checking for podman, podman-compose, and openssl ---"
|
|
if ! command -v podman &> /dev/null; then
|
|
echo "Podman not found. Installing podman..."
|
|
apt update
|
|
apt install -y podman || {
|
|
echo "Error: Failed to install podman via apt. Please install it manually and try again."
|
|
exit 1
|
|
}
|
|
else
|
|
echo "Podman is already installed."
|
|
fi
|
|
|
|
if ! command -v podman-compose &> /dev/null; then
|
|
echo "Podman-compose not found. Attempting to install via apt..."
|
|
apt install -y podman-compose || {
|
|
echo "Failed to install podman-compose via apt. Trying pip installation..."
|
|
apt install -y python3-pip || {
|
|
echo "Error: Failed to install python3-pip. Please install pip manually and try again."
|
|
exit 1
|
|
}
|
|
pip3 install podman-compose || {
|
|
echo "Error: Failed to install podman-compose via pip. Please install it manually:"
|
|
echo "1. Run 'pip3 install podman-compose' as root."
|
|
echo "2. Or download from https://github.com/containers/podman-compose."
|
|
exit 1
|
|
}
|
|
}
|
|
else
|
|
echo "Podman-compose is already installed."
|
|
fi
|
|
|
|
if ! command -v openssl &> /dev/null; then
|
|
echo "OpenSSL not found. Installing openssl..."
|
|
apt install -y openssl || {
|
|
echo "Error: Failed to install openssl via apt. Please install it manually and try again."
|
|
exit 1
|
|
}
|
|
else
|
|
echo "OpenSSL is already installed."
|
|
fi
|
|
|
|
# Verify podman-compose is in PATH
|
|
if ! command -v podman-compose &> /dev/null; then
|
|
echo "Error: podman-compose installed but not found in PATH. Please add it to your PATH or run the script again."
|
|
exit 1
|
|
fi
|
|
echo ""
|
|
|
|
# --- Step 2: Ensure Podman socket is running ---
|
|
echo "--- Checking Podman socket ---"
|
|
if ! systemctl is-active --quiet podman.socket; then
|
|
echo "Starting Podman socket..."
|
|
systemctl enable --now podman.socket || {
|
|
echo "Error: Failed to start Podman socket. Please ensure Podman is properly configured."
|
|
exit 1
|
|
}
|
|
else
|
|
echo "Podman socket is already running."
|
|
fi
|
|
if [ ! -S /var/run/podman/podman.sock ]; then
|
|
echo "Error: Podman socket not found at /var/run/podman/podman.sock."
|
|
echo "Please ensure Podman is properly configured and the socket is running."
|
|
exit 1
|
|
fi
|
|
echo ""
|
|
|
|
# --- Step 3: Prompt for configuration ---
|
|
echo "--- Please provide the following information ---"
|
|
read -p "Enter the Notesnook sync image (e.g., streetwriters/notesnook-sync:latest, press Enter for default): " NOTESNOOK_SYNC_IMAGE
|
|
NOTESNOOK_SYNC_IMAGE=${NOTESNOOK_SYNC_IMAGE:-streetwriters/notesnook-sync:latest}
|
|
read -p "Enter the Notesnook monograph image (e.g., streetwriters/monograph:latest, press Enter for default): " NOTESNOOK_MONOGRAPH_IMAGE
|
|
NOTESNOOK_MONOGRAPH_IMAGE=${NOTESNOOK_MONOGRAPH_IMAGE:-streetwriters/monograph:latest}
|
|
read -p "Enter the Notesnook identity image (e.g., streetwriters/identity:latest, press Enter for default): " NOTESNOOK_IDENTITY_IMAGE
|
|
NOTESNOOK_IDENTITY_IMAGE=${NOTESNOOK_IDENTITY_IMAGE:-streetwriters/identity:latest}
|
|
read -p "Enter the Notesnook SSE image (e.g., streetwriters/sse:latest, press Enter for default): " NOTESNOOK_SSE_IMAGE
|
|
NOTESNOOK_SSE_IMAGE=${NOTESNOOK_SSE_IMAGE:-streetwriters/sse:latest}
|
|
read -p "Enter your Cloudflare-managed domain (e.g., example.com): " BASE_DOMAIN
|
|
if [ -z "$BASE_DOMAIN" ]; then
|
|
echo "Error: A valid domain is required for Cloudflare wildcard setup."
|
|
exit 1
|
|
fi
|
|
read -p "Enter the local IP address to bind services (e.g., 192.168.1.x, press Enter for 127.0.0.1): " LOCAL_IP
|
|
LOCAL_IP=${LOCAL_IP:-127.0.0.1}
|
|
if ! echo "$LOCAL_IP" | grep -E '^(127\.0\.0\.1|[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$' > /dev/null; then
|
|
echo "Error: Invalid IP address format. Using 127.0.0.1 instead."
|
|
LOCAL_IP="127.0.0.1"
|
|
fi
|
|
read -p "Enter SMTP host for email notifications (e.g., smtp.gmail.com): " SMTP_HOST
|
|
read -p "Enter SMTP port (e.g., 587): " SMTP_PORT
|
|
read -p "Enter SMTP username (e.g., your_email@gmail.com): " SMTP_USERNAME
|
|
read -p "Enter SMTP password: " SMTP_PASSWORD
|
|
read -p "Disable signups? (true/false, press Enter for false): " DISABLE_SIGNUPS
|
|
DISABLE_SIGNUPS=${DISABLE_SIGNUPS:-false}
|
|
read -p "Enter instance name (e.g., My Notesnook, press Enter for default): " INSTANCE_NAME
|
|
INSTANCE_NAME=${INSTANCE_NAME:-self-hosted-notesnook-instance}
|
|
echo ""
|
|
|
|
# --- Step 4: Generate environment variables ---
|
|
echo "--- Generating random values for environment variables ---"
|
|
TRAEFIK_USER="admin"
|
|
TRAEFIK_PASS=$(openssl rand -base64 12 | tr -dc 'a-zA-Z0-9!@#$%^&*()_+' | head -c 12)
|
|
TRAEFIK_HASH=$(openssl passwd -apr1 "$TRAEFIK_PASS")
|
|
TRAEFIK_AUTH_USER="${TRAEFIK_USER}:${TRAEFIK_HASH}"
|
|
MINIO_ROOT_PASSWORD=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9!@#$%^&*()_+' | head -c 32)
|
|
NOTESNOOK_API_SECRET=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9!@#$%^&*()_+' | head -c 48)
|
|
SERVICE_DISCOVERY_TOKEN=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9!@#$%^&*()_+' | head -c 48)
|
|
SERVICE_DISCOVERY_URL="https://notesnook.${BASE_DOMAIN}/sync"
|
|
AUTH_SERVER_URL="https://notesnook.${BASE_DOMAIN}/auth"
|
|
EVENTS_SERVER_URL="https://notesnook.${BASE_DOMAIN}/events"
|
|
ATTACHMENTS_SERVER_PUBLIC_URL="https://notesnook.${BASE_DOMAIN}/s3"
|
|
MONOGRAPH_PUBLIC_URL="https://notesnook.${BASE_DOMAIN}/monograph"
|
|
TRAEFIK_DASHBOARD_URL="https://traefik.${BASE_DOMAIN}"
|
|
echo "Generated values:"
|
|
echo "Traefik Dashboard: user = ${TRAEFIK_USER}, password = ${TRAEFIK_PASS}"
|
|
echo "MinIO Root Password: ${MINIO_ROOT_PASSWORD}"
|
|
echo "Notesnook API Secret: ${NOTESNOOK_API_SECRET}"
|
|
echo "Service Discovery Token: ${SERVICE_DISCOVERY_TOKEN}"
|
|
echo "Using domain: ${BASE_DOMAIN}"
|
|
echo "Local IP: ${LOCAL_IP}"
|
|
echo ""
|
|
echo "Proceeding with setup..."
|
|
echo ""
|
|
|
|
# --- Step 5: Create directories ---
|
|
echo "--- Creating data directories ---"
|
|
mkdir -p "${DATA_DIR}/db"
|
|
mkdir -p "${DATA_DIR}/s3"
|
|
mkdir -p "${PROJECT_DIR}"
|
|
echo ""
|
|
|
|
# --- Step 6: Create or verify configuration files ---
|
|
echo "--- Checking and creating required configuration files ---"
|
|
if [ ! -f "docker-compose.yml" ]; then
|
|
echo "Creating docker-compose.yml..."
|
|
cat > "docker-compose.yml" << EOF
|
|
version: '3.8'
|
|
|
|
x-server-discovery: &server-discovery
|
|
NOTESNOOK_SERVER_PORT: 5264
|
|
NOTESNOOK_SERVER_HOST: notesnook-server
|
|
IDENTITY_SERVER_PORT: 8264
|
|
IDENTITY_SERVER_HOST: identity-server
|
|
SSE_SERVER_PORT: 7264
|
|
SSE_SERVER_HOST: sse-server
|
|
SELF_HOSTED: 1
|
|
IDENTITY_SERVER_URL: \${AUTH_SERVER_URL}
|
|
NOTESNOOK_APP_HOST: \${SERVICE_DISCOVERY_URL}
|
|
NOTESNOOK_API_SECRET: \${NOTESNOOK_API_SECRET}
|
|
|
|
services:
|
|
traefik:
|
|
image: traefik:v2.10
|
|
container_name: traefik
|
|
ports:
|
|
- "${LOCAL_IP}:443:443"
|
|
volumes:
|
|
- ./traefik.yml:/etc/traefik/traefik.yml:ro
|
|
- /var/run/podman/podman.sock:/var/run/docker.sock:ro
|
|
networks:
|
|
- notesnook
|
|
restart: unless-stopped
|
|
|
|
validate:
|
|
image: vandot/alpine-bash
|
|
container_name: validate
|
|
entrypoint: /bin/bash
|
|
environment:
|
|
- INSTANCE_NAME=\${INSTANCE_NAME}
|
|
- NOTESNOOK_API_SECRET=\${NOTESNOOK_API_SECRET}
|
|
- DISABLE_SIGNUPS=\${DISABLE_SIGNUPS}
|
|
- SMTP_USERNAME=\${SMTP_USERNAME}
|
|
- SMTP_PASSWORD=\${SMTP_PASSWORD}
|
|
- SMTP_HOST=\${SMTP_HOST}
|
|
- SMTP_PORT=\${SMTP_PORT}
|
|
- AUTH_SERVER_PUBLIC_URL=\${AUTH_SERVER_URL}
|
|
- NOTESNOOK_APP_PUBLIC_URL=\${SERVICE_DISCOVERY_URL}
|
|
- MONOGRAPH_PUBLIC_URL=\${MONOGRAPH_PUBLIC_URL}
|
|
- ATTACHMENTS_SERVER_PUBLIC_URL=\${ATTACHMENTS_SERVER_PUBLIC_URL}
|
|
command:
|
|
- -c
|
|
- |
|
|
required_vars=(
|
|
"INSTANCE_NAME"
|
|
"NOTESNOOK_API_SECRET"
|
|
"DISABLE_SIGNUPS"
|
|
"SMTP_USERNAME"
|
|
"SMTP_PASSWORD"
|
|
"SMTP_HOST"
|
|
"SMTP_PORT"
|
|
"AUTH_SERVER_PUBLIC_URL"
|
|
"NOTESNOOK_APP_PUBLIC_URL"
|
|
"MONOGRAPH_PUBLIC_URL"
|
|
"ATTACHMENTS_SERVER_PUBLIC_URL"
|
|
)
|
|
for var in "\${required_vars[@]}"; do
|
|
if [ -z "\${!var}" ]; then
|
|
echo "Error: Required environment variable \$var is not set."
|
|
exit 1
|
|
fi
|
|
done
|
|
echo "All required environment variables are set."
|
|
networks:
|
|
- notesnook
|
|
restart: "no"
|
|
|
|
notesnook-db:
|
|
image: mongo:7.0.12
|
|
container_name: notesnook-db
|
|
volumes:
|
|
- ${DATA_DIR}/db:/data/db
|
|
- ${DATA_DIR}/db:/data/configdb
|
|
networks:
|
|
- notesnook
|
|
command: --replSet rs0 --bind_ip_all
|
|
depends_on:
|
|
validate:
|
|
condition: service_completed_successfully
|
|
healthcheck:
|
|
test: echo 'db.runCommand("ping").ok' | mongosh mongodb://localhost:27017 --quiet
|
|
interval: 40s
|
|
timeout: 30s
|
|
retries: 3
|
|
start_period: 60s
|
|
restart: unless-stopped
|
|
|
|
initiate-rs0:
|
|
image: mongo:7.0.12
|
|
container_name: initiate-rs0
|
|
networks:
|
|
- notesnook
|
|
depends_on:
|
|
notesnook-db:
|
|
condition: service_healthy
|
|
entrypoint: /bin/sh
|
|
command: -c "mongosh mongodb://notesnook-db:27017 <<EOF\nrs.initiate();\nrs.status();\nEOF"
|
|
restart: "no"
|
|
|
|
minio:
|
|
image: minio/minio:RELEASE.2024-07-29T22-14-52Z
|
|
container_name: minio
|
|
environment:
|
|
- MINIO_ROOT_USER=admin
|
|
- MINIO_ROOT_PASSWORD=\${MINIO_ROOT_PASSWORD}
|
|
- MINIO_BROWSER=on
|
|
command: server /data/s3 --console-address ":9001"
|
|
volumes:
|
|
- ${DATA_DIR}/s3:/data/s3
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.minio.rule=Host(\`notesnook.\${BASE_DOMAIN}\`) && PathPrefix(\`/s3\`)"
|
|
- "traefik.http.routers.minio.entrypoints=websecure"
|
|
- "traefik.http.routers.minio.tls=true"
|
|
networks:
|
|
- notesnook
|
|
healthcheck:
|
|
test: timeout 5s bash -c ':> /dev/tcp/127.0.0.1/9001' || exit 1
|
|
interval: 40s
|
|
timeout: 30s
|
|
retries: 3
|
|
start_period: 60s
|
|
restart: unless-stopped
|
|
|
|
setup-s3:
|
|
image: minio/mc:RELEASE.2024-07-26T13-08-44Z
|
|
container_name: setup-s3
|
|
depends_on:
|
|
- minio
|
|
networks:
|
|
- notesnook
|
|
entrypoint: /bin/bash
|
|
environment:
|
|
- MINIO_ROOT_USER=admin
|
|
- MINIO_ROOT_PASSWORD=\${MINIO_ROOT_PASSWORD}
|
|
command: -c "until mc alias set minio http://minio:9000 \${MINIO_ROOT_USER} \${MINIO_ROOT_PASSWORD}; do sleep 1; done; mc mb minio/attachments -p"
|
|
restart: "no"
|
|
|
|
identity-server:
|
|
image: ${NOTESNOOK_IDENTITY_IMAGE}
|
|
container_name: identity-server
|
|
environment:
|
|
<<: *server-discovery
|
|
- MONGODB_CONNECTION_STRING=mongodb://notesnook-db:27017/identity?replSet=rs0
|
|
- MONGODB_DATABASE_NAME=identity
|
|
- SMTP_HOST=\${SMTP_HOST}
|
|
- SMTP_PORT=\${SMTP_PORT}
|
|
- SMTP_USERNAME=\${SMTP_USERNAME}
|
|
- SMTP_PASSWORD=\${SMTP_PASSWORD}
|
|
- DISABLE_SIGNUPS=\${DISABLE_SIGNUPS}
|
|
- INSTANCE_NAME=\${INSTANCE_NAME}
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.identity-server.rule=Host(\`notesnook.\${BASE_DOMAIN}\`) && PathPrefix(\`/auth\`)"
|
|
- "traefik.http.routers.identity-server.entrypoints=websecure"
|
|
- "traefik.http.routers.identity-server.tls=true"
|
|
networks:
|
|
- notesnook
|
|
depends_on:
|
|
- notesnook-db
|
|
healthcheck:
|
|
test: wget --tries=1 -nv -q http://localhost:8264/health -O- || exit 1
|
|
interval: 40s
|
|
timeout: 30s
|
|
retries: 3
|
|
start_period: 60s
|
|
restart: unless-stopped
|
|
|
|
notesnook-sync:
|
|
image: ${NOTESNOOK_SYNC_IMAGE}
|
|
container_name: notesnook-sync
|
|
environment:
|
|
<<: *server-discovery
|
|
- MONGODB_CONNECTION_STRING=mongodb://notesnook-db:27017/?replSet=rs0
|
|
- MONGODB_DATABASE_NAME=notesnook
|
|
- S3_INTERNAL_SERVICE_URL=http://minio:9000/
|
|
- S3_INTERNAL_BUCKET_NAME=attachments
|
|
- S3_ACCESS_KEY_ID=admin
|
|
- S3_ACCESS_KEY=\${MINIO_ROOT_PASSWORD}
|
|
- S3_SERVICE_URL=\${ATTACHMENTS_SERVER_PUBLIC_URL}
|
|
- S3_REGION=us-east-1
|
|
- S3_BUCKET_NAME=attachments
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.notesnook-sync.rule=Host(\`notesnook.\${BASE_DOMAIN}\`) && PathPrefix(\`/sync\`)"
|
|
- "traefik.http.routers.notesnook-sync.entrypoints=websecure"
|
|
- "traefik.http.routers.notesnook-sync.tls=true"
|
|
networks:
|
|
- notesnook
|
|
depends_on:
|
|
- minio
|
|
- setup-s3
|
|
- identity-server
|
|
healthcheck:
|
|
test: wget --tries=1 -nv -q http://localhost:5264/health -O- || exit 1
|
|
interval: 40s
|
|
timeout: 30s
|
|
retries: 3
|
|
start_period: 60s
|
|
restart: unless-stopped
|
|
|
|
sse-server:
|
|
image: ${NOTESNOOK_SSE_IMAGE}
|
|
container_name: sse-server
|
|
environment:
|
|
<<: *server-discovery
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.sse-server.rule=Host(\`notesnook.\${BASE_DOMAIN}\`) && PathPrefix(\`/events\`)"
|
|
- "traefik.http.routers.sse-server.entrypoints=websecure"
|
|
- "traefik.http.routers.sse-server.tls=true"
|
|
networks:
|
|
- notesnook
|
|
depends_on:
|
|
- identity-server
|
|
- notesnook-sync
|
|
healthcheck:
|
|
test: wget --tries=1 -nv -q http://localhost:7264/health -O- || exit 1
|
|
interval: 40s
|
|
timeout: 30s
|
|
retries: 3
|
|
start_period: 60s
|
|
restart: unless-stopped
|
|
|
|
monograph |