From d26dacf7e8cb8a0f09b4b4498ae9df7a9a8858bf Mon Sep 17 00:00:00 2001 From: beo3000 Date: Sat, 28 Feb 2026 15:22:39 +0100 Subject: [PATCH] upd deploy skript --- ka-note/VERSION | 2 +- ka-note/deploy.ps1 | 68 +++++++++++++++++++++++++++++ ka-note/server/ka-note.db-shm | Bin 32768 -> 32768 bytes ka-note/server/ka-note.db-wal | Bin 4194192 -> 4194192 bytes ka-note/server/src/routes/admin.ts | 16 ++++++- 5 files changed, 84 insertions(+), 2 deletions(-) diff --git a/ka-note/VERSION b/ka-note/VERSION index 0d512a4..168b04a 100644 --- a/ka-note/VERSION +++ b/ka-note/VERSION @@ -1 +1 @@ -1.1.101 \ No newline at end of file +1.1.103 \ No newline at end of file diff --git a/ka-note/deploy.ps1 b/ka-note/deploy.ps1 index af028ea..4cd6995 100644 --- a/ka-note/deploy.ps1 +++ b/ka-note/deploy.ps1 @@ -128,6 +128,74 @@ if ($env:KA_NOTE_DEPLOY_API_KEY) { Write-Host "=== Restart App Service ===" -ForegroundColor Cyan az webapp restart --name $APP --resource-group $RG +Write-Host "=== Post-Deploy DB Validation ===" -ForegroundColor Cyan +Write-Host " Waiting for server to boot (~20s)..." -ForegroundColor DarkGray +$bootDeadline = (Get-Date).AddSeconds(45) +$serverReady = $false +while ((Get-Date) -lt $bootDeadline) { + Start-Sleep -Seconds 3 + try { + $h = Invoke-RestMethod -Uri "$AppUrl/api/health" -Method GET -UseBasicParsing -TimeoutSec 5 + if ($h.status -eq 'ok') { $serverReady = $true; break } + } catch { } +} +if (-not $serverReady) { + Write-Host " WARNING: Server did not respond within 45s" -ForegroundColor Red +} + +$validationOk = $false +if ($serverReady -and $env:KA_NOTE_DEPLOY_API_KEY) { + $authHeader = @{ Authorization = "Bearer $env:KA_NOTE_DEPLOY_API_KEY" } + $maxRetries = 6 + for ($attempt = 1; $attempt -le $maxRetries; $attempt++) { + try { + $stats = Invoke-RestMethod -Uri "$AppUrl/api/admin/stats" -Method GET ` + -Headers $authHeader -UseBasicParsing -TimeoutSec 15 + $contextCount = $stats.contextCount + $topicCount = $stats.topicCount + Write-Host " [$attempt/$maxRetries] Contexts: $contextCount, Topics: $topicCount" -ForegroundColor DarkGray + if ($contextCount -gt 0 -and $topicCount -gt 0) { + Write-Host " DB validation passed." -ForegroundColor Green + $validationOk = $true + break + } + if ($attempt -lt $maxRetries) { Start-Sleep -Seconds 10 } + } catch { + Write-Host " [$attempt/$maxRetries] Request error: $_" -ForegroundColor Yellow + if ($attempt -lt $maxRetries) { Start-Sleep -Seconds 10 } + } + } + if (-not $validationOk) { + Write-Host " DB validation FAILED after $maxRetries attempts" -ForegroundColor Red + } +} elseif (-not $env:KA_NOTE_DEPLOY_API_KEY) { + Write-Host " KA_NOTE_DEPLOY_API_KEY not set, skipping DB validation" -ForegroundColor Yellow + $validationOk = $true +} + +if (-not $validationOk) { + $latestBackup = Get-ChildItem -Path $BackupDir -Filter "*.db" | + Sort-Object LastWriteTime -Descending | Select-Object -First 1 + Write-Host "" + Write-Host " !! DB appears empty or unreachable after deploy !!" -ForegroundColor Red + Write-Host " Pre-deploy backup: $($latestBackup.FullName)" -ForegroundColor Yellow + Write-Host "" + $answer = Read-Host " Restore backup to prod? (yes/no)" + if ($answer -eq 'yes') { + Write-Host " Uploading backup to prod..." -ForegroundColor Cyan + Invoke-WebRequest -Uri $KuduDbUrl ` + -Method PUT ` + -Headers @{ Authorization = "Bearer $KuduToken" } ` + -InFile $latestBackup.FullName -UseBasicParsing | Out-Null + Write-Host " Backup uploaded. Restarting app..." -ForegroundColor Cyan + az webapp restart --name $APP --resource-group $RG + Write-Host " Restore complete. Verify manually: $AppUrl" -ForegroundColor Green + } else { + Write-Host " Restore skipped. Manual intervention required!" -ForegroundColor Red + exit 1 + } +} + Write-Host "=== Done! $VERSION deployed ===" -ForegroundColor Green Write-Host "Check: https://$APP.azurewebsites.net" diff --git a/ka-note/server/ka-note.db-shm b/ka-note/server/ka-note.db-shm index f01edf32bed2bf188a1fa5bd0951821e885df17a..33533b26a512d1ad1efd9bfa0a8465627c78f611 100644 GIT binary patch delta 1199 zcmb7?YgiOj0ETDYS#WndmC(tkXcIw26f#pnGC~qs6$m#}Lc}GNRzgBCP1D%yUXZbttKBbVX=XPoQZ&2YZ(5e7&584)zs>WUd1l^uzi-Z&IngN5D6J{GvMqh! z4%@PN<~WuWaW6u}Y+s)~+?TdvmD66fWaz#XPFY~@(slpW|F_&dWw^Zu|8p4L!?N86 zmZbttKvme@H7jepYQK}Jn!OPf^`xpouUEBuGTr;I>PYmc!3n)x=J8E&=NNWJof_$+ zt7yV96?9Y4gm5gLxZI9;eaXXAs7Q)Drn8ji`HrIzv2a1NIq%n$eW^oaBvx1F$ zz+Q0rIAuhYD-B#6f>K9*u`<~llkv3k4nDoee6=AYMPEX+vXYNU)MBlzN-Cu& z;5=^SRes@k6K@_b#Q-L-n8(j)MopCBG#XjPYy3)Y<2F@Ek;OzVrGxDpV%(x? z=`Um+5AYjlnh9GqPLg6ERb0jzx)8=JoSc_3k}=HWLT=+h*7GjAIY1w0pq_ZAYN(ZB z5NC5aYuUk}CRPxZ{;|yGb@q^MUJ$91BAaTiAjVD(GvOb(GFS5ipOIvma~q}qbk64v-orAz;h9pL zNG;d!B%hP4sk>8lHbwlU+{qjKPKGgSo23}S6t3kdzTgPG*jyENVL#3y$|^ePp;h;H zcFl_To4JcO`Gaf|uW6CuB*L`uG+!dS<7-=bi69qnA8)gt0b2FiYTKNMC}RN+@jkYb zV@j*%N->l=uHzZL;>hlT4j1;Pl1o_4CwPt8IZui{oXJ8SW-E@S?%t`IFU2s<;d<8b zHNCnER=aRCbGe@_{7sgIakY3sgGi@>MLfcXcuao6LMd{Y$`YRC8;;V{{Zv~QNs+-g aF6L1_BEh(otx}xKG;ZKIz9mK1w*3RMPCIS@ delta 602 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 MSbziCmI_b*4`u2(d;kCd delta 5225 zcmY+Ic|26>AIC$Oh#47Ll*XPcjqGV;X;H$2x=EJGm0Q_bM3yWkkw{k=SqdfGY&Cca zS>orSA0sW67D|+oy(}$$=Qz*tJ9)jHKjwYD=R40C-{&)L;%8_1;?Wg~@FDz&0J0Df zM1&Awgn}$WL=aJAF(QVDBUFTjNFYlPNra9_A=1cFWErv?S%JtPE0I-*EV3G5Aacd> z^4ijTdl#TTa^3m-${D7UyEAI?zGOZA1d5`SMQLJlEGU2-;nAjc4Wv`02zKmjT}f7j zh!zM9)1%QSc^Tj3FaBDd2AGAL(3cr@)bgaIkL*BvHkG);H0^?gRL*xJ;L5{F%yhHm zymQ$HKu-$#uzXhTs7W!u?Z@r$Au6V>rus7})Jf2K z5UZH2^L?GacFpp+?J3M{PXU-z>7d)wvyiRm{K`u!jlc?@@qe_6(r$VUXx-RWSkBKU zgTS&Pz`TiFjbi_8syVCW3`}ORtFO0<9ipE*dKS3WVpl6Yz*d0k9vXrr}~*x z%HmT&pSq{dFhG1SRv}IaI^5>PJ%N340yNGY0Ru70qOy+XUANBHgK7Z-v}F3H?K|11 zZr9TFe8jU{sPa%N=A~Z&BMMlBtfXR&ZH9t1VA{@?eC20l#NA%$4j7xUl5?Gk2^m#B z>j6Voljy>GU*YAb`(psG-LR4Xi=7WgEKJ$~gMTqmQsr}5OW8zoIf(y9f?#}?%AQWY zXXS$Gd-Ly>9NE03U*qChkiJnHZFO>=yRlZYFuK3C`4T%%ql#|@!o{Fjgt(ZVO+YWbJT=BHOD0ERI(F40%Ql{(ZN`H88&9hu@*e9{e0eTQz_5YTYlsTp(Mj zD}WBzOXUq>6vxvtsn?a7>cNQehL2SKesxZl-UemL)nIzu*g+v|Niy>RXT|LI1^zrN7?m zV@yRBKs>k*$A;iV8YTHNvAsc494ETupV}cEH;+SbY_W&_<)IsAzDYTbTiV^uWe$#O z61}G|tRmdVcobMj`3C719lW?9P3!33{sVgm?1SUcsU^SA@Y zw~Cu+Oi2duOBkl(SgT~xav?W|c${H2j$@|nW{Pi0hQQv#7je8$y)riPW>5=(V_}nU zuj0_No&5xEz;T{sh?`wIjd=Kxg`6F)Sjv>!9o1nfevBV;=SFcZk`-YjJ5BpEtn&@b z1M!0_bf%B}3V344zr6!y@|653?U#I8{{_{HjnS*j?9{J279@tjOg6J*c?Yh)=>bO6 zv8$a!*SzJ#rQA6_t98A>X_j#YmBMr4_=#8-tb?IEo|P`tA|8?mT*E|(Q{^EZtFY|0 z8Xp$aXcynSr5b^@a_c~=i!S<-=4hbQ@}lwzV2IO+J6J{E?q_}Dj+cgF6^%b6#J%*R z)_|gX?EZx-e#VP>91Q~T4NAlf$%E{HcS^5cfSzWogjYDQOIu=y23)JL1~QYObYAv% zKTzZ~{}y3C%>}OdxC?>!9;_tvq82;!Q?MB@5y!ryH`gpXR6`9cDXGT3qYb2Ww`8wP z4ukodx1>?IVBp#xV1$8Pox1lr$ng3a1CVZw-FkPpUIk$y~w6aJ51XgYKFt+h~CH{l}k`FlmcBEX!jPCmF`xzpu&eFyXPFVQ7}@EnnI%tunVgrMYodg5&(;@;E)^ z*U_b@VT@A~WbQ7*RjoStx~{6pWH%h^Okv^S>$?u_18at{$2AQ&(b!YAoejrY{KdQP ztO4y#4)cJhH*nD~K0!@5GOCJqdMaa48zwKW%S|68rYoI`$X7fN`-hnEvjT%YbskzE zeG@i1VVXkppw|=Vu!V`DUlZjnx?+^JmD8t`d$PMo;<4N5?W16};~OX*bc<1K!LzXx zvUq&9U&}5&TE&FoF?d1w#Z+ol9$7pFtct!Srk>?O@u=4k@gLiIuo`mAjS2&^Ss(uqqmj8>#`H^(XUj0=eaPW<;Ssyn{ za{r4g9*fQ!e~yehkV_ViTMK9Uy9M=FWbt58-g*YdxNm~uQSsi~$-yhdlPn(B2GnLk zY(h^!@rYA-K#zC%)k_wS#FY^I^#muBd+kL^MhkL?o`d{1Z}Zb9)VU1;UV z?)Cje7LW7)d_du~;1%x`mt9}B+p54(Lu0{ftT+*vjr-QOSc3LXiL zMNIdr*xT;#NNhHgb{ykmts(2jx0jVx8D?+0pni;vdCP_&;y4DRGm`QaiN zH4%6!@=bCMyqS*Df&!k|QIm4+hF~o`6%NCdUj*H3G65}ZzQOdrSF!@aeGRb0_u`6= zouqO;?KIY;FeUx#GIdlRz!Om~;B@yd(=aw!#sAn*zHV{hc73Sgo5Q@9Yi4=>gsCr< z^y%)V&JSJW)c10-zNMM_S^`h~q!*i3*lpGS1yeskeRI(LyoMv>)Guq^b;{thLIq5H zxn(V^nKwb!F!cutn;3hNx(3OqpQk!8T(T*f4pX19OXk&Jwp9a6{ho_LHlxYACGphP z>j1ktrw?=B@UL8Sa7iQ3IR{g}vC^6n!7t$qQ~z1e%Kl>}Xw2iOKVB>J=A|8{6{h~! z!EMh)tNnia<1)=&XAOUV`CW4A@5rgWUC{pQElmB;&yB;($ml+p`nKCQ6uZ*baLK8k z8+AMwIoiyHssC#D^TEsU7kpvrt2CD#H^@rs!R5MMNPS!Wi=&ah4G%uu|A4}Xmdf%$ zQt0o!7pHQ{;zu`}@aCBIW{)RYQgFiG`Beq%X^|a=p}#}LGH~el<55!RyIe6|E3bFH zABKK&VW;5>haN_!;MdBl=gw<|zZG$O0;)<21b3|3%eV-f5i(0`J7Jcbn~z)Tvj)1h&HQVY9zg zMLt?c5;zLSg>Svp#dp=OBXBg1E7vd;f~hhB1g^ueZPbK-UfUDmcQdg7$IW7YnkQcR zllZhJHsW}#-}&CM_A|szCj4_g-JPjld%~eh{{T!jL{a zj^P0+pE^#xZyN;^?B$y)MyUo)kGFX#zg=89_lcm?1E;)BW0$);tHZFvUpU>FwL~Xi zqDvdnK+c(IBNbuy+*3x1C$b?7#Y@%un(vi*X3u>X160o4rj05TIg56qyXO&MSa}0C z`Q2OYoxEzT?~5H$prEPqk;?fTt}jN3X?ng+^`zt%)O#kVoI0lHQe0R5BcS!neS^$b zLVS80!sB25mj-=4>xkF_f4Z0ujCk8^8ujfRg2Mn69|4+FHu)c5vqgJsi~gH0I`-8gsNdJCuSv0>s`M$PWT(TZe|PY?Z?eO;LuA1(UGsO@*-H=4K*9eS;?K3N%h^wo zpXuH~|NIAuNl<>cKh$cf%T^|n<;S}=IWd_xTn6RGE9afgiRGa!Wcd-__sCKs!a5ep zkC@tsGA+)omMlN<@|k*B{fESG`8gk`u8l4obm^i?4_*4`GC-Fhx;CK82wfY|#X^@c K2vpxB_4I!Z?P|UN diff --git a/ka-note/server/src/routes/admin.ts b/ka-note/server/src/routes/admin.ts index 12e5f68..d3134fd 100644 --- a/ka-note/server/src/routes/admin.ts +++ b/ka-note/server/src/routes/admin.ts @@ -3,7 +3,9 @@ import { vacuumPurged } from '../lib/sync-service.js'; import { checkIntegrity } from '../lib/backup-service.js'; import { handle } from '../lib/route-utils.js'; import type { AuthEnv } from '../middleware/auth.js'; -import { sqlite } from '../db/connection.js'; +import { db, sqlite } from '../db/connection.js'; +import { contexts, topics } from '../db/schema.js'; +import { isNull, count } from 'drizzle-orm'; const admin = new Hono(); @@ -19,6 +21,18 @@ admin.get('/integrity', handle('admin/integrity', async (c) => { return c.json(result, result.ok ? 200 : 500); })); +admin.get('/stats', handle('admin/stats', async (c) => { + const [{ contextCount }] = await db + .select({ contextCount: count() }) + .from(contexts) + .where(isNull(contexts.deletedAt)); + const [{ topicCount }] = await db + .select({ topicCount: count() }) + .from(topics) + .where(isNull(topics.deletedAt)); + return c.json({ contextCount, topicCount }); +})); + // Graceful shutdown: checkpoint WAL, close DB, exit. // Call from deploy pipeline before restarting the container. admin.post('/shutdown', handle('admin/shutdown', async (c) => {