diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d917b3b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# Shell scripts must keep LF endings or bash breaks on Linux/Mac +setup.sh text eol=lf +*.sh text eol=lf + +# Windows batch files need CRLF +setup.bat text eol=crlf +*.bat text eol=crlf diff --git a/.gitignore b/.gitignore index c406927..bed2d38 100644 --- a/.gitignore +++ b/.gitignore @@ -50,9 +50,19 @@ docs/ # Local launcher scripts (machine-specific, not needed in repo) *.bat *.ps1 +# ...but the guided setup scripts ship with the project +!setup.ps1 +!setup.sh +!setup.bat +!start.sh +!start.bat # Frontend dev scripts and build artifacts frontend/*.cjs frontend/test.js frontend/test_output.txt frontend/src/*.bak + +# Local .env backups (created when testing setup scripts) +*.env.bak +*.env.backup diff --git a/README.md b/README.md index 4feb41d..569b888 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,32 @@ cd CITY_NET --- +## Quick Setup (Guided Script) + +> ## ⚠️ NEVER RUN SCRIPTS FROM AN UNTRUSTED SOURCE +> +> Only use `setup.ps1` / `setup.sh` if you downloaded them **directly from this repository** ([github.com/over2take/CITY_NET](https://github.com/over2take/CITY_NET)). Scripts can do anything your user account can do — if someone sends you a "setup script" for CITY_NET from anywhere else (Discord, forums, a re-upload, a YouTube description), **do not run it.** When in doubt, open the script in a text editor and read it first, or use the manual setup below instead — it's only a few copy-paste steps. + +If you'd rather not edit config files by hand, run the guided setup script. It supports both install methods — Docker (recommended) or manual with Node.js — generates a secure `JWT_SECRET` for you, asks for your admin login and port, optionally sets up DuckDNS (Docker only), writes the `.env` files, and can build and launch the app — all from a few prompts. + +**Windows:** double-click `setup.bat`, or from PowerShell: +```powershell +powershell -ExecutionPolicy Bypass -File setup.ps1 +``` + +**Linux/Mac:** +```bash +bash setup.sh +``` + +Requires Docker (recommended) or Node.js v18+ to be installed. For manual configuration instead, follow the options below. + +**Starting the app later:** +- **Docker install:** containers auto-restart; or run `docker compose up -d` +- **Node.js install:** double-click `start.bat` (Windows) or run `bash start.sh` (Linux/Mac). The server runs in that terminal — closing it stops the app. + +--- + ## Option A: Docker (Recommended) ### 2. Configure environment diff --git a/setup.bat b/setup.bat new file mode 100644 index 0000000..1e773a5 --- /dev/null +++ b/setup.bat @@ -0,0 +1,4 @@ +@echo off +REM City_Net guided setup - double-click launcher for setup.ps1 +powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup.ps1" +pause diff --git a/setup.ps1 b/setup.ps1 new file mode 100644 index 0000000..11fe92e --- /dev/null +++ b/setup.ps1 @@ -0,0 +1,354 @@ +# ============================================================================ +# City_Net - Guided Setup +# Generates your .env configuration and (optionally) launches the app. +# Run from the project root: powershell -ExecutionPolicy Bypass -File setup.ps1 +# ============================================================================ + +$ErrorActionPreference = 'Stop' + +function Write-Header { + Write-Host "" + Write-Host " ============================================" -ForegroundColor Green + Write-Host " CITY_NET // GUIDED SETUP" -ForegroundColor Green + Write-Host " ============================================" -ForegroundColor Green + Write-Host "" +} + +function Read-Default { + param([string]$Prompt, [string]$Default) + $answer = Read-Host "$Prompt [$Default]" + if ([string]::IsNullOrWhiteSpace($answer)) { return $Default } + return $answer +} + +function Read-Required { + param([string]$Prompt) + do { + $answer = Read-Host $Prompt + if ([string]::IsNullOrWhiteSpace($answer)) { + Write-Host " This field is required." -ForegroundColor Yellow + } + } while ([string]::IsNullOrWhiteSpace($answer)) + return $answer +} + +function Read-Password { + param([string]$Prompt) + do { + $secure = Read-Host "$Prompt (input hidden)" -AsSecureString + $answer = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( + [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)) + if ([string]::IsNullOrWhiteSpace($answer)) { + Write-Host " This field is required." -ForegroundColor Yellow + } + } while ([string]::IsNullOrWhiteSpace($answer)) + return $answer +} + +function Read-YesNo { + param([string]$Prompt, [bool]$DefaultYes = $false) + $hint = if ($DefaultYes) { "Y/n" } else { "y/N" } + $answer = Read-Host "$Prompt [$hint]" + if ([string]::IsNullOrWhiteSpace($answer)) { return $DefaultYes } + return $answer -match '^(y|yes)$' +} + +# --- Preflight -------------------------------------------------------------- + +Write-Header + +if (-not (Test-Path ".\docker-compose.yml")) { + Write-Host " ERROR: run this from the City_Net project root (docker-compose.yml not found)." -ForegroundColor Red + exit 1 +} + +# --- Install mode: Docker (recommended) or Manual (Node.js) ------------------ + +$hasDocker = $false +try { docker --version | Out-Null; $hasDocker = $true } catch {} + +$hasNode = $false +try { + $nodeMajor = [int]((node -v) -replace '^v' -split '\.')[0] + if ($nodeMajor -ge 18) { $hasNode = $true } +} catch {} + +$mode = "" +if ($hasDocker -and $hasNode) { + Write-Host " Two install options are available:" -ForegroundColor DarkGray + Write-Host " - Docker (recommended): runs in containers, auto-restarts, includes DuckDNS" -ForegroundColor DarkGray + Write-Host " - Manual: runs directly with Node.js in a terminal" -ForegroundColor DarkGray + Write-Host "" + if (Read-YesNo " Use Docker? (recommended)" $true) { $mode = "docker" } else { $mode = "manual" } +} elseif ($hasDocker) { + $mode = "docker" +} elseif ($hasNode) { + Write-Host " Docker was not found, but Node.js $(node -v) is installed." -ForegroundColor Yellow + if (Read-YesNo " Continue with a manual (Node.js) install?" $true) { + $mode = "manual" + } else { + Write-Host " Install Docker Desktop first: https://www.docker.com/products/docker-desktop/" -ForegroundColor Yellow + exit 1 + } +} else { + Write-Host " Neither Docker nor Node.js (v18+) was found." -ForegroundColor Red + Write-Host " Install one of them first, then re-run this script:" -ForegroundColor Yellow + Write-Host " - Docker Desktop (recommended): https://www.docker.com/products/docker-desktop/" -ForegroundColor Yellow + Write-Host " - Node.js v18+: https://nodejs.org/" -ForegroundColor Yellow + exit 1 +} +Write-Host "" + +if (Test-Path ".\backend\.env") { + Write-Host " A configuration already exists (backend\.env)." -ForegroundColor Yellow + if (-not (Read-YesNo " Overwrite it?" $false)) { + Write-Host " Setup cancelled - existing config kept." -ForegroundColor Green + exit 0 + } + Write-Host "" +} + +# --- Required settings ------------------------------------------------------ + +Write-Host " --- Required settings ---" -ForegroundColor Cyan +Write-Host "" + +$adminUser = Read-Default " Admin username" "admin" +$adminPass = Read-Password " Admin password" + +Write-Host "" +if ($mode -eq "docker") { + $portDefault = "8080" + Write-Host " App port: the port players connect on. 80 gives the cleanest URL," -ForegroundColor DarkGray + Write-Host " but many home ISPs block inbound 80, so 8080 is the safe default." -ForegroundColor DarkGray +} else { + $portDefault = "5000" + Write-Host " App port: the port players connect on (the Node server listens here)." -ForegroundColor DarkGray +} + +# Warn if another program already owns the chosen port (e.g. NVIDIA Broadcast +# holds 127.0.0.1:8080 - localhost would then hit that app instead of City_Net). +do { + $appPort = Read-Default " App port" $portDefault + $portOk = $true + try { + $conflicts = Get-NetTCPConnection -LocalPort ([int]$appPort) -State Listen -ErrorAction SilentlyContinue | + Where-Object { (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName -notmatch 'docker|wslrelay|vpnkit' } + if ($conflicts) { + $names = ($conflicts | ForEach-Object { (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName } | Sort-Object -Unique) -join ', ' + Write-Host "" + Write-Host " WARNING: port $appPort is already in use by: $names" -ForegroundColor Yellow + Write-Host " Connections on this port may reach that app instead of City_Net." -ForegroundColor Yellow + if (-not (Read-YesNo " Use port $appPort anyway?" $false)) { $portOk = $false; Write-Host "" } + } + } catch {} +} while (-not $portOk) + +Write-Host "" +Write-Host " Secure Mode: if ON, players must register an account and be approved." -ForegroundColor DarkGray +Write-Host " If OFF, players just type a name to join (simplest)." -ForegroundColor DarkGray +$secureMode = if (Read-YesNo " Enable Secure Mode?" $false) { "true" } else { "false" } + +# JWT secret - auto-generated, never prompted +$bytes = New-Object 'System.Byte[]' 32 +[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes) +$jwtSecret = ($bytes | ForEach-Object { $_.ToString("x2") }) -join "" + +# --- Optional settings ------------------------------------------------------ + +$duckSub = "yourname" +$duckTok = "your-duckdns-token" +$tz = "America/Chicago" +$useDuck = $false + +if ($mode -eq "docker") { + Write-Host "" + Write-Host " --- Optional: internet access via DuckDNS ---" -ForegroundColor Cyan + Write-Host " DuckDNS gives you a free domain (e.g. yourcity.duckdns.org) so players" -ForegroundColor DarkGray + Write-Host " can connect over the internet. Skip this for LAN-only play." -ForegroundColor DarkGray + Write-Host "" + + if (Read-YesNo " Set up DuckDNS now?" $false) { + $useDuck = $true + Write-Host " Get your subdomain and token at https://www.duckdns.org" -ForegroundColor DarkGray + $duckSub = Read-Required " DuckDNS subdomain (the part before .duckdns.org)" + $duckTok = Read-Required " DuckDNS token" + $tz = Read-Default " Timezone" "America/Chicago" + } +} else { + Write-Host "" + Write-Host " NOTE: the bundled DuckDNS service (automatic internet domain) requires" -ForegroundColor Yellow + Write-Host " Docker and is not available with a manual install. For internet play," -ForegroundColor Yellow + Write-Host " see the 'Connectivity & Deployment' section of the README - Cloudflare" -ForegroundColor Yellow + Write-Host " Tunnel works well with a manual install and needs no port forwarding." -ForegroundColor Yellow +} + +# --- Write .env ------------------------------------------------------------- + +if ($mode -eq "docker") { + $envContent = @" +JWT_SECRET=$jwtSecret +ADMIN_USER=$adminUser +ADMIN_PASS=$adminPass + +# Require player registration and approval before joining (default false) +SECURE_MODE=$secureMode + +# Port the app is exposed on +APP_PORT=$appPort + +# DuckDNS - optional +DUCKDNS_SUBDOMAINS=$duckSub +DUCKDNS_TOKEN=$duckTok + +# Timezone for DuckDNS container +TZ=$tz +"@ + + # Root .env is only used by docker-compose for variable substitution - + # it gets the non-secret values only (no JWT_SECRET / ADMIN_PASS). + $rootEnvContent = @" +APP_PORT=$appPort +DUCKDNS_SUBDOMAINS=$duckSub +DUCKDNS_TOKEN=$duckTok +TZ=$tz +"@ + + Set-Content -Path ".\backend\.env" -Value $envContent -Encoding utf8 -NoNewline + Set-Content -Path ".\.env" -Value $rootEnvContent -Encoding utf8 -NoNewline + + Write-Host "" + Write-Host " Configuration written to backend\.env and .env" -ForegroundColor Green +} else { + $envContent = @" +JWT_SECRET=$jwtSecret +ADMIN_USER=$adminUser +ADMIN_PASS=$adminPass + +# Require player registration and approval before joining (default false) +SECURE_MODE=$secureMode + +# Port the Node server listens on +PORT=$appPort +"@ + + Set-Content -Path ".\backend\.env" -Value $envContent -Encoding utf8 -NoNewline + + Write-Host "" + Write-Host " Configuration written to backend\.env" -ForegroundColor Green +} + +# --- Connection info --------------------------------------------------------- + +function Get-LanIP { + # Prefer the interface that owns the default route; fall back to any private IPv4 + try { + $route = Get-NetRoute -DestinationPrefix '0.0.0.0/0' -ErrorAction Stop | + Sort-Object RouteMetric | Select-Object -First 1 + $ip = (Get-NetIPAddress -InterfaceIndex $route.InterfaceIndex -AddressFamily IPv4 -ErrorAction Stop | + Where-Object { $_.IPAddress -notlike '169.254.*' -and $_.IPAddress -ne '127.0.0.1' } | + Select-Object -First 1).IPAddress + if ($ip) { return $ip } + } catch {} + try { + $ip = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction Stop | + Where-Object { $_.IPAddress -match '^(192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.)' } | + Select-Object -First 1).IPAddress + if ($ip) { return $ip } + } catch {} + return $null +} + +function Show-ConnectionInfo { + $lanIP = Get-LanIP + Write-Host "" + Write-Host " --- How to connect ---" -ForegroundColor Cyan + Write-Host "" + Write-Host " This machine and your home network (LAN):" -ForegroundColor White + if ($lanIP) { + Write-Host " http://$lanIP`:$appPort" -ForegroundColor Green + } else { + Write-Host " http://:$appPort" -ForegroundColor Green + Write-Host " (couldn't auto-detect your LAN IP - run 'ipconfig' and look for IPv4 Address)" -ForegroundColor DarkGray + } + Write-Host "" + if ($useDuck) { + Write-Host " Over the internet (players anywhere):" -ForegroundColor White + Write-Host " http://$duckSub.duckdns.org`:$appPort" -ForegroundColor Green + Write-Host "" + Write-Host " Internet play also needs:" -ForegroundColor DarkGray + Write-Host " 1. A port-forward rule on your router: external $appPort -> this machine's IP" -ForegroundColor DarkGray + Write-Host " 2. A firewall rule allowing inbound connections on port $appPort" -ForegroundColor DarkGray + Write-Host " (Windows: Windows Defender Firewall > Advanced Settings > Inbound Rules)" -ForegroundColor DarkGray + } elseif ($mode -eq "docker") { + Write-Host " Over the internet: not configured (re-run setup and enable DuckDNS," -ForegroundColor DarkGray + Write-Host " or see the Connectivity section of the README for other options)." -ForegroundColor DarkGray + } else { + Write-Host " Over the internet: see the 'Connectivity & Deployment' section of the" -ForegroundColor DarkGray + Write-Host " README - Cloudflare Tunnel is the easiest option for a manual install." -ForegroundColor DarkGray + } + Write-Host "" + Write-Host " Admin login: $adminUser" -ForegroundColor White + Write-Host "" +} + +# --- Launch ----------------------------------------------------------------- + +Write-Host "" +if ($mode -eq "docker") { + if (Read-YesNo " Build and start City_Net now?" $true) { + Write-Host "" + Write-Host " Starting containers (first build can take a few minutes)..." -ForegroundColor Green + docker compose up -d --build + + Write-Host "" + Write-Host " ============================================" -ForegroundColor Green + Write-Host " CITY_NET IS RUNNING" -ForegroundColor Green + Write-Host " ============================================" -ForegroundColor Green + Show-ConnectionInfo + } else { + Write-Host "" + Write-Host " Setup complete. Start the app any time with:" -ForegroundColor Green + Write-Host " docker compose up -d --build" -ForegroundColor White + Show-ConnectionInfo + } +} else { + if (Read-YesNo " Install dependencies and build now? (takes a few minutes)" $true) { + Write-Host "" + Write-Host " Installing backend dependencies..." -ForegroundColor Green + Push-Location backend; npm install; Pop-Location + Write-Host " Installing frontend dependencies..." -ForegroundColor Green + Push-Location frontend; npm install; Pop-Location + Write-Host " Building frontend..." -ForegroundColor Green + Push-Location frontend; npm run build; Pop-Location + + Write-Host "" + Write-Host " ============================================" -ForegroundColor Green + Write-Host " CITY_NET IS BUILT" -ForegroundColor Green + Write-Host " ============================================" -ForegroundColor Green + Show-ConnectionInfo + Write-Host " NOTE: with a manual install the server runs in this terminal." -ForegroundColor Yellow + Write-Host " Closing the terminal stops City_Net. It will not restart on reboot." -ForegroundColor Yellow + Write-Host "" + if (Read-YesNo " Start the server now?" $true) { + Write-Host "" + Write-Host " Starting City_Net... (press Ctrl+C to stop)" -ForegroundColor Green + Set-Location backend + node server.js + } else { + Write-Host "" + Write-Host " Start the server any time by double-clicking start.bat" -ForegroundColor Green + Write-Host "" + } + } else { + Write-Host "" + Write-Host " Setup complete. Install and build with:" -ForegroundColor Green + Write-Host " cd backend; npm install" -ForegroundColor White + Write-Host " cd ..\frontend; npm install; npm run build" -ForegroundColor White + Write-Host " Then start the server any time by double-clicking start.bat" -ForegroundColor Green + Show-ConnectionInfo + Write-Host " NOTE: with a manual install the server runs in a terminal." -ForegroundColor Yellow + Write-Host " Closing that terminal stops City_Net." -ForegroundColor Yellow + Write-Host "" + } +} diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..fc528ec --- /dev/null +++ b/setup.sh @@ -0,0 +1,354 @@ +#!/usr/bin/env bash +# ============================================================================ +# City_Net - Guided Setup +# Generates your .env configuration and (optionally) launches the app. +# Run from the project root: bash setup.sh +# ============================================================================ + +set -euo pipefail + +green() { printf '\033[0;32m%s\033[0m\n' "$1"; } +yellow() { printf '\033[0;33m%s\033[0m\n' "$1"; } +red() { printf '\033[0;31m%s\033[0m\n' "$1"; } +gray() { printf '\033[0;90m%s\033[0m\n' "$1"; } + +read_default() { # prompt, default + local answer + read -r -p "$1 [$2]: " answer + echo "${answer:-$2}" +} + +read_required() { # prompt + local answer + while true; do + read -r -p "$1: " answer + [ -n "$answer" ] && { echo "$answer"; return; } + yellow " This field is required." >&2 + done +} + +read_password() { # prompt + local answer + while true; do + read -rs -p "$1 (input hidden): " answer + echo "" >&2 + [ -n "$answer" ] && { echo "$answer"; return; } + yellow " This field is required." >&2 + done +} + +read_yesno() { # prompt, default (y/n) + local answer hint="y/N" + [ "$2" = "y" ] && hint="Y/n" + read -r -p "$1 [$hint]: " answer + answer="${answer:-$2}" + [[ "$answer" =~ ^[Yy]([Ee][Ss])?$ ]] +} + +# --- Preflight -------------------------------------------------------------- + +echo "" +green " ============================================" +green " CITY_NET // GUIDED SETUP" +green " ============================================" +echo "" + +if [ ! -f "./docker-compose.yml" ]; then + red " ERROR: run this from the City_Net project root (docker-compose.yml not found)." + exit 1 +fi + +# --- Install mode: Docker (recommended) or Manual (Node.js) ------------------ + +has_docker="n" +command -v docker >/dev/null 2>&1 && has_docker="y" + +has_node="n" +if command -v node >/dev/null 2>&1; then + node_major=$(node -v 2>/dev/null | sed 's/^v//' | cut -d. -f1) + [ "${node_major:-0}" -ge 18 ] 2>/dev/null && has_node="y" +fi + +mode="" +if [ "$has_docker" = "y" ] && [ "$has_node" = "y" ]; then + gray " Two install options are available:" + gray " - Docker (recommended): runs in containers, auto-restarts, includes DuckDNS" + gray " - Manual: runs directly with Node.js in a terminal" + echo "" + if read_yesno " Use Docker? (recommended)" "y"; then mode="docker"; else mode="manual"; fi +elif [ "$has_docker" = "y" ]; then + mode="docker" +elif [ "$has_node" = "y" ]; then + yellow " Docker was not found, but Node.js $(node -v) is installed." + if read_yesno " Continue with a manual (Node.js) install?" "y"; then + mode="manual" + else + yellow " Install Docker first: https://docs.docker.com/get-docker/" + exit 1 + fi +else + red " Neither Docker nor Node.js (v18+) was found." + yellow " Install one of them first, then re-run this script:" + yellow " - Docker (recommended): https://docs.docker.com/get-docker/" + yellow " - Node.js v18+: https://nodejs.org/" + exit 1 +fi +echo "" + +if [ -f "./backend/.env" ]; then + yellow " A configuration already exists (backend/.env)." + if ! read_yesno " Overwrite it?" "n"; then + green " Setup cancelled - existing config kept." + exit 0 + fi + echo "" +fi + +# --- Required settings ------------------------------------------------------ + +green " --- Required settings ---" +echo "" + +admin_user=$(read_default " Admin username" "admin") +admin_pass=$(read_password " Admin password") + +echo "" +if [ "$mode" = "docker" ]; then + port_default="8080" + gray " App port: the port players connect on. 80 gives the cleanest URL," + gray " but many home ISPs block inbound 80, so 8080 is the safe default." +else + port_default="5000" + gray " App port: the port players connect on (the Node server listens here)." +fi + +port_in_use() { # port -> 0 if something is already listening on it + if command -v ss >/dev/null 2>&1; then + ss -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]$1\$" + elif command -v netstat >/dev/null 2>&1; then + netstat -an 2>/dev/null | grep -i listen | awk '{print $4}' | grep -qE "[:.]$1\$" + elif command -v lsof >/dev/null 2>&1; then + lsof -iTCP:"$1" -sTCP:LISTEN >/dev/null 2>&1 + else + return 1 # no tool available - skip the check + fi +} + +# Warn if another program already owns the chosen port - localhost would then +# hit that app instead of City_Net on this machine. +while true; do + app_port=$(read_default " App port" "$port_default") + # If it's our own running container holding the port, that's fine + if [ "$mode" = "docker" ] && docker ps --format '{{.Ports}}' 2>/dev/null | grep -q ":$app_port->"; then + break + fi + if port_in_use "$app_port"; then + echo "" + yellow " WARNING: port $app_port is already in use by another program." + yellow " Connections on this port may reach that app instead of City_Net." + if read_yesno " Use port $app_port anyway?" "n"; then break; fi + echo "" + else + break + fi +done + +echo "" +gray " Secure Mode: if ON, players must register an account and be approved." +gray " If OFF, players just type a name to join (simplest)." +if read_yesno " Enable Secure Mode?" "n"; then secure_mode="true"; else secure_mode="false"; fi + +# JWT secret - auto-generated, never prompted +jwt_secret=$(openssl rand -hex 32 2>/dev/null || head -c32 /dev/urandom | od -An -tx1 | tr -d ' \n') + +# --- Optional settings ------------------------------------------------------ + +duck_sub="yourname" +duck_tok="your-duckdns-token" +tz="America/Chicago" +use_duck="n" + +if [ "$mode" = "docker" ]; then + echo "" + green " --- Optional: internet access via DuckDNS ---" + gray " DuckDNS gives you a free domain (e.g. yourcity.duckdns.org) so players" + gray " can connect over the internet. Skip this for LAN-only play." + echo "" + + if read_yesno " Set up DuckDNS now?" "n"; then + use_duck="y" + gray " Get your subdomain and token at https://www.duckdns.org" + duck_sub=$(read_required " DuckDNS subdomain (the part before .duckdns.org)") + duck_tok=$(read_required " DuckDNS token") + tz=$(read_default " Timezone" "America/Chicago") + fi +else + echo "" + yellow " NOTE: the bundled DuckDNS service (automatic internet domain) requires" + yellow " Docker and is not available with a manual install. For internet play," + yellow " see the 'Connectivity & Deployment' section of the README - Cloudflare" + yellow " Tunnel works well with a manual install and needs no port forwarding." +fi + +# --- Write .env ------------------------------------------------------------- + +if [ "$mode" = "docker" ]; then + env_content="JWT_SECRET=$jwt_secret +ADMIN_USER=$admin_user +ADMIN_PASS=$admin_pass + +# Require player registration and approval before joining (default false) +SECURE_MODE=$secure_mode + +# Port the app is exposed on +APP_PORT=$app_port + +# DuckDNS - optional +DUCKDNS_SUBDOMAINS=$duck_sub +DUCKDNS_TOKEN=$duck_tok + +# Timezone for DuckDNS container +TZ=$tz +" + + # Root .env is only used by docker-compose for variable substitution - + # it gets the non-secret values only (no JWT_SECRET / ADMIN_PASS). + root_env_content="APP_PORT=$app_port +DUCKDNS_SUBDOMAINS=$duck_sub +DUCKDNS_TOKEN=$duck_tok +TZ=$tz +" + + printf '%s' "$env_content" > ./backend/.env + printf '%s' "$root_env_content" > ./.env + + echo "" + green " Configuration written to backend/.env and .env" +else + env_content="JWT_SECRET=$jwt_secret +ADMIN_USER=$admin_user +ADMIN_PASS=$admin_pass + +# Require player registration and approval before joining (default false) +SECURE_MODE=$secure_mode + +# Port the Node server listens on +PORT=$app_port +" + + printf '%s' "$env_content" > ./backend/.env + + echo "" + green " Configuration written to backend/.env" +fi + +# --- Connection info --------------------------------------------------------- + +get_lan_ip() { + local ip="" + # Linux: interface that owns the default route + ip=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -n1) + # macOS: getifaddr (guarded - 'ipconfig' is a different tool on Windows) + if [ -z "$ip" ] && [ "$(uname)" = "Darwin" ]; then + ip=$(ipconfig getifaddr en0 2>/dev/null) + [ -z "$ip" ] && ip=$(ipconfig getifaddr en1 2>/dev/null) + fi + # Generic fallback: first private IPv4 from hostname + [ -z "$ip" ] && ip=$(hostname -I 2>/dev/null | tr ' ' '\n' | grep -E '^(192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.)' | head -n1) + echo "$ip" +} + +show_connection_info() { + local lan_ip + lan_ip=$(get_lan_ip) + echo "" + green " --- How to connect ---" + echo "" + echo " This machine and your home network (LAN):" + if [ -n "$lan_ip" ]; then + green " http://$lan_ip:$app_port" + else + green " http://:$app_port" + gray " (couldn't auto-detect your LAN IP - try 'ip addr' or 'ifconfig')" + fi + echo "" + if [ "$use_duck" = "y" ]; then + echo " Over the internet (players anywhere):" + green " http://$duck_sub.duckdns.org:$app_port" + echo "" + gray " Internet play also needs:" + gray " 1. A port-forward rule on your router: external $app_port -> this machine's IP" + gray " 2. A firewall rule allowing inbound connections on port $app_port" + elif [ "$mode" = "docker" ]; then + gray " Over the internet: not configured (re-run setup and enable DuckDNS," + gray " or see the Connectivity section of the README for other options)." + else + gray " Over the internet: see the 'Connectivity & Deployment' section of the" + gray " README - Cloudflare Tunnel is the easiest option for a manual install." + fi + echo "" + echo " Admin login: $admin_user" + echo "" +} + +# --- Launch ----------------------------------------------------------------- + +echo "" +if [ "$mode" = "docker" ]; then + if read_yesno " Build and start City_Net now?" "y"; then + echo "" + green " Starting containers (first build can take a few minutes)..." + docker compose up -d --build + + echo "" + green " ============================================" + green " CITY_NET IS RUNNING" + green " ============================================" + show_connection_info + else + echo "" + green " Setup complete. Start the app any time with:" + echo " docker compose up -d --build" + show_connection_info + fi +else + if read_yesno " Install dependencies and build now? (takes a few minutes)" "y"; then + echo "" + green " Installing backend dependencies..." + (cd backend && npm install) + green " Installing frontend dependencies..." + (cd frontend && npm install) + green " Building frontend..." + (cd frontend && npm run build) + + echo "" + green " ============================================" + green " CITY_NET IS BUILT" + green " ============================================" + show_connection_info + yellow " NOTE: with a manual install the server runs in this terminal." + yellow " Closing the terminal stops City_Net. It will not restart on reboot." + echo "" + if read_yesno " Start the server now?" "y"; then + echo "" + green " Starting City_Net... (press Ctrl+C to stop)" + cd backend && exec node server.js + else + echo "" + green " Start the server any time with:" + echo " bash start.sh" + echo "" + fi + else + echo "" + green " Setup complete. Install and build with:" + echo " cd backend && npm install" + echo " cd frontend && npm install && npm run build" + green " Then start the server any time with:" + echo " bash start.sh" + show_connection_info + yellow " NOTE: with a manual install the server runs in a terminal." + yellow " Closing that terminal stops City_Net." + echo "" + fi +fi diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..ba25b99 --- /dev/null +++ b/start.bat @@ -0,0 +1,24 @@ +@echo off +REM City_Net - start the app (manual/Node.js install) +REM Docker users don't need this: use docker compose up -d instead. + +cd /d "%~dp0" + +if not exist "backend\.env" ( + echo No configuration found ^(backend\.env missing^). + echo Run setup first: double-click setup.bat + pause + exit /b 1 +) + +if not exist "frontend\dist" ( + echo Frontend is not built yet ^(frontend\dist missing^). + echo Run setup first: double-click setup.bat + pause + exit /b 1 +) + +echo Starting City_Net... ^(press Ctrl+C to stop; closing this window stops the app^) +cd backend +node server.js +pause diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..2fe1568 --- /dev/null +++ b/start.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# City_Net - start the app (manual/Node.js install) +# Docker users don't need this: use docker compose up -d instead. + +cd "$(dirname "$0")" + +if [ ! -f "./backend/.env" ]; then + echo " No configuration found (backend/.env missing)." + echo " Run setup first: bash setup.sh" + exit 1 +fi + +if [ ! -d "./frontend/dist" ]; then + echo " Frontend is not built yet (frontend/dist missing)." + echo " Run setup first: bash setup.sh" + echo " Or build manually: cd frontend && npm install && npm run build" + exit 1 +fi + +echo " Starting City_Net... (press Ctrl+C to stop; closing this terminal stops the app)" +cd backend && exec node server.js