From 77908f7a1a7b71de9f5de585b1835c62ab083113 Mon Sep 17 00:00:00 2001 From: Evan Date: Thu, 25 Dec 2025 19:34:36 -0800 Subject: [PATCH] Cleanup unused deployment secrets & args (#2698) ## Description: * Remove unused otel creds * Remove unused R2 creds * remove left-over BASIC_AUTH * Generate an admin token on startup * Removed kick_player since lobby creators already have ability to kick player ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: evan --- .github/workflows/deploy.yml | 7 ---- .github/workflows/release.yml | 28 ------------- build-deploy.sh | 27 +++---------- deploy.sh | 54 ++----------------------- example.env | 8 ---- src/core/configuration/Config.ts | 4 -- src/core/configuration/DefaultConfig.ts | 19 +++------ src/server/Master.ts | 44 ++++---------------- src/server/Worker.ts | 18 --------- tests/util/TestServerConfig.ts | 12 ------ update.sh | 6 +-- 11 files changed, 24 insertions(+), 203 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f18326265..807502598 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -106,21 +106,14 @@ jobs: chmod 600 ~/.ssh/id_rsa - name: 🚢 Deploy env: - ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }} CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} GHCR_REPO: ${{ vars.GHCR_REPO }} GHCR_USERNAME: ${{ vars.GHCR_USERNAME }} ENV: ${{ inputs.target_domain == 'openfront.io' && 'prod' || 'staging' }} HOST: ${{ github.event_name == 'workflow_dispatch' && inputs.target_host || 'staging' }} - OTEL_ENDPOINT: ${{ secrets.OTEL_ENDPOINT }} - OTEL_PASSWORD: ${{ secrets.OTEL_PASSWORD }} - OTEL_USERNAME: ${{ secrets.OTEL_USERNAME }} OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }} OTEL_AUTH_HEADER: ${{ secrets.OTEL_AUTH_HEADER }} - R2_ACCESS_KEY: ${{ secrets.R2_ACCESS_KEY }} - R2_BUCKET: ${{ secrets.R2_BUCKET }} - R2_SECRET_KEY: ${{ secrets.R2_SECRET_KEY }} TURNSTILE_SECRET_KEY: ${{ secrets.TURNSTILE_SECRET_KEY }} API_KEY: ${{ secrets.API_KEY }} SERVER_HOST_MASTERS: ${{ secrets.SERVER_HOST_MASTERS }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 22bbb6ae7..37bfcb865 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,21 +63,14 @@ jobs: chmod 600 ~/.ssh/id_rsa - name: 🚀 Deploy image env: - ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }} CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} GHCR_REPO: openfront-prod GHCR_USERNAME: ${{ vars.GHCR_USERNAME }} DOMAIN: ${{ vars.DOMAIN }} IMAGE_ID: ${{ needs.build.outputs.IMAGE_ID }} - OTEL_ENDPOINT: ${{ secrets.OTEL_ENDPOINT }} - OTEL_PASSWORD: ${{ secrets.OTEL_PASSWORD }} - OTEL_USERNAME: ${{ secrets.OTEL_USERNAME }} OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }} OTEL_AUTH_HEADER: ${{ secrets.OTEL_AUTH_HEADER }} - R2_ACCESS_KEY: ${{ secrets.R2_ACCESS_KEY }} - R2_BUCKET: ${{ secrets.R2_BUCKET }} - R2_SECRET_KEY: ${{ secrets.R2_SECRET_KEY }} TURNSTILE_SECRET_KEY: ${{ secrets.TURNSTILE_SECRET_KEY }} API_KEY: ${{ secrets.API_KEY }} SERVER_HOST_STAGING: ${{ secrets.SERVER_HOST_STAGING }} @@ -121,21 +114,14 @@ jobs: chmod 600 ~/.ssh/id_rsa - name: 🚀 Deploy image env: - ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }} CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} GHCR_REPO: ${{ vars.GHCR_REPO }} GHCR_USERNAME: ${{ vars.GHCR_USERNAME }} DOMAIN: ${{ vars.DOMAIN }} IMAGE_ID: ${{ needs.build.outputs.IMAGE_ID }} - OTEL_ENDPOINT: ${{ secrets.OTEL_ENDPOINT }} - OTEL_PASSWORD: ${{ secrets.OTEL_PASSWORD }} - OTEL_USERNAME: ${{ secrets.OTEL_USERNAME }} OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }} OTEL_AUTH_HEADER: ${{ secrets.OTEL_AUTH_HEADER }} - R2_ACCESS_KEY: ${{ secrets.R2_ACCESS_KEY }} - R2_BUCKET: ${{ secrets.R2_BUCKET }} - R2_SECRET_KEY: ${{ secrets.R2_SECRET_KEY }} TURNSTILE_SECRET_KEY: ${{ secrets.TURNSTILE_SECRET_KEY }} API_KEY: ${{ secrets.API_KEY }} SERVER_HOST_FALK1: ${{ secrets.SERVER_HOST_FALK1 }} @@ -179,21 +165,14 @@ jobs: chmod 600 ~/.ssh/id_rsa - name: 🚀 Deploy image env: - ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }} CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} GHCR_REPO: ${{ vars.GHCR_REPO }} GHCR_USERNAME: ${{ vars.GHCR_USERNAME }} DOMAIN: ${{ vars.DOMAIN }} IMAGE_ID: ${{ needs.build.outputs.IMAGE_ID }} - OTEL_ENDPOINT: ${{ secrets.OTEL_ENDPOINT }} - OTEL_PASSWORD: ${{ secrets.OTEL_PASSWORD }} - OTEL_USERNAME: ${{ secrets.OTEL_USERNAME }} OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }} OTEL_AUTH_HEADER: ${{ secrets.OTEL_AUTH_HEADER }} - R2_ACCESS_KEY: ${{ secrets.R2_ACCESS_KEY }} - R2_BUCKET: ${{ secrets.R2_BUCKET }} - R2_SECRET_KEY: ${{ secrets.R2_SECRET_KEY }} TURNSTILE_SECRET_KEY: ${{ secrets.TURNSTILE_SECRET_KEY }} API_KEY: ${{ secrets.API_KEY }} SERVER_HOST_FALK1: ${{ secrets.SERVER_HOST_FALK1 }} @@ -237,21 +216,14 @@ jobs: chmod 600 ~/.ssh/id_rsa - name: 🚀 Deploy image env: - ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }} CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} GHCR_REPO: ${{ vars.GHCR_REPO }} GHCR_USERNAME: ${{ vars.GHCR_USERNAME }} DOMAIN: ${{ vars.DOMAIN }} IMAGE_ID: ${{ needs.build.outputs.IMAGE_ID }} - OTEL_ENDPOINT: ${{ secrets.OTEL_ENDPOINT }} - OTEL_PASSWORD: ${{ secrets.OTEL_PASSWORD }} - OTEL_USERNAME: ${{ secrets.OTEL_USERNAME }} OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }} OTEL_AUTH_HEADER: ${{ secrets.OTEL_AUTH_HEADER }} - R2_ACCESS_KEY: ${{ secrets.R2_ACCESS_KEY }} - R2_BUCKET: ${{ secrets.R2_BUCKET }} - R2_SECRET_KEY: ${{ secrets.R2_SECRET_KEY }} TURNSTILE_SECRET_KEY: ${{ secrets.TURNSTILE_SECRET_KEY }} API_KEY: ${{ secrets.API_KEY }} SERVER_HOST_FALK1: ${{ secrets.SERVER_HOST_FALK1 }} diff --git a/build-deploy.sh b/build-deploy.sh index 370991b58..e4190cf8f 100755 --- a/build-deploy.sh +++ b/build-deploy.sh @@ -15,34 +15,34 @@ print_header "BUILD AND DEPLOY WRAPPER" echo "This script will run build.sh and deploy.sh in sequence." echo "You can also run them separately:" echo " ./build.sh [prod|staging] [version_tag]" -echo " ./deploy.sh [prod|staging] [falk1|nbg1|staging|masters] [version_tag] [subdomain] [--enable_basic_auth]" +echo " ./deploy.sh [prod|staging] [falk1|nbg1|staging|masters] [version_tag] [subdomain]" echo "" # Check command line arguments if [ $# -lt 3 ] || [ $# -gt 5 ]; then echo "Error: Please specify environment, host, and subdomain" - echo "Usage: $0 [prod|staging] [falk1|nbg1|staging|masters] [subdomain] [--enable_basic_auth]" + echo "Usage: $0 [prod|staging] [falk1|nbg1|staging|masters] [subdomain]" exit 1 fi # Validate first argument (environment) if [ "$1" != "prod" ] && [ "$1" != "staging" ]; then echo "Error: First argument must be either 'prod' or 'staging'" - echo "Usage: $0 [prod|staging] [falk1|nbg1|staging|masters] [subdomain] [--enable_basic_auth]" + echo "Usage: $0 [prod|staging] [falk1|nbg1|staging|masters] [subdomain]" exit 1 fi # Validate second argument (host) if [ "$2" != "falk1" ] && [ "$2" != "nbg1" ] && [ "$2" != "staging" ] && [ "$2" != "masters" ]; then echo "Error: Second argument must be either 'falk1', 'nbg1', 'staging', or 'masters'" - echo "Usage: $0 [prod|staging] [falk1|nbg1|staging|masters] [subdomain] [--enable_basic_auth]" + echo "Usage: $0 [prod|staging] [falk1|nbg1|staging|masters] [subdomain]" exit 1 fi # Validate third argument (subdomain) if [ -z "$3" ]; then echo "Error: Subdomain is required" - echo "Usage: $0 [prod|staging] [falk1|nbg1|staging|masters] [subdomain] [--enable_basic_auth]" + echo "Usage: $0 [prod|staging] [falk1|nbg1|staging|masters] [subdomain]" exit 1 fi @@ -54,23 +54,6 @@ echo "Generated version tag: $VERSION_TAG" ENV="$1" HOST="$2" SUBDOMAIN="$3" -ENABLE_BASIC_AUTH="" - -# Parse remaining arguments -shift 3 -while [[ $# -gt 0 ]]; do - case $1 in - --enable_basic_auth) - ENABLE_BASIC_AUTH="--enable_basic_auth" - shift - ;; - *) - echo "Error: Unknown argument: $1" - echo "Usage: $0 [prod|staging] [falk1|nbg1|staging|masters] [subdomain] [--enable_basic_auth]" - exit 1 - ;; - esac -done # Step 1: Run build.sh echo "Step 1: Running build.sh..." diff --git a/deploy.sh b/deploy.sh index 1a8aa21a8..6e0c6727a 100755 --- a/deploy.sh +++ b/deploy.sh @@ -6,27 +6,6 @@ set -e # Exit immediately if a command exits with a non-zero status -# Initialize variables -ENABLE_BASIC_AUTH=false - -# Parse command line arguments -POSITIONAL_ARGS=() -while [[ $# -gt 0 ]]; do - case $1 in - --enable_basic_auth) - ENABLE_BASIC_AUTH=true - shift - ;; - *) - POSITIONAL_ARGS+=("$1") - shift - ;; - esac -done - -# Restore positional parameters -set -- "${POSITIONAL_ARGS[@]}" - # Function to print section headers print_header() { echo "======================================================" @@ -37,21 +16,21 @@ print_header() { # Check command line arguments if [ $# -ne 4 ]; then echo "Error: Please specify environment, host, version tag, and subdomain" - echo "Usage: $0 [prod|staging] [falk1|nbg1|staging|masters] [version_tag] [subdomain] [--enable_basic_auth]" + echo "Usage: $0 [prod|staging] [falk1|nbg1|staging|masters] [version_tag] [subdomain]" exit 1 fi # Validate first argument (environment) if [ "$1" != "prod" ] && [ "$1" != "staging" ]; then echo "Error: First argument must be either 'prod' or 'staging'" - echo "Usage: $0 [prod|staging] [falk1|nbg1|staging|masters] [version_tag] [subdomain] [--enable_basic_auth]" + echo "Usage: $0 [prod|staging] [falk1|nbg1|staging|masters] [version_tag] [subdomain]" exit 1 fi # Validate second argument (host) if [ "$2" != "falk1" ] && [ "$2" != "nbg1" ] && [ "$2" != "staging" ] && [ "$2" != "masters" ]; then echo "Error: Second argument must be either 'falk1', 'nbg1', 'staging', or 'masters'" - echo "Usage: $0 [prod|staging] [falk1|nbg1|staging|masters] [version_tag] [subdomain] [--enable_basic_auth]" + echo "Usage: $0 [prod|staging] [falk1|nbg1|staging|masters] [version_tag] [subdomain]" exit 1 fi @@ -107,21 +86,6 @@ if [ -z "$SERVER_HOST" ]; then exit 1 fi -# Check if basic auth is enabled and credentials are available -if [ "$ENABLE_BASIC_AUTH" = true ]; then - print_header "BASIC AUTH ENABLED" - if [ -z "$BASIC_AUTH_USER" ] || [ -z "$BASIC_AUTH_PASS" ]; then - echo "Error: Basic Auth is enabled but BASIC_AUTH_USER or BASIC_AUTH_PASS not defined in .env file or environment" - exit 1 - fi - echo "Basic Authentication will be enabled with user: $BASIC_AUTH_USER" -else - # If basic auth is not enabled, set the variables to empty to ensure they don't get used - BASIC_AUTH_USER="" - BASIC_AUTH_PASS="" - echo "Basic Authentication is disabled" -fi - # Configuration UPDATE_SCRIPT="./update.sh" # Path to your update script REMOTE_USER="openfront" @@ -170,23 +134,14 @@ ENV=$ENV HOST=$HOST GHCR_IMAGE=$GHCR_IMAGE GHCR_TOKEN=$GHCR_TOKEN -ADMIN_TOKEN=$ADMIN_TOKEN CF_ACCOUNT_ID=$CF_ACCOUNT_ID -R2_ACCESS_KEY=$R2_ACCESS_KEY -R2_SECRET_KEY=$R2_SECRET_KEY -R2_BUCKET=$R2_BUCKET CF_API_TOKEN=$CF_API_TOKEN TURNSTILE_SECRET_KEY=$TURNSTILE_SECRET_KEY API_KEY=$API_KEY DOMAIN=$DOMAIN SUBDOMAIN=$SUBDOMAIN -OTEL_USERNAME=$OTEL_USERNAME -OTEL_PASSWORD=$OTEL_PASSWORD -OTEL_ENDPOINT=$OTEL_ENDPOINT OTEL_EXPORTER_OTLP_ENDPOINT=$OTEL_EXPORTER_OTLP_ENDPOINT OTEL_AUTH_HEADER=$OTEL_AUTH_HEADER -BASIC_AUTH_USER=$BASIC_AUTH_USER -BASIC_AUTH_PASS=$BASIC_AUTH_PASS EOL chmod 600 $ENV_FILE && \ $REMOTE_UPDATE_SCRIPT $ENV_FILE" @@ -198,8 +153,5 @@ fi print_header "DEPLOYMENT COMPLETED SUCCESSFULLY" echo "✅ New version deployed to ${ENV} environment in ${HOST} with subdomain ${SUBDOMAIN}!" -if [ "$ENABLE_BASIC_AUTH" = true ]; then - echo "🔒 Basic authentication enabled with user: $BASIC_AUTH_USER" -fi echo "🌐 Check your server to verify the deployment." echo "=======================================================" diff --git a/example.env b/example.env index be9ca624f..01cb270ea 100644 --- a/example.env +++ b/example.env @@ -6,19 +6,11 @@ GHCR_USERNAME=username GHCR_REPO=your-repo-name GHCR_TOKEN=your_docker_token_here -# Admin credentials -ADMIN_TOKEN=your_admin_token_here - # Cloudflare Configuration CF_ACCOUNT_ID=your_cloudflare_account_id CF_API_TOKEN=your_cloudflare_api_token DOMAIN=your-domain.com -# R2 Configuration -R2_ACCESS_KEY=your_r2_access_key -R2_SECRET_KEY=your_r2_secret_key -R2_BUCKET=your-bucket-name - # API Key API_KEY=your_api_key_here diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index d9172fc87..c0b915f3c 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -46,10 +46,6 @@ export interface ServerConfig { adminHeader(): string; // Only available on the server gitCommit(): string; - r2Bucket(): string; - r2Endpoint(): string; - r2AccessKey(): string; - r2SecretKey(): string; apiKey(): string; otelEndpoint(): string; otelAuthHeader(): string; diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 965da5cdc..a9b633d29 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -141,19 +141,6 @@ export abstract class DefaultServerConfig implements ServerConfig { gitCommit(): string { return process.env.GIT_COMMIT ?? ""; } - r2Endpoint(): string { - return `https://${process.env.CF_ACCOUNT_ID}.r2.cloudflarestorage.com`; - } - r2AccessKey(): string { - return process.env.R2_ACCESS_KEY ?? ""; - } - r2SecretKey(): string { - return process.env.R2_SECRET_KEY ?? ""; - } - - r2Bucket(): string { - return process.env.R2_BUCKET ?? ""; - } apiKey(): string { return process.env.API_KEY ?? ""; @@ -163,7 +150,11 @@ export abstract class DefaultServerConfig implements ServerConfig { return "x-admin-key"; } adminToken(): string { - return process.env.ADMIN_TOKEN ?? "dummy-admin-token"; + const token = process.env.ADMIN_TOKEN; + if (!token) { + throw new Error("ADMIN_TOKEN not set"); + } + return token; } abstract numWorkers(): number; abstract env(): GameEnv; diff --git a/src/server/Master.ts b/src/server/Master.ts index eb074fa52..a4becf432 100644 --- a/src/server/Master.ts +++ b/src/server/Master.ts @@ -1,11 +1,12 @@ import cluster from "cluster"; +import crypto from "crypto"; import express from "express"; import rateLimit from "express-rate-limit"; import http from "http"; import path from "path"; import { fileURLToPath } from "url"; import { getServerConfigFromServer } from "../core/configuration/ConfigLoader"; -import { GameInfo, ID } from "../core/Schemas"; +import { GameInfo } from "../core/Schemas"; import { generateID } from "../core/Util"; import { logger } from "./Logger"; import { MapPlaylist } from "./MapPlaylist"; @@ -73,10 +74,15 @@ export async function startMaster() { log.info(`Primary ${process.pid} is running`); log.info(`Setting up ${config.numWorkers()} workers...`); + // Generate admin token for worker authentication + const ADMIN_TOKEN = crypto.randomBytes(16).toString("hex"); + process.env.ADMIN_TOKEN = ADMIN_TOKEN; + // Fork workers for (let i = 0; i < config.numWorkers(); i++) { const worker = cluster.fork({ WORKER_ID: i, + ADMIN_TOKEN, }); log.info(`Started worker ${i} (PID: ${worker.process.pid})`); @@ -128,6 +134,7 @@ export async function startMaster() { // Restart the worker with the same ID const newWorker = cluster.fork({ WORKER_ID: workerId, + ADMIN_TOKEN, }); log.info( @@ -154,41 +161,6 @@ app.get("/api/public_lobbies", async (req, res) => { res.send(publicLobbiesJsonStr); }); -app.post("/api/kick_player/:gameID/:clientID", async (req, res) => { - if (req.headers[config.adminHeader()] !== config.adminToken()) { - res.status(401).send("Unauthorized"); - return; - } - - const { gameID, clientID } = req.params; - - if (!ID.safeParse(gameID).success || !ID.safeParse(clientID).success) { - res.sendStatus(400); - return; - } - - try { - const response = await fetch( - `http://localhost:${config.workerPort(gameID)}/api/kick_player/${gameID}/${clientID}`, - { - method: "POST", - headers: { - [config.adminHeader()]: config.adminToken(), - }, - }, - ); - - if (!response.ok) { - throw new Error(`Failed to kick player: ${response.statusText}`); - } - - res.status(200).send("Player kicked successfully"); - } catch (error) { - log.error(`Error kicking player from game ${gameID}:`, error); - res.status(500).send("Failed to kick player"); - } -}); - async function fetchLobbies(): Promise { const fetchPromises: Promise[] = []; diff --git a/src/server/Worker.ts b/src/server/Worker.ts index 50ba20b90..996c9f9fe 100644 --- a/src/server/Worker.ts +++ b/src/server/Worker.ts @@ -270,24 +270,6 @@ export async function startWorker() { } }); - app.post("/api/kick_player/:gameID/:clientID", async (req, res) => { - if (req.headers[config.adminHeader()] !== config.adminToken()) { - res.status(401).send("Unauthorized"); - return; - } - - const { gameID, clientID } = req.params; - - const game = gm.game(gameID); - if (!game) { - res.status(404).send("Game not found"); - return; - } - - game.kickClient(clientID); - res.status(200).send("Player kicked successfully"); - }); - // WebSocket handling wss.on("connection", (ws: WebSocket, req) => { ws.on("message", async (message: string) => { diff --git a/tests/util/TestServerConfig.ts b/tests/util/TestServerConfig.ts index 36b4c91d2..330ba91ed 100644 --- a/tests/util/TestServerConfig.ts +++ b/tests/util/TestServerConfig.ts @@ -82,16 +82,4 @@ export class TestServerConfig implements ServerConfig { gitCommit(): string { throw new Error("Method not implemented."); } - r2Bucket(): string { - throw new Error("Method not implemented."); - } - r2Endpoint(): string { - throw new Error("Method not implemented."); - } - r2AccessKey(): string { - throw new Error("Method not implemented."); - } - r2SecretKey(): string { - throw new Error("Method not implemented."); - } } diff --git a/update.sh b/update.sh index cfd6cfe9e..59309dec8 100755 --- a/update.sh +++ b/update.sh @@ -32,8 +32,8 @@ echo "Pulling ${GHCR_IMAGE} from GitHub Container Registry..." docker pull "${GHCR_IMAGE}" echo "Checking for existing container..." -# Check for running container -RUNNING_CONTAINER="$(docker ps | grep ${CONTAINER_NAME} | awk '{print $1}')" +# Use docker ps with filter for exact name match +RUNNING_CONTAINER="$(docker ps --filter "name=^${CONTAINER_NAME}$" -q)" if [ -n "$RUNNING_CONTAINER" ]; then echo "Stopping running container $RUNNING_CONTAINER..." docker stop "$RUNNING_CONTAINER" @@ -44,7 +44,7 @@ if [ -n "$RUNNING_CONTAINER" ]; then fi # Also check for stopped containers with the same name -STOPPED_CONTAINER="$(docker ps -a | grep ${CONTAINER_NAME} | awk '{print $1}')" +STOPPED_CONTAINER="$(docker ps -a --filter "name=^${CONTAINER_NAME}$" -q)" if [ -n "$STOPPED_CONTAINER" ]; then echo "Removing stopped container $STOPPED_CONTAINER..." docker rm "$STOPPED_CONTAINER"