commit 50f5234488d0d6021240a2e2a92d3b63c82f80f4 Author: Alexis Bruneteau Date: Thu Sep 4 17:17:18 2025 +0200 init diff --git a/README.md b/README.md new file mode 100644 index 0000000..a8ac2f9 --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# Homepage K3s Configuration + +A complete Kubernetes configuration for deploying [Homepage](https://gethomepage.dev/) dashboard on K3s clusters. + +## Quick Start + +Deploy Homepage to your K3s cluster: + +```bash +kubectl apply -k k8s/ +``` + +## Configuration + +The configuration includes: + +- **Namespace**: `homepage` namespace with service account +- **RBAC**: ClusterRole with permissions to read cluster resources +- **ConfigMap**: Pre-configured with widgets for K3s monitoring +- **Deployment**: Secure deployment with resource limits and health checks +- **Service**: ClusterIP service on port 3000 +- **Ingress**: Traefik-compatible ingress with TLS support + +## Access + +After deployment, Homepage will be available at: +- HTTP: `http://sortifal.fr` +- HTTPS: `https://sortifal.fr` (with cert-manager) + +Ensure your DNS points `sortifal.fr` to your K3s ingress IP. + +## Customization + +### Update Configuration + +Edit the ConfigMap in `k8s/configmap.yaml` to customize: +- **services.yaml**: Your applications and services +- **bookmarks.yaml**: Quick links and bookmarks +- **widgets.yaml**: Dashboard widgets (cluster info, resources, etc.) +- **settings.yaml**: Theme, layout, and provider settings + +Apply changes: +```bash +kubectl apply -k k8s/ +kubectl rollout restart deployment/homepage -n homepage +``` + +### Change Domain + +Update `sortifal.fr` in `k8s/ingress.yaml` and the `HOMEPAGE_ALLOWED_HOSTS` environment variable in `k8s/deployment.yaml`. + +### Resource Limits + +Adjust CPU/memory limits in `k8s/deployment.yaml` based on your needs. + +## Monitoring + +Homepage includes widgets for monitoring your K3s cluster: +- Cluster CPU and memory usage +- Node status and resources +- Pod information across namespaces +- System resources + +## Troubleshooting + +Check deployment status: +```bash +kubectl get pods -n homepage +kubectl logs -n homepage deployment/homepage +``` + +Verify RBAC permissions: +```bash +kubectl auth can-i get nodes --as=system:serviceaccount:homepage:homepage +``` + +## Security + +The configuration follows security best practices: +- Non-root container execution +- Read-only root filesystem where possible +- Minimal RBAC permissions +- Resource limits +- Security contexts +- Dropped capabilities \ No newline at end of file diff --git a/k8s/configmap.yaml b/k8s/configmap.yaml new file mode 100644 index 0000000..efd15ad --- /dev/null +++ b/k8s/configmap.yaml @@ -0,0 +1,100 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: homepage-config + namespace: homepage + labels: + app.kubernetes.io/name: homepage +data: + kubernetes.yaml: | + mode: cluster + settings.yaml: | + providers: + openweathermap: openweathermapapikey + weatherapi: weatherapiapikey + title: Dashboard + favicon: https://github.com/walkxcode/dashboard-icons/blob/main/png/homepage.png + theme: dark + color: slate + headerStyle: clean + layout: + My First Group: + style: row + columns: 3 + My Second Group: + style: column + custom.css: "" + custom.js: "" + bookmarks.yaml: | + - Developer: + - Github: + - abbr: GH + href: https://github.com/ + - Docker Hub: + - abbr: DH + href: https://hub.docker.com/ + - Social: + - Reddit: + - abbr: RE + href: https://www.reddit.com/ + services.yaml: | + - Infrastructure: + - Traefik: + href: http://traefik.local + description: Reverse Proxy & Load Balancer + icon: traefik.png + server: my-k3s + container: traefik + - Longhorn: + href: http://longhorn.local + description: Distributed Storage + icon: longhorn.png + + - Monitoring: + - Grafana: + href: http://grafana.local + description: Analytics & Monitoring + icon: grafana.png + - Prometheus: + href: http://prometheus.local + description: Metrics Collection + icon: prometheus.png + + - Applications: + - Nextcloud: + href: http://nextcloud.local + description: File Sharing & Collaboration + icon: nextcloud.png + - Jellyfin: + href: http://jellyfin.local + description: Media Server + icon: jellyfin.png + widgets.yaml: | + - kubernetes: + cluster: + show: true + cpu: true + memory: true + showLabel: true + label: "K3s Cluster" + nodes: + show: true + cpu: true + memory: true + showLabel: true + - resources: + backend: resources + expanded: true + cpu: true + memory: true + - datetime: + text_size: xl + format: + timeStyle: short + dateStyle: short + hourCycle: h23 + - search: + provider: duckduckgo + target: _blank + showSearchSuggestions: true + docker.yaml: "" \ No newline at end of file diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml new file mode 100644 index 0000000..6fa42b7 --- /dev/null +++ b/k8s/deployment.yaml @@ -0,0 +1,110 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: homepage + namespace: homepage + labels: + app.kubernetes.io/name: homepage +spec: + revisionHistoryLimit: 3 + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + selector: + matchLabels: + app.kubernetes.io/name: homepage + template: + metadata: + labels: + app.kubernetes.io/name: homepage + spec: + serviceAccountName: homepage + automountServiceAccountToken: true + dnsPolicy: ClusterFirst + enableServiceLinks: true + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + containers: + - name: homepage + image: "ghcr.io/gethomepage/homepage:latest" + imagePullPolicy: Always + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + capabilities: + drop: + - ALL + env: + - name: HOMEPAGE_ALLOWED_HOSTS + value: "sortifal.fr,localhost" + ports: + - name: http + containerPort: 3000 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 256Mi + volumeMounts: + - mountPath: /app/config/custom.js + name: homepage-config + subPath: custom.js + - mountPath: /app/config/custom.css + name: homepage-config + subPath: custom.css + - mountPath: /app/config/bookmarks.yaml + name: homepage-config + subPath: bookmarks.yaml + - mountPath: /app/config/docker.yaml + name: homepage-config + subPath: docker.yaml + - mountPath: /app/config/kubernetes.yaml + name: homepage-config + subPath: kubernetes.yaml + - mountPath: /app/config/services.yaml + name: homepage-config + subPath: services.yaml + - mountPath: /app/config/settings.yaml + name: homepage-config + subPath: settings.yaml + - mountPath: /app/config/widgets.yaml + name: homepage-config + subPath: widgets.yaml + - mountPath: /app/config/logs + name: logs + volumes: + - name: homepage-config + configMap: + name: homepage-config + - name: logs + emptyDir: {} \ No newline at end of file diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml new file mode 100644 index 0000000..fdc593d --- /dev/null +++ b/k8s/ingress.yaml @@ -0,0 +1,33 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: homepage + namespace: homepage + labels: + app.kubernetes.io/name: homepage + annotations: + kubernetes.io/ingress.class: "traefik" + traefik.ingress.kubernetes.io/router.entrypoints: web,websecure + traefik.ingress.kubernetes.io/router.middlewares: default-redirect-https@kubernetescrd + cert-manager.io/cluster-issuer: "letsencrypt-prod" + gethomepage.dev/description: "Homepage Dashboard" + gethomepage.dev/enabled: "true" + gethomepage.dev/group: "Dashboard" + gethomepage.dev/icon: "homepage.png" + gethomepage.dev/name: "Homepage" +spec: + tls: + - hosts: + - sortifal.fr + secretName: homepage-tls + rules: + - host: "sortifal.fr" + http: + paths: + - path: "/" + pathType: Prefix + backend: + service: + name: homepage + port: + number: 3000 \ No newline at end of file diff --git a/k8s/kustomization.yaml b/k8s/kustomization.yaml new file mode 100644 index 0000000..254013a --- /dev/null +++ b/k8s/kustomization.yaml @@ -0,0 +1,24 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: homepage + +resources: + - namespace.yaml + - configmap.yaml + - rbac.yaml + - service.yaml + - deployment.yaml + - ingress.yaml + +commonLabels: + app: homepage + version: v1.0.0 + +images: + - name: ghcr.io/gethomepage/homepage + newTag: latest + +replicas: + - name: homepage + count: 1 \ No newline at end of file diff --git a/k8s/namespace.yaml b/k8s/namespace.yaml new file mode 100644 index 0000000..0062572 --- /dev/null +++ b/k8s/namespace.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: homepage + labels: + app.kubernetes.io/name: homepage +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: homepage + namespace: homepage + labels: + app.kubernetes.io/name: homepage \ No newline at end of file diff --git a/k8s/rbac.yaml b/k8s/rbac.yaml new file mode 100644 index 0000000..d1a4638 --- /dev/null +++ b/k8s/rbac.yaml @@ -0,0 +1,72 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: homepage + labels: + app.kubernetes.io/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: + - httproutes + - gateways + verbs: + - get + - list + - apiGroups: + - metrics.k8s.io + resources: + - nodes + - pods + verbs: + - get + - list + - apiGroups: + - apps + resources: + - deployments + - replicasets + - statefulsets + verbs: + - get + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: homepage + labels: + app.kubernetes.io/name: homepage +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: homepage +subjects: + - kind: ServiceAccount + name: homepage + namespace: homepage \ No newline at end of file diff --git a/k8s/service.yaml b/k8s/service.yaml new file mode 100644 index 0000000..8927e1f --- /dev/null +++ b/k8s/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: homepage + namespace: homepage + labels: + app.kubernetes.io/name: homepage +spec: + type: ClusterIP + ports: + - port: 3000 + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: homepage \ No newline at end of file