From 000b617dae6bbb90ac58c98c5e06a75abab44653 Mon Sep 17 00:00:00 2001 From: Marco van Zijl Date: Sun, 23 Nov 2025 14:43:30 +0100 Subject: [PATCH] Add OpenProject Helm chart and configuration files --- apps/openproject/Chart.yaml | 10 + apps/openproject/application.yaml | 35 ++ apps/openproject/templates/extra-objects.yaml | 4 + apps/openproject/templates/httproute.yaml | 20 + apps/openproject/values.yaml | 565 ++++++++++++++++++ 5 files changed, 634 insertions(+) create mode 100644 apps/openproject/Chart.yaml create mode 100644 apps/openproject/application.yaml create mode 100644 apps/openproject/templates/extra-objects.yaml create mode 100644 apps/openproject/templates/httproute.yaml create mode 100644 apps/openproject/values.yaml diff --git a/apps/openproject/Chart.yaml b/apps/openproject/Chart.yaml new file mode 100644 index 0000000..adaac6f --- /dev/null +++ b/apps/openproject/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: openproject +type: application +version: 1.0.0 +appVersion: "16.6.1" + +dependencies: + - name: openproject + version: 11.4.2 + repository: https://charts.openproject.org diff --git a/apps/openproject/application.yaml b/apps/openproject/application.yaml new file mode 100644 index 0000000..c9fff82 --- /dev/null +++ b/apps/openproject/application.yaml @@ -0,0 +1,35 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: openproject + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "5" + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: https://git.mvzijl.nl/marco/veda.git + targetRevision: applicationset-rewrite + path: apps/openproject + helm: + valueFiles: + - values.yaml + destination: + server: https://kubernetes.default.svc + namespace: openproject + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + - SkipDryRunOnMissingResource=true + ignoreDifferences: + - group: gateway.networking.k8s.io + kind: HTTPRoute + jqPathExpressions: + - .spec.parentRefs[] | .group, .kind + - .spec.rules[].backendRefs[] | .group, .kind, .weight diff --git a/apps/openproject/templates/extra-objects.yaml b/apps/openproject/templates/extra-objects.yaml new file mode 100644 index 0000000..8dd36ec --- /dev/null +++ b/apps/openproject/templates/extra-objects.yaml @@ -0,0 +1,4 @@ +{{- range .Values.extraObjects }} +--- +{{ toYaml . }} +{{- end }} diff --git a/apps/openproject/templates/httproute.yaml b/apps/openproject/templates/httproute.yaml new file mode 100644 index 0000000..bc99657 --- /dev/null +++ b/apps/openproject/templates/httproute.yaml @@ -0,0 +1,20 @@ +{{- if .Values.route.main.enabled -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + parentRefs: + {{- toYaml .Values.route.main.parentRefs | nindent 4 }} + hostnames: + {{- toYaml .Values.route.main.hostnames | nindent 4 }} + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: {{ include "common.names.fullname" . }} + port: {{ .Values.service.ports.http.port | default 80 }} +{{- end -}} diff --git a/apps/openproject/values.yaml b/apps/openproject/values.yaml new file mode 100644 index 0000000..b3218ad --- /dev/null +++ b/apps/openproject/values.yaml @@ -0,0 +1,565 @@ +openproject: + ingress: + enabled: false + + persistence: + enabled: false + +route: + main: + enabled: true + hostnames: + - openproject.noxxos.nl + parentRefs: + - name: traefik-gateway + namespace: traefik + sectionName: websecure + + s3: + enabled: true + auth: + existingSecret: "openproject-s3-mapped" + bucket: "openproject" + endpoint: "http://rook-ceph-rgw-ceph-objectstore.rook-ceph.svc:80" + + postgresql: + bundled: false + connection: + host: openproject-pg-cluster-rw.openproject.svc.cluster.local + port: 5432 + auth: + existingSecret: openproject-pg-cluster-app + + replicaCount: 1 + + metrics: + enabled: false + + extraVolumes: + - name: enterprise-token + configMap: + name: openproject-enterprise-token + defaultMode: 0644 + + extraVolumeMounts: + - name: enterprise-token + mountPath: /app/app/models/enterprise_token.rb + subPath: enterprise_token.rb + readOnly: true + + + + +extraObjects: + - apiVersion: postgresql.cnpg.io/v1 + kind: Cluster + metadata: + name: openproject-pg-cluster + namespace: openproject + spec: + instances: 2 + postgresql: + parameters: + max_connections: "20" + shared_buffers: "25MB" + effective_cache_size: "75MB" + maintenance_work_mem: "6400kB" + checkpoint_completion_target: "0.9" + wal_buffers: "768kB" + default_statistics_target: "100" + random_page_cost: "1.1" + effective_io_concurrency: "300" + work_mem: "640kB" + huge_pages: "off" + max_wal_size: "128MB" + bootstrap: + initdb: + database: openproject + owner: openproject + storage: + size: 20Gi + storageClass: local-path + resources: + requests: + cpu: 100m + memory: 100Mi + limits: + memory: 512Mi + plugins: + - enabled: true + name: barman-cloud.cloudnative-pg.io + isWALArchiver: true + parameters: + barmanObjectName: backup-store + - apiVersion: barmancloud.cnpg.io/v1 + kind: ObjectStore + metadata: + name: backup-store + namespace: openproject + spec: + retentionPolicy: "30d" + configuration: + destinationPath: s3://cnpg-backup-openproject/ + endpointURL: http://rook-ceph-rgw-ceph-objectstore.rook-ceph.svc:80 + s3Credentials: + accessKeyId: + name: cnpg-backup + key: AWS_ACCESS_KEY_ID + secretAccessKey: + name: cnpg-backup + key: AWS_SECRET_ACCESS_KEY + wal: + compression: bzip2 + data: + compression: bzip2 + immediateCheckpoint: true + - apiVersion: postgresql.cnpg.io/v1 + kind: ScheduledBackup + metadata: + name: cnpg-backup + namespace: openproject + spec: + method: plugin + immediate: true + schedule: "0 45 2 * * *" # 2:45 AM daily + backupOwnerReference: self + cluster: + name: openproject-pg-cluster + pluginConfiguration: + name: barman-cloud.cloudnative-pg.io + - apiVersion: objectbucket.io/v1alpha1 + kind: ObjectBucketClaim + metadata: + name: cnpg-backup + namespace: openproject + spec: + generateBucketName: cnpg-backup-openproject + storageClassName: ceph-bucket + additionalConfig: + maxSize: "50Gi" + - apiVersion: objectbucket.io/v1alpha1 + kind: ObjectBucketClaim + metadata: + name: openproject + namespace: openproject + spec: + generateBucketName: openproject + storageClassName: ceph-bucket + additionalConfig: + maxSize: "200Gi" + - apiVersion: v1 + kind: ServiceAccount + metadata: + name: openproject-s3-sync + namespace: openproject + - apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + name: openproject-s3-sync + namespace: openproject + rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "create", "update", "patch"] + - apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: openproject-s3-sync + namespace: openproject + subjects: + - kind: ServiceAccount + name: openproject-s3-sync + namespace: openproject + roleRef: + kind: Role + name: openproject-s3-sync + apiGroup: rbac.authorization.k8s.io + - apiVersion: batch/v1 + kind: Job + metadata: + name: openproject-s3-sync + namespace: openproject + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation + spec: + template: + spec: + serviceAccountName: openproject-s3-sync + containers: + - name: sync + image: bitnami/kubectl:latest + command: + - /bin/sh + - -c + - | + echo "Waiting for secret openproject..." + until kubectl get secret openproject -n openproject; do sleep 5; done + + ACCESS_KEY=$(kubectl get secret openproject -n openproject -o jsonpath='{.data.AWS_ACCESS_KEY_ID}' | base64 -d) + SECRET_KEY=$(kubectl get secret openproject -n openproject -o jsonpath='{.data.AWS_SECRET_ACCESS_KEY}' | base64 -d) + + kubectl create secret generic openproject-s3-mapped -n openproject \ + --from-literal=OPENPROJECT_FOG_CREDENTIALS_AWS__ACCESS__KEY__ID="$ACCESS_KEY" \ + --from-literal=OPENPROJECT_FOG_CREDENTIALS_AWS__SECRET__ACCESS__KEY="$SECRET_KEY" \ + --dry-run=client -o yaml | kubectl apply -f - + restartPolicy: OnFailure + - apiVersion: v1 + kind: ConfigMap + metadata: + name: openproject-enterprise-token + namespace: openproject + data: + enterprise_token.rb: | + # OpenProject is an open source project management software. + # Copyright (C) the OpenProject GmbH + # + # This program is free software; you can redistribute it and/or + # modify it under the terms of the GNU General Public License version 3. + # + # OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: + # Copyright (C) 2006-2013 Jean-Philippe Lang + # Copyright (C) 2010-2013 the ChiliProject Team + # + # This program is free software; you can redistribute it and/or + # modify it under the terms of the GNU General Public License + # as published by the Free Software Foundation; either version 2 + # of the License, or (at your option) any later version. + # + # This program is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + # GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with this program; if not, write to the Free Software + # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + # + # See COPYRIGHT and LICENSE files for more details. + #++ + class EnterpriseToken < ApplicationRecord + class << self + # On the backend, features are checked only using `allows_to?`, which we can hardcode to return `true`. + # On the frontend, however, it instead checks if particular strings are included in the `available_features` + # Unfortunately there is no canonical variable with all the features, so we have to hardcode. + # Use `rg --pcre2 -INo "(?<=allows_to\?[^:*]:|allowsTo\(')[a-z_]*" | sort -u` to generate this list: + TRUE_FEATURES = %i[ + allowed_action + baseline_comparison + board_view + calculated_values + conditional_highlighting + custom_actions + custom_field_hierarchies + customize_life_cycle + date_alerts + define_custom_style + edit_attribute_groups + forbidden_action + gantt_pdf_export + internal_comments + ldap_groups + nextcloud_sso + one_drive_sharepoint_file_storage + placeholder_users + readonly_work_packages + scim_api + sso_auth_providers + team_planner_view + time_entry_time_restrictions + virus_scanning + work_package_query_relation_columns + work_package_sharing + work_package_subject_generation + ].freeze + + # Not all the methods here are ever actually called outside the enterprise_token.rb file itself + # in upstream openproject, but I'll include all of them that can be reasonably implemented here, + # just in case openproject changes in the future to start using the extra methods. + def current + self.new + end + + def all_tokens + [self.new] + end + + def active_tokens + [self.new] + end + + def active_non_trial_tokens + [self.new] + end + + def active_trial_tokens + [] + end + + def active_trial_token + nil + end + + def allows_to?(feature) + true + end + + def active? + true + end + + def trial_only? + false + end + + def available_features + TRUE_FEATURES + end + + def non_trialling_features + TRUE_FEATURES + end + + def trialling_features + [] + end + + def trialling?(feature) + false + end + + def hide_banners? + true + end + + def show_banners? + false + end + + def user_limit + nil + end + + def non_trial_user_limit + nil + end + + def trial_user_limit + nil + end + + def banner_type_for(feature:) + nil + end + + def get_user_limit_of(tokens) + nil + end + end + + FAR_FUTURE_DATE = Date.new(9999, 1, 1) + + def token_object + Class.new do + def id + "lmao" + end + + def has_feature?(feature) + true + end + + def will_expire? + false + end + + def mail + "admin@example.com" + end + + def subscriber + "markasoftware-free-enterprise-mode" + end + + def company + "markasoftware" + end + + def domain + "markasoftware.com" + end + + def issued_at + Time.zone.today - 1 + end + + def starts_at + Time.zone.today - 1 + end + + def expires_at + Time.zone.today + 1 + end + + def reprieve_days + nil + end + + def reprieve_days_left + 69 + end + + def restrictions + nil + end + + def available_features + EnterpriseToken.TRUE_FEATURES + end + + def plan + "markasoftware_free_enterprise_mode" + end + + def features + EnterpriseToken.TRUE_FEATURES + end + + def version + 69 + end + + def started? + true + end + + def trial? + false + end + + def active? + true + end + end.new + end + + def id + "lmao" + end + + def encoded_token + "oaml" + end + + def will_expire? + false + end + + def mail + "admin@example.com" + end + + def subscriber + "markasoftware-free-enterprise-mode" + end + + def company + "markasoftware" + end + + def domain + "markasoftware.com" + end + + def issued_at + Time.zone.today - 1 + end + + def starts_at + Time.zone.today - 1 + end + + def expires_at + Time.zone.today + 1 + end + + def reprieve_days + nil + end + + def reprieve_days_left + 69 + end + + def restrictions + nil + end + + def available_features + EnterpriseToken.TRUE_FEATURES + end + + def plan + "markasoftware_free_enterprise_mode" + end + + def features + EnterpriseToken.TRUE_FEATURES + end + + def version + 69 + end + + def started? + true + end + + def trial? + false + end + + def active? + true + end + + def allows_to?(action) + true + end + + def expiring_soon? + false + end + + def in_grace_period? + false + end + + def expired?(reprieve: true) + false + end + + def statuses + [] + end + + def invalid_domain? + false + end + + def unlimited_users? + true + end + + def max_active_users + nil + end + + def sort_key + [FAR_FUTURE_DATE, FAR_FUTURE_DATE] + end + + def days_left + 69 + end + end \ No newline at end of file