diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..9a0ae3c7c3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[[shell]] +binary_next_line = true +switch_case_indent = true + +[*.{yaml,yml}] +indent_size = 2 diff --git a/.github/workflows/dockerfile-build.yaml b/.github/workflows/dockerfile-build.yaml new file mode 100644 index 0000000000..0a6f06a514 --- /dev/null +++ b/.github/workflows/dockerfile-build.yaml @@ -0,0 +1,36 @@ +--- +name: Build and push `main` image + +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ghcr.io/${{ github.repository }}:main + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/dockerfile-cleanup.yaml b/.github/workflows/dockerfile-cleanup.yaml new file mode 100644 index 0000000000..d040d980d7 --- /dev/null +++ b/.github/workflows/dockerfile-cleanup.yaml @@ -0,0 +1,22 @@ +--- +name: Cleanup closed PR images + +on: + pull_request: + branches: + - main + types: [closed] + +jobs: + cleanup: + runs-on: ubuntu-latest + permissions: + packages: write + steps: + - name: Delete PR image + uses: actions/delete-package-versions@v5 + with: + package-name: "zulip/docker-zulip" + package-type: "container" + token: ${{ secrets.GITHUB_TOKEN }} + tag-name: pr-${{ github.event.pull_request.number }} diff --git a/.github/workflows/dockerfile.yaml b/.github/workflows/dockerfile.yaml new file mode 100644 index 0000000000..889a490a47 --- /dev/null +++ b/.github/workflows/dockerfile.yaml @@ -0,0 +1,146 @@ +--- +name: Dockerfile lint and build + +on: + pull_request: + branches: + - main + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - uses: hadolint/hadolint-action@v3.1.0 + + build: + runs-on: ubuntu-latest + needs: lint + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ghcr.io/${{ github.repository }}:pr-${{ github.event.pull_request.number }} + cache-from: type=gha + cache-to: type=gha,mode=max + + helm-docs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Run helm-docs + uses: losisin/helm-docs-github-action@v1 + with: + fail-on-diff: true + + helm-test: + runs-on: ubuntu-latest + needs: + - helm-docs + - build + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Set up Helm + uses: azure/setup-helm@v4.2.0 + with: + version: v3.17.0 + + - uses: actions/setup-python@v5.3.0 + with: + python-version: "3.x" + check-latest: true + + - name: Set up chart-testing + uses: helm/chart-testing-action@v2.7.0 + with: + version: 3.14.0 + yamllint_version: 1.37.1 + yamale_version: 6.0.0 + + - name: Set up helm repos + run: | + helm repo add groundhog2k https://groundhog2k.github.io/helm-charts/ + + - name: Run chart-testing (list-changed) + id: list-changed + run: | + changed=$(ct list-changed --chart-dirs kubernetes/chart \ + --target-branch ${{ github.event.repository.default_branch }}) + if [[ -n "$changed" ]]; then + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Run chart-testing (lint) + if: steps.list-changed.outputs.changed == 'true' + run: | + ct lint --github-groups \ + --chart-dirs kubernetes/chart \ + --target-branch ${{ github.event.repository.default_branch }} \ + --lint-conf lintconf.yaml + + - name: Create kind cluster + if: steps.list-changed.outputs.changed == 'true' + uses: helm/kind-action@v1 + + - name: Log in to GHCR + if: steps.list-changed.outputs.changed == 'true' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Load image into kind + if: steps.list-changed.outputs.changed == 'true' + run: | + docker pull ghcr.io/${{ github.repository }}:pr-${{ github.event.pull_request.number }} + kind load docker-image ghcr.io/${{ github.repository }}:pr-${{ github.event.pull_request.number }} --name chart-testing + + - name: Run chart-testing (install) + id: chart-testing-install + if: steps.list-changed.outputs.changed == 'true' + run: | + ct install --github-groups \ + --chart-dirs kubernetes/chart \ + --target-branch ${{ github.event.repository.default_branch }} \ + --helm-extra-set-args "--set image.tag=pr-${{ github.event.pull_request.number }}" \ + --skip-clean-up + + - name: Fetch logs + if: always() + run: | + namespace=$(helm list --all-namespaces --output json \ + | jq -r '[.[] | select(.namespace | startswith("zulip-"))][0].namespace') + kubectl get pods -n "$namespace" + for pod in $(kubectl get pods -n "$namespace" -o name); do + kubectl describe "$pod" -n "$namespace" + done + pod=$(kubectl get pods -n "$namespace" -l app.kubernetes.io/name=zulip --output name) + kubectl -n "$namespace" logs "$pod" + kubectl -n "$namespace" exec "$pod" -c zulip -- cat /var/log/zulip/errors.log diff --git a/.github/workflows/shell-test.yaml b/.github/workflows/shell-test.yaml new file mode 100644 index 0000000000..692ee19aec --- /dev/null +++ b/.github/workflows/shell-test.yaml @@ -0,0 +1,14 @@ +--- +name: Shell check and lint + +on: + pull_request: + branches: + - main + +jobs: + shellcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: luizm/action-sh-checker@master diff --git a/.github/workflows/spelling.yaml b/.github/workflows/spelling.yaml new file mode 100644 index 0000000000..30d1553846 --- /dev/null +++ b/.github/workflows/spelling.yaml @@ -0,0 +1,16 @@ +--- +name: Spelling + +on: + pull_request: + branches: + - main + +jobs: + spelling: + name: Spellcheck with Typos + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Spell Check Repo + uses: crate-ci/typos@v1.39.0 diff --git a/Dockerfile b/Dockerfile index 87ff46e891..8b6bdb5fde 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,7 @@ ENV LANG="C.UTF-8" ARG UBUNTU_MIRROR +# hadolint ignore=DL3005,DL3008,DL3009 RUN { [ ! "$UBUNTU_MIRROR" ] || sed -i "s|http://\(\w*\.\)*archive\.ubuntu\.com/ubuntu/\? |$UBUNTU_MIRROR |" /etc/apt/sources.list; } && \ apt-get -q update && \ apt-get -q dist-upgrade -y && \ @@ -29,8 +30,7 @@ WORKDIR /home/zulip ARG ZULIP_GIT_URL=https://github.com/zulip/zulip.git ARG ZULIP_GIT_REF=11.4 -RUN git clone "$ZULIP_GIT_URL" -b "$ZULIP_GIT_REF" && \ - cd zulip +RUN git clone "$ZULIP_GIT_URL" -b "$ZULIP_GIT_REF" WORKDIR /home/zulip/zulip @@ -53,12 +53,12 @@ COPY custom_zulip_files/ /root/custom_zulip ARG CUSTOM_CA_CERTIFICATES +WORKDIR /root RUN \ # Make sure Nginx is started by Supervisor. dpkg-divert --add --rename /etc/init.d/nginx && \ ln -s /bin/true /etc/init.d/nginx && \ mkdir -p "$DATA_DIR" && \ - cd /root && \ tar -xf zulip-server-docker.tar.gz && \ rm -f zulip-server-docker.tar.gz && \ mv zulip-server-docker zulip && \ diff --git a/UPGRADING.md b/UPGRADING.md index f5d340e8d7..e0c246f9c7 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -219,7 +219,7 @@ image (because we've significantly upgraded the major postgres version). These instructions assume that you have not changed the default PostgreSQL data path (`/opt/docker/zulip/postgresql/data`) in your `docker-compose.yml`. If you -have changed it, please replace all occurences of +have changed it, please replace all occurrences of `/opt/docker/zulip/postgresql/data` with your path. 1. Make a backup of your Zulip PostgreSQL data directory. diff --git a/entrypoint.sh b/entrypoint.sh index a546361ddd..dcb3446220 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -30,7 +30,8 @@ DISABLE_HTTPS="${DISABLE_HTTPS:-false}" NGINX_WORKERS="${NGINX_WORKERS:-2}" NGINX_PROXY_BUFFERING="${NGINX_PROXY_BUFFERING:-off}" NGINX_MAX_UPLOAD_SIZE="${NGINX_MAX_UPLOAD_SIZE:-80m}" -# Zulip certifcate parameters +TRUST_GATEWAY_IP="${TRUST_GATEWAY_IP:-False}" +# Zulip certificate parameters SSL_CERTIFICATE_GENERATION="${SSL_CERTIFICATE_GENERATION:self-signed}" # Zulip related settings ZULIP_AUTH_BACKENDS="${ZULIP_AUTH_BACKENDS:-EmailAuthBackend}" @@ -86,37 +87,37 @@ setConfigurationValue() { local TYPE="$4" if [ -z "$TYPE" ]; then case "$2" in - [Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|[Nn]one) - TYPE="bool" - ;; + [Tt][Rr][Uu][Ee] | [Ff][Aa][Ll][Ss][Ee] | [Nn]one) + TYPE="bool" + ;; +([0-9])) - TYPE="integer" - ;; + TYPE="integer" + ;; [\[\(]*[\]\)]) - TYPE="array" - ;; + TYPE="array" + ;; *) - TYPE="string" - ;; + TYPE="string" + ;; esac fi case "$TYPE" in emptyreturn) - if [ -z "$2" ]; then - return 0 - fi - ;; + if [ -z "$2" ]; then + return 0 + fi + ;; literal) - VALUE="$1" - ;; - bool|boolean|int|integer|array) - VALUE="$KEY = $2" - ;; - string|*) - VALUE="$KEY = '${2//\'/\'}'" - ;; + VALUE="$1" + ;; + bool | boolean | int | integer | array) + VALUE="$KEY = $2" + ;; + string | *) + VALUE="$KEY = '${2//\'/\'}'" + ;; esac - echo "$VALUE" >> "$FILE" + echo "$VALUE" >>"$FILE" echo "Setting key \"$KEY\", type \"$TYPE\" in file \"$FILE\"." } nginxConfiguration() { @@ -141,6 +142,11 @@ puppetConfiguration() { crudini --set /etc/zulip/zulip.conf application_server queue_workers_multiprocess false fi + if [ "$TRUST_GATEWAY_IP" == "True" ] || [ "$TRUST_GATEWAY_IP" == "true" ]; then + GATEWAY_IP=$(ip route | grep default | awk '{print $3}') + echo "Trusting local network gateway $GATEWAY_IP" + LOADBALANCER_IPS="${LOADBALANCER_IPS:+$LOADBALANCER_IPS,}$GATEWAY_IP" + fi if [ -n "$LOADBALANCER_IPS" ]; then echo "Setting IPs for load balancer" crudini --set /etc/zulip/zulip.conf loadbalancer ips "${LOADBALANCER_IPS}" @@ -260,7 +266,7 @@ authenticationBackends() { echo "Activating authentication backends ..." local FIRST=true local auth_backends - IFS=, read -r -a auth_backends <<< "$ZULIP_AUTH_BACKENDS" + IFS=, read -r -a auth_backends <<<"$ZULIP_AUTH_BACKENDS" for AUTH_BACKEND in "${auth_backends[@]}"; do if [ "$FIRST" = true ]; then setConfigurationValue "AUTHENTICATION_BACKENDS" "('zproject.backends.${AUTH_BACKEND//\'/\'}',)" "$SETTINGS_PY" "array" @@ -275,7 +281,7 @@ authenticationBackends() { zulipConfiguration() { echo "Executing Zulip configuration ..." if [ -n "$ZULIP_CUSTOM_SETTINGS" ]; then - echo -e "\n$ZULIP_CUSTOM_SETTINGS" >> "$SETTINGS_PY" + echo -e "\n$ZULIP_CUSTOM_SETTINGS" >>"$SETTINGS_PY" fi local key for key in "${!SETTING_@}"; do @@ -288,35 +294,35 @@ zulipConfiguration() { continue fi # Zulip settings.py / zproject specific overrides here - if [ "$setting_key" = "AUTH_LDAP_CONNECTION_OPTIONS" ] || \ - [ "$setting_key" = "AUTH_LDAP_GLOBAL_OPTIONS" ] || \ - [ "$setting_key" = "AUTH_LDAP_USER_SEARCH" ] || \ - [ "$setting_key" = "AUTH_LDAP_GROUP_SEARCH" ] || \ - [ "$setting_key" = "AUTH_LDAP_REVERSE_EMAIL_SEARCH" ] || \ - [ "$setting_key" = "AUTH_LDAP_USER_ATTR_MAP" ] || \ - [ "$setting_key" = "AUTH_LDAP_USER_FLAGS_BY_GROUP" ] || \ - [ "$setting_key" = "AUTH_LDAP_GROUP_TYPE" ] || \ - [ "$setting_key" = "AUTH_LDAP_ADVANCED_REALM_ACCESS_CONTROL" ] || \ - [ "$setting_key" = "LDAP_SYNCHRONIZED_GROUPS_BY_REALM" ] || \ - [ "$setting_key" = "SOCIAL_AUTH_OIDC_ENABLED_IDPS" ] || \ - [ "$setting_key" = "SOCIAL_AUTH_SAML_ENABLED_IDPS" ] || \ - [ "$setting_key" = "SOCIAL_AUTH_SAML_ORG_INFO" ] || \ - [ "$setting_key" = "SOCIAL_AUTH_SYNC_ATTRS_DICT" ] || \ - { [ "$setting_key" = "LDAP_APPEND_DOMAIN" ] && [ "$setting_var" = "None" ]; } || \ - [ "$setting_key" = "SCIM_CONFIG" ] || \ - [ "$setting_key" = "SECURE_PROXY_SSL_HEADER" ] || \ - [[ "$setting_key" = "CSRF_"* ]] || \ - [ "$setting_key" = "REALM_HOSTS" ] || \ - [ "$setting_key" = "ALLOWED_HOSTS" ]; then + if [ "$setting_key" = "AUTH_LDAP_CONNECTION_OPTIONS" ] \ + || [ "$setting_key" = "AUTH_LDAP_GLOBAL_OPTIONS" ] \ + || [ "$setting_key" = "AUTH_LDAP_USER_SEARCH" ] \ + || [ "$setting_key" = "AUTH_LDAP_GROUP_SEARCH" ] \ + || [ "$setting_key" = "AUTH_LDAP_REVERSE_EMAIL_SEARCH" ] \ + || [ "$setting_key" = "AUTH_LDAP_USER_ATTR_MAP" ] \ + || [ "$setting_key" = "AUTH_LDAP_USER_FLAGS_BY_GROUP" ] \ + || [ "$setting_key" = "AUTH_LDAP_GROUP_TYPE" ] \ + || [ "$setting_key" = "AUTH_LDAP_ADVANCED_REALM_ACCESS_CONTROL" ] \ + || [ "$setting_key" = "LDAP_SYNCHRONIZED_GROUPS_BY_REALM" ] \ + || [ "$setting_key" = "SOCIAL_AUTH_OIDC_ENABLED_IDPS" ] \ + || [ "$setting_key" = "SOCIAL_AUTH_SAML_ENABLED_IDPS" ] \ + || [ "$setting_key" = "SOCIAL_AUTH_SAML_ORG_INFO" ] \ + || [ "$setting_key" = "SOCIAL_AUTH_SYNC_ATTRS_DICT" ] \ + || { [ "$setting_key" = "LDAP_APPEND_DOMAIN" ] && [ "$setting_var" = "None" ]; } \ + || [ "$setting_key" = "SCIM_CONFIG" ] \ + || [ "$setting_key" = "SECURE_PROXY_SSL_HEADER" ] \ + || [[ "$setting_key" = "CSRF_"* ]] \ + || [ "$setting_key" = "REALM_HOSTS" ] \ + || [ "$setting_key" = "ALLOWED_HOSTS" ]; then type="array" fi - if [ "$SPECIAL_SETTING_DETECTION_MODE" = "True" ] || [ "$SPECIAL_SETTING_DETECTION_MODE" = "true" ] || \ - [ "$type" = "string" ]; then + if [ "$SPECIAL_SETTING_DETECTION_MODE" = "True" ] || [ "$SPECIAL_SETTING_DETECTION_MODE" = "true" ] \ + || [ "$type" = "string" ]; then type="" fi - if [ "$setting_key" = "EMAIL_HOST_USER" ] || \ - [ "$setting_key" = "EMAIL_HOST_PASSWORD" ] || \ - [ "$setting_key" = "EXTERNAL_HOST" ]; then + if [ "$setting_key" = "EMAIL_HOST_USER" ] \ + || [ "$setting_key" = "EMAIL_HOST_PASSWORD" ] \ + || [ "$setting_key" = "EXTERNAL_HOST" ]; then type="string" fi setConfigurationValue "$setting_key" "$setting_var" "$SETTINGS_PY" "$type" @@ -333,7 +339,7 @@ autoBackupConfiguration() { echo "Auto backup is disabled. Continuing." return 0 fi - printf 'MAILTO=""\n%s cd /;/sbin/entrypoint.sh app:backup\n' "$AUTO_BACKUP_INTERVAL" > /etc/cron.d/autobackup + printf 'MAILTO=""\n%s cd /;/sbin/entrypoint.sh app:backup\n' "$AUTO_BACKUP_INTERVAL" >/etc/cron.d/autobackup echo "Auto backup enabled." } initialConfiguration() { @@ -349,6 +355,33 @@ initialConfiguration() { secretsConfiguration authenticationBackends zulipConfiguration + else + # Check that the configuration will work + root_path="/etc/zulip" + if [ "$LINK_SETTINGS_TO_DATA" = "True" ] || [ "$LINK_SETTINGS_TO_DATA" = "true" ]; then + root_path="/data/settings/etc-zulip" + fi + failure=0 + for conf_file in zulip.conf zulip-secrets.conf settings.py; do + if [ ! -f "/etc/zulip/$conf_file" ]; then + echo "ERROR: $root_path/$conf_file does not exist!" + failure=1 + elif ! sudo -u zulip test -r "/etc/zulip/$conf_file"; then + echo "ERROR: $root_path/$conf_file is not readable by the zulip user (UID $(id -u zulip))" + failure=1 + elif [ ! -s "/etc/zulip/$conf_file" ]; then + echo "ERROR: $root_path/$conf_file is empty" + failure=1 + fi + done + if [ "$failure" = "1" ]; then + ls -l /etc/zulip/ + exit 1 + fi + if ! su zulip -c "/home/zulip/deployments/current/manage.py checkconfig"; then + echo "Error in the Zulip configuration. Exiting." + exit 1 + fi fi autoBackupConfiguration echo "=== End Initial Configuration Phase ===" @@ -357,8 +390,7 @@ initialConfiguration() { waitingForDatabase() { local TIMEOUT=60 echo "Waiting for database server to allow connections ..." - while ! PGPASSWORD="${SECRETS_postgres_password?}" /usr/bin/pg_isready -h "$DB_HOST" -p "$DB_HOST_PORT" -U "$DB_USER" -t 1 >/dev/null 2>&1 - do + while ! PGPASSWORD="${SECRETS_postgres_password?}" /usr/bin/pg_isready -h "$DB_HOST" -p "$DB_HOST_PORT" -U "$DB_USER" -t 1 >/dev/null 2>&1; do if ! ((TIMEOUT--)); then echo "Could not connect to database server. Exiting." exit 1 @@ -383,7 +415,7 @@ zulipFirstStartInit() { fi set -e touch "$DATA_DIR/.initiated" - echo "Zulip first start init sucessful." + echo "Zulip first start init successful." } zulipMigration() { echo "Running new database migrations..." @@ -435,7 +467,7 @@ function runCertbotAsNeeded() { echo "Waiting for nginx to come online before generating certbot certificate ..." while ! curl -sk "$SETTING_EXTERNAL_HOST" >/dev/null 2>&1; do - sleep 1; + sleep 1 done echo "Generating LetsEncrypt/certbot certificate ..." @@ -507,7 +539,7 @@ appBackup() { BACKUP_FOLDER="/tmp/backup-$TIMESTAMP)" mkdir -p "$BACKUP_FOLDER" waitingForDatabase - pg_dump -h "$DB_HOST" -p "$DB_HOST_PORT" -U "$DB_USER" "$DB_NAME" > "$BACKUP_FOLDER/database-postgres.sql" + pg_dump -h "$DB_HOST" -p "$DB_HOST_PORT" -U "$DB_USER" "$DB_NAME" >"$BACKUP_FOLDER/database-postgres.sql" tar -zcvf "$DATA_DIR/backups/backup-$TIMESTAMP.tar.gz" "$BACKUP_FOLDER/" rm -r "${BACKUP_FOLDER:?}/" echo "Backup process succeeded." @@ -552,7 +584,7 @@ appRestore() { echo "!! WARNING !! Starting restore process ... !! WARNING !!" waitingForDatabase tar -zxvf "$DATA_DIR/backups/$BACKUP_FILE" -C /tmp - psql -h "$DB_HOST" -p "$DB_HOST_PORT" -U "$DB_USER" "$DB_NAME" < "/tmp/$(basename "$BACKUP_FILE" | cut -d. -f1)/database-postgres.sql" + psql -h "$DB_HOST" -p "$DB_HOST_PORT" -U "$DB_USER" "$DB_NAME" <"/tmp/$(basename "$BACKUP_FILE" | cut -d. -f1)/database-postgres.sql" rm -r "/tmp/$(basename "$BACKUP_FILE" | cut -d. -f1)/" echo "Restore process succeeded. Exiting." exit 0 @@ -569,7 +601,7 @@ appHelp() { echo "> app:restore - Restore backups of Zulip instances" echo "> app:certs - Create self-signed certificates" echo "> app:run - Run the Zulip server" - echo "> app:init - Run inital setup of Zulip server" + echo "> app:init - Run initial setup of Zulip server" echo "> [COMMAND] - Run given command with arguments in shell" } appVersion() { @@ -583,30 +615,30 @@ appVersion() { case "$1" in app:run) appRun - ;; + ;; app:init) appInit - ;; + ;; app:managepy) shift 1 appManagePy "$@" - ;; + ;; app:backup) appBackup - ;; + ;; app:restore) appRestore - ;; + ;; app:certs) appCerts - ;; + ;; app:help) appHelp - ;; + ;; app:version) appVersion - ;; + ;; *) exec "$@" || appHelp - ;; + ;; esac diff --git a/kubernetes/chart/zulip/Chart.lock b/kubernetes/chart/zulip/Chart.lock index a227174d48..8ac8b5cf99 100644 --- a/kubernetes/chart/zulip/Chart.lock +++ b/kubernetes/chart/zulip/Chart.lock @@ -1,15 +1,15 @@ dependencies: - name: memcached repository: oci://registry-1.docker.io/bitnamicharts - version: 7.4.16 + version: 8.1.5 - name: rabbitmq repository: oci://registry-1.docker.io/bitnamicharts - version: 14.7.0 + version: 16.0.14 - name: redis repository: oci://registry-1.docker.io/bitnamicharts - version: 20.1.4 + version: 23.2.12 - name: postgresql repository: oci://registry-1.docker.io/bitnamicharts - version: 15.5.32 -digest: sha256:511a69dac54b26810b3b269751c8115d5b8ed7a9ecfe5cc4a656e7feb5dbd1ff -generated: "2024-09-23T17:27:41.004706+02:00" + version: 18.1.8 +digest: sha256:8e1f3e3c40783b573c8551e100b319d88a8dbd630d5f41d228be0621c5ef1bb5 +generated: "2025-11-10T09:49:32.026785-05:00" diff --git a/kubernetes/chart/zulip/Chart.yaml b/kubernetes/chart/zulip/Chart.yaml index 840e2945d9..962939bc5a 100644 --- a/kubernetes/chart/zulip/Chart.yaml +++ b/kubernetes/chart/zulip/Chart.yaml @@ -3,11 +3,14 @@ description: Zulip is an open source threaded team chat that helps teams stay pr name: zulip type: application icon: https://raw.githubusercontent.com/zulip/zulip/main/static/images/logo/zulip-icon-square.svg +maintainers: + - name: zulip + url: https://chat.zulip.org/ ## 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.11.4 +version: 0.11.41 ## This is the version number of the application being deployed. This ## version number should be incremented each time you make changes to @@ -20,23 +23,23 @@ dependencies: repository: oci://registry-1.docker.io/bitnamicharts tags: - memcached - version: 7.4.16 + version: 8.1.5 - name: rabbitmq repository: oci://registry-1.docker.io/bitnamicharts tags: - rabbitmq - version: 14.7.0 + version: 16.0.14 - name: redis repository: oci://registry-1.docker.io/bitnamicharts tags: - redis - version: 20.1.4 + version: 23.2.12 - name: postgresql repository: oci://registry-1.docker.io/bitnamicharts tags: - postgresql ## Note: values.yaml overwrites posgresql image to zulip/zulip-postgresql:14 - version: 15.5.32 + version: 18.1.8 sources: - https://github.com/zulip/zulip diff --git a/kubernetes/chart/zulip/README.md b/kubernetes/chart/zulip/README.md index 811b582876..a842d3f2cc 100644 --- a/kubernetes/chart/zulip/README.md +++ b/kubernetes/chart/zulip/README.md @@ -1,6 +1,6 @@ # Zulip -![Version: 0.11.4](https://img.shields.io/badge/Version-0.11.4-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 11.4-0](https://img.shields.io/badge/AppVersion-11.4--0-informational?style=flat-square) +![Version: 0.11.41](https://img.shields.io/badge/Version-0.11.41-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 11.4-0](https://img.shields.io/badge/AppVersion-11.4--0-informational?style=flat-square) [Zulip](https://zulip.com/) is an open source threaded team chat that helps teams stay productive and focused. @@ -70,6 +70,7 @@ Now you're ready to follow [the installation instructions above](#installation). |-----|------|---------|-------------| | affinity | object | `{}` | | | fullnameOverride | string | `""` | | +| global.security.allowInsecureImages | bool | `true` | | | image.pullPolicy | string | `"IfNotPresent"` | | | image.repository | string | `"zulip/docker-zulip"` | | | image.tag | string | `"11.4-0"` | | @@ -85,6 +86,8 @@ Now you're ready to follow [the installation instructions above](#installation). | livenessProbe.periodSeconds | int | `10` | | | livenessProbe.successThreshold | int | `1` | | | livenessProbe.timeoutSeconds | int | `5` | | +| memcached.image.repository | string | `"bitnamilegacy/memcached"` | | +| memcached.image.tag | string | `"latest"` | | | memcached.memcachedUsername | string | `"zulip@localhost"` | | | nameOverride | string | `""` | | | nodeSelector | object | `{}` | | @@ -96,10 +99,16 @@ Now you're ready to follow [the installation instructions above](#installation). | postgresql.auth.username | string | `"zulip"` | | | postgresql.image.repository | string | `"zulip/zulip-postgresql"` | | | postgresql.image.tag | int | `14` | | -| postgresql.primary.containerSecurityContext.runAsUser | int | `0` | | +| postgresql.primary.containerSecurityContext.readOnlyRootFilesystem | bool | `false` | | +| postgresql.primary.containerSecurityContext.runAsGroup | int | `70` | | +| postgresql.primary.containerSecurityContext.runAsUser | int | `70` | | | rabbitmq.auth.username | string | `"zulip"` | | +| rabbitmq.image.repository | string | `"bitnamilegacy/rabbitmq"` | | +| rabbitmq.image.tag | string | `"4.1.3"` | | | rabbitmq.persistence.enabled | bool | `false` | | | redis.architecture | string | `"standalone"` | | +| redis.image.repository | string | `"bitnamilegacy/redis"` | | +| redis.image.tag | string | `"latest"` | | | redis.master.persistence.enabled | bool | `false` | | | resources | object | `{}` | | | securityContext | object | `{}` | | @@ -128,6 +137,7 @@ Now you're ready to follow [the installation instructions above](#installation). | zulip.environment.SETTING_EXTERNAL_HOST | string | `"zulip.example.com"` | | | zulip.environment.SETTING_ZULIP_ADMINISTRATOR | string | `"admin@example.com"` | | | zulip.environment.SSL_CERTIFICATE_GENERATION | string | `"self-signed"` | | +| zulip.environment.TRUST_GATEWAY_IP | bool | `true` | | | zulip.environment.ZULIP_AUTH_BACKENDS | string | `"EmailAuthBackend"` | | | zulip.persistence.accessMode | string | `"ReadWriteOnce"` | | | zulip.persistence.enabled | bool | `true` | | @@ -174,7 +184,7 @@ image, because it contains the Postgresql plugins that are needed to run Zulip. | Repository | Name | Version | |------------|------|---------| -| oci://registry-1.docker.io/bitnamicharts | memcached | 7.4.16 | -| oci://registry-1.docker.io/bitnamicharts | postgresql | 15.5.32 | -| oci://registry-1.docker.io/bitnamicharts | rabbitmq | 14.7.0 | -| oci://registry-1.docker.io/bitnamicharts | redis | 20.1.4 | +| oci://registry-1.docker.io/bitnamicharts | memcached | 8.1.5 | +| oci://registry-1.docker.io/bitnamicharts | postgresql | 18.1.8 | +| oci://registry-1.docker.io/bitnamicharts | rabbitmq | 16.0.14 | +| oci://registry-1.docker.io/bitnamicharts | redis | 23.2.12 | diff --git a/kubernetes/chart/zulip/ci/simple-values.yaml b/kubernetes/chart/zulip/ci/simple-values.yaml new file mode 100644 index 0000000000..034c164a51 --- /dev/null +++ b/kubernetes/chart/zulip/ci/simple-values.yaml @@ -0,0 +1,31 @@ +--- +image: + repository: ghcr.io/zulip/docker-zulip + pullPolicy: Never + # CI will pass tag: of the PR + +zulip: + password: set-secure-zulip-password + environment: + SETTING_ZULIP_ADMINISTRATOR: "admin@example.net" + SETTING_EXTERNAL_HOST: zulip.example.net + ZULIP_AUTH_BACKENDS: "EmailAuthBackend" + +memcached: + memcachedPassword: set-secure-memcached-password + +rabbitmq: + auth: + password: set-secure-rabbitmq-password + erlangCookie: set-secure-cookie-password + +redis: + auth: + password: set-secure-redis-password + +postgresql: + auth: + # # postgres admin user password + postgresqlPassword: set-secure-pg-postgres-password + # # postgres zulip user password + password: set-secure-pg-zulip-password diff --git a/kubernetes/chart/zulip/templates/_helpers.tpl b/kubernetes/chart/zulip/templates/_helpers.tpl index a6fbde30a8..9af393cfcd 100644 --- a/kubernetes/chart/zulip/templates/_helpers.tpl +++ b/kubernetes/chart/zulip/templates/_helpers.tpl @@ -71,7 +71,7 @@ include all env variables for Zulip pods - name: DB_HOST_PORT value: "{{ template "postgresql.v1.service.port" .Subcharts.postgresql }}" - name: DB_USER - value: "postgres" + value: "zulip" - name: SETTING_MEMCACHED_LOCATION value: "{{ template "common.names.fullname" .Subcharts.memcached }}:11211" - name: SETTING_RABBITMQ_HOST diff --git a/kubernetes/chart/zulip/templates/statefulset.yaml b/kubernetes/chart/zulip/templates/statefulset.yaml index d9f5bbc1b6..91470ca16e 100644 --- a/kubernetes/chart/zulip/templates/statefulset.yaml +++ b/kubernetes/chart/zulip/templates/statefulset.yaml @@ -68,7 +68,7 @@ spec: {{- if .Values.livenessProbe.enabled }} livenessProbe: httpGet: - path: / + path: /health port: http httpHeaders: - name: Host @@ -82,7 +82,7 @@ spec: {{- if .Values.startupProbe.enabled }} startupProbe: httpGet: - path: / + path: /health port: http httpHeaders: - name: Host diff --git a/kubernetes/chart/zulip/values.yaml b/kubernetes/chart/zulip/values.yaml index b11539b16d..27d45a3de0 100644 --- a/kubernetes/chart/zulip/values.yaml +++ b/kubernetes/chart/zulip/values.yaml @@ -22,10 +22,10 @@ image: ## Global Docker registry secret names as an array. imagePullSecrets: [] -## Partially override common.names.fullname template (will maintain the release name). +## Partially override zulip.fullname template (will maintain the release name). nameOverride: "" -## Fully override common.names.fullname template. +## Fully override zulip.fullname template. fullnameOverride: "" serviceAccount: @@ -131,19 +131,21 @@ zulip: ## certificates are managed by the Kubernetes cluster, so by ## default it's disabled inside the container. DISABLE_HTTPS: true + TRUST_GATEWAY_IP: true ## Set SSL certificate generation to self-signed because ## Kubernetes manages the client-facing SSL certs. SSL_CERTIFICATE_GENERATION: self-signed - ## Domain Zulip is hosted on. + ## Domain Zulip is hosted on; this is required SETTING_EXTERNAL_HOST: zulip.example.com - ## SMTP email password. - SECRETS_email_password: "123456789" + ## Administrator's email address; this is required SETTING_ZULIP_ADMINISTRATOR: "admin@example.com" + ## Outgoing email configuration SETTING_EMAIL_HOST: "" # e.g. smtp.example.com SETTING_EMAIL_HOST_USER: "noreply@example.com" SETTING_EMAIL_PORT: "587" SETTING_EMAIL_USE_SSL: "False" SETTING_EMAIL_USE_TLS: "True" + SECRETS_email_password: "123456789" ZULIP_AUTH_BACKENDS: "EmailAuthBackend" ## If `persistence.existingClaim` is not set, a PVC (Persistent ## Volume Claim) is generated with these specifications. @@ -196,11 +198,21 @@ postSetup: # mountPath: /data sidecars: [] +## Bitnami requires this to override PostgreSQL image +global: + security: + allowInsecureImages: true + ## PostgreSQL settings, see [Requirements](#Requirements). postgresql: primary: containerSecurityContext: - runAsUser: 0 + # 70 is the standard uid/gid of the "postgres" user in Alpine, which is + # used as the base for zulip/zulip-postgresql + # https://github.com/docker-library/postgres/blob/23987751b63ce745bd27b1119ab29dc4a1ffd728/Dockerfile-alpine.template#L7 + runAsUser: 70 + runAsGroup: 70 + readOnlyRootFilesystem: false ## We need to override the Postgresql image to get all the plugins Zulip needs image: repository: zulip/zulip-postgresql @@ -211,6 +223,10 @@ postgresql: ## Rabbitmq settings, see [Requirements](#Requirements). rabbitmq: + image: + # TODO: This image is no longer updated, and will need to be updated to one which is. + repository: bitnamilegacy/rabbitmq + tag: 4.1.3 auth: username: zulip ## Set this to true if you need the rabbitmq to be persistent @@ -219,10 +235,18 @@ rabbitmq: ## Memcached settings, see [Requirements](#Requirements). memcached: + image: + # TODO: This image is no longer updated, and will need to be updated to one which is. + repository: bitnamilegacy/memcached + tag: latest memcachedUsername: "zulip@localhost" ## Redis settings, see [Requirements](#Requirements). redis: + image: + # TODO: This image is no longer updated, and will need to be updated to one which is. + repository: bitnamilegacy/redis + tag: latest architecture: standalone master: persistence: diff --git a/kubernetes/manual/zulip-rc.yml b/kubernetes/manual/zulip-rc.yml index 52ef5b0b6d..efbdb95163 100644 --- a/kubernetes/manual/zulip-rc.yml +++ b/kubernetes/manual/zulip-rc.yml @@ -121,7 +121,7 @@ spec: - name: ZULIP_USER_PASS value: "123456789" - name: SECRETS_secret_key - value: "REPLCAE_WITH_SECURE_SECRET_KEY" + value: "REPLACE_WITH_SECURE_SECRET_KEY" ## These should match the passwords configured above - name: SECRETS_postgres_password value: "REPLACE_WITH_SECURE_POSTGRES_PASSWORD" diff --git a/lintconf.yaml b/lintconf.yaml new file mode 100644 index 0000000000..125bb08cb6 --- /dev/null +++ b/lintconf.yaml @@ -0,0 +1,5 @@ +--- +# This is consumed by `yamllint`, which is run by `ct lint` +rules: + comments: + min-spaces-from-content: 1 # Due to prettier: https://github.com/prettier/prettier/pull/10926 diff --git a/upgrade-postgresql b/upgrade-postgresql index 1b50f92ca5..0e5cef2f92 100755 --- a/upgrade-postgresql +++ b/upgrade-postgresql @@ -5,36 +5,36 @@ new_version=14 # Require the `yq` tool if ! command -v yq >/dev/null; then - echo "You must install the 'yq' tool to use this script." - exit 1 + echo "You must install the 'yq' tool to use this script." + exit 1 fi # Require docker compose 2.1.1 or higher, for `docker compose up --wait` docker_compose_version=$(docker compose --version --short) if [ "${docker_compose_version}" = "$(echo -e "2.1.0\n${docker_compose_version}" | sort -V | head -n1)" ]; then - echo "Your docker compose is too old (${docker_compose_version}); upgrade to at least 2.1.1." - exit 1 + echo "Your docker compose is too old (${docker_compose_version}); upgrade to at least 2.1.1." + exit 1 fi image=$(yq ".services.database.image" docker-compose.yml) if [[ $image =~ ^zulip/zulip-postgresql:([0-9]+)$ ]]; then - old_version="${BASH_REMATCH[1]}" + old_version="${BASH_REMATCH[1]}" else - echo "Unexpected PostgreSQL image: $image" - exit 1 + echo "Unexpected PostgreSQL image: $image" + exit 1 fi volume_mount=$(yq ".services.database.volumes.0" docker-compose.yml) if [[ "$volume_mount" =~ ^([^:]+):/var/lib/postgresql/data:rw$ ]]; then - old_mountpoint="${BASH_REMATCH[1]}" + old_mountpoint="${BASH_REMATCH[1]}" else - echo "Unexpected volume mount: $volume_mount" - exit 1 + echo "Unexpected volume mount: $volume_mount" + exit 1 fi if [ "$new_version" -eq "$old_version" ]; then - echo "PostgreSQL image is already version $new_version!" - exit 1 + echo "PostgreSQL image is already version $new_version!" + exit 1 fi # Create a new volume for the data; scope it with the current @@ -48,26 +48,26 @@ trap 'docker volume --force "$full_new_volume"' EXIT # Start a new PostgreSQL container of the right version to read in the # dumped database and write a new data dir on the new volume temp_container=$( - docker run -d \ - -e POSTGRES_DB=zulip \ - -e POSTGRES_USER=zulip \ - -e POSTGRES_PASSWORD=zulip \ - -v "$full_new_volume:/var/lib/postgresql/data:rw" \ - --health-cmd 'psql -U zulip -c "select 1"' \ - --health-interval 10s \ - "zulip/zulip-postgresql:$new_version" + docker run -d \ + -e POSTGRES_DB=zulip \ + -e POSTGRES_USER=zulip \ + -e POSTGRES_PASSWORD=zulip \ + -v "$full_new_volume:/var/lib/postgresql/data:rw" \ + --health-cmd 'psql -U zulip -c "select 1"' \ + --health-interval 10s \ + "zulip/zulip-postgresql:$new_version" ) trap 'docker rm --force "$temp_container"; docker volume rm --force "$full_new_volume"' EXIT # Wait for the new PostgreSQL container to become available tries=0 while [ "$(docker inspect --format='{{json .State.Health.Status}}' "$temp_container")" != '"healthy"' ]; do - tries=$((tries + 1)) - if [ "$tries" -gt 5 ]; then - echo "PostgreSQL $new_version container failed to start!" - exit 1 - fi - sleep 10 + tries=$((tries + 1)) + if [ "$tries" -gt 5 ]; then + echo "PostgreSQL $new_version container failed to start!" + exit 1 + fi + sleep 10 done # Ensure database is running @@ -76,19 +76,19 @@ docker compose up --wait database # Stop the zulip processes which talk to the database zulip_is_running=$(docker compose ps --filter status=running --services | grep zulip || true) if [ -n "$zulip_is_running" ]; then - docker compose stop zulip + docker compose stop zulip fi # Transfer the data to the new container -docker compose exec database pg_dumpall -U zulip | - docker exec -i "$temp_container" psql -U zulip +docker compose exec database pg_dumpall -U zulip \ + | docker exec -i "$temp_container" psql -U zulip if [ "$old_version" -eq "10" ]; then - # Upgrade MD5 password to SCRAM-SHA-256. We escape all 's by doubling them. - database_password=$(yq .services.database.environment.POSTGRES_PASSWORD docker-compose.yml | - perl -pe "s/'/''/g") - echo "ALTER USER zulip WITH PASSWORD '$database_password';" | - docker exec -i "$temp_container" psql -U zulip + # Upgrade MD5 password to SCRAM-SHA-256. We escape all 's by doubling them. + database_password=$(yq .services.database.environment.POSTGRES_PASSWORD docker-compose.yml \ + | perl -pe "s/'/''/g") + echo "ALTER USER zulip WITH PASSWORD '$database_password';" \ + | docker exec -i "$temp_container" psql -U zulip fi # Stop the running database