diff --git a/.github/workflows/publish-helm-chart.yml b/.github/workflows/publish-helm-chart.yml new file mode 100644 index 00000000..d3e9496a --- /dev/null +++ b/.github/workflows/publish-helm-chart.yml @@ -0,0 +1,70 @@ +name: Publish Helm Chart to S3 + +on: + push: + branches: + - master + paths: + - "deployments/helm-chart/**" + release: + types: [published] + + workflow_dispatch: + +# Required permissions for the workflow +permissions: + contents: read # for actions/checkout + +concurrency: + group: helm-publish + cancel-in-progress: false + +jobs: + publish: + runs-on: ubuntu-latest + + env: + AWS_REGION: ${{ secrets.AWS_REGION }} + AWS_ENDPOINT_URL_S3: ${{ secrets.AWS_ENDPOINT }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up Helm + uses: azure/setup-helm@v4 + + - name: Lint Helm chart + run: helm lint deployments/helm-chart + + - name: Install helm-s3 plugin + run: | + helm plugin install https://github.com/hypnoglow/helm-s3.git + + - name: Package Helm chart + run: | + if [[ "${{ github.event_name }}" == "release" ]]; then + RAW_TAG="${{ github.event.release.tag_name }}" + CHART_VERSION="${RAW_TAG#v}" + APP_VERSION="${RAW_TAG#v}" + else + CHART_VERSION="0.0.0-$(git rev-parse --short HEAD)" + APP_VERSION="latest" + fi + helm package deployments/helm-chart --version "${CHART_VERSION}" --app-version "${APP_VERSION}" + + - name: Initialize S3 repository + run: | + if ! helm repo list | grep s3-repo; then + helm repo add s3-repo s3://${{ secrets.AWS_BUCKET }}/charts + fi + + - name: Push Helm chart to S3 + run: | + helm s3 push server-*.tgz s3-repo + + - name: Clean up + run: | + rm -f server-*.tgz index.yaml diff --git a/deployments/helm-chart/Chart.yaml b/deployments/helm-chart/Chart.yaml new file mode 100644 index 00000000..1aab0b1e --- /dev/null +++ b/deployments/helm-chart/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: server +description: Helm chart for SMSGate Server +type: application +version: 0.1.0 +appVersion: "1.29.3" +keywords: + - sms + - gateway + - android + - messaging +home: https://sms-gate.app +sources: + - https://github.com/android-sms-gateway/server +maintainers: + - name: Aleksandr Soloshenko + email: support@sms-gate.app diff --git a/deployments/helm-chart/README.md b/deployments/helm-chart/README.md new file mode 100644 index 00000000..1091ffdc --- /dev/null +++ b/deployments/helm-chart/README.md @@ -0,0 +1,157 @@ +# SMSGate Server Helm Chart + +This Helm chart deploys the SMSGate Server to a Kubernetes cluster. The server acts as the backend component for the [SMSGate app](https://github.com/capcom6/android-sms-gateway), facilitating SMS messaging through connected Android devices. + +## Prerequisites + +- Kubernetes 1.23+ +- Helm 3.2.0+ +- PV provisioner support in the underlying infrastructure (if using persistent storage for database) + +## Installation + +To install the chart with the release name `my-release`: + +```bash +helm repo add sms-gate https://s3.sms-gate.app/charts +helm repo update +helm install my-release sms-gate/server +``` + +## Uninstallation + +To uninstall/delete the `my-release` deployment: + +```bash +helm delete my-release +``` + +## Configuration + +The following table lists the configurable parameters of the SMSGate chart and their default values. + +| Parameter | Description | Default | +| ----------------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------- | +| `replicaCount` | Number of replicas | `1` | +| `image.repository` | Container image repository | `ghcr.io/android-sms-gateway/server` | +| `image.tag` | Container image tag | `latest` | +| `image.pullPolicy` | Container image pull policy | `IfNotPresent` | +| `service.type` | Kubernetes service type | `ClusterIP` | +| `service.port` | Service port | `3000` | +| `ingress.enabled` | Enable ingress | `false` | +| `ingress.className` | Ingress class name | `""` | +| `ingress.hosts` | Ingress hosts configuration | `[{host: sms-gateway.local, paths: [{path: /, pathType: ImplementationSpecific}]}]` | +| `resources` | Resource requests/limits | `{requests: {cpu: 100m, memory: 128Mi}, limits: {cpu: 500m, memory: 512Mi}}` | +| `autoscaling.enabled` | Enable autoscaling | `false` | +| `autoscaling.minReplicas` | Minimum replicas for autoscaling | `1` | +| `autoscaling.maxReplicas` | Maximum replicas for autoscaling | `5` | +| `autoscaling.targetCPUUtilizationPercentage` | Target CPU utilization percentage | `80` | +| `autoscaling.targetMemoryUtilizationPercentage` | Target memory utilization percentage | `80` | +| `database.host` | Database host | `db` | +| `database.port` | Database port | `3306` | +| `database.user` | Database user | `sms` | +| `database.password` | Database password | `""` | +| `database.name` | Database name | `sms` | +| `database.deployInternal` | Deploy internal MariaDB | `true` | +| `gateway.privateToken` | Private token for device registration | `""` | +| `gateway.fcmCredentials` | Firebase Cloud Messaging credentials | `""` | +| `gateway.config.enabled` | Enable config file mounting | `false` | +| `gateway.config.existingConfigMap` | Existing ConfigMap to use | `""` | +| `gateway.config.data` | Inline YAML config content | `""` | +| `env` | Additional environment variables | `{}` | + +## Custom Configuration + +### Using an External Database + +To use an external database instead of the built-in MariaDB: + +```yaml +database: + deployInternal: false + host: external-db-host + port: 3306 + user: external-user + password: "secure-password" + name: external-db +``` + +### Configuring Ingress + +To enable ingress with TLS: + +```yaml +ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: sms-gateway.example.com + paths: + - path: / + pathType: Prefix + tls: + - hosts: + - sms-gateway.example.com + secretName: sms-gateway-tls +``` + +### Setting Resource Limits + +To adjust resource requests and limits: + +```yaml +resources: + limits: + cpu: 1000m + memory: 1Gi + requests: + cpu: 200m + memory: 256Mi +``` + +### Configuring Config File Mounting + +#### Using an Existing ConfigMap + +To use an existing ConfigMap for configuration: + +```yaml +gateway: + config: + enabled: true + existingConfigMap: my-existing-configmap +``` + +#### Using Inline YAML Configuration + +To provide configuration using inline YAML: + +```yaml +gateway: + config: + enabled: true + data: | + tasks: + hashing: + interval_seconds: 900 +``` + +## Notes + +- The application health endpoint is available at `/health` +- When using private mode, you must set `gateway.privateToken` +- For production use, always set secure passwords and enable TLS +- The chart supports persistent storage for the internal MariaDB database +- The config file mounting feature is optional and can be enabled by setting `gateway.config.enabled` to `true` +- When using config file mounting, you can either specify an existing ConfigMap with `gateway.config.existingConfigMap` or provide inline YAML configuration with `gateway.config.data` +- Environment variables remain an alternative configuration method and can be set using the `env` parameter + +## License + +This Helm chart is licensed under the Apache-2.0 license. See [LICENSE](LICENSE) for more information. + +## Legal Notice + +Android is a trademark of Google LLC. \ No newline at end of file diff --git a/deployments/helm-chart/templates/NOTES.txt b/deployments/helm-chart/templates/NOTES.txt new file mode 100644 index 00000000..83c1fb44 --- /dev/null +++ b/deployments/helm-chart/templates/NOTES.txt @@ -0,0 +1,33 @@ +SMSGate Server has been deployed! + +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range .Values.ingress.hosts }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}{{ if .paths }}{{ index .paths 0 "path" }}{{ else }}/{{ end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "sms-gateway.fullname" . }}) + export NODE_IP=$(kubectl get nodes -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 the status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "sms-gateway.fullname" . }}' + export SERVICE_HOST=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "sms-gateway.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}{.status.loadBalancer.ingress[0].hostname}') + echo http://$SERVICE_HOST:{{ .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 "sms-gateway.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 "Visit http://127.0.0.1:8080 to use your application" + kubectl port-forward --namespace {{ .Release.Namespace }} $POD_NAME 8080:$CONTAINER_PORT +{{- end }} + +2. Check the application health: + curl http://
/health + +3. Database information: +{{- if .Values.database.deployInternal }} + Internal MariaDB deployed at: {{ include "sms-gateway.fullname" . }}-db:3306 + Root password: (stored in secret {{ include "sms-gateway.fullname" . }}-db-secrets) +{{- else }} + Using external database at: {{ .Values.database.host }}:{{ .Values.database.port }} +{{- end }} \ No newline at end of file diff --git a/deployments/helm-chart/templates/_helpers.tpl b/deployments/helm-chart/templates/_helpers.tpl new file mode 100644 index 00000000..03bfcff8 --- /dev/null +++ b/deployments/helm-chart/templates/_helpers.tpl @@ -0,0 +1,42 @@ +{{- define "sms-gateway.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 }} + +{{- define "sms-gateway.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "sms-gateway.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "sms-gateway.selectorLabels" -}} +app.kubernetes.io/name: {{ include "sms-gateway.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{- define "sms-gateway.labels" -}} +helm.sh/chart: {{ include "sms-gateway.chart" . }} +{{ include "sms-gateway.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{- define "sms-gateway.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "sms-gateway.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/deployments/helm-chart/templates/configmap.yaml b/deployments/helm-chart/templates/configmap.yaml new file mode 100644 index 00000000..ea392fe7 --- /dev/null +++ b/deployments/helm-chart/templates/configmap.yaml @@ -0,0 +1,11 @@ +{{- if and .Values.gateway.config.enabled (not .Values.gateway.config.existingConfigMap) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "sms-gateway.fullname" . }}-config + labels: + {{- include "sms-gateway.labels" . | nindent 4 }} +data: + config.yml: | + {{- .Values.gateway.config.data | nindent 4 }} +{{- end }} \ No newline at end of file diff --git a/deployments/helm-chart/templates/database.yaml b/deployments/helm-chart/templates/database.yaml new file mode 100644 index 00000000..7a6af9df --- /dev/null +++ b/deployments/helm-chart/templates/database.yaml @@ -0,0 +1,114 @@ +{{- if .Values.database.deployInternal }} +# MariaDB StatefulSet +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "sms-gateway.fullname" . }}-db + labels: + {{- include "sms-gateway.labels" . | nindent 4 }} +spec: + serviceName: {{ include "sms-gateway.fullname" . }}-db + replicas: 1 + selector: + matchLabels: + {{- include "sms-gateway.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: database + template: + metadata: + labels: + {{- include "sms-gateway.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: database + spec: + containers: + - name: mariadb + image: {{ .Values.database.mariadb.image.repository }}:{{ .Values.database.mariadb.image.tag }} + env: + - name: MARIADB_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "sms-gateway.fullname" . }}-db-secrets + key: root-password + - name: MARIADB_DATABASE + value: {{ .Values.database.name }} + - name: MARIADB_USER + value: {{ .Values.database.user }} + - name: MARIADB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "sms-gateway.fullname" . }}-db-secrets + key: database-password + ports: + - name: mysql + containerPort: 3306 + volumeMounts: + - name: mariadb-data + mountPath: /var/lib/mysql + readinessProbe: + initialDelaySeconds: 30 + periodSeconds: 10 + exec: + command: + - healthcheck.sh + - --connect + - --innodb_initialized + livenessProbe: + initialDelaySeconds: 60 + periodSeconds: 30 + exec: + command: + - healthcheck.sh + - --connect + - --innodb_initialized + volumes: + - name: mariadb-data + persistentVolumeClaim: + claimName: {{ include "sms-gateway.fullname" . }}-db-pvc + +--- +# Database Service +apiVersion: v1 +kind: Service +metadata: + name: {{ include "sms-gateway.fullname" . }}-db + labels: + {{- include "sms-gateway.labels" . | nindent 4 }} +spec: + ports: + - port: 3306 + targetPort: mysql + name: mysql + selector: + {{- include "sms-gateway.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: database + +--- +# Database Secrets +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "sms-gateway.fullname" . }}-db-secrets + labels: + {{- include "sms-gateway.labels" . | nindent 4 }} +type: Opaque +data: + root-password: {{ .Values.database.mariadb.rootPassword | b64enc | quote }} + database-password: {{ .Values.database.password | b64enc | quote }} + +--- +# Database Persistent Volume Claim +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "sms-gateway.fullname" . }}-db-pvc + labels: + {{- include "sms-gateway.labels" . | nindent 4 }} +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.database.mariadb.persistence.size }} + {{- if .Values.database.mariadb.persistence.storageClass }} + storageClassName: {{ .Values.database.mariadb.persistence.storageClass }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/deployments/helm-chart/templates/deployment.yaml b/deployments/helm-chart/templates/deployment.yaml new file mode 100644 index 00000000..0141c937 --- /dev/null +++ b/deployments/helm-chart/templates/deployment.yaml @@ -0,0 +1,127 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "sms-gateway.fullname" . }} + labels: + {{- include "sms-gateway.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "sms-gateway.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "sms-gateway.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: backend + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "sms-gateway.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- if .Values.gateway.config.enabled }} + volumes: + - name: config-volume + {{- if .Values.gateway.config.existingConfigMap }} + configMap: + name: {{ .Values.gateway.config.existingConfigMap }} + {{- else }} + configMap: + name: {{ include "sms-gateway.fullname" . }}-config + {{- end }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + env: + - name: CONFIG_PATH + value: "/app/config.yml" + - name: HTTP__LISTEN + value: "0.0.0.0:{{ .Values.service.port }}" + {{- if .Values.database.deployInternal }} + - name: DATABASE__HOST + value: {{ include "sms-gateway.fullname" . }}-db + {{- else }} + - name: DATABASE__HOST + value: {{ .Values.database.host }} + {{- end }} + - name: DATABASE__NAME + value: {{ .Values.database.name }} + - name: DATABASE__USER + value: {{ .Values.database.user }} + - name: DATABASE__PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "sms-gateway.fullname" . }}-secrets + key: database-password + {{- if .Values.gateway.privateToken }} + - name: GATEWAY__MODE + value: "private" + - name: GATEWAY__PRIVATE_TOKEN + valueFrom: + secretKeyRef: + name: {{ include "sms-gateway.fullname" . }}-secrets + key: private-token + {{- else if .Values.gateway.fcmCredentials }} + - name: GATEWAY__MODE + value: "public" + - name: FCM__CREDENTIALS_JSON + valueFrom: + secretKeyRef: + name: {{ include "sms-gateway.fullname" . }}-secrets + key: fcm-credentials + {{- end }} + {{- range $key, $value := .Values.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + livenessProbe: + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + httpGet: + path: /health + port: http + readinessProbe: + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + httpGet: + path: /health + port: http + {{- if .Values.gateway.config.enabled }} + volumeMounts: + - name: config-volume + mountPath: "/app/config.yml" + subPath: config.yml + {{- 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/deployments/helm-chart/templates/hpa.yaml b/deployments/helm-chart/templates/hpa.yaml new file mode 100644 index 00000000..eef59fbc --- /dev/null +++ b/deployments/helm-chart/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "sms-gateway.fullname" . }} + labels: + {{- include "sms-gateway.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "sms-gateway.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} +{{- end }} \ No newline at end of file diff --git a/deployments/helm-chart/templates/ingress.yaml b/deployments/helm-chart/templates/ingress.yaml new file mode 100644 index 00000000..4afa4517 --- /dev/null +++ b/deployments/helm-chart/templates/ingress.yaml @@ -0,0 +1,41 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "sms-gateway.fullname" . }} + labels: + {{- include "sms-gateway.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "sms-gateway.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/deployments/helm-chart/templates/secrets.yaml b/deployments/helm-chart/templates/secrets.yaml new file mode 100644 index 00000000..2f716f4c --- /dev/null +++ b/deployments/helm-chart/templates/secrets.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "sms-gateway.fullname" . }}-secrets + labels: + {{- include "sms-gateway.labels" . | nindent 4 }} +type: Opaque +data: + database-password: {{ .Values.database.password | b64enc | quote }} + {{- if .Values.gateway.privateToken }} + private-token: {{ .Values.gateway.privateToken | b64enc | quote }} + {{- end }} + {{- if .Values.gateway.fcmCredentials }} + fcm-credentials: {{ .Values.gateway.fcmCredentials | b64enc | quote }} + {{- end }} \ No newline at end of file diff --git a/deployments/helm-chart/templates/service.yaml b/deployments/helm-chart/templates/service.yaml new file mode 100644 index 00000000..9169e1c5 --- /dev/null +++ b/deployments/helm-chart/templates/service.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "sms-gateway.fullname" . }} + labels: + {{- include "sms-gateway.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "sms-gateway.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: backend \ No newline at end of file diff --git a/deployments/helm-chart/templates/serviceaccount.yaml b/deployments/helm-chart/templates/serviceaccount.yaml new file mode 100644 index 00000000..6fe6fec0 --- /dev/null +++ b/deployments/helm-chart/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "sms-gateway.serviceAccountName" . }} + labels: + {{- include "sms-gateway.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/deployments/helm-chart/templates/tests/test-connection.yaml b/deployments/helm-chart/templates/tests/test-connection.yaml new file mode 100644 index 00000000..9069bc9b --- /dev/null +++ b/deployments/helm-chart/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "sms-gateway.fullname" . }}-test-connection" + labels: + {{- include "sms-gateway.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + containers: + - name: curl + image: curlimages/curl:latest + command: ['sh', '-c', 'curl --fail http://{{ include "sms-gateway.fullname" . }}:{{ .Values.service.port }}/health'] + restartPolicy: Never \ No newline at end of file diff --git a/deployments/helm-chart/values.yaml b/deployments/helm-chart/values.yaml new file mode 100644 index 00000000..1bffd06b --- /dev/null +++ b/deployments/helm-chart/values.yaml @@ -0,0 +1,106 @@ +# Default values for sms-gateway +# This is a YAML-formatted file +replicaCount: 1 + +image: + repository: ghcr.io/android-sms-gateway/server + tag: "1.29.3" + pullPolicy: IfNotPresent + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + annotations: {} + name: "" + +podAnnotations: {} +podSecurityContext: {} +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 3000 + annotations: {} + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: sms-gateway.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: sms-gateway-tls + # hosts: + # - sms-gateway.local + +resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 5 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} +tolerations: [] +affinity: {} + +# Database configuration +database: + host: "" + port: 3306 + user: sms + password: "" # REQUIRED: Set a strong password for the database user + name: sms + # Set to true to deploy a MariaDB instance with the chart + deployInternal: true + mariadb: + image: + repository: mariadb + tag: lts + rootPassword: "" # REQUIRED: Set a strong root password for MariaDB + persistence: + enabled: true + size: 8Gi + storageClass: "" + +# Gateway configuration +gateway: + privateToken: "" # REQUIRED for private mode: Set your gateway private token + fcmCredentials: "" # REQUIRED for public mode: Set your FCM credentials JSON + config: + enabled: false + existingConfigMap: "" # Name of existing ConfigMap to use (optional) + data: | + # Config content here (only used if existingConfigMap is not provided) + # This will be used to generate a ConfigMap when existingConfigMap is not set + # Example: + # tasks: + # hashing: + # interval_seconds: 900 + +# Environment variables +env: {} + # DEBUG: "" + # HTTP__LISTEN: "0.0.0.0:3000" diff --git a/scripts/test-helm-minikube.sh b/scripts/test-helm-minikube.sh new file mode 100755 index 00000000..e5cfcba5 --- /dev/null +++ b/scripts/test-helm-minikube.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +# Test Helm Chart with Minikube +# This script automates the process of testing the SMS Gateway Helm chart in a Minikube environment + +# Exit on error +set -e + +# Check for required commands +for cmd in minikube helm kubectl docker jq; do + if ! command -v $cmd &> /dev/null; then + echo "Error: $cmd command not found. Please install it first." + exit 1 + fi +done + +# Start Minikube if not running +if ! minikube status &> /dev/null; then + echo "Starting Minikube..." + minikube start --cpus=2 --memory=4096 --driver=docker +else + echo "Minikube is already running" +fi + +# Set Minikube Docker environment +echo "Setting up Minikube Docker environment..." +eval "$(minikube docker-env)" + +# Create namespace for testing +NAMESPACE="sms-gateway-test" +kubectl create namespace $NAMESPACE || true + +# Install the Helm chart +echo "Installing Helm chart..." +helm upgrade --install sms-gateway-test ./deployments/helm-chart \ + --namespace $NAMESPACE \ + --set image.pullPolicy=IfNotPresent \ + --set database.deployInternal=true \ + --set database.mariadb.rootPassword=root \ + --set database.password=sms \ + --set gateway.privateToken=test-token + +# Wait for pods to be ready +echo "Waiting for pods to be ready..." +kubectl wait --namespace $NAMESPACE \ + --for condition=Available \ + deployment/sms-gateway-test-server \ + --timeout=120s + +# Port forward to access the service +echo "Port forwarding to service (http://localhost:8080)..." +kubectl port-forward --namespace $NAMESPACE service/sms-gateway-test-server 8080:3000 & +PORT_FORWARD_PID=$! + +# Wait for port-forward to be ready +echo "Waiting for port-forward to be ready..." +for i in {1..30}; do + if curl -s http://localhost:8080/health > /dev/null 2>&1; then + echo "Port-forward is ready!" + break + fi + if [ $i -eq 30 ]; then + echo "Error: Port-forward failed to establish" + kill $PORT_FORWARD_PID + exit 1 + fi + sleep 1 +done + +# Test the health endpoint +echo "Testing health endpoint..." +curl -s http://localhost:8080/health | jq . || echo "Health check failed" + +# Run Helm tests +echo "Running Helm tests..." +helm test sms-gateway-test --namespace $NAMESPACE + +# Cleanup +echo "Cleaning up..." +kill $PORT_FORWARD_PID +kubectl delete namespace $NAMESPACE + +echo "Note: Minikube cluster is still running. Run 'minikube delete' to remove it." + +echo -e "\n\033[32mTest completed successfully!\033[0m" +echo "You can inspect the test results above." \ No newline at end of file