diff --git a/.gitignore b/.gitignore
index 8891a815a..02bb93207 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,3 +48,7 @@ build
# BoltDB default database file
.jackal.db
+# Helm
+charts/
+requirements.lock
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f0058e36..32e97869d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,10 +2,6 @@
## jackal - main / unreleased
-## 0.59.0 (2022/03/26)
-
-* [FEATURE] Improve k8s compatibility. [#219](https://github.com/ortuman/jackal/pull/219), [#220](https://github.com/ortuman/jackal/pull/220),
-
## 0.58.0 (2022/03/04)
* [FEATURE] Added BoltDB repository type. [#212](https://github.com/ortuman/jackal/pull/212)
diff --git a/README.md b/README.md
index bde1055e1..7dc4f772c 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,28 @@ or environment variable:
$ env JACKAL_CONFIG_FILE=/your-custom-path/your-config.yaml jackal
```
+### Helm chart
+
+To make it easy to install jackal via Helm in Kubernetes a chart has been included into this repository.
+
+After customizing your own [values.yaml](helm/values.yaml) file run the following command to install and configure all required components under `jackal` namespace.
+
+```sh
+sh ./helm/scripts/install .yaml
+```
+
+In turn, an active chart can be updated by running the upgrade script as follows:
+
+```sh
+sh ./helm/scripts/upgrade .yaml
+```
+
+On the other hand, you can also remove the jackal chart from your Kubernetes cluster by running the uninstall script:
+
+```sh
+sh ./helm/scripts/uninstall
+```
+
### PostgreSQL database creation
Create a user and a database for that user:
diff --git a/config/example.config.yaml b/config/example.config.yaml
index 523ff211e..3e02cc491 100644
--- a/config/example.config.yaml
+++ b/config/example.config.yaml
@@ -29,7 +29,7 @@
# pgsql:
# host: 127.0.0.1:5432
# user: jackal
-# password: password
+# password: a-secret-key
# database: jackal
# max_open_conns: 16
#
@@ -44,11 +44,12 @@
# kv:
# type: etcd
# etcd:
+# username: root
# endpoints:
# - http://127.0.0.1:2379
-#
-# server:
-# port: 14369
+
+ server:
+ port: 14369
shapers:
- name: super
diff --git a/helm/Chart.yaml b/helm/Chart.yaml
new file mode 100644
index 000000000..940ce6575
--- /dev/null
+++ b/helm/Chart.yaml
@@ -0,0 +1,26 @@
+apiVersion: "v1"
+name: jackal
+version: 1.0.0
+appVersion: v0.59.0
+kubeVersion: "^1.10.0-0"
+description: "Instant messaging server for the Extensible Messaging and Presence Protocol (XMPP)."
+home: https://github.com/ortuman/jackal
+icon: https://raw.githubusercontent.com/ortuman/jackal/main/logos/logo-0.png
+sources:
+ - https://github.com/ortuman/jackal
+keywords:
+ - jackal
+ - xmpp
+ - chat
+ - asynchronous
+ - messaging
+maintainers:
+ - name: Jackal Maintainers
+ email: ortuman@gmail.com
+dependencies:
+ - name: etcd
+ version: 7.0.2
+ repository: https://charts.bitnami.com/bitnami
+ - name: postgresql-ha
+ version: 8.6.13
+ repository: https://charts.bitnami.com/bitnami
diff --git a/helm/scripts/install.sh b/helm/scripts/install.sh
new file mode 100644
index 000000000..7b3ba59f5
--- /dev/null
+++ b/helm/scripts/install.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+set -eufo pipefail
+
+command -v helm >/dev/null 2>&1 || { echo "helm not installed, aborting." >&2; exit 1; }
+
+if [ "$#" -eq 0 ] || [ -z "$1" ]; then
+ echo "A custom values.yaml file must be provided"
+ exit 1;
+fi
+
+helm install jackal helm/ --dependency-update --create-namespace --namespace=jackal -f "$1"
+
diff --git a/helm/scripts/uninstall.sh b/helm/scripts/uninstall.sh
new file mode 100644
index 000000000..2cbd541ee
--- /dev/null
+++ b/helm/scripts/uninstall.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+set -eufo pipefail
+
+command -v helm >/dev/null 2>&1 || { echo "helm not installed, aborting." >&2; exit 1; }
+
+helm uninstall jackal --namespace=jackal
diff --git a/helm/scripts/upgrade.sh b/helm/scripts/upgrade.sh
new file mode 100644
index 000000000..527cb9670
--- /dev/null
+++ b/helm/scripts/upgrade.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+set -eufo pipefail
+
+command -v kubectl >/dev/null 2>&1 || { echo "kubectl not installed, aborting." >&2; exit 1; }
+command -v helm >/dev/null 2>&1 || { echo "helm not installed, aborting." >&2; exit 1; }
+
+if [ $# -eq 0 ] || [ -z $1 ]; then
+ echo "A custom values.yaml file must be provided"
+ exit 1;
+fi
+
+export POSTGRESQL_PASSWORD=$(kubectl get secret --namespace "jackal" jackal-postgresql-ha-postgresql -o jsonpath="{.data.postgresql-password}" | base64 --decode)
+export REPMGR_PASSWORD=$(kubectl get secret --namespace "jackal" jackal-postgresql-ha-postgresql -o jsonpath="{.data.repmgr-password}" | base64 --decode)
+export ADMIN_PASSWORD=$(kubectl get secret --namespace "jackal" jackal-postgresql-ha-pgpool -o jsonpath="{.data.admin-password}" | base64 --decode)
+
+helm upgrade jackal helm/ --dependency-update \
+--set postgresql-ha.postgresql.password=$POSTGRESQL_PASSWORD \
+--set postgresql-ha.postgresql.repmgrPassword=$REPMGR_PASSWORD \
+--set postgresql-ha.pgpool.adminPassword=$ADMIN_PASSWORD \
+--namespace=jackal \
+-f "$1"
diff --git a/helm/sql/postgres.up.psql b/helm/sql/postgres.up.psql
new file mode 100644
index 000000000..3be5cf8da
--- /dev/null
+++ b/helm/sql/postgres.up.psql
@@ -0,0 +1,172 @@
+/*
+ Copyright 2022 The jackal Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+-- Functions to manage updated_at timestamps
+
+CREATE OR REPLACE FUNCTION enable_updated_at(_tbl regclass) RETURNS VOID AS $$
+BEGIN
+ EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
+ FOR EACH ROW EXECUTE PROCEDURE set_updated_at()', _tbl);
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger AS $$
+BEGIN
+ IF (
+ NEW IS DISTINCT FROM OLD AND
+ NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
+ ) THEN
+ NEW.updated_at := current_timestamp;
+ END IF;
+ RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+-- users
+
+CREATE TABLE IF NOT EXISTS users (
+ username VARCHAR(1023) PRIMARY KEY,
+ h_sha_1 TEXT NOT NULL,
+ h_sha_256 TEXT NOT NULL,
+ h_sha_512 TEXT NOT NULL,
+ h_sha3_512 TEXT NOT NULL,
+ salt TEXT NOT NULL,
+ iteration_count INT NOT NULL,
+ pepper_id VARCHAR(1023) NOT NULL,
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
+);
+
+SELECT enable_updated_at('users');
+
+-- last
+
+CREATE TABLE IF NOT EXISTS last (
+ username VARCHAR(1023) PRIMARY KEY,
+ status TEXT NOT NULl,
+ seconds BIGINT NOT NULL,
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
+);
+
+SELECT enable_updated_at('last');
+
+-- capabilities
+
+CREATE TABLE IF NOT EXISTS capabilities (
+ node VARCHAR(1023) NOT NULL,
+ ver VARCHAR(1023) NOT NULL,
+ features TEXT ARRAY,
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+
+ PRIMARY KEY (node, ver)
+);
+
+SELECT enable_updated_at('capabilities');
+
+-- offline_messages
+
+CREATE TABLE IF NOT EXISTS offline_messages (
+ id SERIAL PRIMARY KEY,
+ username VARCHAR(1023) NOT NULL,
+ message BYTEA NOT NULL,
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX IF NOT EXISTS i_offline_messages_username ON offline_messages(username);
+
+-- blocklist_items
+
+CREATE TABLE IF NOT EXISTS blocklist_items (
+ username VARCHAR(1023) NOT NULL,
+ jid TEXT NOT NULL,
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+
+ PRIMARY KEY(username, jid)
+);
+
+SELECT enable_updated_at('blocklist_items');
+
+-- private_storage
+
+CREATE TABLE IF NOT EXISTS private_storage (
+ username VARCHAR(1023) NOT NULL,
+ namespace VARCHAR(512) NOT NULL,
+ data BYTEA NOT NULL,
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+
+ PRIMARY KEY (username, namespace)
+);
+
+SELECT enable_updated_at('private_storage');
+
+-- roster_notifications
+
+CREATE TABLE IF NOT EXISTS roster_notifications (
+ contact VARCHAR(1023) NOT NULL,
+ jid TEXT NOT NULL,
+ presence BYTEA NOT NULL,
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+
+ PRIMARY KEY (contact, jid)
+);
+
+SELECT enable_updated_at('roster_notifications');
+
+-- roster_items
+
+CREATE TABLE IF NOT EXISTS roster_items (
+ username VARCHAR(1023) NOT NULL,
+ jid TEXT NOT NULL,
+ name TEXT NOT NULL,
+ subscription TEXT NOT NULL,
+ groups TEXT ARRAY,
+ ask BOOL NOT NULL,
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+
+ PRIMARY KEY (username, jid)
+);
+
+SELECT enable_updated_at('roster_items');
+
+-- roster_versions
+
+CREATE TABLE IF NOT EXISTS roster_versions (
+ username VARCHAR(1023) NOT NULL,
+ ver INT NOT NULL DEFAULT 1,
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+
+ PRIMARY KEY (username)
+);
+
+SELECT enable_updated_at('roster_versions');
+
+-- vcards
+
+CREATE TABLE IF NOT EXISTS vcards (
+ username VARCHAR(1023) PRIMARY KEY,
+ vcard BYTEA NOT NULL,
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
+);
+
+SELECT enable_updated_at('vcards');
diff --git a/helm/templates/_config-render.tpl b/helm/templates/_config-render.tpl
new file mode 100644
index 000000000..f76657636
--- /dev/null
+++ b/helm/templates/_config-render.tpl
@@ -0,0 +1,72 @@
+###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~###
+### jackal configuration file ###
+###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~###
+
+logger:
+ level: {{ .Values.jackal.config.logger.level }}
+
+http:
+ port: {{ .Values.jackal.config.http.port }}
+
+admin:
+ port: {{ .Values.jackal.config.admin.port }}
+
+{{ if .Values.jackal.config.domains }}
+hosts:
+{{ toYaml .Values.jackal.config.domains | indent 6 }}
+{{ end }}
+
+{{ if .Values.jackal.config.peppers }}
+peppers:
+{{ toYaml .Values.jackal.config.peppers | indent 6 }}
+{{ end }}
+
+storage:
+ type: pgsql
+ pgsql:
+ host: jackal-postgresql-ha-pgpool.{{ .Release.Namespace }}.svc.cluster.local:5432
+ user: jackal
+ database: jackal
+ max_open_conns: {{ .Values.jackal.config.storage.maxConns }}
+ max_idle_conns: {{ .Values.jackal.config.storage.maxIdleConns }}
+ conn_max_lifetime: {{ .Values.jackal.config.storage.connMaxLifetime }}
+ conn_max_idle_time: {{ .Values.jackal.config.storage.connMaxIdleTime }}
+
+{{ if .Values.redis.enabled }}
+ cache:
+ type: redis
+ redis:
+ srv: _redis._tcp.redis-headless.{{ .Release.Namespace }}.svc.cluster.local
+{{ end }}
+
+cluster:
+ type: kv
+ kv:
+ type: etcd
+ etcd:
+ endpoints:
+ - http://jackal-etcd.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.etcd.containerPorts.client }}
+
+ server:
+ port: {{ .Values.jackal.config.cluster.server.port }}
+
+{{ if .Values.jackal.config.shapers }}
+shapers:
+{{ toYaml .Values.jackal.config.shapers | indent 2 }}
+{{ end }}
+
+c2s:
+{{ toYaml .Values.jackal.config.c2s | indent 2 }}
+
+s2s:
+{{ toYaml .Values.jackal.config.s2s | indent 2 }}
+
+{{ if .Values.jackal.config.modules }}
+modules:
+{{ toYaml .Values.jackal.config.modules | indent 2 }}
+{{ end }}
+
+{{ if .Values.jackal.config.components }}
+components:
+{{ toYaml .Values.jackal.config.components | indent 2 }}
+{{ end }}
diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl
new file mode 100644
index 000000000..915717edc
--- /dev/null
+++ b/helm/templates/_helpers.tpl
@@ -0,0 +1,6 @@
+{{/*
+Calculate the config from structured and unstructred text input
+*/}}
+{{- define "jackal.calculatedConfig" -}}
+{{ include (print $.Template.BasePath "/_config-render.tpl") . }}
+{{- end -}}
diff --git a/helm/templates/clusterrolebinding-default-view.yaml b/helm/templates/clusterrolebinding-default-view.yaml
new file mode 100644
index 000000000..a47bb1f01
--- /dev/null
+++ b/helm/templates/clusterrolebinding-default-view.yaml
@@ -0,0 +1,13 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: jackal-view
+ namespace: {{ .Release.Namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: view
+subjects:
+ - kind: ServiceAccount
+ name: default
+ namespace: {{ .Release.Namespace }}
diff --git a/helm/templates/configmap-jackal.yaml b/helm/templates/configmap-jackal.yaml
new file mode 100644
index 000000000..ae0838a0b
--- /dev/null
+++ b/helm/templates/configmap-jackal.yaml
@@ -0,0 +1,8 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: jackal-config
+ namespace: {{ .Release.Namespace }}
+data:
+ config.yaml: |-
+{{ include "jackal.calculatedConfig" . | indent 4 }}
diff --git a/helm/templates/configmap-pgsql-init-script.yaml b/helm/templates/configmap-pgsql-init-script.yaml
new file mode 100644
index 000000000..9288a0676
--- /dev/null
+++ b/helm/templates/configmap-pgsql-init-script.yaml
@@ -0,0 +1,8 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: pgsql-init-script
+ namespace: {{ .Release.Namespace }}
+data:
+ pgsql.up.sql: |-
+{{ .Files.Get "sql/postgres.up.psql" | indent 4 }}
diff --git a/helm/templates/deployment-jackal.yaml b/helm/templates/deployment-jackal.yaml
new file mode 100644
index 000000000..537f320eb
--- /dev/null
+++ b/helm/templates/deployment-jackal.yaml
@@ -0,0 +1,146 @@
+{{- $replicasCount := int .Values.jackal.replicasCount -}}
+{{- $baseNodePort := 30000 -}}
+{{- range $i := until $replicasCount }}
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: jackal-{{ $i }}
+ namespace: {{ $.Release.Namespace }}
+ labels:
+ app: jackal-{{ $i }}
+spec:
+ type: NodePort
+ selector:
+ app: jackal-{{ $i }}
+ ports:
+ {{- range $j, $ln := $.Values.jackal.config.c2s.listeners }}
+ - port: {{ $ln.port }}
+ name: c2s-{{ $j }}
+ targetPort: {{ $ln.port }}
+ nodePort: {{ $baseNodePort }}
+ {{- $baseNodePort = add1 $baseNodePort -}}
+ {{- end }}
+ {{- range $j, $ln := $.Values.jackal.config.s2s.listeners }}
+ - port: {{ $ln.port }}
+ name: s2s-{{ $j }}
+ targetPort: {{ $ln.port }}
+ nodePort: {{ $baseNodePort }}
+ {{- $baseNodePort = add1 $baseNodePort -}}
+ {{- end }}
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: jackal-{{ $i }}
+ namespace: {{ $.Release.Namespace }}
+ labels:
+ app: jackal-{{ $i }}
+ heritage: {{ $.Release.Service }}
+ release: {{ $.Release.Name }}
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: jackal-{{ $i }}
+ strategy: {}
+ template:
+ metadata:
+ annotations:
+ rollme: {{ randAlphaNum 5 | quote }}
+ labels:
+ app: jackal-{{ $i }}
+ component: jackal
+ spec:
+ nodeSelector:
+ {{- toYaml $.Values.jackal.nodeSelector | nindent 8 }}
+ affinity:
+ {{- toYaml $.Values.jackal.affinity | nindent 8 }}
+ tolerations:
+ {{- toYaml $.Values.jackal.tolerations | nindent 8 }}
+ initContainers:
+ - name: wait-for-etcd
+ image: groundnuty/k8s-wait-for:v1.3
+ imagePullPolicy: Always
+ args:
+ - "pod"
+ - "-lapp.kubernetes.io/name=etcd"
+
+ - name: wait-for-pgpool
+ image: groundnuty/k8s-wait-for:v1.3
+ imagePullPolicy: Always
+ args:
+ - "pod"
+ - "-lapp.kubernetes.io/name=postgresql-ha"
+
+ - name: wait-for-redis
+ image: groundnuty/k8s-wait-for:v1.3
+ imagePullPolicy: Always
+ args:
+ - "pod"
+ - "-lapp=redis"
+
+ containers:
+ - name: jackal-{{ $i }}
+ args:
+ - ./jackal
+ - -config=/etc/jackal/config.yaml
+ image: {{ $.Values.jackal.image.repository }}:{{ $.Values.jackal.image.tag }}
+ imagePullPolicy: {{ $.Values.jackal.image.pullPolicy }}
+ ports:
+ {{- range $.Values.jackal.config.c2s.listeners }}
+ - containerPort: {{ .port }}
+ {{- end }}
+ {{- range $.Values.jackal.config.s2s.listeners }}
+ - containerPort: {{ .port }}
+ {{- end }}
+ - containerPort: {{ $.Values.jackal.config.http.port }}
+ name: http
+ - containerPort: {{ $.Values.jackal.config.admin.port }}
+ name: admin
+ - containerPort: {{ $.Values.jackal.config.cluster.server.port }}
+ name: cluster
+
+ readinessProbe:
+ httpGet:
+ path: /healthz
+ port: {{ $.Values.jackal.config.http.port }}
+ periodSeconds: 10
+ initialDelaySeconds: 15
+
+ resources:
+ {{- toYaml $.Values.jackal.resources | nindent 12 }}
+
+ env:
+ {{- if $.Values.jackal.env }}
+ {{ toYaml $.Values.jackal.env | nindent 12}}
+ {{- end }}
+
+ - name: JACKAL_STORAGE_PGSQL_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: jackal-postgresql-ha-postgresql
+ key: postgresql-password
+ optional: false
+
+ - name: JACKAL_S2S_DIALBACK_SECRET
+ value: {{ randAlphaNum 16 }}
+
+ volumeMounts:
+ {{- if $.Values.jackal.extraVolumeMounts }}
+ {{ toYaml $.Values.jackal.extraVolumeMounts | nindent 12}}
+ {{- end }}
+
+ - mountPath: /etc/jackal
+ name: config-volume
+ readOnly: true
+
+ volumes:
+ {{- if $.Values.jackal.extraVolumes }}
+ {{ toYaml $.Values.jackal.extraVolumes | nindent 8}}
+ {{- end }}
+
+ - name: config-volume
+ configMap:
+ name: jackal-config
+{{- end }}
diff --git a/helm/templates/deployment-redis.yaml b/helm/templates/deployment-redis.yaml
new file mode 100644
index 000000000..1d3656b69
--- /dev/null
+++ b/helm/templates/deployment-redis.yaml
@@ -0,0 +1,32 @@
+{{ if index .Values.redis.enabled }}
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: redis
+ namespace: {{ $.Release.Namespace }}
+ labels:
+ app: redis
+ heritage: {{ $.Release.Service }}
+ release: {{ $.Release.Name }}
+spec:
+ replicas: {{ .Values.redis.replicasCount }}
+ selector:
+ matchLabels:
+ app: redis
+ strategy: {}
+ template:
+ metadata:
+ labels:
+ app: redis
+ spec:
+ containers:
+ - name: redis
+ image: {{ $.Values.redis.image.repository }}:{{ $.Values.redis.image.tag }}
+ imagePullPolicy: {{ $.Values.redis.image.pullPolicy }}
+ ports:
+ - containerPort: {{ $.Values.redis.port }}
+ name: redis
+
+ resources:
+ {{- toYaml $.Values.redis.resources | nindent 12 }}
+{{ end }}
diff --git a/helm/templates/service-jackal-internal.yaml b/helm/templates/service-jackal-internal.yaml
new file mode 100644
index 000000000..f65a66090
--- /dev/null
+++ b/helm/templates/service-jackal-internal.yaml
@@ -0,0 +1,21 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: jackal-internal
+ namespace: {{ .Release.Namespace }}
+ labels:
+ app: jackal
+spec:
+ type: ClusterIP
+ selector:
+ component: jackal
+ ports:
+ - port: {{ .Values.jackal.config.http.port }}
+ name: http
+ targetPort: {{ .Values.jackal.config.http.port }}
+ - port: {{ .Values.jackal.config.admin.port }}
+ name: admin
+ targetPort: {{ .Values.jackal.config.admin.port }}
+ - port: {{ .Values.jackal.config.cluster.server.port }}
+ name: cluster
+ targetPort: {{ .Values.jackal.config.cluster.server.port }}
diff --git a/helm/templates/service-redis.yaml b/helm/templates/service-redis.yaml
new file mode 100644
index 000000000..c3c0bbb3b
--- /dev/null
+++ b/helm/templates/service-redis.yaml
@@ -0,0 +1,18 @@
+{{ if index .Values.redis.enabled }}
+apiVersion: v1
+kind: Service
+metadata:
+ name: redis-headless
+ namespace: {{ .Release.Namespace }}
+ labels:
+ app: redis
+spec:
+ clusterIP: None
+ type: ClusterIP
+ selector:
+ app: redis
+ ports:
+ - port: {{ .Values.redis.port }}
+ protocol: TCP
+ name: redis
+{{ end }}
diff --git a/helm/values.yaml b/helm/values.yaml
new file mode 100644
index 000000000..d437fe9ae
--- /dev/null
+++ b/helm/values.yaml
@@ -0,0 +1,193 @@
+# Default values for jackal.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+# //////////////
+# Jackal
+# //////////////
+jackal:
+ replicasCount: 2
+
+ nodeSelector: {}
+ affinity: {}
+ tolerations: []
+
+ extraVolumes: []
+ extraVolumeMounts: []
+ env: []
+
+ image:
+ repository: ortuman/jackal
+ tag: 0.59.0
+ pullPolicy: IfNotPresent
+ resources:
+ requests:
+ cpu: 120m
+ memory: 64Mi
+
+ config:
+ logger:
+ level: debug
+
+ http:
+ port: 6060
+
+ admin:
+ port: 15280
+
+ #domains:
+ # - domain: jackal.im
+ # tls:
+ # cert_file: /var/jackal/cert/tls.crt
+ # privkey_file: /var/jackal/cert/tls.key
+
+ #peppers:
+ # keys:
+ # v1: a-super-secret-key
+ # use: v1
+
+ storage:
+ maxConns: 16
+ maxIdleConns: 0
+ connMaxLifetime: 0
+ connMaxIdleTime: 0
+
+ cluster:
+ server:
+ port: 14369
+
+ shapers:
+ - name: normal
+ max_sessions: 25
+ rate:
+ limit: 131072
+ burst: 65536
+
+ c2s:
+ listeners:
+ - port: 5222
+ req_timeout: 60s
+ transport: socket
+ sasl:
+ mechanisms:
+ - scram_sha_1
+ - scram_sha_256
+ - scram_sha_512
+ - scram_sha3_512
+
+ - port: 5223
+ direct_tls: true
+ req_timeout: 60s
+ transport: socket
+ sasl:
+ mechanisms:
+ - scram_sha_1
+ - scram_sha_256
+ - scram_sha_512
+ - scram_sha3_512
+
+ s2s:
+ listeners:
+ - port: 5269
+ req_timeout: 60s
+ max_stanza_size: 131072
+
+ - port: 5270
+ direct_tls: true
+ req_timeout: 60s
+ max_stanza_size: 131072
+
+ out:
+ dial_timeout: 5s
+ req_timeout: 60s
+ max_stanza_size: 131072
+
+ modules:
+ enabled:
+ - roster
+ - offline
+ - last # XEP-0012: Last Activity
+ - disco # XEP-0030: Service Discovery
+ - private # XEP-0049: Private XML Storage
+ - vcard # XEP-0054: vcard-temp
+ - version # XEP-0092: Software Version
+ - caps # XEP-0115: Entity Capabilities
+ - blocklist # XEP-0191: Blocking Command
+ - stream_mgmt # XEP-0198: Stream Management
+ - ping # XEP-0199: XMPP Ping
+ - time # XEP-0202: Entity Time
+ - carbons # XEP-0280: Message Carbons
+
+ version:
+ show_os: true
+
+ offline:
+ queue_size: 300
+
+ ping:
+ ack_timeout: 90s
+ interval: 3m
+ send_pings: true
+ timeout_action: kill
+
+ components:
+ # listeners:
+ # - port: 5275
+ # secret: a-super-secret-key
+
+# //////////////
+# etcd
+# //////////////
+etcd:
+ auth:
+ rbac:
+ create: false
+ persistence:
+ enabled: false
+ replicaCount: 2
+ resources:
+ requests:
+ cpu: 120m
+ memory: 64Mi
+
+# //////////////
+# Redis
+# //////////////
+redis:
+ enabled: true
+ replicasCount: 2
+
+ image:
+ repository: redis
+ tag: 6.2.7
+ pullPolicy: IfNotPresent
+
+ resources:
+ requests:
+ cpu: 100m
+ memory: 64Mi
+
+ port: 6379
+
+# //////////////
+# PostgresSQL
+# //////////////
+postgresql-ha:
+ postgresql:
+ syncReplication: true
+ initdbScriptsCM: pgsql-init-script
+ replicaCount: 2
+ username: jackal
+ database: jackal
+ resources:
+ requests:
+ cpu: 250m
+ memory: 256Mi
+ pgpool:
+ replicaCount: 2
+ resources:
+ requests:
+ cpu: 120m
+ memory: 256Mi
+ persistence:
+ size: 2Gi
diff --git a/pkg/storage/pgsql/blocklist.go b/pkg/storage/pgsql/blocklist.go
index bfdf03e20..d9d8a3410 100644
--- a/pkg/storage/pgsql/blocklist.go
+++ b/pkg/storage/pgsql/blocklist.go
@@ -33,6 +33,7 @@ type pgSQLBlockListRep struct {
func (r *pgSQLBlockListRep) UpsertBlockListItem(ctx context.Context, item *blocklistmodel.Item) error {
_, err := sq.Insert(blockListsTableName).
+ Prefix(noLoadBalancePrefix).
Columns("username", "jid").
Values(item.Username, item.Jid).
Suffix("ON CONFLICT (username, jid) DO NOTHING").
@@ -43,6 +44,7 @@ func (r *pgSQLBlockListRep) UpsertBlockListItem(ctx context.Context, item *block
func (r *pgSQLBlockListRep) DeleteBlockListItem(ctx context.Context, item *blocklistmodel.Item) error {
_, err := sq.Delete(blockListsTableName).
+ Prefix(noLoadBalancePrefix).
Where(sq.And{sq.Eq{"username": item.Username}, sq.Eq{"jid": item.Jid}}).
RunWith(r.conn).
ExecContext(ctx)
@@ -66,6 +68,7 @@ func (r *pgSQLBlockListRep) FetchBlockListItems(ctx context.Context, username st
func (r *pgSQLBlockListRep) DeleteBlockListItems(ctx context.Context, username string) error {
_, err := sq.Delete(blockListsTableName).
+ Prefix(noLoadBalancePrefix).
Where(sq.Eq{"username": username}).
RunWith(r.conn).
ExecContext(ctx)
diff --git a/pkg/storage/pgsql/capabilities.go b/pkg/storage/pgsql/capabilities.go
index 2f116a68f..e407f584c 100644
--- a/pkg/storage/pgsql/capabilities.go
+++ b/pkg/storage/pgsql/capabilities.go
@@ -36,6 +36,7 @@ type pgSQLCapabilitiesRep struct {
func (r *pgSQLCapabilitiesRep) UpsertCapabilities(ctx context.Context, caps *capsmodel.Capabilities) error {
_, err := sq.Insert(capsTableName).
+ Prefix(noLoadBalancePrefix).
Columns("node", "ver", "features").
Values(caps.Node, caps.Ver, pq.Array(caps.Features)).
Suffix("ON CONFLICT (node, ver) DO UPDATE SET features = $3").
diff --git a/pkg/storage/pgsql/last.go b/pkg/storage/pgsql/last.go
index 8d1bfe935..eae2b4ff1 100644
--- a/pkg/storage/pgsql/last.go
+++ b/pkg/storage/pgsql/last.go
@@ -35,6 +35,7 @@ type pgSQLLastRep struct {
func (r *pgSQLLastRep) UpsertLast(ctx context.Context, last *lastmodel.Last) error {
_, err := sq.Insert(lastTableName).
+ Prefix(noLoadBalancePrefix).
Columns("username", "seconds", "status").
Values(last.Username, last.Seconds, last.Status).
Suffix("ON CONFLICT (username) DO UPDATE SET seconds = $2, status = $3").
@@ -63,6 +64,7 @@ func (r *pgSQLLastRep) FetchLast(ctx context.Context, username string) (*lastmod
func (r *pgSQLLastRep) DeleteLast(ctx context.Context, username string) error {
_, err := sq.Delete(lastTableName).
+ Prefix(noLoadBalancePrefix).
Where(sq.Eq{"username": username}).
RunWith(r.conn).
ExecContext(ctx)
diff --git a/pkg/storage/pgsql/locker.go b/pkg/storage/pgsql/locker.go
index af5c1709d..fa1f6713d 100644
--- a/pkg/storage/pgsql/locker.go
+++ b/pkg/storage/pgsql/locker.go
@@ -32,7 +32,7 @@ func (l *pgSQLLocker) Lock(ctx context.Context, lockID string) error {
}
var acquired bool
- err := l.conn.QueryRowContext(ctx, "SELECT pg_try_advisory_lock(hashtext($1))", lockID).Scan(&acquired)
+ err := l.conn.QueryRowContext(ctx, "/*NO LOAD BALANCE*/ SELECT pg_try_advisory_lock(hashtext($1))", lockID).Scan(&acquired)
switch err {
case nil:
if acquired {
diff --git a/pkg/storage/pgsql/offline.go b/pkg/storage/pgsql/offline.go
index 69a1b4b81..89dee42e0 100644
--- a/pkg/storage/pgsql/offline.go
+++ b/pkg/storage/pgsql/offline.go
@@ -35,6 +35,7 @@ func (r *pgSQLOfflineRep) InsertOfflineMessage(ctx context.Context, message *str
return err
}
q := sq.Insert(offlineMessagesTableName).
+ Prefix(noLoadBalancePrefix).
Columns("username", "message").
Values(username, b)
@@ -88,6 +89,7 @@ func (r *pgSQLOfflineRep) FetchOfflineMessages(ctx context.Context, username str
func (r *pgSQLOfflineRep) DeleteOfflineMessages(ctx context.Context, username string) error {
q := sq.Delete(offlineMessagesTableName).
+ Prefix(noLoadBalancePrefix).
Where(sq.Eq{"username": username})
_, err := q.RunWith(r.conn).ExecContext(ctx)
return err
diff --git a/pkg/storage/pgsql/private.go b/pkg/storage/pgsql/private.go
index 2429ccd3e..b9ffd1a84 100644
--- a/pkg/storage/pgsql/private.go
+++ b/pkg/storage/pgsql/private.go
@@ -59,6 +59,7 @@ func (r *pgSQLPrivateRep) UpsertPrivate(ctx context.Context, private stravaganza
return err
}
q := sq.Insert(privateStorageTableName).
+ Prefix(noLoadBalancePrefix).
Columns("username", "namespace", "data").
Values(username, namespace, b).
Suffix("ON CONFLICT (username, namespace) DO UPDATE SET data = $3")
@@ -69,6 +70,7 @@ func (r *pgSQLPrivateRep) UpsertPrivate(ctx context.Context, private stravaganza
func (r *pgSQLPrivateRep) DeletePrivates(ctx context.Context, username string) error {
_, err := sq.Delete(privateStorageTableName).
+ Prefix(noLoadBalancePrefix).
Where(sq.Eq{"username": username}).
RunWith(r.conn).
ExecContext(ctx)
diff --git a/pkg/storage/pgsql/repository.go b/pkg/storage/pgsql/repository.go
index 359ee3c16..3a0acb9f9 100644
--- a/pkg/storage/pgsql/repository.go
+++ b/pkg/storage/pgsql/repository.go
@@ -28,6 +28,8 @@ import (
"github.com/ortuman/jackal/pkg/storage/repository"
)
+const noLoadBalancePrefix = "/*NO LOAD BALANCE*/"
+
func init() {
sq.StatementBuilder = sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
}
diff --git a/pkg/storage/pgsql/roster.go b/pkg/storage/pgsql/roster.go
index 0adfa3385..900a4a973 100644
--- a/pkg/storage/pgsql/roster.go
+++ b/pkg/storage/pgsql/roster.go
@@ -39,6 +39,7 @@ type pgSQLRosterRep struct {
func (r *pgSQLRosterRep) TouchRosterVersion(ctx context.Context, username string) (int, error) {
b := sq.Insert(rosterVersionsTableName).
+ Prefix(noLoadBalancePrefix).
Columns("username").
Values(username).
Suffix("ON CONFLICT (username) DO UPDATE SET ver = roster_versions.ver + 1").
@@ -71,6 +72,7 @@ func (r *pgSQLRosterRep) FetchRosterVersion(ctx context.Context, username string
func (r *pgSQLRosterRep) UpsertRosterItem(ctx context.Context, ri *rostermodel.Item) error {
q := sq.Insert(rosterItemsTableName).
+ Prefix(noLoadBalancePrefix).
Columns("username", "jid", "name", "subscription", "groups", "ask").
Values(ri.Username, ri.Jid, ri.Name, ri.Subscription, pq.Array(ri.Groups), ri.Ask).
Suffix("ON CONFLICT (username, jid) DO UPDATE SET name = $3, subscription = $4, groups = $5, ask = $6")
@@ -81,6 +83,7 @@ func (r *pgSQLRosterRep) UpsertRosterItem(ctx context.Context, ri *rostermodel.I
func (r *pgSQLRosterRep) DeleteRosterItem(ctx context.Context, username, jid string) error {
_, err := sq.Delete(rosterItemsTableName).
+ Prefix(noLoadBalancePrefix).
Where(sq.And{sq.Eq{"username": username}, sq.Eq{"jid": jid}}).
RunWith(r.conn).ExecContext(ctx)
return err
@@ -88,6 +91,7 @@ func (r *pgSQLRosterRep) DeleteRosterItem(ctx context.Context, username, jid str
func (r *pgSQLRosterRep) DeleteRosterItems(ctx context.Context, username string) error {
_, err := sq.Delete(rosterItemsTableName).
+ Prefix(noLoadBalancePrefix).
Where(sq.Eq{"username": username}).
RunWith(r.conn).ExecContext(ctx)
return err
@@ -145,6 +149,7 @@ func (r *pgSQLRosterRep) UpsertRosterNotification(ctx context.Context, rn *roste
return err
}
q := sq.Insert(rosterNotificationsTableName).
+ Prefix(noLoadBalancePrefix).
Columns("contact", "jid", "presence").
Values(rn.Contact, rn.Jid, prBytes).
Suffix("ON CONFLICT (contact, jid) DO UPDATE SET presence = $3")
@@ -155,6 +160,7 @@ func (r *pgSQLRosterRep) UpsertRosterNotification(ctx context.Context, rn *roste
func (r *pgSQLRosterRep) DeleteRosterNotification(ctx context.Context, contact, jid string) error {
q := sq.Delete(rosterNotificationsTableName).
+ Prefix(noLoadBalancePrefix).
Where(sq.And{sq.Eq{"contact": contact}, sq.Eq{"jid": jid}})
_, err := q.RunWith(r.conn).ExecContext(ctx)
return err
@@ -162,6 +168,7 @@ func (r *pgSQLRosterRep) DeleteRosterNotification(ctx context.Context, contact,
func (r *pgSQLRosterRep) DeleteRosterNotifications(ctx context.Context, contact string) error {
q := sq.Delete(rosterNotificationsTableName).
+ Prefix(noLoadBalancePrefix).
Where(sq.Eq{"contact": contact})
_, err := q.RunWith(r.conn).ExecContext(ctx)
return err
diff --git a/pkg/storage/pgsql/user.go b/pkg/storage/pgsql/user.go
index 4041b0921..4df98b694 100644
--- a/pkg/storage/pgsql/user.go
+++ b/pkg/storage/pgsql/user.go
@@ -56,6 +56,7 @@ func (r *pgSQLUserRep) UpsertUser(ctx context.Context, user *usermodel.User) err
user.Scram.PepperId,
}
q := sq.Insert(usersTableName).
+ Prefix(noLoadBalancePrefix).
Columns(cols...).
Values(vals...).
Suffix("ON CONFLICT (username) DO UPDATE SET h_sha_1 = $2, h_sha_256 = $3, h_sha_512 = $4, h_sha3_512 = $5, salt = $6, iteration_count = $7, pepper_id = $8")
@@ -66,6 +67,7 @@ func (r *pgSQLUserRep) UpsertUser(ctx context.Context, user *usermodel.User) err
func (r *pgSQLUserRep) DeleteUser(ctx context.Context, username string) error {
_, err := sq.Delete(usersTableName).
+ Prefix(noLoadBalancePrefix).
Where(sq.Eq{"username": username}).
RunWith(r.conn).
ExecContext(ctx)
diff --git a/pkg/storage/pgsql/vcard.go b/pkg/storage/pgsql/vcard.go
index dd7d29336..fd5c8856e 100644
--- a/pkg/storage/pgsql/vcard.go
+++ b/pkg/storage/pgsql/vcard.go
@@ -38,6 +38,7 @@ func (r *pgSQLVCardRep) UpsertVCard(ctx context.Context, vCard stravaganza.Eleme
return err
}
q := sq.Insert(vCardsTableName).
+ Prefix(noLoadBalancePrefix).
Columns("username", "vcard").
Values(username, b).
Suffix("ON CONFLICT (username) DO UPDATE SET vcard = $2")
@@ -71,6 +72,7 @@ func (r *pgSQLVCardRep) FetchVCard(ctx context.Context, username string) (strava
func (r *pgSQLVCardRep) DeleteVCard(ctx context.Context, username string) error {
_, err := sq.Delete(vCardsTableName).
+ Prefix(noLoadBalancePrefix).
Where(sq.Eq{"username": username}).
RunWith(r.conn).
ExecContext(ctx)