START.bat
Rama main · réplica de lectura del repositorio original.
@echo off
setlocal enabledelayedexpansion
cd /d "%~dp0"
title Meistertracker
REM ============================================================
REM Phase 1 (outer wrapper): Update code from git FIRST, then
REM copy the now-updated START.bat to a temp file and re-execute
REM from there. This ensures the temp copy always has the latest
REM code, and git reset can never corrupt the running script.
REM ============================================================
if not "%~1"=="--relaunched" (
echo.
echo ========================================
echo Meistertracker
echo ========================================
echo.
echo [1/5] Updating code from git...
set "IS_WORKTREE=0"
where git >nul 2>&1
if !errorlevel! equ 0 (
set "GIT_DIR_VAL="
for /f "tokens=*" %%G in ('git rev-parse --git-dir 2^>nul') do set "GIT_DIR_VAL=%%G"
if defined GIT_DIR_VAL (
set "WKT_CHECK=!GIT_DIR_VAL:.git/worktrees/=!"
if not "!WKT_CHECK!"=="!GIT_DIR_VAL!" set "IS_WORKTREE=1"
)
if "!IS_WORKTREE!"=="1" (
REM Auto-configure so a worktree run lives alongside prod:
REM - different PORT (default 3001) so prod on 3000 keeps serving
REM - different PM2 name so 'pm2 delete' won't touch prod
REM - WORKTREE_MODE flag so the server can render a UI warning
REM Env vars set here are inherited by the Phase 2 temp copy via cmd /c.
if not defined PORT set "PORT=3001"
REM Always append -worktree so forks with a custom PM2_PROCESS_NAME
REM still get isolation. Skip if already suffixed (re-entrant runs).
if not defined PM2_PROCESS_NAME (
set "PM2_PROCESS_NAME=meisterpilze-worktree"
) else (
set "PM2_NAME_TAIL=!PM2_PROCESS_NAME:~-9!"
if not "!PM2_NAME_TAIL!"=="-worktree" set "PM2_PROCESS_NAME=!PM2_PROCESS_NAME!-worktree"
)
set "WORKTREE_MODE=1"
echo.
echo +------------------------------------------+
echo ^| Running in git worktree ^|
echo ^| Git pull will be skipped ^|
echo +------------------------------------------+
echo -^> Port: !PORT!
echo -^> PM2 name: !PM2_PROCESS_NAME!
echo -^> Production on port 3000 will NOT be touched.
) else if exist ".git" (
git fetch origin >nul 2>&1
if !errorlevel! equ 0 (
git reset --hard origin/main <nul 2>&1
if !errorlevel! neq 0 (
echo WARNING: git reset had errors ^(locked files?^). Continuing with local code.
)
) else (
echo WARNING: git fetch failed. Continuing with local code.
)
) else (
echo WARNING: No .git directory found ^(ZIP download?^). Auto-update disabled.
echo For auto-updates, re-download with: git clone https://github.com/loewenmaehne/meistertracker.git
)
) else (
echo WARNING: Git not found, skipping code update.
)
REM Now copy the (possibly updated) START.bat to temp and run from there
set "TMPBAT=%TEMP%\meisterpilze_start_%RANDOM%.bat"
copy /y "%~f0" "!TMPBAT!" >nul
cmd /c ""!TMPBAT!" --relaunched "%~dp0""
set "RC=!errorlevel!"
del "!TMPBAT!" >nul 2>&1
if !RC! neq 0 ( pause )
exit /b !RC!
)
REM ============================================================
REM Phase 2 (temp copy): Everything after git update
REM ============================================================
cd /d "%~2"
REM ---- Configuration ----
REM Override via env to run multiple instances under distinct PM2 names
REM or to match a fork's branding. Must match the value server.js reads
REM from PM2_PROCESS_NAME.
if not defined PM2_PROCESS_NAME set "PM2_PROCESS_NAME=meisterpilze"
if not defined PORT set "PORT=3000"
set "NEED_PATH_REFRESH=0"
REM ---- Drop administrator rights before anything can start pm2 ----
REM
REM pm2's daemon, its pipe and the server it starts all inherit the token of
REM whatever starts pm2 first. Started with administrator rights, they belong
REM to Administrators, and an ordinary window can then neither stop the server
REM nor send pm2 a command: taskkill is refused, and pm2 fails with
REM "connect EPERM //./pipe/rpc.sock".
REM
REM The boot task is where that comes from. It runs this script through an
REM S4U logon, and Windows does not apply a task's RunLevel to S4U: registered
REM Limited, the boot run still gets the full administrator token. So neither
REM re-registering the task nor asking the scheduler to end it could help --
REM and the task had long finished anyway, leaving pm2 behind.
REM
REM A run that finds itself elevated therefore hands itself to
REM scripts\unelevated.ps1, which runs it again as a standard user. Not when
REM Node.js or Git is missing: installing those is the one thing here that
REM does need administrator rights.
REM
REM The second run deletes the marker file first thing. If it is still there
REM afterwards, the helper never got as far as starting it -- and this run
REM carries on with its rights rather than leave the machine without a server.
REM
REM Keep `exit /b` the last command of every block it sits in. Anything after
REM it in the same block, even a line that never runs, makes cmd /c exit 0 --
REM and then Phase 1 takes a failed second run for a good one and does not
REM stop to show the error.
set "SELF=%~f0"
set "HERE=%~2"
if "%~3"=="--unelevated" (
if defined UNELEVATED_MARK del "!UNELEVATED_MARK!" >nul 2>&1
) else (
call :check_admin
if !errorlevel! equ 0 (
call :check_node
if !errorlevel! equ 0 call :check_git
if !errorlevel! equ 0 (
set "UNELEVATED_ARGS=/d /c ""!SELF!" --relaunched "!HERE!" --unelevated""
set "UNELEVATED_MARK=!TEMP!\meisterpilze_unelevated_!RANDOM!.mark"
type nul > "!UNELEVATED_MARK!"
powershell -NoProfile -ExecutionPolicy Bypass -File "!HERE!scripts\unelevated.ps1"
set "UNELEVATED_RC=!errorlevel!"
if exist "!UNELEVATED_MARK!" (
del "!UNELEVATED_MARK!" >nul 2>&1
echo WARNING: could not drop administrator rights; continuing with them.
) else (
exit /b !UNELEVATED_RC!
)
)
)
)
REM ============================================================
REM Check and auto-install prerequisites
REM ============================================================
REM ---- Check for admin rights (needed for winget installs) ----
call :check_admin
if !errorlevel! neq 0 (
call :check_node
if !errorlevel! neq 0 (
echo.
echo ERROR: Node.js is missing and this terminal is not running as Administrator.
echo winget install requires admin rights. Please right-click START.bat
echo and choose "Run as administrator", then try again.
echo.
exit /b 1
)
)
where winget >nul 2>&1
if !errorlevel! neq 0 (
set "HAS_WINGET=0"
) else (
set "HAS_WINGET=1"
)
REM ---- Check / Install Node.js ----
call :check_node
if !errorlevel! neq 0 (
echo.
echo Node.js is not installed.
if "!HAS_WINGET!"=="1" (
echo Installing Node.js automatically via winget...
echo.
winget install OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements
if !errorlevel! neq 0 (
echo.
echo ERROR: Automatic Node.js installation failed.
echo Please install manually from https://nodejs.org
exit /b 1
)
set "NEED_PATH_REFRESH=1"
) else (
echo ERROR: Cannot auto-install ^(winget not available^).
echo Please install Node.js manually from https://nodejs.org
echo Then run this script again.
echo.
exit /b 1
)
)
REM ---- Check / Install Git ----
call :check_git
if !errorlevel! neq 0 (
echo.
echo Git is not installed.
if "!HAS_WINGET!"=="1" (
echo Installing Git automatically via winget...
echo.
winget install Git.Git --accept-source-agreements --accept-package-agreements
if !errorlevel! neq 0 (
echo.
echo ERROR: Automatic Git installation failed.
echo Please install manually from https://git-scm.com
exit /b 1
)
set "NEED_PATH_REFRESH=1"
) else (
echo WARNING: Git not available.
)
)
REM ---- Refresh PATH if we installed anything ----
if "!NEED_PATH_REFRESH!"=="1" (
echo.
echo Refreshing system PATH...
call :refresh_path
)
REM ---- Verify Node.js is now available ----
call :check_node
if !errorlevel! neq 0 (
echo.
echo ERROR: Node.js is still not found after installation.
echo Please close this window, open a NEW command prompt, and run START.bat again.
echo ^(Windows needs a new terminal to pick up the new PATH.^)
echo.
exit /b 1
)
for /f "tokens=*" %%v in ('node --version') do set "NODE_VER=%%v"
echo -^> Node.js %NODE_VER% found.
REM ---- Ensure PM2 ----
call :check_pm2
if !errorlevel! neq 0 (
echo PM2 not found, installing globally...
call npm install -g pm2
if !errorlevel! neq 0 (
echo ERROR: Failed to install PM2.
exit /b 1
)
call :refresh_path
)
call :check_pm2
if !errorlevel! neq 0 (
echo ERROR: PM2 is still not found after installation.
echo Please close this window, open a NEW command prompt, and run START.bat again.
exit /b 1
)
REM The version comes from pm2's package.json, not from `pm2 --version`. Every
REM pm2 command, --version included, connects to the daemon first and starts
REM one when it cannot -- so with the boot instance's daemon out of reach, each
REM run left a second daemon behind that could never take over the pipe. Step 5
REM checks the daemon before the first pm2 command.
set "PM2_VER="
set "PM2_PKG="
for /f "delims=" %%P in ('where pm2.cmd 2^>nul') do if not defined PM2_PKG set "PM2_PKG=%%~dpPnode_modules\pm2\package.json"
if defined PM2_PKG if exist "!PM2_PKG!" (
for /f "delims=" %%v in ('node -p "require(process.argv[1]).version" "!PM2_PKG!" 2^>nul') do set "PM2_VER=%%v"
)
if defined PM2_VER (
echo -^> PM2 !PM2_VER! found.
) else (
echo -^> PM2 found.
)
REM ============================================================
REM Step 2: Install dependencies
REM ============================================================
echo.
echo [2/5] Installing dependencies...
call npm install --omit=dev
if !errorlevel! neq 0 (
echo ERROR: npm install failed.
exit /b 1
)
REM ============================================================
REM Step 3: Backup data
REM ============================================================
echo.
echo [3/5] Backing up data...
if not exist "backups" mkdir "backups"
for /f "tokens=*" %%T in ('powershell -NoProfile -Command "Get-Date -Format yyyyMMdd_HHmmss"') do set "TIMESTAMP=%%T"
if not defined TIMESTAMP set "TIMESTAMP=backup"
if exist "meistertracker.db" (
set "BACKUP_FILE=backups\meistertracker_!TIMESTAMP!.db"
set "BACKUP_OK=0"
REM node runs the server, so it is always here. The sqlite3 CLI is not:
REM Git for Windows does not ship it, so the raw-copy branch below was not
REM a fallback at all, it was the path that always ran. And `copy` takes
REM meistertracker.db alone: in WAL mode that file is only as current as the
REM last checkpoint, so every snapshot silently dropped whatever was still
REM sitting in meistertracker.db-wal.
node "scripts\backup-db.js" "meistertracker.db" "!BACKUP_FILE!" >nul 2>&1
if !errorlevel! equ 0 (
echo -^> meistertracker.db backed up ^(WAL-consistent^).
set "BACKUP_OK=1"
)
if "!BACKUP_OK!"=="0" (
where sqlite3 >nul 2>&1
if !errorlevel! equ 0 (
sqlite3 meistertracker.db ".backup '!BACKUP_FILE!'" >nul 2>&1
if !errorlevel! equ 0 (
echo -^> meistertracker.db backed up ^(WAL-consistent via sqlite3^).
set "BACKUP_OK=1"
)
)
)
if "!BACKUP_OK!"=="0" (
copy /y "meistertracker.db" "!BACKUP_FILE!" >nul
echo -^> WARNING: raw file copy only. Anything still in meistertracker.db-wal
echo is NOT in this snapshot. Check that scripts\backup-db.js is present.
)
) else (
echo -^> No meistertracker.db found, skipping backup.
)
REM The photos are not zipped here any more. Every start zipped all of
REM data\photos again, onto this same disk, and no zip was ever deleted:
REM the same pictures piling up until the disk is full. The server keeps a
REM second copy under backups\photos as hard links, which cost no space, and
REM the USB stick backup copies each photo once. An update does not touch
REM photo files anyway; what it changes is the database, saved above.
REM ============================================================
REM Step 4: Ensure TLS certificates
REM ============================================================
echo.
echo [4/5] Ensuring TLS certificates...
call :ensure_certs
REM ============================================================
REM Step 5: Start / Restart server via PM2
REM ============================================================
echo.
echo [5/5] Starting server...
REM ---- Can this window send pm2 a command at all? ----
REM Every pm2 command connects to the daemon over a named pipe first. A daemon
REM started with administrator rights owns that pipe as Administrators, and
REM this window may then read it but not write to it: pm2 fails with
REM "connect EPERM //./pipe/rpc.sock" and a stack trace, which reads like the
REM application crashing, after starting a second daemon that can never take
REM the pipe over. So ask before the first pm2 command, and say what it is.
REM Worktree runs ask too -- there is one daemon per machine, and they use it.
set "PM2_ACCESS="
for /f "delims=" %%A in ('powershell -NoProfile -ExecutionPolicy Bypass -File "%CD%\scripts\pm2-access.ps1" 2^>nul') do set "PM2_ACCESS=%%A"
if "!PM2_ACCESS!"=="denied" (
echo.
echo ERROR: pm2 runs with administrator rights, and this window has none.
echo.
echo Every pm2 command from here is refused, so the server can be neither
echo stopped nor updated. Its daemon was started elevated: by the boot
echo task, before this script learned to drop those rights, or from a
echo window run as administrator.
echo.
echo Once, in an Administrator PowerShell:
echo ^& "$env:APPDATA\npm\pm2.cmd" kill
echo then run this script again.
echo.
echo If this is back after the next restart, the boot run could not drop
echo its rights. It wrote down why in %TEMP%\meistertracker-unelevated.log
echo.
pause
exit /b 1
)
REM Match update_server.sh: always delete + start with --update-env so the
REM new process picks up any .env changes made since the last launch.
REM Through pm2 first, before anything is killed on the port: a process pm2
REM runs, killed from outside, is one pm2 starts straight back up.
call pm2 describe %PM2_PROCESS_NAME% >nul 2>&1
if !errorlevel! equ 0 (
echo -^> Process found, deleting for clean restart...
call pm2 delete %PM2_PROCESS_NAME% >nul 2>&1
)
REM Then kill whatever else still holds our port, and look again rather than
REM assume: taskkill's refusal goes to nul, and a refused kill printed exactly
REM what a successful one does.
REM In worktree mode skip this entirely so a parallel run never reaches
REM across into the production instance on a different port.
if "%WORKTREE_MODE%"=="1" (
echo -^> Worktree mode: skipping stale-port cleanup ^(prod on 3000 untouched^).
) else (
for /f "tokens=5" %%P in ('netstat -ano 2^>nul ^| findstr /r "0.0.0.0:%PORT%.*LISTENING"') do (
echo -^> Killing stale process on port %PORT% ^(PID %%P^)...
taskkill /PID %%P /F >nul 2>&1
)
set "PORT_HOLDER="
for /f "tokens=5" %%P in ('netstat -ano 2^>nul ^| findstr /r "0.0.0.0:%PORT%.*LISTENING"') do set "PORT_HOLDER=%%P"
if defined PORT_HOLDER (
set "HOLDER_NAME=unknown program"
for /f "tokens=1 delims=," %%N in ('tasklist /FI "PID eq !PORT_HOLDER!" /FO CSV /NH 2^>nul') do set "HOLDER_NAME=%%~N"
echo.
echo ERROR: port %PORT% is held by PID !PORT_HOLDER! ^(!HOLDER_NAME!^), and this window cannot stop it.
echo.
echo It is not run by the pm2 this window talks to - that one was asked
echo first. To stop it, in an Administrator PowerShell:
echo Stop-Process -Id !PORT_HOLDER! -Force
echo then run this script again.
echo.
pause
exit /b 1
)
)
echo -^> Starting instance...
call pm2 start server.js --name %PM2_PROCESS_NAME% --update-env
call pm2 save >nul 2>&1
REM Wait briefly for the process to initialize, then verify it stayed up
ping -n 4 127.0.0.1 >nul 2>&1
call pm2 show %PM2_PROCESS_NAME% 2>nul | findstr /i /c:"online" >nul 2>&1
if !errorlevel! neq 0 (
echo.
echo ERROR: Server process crashed on startup.
echo.
echo Recent error log:
call pm2 logs %PM2_PROCESS_NAME% --lines 15 --nostream --err 2>nul
echo.
exit /b 1
)
REM Tag this commit as stable (last known-good deployment).
REM Skip in worktree mode — that tag is for prod only and must not move
REM when running a feature-branch instance.
if not "%WORKTREE_MODE%"=="1" (
where git >nul 2>&1
if !errorlevel! equ 0 (
git tag -f stable HEAD >nul 2>&1
if !errorlevel! equ 0 (
echo -^> Tagged current commit as 'stable'.
)
)
)
echo.
echo ========================================
echo Server started successfully!
if "%WORKTREE_MODE%"=="1" (
echo [WORKTREE INSTANCE - production on 3000 is separate]
)
if exist "certs\server.crt" (
echo URL: https://localhost:%PORT%
) else (
echo URL: http://localhost:%PORT%
echo.
echo WARNING: No TLS certs found, running HTTP only.
echo iOS camera scanning requires HTTPS.
)
echo Run 'pm2 logs %PM2_PROCESS_NAME%' to see output.
echo ========================================
echo.
pause
exit /b 0
REM ============================================================
REM Helper functions
REM ============================================================
:check_node
where node >nul 2>&1
exit /b %errorlevel%
:check_git
where git >nul 2>&1
exit /b %errorlevel%
:check_pm2
where pm2 >nul 2>&1
exit /b %errorlevel%
:check_admin
REM Listing file system filters is for administrators only. Unlike
REM `net session`, fltmc does not need the Server service to be running.
fltmc >nul 2>&1
exit /b %errorlevel%
:ensure_certs
if exist "certs\server.key" if exist "certs\server.crt" (
echo -^> TLS certificates found.
REM LE cert renewal is handled by the server on startup.
exit /b 0
)
if exist "gen-cert.ps1" (
echo -^> TLS certificates missing, generating...
if defined CERT_DOMAIN (
echo -^> Including domain in cert: %CERT_DOMAIN%
powershell -NoProfile -ExecutionPolicy Bypass -File "%CD%\gen-cert.ps1" -Domain "%CERT_DOMAIN%"
) else (
powershell -NoProfile -ExecutionPolicy Bypass -File "%CD%\gen-cert.ps1"
)
if exist "certs\server.key" if exist "certs\server.crt" (
echo -^> TLS certificates generated successfully.
exit /b 0
)
echo -^> WARNING: Certificate generation failed.
)
if exist "gen-cert.sh" (
where bash >nul 2>&1
if !errorlevel! equ 0 (
echo -^> Trying gen-cert.sh...
bash "%CD%/gen-cert.sh"
if exist "certs\server.key" if exist "certs\server.crt" (
echo -^> TLS certificates generated successfully.
exit /b 0
)
)
)
echo -^> WARNING: Could not generate TLS certificates.
echo Server will start in HTTP-only mode ^(mobile camera will not work^).
exit /b 0
:refresh_path
set "NEWPATH="
for /f "tokens=2*" %%A in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path 2^>nul') do set "NEWPATH=%%B"
for /f "tokens=2*" %%A in ('reg query "HKCU\Environment" /v Path 2^>nul') do (
if defined NEWPATH (
set "NEWPATH=!NEWPATH!;%%B"
) else (
set "NEWPATH=%%B"
)
)
if defined NEWPATH (
set "PATH=!NEWPATH!;%SystemRoot%\system32;%SystemRoot%"
)
if exist "%ProgramFiles%\nodejs" set "PATH=!PATH!;%ProgramFiles%\nodejs"
if exist "%ProgramFiles%\Git\cmd" set "PATH=!PATH!;%ProgramFiles%\Git\cmd"
if exist "%ProgramFiles%\Git\usr\bin" set "PATH=!PATH!;%ProgramFiles%\Git\usr\bin"
if exist "%LOCALAPPDATA%\Programs\Git\cmd" set "PATH=!PATH!;%LOCALAPPDATA%\Programs\Git\cmd"
if exist "%APPDATA%\npm" set "PATH=!PATH!;%APPDATA%\npm"
exit /b 0