| author | |
| committer | |
| log | 345d7576fee9ee12c5032dec7e5d0ac999e29880 |
| tree | 50ee779247e95f298c8486096999645e1c922fcc |
| parent | 0afc7d74ea327a98280729334118af95fbc07a0c |
| signature |
closes #16
generate-app-list.py renders config/static/apps.html from the
net.paperclover.list.* compose labels, grouped by access level;
caddy serves it at auth.<domain>/apps. regenerate with:
sh docker.sh compose config --format json | python3 generate-app-list.py > config/static/apps.html
also fixes two wrong domain labels (sonarr jkt->snr, navidrome
nd->music) so the generated links match the Caddyfile.4 files changed, 197 insertions(+), 2 deletions(-)
compose.yaml+2-2| ... | ... | @@ -336,7 +336,7 @@ services: |
| 336 | 336 | ND_PORT: "80" |
| 337 | 337 | labels: |
| 338 | 338 | net.paperclover.list.name: Navidrome Music |
| 339 | net.paperclover.list.domain: nd | |
| 339 | net.paperclover.list.domain: music | |
| 340 | 340 | net.paperclover.list.priority: 109 |
| 341 | 341 | net.paperclover.list.access: media |
| 342 | 342 | depends_on: |
| ... | ... | @@ -419,7 +419,7 @@ services: |
| 419 | 419 | restart: unless-stopped |
| 420 | 420 | labels: |
| 421 | 421 | net.paperclover.list.name: Sonarr TV PVR |
| 422 | net.paperclover.list.domain: jkt | |
| 422 | net.paperclover.list.domain: snr | |
| 423 | 423 | net.paperclover.list.priority: 51 |
| 424 | 424 | net.paperclover.list.access: media-manage |
| 425 | 425 | radarr: # port 7878 |
config/Caddyfile+6| ... | ... | @@ -44,6 +44,12 @@ |
| 44 | 44 | auth.{$HOME_DOMAIN} { |
| 45 | 45 | handle / { |
| 46 | 46 | redir / /realms/master/account |
| 47 | } | |
| 48 | handle /apps { | |
| 49 | rewrite * /static/apps.html | |
| 50 | file_server { | |
| 51 | root /etc/caddy | |
| 52 | } | |
| 47 | 53 | } |
| 48 | 54 | 	reverse_proxy "http://keycloak" |
| 49 | 55 | } |
config/static/apps.html created+83| ... | ... | @@ -0,0 +1,83 @@ |
| 1 | <!doctype html> | |
| 2 | <!-- generated by generate-app-list.py — do not edit by hand --> | |
| 3 | <html lang="en"> | |
| 4 | <head> | |
| 5 | <meta charset="utf-8"> | |
| 6 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 7 | <title>apps (snow sign on)</title> | |
| 8 | <style> | |
| 9 | @import "https://file.paperclover.net/.static/font.css"; | |
| 10 | :root { color-scheme: light dark; } | |
| 11 | body { | |
| 12 | font-family: "national-medium", system-ui, sans-serif; | |
| 13 | background-color: light-dark(#e8eefa, #2f4b67); | |
| 14 | color: light-dark(black, white); | |
| 15 | max-width: 34rem; | |
| 16 | margin: 0 auto; | |
| 17 | padding: 2rem 1rem 4rem; | |
| 18 | } | |
| 19 | h1, h2, a { color: light-dark(#1a46cd, #938cff); } | |
| 20 | h1 { text-align: center; font-weight: normal; } | |
| 21 | a { text-decoration: dotted underline; } | |
| 22 | a:hover { text-decoration: underline; } | |
| 23 | .who { opacity: 0.6; font-size: 0.9rem; margin-top: -0.5rem; } | |
| 24 | .domain { opacity: 0.5; font-size: 0.85rem; } | |
| 25 | ul { list-style: "\2744\FE0E "; padding-left: 1.5rem; } | |
| 26 | li { margin: 0.4rem 0; } | |
| 27 | footer { margin-top: 3rem; opacity: 0.7; font-size: 0.9rem; text-align: center; } | |
| 28 | </style> | |
| 29 | </head> | |
| 30 | <body> | |
| 31 | <h1>&#x2744;&#xFE0E; apps on paperclover.net</h1> | |
| 32 | <main> | |
| 33 | <section> | |
| 34 | <h2>for everyone</h2> | |
| 35 | <p class="who">any snow sign on account</p> | |
| 36 | <ul> | |
| 37 | <li><a href="https://xmpp.paperclover.net">paper clover xmpp</a> <span class="domain">xmpp.paperclover.net</span></li> | |
| 38 | <li><a href="https://spindle.paperclover.net">tangled spindle</a> <span class="domain">spindle.paperclover.net</span></li> | |
| 39 | <li><a href="https://knot.paperclover.net">tangled knot</a> <span class="domain">knot.paperclover.net</span></li> | |
| 40 | <li><a href="https://git.paperclover.net">clover git forge</a> <span class="domain">git.paperclover.net</span></li> | |
| 41 | <li><a href="https://file.paperclover.net">copyparty file storage</a> <span class="domain">file.paperclover.net</span></li> | |
| 42 | <li><a href="https://speedtest.paperclover.net">open speed test</a> <span class="domain">speedtest.paperclover.net</span></li> | |
| 43 | </ul> | |
| 44 | </section> | |
| 45 | <section> | |
| 46 | <h2>media</h2> | |
| 47 | <p class="who">members of the media group</p> | |
| 48 | <ul> | |
| 49 | <li><a href="https://jelly.paperclover.net">cloverfin</a> <span class="domain">jelly.paperclover.net</span></li> | |
| 50 | <li><a href="https://music.paperclover.net">navidrome music</a> <span class="domain">music.paperclover.net</span></li> | |
| 51 | </ul> | |
| 52 | </section> | |
| 53 | <section> | |
| 54 | <h2>media management</h2> | |
| 55 | <p class="who">members of the media-manage group</p> | |
| 56 | <ul> | |
| 57 | <li><a href="https://qbt.paperclover.net">qbittorrent</a> <span class="domain">qbt.paperclover.net</span></li> | |
| 58 | <li><a href="https://snr.paperclover.net">sonarr tv pvr</a> <span class="domain">snr.paperclover.net</span></li> | |
| 59 | <li><a href="https://rdr.paperclover.net">radarr movie organizer</a> <span class="domain">rdr.paperclover.net</span></li> | |
| 60 | <li><a href="https://jkt.paperclover.net">jackett api adapter</a> <span class="domain">jkt.paperclover.net</span></li> | |
| 61 | </ul> | |
| 62 | </section> | |
| 63 | <section> | |
| 64 | <h2>personal</h2> | |
| 65 | <p class="who">just clover</p> | |
| 66 | <ul> | |
| 67 | <li><a href="https://opencode.paperclover.net">opencode</a> <span class="domain">opencode.paperclover.net</span></li> | |
| 68 | <li><a href="https://sync.paperclover.net">syncthing</a> <span class="domain">sync.paperclover.net</span></li> | |
| 69 | <li><a href="https://dawarich.paperclover.net">dawarich location history</a> <span class="domain">dawarich.paperclover.net</span></li> | |
| 70 | <li><a href="https://pg.paperclover.net">pgadmin</a> <span class="domain">pg.paperclover.net</span></li> | |
| 71 | <li><a href="https://redis.paperclover.net">redis insight</a> <span class="domain">redis.paperclover.net</span></li> | |
| 72 | <li><a href="https://dns.paperclover.net">dns</a> <span class="domain">dns.paperclover.net</span></li> | |
| 73 | <li>ddns <span class="domain">(no web ui)</span></li> | |
| 74 | </ul> | |
| 75 | </section> | |
| 76 | </main> | |
| 77 | <footer> | |
| 78 | <p>signed in with <a href="https://auth.paperclover.net/realms/master/account">snow sign on</a> — | |
| 79 | one account for everything here.</p> | |
| 80 | <p>missing access? contact clover.</p> | |
| 81 | </footer> | |
| 82 | </body> | |
| 83 | </html> |
generate-app-list.py created+106| ... | ... | @@ -0,0 +1,106 @@ |
| 1 | #!/usr/bin/env python3 | |
| 2 | # generates config/static/apps.html from the net.paperclover.list.* labels | |
| 3 | # in compose.yaml. usage: | |
| 4 | # sh docker.sh compose config --format json | python3 generate-app-list.py > config/static/apps.html | |
| 5 | # (or local: docker compose config --format json | ...) | |
| 6 | import json | |
| 7 | import os | |
| 8 | import sys | |
| 9 | ||
| 10 | PFX = "net.paperclover.list." | |
| 11 | DOMAIN = os.environ.get("HOME_DOMAIN", "paperclover.net") | |
| 12 | ||
| 13 | ACCESS_GROUPS = [ | |
| 14 | (None, "for everyone", "any snow sign on account"), | |
| 15 | ("media", "media", "members of the media group"), | |
| 16 | ("media-manage", "media management", "members of the media-manage group"), | |
| 17 | ("personal", "personal", "just clover"), | |
| 18 | ] | |
| 19 | ||
| 20 | cfg = json.load(sys.stdin) | |
| 21 | apps = [] | |
| 22 | for name, svc in cfg["services"].items(): | |
| 23 | labels = svc.get("labels") or {} | |
| 24 | if isinstance(labels, list): | |
| 25 | labels = dict(l.split("=", 1) for l in labels) | |
| 26 | if PFX + "name" not in labels: | |
| 27 | continue | |
| 28 | apps.append({ | |
| 29 | "name": labels[PFX + "name"], | |
| 30 | "domain": labels.get(PFX + "domain"), | |
| 31 | "priority": int(labels.get(PFX + "priority", 0)), | |
| 32 | "access": labels.get(PFX + "access"), | |
| 33 | }) | |
| 34 | ||
| 35 | apps.sort(key=lambda a: -a["priority"]) | |
| 36 | ||
| 37 | def entries(access): | |
| 38 | out = [] | |
| 39 | for a in apps: | |
| 40 | if a["access"] != access: | |
| 41 | continue | |
| 42 | if a["domain"]: | |
| 43 | out.append( | |
| 44 | f' <li><a href="https://{a["domain"]}.{DOMAIN}">' | |
| 45 | f'{a["name"].lower()}</a>' | |
| 46 | f' <span class="domain">{a["domain"]}.{DOMAIN}</span></li>' | |
| 47 | ) | |
| 48 | else: | |
| 49 | out.append(f' <li>{a["name"].lower()} <span class="domain">(no web ui)</span></li>') | |
| 50 | return out | |
| 51 | ||
| 52 | sections = [] | |
| 53 | for access, title, who in ACCESS_GROUPS: | |
| 54 | items = entries(access) | |
| 55 | if not items: | |
| 56 | continue | |
| 57 | sections.append( | |
| 58 | f' <section>\n <h2>{title}</h2>\n' | |
| 59 | f' <p class="who">{who}</p>\n <ul>\n' | |
| 60 | + "\n".join(" " + i for i in items) | |
| 61 | + "\n </ul>\n </section>" | |
| 62 | ) | |
| 63 | ||
| 64 | body = "\n".join(sections) | |
| 65 | ||
| 66 | print(f"""<!doctype html> | |
| 67 | <!-- generated by generate-app-list.py — do not edit by hand --> | |
| 68 | <html lang="en"> | |
| 69 | <head> | |
| 70 | <meta charset="utf-8"> | |
| 71 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 72 | <title>apps (snow sign on)</title> | |
| 73 | <style> | |
| 74 | @import "https://file.{DOMAIN}/.static/font.css"; | |
| 75 | :root {{ color-scheme: light dark; }} | |
| 76 | body {{ | |
| 77 | font-family: "national-medium", system-ui, sans-serif; | |
| 78 | background-color: light-dark(#e8eefa, #2f4b67); | |
| 79 | color: light-dark(black, white); | |
| 80 | max-width: 34rem; | |
| 81 | margin: 0 auto; | |
| 82 | padding: 2rem 1rem 4rem; | |
| 83 | }} | |
| 84 | h1, h2, a {{ color: light-dark(#1a46cd, #938cff); }} | |
| 85 | h1 {{ text-align: center; font-weight: normal; }} | |
| 86 | a {{ text-decoration: dotted underline; }} | |
| 87 | a:hover {{ text-decoration: underline; }} | |
| 88 | .who {{ opacity: 0.6; font-size: 0.9rem; margin-top: -0.5rem; }} | |
| 89 | .domain {{ opacity: 0.5; font-size: 0.85rem; }} | |
| 90 | ul {{ list-style: "\\2744\\FE0E "; padding-left: 1.5rem; }} | |
| 91 | li {{ margin: 0.4rem 0; }} | |
| 92 | footer {{ margin-top: 3rem; opacity: 0.7; font-size: 0.9rem; text-align: center; }} | |
| 93 | </style> | |
| 94 | </head> | |
| 95 | <body> | |
| 96 | <h1>&#x2744;&#xFE0E; apps on {DOMAIN}</h1> | |
| 97 | <main> | |
| 98 | {body} | |
| 99 | </main> | |
| 100 | <footer> | |
| 101 | <p>signed in with <a href="https://auth.{DOMAIN}/realms/master/account">snow sign on</a> — | |
| 102 | one account for everything here.</p> | |
| 103 | <p>missing access? contact clover.</p> | |
| 104 | </footer> | |
| 105 | </body> | |
| 106 | </html>""") |