update aws deployment, have client get env from server

This commit is contained in:
Evan
2025-03-05 12:37:37 -08:00
parent be5157a25c
commit 2b26cfbbc9
15 changed files with 159 additions and 66 deletions
+4 -5
View File
@@ -18,12 +18,13 @@ import {
} from "../core/game/GameUpdates";
import { WorkerClient } from "../core/worker/WorkerClient";
import { consolex, initRemoteSender } from "../core/Consolex";
import { getConfig, getServerConfig } from "../core/configuration/Config";
import { getConfig, ServerConfig } from "../core/configuration/Config";
import { GameView, PlayerView } from "../core/game/GameView";
import { GameUpdateViewData } from "../core/game/GameUpdates";
import { UserSettings } from "../core/game/UserSettings";
export interface LobbyConfig {
serverConfig: ServerConfig;
flag: () => string;
playerName: () => string;
clientID: ClientID;
@@ -51,8 +52,6 @@ export function joinLobby(
`joinging lobby: gameID: ${lobbyConfig.gameID}, clientID: ${lobbyConfig.clientID}, persistentID: ${lobbyConfig.persistentID}`,
);
const serverConfig = getServerConfig();
const userSettings: UserSettings = new UserSettings();
let gameConfig: GameConfig = null;
if (lobbyConfig.gameType == GameType.Singleplayer) {
@@ -72,7 +71,7 @@ export function joinLobby(
lobbyConfig,
gameConfig,
eventBus,
serverConfig,
lobbyConfig.serverConfig,
);
const onconnect = () => {
@@ -106,7 +105,7 @@ export async function createClientGame(
transport: Transport,
userSettings: UserSettings,
): Promise<ClientGameRunner> {
const config = getConfig(gameConfig, userSettings);
const config = await getConfig(gameConfig, userSettings);
const gameMap = await loadTerrainMap(gameConfig.gameMap);
const worker = new WorkerClient(
+16 -13
View File
@@ -7,7 +7,10 @@ import "./components/Difficulties";
import { DifficultyDescription } from "./components/Difficulties";
import "./components/Maps";
import { generateID } from "../core/Util";
import { getConfig, getServerConfig } from "../core/configuration/Config";
import {
getConfig,
getServerConfigFromClient,
} from "../core/configuration/Config";
@customElement("host-lobby-modal")
export class HostLobbyModal extends LitElement {
@@ -584,8 +587,9 @@ export class HostLobbyModal extends LitElement {
}
private async putGameConfig() {
const config = await getServerConfigFromClient();
const response = await fetch(
`${window.location.origin}/${getServerConfig().workerPath(this.lobbyId)}/game/${this.lobbyId}`,
`${window.location.origin}/${config.workerPath(this.lobbyId)}/game/${this.lobbyId}`,
{
method: "PUT",
headers: {
@@ -609,8 +613,9 @@ export class HostLobbyModal extends LitElement {
`Starting private game with map: ${GameMapType[this.selectedMap]}`,
);
this.close();
const config = await getServerConfigFromClient();
const response = await fetch(
`${window.location.origin}/${getServerConfig().workerPath(this.lobbyId)}/start_game/${this.lobbyId}`,
`${window.location.origin}/${config.workerPath(this.lobbyId)}/start_game/${this.lobbyId}`,
{
method: "POST",
headers: {
@@ -636,15 +641,13 @@ export class HostLobbyModal extends LitElement {
}
private async pollPlayers() {
fetch(
`/${getServerConfig().workerPath(this.lobbyId)}/game/${this.lobbyId}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
const config = await getServerConfigFromClient();
fetch(`/${config.workerPath(this.lobbyId)}/game/${this.lobbyId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
)
})
.then((response) => response.json())
.then((data: GameInfo) => {
console.log(`got response: ${data}`);
@@ -654,11 +657,11 @@ export class HostLobbyModal extends LitElement {
}
async function createLobby(): Promise<GameInfo> {
const serverConfig = getServerConfig();
const config = await getServerConfigFromClient();
try {
const id = generateID();
const response = await fetch(
`/${serverConfig.workerPath(id)}/create_game/${id}`,
`/${config.workerPath(id)}/create_game/${id}`,
{
method: "POST",
headers: {
+6 -4
View File
@@ -1,9 +1,9 @@
import { LitElement, css, html } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import { getServerConfig } from "../core/configuration/Config";
import { consolex } from "../core/Consolex";
import { GameMapType, GameType } from "../core/game/Game";
import { GameInfo } from "../core/Schemas";
import { getServerConfigFromClient } from "../core/configuration/Config";
@customElement("join-private-lobby-modal")
export class JoinPrivateLobbyModal extends LitElement {
@@ -355,12 +355,13 @@ export class JoinPrivateLobbyModal extends LitElement {
}
}
private joinLobby() {
private async joinLobby() {
const lobbyId = this.lobbyIdInput.value;
consolex.log(`Joining lobby with ID: ${lobbyId}`);
this.message = "Checking lobby..."; // Set initial message
const url = `/${getServerConfig().workerPath(lobbyId)}/game/${lobbyId}/exists`;
const config = await getServerConfigFromClient();
const url = `/${config.workerPath(lobbyId)}/game/${lobbyId}/exists`;
fetch(url, {
method: "GET",
headers: {
@@ -398,9 +399,10 @@ export class JoinPrivateLobbyModal extends LitElement {
private async pollPlayers() {
if (!this.lobbyIdInput?.value) return;
const config = await getServerConfigFromClient();
fetch(
`/${getServerConfig().workerPath(this.lobbyIdInput.value)}/game/${this.lobbyIdInput.value}`,
`/${config.workerPath(this.lobbyIdInput.value)}/game/${this.lobbyIdInput.value}`,
{
method: "GET",
headers: {
+2 -7
View File
@@ -1,9 +1,4 @@
import {
Config,
GameEnv,
getServerConfig,
ServerConfig,
} from "../core/configuration/Config";
import { Config, GameEnv, ServerConfig } from "../core/configuration/Config";
import { consolex } from "../core/Consolex";
import { GameEvent } from "../core/EventBus";
import {
@@ -130,7 +125,7 @@ export class LocalServer {
const blob = new Blob([JSON.stringify(GameRecordSchema.parse(record))], {
type: "application/json",
});
const workerPath = getServerConfig().workerPath(this.lobbyConfig.gameID);
const workerPath = this.serverConfig.workerPath(this.lobbyConfig.gameID);
navigator.sendBeacon(`/${workerPath}/archive_singleplayer_game`, blob);
}
}
+3
View File
@@ -19,6 +19,7 @@ import "./DarkModeButton";
import { DarkModeButton } from "./DarkModeButton";
import { HelpModal } from "./HelpModal";
import { GameType } from "../core/game/Game";
import { getServerConfigFromClient } from "../core/configuration/Config";
class Client {
private gameStop: () => void;
@@ -135,9 +136,11 @@ class Client {
consolex.log("joining lobby, stopping existing game");
this.gameStop();
}
const config = await getServerConfigFromClient();
const gameType = event.detail.gameType;
this.gameStop = joinLobby(
{
serverConfig: config,
gameType: gameType,
flag: (): string => this.flagInput.getCurrentFlag(),
playerName: (): string => this.usernameInput.getCurrentUsername(),
+1 -1
View File
@@ -32,7 +32,7 @@ export async function createGameRunner(
clientID: ClientID,
callBack: (gu: GameUpdateViewData) => void,
): Promise<GameRunner> {
const config = getConfig(gameConfig, null);
const config = await getConfig(gameConfig, null);
const gameMap = await loadGameMap(gameConfig.gameMap);
const game = createGame(
gameMap.gameMap,
+42 -16
View File
@@ -22,21 +22,24 @@ import { GameMap, TileRef } from "../game/GameMap";
import { PlayerView } from "../game/GameView";
import { UserSettings } from "../game/UserSettings";
let cachedSC: ServerConfig = null;
export enum GameEnv {
Dev,
Preprod,
Prod,
}
export function getConfig(
export async function getConfig(
gameConfig: GameConfig,
userSettings: UserSettings | null = null,
): Config {
const sc = getServerConfig();
switch (process.env.GAME_ENV) {
case "dev":
): Promise<Config> {
const sc = await getServerConfigFromClient();
switch (sc.env()) {
case GameEnv.Dev:
return new DevConfig(sc, gameConfig, userSettings);
case "preprod":
case "prod":
case GameEnv.Preprod:
case GameEnv.Prod:
consolex.log("using prod config");
return new DefaultConfig(sc, gameConfig, userSettings);
default:
@@ -44,20 +47,43 @@ export function getConfig(
}
}
export function getServerConfig(): ServerConfig {
switch (process.env.GAME_ENV) {
export async function getServerConfigFromClient(): Promise<ServerConfig> {
if (cachedSC) {
return cachedSC;
}
const response = await fetch("/api/env");
if (!response.ok) {
throw new Error(
`Failed to fetch server config: ${response.status} ${response.statusText}`,
);
}
const config = await response.json();
// Log the retrieved configuration
console.log("Server config loaded:", config);
cachedSC = getServerConfig(config.game_env);
return cachedSC;
}
export function getServerConfigFromServer(): ServerConfig {
const gameEnv = process.env.GAME_ENV;
return getServerConfig(gameEnv);
}
function getServerConfig(gameEnv: string) {
switch (gameEnv) {
case "dev":
consolex.log("using dev config");
consolex.log("using dev server config");
return new DevServerConfig();
case "preprod":
consolex.log("using preprod config");
case "staging":
consolex.log("using preprod server config");
return preprodConfig;
case "prod":
default:
consolex.log("using prod config");
consolex.log("using prod server config");
return prodConfig;
// default:
// throw Error(`unsupported server configuration: ${process.env.GAME_ENV}`)
default:
throw Error(`unsupported server configuration: ${gameEnv}`);
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ import { pastelThemeDark } from "./PastelThemeDark";
export abstract class DefaultServerConfig implements ServerConfig {
numWorkers(): number {
return 2;
return 6;
}
abstract env(): GameEnv;
abstract discordRedirectURI(): string;
+3
View File
@@ -15,6 +15,9 @@ export class DevServerConfig extends DefaultServerConfig {
discordRedirectURI(): string {
return "http://localhost:3000/auth/callback";
}
numWorkers(): number {
return 2;
}
}
export class DevConfig extends DefaultConfig {
+3
View File
@@ -8,4 +8,7 @@ export const preprodConfig = new (class extends DefaultServerConfig {
discordRedirectURI(): string {
return "https://openfront.dev/auth/callback";
}
numWorkers(): number {
return 3;
}
})();
+17 -2
View File
@@ -4,14 +4,17 @@ import express from "express";
import { GameMapType, GameType, Difficulty } from "../core/game/Game";
import { generateID } from "../core/Util";
import { PseudoRandom } from "../core/PseudoRandom";
import { GameEnv, getServerConfig } from "../core/configuration/Config";
import {
GameEnv,
getServerConfigFromServer,
} from "../core/configuration/Config";
import { GameInfo } from "../core/Schemas";
import path from "path";
import rateLimit from "express-rate-limit";
import { fileURLToPath } from "url";
import { isHighTrafficTime } from "./Util";
const config = getServerConfig();
const config = getServerConfigFromServer();
const readyWorkers = new Set();
const app = express();
@@ -121,6 +124,18 @@ export async function startMaster() {
});
}
app.get("/api/env", (req, res) => {
const envConfig = {
game_env: process.env.GAME_ENV || "prod",
};
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
res.set("Pragma", "no-cache");
res.set("Expires", "0");
res.json(envConfig);
});
// Add lobbies endpoint to list public games for this worker
app.get("/public_lobbies", (req, res) => {
res.send(publicLobbiesJsonStr);
+2 -2
View File
@@ -4,7 +4,7 @@ import { WebSocketServer } from "ws";
import path from "path";
import { fileURLToPath } from "url";
import { GameManager } from "./GameManager";
import { getServerConfig } from "../core/configuration/Config";
import { getServerConfigFromServer } from "../core/configuration/Config";
import { WebSocket } from "ws";
import { Client } from "./Client";
import rateLimit from "express-rate-limit";
@@ -14,7 +14,7 @@ import { slog } from "./StructuredLog";
import { GameType } from "../core/game/Game";
import { archive } from "./Archive";
const config = getServerConfig();
const config = getServerConfigFromServer();
// Worker setup
export function startWorker() {
+2 -9
View File
@@ -4,7 +4,6 @@
# 1. Builds and uploads the Docker image to ECR with appropriate tag
# 2. Copies the update script to EC2 instance (staging or prod)
# 3. Executes the update script on the EC2 instance
set -e # Exit immediately if a command exits with a non-zero status
# Function to print section headers
@@ -64,8 +63,6 @@ if [ ! -f "$UPDATE_SCRIPT" ]; then
exit 1
fi
# Step 1: Build and upload Docker image to ECR
print_header "STEP 1: Building and uploading Docker image to ECR"
echo "Environment: ${ENV}"
@@ -73,7 +70,6 @@ echo "Using version tag: $VERSION_TAG"
# Execute the build script with the version tag
$BUILD_SCRIPT $VERSION_TAG
if [ $? -ne 0 ]; then
echo "❌ Build and upload failed. Stopping deployment."
exit 1
@@ -88,20 +84,17 @@ chmod +x $UPDATE_SCRIPT
# Copy the update script to the EC2 instance
scp -i $EC2_KEY $UPDATE_SCRIPT $EC2_HOST:$REMOTE_UPDATE_SCRIPT
if [ $? -ne 0 ]; then
echo "❌ Failed to copy update script to EC2 instance. Stopping deployment."
exit 1
fi
echo "✅ Update script successfully copied to EC2 instance."
# Step 3: Execute the update script on the EC2 instance
print_header "STEP 3: Executing update script on EC2 instance"
# Make the script executable on the remote server and execute it
ssh -i $EC2_KEY $EC2_HOST "chmod +x $REMOTE_UPDATE_SCRIPT && $REMOTE_UPDATE_SCRIPT"
# Make the script executable on the remote server and execute it with the environment parameter
ssh -i $EC2_KEY $EC2_HOST "chmod +x $REMOTE_UPDATE_SCRIPT && $REMOTE_UPDATE_SCRIPT $ENV"
if [ $? -ne 0 ]; then
echo "❌ Failed to execute update script on EC2 instance."
exit 1
+56 -6
View File
@@ -1,10 +1,23 @@
#!/bin/bash
# Script to update Docker container
# Check if environment parameter is provided
if [ -z "$1" ]; then
echo "Error: Environment parameter is required (prod or staging)"
echo "Usage: $0 <environment>"
exit 1
fi
# Set environment from parameter
ENV=$1
CONTAINER_NAME="openfront-${ENV}"
LOG_GROUP="/aws/ec2/docker-containers/${ENV}"
# 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 "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
@@ -13,29 +26,66 @@ docker pull $ECR_REPO
echo "Checking for existing container..."
# Check for running container
RUNNING_CONTAINER=$(docker ps | grep openfront | awk '{print $1}')
RUNNING_CONTAINER=$(docker ps | grep ${CONTAINER_NAME} | awk '{print $1}')
if [ -n "$RUNNING_CONTAINER" ]; then
echo "Stopping running container $RUNNING_CONTAINER..."
docker stop $RUNNING_CONTAINER
echo "Waiting for container to fully stop and release resources..."
sleep 5 # Add a 5-second delay
docker rm $RUNNING_CONTAINER
echo "Container $RUNNING_CONTAINER stopped and removed."
fi
# Also check for stopped containers with the same name
STOPPED_CONTAINER=$(docker ps -a | grep openfront | awk '{print $1}')
STOPPED_CONTAINER=$(docker ps -a | grep ${CONTAINER_NAME} | awk '{print $1}')
if [ -n "$STOPPED_CONTAINER" ]; then
echo "Removing stopped container $STOPPED_CONTAINER..."
docker rm $STOPPED_CONTAINER
echo "Container $STOPPED_CONTAINER removed."
fi
echo "Starting new container..."
# Check if port 80 is still in use
echo "Checking if port 80 is still in use..."
if command -v lsof >/dev/null 2>&1; then
PORT_CHECK=$(lsof -i :80 | grep LISTEN)
elif command -v netstat >/dev/null 2>&1; then
PORT_CHECK=$(netstat -tuln | grep ":80 ")
else
PORT_CHECK=""
echo "Warning: Cannot check if port is in use (neither lsof nor netstat found)"
fi
if [ -n "$PORT_CHECK" ]; then
echo "Warning: Port 80 is still in use by another process:"
echo "$PORT_CHECK"
echo "Attempting to proceed anyway..."
fi
echo "Starting new container for ${ENV} environment..."
docker run -d -p 80:80 \
--log-driver=awslogs \
--log-opt awslogs-region=eu-west-1 \
--log-opt awslogs-group=/aws/ec2/docker-containers \
--log-opt awslogs-group=${LOG_GROUP} \
--log-opt awslogs-create-group=true \
--name openfront \
--name ${CONTAINER_NAME} \
$ECR_REPO
echo "Update complete! New container is running."
if [ $? -eq 0 ]; then
echo "Update complete! New ${ENV} container is running."
else
echo "Failed to start container. Trying alternative port 8080..."
docker run -d -p 8080:80 \
--log-driver=awslogs \
--log-opt awslogs-region=eu-west-1 \
--log-opt awslogs-group=${LOG_GROUP} \
--log-opt awslogs-create-group=true \
--name ${CONTAINER_NAME} \
$ECR_REPO
if [ $? -eq 0 ]; then
echo "Container started on port 8080 instead of 80!"
else
echo "Failed to start container on alternative port as well."
exit 1
fi
fi
+1
View File
@@ -186,6 +186,7 @@ export default (env, argv) => {
// Original API endpoints
{
context: [
"/api/env",
"/public_lobbies",
"/join_game",
"/start_game",