From 11aeb87216b24ac6865f12ab4a55f30957e3cfa0 Mon Sep 17 00:00:00 2001 From: Zoe Forbes Date: Mon, 26 Jan 2026 21:32:57 -0600 Subject: [PATCH] update deployment --- .gitea/workflows/ci.yaml | 160 ----------- .gitea/workflows/deploy.yaml | 207 -------------- docs/DEPLOYMENT.md | 519 ++++++++++++++++------------------- scripts/deploy.sh | 391 +++++++++++++------------- scripts/setup-host.sh | 238 +++++++++------- 5 files changed, 585 insertions(+), 930 deletions(-) delete mode 100644 .gitea/workflows/ci.yaml delete mode 100644 .gitea/workflows/deploy.yaml diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml deleted file mode 100644 index f460749..0000000 --- a/.gitea/workflows/ci.yaml +++ /dev/null @@ -1,160 +0,0 @@ -name: CI - -on: - push: - branches: - - main - - 'feature/**' - - 'fix/**' - pull_request: - branches: - - main - -env: - GO_VERSION: '1.23' - -jobs: - lint: - name: Lint - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true - - - name: Run go vet - run: go vet ./... - - - name: Check go mod tidy - run: | - go mod tidy - git diff --exit-code go.mod go.sum - - - name: Check formatting - run: | - gofmt -l . - test -z "$(gofmt -l .)" - - test: - name: Test - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true - - - name: Run tests - run: go test -v -race -coverprofile=coverage.out ./... - - - name: Upload coverage - uses: actions/upload-artifact@v4 - with: - name: coverage - path: coverage.out - retention-days: 7 - - build: - name: Build - runs-on: ubuntu-latest - strategy: - matrix: - goos: [linux] - goarch: [amd64, arm64] - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true - - - name: Get version - id: version - run: | - VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "dev-$(git rev-parse --short HEAD)") - echo "version=${VERSION}" >> $GITHUB_OUTPUT - - - name: Build silod - env: - GOOS: ${{ matrix.goos }} - GOARCH: ${{ matrix.goarch }} - CGO_ENABLED: 0 - run: | - mkdir -p build/out - go build -ldflags="-w -s -X main.Version=${{ steps.version.outputs.version }}" \ - -o build/out/silod-${{ matrix.goos }}-${{ matrix.goarch }} \ - ./cmd/silod - - - name: Build silo CLI - env: - GOOS: ${{ matrix.goos }} - GOARCH: ${{ matrix.goarch }} - CGO_ENABLED: 0 - run: | - go build -ldflags="-w -s -X main.Version=${{ steps.version.outputs.version }}" \ - -o build/out/silo-${{ matrix.goos }}-${{ matrix.goarch }} \ - ./cmd/silo - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: binaries-${{ matrix.goos }}-${{ matrix.goarch }} - path: build/out/ - retention-days: 7 - - schema-validate: - name: Validate Schemas - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true - - - name: Validate YAML schemas - run: | - for f in schemas/*.yaml; do - echo "Validating $f..." - # Use Go to parse and validate schema - go run -exec "echo" ./cmd/silo 2>/dev/null || true - done - echo "Schema files are valid YAML" - - docker: - name: Docker Build - runs-on: ubuntu-latest - needs: [lint, test] - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build Docker image - uses: docker/build-push-action@v5 - with: - context: . - file: ./build/package/Dockerfile - push: false - tags: silo:ci-${{ github.sha }} - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml deleted file mode 100644 index 217ae92..0000000 --- a/.gitea/workflows/deploy.yaml +++ /dev/null @@ -1,207 +0,0 @@ -name: Deploy Silo - -on: - push: - branches: - - main - paths-ignore: - - '**.md' - - 'docs/**' - workflow_dispatch: - inputs: - environment: - description: 'Deployment environment' - required: true - default: 'production' - type: choice - options: - - production - -env: - GO_VERSION: '1.23' - BINARY_NAME: silod - DEPLOY_HOST: silo.kindred.internal - DEPLOY_USER: deploy - -jobs: - build: - name: Build - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Full history for git describe - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true - - - name: Get version - id: version - run: | - VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "dev-$(git rev-parse --short HEAD)") - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "Building version: ${VERSION}" - - - name: Build binary - run: | - mkdir -p build/out - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ - go build -ldflags="-w -s -X main.Version=${{ steps.version.outputs.version }}" \ - -o build/out/${{ env.BINARY_NAME }} \ - ./cmd/silod - - - name: Verify binary - run: | - file build/out/${{ env.BINARY_NAME }} - ls -lh build/out/${{ env.BINARY_NAME }} - - - name: Upload binary artifact - uses: actions/upload-artifact@v4 - with: - name: silod-binary - path: build/out/${{ env.BINARY_NAME }} - retention-days: 7 - - - name: Upload config artifact - uses: actions/upload-artifact@v4 - with: - name: silo-config - path: | - deployments/config.prod.yaml - deployments/systemd/silod.service - schemas/ - retention-days: 7 - - test: - name: Test - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true - - - name: Run tests - run: go test -v -race ./... - - - name: Run go vet - run: go vet ./... - - deploy: - name: Deploy to Production - runs-on: ubuntu-latest - needs: [build, test] - environment: production - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Download binary artifact - uses: actions/download-artifact@v4 - with: - name: silod-binary - path: build/out - - - name: Download config artifact - uses: actions/download-artifact@v4 - with: - name: silo-config - path: deploy-config - - - name: Setup SSH key - run: | - mkdir -p ~/.ssh - echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key - chmod 600 ~/.ssh/deploy_key - ssh-keyscan -H ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true - - - name: Stop service - run: | - ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new \ - ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} \ - "sudo systemctl stop silod || true" - - - name: Deploy binary - run: | - chmod +x build/out/${{ env.BINARY_NAME }} - scp -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new \ - build/out/${{ env.BINARY_NAME }} \ - ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }}:/tmp/${{ env.BINARY_NAME }}.new - - ssh -i ~/.ssh/deploy_key ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} << 'EOF' - sudo mv /tmp/silod.new /opt/silo/bin/silod - sudo chmod 755 /opt/silo/bin/silod - sudo chown root:root /opt/silo/bin/silod - EOF - - - name: Deploy configuration - run: | - scp -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new \ - deploy-config/deployments/config.prod.yaml \ - ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }}:/tmp/config.yaml - - ssh -i ~/.ssh/deploy_key ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} << 'EOF' - sudo mv /tmp/config.yaml /etc/silo/config.yaml - sudo chmod 644 /etc/silo/config.yaml - sudo chown root:silo /etc/silo/config.yaml - EOF - - - name: Deploy schemas - run: | - scp -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new -r \ - deploy-config/schemas/* \ - ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }}:/tmp/silo-schemas/ - - ssh -i ~/.ssh/deploy_key ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} << 'EOF' - sudo rm -rf /etc/silo/schemas/* - sudo mv /tmp/silo-schemas/* /etc/silo/schemas/ - sudo chown -R root:silo /etc/silo/schemas - sudo chmod -R 644 /etc/silo/schemas/* - rm -rf /tmp/silo-schemas - EOF - - - name: Deploy systemd service - run: | - scp -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new \ - deploy-config/deployments/systemd/silod.service \ - ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }}:/tmp/silod.service - - ssh -i ~/.ssh/deploy_key ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} << 'EOF' - sudo mv /tmp/silod.service /etc/systemd/system/silod.service - sudo chmod 644 /etc/systemd/system/silod.service - sudo systemctl daemon-reload - EOF - - - name: Start and enable service - run: | - ssh -i ~/.ssh/deploy_key ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} << 'EOF' - sudo systemctl enable silod - sudo systemctl start silod - sleep 3 - sudo systemctl is-active --quiet silod && echo "Service started successfully" || exit 1 - EOF - - - name: Verify deployment - run: | - ssh -i ~/.ssh/deploy_key ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} << 'EOF' - echo "Checking health endpoint..." - curl -sf http://localhost:8080/health || echo "Health check pending..." - - echo "Checking readiness endpoint..." - curl -sf http://localhost:8080/ready || echo "Readiness check pending..." - - echo "Service status:" - sudo systemctl status silod --no-pager -l - EOF - - - name: Cleanup SSH key - if: always() - run: rm -f ~/.ssh/deploy_key diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 0b810b0..17d7209 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -6,9 +6,10 @@ This guide covers deploying Silo to a dedicated VM using external PostgreSQL and - [Architecture](#architecture) - [External Services](#external-services) -- [Automated Deployment (CI/CD)](#automated-deployment-cicd) -- [Manual Deployment](#manual-deployment) -- [Post-Deployment Configuration](#post-deployment-configuration) +- [Quick Start](#quick-start) +- [Initial Setup](#initial-setup) +- [Deployment](#deployment) +- [Configuration](#configuration) - [Maintenance](#maintenance) - [Troubleshooting](#troubleshooting) @@ -33,377 +34,343 @@ This guide covers deploying Silo to a dedicated VM using external PostgreSQL and └─────────────────────────┘ └─────────────────────────────────┘ ``` -## Prerequisites +## External Services -### On psql.kindred.internal +The following external services are already configured: -1. Create the Silo database and user: +| Service | Host | Database/Bucket | User | +|---------|------|-----------------|------| +| PostgreSQL | psql.kindred.internal:5432 | silo | silo | +| MinIO | minio.kindred.internal:9000 | silo-files | silouser | -```sql --- Connect as postgres superuser -CREATE USER silo WITH PASSWORD 'your-secure-password'; -CREATE DATABASE silo OWNER silo; - --- Grant necessary permissions -GRANT ALL PRIVILEGES ON DATABASE silo TO silo; - --- Connect to silo database -\c silo - --- Enable UUID extension -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -``` - -2. Run migrations: - -```bash -# From the silo project directory -psql -h psql.kindred.internal -U silo -d silo -f migrations/001_initial.sql -psql -h psql.kindred.internal -U silo -d silo -f migrations/002_sequence_by_name.sql -psql -h psql.kindred.internal -U silo -d silo -f migrations/003_remove_material.sql -psql -h psql.kindred.internal -U silo -d silo -f migrations/004_cad_sync_state.sql -psql -h psql.kindred.internal -U silo -d silo -f migrations/005_property_schema_version.sql -psql -h psql.kindred.internal -U silo -d silo -f migrations/006_project_tags.sql -psql -h psql.kindred.internal -U silo -d silo -f migrations/007_revision_status.sql -``` - -Or run all at once: - -```bash -for f in migrations/*.sql; do - echo "Running $f..." - psql -h psql.kindred.internal -U silo -d silo -f "$f" -done -``` - -3. Allow connections from Silo VM in `pg_hba.conf`: - -``` -# Allow silo.kindred.internal to connect -hostssl silo silo silo.kindred.internal/32 scram-sha-256 -``` - -### On minio.kindred.internal - -1. Create the Silo bucket and access credentials: - -```bash -# Using mc (MinIO Client) -mc alias set kindred https://minio.kindred.internal ADMIN_ACCESS_KEY ADMIN_SECRET_KEY - -# Create bucket with versioning -mc mb kindred/silo-files -mc version enable kindred/silo-files - -# Create service account for Silo -mc admin user add kindred silo-service 'your-minio-secret-key' - -# Create policy for silo-files bucket -cat > /tmp/silo-policy.json << 'EOF' -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:GetObject", - "s3:PutObject", - "s3:DeleteObject", - "s3:ListBucket", - "s3:GetBucketVersioning", - "s3:GetObjectVersion", - "s3:DeleteObjectVersion" - ], - "Resource": [ - "arn:aws:s3:::silo-files", - "arn:aws:s3:::silo-files/*" - ] - } - ] -} -EOF - -mc admin policy create kindred silo-policy /tmp/silo-policy.json -mc admin policy attach kindred silo-policy --user silo-service -``` - -2. Verify SSL certificate is valid (or configure Silo to use non-SSL if internal). +Migrations have been applied to the database. --- -## Deployment Options +## Quick Start -### Option A: Systemd Service (Recommended for Production) - -#### 1. Prepare the Silo VM +For a fresh VM, run these commands: ```bash -# Create silo user -sudo useradd -r -m -d /opt/silo -s /sbin/nologin silo +# 1. SSH to the target host +ssh root@silo.kindred.internal -# Create directories -sudo mkdir -p /opt/silo/bin -sudo mkdir -p /etc/silo/schemas -sudo mkdir -p /var/log/silo +# 2. Download and run setup script +curl -fsSL https://gitea.kindred.internal/kindred/silo-0062/raw/branch/main/scripts/setup-host.sh | bash -# Set ownership -sudo chown -R silo:silo /opt/silo /var/log/silo -sudo chown root:silo /etc/silo -sudo chmod 750 /etc/silo +# 3. Configure credentials +nano /etc/silo/silod.env + +# 4. Deploy +/opt/silo/src/scripts/deploy.sh ``` -#### 2. Build and Install Binary +--- + +## Initial Setup + +Run the setup script once on `silo.kindred.internal` to prepare the host: ```bash -# On build machine (requires Go 1.23+) -cd /path/to/silo -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o silod ./cmd/silod +# Option 1: If you have the repo locally +scp scripts/setup-host.sh root@silo.kindred.internal:/tmp/ +ssh root@silo.kindred.internal 'bash /tmp/setup-host.sh' -# Copy to Silo VM -scp silod silo.kindred.internal:/tmp/ -ssh silo.kindred.internal "sudo mv /tmp/silod /opt/silo/bin/ && sudo chmod 755 /opt/silo/bin/silod" +# Option 2: Direct on the host +ssh root@silo.kindred.internal +curl -fsSL https://git.kindred.internal/kindred/silo/raw/branch/main/scripts/setup-host.sh -o /tmp/setup-host.sh +bash /tmp/setup-host.sh ``` -#### 3. Install Configuration +The setup script: +- Installs dependencies (git, Go 1.23) +- Creates the `silo` system user +- Creates directory structure (`/opt/silo`, `/etc/silo`) +- Clones the repository to `/opt/silo/src` +- Creates the environment file template + +### Configure Credentials + +After setup, edit the environment file with your credentials: ```bash -# Copy config file -scp deployments/config.prod.yaml silo.kindred.internal:/tmp/config.yaml -ssh silo.kindred.internal "sudo mv /tmp/config.yaml /etc/silo/config.yaml" +sudo nano /etc/silo/silod.env +``` -# Copy schemas -scp -r schemas/* silo.kindred.internal:/tmp/schemas/ -ssh silo.kindred.internal "sudo mv /tmp/schemas/* /etc/silo/schemas/" +Fill in the values: -# Create environment file with secrets -ssh silo.kindred.internal -sudo cat > /etc/silo/silod.env << 'EOF' +```bash +# Database credentials (psql.kindred.internal) SILO_DB_PASSWORD=your-database-password -SILO_MINIO_ACCESS_KEY=silo-service + +# MinIO credentials (minio.kindred.internal) +SILO_MINIO_ACCESS_KEY=silouser SILO_MINIO_SECRET_KEY=your-minio-secret-key -EOF -sudo chmod 600 /etc/silo/silod.env -sudo chown root:silo /etc/silo/silod.env ``` -#### 4. Install Systemd Service +### Verify External Services + +Before deploying, verify connectivity to external services: ```bash -# Copy service file -scp deployments/systemd/silod.service silo.kindred.internal:/tmp/ -ssh silo.kindred.internal "sudo mv /tmp/silod.service /etc/systemd/system/" +# Test PostgreSQL +psql -h psql.kindred.internal -U silo -d silo -c 'SELECT 1' -# Enable and start -ssh silo.kindred.internal << 'EOF' -sudo systemctl daemon-reload -sudo systemctl enable silod -sudo systemctl start silod -sudo systemctl status silod -EOF +# Test MinIO +curl -I http://minio.kindred.internal:9000/minio/health/live ``` -#### 5. Verify Deployment +--- + +## Deployment + +### Deploy (or Update) + +To deploy or update Silo, run the deploy script on the target host: ```bash +ssh root@silo.kindred.internal +/opt/silo/src/scripts/deploy.sh +``` + +The deploy script: +1. Pulls the latest code from git +2. Builds the `silod` binary +3. Installs configuration and schemas +4. Installs/updates the systemd service +5. Restarts the service +6. Verifies health endpoints + +### Deploy Options + +```bash +# Full deployment (pull, build, deploy, restart) +sudo /opt/silo/src/scripts/deploy.sh + +# Skip git pull (use current checkout) +sudo /opt/silo/src/scripts/deploy.sh --no-pull + +# Skip build (use existing binary) +sudo /opt/silo/src/scripts/deploy.sh --no-build + +# Just restart the service +sudo /opt/silo/src/scripts/deploy.sh --restart-only + # Check service status -sudo systemctl status silod +sudo /opt/silo/src/scripts/deploy.sh --status +``` -# Check logs -sudo journalctl -u silod -f +### Environment Variables -# Test health endpoint -curl http://localhost:8080/health +You can override the git repository URL and branch: -# Test readiness (verifies DB and MinIO connectivity) -curl http://localhost:8080/ready +```bash +export SILO_REPO_URL=https://git.kindred.internal/kindred/silo.git +export SILO_BRANCH=main +sudo -E /opt/silo/src/scripts/deploy.sh ``` --- -### Option B: Docker Compose +## Configuration -#### 1. Install Docker on Silo VM +### File Locations + +| File | Purpose | +|------|---------| +| `/opt/silo/bin/silod` | Server binary | +| `/opt/silo/src/` | Git repository checkout | +| `/etc/silo/config.yaml` | Server configuration | +| `/etc/silo/silod.env` | Environment variables (secrets) | +| `/etc/silo/schemas/` | Part numbering schemas | +| `/var/log/silo/` | Log directory | + +### Configuration File + +The configuration file `/etc/silo/config.yaml` is installed on first deployment and not overwritten on updates. To update it manually: ```bash -# Debian/Ubuntu -sudo apt-get update -sudo apt-get install -y docker.io docker-compose-plugin -sudo usermod -aG docker $USER +sudo cp /opt/silo/src/deployments/config.prod.yaml /etc/silo/config.yaml +sudo systemctl restart silod ``` -#### 2. Clone Repository +### Schemas -```bash -git clone https://github.com/kindred-systems/silo.git /opt/silo -cd /opt/silo -``` - -#### 3. Configure Environment - -```bash -# Create .env file -cat > /opt/silo/deployments/.env << 'EOF' -SILO_DB_PASSWORD=your-database-password -SILO_MINIO_ACCESS_KEY=silo-service -SILO_MINIO_SECRET_KEY=your-minio-secret-key -SILO_BASE_URL=http://silo.kindred.internal:8080 -EOF -chmod 600 /opt/silo/deployments/.env -``` - -#### 4. Start Service - -```bash -cd /opt/silo/deployments -docker compose -f docker-compose.prod.yaml up -d -``` - -#### 5. Verify - -```bash -docker compose -f docker-compose.prod.yaml ps -docker compose -f docker-compose.prod.yaml logs -f -curl http://localhost:8080/ready -``` - ---- - -## Post-Deployment Configuration - -### DNS Setup - -Add DNS records for `silo.kindred.internal` pointing to the Silo VM IP address. - -### Firewall Rules - -```bash -# Allow incoming connections on port 8080 -sudo ufw allow 8080/tcp - -# Or with iptables -sudo iptables -A INPUT -p tcp --dport 8080 -j ACCEPT -``` - -### Reverse Proxy (Optional) - -For TLS termination, configure nginx or caddy: - -```nginx -# /etc/nginx/sites-available/silo -server { - listen 443 ssl http2; - server_name silo.kindred.internal; - - ssl_certificate /etc/ssl/certs/silo.kindred.internal.crt; - ssl_certificate_key /etc/ssl/private/silo.kindred.internal.key; - - location / { - proxy_pass http://127.0.0.1:8080; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} -``` +Schemas in `/etc/silo/schemas/` are updated on every deployment from the repository. --- ## Maintenance +### Service Management + +```bash +# Check status +sudo systemctl status silod + +# Start/stop/restart +sudo systemctl start silod +sudo systemctl stop silod +sudo systemctl restart silod + +# Enable/disable auto-start +sudo systemctl enable silod +sudo systemctl disable silod +``` + ### View Logs ```bash -# Systemd +# Follow logs sudo journalctl -u silod -f -sudo journalctl -u silod --since "1 hour ago" -# Docker -docker compose -f docker-compose.prod.yaml logs -f silo +# Recent logs +sudo journalctl -u silod -n 100 + +# Logs since a time +sudo journalctl -u silod --since "1 hour ago" +sudo journalctl -u silod --since "2024-01-15 10:00:00" ``` -### Restart Service +### Health Checks ```bash -# Systemd -sudo systemctl restart silod +# Basic health check +curl http://localhost:8080/health -# Docker -docker compose -f docker-compose.prod.yaml restart silo +# Full readiness check (includes DB and MinIO) +curl http://localhost:8080/ready ``` ### Update Deployment -```bash -# Systemd - rebuild and replace binary -go build -ldflags="-w -s" -o silod ./cmd/silod -sudo systemctl stop silod -sudo cp silod /opt/silo/bin/silod -sudo systemctl start silod +To update to the latest version: -# Docker - rebuild and restart -docker compose -f docker-compose.prod.yaml build -docker compose -f docker-compose.prod.yaml up -d +```bash +ssh root@silo.kindred.internal +/opt/silo/src/scripts/deploy.sh +``` + +To deploy a specific branch or tag: + +```bash +cd /opt/silo/src +git fetch --all --tags +git checkout v1.2.3 # or a branch name +sudo /opt/silo/src/scripts/deploy.sh --no-pull ``` ### Database Migrations -When updating Silo, check for new migrations: +When new migrations are added, run them manually: ```bash -# List migration files -ls -la migrations/ +# Check for new migrations +ls -la /opt/silo/src/migrations/ -# Run new migrations -psql -h psql.kindred.internal -U silo -d silo -f migrations/008_new_feature.sql +# Run a specific migration +psql -h psql.kindred.internal -U silo -d silo -f /opt/silo/src/migrations/008_new_feature.sql ``` --- ## Troubleshooting +### Service Won't Start + +1. Check logs for errors: + ```bash + sudo journalctl -u silod -n 50 + ``` + +2. Verify configuration: + ```bash + cat /etc/silo/config.yaml + ``` + +3. Check environment file permissions: + ```bash + ls -la /etc/silo/silod.env + # Should be: -rw------- root silo + ``` + +4. Verify binary exists: + ```bash + ls -la /opt/silo/bin/silod + ``` + ### Connection Refused to PostgreSQL -1. Verify network connectivity: `nc -zv psql.kindred.internal 5432` -2. Check `pg_hba.conf` allows connections from Silo VM -3. Verify firewall rules on PostgreSQL server -4. Check credentials in `/etc/silo/silod.env` +1. Test network connectivity: + ```bash + nc -zv psql.kindred.internal 5432 + ``` + +2. Test credentials: + ```bash + source /etc/silo/silod.env + PGPASSWORD=$SILO_DB_PASSWORD psql -h psql.kindred.internal -U silo -d silo -c 'SELECT 1' + ``` + +3. Check `pg_hba.conf` on PostgreSQL server allows connections from this host. ### Connection Refused to MinIO -1. Verify network connectivity: `nc -zv minio.kindred.internal 9000` -2. Check SSL settings match (use_ssl: true/false) -3. Verify access key and secret key -4. Check bucket exists: `mc ls kindred/silo-files` +1. Test network connectivity: + ```bash + nc -zv minio.kindred.internal 9000 + ``` -### Service Won't Start +2. Test with curl: + ```bash + curl -I http://minio.kindred.internal:9000/minio/health/live + ``` -1. Check logs: `sudo journalctl -u silod -n 50` -2. Verify config syntax: `/opt/silo/bin/silod -config /etc/silo/config.yaml -validate` -3. Check file permissions on config and env files -4. Verify schemas directory exists and contains YAML files +3. Check SSL settings in config match MinIO setup: + ```yaml + storage: + use_ssl: true # or false + ``` ### Health Check Fails ```bash -# Test each component -curl http://localhost:8080/health # Basic health -curl http://localhost:8080/ready # Full readiness (DB + MinIO) +# Check individual endpoints +curl -v http://localhost:8080/health +curl -v http://localhost:8080/ready -# If ready fails, check individual services -psql -h psql.kindred.internal -U silo -d silo -c "SELECT 1" -mc ls kindred/silo-files +# If ready fails but health passes, check external services +psql -h psql.kindred.internal -U silo -d silo -c 'SELECT 1' +curl http://minio.kindred.internal:9000/minio/health/live ``` +### Build Fails + +1. Check Go is installed: + ```bash + go version + # Should be 1.23+ + ``` + +2. Check source is present: + ```bash + ls -la /opt/silo/src/ + ``` + +3. Try manual build: + ```bash + cd /opt/silo/src + go build -v ./cmd/silod + ``` + --- ## Security Checklist +- [ ] `/etc/silo/silod.env` has mode 600 (`chmod 600`) - [ ] Database password is strong and unique -- [ ] MinIO credentials are service-account specific -- [ ] `/etc/silo/silod.env` has mode 600 +- [ ] MinIO credentials are specific to silo (not admin) - [ ] SSL/TLS enabled for PostgreSQL (`sslmode: require`) -- [ ] SSL/TLS enabled for MinIO (`use_ssl: true`) +- [ ] SSL/TLS enabled for MinIO (`use_ssl: true`) if available - [ ] Firewall restricts access to port 8080 -- [ ] Silo runs as non-root user -- [ ] Logs don't contain sensitive information +- [ ] Service runs as non-root `silo` user diff --git a/scripts/deploy.sh b/scripts/deploy.sh index c6f8802..61aa353 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,23 +1,19 @@ #!/usr/bin/env bash # # Silo Deployment Script -# Deploys silod to silo.kindred.internal via systemd +# Pulls from git and deploys silod on the local machine # # Usage: -# ./scripts/deploy.sh [options] +# sudo ./scripts/deploy.sh [options] # # Options: -# --build-only Only build the binary, don't deploy -# --no-build Skip build, deploy existing binary +# --no-pull Skip git pull (use current checkout) +# --no-build Skip build (use existing binary) # --restart-only Only restart the service -# --dry-run Show what would be done without doing it +# --status Show service status and exit # --help Show this help message # -# Environment variables: -# SILO_HOST Target host (default: silo.kindred.internal) -# SILO_USER SSH user (default: deploy) -# SILO_SSH_KEY Path to SSH private key (optional) -# BUILD_VERSION Version string for build (default: git describe) +# This script should be run on silo.kindred.internal as root or with sudo. set -euo pipefail @@ -29,27 +25,18 @@ BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration -SILO_HOST="${SILO_HOST:-silo.kindred.internal}" -SILO_USER="${SILO_USER:-deploy}" -SILO_SSH_KEY="${SILO_SSH_KEY:-}" -BUILD_VERSION="${BUILD_VERSION:-$(git describe --tags --always --dirty 2>/dev/null || echo "dev")}" - -# Paths -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" -BUILD_DIR="${PROJECT_ROOT}/build/out" +REPO_URL="${SILO_REPO_URL:-https://gitea.kindred.internal/kindred/silo-0062.git}" +REPO_BRANCH="${SILO_BRANCH:-main}" +INSTALL_DIR="/opt/silo" +CONFIG_DIR="/etc/silo" BINARY_NAME="silod" - -# Remote paths -REMOTE_BIN_DIR="/opt/silo/bin" -REMOTE_CONFIG_DIR="/etc/silo" -REMOTE_SCHEMAS_DIR="/etc/silo/schemas" -REMOTE_SERVICE="silod" +SERVICE_NAME="silod" # Flags -BUILD=true -DEPLOY=true -DRY_RUN=false +DO_PULL=true +DO_BUILD=true +RESTART_ONLY=false +STATUS_ONLY=false # Functions log_info() { @@ -74,33 +61,13 @@ die() { } show_help() { - head -20 "$0" | grep -E '^#' | sed 's/^# *//' + head -18 "$0" | grep -E '^#' | sed 's/^# *//' exit 0 } -ssh_cmd() { - local ssh_opts=(-o StrictHostKeyChecking=accept-new -o ConnectTimeout=10) - if [[ -n "${SILO_SSH_KEY}" ]]; then - ssh_opts+=(-i "${SILO_SSH_KEY}") - fi - - if [[ "${DRY_RUN}" == "true" ]]; then - echo "[DRY-RUN] ssh ${ssh_opts[*]} ${SILO_USER}@${SILO_HOST} $*" - else - ssh "${ssh_opts[@]}" "${SILO_USER}@${SILO_HOST}" "$@" - fi -} - -scp_cmd() { - local scp_opts=(-o StrictHostKeyChecking=accept-new -o ConnectTimeout=10) - if [[ -n "${SILO_SSH_KEY}" ]]; then - scp_opts+=(-i "${SILO_SSH_KEY}") - fi - - if [[ "${DRY_RUN}" == "true" ]]; then - echo "[DRY-RUN] scp ${scp_opts[*]} $*" - else - scp "${scp_opts[@]}" "$@" +check_root() { + if [[ $EUID -ne 0 ]]; then + die "This script must be run as root (use sudo)" fi } @@ -109,174 +76,188 @@ check_dependencies() { local missing=() - command -v go >/dev/null 2>&1 || missing+=("go") - command -v ssh >/dev/null 2>&1 || missing+=("ssh") - command -v scp >/dev/null 2>&1 || missing+=("scp") command -v git >/dev/null 2>&1 || missing+=("git") + command -v go >/dev/null 2>&1 || missing+=("go (golang)") + command -v systemctl >/dev/null 2>&1 || missing+=("systemctl") if [[ ${#missing[@]} -gt 0 ]]; then die "Missing required commands: ${missing[*]}" fi + # Check Go version + local go_version + go_version=$(go version | grep -oP 'go\d+\.\d+' | head -1) + log_info "Found Go version: ${go_version}" + log_success "All dependencies available" } -check_connectivity() { - log_info "Checking connectivity to ${SILO_HOST}..." +setup_directories() { + log_info "Setting up directories..." - if [[ "${DRY_RUN}" == "true" ]]; then - log_info "[DRY-RUN] Would check SSH connectivity" - return 0 + # Create directories if they don't exist + mkdir -p "${INSTALL_DIR}/bin" + mkdir -p "${INSTALL_DIR}/src" + mkdir -p "${CONFIG_DIR}/schemas" + mkdir -p /var/log/silo + + # Create silo user if it doesn't exist + if ! id -u silo >/dev/null 2>&1; then + useradd -r -m -d "${INSTALL_DIR}" -s /sbin/nologin -c "Silo Service" silo + log_info "Created silo user" fi - if ! ssh_cmd "echo 'Connection successful'" >/dev/null 2>&1; then - die "Cannot connect to ${SILO_HOST} as ${SILO_USER}" + log_success "Directories ready" +} + +git_pull() { + log_info "Pulling latest code from ${REPO_BRANCH}..." + + local src_dir="${INSTALL_DIR}/src" + + if [[ -d "${src_dir}/.git" ]]; then + # Existing checkout - pull updates + cd "${src_dir}" + git fetch origin + git checkout "${REPO_BRANCH}" + git reset --hard "origin/${REPO_BRANCH}" + log_success "Updated to $(git rev-parse --short HEAD)" + else + # Fresh clone + log_info "Cloning repository..." + rm -rf "${src_dir}" + git clone --branch "${REPO_BRANCH}" "${REPO_URL}" "${src_dir}" + cd "${src_dir}" + log_success "Cloned $(git rev-parse --short HEAD)" fi - log_success "SSH connection verified" + # Show version info + local version + version=$(git describe --tags --always --dirty 2>/dev/null || git rev-parse --short HEAD) + log_info "Version: ${version}" } build_binary() { - log_info "Building ${BINARY_NAME} (version: ${BUILD_VERSION})..." + log_info "Building ${BINARY_NAME}..." - mkdir -p "${BUILD_DIR}" + local src_dir="${INSTALL_DIR}/src" + cd "${src_dir}" - local ldflags="-w -s -X main.Version=${BUILD_VERSION}" + # Get version from git + local version + version=$(git describe --tags --always --dirty 2>/dev/null || git rev-parse --short HEAD) - if [[ "${DRY_RUN}" == "true" ]]; then - echo "[DRY-RUN] CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags=\"${ldflags}\" -o ${BUILD_DIR}/${BINARY_NAME} ./cmd/silod" - return 0 - fi + local ldflags="-w -s -X main.Version=${version}" - cd "${PROJECT_ROOT}" + # Build + CGO_ENABLED=0 go build -ldflags="${ldflags}" -o "${INSTALL_DIR}/bin/${BINARY_NAME}" ./cmd/silod - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ - go build -ldflags="${ldflags}" \ - -o "${BUILD_DIR}/${BINARY_NAME}" \ - ./cmd/silod - - if [[ ! -f "${BUILD_DIR}/${BINARY_NAME}" ]]; then + if [[ ! -f "${INSTALL_DIR}/bin/${BINARY_NAME}" ]]; then die "Build failed: binary not found" fi + chmod 755 "${INSTALL_DIR}/bin/${BINARY_NAME}" + local size - size=$(du -h "${BUILD_DIR}/${BINARY_NAME}" | cut -f1) + size=$(du -h "${INSTALL_DIR}/bin/${BINARY_NAME}" | cut -f1) log_success "Built ${BINARY_NAME} (${size})" } -deploy_binary() { - log_info "Deploying binary to ${SILO_HOST}..." +install_config() { + log_info "Installing configuration..." - local binary="${BUILD_DIR}/${BINARY_NAME}" + local src_dir="${INSTALL_DIR}/src" - if [[ ! -f "${binary}" ]] && [[ "${DRY_RUN}" != "true" ]]; then - die "Binary not found: ${binary}" + # Install config file if it doesn't exist or is different + if [[ ! -f "${CONFIG_DIR}/config.yaml" ]]; then + cp "${src_dir}/deployments/config.prod.yaml" "${CONFIG_DIR}/config.yaml" + chmod 644 "${CONFIG_DIR}/config.yaml" + chown root:silo "${CONFIG_DIR}/config.yaml" + log_info "Installed config.yaml" + else + log_info "Config file exists, not overwriting" fi - # Upload to temp location first - scp_cmd "${binary}" "${SILO_USER}@${SILO_HOST}:/tmp/${BINARY_NAME}.new" + # Install schemas (always update) + rm -rf "${CONFIG_DIR}/schemas/"* + cp -r "${src_dir}/schemas/"* "${CONFIG_DIR}/schemas/" + chmod -R 644 "${CONFIG_DIR}/schemas/"* + chown -R root:silo "${CONFIG_DIR}/schemas" + log_success "Schemas installed" - # Move to final location with sudo - ssh_cmd "sudo mv /tmp/${BINARY_NAME}.new ${REMOTE_BIN_DIR}/${BINARY_NAME}" - ssh_cmd "sudo chmod 755 ${REMOTE_BIN_DIR}/${BINARY_NAME}" - ssh_cmd "sudo chown root:root ${REMOTE_BIN_DIR}/${BINARY_NAME}" - - log_success "Binary deployed to ${REMOTE_BIN_DIR}/${BINARY_NAME}" -} - -deploy_config() { - log_info "Deploying configuration..." - - local config_file="${PROJECT_ROOT}/deployments/config.prod.yaml" - - if [[ ! -f "${config_file}" ]] && [[ "${DRY_RUN}" != "true" ]]; then - die "Config file not found: ${config_file}" + # Check environment file + if [[ ! -f "${CONFIG_DIR}/silod.env" ]]; then + cp "${src_dir}/deployments/systemd/silod.env.example" "${CONFIG_DIR}/silod.env" + chmod 600 "${CONFIG_DIR}/silod.env" + chown root:silo "${CONFIG_DIR}/silod.env" + log_warn "Created ${CONFIG_DIR}/silod.env - EDIT THIS FILE WITH CREDENTIALS!" fi - - scp_cmd "${config_file}" "${SILO_USER}@${SILO_HOST}:/tmp/config.yaml" - ssh_cmd "sudo mv /tmp/config.yaml ${REMOTE_CONFIG_DIR}/config.yaml" - ssh_cmd "sudo chmod 644 ${REMOTE_CONFIG_DIR}/config.yaml" - ssh_cmd "sudo chown root:silo ${REMOTE_CONFIG_DIR}/config.yaml" - - log_success "Configuration deployed" } -deploy_schemas() { - log_info "Deploying schemas..." +install_systemd() { + log_info "Installing systemd service..." - local schemas_dir="${PROJECT_ROOT}/schemas" + local src_dir="${INSTALL_DIR}/src" + local service_file="${src_dir}/deployments/systemd/silod.service" - if [[ ! -d "${schemas_dir}" ]] && [[ "${DRY_RUN}" != "true" ]]; then - die "Schemas directory not found: ${schemas_dir}" - fi - - # Create temp directory and copy schemas - ssh_cmd "rm -rf /tmp/silo-schemas && mkdir -p /tmp/silo-schemas" - scp_cmd -r "${schemas_dir}/"* "${SILO_USER}@${SILO_HOST}:/tmp/silo-schemas/" - - # Move to final location - ssh_cmd "sudo rm -rf ${REMOTE_SCHEMAS_DIR}/*" - ssh_cmd "sudo mv /tmp/silo-schemas/* ${REMOTE_SCHEMAS_DIR}/" - ssh_cmd "sudo chown -R root:silo ${REMOTE_SCHEMAS_DIR}" - ssh_cmd "sudo chmod -R 644 ${REMOTE_SCHEMAS_DIR}/*" - - log_success "Schemas deployed" -} - -deploy_systemd() { - log_info "Deploying systemd service..." - - local service_file="${PROJECT_ROOT}/deployments/systemd/silod.service" - - if [[ ! -f "${service_file}" ]] && [[ "${DRY_RUN}" != "true" ]]; then + if [[ -f "${service_file}" ]]; then + cp "${service_file}" /etc/systemd/system/silod.service + chmod 644 /etc/systemd/system/silod.service + systemctl daemon-reload + log_success "Systemd service installed" + else die "Service file not found: ${service_file}" fi +} - scp_cmd "${service_file}" "${SILO_USER}@${SILO_HOST}:/tmp/silod.service" - ssh_cmd "sudo mv /tmp/silod.service /etc/systemd/system/silod.service" - ssh_cmd "sudo chmod 644 /etc/systemd/system/silod.service" - ssh_cmd "sudo systemctl daemon-reload" +set_permissions() { + log_info "Setting permissions..." - log_success "Systemd service deployed" + chown -R silo:silo "${INSTALL_DIR}" + chown root:silo "${CONFIG_DIR}" + chmod 750 "${CONFIG_DIR}" + chown silo:silo /var/log/silo + chmod 750 /var/log/silo + + # Binary should be owned by root but executable by silo + chown root:root "${INSTALL_DIR}/bin/${BINARY_NAME}" + chmod 755 "${INSTALL_DIR}/bin/${BINARY_NAME}" + + log_success "Permissions set" } restart_service() { - log_info "Restarting ${REMOTE_SERVICE} service..." + log_info "Restarting ${SERVICE_NAME} service..." - ssh_cmd "sudo systemctl restart ${REMOTE_SERVICE}" + # Enable if not already + systemctl enable "${SERVICE_NAME}" >/dev/null 2>&1 || true - # Wait for service to start + # Restart + systemctl restart "${SERVICE_NAME}" + + # Wait for startup sleep 2 - if [[ "${DRY_RUN}" != "true" ]]; then - if ssh_cmd "sudo systemctl is-active --quiet ${REMOTE_SERVICE}"; then - log_success "Service restarted successfully" - else - log_error "Service failed to start" - ssh_cmd "sudo journalctl -u ${REMOTE_SERVICE} -n 20 --no-pager" || true - die "Deployment failed: service not running" - fi + if systemctl is-active --quiet "${SERVICE_NAME}"; then + log_success "Service started successfully" else - log_info "[DRY-RUN] Would verify service is running" + log_error "Service failed to start" + journalctl -u "${SERVICE_NAME}" -n 20 --no-pager || true + die "Deployment failed: service not running" fi } verify_deployment() { log_info "Verifying deployment..." - if [[ "${DRY_RUN}" == "true" ]]; then - log_info "[DRY-RUN] Would verify health endpoints" - return 0 - fi - # Wait a moment for service to fully start - sleep 3 + sleep 2 # Check health endpoint local health_status - health_status=$(ssh_cmd "curl -sf http://localhost:8080/health" 2>/dev/null || echo "FAILED") + health_status=$(curl -sf http://localhost:8080/health 2>/dev/null || echo "FAILED") - if [[ "${health_status}" == *"ok"* ]] || [[ "${health_status}" == *"healthy"* ]]; then + if [[ "${health_status}" == *"ok"* ]] || [[ "${health_status}" == *"healthy"* ]] || [[ "${health_status}" == "{}" ]]; then log_success "Health check passed" else log_warn "Health check returned: ${health_status}" @@ -284,46 +265,46 @@ verify_deployment() { # Check ready endpoint (includes DB and MinIO) local ready_status - ready_status=$(ssh_cmd "curl -sf http://localhost:8080/ready" 2>/dev/null || echo "FAILED") + ready_status=$(curl -sf http://localhost:8080/ready 2>/dev/null || echo "FAILED") - if [[ "${ready_status}" == *"ok"* ]] || [[ "${ready_status}" == *"ready"* ]]; then + if [[ "${ready_status}" == *"ok"* ]] || [[ "${ready_status}" == *"ready"* ]] || [[ "${ready_status}" == "{}" ]]; then log_success "Readiness check passed (DB and MinIO connected)" else log_warn "Readiness check returned: ${ready_status}" - log_warn "Service may still be starting or external services unavailable" + log_warn "Check credentials in ${CONFIG_DIR}/silod.env" fi - # Show deployed version - local deployed_version - deployed_version=$(ssh_cmd "${REMOTE_BIN_DIR}/${BINARY_NAME} --version" 2>/dev/null || echo "unknown") - log_info "Deployed version: ${deployed_version}" + # Show version + log_info "Deployed version: $("${INSTALL_DIR}/bin/${BINARY_NAME}" --version 2>/dev/null || echo 'unknown')" } -enable_service() { - log_info "Enabling ${REMOTE_SERVICE} service..." - ssh_cmd "sudo systemctl enable ${REMOTE_SERVICE}" - log_success "Service enabled for auto-start" +show_status() { + echo "" + log_info "Service Status" + echo "============================================" + systemctl status "${SERVICE_NAME}" --no-pager -l || true + echo "" + echo "Recent logs:" + journalctl -u "${SERVICE_NAME}" -n 10 --no-pager || true } # Parse arguments while [[ $# -gt 0 ]]; do case $1 in - --build-only) - DEPLOY=false + --no-pull) + DO_PULL=false shift ;; --no-build) - BUILD=false + DO_BUILD=false shift ;; --restart-only) - BUILD=false - DEPLOY=false - restart_service - exit 0 + RESTART_ONLY=true + shift ;; - --dry-run) - DRY_RUN=true + --status) + STATUS_ONLY=true shift ;; --help|-h) @@ -337,36 +318,56 @@ done # Main execution main() { + echo "" log_info "Silo Deployment Script" log_info "======================" - log_info "Target: ${SILO_USER}@${SILO_HOST}" - log_info "Version: ${BUILD_VERSION}" - - if [[ "${DRY_RUN}" == "true" ]]; then - log_warn "DRY-RUN MODE: No changes will be made" - fi - echo "" - check_dependencies + check_root - if [[ "${BUILD}" == "true" ]]; then - build_binary + if [[ "${STATUS_ONLY}" == "true" ]]; then + show_status + exit 0 fi - if [[ "${DEPLOY}" == "true" ]]; then - check_connectivity - deploy_binary - deploy_config - deploy_schemas - deploy_systemd - enable_service + if [[ "${RESTART_ONLY}" == "true" ]]; then restart_service verify_deployment + exit 0 fi + check_dependencies + setup_directories + + if [[ "${DO_PULL}" == "true" ]]; then + git_pull + else + log_info "Skipping git pull (--no-pull)" + cd "${INSTALL_DIR}/src" + fi + + if [[ "${DO_BUILD}" == "true" ]]; then + build_binary + else + log_info "Skipping build (--no-build)" + fi + + install_config + install_systemd + set_permissions + restart_service + verify_deployment + echo "" + log_success "============================================" log_success "Deployment complete!" + log_success "============================================" + echo "" + echo "Useful commands:" + echo " sudo systemctl status silod # Check service status" + echo " sudo journalctl -u silod -f # Follow logs" + echo " curl http://localhost:8080/health # Health check" + echo "" } main "$@" diff --git a/scripts/setup-host.sh b/scripts/setup-host.sh index 23d53e6..e285c38 100755 --- a/scripts/setup-host.sh +++ b/scripts/setup-host.sh @@ -1,16 +1,18 @@ #!/usr/bin/env bash # # Silo Host Setup Script -# Run this on silo.kindred.internal to prepare for deployment +# Run this once on silo.kindred.internal to prepare for deployment # # Usage: -# sudo ./scripts/setup-host.sh +# sudo ./setup-host.sh # # This script: -# 1. Creates the silo system user -# 2. Creates required directories -# 3. Sets up the environment file template -# 4. Configures sudoers for deploy user +# 1. Installs required packages (git, go) +# 2. Creates the silo system user +# 3. Creates required directories +# 4. Sets up the environment file template +# 5. Clones the repository +# 6. Runs initial deployment set -euo pipefail @@ -18,65 +20,147 @@ set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' +BLUE='\033[0;34m' NC='\033[0m' -log_info() { echo -e "${GREEN}[INFO]${NC} $*"; } +# Configuration +REPO_URL="${SILO_REPO_URL:-https://gitea.kindred.internal/kindred/silo-0062.git}" +REPO_BRANCH="${SILO_BRANCH:-main}" +INSTALL_DIR="/opt/silo" +CONFIG_DIR="/etc/silo" +GO_VERSION="1.23.0" + +log_info() { echo -e "${BLUE}[INFO]${NC} $*"; } +log_success() { echo -e "${GREEN}[OK]${NC} $*"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } +die() { log_error "$*"; exit 1; } # Check root if [[ $EUID -ne 0 ]]; then - log_error "This script must be run as root (use sudo)" - exit 1 + die "This script must be run as root (use sudo)" fi -log_info "Setting up Silo host..." +log_info "============================================" +log_info "Silo Host Setup" +log_info "============================================" +echo "" -# Create silo system user (for running the service) +# Detect package manager +if command -v apt-get >/dev/null 2>&1; then + PKG_MANAGER="apt" +elif command -v dnf >/dev/null 2>&1; then + PKG_MANAGER="dnf" +elif command -v yum >/dev/null 2>&1; then + PKG_MANAGER="yum" +else + die "Unsupported package manager. Install git and go manually." +fi + +log_info "Detected package manager: ${PKG_MANAGER}" + +# Install dependencies +log_info "Installing dependencies..." + +case ${PKG_MANAGER} in + apt) + apt-get update -qq + apt-get install -y -qq git curl ca-certificates + ;; + dnf|yum) + ${PKG_MANAGER} install -y -q git curl ca-certificates + ;; +esac + +log_success "System packages installed" + +# Install Go if not present or wrong version +install_go() { + log_info "Installing Go ${GO_VERSION}..." + + local arch + case $(uname -m) in + x86_64) arch="amd64" ;; + aarch64) arch="arm64" ;; + *) die "Unsupported architecture: $(uname -m)" ;; + esac + + local go_tar="go${GO_VERSION}.linux-${arch}.tar.gz" + local go_url="https://go.dev/dl/${go_tar}" + + # Remove existing Go installation + rm -rf /usr/local/go + + # Download and install + curl -fsSL "${go_url}" -o "/tmp/${go_tar}" + tar -C /usr/local -xzf "/tmp/${go_tar}" + rm -f "/tmp/${go_tar}" + + # Add to PATH for all users + cat > /etc/profile.d/go.sh << 'EOF' +export PATH=$PATH:/usr/local/go/bin +export GOPATH=/opt/go +export PATH=$PATH:$GOPATH/bin +EOF + + # Source for current session + export PATH=$PATH:/usr/local/go/bin + + log_success "Go ${GO_VERSION} installed" +} + +if command -v go >/dev/null 2>&1; then + current_go=$(go version | grep -oP '\d+\.\d+' | head -1) + required_go="1.23" + if [[ "$(printf '%s\n' "$required_go" "$current_go" | sort -V | head -n1)" != "$required_go" ]]; then + log_warn "Go ${current_go} found, but ${required_go}+ required" + install_go + else + log_success "Go ${current_go} already installed" + fi +else + install_go +fi + +# Ensure Go is in PATH +export PATH=$PATH:/usr/local/go/bin + +# Create silo system user if ! id -u silo >/dev/null 2>&1; then log_info "Creating silo user..." - useradd -r -m -d /opt/silo -s /sbin/nologin -c "Silo Service" silo - log_info "Created user: silo" + useradd -r -m -d "${INSTALL_DIR}" -s /sbin/nologin -c "Silo Service" silo + log_success "Created user: silo" else log_info "User silo already exists" fi -# Create deploy user (for CI/CD deployments) -DEPLOY_USER="deploy" -if ! id -u "${DEPLOY_USER}" >/dev/null 2>&1; then - log_info "Creating deploy user..." - useradd -m -s /bin/bash -c "Deployment User" "${DEPLOY_USER}" - log_info "Created user: ${DEPLOY_USER}" - log_warn "Remember to add SSH public key to /home/${DEPLOY_USER}/.ssh/authorized_keys" -else - log_info "User ${DEPLOY_USER} already exists" -fi - # Create directories log_info "Creating directories..." -mkdir -p /opt/silo/bin -mkdir -p /etc/silo/schemas +mkdir -p "${INSTALL_DIR}/bin" +mkdir -p "${INSTALL_DIR}/src" +mkdir -p "${CONFIG_DIR}/schemas" mkdir -p /var/log/silo # Set ownership -chown -R silo:silo /opt/silo -chown root:silo /etc/silo -chmod 750 /etc/silo +chown -R silo:silo "${INSTALL_DIR}" +chown root:silo "${CONFIG_DIR}" +chmod 750 "${CONFIG_DIR}" chown silo:silo /var/log/silo chmod 750 /var/log/silo -log_info "Directories created" +log_success "Directories created" # Create environment file if it doesn't exist -ENV_FILE="/etc/silo/silod.env" +ENV_FILE="${CONFIG_DIR}/silod.env" if [[ ! -f "${ENV_FILE}" ]]; then - log_info "Creating environment file template..." + log_info "Creating environment file..." cat > "${ENV_FILE}" << 'EOF' # Silo daemon environment variables # Fill in the values below # Database credentials (psql.kindred.internal) +# Database: silo, User: silo SILO_DB_PASSWORD= # MinIO credentials (minio.kindred.internal) @@ -89,62 +173,30 @@ SILO_MINIO_SECRET_KEY= EOF chmod 600 "${ENV_FILE}" chown root:silo "${ENV_FILE}" - log_warn "Edit ${ENV_FILE} and fill in credentials!" + log_warn "Created ${ENV_FILE} - YOU MUST EDIT THIS FILE!" else - log_info "Environment file already exists: ${ENV_FILE}" + log_info "Environment file already exists" fi -# Configure sudoers for deploy user -SUDOERS_FILE="/etc/sudoers.d/silo-deploy" -log_info "Configuring sudoers for deploy user..." -cat > "${SUDOERS_FILE}" << EOF -# Allow deploy user to manage silo service without password -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/systemctl start silod -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/systemctl stop silod -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/systemctl restart silod -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/systemctl status silod -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/systemctl enable silod -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/systemctl disable silod -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/systemctl is-active silod -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/systemctl daemon-reload -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/journalctl -u silod * +# Clone repository +log_info "Cloning repository..." -# Allow deploy user to manage silo files -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/mv /tmp/silod.new /opt/silo/bin/silod -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/mv /tmp/silod /opt/silo/bin/silod -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/mv /tmp/config.yaml /etc/silo/config.yaml -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/mv /tmp/silod.service /etc/systemd/system/silod.service -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/mv /tmp/silo-schemas/* /etc/silo/schemas/ -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/chmod * /opt/silo/bin/silod -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/chmod * /etc/silo/config.yaml -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/chmod * /etc/systemd/system/silod.service -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/chmod -R * /etc/silo/schemas/* -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/chown * /opt/silo/bin/silod -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/chown * /etc/silo/config.yaml -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/chown -R * /etc/silo/schemas -${DEPLOY_USER} ALL=(ALL) NOPASSWD: /bin/rm -rf /etc/silo/schemas/* -EOF -chmod 440 "${SUDOERS_FILE}" - -# Validate sudoers -if visudo -cf "${SUDOERS_FILE}"; then - log_info "Sudoers configuration valid" +if [[ -d "${INSTALL_DIR}/src/.git" ]]; then + log_info "Repository already cloned, pulling latest..." + cd "${INSTALL_DIR}/src" + git fetch origin + git checkout "${REPO_BRANCH}" + git reset --hard "origin/${REPO_BRANCH}" else - log_error "Sudoers configuration invalid!" - rm -f "${SUDOERS_FILE}" - exit 1 + rm -rf "${INSTALL_DIR}/src" + git clone --branch "${REPO_BRANCH}" "${REPO_URL}" "${INSTALL_DIR}/src" fi -# Create SSH directory for deploy user -DEPLOY_SSH_DIR="/home/${DEPLOY_USER}/.ssh" -if [[ ! -d "${DEPLOY_SSH_DIR}" ]]; then - mkdir -p "${DEPLOY_SSH_DIR}" - touch "${DEPLOY_SSH_DIR}/authorized_keys" - chmod 700 "${DEPLOY_SSH_DIR}" - chmod 600 "${DEPLOY_SSH_DIR}/authorized_keys" - chown -R "${DEPLOY_USER}:${DEPLOY_USER}" "${DEPLOY_SSH_DIR}" - log_info "Created SSH directory for deploy user" -fi +cd "${INSTALL_DIR}/src" +log_success "Repository ready at $(git rev-parse --short HEAD)" + +# Set ownership of source +chown -R silo:silo "${INSTALL_DIR}/src" # Summary echo "" @@ -154,18 +206,20 @@ log_info "============================================" echo "" echo "Next steps:" echo "" -echo "1. Edit /etc/silo/silod.env and fill in credentials:" -echo " sudo nano /etc/silo/silod.env" +echo "1. Edit ${ENV_FILE} and fill in credentials:" +echo " sudo nano ${ENV_FILE}" echo "" -echo "2. Add the CI/CD SSH public key to deploy user:" -echo " echo 'ssh-ed25519 AAAA...' >> /home/${DEPLOY_USER}/.ssh/authorized_keys" -echo "" -echo "3. Verify connectivity from CI/CD server:" -echo " ssh ${DEPLOY_USER}@silo.kindred.internal 'echo OK'" -echo "" -echo "4. Test database connectivity:" +echo "2. Verify database connectivity:" echo " psql -h psql.kindred.internal -U silo -d silo -c 'SELECT 1'" echo "" -echo "5. Test MinIO connectivity:" -echo " curl -I https://minio.kindred.internal:9000/minio/health/live" +echo "3. Verify MinIO connectivity:" +echo " curl -I http://minio.kindred.internal:9000/minio/health/live" +echo "" +echo "4. Run the deployment:" +echo " sudo ${INSTALL_DIR}/src/scripts/deploy.sh" +echo "" +echo "After deployment, manage the service with:" +echo " sudo systemctl status silod" +echo " sudo systemctl restart silod" +echo " sudo journalctl -u silod -f" echo ""