Compare commits

..

56 Commits

Author SHA1 Message Date
Lino Silva 4a11a4ce8e feat: Sparky fitness 2026-05-31 12:14:58 +01:00
Lino Silva 844cef4bef chore: Change endpoint for price-scraper 2026-05-29 14:24:54 +01:00
Lino Silva 3ed21badb3 fix: Runner config path 2026-05-29 13:11:46 +01:00
Lino Silva bab98a020c feat: Forgejo runner 2026-05-29 13:09:40 +01:00
Lino Silva 3148136c22 feat: Forgejo reverse proxy 2026-05-29 13:09:35 +01:00
Lino Silva fe618ac8e4 fix: Forgejo version 2026-05-29 12:24:29 +01:00
Lino Silva d04a68b4df fix: Forgejo mirror 2026-05-29 12:23:08 +01:00
Lino Silva 6ed68a36be feat: Forgejo 2026-05-29 12:20:55 +01:00
Lino Silva 0eb4e4483c feat: Open wealthfolio 2026-05-28 12:55:03 +01:00
Lino Silva 3032dd0882 feat: Jellyfin reverse-proxy 2026-05-22 14:07:53 +01:00
Lino Silva 0ee5f37b54 feat: KVM reverse proxy 2026-05-22 09:26:38 +01:00
Lino Silva 24bd4ec162 chore: Remove env 2026-05-22 09:26:33 +01:00
Lino Silva cec78f8229 fix: Dahua2Mqtt docker compose 2026-05-19 21:31:06 +01:00
Lino Silva 234d9edc68 fix: Dahua2MQTT env file 2026-05-19 21:25:07 +01:00
Lino Silva 59c003f619 feat: Dahua2MQTT 2026-05-19 18:53:48 +01:00
Lino Silva 98c88759be feat: Speedtest 2026-05-19 18:41:09 +01:00
Lino Silva 8173b7cbc1 feat: Expose gitea 2026-05-19 10:13:23 +01:00
Lino Silva 886c6f559b fix: Price tracker reverse proxy 2026-05-12 22:32:32 +01:00
Lino Silva a05b2ea9ea chore: Renaming 2026-05-12 22:28:16 +01:00
Lino Silva ce197c3299 feat: Price tracker 2026-05-12 22:28:10 +01:00
Lino Silva 94984f0635 fix: Homelable images 2026-05-04 10:39:04 +01:00
Lino Silva e1500967cf feat: Homelable
Co-authored-by: Copilot <copilot@github.com>
2026-05-04 10:33:18 +01:00
Lino Silva 9e4a1f7836 feat: Unmanic endpoint 2026-04-21 12:15:16 +01:00
Lino Silva a286b755fe feat: Added apps-2 VM 2026-04-20 16:25:30 +01:00
Lino Silva e7bb6eadb8 feat: New endpoint 2026-04-14 00:56:44 +01:00
Lino Silva c5421a4af4 fix: Typo 2026-04-13 10:06:04 +01:00
Lino Silva fb7e101d6f fix: Subdomain for trek 2026-04-13 09:39:49 +01:00
Lino Silva 9ffae8989a fix: Remove env file 2026-04-12 23:11:14 +01:00
Lino Silva e44dafb0e2 fix: Dummy 2026-04-12 23:04:01 +01:00
Lino Silva 68d5178ab4 fix: Env var naming 2026-04-12 23:01:56 +01:00
Lino Silva 714f2449f6 fix: Revert 2026-04-12 22:44:22 +01:00
Lino Silva 5030362ba4 fix: Try new interpolation 2026-04-12 22:42:52 +01:00
Lino Silva 8fb291704e fix: Spacing? 2026-04-12 22:32:59 +01:00
Lino Silva f0b7351f64 fix: OIDC discovery url 2026-04-12 22:28:42 +01:00
Lino Silva f111fdfcb7 fix: Reverse proxy port 2026-04-12 22:28:31 +01:00
Lino Silva 07ac4dfec4 fix: Port 2026-04-12 22:22:37 +01:00
Lino Silva 0cc349765e feat: Trek compose files 2026-04-12 22:18:24 +01:00
Lino Silva 7ad7b26385 feat: Trek storage 2026-04-12 22:18:17 +01:00
Lino Silva 48ef6eda37 feat: Remove bpi scraper 2026-04-11 00:40:45 +01:00
Lino Silva 0c90b7a155 fix: Update port 2026-04-10 23:42:26 +01:00
Lino Silva ea9ad9eb30 feat: BPI price scraper app 2026-04-10 23:32:00 +01:00
Lino Silva 13022f4c34 feat: Local ip auth override 2026-04-10 23:09:08 +01:00
Lino Silva 57847ca4fe fix: Only deploy stacks if they didn't exist previously 2026-04-10 15:49:20 +01:00
Lino Silva 7a211b2d4b feat: More fake stacks 2026-04-10 15:29:48 +01:00
Lino Silva c3ecd9449e feat: Auto deploy stacks on komodo (no storage yet) 2026-04-10 15:27:40 +01:00
Lino Silva 82b8bb2e2a fix: Ports 2026-04-10 13:45:35 +01:00
Lino Silva a00d3d6ee3 fix: Renaming files 2026-04-10 13:45:03 +01:00
Lino Silva c3ad0b889d fix: Compose names 2026-04-10 13:35:37 +01:00
Lino Silva df602c0832 feat: Hello world docker composes 2026-04-10 13:02:31 +01:00
Lino Silva 13539eebc6 fix: Access komodo locally 2026-04-10 13:02:05 +01:00
Lino Silva a96ddd3ddf chore: Update vault 2026-04-10 12:43:09 +01:00
Lino Silva f60c5caa0a fix: Revert local ip override 2026-04-10 12:33:41 +01:00
Lino Silva 4855448bed feat: Scaffolding for komodo 2026-04-10 12:32:49 +01:00
Lino Silva 828b121a7e feat: Update traefik playbook 2026-04-10 12:32:40 +01:00
Lino Silva d717013e6d feat: media+app VMs, Komodo periphery working 2026-04-09 23:20:40 +01:00
Lino Silva 3b6267e1db fix: Speedtest ip 2026-04-06 00:06:07 +01:00
32 changed files with 1114 additions and 203 deletions
@@ -11,12 +11,21 @@ app_data_disks:
disk_id: scsi1 disk_id: scsi1
mount_point: /data/pocket-id mount_point: /data/pocket-id
device: /dev/sdb device: /dev/sdb
komodo: trek:
vm: infra-core-1 vm: apps-1
vmid: 410 vmid: 430
node: purah node: yunobo
size: "20" size: "10"
storage: purah-mirror-860gb storage: nvme-2tb
disk_id: scsi1
mount_point: /data/trek
device: /dev/sdb
forgejo:
vm: apps-1
vmid: 430
node: yunobo
size: "50"
storage: nvme-2tb
disk_id: scsi2 disk_id: scsi2
mount_point: /data/komodo mount_point: /data/forgejo
device: /dev/sdc device: /dev/sdc
+79 -46
View File
@@ -26,200 +26,231 @@ auto_configure_traefik:
subdomain: "sonarr" subdomain: "sonarr"
host: "10.0.2.25" host: "10.0.2.25"
port: 8989 port: 8989
auth_required: true internal: true
radarr: radarr:
subdomain: "radarr" subdomain: "radarr"
host: "10.0.2.25" host: "10.0.2.25"
port: 7878 port: 7878
auth_required: true internal: true
lidarr: lidarr:
subdomain: "lidarr" subdomain: "lidarr"
host: "10.0.2.25" host: "10.0.2.25"
port: 8686 port: 8686
auth_required: true internal: true
transmission: transmission:
subdomain: "transmission" subdomain: "transmission"
host: "10.0.2.25" host: "10.0.2.25"
port: 9091 port: 9091
auth_required: true internal: true
tdarr: unmanic:
subdomain: "tdarr" subdomain: "unmanic"
host: "10.0.2.25" host: "10.0.2.25"
port: 8265 port: 8888
auth_required: true internal: true
bazarr: bazarr:
subdomain: "bazarr" subdomain: "bazarr"
host: "10.0.2.25" host: "10.0.2.25"
port: 6767 port: 6767
auth_required: true internal: true
seerr: seerr:
subdomain: "overseerr" subdomain: "overseerr"
host: "10.0.2.25" host: "10.0.2.25"
port: 5055 port: 5055
auth_required: false internal: false
prowlarr: prowlarr:
subdomain: "prowlarr" subdomain: "prowlarr"
host: "10.0.2.25" host: "10.0.2.25"
port: 9696 port: 9696
auth_required: true internal: true
unpackerr: unpackerr:
subdomain: "unpackerr" subdomain: "unpackerr"
host: "10.0.2.25" host: "10.0.2.25"
port: 5656 port: 5656
auth_required: true internal: true
questarr:
subdomain: "questarr"
host: "10.0.2.25"
port: 5000
internal: true
# infra # infra
komodo: komodo:
subdomain: "komodo" subdomain: "komodo"
host: "10.0.4.10" host: "10.0.4.10"
port: 9120 port: 9120
auth_required: true internal: true
homeassistant: homeassistant:
subdomain: "homeassistant" subdomain: "homeassistant"
host: "10.0.2.100" host: "10.0.2.100"
port: 8123 port: 8123
auth_required: false internal: false
# media # media
plex: plex:
subdomain: "plex" subdomain: "plex"
host: "10.0.2.10" host: "10.0.2.10"
port: 32400 port: 32400
auth_required: false internal: false
tracearr: tracearr:
subdomain: "tracearr" subdomain: "tracearr"
host: "10.0.2.21" host: "10.0.2.21"
port: 3000 port: 3000
auth_required: true internal: true
jellyfin:
subdomain: "jellyfin"
host: "10.0.2.11"
port: 8096
internal: true
vaultwarden: vaultwarden:
subdomain: "pwds" subdomain: "pwds"
host: "10.0.2.27" host: "10.0.2.27"
port: 8004 port: 8004
auth_required: false internal: false
changedetection: changedetection:
subdomain: "changedetection" subdomain: "changedetection"
host: "10.0.2.24" host: "10.0.2.24"
port: 5000 port: 5000
auth_required: true internal: true
nextcloud: nextcloud:
subdomain: "cloud" subdomain: "cloud"
host: "10.0.2.30" host: "10.0.2.30"
port: 8001 port: 8001
auth_required: false internal: false
convertx: convertx:
subdomain: "convertx" subdomain: "convertx"
host: "10.0.2.43" host: "10.0.2.43"
port: 3000 port: 3000
auth_required: true internal: true
dawarich: dawarich:
subdomain: "places" subdomain: "places"
host: "10.0.2.48" host: "10.0.2.48"
port: 3000 port: 3000
auth_required: false internal: false
frigate: frigate:
subdomain: "frigate" subdomain: "frigate"
host: "10.0.2.14" host: "10.0.2.14"
port: 5000 port: 5000
auth_required: true internal: true
droposs: droposs:
subdomain: "games" subdomain: "games"
host: "10.0.2.46" host: "10.0.2.46"
port: 3000 port: 3000
auth_required: false internal: false
# geoguessr:
# subdomain: "geoguessr"
# host: "10.0.2.39"
# port: 8080
# auth_required: true
gitea: gitea:
subdomain: "gitea" subdomain: "gitea"
host: "10.0.2.28" host: "10.0.2.28"
port: 3000 port: 3000
auth_required: true internal: false
forgejo:
subdomain: "git"
host: "10.0.4.30"
port: 8086
internal: false
immich: immich:
subdomain: "immich" subdomain: "immich"
host: "10.0.2.18" host: "10.0.2.18"
port: 2283 port: 2283
auth_required: false internal: false
mastodon: mastodon:
subdomain: "social" subdomain: "social"
host: "10.0.2.20" host: "10.0.2.20"
port: 80 port: 80
auth_required: false internal: false
forward_https: true forward_https: true
matrix: matrix:
subdomain: "chat" subdomain: "chat"
host: "10.0.2.20" host: "10.0.2.20"
port: 8008 port: 8008
auth_required: false internal: false
mealie: mealie:
subdomain: "recipes" subdomain: "recipes"
host: "10.0.2.26" host: "10.0.2.26"
port: 9000 port: 9000
auth_required: false internal: false
truenas: truenas:
subdomain: "nas" subdomain: "nas"
host: "10.0.2.200" host: "10.0.2.200"
port: 80 port: 80
auth_required: true internal: true
paperless: paperless:
subdomain: "paperless" subdomain: "paperless"
host: "10.0.2.29" host: "10.0.2.29"
port: 8003 port: 8003
auth_required: true internal: true
pbs: pbs:
subdomain: "pbs" subdomain: "pbs"
host: "10.0.2.104" host: "10.0.2.104"
port: 8007 port: 8007
https: true https: true
auth_required: true internal: true
# pinchflat: # pinchflat:
# subdomain: "youtube" # subdomain: "youtube"
# host: "10.0.2.23" # host: "10.0.2.23"
# port: 8081 # port: 8081
# auth_required: true # internal: true
proxmox: proxmox:
subdomain: "proxmox" subdomain: "proxmox"
host: "10.0.2.2" host: "10.0.2.2"
port: 8006 port: 8006
https: true https: true
auth_required: true internal: true
resume: resume:
subdomain: "resume" subdomain: "resume"
host: "10.0.2.53" host: "10.0.2.53"
port: 3000 port: 3000
auth_required: true internal: true
auth_bypass_paths: auth_bypass_paths:
- /lino - /lino
- /assets - /assets
- /api - /api
speedtest-tracker: speedtest-tracker:
subdomain: "fast" subdomain: "fast"
host: "10.0.2.254" host: "10.0.4.30"
port: 8765 port: 8085
auth_required: true internal: true
stocks: stocks:
subdomain: "stocks" subdomain: "stocks"
host: "10.0.2.40" host: "10.0.2.40"
port: 3333 port: 3333
auth_required: false internal: false
super-productivity: super-productivity:
subdomain: "tasks" subdomain: "tasks"
host: "10.0.2.45" host: "10.0.2.45"
port: 80 port: 80
auth_required: true internal: true
uptime-kuma: uptime-kuma:
subdomain: "uptime" subdomain: "uptime"
host: "10.0.2.203" host: "10.0.2.203"
port: 3001 port: 3001
auth_required: true internal: true
wealthfolio: wealthfolio:
subdomain: "wealth" subdomain: "wealth"
host: "10.0.2.40" host: "10.0.2.40"
port: 8088 port: 8088
auth_required: true internal: false
trek:
subdomain: "trips"
host: "10.0.4.30"
port: 8083
internal: true
homelable:
subdomain: "infra"
host: "10.0.4.30"
port: 8084
internal: true
price-tracker:
subdomain: "prices"
host: "10.0.4.30"
port: 3000
internal: true
kvm:
subdomain: "kvm"
host: "10.0.3.1"
port: 80
internal: true
forward_https: true
# Auth services configuration # Auth services configuration
pocketid_host: 10.0.4.10 pocketid_host: 10.0.4.10
@@ -241,3 +272,5 @@ komodo_db_username: "{{ vault_komodo_db_username }}"
komodo_db_password: "{{ vault_komodo_db_password }}" komodo_db_password: "{{ vault_komodo_db_password }}"
komodo_webhook_secret: "{{ vault_komodo_webhook_secret }}" komodo_webhook_secret: "{{ vault_komodo_webhook_secret }}"
komodo_jwt_secret: "{{ vault_komodo_jwt_secret }}" komodo_jwt_secret: "{{ vault_komodo_jwt_secret }}"
komodo_onboarding_key: "{{ vault_komodo_onboarding_key }}"
komodo_core_host: "10.0.4.10:9120"
+52 -41
View File
@@ -1,42 +1,53 @@
$ANSIBLE_VAULT;1.1;AES256 $ANSIBLE_VAULT;1.1;AES256
38316332346633663733346561333162356230356539346265303565316635353866333166363663 63666330376531356135326331306565336237626132383934313666636134366436383831393730
6238393162336531616335643063353061653339393163360a323365376231393636393938356663 3330643462346434663536383231646364613963373833360a386237306230636131313464363636
39636234653963653930393462323034613361366230323661633537326638346335643235653335 66383366616436633538363430383530613635333462383431643438643962633833363632366438
3931313539333239330a306238646231306333353137656332656263363135343830653864323435 3063323965363031360a353662366435323263356235333633306161363664373738313830333562
63636337346165323030646264653036616237313134653537386436383632353237306136366361 36383637363465396161313332316262383132313061356461626331623134653136353566333539
34626530643230353732366135616661633934323638313430386561636362363961613462653839 39626662663539303337306662666131653738633666393434336338616561353061316132653666
31656130313765326431356437326435343431306561393938356162396562316638343333386164 39313537636561396466646366653366663134386564643036613465623061323261343532373037
62396231323661626438356235393033313834366631343539613430333863653836316132373833 39663931326339636533323631333661643736663066643262383766623163333234336364626165
62373665363062336530613462643839616633653233323135376564653134303134323230623833 36333536313332613966303838393262616335343563643965363664653334613330356134353362
65356133646335663333376137613565386462303137336431346338616239653464633839633462 30643636323661666463393564393434383436346636616333626464306261346337623063306136
66633465363365363037613564636336306261393931303065633839336331656264613534323030 33353034386566323366343739626533323735333638313261336666663964633665313962383364
39303736343835646430326535333264383438343631623036326162653964653664303663383833 65326536333462303637376265366532623435336363346230366631313663366234616436363161
36326430653862303539626461303334313436363930633033343339373464313663326464393633 65663932303138623865306163656464613039643936313532396135393966333632333235636461
33623235643432666430336262626130346564396135343064333837633264383435666266333138 65656636666339303365663766323434633032376561333939306336643563653832306362313134
39613830613639636433326334396165353035623136633534393638376532666134626631333764 64616438393235623964356132626231646539333065663334346139353232663263323331643064
34653061346464306632386162633263616365636536656432666636663935353431633562663635 35636563613836376666643664636265626530363235373265306163386331633631313161346133
64363038633561633532633139356236393463656139333933643261366262386364326231633434 64633038306335373236646632356238343763636433623938373064383834346361316232643830
32326462633834613961303266313963363366613534363961383633366435626466666436306232 64316663653638386466313336666164343036373231623066663033336365303934626236633238
65323365303932343933303238323363326637346363353938653630646135623636313636643437 62643032303863366436313934316563346364383536636236633232656164643032653530376665
34393530343133356432616331386332383632333734346563306162396564373332373761643030 30326639646632346638636232626431396536346165656539343535313266666665336366373333
39343561633764376138643634353463663066303637383262383762623534633536663138383363 31333930656330653031656236316261663366396434353063663831643464663134623063386333
62613863383463316634653633343939343863336531623537343563373065616231393038616335 37386431393336396362373834303135663631343366666333613331333136303134656231343530
63336562306435316338323934343331323436326661373762306533346530326637313863333432 64316562663362656662613932393136383837666666316438393235316666323063373732666334
32316164333164353037313762396532343663623537313461376265666162316239366661396666 38356162396630363430633166306566626131643734636536633264336632323734333163306535
66633637303361333065323234656461663333366163313138666465643634313961326431336331 36633965376362373733616635656565636164636562386566643231363835656634343235373838
35336437626664356431313631353661666465353230303663393931373639326566646338333135 34333730353935643935626366656133353330316165633835626333663539623665653662353138
39626334303438313631366466356431663536353639633931323633333135306432326166383937 61363230303634653131356563376263363339613265373534383561313932616635643038363266
64626630666536343138643034663961353133643166633738663864366266643337636132386334 31663835643830666363646635346538656665363730396366643439326537386264663666363332
37366464346364313166393835633465626535623332386162303564363030386430383966396461 64363561623031626531663934636634313536653435663431666463386362306331353137396362
31626630643432376436396537306362633437663763616432633039386564393966333963386133 65653039623636316338326335613164376366613266316134386163626436383539393932666436
62626563383831636136633539353731626463393861393132353834613936643564333365353934 33633264653230663065393632653935633833313233373933326464316331646363613832633330
32376162636466393637336364363239636530316436653632336233613634623261373037646332 30353263343433366362323365386536313535396430373966323337623830373465356437643734
36346162623164613736316263333132356131643461346332303531633439363037316437393661 32313038323664376566656666633364363634613033376636326635343233666134363864343634
33646234333132393333383461663635626161306431323530333666373935323035373833363462 65386538663835663539353533633931323837373738393463336337353865616439336537353933
33623237393033363930323533663434353535353962376539333431313561393062343466343337 32353162363166353265316534643764616132656534666463363439643237396166303833303038
65303230376136323838313730623866306534646531356637323865393262663363383163623131 66383236363738336363643832666130633464333536623134616265316663333237383739336537
31613063623935616438353735386134356139393634383136363935343739313836653637616533 37376331326139633764353166656634323339656435663531313934376565393639316531663237
38316364303332646135373339343830306437613936323235616133363837616363666435613432 64303931386461346632646161393061376534393539663966373562646432386134616435363261
32313830316164306463623861656361353961313235653730363633616464666533343863396662 37356466303165356336626134313135353830316536323165323233613235643733643333613636
38646138326363386164623062353738363236643164666639383532373934636466303138383637 63666139323931616166346566396162373637336331393034623631346437643632643938323834
37313466353036643766353266653737646363626439303962346235306338396631 35383862653464643533663333616263633465663939633064653666353363363830393565666236
31333663663332363533336132346666643734623131643339643865633565393262643966393734
36663533313638386166653334623832323363636262373931356664323766666538343838313039
39393765343430633336373966346161393130386539393366363366366163316363613038613962
36646336396333333863326565303666373963326163393434313534333533323739623035323137
32346535383263316332653239306633343935383166653139323036616133656638643532646634
64333466666533363932323366353762653461336538303166626330333336616437613037363139
63303764313330656366363630636463303461383565346262656136326362623839366531326663
34663534343564313030613366626337336331343637343564386638653530663631613234636237
37373064303137666263666133363865386135333038653231346562333131313233643062376265
34646635663961393837396637383834376136323864646166656436383338623364613861663537
39313230306465626630
+31 -19
View File
@@ -11,26 +11,38 @@ vms:
storage: purah-mirror-860gb storage: purah-mirror-860gb
cpu_type: host cpu_type: host
# media-1: media-1:
# vmid: 420 vmid: 420
# node: purah node: purah
# cores: 8 template_vmid: 9000
# memory: 16384 cores: 8
# disk: 200G memory: 16384
# ip: 10.0.4.20 disk: 50G
# igpu: true ip: 10.0.4.20
# network_bridge: "vmbr0" network_bridge: "vmbr0"
# storage: purah-mirror-860gb storage: purah-mirror-860gb
# apps-1: apps-1:
# vmid: 430 vmid: 430
# node: yunobo node: yunobo
# cores: 6 template_vmid: 9003
# memory: 16384 cores: 6
# disk: 100G memory: 16384
# ip: 10.0.4.30 disk: 100G
# network_bridge: "vmbr2" ip: 10.0.4.30
# storage: nvme-2tb network_bridge: "vmbr2"
storage: nvme-2tb
apps-2:
vmid: 431
node: yuga
template_vmid: 9004
cores: 5
memory: 12288
disk: 30G
ip: 10.0.4.31
network_bridge: "vmbr0"
storage: media
edge-1: edge-1:
vmid: 401 vmid: 401
+5 -9
View File
@@ -14,17 +14,13 @@ all:
hosts: hosts:
infra-core-1: infra-core-1:
ansible_host: 10.0.4.10 ansible_host: 10.0.4.10
yunobo: media:
hosts: hosts:
media-1: media-1:
ansible_host: 10.0.4.20 ansible_host: 10.0.4.20
apps:
hosts:
apps-1: apps-1:
ansible_host: 10.0.4.30 ansible_host: 10.0.4.30
mipha: apps-2:
hosts: ansible_host: 10.0.4.31
edge-1:
ansible_host: 10.0.4.1
sidon:
hosts:
edge-2:
ansible_host: 10.0.4.2
+46
View File
@@ -0,0 +1,46 @@
---
# NOTE: Adding a komodo-periphery role to an edge means you need to
# create an onboarding key on Komodo and change its value on
# the vault
- hosts: infra
become: yes
roles:
- base
- docker
- komodo
- tinyauth
- pocketid
- website
- hosts: edge
become: yes
roles:
- base
- docker
- keepalived
- traefik
- komodo-periphery
- hosts: media
become: yes
roles:
- base
- docker
- komodo-periphery
- hosts: apps-1
become: yes
roles:
- base
- docker
- komodo-periphery
- trek
- forgejo
- hosts: apps-2
become: yes
roles:
- base
- docker
- komodo-periphery
@@ -0,0 +1,45 @@
---
# Playbook to provision Komodo stacks from git repository
# Automatically discovers stacks from docker-compose folder structure
# Structure: docker-compose/{server}/{app}/compose.yaml
# Usage: ansible-playbook -i inventories/production.yml playbooks/provision_komodo_stacks.yml
- name: Provision Komodo stacks
hosts: localhost
connection: local
gather_facts: false
vars:
docker_compose_root: "{{ playbook_dir }}/../../docker-compose"
tasks:
- name: Find all compose.yaml files in docker-compose directory
find:
paths: "{{ docker_compose_root }}"
patterns: "compose.yaml"
recurse: true
register: compose_files
- name: Build stack list from discovered compose files
set_fact:
komodo_stacks: "{{ komodo_stacks | default([]) + [{'server': path_parts[0], 'app': path_parts[1]}] }}"
loop: "{{ compose_files.files }}"
vars:
path_parts: "{{ item.path | regex_replace('^.*/docker-compose/', '') | split('/') }}"
loop_control:
label: "{{ path_parts[0] }}/{{ path_parts[1] }}"
- name: Display discovered stacks
debug:
msg: "Found {{ komodo_stacks | length }} stacks: {{ komodo_stacks | map(attribute='app') | list | join(', ') }}"
- name: Create Komodo stacks
include_role:
name: komodo_stack
vars:
komodo_stack_name: "{{ item.app }}"
komodo_server_name: "{{ item.server }}"
komodo_run_directory: "docker-compose/{{ item.server }}/{{ item.app }}"
loop: "{{ komodo_stacks }}"
loop_control:
label: "{{ item.app }} (on {{ item.server }})"
+3 -22
View File
@@ -1,24 +1,5 @@
--- ---
# - hosts: localhost - hosts: localhost
# gather_facts: no gather_facts: no
# roles:
# - proxmox_vm
- hosts: infra
become: yes
roles: roles:
- base - proxmox_vm
- docker
- komodo
- tinyauth
- pocketid
- website
- hosts: edge
become: yes
roles:
- base
- docker
- komodo-periphery
- keepalived
- traefik
+6
View File
@@ -0,0 +1,6 @@
---
- hosts: edge
become: yes
roles:
- traefik
- fail2ban
+53
View File
@@ -0,0 +1,53 @@
---
- name: Add data disk to VM for forgejo
community.proxmox.proxmox_disk:
api_host: "{{ proxmox_api_host }}"
api_user: "{{ proxmox_api_user }}"
api_token_id: "{{ proxmox_api_token_id }}"
api_token_secret: "{{ proxmox_api_token_secret }}"
vmid: "{{ app_data_disks.forgejo.vmid }}"
disk: "{{ app_data_disks.forgejo.disk_id }}"
storage: "{{ app_data_disks.forgejo.storage }}"
size: "{{ app_data_disks.forgejo.size }}"
state: present
delegate_to: localhost
become: no
run_once: true
ignore_errors: yes
register: disk_result
- name: Display disk creation result
debug:
var: disk_result
- name: Wait for data disk to be available
wait_for:
path: "{{ app_data_disks.forgejo.device }}"
state: present
timeout: 30
- name: Check if data disk is formatted
command: "blkid {{ app_data_disks.forgejo.device }}"
register: disk_formatted
failed_when: false
changed_when: false
- name: Format data disk with ext4
filesystem:
fstype: ext4
dev: "{{ app_data_disks.forgejo.device }}"
when: disk_formatted.rc != 0
- name: Create forgejo data mount point
file:
path: "{{ app_data_disks.forgejo.mount_point }}"
state: directory
mode: "0755"
- name: Mount data disk
mount:
path: "{{ app_data_disks.forgejo.mount_point }}"
src: "{{ app_data_disks.forgejo.device }}"
fstype: ext4
state: mounted
opts: defaults
@@ -1,17 +1,21 @@
services: services:
periphery: periphery:
image: ghcr.io/moghtech/komodo-periphery:2 image: ghcr.io/moghtech/komodo-periphery:2.1.0
init: true init: true
container_name: komodo-periphery container_name: komodo-periphery
restart: unless-stopped restart: unless-stopped
environment: environment:
PERIPHERY_CORE_ADDRESS: 10.0.4.10:9120 PERIPHERY_CORE_ADDRESS: {{ komodo_core_host }}
PERIPHERY_CONNECT_AS: {{ inventory_hostname }} PERIPHERY_CONNECT_AS: {{ inventory_hostname }}
PERIPHERY_CORE_PUBLIC_KEYS: file:/config/keys/core.pub PERIPHERY_CORE_PUBLIC_KEYS: file:/config/keys/core.pub
PERIPHERY_ROOT_DIRECTORY: /etc/komodo PERIPHERY_ROOT_DIRECTORY: /etc/komodo
PERIPHERY_DISABLE_TERMINALS: false PERIPHERY_DISABLE_TERMINALS: false
PERIPHERY_DISABLE_CONTAINER_TERMINALS: false PERIPHERY_DISABLE_CONTAINER_TERMINALS: false
PERIPHERY_INCLUDE_DISK_MOUNTS: /etc/hostname PERIPHERY_INCLUDE_DISK_MOUNTS: /etc/hostname
KOMODO_SSL_ENABLED: false
PERIPHERY_BIND_IP: 0.0.0.0
PERIPHERY_ONBOARDING_KEY: "{{ komodo_onboarding_key }}"
PERIPHERY_CORE_TLS_INSECURE_SKIP_VERIFY: true
TZ: Europe/Lisbon TZ: Europe/Lisbon
volumes: volumes:
- /data/komodo/app/keys:/config/keys - /data/komodo/app/keys:/config/keys
@@ -38,30 +38,10 @@ services:
KOMODO_RESOURCE_POLL_INTERVAL: "1-hr" KOMODO_RESOURCE_POLL_INTERVAL: "1-hr"
KOMODO_DISABLE_USER_REGISTRATION: true KOMODO_DISABLE_USER_REGISTRATION: true
KOMODO_ENABLE_NEW_USERS: false KOMODO_ENABLE_NEW_USERS: false
KOMODO_SSL_ENABLED: false
KOMODO_TLS_INSECURE_SKIP_VERIFY: true
TZ: "Europe/Lisbon" TZ: "Europe/Lisbon"
volumes: volumes:
- /data/komodo/app/keys:/config/keys - /data/komodo/app/keys:/config/keys
- /data/komodo/app/backups:/backups - /data/komodo/app/backups:/backups
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
periphery:
image: ghcr.io/moghtech/komodo-periphery:2
init: true
container_name: komodo-periphery
restart: unless-stopped
depends_on:
- komodo-core
environment:
PERIPHERY_CORE_ADDRESS: ws://komodo-core:9120
PERIPHERY_CONNECT_AS: "infra-core-1"
PERIPHERY_CORE_PUBLIC_KEYS: file:/config/keys/core.pub
PERIPHERY_ROOT_DIRECTORY: /etc/komodo
PERIPHERY_DISABLE_TERMINALS: false
PERIPHERY_DISABLE_CONTAINER_TERMINALS: false
PERIPHERY_INCLUDE_DISK_MOUNTS: /etc/hostname
TZ: "Europe/Lisbon"
volumes:
- /data/komodo/app/keys:/config/keys
- /var/run/docker.sock:/var/run/docker.sock
- /proc:/proc
- /etc/komodo:/etc/komodo
+86
View File
@@ -0,0 +1,86 @@
# Komodo Stack Role
This role automates the creation of stacks in Komodo via its REST API.
## Quick Start
The included `provision_komodo_stacks.yml` playbook automatically discovers all stacks from your `docker-compose/` folder structure and creates them in Komodo:
```bash
ansible-playbook -i inventories/production.yml playbooks/provision_komodo_stacks.yml
```
This will scan for all `compose.yaml` files in the structure: `docker-compose/{server}/{app}/compose.yaml` and create corresponding stacks in Komodo.
## Requirements
- A running Komodo instance with API access
- Valid Komodo API token stored in `vault_komodo_api_token`
- Existing server and repository configured in Komodo
## Role Variables
### Required Variables
- `komodo_stack_name`: Name for the stack (typically the app name, e.g., "changedetection")
- `komodo_server_name`: Name of the server in Komodo where the stack should deploy
- `komodo_repo_name`: Name of the repository in Komodo containing the stack definition
- `komodo_run_directory`: Path within the repository to the compose.yaml file (e.g., `docker-compose/apps-1/changedetection`)
### Optional Variables
- `komodo_host`: Komodo instance URL (default: `https://komodo.{{ domain }}`)
- `komodo_api_token`: API token for authentication (default: `{{ vault_komodo_api_token }}`)
## Example Usage
### Automatic Discovery (Recommended)
Use the provided playbook to automatically discover and create all stacks from your repository:
```bash
ansible-playbook -i inventories/production.yml playbooks/provision_komodo_stacks.yml
```
This scans the `docker-compose/` directory and creates stacks for each app found in the structure:
- `docker-compose/{server}/{app}/compose.yaml`
Example structure:
```
docker-compose/
apps-1/
changedetection/
compose.yaml
turn/
compose.yaml
media-1/
arr/
compose.yaml
```
## How It Works
1. Validates all required variables are provided
2. Fetches list of servers from Komodo API and finds the server ID by name
3. Fetches list of repositories from Komodo API and finds the repo ID by name
4. Checks if a stack with the same name already exists
5. Creates the stack if it doesn't exist (skips if it does)
## API Token Setup
Store your Komodo API token in an Ansible vault file:
```yaml
# inventories/group_vars/all/vault.yml
vault_komodo_api_token: 'your-api-token-here'
```
Generate an API token in Komodo: Settings → API Keys → Create New Token
## Notes
- The role is idempotent - it won't create duplicate stacks
- Server and repo must already exist in Komodo before running this role
- Uses retry logic for API calls to handle temporary network issues
@@ -0,0 +1,13 @@
---
# Komodo API configuration
komodo_core_address: "http://{{ komodo_core_host }}"
komodo_api_key: "{{ vault_komodo_api_key }}"
komodo_api_secret: "{{ vault_komodo_api_secret }}"
# Stack configuration
komodo_repo_name: "homelab"
# Optional stack configuration
komodo_stack_environment: []
komodo_stack_labels: {}
+129
View File
@@ -0,0 +1,129 @@
---
- name: Validate required variables
assert:
that:
- komodo_stack_name is defined and komodo_stack_name | length > 0
- komodo_server_name is defined and komodo_server_name | length > 0
- komodo_repo_name is defined and komodo_repo_name | length > 0
- komodo_run_directory is defined and komodo_run_directory | length > 0
- komodo_api_key is defined and komodo_api_key | length > 0
- komodo_api_secret is defined and komodo_api_secret | length > 0
fail_msg: "Missing required variables: komodo_stack_name, komodo_server_name, komodo_repo_name, komodo_run_directory, komodo_api_key, or komodo_api_secret"
- name: Get Komodo server by name
uri:
url: "{{ komodo_core_address }}/read/GetServer"
method: POST
headers:
"X-Api-Key": "{{ komodo_api_key }}"
"X-Api-Secret": "{{ komodo_api_secret }}"
"Content-Type": "application/json"
body_format: json
body:
server: "{{ komodo_server_name }}"
status_code: 200
validate_certs: false
register: komodo_server
retries: 3
delay: 2
- name: Extract server ID
set_fact:
komodo_server_id: "{{ komodo_server.json._id['$oid'] }}"
- name: Debug server response
debug:
msg: "Server ID: {{ komodo_server_id }}"
- name: Get Komodo repo by name
uri:
url: "{{ komodo_core_address }}/read/GetRepo"
method: POST
headers:
"X-Api-Key": "{{ komodo_api_key }}"
"X-Api-Secret": "{{ komodo_api_secret }}"
"Content-Type": "application/json"
body_format: json
body:
repo: "{{ komodo_repo_name }}"
status_code: 200
validate_certs: false
register: komodo_repo
retries: 3
delay: 2
- name: Extract repo ID
set_fact:
komodo_repo_id: "{{ komodo_repo.json._id['$oid'] }}"
- name: Debug repo response
debug:
msg: "Repo ID: {{ komodo_repo_id }}"
- name: Check if stack already exists
uri:
url: "{{ komodo_core_address }}/read/GetStack"
method: POST
headers:
"X-Api-Key": "{{ komodo_api_key }}"
"X-Api-Secret": "{{ komodo_api_secret }}"
"Content-Type": "application/json"
body_format: json
body:
stack: "{{ komodo_stack_name }}"
status_code: [200, 500]
validate_certs: false
register: existing_stack
retries: 3
delay: 2
failed_when: false
- name: Create Komodo stack
uri:
url: "{{ komodo_core_address }}/write/CreateStack"
method: POST
headers:
"X-Api-Key": "{{ komodo_api_key }}"
"X-Api-Secret": "{{ komodo_api_secret }}"
"Content-Type": "application/json"
body_format: json
body:
name: "{{ komodo_stack_name }}"
config:
server_id: "{{ komodo_server_id }}"
linked_repo: "{{ komodo_repo_id }}"
run_directory: "{{ komodo_run_directory }}"
status_code: [200, 201]
validate_certs: false
register: stack_result
when: existing_stack.status == 500
retries: 3
delay: 2
- name: Display stack creation result
debug:
msg: "Stack '{{ komodo_stack_name }}' created successfully with ID: {{ stack_result.json._id }}"
when: existing_stack.status == 500 and stack_result is succeeded
- name: Display stack already exists message
debug:
msg: "Stack '{{ komodo_stack_name }}' already exists with ID: {{ existing_stack.json._id }}"
when: existing_stack.status == 200
- name: Deploy stack
uri:
url: "{{ komodo_core_address }}/execute/DeployStack"
method: POST
headers:
"X-Api-Key": "{{ komodo_api_key }}"
"X-Api-Secret": "{{ komodo_api_secret }}"
"Content-Type": "application/json"
body_format: json
body:
stack: "{{ komodo_stack_name }}"
status_code: 200
validate_certs: false
register: start_result
retries: 3
delay: 2
when: existing_stack.status == 500 and stack_result is succeeded
@@ -36,6 +36,16 @@ http:
insecureSkipVerify: true insecureSkipVerify: true
routers: routers:
# Local IP bypass - HTTPS (higher priority, no auth)
traefik-secure-local:
rule: "Host(`traefik.{{ domain }}`) && (ClientIP(`192.168.0.0/16`) || ClientIP(`10.0.0.0/8`) || ClientIP(`172.16.0.0/12`))"
entryPoints:
- https
priority: 200
service: api@internal
tls:
certResolver: cloudflare
# Static services - HTTPS # Static services - HTTPS
traefik-secure: traefik-secure:
rule: "Host(`traefik.{{ domain }}`)" rule: "Host(`traefik.{{ domain }}`)"
@@ -106,6 +116,21 @@ http:
# Auto-configured services - HTTPS # Auto-configured services - HTTPS
{% for service_name, config in auto_configure_traefik.items() %} {% for service_name, config in auto_configure_traefik.items() %}
{% if config.internal | default(true) %}
# {{ service_name }} - local IP bypass (no auth)
{{ service_name }}-local:
rule: "Host(`{{ config.subdomain }}.{{ domain }}`) && (ClientIP(`192.168.0.0/16`) || ClientIP(`10.0.0.0/8`) || ClientIP(`172.16.0.0/12`))"
entryPoints:
- https
priority: 200
{% if config.forward_https | default(false) %}
middlewares:
- {{ service_name }}-https-headers
{% endif %}
service: {{ service_name }}
tls:
certResolver: cloudflare
{% endif %}
{% if config.auth_bypass_paths is defined %} {% if config.auth_bypass_paths is defined %}
# {{ service_name }} - bypass paths (no auth) # {{ service_name }} - bypass paths (no auth)
{% for path in config.auth_bypass_paths %} {% for path in config.auth_bypass_paths %}
@@ -128,9 +153,9 @@ http:
entryPoints: entryPoints:
- https - https
priority: 1 priority: 1
{% if config.auth_required | default(true) or config.forward_https | default(false) %} {% if config.internal | default(true) or config.forward_https | default(false) %}
middlewares: middlewares:
{% if config.auth_required | default(true) %} {% if config.internal | default(true) %}
- pocketid-auth - pocketid-auth
{% endif %} {% endif %}
{% if config.forward_https | default(false) %} {% if config.forward_https | default(false) %}
@@ -145,9 +170,9 @@ http:
rule: "Host(`{{ config.subdomain }}.{{ domain }}`)" rule: "Host(`{{ config.subdomain }}.{{ domain }}`)"
entryPoints: entryPoints:
- https - https
{% if config.auth_required | default(true) or config.forward_https | default(false) %} {% if config.internal | default(true) or config.forward_https | default(false) %}
middlewares: middlewares:
{% if config.auth_required | default(true) %} {% if config.internal | default(true) %}
- pocketid-auth - pocketid-auth
{% endif %} {% endif %}
{% if config.forward_https | default(false) %} {% if config.forward_https | default(false) %}
+53
View File
@@ -0,0 +1,53 @@
---
- name: Add data disk to VM for trek
community.proxmox.proxmox_disk:
api_host: "{{ proxmox_api_host }}"
api_user: "{{ proxmox_api_user }}"
api_token_id: "{{ proxmox_api_token_id }}"
api_token_secret: "{{ proxmox_api_token_secret }}"
vmid: "{{ app_data_disks.trek.vmid }}"
disk: "{{ app_data_disks.trek.disk_id }}"
storage: "{{ app_data_disks.trek.storage }}"
size: "{{ app_data_disks.trek.size }}"
state: present
delegate_to: localhost
become: no
run_once: true
ignore_errors: yes
register: disk_result
- name: Display disk creation result
debug:
var: disk_result
- name: Wait for data disk to be available
wait_for:
path: "{{ app_data_disks.trek.device }}"
state: present
timeout: 30
- name: Check if data disk is formatted
command: "blkid {{ app_data_disks.trek.device }}"
register: disk_formatted
failed_when: false
changed_when: false
- name: Format data disk with ext4
filesystem:
fstype: ext4
dev: "{{ app_data_disks.trek.device }}"
when: disk_formatted.rc != 0
- name: Create trek data mount point
file:
path: "{{ app_data_disks.trek.mount_point }}"
state: directory
mode: "0755"
- name: Mount data disk
mount:
path: "{{ app_data_disks.trek.mount_point }}"
src: "{{ app_data_disks.trek.device }}"
fstype: ext4
state: mounted
opts: defaults
-11
View File
@@ -1,11 +0,0 @@
---
- name: Create app directories
ansible.builtin.file:
path: "/data/{{ item }}"
state: directory
mode: "0755"
loop:
- paperless
- nextcloud
- mealie
- outline
-10
View File
@@ -1,10 +0,0 @@
---
- name: Create infra directories
ansible.builtin.file:
path: "/data/{{ item }}"
state: directory
mode: "0755"
loop:
- vaultwarden
- pi-hole
- uptime-kuma
-10
View File
@@ -1,10 +0,0 @@
---
- name: Ensure VM has iGPU passthrough (requires Proxmox pre-config)
ansible.builtin.debug:
msg: "Ensure /dev/dri is passed through on this VM: {{ inventory_hostname }}"
- name: Mount media storage
ansible.builtin.file:
path: /data/media
state: directory
mode: "0755"
@@ -0,0 +1,8 @@
services:
dahuavto2mqtt:
image: "registry.gitlab.com/elad.bar/dahuavto2mqtt:latest"
container_name: "dahua2mqtt"
hostname: "dahua2mqtt"
restart: "unless-stopped"
env_file:
- .env
@@ -0,0 +1,38 @@
services:
server:
image: codeberg.org/forgejo/forgejo:15
container_name: forgejo
environment:
- USER_UID=1000
- USER_GID=1000
restart: always
volumes:
- /data/forgejo:/data
- /etc/localtime:/etc/localtime:ro
ports:
- "8086:3000"
- "222:22"
docker-in-docker:
image: docker:dind
container_name: "docker_dind"
privileged: "true"
command: ["dockerd", "-H", "tcp://0.0.0.0:2375", "--tls=false"]
restart: "unless-stopped"
runner:
image: "data.forgejo.org/forgejo/runner:12"
links:
- docker-in-docker
depends_on:
docker-in-docker:
condition: service_started
container_name: "runner"
environment:
DOCKER_HOST: tcp://docker-in-docker:2375
# User without root privileges, but with access to `./data`.
user: 1001:1001
volumes:
- /data/forgejo/runner:/data
restart: "unless-stopped"
command: "forgejo-runner daemon --config /data/runner-config.yml"
@@ -0,0 +1,36 @@
services:
backend:
image: ghcr.io/pouzor/homelable-backend:latest
restart: unless-stopped
env_file:
- .env
environment:
# Override env_file: SQLite path must point inside the container volume
SQLITE_PATH: /app/data/homelab.db
volumes:
- /data/homelable:/app/data
networks:
- homelable
# Required for ping-based status checks
cap_add:
- NET_RAW
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/health"]
interval: 10s
timeout: 5s
retries: 6
start_period: 15s
frontend:
image: ghcr.io/pouzor/homelable-frontend:latest
restart: unless-stopped
ports:
- "8084:80"
depends_on:
- backend
networks:
- homelable
networks:
homelable:
driver: bridge
@@ -0,0 +1,7 @@
services:
webserver:
image: git.lino.cooking/lino/bpi-stock-price-scraper:latest
container_name: price-tracker
ports:
- "3000:3000"
restart: unless-stopped
+210
View File
@@ -0,0 +1,210 @@
# SparkyFitness Environment Variables
# Copy this file to .env in the root directory and fill in your own values before running 'docker-compose up'.
# --- PostgreSQL Database Settings ---
# These values should match the ones used by your PostgreSQL container.
# For local development (running Node.js directly), use 'localhost' or '127.0.0.1' if PostgreSQL is on your host.
SPARKY_FITNESS_DB_NAME=sparkyfitness_db
#SPARKY_FITNESS_DB_USER is super user for DB initialization and migrations.
SPARKY_FITNESS_DB_USER=sparky
SPARKY_FITNESS_DB_PASSWORD=changeme_db_password
# Application database user with limited privileges. it can be changed any time after initialization.
SPARKY_FITNESS_APP_DB_USER=sparky_app
SPARKY_FITNESS_APP_DB_PASSWORD=password
# For Docker Compose deployments, SPARKY_FITNESS_DB_HOST will be the service name (e.g., 'sparkyfitness-db').
#SPARKY_FITNESS_DB_HOST=sparkyfitness-db
# SPARKY_FITNESS_DB_PORT controls the HOST port for external database access (e.g., pgAdmin, DBeaver).
# To use this, you must also uncomment the 'ports' section under sparkyfitness-db in docker-compose.prod.yml.
# Inside Docker, containers always communicate on port 5432 (the internal PostgreSQL port).
# Changing this value will NOT affect container-to-container communication.
#SPARKY_FITNESS_DB_PORT=5432
# --- Backend Server Settings ---
# The hostname or IP address of the backend server.
# For Docker Compose, this is typically the service name (e.g., 'sparkyfitness-server').
# For local development or other deployments, this might be 'localhost' or a specific IP.
SPARKY_FITNESS_SERVER_HOST=sparkyfitness-server
# The external port the server will be exposed on.
SPARKY_FITNESS_SERVER_PORT=3010
# The public URL of your frontend (e.g., https://fitness.example.com). This is crucial for CORS security.
# For local development, use http://localhost:8080. For production, use your domain with https.
SPARKY_FITNESS_FRONTEND_URL=http://localhost:3004
# Allow CORS requests from private network addresses (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, localhost, etc.)
# SECURITY WARNING: Only enable this if you are running on a private/self-hosted network.
# Do NOT enable on shared hosting or cloud environments where other users might access your network.
# Default: false (secure default - only the configured SPARKY_FITNESS_FRONTEND_URL is allowed)
#ALLOW_PRIVATE_NETWORK_CORS=false
# A comma-separated list of additional URLs that Better Auth should trust.
# This is useful when accessing the app from a specific local IP on your network.
# Example: SPARKY_FITNESS_EXTRA_TRUSTED_ORIGINS=http://192.168.1.175:8080,http://10.0.0.5:8080
# SPARKY_FITNESS_EXTRA_TRUSTED_ORIGINS=
# Logging level for the server (e.g., INFO, DEBUG, WARN, ERROR)
SPARKY_FITNESS_LOG_LEVEL=ERROR
# Node.js environment mode (e.g., development, production, test)
# Set to 'production' for deployment to ensure optimal performance and security.
NODE_ENV=production
# Server timezone. Use a TZ database name (e.g., 'America/New_York', 'Etc/UTC').
# This affects how dates/times are handled by the server if not explicitly managed in code.
TZ=Europe/Lisbon
# --- Security Settings ---
# A 64-character hex string for data encryption.
# You can generate a secure key with the following command:
# openssl rand -hex 32
# or
# node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Changing this will invalidate existing encrypted data. You will need to delete and add External Data sources again.
SPARKY_FITNESS_API_ENCRYPTION_KEY=b65743a2f9946be8b7f14083f59183f26bc07c9d2d352a96b2d186a06907e111
# For Docker Swarm/Kubernetes secrets, you can use a file-based secret:
# SPARKY_FITNESS_API_ENCRYPTION_KEY_FILE=/run/secrets/sparkyfitness_api_key
# BETTER_AUTH_SECRET is used to sign sessions and encrypt 2FA/TOTP data.
# CRITICAL: If you change this after users have enabled 2FA, they will be LOCKED OUT of their accounts.
# Ensure this is set to a strong, persistent value during initial setup and is never changed.
# If you MUST change it, all users must disable Two-Factor Authentication (TOTP) first.
BETTER_AUTH_SECRET=297f9b19e4f13b3ce45af8f1fcc8234264223b6c2052e1f51ea6869583325ecc
# For Docker Swarm/Kubernetes secrets, you can use a file-based secret:
# BETTER_AUTH_SECRET_FILE=/run/secrets/sparkyfitness_better_auth_secret
# --- Signup Settings ---
# Set to 'true' to disable new user registrations.
SPARKY_FITNESS_DISABLE_SIGNUP=false
# --- Admin Settings ---
# Set the email of a user to automatically grant admin privileges on server startup.
# This is useful for development or initial setup.
# Example: SPARKY_FITNESS_ADMIN_EMAIL=admin@example.com
# Optional. If not set, no admin user will be created automatically.
SPARKY_FITNESS_ADMIN_EMAIL=sparkyfitness@lino.cooking
# --- OIDC Authentication Configuration ---
# Set to 'true' to disable email/password login on the login page (overridden by SPARKY_FITNESS_FORCE_EMAIL_LOGIN).
SPARKY_FITNESS_DISABLE_EMAIL_LOGIN=true
# Set to 'true' to enable OIDC login. When set, overrides the database value from Admin > Authentication Settings.
SPARKY_FITNESS_OIDC_AUTH_ENABLED=true
# Display name and provider slug (URL-safe id) for the ENV-configured OIDC provider.
SPARKY_FITNESS_OIDC_PROVIDER_SLUG=pocket-id
SPARKY_FITNESS_OIDC_PROVIDER_NAME=Pocket ID
SPARKY_FITNESS_OIDC_AUTO_REGISTER=true
SPARKY_FITNESS_OIDC_LOGO_URL=https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/pocket-id.svg
# OIDC issuer URL (e.g. https://example.com). Discovery URL is derived as issuer + /.well-known/openid-configuration.
# When set with CLIENT_ID, CLIENT_SECRET and PROVIDER_SLUG, an ENV-configured OIDC provider is upserted at startup.
SPARKY_FITNESS_OIDC_ISSUER_URL=https://auth.lino.cooking/application/o/{{slug}}/
# SPARKY_FITNESS_OIDC_DOMAIN=example.com
# OIDC client credentials from your IdP.
# SPARKY_FITNESS_OIDC_CLIENT_ID=
# SPARKY_FITNESS_OIDC_CLIENT_SECRET=
# SPARKY_FITNESS_OIDC_SCOPE=openid email profile
# Set to 'true' to allow auto-redirect to the single OIDC provider when email login is disabled.
SPARKY_FITNESS_OIDC_AUTO_REDIRECT=true
# Group/claim values from your IdP for role mapping (admin vs user). Configure your IdP to send these in token claims.
# SPARKY_FITNESS_OIDC_ADMIN_GROUP=Admin
# --- Advanced OIDC Settings ---
# SPARKY_FITNESS_OIDC_TOKEN_AUTH_METHOD=client_secret_post
# SPARKY_FITNESS_OIDC_ID_TOKEN_SIGNED_ALG=RS256
# SPARKY_FITNESS_OIDC_USERINFO_SIGNED_ALG=none
# SPARKY_FITNESS_OIDC_TIMEOUT=30000
# Set custom uploads and backups directory. Only needed for standalone installation
# SPARKY_FITNESS_CUSTOM_UPLOADS_DIRECTORY=
# SPARKY_FITNESS_CUSTOM_BACKUP_DIRECTORY=
#
# --- Login Management Fail-Safe ---
# Set to 'true' to force email/password login to be enabled, overriding any in-app settings.
# This is a fail-safe to prevent being locked out if OIDC is misconfigured.
# SPARKY_FITNESS_FORCE_EMAIL_LOGIN=true
# --- Email Settings (Optional) ---
# Configure these variables if you want to enable email notifications (e.g., for password resets).
# If not configured, email functionality will be disabled.
# SPARKY_FITNESS_EMAIL_HOST=smtp.example.com
# SPARKY_FITNESS_EMAIL_PORT=587
# SPARKY_FITNESS_EMAIL_SECURE=true # Use 'true' for TLS/SSL, 'false' for plain text
# SPARKY_FITNESS_EMAIL_USER=your_email@example.com
# SPARKY_FITNESS_EMAIL_PASS=your_email_password
# SPARKY_FITNESS_EMAIL_FROM=no-reply@example.com
# --- Volume Paths (Optional) ---
# These paths define where Docker volumes will store persistent data on your host.
# If not set, Docker will manage volumes automatically in its default location.
DB_PATH=/data/sparky-fitness/postgresql # Path for PostgreSQL database data
SERVER_BACKUP_PATH=/data/sparky-fitness/backup # Path for server backups
SERVER_UPLOADS_PATH=/data/sparky-fitness/uploads # Path for profile pictures and exercise images
# --- API Key Rate Limiting (Optional) ---
# Override the default rate limit for API key authentication (used by automation tools like n8n).
# Defaults to 100 requests per 60-second window if not set.
#SPARKY_FITNESS_API_KEY_RATELIMIT_WINDOW_MS=60000
#SPARKY_FITNESS_API_KEY_RATELIMIT_MAX_REQUESTS=100
# --- Start of Garmin Integration Settings ---
#Below variables are needed only for Garmin integration. If you don't use Garmin integration, you can remove them in your .env file.
# The URL for the Garmin microservice.
# For Docker Compose, this would typically be the service name and port (e.g., 'http://sparkyfitness-garmin:8000').
# For local development, use 'http://localhost:8000' or the port you've configured.
# GARMIN_MICROSERVICE_URL=http://sparkyfitness-garmin:8000
# This is used for Garmin Connect synchronization.
# If you are not using Garmin integration, you don't need this. Make sure this matches with GARMIN_MICROSERVICE_URL.
# GARMIN_SERVICE_PORT=8000
# set to true for China region. Everything else should be false. Optional - defaults to false
# GARMIN_SERVICE_IS_CN=false
# --- End of Garmin Integration Settings ---
# --- MCP Server Settings ---
# The port the MCP server will listen on.
# SPARKY_FITNESS_MCP_PORT=3001
# Vision API Settings (for sparky_analyze_food_image and sparky_scan_label)
# Supported providers: gemini, openai, anthropic
# VISION_API_PROVIDER=gemini
# VISION_API_KEY=
# Set to 'true' to enable developer tools (e.g., sparky_inspect_schema)
# Requires the authenticated user to have the 'admin' role.
# DEV_TOOLS_ENABLED=false
# ----- Developers Section -----
# Data source for external integrations (fitbit, garmin, withings).
# By default, these use live APIs. Set to 'local' to use mock data from the mock_data directory.
# To use these variables, you will also need to pass to Server container. For Garmin, pass to Garmin container.
#SPARKY_FITNESS_FITBIT_DATA_SOURCE=local
#SPARKY_FITNESS_WITHINGS_DATA_SOURCE=local
#SPARKY_FITNESS_GARMIN_DATA_SOURCE=local
#SPARKY_FITNESS_POLAR_DATA_SOURCE=local
#SPARKY_FITNESS_HEVY_DATA_SOURCE=local
# Set to 'true' to capture live API responses into mock data JSON files. Defaults to false.
#SPARKY_FITNESS_SAVE_MOCK_DATA=false
#-----------------------------
@@ -0,0 +1,70 @@
services:
sparkyfitness-db:
image: postgres:18.3-alpine
container_name: sparkyfitness-db
restart: always
# Uncomment below to expose PostgreSQL to the host (e.g., for pgAdmin, DBeaver).
# ports:
# - "${SPARKY_FITNESS_DB_PORT:-5432}:5432"
environment:
POSTGRES_DB: ${SPARKY_FITNESS_DB_NAME:?Variable is required and must be set}
POSTGRES_USER: ${SPARKY_FITNESS_DB_USER:?Variable is required and must be set}
POSTGRES_PASSWORD: ${SPARKY_FITNESS_DB_PASSWORD:?Variable is required and must be set}
PUID: 1000
GUID: 1000
volumes:
- ${DB_PATH:-./postgresql}:/var/lib/postgresql
sparkyfitness-server:
image: codewithcj/sparkyfitness_server:latest # Use pre-built image
environment:
SPARKY_FITNESS_LOG_LEVEL: ${SPARKY_FITNESS_LOG_LEVEL}
ALLOW_PRIVATE_NETWORK_CORS: ${ALLOW_PRIVATE_NETWORK_CORS:-false}
SPARKY_FITNESS_EXTRA_TRUSTED_ORIGINS: ${SPARKY_FITNESS_EXTRA_TRUSTED_ORIGINS:-}
SPARKY_FITNESS_DB_USER: ${SPARKY_FITNESS_DB_USER:-sparky}
SPARKY_FITNESS_DB_HOST: ${SPARKY_FITNESS_DB_HOST:-sparkyfitness-db} # Use the service name 'sparkyfitness-db' for inter-container communication
SPARKY_FITNESS_DB_NAME: ${SPARKY_FITNESS_DB_NAME}
SPARKY_FITNESS_DB_PASSWORD: ${SPARKY_FITNESS_DB_PASSWORD:?Variable is required and must be set}
SPARKY_FITNESS_APP_DB_USER: ${SPARKY_FITNESS_APP_DB_USER:-sparkyapp}
SPARKY_FITNESS_APP_DB_PASSWORD: ${SPARKY_FITNESS_APP_DB_PASSWORD:?Variable is required and must be set}
SPARKY_FITNESS_DB_PORT: 5432 # Uses internal container port. Do NOT change this if SPARKY_FITNESS_DB_HOST is using container name.
SPARKY_FITNESS_API_ENCRYPTION_KEY: ${SPARKY_FITNESS_API_ENCRYPTION_KEY:?Variable is required and must be set}
# Uncomment the line below and comment the line above to use a file-based secret
# SPARKY_FITNESS_API_ENCRYPTION_KEY_FILE: /run/secrets/sparkyfitness_api_key
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?Variable is required and must be set}
# Uncomment the line below and comment the line above to use a file-based secret
# BETTER_AUTH_SECRET_FILE: /run/secrets/sparkyfitness_better_auth_secret
SPARKY_FITNESS_FRONTEND_URL: ${SPARKY_FITNESS_FRONTEND_URL:-http://0.0.0.0:3004}
SPARKY_FITNESS_DISABLE_SIGNUP: ${SPARKY_FITNESS_DISABLE_SIGNUP}
SPARKY_FITNESS_ADMIN_EMAIL: ${SPARKY_FITNESS_ADMIN_EMAIL} #User with this email can access the admin panel
SPARKY_FITNESS_EMAIL_HOST: ${SPARKY_FITNESS_EMAIL_HOST}
SPARKY_FITNESS_EMAIL_PORT: ${SPARKY_FITNESS_EMAIL_PORT}
SPARKY_FITNESS_EMAIL_SECURE: ${SPARKY_FITNESS_EMAIL_SECURE}
SPARKY_FITNESS_EMAIL_USER: ${SPARKY_FITNESS_EMAIL_USER}
SPARKY_FITNESS_EMAIL_PASS: ${SPARKY_FITNESS_EMAIL_PASS}
SPARKY_FITNESS_EMAIL_FROM: ${SPARKY_FITNESS_EMAIL_FROM}
GARMIN_MICROSERVICE_URL: http://sparkyfitness-garmin:8000 # Add Garmin microservice URL
PUID: 1000
GUID: 1000
restart: always
depends_on:
- sparkyfitness-db # Backend depends on the database being available
volumes:
- ${SERVER_BACKUP_PATH:-./backup}:/app/SparkyFitnessServer/backup # Mount volume for backups
- ${SERVER_UPLOADS_PATH:-./uploads}:/app/SparkyFitnessServer/uploads # Mount volume for Profile pictures and excercise images
sparkyfitness-frontend:
image: codewithcj/sparkyfitness:latest # Use pre-built image
ports:
- "8087:80" # Map host port 8087 to container port 80 (Nginx)
environment:
SPARKY_FITNESS_FRONTEND_URL: ${SPARKY_FITNESS_FRONTEND_URL}
SPARKY_FITNESS_SERVER_HOST: sparkyfitness-server # Internal Docker service name for the backend
SPARKY_FITNESS_SERVER_PORT: 3010 # Port the backend server listens on
PUID: 1000
GUID: 1000
restart: always
depends_on:
- sparkyfitness-server # Frontend depends on the server
#- sparkyfitness-garmin # Frontend depends on Garmin microservice. Enable if you are using Garmin Connect features.
@@ -0,0 +1,19 @@
services:
speedtest:
container_name: speedtest
image: henrywhitaker3/speedtest-tracker
ports:
- 8085:80
volumes:
- /data/speedtest:/config
environment:
- TZ=Europe/Lisbon
- PGID=1000
- PUID=1000
- OOKLA_EULA_GDPR=true
logging:
driver: "json-file"
options:
max-file: "10"
max-size: "200k"
restart: unless-stopped
+44
View File
@@ -0,0 +1,44 @@
services:
app:
image: mauriceboe/trek:latest
container_name: trek
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
tmpfs:
- /tmp:noexec,nosuid,size=64m
ports:
- "8083:3000"
environment:
- NODE_ENV=production
- PORT=3000
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
- TZ=Europe/Lisbon
- LOG_LEVEL=info
- ALLOWED_ORIGINS=https://trips.lino.cooking
- FORCE_HTTPS=true
- TRUST_PROXY=1
- ALLOW_INTERNAL_NETWORK=true
- APP_URL=https://trips.lino.cooking
- OIDC_ISSUER=https://auth.lino.cooking
- OIDC_CLIENT_ID=652278a5-b695-4589-9d51-d23cfb2e15dd
- OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET}
- OIDC_DISPLAY_NAME=PocketID
- OIDC_ONLY=true
- OIDC_DISCOVERY_URL=https://auth.lino.cooking/.well-known/openid-configuration
volumes:
- /data/trek/app:/app/data
- /data/trek/uploads:/app/uploads
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/health"]
interval: 30s
timeout: 15s
retries: 3
start_period: 15s
+7
View File
@@ -0,0 +1,7 @@
services:
webserver:
image: nginx:alpine
container_name: hello-webserver
ports:
- "8082:80"
restart: unless-stopped
+7
View File
@@ -0,0 +1,7 @@
services:
webserver:
image: nginx:alpine
container_name: hello-webserver
ports:
- "8080:80"
restart: unless-stopped
@@ -0,0 +1,7 @@
services:
webserver:
image: nginx:alpine
container_name: hello-webserver
ports:
- "8081:80"
restart: unless-stopped
@@ -0,0 +1,7 @@
services:
webserver:
image: nginx:alpine
container_name: hello-webserver
ports:
- "8082:80"
restart: unless-stopped