diff --git a/.github/workflows/publish-helmchart.yaml b/.github/workflows/publish-helmchart.yaml new file mode 100644 index 00000000..fa4e53d2 --- /dev/null +++ b/.github/workflows/publish-helmchart.yaml @@ -0,0 +1,51 @@ +name: release + +on: + push: + tags: + - '*' + branches: + - add-helmchart + + +permissions: + contents: read + +jobs: + release: + runs-on: ubuntu-latest + permissions: + #contents: write # needed to write releases + #id-token: write # needed for keyless signing + packages: write # needed for ghcr access + steps: + - uses: actions/checkout@v4 + - name: Setup Helm + uses: azure/setup-helm@v4.2.0 + + - name: Prepare + id: prep + run: | + VERSION=sha-${GITHUB_SHA::8} + if [[ $GITHUB_REF == refs/tags/* ]]; then + VERSION=${GITHUB_REF/refs\/tags\//} + fi + echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT + echo "REVISION=${GITHUB_SHA}" >> $GITHUB_OUTPUT + echo "CHART_VERSION=$(yq eval '.version' charts/factorio/Chart.yaml)" >> $GITHUB_OUTPUT + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + #Docker hub login - https://github.com/docker/login-action?tab=readme-ov-file#docker-hub + + - name: Publish Helm chart to GHCR + run: | + helm package charts/factorio + helm push factorio-${{ steps.prep.outputs.CHART_VERSION }}.tgz oci://ghcr.io/fredx30/factorio-docker + rm factorio-${{ steps.prep.outputs.CHART_VERSION }}.tgz diff --git a/charts/factorio/.helmignore b/charts/factorio/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/charts/factorio/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/factorio/Chart.yaml b/charts/factorio/Chart.yaml new file mode 100644 index 00000000..44ede8ff --- /dev/null +++ b/charts/factorio/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: factorio +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.1 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "2.0.15" # Match factorio client version. diff --git a/charts/factorio/templates/NOTES.txt b/charts/factorio/templates/NOTES.txt new file mode 100644 index 00000000..531a2a8d --- /dev/null +++ b/charts/factorio/templates/NOTES.txt @@ -0,0 +1,16 @@ +1. Get the application URL by running these commands: +{{- if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "factorio.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "factorio.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "factorio.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "factorio.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Connect to ip 127.0.0.1:34197 in your factorio client." + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 34197:$CONTAINER_PORT +{{- end }} diff --git a/charts/factorio/templates/_helpers.tpl b/charts/factorio/templates/_helpers.tpl new file mode 100644 index 00000000..f833e9bb --- /dev/null +++ b/charts/factorio/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "factorio.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "factorio.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "factorio.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "factorio.labels" -}} +helm.sh/chart: {{ include "factorio.chart" . }} +{{ include "factorio.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "factorio.selectorLabels" -}} +app.kubernetes.io/name: {{ include "factorio.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "factorio.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "factorio.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/factorio/templates/cm-server-settings.yaml b/charts/factorio/templates/cm-server-settings.yaml new file mode 100644 index 00000000..c2222ccb --- /dev/null +++ b/charts/factorio/templates/cm-server-settings.yaml @@ -0,0 +1,82 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: server-settings +data: + server-settings: | + { + "name": "Name of the game as it will appear in the game listing", + "description": "Description of the game that will appear in the listing", + "tags": [ "game", "tags" ], + + "_comment_max_players": "Maximum number of players allowed, admins can join even a full server. 0 means unlimited.", + "max_players": 0, + + "_comment_visibility": [ + "public: Game will be published on the official Factorio matching server", + "lan: Game will be broadcast on LAN" + ], + "visibility": { + "public": false, + "lan": true + }, + + "_comment_credentials": "Your factorio.com login credentials. Required for games with visibility public", + "username": "", + "password": "", + + "_comment_token": "Authentication token. May be used instead of 'password' above.", + "token": "", + + "game_password": "", + + "_comment_require_user_verification": "When set to true, the server will only allow clients that have a valid Factorio.com account", + "require_user_verification": true, + + "_comment_max_upload_in_kilobytes_per_second": "optional, default value is 0. 0 means unlimited.", + "max_upload_in_kilobytes_per_second": 0, + + "_comment_max_upload_slots": "optional, default value is 5. 0 means unlimited.", + "max_upload_slots": 5, + + "_comment_minimum_latency_in_ticks": "optional one tick is 16ms in default speed, default value is 0. 0 means no minimum.", + "minimum_latency_in_ticks": 0, + + "_comment_max_heartbeats_per_second": "Network tick rate. Maximum rate game updates packets are sent at before bundling them together. Minimum value is 6, maximum value is 240.", + "max_heartbeats_per_second": 60, + + "_comment_ignore_player_limit_for_returning_players": "Players that played on this map already can join even when the max player limit was reached.", + "ignore_player_limit_for_returning_players": false, + + "_comment_allow_commands": "possible values are, true, false and admins-only", + "allow_commands": "admins-only", + + "_comment_autosave_interval": "Autosave interval in minutes", + "autosave_interval": 10, + + "_comment_autosave_slots": "server autosave slots, it is cycled through when the server autosaves.", + "autosave_slots": 5, + + "_comment_afk_autokick_interval": "How many minutes until someone is kicked when doing nothing, 0 for never.", + "afk_autokick_interval": 15, + + "_comment_auto_pause": "Whether should the server be paused when no players are present.", + "auto_pause": true, + + "_comment_auto_pause_when_players_connect": "Whether should the server be paused when someone is connecting to the server.", + "auto_pause_when_players_connect": false, + + "only_admins_can_pause_the_game": true, + + "_comment_autosave_only_on_server": "Whether autosaves should be saved only on server or also on all connected clients. Default is true.", + "autosave_only_on_server": true, + + "_comment_non_blocking_saving": "Highly experimental feature, enable only at your own risk of losing your saves. On UNIX systems, server will fork itself to create an autosave. Autosaving on connected Windows clients will be disabled regardless of autosave_only_on_server option.", + "non_blocking_saving": false, + + "_comment_segment_sizes": "Long network messages are split into segments that are sent over multiple ticks. Their size depends on the number of peers currently connected. Increasing the segment size will increase upload bandwidth requirement for the server and download bandwidth requirement for clients. This setting only affects server outbound messages. Changing these settings can have a negative impact on connection stability for some clients.", + "minimum_segment_size": 25, + "minimum_segment_size_peer_count": 20, + "maximum_segment_size": 100, + "maximum_segment_size_peer_count": 10 + } diff --git a/charts/factorio/templates/deployment.yaml b/charts/factorio/templates/deployment.yaml new file mode 100644 index 00000000..4a52b04d --- /dev/null +++ b/charts/factorio/templates/deployment.yaml @@ -0,0 +1,67 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "factorio.fullname" . }} + labels: + {{- include "factorio.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "factorio.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "factorio.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + hostNetwork: {{ .Values.hostNetwork }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + env: + {{- toYaml .Values.envVars | nindent 12 }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: rcon + containerPort: {{ .Values.service.portTcp }} + protocol: TCP + - name: game + containerPort: {{ .Values.service.portUdp }} + protocol: UDP + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- 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 }} diff --git a/charts/factorio/templates/service.yaml b/charts/factorio/templates/service.yaml new file mode 100644 index 00000000..92e0d252 --- /dev/null +++ b/charts/factorio/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "factorio.fullname" . }} + labels: + {{- include "factorio.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.portTcp }} + targetPort: rcon + protocol: TCP + name: rcon + - port: {{ .Values.service.portUdp }} + targetPort: game + protocol: UDP + name: game + selector: + {{- include "factorio.selectorLabels" . | nindent 4 }} diff --git a/charts/factorio/templates/tests/test-connection.yaml b/charts/factorio/templates/tests/test-connection.yaml new file mode 100644 index 00000000..669d0986 --- /dev/null +++ b/charts/factorio/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "factorio.fullname" . }}-test-connection" + labels: + {{- include "factorio.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "factorio.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/charts/factorio/values.yaml b/charts/factorio/values.yaml new file mode 100644 index 00000000..ab6d0b6b --- /dev/null +++ b/charts/factorio/values.yaml @@ -0,0 +1,117 @@ +# Default values for factorio. +# Dockerhub ref: https://hub.docker.com/r/factoriotools/factorio/ +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +image: + repository: factoriotools/factorio + # This sets the pull policy for images. + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "stable" + +# This is for the secretes for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ +imagePullSecrets: [ ] +# This is to override the chart name. +nameOverride: "" +fullnameOverride: "" + +envVars: + - name: INSTANCE_NAME + value: Your Instance's Name + - name: INSTANCE_DESC + value: Your Instance's Description + - name: PORT + value: "34197" + - name: RCON_PORT + value: "27015" + - name: SAVE_NAME + value: "game_save_1" + - name: DLC_SPACE_AGE + value: "true" + - name: USERNAME + value: "" + - name: TOKEN + value: "" + # - name: UPDATE_MODS_ON_START + # value: "" + +# This is for setting Kubernetes Annotations to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +podAnnotations: { } +# This is for setting Kubernetes Labels to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +podLabels: { } + +# 845 matches the defaults for the container documentation. +podSecurityContext: + fsGroup: 845 + +securityContext: { } + # capabilities: + # drop: + # - ALL +# readOnlyRootFilesystem: true +# runAsNonRoot: true +# runAsUser: 1000 + +hostNetwork: true + +# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/ +service: + enabled: true + # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: ClusterIP + # This is the RCON port - + portTcp: 27015 + # This is required - game traffic is sent over UDP + portUdp: 34197 + +resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + requests: + cpu: 1000m + memory: 4Gi + +# Additional volumes on the output Deployment definition. +volumes: + - name: factorio + # HostPath sample + hostPath: + path: /home/factorio + # + # Ephemeral sample (local storage) + # ephemeral: + # volumeClaimTemplate: + # spec: + # accessModes: [ "ReadWriteOnce" ] + # resources: + # requests: + # storage: 50Gi + # + # PVC Sample + # persistentVolumeClaim: + # claimName: factorio + - name: server-settings + configMap: + name: server-settings + +# Additional volumeMounts on the output Deployment definition. +volumeMounts: + - name: factorio + mountPath: "/factorio" + #- name: server-settings + # mountPath: "/server-settings.json" + # key: "server-settings" + +nodeSelector: { } + +tolerations: [ ] + +affinity: { }