This commit is contained in:
Nathan Rashleigh 2026-02-10 10:46:45 +11:00
parent 31a2ac82d0
commit bcf798a5f9
7 changed files with 99 additions and 11 deletions

4
.env
View File

@ -30,6 +30,10 @@ SRCDS_MAPCYCLE="mapcycle.txt"
SRCDS_ADMINS="STEAM_1:1:12270329" SRCDS_ADMINS="STEAM_1:1:12270329"
# ----------
# fastdl
FASTDL_URL=https://fastdl.megastructure.surf
# ---------- # ----------
# mariadb # mariadb
MARIADB_ROOT_PASSWORD="surfsup" MARIADB_ROOT_PASSWORD="surfsup"

View File

@ -24,6 +24,7 @@ services:
- ./etc:/home/steam/etc - ./etc:/home/steam/etc
- ./data/cssds:/home/steam/cssds - ./data/cssds:/home/steam/cssds
- tf2ds:/home/steam/tf2ds - tf2ds:/home/steam/tf2ds
- fastdl-maps:/fastdl/maps:ro
db: db:
image: mariadb:12 image: mariadb:12
@ -47,8 +48,8 @@ services:
# Mount credentials (read-only) # Mount credentials (read-only)
- ./src/mapsdl/credentials:/app/credentials:ro - ./src/mapsdl/credentials:/app/credentials:ro
# Mount maps directory (read-write) # Output bz2 files to shared FastDL volume
- ./data/cssds/cstrike/maps:/maps - fastdl-maps:/maps
environment: environment:
- MAPS_DIR=/maps - MAPS_DIR=/maps
@ -62,5 +63,13 @@ services:
# Run once and exit # Run once and exit
restart: "no" restart: "no"
fastdl:
build: ./src/fastdl
ports:
- "8080:80"
volumes:
- fastdl-maps:/srv/maps:ro
volumes: volumes:
tf2ds: tf2ds:
fastdl-maps:

1
etc/cfg/mapcycle.txt Normal file
View File

@ -0,0 +1 @@
surf_boreas

View File

@ -451,6 +451,55 @@ EOF
cfg() { cfg() {
cd cd
cp etc/cfg/server.cfg cssds/cstrike/cfg/ cp etc/cfg/server.cfg cssds/cstrike/cfg/
cp etc/cfg/mapcycle.txt cssds/cstrike/cfg/
sed -i "s|sv_downloadurl .*|sv_downloadurl \"${FASTDL_URL:-}/cstrike/\"|" "$CSTRIKE/cfg/server.cfg"
}
populate_maps() {
local BZ2_DIR="/fastdl/maps"
local MAPS_DIR="$CSTRIKE/maps"
local MAPCYCLE="$CSTRIKE/cfg/mapcycle.txt"
echo "--------------------------------------------------------------"
echo "Populating maps from compressed archive"
echo "--------------------------------------------------------------"
if [ ! -f "$MAPCYCLE" ]; then
echo "No mapcycle.txt found, skipping map population"
echo "--------------------------------------------------------------"
return
fi
mkdir -p "$MAPS_DIR"
local total=0 skipped=0 extracted=0 missing=0
while IFS= read -r line || [ -n "$line" ]; do
# skip empty lines and comments
line=$(echo "$line" | sed 's|//.*||' | xargs)
[ -z "$line" ] && continue
total=$((total + 1))
local bz2="$BZ2_DIR/${line}.bsp.bz2"
local bsp="$MAPS_DIR/${line}.bsp"
if [ -f "$bsp" ]; then
skipped=$((skipped + 1))
continue
fi
if [ ! -f "$bz2" ]; then
missing=$((missing + 1))
echo " [!] ${line}: not in fastdl volume"
continue
fi
bunzip2 -c "$bz2" > "$bsp"
extracted=$((extracted + 1))
done < "$MAPCYCLE"
echo "Maps: $total in mapcycle, $extracted extracted, $skipped already present, $missing not available"
echo "--------------------------------------------------------------"
} }
@ -482,6 +531,8 @@ main() {
# Zones will be available after server starts and creates tables # Zones will be available after server starts and creates tables
import_zone_data import_zone_data
populate_maps
run_cssds run_cssds
} }

