mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 17:00:16 +00:00
update to hetzner deployment
This commit is contained in:
@@ -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 "======================================================="
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -203,7 +203,7 @@
|
||||
</g>
|
||||
</svg>
|
||||
<div class="flex justify-center text-sm font-bold mt-[-5px] logo-version">
|
||||
v0.17.6
|
||||
v0.17.7
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+37
-28
@@ -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<GameRecord> {
|
||||
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<GameRecord> {
|
||||
|
||||
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<GameRecord> {
|
||||
|
||||
export async function gameRecordExists(gameId: GameID): Promise<boolean> {
|
||||
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) {
|
||||
|
||||
@@ -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."
|
||||
@@ -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 <environment>"
|
||||
if [ $# -lt 3 ]; then
|
||||
echo "Error: Required parameters missing"
|
||||
echo "Usage: $0 <environment> <docker_username> <docker_repo>"
|
||||
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
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "======================================================"
|
||||
echo "✅ SERVER UPDATE COMPLETED SUCCESSFULLY"
|
||||
echo "Container name: ${CONTAINER_NAME}"
|
||||
echo "Image: ${FULL_IMAGE_NAME}"
|
||||
echo "======================================================"
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user