mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:30:45 +00:00
cloudflare fixed tunnel name (#1096)
## Description: The binary created a new tunnel on startup, and if the container crashed looped, then it would generate 100s of tunnels causing us to reach the 1000 tunnel limit. Now the config is stored on a mounted volume, so if the container restarts it sees the existing config and does not create a new tunnel. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
This commit is contained in:
+7
-2
@@ -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"]
|
||||
|
||||
@@ -60,7 +60,8 @@ export interface ServerConfig {
|
||||
subdomain(): string;
|
||||
cloudflareAccountId(): string;
|
||||
cloudflareApiToken(): string;
|
||||
cloudflareConfigDir(): string;
|
||||
cloudflareConfigPath(): string;
|
||||
cloudflareCredsPath(): string;
|
||||
}
|
||||
|
||||
export interface NukeMagnitude {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+23
-20
@@ -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<T>(
|
||||
@@ -77,11 +78,19 @@ export class Cloudflare {
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
public async configAlreadyExists(): Promise<boolean> {
|
||||
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<string, string>,
|
||||
tunnelName: string,
|
||||
): Promise<string> {
|
||||
): Promise<void> {
|
||||
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"],
|
||||
|
||||
+12
-7
@@ -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<string, string>().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();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user