2
src/fastdl/Dockerfile Normal file
View File

@ -0,0 +1,2 @@
FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf

8
src/fastdl/nginx.conf Normal file
View File

@ -0,0 +1,8 @@
server {
listen 80;
root /srv;
location /cstrike/maps/ {
alias /srv/maps/;
autoindex off;
}
}

View File

@ -156,8 +156,8 @@ export async function getLocalMaps(mapsDir: string): Promise<Set<string>> {
try { try {
for await (const entry of Deno.readDir(mapsDir)) { for await (const entry of Deno.readDir(mapsDir)) {
if (entry.isFile && entry.name.endsWith(".bsp")) { if (entry.isFile && entry.name.endsWith(".bsp.bz2")) {
const mapName = entry.name.replace(/\.bsp$/, ""); const mapName = entry.name.replace(/\.bsp\.bz2$/, "");
localMaps.add(mapName); localMaps.add(mapName);
} }
} }
@ -317,29 +317,32 @@ export async function downloadAndExtractMaps(
console.log(`[${completed}/${mapsToDownload.size}] Downloading ${mapName}...`); console.log(`[${completed}/${mapsToDownload.size}] Downloading ${mapName}...`);
const archivePath = join(config.tempDir, gdriveFile.name); const archivePath = join(config.tempDir, gdriveFile.name);
const bspPath = join(config.mapsDir, `${mapName}.bsp`); const tempBspPath = join(config.tempDir, `${mapName}.bsp`);
const bz2Path = join(config.mapsDir, `${mapName}.bsp.bz2`);
await client.downloadFile(gdriveFile.id, archivePath); await client.downloadFile(gdriveFile.id, archivePath);
if (gdriveFile.name.endsWith(".rar")) { if (gdriveFile.name.endsWith(".rar")) {
await extractRAR(archivePath, mapName, config.mapsDir, config.tempDir); await extractRAR(archivePath, mapName, config.tempDir, config.tempDir);
await Deno.remove(archivePath); await Deno.remove(archivePath);
} else if (gdriveFile.name.endsWith(".bz2")) { } else if (gdriveFile.name.endsWith(".bz2")) {
await decompressBZ2(archivePath, bspPath); await decompressBZ2(archivePath, tempBspPath);
await Deno.remove(archivePath); await Deno.remove(archivePath);
} else if (gdriveFile.name.endsWith(".zip")) { } else if (gdriveFile.name.endsWith(".zip")) {
await extractZIP(archivePath, mapName, config.mapsDir, config.tempDir); await extractZIP(archivePath, mapName, config.tempDir, config.tempDir);
await Deno.remove(archivePath); await Deno.remove(archivePath);
} else if (gdriveFile.name.endsWith(".bsp")) { } else if (gdriveFile.name.endsWith(".bsp")) {
await moveFile(archivePath, bspPath); await moveFile(archivePath, tempBspPath);
} else { } else {
throw new Error(`Unknown archive format: ${gdriveFile.name}`); throw new Error(`Unknown archive format: ${gdriveFile.name}`);
} }
await verifyBSP(bspPath); await verifyBSP(tempBspPath);
await compressBSP(tempBspPath, bz2Path);
await Deno.remove(tempBspPath);
stats.success++; stats.success++;
console.log(` [OK] ${mapName} downloaded and extracted`); console.log(` [OK] ${mapName} downloaded and compressed`);
} catch (error) { } catch (error) {
stats.failed++; stats.failed++;
console.error(` [X] Failed to download ${mapName}: ${(error as Error).message}`); console.error(` [X] Failed to download ${mapName}: ${(error as Error).message}`);
@ -459,3 +462,13 @@ export async function verifyBSP(bspPath: string): Promise<void> {
file.close(); file.close();
} }
} }
export async function compressBSP(bspPath: string, bz2Path: string): Promise<void> {
const process = new Deno.Command("bzip2", {
args: ["-c", bspPath],
stdout: "piped",
});
const { code, stdout } = await process.output();
if (code !== 0) throw new Error(`bzip2 failed with exit code ${code}`);
await Deno.writeFile(bz2Path, stdout);
}