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) <noreply@anthropic.com>
This commit is contained in:
12
CLAUDE.md
Normal file
12
CLAUDE.md
Normal file
@@ -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
|
||||||
167
create_pod_affine.sh
Executable file
167
create_pod_affine.sh
Executable file
@@ -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
|
||||||
64
prompt_shell-script.md
Normal file
64
prompt_shell-script.md
Normal file
@@ -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.
|
||||||
Reference in New Issue
Block a user