commit 39ab6d08b57f396abac58a26efa3bbf29aa5ec6c Author: xoffio Date: Mon Nov 3 23:45:48 2025 -0500 initial files diff --git a/.helmignore b/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/.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/Chart.yaml b/Chart.yaml new file mode 100644 index 0000000..5467f7b --- /dev/null +++ b/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: xo-pangolin +description: Unofficial helm chart to deploy Pangolin on 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.0 + +# 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: "1.11.0" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2b748ea --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 XpaceOff + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4431034 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ + +Unofficial Pangolin Helm chart + +```bash +git clone https://github.com/XpaceOff/xo-pangolin.git +cd xo-pangolin + +# Install example: +helm install pangolin xo-pangolin \ +--set-string settings.pangolin.serverSecretKey=SECRET_KEY_HERE \ +--set-string ingress.hostDomain="pangolin.test" \ +--set-string volumes[0].name=vol-pangolin \ +--set-string volumes[0].nfs.path=/volume/cluster/pangolin \ +--set-string volumes[0].nfs.server=nas.lan + +# Uninstall example: +helm uninstall pangolin +``` diff --git a/files/config.yml b/files/config.yml new file mode 100644 index 0000000..53a2c80 --- /dev/null +++ b/files/config.yml @@ -0,0 +1,20 @@ +app: + dashboard_url: "https://{{ .Values.ingress.hostDomain }}" + log_level: info + save_logs: true + +domains: + domain1: + base_domain: "{{ .Values.ingress.hostDomain }}" + +server: + secret: "{{ .Values.settings.pangolin.serverSecretKey }}" + internal_hostname: "{{ include "xo-pangolin.fullname" . }}-pangolin-cip.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain | default "cluster.local" }}" + +gerbil: + base_endpoint: "{{ .Values.ingress.hostDomain }}" + +flags: + require_email_verification: false + disable_signup_without_invite: true + disable_user_create_org: true diff --git a/files/dynamic_config.yml b/files/dynamic_config.yml new file mode 100644 index 0000000..d466b68 --- /dev/null +++ b/files/dynamic_config.yml @@ -0,0 +1,21 @@ +http: + routers: + next-router: + rule: Host(`{{ .Values.ingress.hostDomain }}`) && !PathPrefix(`/api/v1`) + service: next-service + entryPoints: [web] + api-router: + rule: "Host(`{{ .Values.ingress.hostDomain }}`) && PathPrefix(`/api/v1`)" + service: api-service + entryPoints: [web] + ws-router: + rule: "Host(`{{ .Values.ingress.hostDomain }}`)" + service: api-service + entryPoints: [web] + services: + next-service: + loadBalancer: + servers: [{ url: "http://{{ include "xo-pangolin.fullname" . }}-pangolin-cip.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain | default "cluster.local" }}:3002" }] + api-service: + loadBalancer: + servers: [{ url: "http://{{ include "xo-pangolin.fullname" . }}-pangolin-cip.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain | default "cluster.local" }}:3000" }] diff --git a/files/traefik_config.yml b/files/traefik_config.yml new file mode 100644 index 0000000..7bb2430 --- /dev/null +++ b/files/traefik_config.yml @@ -0,0 +1,42 @@ +providers: + http: + endpoint: "http://{{ include "xo-pangolin.fullname" . }}-pangolin-cip.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain | default "cluster.local" }}:3001/api/v1/traefik-config" + pollInterval: "5s" + file: + filename: "/etc/traefik/dynamic_config.yml" + +experimental: + plugins: + badger: + moduleName: "github.com/fosrl/badger" + version: {{ .Values.settings.traefik.badgerVersion }} + +log: + level: "DEBUG" + format: "common" + +accessLog: + # JSON format + format: json + # Filter on status codes, retry attempts and minimal duration + filters: + statusCodes: + - "200-599" + fields: + headers: + defaultMode: drop + names: + User-Agent: keep + X-Forwarded-For: keep + +entryPoints: + web: + address: ":80" + websecure: + address: ":443" + +serversTransport: + insecureSkipVerify: true + +ping: + entryPoint: "web" diff --git a/templates/NOTES.txt b/templates/NOTES.txt new file mode 100644 index 0000000..6c17516 --- /dev/null +++ b/templates/NOTES.txt @@ -0,0 +1,6 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $.Values.ingress.hostDomain }} +{{- end }} + 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 "xo-pangolin.fullname" . }}-gerbil-lb' diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl new file mode 100644 index 0000000..dc55267 --- /dev/null +++ b/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "xo-pangolin.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 "xo-pangolin.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 "xo-pangolin.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "xo-pangolin.labels" -}} +helm.sh/chart: {{ include "xo-pangolin.chart" . }} +{{ include "xo-pangolin.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "xo-pangolin.selectorLabels" -}} +app.kubernetes.io/name: {{ include "xo-pangolin.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "xo-pangolin.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "xo-pangolin.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/templates/gerbil-deployment.yaml b/templates/gerbil-deployment.yaml new file mode 100644 index 0000000..46ffa45 --- /dev/null +++ b/templates/gerbil-deployment.yaml @@ -0,0 +1,72 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "xo-pangolin.fullname" . }}-gerbil +spec: + selector: + matchLabels: + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: gerbil + replicas: 1 + template: + metadata: + labels: + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: gerbil + app: {{ include "xo-pangolin.fullname" . }}-gerbil + spec: + containers: + - name: gerbil + env: + - name: LOG_LEVEL + value: DEBUG + image: "{{ .Values.deployments.gerbil.image.repository }}:{{ .Values.deployments.gerbil.image.tag }}" + imagePullPolicy: "{{ .Values.deployments.gerbil.image.pullPolicy }}" + args: + - '--remoteConfig=http://{{ include "xo-pangolin.fullname" . }}-pangolin-cip.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain | default "cluster.local" }}:3001/api/v1/' + - '--reachableAt=http://{{ include "xo-pangolin.fullname" . }}-gerbil-cip.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain | default "cluster.local" }}:3004' + - '--generateAndSaveKeyTo=/var/config/key' + securityContext: + capabilities: + add: + - NET_ADMIN + + {{- $vols := required "values.volumes must be a non-empty list" .Values.volumes }} + {{- $first := required "values.volumes[0] is missing" (index $vols 0) }} + {{- $volName := required "values.volumes[0].name is required" $first.name }} + volumeMounts: + - mountPath: /var/config + name: {{ $volName }} + subPath: config + + - name: traefik + image: "{{ .Values.deployments.traefik.image.repository }}:{{ .Values.deployments.traefik.image.tag }}" + imagePullPolicy: "{{ .Values.deployments.traefik.image.pullPolicy }}" + args: + - '--configFile=/etc/traefik/traefik_config.yml' + + volumeMounts: + - mountPath: /var/certificates + name: {{ $volName }} + readOnly: true + subPath: pangolin-data + - mountPath: /var/dynamic + name: {{ $volName }} + readOnly: true + subPath: pangolin-data + - mountPath: /letsencrypt + name: {{ $volName }} + subPath: config/letsencrypt + - mountPath: /etc/traefik/traefik_config.yml + name: config-file + subPath: traefik_config.yml + readOnly: true + - mountPath: /etc/traefik/dynamic_config.yml + name: config-file + subPath: dynamic_config.yml + readOnly: true + volumes: + {{- toYaml $vols | nindent 8 }} + - name: config-file + secret: + secretName: {{ include "xo-pangolin.fullname" . }}-config diff --git a/templates/gerbil-service-cip.yaml b/templates/gerbil-service-cip.yaml new file mode 100644 index 0000000..196c2c5 --- /dev/null +++ b/templates/gerbil-service-cip.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "xo-pangolin.fullname" . }}-gerbil-cip +spec: + ports: + - name: api + port: 3004 + protocol: TCP + targetPort: 3004 + - name: http + port: 80 + protocol: TCP + targetPort: 80 + - name: https + port: 443 + protocol: TCP + targetPort: 443 + selector: + app: {{ include "xo-pangolin.fullname" . }}-gerbil + type: ClusterIP diff --git a/templates/gerbil-service-lb.yaml b/templates/gerbil-service-lb.yaml new file mode 100644 index 0000000..314638e --- /dev/null +++ b/templates/gerbil-service-lb.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "xo-pangolin.fullname" . }}-gerbil-lb +spec: + externalTrafficPolicy: Local + ports: + - name: wg0 + port: 51820 + protocol: UDP + targetPort: 51820 + - name: wg1 + port: 21820 + protocol: UDP + targetPort: 21820 + selector: + app: {{ include "xo-pangolin.fullname" . }}-gerbil + type: LoadBalancer diff --git a/templates/pangolin-deployment.yaml b/templates/pangolin-deployment.yaml new file mode 100644 index 0000000..b3a9123 --- /dev/null +++ b/templates/pangolin-deployment.yaml @@ -0,0 +1,49 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "xo-pangolin.fullname" . }}-pangolin +spec: + selector: + matchLabels: + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: pangolin + replicas: 1 + template: + metadata: + labels: + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: pangolin + app: {{ include "xo-pangolin.fullname" . }}-pangolin + spec: + containers: + - image: "{{ .Values.deployments.pangolin.image.repository }}:{{ .Values.deployments.pangolin.image.tag }}" + imagePullPolicy: "{{ .Values.deployments.pangolin.image.pullPolicy }}" + name: pangolin + readinessProbe: + failureThreshold: 15 + httpGet: + path: /api/v1/ + port: 3001 + scheme: HTTP + + {{- $vols := required "values.volumes must be a non-empty list" .Values.volumes }} + {{- $first := required "values.volumes[0] is missing" (index $vols 0) }} + {{- $volName := required "values.volumes[0].name is required" $first.name }} + volumeMounts: + - mountPath: /var/certificates + name: {{ $volName }} + subPath: pangolin-data + - mountPath: /var/dynamic + name: {{ $volName }} + subPath: pangolin-data + - mountPath: /app/config + name: {{ $volName }} + subPath: config + - mountPath: /app/config/config.yml + name: config-file + subPath: config.yml + volumes: + {{- toYaml $vols | nindent 8 }} + - name: config-file + secret: + secretName: {{ include "xo-pangolin.fullname" . }}-config diff --git a/templates/pangolin-ingress.yaml b/templates/pangolin-ingress.yaml new file mode 100644 index 0000000..3f82085 --- /dev/null +++ b/templates/pangolin-ingress.yaml @@ -0,0 +1,35 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "xo-pangolin.fullname" . }}-pangolin-in +spec: + rules: + - host: {{ .Values.ingress.hostDomain }} + http: + paths: + - backend: + service: + name: {{ include "xo-pangolin.fullname" . }}-gerbil-cip + port: + number: 80 + path: / + pathType: Prefix + - backend: + service: + name: {{ include "xo-pangolin.fullname" . }}-gerbil-cip + port: + number: 80 + path: /api/v1 + pathType: Prefix + - host: '*.{{ .Values.ingress.hostDomain }}' + http: + paths: + - backend: + service: + name: {{ include "xo-pangolin.fullname" . }}-gerbil-cip + port: + number: 80 + path: / + pathType: Prefix +{{- end }} diff --git a/templates/pangolin-service.yaml b/templates/pangolin-service.yaml new file mode 100644 index 0000000..03d6716 --- /dev/null +++ b/templates/pangolin-service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "xo-pangolin.fullname" . }}-pangolin-cip +spec: + ports: + - name: http-ext-api + port: 3000 + protocol: TCP + targetPort: 3000 + - name: http-int-api + port: 3001 + protocol: TCP + targetPort: 3001 + - name: http-ui + port: 3002 + protocol: TCP + targetPort: 3002 + selector: + app: {{ include "xo-pangolin.fullname" . }}-pangolin + type: ClusterIP diff --git a/templates/secret-pangolin-config.yaml b/templates/secret-pangolin-config.yaml new file mode 100644 index 0000000..6afc718 --- /dev/null +++ b/templates/secret-pangolin-config.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ include "xo-pangolin.fullname" . }}-config +stringData: + config.yml: | + {{- $file := required "files/config.yml missing" (.Files.Get "files/config.yml") -}} + {{- tpl $file . | nindent 4 }} + traefik_config.yml: | + {{- $file := required "files/traefik_config.yml missing" (.Files.Get "files/traefik_config.yml") -}} + {{- tpl $file . | nindent 4 }} + dynamic_config.yml: | + {{- $file := required "files/dynamic_config.yml missing" (.Files.Get "files/dynamic_config.yml") -}} + {{- tpl $file . | nindent 4 }} diff --git a/values.yaml b/values.yaml new file mode 100644 index 0000000..033d25b --- /dev/null +++ b/values.yaml @@ -0,0 +1,50 @@ +# Default values for xo-pangolin. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +settings: + pangolin: + serverSecretKey: 'SECRET_KEY_HERE' + traefik: + badgerVersion: "v1.2.0" + +deployments: + pangolin: + replicaCount: 1 + image: + repository: "fosrl/pangolin" + pullPolicy: IfNotPresent + tag: "1.11.0" + gerbil: + replicaCount: 1 + image: + repository: "fosrl/gerbil" + pullPolicy: IfNotPresent + tag: "1.2.2" + traefik: + replicaCount: 1 + image: + repository: "traefik" + pullPolicy: IfNotPresent + tag: "3.4.0" + +# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ +ingress: + hostDomain: pangolin.test + enabled: true + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +# Additional volumes on the output Deployment definition. +volumes: []