Files
Notesnook/newcloud-setup.sh
2025-08-25 03:28:11 -07:00

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