@echo off REM ============================================================================ REM Scoreboard one-click backup (Windows) REM REM Calls scoreboard.exe's backup subcommand (VACUUM INTO), so it is safe to REM run while the service is up and does not need an external sqlite3.exe. REM Output goes to \backups\; the server keeps the newest 20 per DB REM (system / each event). REM The data dir is the one the Console uses, i.e. %LOCALAPPDATA%\ScoreboardConsole\data REM (falling back to \data when that does not exist yet). It lives outside REM the install dir on purpose: reinstalling over an existing install wipes the REM install dir, so data must not be kept there. REM NOTE: the .db snapshots below are not the whole event. Uploaded image assets REM (event logo, display background/logo, team logos) and uploaded fonts REM live as files under \event_projs\\uploads\ and are REM referenced from the DB by path, so this script also zips each REM project's uploads\ into \backups\ next to its snapshots. REM Logs under \logs\ are NOT archived: the server rotates them REM (log.* in config.yaml), they can grow without bound, and they are REM diagnostics rather than data -- copy that directory by hand when a REM post-mortem needs it. REM The uploads zips are NOT pruned by the server (its 20-file rotation REM only counts .db files), so delete old *-uploads-*.zip by hand when REM they pile up -- keep at least the newest one per project. REM REM Usage: deploy\backup.bat manual backup (reason=manual) REM deploy\backup.bat publish pre-publish backup (reason=publish) REM REM Keep this file ASCII-only. cmd.exe mis-parses UTF-8 non-ASCII text inside REM batch files (especially inside if(...) blocks). See deploy\build.bat. REM ============================================================================ setlocal enabledelayedexpansion chcp 65001 >nul 2>&1 cd /d "%~dp0.." set "ROOT=%CD%" REM Same data dir the Console hands to the server via -data. set "DATA=%ROOT%\data" if defined LOCALAPPDATA if exist "%LOCALAPPDATA%\ScoreboardConsole\data" set "DATA=%LOCALAPPDATA%\ScoreboardConsole\data" REM Same config.yaml the Console hands to the server via -config: it lives in the REM per-user dir now, and the copy next to this script is only the shipped template REM (the uninstaller deletes the install dir, so edits there are lost). set "CONFIG=%ROOT%\config.yaml" if defined LOCALAPPDATA if exist "%LOCALAPPDATA%\ScoreboardConsole\config.yaml" set "CONFIG=%LOCALAPPDATA%\ScoreboardConsole\config.yaml" set "REASON=manual" if not "%~1"=="" set "REASON=%~1" set "REASON=%REASON: =_%" set "REASON=%REASON:/=-%" set "REASON=%REASON:\=-%" set "REASON=%REASON:"=%" if not exist "%DATA%" ( echo [ERROR] data directory not found: %DATA% echo Start Scoreboard_Console.exe once so the service creates it, echo or point this script at the data dir via -data manually. exit /b 1 ) REM ---- locate scoreboard.exe ------------------------------------------------- set "EXE=" if exist "%~dp0..\dist\scoreboard.exe" set "EXE=%~dp0..\dist\scoreboard.exe" if not defined EXE if exist "%ROOT%\scoreboard.exe" set "EXE=%ROOT%\scoreboard.exe" if not defined EXE if exist "%~dp0scoreboard.exe" set "EXE=%~dp0scoreboard.exe" if not defined EXE for %%I in (scoreboard.exe) do if exist "%%~$PATH:I" set "EXE=%%~$PATH:I" if not defined EXE ( echo [ERROR] scoreboard.exe not found. echo Run deploy\build.bat first, or put it in the project root / PATH. exit /b 1 ) REM config.yaml is still needed for port / backup settings; -data below picks the REM data dir explicitly, so the current directory no longer decides it. if not exist "%CONFIG%" ( echo [ERROR] config.yaml not found: %CONFIG% exit /b 1 ) echo === Scoreboard backup === echo Data dir : %DATA% echo Config : %CONFIG% echo Server : %EXE% echo Reason : %REASON% echo. set /a OK=0 set /a FAIL=0 REM The server backs up one event DB at a time (system.db is always included), REM so loop over the project directories. A typical deployment has one project. REM Project names come from `dir /b /ad` (names only, no paths) rather than from REM `for /d %%D in ("%DATA%\event_projs\*")`: cmd runs that FOR body once with the REM unexpanded pattern when the wildcard matches nothing, which set FOUND, handed REM "-project *" to the server (illegal name -> exit 1) and made the fallback below REM dead code. `dir /b` prints nothing for an empty or missing folder, so the REM fallback is reachable by construction. "eol=|" only disables the default ";" REM end-of-line comment so a project name starting with ; is not skipped. set "FOUND=" for /f "delims= eol=|" %%D in ('dir /b /ad "%DATA%\event_projs" 2^>nul') do ( set "FOUND=1" echo backup event %%D "%EXE%" backup -config "%CONFIG%" -project "%%D" -data "%DATA%" -reason "%REASON%" if errorlevel 1 (set /a FAIL+=1) else (set /a OK+=1) ) if not defined FOUND ( echo backup default event "%EXE%" backup -config "%CONFIG%" -data "%DATA%" -reason "%REASON%" if errorlevel 1 (set /a FAIL+=1) else (set /a OK+=1) ) REM Archive each project's uploads\ (images + uploaded fonts under REM uploads\fonts\). One timestamp is reused for every project so the snapshots REM of a single run sort together. Same iteration as above (see the note there). set /a ASSETS=0 set /a ASSETS_FAIL=0 for /f %%T in ('powershell -NoProfile -NonInteractive -Command "Get-Date -Format yyyyMMdd-HHmmss"') do set "STAMP=%%T" if not defined STAMP set "STAMP=unknown" for /f "delims= eol=|" %%D in ('dir /b /ad "%DATA%\event_projs" 2^>nul') do call :archive_uploads "%%D" "%DATA%\event_projs\%%D" echo. echo === backup done: ok=!OK! fail=!FAIL! assets=!ASSETS! assets_fail=!ASSETS_FAIL! === dir /b "%DATA%\backups\*.db" 2>nul dir /b "%DATA%\backups\*-uploads-*.zip" 2>nul if !FAIL! GTR 0 ( endlocal exit /b 1 ) if !ASSETS_FAIL! GTR 0 ( endlocal exit /b 1 ) endlocal exit /b 0 REM ==== subroutines =========================================================== REM %1 = project name, %2 = project dir. The archive keeps the top-level REM "uploads" folder name, so unzipping into a project dir puts the files back REM exactly where the server reads them from. :archive_uploads set "HASFILES=" for /f "delims=" %%F in ('dir /b /s /a-d "%~2\uploads" 2^>nul') do set "HASFILES=1" if not defined HASFILES ( echo no assets %~1\uploads (empty, skipped) exit /b 0 ) echo archive assets %~1\uploads REM The paths go through the environment instead of being spliced into the REM PowerShell source: project names are user-supplied (the server only rejects REM / \ : * ? " < > | and control chars, so a single quote is allowed), and a REM single quote would close the -Path string, make PowerShell fail to parse the REM command and leave the whole script exiting 1. Environment values need no REM quoting at all inside the command string. set "SCOREBOARD_UPLOADS=%~2\uploads" set "SCOREBOARD_ZIP=%DATA%\backups\%~1-uploads-%STAMP%.zip" powershell -NoProfile -NonInteractive -Command "$ErrorActionPreference='Stop'; Compress-Archive -Path $env:SCOREBOARD_UPLOADS -DestinationPath $env:SCOREBOARD_ZIP -Force" if errorlevel 1 ( echo [ERROR] archiving %~1\uploads failed set /a ASSETS_FAIL+=1 ) else ( set /a ASSETS+=1 ) exit /b 0