From edf05d73ee8a96f13d06189774f6356d2cd5cfec Mon Sep 17 00:00:00 2001 From: Peter O'Leary Date: Sat, 22 Nov 2025 11:06:05 -0800 Subject: [PATCH 1/3] Initial vibe coded results (non-working) --- backend/.dockerignore | 59 ++++++++++ backend/.env.example | 29 +++-- backend/Dockerfile | 40 +++++++ backend/README.Docker.md | 227 +++++++++++++++++++++++++++++++++++++ backend/docker-compose.yml | 86 ++++++++++++++ backend/router/router.go | 7 ++ 6 files changed, 437 insertions(+), 11 deletions(-) create mode 100644 backend/.dockerignore create mode 100644 backend/Dockerfile create mode 100644 backend/README.Docker.md create mode 100644 backend/docker-compose.yml diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..47589a3 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,59 @@ +# Git +.git +.gitignore + +# Environment files +.env +.env.local +.env.*.local + +# Logs +logs/ +*.log + +# Documentation +*.md +README* + +# Test files +*_test.go +test/ + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Temporary files +*.tmp +*.temp + +# Dependencies +node_modules/ + +# Build artifacts +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Backup files +*.bak +*.backup + +# Docker files (avoid recursion) +Dockerfile* +docker-compose* +.dockerignore \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example index 04add26..2cc5836 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -2,20 +2,27 @@ PORT=8080 #DATABASE -DB_TYPE=x -DB_URL= -DB_USER= # only needed for postgres -DB_PASSWORD= # only needed for postgres +DB_TYPE=postgres +DB_URL=postgres://sfluv_user:sfluv_password@localhost:5432/sfluv_db?sslmode=disable +DB_USER=sfluv_user +DB_PASSWORD=sfluv_password +DB_NAME=sfluv_db #AUTH -ADMIN_KEY=x +ADMIN_KEY=your_admin_key_here #CHAIN -TOKEN_ID=x -TOKEN_DECIMALS=x -RPC_URL=x -BOT_KEY=x +TOKEN_ID=your_token_id +TOKEN_DECIMALS=18 +RPC_URL=your_rpc_url +BOT_KEY=your_bot_private_key #PRIVY -PRIVY_APP_ID= -PRIVY_VKEY="" +PRIVY_APP_ID=your_privy_app_id +PRIVY_VKEY="your_privy_verification_key" + +# Docker-specific variables (used in docker-compose.yml) +# These can be overridden if needed +# POSTGRES_USER=sfluv_user +# POSTGRES_PASSWORD=sfluv_password +# POSTGRES_DB=sfluv_db diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..9b72b27 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,40 @@ +# Use the official Go image as the base image +FROM golang:1.24-alpine AS builder + +# Set the working directory inside the container +WORKDIR /app + +# Install git and other dependencies +RUN apk add --no-cache git ca-certificates tzdata + +# Copy go mod and sum files +COPY go.mod go.sum ./ + +# Download dependencies +RUN go mod download + +# Copy the source code +COPY . . + +# Build the application +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . + +# Start a new stage from scratch +FROM alpine:latest + +# Install ca-certificates for HTTPS requests +RUN apk --no-cache add ca-certificates tzdata + +WORKDIR /root/ + +# Copy the pre-built binary file from the previous stage +COPY --from=builder /app/main . + +# Create logs directory +RUN mkdir -p logs/prod logs/test/app + +# Expose port +EXPOSE 8080 + +# Command to run the executable +CMD ["./main"] \ No newline at end of file diff --git a/backend/README.Docker.md b/backend/README.Docker.md new file mode 100644 index 0000000..3579221 --- /dev/null +++ b/backend/README.Docker.md @@ -0,0 +1,227 @@ +# Docker Setup for SFLuv Backend + +This guide explains how to run the SFLuv backend application using Docker and Docker Compose. + +## Prerequisites + +- Docker +- Docker Compose + +## Quick Start + +1. **Clone the repository and navigate to the backend directory** + ```bash + cd /path/to/sfluv/app/backend + ``` + +2. **Create your environment file** + ```bash + cp .env.example .env + ``` + + Edit `.env` with your actual values: + ```bash + nano .env + ``` + +3. **Start the services** + ```bash + docker-compose up -d + ``` + +4. **Check the logs** + ```bash + docker-compose logs -f backend + ``` + +5. **Access the application** + - Backend API: http://localhost:8080 + - Health check: http://localhost:8080/health + - PostgreSQL: localhost:5432 + - Redis: localhost:6379 + +## Services + +### Backend Application +- **Container name**: `sfluv-backend` +- **Port**: 8080 +- **Health check**: `/health` endpoint + +### PostgreSQL Database +- **Container name**: `sfluv-postgres` +- **Port**: 5432 +- **Default credentials**: See `.env.example` +- **Persistent storage**: Named volume `postgres_data` + +### Redis Cache +- **Container name**: `sfluv-redis` +- **Port**: 6379 +- **Persistent storage**: Named volume `redis_data` + +## Commands + +### Development Commands + +```bash +# Start all services in background +docker-compose up -d + +# Start with logs visible +docker-compose up + +# Stop all services +docker-compose down + +# Stop and remove volumes (WARNING: This will delete all data) +docker-compose down -v + +# Rebuild backend image +docker-compose build backend + +# Force rebuild without cache +docker-compose build --no-cache backend + +# View logs +docker-compose logs backend +docker-compose logs postgres +docker-compose logs redis + +# Follow logs +docker-compose logs -f backend + +# Execute commands in running containers +docker-compose exec backend sh +docker-compose exec postgres psql -U sfluv_user -d sfluv_db +docker-compose exec redis redis-cli +``` + +### Database Commands + +```bash +# Connect to PostgreSQL +docker-compose exec postgres psql -U sfluv_user -d sfluv_db + +# Create database backup +docker-compose exec postgres pg_dump -U sfluv_user sfluv_db > backup.sql + +# Restore database from backup +docker-compose exec -T postgres psql -U sfluv_user -d sfluv_db < backup.sql +``` + +### Maintenance Commands + +```bash +# View resource usage +docker-compose top + +# Check service health +docker-compose ps + +# Clean up unused Docker resources +docker system prune + +# Remove stopped containers +docker container prune + +# Remove unused images +docker image prune + +# Remove unused volumes (BE CAREFUL) +docker volume prune +``` + +## Environment Variables + +The following environment variables need to be set in your `.env` file: + +### Required +- `PORT`: Server port (default: 8080) +- `DB_USER`: Database user +- `DB_PASSWORD`: Database password +- `DB_NAME`: Database name +- `ADMIN_KEY`: Admin authentication key + +### Blockchain/Web3 +- `TOKEN_ID`: Token contract ID +- `TOKEN_DECIMALS`: Token decimals +- `RPC_URL`: Blockchain RPC URL +- `BOT_KEY`: Bot private key + +### Authentication +- `PRIVY_APP_ID`: Privy application ID +- `PRIVY_VKEY`: Privy verification key + +## Troubleshooting + +### Common Issues + +1. **Port already in use** + ```bash + # Check what's using the port + lsof -i :8080 + # Kill the process or change the port in .env + ``` + +2. **Database connection issues** + ```bash + # Check if PostgreSQL is running + docker-compose ps postgres + # Check PostgreSQL logs + docker-compose logs postgres + ``` + +3. **Permission issues with logs** + ```bash + # Create logs directory with proper permissions + mkdir -p logs/prod logs/test/app + chmod 755 logs logs/prod logs/test logs/test/app + ``` + +4. **Build issues** + ```bash + # Clean rebuild + docker-compose down + docker-compose build --no-cache + docker-compose up + ``` + +### Health Checks + +The application includes health checks for monitoring: + +- **Backend**: HTTP GET to `/health` +- **PostgreSQL**: `pg_isready` command +- **Redis**: Built-in health check + +Check health status: +```bash +docker-compose ps +``` + +### Logs Location + +- **Application logs**: `./logs/prod/app.log` +- **Docker logs**: `docker-compose logs [service_name]` + +## Production Considerations + +1. **Security** + - Change default passwords + - Use secrets management + - Enable SSL/TLS + - Restrict network access + +2. **Performance** + - Adjust resource limits + - Configure connection pooling + - Set up monitoring + +3. **Backup** + - Regular database backups + - Persistent volume snapshots + - Log rotation + +4. **Scaling** + - Use orchestration (Kubernetes) + - Load balancing + - Database clustering \ No newline at end of file diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml new file mode 100644 index 0000000..f10d17b --- /dev/null +++ b/backend/docker-compose.yml @@ -0,0 +1,86 @@ +version: '3.8' + +services: + # PostgreSQL Database + postgres: + image: postgres:15-alpine + container_name: sfluv-postgres + environment: + POSTGRES_USER: ${DB_USER:-sfluv_user} + POSTGRES_PASSWORD: ${DB_PASSWORD:-sfluv_password} + POSTGRES_DB: ${DB_NAME:-sfluv_db} + PGDATA: /var/lib/postgresql/data/pgdata + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro + networks: + - sfluv-network + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-sfluv_user} -d ${DB_NAME:-sfluv_db}"] + interval: 30s + timeout: 10s + retries: 3 + + # Go Backend Application + backend: + build: + context: . + dockerfile: Dockerfile + container_name: sfluv-backend + environment: + - PORT=${PORT:-8080} + - DB_TYPE=${DB_TYPE:-postgres} + - DB_URL=postgres://${DB_USER:-sfluv_user}:${DB_PASSWORD:-sfluv_password}@postgres:5432/${DB_NAME:-sfluv_db}?sslmode=disable + - DB_USER=${DB_USER:-sfluv_user} + - DB_PASSWORD=${DB_PASSWORD:-sfluv_password} + - ADMIN_KEY=${ADMIN_KEY} + - TOKEN_ID=${TOKEN_ID} + - TOKEN_DECIMALS=${TOKEN_DECIMALS} + - RPC_URL=${RPC_URL} + - BOT_KEY=${BOT_KEY} + - PRIVY_APP_ID=${PRIVY_APP_ID} + - PRIVY_VKEY=${PRIVY_VKEY} + ports: + - "8080:8080" + volumes: + - ./logs:/root/logs + networks: + - sfluv-network + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + # Redis Cache (optional, for future use) + redis: + image: redis:7-alpine + container_name: sfluv-redis + ports: + - "6379:6379" + volumes: + - redis_data:/data + networks: + - sfluv-network + restart: unless-stopped + command: redis-server --appendonly yes + +# Named volumes +volumes: + postgres_data: + driver: local + redis_data: + driver: local + +# Networks +networks: + sfluv-network: + driver: bridge \ No newline at end of file diff --git a/backend/router/router.go b/backend/router/router.go index 570a707..72a5877 100644 --- a/backend/router/router.go +++ b/backend/router/router.go @@ -26,6 +26,13 @@ func New(s *handlers.BotService, p *handlers.AppService) *chi.Mux { })) r.Use(m.AuthMiddleware) + // Health check endpoint + r.Get("/health", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status":"ok"}`)) + }) + AddBotRoutes(r, s, p) AddUserRoutes(r, p) AddAdminRoutes(r, p) From 3679c172f26de3584b986089f501af850052ef19 Mon Sep 17 00:00:00 2001 From: Peter O'Leary Date: Sun, 30 Nov 2025 07:17:38 -0800 Subject: [PATCH 2/3] Working Google Cloud Kubernetes --- backend/Dockerfile | 25 ++---- backend/Makefile | 3 + backend/db/db.go | 36 ++------ backend/deploy-gke.sh | 83 +++++++++++++++++ backend/deployment-1.yaml | 122 +++++++++++++++++++++++++ backend/docker-compose.yml | 35 ++------ backend/main.go | 19 +++- backend/secret-provider-class.yaml | 33 +++++++ backend/setup-secrets.sh | 137 +++++++++++++++++++++++++++++ 9 files changed, 413 insertions(+), 80 deletions(-) create mode 100644 backend/Makefile create mode 100644 backend/deploy-gke.sh create mode 100644 backend/deployment-1.yaml create mode 100644 backend/secret-provider-class.yaml create mode 100755 backend/setup-secrets.sh diff --git a/backend/Dockerfile b/backend/Dockerfile index 9b72b27..56800f5 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -17,24 +17,13 @@ RUN go mod download COPY . . # Build the application -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o main . # Start a new stage from scratch -FROM alpine:latest - -# Install ca-certificates for HTTPS requests -RUN apk --no-cache add ca-certificates tzdata - -WORKDIR /root/ - -# Copy the pre-built binary file from the previous stage -COPY --from=builder /app/main . - -# Create logs directory -RUN mkdir -p logs/prod logs/test/app - -# Expose port +FROM gcr.io/distroless/static:nonroot +WORKDIR /app +COPY --from=builder /app/main ./main +COPY --from=builder /app/go.mod ./go.mod EXPOSE 8080 - -# Command to run the executable -CMD ["./main"] \ No newline at end of file +USER nonroot:nonroot +ENTRYPOINT ["./main"] \ No newline at end of file diff --git a/backend/Makefile b/backend/Makefile new file mode 100644 index 0000000..0af6b97 --- /dev/null +++ b/backend/Makefile @@ -0,0 +1,3 @@ +docker build -t backend_sfluv . +docker tag backend_sfluv:latest us-central1-docker.pkg.dev/sfluv-app/sfluv-images/backend_sfluv:latest +docker push us-central1-docker.pkg.dev/sfluv-app/sfluv-images/backend_sfluv:latest \ No newline at end of file diff --git a/backend/db/db.go b/backend/db/db.go index 4563f0e..9aadd7c 100644 --- a/backend/db/db.go +++ b/backend/db/db.go @@ -3,9 +3,8 @@ package db import ( "context" "fmt" + "log" "os" - "path/filepath" - "runtime" "github.com/jackc/pgx/v5/pgxpool" ) @@ -25,38 +24,17 @@ func PgxDB(name string) (*pgxpool.Pool, error) { func MakeDbConnString(name string) string { dbUser := os.Getenv("DB_USER") dbPassword := os.Getenv("DB_PASSWORD") - dbPath := os.Getenv("DB_URL") + dbHost := os.Getenv("DB_HOST") - if dbPath == "" { - dbPath = "localhost:5432" + if dbHost == "" { + dbHost = "localhost:5432" } if dbUser == "" { dbUser = "postgres" } - return fmt.Sprintf("postgres://%s:%s@%s/%s", dbUser, dbPassword, dbPath, name) -} + log.Printf("DB_USER=%s", dbUser) + log.Printf("DB_HOST=%s", dbHost) -func getProjectRoot() (string, error) { - _, filename, _, ok := runtime.Caller(0) - if !ok { - return "", fmt.Errorf("failed to get caller information") - } - currentDir := filepath.Dir(filename) - - for { - // Check for common project root indicators - if _, err := os.Stat(filepath.Join(currentDir, "go.mod")); err == nil { - return currentDir, nil - } - if _, err := os.Stat(filepath.Join(currentDir, ".git")); err == nil { - return currentDir, nil - } - - parentDir := filepath.Dir(currentDir) - if parentDir == currentDir { // Reached file system root - return "", fmt.Errorf("project root not found") - } - currentDir = parentDir - } + return fmt.Sprintf("postgres://%s:%s@%s/%s", dbUser, dbPassword, dbHost, name) } diff --git a/backend/deploy-gke.sh b/backend/deploy-gke.sh new file mode 100644 index 0000000..30c7b03 --- /dev/null +++ b/backend/deploy-gke.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# Build and deploy script for Google Kubernetes Engine +set -e + +# Configuration +PROJECT_ID="${PROJECT_ID:-your-gcp-project-id}" +REGION="${REGION:-us-central1}" +CLUSTER_NAME="${CLUSTER_NAME:-sfluv-cluster}" +IMAGE_NAME="gcr.io/${PROJECT_ID}/sfluv-backend" +IMAGE_TAG="${IMAGE_TAG:-$(git rev-parse --short HEAD)}" + +echo "๐Ÿš€ Starting GKE deployment..." +echo "Project: $PROJECT_ID" +echo "Region: $REGION" +echo "Cluster: $CLUSTER_NAME" +echo "Image: $IMAGE_NAME:$IMAGE_TAG" + +# 1. Build the Docker image +echo "๐Ÿ“ฆ Building Docker image..." +docker build -f Dockerfile.gke -t $IMAGE_NAME:$IMAGE_TAG . +docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:latest + +# 2. Configure Docker for GCR +echo "๐Ÿ” Configuring Docker for Google Container Registry..." +gcloud auth configure-docker + +# 3. Push to Google Container Registry +echo "๐Ÿ“ค Pushing image to GCR..." +docker push $IMAGE_NAME:$IMAGE_TAG +docker push $IMAGE_NAME:latest + +# 4. Get GKE credentials +echo "๐Ÿ”‘ Getting GKE credentials..." +gcloud container clusters get-credentials $CLUSTER_NAME --region=$REGION --project=$PROJECT_ID + +# 5. Create secrets and configmaps +echo "๐Ÿ—๏ธ Creating Kubernetes secrets..." +kubectl create namespace sfluv --dry-run=client -o yaml | kubectl apply -f - + +# Create secrets (you'll need to provide actual values) +kubectl create secret generic sfluv-secrets \ + --from-literal=db-url="postgres://user:password@host:5432/dbname?sslmode=require" \ + --from-literal=admin-key="${ADMIN_KEY}" \ + --from-literal=bot-key="${BOT_KEY}" \ + --from-literal=privy-vkey="${PRIVY_VKEY}" \ + --namespace=sfluv \ + --dry-run=client -o yaml | kubectl apply -f - + +# Create configmap +kubectl create configmap sfluv-config \ + --from-literal=privy-app-id="${PRIVY_APP_ID}" \ + --from-literal=rpc-url="${RPC_URL}" \ + --from-literal=token-id="${TOKEN_ID}" \ + --from-literal=token-decimals="${TOKEN_DECIMALS}" \ + --namespace=sfluv \ + --dry-run=client -o yaml | kubectl apply -f - + +# 6. Update deployment with new image +echo "๐Ÿšข Deploying to Kubernetes..." +sed "s|gcr.io/YOUR_PROJECT_ID|gcr.io/${PROJECT_ID}|g" k8s-deployment.yaml | \ +sed "s|:latest|:${IMAGE_TAG}|g" | \ +kubectl apply -f - + +# 7. Wait for deployment to complete +echo "โณ Waiting for deployment to complete..." +kubectl rollout status deployment/sfluv-backend -n sfluv --timeout=300s + +# 8. Get service info +echo "โœ… Deployment complete!" +echo "" +echo "Service information:" +kubectl get services -n sfluv +echo "" +echo "Pod information:" +kubectl get pods -n sfluv +echo "" +echo "Ingress information:" +kubectl get ingress -n sfluv + +echo "" +echo "๐ŸŽ‰ Deployment successful!" +echo "Your application should be available at: https://api.sfluv.org" \ No newline at end of file diff --git a/backend/deployment-1.yaml b/backend/deployment-1.yaml new file mode 100644 index 0000000..f9f427c --- /dev/null +++ b/backend/deployment-1.yaml @@ -0,0 +1,122 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-1 + namespace: default + labels: + app: deployment-1 + app.kubernetes.io/managed-by: cloud-console +spec: + progressDeadlineSeconds: 600 + replicas: 3 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: deployment-1 + app.kubernetes.io/managed-by: cloud-console + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: deployment-1 + app.kubernetes.io/managed-by: cloud-console + spec: + serviceAccountName: sfluv-ksa + containers: + - env: + - name: PORT + value: "8080" + - name: DB_HOST + value: localhost:5432 + - name: DB_USER + value: postgres + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: sfluv-secrets + key: db-password + - name: ADMIN_KEY + valueFrom: + secretKeyRef: + name: sfluv-secrets + key: admin-key + - name: BOT_KEY + valueFrom: + secretKeyRef: + name: sfluv-secrets + key: bot-key + - name: TOKEN_ID + value: 0x881cad4f885c6701d8481c0ed347f6d35444ea7e + - name: TOKEN_DECIMALS + value: "1000000000000000000" + - name: RPC_URL + value: https://rpc.berachain.com/ + - name: PRIVY_APP_ID + value: cmc3nhnmc004yl10mwcmj4be3 + - name: PRIVY_VKEY + valueFrom: + secretKeyRef: + name: sfluv-secrets + key: privy-vkey + image: us-central1-docker.pkg.dev/sfluv-app/sfluv-images/backend_sfluv@sha256:8c0b5e88c86be1c54e12e5358362a18f048fa479c42a34b6721dffaf62638a9d + imagePullPolicy: IfNotPresent + name: backend-sfluv-sha256-1 + volumeMounts: + - name: secrets-store + mountPath: "/mnt/secrets" + readOnly: true + resources: + limits: + ephemeral-storage: 1Gi + requests: + cpu: 500m + ephemeral-storage: 1Gi + memory: 2Gi + securityContext: + capabilities: + drop: + - NET_RAW + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + - name: cloud-sql-proxy + image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.8.0 + args: + - "--structured-logs" + - "--port=5432" + - "sfluv-app:us-central1:sfluv-app-prod" + securityContext: + runAsNonRoot: true + capabilities: + drop: + - ALL + resources: + requests: + cpu: "100m" + memory: "128Mi" + limits: + cpu: "200m" + memory: "256Mi" + volumes: + - name: secrets-store + csi: + driver: secrets-store-gke.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: sfluv-secrets-spc + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 30 + tolerations: + - effect: NoSchedule + key: kubernetes.io/arch + operator: Equal + value: amd64 \ No newline at end of file diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index f10d17b..8d2af56 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -8,13 +8,11 @@ services: environment: POSTGRES_USER: ${DB_USER:-sfluv_user} POSTGRES_PASSWORD: ${DB_PASSWORD:-sfluv_password} - POSTGRES_DB: ${DB_NAME:-sfluv_db} PGDATA: /var/lib/postgresql/data/pgdata ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data - - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro networks: - sfluv-network restart: unless-stopped @@ -25,24 +23,16 @@ services: retries: 3 # Go Backend Application - backend: + sfluv: build: context: . dockerfile: Dockerfile container_name: sfluv-backend + env_file: + - .env environment: - - PORT=${PORT:-8080} - - DB_TYPE=${DB_TYPE:-postgres} - - DB_URL=postgres://${DB_USER:-sfluv_user}:${DB_PASSWORD:-sfluv_password}@postgres:5432/${DB_NAME:-sfluv_db}?sslmode=disable - - DB_USER=${DB_USER:-sfluv_user} - - DB_PASSWORD=${DB_PASSWORD:-sfluv_password} - - ADMIN_KEY=${ADMIN_KEY} - - TOKEN_ID=${TOKEN_ID} - - TOKEN_DECIMALS=${TOKEN_DECIMALS} - - RPC_URL=${RPC_URL} - - BOT_KEY=${BOT_KEY} - - PRIVY_APP_ID=${PRIVY_APP_ID} - - PRIVY_VKEY=${PRIVY_VKEY} + # Override database connection for Docker network + - DB_HOST=postgres ports: - "8080:8080" volumes: @@ -60,25 +50,10 @@ services: retries: 3 start_period: 40s - # Redis Cache (optional, for future use) - redis: - image: redis:7-alpine - container_name: sfluv-redis - ports: - - "6379:6379" - volumes: - - redis_data:/data - networks: - - sfluv-network - restart: unless-stopped - command: redis-server --appendonly yes - # Named volumes volumes: postgres_data: driver: local - redis_data: - driver: local # Networks networks: diff --git a/backend/main.go b/backend/main.go index aa87800..ff814b7 100644 --- a/backend/main.go +++ b/backend/main.go @@ -19,11 +19,13 @@ func main() { bdb, err := db.PgxDB("bot") if err != nil { - log.Fatal(fmt.Sprintf("error initializing bot db: %s\n", err)) + log.Fatalf("error initializing bot db: %s\n", err) + os.Exit(1) } pdb, err := db.PgxDB("app") if err != nil { - log.Fatal(fmt.Sprintf("error initializing app db: %s\n", err)) + log.Fatalf("error initializing app db: %s\n", err) + os.Exit(1) } botDb := db.Bot(bdb) @@ -33,7 +35,18 @@ func main() { return } - appLogger, err := logger.New("./logs/prod/app.log", "APP: ") + appLogFile := "./logs/prod/app.log" + + // create folders if they don't exist + if _, err := os.Stat("./logs/prod"); os.IsNotExist(err) { + err = os.MkdirAll("./logs/prod", os.ModePerm) + if err != nil { + fmt.Printf("error creating log directories: %s\n", err) + return + } + } + + appLogger, err := logger.New(appLogFile, "APP: ") if err != nil { fmt.Printf("error initializing app logger: %s\n", err) return diff --git a/backend/secret-provider-class.yaml b/backend/secret-provider-class.yaml new file mode 100644 index 0000000..94cb3d5 --- /dev/null +++ b/backend/secret-provider-class.yaml @@ -0,0 +1,33 @@ +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: sfluv-secrets-spc + namespace: default +spec: + provider: gke + parameters: + secrets: | + - resourceName: "projects/sfluv-app/secrets/DB_PASSWORD/versions/latest" + path: "db-password" + region: "us-central1" + - resourceName: "projects/sfluv-app/secrets/ADMIN_KEY/versions/latest" + path: "admin-key" + region: "us-central1" + - resourceName: "projects/sfluv-app/secrets/BOT_KEY/versions/latest" + path: "bot-key" + region: "us-central1" + - resourceName: "projects/sfluv-app/secrets/PRIVY_VKEY/versions/latest" + path: "privy-vkey" + region: "us-central1" + secretObjects: + - secretName: sfluv-secrets + type: Opaque + data: + - objectName: db-password + key: db-password + - objectName: admin-key + key: admin-key + - objectName: bot-key + key: bot-key + - objectName: privy-vkey + key: privy-vkey \ No newline at end of file diff --git a/backend/setup-secrets.sh b/backend/setup-secrets.sh new file mode 100755 index 0000000..94891f9 --- /dev/null +++ b/backend/setup-secrets.sh @@ -0,0 +1,137 @@ +#!/bin/bash + +# Setup script for Google Cloud Secret Manager integration with GKE +set -e + +# Configuration +PROJECT_ID="${PROJECT_ID:-sfluv-app}" +REGION="${REGION:-us-central1}" +CLUSTER_NAME="${CLUSTER_NAME:-sfluv-deployment-1-cluster}" +KSA_NAME="sfluv-ksa" +GSA_NAME="sfluv-gsa" +NAMESPACE="default" + +echo "๐Ÿ” Setting up Google Secret Manager integration for GKE..." +echo "Project: $PROJECT_ID" +echo "Region: $REGION" +echo "Cluster: $CLUSTER_NAME" + +# 1. Enable required APIs +echo "๐Ÿ”Œ Enabling required Google Cloud APIs..." +gcloud services enable secretmanager.googleapis.com --project=$PROJECT_ID +gcloud services enable container.googleapis.com --project=$PROJECT_ID + +# 2. Enable Secret Manager on GKE cluster +echo "๐Ÿ”ง Enabling Secret Manager on GKE cluster..." +gcloud container clusters update $CLUSTER_NAME \ + --region=$REGION \ + --enable-secret-manager \ + --project=$PROJECT_ID + +# 3. Create Google Service Account (if not exists) +echo "๐Ÿ‘ค Creating Google Service Account..." +if ! gcloud iam service-accounts describe $GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com --project=$PROJECT_ID >/dev/null 2>&1; then + gcloud iam service-accounts create $GSA_NAME \ + --display-name="SFLuv GKE Service Account" \ + --description="Service account for SFLuv application to access secrets" \ + --project=$PROJECT_ID +else + echo "Google Service Account $GSA_NAME already exists" +fi + +# 4. Grant Secret Manager permissions +echo "๐Ÿ—๏ธ Granting Secret Manager permissions..." +gcloud projects add-iam-policy-binding $PROJECT_ID \ + --member="serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \ + --role="roles/secretmanager.secretAccessor" + +# 5. Create Kubernetes Service Account and bind to Google Service Account +echo "๐ŸŽญ Setting up Kubernetes Service Account..." +kubectl create serviceaccount $KSA_NAME \ + --namespace=$NAMESPACE \ + --dry-run=client -o yaml | kubectl apply -f - + +kubectl annotate serviceaccount $KSA_NAME \ + --namespace=$NAMESPACE \ + iam.gke.io/gcp-service-account=${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com \ + --overwrite + +# 6. Bind the Kubernetes Service Account to Google Service Account +echo "๐Ÿ”— Binding service accounts for Workload Identity..." +gcloud iam service-accounts add-iam-policy-binding \ + ${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com \ + --role roles/iam.workloadIdentityUser \ + --member "serviceAccount:${PROJECT_ID}.svc.id.goog[${NAMESPACE}/${KSA_NAME}]" \ + --project=$PROJECT_ID + +# 7. Function to create regional secret if it doesn't exist +create_regional_secret_if_not_exists() { + local secret_name=$1 + local secret_value=$2 + + if ! gcloud secrets describe $secret_name --project=$PROJECT_ID >/dev/null 2>&1; then + echo "Creating regional secret: $secret_name" + echo -n "$secret_value" | gcloud secrets create $secret_name \ + --replication-policy=user-managed \ + --locations=$REGION \ + --data-file=- \ + --project=$PROJECT_ID + else + echo "Secret $secret_name already exists" + fi +} + +echo "๐Ÿ“ Creating regional secrets in Google Secret Manager..." + +# Check if required environment variables are set +if [[ -z "$DB_PASSWORD" || -z "$ADMIN_KEY" || -z "$BOT_KEY" || -z "$PRIVY_VKEY" ]]; then + echo "โŒ Error: Required environment variables are not set." + echo "Please set the following environment variables before running this script:" + echo " export DB_PASSWORD='your_db_password'" + echo " export ADMIN_KEY='your_admin_key'" + echo " export BOT_KEY='your_bot_key'" + echo " export PRIVY_VKEY='your_privy_verification_key'" + echo "" + echo "Alternatively, you can source your .env file:" + echo " source .env.production && ./setup-secrets.sh" + exit 1 +fi + +echo "โœ… Using environment variables for secret values" + +# Create regional secrets with values from environment variables +create_regional_secret_if_not_exists "DB_PASSWORD" "$DB_PASSWORD" +create_regional_secret_if_not_exists "ADMIN_KEY" "$ADMIN_KEY" +create_regional_secret_if_not_exists "BOT_KEY" "$BOT_KEY" +create_regional_secret_if_not_exists "PRIVY_VKEY" "$PRIVY_VKEY" + +# 8. Apply SecretProviderClass +echo "๐Ÿš€ Applying SecretProviderClass..." +kubectl apply -f secret-provider-class.yaml + +echo "" +echo "โœ… Setup complete!" +echo "" +echo "๐Ÿ“‹ Usage:" +echo "To use this script, set the required environment variables first:" +echo " export DB_PASSWORD='your_actual_db_password'" +echo " export ADMIN_KEY='your_actual_admin_key'" +echo " export BOT_KEY='your_actual_bot_key'" +echo " export PRIVY_VKEY='your_actual_privy_verification_key'" +echo " ./setup-secrets.sh" +echo "" +echo "Or source your .env file:" +echo " source .env.production && ./setup-secrets.sh" +echo "" +echo "๐Ÿ“‹ Next steps:" +echo "1. Deploy your application:" +echo " kubectl apply -f deployment-1.yaml" +echo "" +echo "2. Check deployment status:" +echo " kubectl get pods -l app=deployment-1" +echo "" +echo "3. Verify secrets are mounted:" +echo " kubectl exec -it deployment/deployment-1 -- ls -la /mnt/secrets" +echo "" +echo "4. Check application logs:" +echo " kubectl logs deployment/deployment-1" \ No newline at end of file From 0ebbb7d840ccfdf87276deab29e8529608b83bff Mon Sep 17 00:00:00 2001 From: Peter O'Leary Date: Sun, 30 Nov 2025 08:06:04 -0800 Subject: [PATCH 3/3] Add teardown.sh script also --- backend/teardown.sh | 151 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100755 backend/teardown.sh diff --git a/backend/teardown.sh b/backend/teardown.sh new file mode 100755 index 0000000..caaac1f --- /dev/null +++ b/backend/teardown.sh @@ -0,0 +1,151 @@ +#!/bin/bash + +# Teardown script for Google Cloud resources +# This script will remove all resources created for the SFLuv application +set -e + +# Configuration +PROJECT_ID="${PROJECT_ID:-sfluv-app}" +REGION="${REGION:-us-central1}" +CLUSTER_NAME="${CLUSTER_NAME:-sfluv-deployment-1-cluster}" +KSA_NAME="sfluv-ksa" +GSA_NAME="sfluv-gsa" +NAMESPACE="default" +REPOSITORY_NAME="sfluv-images" + +echo "๐Ÿ—‘๏ธ SFLuv Google Cloud Teardown Script" +echo "======================================" +echo "โš ๏ธ This will DELETE the following resources:" +echo " โ€ข Kubernetes cluster: $CLUSTER_NAME" +echo " โ€ข Google Service Account: $GSA_NAME" +echo " โ€ข Secret Manager secrets: DB_PASSWORD, ADMIN_KEY, BOT_KEY, PRIVY_VKEY" +echo " โ€ข Artifact Registry repository: $REPOSITORY_NAME" +echo " โ€ข All deployed Kubernetes resources" +echo "" +echo "Project: $PROJECT_ID" +echo "Region: $REGION" +echo "" + +# Confirmation prompt +read -p "Are you sure you want to proceed? This action cannot be undone. (yes/no): " confirm +if [[ $confirm != "yes" ]]; then + echo "โŒ Teardown cancelled." + exit 0 +fi + +echo "" +echo "๐Ÿš€ Starting teardown process..." + +# 1. Delete Kubernetes resources first (if cluster exists) +echo "๐Ÿงน Cleaning up Kubernetes resources..." +if gcloud container clusters describe $CLUSTER_NAME --region=$REGION --project=$PROJECT_ID >/dev/null 2>&1; then + echo " โ€ข Deleting deployments..." + kubectl delete deployment deployment-1 --ignore-not-found=true --timeout=60s + + echo " โ€ข Deleting secrets..." + kubectl delete secret sfluv-secrets --ignore-not-found=true + + echo " โ€ข Deleting secret provider class..." + kubectl delete secretproviderclass sfluv-secrets-spc --ignore-not-found=true + + echo " โ€ข Deleting service account..." + kubectl delete serviceaccount $KSA_NAME --namespace=$NAMESPACE --ignore-not-found=true + + echo " โ€ข Waiting for resources to terminate..." + sleep 10 +else + echo " โ€ข Cluster $CLUSTER_NAME not found, skipping Kubernetes cleanup" +fi + +# 2. Delete GKE Cluster +echo "๐Ÿ—๏ธ Deleting GKE cluster..." +if gcloud container clusters describe $CLUSTER_NAME --region=$REGION --project=$PROJECT_ID >/dev/null 2>&1; then + gcloud container clusters delete $CLUSTER_NAME \ + --region=$REGION \ + --project=$PROJECT_ID \ + --quiet + echo " โœ… Cluster deleted" +else + echo " โ€ข Cluster $CLUSTER_NAME not found" +fi + +# 3. Remove IAM policy binding +echo "๐Ÿ”— Removing IAM policy bindings..." +if gcloud iam service-accounts describe $GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com --project=$PROJECT_ID >/dev/null 2>&1; then + echo " โ€ข Removing workload identity binding..." + gcloud iam service-accounts remove-iam-policy-binding \ + ${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com \ + --role roles/iam.workloadIdentityUser \ + --member "serviceAccount:${PROJECT_ID}.svc.id.goog[${NAMESPACE}/${KSA_NAME}]" \ + --project=$PROJECT_ID \ + --quiet || echo " (binding may not exist)" + + echo " โ€ข Removing project IAM binding..." + gcloud projects remove-iam-policy-binding $PROJECT_ID \ + --member="serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \ + --role="roles/secretmanager.secretAccessor" \ + --quiet || echo " (binding may not exist)" +fi + +# 4. Delete Google Service Account +echo "๐Ÿ‘ค Deleting Google Service Account..." +if gcloud iam service-accounts describe $GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com --project=$PROJECT_ID >/dev/null 2>&1; then + gcloud iam service-accounts delete ${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com \ + --project=$PROJECT_ID \ + --quiet + echo " โœ… Service account deleted" +else + echo " โ€ข Service account $GSA_NAME not found" +fi + +# 5. Delete Secret Manager secrets +echo "๐Ÿ” Deleting Secret Manager secrets..." +secrets=("DB_PASSWORD" "ADMIN_KEY" "BOT_KEY" "PRIVY_VKEY") +for secret in "${secrets[@]}"; do + if gcloud secrets describe $secret --project=$PROJECT_ID >/dev/null 2>&1; then + gcloud secrets delete $secret --project=$PROJECT_ID --quiet + echo " โœ… Secret $secret deleted" + else + echo " โ€ข Secret $secret not found" + fi +done + +# 6. Delete Artifact Registry repository +echo "๐Ÿ“ฆ Deleting Artifact Registry repository..." +if gcloud artifacts repositories describe $REPOSITORY_NAME \ + --location=$REGION \ + --project=$PROJECT_ID >/dev/null 2>&1; then + gcloud artifacts repositories delete $REPOSITORY_NAME \ + --location=$REGION \ + --project=$PROJECT_ID \ + --quiet + echo " โœ… Repository deleted" +else + echo " โ€ข Repository $REPOSITORY_NAME not found" +fi + +# 7. Optional: Disable APIs (commented out by default to avoid affecting other projects) +echo "๐Ÿ”Œ APIs will remain enabled (to avoid affecting other services)" +echo " If you want to disable APIs manually:" +echo " gcloud services disable secretmanager.googleapis.com --project=$PROJECT_ID" +echo " gcloud services disable container.googleapis.com --project=$PROJECT_ID" +echo " gcloud services disable artifactregistry.googleapis.com --project=$PROJECT_ID" + +echo "" +echo "๐ŸŽ‰ Teardown complete!" +echo "" +echo "๐Ÿ“‹ Summary of deleted resources:" +echo " โœ… GKE Cluster: $CLUSTER_NAME" +echo " โœ… Google Service Account: $GSA_NAME" +echo " โœ… Secret Manager secrets: DB_PASSWORD, ADMIN_KEY, BOT_KEY, PRIVY_VKEY" +echo " โœ… Artifact Registry repository: $REPOSITORY_NAME" +echo " โœ… All Kubernetes deployments and resources" +echo "" +echo "๐Ÿ’ก Note: Google Cloud billing will stop for these resources." +echo " APIs remain enabled to avoid affecting other services." +echo "" +echo "๐Ÿ” To verify cleanup:" +echo " gcloud container clusters list --project=$PROJECT_ID" +echo " gcloud secrets list --project=$PROJECT_ID" +echo " gcloud iam service-accounts list --project=$PROJECT_ID" +echo " gcloud artifacts repositories list --project=$PROJECT_ID" \ No newline at end of file