upgrade.sh raw
1 #!/bin/bash
2 # ORLY Relay Upgrade Script
3 #
4 # Single-command upgrade: reads version from pkg/version/version, builds the
5 # unified binary (with embedded web UI), deploys to the target host via rsync,
6 # and restarts the systemd service.
7 #
8 # Usage:
9 # ./scripts/upgrade.sh # Build + deploy + restart relay.orly.dev
10 # ./scripts/upgrade.sh --dry-run # Show what would happen, don't execute
11 # ./scripts/upgrade.sh --build-only # Build locally, don't deploy
12 # ./scripts/upgrade.sh --host new.orly.dev # Deploy to a different host
13 # ./scripts/upgrade.sh --no-restart # Deploy without restarting the service
14 # ./scripts/upgrade.sh --no-web # Skip web UI rebuild (faster iteration)
15 # ./scripts/upgrade.sh --skip-deploy # Build and show version, don't deploy
16 #
17 # The typical workflow is:
18 # 1. Edit pkg/version/version (bump the version)
19 # 2. Run: ./scripts/upgrade.sh
20 # 3. The script builds, deploys, and restarts automatically.
21 #
22 # Architecture:
23 # - Builds the UNIFIED binary (./cmd/orly) which includes launcher subcommands
24 # - Target arch is amd64 (relay.orly.dev is x86_64, NOT arm64)
25 # - Web UI is rebuilt and embedded unless --no-web is passed
26 # - Previous binary is preserved on the server for manual rollback
27
28 set -euo pipefail
29
30 # --- Configuration ---
31 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
32 PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
33 VERSION_FILE="$PROJECT_DIR/pkg/version/version"
34
35 DEPLOY_HOST="relay.orly.dev"
36 DEPLOY_USER="root"
37 DEPLOY_IP="69.164.249.71"
38 REMOTE_BIN="/home/mleku/.local/bin/orly"
39 SERVICE_NAME="orly"
40 SSH_KEY="$HOME/.ssh/id_ed25519"
41 SSH_OPTS="-i $SSH_KEY -o IdentitiesOnly=yes"
42
43 # Build settings
44 TARGET_GOOS="linux"
45 TARGET_GOARCH="amd64"
46 BUILD_OUTPUT="$PROJECT_DIR/orly"
47
48 # Flags
49 DRY_RUN=false
50 BUILD_ONLY=false
51 NO_RESTART=false
52 NO_WEB=false
53
54 # Colors
55 RED='\033[0;31m'
56 GREEN='\033[0;32m'
57 YELLOW='\033[1;33m'
58 BLUE='\033[0;34m'
59 BOLD='\033[1m'
60 NC='\033[0m'
61
62 log() { echo -e "${BLUE}[upgrade]${NC} $1"; }
63 ok() { echo -e "${GREEN}[upgrade]${NC} $1"; }
64 warn() { echo -e "${YELLOW}[upgrade]${NC} $1"; }
65 err() { echo -e "${RED}[upgrade]${NC} $1" >&2; }
66 bold() { echo -e "${BOLD}$1${NC}"; }
67
68 # --- Parse arguments ---
69 while [[ $# -gt 0 ]]; do
70 case $1 in
71 --dry-run) DRY_RUN=true; shift ;;
72 --build-only) BUILD_ONLY=true; shift ;;
73 --no-restart) NO_RESTART=true; shift ;;
74 --no-web) NO_WEB=true; shift ;;
75 --skip-deploy) BUILD_ONLY=true; shift ;;
76 --host) DEPLOY_HOST="$2"; shift 2 ;;
77 --help|-h)
78 head -25 "$0" | tail -23
79 exit 0
80 ;;
81 *)
82 err "Unknown option: $1"
83 exit 1
84 ;;
85 esac
86 done
87
88 # --- Preflight checks ---
89 cd "$PROJECT_DIR"
90
91 if [[ ! -f "$VERSION_FILE" ]]; then
92 err "Version file not found: $VERSION_FILE"
93 exit 1
94 fi
95
96 VERSION=$(cat "$VERSION_FILE" | tr -d '[:space:]')
97 if [[ -z "$VERSION" ]]; then
98 err "Version file is empty"
99 exit 1
100 fi
101
102 # Check for required tools
103 for cmd in go rsync ssh; do
104 if ! command -v "$cmd" &>/dev/null; then
105 err "Required command not found: $cmd"
106 exit 1
107 fi
108 done
109
110 if [[ "$NO_WEB" == false ]] && ! command -v bun &>/dev/null; then
111 warn "bun not found — skipping web UI build (use --no-web to suppress this warning)"
112 NO_WEB=true
113 fi
114
115 # Show plan
116 echo ""
117 bold "ORLY Upgrade Plan"
118 echo " Version: $VERSION"
119 echo " Binary: unified (./cmd/orly)"
120 echo " Target: ${TARGET_GOOS}/${TARGET_GOARCH}"
121 echo " Host: $DEPLOY_HOST"
122 echo " Web UI: $(if $NO_WEB; then echo 'skip'; else echo 'rebuild'; fi)"
123 echo " Restart: $(if $NO_RESTART; then echo 'no'; else echo 'yes'; fi)"
124 if $DRY_RUN; then
125 echo " Mode: DRY RUN (no changes)"
126 fi
127 echo ""
128
129 if $DRY_RUN; then
130 log "Dry run — exiting."
131 exit 0
132 fi
133
134 # --- Step 1: Build web UI ---
135 if [[ "$NO_WEB" == false ]]; then
136 log "Building web UI..."
137 WEB_DIR="$PROJECT_DIR/app/web"
138 if [[ -d "$WEB_DIR" ]]; then
139 (cd "$WEB_DIR" && bun install --silent && bun run build)
140 ok "Web UI built"
141 else
142 warn "Web directory not found at $WEB_DIR — skipping"
143 fi
144 else
145 log "Skipping web UI build (--no-web)"
146 fi
147
148 # --- Step 2: Build unified binary ---
149 log "Building unified binary for ${TARGET_GOOS}/${TARGET_GOARCH}..."
150 CGO_ENABLED=0 GOOS="$TARGET_GOOS" GOARCH="$TARGET_GOARCH" \
151 go build -ldflags "-s -w" -o "$BUILD_OUTPUT" ./cmd/orly
152
153 BINARY_SIZE=$(du -h "$BUILD_OUTPUT" | cut -f1)
154 ok "Built: $BUILD_OUTPUT ($BINARY_SIZE)"
155
156 # Verify the binary has the right architecture
157 BINARY_ARCH=$(file "$BUILD_OUTPUT" | grep -o 'x86-64\|aarch64\|ARM' || echo 'unknown')
158 if [[ "$TARGET_GOARCH" == "amd64" && "$BINARY_ARCH" != *"x86-64"* ]]; then
159 warn "Binary architecture mismatch — expected x86-64, got: $BINARY_ARCH"
160 fi
161
162 if $BUILD_ONLY; then
163 ok "Build complete. Binary: $BUILD_OUTPUT ($VERSION)"
164 exit 0
165 fi
166
167 # --- Step 3: Deploy ---
168 log "Stopping service on $DEPLOY_HOST..."
169 ssh $SSH_OPTS "${DEPLOY_USER}@${DEPLOY_IP}" "systemctl stop $SERVICE_NAME" || warn "Service may not be running"
170
171 log "Backing up current binary..."
172 ssh $SSH_OPTS "${DEPLOY_USER}@${DEPLOY_IP}" \
173 "cp -f $REMOTE_BIN ${REMOTE_BIN}.prev 2>/dev/null || true"
174
175 log "Deploying $VERSION to $DEPLOY_HOST..."
176 rsync -avz --compress -e "ssh $SSH_OPTS" \
177 "$BUILD_OUTPUT" "${DEPLOY_USER}@${DEPLOY_IP}:${REMOTE_BIN}"
178
179 # Fix ownership (we deploy as root but binary should be owned by mleku)
180 ssh $SSH_OPTS "${DEPLOY_USER}@${DEPLOY_IP}" \
181 "chown mleku:mleku $REMOTE_BIN && chmod +x $REMOTE_BIN"
182 ok "Deployed to $DEPLOY_HOST:$REMOTE_BIN"
183
184 # --- Step 4: Restart ---
185 if [[ "$NO_RESTART" == false ]]; then
186 log "Starting service..."
187 ssh $SSH_OPTS "${DEPLOY_USER}@${DEPLOY_IP}" "systemctl start $SERVICE_NAME"
188
189 # Brief pause then verify
190 sleep 3
191 log "Verifying..."
192 if ssh $SSH_OPTS "${DEPLOY_USER}@${DEPLOY_IP}" "systemctl is-active $SERVICE_NAME" | grep -q "active"; then
193 ok "Service is running"
194 else
195 err "Service failed to start — check: ssh ${DEPLOY_USER}@${DEPLOY_IP} 'journalctl -u $SERVICE_NAME -n 50'"
196 err "Rollback: ssh ${DEPLOY_USER}@${DEPLOY_IP} 'cp ${REMOTE_BIN}.prev ${REMOTE_BIN} && systemctl start $SERVICE_NAME'"
197 exit 1
198 fi
199
200 # Show version from running relay
201 REMOTE_VERSION=$(ssh $SSH_OPTS "${DEPLOY_USER}@${DEPLOY_IP}" "$REMOTE_BIN version" 2>/dev/null || echo "unknown")
202 ok "Remote version: $REMOTE_VERSION"
203 else
204 log "Skipping restart (--no-restart)"
205 log "Start manually: ssh ${DEPLOY_USER}@${DEPLOY_IP} 'systemctl start $SERVICE_NAME'"
206 fi
207
208 echo ""
209 ok "Upgrade complete: $VERSION deployed to $DEPLOY_HOST"
210 echo ""
211 echo " Rollback: ssh ${DEPLOY_USER}@${DEPLOY_IP} 'cp ${REMOTE_BIN}.prev ${REMOTE_BIN} && systemctl restart $SERVICE_NAME'"
212 echo " Logs: ssh ${DEPLOY_USER}@${DEPLOY_IP} 'journalctl -u $SERVICE_NAME -f'"
213 echo ""
214