From ea9b8b141d3d5b39a4325e59088b0a67bd734091 Mon Sep 17 00:00:00 2001 From: Evan Date: Mon, 10 Mar 2025 17:02:15 -0700 Subject: [PATCH] update to hetzner deployment --- deploy.sh | 134 +++++++++++++++ openfront-setup.sh | 21 --- setup.sh | 214 ++++++++++++++++++++++++ src/client/index.html | 2 +- src/core/configuration/Config.ts | 7 + src/core/configuration/DefaultConfig.ts | 19 +++ src/core/configuration/DevConfig.ts | 7 + src/core/configuration/PreprodConfig.ts | 3 + src/core/configuration/ProdConfig.ts | 3 + src/server/Archive.ts | 65 +++---- update-deploy.sh | 52 ------ update.sh | 59 ++++--- upload.sh | 107 ------------ 13 files changed, 463 insertions(+), 230 deletions(-) create mode 100755 deploy.sh delete mode 100644 openfront-setup.sh create mode 100644 setup.sh delete mode 100755 update-deploy.sh delete mode 100755 upload.sh diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 000000000..dba9fffc1 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,134 @@ +#!/bin/bash +# deploy.sh - Complete deployment script for Hetzner with Docker Hub and R2 +# This script: +# 1. Builds and uploads the Docker image to Docker Hub with appropriate tag +# 2. Copies the update script to Hetzner server +# 3. Executes the update script on the Hetzner server + +set -e # Exit immediately if a command exits with a non-zero status + +# Function to print section headers +print_header() { + echo "======================================================" + echo "🚀 $1" + echo "======================================================" +} + +# Load environment variables +if [ -f .env ]; then + echo "Loading configuration from .env file..." + export $(grep -v '^#' .env | xargs) +fi + +# Check command line argument +if [ $# -ne 1 ] || ([ "$1" != "staging" ] && [ "$1" != "prod" ]); then + echo "Error: Please specify environment (staging or prod)" + echo "Usage: $0 [staging|prod]" + exit 1 +fi + +# TODO: fix this - need to build before creating the image +bun run build-prod + +ENV=$1 +VERSION_TAG="latest" +DOCKER_REPO="" + +# Set environment-specific variables +if [ "$ENV" == "staging" ]; then + print_header "DEPLOYING TO STAGING ENVIRONMENT" + SERVER_HOST=$SERVER_HOST_STAGING + DOCKER_REPO=$DOCKER_REPO_STAGING +else + print_header "DEPLOYING TO PRODUCTION ENVIRONMENT" + SERVER_HOST=$SERVER_HOST_PROD + DOCKER_REPO=$DOCKER_REPO_PROD +fi + +# Check required environment variables +if [ -z "$SERVER_HOST" ]; then + echo "Error: SERVER_HOST_${ENV^^} not defined in .env file or environment" + exit 1 +fi + +# Configuration +SSH_KEY=${SSH_KEY:-"~/.ssh/id_rsa"} # Use default or override from .env +DOCKER_USERNAME=${DOCKER_USERNAME} # Docker Hub username +UPDATE_SCRIPT="./update.sh" # Path to your update script +REMOTE_UPDATE_SCRIPT="/root/update-openfront.sh" # Where to place the script on server + +# Check if update script exists +if [ ! -f "$UPDATE_SCRIPT" ]; then + echo "Error: Update script $UPDATE_SCRIPT not found!" + exit 1 +fi + +# Step 1: Build and upload Docker image to Docker Hub +print_header "STEP 1: Building and uploading Docker image to Docker Hub" +echo "Environment: ${ENV}" +echo "Using version tag: $VERSION_TAG" +echo "Docker repository: $DOCKER_REPO" + +# Get Git commit for build info +GIT_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "unknown") +echo "Git commit: $GIT_COMMIT" + +docker buildx build \ + --no-cache \ + --platform linux/amd64 \ + --build-arg GIT_COMMIT=$GIT_COMMIT \ + -t $DOCKER_USERNAME/$DOCKER_REPO:$VERSION_TAG \ + --push \ + . + +if [ $? -ne 0 ]; then + echo "❌ Docker build failed. Stopping deployment." + exit 1 +fi + +if [ $? -ne 0 ]; then + echo "❌ Failed to push image to Docker Hub. Stopping deployment." + exit 1 +fi + +echo "✅ Docker image built and pushed successfully." + +# Step 2: Copy update script to Hetzner server +print_header "STEP 2: Copying update script to server" +echo "Target: $SERVER_HOST" + +# Make sure the update script is executable +chmod +x $UPDATE_SCRIPT + +# Copy the update script to the server +scp -i $SSH_KEY $UPDATE_SCRIPT $SERVER_HOST:$REMOTE_UPDATE_SCRIPT + +# Copy environment variables if needed +if [ -f .env ]; then + scp -i $SSH_KEY .env $SERVER_HOST:/root/.env + # Secure the .env file + ssh -i $SSH_KEY $SERVER_HOST "chmod 600 /root/.env" +fi + +if [ $? -ne 0 ]; then + echo "❌ Failed to copy update script to server. Stopping deployment." + exit 1 +fi + +echo "✅ Update script successfully copied to server." + +# Step 3: Execute the update script on the server +print_header "STEP 3: Executing update script on server" + +# Make the script executable on the remote server and execute it with the environment parameter +ssh -i $SSH_KEY $SERVER_HOST "chmod +x $REMOTE_UPDATE_SCRIPT && $REMOTE_UPDATE_SCRIPT $ENV $DOCKER_USERNAME $DOCKER_REPO" + +if [ $? -ne 0 ]; then + echo "❌ Failed to execute update script on server." + exit 1 +fi + +print_header "DEPLOYMENT COMPLETED SUCCESSFULLY" +echo "✅ New version deployed to ${ENV} environment!" +echo "🌐 Check your ${ENV} server to verify the deployment." +echo "=======================================================" \ No newline at end of file diff --git a/openfront-setup.sh b/openfront-setup.sh deleted file mode 100644 index 33b428eff..000000000 --- a/openfront-setup.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -# Executed on ec2 startup - -yum update -y -amazon-linux-extras install docker -y -service docker start -systemctl enable docker -usermod -a -G docker ec2-user - -# Install AWS CLI v2 -curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" -unzip awscliv2.zip -./aws/install - -# Authenticate to ECR and run container -AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) -aws ecr get-login-password --region eu-west-1 | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.eu-west-1.amazonaws.com - -docker pull ${AWS_ACCOUNT_ID}.dkr.ecr.eu-west-1.amazonaws.com/openfront:latest -docker run -d -p 80:80 ${AWS_ACCOUNT_ID}.dkr.ecr.eu-west-1.amazonaws.com/openfront:latest \ No newline at end of file diff --git a/setup.sh b/setup.sh new file mode 100644 index 000000000..b6af5bc39 --- /dev/null +++ b/setup.sh @@ -0,0 +1,214 @@ +#!/bin/bash +# Comprehensive idempotent setup script for Hetzner server with Docker, Docker Compose, and Cloudflare R2 configuration +# Exit on error +set -e + +echo 'export EDITOR=vim' >> ~/.bashrc +source ~/.bashrc + +echo "🔄 Updating system..." +apt update && apt upgrade -y + +# Docker installation - check if already installed +if command -v docker &> /dev/null; then + echo "✅ Docker is already installed" +else + echo "🐳 Installing Docker..." + # Install Docker using official script + curl -fsSL https://get.docker.com -o get-docker.sh + sh get-docker.sh + rm get-docker.sh + # Make sure Docker is enabled to start at boot + systemctl enable docker + echo "✅ Docker installed successfully" +fi + +# Check if Docker is running +if systemctl is-active --quiet docker; then + echo "✅ Docker service is already running" +else + echo "🚀 Starting Docker service..." + systemctl start docker + echo "✅ Docker service started" +fi + +# Docker Compose v1 installation - check if already installed +if command -v docker-compose &> /dev/null; then + echo "✅ Docker Compose v1 is already installed" +else + echo "🔧 Installing Docker Compose v1..." + # Get latest docker compose version + COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep 'tag_name' | cut -d\" -f4) + # Install Docker Compose v1 + curl -L "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose + echo "✅ Docker Compose v1 installed successfully" +fi + +# Docker Compose v2 installation - check if already installed +if command -v docker compose &> /dev/null; then + echo "✅ Docker Compose plugin (v2) is already installed" +else + echo "🔧 Installing Docker Compose plugin (v2)..." + # Install Docker Compose v2 + apt install -y docker-compose-plugin + echo "✅ Docker Compose plugin (v2) installed successfully" +fi + +# Verify Docker Compose installations +echo "Verifying Docker Compose installations..." +echo "Docker Compose v1:" +docker-compose --version +echo "Docker Compose v2:" +docker compose version + +# Docker Hub login - only prompt if not already logged in +if [ ! -f ~/.docker/config.json ] || ! grep -q "auth" ~/.docker/config.json; then + echo "🔐 Setting up Docker Hub login..." + docker login + echo "✅ Docker Hub login configured" +else + echo "✅ Docker Hub login already configured" +fi + +# AWS CLI installation - check if already installed +if command -v aws &> /dev/null; then + echo "✅ AWS CLI is already installed" +else + echo "☁️ Installing AWS CLI for Cloudflare R2..." + # Install AWS CLI + apt install -y unzip curl + curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" + unzip awscliv2.zip + ./aws/install + rm -rf aws awscliv2.zip + echo "✅ AWS CLI installed successfully" +fi + +# R2 configuration - check if already configured +if [ -f ~/.aws/credentials ] && grep -q "\[r2\]" ~/.aws/credentials; then + echo "✅ R2 configuration already exists" + echo "Do you want to update the R2 configuration? (y/n)" + read update_r2 + if [ "$update_r2" = "y" ]; then + configure_r2=true + else + configure_r2=false + fi +else + configure_r2=true +fi + +if [ "$configure_r2" = true ]; then + # Configure AWS CLI for R2 + echo "🔧 Configuring AWS CLI for Cloudflare R2..." + echo "Enter your Cloudflare R2 Access Key ID:" + read R2_ACCESS_KEY + echo "Enter your Cloudflare R2 Secret Access Key:" + read -s R2_SECRET_KEY + echo "Enter your Cloudflare Account ID:" + read CLOUDFLARE_ACCOUNT_ID + + # Create R2 profile configuration + mkdir -p ~/.aws + + # Update or create credentials file + if [ -f ~/.aws/credentials ]; then + # Remove existing r2 section if it exists + sed -i '/\[r2\]/,/^$/d' ~/.aws/credentials + fi + + # Append r2 credentials + cat >> ~/.aws/credentials << EOL +[r2] +aws_access_key_id = $R2_ACCESS_KEY +aws_secret_access_key = $R2_SECRET_KEY +EOL + + # Update or create config file + if [ -f ~/.aws/config ]; then + # Remove existing r2 profile if it exists + sed -i '/\[profile r2\]/,/^$/d' ~/.aws/config + fi + + # Append r2 config + cat >> ~/.aws/config << EOL +[profile r2] +region = auto +endpoint_url = https://$CLOUDFLARE_ACCOUNT_ID.r2.cloudflarestorage.com +EOL + echo "✅ R2 configuration complete" +fi + +# Setting up Node Exporter for system metrics +echo "📊 Setting up Node Exporter..." + +# Create a monitoring network if it doesn't exist +if ! docker network inspect monitoring &>/dev/null; then + echo "Creating monitoring network..." + docker network create monitoring +else + echo "✅ Monitoring network already exists" +fi + +# Check if Node Exporter is already running correctly +if docker ps | grep -q "node_exporter"; then + echo "✅ Node Exporter is already running" +else + # Remove existing container if it exists but not running + if docker ps -a | grep -q node_exporter; then + echo "Removing existing stopped Node Exporter container..." + docker rm -f node_exporter + fi + + # Run Node Exporter container + echo "Starting Node Exporter..." + docker run -d \ + --name node_exporter \ + --restart unless-stopped \ + --network monitoring \ + -p 9100:9100 \ + -v "/proc:/host/proc:ro" \ + -v "/sys:/host/sys:ro" \ + -v "/:/rootfs:ro" \ + prom/node-exporter:latest \ + --path.procfs=/host/proc \ + --path.sysfs=/host/sys \ + --path.rootfs=/rootfs \ + --collector.filesystem.mount-points-exclude="^/(sys|proc|dev|host|etc)($$|/)" + echo "✅ Node Exporter is now running and exposing metrics on port 9100" +fi + +# Setting up Loki Docker driver for log collection +echo "📜 Setting up Loki Docker driver..." + +# Check if plugin is already installed and up to date +if docker plugin ls | grep -q "loki.*latest.*true"; then + echo "✅ Loki Docker driver is already installed and enabled" +else + # Remove plugin if it exists but not up to date or not enabled + if docker plugin ls | grep -q "loki"; then + echo "Updating Loki Docker driver..." + docker plugin disable -f loki + docker plugin rm -f loki + fi + + # Install Loki Docker driver + echo "Installing Loki Docker driver..." + docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions + echo "✅ Loki Docker driver installed successfully!" +fi + +echo "Note: Configure your containers with the Loki logging driver by adding this to your docker-compose.yml:" +echo " + logging: + driver: loki + options: + loki-url: \"http://your-loki-server:3100/loki/api/v1/push\" + loki-batch-size: \"400\" + loki-external-labels: \"job=your_app,environment=production\" +" + +echo "🎉 Setup complete!" +echo "Test your R2 connection: aws s3 ls --profile r2" +echo "Metrics available at: http://$(hostname -I | awk '{print $1}'):9100/metrics" \ No newline at end of file diff --git a/src/client/index.html b/src/client/index.html index 49e6fac7f..41cdf8665 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -203,7 +203,7 @@
- v0.17.6 + v0.17.7
diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index 32da46374..13b413fce 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -98,6 +98,13 @@ export interface ServerConfig { workerPort(gameID: GameID): number; workerPortByIndex(workerID: number): number; env(): GameEnv; + adminToken(): string; + adminHeader(): string; + // Only available on the server + r2Bucket(): string; + r2Endpoint(): string; + r2AccessKey(): string; + r2SecretKey(): string; } export interface Config { diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 694489a56..09b4d751d 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -22,6 +22,25 @@ import { pastelTheme } from "./PastelTheme"; import { pastelThemeDark } from "./PastelThemeDark"; export abstract class DefaultServerConfig implements ServerConfig { + gitCommit(): string { + return process.env.GIT_COMMIT; + } + r2Endpoint(): string { + return process.env.R2_ENDPOINT; + } + r2AccessKey(): string { + return process.env.R2_ACCESS_KEY; + } + r2SecretKey(): string { + return process.env.R2_SECRET_KEY; + } + abstract r2Bucket(): string; + adminHeader(): string { + return "x-admin-key"; + } + adminToken(): string { + return process.env.ADMIN_TOKEN; + } abstract numWorkers(): number; abstract env(): GameEnv; abstract discordRedirectURI(): string; diff --git a/src/core/configuration/DevConfig.ts b/src/core/configuration/DevConfig.ts index abeb91ecf..639d7f57f 100644 --- a/src/core/configuration/DevConfig.ts +++ b/src/core/configuration/DevConfig.ts @@ -5,6 +5,13 @@ import { GameEnv, ServerConfig } from "./Config"; import { DefaultConfig, DefaultServerConfig } from "./DefaultConfig"; export class DevServerConfig extends DefaultServerConfig { + r2Bucket(): string { + return "openfront-staging"; + } + adminToken(): string { + return "WARNING_DEV_ADMIN_KEY_DO_NOT_USE_IN_PRODUCTION"; + } + env(): GameEnv { return GameEnv.Dev; } diff --git a/src/core/configuration/PreprodConfig.ts b/src/core/configuration/PreprodConfig.ts index 3b1aa061f..9340053d4 100644 --- a/src/core/configuration/PreprodConfig.ts +++ b/src/core/configuration/PreprodConfig.ts @@ -2,6 +2,9 @@ import { GameEnv } from "./Config"; import { DefaultConfig, DefaultServerConfig } from "./DefaultConfig"; export const preprodConfig = new (class extends DefaultServerConfig { + r2Bucket(): string { + return "openfront-staging"; + } env(): GameEnv { return GameEnv.Preprod; } diff --git a/src/core/configuration/ProdConfig.ts b/src/core/configuration/ProdConfig.ts index 71b9bd122..8d6860358 100644 --- a/src/core/configuration/ProdConfig.ts +++ b/src/core/configuration/ProdConfig.ts @@ -2,6 +2,9 @@ import { GameEnv } from "./Config"; import { DefaultConfig, DefaultServerConfig } from "./DefaultConfig"; export const prodConfig = new (class extends DefaultServerConfig { + r2Bucket(): string { + return "openfront-prod"; + } numWorkers(): number { return 6; } diff --git a/src/server/Archive.ts b/src/server/Archive.ts index 01094599f..143fc018d 100644 --- a/src/server/Archive.ts +++ b/src/server/Archive.ts @@ -1,6 +1,5 @@ import { GameRecord, GameID } from "../core/Schemas"; import { S3 } from "@aws-sdk/client-s3"; -import { RedshiftData } from "@aws-sdk/client-redshift-data"; import { GameEnv, getServerConfigFromServer, @@ -8,22 +7,31 @@ import { const config = getServerConfigFromServer(); -const s3 = new S3({ region: "eu-west-1" }); +// R2 client configuration +const r2 = new S3({ + region: "auto", // R2 ignores region, but it's required by the SDK + endpoint: config.r2Endpoint(), // You'll need to add this to your config + credentials: { + accessKeyId: config.r2AccessKey(), // You'll need to add these + secretAccessKey: config.r2SecretKey(), // credential methods to your config + }, +}); -const gameBucket = "openfront-games"; -const analyticsBucket = "openfront-analytics"; +const bucket = config.r2Bucket(); +const gameFolder = "games"; +const analyticsFolder = "analytics"; export async function archive(gameRecord: GameRecord) { try { - // Archive to Redshift Serverless - await archiveAnalyticsToS3(gameRecord); + // Archive to R2 + await archiveAnalyticsToR2(gameRecord); - // Archive to S3 if there are turns + // Archive full game if there are turns if (gameRecord.turns.length > 0) { console.log( - `${gameRecord.id}: game has more than zero turns, attempting to write to full game to S3`, + `${gameRecord.id}: game has more than zero turns, attempting to write to full game to R2`, ); - await archiveFullGameToS3(gameRecord); + await archiveFullGameToR2(gameRecord); } } catch (error) { console.error(`${gameRecord.id}: Final archive error: ${error}`, { @@ -35,8 +43,8 @@ export async function archive(gameRecord: GameRecord) { } } -async function archiveAnalyticsToS3(gameRecord: GameRecord) { - // Create analytics data object (similar to what was going to Redshift) +async function archiveAnalyticsToR2(gameRecord: GameRecord) { + // Create analytics data object const analyticsData = { id: gameRecord.id, env: config.env(), @@ -60,17 +68,17 @@ async function archiveAnalyticsToS3(gameRecord: GameRecord) { // Store analytics data using just the game ID as the key const analyticsKey = `${gameRecord.id}.json`; - await s3.putObject({ - Bucket: analyticsBucket, - Key: analyticsKey, + await r2.putObject({ + Bucket: bucket, + Key: `${analyticsFolder}/${analyticsKey}`, Body: JSON.stringify(analyticsData), ContentType: "application/json", }); - console.log(`${gameRecord.id}: successfully wrote game analytics to S3`); + console.log(`${gameRecord.id}: successfully wrote game analytics to R2`); } catch (error) { console.error( - `${gameRecord.id}: Error writing game analytics to S3: ${error}`, + `${gameRecord.id}: Error writing game analytics to R2: ${error}`, { message: error?.message || error, stack: error?.stack, @@ -82,7 +90,7 @@ async function archiveAnalyticsToS3(gameRecord: GameRecord) { } } -async function archiveFullGameToS3(gameRecord: GameRecord) { +async function archiveFullGameToR2(gameRecord: GameRecord) { // Create a deep copy to avoid modifying the original const recordCopy = JSON.parse(JSON.stringify(gameRecord)); @@ -93,9 +101,9 @@ async function archiveFullGameToS3(gameRecord: GameRecord) { }); try { - await s3.putObject({ - Bucket: gameBucket, - Key: recordCopy.id, + await r2.putObject({ + Bucket: bucket, + Key: `${gameFolder}/${recordCopy.id}`, Body: JSON.stringify(recordCopy), ContentType: "application/json", }); @@ -104,15 +112,15 @@ async function archiveFullGameToS3(gameRecord: GameRecord) { throw error; } - console.log(`${gameRecord.id}: game record successfully written to S3`); + console.log(`${gameRecord.id}: game record successfully written to R2`); } export async function readGameRecord(gameId: GameID): Promise { try { // Check if file exists and download in one operation - const response = await s3.getObject({ - Bucket: gameBucket, - Key: gameId, + const response = await r2.getObject({ + Bucket: bucket, + Key: `${gameFolder}/${gameId}`, // Fixed - needed to include gameFolder }); // Parse the response body @@ -121,7 +129,8 @@ export async function readGameRecord(gameId: GameID): Promise { return gameRecord as GameRecord; } catch (error) { - console.error(`${gameId}: Error reading game record from S3: ${error}`, { + // Log the error for monitoring purposes + console.error(`${gameId}: Error reading game record from R2: ${error}`, { message: error?.message || error, stack: error?.stack, name: error?.name, @@ -133,9 +142,9 @@ export async function readGameRecord(gameId: GameID): Promise { export async function gameRecordExists(gameId: GameID): Promise { try { - await s3.headObject({ - Bucket: gameBucket, - Key: gameId, + await r2.headObject({ + Bucket: bucket, + Key: `${gameFolder}/${gameId}`, // Fixed - needed to include gameFolder }); return true; } catch (error) { diff --git a/update-deploy.sh b/update-deploy.sh deleted file mode 100755 index ca46381f5..000000000 --- a/update-deploy.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -# Check if the --env flag is provided -if [[ "$1" != "--env" ]]; then - echo "Usage: $0 --env [dev|prod]" - exit 1 -fi - -# Get the environment from the command line argument -ENV="$2" - -# Validate the environment -if [[ "$ENV" != "dev" && "$ENV" != "prod" ]]; then - echo "Invalid environment. Use 'dev' or 'prod'." - exit 1 -fi - -# Set the instance name based on the environment -if [[ "$ENV" == "dev" ]]; then - INSTANCE_NAME="openfrontio-dev-instance" - TAG="dev" - GAME_ENV="preprod" - echo "[DEV] Deploying to openfront.dev" -else - INSTANCE_NAME="openfrontio-instance" - TAG="latest" - GAME_ENV="prod" - echo "[PROD] Deploying to openfront.io" -fi - -# Ensure you're authenticated with Google Cloud -gcloud auth configure-docker us-central1-docker.pkg.dev - -# Build the new Docker image with platform specification and GAME_ENV build argument -docker build --platform linux/amd64 --build-arg GAME_ENV=$GAME_ENV -t openfrontio . - -# Tag the new image -docker tag openfrontio us-central1-docker.pkg.dev/openfrontio/openfrontio/game-server:$TAG - -# Push the new image to Google Container Registry -docker push us-central1-docker.pkg.dev/openfrontio/openfrontio/game-server:$TAG - -# Prune Docker system on the instance -gcloud compute ssh $INSTANCE_NAME --zone us-central1-a --command 'docker system prune -f -a' -gcloud compute ssh $INSTANCE_NAME --zone us-central1-a --command 'docker kill $(docker ps -q)' -gcloud compute ssh $INSTANCE_NAME --zone us-central1-a --command 'docker rmi $(docker images -q) -f' - -# Update the GCE instance with the new container image -gcloud compute instances update-container $INSTANCE_NAME \ - --container-image us-central1-docker.pkg.dev/openfrontio/openfrontio/game-server:$TAG \ - --zone=us-central1-a - -echo "Deployment to $ENV environment complete. New version should be live soon on $INSTANCE_NAME." \ No newline at end of file diff --git a/update.sh b/update.sh index 5f0d6c50c..230cd2449 100755 --- a/update.sh +++ b/update.sh @@ -1,28 +1,38 @@ #!/bin/bash -# Script to update Docker container +# update.sh - Script to update Docker container on Hetzner server +# Called by deploy.sh after uploading Docker image to Docker Hub # Check if environment parameter is provided -if [ -z "$1" ]; then - echo "Error: Environment parameter is required (prod or staging)" - echo "Usage: $0 " +if [ $# -lt 3 ]; then + echo "Error: Required parameters missing" + echo "Usage: $0 " exit 1 fi -# Set environment from parameter +# Set parameters ENV=$1 +DOCKER_USERNAME=$2 +DOCKER_REPO=$3 + +# Container and image configuration CONTAINER_NAME="openfront-${ENV}" -LOG_GROUP="/aws/ec2/docker-containers/${ENV}" +IMAGE_NAME="${DOCKER_USERNAME}/${DOCKER_REPO}" +FULL_IMAGE_NAME="${IMAGE_NAME}:latest" -# Get AWS account ID -AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) -ECR_REPO="${AWS_ACCOUNT_ID}.dkr.ecr.eu-west-1.amazonaws.com/openfront:latest" +echo "======================================================" +echo "🔄 UPDATING SERVER: ${ENV} ENVIRONMENT" +echo "======================================================" +echo "Container name: ${CONTAINER_NAME}" +echo "Docker image: ${FULL_IMAGE_NAME}" -echo "Deploying to ${ENV} environment..." -echo "Logging in to ECR..." -aws ecr get-login-password --region eu-west-1 | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.eu-west-1.amazonaws.com +# Load environment variables if .env exists +if [ -f /root/.env ]; then + echo "Loading environment variables from .env file..." + export $(grep -v '^#' /root/.env | xargs) +fi -echo "Pulling latest image..." -docker pull $ECR_REPO +echo "Pulling latest image from Docker Hub..." +docker pull $FULL_IMAGE_NAME echo "Checking for existing container..." # Check for running container @@ -64,17 +74,17 @@ fi echo "Starting new container for ${ENV} environment..." docker run -d -p 80:80 \ --restart=always \ - --log-driver=awslogs \ - --log-opt awslogs-region=eu-west-1 \ - --log-opt awslogs-group=${LOG_GROUP} \ - --log-opt awslogs-create-group=true \ + $VOLUME_MOUNTS \ + $NETWORK_FLAGS \ --env GAME_ENV=${ENV} \ + --env-file /root/.env \ --name ${CONTAINER_NAME} \ - $ECR_REPO + $FULL_IMAGE_NAME if [ $? -eq 0 ]; then echo "Update complete! New ${ENV} container is running." - # Final cleanup after successful deployment + + # Final cleanup after successful deployment echo "Performing final cleanup of unused Docker resources..." echo "Removing unused images (not tagged and not referenced)..." docker image prune -f @@ -82,4 +92,11 @@ if [ $? -eq 0 ]; then echo "Cleanup complete." else echo "Failed to start container" -fi \ No newline at end of file + exit 1 +fi + +echo "======================================================" +echo "✅ SERVER UPDATE COMPLETED SUCCESSFULLY" +echo "Container name: ${CONTAINER_NAME}" +echo "Image: ${FULL_IMAGE_NAME}" +echo "======================================================" \ No newline at end of file diff --git a/upload.sh b/upload.sh deleted file mode 100755 index de92eca61..000000000 --- a/upload.sh +++ /dev/null @@ -1,107 +0,0 @@ -#!/bin/bash - -# Script to build and upload OpenFront Docker image to ECR -# Usage: ./upload-openfront.sh [version_tag] - -# Load environment variables from .env file if it exists -if [ -f .env ]; then - echo "Loading configuration from .env file..." - export $(grep -v '^#' .env | xargs) -fi - -# Configuration with fallbacks -AWS_REGION=${AWS_REGION:-"eu-west-1"} -ECR_REPO_NAME=${ECR_REPO_NAME:-"openfront"} -AWS_ACCOUNT_ID=${AWS_ACCOUNT_ID:-$(aws sts get-caller-identity --query Account --output text)} -ECR_REPO_URI="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO_NAME" - -# Default version tag is 'latest' if not provided -VERSION_TAG=${1:-"latest"} - -echo "===== OpenFront Docker Image Upload Script =====" -echo "Repository: $ECR_REPO_URI" -echo "Version tag: $VERSION_TAG" -echo "================================================" - -# Check if Docker is installed -if ! command -v docker &> /dev/null; then - echo "Error: Docker is not installed. Please install Docker first." - exit 1 -fi - -# Check if AWS CLI is installed -if ! command -v aws &> /dev/null; then - echo "Error: AWS CLI is not installed. Please install AWS CLI first." - exit 1 -fi - -# Check if we're in the correct directory -if [ ! -f "Dockerfile" ]; then - echo "Error: Dockerfile not found in current directory." - echo "Please run this script from the directory containing your Dockerfile." - exit 1 -fi - -# Ensure the ECR repository exists -echo "Ensuring ECR repository exists..." -aws ecr describe-repositories --repository-names $ECR_REPO_NAME --region $AWS_REGION &> /dev/null -if [ $? -ne 0 ]; then - echo "Creating ECR repository $ECR_REPO_NAME..." - aws ecr create-repository --repository-name $ECR_REPO_NAME --region $AWS_REGION - if [ $? -ne 0 ]; then - echo "Error: Failed to create ECR repository." - exit 1 - fi -fi - -# Build the Docker image -echo "Building Docker image..." -docker buildx build --platform linux/amd64 -t $ECR_REPO_NAME:$VERSION_TAG . -if [ $? -ne 0 ]; then - echo "Error: Docker build failed." - exit 1 -fi - -# Authenticate to ECR -echo "Authenticating to ECR..." -aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ECR_REPO_URI -if [ $? -ne 0 ]; then - echo "Error: Failed to authenticate to ECR." - exit 1 -fi - -# Tag the image for ECR -echo "Tagging image for ECR..." -docker tag $ECR_REPO_NAME:$VERSION_TAG $ECR_REPO_URI:$VERSION_TAG -if [ $? -ne 0 ]; then - echo "Error: Failed to tag image." - exit 1 -fi - -# Push the image to ECR -echo "Pushing image to ECR..." -docker push $ECR_REPO_URI:$VERSION_TAG -if [ $? -ne 0 ]; then - echo "Error: Failed to push image to ECR." - exit 1 -fi - -# Also tag and push as 'latest' if we're using a specific version -if [ "$VERSION_TAG" != "latest" ]; then - echo "Also tagging as 'latest'..." - docker tag $ECR_REPO_NAME:$VERSION_TAG $ECR_REPO_URI:latest - docker push $ECR_REPO_URI:latest -fi - -echo "Verifying upload..." -aws ecr describe-images --repository-name $ECR_REPO_NAME --region $AWS_REGION --query "imageDetails[?contains(imageTags, '$VERSION_TAG')]" - -echo "================================================" -echo "✅ Success! Image uploaded to $ECR_REPO_URI:$VERSION_TAG" -echo "================================================" - -# Print helpful deployment instructions -echo "To deploy this image to your EC2 instance, SSH into your instance and run:" -echo "docker pull $ECR_REPO_URI:$VERSION_TAG" -echo "docker stop \$(docker ps -q --filter ancestor=$ECR_REPO_URI)" -echo "docker run -d -p 80:80 $ECR_REPO_URI:$VERSION_TAG" \ No newline at end of file