#!/bin/bash # To be run by user pln to create pod for AFFiNE with PostgreSQL and Redis set -e # Environment variables POD_NAME='affine_pod' AFFINE_CTR_NAME='affine_ctr' AFFINE_MIGRATION_CTR_NAME='affine_migration_ctr' POSTGRES_CTR_NAME='affine_postgres_ctr' REDIS_CTR_NAME='affine_redis_ctr' AFFINE_IMAGE='ghcr.io/toeverything/affine:0.26.3' POSTGRES_IMAGE='docker.io/pgvector/pgvector:0.7.1-pg16' REDIS_IMAGE='docker.io/library/redis:7.4.8' HOST_LOCAL_IP='127.0.0.1' # Expose AFFiNE Web UI on 8092 -> 3010 AFFINE_HOST_PORT='8092' AFFINE_CONTAINER_PORT='3010' # All other services are only reachable inside the pod BIND_DIR="$HOME/.local/share/$POD_NAME" POSTGRES_DATA_DIR="$BIND_DIR/postgres-data" AFFINE_STORAGE_DIR="$BIND_DIR/affine-storage" AFFINE_CONFIG_DIR="$BIND_DIR/affine-config" USER_SYSTEMD_DIR="$HOME/.config/systemd/user" DB_USERNAME='affine' DB_PASSWORD='affine' DB_DATABASE='affine' DATABASE_URL="postgresql://$DB_USERNAME:$DB_PASSWORD@localhost:5432/$DB_DATABASE" AFFINE_COMMON_ENV=( -e REDIS_SERVER_HOST=localhost -e DATABASE_URL="$DATABASE_URL" -e AFFINE_INDEXER_ENABLED=false ) # Stop existing systemd-managed pod if present, to avoid conflicts on rerun echo "Stopping systemd-managed pod 'pod-$POD_NAME.service' if it exists..." if systemctl --user list-units --type=service --all 2>/dev/null | \ grep -q "pod-$POD_NAME.service"; then systemctl --user stop "pod-$POD_NAME.service" || true fi # Prepare directories mkdir -p "$POSTGRES_DATA_DIR" "$AFFINE_STORAGE_DIR" "$AFFINE_CONFIG_DIR" \ "$USER_SYSTEMD_DIR" # Create pod if not yet existing if ! podman pod exists "$POD_NAME"; then podman pod create -n "$POD_NAME" \ -p "$HOST_LOCAL_IP:$AFFINE_HOST_PORT:$AFFINE_CONTAINER_PORT" echo "Pod '$POD_NAME' created (rc=$?)" else echo "Pod '$POD_NAME' already exists." fi # Remove any old containers (ignore errors if they don't exist) podman rm -f "$AFFINE_CTR_NAME" || true podman rm -f "$AFFINE_MIGRATION_CTR_NAME" || true podman rm -f "$POSTGRES_CTR_NAME" || true podman rm -f "$REDIS_CTR_NAME" || true # Postgres container (pgvector for AFFiNE) podman run -d --name "$POSTGRES_CTR_NAME" --pod "$POD_NAME" \ -e POSTGRES_USER="$DB_USERNAME" \ -e POSTGRES_PASSWORD="$DB_PASSWORD" \ -e POSTGRES_DB="$DB_DATABASE" \ -e POSTGRES_INITDB_ARGS='--data-checksums' \ -v "$POSTGRES_DATA_DIR:/var/lib/postgresql/data:Z" \ "$POSTGRES_IMAGE" echo "Container '$POSTGRES_CTR_NAME' started (rc=$?)" # Wait for Postgres to be ready echo "Waiting for Postgres to be ready (pg_isready)..." for attempt in $(seq 1 30); do if podman exec "$POSTGRES_CTR_NAME" pg_isready -U "$DB_USERNAME" -d "$DB_DATABASE" \ >/dev/null 2>&1; then echo "Postgres is ready." break fi sleep 2 if [ "$attempt" -eq 30 ]; then echo "ERROR: Postgres did not become ready in time." >&2 exit 1 fi done # Redis container podman run -d --name "$REDIS_CTR_NAME" --pod "$POD_NAME" \ "$REDIS_IMAGE" echo "Container '$REDIS_CTR_NAME' started (rc=$?)" # Wait for Redis to be ready echo "Waiting for Redis to be ready..." for attempt in $(seq 1 30); do if podman exec "$REDIS_CTR_NAME" redis-cli ping >/dev/null 2>&1; then echo "Redis is ready." break fi sleep 2 if [ "$attempt" -eq 30 ]; then echo "ERROR: Redis did not become ready in time." >&2 exit 1 fi done # AFFiNE migration job (must complete before starting the server) echo "Running AFFiNE database migration..." podman run --rm --name "$AFFINE_MIGRATION_CTR_NAME" --pod "$POD_NAME" \ "${AFFINE_COMMON_ENV[@]}" \ -v "$AFFINE_STORAGE_DIR:/root/.affine/storage:Z" \ -v "$AFFINE_CONFIG_DIR:/root/.affine/config:Z" \ "$AFFINE_IMAGE" \ sh -c 'node ./scripts/self-host-predeploy.js' echo "AFFiNE migration completed (rc=$?)" # AFFiNE server container podman run -d --name "$AFFINE_CTR_NAME" --pod "$POD_NAME" \ "${AFFINE_COMMON_ENV[@]}" \ -v "$AFFINE_STORAGE_DIR:/root/.affine/storage:Z" \ -v "$AFFINE_CONFIG_DIR:/root/.affine/config:Z" \ "$AFFINE_IMAGE" echo "Container '$AFFINE_CTR_NAME' started (rc=$?)" # Generate systemd service files cd "$USER_SYSTEMD_DIR" podman generate systemd --name --new --files "$POD_NAME" echo "Generated systemd service files (rc=$?)" # Stop & remove live pod and containers podman pod stop --ignore --time 15 "$POD_NAME" podman pod rm -f --ignore "$POD_NAME" if podman pod exists "$POD_NAME"; then echo "ERROR: Pod $POD_NAME still exists." >&2 exit 1 else echo "Stopped & removed live pod $POD_NAME and containers." fi # Enable systemd user services systemctl --user daemon-reload # pod service (creates pod + containers) systemctl --user enable --now "pod-${POD_NAME}.service" systemctl --user is-enabled "pod-${POD_NAME}.service" systemctl --user is-active "pod-${POD_NAME}.service" echo "Enabled systemd service pod-${POD_NAME}.service (rc=$?)" echo "To view status: systemctl --user status pod-${POD_NAME}.service" echo "To view logs: journalctl --user -u pod-${POD_NAME}.service -f" # Wait for AFFiNE Web UI readiness CHECK_URL="http://$HOST_LOCAL_IP:$AFFINE_HOST_PORT" for attempt in $(seq 1 30); do if curl -fsS "$CHECK_URL" >/dev/null 2>&1; then echo "AFFiNE Web UI is reachable at $CHECK_URL." echo "AFFiNE GraphQL API is reachable at $CHECK_URL/graphql." break fi sleep 2 if [ "$attempt" -eq 30 ]; then echo "timeout error." >&2 exit 1 fi done