diff --git a/argocd/apps/homepage.yaml b/argocd/apps/homepage.yaml index 86a0f8d..22147f2 100644 --- a/argocd/apps/homepage.yaml +++ b/argocd/apps/homepage.yaml @@ -14,7 +14,7 @@ spec: targetRevision: main path: argocd/manifests/homepage destination: - server: https://kubernetes.default.svc + server: https://ringtail.tail8d86e.ts.net:6443 namespace: homepage syncPolicy: syncOptions: diff --git a/argocd/manifests/homepage/kustomization.yaml b/argocd/manifests/homepage/kustomization.yaml index 27de0eb..ce627ac 100644 --- a/argocd/manifests/homepage/kustomization.yaml +++ b/argocd/manifests/homepage/kustomization.yaml @@ -17,7 +17,7 @@ resources: images: - name: registry.ops.eblu.me/blumeops/homepage - newTag: v1.11.0-e375859 + newTag: v1.11.0-b87f62e-nix configMapGenerator: - name: homepage-config diff --git a/argocd/manifests/homepage/services.yaml b/argocd/manifests/homepage/services.yaml index 211e043..d552ff2 100644 --- a/argocd/manifests/homepage/services.yaml +++ b/argocd/manifests/homepage/services.yaml @@ -1,3 +1,6 @@ +# Homepage runs on ringtail (k3s) — its k8s autodiscovery only sees ringtail +# Ingresses (frigate→NVR, authentik, ntfy, ollama). Services that live on +# minikube (and indri-native) need explicit static entries here. - Host Services: - Forgejo: href: https://forge.eblu.me @@ -57,10 +60,6 @@ # type: caddy # url: http://indri.tail8d86e.ts.net:2019 - Home: - - NVR: - href: https://nvr.ops.eblu.me - icon: frigate.png - description: Network video recorder - Jellyfin: href: https://jellyfin.ops.eblu.me icon: jellyfin @@ -72,15 +71,61 @@ enableBlocks: true enableNowPlaying: false fields: ["movies", "series", "episodes"] + - Mealie: + href: https://meals.ops.eblu.me + icon: mealie.png + description: Recipe manager + - DJ: + href: https://dj.ops.eblu.me + icon: navidrome.png + description: Music streaming server + widget: + type: navidrome + url: https://dj.ops.eblu.me + user: "{{HOMEPAGE_VAR_NAVIDROME_USER}}" + token: "{{HOMEPAGE_VAR_NAVIDROME_TOKEN}}" + salt: "{{HOMEPAGE_VAR_NAVIDROME_SALT}}" + - Paperless: + href: https://paperless.ops.eblu.me + icon: paperless-ngx.png + description: Document management +- Content: + - Immich: + href: https://photos.ops.eblu.me + icon: immich.png + description: Photo management + - Kiwix: + href: https://kiwix.ops.eblu.me + icon: kiwix.png + description: Offline Wikipedia + - Miniflux: + href: https://feed.ops.eblu.me + icon: miniflux.png + description: RSS reader + widget: + type: miniflux + url: https://feed.ops.eblu.me + key: "{{HOMEPAGE_VAR_MINIFLUX_API_KEY}}" + fields: ["unread"] - Infrastructure: - - Authentik: - href: https://authentik.ops.eblu.me - icon: authentik - description: Identity provider - - Ntfy: - href: https://ntfy.ops.eblu.me - icon: ntfy.png - description: Push notifications + - ArgoCD: + href: https://argocd.ops.eblu.me + icon: argo-cd.png + description: GitOps CD + - Grafana: + href: https://grafana.ops.eblu.me + icon: grafana.png + description: Metrics dashboards + widget: + type: grafana + url: https://grafana.ops.eblu.me + username: "{{HOMEPAGE_VAR_GRAFANA_USERNAME}}" + password: "{{HOMEPAGE_VAR_GRAFANA_PASSWORD}}" + fields: ["dashboards", "totalalerts", "alertstriggered"] + - Prometheus: + href: https://prometheus.ops.eblu.me + icon: prometheus.png + description: Metrics storage - Services: # CV and Docs were previously auto-discovered from k8s Ingresses; after # the indri-native migration ([[cv-on-indri]], [[docs-on-indri]]) there @@ -93,3 +138,11 @@ href: https://docs.eblu.me icon: mdi-book-open-page-variant description: BlumeOps Documentation + - TeslaMate: + href: https://tesla.ops.eblu.me + icon: teslamate.png + description: Tesla data logger + - Transmission: + href: https://torrent.ops.eblu.me + icon: transmission.png + description: Torrent client diff --git a/containers/homepage/Dockerfile b/containers/homepage/Dockerfile deleted file mode 100644 index 6e53e1c..0000000 --- a/containers/homepage/Dockerfile +++ /dev/null @@ -1,47 +0,0 @@ -# Homepage - self-hosted services dashboard -# Two-stage build: Node.js build, Alpine runtime - -ARG CONTAINER_APP_VERSION=v1.11.0 -ARG HOMEPAGE_VERSION=${CONTAINER_APP_VERSION} - -FROM node:24-slim AS builder - -ARG HOMEPAGE_VERSION -RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -RUN git clone --depth 1 --branch ${HOMEPAGE_VERSION} \ - https://forge.ops.eblu.me/mirrors/homepage.git /app - -WORKDIR /app -RUN mkdir -p config \ - && corepack enable && corepack prepare pnpm@latest --activate \ - && pnpm install --frozen-lockfile \ - && NEXT_TELEMETRY_DISABLED=1 pnpm run build - -FROM node:24-alpine - -ARG CONTAINER_APP_VERSION -LABEL org.opencontainers.image.title="Homepage" -LABEL org.opencontainers.image.description="A self-hosted services landing page" -LABEL org.opencontainers.image.version="${CONTAINER_APP_VERSION}" -LABEL org.opencontainers.image.source="https://forge.eblu.me/eblume/blumeops" -LABEL org.opencontainers.image.vendor="blumeops" - -WORKDIR /app - -COPY --from=builder --chown=1000:1000 /app/public ./public -COPY --from=builder --chown=1000:1000 /app/.next/standalone/ ./ -COPY --from=builder --chown=1000:1000 /app/.next/static/ ./.next/static - -RUN mkdir -p /app/config && chown 1000:1000 /app/config - -ENV NODE_ENV=production -ENV PORT=3000 -EXPOSE 3000 - -HEALTHCHECK --interval=10s --timeout=3s --start-period=20s \ - CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:3000/api/healthcheck || exit 1 - -USER 1000 -CMD ["node", "server.js"] diff --git a/containers/homepage/default.nix b/containers/homepage/default.nix new file mode 100644 index 0000000..7b4becb --- /dev/null +++ b/containers/homepage/default.nix @@ -0,0 +1,119 @@ +# Nix-built gethomepage/homepage dashboard +# Builds v1.11.0 from forge mirror. +# +# Adapted from nixpkgs pkgs/by-name/ho/homepage-dashboard (commit master), +# changed to fetch from our forge mirror and wrap with dockerTools for an +# amd64 image runnable on ringtail's k3s. +# +# The preBuild substitutions are not optional — without them Next.js writes +# its file-system-cache to a read-only path and prerender state breaks after +# restart (nixpkgs issues #328621 and #458494). +{ pkgs ? import { } }: + +let + version = "1.11.0"; + + homepage = pkgs.stdenv.mkDerivation (finalAttrs: { + pname = "homepage-dashboard"; + inherit version; + + src = pkgs.fetchgit { + url = "https://forge.ops.eblu.me/mirrors/homepage.git"; + rev = "v${version}"; + hash = "sha256-jnv9PnClm/jIQ4uU6c4A1UiAmwoihG0l6k3fUbD47I4="; + }; + + pnpmDeps = pkgs.fetchPnpmDeps { + inherit (finalAttrs) pname version src; + pnpm = pkgs.pnpm_10; + fetcherVersion = 3; + hash = "sha256-X5j9XppbcasGuC7fUsj4XzbaQFM9WcRcXjgJHN/inR8="; + }; + + nativeBuildInputs = [ + pkgs.makeBinaryWrapper + pkgs.nodejs_24 + pkgs.pnpmConfigHook + pkgs.pnpm_10 + ]; + + buildInputs = [ + pkgs.nodePackages.node-gyp-build + ]; + + env.PYTHON = "${pkgs.python3}/bin/python"; + + preBuild = '' + substituteInPlace node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js \ + --replace-fail 'this.serverDistDir = ctx.serverDistDir;' \ + 'this.serverDistDir = require("path").join((process.env.NIXPKGS_HOMEPAGE_CACHE_DIR || "/tmp/homepage-cache"), "homepage");' + + for bundle in node_modules/next/dist/compiled/next-server/*.runtime.prod.js; do + substituteInPlace "$bundle" \ + --replace-fail 'this.serverDistDir=e.serverDistDir' \ + 'this.serverDistDir=(process.env.NIXPKGS_HOMEPAGE_CACHE_DIR||"/tmp/homepage-cache")+"/homepage"' + done + ''; + + buildPhase = '' + runHook preBuild + mkdir -p config + pnpm build + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/{bin,share} + cp -r .next/standalone $out/share/homepage/ + cp -r public $out/share/homepage/public + chmod +x $out/share/homepage/server.js + + mkdir -p $out/share/homepage/.next + cp -r .next/static $out/share/homepage/.next/static + + makeWrapper "${pkgs.lib.getExe pkgs.nodejs_24}" $out/bin/homepage \ + --set-default PORT 3000 \ + --set-default HOMEPAGE_CONFIG_DIR /app/config \ + --set-default NIXPKGS_HOMEPAGE_CACHE_DIR /tmp/homepage-cache \ + --add-flags "$out/share/homepage/server.js" \ + --prefix PATH : "${pkgs.lib.makeBinPath [ pkgs.unixtools.ping ]}" + + runHook postInstall + ''; + + doDist = false; + }); +in + +pkgs.dockerTools.buildLayeredImage { + name = "blumeops/homepage"; + contents = [ + homepage + pkgs.cacert + pkgs.tzdata + ]; + + extraCommands = '' + mkdir -p tmp + chmod 1777 tmp + ''; + + config = { + Entrypoint = [ "${homepage}/bin/homepage" ]; + Env = [ + "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" + "TZDIR=${pkgs.tzdata}/share/zoneinfo" + "TMPDIR=/tmp" + "NIXPKGS_HOMEPAGE_CACHE_DIR=/tmp/homepage-cache" + "HOMEPAGE_CONFIG_DIR=/app/config" + "NEXT_TELEMETRY_DISABLED=1" + "PORT=3000" + ]; + ExposedPorts = { + "3000/tcp" = { }; + }; + User = "1000"; + }; +} diff --git a/docs/changelog.d/homepage-to-ringtail.infra.md b/docs/changelog.d/homepage-to-ringtail.infra.md new file mode 100644 index 0000000..1e3e795 --- /dev/null +++ b/docs/changelog.d/homepage-to-ringtail.infra.md @@ -0,0 +1,8 @@ +Migrated homepage dashboard from minikube (indri/arm64) to k3s (ringtail/amd64). +The container is now built via nix (`containers/homepage/default.nix`), adapted +from nixpkgs `homepage-dashboard` with the upstream Next.js cache patches and +wrapped with `dockerTools.buildLayeredImage`. Autodiscovery shifts: services on +minikube (ArgoCD, Immich, Kiwix, Mealie, Miniflux, Grafana, Prometheus, +Navidrome, Paperless, TeslaMate, Transmission) become explicit static entries +in `services.yaml`; ringtail services (Authentik, Frigate/NVR, Ntfy, Ollama) +auto-populate via Ingress annotations.