consolidate css/, badges into styles/ directory at project root
Some checks failed
ci/woodpecker/push/infra-ci Pipeline failed
ci/woodpecker/push/deploy Pipeline failed
coverage 99% coverage
ci/woodpecker/push/ci Pipeline was successful

Rename css/ → styles/ and add coverage badge SVG. CI updates
styles/coverage.svg in the repo via Gitea contents API so the
README badge renders without external URL dependencies.

All compose.yml mounts, .dockerignore, README, and docs refs updated.
This commit is contained in:
kert
2026-03-13 11:11:45 -04:00
parent a339d9b4e4
commit 05e8935b4a
19 changed files with 72 additions and 35 deletions

View File

@@ -19,6 +19,6 @@ trino/
traefik/ traefik/
nginx/ nginx/
gitea/ gitea/
css/ styles/
.woodpecker/ .woodpecker/
.claude/ .claude/

View File

@@ -1,6 +1,6 @@
# Homelab Stack # Homelab Stack
![coverage](http://gitea.homelab.fhirworx.io/api/packages/homelab/generic/coverage/latest/badge.svg) ![coverage](styles/coverage.svg)
Self-hosted infrastructure stack with GPU support, S3-compatible storage, Git server with container registry, CI/CD, data lakehouse, observability, documentation, and research tools. All services share a unified retro "throwback" theme injected via Traefik middleware. Self-hosted infrastructure stack with GPU support, S3-compatible storage, Git server with container registry, CI/CD, data lakehouse, observability, documentation, and research tools. All services share a unified retro "throwback" theme injected via Traefik middleware.
@@ -100,18 +100,18 @@ Per-service CSS files handle service-specific overrides:
| File | Purpose | | File | Purpose |
|------|---------| |------|---------|
| `css/inject.css` | Global throwback palette + logo hiding (served as `throwback.css`) | | `styles/inject.css` | Global throwback palette + logo hiding (served as `throwback.css`) |
| `css/dashboard.css` | Dashboard tile grid layout | | `styles/dashboard.css` | Dashboard tile grid layout |
| `css/gitea.css` | Gitea theme (registered as `throwback` in Gitea UI settings) | | `styles/gitea.css` | Gitea theme (registered as `throwback` in Gitea UI settings) |
| `css/woodpecker.css` | Woodpecker custom CSS (via `WOODPECKER_CUSTOM_CSS_FILE`) | | `styles/woodpecker.css` | Woodpecker custom CSS (via `WOODPECKER_CUSTOM_CSS_FILE`) |
| `css/marimo.css` | Marimo notebook theme | | `styles/marimo.css` | Marimo notebook theme |
| `css/fav32.png` | 32x32 PNG favicon (canonical, injected by Traefik) | | `styles/fav32.png` | 32x32 PNG favicon (canonical, injected by Traefik) |
| `css/favicon.png` | 64x64 PNG favicon | | `styles/favicon.png` | 64x64 PNG favicon |
| `css/fav16.png` | 16x16 PNG favicon | | `styles/fav16.png` | 16x16 PNG favicon |
| `css/apple-touch-icon.png` | 180x180 Apple touch icon | | `styles/apple-touch-icon.png` | 180x180 Apple touch icon |
| `css/favicon.svg` | SVG favicon (legacy) | | `styles/favicon.svg` | SVG favicon (legacy) |
| `css/logo.svg` | Navbar logo for Gitea | | `styles/logo.svg` | Navbar logo for Gitea |
| `css/logo.png` | 256x256 PNG logo | | `styles/logo.png` | 256x256 PNG logo |
Grafana favicons are replaced via volume mounts over the default Grafana icons. Grafana favicons are replaced via volume mounts over the default Grafana icons.
@@ -467,7 +467,7 @@ stack/
├── compose.yml # Docker Compose (all services) ├── compose.yml # Docker Compose (all services)
├── .env # Environment variables (not in git) ├── .env # Environment variables (not in git)
├── README.md ├── README.md
├── css/ # Centralized theme assets ├── styles/ # Centralized theme assets
│ ├── inject.css # Global throwback CSS (served as throwback.css) │ ├── inject.css # Global throwback CSS (served as throwback.css)
│ ├── dashboard.css # Dashboard tile grid │ ├── dashboard.css # Dashboard tile grid
│ ├── gitea.css # Gitea throwback theme │ ├── gitea.css # Gitea throwback theme

View File

@@ -116,7 +116,7 @@ services:
- gitea_data:/var/lib/gitea - gitea_data:/var/lib/gitea
- gitea_config:/etc/gitea - gitea_config:/etc/gitea
- ./gitea/custom:/var/lib/gitea/custom - ./gitea/custom:/var/lib/gitea/custom
- ./css/gitea.css:/var/lib/gitea/custom/public/assets/css/theme-throwback.css:ro - ./styles/gitea.css:/var/lib/gitea/custom/public/assets/css/theme-throwback.css:ro
ports: ports:
- "2222:2222" - "2222:2222"
depends_on: depends_on:
@@ -149,7 +149,7 @@ services:
- WOODPECKER_CUSTOM_CSS_FILE=/etc/woodpecker/custom.css - WOODPECKER_CUSTOM_CSS_FILE=/etc/woodpecker/custom.css
volumes: volumes:
- woodpecker_data:/var/lib/woodpecker - woodpecker_data:/var/lib/woodpecker
- ./css/woodpecker.css:/etc/woodpecker/custom.css:ro - ./styles/woodpecker.css:/etc/woodpecker/custom.css:ro
depends_on: depends_on:
- gitea - gitea
- postgres - postgres
@@ -185,7 +185,7 @@ services:
volumes: volumes:
- ./notebooks:/home/kert/notebooks - ./notebooks:/home/kert/notebooks
- ./notebooks/.marimo-config:/home/kert/.config/marimo - ./notebooks/.marimo-config:/home/kert/.config/marimo
- ./css/marimo.css:/home/kert/.config/marimo/retro-arcade.css:ro - ./styles/marimo.css:/home/kert/.config/marimo/retro-arcade.css:ro
- ./notebooks/home-page-DUMF8ZY4.js:/home/kert/workspace/.venv/lib/python3.13/site-packages/marimo/_static/assets/home-page-DUMF8ZY4.js:ro - ./notebooks/home-page-DUMF8ZY4.js:/home/kert/workspace/.venv/lib/python3.13/site-packages/marimo/_static/assets/home-page-DUMF8ZY4.js:ro
- ./data:/home/kert/data - ./data:/home/kert/data
- ./zotero/data:/home/kert/zotero:ro - ./zotero/data:/home/kert/zotero:ro
@@ -334,9 +334,9 @@ services:
volumes: volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/index.html:/usr/share/nginx/html/index.html:ro - ./nginx/index.html:/usr/share/nginx/html/index.html:ro
- ./css/dashboard.css:/usr/share/nginx/html/dashboard.css:ro - ./styles/dashboard.css:/usr/share/nginx/html/dashboard.css:ro
- ./css/inject.css:/usr/share/nginx/html/throwback.css:ro - ./styles/inject.css:/usr/share/nginx/html/throwback.css:ro
- ./css/fav32.png:/usr/share/nginx/html/fav32.png:ro - ./styles/fav32.png:/usr/share/nginx/html/fav32.png:ro
security_opt: security_opt:
- no-new-privileges:true - no-new-privileges:true
restart: unless-stopped restart: unless-stopped
@@ -452,9 +452,9 @@ services:
- ./grafana/provisioning:/etc/grafana/provisioning:ro - ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro - ./grafana/dashboards:/var/lib/grafana/dashboards:ro
- grafana_data:/var/lib/grafana - grafana_data:/var/lib/grafana
- ./css/favicon.svg:/usr/share/grafana/public/img/grafana_icon.svg:ro - ./styles/favicon.svg:/usr/share/grafana/public/img/grafana_icon.svg:ro
- ./css/fav32.png:/usr/share/grafana/public/img/fav32.png:ro - ./styles/fav32.png:/usr/share/grafana/public/img/fav32.png:ro
- ./css/apple-touch-icon.png:/usr/share/grafana/public/img/apple-touch-icon.png:ro - ./styles/apple-touch-icon.png:/usr/share/grafana/public/img/apple-touch-icon.png:ro
depends_on: depends_on:
- loki - loki
- jaeger - jaeger

View File

@@ -7,14 +7,19 @@ Usage:
# Post coverage as a Gitea commit status (reads GITEA_TOKEN, GITEA_URL, # Post coverage as a Gitea commit status (reads GITEA_TOKEN, GITEA_URL,
# CI_COMMIT_SHA from environment): # CI_COMMIT_SHA from environment):
uv run python dev/scripts/coverage_badge.py --post pytest.out uv run python dev/scripts/coverage_badge.py --post pytest.out
# Update styles/coverage.svg in the repo via Gitea API:
uv run python dev/scripts/coverage_badge.py --upload coverage.svg
""" """
from __future__ import annotations from __future__ import annotations
import base64
import json import json
import os import os
import re import re
import sys import sys
import urllib.error
import urllib.request import urllib.request
TEMPLATE = """\ TEMPLATE = """\
@@ -39,6 +44,8 @@ TEMPLATE = """\
</g> </g>
</svg>""" </svg>"""
FILE_PATH = "styles/coverage.svg"
def color_for(pct: int) -> str: def color_for(pct: int) -> str:
if pct >= 95: if pct >= 95:
@@ -85,30 +92,40 @@ def post_status(pct: int) -> None:
def upload_badge(svg_path: str) -> None: def upload_badge(svg_path: str) -> None:
"""Update styles/coverage.svg in the repo via Gitea contents API."""
token, url = _gitea_env() token, url = _gitea_env()
endpoint = f"{url}/api/packages/homelab/generic/coverage/latest/badge.svg" endpoint = f"{url}/api/v1/repos/homelab/stack/contents/{FILE_PATH}"
data = open(svg_path, "rb").read() svg_data = open(svg_path, "rb").read()
# Delete existing package version first (ignore 404) content_b64 = base64.b64encode(svg_data).decode()
# Get current file SHA (needed for update)
sha = None
try: try:
req = urllib.request.Request(
f"{url}/api/packages/homelab/generic/coverage/latest",
headers={"Authorization": f"token {token}"},
method="DELETE",
)
urllib.request.urlopen(req)
except urllib.error.HTTPError:
pass
req = urllib.request.Request( req = urllib.request.Request(
endpoint, endpoint,
data=data, headers={"Authorization": f"token {token}"},
headers={
"Authorization": f"token {token}",
"Content-Type": "image/svg+xml",
},
method="PUT",
) )
with urllib.request.urlopen(req) as resp: with urllib.request.urlopen(req) as resp:
print(f"Uploaded badge SVG (HTTP {resp.status})") sha = json.loads(resp.read())["sha"]
except urllib.error.HTTPError:
pass
body = {"content": content_b64, "message": "update coverage badge [skip ci]"}
if sha:
body["sha"] = sha
method = "PUT" if sha else "POST"
req = urllib.request.Request(
endpoint,
data=json.dumps(body).encode(),
headers={
"Authorization": f"token {token}",
"Content-Type": "application/json",
},
method=method,
)
with urllib.request.urlopen(req) as resp:
print(f"Updated {FILE_PATH} in repo (HTTP {resp.status})")
def main() -> None: def main() -> None:

View File

@@ -1,4 +1,4 @@
/* Throwback palette — matches css/inject.css */ /* Throwback palette — matches styles/inject.css */
:root { :root {
--ifm-color-primary: #3366cc; --ifm-color-primary: #3366cc;

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

20
styles/coverage.svg Normal file
View File

@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" width="116" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="a">
<rect width="116" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#a)">
<path fill="#555" d="M0 0h65v20H0z"/>
<path fill="#4c1" d="M65 0h51v20H65z"/>
<path fill="url(#b)" d="M0 0h116v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">
<text x="32.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="32.5" y="14">coverage</text>
<text x="90" y="15" fill="#010101" fill-opacity=".3">99%</text>
<text x="90" y="14">99%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 822 B

View File

Before

Width:  |  Height:  |  Size: 741 B

After

Width:  |  Height:  |  Size: 741 B

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB