diff --git a/.env.docker.sample b/.env.docker.sample index e69d0fe..b21b599 100644 --- a/.env.docker.sample +++ b/.env.docker.sample @@ -11,3 +11,19 @@ POSTGRES_MAIN_DB=soaricarus_db POSTGRES_MAIN_HOST=main_db POSTGRES_MAIN_PORT=5432 DATABASE_URL=postgres://${POSTGRES_MAIN_USER}:${POSTGRES_MAIN_PASSWORD}@${POSTGRES_MAIN_HOST}:${POSTGRES_MAIN_PORT}/${POSTGRES_MAIN_DB} + +GARAGE_RPC_SECRET=c2bde5c3f9c04116a7563e25ea5ba6fab25319daa2fe8a952ad57d0b8159b590 +GARAGE_METADATA_DIR=/var/lib/garage/meta +GARAGE_DATA_DIR=/var/lib/garage/data +GARAGE_DB_ENGINE=sqlite +GARAGE_REPLICATION_FACTOR=1 +GARAGE_RPC_BIND_ADDR=[::]:3901 +GARAGE_S3_API_BIND_ADDR=[::]:3900 +GARAGE_S3_WEB_BIND_ADDR=[::]:3902 +GARAGE_S3_REGION=maze +GARAGE_ROOT_DOMAIN=.localhost +AWS_DEFAULT_REGION=maze +AWS_SECRET_ACCESS_KEY=3o4n7yfo34985yrfcq9238765ry3928475rcy349857ty394578cy +AWS_ACCESS_KEY_ID=2q39fnoy59q2347yfdrc327894ryfc0q2834rcyq023897 + +S3_BUCKET_NAME=soaricarus-storage diff --git a/Dockerfile b/Dockerfile index 11cfe6b..0c2ac50 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,6 +30,10 @@ RUN --mount=type=ssh mkdir src && \ COPY src ./src COPY .env ./.env COPY migrations ./migrations +COPY scripts/init-garage.sh /scripts/init-garage.sh + +# Make it executable +RUN chmod +x /scripts/init-garage.sh RUN --mount=type=ssh \ cargo build --release --quiet diff --git a/docker-compose.yaml b/docker-compose.yaml index bb2612c..9960eaf 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -11,6 +11,8 @@ services: - "8000:8000" env_file: - .env + volumes: + - shared_credentials:/shared:ro # Mount as read-only depends_on: main_db: condition: service_healthy @@ -56,6 +58,44 @@ services: - auth_db networks: - soaricarus-network + # --- storage service --- + maze: + build: + context: ./garage + dockerfile: Dockerfile + container_name: maze + restart: unless-stopped + ports: + - "3900:3900" # S3 API + - "3901:3901" # RPC for cluster communication + - "3902:3902" # S3 web interface + - "3903:3903" # Admin API + volumes: + - ./garage.toml:/etc/garage.toml:ro + - ./garage/entrypoint.sh:/entrypoint.sh:ro + - shared_credentials:/shared + - object_data:/var/lib/garage + # - ./scripts:/scripts:ro # ← ADD THIS - mount the scripts directory + env_file: + - .env + environment: + - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} + - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} + - GARAGE_RPC_SECRET=${GARAGE_RPC_SECRET} + - BUCKET_NAME=${S3_BUCKET_NAME} + # entrypoint: ["/entrypoint.sh"] + # command: [] + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:3901/v0/status || exit 1"] + # test: ["CMD-SHELL", "echo 'What is to be done?'"] + # test: ["CMD-SHELL", "nc -z localhost 3900 || exit 1"] + # test: ["CMD-SHELL", "/garage status | grep -q 'Health: ok' || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - soaricarus-network # PostgreSQL Database Service @@ -113,6 +153,10 @@ volumes: driver: local # Use the default local driver postgres_data_auth: driver: local # Use the default local driver + object_data: + driver: local # Use the default local driver + shared_credentials: # New shared volume + driver: local networks: soaricarus-network: diff --git a/garage.toml b/garage.toml new file mode 100644 index 0000000..6c3d0ba --- /dev/null +++ b/garage.toml @@ -0,0 +1,16 @@ +metadata_dir = "/var/lib/garage/meta" +data_dir = "/var/lib/garage/data" +db_engine = "sqlite" +replication_factor = 1 + +rpc_bind_addr = "[::]:3901" +rpc_secret = "${GARAGE_RPC_SECRET}" + +[s3_api] +s3_region = "maze" +api_bind_addr = "[::]:3900" +root_domain = ".localhost" + +[s3_web] +bind_addr = "[::]:3902" +root_domain = ".localhost" diff --git a/garage/Dockerfile b/garage/Dockerfile new file mode 100644 index 0000000..5f4acaa --- /dev/null +++ b/garage/Dockerfile @@ -0,0 +1,18 @@ +# Use a base image with a shell (Alpine) +FROM alpine:latest + +# Install necessary tools +RUN apk add --no-cache curl + +# Copy the Garage binary from the official image +COPY --from=dxflrs/garage:v2.3.0 /garage /usr/local/bin/garage + +# Copy the entrypoint script +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# Set the entrypoint +ENTRYPOINT ["/entrypoint.sh"] + +# Pass through any arguments to Garage +CMD [] diff --git a/garage/entrypoint.sh b/garage/entrypoint.sh new file mode 100755 index 0000000..5273235 --- /dev/null +++ b/garage/entrypoint.sh @@ -0,0 +1,103 @@ +#!/bin/sh +set -e + +BUCKET_NAME="${BUCKET_NAME:-soaricarus-storage}" +CREDENTIALS_FILE="/shared/garage-credentials.env" +SETUP_DONE="/shared/setup.done" +GARAGE_BIN="/usr/local/bin/garage" + +# Function to write credentials +write_credentials() { + local access_key="$1" + local secret_key="$2" + mkdir -p "$(dirname "$CREDENTIALS_FILE")" + cat > "$CREDENTIALS_FILE" << EOF +GARAGE_ACCESS_KEY_ID=$access_key +GARAGE_SECRET_ACCESS_KEY=$secret_key +GARAGE_BUCKET_NAME=$BUCKET_NAME +GARAGE_ENDPOINT=http://localhost:3900 +EOF + touch "$SETUP_DONE" + echo "✅ Credentials saved to $CREDENTIALS_FILE" +} + +# Check if setup already completed +if [ -f "$SETUP_DONE" ]; then + echo "â„šī¸ Setup already completed. Starting Garage..." + exec $GARAGE_BIN "$@" +fi + +echo "🔧 First-time setup - initializing Garage..." + +# Start Garage in background for setup (just the binary, no daemon subcommand) +echo "âŗ Starting Garage in background..." +$GARAGE_BIN "$@" & +GARAGE_PID=$! + +# Wait for Garage to be ready +echo "âŗ Waiting for Garage API..." +until curl -s -f "http://localhost:3901/v0/status" > /dev/null 2>&1; do + echo " Not ready yet, sleeping 2s..." + sleep 2 +done +echo "✅ Garage API is responding!" + +# Apply cluster layout +if ! $GARAGE_BIN layout show 2>/dev/null | grep -q "Current cluster layout"; then + echo "đŸ“Ļ Applying cluster layout..." + NODE_ID=$($GARAGE_BIN node id | head -n1) + $GARAGE_BIN layout assign --version 1 "$NODE_ID" + $GARAGE_BIN layout apply --version 1 + echo " ✅ Layout applied" + sleep 2 +else + echo "â„šī¸ Layout already applied" +fi + +# Create bucket +if ! $GARAGE_BIN bucket info "$BUCKET_NAME" 2>/dev/null; then + echo "đŸ“Ļ Creating bucket: $BUCKET_NAME" + $GARAGE_BIN bucket create "$BUCKET_NAME" +else + echo "â„šī¸ Bucket $BUCKET_NAME already exists" +fi + +# Handle key +if [ -n "$AWS_ACCESS_KEY_ID" ] && [ -n "$AWS_SECRET_ACCESS_KEY" ]; then + echo "🔑 Using pre-defined credentials from .env" + if ! $GARAGE_BIN key info "$AWS_ACCESS_KEY_ID" 2>/dev/null; then + $GARAGE_BIN key import "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY" "app-key" + echo " ✅ Key imported: $AWS_ACCESS_KEY_ID" + else + echo " â„šī¸ Key already exists: $AWS_ACCESS_KEY_ID" + fi + ACCESS_KEY="$AWS_ACCESS_KEY_ID" + SECRET_KEY="$AWS_SECRET_ACCESS_KEY" +else + echo "🔑 Generating new key..." + KEY_OUTPUT=$($GARAGE_BIN key create --name "app-key") + echo "$KEY_OUTPUT" + ACCESS_KEY=$(echo "$KEY_OUTPUT" | grep 'Key ID:' | awk '{print $3}') + SECRET_KEY=$(echo "$KEY_OUTPUT" | grep 'Secret key:' | awk '{print $3}') + echo " ✅ Key generated: $ACCESS_KEY" +fi + +# Grant permissions +$GARAGE_BIN bucket allow --read --write --owner "$BUCKET_NAME" --key "$ACCESS_KEY" +echo " ✅ Permissions granted" + +# Save credentials +write_credentials "$ACCESS_KEY" "$SECRET_KEY" + +echo "🎉 Setup complete!" +echo " Bucket: $BUCKET_NAME" +echo " Access Key: $ACCESS_KEY" + +# Stop background Garage +echo "âšī¸ Stopping background Garage..." +kill "$GARAGE_PID" +wait "$GARAGE_PID" 2>/dev/null || true + +# Start Garage in foreground +echo "🚀 Starting Garage in foreground..." +exec $GARAGE_BIN "$@" diff --git a/garage/full-init-garage.sh b/garage/full-init-garage.sh new file mode 100644 index 0000000..c93be4b --- /dev/null +++ b/garage/full-init-garage.sh @@ -0,0 +1,109 @@ +#!/bin/sh +set -e + +BUCKET_NAME="${BUCKET_NAME:-soaricarus-storage}" +CREDENTIALS_FILE="/shared/garage-credentials.env" +SETUP_DONE="/shared/setup.done" + +# Function to write credentials to shared volume +write_credentials() { + local access_key="$1" + local secret_key="$2" + mkdir -p "$(dirname "$CREDENTIALS_FILE")" + cat > "$CREDENTIALS_FILE" << EOF +GARAGE_ACCESS_KEY_ID=$access_key +GARAGE_SECRET_ACCESS_KEY=$secret_key +GARAGE_BUCKET_NAME=$BUCKET_NAME +GARAGE_ENDPOINT=http://maze:3900 +EOF + touch "$SETUP_DONE" + echo "✅ Credentials saved to $CREDENTIALS_FILE" +} + +# Check if setup already done +if [ -f "$SETUP_DONE" ]; then + echo "â„šī¸ Setup already completed. Starting Garage..." + # Source credentials if needed for the main Garage process + # shellcheck source=/dev/null + [ -f "$CREDENTIALS_FILE" ] && . "$CREDENTIALS_FILE" + exec /usr/local/bin/garage "$@" +fi + +echo "🔧 Running initial setup..." + +# Start Garage in background for setup +echo "âŗ Starting Garage in background..." +/usr/local/bin/garage "$@" & +GARAGE_PID=$! + +# Wait for Garage API to be ready +echo "âŗ Waiting for Garage API..." +# until curl -s -f "http://localhost:3900/v0/status" > /dev/null 2>&1; do +# echo " Not ready yet, sleeping 2s..." +# sleep 2 +#done +echo "✅ Garage API is responding!" + +# Apply layout if not already applied +if ! /usr/local/bin/garage layout show 2>/dev/null | grep -q "Current cluster layout"; then + echo "đŸ“Ļ Applying cluster layout..." + NODE_ID=$(/usr/local/bin/garage node id | head -n1) + /usr/local/bin/garage layout assign --version 1 "$NODE_ID" + /usr/local/bin/garage layout apply --version 1 + echo " ✅ Layout applied" + sleep 3 +else + echo "â„šī¸ Layout already applied" +fi + +echo "Laid out" + +# Create bucket if it doesn't exist +if ! /usr/local/bin/garage bucket info "$BUCKET_NAME" 2>/dev/null; then + echo "đŸ“Ļ Creating bucket: $BUCKET_NAME" + /usr/local/bin/garage bucket create "$BUCKET_NAME" +else + echo "â„šī¸ Bucket $BUCKET_NAME already exists" +fi + +# Create or import key +# We'll use the credentials from .env if provided, otherwise generate new ones +if [ -n "$AWS_ACCESS_KEY_ID" ] && [ -n "$AWS_SECRET_ACCESS_KEY" ]; then + echo "🔑 Using pre-defined AWS credentials from .env" + if ! /usr/local/bin/garage key info "$AWS_ACCESS_KEY_ID" 2>/dev/null; then + # Key doesn't exist, import it + /usr/local/bin/garage key import "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY" "app-key" + echo " ✅ Key imported: $AWS_ACCESS_KEY_ID" + else + echo " â„šī¸ Key already exists: $AWS_ACCESS_KEY_ID" + fi + ACCESS_KEY="$AWS_ACCESS_KEY_ID" + SECRET_KEY="$AWS_SECRET_ACCESS_KEY" +else + echo "🔑 Generating new key..." + KEY_OUTPUT=$(/usr/local/bin/garage key create --name "app-key") + echo "$KEY_OUTPUT" + ACCESS_KEY=$(echo "$KEY_OUTPUT" | grep 'Key ID:' | awk '{print $3}') + SECRET_KEY=$(echo "$KEY_OUTPUT" | grep 'Secret key:' | awk '{print $3}') + echo " ✅ Key generated: $ACCESS_KEY" +fi + +# Grant permissions +/usr/local/bin/garage bucket allow --read --write --owner "$BUCKET_NAME" --key "$ACCESS_KEY" +echo " ✅ Permissions granted" + +# Write credentials to shared volume +write_credentials "$ACCESS_KEY" "$SECRET_KEY" + +echo "🎉 Setup complete!" +echo " Bucket: $BUCKET_NAME" +echo " Access Key: $ACCESS_KEY" + +# Stop the background Garage process +echo "âšī¸ Stopping background Garage process..." +kill "$GARAGE_PID" +wait "$GARAGE_PID" 2>/dev/null || true + +echo "🚀 Starting Garage in foreground..." +exec /usr/local/bin/garage "$@" + diff --git a/garage/init-garage.sh b/garage/init-garage.sh new file mode 100755 index 0000000..1650ede --- /dev/null +++ b/garage/init-garage.sh @@ -0,0 +1,36 @@ +#!/bin/sh +set -e + +BUCKET_NAME="${BUCKET_NAME:-soaricarus-storage}" +CREDENTIALS_FILE="/shared/garage-credentials.env" +SETUP_DONE="/shared/setup.done" + +echo "🔧 Running initial setup..." + +# Start Garage in background for setup +echo "âŗ Starting Garage in background..." +/usr/local/bin/garage "$@" & +GARAGE_PID=$! + +# Wait for Garage API to be ready +echo "âŗ Waiting for Garage API..." +# until curl -s -f "http://localhost:3900/v0/status" > /dev/null 2>&1; do +# echo " Not ready yet, sleeping 2s..." +# sleep 2 +#done +echo "✅ Garage API is responding!" + +# Apply layout if not already applied +#if ! /usr/local/bin/garage layout show 2>/dev/null | grep -q "Current cluster layout"; then +# echo "đŸ“Ļ Applying cluster layout..." +# NODE_ID=$(/usr/local/bin/garage node id | head -n1) +# echo $NODE_ID + # /usr/local/bin/garage layout assign --version 1 "$NODE_ID" + # /usr/local/bin/garage layout apply --version 1 +# echo " ✅ Layout applied" +# sleep 3 +#else + # echo "â„šī¸ Layout already applied" +# fi + +echo "Laid out" diff --git a/scripts/init-garage.sh b/scripts/init-garage.sh new file mode 100755 index 0000000..b6b3792 --- /dev/null +++ b/scripts/init-garage.sh @@ -0,0 +1,84 @@ +#!/bin/sh +set -e + +BUCKET_NAME="soaricarus-storage" + +echo "âŗ Waiting for Garage to be ready..." +# until docker exec maze curl -s -f "http://localhost:3901/v0/status" > /dev/null 2>&1; do +until docker exec maze nc -z "localhost 3900" > /dev/null 2>&1; do + echo " Garage not ready yet, retrying in 2s..." + sleep 2 +done +echo "✅ Garage API is responding!" + +sleep 3 + +# Check if layout is already applied +if docker exec maze garage layout show 2>/dev/null | grep -q "Current cluster layout"; then + echo "â„šī¸ Layout already applied, checking bucket..." +else + echo "đŸ“Ļ Applying cluster layout..." + + # Get node ID + MAX_RETRIES=10 + RETRY_COUNT=0 + NODE_ID="" + while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do + NODE_ID=$(docker exec maze garage node id 2>/dev/null | head -n1) + if [ -n "$NODE_ID" ]; then + break + fi + RETRY_COUNT=$((RETRY_COUNT + 1)) + echo " Waiting for node ID (attempt $RETRY_COUNT/$MAX_RETRIES)..." + sleep 2 + done + + if [ -z "$NODE_ID" ]; then + echo "❌ Failed to get node ID after $MAX_RETRIES attempts" + exit 1 + fi + + echo " Node ID: $NODE_ID" + docker exec maze garage layout assign --version 1 "$NODE_ID" + docker exec maze garage layout apply --version 1 + echo " ✅ Layout applied" + + sleep 3 +fi + +# Check if bucket exists +if docker exec maze garage bucket info "${BUCKET_NAME}" 2>/dev/null; then + echo "â„šī¸ Bucket '${BUCKET_NAME}' already exists. Skipping setup." + exit 0 +fi + +echo "đŸ“Ļ Creating bucket and configuring permissions..." + +# Create the bucket +docker exec maze garage bucket create "${BUCKET_NAME}" +echo " ✅ Bucket created: ${BUCKET_NAME}" + +# Check if key exists +if docker exec maze garage key info "${AWS_ACCESS_KEY_ID}" 2>/dev/null; then + echo " â„šī¸ Key already exists: ${AWS_ACCESS_KEY_ID}" +else + docker exec maze garage key create \ + --name "app-key" \ + "${AWS_ACCESS_KEY_ID}" \ + "${AWS_SECRET_ACCESS_KEY}" + echo " ✅ Key created: ${AWS_ACCESS_KEY_ID}" +fi + +# Grant permissions +docker exec maze garage bucket allow \ + --read \ + --write \ + --owner \ + "${BUCKET_NAME}" \ + --key "${AWS_ACCESS_KEY_ID}" +echo " ✅ Permissions granted" + +echo "🎉 Setup complete!" +echo " Bucket: ${BUCKET_NAME}" +echo " Access Key: ${AWS_ACCESS_KEY_ID}" +echo " Endpoint: http://maze:3900"