Compare commits

Author SHA1 Message Date
Vezpi 2785900d4f Merge branch 'preview'
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Successful in 7s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Successful in 2s
Blog Deployment / Deploy-Staging (push) Successful in 24s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Notify (push) Successful in 2s
2026-10-14 18:56:39 +00:00
VezpiandGitea Actions 90451c07c3 feat: use dart sass instead of libsass (#17)
Co-authored-by: Gitea Actions <actions@local>
Reviewed-on: #17
2026-10-14 20:54:36 +02:00
Gitea Actions d844a331be Auto-update blog content from Obsidian: 2026-10-14 12:21:59
Blog Deployment / Build (push) Successful in 32s
Blog Deployment / Deploy-Staging (push) Failing after 15s
Blog Deployment / Test-Staging (push) Skipped
Blog Deployment / Merge (push) Skipped
Blog Deployment / Deploy-Production (push) Skipped
Blog Deployment / Test-Production (push) Skipped
Blog Deployment / Clean (push) Skipped
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Notify (push) Successful in 4s
2026-10-14 12:21:59 +00:00
Gitea Actions 634c579ac5 Auto-update blog content from Obsidian: 2026-10-01 21:17:09
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Successful in 49s
Blog Deployment / Deploy-Staging (push) Successful in 27s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 29s
Blog Deployment / Test-Production (push) Successful in 4s
Blog Deployment / Clean (push) Successful in 3s
Blog Deployment / Notify (push) Successful in 3s
2026-10-01 21:17:09 +00:00
Gitea Actions e416769280 Auto-update blog content from Obsidian: 2026-09-27 21:18:29
Blog Deployment / Deploy-Staging (push) Successful in 16s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Notify (push) Successful in 3s
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Skipped
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 16s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Skipped
2026-09-27 21:18:29 +00:00
Gitea Actions 28bc4a8c13 Auto-update blog content from Obsidian: 2026-09-27 21:13:20
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 17s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 16s
2026-09-27 21:13:20 +00:00
Gitea Actions a222745af2 Auto-update blog content from Obsidian: 2026-09-27 20:57:07
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 18s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 17s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 3s
2026-09-27 20:57:07 +00:00
Gitea Actions f2ca91361c Auto-update blog content from Obsidian: 2026-09-20 19:00:58
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 16s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 17s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 3s
2026-09-20 19:00:58 +00:00
Gitea Actions 0501815481 Auto-update blog content from Obsidian: 2026-09-20 18:54:54
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 17s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 16s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 3s
2026-09-20 18:54:54 +00:00
Gitea Actions a24e95ff7c Auto-update blog content from Obsidian: 2026-09-19 21:43:07
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Deploy-Staging (push) Successful in 16s
Blog Deployment / Build (push) Skipped
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 16s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 3s
2026-09-19 21:43:07 +00:00
Gitea Actions 5f87c0dabf Auto-update blog content from Obsidian: 2026-09-19 21:42:13
Blog Deployment / Deploy-Staging (push) Successful in 16s
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Skipped
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 3s
Blog Deployment / Deploy-Production (push) Successful in 18s
2026-09-19 21:42:13 +00:00
Gitea Actions 511d4a7718 Auto-update blog content from Obsidian: 2026-09-19 20:42:22
Blog Deployment / Check-Rebuild (push) Successful in 12s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 17s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 16s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 3s
2026-09-19 20:42:22 +00:00
Gitea Actions 4784b46dfa Auto-update blog content from Obsidian: 2026-09-18 21:21:03
Blog Deployment / Merge (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 18s
Blog Deployment / Test-Staging (push) Successful in 4s
Blog Deployment / Deploy-Production (push) Successful in 16s
Blog Deployment / Notify (push) Successful in 2s
2026-09-18 21:21:03 +00:00
Gitea Actions 61d870afcd Auto-update blog content from Obsidian: 2026-09-16 11:38:12
Blog Deployment / Deploy-Production (push) Successful in 31s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 31s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
2026-09-16 11:38:12 +00:00
Vezpi 81f032031b feat: leverage healthcheck (#16)
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 2s
Reviewed-on: #16
2026-09-16 11:24:25 +02:00
Vezpi 246ba7bf07 feat: use persistent hugo resources folder (#15)
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Merge (push) Successful in 10s
Blog Deployment / Build (push) Successful in 6s
Blog Deployment / Deploy-Staging (push) Successful in 34s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Deploy-Production (push) Successful in 45s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Successful in 2s
Blog Deployment / Notify (push) Successful in 2s
Reviewed-on: #15
2026-09-16 10:55:40 +02:00
Gitea Actions a4c8a3215f Auto-update blog content from Obsidian: 2026-09-16 06:49:51
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 34s
Blog Deployment / Test-Staging (push) Failing after 3s
Blog Deployment / Merge (push) Skipped
Blog Deployment / Deploy-Production (push) Skipped
Blog Deployment / Test-Production (push) Skipped
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 2s
2026-09-16 06:49:51 +00:00
Gitea Actions 1ac0e63ddf Auto-update blog content from Obsidian: 2026-09-16 06:38:47
Blog Deployment / Deploy-Staging (push) Successful in 34s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Notify (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Skipped
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 37s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Skipped
2026-09-16 06:38:47 +00:00
Gitea Actions c14cb6b00b Auto-update blog content from Obsidian: 2026-09-14 12:13:14
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 34s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 34s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 1s
2026-09-14 12:13:14 +00:00
Gitea Actions cd29c474c8 Auto-update blog content from Obsidian: 2026-09-14 11:13:09
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 33s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 34s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Notify (push) Successful in 1s
2026-09-14 11:13:09 +00:00
Gitea Actions 495b650d25 Auto-update blog content from Obsidian: 2026-09-14 09:51:29
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 35s
Blog Deployment / Test-Staging (push) Successful in 1s
Blog Deployment / Notify (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 33s
Blog Deployment / Test-Production (push) Successful in 1s
Blog Deployment / Clean (push) Skipped
2026-09-14 09:51:29 +00:00
Gitea Actions 5c10bad790 Auto-update blog content from Obsidian: 2026-09-14 09:35:02
Blog Deployment / Check-Rebuild (push) Successful in 4s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 33s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 33s
Blog Deployment / Test-Production (push) Successful in 1s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 1s
2026-09-14 09:35:02 +00:00
Gitea Actions f92688be0f Auto-update blog content from Obsidian: 2026-09-14 09:27:41
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 34s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 5s
Blog Deployment / Deploy-Production (push) Successful in 34s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 2s
2026-09-14 09:27:41 +00:00
Gitea Actions 10d1ea8f9b Auto-update blog content from Obsidian: 2026-09-14 09:14:20
Blog Deployment / Deploy-Staging (push) Successful in 35s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Skipped
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 34s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 2s
2026-09-14 09:14:20 +00:00
Gitea Actions b86691b3bf Auto-update blog content from Obsidian: 2026-09-14 06:57:34
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 34s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 33s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 2s
2026-09-14 06:57:34 +00:00
Gitea Actions 1632d1a3f7 Auto-update blog content from Obsidian: 2026-09-13 21:54:50
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 33s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Skipped
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 45s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Notify (push) Successful in 2s
2026-09-13 21:54:50 +00:00
Vezpi 6ab502e9d9 add: if always for Deploy-Staging step
Blog Deployment / Notify (push) Successful in 3s
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Successful in 34s
Blog Deployment / Test-Staging (push) Failing after 2s
Blog Deployment / Merge (push) Skipped
Blog Deployment / Deploy-Production (push) Skipped
Blog Deployment / Test-Production (push) Skipped
Blog Deployment / Clean (push) Skipped
2026-09-13 21:36:46 +00:00
Vezpi 838d360a90 remove: if condition for Deploy-Staging step
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Skipped
Blog Deployment / Test-Staging (push) Skipped
Blog Deployment / Merge (push) Skipped
Blog Deployment / Deploy-Production (push) Skipped
Blog Deployment / Test-Production (push) Skipped
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 2s
2026-09-13 21:29:45 +00:00
Gitea Actions 648c7b94dc Auto-update blog content from Obsidian: 2026-09-13 21:09:40
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Skipped
Blog Deployment / Deploy-Staging (push) Skipped
Blog Deployment / Test-Staging (push) Skipped
Blog Deployment / Merge (push) Skipped
Blog Deployment / Deploy-Production (push) Skipped
Blog Deployment / Test-Production (push) Skipped
Blog Deployment / Clean (push) Skipped
Blog Deployment / Notify (push) Successful in 2s
2026-09-13 21:09:40 +00:00
Gitea Actions 576371646f Auto-update blog content from Obsidian: 2026-09-13 20:22:22
Blog Deployment / Check-Rebuild (push) Successful in 9s
Blog Deployment / Build (push) Successful in 28s
Blog Deployment / Test-Staging (push) Successful in 33s
Blog Deployment / Deploy-Production (push) Successful in 34s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Successful in 2s
Blog Deployment / Deploy-Staging (push) Successful in 36s
Blog Deployment / Merge (push) Successful in 13s
Blog Deployment / Notify (push) Successful in 2s
2026-09-13 20:22:22 +00:00
Gitea Actions 3e25cf0b48 Auto-update blog content from Obsidian: 2026-08-21 20:36:36
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 34s
Blog Deployment / Test-Staging (push) Successful in 1s
Blog Deployment / Merge (push) Successful in 5s
Blog Deployment / Deploy-Production (push) Successful in 34s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-08-21 20:36:36 +00:00
Gitea Actions 79391d97ef Auto-update blog content from Obsidian: 2026-08-21 20:31:22
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Successful in 25s
Blog Deployment / Deploy-Staging (push) Successful in 35s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 33s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Successful in 2s
Blog Deployment / Notify (push) Successful in 2s
2026-08-21 20:31:22 +00:00
Gitea Actions 091b3b8e29 Auto-update blog content from Obsidian: 2026-08-21 07:25:55
Blog Deployment / Check-Rebuild (push) Failing after 2s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Has been skipped
Blog Deployment / Test-Staging (push) Has been skipped
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-08-21 07:25:55 +00:00
Gitea Actions 64c29c71db Auto-update blog content from Obsidian: 2026-08-01 20:55:28
Blog Deployment / Check-Rebuild (push) Successful in 14s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 46s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 14s
Blog Deployment / Deploy-Production (push) Successful in 49s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-08-01 20:55:28 +00:00
Gitea Actions d7523ba978 Auto-update blog content from Obsidian: 2026-07-19 21:21:41
Blog Deployment / Check-Rebuild (push) Successful in 13s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 49s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 14s
Blog Deployment / Deploy-Production (push) Successful in 47s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-07-19 21:21:41 +00:00
Gitea Actions 400f4abf95 Auto-update blog content from Obsidian: 2026-07-18 22:26:17
Blog Deployment / Check-Rebuild (push) Successful in 11s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 49s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 15s
Blog Deployment / Deploy-Production (push) Successful in 49s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-07-18 22:26:17 +00:00
Gitea Actions 3240e029fa Auto-update blog content from Obsidian: 2026-07-18 21:26:18
Blog Deployment / Check-Rebuild (push) Successful in 13s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 35s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 15s
Blog Deployment / Deploy-Production (push) Successful in 35s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-07-18 21:26:18 +00:00
Gitea Actions 57768b598f Auto-update blog content from Obsidian: 2026-06-09 20:24:47
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 35s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 35s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-06-09 20:24:47 +00:00
Gitea Actions 2fac832345 Auto-update blog content from Obsidian: 2026-06-09 20:17:47
Blog Deployment / Deploy-Staging (push) Successful in 35s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 35s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
2026-06-09 20:17:47 +00:00
Gitea Actions bd6934ee8e Auto-update blog content from Obsidian: 2026-06-09 19:39:07
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Successful in 30s
Blog Deployment / Deploy-Staging (push) Successful in 34s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 34s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Successful in 2s
Blog Deployment / Notify (push) Successful in 3s
2026-06-09 19:39:07 +00:00
Vezpi e41c25b64f feat: secure docker image removal
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 35s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 35s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-05-25 13:47:56 +00:00
Vezpi ce1c7e36bb feat: reduce the sleep to 30s
Blog Test / Check-Rebuild (push) Successful in 6s
Blog Test / Build (push) Successful in 7s
Blog Test / Deploy-Test (push) Successful in 36s
Blog Test / Test (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Successful in 7s
Blog Deployment / Deploy-Staging (push) Successful in 35s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 34s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Failing after 2s
Blog Deployment / Notify (push) Successful in 3s
2026-05-25 13:30:26 +00:00
Vezpi 6a4fdcb6ff fix: corrected new sleep timer to 60 for test pipeline
Blog Test / Check-Rebuild (push) Successful in 6s
Blog Test / Build (push) Successful in 8s
Blog Test / Deploy-Test (push) Successful in 1m3s
Blog Test / Test (push) Successful in 2s
2026-05-25 13:27:35 +00:00
Vezpi 07ce7c58ef feat: remove deprecation workaround
Blog Test / Check-Rebuild (push) Successful in 6s
Blog Test / Build (push) Successful in 36s
Blog Test / Deploy-Test (push) Successful in 8s
Blog Test / Test (push) Failing after 3s
2026-05-25 13:18:04 +00:00
Gitea Actions bd121d794c Auto-update blog content from Obsidian: 2026-05-24 21:07:44
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 1m3s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 1m3s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
Blog Test / Check-Rebuild (push) Successful in 7s
Blog Test / Build (push) Has been skipped
Blog Test / Deploy-Test (push) Failing after 4s
Blog Test / Test (push) Has been skipped
2026-05-24 21:07:44 +00:00
Gitea Actions 19258d081c Auto-update blog content from Obsidian: 2026-05-24 20:58:30
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 1m4s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 1m4s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-05-24 20:58:30 +00:00
Gitea Actions 40ec16e974 Auto-update blog content from Obsidian: 2026-05-24 20:12:49
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 1m3s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 1m3s
Blog Deployment / Test-Staging (push) Successful in 2s
2026-05-24 20:12:49 +00:00
Gitea Actions 8e4e4601d7 Auto-update blog content from Obsidian: 2026-05-24 19:12:42
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 1m4s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 1m5s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-05-24 19:12:42 +00:00
Gitea Actions 8facd6010b Auto-update blog content from Obsidian: 2026-05-24 14:31:31
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Production (push) Successful in 1m4s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Deploy-Staging (push) Successful in 1m4s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 9s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-05-24 14:31:31 +00:00
Gitea Actions 49631bbabc Auto-update blog content from Obsidian: 2026-05-24 13:31:24
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 1m4s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 9s
Blog Deployment / Deploy-Production (push) Successful in 1m5s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-05-24 13:31:24 +00:00
Gitea Actions 0e81ddf7ed Auto-update blog content from Obsidian: 2026-05-24 12:30:51
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 1m5s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 1m4s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-05-24 12:30:51 +00:00
Vezpi a01f4dcf4e fix: typo
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 1m4s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 9s
Blog Deployment / Deploy-Production (push) Successful in 1m4s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-05-02 17:09:10 +00:00
Vezpi 8d88e5c87f change: wait 60s before showing logs
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Successful in 7s
Blog Deployment / Deploy-Staging (push) Successful in 1m3s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 1m3s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Successful in 2s
Blog Deployment / Notify (push) Successful in 2s
2026-04-30 10:52:02 +00:00
Vezpi 7728af1cdb change: wait 15s before showing logs
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Build (push) Successful in 7s
Blog Deployment / Test-Staging (push) Failing after 3s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-04-30 10:43:39 +00:00
Vezpi ab9a714b3e change: wait 10s before showing logs
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Successful in 9s
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Failing after 3s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-04-30 10:39:30 +00:00
Vezpi 2a1debc648 add: i18n pagination 2026-04-30 10:38:27 +00:00
Vezpi ec54ddd1c3 fix: Locale
Blog Deployment / Check-Rebuild (push) Successful in 11s
Blog Deployment / Build (push) Successful in 25s
Blog Deployment / Deploy-Staging (push) Successful in 28s
Blog Deployment / Test-Staging (push) Failing after 6s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 26s
2026-04-29 22:20:45 +00:00
Vezpi 2b14785ea4 test: replace site by hugo
Blog Deployment / Check-Rebuild (push) Successful in 11s
Blog Deployment / Build (push) Successful in 16s
Blog Deployment / Deploy-Staging (push) Successful in 23s
Blog Deployment / Test-Staging (push) Failing after 15s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 20s
2026-04-29 22:15:24 +00:00
Vezpi b5612b24ee add: workaround for rss.xml
Blog Deployment / Check-Rebuild (push) Successful in 11s
Blog Deployment / Test-Staging (push) Failing after 17s
Blog Deployment / Build (push) Successful in 49s
Blog Deployment / Deploy-Staging (push) Successful in 48s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 17s
2026-04-29 22:07:00 +00:00
Vezpi 14f9160c3d add: workaround for LanguageCode
Blog Deployment / Check-Rebuild (push) Successful in 33s
Blog Deployment / Build (push) Successful in 27s
Blog Deployment / Deploy-Staging (push) Successful in 27s
Blog Deployment / Test-Staging (push) Failing after 6s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 8s
2026-04-29 22:02:00 +00:00
Vezpi 88cbc90fca add: workaround for LanguageDirection
Blog Deployment / Check-Rebuild (push) Successful in 11s
Blog Deployment / Build (push) Successful in 22s
Blog Deployment / Deploy-Staging (push) Successful in 33s
Blog Deployment / Test-Staging (push) Failing after 20s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 11s
2026-04-29 21:56:55 +00:00
Vezpi fb06ae4e93 update: stack theme
Blog Deployment / Test-Staging (push) Failing after 5s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 6s
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 11s
2026-04-29 21:27:21 +00:00
Vezpi 6403ffd8ba change: languageName by label
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Deploy-Staging (push) Successful in 8s
Blog Deployment / Test-Staging (push) Failing after 3s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
2026-04-29 20:54:16 +00:00
Vezpi 3fb76c0caf change: languageCode by locale
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Failing after 4s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
2026-04-29 20:52:34 +00:00
Vezpi 18c318f31e delete: img folder in static
Blog Deployment / Test-Staging (push) Failing after 3s
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 8s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-04-29 20:48:39 +00:00
Gitea Actions 4143724c86 Auto-update blog content from Obsidian: 2026-04-29 20:40:29
Blog Deployment / Notify (push) Successful in 3s
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Failing after 3s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
2026-04-29 20:40:29 +00:00
Gitea Actions 5936475f99 Auto-update blog content from Obsidian: 2026-04-29 19:59:52
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Successful in 31s
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Failing after 3s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-04-29 19:59:52 +00:00
Vezpi c731b7eaf5 fix: refactor removal of old docker images
Blog Test / Check-Rebuild (push) Successful in 6s
Blog Test / Build (push) Has been skipped
Blog Test / Deploy-Test (push) Successful in 9s
Blog Test / Test (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 9s
Blog Deployment / Deploy-Production (push) Successful in 8s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-04-10 15:38:28 +02:00
Vezpi 4cc467b0db Merge branch 'main' into preview
Blog Test / Check-Rebuild (push) Successful in 8s
Blog Test / Build (push) Has been skipped
Blog Test / Deploy-Test (push) Successful in 10s
Blog Test / Test (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 16s
Blog Deployment / Build (push) Successful in 39s
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 34s
Blog Deployment / Merge (push) Successful in 9s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Successful in 3s
Blog Deployment / Notify (push) Successful in 2s
2026-04-03 12:45:14 +00:00
Vezpi 1712356889 fix: no-redirect (#12)
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 12s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Failing after 8s
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
Reviewed-on: #12
2026-04-03 14:43:06 +02:00
Vezpi 9a4f91dfbb Merge branch 'preview'
Blog Test / Check-Rebuild (push) Successful in 8s
Blog Test / Build (push) Has been skipped
Blog Test / Deploy-Test (push) Successful in 11s
Blog Test / Test (push) Successful in 3s
2026-04-03 11:35:51 +00:00
Vezpi eea734f2fe feat: create a dedicated dev pipeline (#10)
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 11s
Blog Deployment / Test-Staging (push) Successful in 4s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 12s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
Reviewed-on: #10
2026-04-03 13:33:18 +02:00
Vezpi c23735f94d fix/not-deploying-hugo-v159 (#9)
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Successful in 10s
Blog Deployment / Deploy-Staging (push) Successful in 11s
Blog Deployment / Test-Staging (push) Failing after 3s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Notify (push) Successful in 4s
Reviewed-on: #9
2026-04-03 10:02:12 +02:00
Vezpi 4c2265cde6 fix: revert to git clone instead of checkout
Blog Deployment / Check-Rebuild (push) Successful in 20s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 14s
Blog Deployment / Test-Staging (push) Failing after 4s
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Notify (push) Successful in 5s
2026-03-30 10:49:02 +00:00
Vezpi 73bd106b02 feat: separate workflow for testing
Blog Deployment / Check-Rebuild (push) Successful in 9s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 11s
Blog Deployment / Test-Staging (push) Failing after 5s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 5s
Blog Test / Check-Rebuild (push) Failing after 6s
Blog Test / Build (push) Has been skipped
Blog Test / Deploy-Test (push) Has been skipped
Blog Test / Test (push) Has been skipped
2026-03-30 08:05:52 +00:00
Gitea Actions e1012e2480 Auto-update blog content from Obsidian: 2026-03-13 18:19:09
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-03-13 18:19:09 +00:00
Gitea Actions 2edae8b301 Auto-update blog content from Obsidian: 2026-03-12 21:06:30
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-03-12 21:06:30 +00:00
Gitea Actions 44ba9fc8b4 Auto-update blog content from Obsidian: 2026-02-27 22:10:02
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-27 22:10:02 +00:00
Gitea Actions ef4a5aefd1 Auto-update blog content from Obsidian: 2026-02-27 22:01:25
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-27 22:01:25 +00:00
Gitea Actions 6fa0fed3c5 Auto-update blog content from Obsidian: 2026-02-27 21:30:47
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-27 21:30:47 +00:00
Gitea Actions 5fb57c5dc0 Auto-update blog content from Obsidian: 2026-02-27 20:30:37
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 1s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-27 20:30:37 +00:00
Gitea Actions 7d7ab3493c Auto-update blog content from Obsidian: 2026-02-27 19:30:30
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 1s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-27 19:30:30 +00:00
Gitea Actions 44b8f36979 Auto-update blog content from Obsidian: 2026-02-27 17:43:07
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 1s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-27 17:43:07 +00:00
Gitea Actions b388107289 Auto-update blog content from Obsidian: 2026-02-27 15:43:01
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-27 15:43:01 +00:00
Gitea Actions 68cbef5791 Auto-update blog content from Obsidian: 2026-02-27 13:02:25
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
2026-02-27 13:02:25 +00:00
Gitea Actions 48ff17c292 Auto-update blog content from Obsidian: 2026-02-26 22:52:19
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-26 22:52:19 +00:00
Gitea Actions d495546120 Auto-update blog content from Obsidian: 2026-02-26 22:06:14
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-26 22:06:14 +00:00
Gitea Actions 4d2f015de0 Auto-update blog content from Obsidian: 2026-02-25 19:40:02
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Successful in 36s
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Successful in 2s
Blog Deployment / Notify (push) Successful in 2s
2026-02-25 19:40:02 +00:00
Gitea Actions 2fdbd11020 Auto-update blog content from Obsidian: 2026-02-24 18:56:22
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-24 18:56:22 +00:00
Gitea Actions 4f152bf733 Auto-update blog content from Obsidian: 2026-02-24 14:37:54
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-24 14:37:54 +00:00
Gitea Actions 08708e2c6c Auto-update blog content from Obsidian: 2026-02-24 13:37:37
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-24 13:37:37 +00:00
Gitea Actions 0b24515e7f Auto-update blog content from Obsidian: 2026-02-24 12:37:21
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-24 12:37:21 +00:00
Gitea Actions fc6c382c52 Auto-update blog content from Obsidian: 2026-02-24 10:34:56
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 8s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
2026-02-24 10:34:56 +00:00
Gitea Actions 3048c96362 Auto-update blog content from Obsidian: 2026-02-24 09:34:43
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-24 09:34:43 +00:00
Gitea Actions 0ff5d0de9e Auto-update blog content from Obsidian: 2026-02-23 14:15:30
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-23 14:15:30 +00:00
Gitea Actions 3fce8381c5 Auto-update blog content from Obsidian: 2026-02-23 10:31:22
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-23 10:31:22 +00:00
Gitea Actions 53c3af0e11 Auto-update blog content from Obsidian: 2026-02-23 09:31:08
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 8s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-23 09:31:08 +00:00
Gitea Actions 437f03f496 Auto-update blog content from Obsidian: 2026-02-23 08:30:52
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-23 08:30:52 +00:00
Gitea Actions ae86e4299e Auto-update blog content from Obsidian: 2026-02-20 13:13:10
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-02-20 13:13:10 +00:00
Gitea Actions 4eaf78ada7 Auto-update blog content from Obsidian: 2026-02-16 13:41:48
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-16 13:41:48 +00:00
Gitea Actions 940bc95436 Auto-update blog content from Obsidian: 2026-02-14 16:23:07
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-02-14 16:23:07 +00:00
Gitea Actions 1266824a75 Auto-update blog content from Obsidian: 2026-02-09 09:49:05
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 11s
Blog Deployment / Notify (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 8s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
2026-02-09 09:49:05 +00:00
Gitea Actions 52cb5cf946 Auto-update blog content from Obsidian: 2026-02-09 09:36:32
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 1s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-02-09 09:36:32 +00:00
Gitea Actions 3bf8b55f9b Auto-update blog content from Obsidian: 2026-02-09 08:48:46
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Successful in 35s
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Clean (push) Successful in 1s
Blog Deployment / Notify (push) Successful in 2s
2026-02-09 08:48:46 +00:00
Gitea Actions 2ec0509d55 Auto-update blog content from Obsidian: 2026-02-07 21:28:16
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 1s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-07 21:28:16 +00:00
Gitea Actions e9aef5e181 Auto-update blog content from Obsidian: 2026-02-07 14:23:43
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 5s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-07 14:23:43 +00:00
Gitea Actions 5919b95364 Auto-update blog content from Obsidian: 2026-02-07 13:53:31
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-07 13:53:31 +00:00
Gitea Actions 5829b3a198 Auto-update blog content from Obsidian: 2026-02-06 22:28:54
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-06 22:28:54 +00:00
Gitea Actions e088e8ab18 Auto-update blog content from Obsidian: 2026-02-06 21:55:10
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-06 21:55:10 +00:00
Gitea Actions ab7996e685 Auto-update blog content from Obsidian: 2026-02-06 14:57:50
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-06 14:57:50 +00:00
Gitea Actions 97b79cc592 Auto-update blog content from Obsidian: 2026-02-06 08:03:38
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
2026-02-06 08:03:38 +00:00
Gitea Actions dfb50b5e9e Auto-update blog content from Obsidian: 2026-02-06 07:55:26
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 1s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-06 07:55:26 +00:00
Gitea Actions ea07c41c93 Auto-update blog content from Obsidian: 2026-02-05 14:24:33
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-05 14:24:33 +00:00
Gitea Actions d55e0e1724 Auto-update blog content from Obsidian: 2026-02-04 15:22:28
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 8s
Blog Deployment / Test-Production (push) Successful in 1s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-04 15:22:28 +00:00
Gitea Actions f609fda5a2 Auto-update blog content from Obsidian: 2026-02-04 10:21:41
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 5s
Blog Deployment / Deploy-Production (push) Successful in 8s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-04 10:21:41 +00:00
Gitea Actions 697af312f0 Auto-update blog content from Obsidian: 2026-02-04 09:21:24
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-04 09:21:24 +00:00
Gitea Actions 9c98652535 Auto-update blog content from Obsidian: 2026-02-04 08:21:10
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-04 08:21:10 +00:00
Gitea Actions dc6bf7b47d Auto-update blog content from Obsidian: 2026-02-03 15:00:18
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Deploy-Production (push) Successful in 8s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-03 15:00:18 +00:00
Gitea Actions 6f0b6363e0 Auto-update blog content from Obsidian: 2026-02-02 20:29:09
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
2026-02-02 20:29:09 +00:00
Gitea Actions 80c05adf83 Auto-update blog content from Obsidian: 2026-02-02 19:55:52
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 1s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-02 19:55:52 +00:00
Gitea Actions 5213faad68 Auto-update blog content from Obsidian: 2026-02-02 17:55:37
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 8s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-02-02 17:55:37 +00:00
Gitea Actions 54569bc98b Auto-update blog content from Obsidian: 2026-02-02 15:39:09
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 5s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-02-02 15:39:09 +00:00
Gitea Actions e2b1e02c0a Auto-update blog content from Obsidian: 2026-02-02 14:39:10
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 11s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 12s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-02-02 14:39:10 +00:00
Gitea Actions e20fb0b37e Auto-update blog content from Obsidian: 2026-02-02 13:38:57
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Successful in 30s
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 8s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Successful in 2s
Blog Deployment / Notify (push) Successful in 2s
2026-02-02 13:38:57 +00:00
Gitea Actions 1ea2845767 Auto-update blog content from Obsidian: 2026-01-31 22:07:40
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-01-31 22:07:40 +00:00
Gitea Actions 97c2ef3a85 Auto-update blog content from Obsidian: 2026-01-31 10:46:44
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-01-31 10:46:44 +00:00
Gitea Actions c206deeafb Auto-update blog content from Obsidian: 2026-01-30 21:54:43
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 5s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-01-30 21:54:43 +00:00
Gitea Actions c5c6557691 Auto-update blog content from Obsidian: 2026-01-30 21:32:42
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-01-30 21:32:42 +00:00
Gitea Actions 54d09bf788 Auto-update blog content from Obsidian: 2026-01-30 20:32:32
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Successful in 29s
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Successful in 2s
Blog Deployment / Notify (push) Successful in 2s
2026-01-30 20:32:32 +00:00
Gitea Actions a741aebd3f Auto-update blog content from Obsidian: 2026-01-28 20:32:42
Blog Deployment / Check-Rebuild (push) Successful in 22s
Blog Deployment / Build (push) Successful in 54s
Blog Deployment / Deploy-Staging (push) Successful in 13s
Blog Deployment / Test-Staging (push) Successful in 4s
Blog Deployment / Merge (push) Successful in 11s
Blog Deployment / Deploy-Production (push) Successful in 15s
Blog Deployment / Test-Production (push) Successful in 8s
Blog Deployment / Clean (push) Successful in 4s
Blog Deployment / Notify (push) Successful in 7s
2026-01-28 20:32:42 +00:00
Gitea Actions fd48512c1b Auto-update blog content from Obsidian: 2025-12-10 21:35:16
Blog Deployment / Check-Rebuild (push) Successful in 13s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 22s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Successful in 3s
Blog Deployment / Notify (push) Successful in 4s
Blog Deployment / Build (push) Successful in 32s
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
2025-12-10 21:35:16 +00:00
Gitea Actions d7ab8cdab3 Auto-update blog content from Obsidian: 2025-11-24 10:15:07
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2025-11-24 10:15:07 +00:00
Gitea Actions bc44f93c3f Auto-update blog content from Obsidian: 2025-11-20 20:53:52
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-20 20:53:52 +00:00
Gitea Actions 61309cfb4e Auto-update blog content from Obsidian: 2025-11-20 20:52:11
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 11s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-20 20:52:11 +00:00
Gitea Actions 50cc6c195a Auto-update blog content from Obsidian: 2025-11-20 20:50:20
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2025-11-20 20:50:20 +00:00
Gitea Actions c27bd9f906 Auto-update blog content from Obsidian: 2025-11-20 20:44:04
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
2025-11-20 20:44:04 +00:00
Gitea Actions e495593cc1 Auto-update blog content from Obsidian: 2025-11-20 20:02:03
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 11s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 3s
2025-11-20 20:02:03 +00:00
Gitea Actions dc9c6d7164 Auto-update blog content from Obsidian: 2025-11-20 19:53:59
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
Blog Deployment / Deploy-Staging (push) Successful in 14s
Blog Deployment / Test-Staging (push) Successful in 2s
2025-11-20 19:53:59 +00:00
Gitea Actions ca68e911eb Auto-update blog content from Obsidian: 2025-11-20 19:39:23
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2025-11-20 19:39:23 +00:00
Gitea Actions 721e911258 Auto-update blog content from Obsidian: 2025-11-20 19:35:27
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 11s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2025-11-20 19:35:27 +00:00
Gitea Actions 09ed5ade30 Auto-update blog content from Obsidian: 2025-11-19 21:46:00
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2025-11-19 21:46:00 +00:00
Gitea Actions ddb46f8aa3 Auto-update blog content from Obsidian: 2025-11-17 22:38:59
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2025-11-17 22:38:59 +00:00
Gitea Actions 65af7bcee5 Auto-update blog content from Obsidian: 2025-11-17 22:37:32
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-17 22:37:32 +00:00
Gitea Actions 271fe23e23 Auto-update blog content from Obsidian: 2025-11-17 21:58:34
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 11s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-17 21:58:34 +00:00
Gitea Actions 5a2a530d32 Auto-update blog content from Obsidian: 2025-11-16 20:56:31
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2025-11-16 20:56:31 +00:00
Gitea Actions a88e3158c5 Auto-update blog content from Obsidian: 2025-11-16 20:53:15
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
2025-11-16 20:53:15 +00:00
Gitea Actions 4cd7c76c0a Auto-update blog content from Obsidian: 2025-11-16 18:45:45
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-16 18:45:45 +00:00
Gitea Actions 67d23c90ac Auto-update blog content from Obsidian: 2025-11-15 22:19:47
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 11s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2025-11-15 22:19:47 +00:00
Gitea Actions 36f1374128 Auto-update blog content from Obsidian: 2025-11-14 19:51:04
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2025-11-14 19:51:04 +00:00
Gitea Actions b801726508 Auto-update blog content from Obsidian: 2025-11-12 20:19:17
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-12 20:19:17 +00:00
Gitea Actions c87b9f4bc9 Auto-update blog content from Obsidian: 2025-11-11 22:50:23
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
2025-11-11 22:50:23 +00:00
Gitea Actions 08a8b65a1d Auto-update blog content from Obsidian: 2025-11-11 20:49:53
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Deploy-Staging (push) Successful in 11s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-11 20:49:53 +00:00
Gitea Actions ce1249a924 Auto-update blog content from Obsidian: 2025-11-11 19:49:48
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-11 19:49:48 +00:00
Gitea Actions 545720c4c0 Auto-update blog content from Obsidian: 2025-11-07 21:38:49
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 4s
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
2025-11-07 21:38:49 +00:00
Gitea Actions 2119cbf695 Auto-update blog content from Obsidian: 2025-11-07 19:38:18
Blog Deployment / Check-Rebuild (push) Successful in 12s
Blog Deployment / Build (push) Successful in 39s
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Successful in 3s
Blog Deployment / Notify (push) Successful in 3s
2025-11-07 19:38:18 +00:00
Gitea Actions ce2288dabe Auto-update blog content from Obsidian: 2025-11-07 14:37:12
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2025-11-07 14:37:12 +00:00
Gitea Actions a1fa2c0d53 Auto-update blog content from Obsidian: 2025-11-07 13:37:01
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Failing after 3s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-07 13:37:01 +00:00
Gitea Actions bacb22987a Auto-update blog content from Obsidian: 2025-11-07 10:36:35
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Failing after 3s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2025-11-07 10:36:35 +00:00
Gitea Actions 4a20a913e0 Auto-update blog content from Obsidian: 2025-11-07 08:36:22
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 8s
Blog Deployment / Test-Staging (push) Failing after 2s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Notify (push) Successful in 5s
2025-11-07 08:36:22 +00:00
Gitea Actions c5c6d9b91d Auto-update blog content from Obsidian: 2025-11-04 21:43:49
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
2025-11-04 21:43:49 +00:00
Gitea Actions 236e9fa668 Auto-update blog content from Obsidian: 2025-11-04 21:37:02
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 3s
2025-11-04 21:37:02 +00:00
Gitea Actions 35b8a8596f Auto-update blog content from Obsidian: 2025-11-04 21:30:44
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-04 21:30:44 +00:00
Gitea Actions d73aaad0b4 Auto-update blog content from Obsidian: 2025-11-04 21:26:16
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 11s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 11s
Blog Deployment / Test-Staging (push) Successful in 2s
2025-11-04 21:26:16 +00:00
Gitea Actions c43d2af086 Auto-update blog content from Obsidian: 2025-11-04 21:22:05
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 10s
2025-11-04 21:22:05 +00:00
Gitea Actions 011dbc7293 Auto-update blog content from Obsidian: 2025-11-04 21:16:26
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-04 21:16:26 +00:00
Gitea Actions 37025b683a Auto-update blog content from Obsidian: 2025-11-04 20:52:43
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-04 20:52:43 +00:00
Gitea Actions ef84e229b2 Auto-update blog content from Obsidian: 2025-11-04 19:52:37
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Notify (push) Successful in 2s
2025-11-04 19:52:37 +00:00
Gitea Actions 0598cf2a5f Auto-update blog content from Obsidian: 2025-11-04 18:52:30
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-04 18:52:30 +00:00
Gitea Actions e851ee9bd9 Auto-update blog content from Obsidian: 2025-11-04 14:55:05
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2025-11-04 14:55:05 +00:00
Gitea Actions 3389de98d8 Auto-update blog content from Obsidian: 2025-11-04 14:46:39
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 11s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-04 14:46:39 +00:00
Gitea Actions aa9077a47b Auto-update blog content from Obsidian: 2025-11-04 13:54:03
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-04 13:54:03 +00:00
Gitea Actions d888220239 Auto-update blog content from Obsidian: 2025-11-03 22:42:44
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 11s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 11s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-03 22:42:44 +00:00
Gitea Actions 44ddcb6589 Auto-update blog content from Obsidian: 2025-11-03 21:29:17
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-03 21:29:17 +00:00
Gitea Actions d3ad691387 Auto-update blog content from Obsidian: 2025-11-03 19:20:46
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2025-11-03 19:20:46 +00:00
Gitea Actions 57db4726d7 Auto-update blog content from Obsidian: 2025-11-03 11:17:20
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 11s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 11s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2025-11-03 11:17:20 +00:00
Gitea Actions 58856f4668 Auto-update blog content from Obsidian: 2025-11-02 08:44:06
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 11s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-02 08:44:06 +00:00
Gitea Actions 62833b288a Auto-update blog content from Obsidian: 2025-11-01 18:37:00
Blog Deployment / Merge (push) Successful in 9s
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Deploy-Production (push) Successful in 12s
Blog Deployment / Test-Production (push) Successful in 4s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2025-11-01 18:37:00 +00:00
Gitea Actions 739763bc9c Auto-update blog content from Obsidian: 2025-11-01 16:43:01
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 9s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-11-01 16:43:01 +00:00
Gitea Actions 8482223f48 Auto-update blog content from Obsidian: 2025-10-31 19:55:42
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-10-31 19:55:42 +00:00
Gitea Actions 302c6d1a46 Auto-update blog content from Obsidian: 2025-10-31 16:59:25
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 11s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-10-31 16:59:25 +00:00
Gitea Actions fbafb580a0 Auto-update blog content from Obsidian: 2025-10-29 20:21:07
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 13s
Blog Deployment / Test-Staging (push) Successful in 4s
Blog Deployment / Merge (push) Successful in 10s
Blog Deployment / Deploy-Production (push) Successful in 16s
Blog Deployment / Test-Production (push) Successful in 5s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 4s
2025-10-29 20:21:07 +00:00
Gitea Actions 8ed82a75ab Auto-update blog content from Obsidian: 2025-10-28 10:38:32
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 10s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 10s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2025-10-28 10:38:32 +00:00
Gitea Actions b6b8083adb Auto-update blog content from Obsidian: 2025-10-27 20:28:54
Blog Deployment / Check-Rebuild (push) Successful in 10s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 13s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 12s
Blog Deployment / Test-Production (push) Successful in 4s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 4s
2025-10-27 20:28:54 +00:00
265 changed files with 7008 additions and 445 deletions
+101 -20
View File
@@ -22,10 +22,9 @@ jobs:
newer_version_available: ${{ steps.compare.outputs.version }} newer_version_available: ${{ steps.compare.outputs.version }}
current_docker_image: ${{ steps.current_docker.outputs.image }} current_docker_image: ${{ steps.current_docker.outputs.image }}
docker_folder_changed: ${{ steps.docker_folder.outputs.changed }} docker_folder_changed: ${{ steps.docker_folder.outputs.changed }}
dev_lock_present: ${{ steps.check_dev_lock.outputs.locked }}
steps: steps:
- name: Checkout Repository - name: Checkout Repository
run: git clone --branch preview https://${{ secrets.REPO_TOKEN }}@git.vezpi.com/Vezpi/blog.git . run: git clone --branch ${{ gitea.ref_name }} https://${{ secrets.REPO_TOKEN }}@git.vezpi.com/Vezpi/blog.git .
- name: Check Latest Hugo Version - name: Check Latest Hugo Version
id: get_latest id: get_latest
@@ -71,15 +70,6 @@ jobs:
fi fi
echo "changed=$docker_folder_changed" | tee -a $GITEA_OUTPUT echo "changed=$docker_folder_changed" | tee -a $GITEA_OUTPUT
- name: Check for .dev-lock file
id: check_dev_lock
run: |
if [ -f .dev-lock ]; then
echo "locked=true" | tee -a $GITEA_OUTPUT
else
echo "locked=false" | tee -a $GITEA_OUTPUT
fi
Build: Build:
needs: Check-Rebuild needs: Check-Rebuild
if: needs.Check-Rebuild.outputs.newer_version_available == 'true' || needs.Check-Rebuild.outputs.docker_folder_changed == 'true' if: needs.Check-Rebuild.outputs.newer_version_available == 'true' || needs.Check-Rebuild.outputs.docker_folder_changed == 'true'
@@ -89,7 +79,7 @@ jobs:
shell: sh shell: sh
steps: steps:
- name: Checkout Repository - name: Checkout Repository
run: git clone --branch preview https://${{ secrets.REPO_TOKEN }}@git.vezpi.com/Vezpi/blog.git . run: git clone --branch ${{ gitea.ref_name }} https://${{ secrets.REPO_TOKEN }}@git.vezpi.com/Vezpi/blog.git .
- name: Build Docker Image - name: Build Docker Image
run: | run: |
@@ -102,7 +92,7 @@ jobs:
Deploy-Staging: Deploy-Staging:
needs: [Check-Rebuild, Build] needs: [Check-Rebuild, Build]
if: always() && needs.Check-Rebuild.result == 'success' && (needs.Build.result == 'skipped' || needs.Build.result == 'success') if: always()
runs-on: docker runs-on: docker
container: container:
volumes: volumes:
@@ -116,9 +106,53 @@ jobs:
- name: Launch Blog Deployment - name: Launch Blog Deployment
run: | run: |
cd /blog cd /blog
echo "- Stopping existing ${CONTAINER_NAME}..."
docker compose down ${CONTAINER_NAME} docker compose down ${CONTAINER_NAME}
echo "- Starting ${CONTAINER_NAME}..."
docker compose up -d ${CONTAINER_NAME} docker compose up -d ${CONTAINER_NAME}
sleep 5
echo "- Waiting for ${CONTAINER_NAME} to become healthy..."
timeout=300
elapsed=0
while true; do
status=$(docker inspect \
--format='{{.State.Health.Status}}' \
${CONTAINER_NAME})
echo "Health status: ${status}"
case "$status" in
healthy)
echo "✅ ${CONTAINER_NAME} is healthy"
break
;;
unhealthy)
echo "❌ ${CONTAINER_NAME} is unhealthy"
echo "- Displaying container logs"
docker compose logs ${CONTAINER_NAME}
exit 1
;;
*)
;;
esac
if [ "$elapsed" -ge "$timeout" ]; then
echo "❌ Timed out waiting for ${CONTAINER_NAME}"
echo "- Displaying container logs"
docker compose logs ${CONTAINER_NAME}
exit 1
fi
sleep 2
elapsed=$((elapsed + 2))
done
echo "- Displaying container logs" echo "- Displaying container logs"
docker compose logs ${CONTAINER_NAME} docker compose logs ${CONTAINER_NAME}
@@ -126,7 +160,7 @@ jobs:
needs: Deploy-Staging needs: Deploy-Staging
runs-on: ubuntu runs-on: ubuntu
env: env:
URL: "https://blog-dev.vezpi.com/en/" URL: "https://blog-staging.vezpi.com/en/"
steps: steps:
- name: Check HTTP Response - name: Check HTTP Response
run: | run: |
@@ -142,7 +176,7 @@ jobs:
Merge: Merge:
needs: [Check-Rebuild, Test-Staging] needs: [Check-Rebuild, Test-Staging]
if: needs.Test-Staging.result == 'success' && needs.Check-Rebuild.outputs.dev_lock_present == 'false' if: needs.Test-Staging.result == 'success'
runs-on: ubuntu runs-on: ubuntu
steps: steps:
- name: Checkout Repository - name: Checkout Repository
@@ -153,7 +187,7 @@ jobs:
- name: Merge preview Branch on main - name: Merge preview Branch on main
run: | run: |
git merge --ff-only origin/preview git merge --ff-only origin/${{ gitea.ref_name }}
git push origin main git push origin main
Deploy-Production: Deploy-Production:
@@ -171,9 +205,53 @@ jobs:
- name: Launch Blog Deployment - name: Launch Blog Deployment
run: | run: |
cd /blog cd /blog
echo "- Stopping existing ${CONTAINER_NAME}..."
docker compose down ${CONTAINER_NAME} docker compose down ${CONTAINER_NAME}
echo "- Starting ${CONTAINER_NAME}..."
docker compose up -d ${CONTAINER_NAME} docker compose up -d ${CONTAINER_NAME}
sleep 5
echo "- Waiting for ${CONTAINER_NAME} to become healthy..."
timeout=300
elapsed=0
while true; do
status=$(docker inspect \
--format='{{.State.Health.Status}}' \
${CONTAINER_NAME})
echo "Health status: ${status}"
case "$status" in
healthy)
echo "✅ ${CONTAINER_NAME} is healthy"
break
;;
unhealthy)
echo "❌ ${CONTAINER_NAME} is unhealthy"
echo "- Displaying container logs"
docker compose logs ${CONTAINER_NAME}
exit 1
;;
*)
;;
esac
if [ "$elapsed" -ge "$timeout" ]; then
echo "❌ Timed out waiting for ${CONTAINER_NAME}"
echo "- Displaying container logs"
docker compose logs ${CONTAINER_NAME}
exit 1
fi
sleep 2
elapsed=$((elapsed + 2))
done
echo "- Displaying container logs" echo "- Displaying container logs"
docker compose logs ${CONTAINER_NAME} docker compose logs ${CONTAINER_NAME}
@@ -204,12 +282,15 @@ jobs:
steps: steps:
- name: Remove Old Docker Image - name: Remove Old Docker Image
run: | run: |
docker image rm ${{ needs.Check-Rebuild.outputs.current_docker_image }} --force IMAGE_IDS=$(docker image ls "${DOCKER_IMAGE}" 2>/dev/null | awk '$NF != "U" && NR>1 {print $2}')
if [ -n "$IMAGE_IDS" ]; then
docker image rm $IMAGE_IDS
fi
Notify: Notify:
needs: [Check-Rebuild, Build, Deploy-Staging, Test-Staging, Merge, Deploy-Production, Test-Production, Clean] needs: [Check-Rebuild, Build, Deploy-Staging, Test-Staging, Merge, Deploy-Production, Test-Production, Clean]
runs-on: ubuntu runs-on: ubuntu
if: always() && needs.Check-Rebuild.outputs.dev_lock_present == 'false' if: always()
env: env:
NTFY_URL: https://ntfy.vezpi.com NTFY_URL: https://ntfy.vezpi.com
NTFY_TOPIC: blog NTFY_TOPIC: blog
+179
View File
@@ -0,0 +1,179 @@
name: Blog Test
on:
push:
branches:
- 'fix/**'
- 'feat/**'
env:
DOCKER_IMAGE: vezpi-blog
jobs:
Check-Rebuild:
runs-on: docker
defaults:
run:
shell: sh
outputs:
latest_hugo_version: ${{ steps.get_latest.outputs.version }}
current_hugo_version: ${{ steps.get_current.outputs.version }}
newer_version_available: ${{ steps.compare.outputs.version }}
current_docker_image: ${{ steps.current_docker.outputs.image }}
docker_folder_changed: ${{ steps.docker_folder.outputs.changed }}
steps:
- name: Checkout Repository
run: git clone --branch ${{ gitea.ref_name }} https://${{ secrets.REPO_TOKEN }}@git.vezpi.com/Vezpi/blog.git .
- name: Check Latest Hugo Version
id: get_latest
run: |
apk add curl
latest_version=$(curl -s https://api.github.com/repos/gohugoio/hugo/releases/latest | grep tag_name | sed -E 's/.*"v([^"]+)".*/\1/')
echo "version=$latest_version" | tee -a $GITEA_OUTPUT
- name: Check Current Hugo Version
id: get_current
run: |
current_version=$(docker image ls ${DOCKER_IMAGE} --format '{{.Tag}}' | head -n1)
echo "version=$current_version" | tee -a $GITEA_OUTPUT
- name: Compare Current and Latest Hugo Versions
id: compare
run: |
if [ "${{ steps.get_latest.outputs.version }}" != "${{ steps.get_current.outputs.version }}" ]; then
new_version_available=true
echo "New version available: ${{ steps.get_latest.outputs.version }}"
else
new_version_available=false
echo "Current version is the latest: ${{ steps.get_latest.outputs.version }}"
fi
echo "version=$new_version_available" | tee -a $GITEA_OUTPUT
- name: Get Current Docker Image ID
id: current_docker
run: |
current_image=$(docker image ls ${DOCKER_IMAGE}:latest --format '{{.ID}}' | head -n1)
echo "image=$current_image" | tee -a $GITEA_OUTPUT
- name: Check Changes in the Docker Folder
id: docker_folder
run: |
if git diff --name-only origin/main | grep -q '^docker/';
then
docker_folder_changed=true
echo "Change detected in the /docker folder"
else
docker_folder_changed=false
echo "No change in the /docker folder"
fi
echo "changed=$docker_folder_changed" | tee -a $GITEA_OUTPUT
Build:
needs: Check-Rebuild
if: needs.Check-Rebuild.outputs.newer_version_available == 'true' || needs.Check-Rebuild.outputs.docker_folder_changed == 'true'
runs-on: docker
defaults:
run:
shell: sh
steps:
- name: Checkout Repository
run: git clone --branch ${{ gitea.ref_name }} https://${{ secrets.REPO_TOKEN }}@git.vezpi.com/Vezpi/blog.git .
- name: Build Docker Image
run: |
cd docker
docker build \
--build-arg HUGO_VERSION=${{ needs.Check-Rebuild.outputs.latest_hugo_version }} \
--tag ${DOCKER_IMAGE}:${{ needs.Check-Rebuild.outputs.latest_hugo_version }} \
.
docker tag ${DOCKER_IMAGE}:${{ needs.Check-Rebuild.outputs.latest_hugo_version }} ${DOCKER_IMAGE}:dev
Deploy-Test:
needs:
- Check-Rebuild
- Build
if: always()
runs-on: docker
container:
volumes:
- /appli/docker/blog:/blog
defaults:
run:
shell: sh
env:
CONTAINER_NAME: blog_dev
steps:
- name: Launch Blog Test Deployment
run: |
cd /blog
echo "- Stopping existing ${CONTAINER_NAME}..."
docker compose down ${CONTAINER_NAME}
echo "- Starting ${CONTAINER_NAME}..."
BLOG_TEST_BRANCH=${{ gitea.ref_name }} \
docker compose up -d ${CONTAINER_NAME}
echo "- Waiting for ${CONTAINER_NAME} to become healthy..."
timeout=300
elapsed=0
while true; do
status=$(docker inspect \
--format='{{.State.Health.Status}}' \
${CONTAINER_NAME})
echo "Health status: ${status}"
case "$status" in
healthy)
echo "✅ ${CONTAINER_NAME} is healthy"
break
;;
unhealthy)
echo "❌ ${CONTAINER_NAME} is unhealthy"
echo "- Displaying container logs"
docker compose logs ${CONTAINER_NAME}
exit 1
;;
*)
;;
esac
if [ "$elapsed" -ge "$timeout" ]; then
echo "❌ Timed out waiting for ${CONTAINER_NAME}"
echo "- Displaying container logs"
docker compose logs ${CONTAINER_NAME}
exit 1
fi
sleep 2
elapsed=$((elapsed + 2))
done
echo "- Displaying container logs"
docker compose logs ${CONTAINER_NAME}
Test:
needs: Deploy-Test
runs-on: ubuntu
env:
URL: "https://blog-dev.vezpi.com/en/"
steps:
- name: Check HTTP Response
run: |
code=$(curl -s -o /dev/null -w "%{http_code}" "$URL")
echo "HTTP response code: $code"
if [ "$code" -ne 200 ]; then
echo "❌ Service is not healthy (HTTP $code)"
exit 1
else
echo "✅ Service is healthy"
fi
+7 -1
View File
@@ -3,9 +3,15 @@
} }
.lang-toggle-icon { .lang-toggle-icon {
margin-left: auto; margin-left: 0;
svg { svg {
width: 64px; width: 64px;
height: 24px; height: 24px;
} }
} }
.container {
.left-sidebar {
width: unset;
}
}

Before

Width:  |  Height:  |  Size: 386 KiB

After

Width:  |  Height:  |  Size: 386 KiB

Before

Width:  |  Height:  |  Size: 295 KiB

After

Width:  |  Height:  |  Size: 295 KiB

Before

Width:  |  Height:  |  Size: 568 KiB

After

Width:  |  Height:  |  Size: 568 KiB

+3 -3
View File
@@ -122,9 +122,9 @@ La meilleure solution que j'ai trouvée a été de percer deux trous de 40 mm a
Voici à quoi ça ressemble : Voici à quoi ça ressemble :
![Front view of my homelab with legend](img/homelab-rack-legend.png) ![Vue de face de mon homelab avec légende](images/homelab-rack-legend.png)
![Different views of my homelab with open and closed enclosure](img/homelab-enclore-open-closed.png) ![Différentes vues de mon homelab avec armoire ouverte et fermée](images/homelab-enclore-open-closed.png)
--- ---
## Stack Logicielle ## Stack Logicielle
@@ -183,7 +183,7 @@ Cette configuration de proxy à deux couches centralise la gestion des certifica
Pour un accès distant sécurisé, j'ai configuré **WireGuard** sur OPNsense. Ce VPN léger fournit une connectivité chiffrée à mon lab où que je sois, permettant ainsi de gérer tous mes services sans les exposer directement à Internet. Pour un accès distant sécurisé, j'ai configuré **WireGuard** sur OPNsense. Ce VPN léger fournit une connectivité chiffrée à mon lab où que je sois, permettant ainsi de gérer tous mes services sans les exposer directement à Internet.
#### Schéma Réseau #### Schéma Réseau
![Diagram of my home network ](img/homelab-network-schema.png) ![Schéma de mon réseau domestique](images/homelab-network-schema.png)
### Application ### Application
+3 -3
View File
@@ -121,9 +121,9 @@ Inside the rack, I also added two 80mm fans to help with airflow. To keep everyt
Here what is look like: Here what is look like:
![Front view of my homelab with legend](img/homelab-rack-legend.png) ![Front view of my homelab with legend](images/homelab-rack-legend.png)
![Different views of my homelab with open and closed enclosure](img/homelab-enclore-open-closed.png) ![Different views of my homelab with open and closed enclosure](images/homelab-enclore-open-closed.png)
--- ---
@@ -184,7 +184,7 @@ This two-layer proxy setup centralizes SSL certificate management in **Caddy** w
For secure remote access, I configured **WireGuard** on OPNsense. This lightweight VPN provides encrypted connectivity to my lab from anywhere, allowing management of all my services without exposing them all directly to the internet. For secure remote access, I configured **WireGuard** on OPNsense. This lightweight VPN provides encrypted connectivity to my lab from anywhere, allowing management of all my services without exposing them all directly to the internet.
#### Network Diagram #### Network Diagram
![Diagram of my home network ](img/homelab-network-schema.png) ![Diagram of my home network ](images/homelab-network-schema.png)
### Application ### Application
Let's dive into the fun part! What started as a modest setup meant to serve a few personal needs quickly turned into a full ecosystem of open source services, each solving a specific need or just satisfying curiosity. Let's dive into the fun part! What started as a modest setup meant to serve a few personal needs quickly turned into a full ecosystem of open source services, each solving a specific need or just satisfying curiosity.
Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

@@ -9,6 +9,7 @@ tags:
- cloud-init - cloud-init
categories: categories:
- homelab - homelab
image: cover-1.webp
--- ---
## Intro ## Intro
@@ -32,7 +33,7 @@ Tout d'abord, nous devons télécharger une image compatible cloud-init. Bien qu
Trouvez des images compatibles cloud dans le [Guide des images OpenStack](https://docs.openstack.org/image-guide/obtain-images.html). Trouvez des images compatibles cloud dans le [Guide des images OpenStack](https://docs.openstack.org/image-guide/obtain-images.html).
Dans Proxmox, accédez à **Storage > ISO Images > Upload** pour uploader l'image téléchargée. Dans Proxmox, accédez à **Storage > ISO Images > Upload** pour uploader l'image téléchargée.
![Download window for ISO images in Proxmox](img/proxmox-download-iso-img.png) ![Fenêtre de téléchargement pour images ISO dans Proxmox](images/proxmox-download-iso-img.png)
## Créer la VM ## Créer la VM
Ensuite, on crée une VM en utilisant la ligne de commande (CLI) depuis le nœud Proxmox avec la commande suivantes : Ensuite, on crée une VM en utilisant la ligne de commande (CLI) depuis le nœud Proxmox avec la commande suivantes :
@@ -9,6 +9,7 @@ tags:
- cloud-init - cloud-init
categories: categories:
- homelab - homelab
image: cover-1.webp
--- ---
## Intro ## Intro
@@ -32,7 +33,7 @@ First, we need to download an image with cloud-init support. Although Rocky Linu
Find cloud-ready images from the [OpenStack Image Guide](https://docs.openstack.org/image-guide/obtain-images.html). Find cloud-ready images from the [OpenStack Image Guide](https://docs.openstack.org/image-guide/obtain-images.html).
In Proxmox, navigate to **Storage > ISO Images > Upload** to upload the downloaded image. In Proxmox, navigate to **Storage > ISO Images > Upload** to upload the downloaded image.
![Download window for ISO images in Proxmox](img/proxmox-download-iso-img.png) ![Download window for ISO images in Proxmox](images/proxmox-download-iso-img.png)
## Create the VM ## Create the VM
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

@@ -8,6 +8,7 @@ tags:
- opnsense - opnsense
categories: categories:
- homelab - homelab
image: cover-10.webp
--- ---
## Intro ## Intro
@@ -21,7 +22,7 @@ Ce genre d’exercice est la pire chose que vous souhaitez voir arriver, parce q
Ma box OPNsense tournait parfaitement depuis des mois. Routeur, pare-feu, DNS, DHCP, VLANs, VPN, reverse proxy et même contrôleur UniFi : toutes les pièces de mon homelab passe par elle. Mais pas seulement, elle fournit aussi Internet à la maison. Ma box OPNsense tournait parfaitement depuis des mois. Routeur, pare-feu, DNS, DHCP, VLANs, VPN, reverse proxy et même contrôleur UniFi : toutes les pièces de mon homelab passe par elle. Mais pas seulement, elle fournit aussi Internet à la maison.
![Diagram of my home network ](img/homelab-network-schema.png) ![Le schéma réseau de mon Homelab](images/homelab-network-schema.png)
Cette box est le cœur de mon réseau, sans elle, je ne peux quasiment rien faire. J’ai détaillé son fonctionnement dans ma section [Homelab]({{< ref "page/homelab" >}}). Tout “fonctionnait juste”, et je ne m’en inquiétait pas. J’étais confiant, sa sauvegarde vivait uniquement à l’intérieur de la machine… Cette box est le cœur de mon réseau, sans elle, je ne peux quasiment rien faire. J’ai détaillé son fonctionnement dans ma section [Homelab]({{< ref "page/homelab" >}}). Tout “fonctionnait juste”, et je ne m’en inquiétait pas. J’étais confiant, sa sauvegarde vivait uniquement à l’intérieur de la machine…
@@ -61,7 +62,7 @@ pkg: sqlite error while executing iterator in file pkgdb_iterator.c:1110: databa
``` ```
🚨 Mon alarme interne s'est déclenchée. J’ai pensé aux sauvegardes et j’ai immédiatement téléchargé la dernière : 🚨 Mon alarme interne s'est déclenchée. J’ai pensé aux sauvegardes et j’ai immédiatement téléchargé la dernière :
![Backup configuration in OPNsense](img/opnsense-download-backup.png) ![Sauvegarde de configuration dans OPNsense](images/opnsense-download-backup.png)
En cliquant sur le bouton `Download configuration`, j’ai récupéré le `config.xml` en cours d’utilisation. Je pensais que ça suffirait. En cliquant sur le bouton `Download configuration`, j’ai récupéré le `config.xml` en cours d’utilisation. Je pensais que ça suffirait.
@@ -8,6 +8,7 @@ tags:
- opnsense - opnsense
categories: categories:
- homelab - homelab
image: cover-10.webp
--- ---
## Intro ## Intro
@@ -21,7 +22,7 @@ This kind of exercise is the worst thing you want to happen because it's never f
My OPNsense box had been running smoothly for months. Router, firewall, DNS, DHCP, VLANs, VPN, reverse proxy and even UniFi controller: all the pieces of my homelab run through it. but not only, it is also serving internet at home. My OPNsense box had been running smoothly for months. Router, firewall, DNS, DHCP, VLANs, VPN, reverse proxy and even UniFi controller: all the pieces of my homelab run through it. but not only, it is also serving internet at home.
![Diagram of my home network ](img/homelab-network-schema.png) ![My Homelab network diagram](images/homelab-network-schema.png)
This box is the heart of my network, without it, I can hardly do anything. I have detailed how this is working in my [Homelab]({{< ref "page/homelab" >}}) section. It was “just working,” and I wasn’t worried about it. I felt confident, its backup was living only inside the machine... This box is the heart of my network, without it, I can hardly do anything. I have detailed how this is working in my [Homelab]({{< ref "page/homelab" >}}) section. It was “just working,” and I wasn’t worried about it. I felt confident, its backup was living only inside the machine...
@@ -62,7 +63,7 @@ pkg: sqlite error while executing iterator in file pkgdb_iterator.c:1110: databa
``` ```
🚨 My internal alarm sensor triggered, I wondered about backups, I immediately decided to download the latest backup: 🚨 My internal alarm sensor triggered, I wondered about backups, I immediately decided to download the latest backup:
![Backup configuration in OPNsense](img/opnsense-download-backup.png) ![Backup configuration in OPNsense](images/opnsense-download-backup.png)
Clicking the `Download configuration` button, I downloaded the current `config.xml` in use my the instance, I though it was enough. Clicking the `Download configuration` button, I downloaded the current `config.xml` in use my the instance, I though it was enough.
Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

@@ -8,12 +8,13 @@ tags:
- proxmox - proxmox
categories: categories:
- homelab - homelab
image: cover-11.webp
--- ---
## Intro ## Intro
Quand j’ai construit mon cluster **Proxmox VE 8** pour la première fois, le réseau n’était pas ma priorité. Je voulais simplement remplacer rapidement un vieux serveur physique, alors j’ai donné la même configuration de base à chacun de mes trois nœuds, créé le cluster et commencé à créer des VM : Quand j’ai construit mon cluster **Proxmox VE 8** pour la première fois, le réseau n’était pas ma priorité. Je voulais simplement remplacer rapidement un vieux serveur physique, alors j’ai donné la même configuration de base à chacun de mes trois nœuds, créé le cluster et commencé à créer des VM :
![Configuration réseau d’un nœud Proxmox](img/proxmox-node-network-configuration.png) ![Configuration réseau d’un nœud Proxmox](images/proxmox-node-network-configuration.png)
Cela a bien fonctionné pendant un moment. Mais comme je prévois de virtualiser mon routeur **OPNsense**, j’ai besoin de quelque chose de plus structuré et cohérent. C’est là que la fonctionnalité **S**oftware-**D**efined **N**etworking (SDN) de Proxmox entre en jeu. Cela a bien fonctionné pendant un moment. Mais comme je prévois de virtualiser mon routeur **OPNsense**, j’ai besoin de quelque chose de plus structuré et cohérent. C’est là que la fonctionnalité **S**oftware-**D**efined **N**etworking (SDN) de Proxmox entre en jeu.
@@ -21,7 +22,7 @@ Cela a bien fonctionné pendant un moment. Mais comme je prévois de virtualiser
## Mon Réseau Homelab ## Mon Réseau Homelab
Par défaut, chaque nœud Proxmox dispose de sa propre zone locale, appelée `localnetwork`, qui contient le pont Linux par défaut (`vmbr0`) comme VNet : Par défaut, chaque nœud Proxmox dispose de sa propre zone locale, appelée `localnetwork`, qui contient le pont Linux par défaut (`vmbr0`) comme VNet :
![Proxmox default `localnetwork` zones](img/proxmox-default-localnetwork-zone.png) ![Zones `localnetwork` par défaut dans Proxmox](images/proxmox-default-localnetwork-zone.png)
C’est suffisant pour des configurations isolées, mais rien n’est coordonné au niveau du cluster. C’est suffisant pour des configurations isolées, mais rien n’est coordonné au niveau du cluster.
@@ -61,29 +62,29 @@ Proxmox prend en charge plusieurs types de zones :
- **EVPN** : VXLAN avec BGP pour du routage L3 dynamique - **EVPN** : VXLAN avec BGP pour du routage L3 dynamique
Comme mon réseau domestique utilise déjà des VLAN, j’ai créé une **zone VLAN** appelée `homelan`, en utilisant `vmbr0` comme pont et en l’appliquant à tout le cluster : Comme mon réseau domestique utilise déjà des VLAN, j’ai créé une **zone VLAN** appelée `homelan`, en utilisant `vmbr0` comme pont et en l’appliquant à tout le cluster :
![Create a VLAN zone in the Proxmox SDN](img/proxmox-create-vlan-zone-homelan.png) ![Création d’une zone VLAN dans Proxmox SDN](images/proxmox-create-vlan-zone-homelan.png)
### VNets ### VNets
Un **VNet** est un réseau virtuel à l’intérieur d’une zone. Dans une zone VLAN, chaque VNet correspond à un ID VLAN spécifique. Un **VNet** est un réseau virtuel à l’intérieur d’une zone. Dans une zone VLAN, chaque VNet correspond à un ID VLAN spécifique.
J’ai commencé par créer `vlan55` dans la zone `homelan` pour mon réseau DMZ : J’ai commencé par créer `vlan55` dans la zone `homelan` pour mon réseau DMZ :
![Create a VNet for VLAN 55 in the homelan zone](img/proxmox-create-vlan-vnet-homelan.png) ![Création d’un VNet pour le VLAN 55 dans la zone homelan](images/proxmox-create-vlan-vnet-homelan.png)
Puis j’ai ajouté les VNets correspondant à la plupart de mes VLAN, puisque je prévois de les rattacher à une VM OPNsense : Puis j’ai ajouté les VNets correspondant à la plupart de mes VLAN, puisque je prévois de les rattacher à une VM OPNsense :
![All my VLANs created in the Proxmox SDN](img/proxmox-sdn-all-vlan-homelan.png) ![Tous mes VLANs créés dans le Proxmox SDN](images/proxmox-sdn-all-vlan-homelan.png)
Enfin, j’ai appliqué la configuration dans **Datacenter → SDN** : Enfin, j’ai appliqué la configuration dans **Datacenter → SDN** :
![Application de la configuration SDN dans Proxmox](img/proxmox-apply-sdn-homelan-configuration.png) ![Application de la configuration SDN dans Proxmox](images/proxmox-apply-sdn-homelan-configuration.png)
--- ---
## Test de la Configuration Réseau ## Test de la Configuration Réseau
Dans une vieille VM que je n'utilise plus, je remplace l'actuel `vmbr0` avec le VLAN tag 66 par mon nouveau VNet `vlan66`: Dans une vieille VM que je n'utilise plus, je remplace l'actuel `vmbr0` avec le VLAN tag 66 par mon nouveau VNet `vlan66`:
![Change the network bridge in a VM](img/proxmox-change-vm-nic-vlan-vnet.png) ![Changement du pont réseau dans une VM](images/proxmox-change-vm-nic-vlan-vnet.png)
Après l'avoir démarrée, la VM obtient une IP du DHCP d'OPNsense sur ce VLAN, ce qui est super. J'essaye également de ping une autre machine et ça fonctionne : Après l'avoir démarrée, la VM obtient une IP du DHCP d'OPNsense sur ce VLAN, ce qui est super. J'essaye également de ping une autre machine et ça fonctionne :
![Ping another machine in the same VLAN](img/proxmox-console-ping-vm-vlan-66.png) ![Ping d’une autre machine dans le même VLAN](images/proxmox-console-ping-vm-vlan-66.png)
--- ---
## Mise à jour de Cloud-Init et Terraform ## Mise à jour de Cloud-Init et Terraform
@@ -92,7 +93,7 @@ Pour aller plus loin, j’ai mis à jour le pont réseau utilisé dans mon **tem
Comme avec la VM précédente, j’ai remplacé `vmbr0` et le tag VLAN 66 par le nouveau VNet `vlan66`. Comme avec la VM précédente, j’ai remplacé `vmbr0` et le tag VLAN 66 par le nouveau VNet `vlan66`.
J’ai aussi adapté mon code **Terraform** pour refléter ce changement : J’ai aussi adapté mon code **Terraform** pour refléter ce changement :
![Mise à jour du code Terraform pour vlan66](img/terraform-code-update-vlan66.png) ![Mise à jour du code Terraform pour vlan66](images/terraform-code-update-vlan66.png)
Ensuite, j’ai validé qu’aucune régression n’était introduite en déployant une VM de test : Ensuite, j’ai validé qu’aucune régression n’était introduite en déployant une VM de test :
```bash ```bash
@@ -129,7 +130,7 @@ vm_ip = "192.168.66.181"
``` ```
La création s’est déroulée sans problème, tout est bon : La création s’est déroulée sans problème, tout est bon :
![VM déployée par Terraform sur vlan66](img/proxmox-terraform-test-deploy-vlan66.png) ![VM déployée par Terraform sur vlan66](images/proxmox-terraform-test-deploy-vlan66.png)
--- ---
## Conclusion ## Conclusion
@@ -8,12 +8,13 @@ tags:
- proxmox - proxmox
categories: categories:
- homelab - homelab
image: cover-11.webp
--- ---
## Intro ## Intro
When I first built my **Proxmox VE 8** cluster, networking wasn’t my main concern. I just wanted to replace an old physical server quickly, so I gave each of my three nodes the same basic config, created the cluster, and started running VMs: When I first built my **Proxmox VE 8** cluster, networking wasn’t my main concern. I just wanted to replace an old physical server quickly, so I gave each of my three nodes the same basic config, created the cluster, and started running VMs:
![Configuration réseau d’un nœud Proxmox](img/proxmox-node-network-configuration.png) ![Proxmox node network configuration](images/proxmox-node-network-configuration.png)
That worked fine for a while. But as I plan to virtualize my **OPNsense** router, I need something more structured and consistent. This is where Proxmox **S**oftware-**D**efined **N**etworking (SDN) feature comes in. That worked fine for a while. But as I plan to virtualize my **OPNsense** router, I need something more structured and consistent. This is where Proxmox **S**oftware-**D**efined **N**etworking (SDN) feature comes in.
@@ -21,7 +22,7 @@ That worked fine for a while. But as I plan to virtualize my **OPNsense** router
## My Homelab Network ## My Homelab Network
By default, every Proxmox node comes with its own local zone, called `localnetwork`, which contains the default Linux bridge (`vmbr0`) as a VNet: By default, every Proxmox node comes with its own local zone, called `localnetwork`, which contains the default Linux bridge (`vmbr0`) as a VNet:
![Proxmox default `localnetwork` zones](img/proxmox-default-localnetwork-zone.png) ![Proxmox default `localnetwork` zones](images/proxmox-default-localnetwork-zone.png)
That’s fine for isolated setups, but at the cluster level nothing is coordinated. That’s fine for isolated setups, but at the cluster level nothing is coordinated.
@@ -61,29 +62,29 @@ Proxmox supports several zone types:
- **EVPN**: VXLAN with BGP to establish Layer 3 routing - **EVPN**: VXLAN with BGP to establish Layer 3 routing
Since my home network already relies on VLANs, I created a **VLAN Zone** named `homelan`, using `vmbr0` as the bridge and applying it cluster-wide: Since my home network already relies on VLANs, I created a **VLAN Zone** named `homelan`, using `vmbr0` as the bridge and applying it cluster-wide:
![Create a VLAN zone in the Proxmox SDN](img/proxmox-create-vlan-zone-homelan.png) ![Create a VLAN zone in the Proxmox SDN](images/proxmox-create-vlan-zone-homelan.png)
### VNets ### VNets
A **VNet** is a virtual network inside a zone. In a VLAN zone, each VNet corresponds to a specific VLAN ID. A **VNet** is a virtual network inside a zone. In a VLAN zone, each VNet corresponds to a specific VLAN ID.
I started by creating `vlan55` in the `homelan` zone for my DMZ network: I started by creating `vlan55` in the `homelan` zone for my DMZ network:
![Create a VNet for VLAN 55 in the homelan zone](img/proxmox-create-vlan-vnet-homelan.png) ![Create a VNet for VLAN 55 in the homelan zone](images/proxmox-create-vlan-vnet-homelan.png)
Then I added VNets for most of my VLANs, since I plan to attach them to an OPNsense VM: Then I added VNets for most of my VLANs, since I plan to attach them to an OPNsense VM:
![All my VLANs created in the Proxmox SDN](img/proxmox-sdn-all-vlan-homelan.png) ![All my VLANs created in the Proxmox SDN](images/proxmox-sdn-all-vlan-homelan.png)
Finally, I applied the configuration in **Datacenter → SDN**: Finally, I applied the configuration in **Datacenter → SDN**:
![Application de la configuration SDN dans Proxmox](img/proxmox-apply-sdn-homelan-configuration.png) ![Apply SDN configuration in Proxmox](images/proxmox-apply-sdn-homelan-configuration.png)
--- ---
## Test the Network Configuration ## Test the Network Configuration
In a old VM which I don't use anymore, I replace the current `vmbr0` with VLAN tag 66 to my new VNet `vlan66`: In a old VM which I don't use anymore, I replace the current `vmbr0` with VLAN tag 66 to my new VNet `vlan66`:
![Change the network bridge in a VM](img/proxmox-change-vm-nic-vlan-vnet.png) ![Change the network bridge in a VM](images/proxmox-change-vm-nic-vlan-vnet.png)
After starting it, the VM gets an IP from the DHCP on OPNsense on that VLAN, which sounds good. I also try to ping another machine and it works: After starting it, the VM gets an IP from the DHCP on OPNsense on that VLAN, which sounds good. I also try to ping another machine and it works:
![Ping another machine in the same VLAN](img/proxmox-console-ping-vm-vlan-66.png) ![Ping another machine in the same VLAN](images/proxmox-console-ping-vm-vlan-66.png)
--- ---
## Update Cloud-Init Template and Terraform ## Update Cloud-Init Template and Terraform
@@ -91,7 +92,7 @@ After starting it, the VM gets an IP from the DHCP on OPNsense on that VLAN, whi
To go further, I update the bridge used in my **cloud-init** template, which I detailed the creation in that [post]({{< ref "post/1-proxmox-cloud-init-vm-template" >}}). Pretty much the same thing I've done with the VM, I replace the current `vmbr0` with VLAN tag 66 with my new VNet `vlan66`. To go further, I update the bridge used in my **cloud-init** template, which I detailed the creation in that [post]({{< ref "post/1-proxmox-cloud-init-vm-template" >}}). Pretty much the same thing I've done with the VM, I replace the current `vmbr0` with VLAN tag 66 with my new VNet `vlan66`.
I also update the **Terrafom** code to take this change into account: I also update the **Terrafom** code to take this change into account:
![Mise à jour du code Terraform pour vlan66](img/terraform-code-update-vlan66.png) ![Terraform code change for the vlan66](images/terraform-code-update-vlan66.png)
I quicky check if I don't have regression and can still deploy a VM with Terraform: I quicky check if I don't have regression and can still deploy a VM with Terraform:
```bash ```bash
@@ -128,7 +129,7 @@ vm_ip = "192.168.66.181"
``` ```
The VM is deploying without any issue, everything is OK: The VM is deploying without any issue, everything is OK:
![VM déployée par Terraform sur vlan66](img/proxmox-terraform-test-deploy-vlan66.png) ![VM hardware in Proxmox deployed by Terraform](images/proxmox-terraform-test-deploy-vlan66.png)
--- ---
## Conclusion ## Conclusion
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 169 KiB

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

@@ -10,6 +10,7 @@ tags:
- high-availability - high-availability
categories: categories:
- homelab - homelab
image: cover-12.webp
--- ---
## Intro ## Intro
@@ -29,7 +30,7 @@ Au sommet de mon installation, mon modem FAI, une _Freebox_ en mode bridge, reli
Ce switch relie également mes trois nœuds Proxmox, chacun sur un port trunk avec le même VLAN natif. Chaque nœud dispose de deux cartes réseau : une pour le trafic général, et l’autre dédiée au réseau de stockage Ceph, connecté à un switch séparé de 2,5 Gbps. Ce switch relie également mes trois nœuds Proxmox, chacun sur un port trunk avec le même VLAN natif. Chaque nœud dispose de deux cartes réseau : une pour le trafic général, et l’autre dédiée au réseau de stockage Ceph, connecté à un switch séparé de 2,5 Gbps.
Depuis le crash d’OPNsense, j’ai simplifié l’architecture en supprimant le lien LACP, qui n’apportait pas de réelle valeur : Depuis le crash d’OPNsense, j’ai simplifié l’architecture en supprimant le lien LACP, qui n’apportait pas de réelle valeur :
![Current homelab network diagram](img/homelan-current-physical-layout.png) ![Schéma réseau du homelab actuel](images/homelan-current-physical-layout.png)
Jusqu’à récemment, le réseau Proxmox de mon cluster était très basique : chaque nœud était configuré individuellement sans véritable logique commune. Cela a changé après la découverte du SDN Proxmox, qui m’a permis de centraliser les définitions de VLAN sur l’ensemble du cluster. J’ai décrit cette étape dans [cet article]({{< ref "post/11-proxmox-cluster-networking-sdn" >}}). Jusqu’à récemment, le réseau Proxmox de mon cluster était très basique : chaque nœud était configuré individuellement sans véritable logique commune. Cela a changé après la découverte du SDN Proxmox, qui m’a permis de centraliser les définitions de VLAN sur l’ensemble du cluster. J’ai décrit cette étape dans [cet article]({{< ref "post/11-proxmox-cluster-networking-sdn" >}}).
@@ -43,7 +44,7 @@ Place au lab. Voici les étapes principales :
4. Configurer la haute disponibilité 4. Configurer la haute disponibilité
5. Tester la bascule 5. Tester la bascule
![Diagram of the POC for OPNsense high availability](img/poc-opnsense-diagram.png) ![Schéma du POC pour OPNsense en haute disponibilité](images/poc-opnsense-diagram.png)
### Ajouter des VLANs dans mon homelab ### Ajouter des VLANs dans mon homelab
@@ -53,7 +54,7 @@ Pour cette expérimentation, je crée trois nouveaux VLANs :
- **VLAN 103** : _POC pfSync_ - **VLAN 103** : _POC pfSync_
Dans l’interface Proxmox, je vais dans `Datacenter` > `SDN` > `VNets` et je clique sur `Create` : Dans l’interface Proxmox, je vais dans `Datacenter` > `SDN` > `VNets` et je clique sur `Create` :
![Create POC VLANs in the Proxmox SDN](img/proxmox-sdn-create-poc-vlans.png) ![Création des VLANs POC dans le SDN Proxmox](images/proxmox-sdn-create-poc-vlans.png)
Une fois les trois VLANs créés, j’applique la configuration. Une fois les trois VLANs créés, j’applique la configuration.
@@ -114,7 +115,7 @@ La VM `fake-freebox` est maintenant prête à fournir du DHCP sur le VLAN 101, a
### Construire les VMs OPNsense ### Construire les VMs OPNsense
Je commence par télécharger l’ISO d’OPNsense et je l’upload sur un de mes nœuds Proxmox : Je commence par télécharger l’ISO d’OPNsense et je l’upload sur un de mes nœuds Proxmox :
![Upload de l’ISO OPNsense dans Proxmox](img/proxmox-upload-opnsense-iso.png) ![Upload de l’ISO OPNsense dans Proxmox](images/proxmox-upload-opnsense-iso.png)
#### Création de la VM #### Création de la VM
@@ -128,69 +129,69 @@ Je crée la première VM `poc-opnsense-1` avec les paramètres suivants :
1. VLAN 101 (_POC WAN_) 1. VLAN 101 (_POC WAN_)
2. VLAN 102 (_POC LAN_) 2. VLAN 102 (_POC LAN_)
3. VLAN 103 (_POC pfSync_) 3. VLAN 103 (_POC pfSync_)
![OPNsense VM settings in Proxmox](img/proxmox-create-poc-vm-opnsense.png) ![Paramètres de la VM OPNsense dans Proxmox](images/proxmox-create-poc-vm-opnsense.png)
ℹ️ Avant de la démarrer, je clone cette VM pour préparer la seconde : `poc-opnsense-2` ℹ️ Avant de la démarrer, je clone cette VM pour préparer la seconde : `poc-opnsense-2`
Au premier démarrage, je tombe sur une erreur “access denied”. Pour corriger, j’entre dans le BIOS, **Device Manager > Secure Boot Configuration**, je décoche _Attempt Secure Boot_ et je redémarre : Au premier démarrage, je tombe sur une erreur “access denied”. Pour corriger, j’entre dans le BIOS, **Device Manager > Secure Boot Configuration**, je décoche _Attempt Secure Boot_ et je redémarre :
![Disable Secure Boot in Proxmox BIOS](img/proxmox-disable-secure-boot-option.png) ![Désactiver Secure Boot dans le BIOS de Proxmox](images/proxmox-disable-secure-boot-option.png)
#### Installation d’OPNsense #### Installation d’OPNsense
La VM démarre sur l’ISO, je ne touche à rien jusqu’à l’écran de login : La VM démarre sur l’ISO, je ne touche à rien jusqu’à l’écran de login :
![OPNsense CLI login screen in LiveCD](img/opnsense-vm-installation-welcome.png) ![Écran de login OPNsense en mode LiveCD](images/opnsense-vm-installation-welcome.png)
Je me connecte avec `installer` / `opnsense` et je lance l’installateur. Je sélectionne le disque QEMU de 20 Go comme destination et je démarre l’installation : Je me connecte avec `installer` / `opnsense` et je lance l’installateur. Je sélectionne le disque QEMU de 20 Go comme destination et je démarre l’installation :
![Barre de progression de l’installation OPNsense](img/opnsense-vm-installation-progress-bar.png) ![Barre de progression de l’installation OPNsense](images/opnsense-vm-installation-progress-bar.png)
Une fois terminé, je retire l’ISO du lecteur et je redémarre la machine. Une fois terminé, je retire l’ISO du lecteur et je redémarre la machine.
#### Configuration de Base d’OPNsense #### Configuration de Base d’OPNsense
Au redémarrage, je me connecte avec `root` / `opnsense` et j’arrive au menu CLI : Au redémarrage, je me connecte avec `root` / `opnsense` et j’arrive au menu CLI :
![Menu CLI après une installation fraîche d’OPNsense](img/opnsense-vm-installation-cli-menu.png) ![Menu CLI après une installation fraîche d’OPNsense](images/opnsense-vm-installation-cli-menu.png)
Avec l’option 1, je réassigne les interfaces : Avec l’option 1, je réassigne les interfaces :
![Configuration des interfaces dans OPNsense via le CLI](img/opnsense-vm-installation-assign-interfaces.png) ![Configuration des interfaces dans OPNsense via le CLI](images/opnsense-vm-installation-assign-interfaces.png)
L’interface WAN récupère bien `10.101.0.150/24` depuis la `fake-freebox`. Je configure le LAN sur `10.102.0.2/24` et j’ajoute un pool DHCP de `10.102.0.10` à `10.102.0.99` : L’interface WAN récupère bien `10.101.0.150/24` depuis la `fake-freebox`. Je configure le LAN sur `10.102.0.2/24` et j’ajoute un pool DHCP de `10.102.0.10` à `10.102.0.99` :
![Interface WAN OPNsense recevant une IP depuis la VM `fake-freebox`](img/opnsense-vm-installation-interfaces-configured.png) ![Interface WAN OPNsense recevant une IP depuis la VM `fake-freebox`](images/opnsense-vm-installation-interfaces-configured.png)
✅ La première VM est prête, je reproduis l’opération pour la seconde OPNsense `poc-opnsense-2`, qui aura l’IP `10.102.0.3`. ✅ La première VM est prête, je reproduis l’opération pour la seconde OPNsense `poc-opnsense-2`, qui aura l’IP `10.102.0.3`.
### Configurer OPNsense en Haute Disponibilité ### Configurer OPNsense en Haute Disponibilité
Avec les deux VMs OPNsense opérationnelles, il est temps de passer à la configuration via le WebGUI. Pour y accéder, j’ai connecté une VM Windows au VLAN _POC LAN_ et ouvert l’IP de l’OPNsense sur le port 443 : Avec les deux VMs OPNsense opérationnelles, il est temps de passer à la configuration via le WebGUI. Pour y accéder, j’ai connecté une VM Windows au VLAN _POC LAN_ et ouvert l’IP de l’OPNsense sur le port 443 :
![OPNsense WebGUI depuis une VM Windows](img/opnsense-vm-webgui-from-poc-lan.png) ![OPNsense WebGUI depuis une VM Windows](images/opnsense-vm-webgui-from-poc-lan.png)
#### Ajouter l’Interface pfSync #### Ajouter l’Interface pfSync
La troisième carte réseau (`vtnet2`) est assignée à l’interface _pfSync_. Ce réseau dédié permet aux deux firewalls de synchroniser leurs états via le VLAN _POC pfSync_ : La troisième carte réseau (`vtnet2`) est assignée à l’interface _pfSync_. Ce réseau dédié permet aux deux firewalls de synchroniser leurs états via le VLAN _POC pfSync_ :
![Add pfSync interface in OPNsense](img/opnsense-vm-assign-pfsync-interface.png) ![Ajouter l’interface pfSync dans OPNsense](images/opnsense-vm-assign-pfsync-interface.png)
J’active l’interface sur chaque instance et je leur attribue une IP statique : J’active l’interface sur chaque instance et je leur attribue une IP statique :
- **poc-opnsense-1** : `10.103.0.2/24` - **poc-opnsense-1** : `10.103.0.2/24`
- **poc-opnsense-2** : `10.103.0.3/24` - **poc-opnsense-2** : `10.103.0.3/24`
Puis, j’ajoute une règle firewall sur chaque nœud pour autoriser tout le trafic provenant de ce réseau sur l’interface _pfSync_ : Puis, j’ajoute une règle firewall sur chaque nœud pour autoriser tout le trafic provenant de ce réseau sur l’interface _pfSync_ :
![Create new firewall rule on pfSync interface to allow any traffic in that network](img/opnsense-vm-firewall-allow-pfsync.png) ![Règle firewall pour autoriser tout le trafic pfSync](images/opnsense-vm-firewall-allow-pfsync.png)
#### Configurer la Haute Disponibilité #### Configurer la Haute Disponibilité
Direction `System` > `High Availability` > `Settings`. Direction `System` > `High Availability` > `Settings`.
- Sur le master (`poc-opnsense-1`), je configure les `General Settings` et les `Synchronization Settings`. - Sur le master (`poc-opnsense-1`), je configure les `General Settings` et les `Synchronization Settings`.
- Sur le backup (`poc-opnsense-2`), seuls les `General Settings` suffisent (on ne veut pas qu’il écrase la config du master). - Sur le backup (`poc-opnsense-2`), seuls les `General Settings` suffisent (on ne veut pas qu’il écrase la config du master).
![OPNsense High Availability settings](img/opnsense-vm-high-availability-settings.png) ![Paramètres de Haute Disponibilité dans OPNsense](images/opnsense-vm-high-availability-settings.png)
Une fois appliqué, je vérifie la synchro dans l’onglet `Status` : Une fois appliqué, je vérifie la synchro dans l’onglet `Status` :
![OPNsense High Availability status](img/opnsense-vm-high-availability-status.png) ![Statut de la Haute Disponibilité OPNsense](images/opnsense-vm-high-availability-status.png)
#### Créer une IP Virtuelle #### Créer une IP Virtuelle
Pour fournir une passerelle partagée aux clients, je crée une IP virtuelle (VIP) en **CARP** (Common Address Redundancy Protocol) sur l’interface LAN. L’IP est portée par le nœud actif et bascule automatiquement en cas de failover. Pour fournir une passerelle partagée aux clients, je crée une IP virtuelle (VIP) en **CARP** (Common Address Redundancy Protocol) sur l’interface LAN. L’IP est portée par le nœud actif et bascule automatiquement en cas de failover.
Menu : `Interfaces` > `Virtual IPs` > `Settings` : Menu : `Interfaces` > `Virtual IPs` > `Settings` :
![Create CARP virtual IP in OPNsense](img/opnsense-vm-create-vip-carp.png) ![Création d’une VIP CARP dans OPNsense](images/opnsense-vm-create-vip-carp.png)
Je réplique ensuite la config depuis `System > High Availability > Status` avec le bouton `Synchronize and reconfigure all`. Je réplique ensuite la config depuis `System > High Availability > Status` avec le bouton `Synchronize and reconfigure all`.
@@ -205,7 +206,7 @@ Sur le master :
- `DHCP ranges` : cocher aussi `Disable HA sync` - `DHCP ranges` : cocher aussi `Disable HA sync`
- `DHCP options` : ajouter l’option `router [3]` avec la valeur `10.102.0.1` (VIP LAN) - `DHCP options` : ajouter l’option `router [3]` avec la valeur `10.102.0.1` (VIP LAN)
- `DHCP options` : cloner la règle pour `dns-server [6]` vers la même VIP. - `DHCP options` : cloner la règle pour `dns-server [6]` vers la même VIP.
![Edit DHCP options for Dnsmasq in OPNsense](img/opnsense-vm-dnsmasq-add-option.png) ![Options DHCP dans Dnsmasq](images/opnsense-vm-dnsmasq-add-option.png)
Sur le backup : Sur le backup :
- `Services` > `Dnsmasq DNS & DHCP` > `General` : cocher `Disable HA sync` - `Services` > `Dnsmasq DNS & DHCP` > `General` : cocher `Disable HA sync`
@@ -262,7 +263,7 @@ if ($type === "MASTER") {
Passons aux tests ! Passons aux tests !
OPNsense propose un _CARP Maintenance Mode_. Avec le master actif, seul lui avait son WAN monté. En activant le mode maintenance, les rôles basculent : le master devient backup, son WAN est désactivé et celui du backup est activé : OPNsense propose un _CARP Maintenance Mode_. Avec le master actif, seul lui avait son WAN monté. En activant le mode maintenance, les rôles basculent : le master devient backup, son WAN est désactivé et celui du backup est activé :
![Mode maintenance CARP dans OPNsense](img/opnsense-vm-carp-status.png) ![Mode maintenance CARP dans OPNsense](images/opnsense-vm-carp-status.png)
Pendant mes pings vers l’extérieur, aucune perte de paquets au moment du basculement. Pendant mes pings vers l’extérieur, aucune perte de paquets au moment du basculement.
@@ -10,6 +10,7 @@ tags:
- high-availability - high-availability
categories: categories:
- homelab - homelab
image: cover-12.webp
--- ---
## Intro ## Intro
@@ -29,7 +30,7 @@ On top of my setup, my ISP modem, a *Freebox* in bridge mode, connects directly
The switch also connects my three Proxmox nodes, each on trunk ports with the same native VLAN. Every node has two NICs: one for general networking and the other dedicated to the Ceph storage network, which runs through a separate 2.5 Gbps switch. The switch also connects my three Proxmox nodes, each on trunk ports with the same native VLAN. Every node has two NICs: one for general networking and the other dedicated to the Ceph storage network, which runs through a separate 2.5 Gbps switch.
Since the OPNsense crash, I’ve simplified things by removing the LACP link, it wasn’t adding real value: Since the OPNsense crash, I’ve simplified things by removing the LACP link, it wasn’t adding real value:
![Current homelab network diagram](img/homelan-current-physical-layout.png) ![Current homelab network diagram](images/homelan-current-physical-layout.png)
Until recently, Proxmox networking on my cluster was very basic: each node was configured individually with no real overlay logic. That changed after I explored Proxmox SDN, where I centralized VLAN definitions across the cluster. I described that step in [this article]({{< ref "post/11-proxmox-cluster-networking-sdn" >}}). Until recently, Proxmox networking on my cluster was very basic: each node was configured individually with no real overlay logic. That changed after I explored Proxmox SDN, where I centralized VLAN definitions across the cluster. I described that step in [this article]({{< ref "post/11-proxmox-cluster-networking-sdn" >}}).
@@ -44,7 +45,7 @@ Time to move into the lab. Here are the main steps:
4. Configure high availability 4. Configure high availability
5. Test failover 5. Test failover
![Diagram of the POC for OPNsense high availability](img/poc-opnsense-diagram.png) ![Diagram of the POC for OPNsense high availability](images/poc-opnsense-diagram.png)
### Add VLANs in my Homelab ### Add VLANs in my Homelab
@@ -54,7 +55,7 @@ For this experiment, I create 3 new VLANs:
- **VLAN 103**: *POC pfSync* - **VLAN 103**: *POC pfSync*
In the Proxmox UI, I navigate to `Datacenter` > `SDN` > `VNets` and I click `Create`: In the Proxmox UI, I navigate to `Datacenter` > `SDN` > `VNets` and I click `Create`:
![Create POC VLANs in the Proxmox SDN](img/proxmox-sdn-create-poc-vlans.png) ![Create POC VLANs in the Proxmox SDN](images/proxmox-sdn-create-poc-vlans.png)
Once the 3 new VLAN have been created, I apply the configuration. Once the 3 new VLAN have been created, I apply the configuration.
@@ -115,7 +116,7 @@ The `fake-freebox` VM is now ready to serve DHCP on VLAN 101 and serve only one
### Build OPNsense VMs ### Build OPNsense VMs
First I download the OPNsense ISO and upload it to one of my Proxmox nodes: First I download the OPNsense ISO and upload it to one of my Proxmox nodes:
![Upload de l’ISO OPNsense dans Proxmox](img/proxmox-upload-opnsense-iso.png) ![Upload the OPNsense ISO into Proxmox](images/proxmox-upload-opnsense-iso.png)
#### VM Creation #### VM Creation
@@ -129,69 +130,69 @@ I create the first VM `poc-opnsense-1`, with the following settings:
1. VLAN 101 (POC WAN) 1. VLAN 101 (POC WAN)
2. VLAN 102 (POC LAN) 2. VLAN 102 (POC LAN)
3. VLAN 103 (POC pfSync) 3. VLAN 103 (POC pfSync)
![OPNsense VM settings in Proxmox](img/proxmox-create-poc-vm-opnsense.png) ![OPNsense VM settings in Proxmox](images/proxmox-create-poc-vm-opnsense.png)
ℹ️ Before booting it, I clone this VM to prepare the second one: `poc-opnsense-2` ℹ️ Before booting it, I clone this VM to prepare the second one: `poc-opnsense-2`
On first boot, I hit an “access denied” error. To fix this, I enter the BIOS, go to **Device Manager > Secure Boot Configuration**, uncheck _Attempt Secure Boot_, and restart the VM: On first boot, I hit an “access denied” error. To fix this, I enter the BIOS, go to **Device Manager > Secure Boot Configuration**, uncheck _Attempt Secure Boot_, and restart the VM:
![Disable Secure Boot in Proxmox BIOS](img/proxmox-disable-secure-boot-option.png) ![Disable Secure Boot in Proxmox BIOS](images/proxmox-disable-secure-boot-option.png)
#### OPNsense Installation #### OPNsense Installation
The VM boots on the ISO, I touch nothing until I get into the login screen: The VM boots on the ISO, I touch nothing until I get into the login screen:
![OPNsense CLI login screen in LiveCD](img/opnsense-vm-installation-welcome.png) ![OPNsense CLI login screen in LiveCD](images/opnsense-vm-installation-welcome.png)
I log in as `installer` / `opnsense` and launch the installer. I select the QEMU hard disk of 20GB as destination and launch the installation: I log in as `installer` / `opnsense` and launch the installer. I select the QEMU hard disk of 20GB as destination and launch the installation:
![Barre de progression de l’installation OPNsense](img/opnsense-vm-installation-progress-bar.png) ![OPNsense installation progress bar](images/opnsense-vm-installation-progress-bar.png)
Once the installation is finished, I remove the ISO from the drive and restart the machine. Once the installation is finished, I remove the ISO from the drive and restart the machine.
#### OPNsense Basic Configuration #### OPNsense Basic Configuration
After reboot, I log in as `root` / `opnsense` and get into the CLI menu: After reboot, I log in as `root` / `opnsense` and get into the CLI menu:
![Menu CLI après une installation fraîche d’OPNsense](img/opnsense-vm-installation-cli-menu.png) ![OPNsense CLI login screen after fresh installation](images/opnsense-vm-installation-cli-menu.png)
Using option 1, I reassigned interfaces: Using option 1, I reassigned interfaces:
![Configuration des interfaces dans OPNsense via le CLI](img/opnsense-vm-installation-assign-interfaces.png) ![OPNsense interface configuration using CLI](images/opnsense-vm-installation-assign-interfaces.png)
The WAN interface successfully pulled `10.101.0.150/24` from the `fake-freebox`. I set the LAN interface to `10.102.0.2/24` and configured a DHCP pool from `10.102.0.10` to `10.102.0.99`: The WAN interface successfully pulled `10.101.0.150/24` from the `fake-freebox`. I set the LAN interface to `10.102.0.2/24` and configured a DHCP pool from `10.102.0.10` to `10.102.0.99`:
![Interface WAN OPNsense recevant une IP depuis la VM `fake-freebox`](img/opnsense-vm-installation-interfaces-configured.png) ![OPNsense WAN interface getting IP from `fake-freebox` VM](images/opnsense-vm-installation-interfaces-configured.png)
✅ The first VM is ready, I start over for the second OPNsense VM, `poc-opnsense-2` which will have the IP `10.102.0.3` ✅ The first VM is ready, I start over for the second OPNsense VM, `poc-opnsense-2` which will have the IP `10.102.0.3`
### Configure OPNsense Highly Available ### Configure OPNsense Highly Available
With both OPNsense VMs operational, it’s time to configure them from the WebGUI. To access the interface, I connected a Windows VM into the _POC LAN_ VLAN and browsed to the OPNsense IP on port 443: With both OPNsense VMs operational, it’s time to configure them from the WebGUI. To access the interface, I connected a Windows VM into the _POC LAN_ VLAN and browsed to the OPNsense IP on port 443:
![OPNsense WebGUI depuis une VM Windows](img/opnsense-vm-webgui-from-poc-lan.png) ![OPNsense WebGUI from Windows VM](images/opnsense-vm-webgui-from-poc-lan.png)
#### Add pfSync Interface #### Add pfSync Interface
The third NIC (`vtnet2`) is assigned to the _pfSync_ interface. This dedicated network allows the two firewalls to synchronize states on the VLAN *POC pfSync*: The third NIC (`vtnet2`) is assigned to the _pfSync_ interface. This dedicated network allows the two firewalls to synchronize states on the VLAN *POC pfSync*:
![Add pfSync interface in OPNsense](img/opnsense-vm-assign-pfsync-interface.png) ![Add pfSync interface in OPNsense](images/opnsense-vm-assign-pfsync-interface.png)
I enable the interface on each instance and configure it with a static IP address: I enable the interface on each instance and configure it with a static IP address:
- **poc-opnsense-1**: `10.103.0.2/24` - **poc-opnsense-1**: `10.103.0.2/24`
- **poc-opnsense-2**: `10.103.0.3/24` - **poc-opnsense-2**: `10.103.0.3/24`
Then, I add a firewall rule on each node to allow all traffic coming from this network on that *pfSync* interface: Then, I add a firewall rule on each node to allow all traffic coming from this network on that *pfSync* interface:
![Create new firewall rule on pfSync interface to allow any traffic in that network](img/opnsense-vm-firewall-allow-pfsync.png) ![Create new firewall rule on pfSync interface to allow any traffic in that network](images/opnsense-vm-firewall-allow-pfsync.png)
#### Setup High Availability #### Setup High Availability
Next, in `System` > `High Availability` > `Settings`. Next, in `System` > `High Availability` > `Settings`.
- On the master (`poc-opnsense-1`), I configure both the `General Settings` and the `Synchronization Settings`. - On the master (`poc-opnsense-1`), I configure both the `General Settings` and the `Synchronization Settings`.
- On the backup (`poc-opnsense-2`), only `General Settings` are needed, you don't want your backup overwrite the master config. - On the backup (`poc-opnsense-2`), only `General Settings` are needed, you don't want your backup overwrite the master config.
![OPNsense High Availability settings](img/opnsense-vm-high-availability-settings.png) ![OPNsense High Availability settings](images/opnsense-vm-high-availability-settings.png)
Once applied, I verify synchronization on the `Status` page: Once applied, I verify synchronization on the `Status` page:
![OPNsense High Availability status](img/opnsense-vm-high-availability-status.png) ![OPNsense High Availability status](images/opnsense-vm-high-availability-status.png)
#### Create Virtual IP Address #### Create Virtual IP Address
To provide a shared gateway for clients, I create a CARP Virtual IP (VIP) on the LAN interface. It is using the Common Address Redundancy Protocol. This IP is claimed by the active node and automatically fails over. To provide a shared gateway for clients, I create a CARP Virtual IP (VIP) on the LAN interface. It is using the Common Address Redundancy Protocol. This IP is claimed by the active node and automatically fails over.
Navigate to `Interfaces` > `Virtual IPs` > `Settings`: Navigate to `Interfaces` > `Virtual IPs` > `Settings`:
![Create CARP virtual IP in OPNsense](img/opnsense-vm-create-vip-carp.png) ![Create CARP virtual IP in OPNsense](images/opnsense-vm-create-vip-carp.png)
To replicate the config, I go to `System > High Availability > Status` and click the button next to `Synchronize and reconfigure all`. To replicate the config, I go to `System > High Availability > Status` and click the button next to `Synchronize and reconfigure all`.
@@ -206,7 +207,7 @@ On the master:
- `DHCP ranges`: also tick the `Disable HA sync` box - `DHCP ranges`: also tick the `Disable HA sync` box
- `DHCP options`: add the option `router [3]` with the value `10.102.0.1` (LAN VIP) - `DHCP options`: add the option `router [3]` with the value `10.102.0.1` (LAN VIP)
- `DHCP options`: clone the rule for `router [6]` pointing to the same VIP. - `DHCP options`: clone the rule for `router [6]` pointing to the same VIP.
![Edit DHCP options for Dnsmasq in OPNsense](img/opnsense-vm-dnsmasq-add-option.png) ![Edit DHCP options for Dnsmasq in OPNsense](images/opnsense-vm-dnsmasq-add-option.png)
On the backup: On the backup:
- `Services` > `Dnsmasq DNS & DHCP` > `General`: also tick the `Disable HA sync` box - `Services` > `Dnsmasq DNS & DHCP` > `General`: also tick the `Disable HA sync` box
@@ -264,7 +265,7 @@ if ($type === "MASTER") {
Time for the real test! Time for the real test!
OPNsense provides a _CARP Maintenance Mode_. With the master active, WAN was enabled only on that node. Entering maintenance mode flipped the roles: the master became backup, its WAN disabled, while the backup enabled its WAN: OPNsense provides a _CARP Maintenance Mode_. With the master active, WAN was enabled only on that node. Entering maintenance mode flipped the roles: the master became backup, its WAN disabled, while the backup enabled its WAN:
![Mode maintenance CARP dans OPNsense](img/opnsense-vm-carp-status.png) ![OPNsense CARP maintenance mode](images/opnsense-vm-carp-status.png)
While pinging outside the network, I observed zero packet loss during the failover. While pinging outside the network, I observed zero packet loss during the failover.
Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

@@ -14,6 +14,7 @@ tags:
- dnsmasq - dnsmasq
categories: categories:
- homelab - homelab
image: cover-13.webp
--- ---
## Intro ## Intro
@@ -84,7 +85,7 @@ Il est temps de mettre à jour, dans `System` > `Firmware` > `Status`, je v
Une fois mis à jour et redémarré, je vais dans `System` > `Firmware` > `Plugins`, je coche l'option pour afficher les plugins communautaires. J'installe que le **QEMU Guest Agent**, `os-qemu-guest-agent`, pour permettre la communication entre la VM et l'hôte Proxmox. Une fois mis à jour et redémarré, je vais dans `System` > `Firmware` > `Plugins`, je coche l'option pour afficher les plugins communautaires. J'installe que le **QEMU Guest Agent**, `os-qemu-guest-agent`, pour permettre la communication entre la VM et l'hôte Proxmox.
Cela nécessite un arrêt. Dans Proxmox, j'active le `QEMU Guest Agent` dans les options de la VM : Cela nécessite un arrêt. Dans Proxmox, j'active le `QEMU Guest Agent` dans les options de la VM :
![Options d'une VM Proxmox avec QEMU Guest Agent activé](img/proxmox-opnsense-enable-qemu-guest-agent.png) ![Options d'une VM Proxmox avec QEMU Guest Agent activé](images/proxmox-opnsense-enable-qemu-guest-agent.png)
Finalement je redémarre la VM. Une fois démarrée, depuis la WebGUI de Proxmox, je peux voir les IPs de la VM ce qui confirme que le guest agent fonctionne. Finalement je redémarre la VM. Une fois démarrée, depuis la WebGUI de Proxmox, je peux voir les IPs de la VM ce qui confirme que le guest agent fonctionne.
@@ -92,7 +93,7 @@ Finalement je redémarre la VM. Une fois démarrée, depuis la WebGUI de Proxmox
## Interfaces ## Interfaces
Sur les deux pare‑feu, j'assigne les NIC restantes à de nouvelles interfaces en ajoutant une description. Les VMs ont 7 interfaces, je compare attentivement les adresses MAC pour éviter de mélanger les interfaces : Sur les deux pare‑feu, j'assigne les NIC restantes à de nouvelles interfaces en ajoutant une description. Les VMs ont 7 interfaces, je compare attentivement les adresses MAC pour éviter de mélanger les interfaces :
![Assign interfaces menu in OPNsense](img/opnsense-assign-interfaces.png) ![Assignement des interfaces dans OPNsense](images/opnsense-assign-interfaces.png)
Au final, la configuration des interfaces ressemble à ceci : Au final, la configuration des interfaces ressemble à ceci :
@@ -160,13 +161,13 @@ La HA est configurée dans `System` > `High Availability` > `Settings`
### Statut de la HA ### Statut de la HA
Dans `System` > `High Availability` > `Status`, je peux vérifier si la synchronisation fonctionne. Sur cette page je peux répliquer un ou tous les services du master vers le nœud backup : Dans `System` > `High Availability` > `Status`, je peux vérifier si la synchronisation fonctionne. Sur cette page je peux répliquer un ou tous les services du master vers le nœud backup :
![OPNsense high availability status page](img/opnsense-high-availability-status.png) ![Page de statut de la haute disponibilité dans OPNsense](images/opnsense-high-availability-status.png)
--- ---
## IPs Virtuelles ## IPs Virtuelles
Maintenant que la HA est configurée, je peux attribuer à mes réseaux une IP virtuelle partagée entre mes nœuds. Dans `Interfaces` > `Virtual IPs` > `Settings`, je crée un VIP pour chacun de mes réseaux en utilisant **CARP** (Common Address Redundancy Protocol). L'objectif est de réutiliser les adresses IP utilisées par mon instance OPNsense actuelle, mais comme elle route encore mon réseau, j'utilise des IP différentes pour la phase de configuration : Maintenant que la HA est configurée, je peux attribuer à mes réseaux une IP virtuelle partagée entre mes nœuds. Dans `Interfaces` > `Virtual IPs` > `Settings`, je crée un VIP pour chacun de mes réseaux en utilisant **CARP** (Common Address Redundancy Protocol). L'objectif est de réutiliser les adresses IP utilisées par mon instance OPNsense actuelle, mais comme elle route encore mon réseau, j'utilise des IP différentes pour la phase de configuration :
![Liste des IPs virtuelles dans OPNsense](img/opnsense-interface-virtual-ips.png) ![Liste des IPs virtuelles dans OPNsense](images/opnsense-interface-virtual-ips.png)
ℹ️ OPNsense permet CARP par défaut, aucune règle de pare‑feu spéciale requise ℹ️ OPNsense permet CARP par défaut, aucune règle de pare‑feu spéciale requise
@@ -242,7 +243,7 @@ Pour commencer, dans `Firewall` > `Groups`, je crée 2 zones pour regrouper m
### Network Aliases ### Network Aliases
Ensuite, dans `Firewall` > `Aliases`, je crée un alias `InternalNetworks` pour regrouper tous mes réseaux internes : Ensuite, dans `Firewall` > `Aliases`, je crée un alias `InternalNetworks` pour regrouper tous mes réseaux internes :
![Création d'alias pour les réseaux locaux dansOPNsense](img/opnsense-create-alias-internalnetworks.png) ![Création d'alias pour les réseaux locaux dansOPNsense](images/opnsense-create-alias-internalnetworks.png)
### Règles de Pare-feu Rules ### Règles de Pare-feu Rules
@@ -345,17 +346,17 @@ Sur le nœud backup, je le configure de la même manière, la seule différence
### Plages DHCP ### Plages DHCP
Ensuite je configure les plages DHCP. Les deux pare‑feu auront des plages différentes, le nœud backup aura des plages plus petites (10 baux devraient suffire). Sur le master, elles sont configurées comme suit : Ensuite je configure les plages DHCP. Les deux pare‑feu auront des plages différentes, le nœud backup aura des plages plus petites (10 baux devraient suffire). Sur le master, elles sont configurées comme suit :
![OPNsense DHCP ranges in Dnsmasq](img/opnsense-dnsmasq-dhcp-ranges.png) ![Plages DHCP de Dnsmasq dans OPNsense](images/opnsense-dnsmasq-dhcp-ranges.png)
### Options DHCP ### Options DHCP
Puis je définis quelques options DHCP pour chaque domaine : le `router`, le `dns-server` et le `domain-name`. Je pointe les adresses IP vers la VIP de l'interface : Puis je définis quelques options DHCP pour chaque domaine : le `router`, le `dns-server` et le `domain-name`. Je pointe les adresses IP vers la VIP de l'interface :
![OPNsense DHCP options in Dnsmasq](img/opnsense-dnsmasq-dhcp-options.png) ![Options DHCP de Dnsmasq dans OPNsense](images/opnsense-dnsmasq-dhcp-options.png)
### Hôtes ### Hôtes
Enfin, dans l'onglet `Hosts`, je définis des mappings DHCP statiques mais aussi des IP statiques non gérées par le DHCP, pour qu'elles soient enregistrées dans le DNS : Enfin, dans l'onglet `Hosts`, je définis des mappings DHCP statiques mais aussi des IP statiques non gérées par le DHCP, pour qu'elles soient enregistrées dans le DNS :
![Hôtes DHCP de Dnsmasq dans OPNsense](img/opnsense-dnsmasq-dhcp-hosts.png) ![Hôtes DHCP de Dnsmasq dans OPNsense](images/opnsense-dnsmasq-dhcp-hosts.png)
--- ---
## DNS ## DNS
@@ -372,7 +373,7 @@ Unbound est le résolveur récursif, pour les zones locales j'effectue un forwar
### Paramètres Généraux d'Unbound ### Paramètres Généraux d'Unbound
Configurons-le, dans `Services` > `Unbound DNS` > `General` : Configurons-le, dans `Services` > `Unbound DNS` > `General` :
![OPNsense Unbound DNS general settings](img/opnsense-unbound-general-settings.png) ![Paramètres généraux d'Unbound DNS dans OPNsense](images/opnsense-unbound-general-settings.png)
### Liste de Blocage DNS ### Liste de Blocage DNS
@@ -383,7 +384,7 @@ Pour maintenir le service à jour, dans `System` > `Settings` > `Cron`, j'a
### Transfert de Requêtes ### Transfert de Requêtes
Enfin je configure le transfert de requêtes pour mes domaines locaux vers Dnsmasq. Dans `Services` > `Unbound DNS` > `Query Forwarding`, j'ajoute chacun de mes domaines locaux avec leurs reverse lookups (enregistrements PTR) : Enfin je configure le transfert de requêtes pour mes domaines locaux vers Dnsmasq. Dans `Services` > `Unbound DNS` > `Query Forwarding`, j'ajoute chacun de mes domaines locaux avec leurs reverse lookups (enregistrements PTR) :
![Configuration du transfert de requêtes d'Unbound DNS dans OPNsense](img/opnsense-unbound-dns-query-forwarding.png) ![Configuration du transfert de requêtes d'Unbound DNS dans OPNsense](images/opnsense-unbound-dns-query-forwarding.png)
--- ---
## VPN ## VPN
@@ -14,6 +14,7 @@ tags:
- dnsmasq - dnsmasq
categories: categories:
- homelab - homelab
image: cover-13.webp
--- ---
## Intro ## Intro
@@ -92,7 +93,7 @@ Finally I restart the VM. Once started, from the Proxmox WebGUI, I can see the I
## Interfaces ## Interfaces
On both firewalls, I assign the remaining NICs to new interfaces adding a description. The VMs have 7 interfaces, I carefully compare MAC addresses to avoid mixing interfaces: On both firewalls, I assign the remaining NICs to new interfaces adding a description. The VMs have 7 interfaces, I carefully compare MAC addresses to avoid mixing interfaces:
![Assign interfaces menu in OPNsense](img/opnsense-assign-interfaces.png) ![Assign interfaces menu in OPNsense](images/opnsense-assign-interfaces.png)
In the end, the interfaces configuration looks like this: In the end, the interfaces configuration looks like this:
@@ -157,13 +158,13 @@ The HA is setup in `System` > `High Availability` > `Settings`
### HA Status ### HA Status
In the section `System` > `High Availability` > `Status`, I can verify if the synchronization is working. On this page I can replicate any or all services from my master to my backup node: In the section `System` > `High Availability` > `Status`, I can verify if the synchronization is working. On this page I can replicate any or all services from my master to my backup node:
![OPNsense high availability status page](img/opnsense-high-availability-status.png) ![OPNsense high availability status page](images/opnsense-high-availability-status.png)
--- ---
## Virtual IPs ## Virtual IPs
Now that HA is configured, I can give my networks a virtual IP shared across my nodes. In `Interfaces` > `Virtual IPs` > `Settings`, I create one VIP for each of my networks using **CARP** (Common Address Redundancy Protocol). The target is to reuse the IP addresses used by my current OPNsense instance, but as it is still routing my network, I use different IPs for the configuration phase: Now that HA is configured, I can give my networks a virtual IP shared across my nodes. In `Interfaces` > `Virtual IPs` > `Settings`, I create one VIP for each of my networks using **CARP** (Common Address Redundancy Protocol). The target is to reuse the IP addresses used by my current OPNsense instance, but as it is still routing my network, I use different IPs for the configuration phase:
![Liste des IPs virtuelles dans OPNsense](img/opnsense-interface-virtual-ips.png) ![OPNsense virtual IPs list](images/opnsense-interface-virtual-ips.png)
ℹ️ OPNsense allows CARP by default, no special firewall rule required ℹ️ OPNsense allows CARP by default, no special firewall rule required
@@ -239,7 +240,7 @@ To begin, in `Firewall` > `Groups`, I create 2 zones to regroup my interfaces:
### Network Aliases ### Network Aliases
Next, in `Firewall` > `Aliases`, I create an alias `InternalNetworks` to regroup all my internal networks: Next, in `Firewall` > `Aliases`, I create an alias `InternalNetworks` to regroup all my internal networks:
![Création d'alias pour les réseaux locaux dansOPNsense](img/opnsense-create-alias-internalnetworks.png) ![OPNsense alias creation for internal networks ](images/opnsense-create-alias-internalnetworks.png)
### Firewall Rules ### Firewall Rules
@@ -343,17 +344,17 @@ On the backup node, I configure it the same, the only difference will be the **D
### DHCP Ranges ### DHCP Ranges
Next I configure the DHCP ranges. Both firewalls will have different ranges, the backup node will have smaller ones (only 10 leases should be enough). On the master, they are configured as follow: Next I configure the DHCP ranges. Both firewalls will have different ranges, the backup node will have smaller ones (only 10 leases should be enough). On the master, they are configured as follow:
![OPNsense DHCP ranges in Dnsmasq](img/opnsense-dnsmasq-dhcp-ranges.png) ![OPNsense DHCP ranges in Dnsmasq](images/opnsense-dnsmasq-dhcp-ranges.png)
### DHCP Options ### DHCP Options
Then I set some DHCP options for each domain: the `router`, the `dns-server` and the `domain-name`. I'm pointing the IP addresses to the interface's VIP: Then I set some DHCP options for each domain: the `router`, the `dns-server` and the `domain-name`. I'm pointing the IP addresses to the interface's VIP:
![OPNsense DHCP options in Dnsmasq](img/opnsense-dnsmasq-dhcp-options.png) ![OPNsense DHCP options in Dnsmasq](images/opnsense-dnsmasq-dhcp-options.png)
### Hosts ### Hosts
Finally in in the `Hosts` tab, I define static DHCP mappings but also static IP not managed by the DHCP, to have them registered in the DNS: Finally in in the `Hosts` tab, I define static DHCP mappings but also static IP not managed by the DHCP, to have them registered in the DNS:
![Hôtes DHCP de Dnsmasq dans OPNsense](img/opnsense-dnsmasq-dhcp-hosts.png) ![OPNsense DHCP hosts in Dnsmasq](images/opnsense-dnsmasq-dhcp-hosts.png)
--- ---
## DNS ## DNS
@@ -370,7 +371,7 @@ Unbound is the recursive resolver, for local zones I forward queries to Dnsmasq.
### Unbound General Settings ### Unbound General Settings
Let's configure it, in `Services` > `Unbound DNS` > `General`: Let's configure it, in `Services` > `Unbound DNS` > `General`:
![OPNsense Unbound DNS general settings](img/opnsense-unbound-general-settings.png) ![OPNsense Unbound DNS general settings](images/opnsense-unbound-general-settings.png)
### DNS Blocklist ### DNS Blocklist
@@ -381,7 +382,7 @@ To maintain the service up to date, in `System` > `Settings` > `Cron`, I add my
### Query Forwarding ### Query Forwarding
Finally I configure query forwarding for my local domains to Dnsmasq. In `Services` > `Unbound DNS` > `Query Forwarding`, I add each of my local domains with their reverse lookup (PTR record): Finally I configure query forwarding for my local domains to Dnsmasq. In `Services` > `Unbound DNS` > `Query Forwarding`, I add each of my local domains with their reverse lookup (PTR record):
![Configuration du transfert de requêtes d'Unbound DNS dans OPNsense](img/opnsense-unbound-dns-query-forwarding.png) ![OPNsense Unbound DNS query forwarding configuration](images/opnsense-unbound-dns-query-forwarding.png)
--- ---
## VPN ## VPN
@@ -1,137 +0,0 @@
---
slug:
title: Template
description:
date:
draft: true
tags:
- opnsense
- high-availability
- proxmox
categories:
---
## Intro
In my previous [post]({{< ref "post/12-opnsense-virtualization-highly-available" >}}), I've set up a PoC to validate the possibility to create a cluster of 2 **OPNsense** VMs in **Proxmox VE** and make the firewall highly available.
This time, I will cover the creation of my future OPNsense cluster from scratch, plan the cut over and finally migrate from my current physical box.
---
## Build the Foundation
For the real thing, I'll have to connect the WAN, coming from my ISP box, to my main switch. For that I have to add a VLAN to transport this flow to my Proxmox nodes.
### UniFi
The first thing I do is to configure my layer 2 network which is managed by UniFi. There I need to create two VLANs:
- *WAN* (20): transport the WAN between my ISP box and my Proxmox nodes.
- *pfSync* (44), communication between my OPNsense nodes.
In the UniFi controller, in `Settings` > `Networks`, I add a `New Virtual Network`. I name it `WAN` and give it the VLAN ID 20:
![unifi-add-vlan-for-wan.png](img/unifi-add-vlan-for-wan.png)
I do the same thing again for the `pfSync` VLAN with the VLAN ID 44.
I will plug my ISP box on the port 15 of my switch, which is disabled for now. I set it as active, set the native VLAN on the newly created one `WAN (20)` and disable trunking:
![unifi-enable-port-wan-vlan.png](img/unifi-enable-port-wan-vlan.png)
Once this setting applied, I make sure that only the ports where are connected my Proxmox nodes propagate these VLAN on their trunk.
We are done with UniFi configuration.
### Proxmox SDN
Now that the VLAN can reach my nodes, I want to handle it in the Proxmox SDN.
In `Datacenter` > `SDN` > `VNets`, I create a new VNet, name it `vlan20` to follow my own naming convention, give it the *WAN* alias and use the tag (ID) 20:
![proxmox-sdn-new-vnet-wan.png](img/proxmox-sdn-new-vnet-wan.png)
I also create the `vlan44` for the *pfSync* VLAN, then I apply this configuration and we are done with the SDN.
---
## Create the VMs
Now that the VLAN configuration is done, I can start buiding the virtual machines on Proxmox.
The first VM is named `cerbere-head1` (I didn't tell you? My current firewall is named `cerbere`, it makes even more sense now!) Here are the settings:
- OS type: Linux
- Machine type: `q35`
- BIOS: `OVMF (UEFI)`
- Disk: 20 GiB on Ceph storage
- CPU/RAM: 2 vCPU, 4 GiB RAM
- NICs:
1. `vmbr0` (*Mgmt*)
2. `vlan20` (*WAN*)
3. `vlan13` *(User)*
4. `vlan37` *(IoT)*
5. `vlan44` *(pfSync)*
6. `vlan55` *(DMZ)*
7. `vlan66` *(Lab)*
![proxmox-cerbere-vm-settings.png](img/proxmox-cerbere-vm-settings.png)
ℹ️ Now I clone that VM to create `cerbere-head2`, then I proceed with OPNsense installation. I don't want to go into much details about OPNsense installation, I already documented it in the previous [post]({{< ref "post/12-opnsense-virtualization-highly-available" >}}).
After the installation of both OPNsense instances, I give to each of them their IP in the *Mgmt* network:
- `cerbere-head1`: `192.168.88.2/24`
- `cerbere-head2`: `192.168.88.3/24`
While these routers are not managing the networks, I give them my current OPNsense routeur as gateway (`192.168.88.1`) to able to reach them from my PC in another VLAN.
---
## Configure OPNsense
## TODO
HA in proxmox
Make sure VM start at proxmox boot
Check conso Watt average
Check temp average
## Switch
Backup OPNsense box
Disable DHCP on OPNsene box
Change OPNsense box IPs
Remove GW on VM
Configure DHCP on both instance
Enable DHCP on VM
Change VIP on VM
Replicate configuration on VM
Unplug OPNsense box WAN
Plug WAN on port 15
## Verify
Ping VIP
Vérifier interface
tests locaux (ssh, ping)
Basic (dhcp, dns, internet)
Firewall
All sites
mDNS (chromecast)
VPN
TV
Vérifier tous les devices
DNS blocklist
Check load (ram, cpu)
Failover
Test proxmox full shutdown
## Clean Up
Shutdown OPNsense
Check watt
Check temp
## Rollback
Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

@@ -0,0 +1,425 @@
---
slug: proxmox-cluster-upgrade-8-to-9-ceph
title: Mise à niveau de mon cluster Proxmox VE HA 3 nœuds de 8 vers 9 basé sur Ceph
description: Mise à niveau pas à pas de mon cluster Proxmox VE 3 nœuds en haute disponibilité, de 8 vers 9, basé sur Ceph, sans aucune interruption.
date: 2025-11-04
draft: false
tags:
- proxmox
- high-availability
- ceph
categories:
- homelab
image: cover-14.webp
---
## Intro
Mon **cluster Proxmox VE** a presque un an maintenant, et je n’ai pas tenu les nœuds complètement à jour. Il est temps de m’en occuper et de le passer en Proxmox VE **9**.
Je recherche principalement les nouvelles règles d’affinité HA, mais voici les changements utiles apportés par cette version :
- Debian 13 "Trixie".
- Snapshots pour le stockage LVM partagé thick-provisioned.
- Fonctionnalité SDN fabrics.
- Interface mobile améliorée.
- Règles d’affinité dans le cluster HA.
Le cluster est composée de 3 nœuds, hautement disponible, avec une configuration hyper‑convergée, utilisant Ceph pour le stockage distribué.
Dans cet article, je décris les étapes de mise à niveau de mon cluster Proxmox VE, de la version 8 vers 9, tout en gardant les ressources actives. [Documentation officielle](https://pve.proxmox.com/wiki/Upgrade_from_8_to_9).
---
## Prérequis
Avant de se lancer dans la mise à niveau, passons en revue les prérequis :
1. Tous les nœuds mis à jour vers la dernière version Proxmox VE `8.4`.
2. Cluster Ceph mis à niveau vers Squid (`19.2`).
3. Proxmox Backup Server mis à jour vers la version 4.
4. Accès fiable au nœud.
5. Cluster en bonne santé.
6. Sauvegarde de toutes les VM et CT.
7. Au moins 5 Go libres sur `/`.
Remarques sur mon environnement :
- Les nœuds PVE sont en `8.3.2`, donc une mise à jour mineure vers 8.4 est d’abord requise.
- Ceph tourne sous Reef (`18.2.4`) et sera mis à niveau vers Squid après PVE 8.4.
- Je n’utilise pas PBS dans mon homelab, donc je peux sauter cette étape.
- J’ai plus de 10 Go disponibles sur `/` sur mes nœuds, c’est suffisant.
- Je n’ai qu’un accès console SSH, si un nœud ne répond plus je pourrais avoir besoin d’un accès physique.
- Une VM a un passthrough CPU (APU). Le passthrough empêche la migration à chaud, donc je supprime ce mapping avant la mise à niveau.
- Mettre les OSD Ceph en `noout` pendant la mise à niveau pour éviter le rebalancing automatique :
```bash
ceph osd set noout
```
### Mettre à Jour Proxmox VE vers 8.4.14
Le plan est simple, pour tous les nœuds, un par un :
1. Activer le mode maintenance
```bash
ha-manager crm-command node-maintenance enable $(hostname)
```
2. Mettre à jour le nœud
```bash
apt-get update
apt-get dist-upgrade -y
```
À la fin de la mise à jour, on me propose de retirer booloader, ce que j’exécute :
```plaintext
Removable bootloader found at '/boot/efi/EFI/BOOT/BOOTX64.efi', but GRUB packages not set up to update it!
Run the following command:
echo 'grub-efi-amd64 grub2/force_efi_extra_removable boolean true' | debconf-set-selections -v -u
Then reinstall GRUB with 'apt install --reinstall grub-efi-amd64'
```
3. Redémarrer la machine
```bash
reboot
```
4. Désactiver le mode maintenance
```bash
ha-manager crm-command node-maintenance disable $(hostname)
```
Entre chaque nœud, j’attends que le statut Ceph soit clean, sans alertes.
✅ À la fin, le cluster Proxmox VE est mis à jour vers `8.4.14`
### Mettre à Niveau Ceph de Reef vers Squid
Je peux maintenant passer à la mise à niveau de Ceph, la documentation Proxmox pour cette procédure est [ici](https://pve.proxmox.com/wiki/Ceph_Reef_to_Squid).
Mettre à jour les sources de paquets Ceph sur chaque nœud :
```bash
sed -i 's/reef/squid/' /etc/apt/sources.list.d/ceph.list
```
Mettre à niveau les paquets Ceph :
```
apt update
apt full-upgrade -y
```
Après la mise à niveau sur le premier nœud, la version Ceph affiche maintenant `19.2.3`, je peux voir mes OSD apparaître comme obsolètes, les moniteurs nécessitent soit une mise à niveau soit un redémarrage :
![État du stockage Ceph dans Proxmox après la mise à jour des paquets Ceph du premier nœud](images/proxmox-ceph-version-upgrade.png)
Je poursuis et mets à niveau les paquets sur les 2 autres nœuds.
J’ai un moniteur sur chaque nœud, donc je dois redémarrer chaque moniteur, un nœud à la fois :
```bash
systemctl restart ceph-mon.target
```
Je vérifie le statut Ceph entre chaque redémarrage :
```bash
ceph status
```
Une fois tous les moniteurs redémarrés, ils rapportent la dernière version, avec `ceph mon dump` :
- Avant : `min_mon_release 18 (reef)`
- Après : `min_mon_release 19 (squid)`
Je peux maintenant redémarrer les OSD, toujours un nœud à la fois. Dans ma configuration, j’ai un OSD par nœud :
```bash
systemctl restart ceph-osd.target
```
Je surveille le statut Ceph avec la WebGUI Proxmox. Après le redémarrage, elle affiche quelques couleurs fancy. J’attends juste que les PG redeviennent verts, cela prend moins d’une minute :
![État du stockage Ceph dans Proxmox lors du premier redémarrage d'un OSD](images/proxmox-ceph-status-osd-restart.png)
Un avertissement apparaît : `HEALTH_WARN: all OSDs are running squid or later but require_osd_release < squid`
Maintenant tous mes OSD tournent sous Squid, je peux fixer la version minimum à celle‑ci :
```bash
ceph osd require-osd-release squid
```
ℹ️ Je n’utilise pas actuellement CephFS donc je n’ai pas à me soucier du daemon MDS (MetaData Server).
✅ Le cluster Ceph a été mis à niveau avec succès vers Squid (`19.2.3`).
---
## Vérifications
Les prérequis pour mettre à niveau le cluster vers Proxmox VE 9 sont maintenant complets. Suis‑je prêt à mettre à niveau ? Pas encore.
Un petit programme de checklist nommé **`pve8to9`** est inclus dans les derniers paquets Proxmox VE 8.4. Le programme fournit des indices et des alertes sur les problèmes potentiels avant, pendant et après la mise à niveau. Pratique non ?
Lancer l’outil la première fois me donne des indications sur ce que je dois faire. Le script vérifie un certain nombre de paramètres, regroupés par thème. Par exemple, voici la section sur les Virtual Guest :
```plaintext
= VIRTUAL GUEST CHECKS =
INFO: Checking for running guests..
WARN: 1 running guest(s) detected - consider migrating or stopping them.
INFO: Checking if LXCFS is running with FUSE3 library, if already upgraded..
SKIP: not yet upgraded, no need to check the FUSE library version LXCFS uses
INFO: Checking for VirtIO devices that would change their MTU...
PASS: All guest config descriptions fit in the new limit of 8 KiB
INFO: Checking container configs for deprecated lxc.cgroup entries
PASS: No legacy 'lxc.cgroup' keys found.
INFO: Checking VM configurations for outdated machine versions
PASS: All VM machine versions are recent enough
```
À la fin, vous avez le résumé. L’objectif est de corriger autant de `FAILURES` et `WARNINGS` que possible :
```plaintext
= SUMMARY =
TOTAL: 57
PASSED: 43
SKIPPED: 7
WARNINGS: 2
FAILURES: 2
```
Passons en revue les problèmes qu’il a trouvés :
```
FAIL: 1 custom role(s) use the to-be-dropped 'VM.Monitor' privilege and need to be adapted after the upgrade
```
Il y a quelque temps, pour utiliser Terraform avec mon cluster Proxmox, j'ai créé un rôle dédié. C'était détaillé dans cet [article]({{< ref "post/3-terraform-create-vm-proxmox" >}}).
Ce rôle utilise le privilège `VM.Monitor`, qui a été supprimé dans Proxmox VE 9. De nouveaux privilèges, sous `VM.GuestAgent.*`, existent à la place. Je supprime donc celui-ci et j'ajouterai les nouveaux une fois le cluster mis à niveau.
```
FAIL: systemd-boot meta-package installed. This will cause problems on upgrades of other boot-related packages. Remove 'systemd-boot' See https://pve.proxmox.com/wiki/Upgrade_from_8_to_9#sd-boot-warning for more information.
```
Proxmox VE utilise généralement `systemd-boot` pour le démarrage uniquement dans certaines configurations gérées par proxmox-boot-tool. Le méta-paquet `systemd-boot` doit être supprimé. Ce paquet était automatiquement installé sur les systèmes de PVE 8.1 à 8.4, car il contenait `bootctl` dans Bookworm.
Si le script de la checklist pve8to9 le suggère, vous pouvez supprimer le méta-paquet `systemd-boot` sans risque, sauf si vous l'avez installé manuellement et que vous utilisez `systemd-boot` comme bootloader :
```bash
apt remove systemd-boot -y
```
```
WARN: 1 running guest(s) detected - consider migrating or stopping them.
```
Dans une configuration HA, avant de mettre à jour un nœud, je le mets en mode maintenance. Cela déplace automatiquement les ressources ailleurs. Quand ce mode est désactivé, la machine revient à son emplacement précédent.
```
WARN: The matching CPU microcode package 'amd64-microcode' could not be found! Consider installing it to receive the latest security and bug fixes for your CPU.
Ensure you enable the 'non-free-firmware' component in the apt sources and run:
apt install amd64-microcode
```
Il est recommandé d’installer le microcode processeur pour les mises à jour qui peuvent corriger des bogues matériels, améliorer les performances et renforcer la sécurité du processeur.
J’ajoute la source `non-free-firmware` aux sources actuelles :
```bash
sed -i '/^deb /{/non-free-firmware/!s/$/ non-free-firmware/}' /etc/apt/sources.list
```
Puis installe le paquet `amd64-microcode` :
```bash
apt update
apt install amd64-microcode -y
```
Après ces petits ajustements, suis‑je prêt ? Vérifions en relançant le script `pve8to9`.
⚠️ N’oubliez pas de lancer `pve8to9` sur tous les nœuds pour vous assurer que tout est OK.
---
## Mise à Niveau
🚀 Maintenant tout est prêt pour le grand saut ! Comme pour la mise à jour mineure, je procéderai nœud par nœud, en gardant mes VM et CT actives.
### Mettre le Mode Maintenance
D’abord, j’entre le nœud en mode maintenance. Cela déplacera la charge existante sur les autres nœuds :
```bash
ha-manager crm-command node-maintenance enable $(hostname)
```
Après avoir exécuté la commande, j’attends environ une minute pour laisser le temps aux ressources de migrer.
### Changer les Dépôts Sources vers Trixie
Depuis Debian Trixie, le format `deb822` est désormais disponible et recommandé pour les sources. Il est structuré autour d’un format clé/valeur. Cela offre une meilleure lisibilité et sécurité.
#### Sources Debian
```bash
cat > /etc/apt/sources.list.d/debian.sources << EOF
Types: deb deb-src
URIs: http://deb.debian.org/debian/
Suites: trixie trixie-updates
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
Types: deb deb-src
URIs: http://security.debian.org/debian-security/
Suites: trixie-security
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
EOF
```
#### Sources Proxmox (sans subscription)
```bash
cat > /etc/apt/sources.list.d/proxmox.sources << EOF
Types: deb
URIs: http://download.proxmox.com/debian/pve
Suites: trixie
Components: pve-no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
EOF
```
#### Sources Ceph Squid (sans subscription)
```bash
cat > /etc/apt/sources.list.d/ceph.sources << EOF
Types: deb
URIs: http://download.proxmox.com/debian/ceph-squid
Suites: trixie
Components: no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
EOF
```
#### Supprimer les Anciennes Listes Bookworm
Les listes pour Debian Bookworm au format ancien doivent être supprimées :
```bash
rm -f /etc/apt/sources.list{,.d/*.list}
```
### Mettre à Jour les Dépôts `apt` Configurés
Rafraîchir les dépôts :
```bash
apt update
```
```plaintext
Get:1 http://security.debian.org/debian-security trixie-security InRelease [43.4 kB]
Get:2 http://deb.debian.org/debian trixie InRelease [140 kB]
Get:3 http://download.proxmox.com/debian/ceph-squid trixie InRelease [2,736 B]
Get:4 http://download.proxmox.com/debian/pve trixie InRelease [2,771 B]
Get:5 http://deb.debian.org/debian trixie-updates InRelease [47.3 kB]
Get:6 http://security.debian.org/debian-security trixie-security/main Sources [91.1 kB]
Get:7 http://security.debian.org/debian-security trixie-security/non-free-firmware Sources [696 B]
Get:8 http://security.debian.org/debian-security trixie-security/main amd64 Packages [69.0 kB]
Get:9 http://security.debian.org/debian-security trixie-security/main Translation-en [45.1 kB]
Get:10 http://security.debian.org/debian-security trixie-security/non-free-firmware amd64 Packages [544 B]
Get:11 http://security.debian.org/debian-security trixie-security/non-free-firmware Translation-en [352 B]
Get:12 http://download.proxmox.com/debian/ceph-squid trixie/no-subscription amd64 Packages [33.2 kB]
Get:13 http://deb.debian.org/debian trixie/main Sources [10.5 MB]
Get:14 http://download.proxmox.com/debian/pve trixie/pve-no-subscription amd64 Packages [241 kB]
Get:15 http://deb.debian.org/debian trixie/non-free-firmware Sources [6,536 B]
Get:16 http://deb.debian.org/debian trixie/contrib Sources [52.3 kB]
Get:17 http://deb.debian.org/debian trixie/main amd64 Packages [9,669 kB]
Get:18 http://deb.debian.org/debian trixie/main Translation-en [6,484 kB]
Get:19 http://deb.debian.org/debian trixie/contrib amd64 Packages [53.8 kB]
Get:20 http://deb.debian.org/debian trixie/contrib Translation-en [49.6 kB]
Get:21 http://deb.debian.org/debian trixie/non-free-firmware amd64 Packages [6,868 B]
Get:22 http://deb.debian.org/debian trixie/non-free-firmware Translation-en [4,704 B]
Get:23 http://deb.debian.org/debian trixie-updates/main Sources [2,788 B]
Get:24 http://deb.debian.org/debian trixie-updates/main amd64 Packages [5,412 B]
Get:25 http://deb.debian.org/debian trixie-updates/main Translation-en [4,096 B]
Fetched 27.6 MB in 3s (8,912 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
666 packages can be upgraded. Run 'apt list --upgradable' to see them.
```
😈 666 paquets, je suis condamné !
### Mise à Niveau vers Debian Trixie et Proxmox VE 9
Lancer la mise à niveau :
```bash
apt-get dist-upgrade -y
```
Pendant le processus, vous serez invité à approuver des changements de fichiers de configuration et certains redémarrages de services. Il se peut aussi que vous voyiez la sortie de certains changements, vous pouvez simplement en sortir en appuyant sur `q` :
- `/etc/issue` : Proxmox VE régénérera automatiquement ce fichier au démarrage -> `No`
- `/etc/lvm/lvm.conf` : Changements pertinents pour Proxmox VE seront mis à jour -> `Yes`
- `/etc/ssh/sshd_config` : Selon votre configuration -> `Inspect`
- `/etc/default/grub` : Seulement si vous l’avez modifié manuellement -> `Inspect`
- `/etc/chrony/chrony.conf` : Si vous n’avez pas fait de modifications supplémentaires -> `Yes`
La mise à niveau a pris environ 5 minutes, selon le matériel.
À la fin de la mise à niveau, redémarrez la machine :
```bash
reboot
```
### Sortir du Mode Maintenance
Enfin, quand le nœud (espérons‑le) est revenu, vous pouvez désactiver le mode maintenance. La charge qui était localisée sur cette machine reviendra :
```bash
ha-manager crm-command node-maintenance disable $(hostname)
```
### Validation Après Mise à Niveau
- Vérifier la communication du cluster :
```bash
pvecm status
```
- Vérifier les points de montage des stockages
- Vérifier la santé du cluster Ceph :
```bash
ceph status
```
- Confirmer les opérations VM, les sauvegardes et les groupes HA
Les groupes HA ont été retirés au profit des règles d’affinité HA. Les groupes HA sont automatiquement migrés en règles HA.
- Désactiver le dépôt PVE Enterprise
Si vous n’utilisez pas le dépôt `pve-enterprise`, vous pouvez le désactiver : `` ```
```bash
sed -i 's/^/#/' /etc/apt/sources.list.d/pve-enterprise.sources
```
🔁 Ce nœud est maintenant mis à niveau vers Proxmox VE 9. Procédez aux autres nœuds.
## Actions Postérieures
Une fois que tout le cluster a été mis à niveau, procédez aux actions postérieures :
- Supprimer le flag `noout` du cluster Ceph :
```bash
ceph osd unset noout
```
- Recréer les mappings PCI passthrough
Pour la VM pour laquelle j’ai retiré le mapping hôte au début de la procédure, je peux maintenant recréer le mapping.
- Ajouter les privilèges pour le rôle Terraform
Pendant la phase de vérification, il m’a été conseillé de supprimer le privilège `VM.Monitor` de mon rôle personnalisé pour Terraform. Maintenant que de nouveaux privilèges ont été ajoutés avec Proxmox VE 9, je peux les attribuer à ce rôle :
- VM.GuestAgent.Audit
- VM.GuestAgent.FileRead
- VM.GuestAgent.FileWrite
- VM.GuestAgent.FileSystemMgmt
- VM.GuestAgent.Unrestricted
## Conclusion
🎉 Mon cluster Proxmox VE est maintenant en version 9 !
Le processus de mise à niveau s’est déroulé assez tranquillement, sans aucune interruption pour mes ressources.
J’ai maintenant accès aux règles d’affinité HA, dont j’avais besoin pour mon cluster OPNsense.
Comme vous avez pu le constater, je ne maintiens pas mes nœuds à jour très souvent. Je pourrais automatiser cela la prochaine fois, pour les garder à jour sans effort.
@@ -0,0 +1,426 @@
---
slug: proxmox-cluster-upgrade-8-to-9-ceph
title: Upgrading my 3-node Proxmox VE HA Cluster from 8 to 9 based on Ceph
description: Step-by-step upgrade of my 3-node Proxmox VE highly available cluster from 8 to 9, based on Ceph distributed storage, without any downtime.
date: 2025-11-04
draft: false
tags:
- proxmox
- high-availability
- ceph
categories:
- homelab
image: cover-14.webp
---
## Intro
My **Proxmox VE** cluster is almost one year old now, and I haven’t kept the nodes fully up to date. Time to address this and bump it to Proxmox VE **9**.
I'm mainly after the new HA affinity rules, but here the useful changes that this version brings:
- Debian 13 "Trixie".
- Snapshots for thick-provisioned LVM shared storage.
- SDN fabrics feature.
- Improved mobile UI.
- Affinity rules in HA cluster.
The cluster is a three‑node, highly available, hyper‑converged setup using Ceph for distributed storage.
In this article, I'll walk through the upgrade steps for my Proxmox VE cluster, from 8 to 9, while keeping the resources up and running. [Official docs](https://pve.proxmox.com/wiki/Upgrade_from_8_to_9).
---
## Prerequisites
Before jumping into the upgrade, let's review the prerequisites:
1. All nodes upgraded to the latest Proxmox VE `8.4`.
2. Ceph cluster upgraded to Squid (`19.2`).
3. Proxmox Backup Server upgraded to version 4.
4. Reliable access to the node.
5. Healthy cluster.
6. Backup of all VMs and CTs.
7. At least 5 GB free on `/`.
Notes about my environment:
- PVE nodes are on `8.3.2`, so a minor upgrade to 8.4 is required first.
- Ceph is Reef (`18.2.4`) and will be upgraded to Squid after PVE 8.4.
- I don’t use PBS in my homelab, so I can skip that step.
- I have more than 10GB available on `/` on my nodes, this is fine.
- I only have SSH console access, if a node becomes unresponsive I may need physical access.
- One VM has a CPU passthrough (APU). Passthrough prevents live‑migration, so I remove that mapping prior to the upgrade.
- Set Ceph OSDs to `noout` during the upgrade to avoid automatic rebalancing:
```bash
ceph osd set noout
```
### Update Proxmox VE to 8.4.14
The plan is simple, for all nodes, one at a time:
1. Enable the maintenance mode
```bash
ha-manager crm-command node-maintenance enable $(hostname)
```
2. Update the node
```bash
apt-get update
apt-get dist-upgrade -y
```
At the end of the update, I'm invited to remove a bootloader, which I execute:
```plaintext
Removable bootloader found at '/boot/efi/EFI/BOOT/BOOTX64.efi', but GRUB packages not set up to update it!
Run the following command:
echo 'grub-efi-amd64 grub2/force_efi_extra_removable boolean true' | debconf-set-selections -v -u
Then reinstall GRUB with 'apt install --reinstall grub-efi-amd64'
```
3. Restart the machine
```bash
reboot
```
4. Disable the maintenance node
```bash
ha-manager crm-command node-maintenance disable $(hostname)
```
Between each node, I wait for the Ceph status to be clean, without warnings.
✅ At the end, the Proxmox VE cluster is updated to `8.4.14`
### Upgrade Ceph from Reef to Squid
I can now move on into the Ceph upgrade, the Proxmox documentation for that procedure is [here](https://pve.proxmox.com/wiki/Ceph_Reef_to_Squid).
Update Ceph package sources on every node:
```bash
sed -i 's/reef/squid/' /etc/apt/sources.list.d/ceph.list
```
Upgrade the Ceph packages:
```
apt update
apt full-upgrade -y
```
After the upgrade on the first node, the Ceph version now shows `19.2.3`, I can see my OSDs appear as outdated, the monitors need either an upgrade or a restart:
![Ceph storage status in Proxmox after first node Ceph package udpate](images/proxmox-ceph-version-upgrade.png)
I carry on and upgrade the packages on the 2 other nodes.
I have a monitor on each node, so I have to restart each monitor, one node at a time:
```bash
systemctl restart ceph-mon.target
```
I verify the Ceph status between each restart:
```bash
ceph status
```
Once all monitors are restarted, they report the latest version, with `ceph mon dump`:
- Before: `min_mon_release 18 (reef)`
- After: `min_mon_release 19 (squid)`
Now I can restart the OSDs, still one node at a time. In my setup, I have one OSD per node:
```bash
systemctl restart ceph-osd.target
```
I monitor the Ceph status with the Proxmox WebGUI. After the restart, it is showing some fancy colors. I'm just waiting for the PGs to be back to green, it takes less than a minute:
![Ceph storage status in Proxmox during the first OSD restart](images/proxmox-ceph-status-osd-restart.png)
A warning shows up: `HEALTH_WARN: all OSDs are running squid or later but require_osd_release < squid`
Now all my OSDs are running Squid, I can set the minimum version to it:
```bash
ceph osd require-osd-release squid
```
ℹ️ I'm not currently using CephFS so I don't have to care about the MDS (MetaData Server) daemon.
✅ The Ceph cluster has been successfully upgraded to Squid (`19.2.3`).
---
## Checks
The prerequisites to upgrade the cluster to Proxmox VE 9 are now complete. Am I ready to upgrade? Not yet.
A small checklist program named **`pve8to9`** is included in the latest Proxmox VE 8.4 packages. The program will provide hints and warnings about potential issues before, during and after the upgrade process. Pretty handy isn't it?
Running the tool the first time give me some insights on what I need to do. The script checks a number of parameters, grouped by theme. For example, this is the Virtual Guest section:
```plaintext
= VIRTUAL GUEST CHECKS =
INFO: Checking for running guests..
WARN: 1 running guest(s) detected - consider migrating or stopping them.
INFO: Checking if LXCFS is running with FUSE3 library, if already upgraded..
SKIP: not yet upgraded, no need to check the FUSE library version LXCFS uses
INFO: Checking for VirtIO devices that would change their MTU...
PASS: All guest config descriptions fit in the new limit of 8 KiB
INFO: Checking container configs for deprecated lxc.cgroup entries
PASS: No legacy 'lxc.cgroup' keys found.
INFO: Checking VM configurations for outdated machine versions
PASS: All VM machine versions are recent enough
```
At the end, you have the summary. The goal is to address as many `FAILURES` and `WARNINGS` as possible:
```plaintext
= SUMMARY =
TOTAL: 57
PASSED: 43
SKIPPED: 7
WARNINGS: 2
FAILURES: 2
```
Let's review the problems it found:
```
FAIL: 1 custom role(s) use the to-be-dropped 'VM.Monitor' privilege and need to be adapted after the upgrade
```
Some time ago, in order to use Terraform with my Proxmox cluster, I created a dedicated role. This was detailed in that [post]({{< ref "post/3-terraform-create-vm-proxmox" >}}).
This role is using the `VM.Monitor` privilege, which is removed in Proxmox VE 9. Instead, new privileges under `VM.GuestAgent.*` exist. So I remove this one and I'll add those once the cluster have been upgraded.
```
FAIL: systemd-boot meta-package installed. This will cause problems on upgrades of other boot-related packages. Remove 'systemd-boot' See https://pve.proxmox.com/wiki/Upgrade_from_8_to_9#sd-boot-warning for more information.
```
Proxmox VE usually uses `systemd-boot` for booting only in some configurations which are managed by `proxmox-boot-tool`, the meta-package `systemd-boot` should be removed. The package was automatically shipped for systems installed from the PVE 8.1 to PVE 8.4, as it contained `bootctl` in Bookworm.
If the `pve8to9` checklist script suggests it, the `systemd-boot` meta-package is safe to remove unless you manually installed it and are using `systemd-boot` as a bootloader:
```bash
apt remove systemd-boot -y
```
```
WARN: 1 running guest(s) detected - consider migrating or stopping them.
```
In HA setup, before updating a node, I put it in maintenance mode. This automatically moves the workload elsewhere. When this mode is disabled, the workload moves back to its previous location.
```
WARN: The matching CPU microcode package 'amd64-microcode' could not be found! Consider installing it to receive the latest security and bug fixes for your CPU.
Ensure you enable the 'non-free-firmware' component in the apt sources and run:
apt install amd64-microcode
```
It is recommended to install processor microcode for updates which can fix hardware bugs, improve performance, and enhance security features of the processor.
I add the `non-free-firmware` source to the current ones:
```bash
sed -i '/^deb /{/non-free-firmware/!s/$/ non-free-firmware/}' /etc/apt/sources.list
```
Then install the `amd64-microcode` package:
```bash
apt update
apt install amd64-microcode -y
```
After these small adjustments, am I ready yet? Let's find out by relaunching the `pve8to9` script.
⚠️ Don't forget to run the `pve8to9` on all nodes to make sure everything is good.
---
## Upgrade
🚀 Now everything is ready for the big move! Like I did for the minor update, I'll proceed one node at a time, keeping my VMs and CTs up and running.
### Set Maintenance Mode
First, I enter the node into maintenance mode. This will move existing workload on other nodes:
```bash
ha-manager crm-command node-maintenance enable $(hostname)
```
After issuing the command, I wait about one minute to give the resources the time to migrate.
### Change Source Repositories to Trixie
Since Debian Trixie, the `deb822` format is now available and recommended for sources. It is structured around key/value format. This offers better readability and security.
#### Debian Sources
```bash
cat > /etc/apt/sources.list.d/debian.sources << EOF
Types: deb deb-src
URIs: http://deb.debian.org/debian/
Suites: trixie trixie-updates
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
Types: deb deb-src
URIs: http://security.debian.org/debian-security/
Suites: trixie-security
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
EOF
```
#### Proxmox Sources (without subscription)
```bash
cat > /etc/apt/sources.list.d/proxmox.sources << EOF
Types: deb
URIs: http://download.proxmox.com/debian/pve
Suites: trixie
Components: pve-no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
EOF
```
#### Ceph Squid Sources (without subscription)
```bash
cat > /etc/apt/sources.list.d/ceph.sources << EOF
Types: deb
URIs: http://download.proxmox.com/debian/ceph-squid
Suites: trixie
Components: no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
EOF
```
#### Remove Old Bookworm Source Lists
The lists for Debian Bookworm in the old format must be removed:
```bash
rm -f /etc/apt/sources.list{,.d/*.list}
```
### Update the Configured `apt` Repositories
Refresh the repositories:
```bash
apt update
```
```plaintext
Get:1 http://security.debian.org/debian-security trixie-security InRelease [43.4 kB]
Get:2 http://deb.debian.org/debian trixie InRelease [140 kB]
Get:3 http://download.proxmox.com/debian/ceph-squid trixie InRelease [2,736 B]
Get:4 http://download.proxmox.com/debian/pve trixie InRelease [2,771 B]
Get:5 http://deb.debian.org/debian trixie-updates InRelease [47.3 kB]
Get:6 http://security.debian.org/debian-security trixie-security/main Sources [91.1 kB]
Get:7 http://security.debian.org/debian-security trixie-security/non-free-firmware Sources [696 B]
Get:8 http://security.debian.org/debian-security trixie-security/main amd64 Packages [69.0 kB]
Get:9 http://security.debian.org/debian-security trixie-security/main Translation-en [45.1 kB]
Get:10 http://security.debian.org/debian-security trixie-security/non-free-firmware amd64 Packages [544 B]
Get:11 http://security.debian.org/debian-security trixie-security/non-free-firmware Translation-en [352 B]
Get:12 http://download.proxmox.com/debian/ceph-squid trixie/no-subscription amd64 Packages [33.2 kB]
Get:13 http://deb.debian.org/debian trixie/main Sources [10.5 MB]
Get:14 http://download.proxmox.com/debian/pve trixie/pve-no-subscription amd64 Packages [241 kB]
Get:15 http://deb.debian.org/debian trixie/non-free-firmware Sources [6,536 B]
Get:16 http://deb.debian.org/debian trixie/contrib Sources [52.3 kB]
Get:17 http://deb.debian.org/debian trixie/main amd64 Packages [9,669 kB]
Get:18 http://deb.debian.org/debian trixie/main Translation-en [6,484 kB]
Get:19 http://deb.debian.org/debian trixie/contrib amd64 Packages [53.8 kB]
Get:20 http://deb.debian.org/debian trixie/contrib Translation-en [49.6 kB]
Get:21 http://deb.debian.org/debian trixie/non-free-firmware amd64 Packages [6,868 B]
Get:22 http://deb.debian.org/debian trixie/non-free-firmware Translation-en [4,704 B]
Get:23 http://deb.debian.org/debian trixie-updates/main Sources [2,788 B]
Get:24 http://deb.debian.org/debian trixie-updates/main amd64 Packages [5,412 B]
Get:25 http://deb.debian.org/debian trixie-updates/main Translation-en [4,096 B]
Fetched 27.6 MB in 3s (8,912 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
666 packages can be upgraded. Run 'apt list --upgradable' to see them.
```
😈 666 packages, I'm doomed!
### Upgrade to Debian Trixie and Proxmox VE 9
Launch the upgrade:
```bash
apt-get dist-upgrade -y
```
During the process , you will be prompted to approve changes to configuration files and some service restarts. You may also be shown the output of changes, you can simply exit there by pressing `q`:
- `/etc/issue`: Proxmox VE will auto-generate this file on boot -> `No`
- `/etc/lvm/lvm.conf`: Changes relevant for Proxmox VE will be updated -> `Yes`
- `/etc/ssh/sshd_config`: Depending your setup -> `Inspect`
- `/etc/default/grub`: Only if you changed it manually -> `Inspect`
- `/etc/chrony/chrony.conf`: If you did not make extra changes yourself -> `Yes`
The upgrade took about 5 minutes, depending of the hardware.
At the end of the upgrade, restart the machine:
```bash
reboot
```
### Remove Maintenance Mode
Finally when the node (hopefully) comes back, you can disable the maintenance mode. The workload which was located on that machine will come back:
```bash
ha-manager crm-command node-maintenance disable $(hostname)
```
### Post-Upgrade Validation
- Check cluster communication:
```bash
pvecm status
```
- Verify storage mounts points
- Check Ceph cluster health :
```bash
ceph status
```
- Confirm VM operations, backups, and HA groups
HA groups have been removed at the profit of HA affinity rules. HA groups are automatically migrated to HA rules.
- Disable PVE Enterprise repository
If you don't use the `pve-enterprise` repo, you can disable it:
```bash
sed -i 's/^/#/' /etc/apt/sources.list.d/pve-enterprise.sources
```
🔁 This node is now upgraded to Proxmox VE 9. Proceed to other nodes.
## Post Actions
Once the whole cluster has been upgraded, proceed to post actions:
- Remove the Ceph cluster `noout` flag:
```bash
ceph osd unset noout
```
- Recreate PCI passthrough mapping
For the VM which I removed the host mapping at the beginning of the procedure, I can now recreate the mapping.
- Add privileges for the Terraform role
During the check phase, I was advised to remove the privilege `VM.Monitor` from my custom role for Terraform. Now that new privileges have been added with Proxmox VE 9, I can assign them to that role:
- VM.GuestAgent.Audit
- VM.GuestAgent.FileRead
- VM.GuestAgent.FileWrite
- VM.GuestAgent.FileSystemMgmt
- VM.GuestAgent.Unrestricted
## Conclusion
🎉My Proxmox VE cluster is now is version 9!
The upgrade process was pretty smooth, without any downtime for my resources.
Now I have access to HA affinity rules, which I was needing for my OPNsense cluster.
As you could observe, I'm not maintaining my node up to date quite often. I might automate this next time, to keep them updated without any effort.
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Before

Width:  |  Height:  |  Size: 233 KiB

After

Width:  |  Height:  |  Size: 233 KiB

@@ -0,0 +1,424 @@
---
slug: migration-opnsense-proxmox-highly-available
title: Migration vers mon cluster OPNsense HA dans Proxmox VE
description: La démarche détaillée de la migration de ma box OPNsense physique vers un cluster de VM hautement disponible dans Proxmox VE.
date: 2025-11-20
draft: false
tags:
- opnsense
- high-availability
- proxmox
categories:
- homelab
image: cover-15.webp
---
## Intro
C'est la dernière étape de mon aventure de virtualisation d'**OPNsense**.
Il y a quelques mois, ma [box OPNsense physique a crash]({{< ref "post/10-opnsense-crash-disk-panic" >}}) à cause d'une défaillance matérielle. Cela a plongé ma maison dans le noir, littéralement. Pas de réseau, pas de lumières.
💡 Pour éviter de me retrouver à nouveau dans cette situation, j'ai imaginé un plan pour virtualiser mon pare-feu OPNsense dans mon cluster **Proxmox VE**. La dernière fois, j'avais mis en place un [proof of concept]({{< ref "post/12-opnsense-virtualization-highly-available" >}}) pour valider cette solution : créer un cluster de deux VM **OPNsense** dans Proxmox et rendre le firewall hautement disponible.
Cette fois, je vais couvrir la création de mon futur cluster OPNsense depuis zéro, planifier la bascule et finalement migrer depuis ma box physique actuelle. C'est parti !
---
## La Configuration VLAN
Pour mes plans, je dois connecter le WAN, provenant de ma box FAI, à mon switch principal. Pour cela je crée un VLAN dédié pour transporter ce flux jusqu'à mes nœuds Proxmox.
### UniFi
D'abord, je configure mon réseau de couche 2 qui est géré par UniFi. Là, je dois créer deux VLANs :
- _WAN_ (20) : transporte le WAN entre ma box FAI et mes nœuds Proxmox.
- _pfSync_ (44), communication entre mes nœuds OPNsense.
Dans le contrôleur UniFi, dans `Paramètres` > `Réseaux`, j'ajoute un `New Virtual Network`. Je le nomme `WAN` et lui donne l'ID VLAN 20 :
![Création du VLAN WAN dans le contrôleur UniFi](images/unifi-add-vlan-for-wan.png)
Je fais la même chose pour le VLAN `pfSync` avec l'ID VLAN 44.
Je prévois de brancher ma box FAI sur le port 15 de mon switch, qui est désactivé pour l'instant. Je l'active, définis le VLAN natif sur le nouveau `WAN (20)` et désactive le trunking :
![Configuration du port du switch UniFi pour la liaison WAN](images/unifi-enable-port-wan-vlan.png)
Une fois ce réglage appliqué, je m'assure que seules les ports où sont connectés mes nœuds Proxmox propagent ces VLANs sur leur trunk.
J'ai fini la configuration UniFi.
### Proxmox SDN
Maintenant que le VLAN peut atteindre mes nœuds, je veux le gérer dans le SDN de Proxmox. J'ai configuré le SDN dans [cet article]({{< ref "post/11-proxmox-cluster-networking-sdn" >}}).
Dans `Datacenter` > `SDN` > `VNets`, je crée un nouveau VNet, je l'appelle `vlan20` pour suivre ma propre convention de nommage, je lui donne l'alias _WAN_ et j'utilise le tag (ID VLAN) 20 :
![Création du VNet pour le WAN dans le SDN Proxmox](images/proxmox-sdn-new-vnet-wan.png)
Je crée aussi le `vlan44` pour le VLAN _pfSync_, puis j'applique cette configuration et nous avons terminé avec le SDN.
---
## Création des VMs
Maintenant que la configuration VLAN est faite, je peux commencer à construire les machines virtuelles sur Proxmox.
La première VM s'appelle `cerbere-head1` (je ne vous l'ai pas dit ? Mon firewall actuel s'appelle `cerbere`, ça a encore plus de sens maintenant !). Voici les réglages :
- **Type d'OS** : Linux (même si OPNsense est basé sur FreeBSD)
- **Type de machine** : `q35`
- **BIOS** : `OVMF (UEFI)`
- **Disque** : 20 Go sur stockage Ceph distribué
- **RAM** : 4 Go, ballooning désactivé
- **CPU** : 2 vCPU
- **NICs**, pare-feu désactivé :
1. `vmbr0` (_Mgmt_)
2. `vlan20` (_WAN_)
3. `vlan13` _(User)_
4. `vlan37` _(IoT)_
5. `vlan44` _(pfSync)_
6. `vlan55` _(DMZ)_
7. `vlan66` _(Lab)_
![Paramètres matériels de la VM OPNsense dans Proxmox](images/proxmox-cerbere-vm-settings.png)
ℹ️ Maintenant je clone cette VM pour créer `cerbere-head2`, puis je procède à l'installation d'OPNsense. Je ne veux pas entrer trop dans les détails de l'installation d'OPNsense, je l'ai déjà documentée dans le [proof of concept]({{< ref "post/12-opnsense-virtualization-highly-available" >}}).
Après l'installation des deux instances OPNsense, j'attribue à chacune leur IP sur le réseau _Mgmt_ :
- `cerbere-head1` : `192.168.88.2/24`
- `cerbere-head2` : `192.168.88.3/24`
Tant que ces routeurs ne gèrent pas encore les réseaux, je leur donne comme passerelle mon routeur OPNsense actuel (`192.168.88.1`) pour me permettre de les atteindre depuis mon portable dans un autre VLAN.
---
## Configuration d'OPNsense
Initialement, j'envisageais de restaurer ma configuration OPNsense existante et de l'adapter à l'installation.
Puis j'ai décidé de repartir de zéro pour documenter et partager la procédure. Cette partie devenant trop longue, j'ai préféré créer un article dédié.
📖 Vous pouvez trouver les détails de la configuration complète d'OPNsense dans cet [article]({{< ref "post/13-opnsense-full-configuration" >}}), couvrant HA, DNS, DHCP, VPN et reverse proxy.
---
## VM Proxmox Hautement Disponible
Les ressources (VM ou LXC) dans Proxmox VE peuvent être marquées comme hautement disponibles, voyons comment les configurer.
### Prérequis pour la HA Proxmox
D'abord, votre cluster Proxmox doit le permettre. Il y a quelques exigences :
- Au moins 3 nœuds pour avoir le quorum
- Stockage partagé pour vos ressources
- Horloge synchronisée
- Réseau fiable
Un mécanisme de fencing doit être activé. Le fencing est le processus d'isoler un nœud de cluster défaillant pour s'assurer qu'il n'accède plus aux ressources partagées. Cela évite les situations de split-brain et permet à Proxmox HA de redémarrer en toute sécurité les VM affectées sur des nœuds sains. Par défaut, il utilise le watchdog logiciel Linux, _softdog_, suffisant pour moi.
Dans Proxmox VE 8, il était possible de créer des groupes HA, en fonction de leurs ressources, emplacements, etc. Cela a été remplacé, dans Proxmox VE 9, par des règles d'affinité HA. C'est la raison principale derrière la mise à niveau de mon cluster Proxmox VE, que j'ai détaillée dans ce [post]({{< ref "post/14-proxmox-cluster-upgrade-8-to-9-ceph" >}}).
### Configurer la HA pour les VM
Le cluster Proxmox est capable de fournir de la HA pour les ressources, mais vous devez définir les règles.
Dans `Datacenter` > `HA`, vous pouvez voir le statut et gérer les ressources. Dans le panneau `Resources` je clique sur `Add`. Je dois choisir la ressource à configurer en HA dans la liste, ici `cerbere-head1` avec l'ID 122. Puis dans l'infobulle je peux définir le maximum de redémarrages et de relocations, je laisse `Failback` activé et l'état demandé à `started` :
![Créer une ressource HA dans Proxmox](images/proxmox-add-vm-ha.png)
Le cluster Proxmox s'assurera maintenant que cette VM est démarrée. Je fais de même pour l'autre VM OPNsense, `cerbere-head2`.
### Règles d'Affinité HA
Super, mais je ne veux pas qu'elles tournent sur le même nœud. C'est là qu'intervient la nouvelle fonctionnalité des règles d'affinité HA de Proxmox VE 9. Proxmox permet de créer des règles d'affinité de nœud et de ressource. Peu m'importe sur quel nœud elles tournent, mais je ne veux pas qu'elles soient ensemble. J'ai besoin d'une règle d'affinité de ressource.
Dans `Datacenter` > `HA` > `Affinity Rules`, j'ajoute une nouvelle règle d'affinité de ressource HA. Je sélectionne les deux VMs et choisis l'option `Keep Separate` :
![Créer une affinité de ressource HA dans Proxmox](images/proxmox-ha-resource-affinity-rule.png)
✅ Mes VMs OPNsense sont maintenant entièrement prêtes !
---
## Migration
🚀 Il est temps de rendre cela réel !
Je ne vais pas mentir, je suis assez excité. Je travaille pour ce moment depuis des jours.
### Le Plan de Migration
Ma box OPNsense physique est directement connectée à ma box FAI. Je veux la remplacer par le cluster de VM. (Pour éviter d'écrire le mot OPNsense à chaque ligne, j'appellerai simplement l'ancienne instance "la box" et la nouvelle "la VM" )
Voici le plan :
1. Sauvegarde de la configuration de la box.
2. Désactiver le serveur DHCP sur la box.
3. Changer les adresses IP de la box.
4. Changer les VIP sur la VM.
5. Désactiver la passerelle sur la VM.
6. Configurer le DHCP sur les deux VMs.
7. Activer le répéteur mDNS sur la VM.
8. Répliquer les services sur la VM.
9. Déplacement du câble Ethernet.
### Stratégie de Retour Arrière
Aucune. 😎
Je plaisante, le retour arrière consiste à restaurer la configuration de la box, arrêter les VMs OPNsense et rebrancher le câble Ethernet dans la box.
### Plan de vérification
Pour valider la migration, je dresse une checklist :
1. Bail DHCP WAN dans la VM.
2. Ping depuis mon PC vers le VIP du VLAN User.
3. Ping entre les VLANs.
4. SSH vers mes machines.
5. Renouveler le bail DHCP.
6. Vérifier `ipconfig`
7. Tester l'accès à des sites internet.
8. Vérifier les logs du pare-feu.
9. Vérifier mes services web.
10. Vérifier que mes services internes ne sont pas accessibles depuis l'extérieur.
11. Tester le VPN.
12. Vérifier tous les appareils IoT.
13. Vérifier les fonctionnalités Home Assistant.
14. Vérifier que la TV fonctionne.
15. Tester le Chromecast.
16. Imprimer quelque chose.
17. Vérifier la blocklist DNS.
18. Speedtest.
19. Bascule.
20. Failover.
21. Reprise après sinistre.
22. Champagne !
Est-ce que ça va marcher ? On verra bien !
### Étapes de Migration
1. **Sauvegarde de la configuration de la box.**
Sur mon instance OPNsense physique, dans `System` > `Configuration` > `Backups`, je clique sur le bouton `Download configuration` qui me donne le précieux fichier XML. Celui qui m'a sauvé la mise la [dernière fois]({{< ref "post/10-opnsense-crash-disk-panic" >}}).
2. **Désactiver le serveur DHCP sur la box.**
Dans `Services` > `ISC DHCPv4`, et pour toutes mes interfaces, je désactive le serveur DHCP. Je ne fournis que du DHCPv4 dans mon réseau.
3. **Changer les adresses IP de la box.**
Dans `Interfaces`, et pour toutes mes interfaces, je modifie l'IP du firewall, de `.1` à `.253`. Je veux réutiliser la même adresse IP comme VIP, et garder cette instance encore joignable si besoin.
Dès que je clique sur `Apply`, je perds la communication, ce qui est attendu.
4. **Changer les VIP sur la VM.**
Sur ma VM maître, dans `Interfaces` > `Virtual IPs` > `Settings`, je change l'adresse VIP pour chaque interface et la mets en `.1`.
5. **Désactiver la passerelle sur la VM.**
Dans `System` > `Gateways` > `Configuration`, je désactive `LAN_GW` qui n'est plus nécessaire.
6. **Configurer le DHCP sur les deux VMs.**
Sur les deux VMs, dans `Services` > `Dnsmasq DNS & DHCP`, j'active le service sur mes 5 interfaces.
7. **Activer le répéteur mDNS sur la VM.**
Dans `Services` > `mDNS Repeater`, j'active le service et j'active aussi le `CARP Failover`.
Le service ne démarre pas. Je verrai ce problème plus tard.
8. **Répliquer les services sur la VM.**
Dans `Système` > `High Availability` > `Status`, je clique sur le bouton `Synchronize and reconfigure all`.
9. **Déplacement du câble Ethernet.**
Physiquement dans mon rack, je débranche le câble Ethernet du port WAN (`igc0`) de ma box OPNsense physique et je le branche sur le port 15 de mon switch UniFi.
---
## Vérification
😮‍💨 Je prends une grande inspiration et commence la phase de vérification.
### Checklist
- ✅ Bail DHCP WAN dans la VM.
- ✅ Ping depuis mon PC vers le VIP du VLAN User.
- ⚠️ Ping entre VLANs.
Les pings fonctionnent, mais j'observe quelques pertes, environ 10 %.
- ✅ SSH vers mes machines.
- ✅ Renouvellement du bail DHCP.
- ✅ Vérifier `ipconfig`
- ❌ Tester un site internet. → ✅
Quelques sites fonctionnent, tout est incroyablement lent... Ça doit être le DNS. J'essaie de résoudre un domaine au hasard, ça marche. Mais je ne peux pas résoudre `google.com`. Je redémarre le service Unbound DNS, tout fonctionne maintenant. C'est toujours le DNS...
- ⚠️ Vérifier les logs du pare-feu.
Quelques flux sont bloqués, pas critique.
- ✅ Vérifier mes services web.
- ✅ Vérifier que mes services internes ne sont pas accessibles depuis l'extérieur.
- ✅ Tester le VPN.
- ✅ Vérifier tous les appareils IoT.
- ✅ Vérifier les fonctionnalités Home Assistant.
- ✅ Vérifier que la TV fonctionne.
- ❌ Tester le Chromecast.
C'est lié au service mDNS qui ne parvient pas à démarrer. Je peux le démarrer si je décoche l'option `CARP Failover`. Le Chromecast est visible maintenant. → ⚠️
- ✅ Imprimer quelque chose.
- ✅ Vérifier la blocklist DNS.
- ✅ Speedtest.
J'observe environ 15 % de diminution de bande passante (de 940Mbps à 825Mbps).
- ❌ Bascule.
La bascule fonctionne difficilement, beaucoup de paquets perdus pendant la bascule. Le service rendu n'est pas génial : plus d'accès internet et mes services web sont inaccessibles.
- ⌛ Failover.
- ⌛ Reprise après sinistre.
À tester plus tard.
📝 Bon, les résultats sont plutôt bons, pas parfaits, mais satisfaisants !
### Résolution des Problèmes
Je me concentre sur la résolution des problèmes restants rencontrés lors des tests.
1. **DNS**
Lors de la bascule, la connexion internet ne fonctionne pas. Pas de DNS, c'est toujours le DNS.
C'est parce que le nœud de secours n'a pas de passerelle lorsqu'il est en mode passif. L'absence de passerelle empêche le DNS de résoudre. Après la bascule, il conserve des domaines non résolus dans son cache. Ce problème conduit aussi à un autre souci : quand il est passif, je ne peux pas mettre à jour le système.
**Solution** : Définir une passerelle pointant vers l'autre nœud, avec un numéro de priorité plus élevé que la passerelle WAN (un numéro plus élevé signifie une priorité plus basse). Ainsi, cette passerelle n'est pas active tant que le nœud est maître.
2. **Reverse Proxy**
Lors de la bascule, tous les services web que j'héberge (reverse proxy/proxy couche 4) renvoient cette erreur : `SSL_ERROR_INTERNAL_ERROR_ALERT`. Après vérification des services synchronisés via XMLRPC Sync, Caddy et mDNS repeater n'étaient pas sélectionnés. C'est parce que ces services ont été installés après la configuration initiale du HA.
**Solution** : Ajouter Caddy à XMLRPC Sync.
3. **Pertes de paquets**
J'observe environ 10 % de pertes de paquets pour les pings depuis n'importe quel VLAN vers le VLAN _Mgmt_. Je n'ai pas ce problème pour les autres VLANs.
Le VLAN _Mgmt_ est le VLAN natif dans mon réseau, cela pourrait être la raison de ce problème. C'est le seul réseau non défini dans le SDN Proxmox. Je ne veux pas avoir à tagger ce VLAN.
**Solution** : Désactiver le pare-feu Proxmox de cette interface pour la VM. En réalité, je les ai tous désactivés et mis à jour la documentation ci-dessus. Je ne sais pas exactement pourquoi cela causait ce type de problème, mais la désactivation a résolu mon souci (j'ai pu reproduire le comportement en réactivant le pare-feu).
4. **Script CARP**
Lors de la bascule, le script d'événement CARP est déclenché autant de fois qu'il y a d'interfaces. J'ai 5 IPs virtuelles, le script reconfigure mon interface WAN 5 fois.
**Solution** : Retravailler le script pour récupérer l'état de l'interface WAN et ne reconfigurer l'interface que lorsque c'est nécessaire :
```php
#!/usr/local/bin/php
<?php
/**
* OPNsense CARP event script
* - Enables/disables the WAN interface only when needed
* - Avoids reapplying config when CARP triggers multiple times
*/
require_once("config.inc");
require_once("interfaces.inc");
require_once("util.inc");
require_once("system.inc");
// Read CARP event arguments
$subsystem = !empty($argv[1]) ? $argv[1] : '';
$type = !empty($argv[2]) ? $argv[2] : '';
// Accept only MASTER/BACKUP events
if (!in_array($type, ['MASTER', 'BACKUP'])) {
// Ignore CARP INIT, DEMOTED, etc.
exit(0);
}
// Validate subsystem name format, expected pattern: <ifname>@<vhid>
if (!preg_match('/^[a-z0-9_]+@\S+$/i', $subsystem)) {
log_error("Malformed subsystem argument: '{$subsystem}'.");
exit(0);
}
// Interface key to manage
$ifkey = 'wan';
// Determine whether WAN interface is currently enabled
$ifkey_enabled = !empty($config['interfaces'][$ifkey]['enable']) ? true : false;
// MASTER event
if ($type === "MASTER") {
// Enable WAN only if it's currently disabled
if (!$ifkey_enabled) {
log_msg("CARP event: switching to '$type', enabling interface '$ifkey'.", LOG_WARNING);
$config['interfaces'][$ifkey]['enable'] = '1';
write_config("enable interface '$ifkey' due CARP event '$type'", false);
interface_configure(false, $ifkey, false, false);
} else {
log_msg("CARP event: already '$type' for interface '$ifkey', nothing to do.");
}
// BACKUP event
} else {
// Disable WAN only if it's currently enabled
if ($ifkey_enabled) {
log_msg("CARP event: switching to '$type', disabling interface '$ifkey'.", LOG_WARNING);
unset($config['interfaces'][$ifkey]['enable']);
write_config("disable interface '$ifkey' due CARP event '$type'", false);
interface_configure(false, $ifkey, false, false);
} else {
log_msg("CARP event: already '$type' for interface '$ifkey', nothing to do.");
}
}
```
5. **mDNS Repeater**
Le répéteur mDNS ne veut pas démarrer quand je sélectionne l'option `CARP Failover`.
**Solution** : La machine nécessite un redémarrage pour démarrer ce service compatible CARP.
6. **Adresse IPv6**
Mon nœud `cerbere-head1` crie dans le fichier de logs tandis que l'autre ne le fait pas. Voici les messages affichés chaque seconde quand il est maître :
```plaintext
Warning rtsold <interface_up> vtnet1 is disabled. in the logs (OPNsense)
```
Un autre message que j'ai plusieurs fois après un switchback :
```plaintext
Error dhcp6c transmit failed: Can't assign requested address
```
Ceci est lié à IPv6. J'observe que mon nœud principal n'a pas d'adresse IPv6 globale, seulement une link-local. De plus, il n'a pas de passerelle IPv6. Mon nœud secondaire, en revanche, a à la fois l'adresse globale et la passerelle.
Je ne suis pas expert IPv6, après quelques heures de recherche, j'abandonne IPv6. Si quelqu'un peut m'aider, ce serait vraiment apprécié !
**Contournement** : Supprimer DHCPv6 pour mon interface WAN.
### Confirmation
Maintenant que tout est corrigé, je peux évaluer les performances du failover.
1. **Basculement**
En entrant manuellement en mode maintenance CARP depuis l'interface WebGUI, aucune perte de paquets n'est observée. Impressionnant.
2. **Failover**
Pour simuler un failover, je tue la VM OPNsense active. Ici j'observe une seule perte de paquet. Génial.
![Test de ping pendant le CARP failover d'OPNsense](images/opnsense-ping-failover.png)
3. **Reprise après sinistre**
Une reprise après sinistre est ce qui se produirait après un arrêt complet d'un cluster Proxmox, suite à une coupure de courant par exemple. Je n'ai pas eu le temps (ni le courage) de m'en occuper, je préfère mieux me préparer pour éviter les dommages collatéraux. Mais il est certain que ce genre de scénario doit être évalué.
### Avantages Supplémentaires
Outre le fait que cette nouvelle configuration est plus résiliente, j'ai constaté quelques autres avantages.
Mon rack est minuscule et l'espace est restreint. L'ensemble chauffe beaucoup, dépassant les 40 °C au sommet du rack en été. Réduire le nombre de machines allumées a permis de faire baisser la température. J'ai gagné 1,5 °C après avoir éteint l'ancien boîtier OPNsense, c'est super !
La consommation électrique est également un point important, mon petit datacenter consommait en moyenne 85 W. Là encore, j'ai constaté une légère baisse, d'environ 8 W. Sachant que le système fonctionne 24/7, ce n'est pas négligeable.
Enfin, j'ai également retiré le boîtier lui-même et le câble d'alimentation. Les places sont très limitées, ce qui est un autre point positif.
---
## Conclusion
🎉 J'ai réussi les gars ! Je suis très fier du résultat, et fier de moi.
De mon [premier crash de ma box OPNsense]({{< ref "post/10-opnsense-crash-disk-panic" >}}), à la recherche d'une solution, en passant par la [proof of concept]({{< ref "post/12-opnsense-virtualization-highly-available" >}}) de haute disponibilité, jusqu'à cette migration, ce fut un projet assez long, mais extrêmement intéressant.
🎯 Se fixer des objectifs, c'est bien, mais les atteindre, c'est encore mieux.
Je vais maintenant mettre OPNsense de côté un petit moment pour me recentrer sur mon apprentissage de Kubernetes !
Comme toujours, si vous avez des questions, des remarques ou une solution à mon problème d'IPv6, je serai ravi de vous aider.
@@ -0,0 +1,421 @@
---
slug: migration-opnsense-proxmox-highly-available
title: Migration of my OPNsense HA Cluster in Proxmox VE
description: The detailed steps of the migration from my OPNsense physical box to a highly available cluster of VM in Proxmox VE.
date: 2025-11-20
draft: false
tags:
- opnsense
- high-availability
- proxmox
categories:
- homelab
image: cover-15.webp
---
## Intro
This is the final stage of my **OPNsense** virtualization journey.
A few months ago, my physical [OPNsense box crashed]({{< ref "post/10-opnsense-crash-disk-panic" >}}) because of a hardware failure. This leads my home in the dark, literally. No network, no lights.
💡 To avoid being in that situation again, I imagined a plan to virtualize my OPNsense firewall into my **Proxmox VE** cluster. The last time, I've set up a [proof of concept]({{< ref "post/12-opnsense-virtualization-highly-available" >}}) to validate this solution: create a cluster of two **OPNsense** VMs in Proxmox and make the firewall highly available.
This time, I will cover the creation of my future OPNsense cluster from scratch, plan the cut over and finally migrate from my current physical box. Let's go!
---
## The VLAN Configuration
For my plans, I'll have to connect the WAN, coming from my ISP box, to my main switch. For that I create a dedicated VLAN to transport this flow to my Proxmox nodes.
### UniFi
First, I configure my layer 2 network which is managed by UniFi. There I need to create two VLANs:
- *WAN* (20): transport the WAN between my ISP box and my Proxmox nodes.
- *pfSync* (44), communication between my OPNsense nodes.
In the UniFi controller, in `Settings` > `Networks`, I add a `New Virtual Network`. I name it `WAN` and give it the VLAN ID 20:
![Creation of the WAN VLAN in the UniFi Controller](images/unifi-add-vlan-for-wan.png)
I do the same thing again for the `pfSync` VLAN with the VLAN ID 44.
I plan to plug my ISP box on the port 15 of my switch, which is disabled for now. I set it as active, set the native VLAN on the newly created one `WAN (20)` and disable trunking:
![Configuration of the UniFi switch port for the WAN uplink](images/unifi-enable-port-wan-vlan.png)
Once this setting applied, I make sure that only the ports where are connected my Proxmox nodes propagate these VLAN on their trunk.
I'm done with UniFi configuration.
### Proxmox SDN
Now that the VLAN can reach my nodes, I want to handle it in the Proxmox SDN. I've configured the SDN in [that article]({{< ref "post/11-proxmox-cluster-networking-sdn" >}}).
In `Datacenter` > `SDN` > `VNets`, I create a new VNet, call it `vlan20` to follow my own naming convention, give it the *WAN* alias and use the tag (VLAN ID) 20:
![Creation of the VNet for the WAN in the Proxmox SDN](images/proxmox-sdn-new-vnet-wan.png)
I also create the `vlan44` for the *pfSync* VLAN, then I apply this configuration and we are done with the SDN.
---
## Create the VMs
Now that the VLAN configuration is done, I can start buiding the virtual machines on Proxmox.
The first VM is named `cerbere-head1` (I didn't tell you? My current firewall is named `cerbere`, it makes even more sense now!). Here are the settings:
- **OS type**: Linux (even if OPNsense is based on FreeBSD)
- **Machine type**: `q35`
- **BIOS**: `OVMF (UEFI)`
- **Disk**: 20 GB on Ceph distributed storage
- **RAM**: 4 GB RAM, ballooning disabled
- **CPU**: 2 vCPU
- **NICs**, firewall disabled:
1. `vmbr0` (*Mgmt*)
2. `vlan20` (*WAN*)
3. `vlan13` *(User)*
4. `vlan37` *(IoT)*
5. `vlan44` *(pfSync)*
6. `vlan55` *(DMZ)*
7. `vlan66` *(Lab)*
![Hardware settings of the OPNsense VM in Proxmox](images/proxmox-cerbere-vm-settings.png)
ℹ️ Now I clone that VM to create `cerbere-head2`, then I proceed with OPNsense installation. I don't want to go into much details about OPNsense installation, I already documented it in the [proof of concept]({{< ref "post/12-opnsense-virtualization-highly-available" >}}).
After the installation of both OPNsense instances, I give to each of them their IP in the *Mgmt* network:
- `cerbere-head1`: `192.168.88.2/24`
- `cerbere-head2`: `192.168.88.3/24`
I give them the other OPNsense node as gateway (`192.168.88.1`) to allow me to reach them from my laptop in another VLAN.
---
## Configure OPNsense
Initially, I considered restoring my existing OPNsense configuration and adapt it to the setup.
Then I decided to start over to document and share it. This part was getting so long that I prefered create a dedicated post instead.
📖 You can find the details of the full OPNsense configuration in that [article]({{< ref "post/13-opnsense-full-configuration" >}}), covering HA, DNS, DHCP, VPN and reverse proxy.
---
## Proxmox VM High Availability
Resources (VM or LXC) in Proxmox VE can be tagged as highly available, let see how to set it up.
### Proxmox HA Requirements
First, your Proxmox cluster must allow it. There are some requirements:
- At least 3 nodes to have quorum
- Shared storage for your resources
- Time synchronized
- Reliable network
A fencing mechanism must be enabled. Fencing is the process of isolating a failed cluster node to ensure it no longer accesses shared resources. This prevents split-brain situations and allows Proxmox HA to safely restart affected VMs on healthy nodes. By default, it is using Linux software watchdog, *softdog*, good enough for me.
In Proxmox VE 8, It was possible to create HA groups, depending of their resources, locations, etc. This has been replaced, in Proxmox VE 9, by HA affinity rules. This is actually the main reason behind my Proxmox VE cluster upgrade, which I've detailed in that [post]({{< ref "post/14-proxmox-cluster-upgrade-8-to-9-ceph" >}}).
### Configure VM HA
The Proxmox cluster is able to provide HA for the resources, but you need to define the rules.
In `Datacenter` > `HA`, you can see the status and manage the resources. In the `Resources` panel I click on `Add`. I need to pick the resource to configure as HA in the list, here `cerbere-head1` with ID 122. Then in the tooltip I can define the maximum of restart and relocate, I keep `Failback` enabled and the requested state to `started`:
![Create HA resource in Proxmox](images/proxmox-add-vm-ha.png)
The Proxmox cluster will now make sure this VM is started. I do the same for the other OPNsense VM, `cerbere-head2`.
### HA Affinity Rules
Great, but I don't want them on the same node. This is when the new feature HA affinity rules, of Proxmox VE 9, come in. Proxmox allows to create node affinity and resource affinity rules. I don't mind on which node they run, but I don't want them together. I need a resource affinity rule.
In `Datacenter` > `HA` > `Affinity Rules`, I add a new HA resource affinity rule. I select both VMs and pick the option `Keep Separate`:
![Create HA resource affinity in Proxmox](images/proxmox-ha-resource-affinity-rule.png)
✅ My OPNsense VMs are now fully ready!
---
## Migration
🚀 Time to make it real!
I'm not gonna lie, I'm quite excited. I'm working for this moment for days.
### The Migration Plan
I have my physical OPNsense box directly connected to my ISP box. I want to swap it for the VM cluster. (To avoid writing the word OPNsense on each line, I'll simply name it "the box" and "the VM")
Here is the plan:
1. Backup of the box configuration.
2. Disable DHCP server on the box.
3. Change IP addresses of the box.
4. Change VIP on the VM.
5. Disable gateway on VM.
6. Configure DHCP on both VMs.
7. Enable mDNS repeater on VM.
8. Replicate services on VM.
9. Move of the Ethernet cable.
### Rollback Strategy
None. 😎
I'm kidding, the rollback consists of restoring the box configuration, shutdown the OPNsense VMs and plug back the Ethernet cable into the box.
### Verification Plan
To validate the migration, I'm drawing up a checklist:
1. WAN DHCP lease in the VM.
2. Ping from my PC to the VIP of the User VLAN.
3. Ping cross VLAN.
4. SSH into my machines.
5. Renew DHCP lease.
6. Check `ipconfig`
7. Test internet website.
8. Check firewall logs.
9. Check my webservices.
10. Verify if my internal webservices are not accessible from outside.
11. Test VPN.
12. Check all IoT devices.
13. Check Home Assistant features.
14. Check if the TV works.
15. Test the Chromecast.
16. Print something.
17. Verify DNS blocklist.
18. Speedtest.
19. Switchover.
20. Failover.
21. Disaster Recovery.
22. Champaign!
Will it work? Let's find out!
### Migration Steps
1. **Backup of the box configuration.**
On my physical OPNsense instance, in `System` > `Configuration` > `Backups`, I click the `Download configuration` button which give me the precious XML file. The one that saved my ass the [last time]({{< ref "post/10-opnsense-crash-disk-panic" >}}).
2. **Disable DHCP server on the box.**
In `Services` > `ISC DHCPv4`, and for all my interfaces, I disable the DHCP server. I only serve DHCPv4 in my network.
3. **Change IP addresses of the box.**
In `Interfaces`, and for all my interfaces, I modify the IP of the firewall, from `.1` to `.253`. I want to reuse the same IP address as VIP, and have this instance still reachable if needed.
As soon as I click on `Apply`, I lost the communication, which is expected.
4. **Change VIP on the VM.**
On my master VM, In `Interfaces` > `Virtual IPs` > `Settings`, I change the VIP address for each interface and set it to `.1`.
5. **Disable gateway on VM.**
In `System` > `Gateways` > `Configuration`, I disable the `LAN_GW` which is not needed anymore.
6. **Configure DHCP on both VMs.**
In both VM, in `Services` > `Dnsmasq DNS & DHCP`, I enable the service on my 5 interfaces.
7. **Enable mDNS repeater on VM.**
In `Services` > `mDNS Repeater`, I enable the service and also enable CARP Failover.
The service does not start. I'll see that problem later.
8. **Replicate services on VM.**
In `System` > `High Availability` > `Status`, I click the button to `Synchronize and reconfigure all`.
9. **Move of the Ethernet cable.**
Physically in my rack, I unplug the Ethernet cable from the WAN port (`igc0`) of my physical OPNsense box and plug it into the port 15 of my UniFi switch.
---
## Verification
😮‍💨 I take a deep breath and start the verification phase.
### Checklist
- ✅ WAN DHCP lease in the VM.
- ✅ Ping from my PC to the VIP of the User VLAN.
- ⚠️ Ping cross VLAN.
Pings are working, but I observe some drops, about 10%.
- ✅ SSH into my machines.
- ✅ Renew DHCP lease.
- ✅ Check `ipconfig`
- ❌ Test internet website. → ✅
A few websites are working, everything is incredibly slow... It must be the DNS. I try to lookup a random domain, it is working. But I can't lookup `google.com`. I restart the Unbound DNS service, everything works now. It is always the DNS...
- ⚠️ Check firewall logs.
Few flows are blocks, not mandatory.
- ✅Check my webservices.
- ✅Verify if my internal webservices are not accessible from outside.
- ✅ Test VPN.
- ✅ Check all IoT devices.
- ✅ Check Home Assistant features.
- ✅Check if the TV works.
- ❌ Test the Chromecast.
It is related to the mDNS service not able to start. I can start it if I uncheck the `CARP Failover` option. the Chromecast is visible now. → ⚠️
- ✅Print something.
- ✅Verify DNS blocklist.
- ✅Speedtest.
I observe roughly 15% of decrease bandwidth (from 940Mbps to 825Mbps).
- ❌ Switchover.
The switchover barely works, a lot of dropped packets during the switch. The service provided is not great: no more internet and my webservices are not reachable.
- ⌛ Failover.
- ⌛ Disaster Recovery.
To be tested later.
📝 Well, the results are pretty good, not perfect, but satisfying!
### Problem Solving
I focus on resolving remaining problems experienced during the tests.
1. **DNS**
During the switchover, the internet connection is not working. No DNS, it is always DNS.
It's because the backup node does not have a gateway while passive. No gateway prevents the DNS to resolve. After the switchover, it still has unresolved domains in its cache. This problem also lead to another issue, while passive, I can't update the system.
**Solution**: Create a gateway pointing to the other node, with a higher priority number than the WAN gateway (higher number means lower priority). This way, that gateway is not active while the node is master.
2. **Reverse Proxy**
During the switchover, every webservices which I host (reverse proxy/layer 4 proxy) give this error: `SSL_ERROR_INTERNAL_ERROR_ALERT`. After checking the services synchronized throught XMLRPC Sync, Caddy and mDNS repeater were not selected. It is because these services were installed after the initial configuration of the HA.
**Solution**: Add Caddy to XMLRPC Sync.
3. **Packet Drops**
I observe about 10% packet drops for pings from any VLAN to the *Mgmt* VLAN. I don't have this problem for the other VLANs.
The *Mgmt* VLAN is the native one in my network, it might be the reason behind this issue. This is the only network not defined in the Proxmox SDN. I don't want to have to tag this VLAN.
**Solution**: Disable the Proxmox firewall of this interface for the VM. I actually disable them all and update the documentation above. I'm not sure why this cause that kind of problem, but disabling it fixed my issue (I could reproduce the behavior while activating the firewall again).
4. **CARP Script**
During a switchover, the CARP event script is triggered as many times as the number of interfaces. I have 5 virtual IPs, the script reconfigure my WAN interface 5 times.
**Solution**: Rework the script to get the WAN interface state and only reconfigure the inteface when needed:
```php
#!/usr/local/bin/php
<?php
/**
* OPNsense CARP event script
* - Enables/disables the WAN interface only when needed
* - Avoids reapplying config when CARP triggers multiple times
*/
require_once("config.inc");
require_once("interfaces.inc");
require_once("util.inc");
require_once("system.inc");
// Read CARP event arguments
$subsystem = !empty($argv[1]) ? $argv[1] : '';
$type = !empty($argv[2]) ? $argv[2] : '';
// Accept only MASTER/BACKUP events
if (!in_array($type, ['MASTER', 'BACKUP'])) {
// Ignore CARP INIT, DEMOTED, etc.
exit(0);
}
// Validate subsystem name format, expected pattern: <ifname>@<vhid>
if (!preg_match('/^[a-z0-9_]+@\S+$/i', $subsystem)) {
log_error("Malformed subsystem argument: '{$subsystem}'.");
exit(0);
}
// Interface key to manage
$ifkey = 'wan';
// Determine whether WAN interface is currently enabled
$ifkey_enabled = !empty($config['interfaces'][$ifkey]['enable']) ? true : false;
// MASTER event
if ($type === "MASTER") {
// Enable WAN only if it's currently disabled
if (!$ifkey_enabled) {
log_msg("CARP event: switching to '$type', enabling interface '$ifkey'.", LOG_WARNING);
$config['interfaces'][$ifkey]['enable'] = '1';
write_config("enable interface '$ifkey' due CARP event '$type'", false);
interface_configure(false, $ifkey, false, false);
} else {
log_msg("CARP event: already '$type' for interface '$ifkey', nothing to do.");
}
// BACKUP event
} else {
// Disable WAN only if it's currently enabled
if ($ifkey_enabled) {
log_msg("CARP event: switching to '$type', disabling interface '$ifkey'.", LOG_WARNING);
unset($config['interfaces'][$ifkey]['enable']);
write_config("disable interface '$ifkey' due CARP event '$type'", false);
interface_configure(false, $ifkey, false, false);
} else {
log_msg("CARP event: already '$type' for interface '$ifkey', nothing to do.");
}
}
```
5. **mDNS Repeater**
The mDNS repeater does not want to start when I select the option for `CARP Failover`.
**Solution**: The machine requires a reboot to start this service CARP aware.
6. **IPv6 Address**
My `cerbere-head1` node is crying in the log file while the other does not. Here are the messages spit every seconds while it is master:
```plaintext
Warning rtsold <interface_up> vtnet1 is disabled. in the logs (OPNsense)
```
Another one I'm having several times after a switchback:
```plaintext
Error dhcp6c transmit failed: Can't assign requested address
```
This is related to IPv6. I observe that my main node does not have a global IPv6 address, only a link-local. Also, it does not have a IPv6 gateway. My secondary node, in the other hand, has both addresses and the gateway.
I'm no IPv6 expert, after searching for a couple of hours, I give up the IPv6. If someone out here can help, it would be really appreciated!
**Workaround**: Remove DHCPv6 for my WAN interface.
### Confirmation
Now that everything is fixed, I can evaluate the failover performance.
1. **Switchover**
When manually entering CARP maintenance mode from the WebGUI interface, no packet drop is observed. Impressive.
2. **Failover**
To simulate a failover, I kill the active OPNsense VM. Here I observe only one packet dropped. Awesome.
![Ping test during OPNsense CARP failover](images/opnsense-ping-failover.png)
3. **Disaster Recovery**
A disaster recovery is what would happen after a full Proxmox cluster stop, after an electrical outage for example. I didn't have the time (or the courage) to do that, I'd prefer to prepare a bit better to avoid collateral damages. But surely, this kind of scenario must be evaluated.
### Extras Benefits
Leaving aside the fact that this new setup is more resilient, I have few more bonuses.
My rack is tiny and the space is tight. The whole thing is heating quite much, exceeding 40°C on top of the rack in summer. Reducing the number of machines powered up lower the temperature. I've gained **1,5°C** after shutting down the old OPNsense box, cool!
Power consumption is also a concern, my tiny datacenter was drawing 85W on average. Here again I could observe a small decrease, about 8W lower. Considering that this run 24/7, not negligible.
Finally I also removed the box itself and the power cable. Slots are very limited, another good point.
---
## Conclusion
🎉 I did it guys! I'm very proud of the results, proud of myself.
From my [first OPNsense box crash]({{< ref "post/10-opnsense-crash-disk-panic" >}}), the thinking about a solution, the HA [proof of concept]({{< ref "post/12-opnsense-virtualization-highly-available" >}}), to this migration. This has been a quite long project, but extremly interesting.
🎯 This is great to set objectives, but this is even better when you reach them.
Now I'm going to leave OPNsense aside for a bit, to be able to re-focus on my Kubernetes journey!
As always, if you have questions, remarks or a solution for my IPv6 problem, I'll be really happy to share with you.
Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

@@ -0,0 +1,171 @@
---
slug: how-I-deploy-application
title: Comment je Déploie des Applications Aujourd’hui
description: La méthode que j’utilise aujourd’hui pour déployer de nouvelles applications dans mon homelab. Workflow simple tirant parti de Docker Compose dans une VM sur Proxmox VE
date: 2026-01-31
draft: false
tags:
- docker
- proxmox
- opnsense
- gitea
- traefik
categories:
- homelab
image: cover-16.webp
---
## Intro
Dans cet article, je ne vais pas expliquer les bonnes pratiques pour déployer des applications. À la place, je veux documenter comment je déploie actuellement de nouvelles applications dans mon homelab.
Considérez cet article comme un snapshot. C’est comme ça que les choses fonctionnent vraiment aujourd’hui, sachant que dans un futur proche j’aimerais évoluer vers un workflow plus orienté GitOps.
La méthode que j’utilise est assez simple. J’ai essayé de la standardiser autant que possible, mais elle implique encore pas mal d’étapes manuelles. J’expliquerai aussi comment je mets à jour les applications, ce qui est, à mon avis, la plus grande faiblesse de cette configuration. À mesure que le nombre d’applications augmente, garder le tout à jour demande de plus en plus de temps.
---
## Overview de la Plateforme
Avant d’entrer dans le workflow, voici un rapide aperçu des principaux composants impliqués.
### Docker
Docker est la base de ma stack applicative. Quand c’est possible, je déploie les applications sous forme de conteneurs.
J’utilise Docker Compose depuis des années. À l’époque, tout tournait sur un seul serveur physique. Aujourd’hui, mon installation est basée sur des VM, et je pourrais migrer vers Docker Swarm, mais j’ai choisi de ne pas le faire. Cela peut avoir du sens dans certains scénarios, mais ce n’est pas aligné avec là où je veux aller à long terme.
Pour l’instant, je m’appuie toujours sur une seule VM pour héberger toutes les applications Docker. Cette VM est plus ou moins un clone de mon ancien serveur physique, simplement virtualisé.
### Proxmox VE
Cette VM est hébergée sur un cluster Proxmox VE, composé de trois nœuds et utilisant Ceph comme stockage distribué.
Cela me donne de la haute disponibilité et facilite grandement la gestion des VM, même si le workload Docker n'est pas hautement disponible.
### Traefik
Traefik tourne directement sur l’hôte Docker et fait office de reverse proxy.
Il est responsable d’acheminer le trafic HTTPS vers les bons conteneurs et de gérer automatiquement les certificats TLS via Let’s Encrypt. Cela garde la configuration au niveau des applications simple et centralisée.
### OPNsense
OPNsense est mon routeur, pare-feu et agit aussi comme reverse proxy.
Le trafic HTTPS entrant est transféré vers Traefik en utilisant le plugin Caddy avec des règles Layer 4. Le TLS n’est pas terminé au niveau du pare-feu. Il est transmis à Traefik, qui gère l’émission et le renouvellement des certificats.
### Gitea
Gitea est un dépôt Git self-hosted, j’ai une instance qui tourne dans mon homelab.
Dans Gitea, j’ai un dépôt privé qui contient toutes mes configurations Docker Compose. Chaque application a son propre dossier, ce qui rend le dépôt facile à parcourir et à maintenir.
---
## Déployer une Nouvelle Application
Pour standardiser les déploiements, j’utilise un template `docker-compose.yml` qui ressemble à ceci :
```yml
services:
NAME:
image: IMAGE
container_name: NAME
volumes:
- /appli/data/NAME/:/
environment:
- TZ=Europe/Paris
networks:
- web
labels:
- traefik.enable=true
- traefik.http.routers.NAME.rule=Host(`HOST.vezpi.com`)
- traefik.http.routers.NAME.entrypoints=https
- traefik.http.routers.NAME.tls.certresolver=letsencrypt
- traefik.http.services.NAME.loadbalancer.server.port=PORT
restart: always
networks:
web:
external: true
```
Laissez-moi expliquer.
Pour l’image, selon l’application, le registre utilisé peut varier, mais j’utilise quand même Docker Hub par défaut. Quand j’essaie une nouvelle application, je peux utiliser le tag `latest` au début. Ensuite, si je choisis de la garder, je préfère épingler la version actuelle plutôt que `latest`.
J’utilise des montages de volumes pour tout ce qui est stateful. Chaque application a son propre dossier dans le filesystem `/appli/data`.
Quand une application doit être accessible en HTTPS, je relie le conteneur qui sert les requêtes au réseau `web`, qui est géré par Traefik et je lui associe des labels. Les `entrypoint` et `certresolver` sont définis dans ma configuration Traefik. L’URL définie dans `Host()` est celle qui sera utilisée pour accéder à l’application. Elle doit être identique à ce qui est défini dans la route Layer4 du plugin Caddy d’OPNsense.
Si plusieurs conteneurs doivent communiquer entre eux, j’ajoute un réseau `backend` qui sera créé lors du déploiement de la stack, dédié à l’application. Ainsi, aucun port n’a besoin d’être ouvert sur l’hôte.
### Étapes de Déploiement
La plupart du travail est effectué depuis VScode :
- Créer un nouveau dossier dans ce dépôt, avec le nom de l’application.
- Copier le template ci-dessus dans ce dossier.
- Adapter le template avec les valeurs fournies par la documentation de l’application.
- Créer un fichier `.env` pour les secrets si nécessaire. Ce fichier est ignoré par `.gitignore`.
- Démarrer les services directement depuis VS Code en utilisant l’extension Docker.
Puis dans l’interface Web OPNsense, je mets à jour 2 routes Layer4 pour le plugin Caddy:
- Selon que l’application doit être exposée sur Internet ou non, j’ai une route _Internal_ et une route _External_. J’ajoute l’URL donnée à Traefik dans l’une d’elles.
- J’ajoute aussi cette URL dans une autre route pour rediriger le challenge HTTP Let’s Encrypt vers Traefik.
Une fois terminé, je teste l’URL. Si tout est correctement configuré, l’application devrait être accessible en HTTPS.
Quand tout fonctionne comme prévu, je commit le nouveau dossier de l’application dans le dépôt.
---
## Mettre à Jour une Application
Les mises à jour d’applications sont encore entièrement manuelles.
Je n’utilise pas d’outils automatisés comme Watchtower pour l’instant. Environ une fois par mois, je cherche de nouvelles versions en regardant Docker Hub, les releases GitHub ou la documentation de l’application.
Pour chaque application que je veux mettre à jour, je passe en revue:
- Nouvelles fonctionnalités
- Breaking changes
- Chemins de mise à niveau si nécessaire
La plupart du temps, les mises à jour sont simples:
- Mettre à jour le tag de l’image dans le fichier Docker Compose
- Redémarrer la stack.
- Vérifier que les conteneurs redémarrent correctement
- Consulter les logs Docker
- Tester l’application pour détecter des régressions
Si ça fonctionne, je continue à mettre à niveau étape par étape jusqu’à atteindre la dernière version disponible.
Sinon, je débogue jusqu’à corriger le problème. Les retours arrière sont pénibles.
Une fois la dernière version atteinte, je commit les changements dans le dépôt.
---
## Avantages et inconvénients
Qu’est-ce qui fonctionne bien et qu’est-ce qui fonctionne moins ?
### Avantages
- Modèle simple, une VM, un fichier compose par application.
- Facile à déployer, idéal pour tester une application.
- Emplacement central pour les configurations.
### Inconvénients
- La VM Docker unique est un point de défaillance unique.
- Les mises à jour manuelles ne passent pas à l’échelle quand le nombre d’applications augmente.
- Devoir déclarer l’URL dans Caddy est fastidieux.
- Difficile de suivre ce qui est en ligne et ce qui ne l’est pas.
- Les secrets dans .env sont pratiques mais basiques.
- Pas de moyen rapide de rollback.
- Les opérations sur la VM sont critiques.
---
## Conclusion
Cette configuration fonctionne, et elle m’a bien servi jusqu’ici. Elle est simple et intuitive. Cependant, elle est aussi très manuelle, surtout pour les mises à jour et la maintenance à long terme.
À mesure que le nombre d’applications augmente, cette approche ne passe clairement pas très bien à l’échelle. C’est l’une des principales raisons pour lesquelles je regarde vers GitOps et des workflows plus déclaratifs pour l’avenir.
Pour l'instant, cependant, c'est ainsi que je déploie des applications dans mon homelab, et cet article sert de point de référence pour savoir par où j'ai commencé.
@@ -0,0 +1,170 @@
---
slug: how-I-deploy-application
title: How Do I Deploy Application Today
description: The method I use today to deploy new application in my homelab. Simple workflow taking advantage of Docker Compose in a VM on Proxmox VE
date: 2026-01-31
draft: false
tags:
- docker
- proxmox
- opnsense
- gitea
- traefik
categories:
- homelab
image: cover-16.webp
---
## Intro
In this post, I am not going to explain best practices for deploying applications. Instead, I want to document how I am currently deploying new applications in my homelab.
Think of this article as a snapshot in time. This is how things really work today, knowing that in the near future I would like to move toward a more GitOps-oriented workflow.
The method I use is fairly simple. I have tried to standardize it as much as possible, but it still involves quite a few manual steps. I will also explain how I update applications, which is, in my opinion, the biggest weakness of this setup. As the number of applications keeps growing, keeping everything up to date requires more and more time.
---
## Platform Overview
Before diving into the workflow, here is a quick overview of the main components involved.
### Docker
Docker is the foundation of my application stack. Whenever possible, I deploy applications as containers.
I have been using Docker Compose for years. At the time, everything was running on a single physical server. Today, my setup is VM-based, and I could migrate to Docker Swarm, but I have chosen not to. It might make sense in some scenarios, but it is not aligned with where I want to go long term.
For now, I still rely on a single VM to host all Docker applications. This VM is more or less a clone of my old physical server, just virtualized.
### Proxmox VE
This VM is hosted on a Proxmox VE cluster, composed of three nodes and uses Ceph as a distributed storage backend.
This gives me high availability and makes VM management much easier, even though the Docker workloads themselves are not highly available.
### Traefik
Traefik runs directly on the Docker host and acts as the reverse proxy.
It is responsible for routing the HTTPS traffic to the correct containers and for managing TLS certificates automatically using Let’s Encrypt. This keeps application-level configuration simple and centralized.
### OPNsense
OPNsense is my router, firewall and also acts as reverse proxy.
Incoming HTTPS traffic is forwarded to Traefik using the Caddy plugin with Layer 4 rules. TLS is not terminated at the firewall level. It is passed through to Traefik, which handles certificate issuance and renewal.
### Gitea
Gitea is a self-hosted Git repository, I have one instance running in my homelab.
Inside Gitea, I have a private repository that contains all my Docker Compose configurations. Each application has its own folder, making the repository easy to navigate and maintain.
---
## Deploy New Application
To standardize deployments, I use a `docker-compose.yml` template that looks like this:
```yml
services:
NAME:
image: IMAGE
container_name: NAME
volumes:
- /appli/data/NAME/:/
environment:
- TZ=Europe/Paris
networks:
- web
labels:
- traefik.enable=true
- traefik.http.routers.NAME.rule=Host(`HOST.vezpi.com`)
- traefik.http.routers.NAME.entrypoints=https
- traefik.http.routers.NAME.tls.certresolver=letsencrypt
- traefik.http.services.NAME.loadbalancer.server.port=PORT
restart: always
networks:
web:
external: true
```
Let me explain.
For the image, depending on the application, the registry used could differ, but I still the Docker Hub by default. When I try a new application, I might use the `latest` tag at first. Then if I choose to keep the it, I prefer to pin the current version instead of `latest`.
I use volume binds for everything stateful. Every application got its own folder in the `/appli/data` filesystem.
When an application needs to be reachable with HTTPS, I link the container serving the requests in the `web` network, which is managed by Traefik and I associate it labels. The `entrypoint` and `certresolver` is defined in my Traefik configuration. The URL defined in `Host()` is the one which will be used to access the application. This needs to be the same as defined in the Layer4 route in the Caddy plugin of OPNsense.
If several containers need to talk to each other, I add a `backend` network which will be created when the stack will be deployed, dedicated for the application. This way, no ports need to be opened on the host.
### Steps to Deploy
Most of the work is done from VScode:
- Create a new folder in that repository, with the application name.
- Copy the template above inside this folder.
- Adapt the template with the values given by the application documentation.
- Create a `.env` file for secrets if needed. This file is ignored by `.gitignore`.
- Start the services directly from VS Code using the Docker extension.
Then in the OPNsense WebUI, I update 2 Layer4 routes for the Caddy plugin:
- Depending if the application should be exposed on the internet or not, I have an *Internal* and *External* route. I add the URL given to Traefik in one of these.
- I also add this URL in another route to redirect the Let's Encrypt HTTP challenge to Traefik.
Once complete, I test the URL. If everything is configured correctly, the application should be reachable over HTTPS.
When everything works as expected, I commit the new application folder to the repository.
---
## Update Application
Application updates are still entirely manual.
I do not use automated tools like Watchtower for now. About once a month, I check for new versions by looking at Docker Hub, GitHub releases, or the application documentation.
For each application I want to update, I review:
- New features
- Breaking changes
- Upgrade paths if required
Most of the time, updates are straightforward:
- Bump the image tag in the Docker Compose file
- Restart the stack.
- Verify that the containers restart properly
- Check Docker logs
- Test the application to detect regressions
If it works, I continue upgrading step by step until I reach the latest available version.
If not, I debug until I fix the problem. Rollbacks are painful.
Once the latest version is reached, I commit the changes to the repository.
---
## Pros and Cons
What works well and what doesn't?
### Pros
- Simple model, one VM, one compose file per application.
- Easy to deploy, great to test an application.
- Central location for the configurations.
### Cons
- Single Docker VM is a single point of failure.
- Manual updates don’t scale as the app count grows.
- Having to declare the URL on Caddy is boring.
- Hard to follow what is up, and what is not.
- Secrets in .env are convenient but basic.
- No fast way to rollback.
- Operations on the VM are critical.
---
## Conclusion
This setup works, and it has served me well so far. It is simple and intuitive. However, it is also very manual, especially when it comes to updates and long-term maintenance.
As the number of applications grows, this approach clearly does not scale very well. That is one of the main reasons why I am looking toward GitOps and more declarative workflows for the future.
For now, though, this is how I deploy applications in my homelab, and this post serves as a reference point for where I started.
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Some files were not shown because too many files have changed in this diff Show More