mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-28 03:44:33 +00:00
update to hetzner deployment
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user