diff --git a/Dockerfile b/Dockerfile
index 513e5dfdb..d28764cb8 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -59,8 +59,13 @@ COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY startup.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/startup.sh
-RUN mkdir -p /tmp/.cloudflared && chmod 777 /tmp/.cloudflared
-ENV CF_CONFIG_DIR=/tmp/.cloudflared
+RUN mkdir -p /etc/cloudflared && \
+ chown -R node:node /etc/cloudflared && \
+ chmod -R 755 /etc/cloudflared
+
+# Set Cloudflared config directory to a volume mount location
+ENV CF_CONFIG_PATH=/etc/cloudflared/config.yml
+ENV CF_CREDS_PATH=/etc/cloudflared/creds.json
# Use the startup script as the entrypoint
ENTRYPOINT ["/usr/local/bin/startup.sh"]
diff --git a/src/client/HelpModal.ts b/src/client/HelpModal.ts
index ad7bc184d..0f2aebe60 100644
--- a/src/client/HelpModal.ts
+++ b/src/client/HelpModal.ts
@@ -1,6 +1,6 @@
import { LitElement, html } from "lit";
import { customElement, query } from "lit/decorators.js";
-import { getEmojiKey, getModifierKey, translateText } from "../client/Utils";
+import { getAltKey, getModifierKey, translateText } from "../client/Utils";
import "./components/Difficulties";
import "./components/Maps";
@@ -67,7 +67,7 @@ export class HelpModal extends LitElement {
|
|
|
- ALT + R
+ ${getAltKey()} +
+ R
|
${translateText("help_modal.action_reset_gfx")} |
diff --git a/src/client/InputHandler.ts b/src/client/InputHandler.ts
index 82801c746..a4ba1eae6 100644
--- a/src/client/InputHandler.ts
+++ b/src/client/InputHandler.ts
@@ -129,7 +129,7 @@ export class InputHandler {
attackRatioUp: "Digit2",
boatAttack: "KeyB",
modifierKey: "ControlLeft",
- emojiKey: "AltLeft",
+ altKey: "AltLeft",
...JSON.parse(localStorage.getItem("settings.keybinds") ?? "{}"),
};
@@ -137,7 +137,6 @@ export class InputHandler {
const isMac = /Mac/.test(navigator.userAgent);
if (isMac) {
this.keybinds.modifierKey = "MetaLeft"; // Use Command key on Mac
- this.keybinds.emojiKey = "AltLeft"; // Use Option key for emoji menu on Mac
}
this.canvas.addEventListener("pointerdown", (e) => this.onPointerDown(e));
@@ -312,7 +311,7 @@ export class InputHandler {
this.eventBus.emit(new ShowBuildMenuEvent(event.clientX, event.clientY));
return;
}
- if (this.isEmojiKeyPressed(event)) {
+ if (this.isAltKeyPressed(event)) {
this.eventBus.emit(new ShowEmojiMenuEvent(event.clientX, event.clientY));
return;
}
@@ -421,12 +420,12 @@ export class InputHandler {
);
}
- isEmojiKeyPressed(event: PointerEvent): boolean {
+ isAltKeyPressed(event: PointerEvent): boolean {
return (
- (this.keybinds.emojiKey === "AltLeft" && event.altKey) ||
- (this.keybinds.emojiKey === "ControlLeft" && event.ctrlKey) ||
- (this.keybinds.emojiKey === "ShiftLeft" && event.shiftKey) ||
- (this.keybinds.emojiKey === "MetaLeft" && event.metaKey)
+ (this.keybinds.altKey === "AltLeft" && event.altKey) ||
+ (this.keybinds.altKey === "ControlLeft" && event.ctrlKey) ||
+ (this.keybinds.altKey === "ShiftLeft" && event.shiftKey) ||
+ (this.keybinds.altKey === "MetaLeft" && event.metaKey)
);
}
}
diff --git a/src/client/Main.ts b/src/client/Main.ts
index 4138224df..9e5c6dfc1 100644
--- a/src/client/Main.ts
+++ b/src/client/Main.ts
@@ -290,6 +290,9 @@ class Client {
() => {
console.log("Closing modals");
document.getElementById("settings-button")?.classList.add("hidden");
+ document
+ .getElementById("username-validation-error")
+ ?.classList.add("hidden");
[
"single-player-modal",
"host-lobby-modal",
diff --git a/src/client/UsernameInput.ts b/src/client/UsernameInput.ts
index c0cd900c9..3b3c24b7d 100644
--- a/src/client/UsernameInput.ts
+++ b/src/client/UsernameInput.ts
@@ -47,6 +47,7 @@ export class UsernameInput extends LitElement {
/>
${this.validationError
? html`
${this.validationError}
diff --git a/src/client/Utils.ts b/src/client/Utils.ts
index 7a884c9cf..d8e782f16 100644
--- a/src/client/Utils.ts
+++ b/src/client/Utils.ts
@@ -160,11 +160,11 @@ export function getModifierKey(): string {
}
}
-export function getEmojiKey(): string {
+export function getAltKey(): string {
const isMac = /Mac/.test(navigator.userAgent);
if (isMac) {
return "⌥"; // Option key
} else {
- return "Ctrl";
+ return "Alt";
}
}
diff --git a/src/client/graphics/layers/TeamStats.ts b/src/client/graphics/layers/TeamStats.ts
index dcbbd10b0..42a603a69 100644
--- a/src/client/graphics/layers/TeamStats.ts
+++ b/src/client/graphics/layers/TeamStats.ts
@@ -63,7 +63,6 @@ export class TeamStats extends LitElement implements Layer {
let totalScoreSort = 0;
for (const p of teamPlayers) {
- totalGold += p.gold();
if (p.isAlive()) {
totalTroops += p.troops();
totalGold += p.gold();
diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts
index 69315c4db..8f4cca4c5 100644
--- a/src/core/configuration/Config.ts
+++ b/src/core/configuration/Config.ts
@@ -60,7 +60,8 @@ export interface ServerConfig {
subdomain(): string;
cloudflareAccountId(): string;
cloudflareApiToken(): string;
- cloudflareConfigDir(): string;
+ cloudflareConfigPath(): string;
+ cloudflareCredsPath(): string;
}
export interface NukeMagnitude {
diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts
index b3daebb94..c59841798 100644
--- a/src/core/configuration/DefaultConfig.ts
+++ b/src/core/configuration/DefaultConfig.ts
@@ -78,9 +78,13 @@ export abstract class DefaultServerConfig implements ServerConfig {
cloudflareApiToken(): string {
return process.env.CF_API_TOKEN ?? "";
}
- cloudflareConfigDir(): string {
- return process.env.CF_CONFIG_DIR ?? "";
+ cloudflareConfigPath(): string {
+ return process.env.CF_CONFIG_PATH ?? "";
}
+ cloudflareCredsPath(): string {
+ return process.env.CF_CREDS_PATH ?? "";
+ }
+
private publicKey: JWK;
abstract jwtAudience(): string;
jwtIssuer(): string {
diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts
index 90d129b96..2314ce747 100644
--- a/src/core/execution/FakeHumanExecution.ts
+++ b/src/core/execution/FakeHumanExecution.ts
@@ -470,6 +470,7 @@ export class FakeHumanExecution implements Execution {
this.mg.isOceanShore(t),
)
: Array.from(this.player.tiles());
+ if (tiles.length === 0) return null;
return this.random.randElement(tiles);
}
diff --git a/src/server/Cloudflare.ts b/src/server/Cloudflare.ts
index ceea88fc3..aac1f3bf7 100644
--- a/src/server/Cloudflare.ts
+++ b/src/server/Cloudflare.ts
@@ -1,7 +1,6 @@
import { spawn } from "child_process";
import { promises as fs } from "fs";
import yaml from "js-yaml";
-import { join } from "path";
import { logger } from "./Logger";
const log = logger.child({
@@ -48,9 +47,11 @@ export class Cloudflare {
constructor(
private accountId: string,
private apiToken: string,
- private configDir: string,
+ private configPath: string,
+ private credsPath: string,
) {
- log.info(`Using config directory: ${this.configDir}`);
+ log.info(`Using config: ${this.configPath}`);
+ log.info(`Using credentials: ${this.credsPath}`);
}
private async makeRequest(
@@ -77,11 +78,19 @@ export class Cloudflare {
return response.json() as Promise;
}
+ public async configAlreadyExists(): Promise {
+ try {
+ await fs.access(this.configPath);
+ return true;
+ } catch {
+ return false;
+ }
+ }
+
public async createTunnel(config: TunnelConfig): Promise<{
tunnelId: string;
tunnelToken: string;
tunnelUrl: string;
- configPath: string;
}> {
const { domain, subdomain, subdomainToService } = config;
@@ -108,7 +117,7 @@ export class Cloudflare {
log.info(`Tunnel created with ID: ${tunnelId}`);
// Create local config file instead of using API configuration
- const configPath = await this.writeTunnelConfig(
+ await this.writeTunnelConfig(
tunnelId,
tunnelToken,
subdomain,
@@ -136,7 +145,7 @@ export class Cloudflare {
const tunnelUrl = `https://${subdomain}.${domain}`;
log.info(`Tunnel is set up! Site will be available at: ${tunnelUrl}`);
- return { tunnelId, tunnelToken, tunnelUrl, configPath };
+ return { tunnelId, tunnelToken, tunnelUrl };
}
private async writeTunnelConfig(
@@ -146,12 +155,8 @@ export class Cloudflare {
domain: string,
subdomainToService: Map,
tunnelName: string,
- ): Promise {
+ ): Promise {
log.info(`Creating local config for tunnel ${subdomain}.${domain}...`);
-
- const configPath = join(this.configDir, `${tunnelName}.yml`);
- const credentialsFile = join(this.configDir, `${tunnelId}.json`);
-
const tokenData = JSON.parse(
Buffer.from(tunnelToken, "base64").toString("utf8"),
);
@@ -164,15 +169,15 @@ export class Cloudflare {
};
await fs.writeFile(
- credentialsFile,
+ this.credsPath,
JSON.stringify(credentials, null, 2),
"utf8",
);
- log.info(`Created credentials file at: ${credentialsFile}`);
+ log.info(`Created credentials file at: ${this.credsPath}`);
const tunnelConfig: CloudflaredConfig = {
tunnel: tunnelId,
- "credentials-file": credentialsFile,
+ "credentials-file": this.credsPath,
ingress: [
...Array.from(subdomainToService.entries()).map(
([subdomain, service]) => ({
@@ -187,10 +192,8 @@ export class Cloudflare {
};
// Write config file
- await fs.writeFile(configPath, yaml.dump(tunnelConfig), "utf8");
- log.info(`Created config file at: ${configPath}`);
-
- return configPath;
+ await fs.writeFile(this.configPath, yaml.dump(tunnelConfig), "utf8");
+ log.info(`Created config file at: ${this.configPath}`);
}
private async updateDNSRecord(
@@ -229,10 +232,10 @@ export class Cloudflare {
}
}
- public async startCloudflared(configPath: string) {
+ public async startCloudflared() {
const cloudflared = spawn(
"cloudflared",
- ["tunnel", "--config", configPath, "--loglevel", "error", "run"],
+ ["tunnel", "--config", this.configPath, "--loglevel", "error", "run"],
{
detached: true,
stdio: ["ignore", "pipe", "pipe"],
diff --git a/src/server/Server.ts b/src/server/Server.ts
index 92cecff2f..8486f974a 100644
--- a/src/server/Server.ts
+++ b/src/server/Server.ts
@@ -36,7 +36,8 @@ async function setupTunnels() {
const cloudflare = new Cloudflare(
config.cloudflareAccountId(),
config.cloudflareApiToken(),
- config.cloudflareConfigDir(),
+ config.cloudflareConfigPath(),
+ config.cloudflareCredsPath(),
);
const domainToService = new Map().set(
@@ -51,11 +52,15 @@ async function setupTunnels() {
);
}
- const tunnel = await cloudflare.createTunnel({
- subdomain: config.subdomain(),
- domain: config.domain(),
- subdomainToService: domainToService,
- } as TunnelConfig);
+ if (!(await cloudflare.configAlreadyExists())) {
+ await cloudflare.createTunnel({
+ subdomain: config.subdomain(),
+ domain: config.domain(),
+ subdomainToService: domainToService,
+ } as TunnelConfig);
+ } else {
+ console.log("Config already exists, skipping tunnel creation");
+ }
- await cloudflare.startCloudflared(tunnel.configPath);
+ await cloudflare.startCloudflared();
}
diff --git a/tests/util/TestServerConfig.ts b/tests/util/TestServerConfig.ts
index b6f6f8442..be5155d5a 100644
--- a/tests/util/TestServerConfig.ts
+++ b/tests/util/TestServerConfig.ts
@@ -4,7 +4,10 @@ import { GameMapType } from "../../src/core/game/Game";
import { GameID } from "../../src/core/Schemas";
export class TestServerConfig implements ServerConfig {
- cloudflareConfigDir(): string {
+ cloudflareConfigPath(): string {
+ throw new Error("Method not implemented.");
+ }
+ cloudflareCredsPath(): string {
throw new Error("Method not implemented.");
}
domain(): string {
diff --git a/update.sh b/update.sh
index 761c353f9..5bfa32dc3 100755
--- a/update.sh
+++ b/update.sh
@@ -58,10 +58,15 @@ else
fi
echo "Starting new container for ${HOST} environment..."
+
+# Remove any existing volume for this container if it exists
+docker volume rm "cloudflared-${CONTAINER_NAME}" 2> /dev/null || true
+
docker run -d \
--restart="${RESTART}" \
--env-file "$ENV_FILE" \
--name "${CONTAINER_NAME}" \
+ -v "cloudflared-${CONTAINER_NAME}:/etc/cloudflared" \
"${DOCKER_IMAGE}"
if [ $? -eq 0 ]; then