Replace Homepage Helm chart with kustomize manifests and custom Dockerfile (#221)

## Summary
- Replace third-party Helm chart (jameswynn/homepage v2.1.0, pinned at app v1.2.0) with plain kustomize manifests and a custom Dockerfile building from forge mirror at v1.10.1
- Adds Dockerfile (`containers/homepage/`) with multi-stage build (node:22-slim builder, node:22-alpine runtime)
- Creates kustomize manifests: Deployment, Service, ConfigMap (6 config files), ServiceAccount, ClusterRole, ClusterRoleBinding
- Keeps existing ingress-tailscale.yaml and all 6 ExternalSecret resources unchanged
- Updates ArgoCD app definition from multi-source Helm to single directory source

## Prerequisite
- Homepage source mirrored at forge.ops.eblu.me/eblume/homepage.git 
- Container must be built and pushed before syncing: `mise run container-release homepage v1.10.1`

## Deployment and Testing
- [ ] Build and push container image: `mise run container-release homepage v1.10.1`
- [ ] Branch-test via ArgoCD: `argocd app set homepage --revision feature/homepage-kustomize && argocd app sync homepage`
- [ ] Verify dashboard loads at go.ops.eblu.me / go.tail8d86e.ts.net
- [ ] Verify k8s autodiscovery works (services appear on dashboard)
- [ ] Verify widgets load (weather, Forgejo, Jellyfin, etc.)
- [ ] After merge: `argocd app set homepage --revision main && argocd app sync homepage`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/221
This commit is contained in:
Erich Blume 2026-02-19 18:29:19 -08:00
commit b876e39981
12 changed files with 290 additions and 127 deletions

View file

@ -1,9 +1,7 @@
# Homepage - Service Dashboard / Start Page
#
# Replaced hajimari with gethomepage for active maintenance and better features.
# Auto-discovers k8s services via ingress annotations.
#
# Helm chart: https://github.com/jameswynn/helm-charts/tree/main/charts/homepage
# Custom container built from gethomepage/homepage, kustomize manifests.
# Dashboard at go.ops.eblu.me / go.tail8d86e.ts.net
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
@ -11,25 +9,10 @@ metadata:
namespace: argocd
spec:
project: default
sources:
# Helm chart
- repoURL: https://jameswynn.github.io/helm-charts
chart: homepage
targetRevision: 2.1.0
helm:
releaseName: homepage
valueFiles:
- $values/argocd/manifests/homepage/values.yaml
# Values file reference
- repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
targetRevision: main
ref: values
# Extra manifests (ExternalSecrets, etc)
- repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
targetRevision: main
path: argocd/manifests/homepage
directory:
exclude: "values.yaml"
source:
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
targetRevision: main
path: argocd/manifests/homepage
destination:
server: https://kubernetes.default.svc
namespace: homepage

View file

@ -0,0 +1,23 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: homepage
rules:
- apiGroups: [""]
resources: ["namespaces", "pods", "nodes"]
verbs: ["get", "list"]
- apiGroups: ["extensions", "networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list"]
- apiGroups: ["traefik.containo.us", "traefik.io"]
resources: ["ingressroutes"]
verbs: ["get", "list"]
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways", "httproutes"]
verbs: ["get", "list"]
- apiGroups: ["metrics.k8s.io"]
resources: ["nodes", "pods"]
verbs: ["get", "list"]
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions/status"]
verbs: ["get"]

View file

@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: homepage
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: homepage
subjects:
- kind: ServiceAccount
name: homepage
namespace: homepage

View file

@ -1,76 +1,30 @@
# Homepage values for blumeops
# Service dashboard at go.ops.eblu.me
# Homepage configuration files
# Extracted from former Helm values.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: homepage-config
namespace: homepage
data:
bookmarks.yaml: |
- Admin:
- Tailscale Admin:
- href: https://login.tailscale.com/admin
icon: tailscale
- 1Password:
- href: https://my.1password.com
icon: 1password
- Pulumi:
- href: https://app.pulumi.com/eblume/blumeops-tailnet
icon: si-pulumi
- ArgoCD:
- href: https://argocd.ops.eblu.me
icon: argo-cd
- UniFi:
- href: https://unifi.ui.com
icon: ubiquiti
# Enable RBAC for Kubernetes service autodiscovery
enableRbac: true
serviceAccount:
create: true
# Tailscale Ingress is managed separately in ingress-tailscale.yaml
# (Helm chart template doesn't support tailscale.com/* annotations)
ingress:
main:
enabled: false
env:
- name: HOMEPAGE_ALLOWED_HOSTS
value: "go.tail8d86e.ts.net,go.ops.eblu.me"
# Weather widget
- name: HOMEPAGE_VAR_OPENWEATHERMAP_API_KEY
valueFrom:
secretKeyRef:
name: homepage-openweathermap
key: apikey
# Jellyfin widget
- name: HOMEPAGE_VAR_JELLYFIN_API_KEY
valueFrom:
secretKeyRef:
name: homepage-jellyfin
key: apikey
# Miniflux widget
- name: HOMEPAGE_VAR_MINIFLUX_API_KEY
valueFrom:
secretKeyRef:
name: homepage-miniflux
key: apikey
# Grafana widget
- name: HOMEPAGE_VAR_GRAFANA_USERNAME
valueFrom:
secretKeyRef:
name: homepage-grafana
key: username
- name: HOMEPAGE_VAR_GRAFANA_PASSWORD
valueFrom:
secretKeyRef:
name: homepage-grafana
key: password
# Forgejo widget
- name: HOMEPAGE_VAR_FORGEJO_API_KEY
valueFrom:
secretKeyRef:
name: homepage-forgejo
key: apikey
# Navidrome widget
- name: HOMEPAGE_VAR_NAVIDROME_USER
valueFrom:
secretKeyRef:
name: homepage-navidrome
key: user
- name: HOMEPAGE_VAR_NAVIDROME_SALT
valueFrom:
secretKeyRef:
name: homepage-navidrome
key: salt
- name: HOMEPAGE_VAR_NAVIDROME_TOKEN
valueFrom:
secretKeyRef:
name: homepage-navidrome
key: token
config:
# Host services (non-k8s, on indri or LAN)
services:
services.yaml: |
- Host Services:
- Forgejo:
href: https://forge.ops.eblu.me
@ -134,8 +88,6 @@ config:
# widget:
# type: caddy
# url: http://indri.tail8d86e.ts.net:2019
# Services on ringtail k3s (not autodiscovered — different cluster)
- Infrastructure:
- NVR:
href: https://nvr.ops.eblu.me
@ -146,27 +98,7 @@ config:
icon: ntfy.png
description: Push notifications
# External bookmarks
bookmarks:
- Admin:
- Tailscale Admin:
- href: https://login.tailscale.com/admin
icon: tailscale
- 1Password:
- href: https://my.1password.com
icon: 1password
- Pulumi:
- href: https://app.pulumi.com/eblume/blumeops-tailnet
icon: si-pulumi
- ArgoCD:
- href: https://argocd.ops.eblu.me
icon: argo-cd
- UniFi:
- href: https://unifi.ui.com
icon: ubiquiti
# Widgets on the page (info bar at top)
widgets:
widgets.yaml: |
- greeting:
text_size: xl
text: Welcome to Blue Mops
@ -194,12 +126,12 @@ config:
# url: http://indri.tail8d86e.ts.net:61208
# metric: cpu
# Kubernetes autodiscovery
kubernetes:
kubernetes.yaml: |
mode: cluster
# Layout and styling
settingsString: |
docker.yaml: ""
settings.yaml: |
title: BlumeOps
headerStyle: boxed
quicklaunch:

View file

@ -0,0 +1,131 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: homepage
namespace: homepage
spec:
replicas: 1
selector:
matchLabels:
app: homepage
template:
metadata:
labels:
app: homepage
spec:
serviceAccountName: homepage
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
containers:
- name: homepage
image: registry.ops.eblu.me/blumeops/homepage:v1.0.0
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
ports:
- containerPort: 3000
name: http
env:
- name: HOMEPAGE_ALLOWED_HOSTS
value: "go.tail8d86e.ts.net,go.ops.eblu.me"
# Weather widget
- name: HOMEPAGE_VAR_OPENWEATHERMAP_API_KEY
valueFrom:
secretKeyRef:
name: homepage-openweathermap
key: apikey
# Jellyfin widget
- name: HOMEPAGE_VAR_JELLYFIN_API_KEY
valueFrom:
secretKeyRef:
name: homepage-jellyfin
key: apikey
# Miniflux widget
- name: HOMEPAGE_VAR_MINIFLUX_API_KEY
valueFrom:
secretKeyRef:
name: homepage-miniflux
key: apikey
# Grafana widget
- name: HOMEPAGE_VAR_GRAFANA_USERNAME
valueFrom:
secretKeyRef:
name: homepage-grafana
key: username
- name: HOMEPAGE_VAR_GRAFANA_PASSWORD
valueFrom:
secretKeyRef:
name: homepage-grafana
key: password
# Forgejo widget
- name: HOMEPAGE_VAR_FORGEJO_API_KEY
valueFrom:
secretKeyRef:
name: homepage-forgejo
key: apikey
# Navidrome widget
- name: HOMEPAGE_VAR_NAVIDROME_USER
valueFrom:
secretKeyRef:
name: homepage-navidrome
key: user
- name: HOMEPAGE_VAR_NAVIDROME_SALT
valueFrom:
secretKeyRef:
name: homepage-navidrome
key: salt
- name: HOMEPAGE_VAR_NAVIDROME_TOKEN
valueFrom:
secretKeyRef:
name: homepage-navidrome
key: token
volumeMounts:
- name: config
mountPath: /app/config/bookmarks.yaml
subPath: bookmarks.yaml
- name: config
mountPath: /app/config/services.yaml
subPath: services.yaml
- name: config
mountPath: /app/config/widgets.yaml
subPath: widgets.yaml
- name: config
mountPath: /app/config/kubernetes.yaml
subPath: kubernetes.yaml
- name: config
mountPath: /app/config/docker.yaml
subPath: docker.yaml
- name: config
mountPath: /app/config/settings.yaml
subPath: settings.yaml
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /api/healthcheck
port: 3000
httpHeaders:
- name: Host
value: go.ops.eblu.me
initialDelaySeconds: 20
periodSeconds: 30
readinessProbe:
httpGet:
path: /api/healthcheck
port: 3000
httpHeaders:
- name: Host
value: go.ops.eblu.me
initialDelaySeconds: 10
periodSeconds: 10
volumes:
- name: config
configMap:
name: homepage-config

View file

@ -0,0 +1,17 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: homepage
resources:
- serviceaccount.yaml
- clusterrole.yaml
- clusterrolebinding.yaml
- configmap.yaml
- deployment.yaml
- service.yaml
- ingress-tailscale.yaml
- external-secret-openweathermap.yaml
- external-secret-jellyfin.yaml
- external-secret-forgejo.yaml
- external-secret-grafana.yaml
- external-secret-miniflux.yaml
- external-secret-navidrome.yaml

View file

@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: homepage
namespace: homepage
spec:
selector:
app: homepage
ports:
- name: http
port: 3000
targetPort: 3000

View file

@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: homepage
namespace: homepage