update deployment instructions

This commit is contained in:
Forbes
2026-01-26 21:31:49 -06:00
parent f9324686c5
commit f37052c2f0
6 changed files with 923 additions and 1 deletions

160
.gitea/workflows/ci.yaml Normal file
View File

@@ -0,0 +1,160 @@
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

View File

@@ -0,0 +1,207 @@
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

View File

@@ -3,10 +3,12 @@
# Permissions: chmod 600 /etc/silo/silod.env
# Database credentials (psql.kindred.internal)
# Database: silo, User: silo
SILO_DB_PASSWORD=
# MinIO credentials (minio.kindred.internal)
SILO_MINIO_ACCESS_KEY=
# User: silouser
SILO_MINIO_ACCESS_KEY=silouser
SILO_MINIO_SECRET_KEY=
# Optional: Override server base URL

View File

@@ -2,6 +2,16 @@
This guide covers deploying Silo to a dedicated VM using external PostgreSQL and MinIO services.
## Table of Contents
- [Architecture](#architecture)
- [External Services](#external-services)
- [Automated Deployment (CI/CD)](#automated-deployment-cicd)
- [Manual Deployment](#manual-deployment)
- [Post-Deployment Configuration](#post-deployment-configuration)
- [Maintenance](#maintenance)
- [Troubleshooting](#troubleshooting)
## Architecture
```

372
scripts/deploy.sh Executable file
View File

@@ -0,0 +1,372 @@
#!/usr/bin/env bash
#
# Silo Deployment Script
# Deploys silod to silo.kindred.internal via systemd
#
# Usage:
# ./scripts/deploy.sh [options]
#
# Options:
# --build-only Only build the binary, don't deploy
# --no-build Skip build, deploy existing binary
# --restart-only Only restart the service
# --dry-run Show what would be done without doing it
# --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)
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
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"
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"
# Flags
BUILD=true
DEPLOY=true
DRY_RUN=false
# Functions
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
}
show_help() {
head -20 "$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[@]}" "$@"
fi
}
check_dependencies() {
log_info "Checking 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")
if [[ ${#missing[@]} -gt 0 ]]; then
die "Missing required commands: ${missing[*]}"
fi
log_success "All dependencies available"
}
check_connectivity() {
log_info "Checking connectivity to ${SILO_HOST}..."
if [[ "${DRY_RUN}" == "true" ]]; then
log_info "[DRY-RUN] Would check SSH connectivity"
return 0
fi
if ! ssh_cmd "echo 'Connection successful'" >/dev/null 2>&1; then
die "Cannot connect to ${SILO_HOST} as ${SILO_USER}"
fi
log_success "SSH connection verified"
}
build_binary() {
log_info "Building ${BINARY_NAME} (version: ${BUILD_VERSION})..."
mkdir -p "${BUILD_DIR}"
local ldflags="-w -s -X main.Version=${BUILD_VERSION}"
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
cd "${PROJECT_ROOT}"
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
die "Build failed: binary not found"
fi
local size
size=$(du -h "${BUILD_DIR}/${BINARY_NAME}" | cut -f1)
log_success "Built ${BINARY_NAME} (${size})"
}
deploy_binary() {
log_info "Deploying binary to ${SILO_HOST}..."
local binary="${BUILD_DIR}/${BINARY_NAME}"
if [[ ! -f "${binary}" ]] && [[ "${DRY_RUN}" != "true" ]]; then
die "Binary not found: ${binary}"
fi
# Upload to temp location first
scp_cmd "${binary}" "${SILO_USER}@${SILO_HOST}:/tmp/${BINARY_NAME}.new"
# 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}"
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..."
local schemas_dir="${PROJECT_ROOT}/schemas"
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
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"
log_success "Systemd service deployed"
}
restart_service() {
log_info "Restarting ${REMOTE_SERVICE} service..."
ssh_cmd "sudo systemctl restart ${REMOTE_SERVICE}"
# Wait for service to start
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
else
log_info "[DRY-RUN] Would verify service is 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
# Check health endpoint
local health_status
health_status=$(ssh_cmd "curl -sf http://localhost:8080/health" 2>/dev/null || echo "FAILED")
if [[ "${health_status}" == *"ok"* ]] || [[ "${health_status}" == *"healthy"* ]]; then
log_success "Health check passed"
else
log_warn "Health check returned: ${health_status}"
fi
# 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")
if [[ "${ready_status}" == *"ok"* ]] || [[ "${ready_status}" == *"ready"* ]]; 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"
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}"
}
enable_service() {
log_info "Enabling ${REMOTE_SERVICE} service..."
ssh_cmd "sudo systemctl enable ${REMOTE_SERVICE}"
log_success "Service enabled for auto-start"
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--build-only)
DEPLOY=false
shift
;;
--no-build)
BUILD=false
shift
;;
--restart-only)
BUILD=false
DEPLOY=false
restart_service
exit 0
;;
--dry-run)
DRY_RUN=true
shift
;;
--help|-h)
show_help
;;
*)
die "Unknown option: $1"
;;
esac
done
# Main execution
main() {
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
if [[ "${BUILD}" == "true" ]]; then
build_binary
fi
if [[ "${DEPLOY}" == "true" ]]; then
check_connectivity
deploy_binary
deploy_config
deploy_schemas
deploy_systemd
enable_service
restart_service
verify_deployment
fi
echo ""
log_success "Deployment complete!"
}
main "$@"

171
scripts/setup-host.sh Executable file
View File

@@ -0,0 +1,171 @@
#!/usr/bin/env bash
#
# Silo Host Setup Script
# Run this on silo.kindred.internal to prepare for deployment
#
# Usage:
# sudo ./scripts/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
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
# Check root
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root (use sudo)"
exit 1
fi
log_info "Setting up Silo host..."
# Create silo system user (for running the service)
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"
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 /var/log/silo
# Set ownership
chown -R silo:silo /opt/silo
chown root:silo /etc/silo
chmod 750 /etc/silo
chown silo:silo /var/log/silo
chmod 750 /var/log/silo
log_info "Directories created"
# Create environment file if it doesn't exist
ENV_FILE="/etc/silo/silod.env"
if [[ ! -f "${ENV_FILE}" ]]; then
log_info "Creating environment file template..."
cat > "${ENV_FILE}" << 'EOF'
# Silo daemon environment variables
# Fill in the values below
# Database credentials (psql.kindred.internal)
SILO_DB_PASSWORD=
# MinIO credentials (minio.kindred.internal)
# User: silouser
SILO_MINIO_ACCESS_KEY=silouser
SILO_MINIO_SECRET_KEY=
# Optional overrides
# SILO_SERVER_BASE_URL=http://silo.kindred.internal:8080
EOF
chmod 600 "${ENV_FILE}"
chown root:silo "${ENV_FILE}"
log_warn "Edit ${ENV_FILE} and fill in credentials!"
else
log_info "Environment file already exists: ${ENV_FILE}"
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 *
# 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"
else
log_error "Sudoers configuration invalid!"
rm -f "${SUDOERS_FILE}"
exit 1
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
# Summary
echo ""
log_info "============================================"
log_info "Host setup complete!"
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 ""
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 " 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 ""