Compare commits

...

50 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
31 changed files with 1038 additions and 123 deletions
@@ -11,30 +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
vmid: 410
node: purah
size: "20"
storage: purah-mirror-860gb
disk_id: scsi2
mount_point: /data/komodo
device: /dev/sdc
komodo-periphery-media:
vm: media-1
vmid: 420
node: purah
size: "20"
storage: purah-mirror-860gb
disk_id: scsi1
mount_point: /data/komodo-periphery
device: /dev/sdb
komodo-periphery-apps:
vm: apps-1 vm: apps-1
vmid: 430 vmid: 430
node: yunobo node: yunobo
size: "20" size: "10"
storage: nvme-2tb storage: nvme-2tb
disk_id: scsi1 disk_id: scsi1
mount_point: /data/komodo-periphery mount_point: /data/trek
device: /dev/sdb device: /dev/sdb
forgejo:
vm: apps-1
vmid: 430
node: yunobo
size: "50"
storage: nvme-2tb
disk_id: scsi2
mount_point: /data/forgejo
device: /dev/sdc
+78 -47
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.15" 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
@@ -242,4 +273,4 @@ 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_onboarding_key: "{{ vault_komodo_onboarding_key }}"
komodo_core_address: "komodo.lino.cooking" komodo_core_host: "10.0.4.10:9120"
+52 -45
View File
@@ -1,46 +1,53 @@
$ANSIBLE_VAULT;1.1;AES256 $ANSIBLE_VAULT;1.1;AES256
34633839643463656436373363623239343634393834353863336665613863663464316636343564 63666330376531356135326331306565336237626132383934313666636134366436383831393730
6561613766613735303865363339326563363961343739370a626535346366366261666131653565 3330643462346434663536383231646364613963373833360a386237306230636131313464363636
39653832326235353662656466626132383531613338653138363039633563313536313765333166 66383366616436633538363430383530613635333462383431643438643962633833363632366438
6265343938386332340a313230353564653739633939363031623130316265373538333237343133 3063323965363031360a353662366435323263356235333633306161363664373738313830333562
39303831303230323738383565303335363763353462353966643832626131646436336434323138 36383637363465396161313332316262383132313061356461626331623134653136353566333539
66636635326634383137376431323736613434656533633664383161393338326565336130303930 39626662663539303337306662666131653738633666393434336338616561353061316132653666
31303232393332623839323162373239616634356434643364323933613335363861343738313661 39313537636561396466646366653366663134386564643036613465623061323261343532373037
63306435303962323566346564383066636634663536646264383961343461623236386362336239 39663931326339636533323631333661643736663066643262383766623163333234336364626165
36316536346533626438366430363664383135346337303239643335656261626639323433313765 36333536313332613966303838393262616335343563643965363664653334613330356134353362
33613564626235323337343164346235336663613238323966346331623266613832633234393232 30643636323661666463393564393434383436346636616333626464306261346337623063306136
39393565383235373365353538373764333433386439333132643064373839643963376333373032 33353034386566323366343739626533323735333638313261336666663964633665313962383364
34613361663630323937333162373534356263373861363163303062363232663731316164653966 65326536333462303637376265366532623435336363346230366631313663366234616436363161
39346163353635663738316262363465643639313766346339333963333964336265303666643064 65663932303138623865306163656464613039643936313532396135393966333632333235636461
62643366613034663838326436313739363863316666393238306564306539623162636266393033 65656636666339303365663766323434633032376561333939306336643563653832306362313134
39653438663938346233656630366130316161373363393561663631663737373131383736666136 64616438393235623964356132626231646539333065663334346139353232663263323331643064
66346231663338643735313739343361643431396638613837303833616539373261346163306132 35636563613836376666643664636265626530363235373265306163386331633631313161346133
39656436626635633733396138303963343135343437313637346565643835613739653839343938 64633038306335373236646632356238343763636433623938373064383834346361316232643830
61623561303930306335383963393561393735316331323639373536616236383532366136653134 64316663653638386466313336666164343036373231623066663033336365303934626236633238
63366661363332366338656234386139363066363239656136383634623430303262626465356536 62643032303863366436313934316563346364383536636236633232656164643032653530376665
34303862303763613761616134323236316232373931386336653034633230396633363965313331 30326639646632346638636232626431396536346165656539343535313266666665336366373333
31666234363064323331666535316134623461663433366538613133396633353161323766363437 31333930656330653031656236316261663366396434353063663831643464663134623063386333
66396439303235313137393135383837323737663731386331376362613964323737366633623733 37386431393336396362373834303135663631343366666333613331333136303134656231343530
31353261306163356333303333393466643338343765323637656565333762386133363031396638 64316562663362656662613932393136383837666666316438393235316666323063373732666334
34386639393133303134383034316439363262386461346534326236326135623964363461646430 38356162396630363430633166306566626131643734636536633264336632323734333163306535
33623433306130393232353135623332396136653230383439333665303737343137383864356433 36633965376362373733616635656565636164636562386566643231363835656634343235373838
37313262383961656133393731656162316433356336336530333862363561353238663433343131 34333730353935643935626366656133353330316165633835626333663539623665653662353138
32353862363863343666383766363135663736613332313438356366303332326632653961313762 61363230303634653131356563376263363339613265373534383561313932616635643038363266
34363230633461616461373536366239313962616565396566316139393466303332643265353835 31663835643830666363646635346538656665363730396366643439326537386264663666363332
35356364343734663963303839363733393730633262643438336562643733623966663130353635 64363561623031626531663934636634313536653435663431666463386362306331353137396362
38363833633936363164313239323265623332643539356532623636383232323538386431653165 65653039623636316338326335613164376366613266316134386163626436383539393932666436
31353138666537616565646239303165376439373063356663316664356564323961613039663061 33633264653230663065393632653935633833313233373933326464316331646363613832633330
62336261306532386136616465383265613262363936633164373236353238633463303939623133 30353263343433366362323365386536313535396430373966323337623830373465356437643734
65313335653935346364383232373261666334663634373938353861383561613936393339366530 32313038323664376566656666633364363634613033376636326635343233666134363864343634
34303961373561303031323834663466346436383966373530633430333536373833383166313938 65386538663835663539353533633931323837373738393463336337353865616439336537353933
31376531366166333762623233646433323134663366323730613932393333333737623463653634 32353162363166353265316534643764616132656534666463363439643237396166303833303038
31356466646465333666363034343730313164353039646236393564326536323330363639343664 66383236363738336363643832666130633464333536623134616265316663333237383739336537
39363836663332333234396239336339356532626430653861373137333562623830653936323432 37376331326139633764353166656634323339656435663531313934376565393639316531663237
34306238623137313232333033356266303564376163333065653137616661643537373831666136 64303931386461346632646161393061376534393539663966373562646432386134616435363261
39303037383661313064396364363639346564643564303638623661356235336162633633616531 37356466303165356336626134313135353830316536323165323233613235643733643333613636
34626331393564363564613937346165333936366231333462633561356362616434306165343130 63666139323931616166346566396162373637336331393034623631346437643632643938323834
62346665653532663434396238366562336264656130643566666466313964303066373462373034 35383862653464643533663333616263633465663939633064653666353363363830393565666236
32613434363832626265636234343535313438313532643631363633363335653364393564356632 31333663663332363533336132346666643734623131643339643865633565393262643966393734
37613135343632613433616630333163323230333235363531343966333232646664393163386238 36663533313638386166653334623832323363636262373931356664323766666538343838313039
36613731656666663236316133353237636363663336656162316163393230346565366536376236 39393765343430633336373966346161393130386539393366363366366163316363613038613962
6336 36646336396333333863326565303666373963326163393434313534333533323739623035323137
32346535383263316332653239306633343935383166653139323036616133656638643532646634
64333466666533363932323366353762653461336538303166626330333336616437613037363139
63303764313330656366363630636463303461383565346262656136326362623839366531326663
34663534343564313030613366626337336331343637343564386638653530663631613234636237
37373064303137666263666133363865386135333038653231346562333131313233643062376265
34646635663961393837396637383834376136323864646166656436383338623364613861663537
39313230306465626630
@@ -33,6 +33,17 @@ vms:
network_bridge: "vmbr2" network_bridge: "vmbr2"
storage: nvme-2tb 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
node: mipha node: mipha
@@ -6,5 +6,3 @@ keepalived_interface: eth0
keepalived_router_id: 51 keepalived_router_id: 51
keepalived_vip: 10.0.4.254 keepalived_vip: 10.0.4.254
keepalived_password: "{{ vault_keepalived_password | default('changeme') }}" keepalived_password: "{{ vault_keepalived_password | default('changeme') }}"
komodo_core_address: "10.0.4.10:9120"
@@ -6,5 +6,3 @@ keepalived_interface: eth0
keepalived_router_id: 51 keepalived_router_id: 51
keepalived_vip: 10.0.4.254 keepalived_vip: 10.0.4.254
keepalived_password: "{{ vault_keepalived_password | default('changeme') }}" keepalived_password: "{{ vault_keepalived_password | default('changeme') }}"
komodo_core_address: "10.0.4.10:9120"
+2
View File
@@ -22,3 +22,5 @@ all:
hosts: hosts:
apps-1: apps-1:
ansible_host: 10.0.4.30 ansible_host: 10.0.4.30
apps-2:
ansible_host: 10.0.4.31
+14 -1
View File
@@ -1,4 +1,8 @@
--- ---
# 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 - hosts: infra
become: yes become: yes
roles: roles:
@@ -25,7 +29,16 @@
- docker - docker
- komodo-periphery - komodo-periphery
- hosts: apps - hosts: apps-1
become: yes
roles:
- base
- docker
- komodo-periphery
- trek
- forgejo
- hosts: apps-2
become: yes become: yes
roles: roles:
- base - base
@@ -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 }})"
+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
@@ -5,7 +5,7 @@ services:
container_name: komodo-periphery container_name: komodo-periphery
restart: unless-stopped restart: unless-stopped
environment: environment:
PERIPHERY_CORE_ADDRESS: {{ komodo_core_address }} 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
+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
@@ -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