From 626a9516810fc04f5e73f967ce921558fe58d8bd1882659b9ba03650728d97b6 Mon Sep 17 00:00:00 2001 From: pln Date: Sun, 15 Mar 2026 16:28:36 +0100 Subject: [PATCH] Add AFFiNE pod creation script with PostgreSQL and Redis Shell script to create a rootless Podman pod running AFFiNE v0.26.3 with pgvector/PostgreSQL and Redis, managed via systemd user services. Exposes AFFiNE Web UI and GraphQL API on port 8092. Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 12 +++ create_pod_affine.sh | 167 +++++++++++++++++++++++++++++++++++++++++ prompt_shell-script.md | 64 ++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 CLAUDE.md create mode 100755 create_pod_affine.sh create mode 100644 prompt_shell-script.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..52e6e20 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,12 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +This is a personal `~/bin` utilities repository. It is currently empty and ready for scripts and tools. + +## Conventions + +- Main branch: `main` +- Scripts should be executable and include appropriate shebangs diff --git a/create_pod_affine.sh b/create_pod_affine.sh new file mode 100755 index 0000000..7971ab8 --- /dev/null +++ b/create_pod_affine.sh @@ -0,0 +1,167 @@ +#!/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 diff --git a/prompt_shell-script.md b/prompt_shell-script.md new file mode 100644 index 0000000..0bee9e0 --- /dev/null +++ b/prompt_shell-script.md @@ -0,0 +1,64 @@ +# Podman shell script for AFFiNE service + +## Motivation + +A Podman Pod is needed on a Linux server which provides an AFFiNE service. +This Podman Pod needs to be created and started with a shell script which needs to be designed. + +The AFFiNE service will be used by humans and by AI agents; it will also be part of a design environment for designing a special MCP server for AFFiNE. + +## Shell script requirements + +Requirements for the shell script: +- Must use a container image with a Pinned Tag (for exact consistency) which points to the AFFiNE version 0.26.3. The name likely is something like "ghcr.io/toeverything/affine:0.26.3". +- Must provide the AFFiNE web user interface at port 8092. +- Must provide the GraphQL API. Background: The AFFiNE web and desktop apps use an internal GraphQL API to communicate with the backend. There is a /graphql endpoint but it is not documented for third-party use. +- Must be in folder /home/pln/bin. +- Must have the name create_pod_affine.sh + +The shell script shall be run by user pln which has permissions to run rootless pods. + +## Shell script style + +The needed shell script must have the same style as other shell scripts on the server. + +These files are examples: +/home/lwc/bin/create_pod_langflow.sh +/home/krt/bin/create_pod_qdrant.sh + +## Your tasks + +### Ask first + +Before starting to design the shell script, ask between two and five questions to fully understand the situation, your tasks and the objectives. + +### Identify the container image + +Find the container image with Pinned Tag pointing to AFFiNE version 0.26.3. + +### Write the shell script + +Write the shell script. + +### Test the shell script + +Run the shell script and test it. + +### Redesign if necessary + +If the test failed, understand the problem, improve the shell script and go back to Test the shell script. + +Repeat this in a loop up to five times. + +## Your objectives + +Your objectives are: +- All requirements are fulfilled. +- AFFiNE web user interface shows up at 127.0.0.1:8092. +- The AFFiNE GraphQL API shows up under 127.0.0.1:8092 at /graphql or another link. + +## Your behaviour + +If it is not possible to achieve your objectives, interrupt and ask me. + +Complete all your tasks without asking in between if you can achieve your objectives.