From e4e9ebd73706f573b44e8b72746ad52e19b17810 Mon Sep 17 00:00:00 2001 From: Caesar2011 Date: Sun, 17 May 2026 20:36:29 +0200 Subject: [PATCH] feat: add Dockerfile, Helm chart, Gitea Actions workflow --- .gitea/workflows/build.yml | 44 ++++++++++++ web/README.md | 44 ++++-------- web/charts/factorio-dashboard/Chart.yaml | 6 ++ .../factorio-dashboard/templates/_helpers.tpl | 50 +++++++++++++ .../templates/db-service.yaml | 15 ++++ .../templates/db-statefulset.yaml | 57 +++++++++++++++ .../templates/deployment.yaml | 70 +++++++++++++++++++ .../factorio-dashboard/templates/ingress.yaml | 33 +++++++++ .../factorio-dashboard/templates/secret.yaml | 12 ++++ .../factorio-dashboard/templates/service.yaml | 13 ++++ web/charts/factorio-dashboard/values.yaml | 41 +++++++++++ web/docker-compose.yml | 1 - 12 files changed, 356 insertions(+), 30 deletions(-) create mode 100644 .gitea/workflows/build.yml create mode 100644 web/charts/factorio-dashboard/Chart.yaml create mode 100644 web/charts/factorio-dashboard/templates/_helpers.tpl create mode 100644 web/charts/factorio-dashboard/templates/db-service.yaml create mode 100644 web/charts/factorio-dashboard/templates/db-statefulset.yaml create mode 100644 web/charts/factorio-dashboard/templates/deployment.yaml create mode 100644 web/charts/factorio-dashboard/templates/ingress.yaml create mode 100644 web/charts/factorio-dashboard/templates/secret.yaml create mode 100644 web/charts/factorio-dashboard/templates/service.yaml create mode 100644 web/charts/factorio-dashboard/values.yaml diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..d7bb66e --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,44 @@ +name: Build & Push + +on: + push: + branches: + - main + +env: + REGISTRY: git.sebse.de + IMAGE: git.sebse.de/sebse/factorio-signal-exporter + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Log in to Gitea Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.GITEA_USERNAME }} + password: ${{ secrets.GITEA_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE }} + tags: | + type=sha,prefix=,format=short + type=raw,value=latest + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: ./web + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=registry,ref=${{ env.IMAGE }}:latest + cache-to: type=inline \ No newline at end of file diff --git a/web/README.md b/web/README.md index e215bc4..c7b702e 100644 --- a/web/README.md +++ b/web/README.md @@ -1,36 +1,22 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# Factorio Dashboard -## Getting Started +Next.js dashboard for the [Signal Exporter](../README.md) Factorio mod. +Ingests combinator data via a POST API and visualises it with TimescaleDB as the backing store. -First, run the development server: +## Local Development + +**Prerequisites:** Docker, Node.js 24, npm ```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` +# 1. Start TimescaleDB +docker compose up -d -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +# 2. Install dependencies +npm install -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +# 3. Run migrations against the local DB +DATABASE_URL=postgresql://factorio:factorio@localhost:5432/factorio npm run migrate -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +# 4. Start dev server +cp .env.local.example .env.local # fill in API_TOKEN +npm run dev \ No newline at end of file diff --git a/web/charts/factorio-dashboard/Chart.yaml b/web/charts/factorio-dashboard/Chart.yaml new file mode 100644 index 0000000..ea48685 --- /dev/null +++ b/web/charts/factorio-dashboard/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: factorio-dashboard +description: Factorio Signal Exporter — Next.js dashboard with TimescaleDB +type: application +version: 0.1.0 +appVersion: "latest" \ No newline at end of file diff --git a/web/charts/factorio-dashboard/templates/_helpers.tpl b/web/charts/factorio-dashboard/templates/_helpers.tpl new file mode 100644 index 0000000..58c0407 --- /dev/null +++ b/web/charts/factorio-dashboard/templates/_helpers.tpl @@ -0,0 +1,50 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "factorio-dashboard.name" -}} +{{- .Chart.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a fully qualified app name. +*/}} +{{- define "factorio-dashboard.fullname" -}} +{{- printf "%s" (include "factorio-dashboard.name" .) | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels. +*/}} +{{- define "factorio-dashboard.labels" -}} +helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} +app.kubernetes.io/name: {{ include "factorio-dashboard.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels. +*/}} +{{- define "factorio-dashboard.selectorLabels" -}} +app.kubernetes.io/name: {{ include "factorio-dashboard.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Name of the secret that holds API_TOKEN and DATABASE_URL. +*/}} +{{- define "factorio-dashboard.secretName" -}} +{{- if .Values.app.existingSecret }} +{{- .Values.app.existingSecret }} +{{- else }} +{{- include "factorio-dashboard.fullname" . }}-secret +{{- end }} +{{- end }} + +{{/* +Construct DATABASE_URL from db values. +*/}} +{{- define "factorio-dashboard.databaseUrl" -}} +{{- printf "postgresql://%s:%s@%s-db:%d/%s" .Values.db.user .Values.db.password (include "factorio-dashboard.fullname" .) (int .Values.db.port) .Values.db.name }} +{{- end }} \ No newline at end of file diff --git a/web/charts/factorio-dashboard/templates/db-service.yaml b/web/charts/factorio-dashboard/templates/db-service.yaml new file mode 100644 index 0000000..abd4638 --- /dev/null +++ b/web/charts/factorio-dashboard/templates/db-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "factorio-dashboard.fullname" . }}-db + labels: + {{- include "factorio-dashboard.labels" . | nindent 4 }} + app.kubernetes.io/component: db +spec: + clusterIP: None + selector: + {{- include "factorio-dashboard.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: db + ports: + - port: {{ .Values.db.port }} + targetPort: {{ .Values.db.port }} \ No newline at end of file diff --git a/web/charts/factorio-dashboard/templates/db-statefulset.yaml b/web/charts/factorio-dashboard/templates/db-statefulset.yaml new file mode 100644 index 0000000..b61ea8f --- /dev/null +++ b/web/charts/factorio-dashboard/templates/db-statefulset.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "factorio-dashboard.fullname" . }}-db + labels: + {{- include "factorio-dashboard.labels" . | nindent 4 }} + app.kubernetes.io/component: db +spec: + serviceName: {{ include "factorio-dashboard.fullname" . }}-db + replicas: 1 + selector: + matchLabels: + {{- include "factorio-dashboard.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: db + template: + metadata: + labels: + {{- include "factorio-dashboard.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: db + spec: + containers: + - name: timescaledb + image: timescale/timescaledb:latest-pg16 + ports: + - containerPort: {{ .Values.db.port }} + env: + - name: POSTGRES_USER + value: {{ .Values.db.user | quote }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "factorio-dashboard.secretName" . }} + key: DATABASE_URL + optional: false + - name: POSTGRES_PASSWORD + value: {{ .Values.db.password | quote }} + - name: POSTGRES_DB + value: {{ .Values.db.name | quote }} + volumeMounts: + - name: db-data + mountPath: /var/lib/postgresql/data + readinessProbe: + exec: + command: ["pg_isready", "-U", {{ .Values.db.user | quote }}] + initialDelaySeconds: 10 + periodSeconds: 5 + volumeClaimTemplates: + - metadata: + name: db-data + spec: + accessModes: ["ReadWriteOnce"] + {{- if .Values.db.storageClassName }} + storageClassName: {{ .Values.db.storageClassName | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.db.storage }} \ No newline at end of file diff --git a/web/charts/factorio-dashboard/templates/deployment.yaml b/web/charts/factorio-dashboard/templates/deployment.yaml new file mode 100644 index 0000000..d56dd79 --- /dev/null +++ b/web/charts/factorio-dashboard/templates/deployment.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "factorio-dashboard.fullname" . }} + labels: + {{- include "factorio-dashboard.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.app.replicaCount }} + selector: + matchLabels: + {{- include "factorio-dashboard.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "factorio-dashboard.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + initContainers: + - name: migrate + image: {{ .Values.image.repository }}:{{ .Values.image.tag }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ["npm", "run", "migrate"] + env: + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: {{ include "factorio-dashboard.secretName" . }} + key: DATABASE_URL + containers: + - name: app + image: {{ .Values.image.repository }}:{{ .Values.image.tag }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: 3000 + env: + - name: API_TOKEN + valueFrom: + secretKeyRef: + name: {{ include "factorio-dashboard.secretName" . }} + key: API_TOKEN + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: {{ include "factorio-dashboard.secretName" . }} + key: DATABASE_URL + readinessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 10 + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} \ No newline at end of file diff --git a/web/charts/factorio-dashboard/templates/ingress.yaml b/web/charts/factorio-dashboard/templates/ingress.yaml new file mode 100644 index 0000000..1e7909a --- /dev/null +++ b/web/charts/factorio-dashboard/templates/ingress.yaml @@ -0,0 +1,33 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "factorio-dashboard.fullname" . }} + labels: + {{- include "factorio-dashboard.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + - hosts: + - {{ .Values.ingress.host | quote }} + secretName: {{ .Values.ingress.tlsSecretName | default (printf "%s-tls" (include "factorio-dashboard.fullname" .)) | quote }} + {{- end }} + rules: + - host: {{ .Values.ingress.host | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "factorio-dashboard.fullname" . }} + port: + number: {{ .Values.service.port }} +{{- end }} \ No newline at end of file diff --git a/web/charts/factorio-dashboard/templates/secret.yaml b/web/charts/factorio-dashboard/templates/secret.yaml new file mode 100644 index 0000000..9248866 --- /dev/null +++ b/web/charts/factorio-dashboard/templates/secret.yaml @@ -0,0 +1,12 @@ +{{- if not .Values.app.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "factorio-dashboard.fullname" . }}-secret + labels: + {{- include "factorio-dashboard.labels" . | nindent 4 }} +type: Opaque +stringData: + API_TOKEN: {{ .Values.app.apiToken | required "app.apiToken is required" | quote }} + DATABASE_URL: {{ include "factorio-dashboard.databaseUrl" . | quote }} +{{- end }} \ No newline at end of file diff --git a/web/charts/factorio-dashboard/templates/service.yaml b/web/charts/factorio-dashboard/templates/service.yaml new file mode 100644 index 0000000..1e032b3 --- /dev/null +++ b/web/charts/factorio-dashboard/templates/service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "factorio-dashboard.fullname" . }} + labels: + {{- include "factorio-dashboard.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "factorio-dashboard.selectorLabels" . | nindent 4 }} + ports: + - port: {{ .Values.service.port }} + targetPort: 3000 \ No newline at end of file diff --git a/web/charts/factorio-dashboard/values.yaml b/web/charts/factorio-dashboard/values.yaml new file mode 100644 index 0000000..0634709 --- /dev/null +++ b/web/charts/factorio-dashboard/values.yaml @@ -0,0 +1,41 @@ +image: + repository: git.sebse.de/sebse/factorio-signal-exporter + tag: latest + pullPolicy: IfNotPresent + +imagePullSecrets: [] + +app: + replicaCount: 1 + ## API token for ingest POST and dashboard GET ?token= + apiToken: "" + ## If set, use this existing K8s secret instead of creating one. + ## Secret must contain keys: API_TOKEN, DATABASE_URL + existingSecret: "" + +db: + ## TimescaleDB credentials — used to build DATABASE_URL and configure the StatefulSet + user: factorio + password: factorio + name: factorio + port: 5432 + storage: 10Gi + storageClassName: "" # leave empty for cluster default + +service: + type: ClusterIP + port: 3000 + +ingress: + enabled: false + className: "" + host: factorio.example.com + tls: false + tlsSecretName: "" + annotations: {} + +resources: {} + +nodeSelector: {} +tolerations: [] +affinity: {} \ No newline at end of file diff --git a/web/docker-compose.yml b/web/docker-compose.yml index 6dd2cbf..0edc766 100644 --- a/web/docker-compose.yml +++ b/web/docker-compose.yml @@ -10,7 +10,6 @@ services: - "5432:5432" volumes: - db_data:/var/lib/postgresql/data - - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro volumes: db_data: \ No newline at end of file