#!/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 "$@"