Files
lego-instructions-manager/install_instruction_fetcher.bat
jessikitty f7c20de5ac Add owner-only logging and Brickset API call tracking
LIM configured no logging at all, so the instruction fetcher had no trail to follow. Adds a rotating file handler on the root logger plus two owner-only admin pages.

- Admin > Logs: filter by level, source and text; tracebacks kept with their error; auto-refresh, raw download, rotated-file browsing and rotate-now

- Admin > Logs > Brickset API calls: every outbound call recorded with method, params, caller, duration, result count and error, plus daily quota accounting (only getSets counts) and a comparison against getKeyUsageStats

- New owner concept (services/ownership.py): a single account, distinct from is_admin, since these pages expose other admins activity. Defaults to the first admin; flask owner show/set to change

- Services now log through module loggers; retention setting and pruning for call records

- New CLI: flask owner show/set, flask logs path/prune

- New table brickset_api_calls, created automatically at boot; LOG_* env vars, /data/logs in Docker
2026-09-27 10:42:49 +10:00

236 lines
7.6 KiB
Batchfile

@echo off
REM ===========================================================================
REM LEGO Instructions Manager - Instruction Fetcher + Logging installer
REM
REM RUN THIS FROM THE FOLDER YOU EXTRACTED THE UPDATE INTO, not from your
REM LIM install - the two cannot be the same folder.
REM
REM Copies 22 named files into an existing LIM install, backing up every
REM file it replaces first. Whole directories are never copied, so your
REM uploads and database are not involved.
REM
REM Usage: install_instruction_fetcher.bat [target]
REM Default target: E:\LIM
REM
REM Created by: Jess Rogerson (yelling commands at Claude.AI)
REM ===========================================================================
setlocal EnableDelayedExpansion
set "SOURCE=%~dp0"
set "TARGET=%~1"
if "%TARGET%"=="" set "TARGET=E:\LIM"
set "MISSING=0"
set "COPYFAIL=0"
set "SAVED=0"
for %%A in ("%SOURCE%.") do set "SRCFULL=%%~fA"
for %%B in ("%TARGET%\.") do set "DSTFULL=%%~fB"
echo.
echo Source : %SRCFULL%
echo Target : %DSTFULL%
echo.
REM --- refuse to copy a folder onto itself -----------------------------------
if /i "%SRCFULL%"=="%DSTFULL%" goto :samedir
if not exist "%TARGET%\app\__init__.py" goto :nottarget
REM --- are the update files actually here? -----------------------------------
echo Checking the update files...
call :need ".env.docker.example"
call :need ".env.example"
call :need "README.md"
call :need "app\__init__.py"
call :need "app\cli.py"
call :need "app\config.py"
call :need "app\models\__init__.py"
call :need "app\models\api_call.py"
call :need "app\models\instruction.py"
call :need "app\models\instruction_fetch.py"
call :need "app\models\site_setting.py"
call :need "app\routes\__init__.py"
call :need "app\routes\instruction_fetch.py"
call :need "app\routes\logs.py"
call :need "app\services\brickset_api.py"
call :need "app\services\fetch_worker.py"
call :need "app\services\instruction_fetcher.py"
call :need "app\services\logging_setup.py"
call :need "app\services\ownership.py"
call :need "app\services\schema_upgrade.py"
call :need "app\templates\admin\brickset_calls.html"
call :need "app\templates\admin\dashboard.html"
call :need "app\templates\admin\instruction_fetch.html"
call :need "app\templates\admin\logs.html"
call :need "app\templates\admin\settings.html"
call :need "diagnose_fetcher.py"
call :need "docker-compose.yml"
call :need "docker\entrypoint.sh"
call :need "docs\INSTRUCTION_FETCHER.md"
call :need "docs\LOGGING.md"
if not "%MISSING%"=="0" goto :incomplete
echo all 30 present.
REM --- backup ------------------------------------------------------------------
for /f "tokens=2 delims==" %%I in ('wmic os get localdatetime /value') do set "DT=%%I"
set "STAMP=!DT:~0,8!-!DT:~8,6!"
set "BACKUP=%TARGET%\_backup_fetcher_!STAMP!"
echo.
echo Backing up the files about to be replaced:
echo !BACKUP!
echo.
mkdir "!BACKUP!" 2>nul
call :save ".env.docker.example"
call :save ".env.example"
call :save "README.md"
call :save "app\__init__.py"
call :save "app\cli.py"
call :save "app\config.py"
call :save "app\models\__init__.py"
call :save "app\models\api_call.py"
call :save "app\models\instruction.py"
call :save "app\models\instruction_fetch.py"
call :save "app\models\site_setting.py"
call :save "app\routes\__init__.py"
call :save "app\routes\instruction_fetch.py"
call :save "app\routes\logs.py"
call :save "app\services\brickset_api.py"
call :save "app\services\fetch_worker.py"
call :save "app\services\instruction_fetcher.py"
call :save "app\services\logging_setup.py"
call :save "app\services\ownership.py"
call :save "app\services\schema_upgrade.py"
call :save "app\templates\admin\brickset_calls.html"
call :save "app\templates\admin\dashboard.html"
call :save "app\templates\admin\instruction_fetch.html"
call :save "app\templates\admin\logs.html"
call :save "app\templates\admin\settings.html"
call :save "diagnose_fetcher.py"
call :save "docker-compose.yml"
call :save "docker\entrypoint.sh"
call :save "docs\INSTRUCTION_FETCHER.md"
call :save "docs\LOGGING.md"
echo %SAVED% file^(s^) backed up.
REM --- install -------------------------------------------------------------------
echo.
echo Installing...
call :put ".env.docker.example"
call :put ".env.example"
call :put "README.md"
call :put "app\__init__.py"
call :put "app\cli.py"
call :put "app\config.py"
call :put "app\models\__init__.py"
call :put "app\models\api_call.py"
call :put "app\models\instruction.py"
call :put "app\models\instruction_fetch.py"
call :put "app\models\site_setting.py"
call :put "app\routes\__init__.py"
call :put "app\routes\instruction_fetch.py"
call :put "app\routes\logs.py"
call :put "app\services\brickset_api.py"
call :put "app\services\fetch_worker.py"
call :put "app\services\instruction_fetcher.py"
call :put "app\services\logging_setup.py"
call :put "app\services\ownership.py"
call :put "app\services\schema_upgrade.py"
call :put "app\templates\admin\brickset_calls.html"
call :put "app\templates\admin\dashboard.html"
call :put "app\templates\admin\instruction_fetch.html"
call :put "app\templates\admin\logs.html"
call :put "app\templates\admin\settings.html"
call :put "diagnose_fetcher.py"
call :put "docker-compose.yml"
call :put "docker\entrypoint.sh"
call :put "docs\INSTRUCTION_FETCHER.md"
call :put "docs\LOGGING.md"
if not "%COPYFAIL%"=="0" goto :copyfailed
echo.
echo ==========================================================
echo Done - 30 files installed.
echo.
echo No migration step: the new table and columns are created
echo the next time LIM starts.
echo.
echo 1. Restart LIM (start_lim.bat)
echo 2. Admin ^> Instruction Fetcher
echo 3. Queue sets, then switch the fetcher on
echo.
echo To push the same change to Gitea, run push_fetcher_update.bat
echo FROM THIS FOLDER (not from %TARGET%).
echo.
echo Rollback: copy the files in
echo !BACKUP!
echo back over %TARGET%
echo ==========================================================
echo.
pause
exit /b 0
REM --- exits ---------------------------------------------------------------------
:samedir
echo ERROR: the source and target are the same folder.
echo.
echo You are running this script from inside your LIM install, so there
echo is nothing to copy - which is why an earlier run appeared to work
echo but changed nothing.
echo.
echo Open the folder you extracted the update into (the one containing
echo app\services\instruction_fetcher.py) and run it from there:
echo.
echo cd /d C:\path\to\lim-instruction-fetcher
echo install_instruction_fetcher.bat %TARGET%
goto :fail
:nottarget
echo ERROR: %TARGET% does not look like a LIM install
echo ^(no app\__init__.py^).
echo.
echo Pass the right path, e.g. install_instruction_fetcher.bat D:\LIM
goto :fail
:incomplete
echo.
echo ERROR: %MISSING% file^(s^) missing from
echo %SRCFULL%
echo.
echo This is not the extracted update folder. Look for the one
echo containing app\services\instruction_fetcher.py.
goto :fail
:copyfailed
echo.
echo ERROR: %COPYFAIL% file^(s^) could not be copied. Is LIM still running?
echo Stop it and run this again; the backup in
echo !BACKUP!
echo has the originals.
goto :fail
:need
if not exist "%SOURCE%%~1" echo MISSING: %~1 & set /a MISSING+=1
goto :eof
:save
if not exist "%TARGET%\%~1" goto :eof
for %%F in ("!BACKUP!\%~1") do if not exist "%%~dpF" mkdir "%%~dpF"
copy /Y "%TARGET%\%~1" "!BACKUP!\%~1" >nul
if not errorlevel 1 echo saved %~1 & set /a SAVED+=1
goto :eof
:put
for %%F in ("%TARGET%\%~1") do if not exist "%%~dpF" mkdir "%%~dpF"
copy /Y "%SOURCE%%~1" "%TARGET%\%~1" >nul
if errorlevel 1 echo FAILED: %~1 & set /a COPYFAIL+=1
goto :eof
:fail
echo.
echo Nothing was changed.
pause
exit /b 1