├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── bin └── .gitkeep ├── cids └── .gitkeep ├── containers └── .gitkeep ├── discourse-doctor ├── discourse-setup ├── image ├── README.md ├── auto_build.rb ├── base │ ├── Dockerfile │ ├── etc │ │ ├── cron.d │ │ │ └── cron.d_anacron │ │ ├── runit │ │ │ ├── 1 │ │ │ ├── 2 │ │ │ ├── 3 │ │ │ └── 1.d │ │ │ │ ├── 00-fix-var-logs │ │ │ │ ├── anacron │ │ │ │ └── cleanup-pids │ │ └── service │ │ │ ├── cron │ │ │ └── run │ │ │ └── rsyslog │ │ │ └── run │ ├── install-imagemagick │ ├── install-jemalloc │ ├── install-nginx │ ├── install-oxipng │ ├── install-redis │ ├── install-rust │ ├── nginx_public_keys.key │ ├── sbin │ │ └── boot │ └── thpoff.c ├── discourse_dev │ ├── Dockerfile │ ├── ensure-database │ ├── install-selenium │ ├── postgres_dev.template.yml │ └── sudoers.discourse └── discourse_test │ ├── Dockerfile │ └── install-chrome ├── launcher ├── samples ├── data.yml ├── mail-receiver.yml ├── redis.yml ├── standalone.yml └── web_only.yml ├── scripts └── launcher-completion.bash ├── shared └── .gitkeep ├── templates ├── README.md ├── cache-dns.template.yml ├── cloudflare.template.yml ├── cron.template.yml ├── enable-ruby-yjit.yml ├── fastly.template.yml ├── import │ ├── chrome-dep.template.yml │ ├── mbox.template.yml │ ├── mssql-dep.template.yml │ ├── mysql-dep.template.yml │ ├── phpbb3.template.yml │ └── vanilla.template.yml ├── offline-page.template.yml ├── postgres.10.template.yml ├── postgres.12.template.yml ├── postgres.13.template.yml ├── postgres.15.template.yml ├── postgres.9.5.template.yml ├── postgres.template.yml ├── redis.template.yml ├── sshd.template.yml ├── syslog.template.yml ├── web.china.template.yml ├── web.ipv6.template.yml ├── web.letsencrypt.ssl.template.yml ├── web.onion.template.yml ├── web.ratelimited.template.yml ├── web.socketed.template.yml ├── web.ssl.template.yml └── web.template.yml └── tests ├── README.md ├── run-all-tests ├── standalone ├── two-container └── update-old-templates /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | schedule: 7 | - cron: "0 0 * * *" 8 | 9 | concurrency: 10 | group: build-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }} 11 | cancel-in-progress: true 12 | 13 | env: 14 | BUILDKIT_PROGRESS: plain 15 | 16 | jobs: 17 | timestamp: 18 | runs-on: ubuntu-latest 19 | outputs: 20 | timestamp: ${{ steps.timestamp.outputs.timestamp }} 21 | steps: 22 | - id: timestamp 23 | run: | 24 | timestamp=`date +%Y%m%d-%H%M` 25 | echo "timestamp=$timestamp" 26 | echo "timestamp=$timestamp" >> $GITHUB_OUTPUT 27 | 28 | base: 29 | # `debian-12-8core` for amd64 builds 30 | # `ubuntu-24.04-8core-arm` for arm64 builds 31 | runs-on: ${{ (matrix.arch == 'amd64' && 'debian-12-8core') || 'ubuntu-24.04-8core-arm' }} 32 | strategy: 33 | matrix: 34 | arch: [amd64, arm64] 35 | timeout-minutes: ${{ (github.event_name == 'schedule' && 90) || 45 }} 36 | needs: timestamp 37 | env: 38 | TIMESTAMP: ${{ needs.timestamp.outputs.timestamp }} 39 | services: 40 | registry: 41 | image: registry:2 42 | ports: 43 | - 5000:5000 44 | steps: 45 | - uses: actions/checkout@v4 46 | with: 47 | fetch-depth: 1 48 | 49 | - name: build deps image 50 | working-directory: image 51 | run: | 52 | ruby auto_build.rb base_deps_${{ matrix.arch }} 53 | 54 | - name: build slim image for `main` branch 55 | working-directory: image 56 | run: | 57 | ruby auto_build.rb base_slim_main_${{ matrix.arch }} 58 | 59 | - name: tag slim image for `main` branch 60 | run: | 61 | docker tag discourse/base:build_slim_main_${{ matrix.arch }} discourse/base:2.0.${{ env.TIMESTAMP }}-slim-${{ matrix.arch }} 62 | 63 | - name: build slim image for `stable` branch 64 | working-directory: image 65 | run: | 66 | ruby auto_build.rb base_slim_stable_${{ matrix.arch }} 67 | 68 | - name: build release images for `main` branch 69 | working-directory: image 70 | run: | 71 | ruby auto_build.rb base_release_main_${{ matrix.arch }} 72 | 73 | # Temporarily build images with PG 15 so that we can ship the changes to the postgres templates in the same PR. 74 | - name: build release images for `main` branch with PG 15 75 | working-directory: image 76 | run: | 77 | ruby auto_build.rb base_release_main_${{ matrix.arch }}_pg_15 78 | 79 | - name: build release images for `stable` branch 80 | working-directory: image 81 | run: | 82 | ruby auto_build.rb base_release_stable_${{ matrix.arch }} 83 | 84 | - name: tag release images 85 | run: | 86 | docker tag discourse/base:build_release_main_${{ matrix.arch }} discourse/base:2.0.${{ env.TIMESTAMP }}-main-${{ matrix.arch }} 87 | docker tag discourse/base:build_release_main_${{ matrix.arch }}_pg_15 discourse/base:2.0.${{ env.TIMESTAMP }}-main-${{ matrix.arch }}-pg-15 88 | docker tag discourse/base:build_release_stable_${{ matrix.arch }} discourse/base:2.0.${{ env.TIMESTAMP }}-stable-${{ matrix.arch }} 89 | 90 | - name: build test_build image for `main` branch 91 | working-directory: image 92 | run: | 93 | ruby auto_build.rb discourse_test_build_${{ matrix.arch }} 94 | 95 | - name: run specs for `main` branch 96 | run: | 97 | docker run --rm -e RUBY_ONLY=1 -e USE_TURBO=1 -e SKIP_PLUGINS=1 -e SKIP_LINT=1 -e DISCOURSE_TURBO_RSPEC_RETRY_AND_LOG_FLAKY_TESTS=1 discourse/discourse_test:build_${{ matrix.arch }} 98 | 99 | - name: build & tag dev image for `main` branch 100 | working-directory: image 101 | run: | 102 | ruby auto_build.rb discourse_dev_${{ matrix.arch }} 103 | docker tag discourse/discourse_dev:build_${{ matrix.arch }} discourse/discourse_dev:${{ env.TIMESTAMP }}-${{ matrix.arch }} 104 | 105 | - name: Print summary 106 | run: | 107 | docker images discourse/base 108 | 109 | - name: Print `docker history` summary for main branch image 110 | run: | 111 | docker history discourse/base:2.0.${{ env.TIMESTAMP }}-main-${{ matrix.arch }} 112 | 113 | - name: Print compressed summary 114 | if: github.event_name == 'pull_request' && matrix.arch == 'amd64' 115 | run: | 116 | # Push to local repo to compare sizes 117 | docker tag discourse/base:2.0.${{ env.TIMESTAMP }}-slim-${{ matrix.arch }} localhost:5000/base:2.0.${{ env.TIMESTAMP }}-slim-${{ matrix.arch }} 118 | docker tag discourse/base:2.0.${{ env.TIMESTAMP }}-main-${{ matrix.arch }} localhost:5000/base:2.0.${{ env.TIMESTAMP }}-main-${{ matrix.arch }} 119 | docker push --quiet localhost:5000/base:2.0.${{ env.TIMESTAMP }}-slim-${{ matrix.arch }} 120 | docker push --quiet localhost:5000/base:2.0.${{ env.TIMESTAMP }}-main-${{ matrix.arch }} 121 | # multi-arch manifest is an array of schemas - [0] is amd64, [1] is arch64: Compare amd64. 122 | CURRENT_SLIM=$(docker manifest inspect -v discourse/base:slim | jq -r '.[0].SchemaV2Manifest.layers[] | .size / 1024 / 1024 | .*100 | round/100' | awk '{print $0; sum+= $0}; END {print sum}' | tail -n 1) 123 | CURRENT_RELEASE=$(docker manifest inspect -v discourse/base:release | jq -r '.[0].SchemaV2Manifest.layers[] | .size / 1024 / 1024 | .*100 | round/100' | awk '{print $0; sum+= $0}; END {print sum}' | tail -n 1) 124 | NEW_SLIM=$(docker manifest inspect -v --insecure localhost:5000/base:2.0.${{ env.TIMESTAMP }}-slim-${{ matrix.arch }} | jq -r '.SchemaV2Manifest.layers[] | .size / 1024 / 1024 | .*100 | round/100' | awk '{print $0; sum+= $0}; END {print sum}' | tail -n 1) 125 | NEW_RELEASE=$(docker manifest inspect -v --insecure localhost:5000/base:2.0.${{ env.TIMESTAMP }}-main-${{ matrix.arch }} | jq -r '.SchemaV2Manifest.layers[] | .size / 1024 / 1024 | .*100 | round/100' | awk '{print $0; sum+= $0}; END {print sum}' | tail -n 1) 126 | echo "current slim: ${CURRENT_SLIM}MB release: ${CURRENT_RELEASE}MB. new slim: ${NEW_SLIM}MB release: ${NEW_RELEASE}MB" 127 | 128 | - name: push to dockerhub 129 | if: github.ref == 'refs/heads/main' 130 | env: 131 | DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} 132 | run: | 133 | docker login --username discoursebuild --password $DOCKERHUB_PASSWORD 134 | docker push discourse/base:2.0.${{ env.TIMESTAMP }}-slim-${{ matrix.arch }} 135 | docker push discourse/base:2.0.${{ env.TIMESTAMP }}-main-${{ matrix.arch }} 136 | docker push discourse/base:2.0.${{ env.TIMESTAMP }}-main-${{ matrix.arch }}-pg-15 137 | docker push discourse/base:2.0.${{ env.TIMESTAMP }}-stable-${{ matrix.arch }} 138 | docker push discourse/discourse_dev:${{ env.TIMESTAMP }}-${{ matrix.arch }} 139 | 140 | - name: Push discourse/base:aarch64 image for backwards compatibility 141 | if: (github.ref == 'refs/heads/main') && (matrix.arch == 'arm64') 142 | run: | 143 | docker tag discourse/base:2.0.${{ env.TIMESTAMP }}-main-${{ matrix.arch }} discourse/base:aarch64 144 | docker push discourse/base:aarch64 145 | push_multi_arch_manifests: 146 | runs-on: ubuntu-latest 147 | needs: [base, timestamp] 148 | env: 149 | TIMESTAMP: ${{ needs.timestamp.outputs.timestamp }} 150 | if: github.ref == 'refs/heads/main' 151 | steps: 152 | - name: create and push multi-arch manifests 153 | run: | 154 | docker login --username discoursebuild --password ${{ secrets.DOCKERHUB_PASSWORD }} 155 | 156 | # Slim timestamped 157 | docker manifest create discourse/base:2.0.${{ env.TIMESTAMP }}-slim \ 158 | -a discourse/base:2.0.${{ env.TIMESTAMP }}-slim-amd64 \ 159 | -a discourse/base:2.0.${{ env.TIMESTAMP }}-slim-arm64 160 | 161 | # Slim release 162 | docker manifest create discourse/base:slim \ 163 | -a discourse/base:2.0.${{ env.TIMESTAMP }}-slim-amd64 \ 164 | -a discourse/base:2.0.${{ env.TIMESTAMP }}-slim-arm64 165 | 166 | # Full Discourse `main` branch timestamped 167 | docker manifest create discourse/base:2.0.${{ env.TIMESTAMP }} \ 168 | -a discourse/base:2.0.${{ env.TIMESTAMP }}-main-amd64 \ 169 | -a discourse/base:2.0.${{ env.TIMESTAMP }}-main-arm64 170 | 171 | # Full Discourse `main` branch timestamped with PG 15 172 | docker manifest create discourse/base:2.0.${{ env.TIMESTAMP }}-pg-15 \ 173 | -a discourse/base:2.0.${{ env.TIMESTAMP }}-main-amd64-pg-15 \ 174 | -a discourse/base:2.0.${{ env.TIMESTAMP }}-main-arm64-pg-15 175 | 176 | # Full Discourse `stable` branch timestamped 177 | docker manifest create discourse/base:2.0.${{ env.TIMESTAMP }}-stable \ 178 | -a discourse/base:2.0.${{ env.TIMESTAMP }}-stable-amd64 \ 179 | -a discourse/base:2.0.${{ env.TIMESTAMP }}-stable-arm64 180 | 181 | # Full Discourse `main` branch release 182 | docker manifest create discourse/base:release \ 183 | -a discourse/base:2.0.${{ env.TIMESTAMP }}-main-amd64 \ 184 | -a discourse/base:2.0.${{ env.TIMESTAMP }}-main-arm64 185 | 186 | # Full Discourse `stable` branch release 187 | docker manifest create discourse/base:release-stable \ 188 | -a discourse/base:2.0.${{ env.TIMESTAMP }}-stable-amd64 \ 189 | -a discourse/base:2.0.${{ env.TIMESTAMP }}-stable-arm64 190 | 191 | # Dev timestamped 192 | docker manifest create discourse/discourse_dev:${{ env.TIMESTAMP }} \ 193 | -a discourse/discourse_dev:${{ env.TIMESTAMP }}-amd64 \ 194 | -a discourse/discourse_dev:${{ env.TIMESTAMP }}-arm64 195 | 196 | # Dev release 197 | docker manifest create discourse/discourse_dev:release \ 198 | -a discourse/discourse_dev:${{ env.TIMESTAMP }}-amd64 \ 199 | -a discourse/discourse_dev:${{ env.TIMESTAMP }}-arm64 200 | 201 | docker manifest push discourse/base:2.0.${{ env.TIMESTAMP }}-slim 202 | docker manifest push discourse/base:slim 203 | docker manifest push discourse/base:2.0.${{ env.TIMESTAMP }} 204 | docker manifest push discourse/base:2.0.${{ env.TIMESTAMP }}-pg-15 205 | docker manifest push discourse/base:2.0.${{ env.TIMESTAMP }}-stable 206 | docker manifest push discourse/base:release 207 | docker manifest push discourse/base:release-stable 208 | docker manifest push discourse/discourse_dev:${{ env.TIMESTAMP }} 209 | docker manifest push discourse/discourse_dev:release 210 | test: 211 | runs-on: debian-12-8core 212 | timeout-minutes: 30 213 | needs: base 214 | defaults: 215 | run: 216 | working-directory: image/discourse_test 217 | steps: 218 | - uses: actions/checkout@v4 219 | with: 220 | fetch-depth: 1 221 | - name: build discourse_test:slim 222 | run: | 223 | docker buildx build . --load \ 224 | --build-arg from_tag=slim \ 225 | --target base \ 226 | --tag discourse/discourse_test:slim 227 | - name: build discourse_test:slim-browsers 228 | run: | 229 | docker buildx build . --load \ 230 | --build-arg from_tag=slim \ 231 | --target with_browsers \ 232 | --tag discourse/discourse_test:slim-browsers 233 | - name: build discourse_test:release 234 | run: | 235 | docker buildx build . --load \ 236 | --build-arg from_tag=release \ 237 | --target release \ 238 | --tag discourse/discourse_test:release 239 | - name: Print summary 240 | run: | 241 | docker images discourse/discourse_test 242 | - name: push to dockerhub 243 | if: success() && (github.ref == 'refs/heads/main') 244 | env: 245 | DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} 246 | run: | 247 | docker login --username discoursebuild --password $DOCKERHUB_PASSWORD 248 | docker push discourse/discourse_test:slim 249 | docker push discourse/discourse_test:slim-browsers 250 | docker push discourse/discourse_test:release 251 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | shared/* 2 | *.swp 3 | *~ 4 | \#*\# 5 | containers/* 6 | cids/* 7 | bin/* 8 | image/.build.out 9 | image/.build.hash 10 | image/.docker_temp_* 11 | image/img.tar 12 | image/squash.tar 13 | image/nsenter/nsenter 14 | image/docker-squash 15 | image/docker-squash.tar.gz 16 | image/discourse_dev/install-rust 17 | image/discourse_dev/postgres.template.yml 18 | image/discourse_dev/redis.template.yml 19 | .gc-state/* 20 | .vagrant/ 21 | tmp/* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Civilized Discourse Construction Kit, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Discourse Docker images 2 | 3 | ## About 4 | 5 | - [Docker](https://docker.com/) is an open source project to pack, ship and run any Linux application in a lighter weight, faster container than a traditional virtual machine. 6 | 7 | - Docker makes it much easier to deploy [a Discourse forum](https://github.com/discourse/discourse) on your servers and keep it updated. For background, see [Sam's blog post](http://samsaffron.com/archive/2013/11/07/discourse-in-a-docker-container). 8 | 9 | - The templates and base image configure Discourse with the Discourse team's recommended optimal defaults. 10 | 11 | ## Getting Started 12 | 13 | > Did you know that Discourse also offers affordable hosting options? All of our hosting services directly support the development of Discourse. 14 | > 15 | > 👉 [Learn more about Discourse hosting](https://discourse.org/pricing) 16 | 17 | The easiest way to get started with self-hosting Discourse is via the standalone template, which can be installed in 30 minutes or less. 18 | 19 | For detailed install instructions, check out: 20 | https://github.com/discourse/discourse/blob/main/docs/INSTALL-cloud.md 21 | 22 | ## Directory Structure 23 | 24 | ### `/cids` 25 | 26 | Contains container ids for currently running Docker containers. cids are Docker's "equivalent" of pids. Each container will have a unique git like hash. 27 | 28 | ### `/containers` 29 | 30 | This directory is for container definitions for your various Discourse containers. You are in charge of this directory, it ships empty. 31 | 32 | ### `/samples` 33 | 34 | Sample container definitions you may use to bootstrap your environment. You can copy templates from here into the containers directory. 35 | 36 | ### `/shared` 37 | 38 | Placeholder spot for shared volumes with various Discourse containers. You may elect to store certain persistent information outside of a container, in our case we keep various logfiles and upload directory outside. This allows you to rebuild containers easily without losing important information. Keeping uploads outside of the container allows you to share them between multiple web instances. 39 | 40 | ### `/templates` 41 | 42 | [pups](https://github.com/discourse/pups)-managed templates you may use to bootstrap your environment. 43 | 44 | ### `/image` 45 | 46 | Dockerfiles for Discourse; see [the README](image/README.md) for further details. 47 | 48 | The Docker repository will always contain the latest built version at: https://hub.docker.com/r/discourse/base/, you should not need to build the base image. 49 | 50 | ## Launcher 51 | 52 | The base directory contains a single bash script which is used to manage containers. You can use it to "bootstrap" a new container, enter, start, stop and destroy a container. 53 | 54 | ``` 55 | Usage: launcher COMMAND CONFIG [--skip-prereqs] [--docker-args STRING] 56 | Commands: 57 | start: Start/initialize a container 58 | stop: Stop a running container 59 | restart: Restart a container 60 | destroy: Stop and remove a container 61 | enter: Open a shell to run commands inside the container 62 | logs: View the Docker logs for a container 63 | bootstrap: Bootstrap a container for the config based on a template 64 | run: Run the given command with the config in the context of the last bootstrapped image 65 | rebuild: Rebuild a container (destroy old, bootstrap, start new) 66 | cleanup: Remove all containers that have stopped for > 24 hours 67 | start-cmd: Generate docker command used to start container 68 | ``` 69 | 70 | If the environment variable "SUPERVISED" is set to true, the container won't be detached, allowing a process monitoring tool to manage the restart behaviour of the container. 71 | 72 | ## Container Configuration 73 | 74 | The beginning of the container definition can contain the following "special" sections: 75 | 76 | ### templates: 77 | 78 | ```yaml 79 | templates: 80 | - 'templates/cron.template.yml' 81 | - 'templates/postgres.template.yml' 82 | ``` 83 | 84 | This template is "composed" out of all these child templates, this allows for a very flexible configuration structure. Furthermore you may add specific hooks that extend the templates you reference. 85 | 86 | ### expose: 87 | 88 | ```yaml 89 | expose: 90 | - '2222:22' 91 | - '127.0.0.1:20080:80' 92 | ``` 93 | 94 | Publish port 22 inside the container on port 2222 on ALL local host interfaces. In order to bind to only one interface, you may specify the host's IP address as `([:[host_port]])|():[/udp]` as defined in the [docker port binding documentation](http://docs.docker.com/userguide/dockerlinks/). To expose a port without publishing it, specify only the port number (e.g., `80`). 95 | 96 | ### volumes: 97 | 98 | ```yaml 99 | volumes: 100 | - volume: 101 | host: /var/discourse/shared 102 | guest: /shared 103 | ``` 104 | 105 | Expose a directory inside the host to the container. 106 | 107 | ### links: 108 | 109 | ```yaml 110 | links: 111 | - link: 112 | name: postgres 113 | alias: postgres 114 | ``` 115 | 116 | Links another container to the current container. This will add `--link postgres:postgres` 117 | to the options when running the container. 118 | 119 | ### environment variables: 120 | 121 | Setting environment variables to the current container. 122 | 123 | ```yaml 124 | env: 125 | DISCOURSE_DB_HOST: some-host 126 | DISCOURSE_DB_NAME: '{{config}}_discourse' 127 | ``` 128 | 129 | The above will add `-e DISCOURSE_DB_HOST=some-host -e DISCOURSE_DB_NAME=app_discourse` to the options when running the container. 130 | 131 | ### labels: 132 | 133 | ```yaml 134 | labels: 135 | monitor: 'true' 136 | app_name: '{{config}}_discourse' 137 | ``` 138 | 139 | Add labels to the current container. The above will add `--l monitor=true -l app_name=dev_discourse` to the options 140 | when running the container 141 | 142 | ## Upgrading Discourse 143 | 144 | The Docker setup gives you multiple upgrade options: 145 | 146 | 1. Use the front end at http://yoursite.com/admin/upgrade to upgrade an already running image. 147 | 148 | 2. Create a new base image manually by running: 149 | `./launcher rebuild my_image` 150 | 151 | ## Single Container vs. Multiple Containers 152 | 153 | The samples directory contains a standalone template. This template bundles all of the software required to run Discourse into a single container. The advantage is that it is easy. 154 | 155 | The multiple container configuration setup is far more flexible and robust, however it is also more complicated to set up. A multiple container setup allows you to: 156 | 157 | - Minimize downtime when upgrading to new versions of Discourse. You can bootstrap new web processes while your site is running and only after it is built, switch the new image in. 158 | - Scale your forum to multiple servers. 159 | - Add servers for redundancy. 160 | - Have some required services (e.g. the database) run on beefier hardware. 161 | 162 | If you want a multiple container setup, see the `data.yml` and `web_only.yml` templates in the samples directory. To ease this process, `launcher` will inject an env var called `DISCOURSE_HOST_IP` which will be available inside the image. 163 | 164 | WARNING: In a multiple container configuration, _make sure_ you setup iptables or some other firewall to protect various ports (for postgres/redis). 165 | On Ubuntu, install the `ufw` or `iptables-persistent` package to manage firewall rules. 166 | 167 | ## Email 168 | 169 | For a Discourse instance to function properly Email must be set up. Use the `SMTP_URL` env var to set your SMTP address, see sample templates for an example. The Docker image does not contain postfix, exim or another MTA, it was omitted because it is very tricky to set up correctly. 170 | 171 | ## Troubleshooting 172 | 173 | View the container logs: `./launcher logs my_container` 174 | 175 | Spawn a shell inside your container using `./launcher enter my_container`. This is the most foolproof method if you have host root access. 176 | 177 | If you see network errors trying to retrieve code from `github.com` or `rubygems.org` try again - sometimes there are temporary interruptions and a retry is all it takes. 178 | 179 | Behind a proxy network with no direct access to the Internet? Add proxy information to the container environment by adding to the existing `env` block in the `container.yml` file: 180 | 181 | ```yaml 182 | env: 183 | …existing entries… 184 | HTTP_PROXY: http://proxyserver:port/ 185 | http_proxy: http://proxyserver:port/ 186 | HTTPS_PROXY: http://proxyserver:port/ 187 | https_proxy: http://proxyserver:port/ 188 | ``` 189 | 190 | ## Security 191 | 192 | Directory permissions in Linux are UID/GID based, if your numeric IDs on the 193 | host do not match the IDs in the guest, permissions will mismatch. On clean 194 | installs you can ensure they are in sync by looking at `/etc/passwd` and 195 | `/etc/group`, the Discourse account will have UID 1000. 196 | 197 | ## Advanced topics 198 | 199 | - [Setting up SSL with Discourse Docker](https://meta.discourse.org/t/allowing-ssl-for-your-discourse-docker-setup/13847) 200 | - [Multisite configuration with Docker](https://meta.discourse.org/t/multisite-configuration-with-docker/14084) 201 | - [Linking containers for a multiple container setup](https://meta.discourse.org/t/linking-containers-for-a-multiple-container-setup/20867) 202 | - [Using Rubygems mirror to improve connection problem in China](https://meta.discourse.org/t/replace-rubygems-org-with-taobao-mirror-to-resolve-network-error-in-china/21988/1) 203 | 204 | ## License 205 | 206 | MIT 207 | -------------------------------------------------------------------------------- /bin/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discourse/discourse_docker/9ebce86fda3d9787d752de6311df15dcab9700f1/bin/.gitkeep -------------------------------------------------------------------------------- /cids/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discourse/discourse_docker/9ebce86fda3d9787d752de6311df15dcab9700f1/cids/.gitkeep -------------------------------------------------------------------------------- /containers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discourse/discourse_docker/9ebce86fda3d9787d752de6311df15dcab9700f1/containers/.gitkeep -------------------------------------------------------------------------------- /discourse-doctor: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | LOG_FILE="/tmp/discourse-debug.txt" 3 | WORKING_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | log() { 6 | if [ "$1" == "-e" ] 7 | then 8 | shift 9 | echo -e "$*" | tee -a "$LOG_FILE" 10 | else 11 | echo "$*" | tee -a "$LOG_FILE" 12 | fi 13 | } 14 | 15 | check_root() { 16 | if [[ $EUID -ne 0 ]]; then 17 | log "This script must be run as root. Please sudo or log in as root first." 1>&2 18 | exit 1 19 | fi 20 | } 21 | 22 | ## 23 | ## Check whether a connection to HOSTNAME ($1) on PORT ($2) is possible 24 | ## 25 | connect_to_port() { 26 | HOST="$1" 27 | PORT="$2" 28 | VERIFY=$(date +%s | sha256sum | base64 | head -c 20) 29 | echo -e "HTTP/1.1 200 OK\n\n $VERIFY" | nc -w 4 -l -p $PORT >/dev/null 2>&1 & 30 | if curl --proto =http -s $HOST:$PORT --connect-timeout 3 | grep $VERIFY >/dev/null 2>&1 & 31 | then 32 | return 0 33 | else 34 | return 1 35 | fi 36 | } 37 | 38 | check_ip_match() { 39 | HOST="$1" 40 | log 41 | log Checking your domain name . . . 42 | if connect_to_port $HOST 443 43 | then 44 | log 45 | log "Connection to $HOST succeeded." 46 | else 47 | log WARNING:: This server does not appear to be accessible at $HOST:443. 48 | log 49 | if connect_to_port $HOST 80 50 | then 51 | log A connection to port 80 succeeds, however. 52 | log This suggests that your DNS settings are correct, 53 | log but something is keeping traffic to port 443 from getting to your server. 54 | log Check your networking configuration to see that connections to port 443 are allowed. 55 | else 56 | log "A connection to http://$HOST (port 80) also fails." 57 | log 58 | log This suggests that $HOST resolves to the wrong IP address 59 | log or that traffic is not being routed to your server. 60 | fi 61 | log 62 | log Google: \"open ports YOUR CLOUD SERVICE\" for information for resolving this problem. 63 | log 64 | log This test might not work for all situations, 65 | log so if you can access Discourse at http://$HOST, this might not indicate a problem. 66 | sleep 3 67 | fi 68 | } 69 | 70 | check_docker_is_installed() { 71 | log -e "\n==================== DOCKER INFO ====================" 72 | docker_path="$(which docker.io || which docker)" 73 | if [ -z $docker_path ]; then 74 | log "Docker is not installed. Have you installed Discourse at all?" 75 | log "Perhaps you're looking for ./discourse-setup ." 76 | log "There is no point in continuing." 77 | exit 78 | else 79 | log -e "DOCKER VERSION: $(docker --version)" 80 | log -e "\nDOCKER PROCESSES (docker ps -a)\n\n$(sudo docker ps -a)\n" 81 | fi 82 | } 83 | 84 | get_OS() { 85 | log -e "OS: $(uname -s)" 86 | } 87 | 88 | check_disk_and_memory() { 89 | log -e "\n\n==================== MEMORY INFORMATION ====================" 90 | os_type=$(get_OS) 91 | if [ "$os_type" == "Darwin" ]; then 92 | log -e "RAM: $( free -m | awk '/Mem:/ {print $2}' ) \n" 93 | else 94 | log -e "RAM (MB): $( free -m --si | awk ' /Mem:/ {print $2} ')\n" 95 | fi 96 | log "$(free -m)" 97 | 98 | log -e "\n==================== DISK SPACE CHECK ====================" 99 | log "---------- OS Disk Space ----------" 100 | log "$(df -h / /var/discourse /var/lib/docker /var/lib/docker/* | uniq)" 101 | 102 | if [ "$version" != "NOT FOUND" ] 103 | then 104 | log 105 | log "---------- Container Disk Space ----------" 106 | log "$(sudo docker exec -w /var/www/discourse -i $app_name df -h / /shared/ /shared/postgres_data /shared/redis_data /shared/backups /var/log | uniq)" 107 | fi 108 | 109 | log -e "\n==================== DISK INFORMATION ====================" 110 | log "$( fdisk -l )" 111 | log -e "\n==================== END DISK INFORMATION ====================" 112 | 113 | free_disk="$(df /var | tail -n 1 | awk '{print $4}')" 114 | # Arguably ./launcher is doing this so discourse-doctor does not need to . . . 115 | if [ "$free_disk" -lt 5000 ]; then 116 | log "\n\n==================== DISK SPACE PROBLEM ====================" 117 | log "WARNING: you appear to have very low disk space." 118 | log "This could be the cause of problems running your site." 119 | log "Please free up some space, or expand your disk, before continuing." 120 | log 121 | log "Run \'apt-get autoremove && apt-get autoclean\' to clean up unused" 122 | log "packages and \'./launcher cleanup\' to remove stale Docker containers." 123 | exit 1 124 | fi 125 | } 126 | 127 | get_discourse_version() { 128 | version="" 129 | version=$(wget -q --timeout=3 https://$VERSION_HOSTNAME/privacy -O -|grep generator|head -1 |cut -d "=" -f 3|cut -d '-' -f 1 |cut -d '"' -f 2) &> /dev/null 130 | if ! echo $version | grep Discourse 131 | then 132 | version=$(wget -q --timeout=3 http://$VERSION_HOSTNAME/privacy -O -|grep generator|head -1 |cut -d "=" -f 3|cut -d '-' -f 1 |cut -d '"' -f 2) &> /dev/null 133 | fi 134 | if [ -z "$version" ] 135 | then 136 | version="NOT FOUND" 137 | fi 138 | log "Discourse version at $VERSION_HOSTNAME: $version" 139 | } 140 | 141 | check_if_hostname_resolves_here() { 142 | log "========================================" 143 | VERSION_HOSTNAME=$DISCOURSE_HOSTNAME 144 | get_discourse_version 145 | DISCOURSE_VERSION="$version" 146 | VERSION_HOSTNAME=localhost 147 | get_discourse_version 148 | LOCALHOST_VERSION="$version" 149 | if [ "$DISCOURSE_VERSION" != "$LOCALHOST_VERSION" ] 150 | then 151 | log "==================== DNS PROBLEM ====================" 152 | log "This server reports $LOCALHOST_VERSION, but $DISCOURSE_HOSTNAME reports $DISCOURSE_VERSION." 153 | log "This suggests that you have a DNS problem or that an intermediate proxy is to blame." 154 | log "If you are using Cloudflare, or a CDN, it may be improperly configured." 155 | fi 156 | } 157 | 158 | ## 159 | ## get discourse configuration values from YML file 160 | ## 161 | get_discourse_config() { 162 | log -e "\n==================== YML SETTINGS ====================" 163 | read_config "DISCOURSE_HOSTNAME" 164 | DISCOURSE_HOSTNAME=$read_config_result 165 | log DISCOURSE_HOSTNAME=$DISCOURSE_HOSTNAME 166 | read_config "DISCOURSE_SMTP_ADDRESS" 167 | SMTP_ADDRESS=$read_config_result 168 | log SMTP_ADDRESS=$SMTP_ADDRESS 169 | read_config "DISCOURSE_DEVELOPER_EMAILS" 170 | DEVELOPER_EMAILS=$read_config_result 171 | log DEVELOPER_EMAILS=$DEVELOPER_EMAILS 172 | read_config "DISCOURSE_SMTP_PASSWORD" 173 | SMTP_PASSWORD=$read_config_result 174 | log SMTP_PASSWORD=$read_config_result 175 | read_config "DISCOURSE_SMTP_PORT" 176 | SMTP_PORT=$read_config_result 177 | log SMTP_PORT=$read_config_result 178 | read_config "DISCOURSE_SMTP_USER_NAME" 179 | SMTP_USER_NAME=$read_config_result 180 | log SMTP_USER_NAME=$read_config_result 181 | read_config "LETSENCRYPT_ACCOUNT_EMAIL" 182 | letsencrypt_account_email=$read_config_result 183 | log "LETSENCRYPT_ACCOUNT_EMAIL=$letsencrypt_account_email" 184 | } 185 | 186 | check_plugins() { 187 | log -e "\n\n==================== PLUGINS ====================" 188 | log -e "$(grep 'git clone' containers/$app_name.yml)" 189 | grep git containers/$app_name.yml > /tmp/$PPID.grep 190 | 191 | if grep -cv "github.com/discourse" /tmp/$PPID.grep > /dev/null 192 | then 193 | log -e "\nWARNING:" 194 | log You have what appear to be non-official plugins. 195 | log "If you are having trouble, you should disable them and try rebuilding again." 196 | else 197 | log -e "\nNo non-official plugins detected." 198 | fi 199 | log -e "\nSee https://github.com/discourse/discourse/blob/main/lib/plugin/metadata.rb for the official list.\n" 200 | } 201 | 202 | dump_yaml() { 203 | log -e "\n\n==================== YML DUMP ====================" 204 | log Dumping $app_name.yml 205 | log -e "\n\n" 206 | } 207 | 208 | ## 209 | ## read a variable from the config file and stick it in read_config_result 210 | ## 211 | read_config() { 212 | config_line=$(grep -E "^ #?$1:" $web_file) 213 | read_config_result=$(echo $config_line | awk -F ":" '{print $2}') 214 | read_config_result=$(echo $read_config_result | sed "s/^\([\"']\)\(.*\)\1\$/\2/g") 215 | } 216 | 217 | ## 218 | ## call rake emails:test inside the container 219 | ## 220 | check_email() { 221 | log -e "\n==================== MAIL TEST ====================" 222 | log "For a robust test, get an address from http://www.mail-tester.com/" 223 | echo "Or just send a test message to yourself." 224 | EMAIL=$(echo $DEVELOPER_EMAILS |cut -d , -f 1) 225 | read -p "Email address for mail test? ('n' to skip) [$EMAIL]: " new_value 226 | if [ ! -z "$new_value" ] 227 | then 228 | EMAIL="$new_value" 229 | fi 230 | if [ "$new_value" != "n" ] && [ "$new_value" != "N" ] 231 | then 232 | log "Sending mail to $EMAIL. . . " 233 | log "$(sudo docker exec -w /var/www/discourse -i $app_name rake emails:test[$EMAIL])" 234 | else 235 | log "Mail test skipped." 236 | fi 237 | } 238 | 239 | get_yml_file() { 240 | app_name="" 241 | if [ -f containers/app.yml ] 242 | then 243 | app_name="app" 244 | web_file=containers/$app_name.yml 245 | log "Found $web_file" 246 | elif [ -f containers/web_only.yml ] 247 | then 248 | log "YML=web_only.yml" 249 | app_name="web_only" 250 | web_file=containers/$app_name.yml 251 | log "Found $web_file" 252 | else 253 | log "Can't find app.yml or web_only.yml." 254 | log "Giving up." 255 | exit 256 | fi 257 | } 258 | 259 | check_docker() { 260 | docker ps | tail -n +2 > /tmp/$UUID-docker.txt 261 | 262 | if grep $app_name /tmp/$UUID-docker.txt 263 | then 264 | log -e "\nDiscourse container $app_name is running" 265 | else 266 | log "==================== SERIOUS PROBLEM!!!! ====================" 267 | log "$app_name not running!" 268 | log "Attempting to rebuild" 269 | log "==================== REBUILD LOG ====================" 270 | # too hard to pass STDERR of ./launcher to log() 271 | ./launcher rebuild $app_name 2>&1 | tee -a $LOG_FILE 272 | log "==================== END REBUILD LOG ====================" 273 | docker ps | tail -n +2 > /tmp/$UUID-docker.txt 274 | if grep $app_name /tmp/$UUID-docker.txt 275 | then 276 | log -e "\nDiscourse container $app_name is now running." 277 | log ". . . waiting 30 seconds for container to crank up. . . " 278 | sleep 30 279 | else 280 | log "Failed to rebuild $app_name." 281 | # check_ip_match checks if curl to $DISCOURSE_HOSTNAME gets to this server 282 | # It works only if ports 80 and 443 are free 283 | check_ip_match $DISCOURSE_HOSTNAME 284 | log "You should probably remove any non-standard plugins and rebuild." 285 | NO_CONTAINER='y' 286 | log "Attempting to restart existing container. . . " 287 | ./launcher start $app_name 2>&1 | tee -a $LOG_FILE 288 | docker ps | tail -n +2 > /tmp/$UUID-docker.txt 289 | if grep $app_name /tmp/$UUID-docker.txt 290 | then 291 | log "Restarted the container." 292 | NO_CONTAINER='n' 293 | else 294 | log "Failed to restart the container." 295 | fi 296 | fi 297 | fi 298 | } 299 | 300 | ## 301 | ## redact passwords and email addresses from log file 302 | ## 303 | clean_up_log_file() { 304 | for VAR 305 | in SMTP_PASSWORD LETSENCRYPT_ACCOUNT_EMAIL DEVELOPER_EMAILS DISCOURSE_DB_PASSWORD 'Sending mail to' 306 | do 307 | echo "Replacing: $VAR" 308 | sed -i -e 's/'"$VAR"'\([=: ]\)\S*/'"$VAR"'\1REDACTED /g' $LOG_FILE 309 | done 310 | } 311 | 312 | print_done() { 313 | log 314 | log "==================== DONE! ====================" 315 | DOCTOR_FILE=$(date +%s | sha256sum | base64 | head -c 20).txt 316 | 317 | if [ $app_name == 'app' ] && [ "$NO_CONTAINER" != 'y' ]; then 318 | read -p "Would you like to serve a publicly available version of this file? (Y/n) " serve 319 | case "${serve:-Y}" in 320 | y*|Y*) 321 | cp $LOG_FILE shared/standalone/log/var-log/$DOCTOR_FILE 322 | sudo docker exec -w /var/www/discourse -i $app_name cp /var/log/$DOCTOR_FILE public 323 | log "The output of this program may be available at http://$DISCOURSE_HOSTNAME/$DOCTOR_FILE" 324 | log "You should inspect that file carefully before sharing the URL." 325 | ;; 326 | *) 327 | log "Publicly available log not generated." 328 | ;; 329 | esac 330 | fi 331 | # The following is not in the web log file since it was copied above, which seems correct 332 | log 333 | log "You can examine the output of this script with " 334 | log "LESS=-Ri less $LOG_FILE" 335 | log 336 | log "BUT FIRST, make sure that you know the first three commands below!!!" 337 | log 338 | log "Commands to know when viewing the file with the above command (called 'less'): " 339 | log "q -- quit" 340 | log "/error -- search for the word 'error'" 341 | log "n -- search for the next occurrence" 342 | log "g -- go to the beginning of the file" 343 | log "f -- go forward a page" 344 | log "b -- go back a page" 345 | log "G -- go to the end of the file" 346 | } 347 | 348 | initialize_log_file() { 349 | rm -f $LOG_FILE 350 | touch $LOG_FILE 351 | log DISCOURSE DOCTOR $(date) 352 | log -e "OS: $(uname -a)\n\n" 353 | } 354 | 355 | ## 356 | ## END FUNCTION DECLARATION 357 | ## 358 | 359 | check_root 360 | cd $WORKING_DIR || exit 361 | initialize_log_file 362 | get_yml_file 363 | get_discourse_config 364 | check_docker_is_installed 365 | check_docker 366 | check_plugins 367 | check_if_hostname_resolves_here 368 | check_disk_and_memory 369 | check_email 370 | clean_up_log_file 371 | print_done 372 | -------------------------------------------------------------------------------- /image/README.md: -------------------------------------------------------------------------------- 1 | # Docker images 2 | 3 | ## Building new images 4 | 5 | To build a new image, just run `ruby auto_build.rb image-name`. The build process will build a local image with a predefined tag. 6 | 7 | Images and tag names are defined [here](https://github.com/discourse/discourse_docker/blob/main/image/auto_build.rb#L6-L88). 8 | 9 | > **A note about --squash**: By default we squash the images we serve on Docker Hub. You will need to [enable experimental features](https://github.com/docker/docker-ce/blob/master/components/cli/experimental/README.md) on your Docker daemon for that. 10 | 11 | 12 | ## More about the images 13 | 14 | See both `auto_build.rb` and the respective `Dockerfile`s for details on _how_ all of this happens. 15 | 16 | 17 | ### base ([discourse/base](https://hub.docker.com/r/discourse/base/)) 18 | 19 | All of the dependencies for running Discourse. This includes runit, postgres, nginx, ruby, imagemagick, etc. It also includes the creation of the "discourse" user and `/var/www` directory. 20 | 21 | 22 | ### discourse_dev ([discourse/discourse_dev](https://hub.docker.com/r/discourse/discourse_dev/)) 23 | 24 | Adds redis and postgres just like the "standalone" template for Discourse in order to have an all-in-one container for development. Note that you are expected to mount your local discourse source directory to `/src`. See [the README in GitHub's discourse/bin/docker](https://github.com/discourse/discourse/tree/main/bin/docker/) for utilities that help with this. 25 | 26 | Note that the discourse user is granted "sudo" permission without asking for a password in the discourse_dev image. This is to facilitate the command-line Docker tools in discourse proper that run commands as the discourse user. 27 | 28 | 29 | ### discourse_test ([discourse/discourse_test](https://hub.docker.com/r/discourse/discourse_test/)) 30 | 31 | Builds on the discourse image and adds testing tools and a default testing entrypoint. 32 | -------------------------------------------------------------------------------- /image/auto_build.rb: -------------------------------------------------------------------------------- 1 | require "pty" 2 | require "optparse" 3 | 4 | images = { 5 | base_deps_amd64: { 6 | name: "base", 7 | tag: "discourse/base:build_deps_amd64", 8 | extra_args: "--target discourse_dependencies", 9 | }, 10 | base_deps_arm64: { 11 | name: "base", 12 | tag: "discourse/base:build_deps_arm64", 13 | extra_args: "--platform linux/arm64 --target discourse_dependencies", 14 | }, 15 | base_slim_main_amd64: { 16 | name: "base", 17 | tag: "discourse/base:build_slim_main_amd64", 18 | extra_args: "--target discourse_slim", 19 | use_cache: true, 20 | }, 21 | base_slim_stable_amd64: { 22 | name: "base", 23 | tag: "discourse/base:build_slim_main_amd64", 24 | extra_args: "--target discourse_slim --build-arg=\"DISCOURSE_BRANCH=stable\"", 25 | use_cache: true, 26 | }, 27 | base_slim_main_arm64: { 28 | name: "base", 29 | tag: "discourse/base:build_slim_main_arm64", 30 | extra_args: "--platform linux/arm64 --target discourse_slim", 31 | use_cache: true, 32 | }, 33 | base_slim_stable_arm64: { 34 | name: "base", 35 | tag: "discourse/base:build_slim_stable_arm64", 36 | extra_args: 37 | "--platform linux/arm64 --target discourse_slim --build-arg=\"DISCOURSE_BRANCH=stable\"", 38 | use_cache: true, 39 | }, 40 | base_release_main_amd64: { 41 | name: "base", 42 | tag: "discourse/base:build_release_main_amd64", 43 | extra_args: "--build-arg=\"DISCOURSE_BRANCH=main\" --target discourse_release", 44 | use_cache: true, 45 | }, 46 | base_release_main_amd64_pg_15: { 47 | name: "base", 48 | tag: "discourse/base:build_release_main_amd64_pg_15", 49 | extra_args: 50 | "--build-arg=\"DISCOURSE_BRANCH=main\" --target discourse_release --build-arg PG_MAJOR=15", 51 | use_cache: true, 52 | }, 53 | base_release_main_arm64: { 54 | name: "base", 55 | tag: "discourse/base:build_release_main_arm64", 56 | extra_args: 57 | "--platform linux/arm64 --build-arg=\"DISCOURSE_BRANCH=main\" --target discourse_release", 58 | use_cache: true, 59 | }, 60 | base_release_main_arm64_pg_15: { 61 | name: "base", 62 | tag: "discourse/base:build_release_main_arm64_pg_15", 63 | extra_args: 64 | "--build-arg=\"DISCOURSE_BRANCH=main\" --target discourse_release --build-arg PG_MAJOR=15", 65 | use_cache: true, 66 | }, 67 | base_release_stable_amd64: { 68 | name: "base", 69 | tag: "discourse/base:build_release_stable_amd64", 70 | extra_args: "--build-arg=\"DISCOURSE_BRANCH=stable\" --target discourse_release", 71 | use_cache: true, 72 | }, 73 | base_release_stable_arm64: { 74 | name: "base", 75 | tag: "discourse/base:build_release_stable_arm64", 76 | extra_args: 77 | "--platform linux/arm64 --build-arg=\"DISCOURSE_BRANCH=stable\" --target discourse_release", 78 | use_cache: true, 79 | }, 80 | discourse_test_build_amd64: { 81 | name: "discourse_test", 82 | tag: "discourse/discourse_test:build_amd64", 83 | extra_args: "--build-arg=\"from_tag=build_release_main_amd64\"", 84 | }, 85 | discourse_test_build_arm64: { 86 | name: "discourse_test", 87 | tag: "discourse/discourse_test:build_arm64", 88 | extra_args: "--platform linux/arm64 --build-arg=\"from_tag=build_release_main_arm64\"", 89 | }, 90 | discourse_dev_amd64: { 91 | name: "discourse_dev", 92 | tag: "discourse/discourse_dev:build_amd64", 93 | extra_args: "--build-arg=\"from_tag=build_slim_main_amd64\"", 94 | }, 95 | discourse_dev_arm64: { 96 | name: "discourse_dev", 97 | tag: "discourse/discourse_dev:build_arm64", 98 | extra_args: "--platform linux/arm64 --build-arg=\"from_tag=build_slim_main_arm64\"", 99 | }, 100 | } 101 | 102 | def run(command) 103 | lines = [] 104 | PTY.spawn(command) do |stdout, stdin, pid| 105 | begin 106 | stdout.each do |line| 107 | lines << line 108 | puts line 109 | end 110 | rescue Errno::EIO 111 | # we are done 112 | end 113 | Process.wait(pid) 114 | end 115 | 116 | raise "'#{command}' exited with status #{$?.exitstatus}" if $?.exitstatus != 0 117 | 118 | lines 119 | end 120 | 121 | def build(image, cli_args) 122 | lines = 123 | run( 124 | "cd #{image[:name]} && docker buildx build . --load #{image[:use_cache] == true ? "" : "--no-cache"} --tag #{image[:tag]} #{image[:extra_args] ? image[:extra_args] : ""} #{cli_args}", 125 | ) 126 | 127 | if lines[-1] =~ /successfully built/ 128 | raise "Error building the image for #{image[:name]}: #{lines[-1]}" 129 | end 130 | end 131 | 132 | def dev_deps() 133 | run( 134 | "sed -e 's/\(db_name: discourse\)/\1_development/' ../templates/postgres.template.yml > discourse_dev/postgres.template.yml", 135 | ) 136 | run("cp ../templates/redis.template.yml discourse_dev/redis.template.yml") 137 | run("cp base/install-rust discourse_dev/install-rust") 138 | end 139 | 140 | if ARGV.length == 0 141 | puts <<~TEXT 142 | Usage: 143 | ruby auto_build.rb IMAGE 144 | 145 | Available images: 146 | #{images.keys.join(", ")} 147 | TEXT 148 | exit 1 149 | else 150 | image = ARGV[0].to_sym 151 | 152 | if !images.include?(image) 153 | $stderr.puts "Image not found" 154 | exit 1 155 | end 156 | 157 | puts "Building #{images[image]}" 158 | dev_deps() if image == :discourse_dev_amd64 || image == :discourse_dev_arm64 159 | 160 | build(images[image], ARGV[1..-1].join(" ")) 161 | end 162 | -------------------------------------------------------------------------------- /image/base/Dockerfile: -------------------------------------------------------------------------------- 1 | # NAME: discourse/base 2 | # VERSION: release 3 | 4 | ARG DEBIAN_RELEASE=bookworm 5 | ARG RUBY_VERSION=3.3.8 6 | 7 | FROM discourse/ruby:${RUBY_VERSION}-${DEBIAN_RELEASE}-slim AS builder 8 | ARG DEBIAN_RELEASE 9 | ENV DEBIAN_RELEASE=${DEBIAN_RELEASE} 10 | RUN echo "deb http://deb.debian.org/debian ${DEBIAN_RELEASE}-backports main" > "/etc/apt/sources.list.d/${DEBIAN_RELEASE}-backports.list" 11 | RUN apt update && \ 12 | DEBIAN_FRONTEND=noninteractive apt-get -y install wget \ 13 | autoconf build-essential \ 14 | git \ 15 | cmake \ 16 | gnupg \ 17 | libpcre3-dev \ 18 | libfreetype6-dev \ 19 | libbrotli-dev 20 | 21 | FROM builder AS imagemagick_builder 22 | ADD install-imagemagick /tmp/install-imagemagick 23 | RUN /tmp/install-imagemagick 24 | 25 | FROM builder AS nginx_builder 26 | # From https://nginx.org/en/pgp_keys.html 27 | ADD nginx_public_keys.key /tmp/nginx_public_keys.key 28 | ADD install-nginx /tmp/install-nginx 29 | RUN gpg --import /tmp/nginx_public_keys.key &&\ 30 | rm /tmp/nginx_public_keys.key &&\ 31 | /tmp/install-nginx 32 | 33 | FROM discourse/ruby:${RUBY_VERSION}-${DEBIAN_RELEASE}-slim AS discourse_dependencies 34 | 35 | ARG DEBIAN_RELEASE 36 | ARG PG_MAJOR=15 37 | ENV PG_MAJOR=${PG_MAJOR} \ 38 | RUBY_ALLOCATOR=/usr/lib/libjemalloc.so \ 39 | LEFTHOOK=0 \ 40 | DEBIAN_RELEASE=${DEBIAN_RELEASE} 41 | 42 | #LABEL maintainer="Sam Saffron \"https://twitter.com/samsaffron\"" 43 | 44 | # Ensures that the gid and uid of the following users are consistent to avoid permission issues on directories in the 45 | # mounted volumes. 46 | RUN groupadd --gid 104 postgres &&\ 47 | useradd --uid 101 --gid 104 --home /var/lib/postgresql --shell /bin/bash -c "PostgreSQL administrator,,," postgres &&\ 48 | groupadd --gid 106 redis &&\ 49 | useradd --uid 103 --gid 106 --home /var/lib/redis --shell /usr/sbin/nologin redis &&\ 50 | groupadd --gid 1000 discourse &&\ 51 | useradd --uid 1000 --gid 1000 -m --shell /bin/bash discourse 52 | 53 | RUN echo 2.0.`date +%Y%m%d` > /VERSION 54 | RUN echo "deb http://deb.debian.org/debian ${DEBIAN_RELEASE}-backports main" > "/etc/apt/sources.list.d/${DEBIAN_RELEASE}-backports.list" 55 | 56 | RUN --mount=type=tmpfs,target=/var/log \ 57 | echo "debconf debconf/frontend select Teletype" | debconf-set-selections; \ 58 | apt-get -y update && DEBIAN_FRONTEND=noninteractive apt-get -y install gnupg sudo curl fping locales \ 59 | ca-certificates rsync \ 60 | cmake g++ pkg-config patch \ 61 | libxslt-dev libcurl4-openssl-dev \ 62 | libssl-dev libyaml-dev libtool \ 63 | libpcre3 libpcre3-dev zlib1g zlib1g-dev \ 64 | libxml2-dev gawk parallel \ 65 | libreadline-dev anacron wget \ 66 | psmisc whois brotli libunwind-dev \ 67 | libtcmalloc-minimal4 cmake \ 68 | pngcrush pngquant ripgrep poppler-utils \ 69 | # imagemagick runtime dependencies 70 | ghostscript libjbig0 libtiff6 libpng16-16 libfontconfig1 \ 71 | libwebpdemux2 libwebpmux3 libxext6 librsvg2-2 libgomp1 \ 72 | fonts-urw-base35 libheif1/${DEBIAN_RELEASE}-backports \ 73 | # nginx runtime dependencies \ 74 | nginx-common && \ 75 | # install these without recommends to avoid pulling in e.g. 76 | # X11 libraries, mailutils 77 | DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends git rsyslog logrotate cron ssh-client less; \ 78 | # postgres packages 79 | install -d /usr/share/postgresql-common/pgdg &&\ 80 | curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc &&\ 81 | echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt ${DEBIAN_RELEASE}-pgdg main" > /etc/apt/sources.list.d/pgdg.list; \ 82 | # yarn packages 83 | curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -; \ 84 | echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list; \ 85 | # node packages 86 | curl --silent --location https://deb.nodesource.com/setup_22.x | sudo bash -; \ 87 | # setup anacron, rsyslog, initctl 88 | sed -i -e 's/start -q anacron/anacron -s/' /etc/cron.d/anacron; \ 89 | sed -i.bak 's/$ModLoad imklog/#$ModLoad imklog/' /etc/rsyslog.conf; \ 90 | sed -i.bak 's/module(load="imklog")/#module(load="imklog")/' /etc/rsyslog.conf; \ 91 | dpkg-divert --local --rename --add /sbin/initctl; \ 92 | sh -c "test -f /sbin/initctl || ln -s /bin/true /sbin/initctl"; \ 93 | apt-get -y update && DEBIAN_FRONTEND=noninteractive apt-get -y install runit socat \ 94 | libpq-dev postgresql-client-${PG_MAJOR} \ 95 | postgresql-${PG_MAJOR} postgresql-contrib-${PG_MAJOR} postgresql-${PG_MAJOR}-pgvector \ 96 | nodejs yarn &&\ 97 | mkdir -p /etc/runit/1.d 98 | 99 | ENV LC_ALL=en_US.UTF-8 100 | ENV LANG=en_US.UTF-8 101 | ENV LANGUAGE=en_US.UTF-8 102 | RUN sed -i "s/^# $LANG/$LANG/" /etc/locale.gen; \ 103 | locale-gen 104 | 105 | RUN --mount=type=tmpfs,target=/root/.npm \ 106 | npm install -g terser uglify-js pnpm@9 107 | 108 | COPY --from=nginx_builder /usr/sbin/nginx /usr/sbin 109 | 110 | # Copy binary and configuration files for magick 111 | COPY --from=imagemagick_builder /usr/local/bin/magick /usr/local/bin/magick 112 | COPY --from=imagemagick_builder /usr/local/etc/ImageMagick-7 /usr/local/etc/ImageMagick-7 113 | COPY --from=imagemagick_builder /usr/local/share/ImageMagick-7 /usr/local/share/ImageMagick-7 114 | # Create symlinks to imagemagick tools 115 | RUN ln -s /usr/local/bin/magick /usr/local/bin/animate &&\ 116 | ln -s /usr/local/bin/magick /usr/local/bin/compare &&\ 117 | ln -s /usr/local/bin/magick /usr/local/bin/composite &&\ 118 | ln -s /usr/local/bin/magick /usr/local/bin/conjure &&\ 119 | ln -s /usr/local/bin/magick /usr/local/bin/convert &&\ 120 | ln -s /usr/local/bin/magick /usr/local/bin/display &&\ 121 | ln -s /usr/local/bin/magick /usr/local/bin/identify &&\ 122 | ln -s /usr/local/bin/magick /usr/local/bin/import &&\ 123 | ln -s /usr/local/bin/magick /usr/local/bin/magick-script &&\ 124 | ln -s /usr/local/bin/magick /usr/local/bin/mogrify &&\ 125 | ln -s /usr/local/bin/magick /usr/local/bin/montage &&\ 126 | ln -s /usr/local/bin/magick /usr/local/bin/stream &&\ 127 | test $(magick -version | grep -o -e png -e tiff -e jpeg -e freetype -e heic -e webp | wc -l) -eq 6 128 | 129 | ADD install-jemalloc /tmp/install-jemalloc 130 | RUN /tmp/install-jemalloc 131 | 132 | ADD install-redis /tmp/install-redis 133 | RUN /tmp/install-redis 134 | 135 | ADD install-oxipng /tmp/install-oxipng 136 | RUN /tmp/install-oxipng 137 | 138 | RUN gem install pups --force &&\ 139 | mkdir -p /pups/bin/ &&\ 140 | ln -s /usr/local/bin/pups /pups/bin/pups 141 | 142 | # This tool allows us to disable huge page support for our current process 143 | # since the flag is preserved through forks and execs it can be used on any 144 | # process 145 | ADD thpoff.c /src/thpoff.c 146 | RUN gcc -o /usr/local/sbin/thpoff /src/thpoff.c && rm /src/thpoff.c 147 | 148 | # clean up for docker squash 149 | RUN rm -fr /usr/local/share/doc &&\ 150 | rm -fr /usr/local/share/ri &&\ 151 | rm -fr /var/lib/apt/lists/* &&\ 152 | rm -fr /root/.gem &&\ 153 | rm -fr /root/.npm &&\ 154 | rm -fr /tmp/* 155 | 156 | # this is required for aarch64 which uses buildx 157 | # see https://github.com/docker/buildx/issues/150 158 | RUN rm -f /etc/service 159 | 160 | COPY etc/ /etc 161 | COPY sbin/ /sbin 162 | 163 | FROM discourse_dependencies AS discourse_slim 164 | ARG DISCOURSE_BRANCH=main 165 | 166 | # Discourse specific bits 167 | RUN install -dm 0755 -o discourse -g discourse /var/www/discourse &&\ 168 | sudo -u discourse git clone --branch $DISCOURSE_BRANCH --filter=tree:0 https://github.com/discourse/discourse.git /var/www/discourse &&\ 169 | gem install bundler --conservative -v $(awk '/BUNDLED WITH/ { getline; gsub(/ /,""); print $0 }' /var/www/discourse/Gemfile.lock) 170 | 171 | FROM discourse_slim AS discourse_release 172 | ENV RAILS_ENV=production 173 | 174 | RUN cd /var/www/discourse &&\ 175 | sudo -u discourse bundle config --local deployment true &&\ 176 | sudo -u discourse bundle config --local path ./vendor/bundle &&\ 177 | sudo -u discourse bundle config --local without test development &&\ 178 | sudo -u discourse bundle install --jobs $(($(nproc) - 1)) &&\ 179 | find /var/www/discourse/vendor/bundle -name cache -not -path '*/gems/*' -type d -exec rm -rf {} + &&\ 180 | find /var/www/discourse/vendor/bundle -name tmp -type d -exec rm -rf {} + 181 | 182 | RUN cd /var/www/discourse &&\ 183 | sudo -u discourse /bin/bash -c 'if [ -f yarn.lock ]; then yarn install --frozen-lockfile && yarn cache clean; else pnpm install --frozen-lockfile; fi' 184 | -------------------------------------------------------------------------------- /image/base/etc/cron.d/cron.d_anacron: -------------------------------------------------------------------------------- 1 | # this avoids using invoke-rc.d which is bust in image 2 | 3 | SHELL=/bin/sh 4 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 5 | 6 | 30 7 * * * root /usr/sbin/anacron -s >/dev/null 7 | -------------------------------------------------------------------------------- /image/base/etc/runit/1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | /bin/run-parts --verbose --exit-on-error /etc/runit/1.d || exit 100 4 | -------------------------------------------------------------------------------- /image/base/etc/runit/1.d/00-fix-var-logs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkdir -p /var/log/nginx 3 | chown -R www-data:www-data /var/log/nginx 4 | chmod -R 644 /var/log/nginx 5 | chmod 755 /var/log/nginx 6 | touch /var/log/syslog && chown -f root:adm /var/log/syslog* 7 | touch /var/log/auth.log && chown -f root:adm /var/log/auth.log* 8 | touch /var/log/kern.log && chown -f root:adm /var/log/kern.log* 9 | -------------------------------------------------------------------------------- /image/base/etc/runit/1.d/anacron: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /usr/sbin/anacron -s 3 | -------------------------------------------------------------------------------- /image/base/etc/runit/1.d/cleanup-pids: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /bin/echo "Cleaning stale PID files" 3 | /bin/rm -f /var/run/*.pid 4 | -------------------------------------------------------------------------------- /image/base/etc/runit/2: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec /usr/bin/runsvdir -P /etc/service 3 | -------------------------------------------------------------------------------- /image/base/etc/runit/3: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | /bin/run-parts --verbose /etc/runit/3.d 4 | -------------------------------------------------------------------------------- /image/base/etc/service/cron/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec 2>&1 3 | cd / 4 | exec cron -f 5 | -------------------------------------------------------------------------------- /image/base/etc/service/rsyslog/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec 2>&1 3 | cd / 4 | chgrp adm /var/log 5 | chmod g+w /var/log 6 | rm -f /var/run/rsyslogd.pid 7 | exec rsyslogd -n 8 | -------------------------------------------------------------------------------- /image/base/install-imagemagick: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # version check: https://github.com/ImageMagick/ImageMagick/releases 5 | IMAGE_MAGICK_VERSION="7.1.1-43" 6 | IMAGE_MAGICK_HASH="ceb972266b23dc7c1cfce0da5a7f0c9acfb4dc81f40eb542a49476fedbc2618f" 7 | 8 | LIBJPEGTURBO=$(cat /etc/issue | grep -qi Debian && echo 'libjpeg62-turbo libjpeg62-turbo-dev' || echo 'libjpeg-turbo8 libjpeg-turbo8-dev') 9 | 10 | PREFIX=/usr/local 11 | WDIR=/tmp/imagemagick 12 | 13 | # Install build deps 14 | apt -y -q remove imagemagick 15 | apt -y -q install git make gcc pkg-config autoconf curl g++ yasm cmake \ 16 | libde265-0 libde265-dev ${LIBJPEGTURBO} libwebp7 x265 libx265-dev libtool \ 17 | libpng16-16 libpng-dev libwebp-dev libgomp1 libaom-dev \ 18 | libwebpmux3 libwebpdemux2 ghostscript libxml2-dev libxml2-utils librsvg2-dev \ 19 | libltdl7-dev libbz2-dev gsfonts libtiff-dev libfreetype6-dev libjpeg-dev 20 | 21 | if cat /etc/issue | grep -qi Debian; then 22 | # Get VERSION_CODENAME 23 | . /etc/os-release 24 | # Use backports 25 | apt -y -q install libheif1/$VERSION_CODENAME-backports libheif-dev/$VERSION_CODENAME-backports 26 | else 27 | apt -y -q install libheif1 libheif-dev 28 | fi 29 | 30 | mkdir -p $WDIR 31 | cd $WDIR 32 | 33 | # Build and install ImageMagick 34 | wget -q -O $WDIR/ImageMagick.tar.gz "https://github.com/ImageMagick/ImageMagick/archive/$IMAGE_MAGICK_VERSION.tar.gz" 35 | sha256sum $WDIR/ImageMagick.tar.gz 36 | echo "$IMAGE_MAGICK_HASH $WDIR/ImageMagick.tar.gz" | sha256sum -c 37 | IMDIR=$WDIR/$(tar tzf $WDIR/ImageMagick.tar.gz --wildcards "ImageMagick-*/configure" |cut -d/ -f1) 38 | tar zxf $WDIR/ImageMagick.tar.gz -C $WDIR 39 | cd $IMDIR 40 | PKG_CONF_LIBDIR=$PREFIX/lib LDFLAGS=-L$PREFIX/lib CFLAGS='-O2 -I$PREFIX/include' ./configure \ 41 | --prefix=$PREFIX \ 42 | --disable-shared \ 43 | --enable-delegate-build \ 44 | --enable-static \ 45 | --enable-bounds-checking \ 46 | --enable-hdri \ 47 | --enable-hugepages \ 48 | --with-threads \ 49 | --with-modules \ 50 | --with-quantum-depth=16 \ 51 | --without-magick-plus-plus \ 52 | --with-bzlib \ 53 | --with-zlib \ 54 | --without-autotrace \ 55 | --with-freetype \ 56 | --with-jpeg \ 57 | --without-lcms \ 58 | --with-lzma \ 59 | --with-png \ 60 | --with-tiff \ 61 | --with-heic \ 62 | --with-rsvg \ 63 | --with-webp 64 | make all -j"$(nproc)" && make install 65 | 66 | cd $HOME 67 | rm -rf $WDIR 68 | ldconfig /usr/local/lib 69 | 70 | # Validate ImageMagick install 71 | test $(magick -version | grep -o -e png -e tiff -e jpeg -e freetype -e heic -e webp | wc -l) -eq 6 72 | -------------------------------------------------------------------------------- /image/base/install-jemalloc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # version check: https://github.com/jemalloc/jemalloc/releases 5 | 6 | # Newer aarch64 platforms, like Raspberry Pi 5 with Debian Bookworm, are 7 | # shipping with PAGESIZE=16K. Setting it here is retrocompatible with older 8 | # systems, so it's safe to set it unconditionally for arm. 9 | # This means aarch64 will use the latest jemalloc, where we can configure the 10 | # page size, while x64 will keep using our pinned 3.6.0 jemalloc 11 | if uname -m | grep -qi 'aarch64'; then 12 | mkdir /jemalloc-new 13 | cd /jemalloc-new 14 | 15 | wget -q https://github.com/jemalloc/jemalloc/releases/download/5.3.0/jemalloc-5.3.0.tar.bz2 16 | sha256sum jemalloc-5.3.0.tar.bz2 17 | echo "2db82d1e7119df3e71b7640219b6dfe84789bc0537983c3b7ac4f7189aecfeaa jemalloc-5.3.0.tar.bz2" | sha256sum -c 18 | tar --strip-components=1 -xjf jemalloc-5.3.0.tar.bz2 19 | ./configure --prefix=/usr --with-lg-page=16 && make build_lib -j"$(nproc)" && make install_lib_shared 20 | cd / && rm -rf /jemalloc-new 21 | else 22 | # jemalloc stable 23 | mkdir /jemalloc-stable 24 | cd /jemalloc-stable 25 | 26 | wget -q https://github.com/jemalloc/jemalloc/releases/download/3.6.0/jemalloc-3.6.0.tar.bz2 27 | sha256sum jemalloc-3.6.0.tar.bz2 28 | echo "e16c2159dd3c81ca2dc3b5c9ef0d43e1f2f45b04548f42db12e7c12d7bdf84fe jemalloc-3.6.0.tar.bz2" | sha256sum -c 29 | tar --strip-components=1 -xjf jemalloc-3.6.0.tar.bz2 30 | ./configure --prefix=/usr $EXTRA_CONF && make -j"$(nproc)" && make install_lib_shared 31 | cd / && rm -rf /jemalloc-stable 32 | 33 | # jemalloc new 34 | mkdir /jemalloc-new 35 | cd /jemalloc-new 36 | 37 | wget -q https://github.com/jemalloc/jemalloc/releases/download/5.3.0/jemalloc-5.3.0.tar.bz2 38 | sha256sum jemalloc-5.3.0.tar.bz2 39 | echo "2db82d1e7119df3e71b7640219b6dfe84789bc0537983c3b7ac4f7189aecfeaa jemalloc-5.3.0.tar.bz2" | sha256sum -c 40 | tar --strip-components=1 -xjf jemalloc-5.3.0.tar.bz2 41 | ./configure --prefix=/usr --with-install-suffix=5.3.0 && make build_lib -j"$(nproc)" && make install_lib_shared 42 | cd / && rm -rf /jemalloc-new 43 | fi 44 | -------------------------------------------------------------------------------- /image/base/install-nginx: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # version check: https://nginx.org/en/download.html 5 | VERSION=1.26.2 6 | 7 | cd /tmp 8 | wget -q https://nginx.org/download/nginx-$VERSION.tar.gz 9 | wget -q https://nginx.org/download/nginx-$VERSION.tar.gz.asc 10 | gpg --verify nginx-$VERSION.tar.gz.asc nginx-$VERSION.tar.gz 11 | tar zxf nginx-$VERSION.tar.gz 12 | cd nginx-$VERSION 13 | 14 | # nginx-common for boilerplate files etc. 15 | apt install -y nginx-common 16 | 17 | cd /tmp 18 | # this is the reason we are compiling by hand... 19 | git clone https://github.com/google/ngx_brotli.git 20 | # now ngx_brotli has brotli as a submodule 21 | cd /tmp/ngx_brotli 22 | git submodule update --init 23 | 24 | cd /tmp/nginx-$VERSION 25 | # ignoring deprecations with -Wno-deprecated-declarations while we wait for this https://github.com/google/ngx_brotli/issues/39#issuecomment-254093378 26 | ./configure --with-cc-opt='-g -O2 -fPIE -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wno-deprecated-declarations' --with-ld-opt='-Wl,-Bsymbolic-functions -fPIE -pie -Wl,-z,relro -Wl,-z,now' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_addition_module --with-http_dav_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_v2_module --with-http_sub_module --with-stream --with-stream_ssl_module --with-mail --with-mail_ssl_module --with-threads --add-module=/tmp/ngx_brotli 27 | 28 | make install 29 | 30 | mv /usr/share/nginx/sbin/nginx /usr/sbin 31 | 32 | cd / 33 | rm -fr /tmp/nginx-$VERSION 34 | rm -f /tmp/nginx-$VERSION.tar.gz 35 | rm -f /tmp/nginx-$VERSION.tar.gz.asc 36 | rm -fr /tmp/libbrotli 37 | rm -fr /tmp/ngx_brotli 38 | rm -fr /etc/nginx/modules-enabled/* 39 | -------------------------------------------------------------------------------- /image/base/install-oxipng: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # version check: https://github.com/shssoichiro/oxipng/releases 5 | OXIPNG_VERSION="9.1.2" 6 | dpkgArch="$(dpkg --print-architecture)" 7 | 8 | case "${dpkgArch##*-}" in 9 | amd64) OXIPNG_FILE="oxipng-${OXIPNG_VERSION}-x86_64-unknown-linux-musl.tar.gz"; OXIPNG_HASH='211d53f3781be4a71566fbaad6611a3da018ac9b22d500651b091c2b42ebe318' ;; 10 | arm64) OXIPNG_FILE="oxipng-${OXIPNG_VERSION}-aarch64-unknown-linux-musl.tar.gz"; OXIPNG_HASH='818d47d7195e1e0c4d58a9f3b6fd84aa3cd21770c60c876e73e2e6a17ca69b52' ;; 11 | *) echo >&2 "unsupported architecture: ${dpkgArch}"; exit 1 ;; 12 | esac 13 | 14 | # Install other deps 15 | apt -y -q install advancecomp jpegoptim libjpeg-turbo-progs 16 | 17 | git clone --depth 1 --branch "3.08" https://github.com/Matthias-Wandel/jhead.git /tmp/jhead 18 | cd /tmp/jhead && make && cp /tmp/jhead/jhead /usr/local/bin/jhead 19 | cd / && rm -rf /tmp/jhead 20 | 21 | mkdir /oxipng-install 22 | cd /oxipng-install 23 | 24 | wget -q https://github.com/shssoichiro/oxipng/releases/download/v${OXIPNG_VERSION}/${OXIPNG_FILE} 25 | sha256sum ${OXIPNG_FILE} 26 | echo "${OXIPNG_HASH} ${OXIPNG_FILE}" | sha256sum -c 27 | tar --strip-components=1 -xzf $OXIPNG_FILE 28 | cp -v ./oxipng /usr/local/bin 29 | cd / && rm -fr /oxipng-install 30 | -------------------------------------------------------------------------------- /image/base/install-redis: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # version check: https://redis.io/ 5 | REDIS_VERSION=7.0.15 6 | REDIS_HASH="98066f5363504b26c34dd20fbcc3c957990d764cdf42576c836fc021073f4341" 7 | 8 | cd /tmp 9 | # Prepare Redis source. 10 | wget -q http://download.redis.io/releases/redis-$REDIS_VERSION.tar.gz 11 | sha256sum redis-$REDIS_VERSION.tar.gz 12 | echo "$REDIS_HASH redis-$REDIS_VERSION.tar.gz" | sha256sum -c 13 | 14 | tar zxf redis-$REDIS_VERSION.tar.gz 15 | cd redis-$REDIS_VERSION 16 | 17 | # aarch64 compatibility 18 | if uname -m | grep -qi 'aarch64'; then 19 | export JEMALLOC_CONFIGURE_OPTS="--with-lg-page=16" 20 | fi 21 | 22 | # Building and installing binaries. 23 | make -j"$(nproc)" BUILD_TLS=yes && make install PREFIX=/usr 24 | 25 | # Add `redis` user and group. 26 | adduser --system --home /var/lib/redis --quiet --group redis || true 27 | 28 | # Configure Redis. 29 | mkdir -p /etc/redis 30 | mkdir -p /var/lib/redis 31 | mkdir -p /var/log/redis 32 | cp /tmp/redis-$REDIS_VERSION/redis.conf /etc/redis 33 | 34 | chown -R redis:redis /var/lib/redis 35 | chmod 750 /var/lib/redis 36 | 37 | chown -R redis:redis /var/log/redis 38 | chmod 750 /var/log/redis 39 | 40 | # Clean up. 41 | cd / && rm -rf /tmp/redis* 42 | -------------------------------------------------------------------------------- /image/base/install-rust: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # see https://github.com/rust-lang/docker-rust/blob/master/Dockerfile-debian.template 5 | export RUSTUP_HOME=/usr/local/rustup 6 | export CARGO_HOME=/usr/local/cargo 7 | export PATH=/usr/local/cargo/bin:$PATH 8 | export RUST_VERSION=1.82.0 9 | export RUSTUP_VERSION=1.25.2 10 | 11 | dpkgArch="$(dpkg --print-architecture)" 12 | 13 | case "${dpkgArch##*-}" in 14 | amd64) rustArch='x86_64-unknown-linux-gnu'; rustupSha256='bb31eaf643926b2ee9f4d8d6fc0e2835e03c0a60f34d324048aa194f0b29a71c' ;; 15 | armhf) rustArch='armv7-unknown-linux-gnueabihf'; rustupSha256='6626b90205d7fe7058754c8e993b7efd91dedc6833a11a225b296b7c2941194f' ;; 16 | arm64) rustArch='aarch64-unknown-linux-gnu'; rustupSha256='4ccaa7de6b8be1569f6b764acc28e84f5eca342f5162cd5c810891bff7ed7f74' ;; 17 | i386) rustArch='i686-unknown-linux-gnu'; rustupSha256='34392b53a25c56435b411d3e575b63aab962034dd1409ba405e708610c829607' ;; 18 | *) echo >&2 "unsupported architecture: ${dpkgArch}"; exit 1 ;; 19 | esac 20 | 21 | url="https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/${rustArch}/rustup-init" 22 | wget "$url" 23 | echo "${rustupSha256} *rustup-init" | sha256sum -c - 24 | chmod +x rustup-init 25 | ./rustup-init -y --no-modify-path --profile minimal --default-toolchain $RUST_VERSION --default-host ${rustArch} 26 | rm rustup-init 27 | chmod -R a+w $RUSTUP_HOME $CARGO_HOME 28 | rustup --version 29 | cargo --version 30 | rustc --version 31 | -------------------------------------------------------------------------------- /image/base/sbin/boot: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # we use this to boot up cause runit will not handle TERM and will not exit when done 4 | 5 | shutdown() { 6 | echo Shutting Down 7 | /etc/runit/3 8 | ls /etc/service | SHELL=/bin/sh parallel sv force-stop {} 9 | kill -HUP $RUNSVDIR 10 | wait $RUNSVDIR 11 | 12 | # give stuff a bit of time to finish 13 | sleep 0.1 14 | 15 | ORPHANS=`ps -eo pid | grep -v PID | tr -d ' ' | grep -v '^1$'` 16 | SHELL=/bin/bash parallel 'timeout 5 /bin/bash -c "kill {} && wait {}" || kill -9 {}' ::: $ORPHANS 2> /dev/null 17 | exit 18 | } 19 | 20 | /etc/runit/1 || exit $? 21 | /etc/runit/2& 22 | RUNSVDIR=$! 23 | echo "Started runsvdir, PID is $RUNSVDIR" 24 | trap shutdown SIGTERM SIGHUP 25 | wait $RUNSVDIR 26 | 27 | shutdown 28 | -------------------------------------------------------------------------------- /image/base/thpoff.c: -------------------------------------------------------------------------------- 1 | // PUBLIC DOMAIN CODE 2 | // 3 | // A tiny program that disable transparent huge pages on arbitrary processes 4 | // thpoff echo 1 : will run echo 1 with SET_THP_DISABLE true on the process 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | int main( int argc, char **argv) { 11 | if (argc < 2) { 12 | fprintf(stderr, "ERROR: expecting at least 1 argument!\n"); 13 | return -1; 14 | } 15 | prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0); 16 | 17 | char* newargv[argc]; 18 | int i; 19 | 20 | newargv[argc-1] = NULL; 21 | for (i=1; i 0) { 34 | fprintf(stderr, "ERROR: %i errno while attempting to run file\n", errno); 35 | return -1; 36 | } 37 | 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /image/discourse_dev/Dockerfile: -------------------------------------------------------------------------------- 1 | # NAME: discourse/discourse_dev 2 | # VERSION: release 3 | 4 | ARG from_tag=slim 5 | 6 | FROM discourse/base:$from_tag AS repo-fetcher 7 | WORKDIR /repo 8 | RUN chown discourse . 9 | USER discourse 10 | RUN git clone https://github.com/discourse/discourse . --depth 1 11 | 12 | FROM discourse/base:$from_tag 13 | 14 | #LABEL maintainer="Sam Saffron \"https://twitter.com/samsaffron\"" 15 | 16 | # Remove the code added on base image 17 | RUN rm -rf /var/www/* 18 | 19 | # Give discourse user no-passwd sudo permissions (for bundle install) 20 | ADD sudoers.discourse /etc/sudoers.d/discourse 21 | 22 | RUN sudo -u discourse bundle config set --global path /home/discourse/.bundle/gems 23 | 24 | # Add user-install ruby gems to PATH 25 | RUN echo 'PATH="$(ruby -r rubygems -e "puts Gem.user_dir")/bin:$PATH"' >> /home/discourse/.profile 26 | 27 | # get redis going 28 | ADD redis.template.yml /pups/redis.yml 29 | RUN /pups/bin/pups /pups/redis.yml 30 | 31 | RUN locale-gen en_US.UTF-8 32 | ENV LANG=en_US.UTF-8 \ 33 | LANGUAGE=en_US:en \ 34 | LC_ALL=en_US.UTF-8 \ 35 | RUSTUP_HOME=/usr/local/rustup \ 36 | CARGO_HOME=/usr/local/cargo \ 37 | PATH=/usr/local/cargo/bin:$PATH 38 | 39 | # get postgres going 40 | ADD postgres.template.yml /pups/postgres.yml 41 | RUN LANG=en_US.UTF-8 /pups/bin/pups /pups/postgres.yml 42 | 43 | # add dev databases 44 | ADD postgres_dev.template.yml /pups/postgres_dev.yml 45 | RUN /pups/bin/pups /pups/postgres_dev.yml 46 | 47 | # move default postgres_data out of the way 48 | RUN mv /shared/postgres_data /shared/postgres_data_orig 49 | 50 | # re-instantiate data on boot if needed (this will allow it to persist across 51 | # invocations when used with a mounted volume) 52 | ADD ensure-database /etc/runit/1.d/ensure-database 53 | 54 | ADD install-rust /tmp/install-rust 55 | ADD install-selenium /tmp/install-selenium 56 | RUN /tmp/install-selenium 57 | 58 | # Install & Configure MailHog (https://github.com/mailhog/MailHog) 59 | RUN wget -qO /tmp/mailhog https://github.com/mailhog/MailHog/releases/download/v1.0.1/MailHog_linux_amd64\ 60 | && echo "e2ed634ded49929f089b20045581955ed217672078fd86082dd7a6c67c5d09c7 /tmp/mailhog" | sha256sum -c -\ 61 | && mv /tmp/mailhog /usr/local/bin/mailhog\ 62 | && chmod +x /usr/local/bin/mailhog\ 63 | && rm -rf /tmp/* 64 | 65 | USER discourse 66 | 67 | # Warm global bundle cache, then delete the compressed `cache/` versions (`/gem/` are enough) 68 | RUN --mount=type=bind,src=/repo,from=repo-fetcher,target=/tmp/discourse-clone,readwrite \ 69 | cd /tmp/discourse-clone \ 70 | && bundle install --deployment \ 71 | && rm -rf /home/discourse/.bundle/gems/ruby/*/cache/* 72 | 73 | # Warm global yarn cache 74 | RUN --mount=type=bind,src=/repo,from=repo-fetcher,target=/tmp/discourse-clone,readwrite \ 75 | cd /tmp/discourse-clone \ 76 | && (if [ -f yarn.lock ]; then yarn install; else CI=1 pnpm install; fi) 77 | 78 | USER root 79 | -------------------------------------------------------------------------------- /image/discourse_dev/ensure-database: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -f /shared/postgres_data/PG_VERSION ]; then 4 | mkdir -p /shared/postgres_data 5 | chown -R postgres:postgres /shared/postgres_data 6 | chmod 700 /shared/postgres_data 7 | cp -R /shared/postgres_data_orig/* /shared/postgres_data 8 | chown -R postgres:postgres /shared/postgres_data 9 | chmod 700 /shared/postgres_data 10 | fi 11 | -------------------------------------------------------------------------------- /image/discourse_dev/install-selenium: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # The Selenium gem isn’t shipped with the `selenium-manager` binary for aarch64 5 | # (yet). So we have to compile it ourselves. 6 | if [ "$(dpkg --print-architecture)" = "arm64" ]; then 7 | apt update && apt install -y firefox-esr chromium-driver 8 | cd /tmp 9 | /tmp/install-rust 10 | git clone --depth 1 --no-checkout https://github.com/SeleniumHQ/selenium.git 11 | cd selenium 12 | git sparse-checkout set rust 13 | git checkout 14 | cd rust 15 | cargo build --release 16 | cp target/release/selenium-manager /usr/local/bin 17 | rustup self uninstall -y 18 | cd / 19 | rm -rf /tmp/* 20 | else 21 | wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - &&\ 22 | echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list &&\ 23 | apt update &&\ 24 | apt install -y google-chrome-stable firefox-esr chromium-driver 25 | fi 26 | -------------------------------------------------------------------------------- /image/discourse_dev/postgres_dev.template.yml: -------------------------------------------------------------------------------- 1 | run: 2 | 3 | - replace: 4 | filename: "/etc/postgresql/15/main/postgresql.conf" 5 | from: /#?full_page_writes *=.*/ 6 | to: "full_page_writes = off" 7 | 8 | - replace: 9 | filename: "/etc/postgresql/15/main/postgresql.conf" 10 | from: /#?fsync *=.*/ 11 | to: "fsync = off" 12 | 13 | - exec: 14 | background: true 15 | # use fast shutdown for pg 16 | stop_signal: INT 17 | cmd: HOME=/var/lib/postgresql USER=postgres exec chpst -u postgres:postgres:ssl-cert -U postgres:postgres:ssl-cert /usr/lib/postgresql/15/bin/postmaster -D /etc/postgresql/15/main 18 | 19 | - exec: 20 | background: true 21 | cmd: exec chpst -u redis -U redis /usr/bin/redis-server /etc/redis/redis.conf 22 | 23 | # give db a few secs to start up 24 | - exec: "sleep 5" 25 | 26 | - exec: su postgres -c 'psql -c "ALTER USER discourse WITH SUPERUSER;"' 27 | -------------------------------------------------------------------------------- /image/discourse_dev/sudoers.discourse: -------------------------------------------------------------------------------- 1 | discourse ALL = NOPASSWD: ALL 2 | -------------------------------------------------------------------------------- /image/discourse_test/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG from_tag=build 2 | 3 | FROM discourse/base:$from_tag AS base 4 | ENV RAILS_ENV test 5 | 6 | WORKDIR /var/www/discourse 7 | ENV LANG en_US.UTF-8 8 | 9 | # configure Git to suppress warnings 10 | RUN sudo -E -u discourse -H git config --global user.email "you@example.com" &&\ 11 | sudo -E -u discourse -H git config --global user.name "Your Name" 12 | 13 | RUN chown -R discourse . &&\ 14 | chown -R discourse /var/run/postgresql &&\ 15 | bundle config unset deployment &&\ 16 | bundle config unset without 17 | 18 | FROM base AS with_browsers 19 | 20 | ENV TESTEM_DEFAULT_BROWSER Chrome 21 | ADD install-chrome /tmp/install-chrome 22 | RUN /tmp/install-chrome &&\ 23 | apt update &&\ 24 | apt install -y libgconf-2-4 libxss1 firefox-esr &&\ 25 | cd /tmp && wget -q "https://download.mozilla.org/?product=firefox-latest-ssl&os=linux64&lang=en-US" -O firefox.tar.xz &&\ 26 | tar xJvf firefox.tar.xz && mv /tmp/firefox /opt/firefox-evergreen &&\ 27 | apt clean &&\ 28 | rm /tmp/firefox.tar.xz 29 | 30 | FROM with_browsers AS release 31 | 32 | RUN cd /var/www/discourse &&\ 33 | sudo -u discourse bundle install --jobs $(($(nproc) - 1)) &&\ 34 | sudo -E -u discourse -H /bin/bash -c 'CI=1 pnpm install' 35 | 36 | RUN cd /var/www/discourse && sudo -E -u discourse -H bundle exec rake plugin:install_all_official &&\ 37 | LOAD_PLUGINS=1 sudo -E -u discourse -H bundle exec rake plugin:install_all_gems &&\ 38 | sudo -E -u discourse -H bundle exec ruby script/install_minio_binaries.rb 39 | 40 | RUN cd /var/www/discourse && \ 41 | pnpm playwright install ffmpeg 42 | 43 | RUN cd /var/www/discourse && \ 44 | sudo -E -u discourse -H pnpm playwright install --no-shell chromium 45 | 46 | ENTRYPOINT ["sudo", "-E", "-u", "discourse", "-H", "ruby", "script/docker_test.rb"] 47 | -------------------------------------------------------------------------------- /image/discourse_test/install-chrome: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # https://googlechromelabs.github.io/chrome-for-testing/ doesn't provide 5 | # linux/arm64 binaries for chrome or chromedriver yet. Therefore on arm64, we 6 | # install chromium instead of chrome. 7 | if [ "$(dpkg --print-architecture)" = "arm64" ]; then 8 | apt update && apt install -y chromium-driver 9 | else 10 | wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - &&\ 11 | echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list &&\ 12 | apt update &&\ 13 | apt install -y google-chrome-stable 14 | fi 15 | -------------------------------------------------------------------------------- /samples/data.yml: -------------------------------------------------------------------------------- 1 | # A container for all things Data, be sure to set a secret password for 2 | # discourse account, SOME_SECRET is just an example 3 | # 4 | 5 | templates: 6 | - "templates/postgres.template.yml" 7 | - "templates/redis.template.yml" 8 | 9 | # any extra arguments for Docker? 10 | # docker_args: 11 | 12 | params: 13 | db_default_text_search_config: "pg_catalog.english" 14 | 15 | ## Set db_shared_buffers to a max of 25% of the total memory. 16 | ## will be set automatically by bootstrap based on detected RAM, or you can override 17 | #db_shared_buffers: "256MB" 18 | 19 | ## can improve sorting performance, but adds memory usage per-connection 20 | #db_work_mem: "40MB" 21 | 22 | env: 23 | # ensure locale exists in container, you may need to install it 24 | LC_ALL: en_US.UTF-8 25 | LANG: en_US.UTF-8 26 | LANGUAGE: en_US.UTF-8 27 | 28 | volumes: 29 | - volume: 30 | host: /var/discourse/shared/data 31 | guest: /shared 32 | - volume: 33 | host: /var/discourse/shared/data/log/var-log 34 | guest: /var/log 35 | 36 | # TODO: SOME_SECRET to a password for the discourse user 37 | hooks: 38 | after_postgres: 39 | - exec: 40 | stdin: | 41 | alter user discourse with password 'SOME_SECRET'; 42 | cmd: su - postgres -c 'psql discourse' 43 | 44 | raise_on_fail: false 45 | -------------------------------------------------------------------------------- /samples/mail-receiver.yml: -------------------------------------------------------------------------------- 1 | ## this is the incoming mail receiver container template 2 | ## 3 | ## After making changes to this file, you MUST rebuild 4 | ## /var/discourse/launcher rebuild mail-receiver 5 | ## 6 | ## BE *VERY* CAREFUL WHEN EDITING! 7 | ## YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT! 8 | ## visit http://www.yamllint.com/ to validate this file as needed 9 | 10 | base_image: discourse/mail-receiver:release 11 | update_pups: false 12 | 13 | expose: 14 | - "25:25" # SMTP 15 | 16 | env: 17 | LC_ALL: en_US.UTF-8 18 | LANG: en_US.UTF-8 19 | LANGUAGE: en_US.UTF-8 20 | 21 | ## Where e-mail to your forum should be sent. In general, it's perfectly fine 22 | ## to use the same domain as the forum itself here. 23 | MAIL_DOMAIN: discourse.example.com 24 | # uncomment these (and the volume below!) to support TLS 25 | # POSTCONF_smtpd_tls_key_file: /letsencrypt/discourse.example.com/discourse.example.com.key 26 | # POSTCONF_smtpd_tls_cert_file: /letsencrypt/discourse.example.com/fullchain.cer 27 | # POSTCONF_smtpd_tls_security_level: may 28 | 29 | 30 | ## The base URL for this Discourse instance. 31 | ## This will be whatever your Discourse site URL is. For example, 32 | ## https://discourse.example.com. If you're running a subfolder setup, 33 | ## be sure to account for that (ie https://example.com/forum). 34 | DISCOURSE_BASE_URL: 'https://discourse.example.com' 35 | 36 | ## The master API key of your Discourse forum. You can get this from 37 | ## the "API" tab of your admin panel. 38 | DISCOURSE_API_KEY: abcdefghijklmnop 39 | 40 | ## The username to use for processing incoming e-mail. Unless you have 41 | ## renamed the `system` user, you should leave this as-is. 42 | DISCOURSE_API_USERNAME: system 43 | 44 | volumes: 45 | - volume: 46 | host: /var/discourse/shared/mail-receiver/postfix-spool 47 | guest: /var/spool/postfix 48 | # uncomment to support TLS 49 | # - volume: 50 | # host: /var/discourse/shared/standalone/letsencrypt 51 | # guest: /letsencrypt 52 | 53 | 54 | -------------------------------------------------------------------------------- /samples/redis.yml: -------------------------------------------------------------------------------- 1 | templates: 2 | - "templates/redis.template.yml" 3 | 4 | env: 5 | LC_ALL: en_US.UTF-8 6 | LANG: en_US.UTF-8 7 | LANGUAGE: en_US.UTF-8 8 | 9 | # any extra arguments for Docker? 10 | # docker_args: 11 | 12 | volumes: 13 | - volume: 14 | host: /var/discourse/shared/redis 15 | guest: /shared 16 | - volume: 17 | host: /var/discourse/shared/redis/log/var-log 18 | guest: /var/log 19 | -------------------------------------------------------------------------------- /samples/standalone.yml: -------------------------------------------------------------------------------- 1 | ## this is the all-in-one, standalone Discourse Docker container template 2 | ## 3 | ## After making changes to this file, you MUST rebuild 4 | ## /var/discourse/launcher rebuild app 5 | ## 6 | ## BE *VERY* CAREFUL WHEN EDITING! 7 | ## YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT! 8 | ## visit http://www.yamllint.com/ to validate this file as needed 9 | 10 | templates: 11 | - "templates/postgres.template.yml" 12 | - "templates/redis.template.yml" 13 | - "templates/web.template.yml" 14 | - "templates/web.ratelimited.template.yml" 15 | ## Uncomment these two lines if you wish to add Lets Encrypt (https) 16 | #- "templates/web.ssl.template.yml" 17 | #- "templates/web.letsencrypt.ssl.template.yml" 18 | 19 | ## which TCP/IP ports should this container expose? 20 | ## If you want Discourse to share a port with another webserver like Apache or nginx, 21 | ## see https://meta.discourse.org/t/17247 for details 22 | expose: 23 | - "80:80" # http 24 | - "443:443" # https 25 | 26 | params: 27 | db_default_text_search_config: "pg_catalog.english" 28 | 29 | ## Set db_shared_buffers to a max of 25% of the total memory. 30 | ## will be set automatically by bootstrap based on detected RAM, or you can override 31 | #db_shared_buffers: "256MB" 32 | 33 | ## can improve sorting performance, but adds memory usage per-connection 34 | #db_work_mem: "40MB" 35 | 36 | ## Which Git revision should this container use? (default: tests-passed) 37 | #version: tests-passed 38 | 39 | env: 40 | LC_ALL: en_US.UTF-8 41 | LANG: en_US.UTF-8 42 | LANGUAGE: en_US.UTF-8 43 | # DISCOURSE_DEFAULT_LOCALE: en 44 | 45 | ## How many concurrent web requests are supported? Depends on memory and CPU cores. 46 | ## will be set automatically by bootstrap based on detected CPUs, or you can override 47 | #UNICORN_WORKERS: 3 48 | 49 | ## TODO: The domain name this Discourse instance will respond to 50 | ## Required. Discourse will not work with a bare IP number. 51 | DISCOURSE_HOSTNAME: 'discourse.example.com' 52 | 53 | ## Uncomment if you want the container to be started with the same 54 | ## hostname (-h option) as specified above (default "$hostname-$config") 55 | #DOCKER_USE_HOSTNAME: true 56 | 57 | ## TODO: List of comma delimited emails that will be made admin and developer 58 | ## on initial signup example 'user1@example.com,user2@example.com' 59 | DISCOURSE_DEVELOPER_EMAILS: 'me@example.com,you@example.com' 60 | 61 | ## TODO: The SMTP mail server used to validate new accounts and send notifications 62 | # SMTP ADDRESS is required 63 | # WARNING: SMTP password should be wrapped in quotes to avoid problems 64 | DISCOURSE_SMTP_ADDRESS: smtp.example.com 65 | #DISCOURSE_SMTP_PORT: 587 66 | DISCOURSE_SMTP_USER_NAME: user@example.com 67 | DISCOURSE_SMTP_PASSWORD: "pa$$word" 68 | #DISCOURSE_SMTP_ENABLE_START_TLS: true # (optional, default: true) 69 | #DISCOURSE_SMTP_DOMAIN: discourse.example.com # (required by some providers) 70 | #DISCOURSE_NOTIFICATION_EMAIL: noreply@discourse.example.com # (address to send notifications from) 71 | #DISCOURSE_SMTP_OPENSSL_VERIFY_MODE: peer # (optional, default: peer, valid values: none, peer, client_once, fail_if_no_peer_cert) 72 | #DISCOURSE_SMTP_AUTHENTICATION: plain # (default: plain, valid values: plain, login, cram_md5) 73 | 74 | ## If you added the Lets Encrypt template, uncomment below to get a free SSL certificate 75 | #LETSENCRYPT_ACCOUNT_EMAIL: me@example.com 76 | 77 | ## The http or https CDN address for this Discourse instance (configured to pull) 78 | ## see https://meta.discourse.org/t/14857 for details 79 | #DISCOURSE_CDN_URL: https://discourse-cdn.example.com 80 | 81 | ## The maxmind geolocation IP account ID and license key for IP address lookups 82 | ## see https://meta.discourse.org/t/-/173941 for details 83 | #DISCOURSE_MAXMIND_ACCOUNT_ID: 123456 84 | #DISCOURSE_MAXMIND_LICENSE_KEY: 1234567890123456 85 | 86 | ## The Docker container is stateless; all data is stored in /shared 87 | volumes: 88 | - volume: 89 | host: /var/discourse/shared/standalone 90 | guest: /shared 91 | - volume: 92 | host: /var/discourse/shared/standalone/log/var-log 93 | guest: /var/log 94 | 95 | ## Plugins go here 96 | ## see https://meta.discourse.org/t/19157 for details 97 | hooks: 98 | after_code: 99 | - exec: 100 | cd: $home/plugins 101 | cmd: 102 | - git clone https://github.com/discourse/docker_manager.git 103 | 104 | ## Any custom commands to run after building 105 | run: 106 | - exec: echo "Beginning of custom commands" 107 | ## If you want to set the 'From' email address for your first registration, uncomment and change: 108 | ## After getting the first signup email, re-comment the line. It only needs to run once. 109 | #- exec: rails r "SiteSetting.notification_email='info@unconfigured.discourse.org'" 110 | - exec: echo "End of custom commands" 111 | -------------------------------------------------------------------------------- /samples/web_only.yml: -------------------------------------------------------------------------------- 1 | # IMPORTANT: SET A SECRET PASSWORD in Postgres for the Discourse User 2 | # TODO: change SOME_SECRET in this template 3 | 4 | templates: 5 | - "templates/web.template.yml" 6 | - "templates/web.ratelimited.template.yml" 7 | ## Uncomment these two lines if you wish to add Lets Encrypt (https) 8 | #- "templates/web.ssl.template.yml" 9 | #- "templates/web.letsencrypt.ssl.template.yml" 10 | 11 | ## which TCP/IP ports should this container expose? 12 | ## If you want Discourse to share a port with another webserver like Apache or nginx, 13 | ## see https://meta.discourse.org/t/17247 for details 14 | expose: 15 | - "80:80" # http 16 | - "443:443" # https 17 | 18 | # Use 'links' key to link containers together, aka use Docker --link flag. 19 | links: 20 | - link: 21 | name: data 22 | alias: data 23 | 24 | # any extra arguments for Docker? 25 | # docker_args: 26 | 27 | params: 28 | ## Which Git revision should this container use? (default: tests-passed) 29 | #version: tests-passed 30 | 31 | env: 32 | LC_ALL: en_US.UTF-8 33 | LANG: en_US.UTF-8 34 | LANGUAGE: en_US.UTF-8 35 | # DISCOURSE_DEFAULT_LOCALE: en 36 | 37 | ## How many concurrent web requests are supported? Depends on memory and CPU cores. 38 | ## will be set automatically by bootstrap based on detected CPUs, or you can override 39 | #UNICORN_WORKERS: 3 40 | 41 | ## TODO: The domain name this Discourse instance will respond to 42 | DISCOURSE_HOSTNAME: 'discourse.example.com' 43 | 44 | ## Uncomment if you want the container to be started with the same 45 | ## hostname (-h option) as specified above (default "$hostname-$config") 46 | #DOCKER_USE_HOSTNAME: true 47 | 48 | ## TODO: List of comma delimited emails that will be made admin and developer 49 | ## on initial signup example 'user1@example.com,user2@example.com' 50 | DISCOURSE_DEVELOPER_EMAILS: 'me@example.com,you@example.com' 51 | 52 | ## TODO: The SMTP mail server used to validate new accounts and send notifications 53 | # SMTP ADDRESS, username, and password are required 54 | # WARNING the char '#' in SMTP password can cause problems! 55 | DISCOURSE_SMTP_ADDRESS: smtp.example.com 56 | #DISCOURSE_SMTP_PORT: 587 57 | DISCOURSE_SMTP_USER_NAME: user@example.com 58 | DISCOURSE_SMTP_PASSWORD: pa$$word 59 | #DISCOURSE_SMTP_ENABLE_START_TLS: true # (optional, default true) 60 | #DISCOURSE_SMTP_DOMAIN: discourse.example.com # (required by some providers) 61 | #DISCOURSE_NOTIFICATION_EMAIL: noreply@discourse.example.com # (address to send notifications from) 62 | 63 | ## If you added the Lets Encrypt template, uncomment below to get a free SSL certificate 64 | #LETSENCRYPT_ACCOUNT_EMAIL: me@example.com 65 | 66 | ## TODO: configure connectivity to the databases 67 | DISCOURSE_DB_SOCKET: '' 68 | #DISCOURSE_DB_USERNAME: discourse 69 | DISCOURSE_DB_PASSWORD: SOME_SECRET 70 | DISCOURSE_DB_HOST: data 71 | DISCOURSE_REDIS_HOST: data 72 | 73 | ## The http or https CDN address for this Discourse instance (configured to pull) 74 | ## see https://meta.discourse.org/t/14857 for details 75 | #DISCOURSE_CDN_URL: https://discourse-cdn.example.com 76 | 77 | ## The maxmind geolocation IP account ID and license key for IP address lookups 78 | ## see https://meta.discourse.org/t/-/173941 for details 79 | #DISCOURSE_MAXMIND_ACCOUNT_ID: 123456 80 | #DISCOURSE_MAXMIND_LICENSE_KEY: 1234567890123456 81 | 82 | volumes: 83 | - volume: 84 | host: /var/discourse/shared/web-only 85 | guest: /shared 86 | - volume: 87 | host: /var/discourse/shared/web-only/log/var-log 88 | guest: /var/log 89 | 90 | ## Plugins go here 91 | ## see https://meta.discourse.org/t/19157 for details 92 | hooks: 93 | after_code: 94 | - exec: 95 | cd: $home/plugins 96 | cmd: 97 | - git clone https://github.com/discourse/docker_manager.git 98 | 99 | ## Remember, this is YAML syntax - you can only have one block with a name 100 | run: 101 | - exec: echo "Beginning of custom commands" 102 | 103 | ## If you want to configure password login for root, uncomment and change: 104 | ## Use only one of the following lines: 105 | #- exec: /usr/sbin/usermod -p 'PASSWORD_HASH' root 106 | #- exec: /usr/sbin/usermod -p "$(mkpasswd -m sha-256 'RAW_PASSWORD')" root 107 | 108 | ## If you want to authorized additional users, uncomment and change: 109 | #- exec: ssh-import-id username 110 | #- exec: ssh-import-id anotherusername 111 | 112 | - exec: echo "End of custom commands" 113 | - exec: awk -F\# '{print $1;}' ~/.ssh/authorized_keys | awk 'BEGIN { print "Authorized SSH keys for this container:"; } NF>=2 {print $NF;}' 114 | -------------------------------------------------------------------------------- /scripts/launcher-completion.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 3 | CONTAINER_DIR=$DIR/../containers/ 4 | 5 | _containers_compgen_filenames() { 6 | local cur="$1" 7 | compgen -G "$CONTAINER_DIR$cur*.yml" -- $CONTAINER_DIR"$cur" | xargs -n 1 basename -s .yml 8 | } 9 | 10 | _launcher () 11 | { 12 | local cur 13 | 14 | switches='--skip-prereqs --docker-args --skip-mac-address --run-image' 15 | commands='start stop restart destroy enter logs bootstrap run rebuild cleanup start-cmd' 16 | 17 | COMPREPLY=() 18 | cur=${COMP_WORDS[COMP_CWORD]} 19 | 20 | case $COMP_CWORD in 21 | 1) 22 | COMPREPLY=( $( compgen -W "$commands" -- $cur ) );; 23 | 2) 24 | COMPREPLY=( $(_containers_compgen_filenames "$cur") ) ;; 25 | *) 26 | COMPREPLY=( $( compgen -W "$switches" -- $cur ) );; 27 | esac 28 | return 0 29 | } 30 | 31 | _discourse_setup() 32 | { 33 | local cur 34 | switches='--debug --skip-rebuild --two-container --skip-connection-test' 35 | 36 | cur=${COMP_WORDS[COMP_CWORD]} 37 | COMPREPLY=( $(compgen -W "$switches" -- $cur ) ) 38 | return 0 39 | } 40 | 41 | complete -F _launcher launcher 42 | complete -F _launcher ./launcher 43 | complete -F _discourse_setup discourse-setup 44 | complete -F _discourse_setup ./discourse-setup 45 | -------------------------------------------------------------------------------- /shared/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discourse/discourse_docker/9ebce86fda3d9787d752de6311df15dcab9700f1/shared/.gitkeep -------------------------------------------------------------------------------- /templates/README.md: -------------------------------------------------------------------------------- 1 | [pups](https://github.com/samsaffron/pups)-managed templates you may use to bootstrap your environment. 2 | -------------------------------------------------------------------------------- /templates/cache-dns.template.yml: -------------------------------------------------------------------------------- 1 | # You can use this template to cache DNS lookups for 2 | # 3 | # DISCOURSE_DB_HOST 4 | # DISCOURSE_DB_BACKUP_HOST 5 | # DISCOURSE_REDIS_HOST 6 | # DISCOURSE_REDIS_SLAVE_HOST 7 | # 8 | # If you are using a hostname vs IP for these entries and have 9 | # an internal DNS outage Discourse will stop working 10 | # 11 | # This template allows you to cache entry in the hosts file 12 | # 13 | # Note, for this to work correctly you must also install the 14 | # discourse-prometheus plugin to collect stats 15 | 16 | hooks: 17 | after_bundle_exec: 18 | - exec: "mkdir -p /etc/service/cache_critical_dns/" 19 | - exec: "cp /var/www/discourse/script/cache_critical_dns /etc/service/cache_critical_dns/run" 20 | -------------------------------------------------------------------------------- /templates/cloudflare.template.yml: -------------------------------------------------------------------------------- 1 | run: 2 | - file: 3 | path: /tmp/add-cloudflare-ips 4 | chmod: +x 5 | contents: | 6 | #!/bin/bash -e 7 | # Download list of CloudFlare ips 8 | wget -q https://www.cloudflare.com/ips-v4/ -O - > /tmp/cloudflare-ips 9 | echo >> /tmp/cloudflare-ips 10 | wget -q https://www.cloudflare.com/ips-v6/ -O - >> /tmp/cloudflare-ips 11 | # Make into nginx commands and escape for inclusion into sed append command 12 | CONTENTS=$(> /etc/apt/sources.list.d/google-chrome.list 10 | - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y google-chrome-stable -------------------------------------------------------------------------------- /templates/import/mbox.template.yml: -------------------------------------------------------------------------------- 1 | # This template configures the container for importing emails and mbox files. 2 | 3 | params: 4 | home: /var/www/discourse 5 | 6 | hooks: 7 | after_web_config: 8 | - file: 9 | path: /usr/local/bin/import_mbox.sh 10 | chmod: "+x" 11 | contents: | 12 | #!/bin/bash 13 | set -e 14 | 15 | chown discourse /shared/import/settings.yml 16 | chown discourse -R /shared/import/data 17 | 18 | cd $home 19 | echo "The mbox import is starting..." 20 | echo 21 | su discourse -c 'bundle exec ruby script/import_scripts/mbox.rb /shared/import/settings.yml' 22 | 23 | - exec: 24 | cd: $home 25 | cmd: 26 | - mkdir -p /shared/import/data 27 | - chown discourse -R /shared/import 28 | - cp -n script/import_scripts/mbox/settings.yml /shared/import/settings.yml 29 | 30 | after_bundle_exec: 31 | - exec: 32 | cd: $home 33 | cmd: 34 | - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y libsqlite3-dev 35 | - echo "gem 'sqlite3'" >> Gemfile 36 | - su discourse -c 'bundle config unset deployment' 37 | - su discourse -c 'bundle install --no-deployment --path vendor/bundle --jobs $(($(nproc) - 1)) --without test development' 38 | -------------------------------------------------------------------------------- /templates/import/mssql-dep.template.yml: -------------------------------------------------------------------------------- 1 | # This template adds the 'tiny_tds' gem for import scripts depending on it 2 | 3 | params: 4 | home: /var/www/discourse 5 | 6 | hooks: 7 | after_web_config: 8 | - exec: 9 | cd: /tmp 10 | cmd: 11 | - wget -q -O freetds.tar.gz http://www.freetds.org/files/stable/freetds-1.00.91.tar.gz 12 | - tar -xzf freetds.tar.gz 13 | - exec: 14 | cd: /tmp/freetds-* 15 | cmd: 16 | - ./configure --prefix=/usr/local --with-tdsver=7.3 17 | - make && make install 18 | 19 | after_bundle_exec: 20 | - exec: 21 | cd: $home 22 | cmd: 23 | - echo "gem 'tiny_tds'" >> Gemfile 24 | - su discourse -c 'bundle config unset deployment' 25 | - su discourse -c 'bundle install --no-deployment --path vendor/bundle --jobs $(($(nproc) - 1)) --without test development' 26 | -------------------------------------------------------------------------------- /templates/import/mysql-dep.template.yml: -------------------------------------------------------------------------------- 1 | # This template adds the 'mysql2' gem for import scripts depending on it 2 | 3 | params: 4 | home: /var/www/discourse 5 | 6 | hooks: 7 | after_bundle_exec: 8 | - exec: 9 | cd: $home 10 | cmd: 11 | - echo "gem 'mysql2'" >> Gemfile 12 | - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y libmariadb-dev 13 | - su discourse -c 'bundle config unset deployment' 14 | - su discourse -c 'bundle install --no-deployment --path vendor/bundle --jobs $(($(nproc) - 1)) --without test development' 15 | -------------------------------------------------------------------------------- /templates/import/phpbb3.template.yml: -------------------------------------------------------------------------------- 1 | # This template installs MariaDB and all dependencies needed for importing from phpBB3. 2 | 3 | env: 4 | UNICORN_SIDEKIQS: 0 5 | 6 | params: 7 | home: /var/www/discourse 8 | 9 | hooks: 10 | after_web_config: 11 | - exec: 12 | cd: /etc/service 13 | cmd: 14 | - rm -R nginx 15 | - rm -R cron 16 | 17 | - exec: 18 | cd: /etc/runit/3.d 19 | cmd: 20 | - rm 01-nginx 21 | 22 | - file: 23 | path: /etc/mysql/conf.d/import.cnf 24 | contents: | 25 | [mysqld] 26 | # disable InnoDB since it is extremely slow in Docker container 27 | default-storage-engine=MyISAM 28 | default-tmp-storage-engine=MyISAM 29 | innodb=OFF 30 | sql_mode=NO_AUTO_CREATE_USER 31 | 32 | datadir=/shared/import/mysql/data 33 | 34 | skip-host-cache 35 | skip-name-resolve 36 | 37 | - exec: 38 | cmd: 39 | - mkdir -p /shared/import/mysql/data 40 | - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y nano libmariadb-dev mariadb-server 41 | - sed -Ei 's/^log/#&/' /etc/mysql/my.cnf 42 | 43 | - file: 44 | path: /etc/service/mysql/run 45 | chmod: "+x" 46 | contents: | 47 | #!/bin/bash 48 | cd / 49 | umask 077 50 | 51 | # Make sure the datadir exists, is accessible and contains all system tables 52 | mkdir -p /shared/import/mysql/data 53 | chown mysql -R /shared/import/mysql/data 54 | /usr/bin/mysql_install_db --user=mysql 55 | 56 | # Shamelessly copied from http://smarden.org/runit1/runscripts.html#mysql 57 | MYSQLADMIN='/usr/bin/mysqladmin --defaults-extra-file=/etc/mysql/debian.cnf' 58 | trap "$MYSQLADMIN shutdown" 0 59 | trap 'exit 2' 1 2 3 15 60 | /usr/bin/mysqld_safe & wait 61 | 62 | - file: 63 | path: /etc/runit/3.d/99-mysql 64 | chmod: "+x" 65 | contents: | 66 | #!/bin/bash 67 | sv stop mysql 68 | 69 | - file: 70 | path: /usr/local/bin/import_phpbb3.sh 71 | chmod: "+x" 72 | contents: | 73 | #!/bin/bash 74 | set -e 75 | 76 | chown discourse /shared/import/settings.yml 77 | chown discourse -R /shared/import/data 78 | 79 | # Set password for root user 80 | mysql -uroot -e "ALTER USER root@localhost IDENTIFIED VIA mysql_native_password USING PASSWORD('password'); FLUSH PRIVILEGES;" &>/dev/null || true 81 | 82 | if [ -f "/shared/import/data/phpbb_mysql.sql" ]; then 83 | if [ -f "/shared/import/mysql/imported" ] && ! sha256sum --check /shared/import/mysql/imported &>/dev/null ; then 84 | echo "Checksum of database dump changed..." 85 | rm /shared/import/mysql/imported 86 | fi 87 | 88 | if [ ! -f "/shared/import/mysql/imported" ]; then 89 | echo "Loading database dump into MySQL..." 90 | mysql -uroot -ppassword -e "DROP DATABASE IF EXISTS phpbb" 91 | mysql -uroot -ppassword -e "CREATE DATABASE phpbb" 92 | mysql -uroot -ppassword --default-character-set=utf8 --database=phpbb < /shared/import/data/phpbb_mysql.sql 93 | sha256sum /shared/import/data/phpbb_mysql.sql > /shared/import/mysql/imported 94 | fi 95 | else 96 | sv stop mysql 97 | fi 98 | 99 | cd $home 100 | echo "The phpBB3 import is starting..." 101 | echo 102 | su discourse -c 'bundle exec ruby script/import_scripts/phpbb3.rb /shared/import/settings.yml' 103 | 104 | - exec: 105 | cd: $home 106 | cmd: 107 | - mkdir -p /shared/import/data 108 | - chown discourse -R /shared/import 109 | - cp -n script/import_scripts/phpbb3/settings.yml /shared/import/settings.yml 110 | 111 | after_bundle_exec: 112 | - exec: 113 | cd: $home 114 | cmd: 115 | - echo "gem 'mysql2'" >> Gemfile 116 | - echo "gem 'ruby-bbcode-to-md', :github => 'nlalonde/ruby-bbcode-to-md'" >> Gemfile 117 | - su discourse -c 'bundle config unset deployment' 118 | - su discourse -c 'bundle install --no-deployment --path vendor/bundle --jobs $(($(nproc) - 1)) --without test development' 119 | -------------------------------------------------------------------------------- /templates/import/vanilla.template.yml: -------------------------------------------------------------------------------- 1 | # This template installs MariaDB and all dependencies needed for importing from vanilla. 2 | env: 3 | UNICORN_SIDEKIQS: 0 4 | 5 | 6 | params: 7 | home: /var/www/discourse 8 | 9 | hooks: 10 | after_web_config: 11 | - exec: 12 | cd: /etc/runit/3.d 13 | cmd: 14 | - rm 01-nginx 15 | - rm 02-unicorn 16 | 17 | - file: 18 | path: /etc/mysql/conf.d/import.cnf 19 | contents: | 20 | [mysqld] 21 | # disable InnoDB since it is extremely slow in Docker container 22 | default-storage-engine=MyISAM 23 | default-tmp-storage-engine=MyISAM 24 | innodb=OFF 25 | sql_mode=NO_AUTO_CREATE_USER 26 | 27 | datadir=/shared/import/mysql/data 28 | 29 | skip-host-cache 30 | skip-name-resolve 31 | 32 | - exec: 33 | cmd: 34 | - mkdir -p /shared/import/mysql/data 35 | - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y nano libmariadb-dev mariadb-server 36 | - sed -Ei 's/^log/#&/' /etc/mysql/my.cnf 37 | 38 | - file: 39 | path: /etc/service/mysql/run 40 | chmod: "+x" 41 | contents: | 42 | #!/bin/bash 43 | cd / 44 | umask 077 45 | 46 | # Make sure the datadir exists, is accessible and contains all system tables 47 | mkdir -p /shared/import/mysql/data 48 | chown mysql -R /shared/import/mysql/data 49 | /usr/bin/mysql_install_db --user=mysql 50 | 51 | # Shamelessly copied from http://smarden.org/runit1/runscripts.html#mysql 52 | MYSQLADMIN='/usr/bin/mysqladmin --defaults-extra-file=/etc/mysql/debian.cnf' 53 | trap "$MYSQLADMIN shutdown" 0 54 | trap 'exit 2' 1 2 3 15 55 | /usr/bin/mysqld_safe & wait 56 | 57 | - file: 58 | path: /etc/runit/3.d/99-mysql 59 | chmod: "+x" 60 | contents: | 61 | #!/bin/bash 62 | sv stop mysql 63 | 64 | - file: 65 | path: /usr/local/bin/import_vanilla.sh 66 | chmod: "+x" 67 | contents: | 68 | #!/bin/bash 69 | set -e 70 | 71 | chown discourse -R /shared/import/data 72 | 73 | # Allow connection as root user without password 74 | mysql -uroot -e "UPDATE mysql.user SET plugin = 'mysql_native_password' WHERE user = 'root' AND plugin = 'unix_socket'" 75 | mysql -uroot -e "FLUSH PRIVILEGES" 76 | 77 | if [ -f "/shared/import/data/vanilla_mysql.sql" ]; then 78 | if [ -f "/shared/import/mysql/imported" ] && ! sha256sum --check /shared/import/mysql/imported &>/dev/null ; then 79 | echo "Checksum of database dump changed..." 80 | rm /shared/import/mysql/imported 81 | fi 82 | 83 | if [ ! -f "/shared/import/mysql/imported" ]; then 84 | echo "Loading database dump into MySQL..." 85 | mysql -uroot -e "DROP DATABASE IF EXISTS vanilla" 86 | mysql -uroot -e "CREATE DATABASE vanilla" 87 | mysql -uroot --default-character-set=utf8 --database=vanilla < /shared/import/data/vanilla_mysql.sql 88 | sha256sum /shared/import/data/vanilla_mysql.sql > /shared/import/mysql/imported 89 | fi 90 | else 91 | sv stop mysql 92 | fi 93 | 94 | echo "import_vanilla.sh completed" 95 | 96 | - exec: 97 | cd: $home 98 | cmd: 99 | - mkdir -p /shared/import/data 100 | - chown discourse -R /shared/import 101 | 102 | before_code: 103 | - exec: 104 | cd: $home 105 | cmd: 106 | # Add your discourse core fork and pull custom code 107 | - su discourse -c 'git remote set-url origin https://github.com/{github_username}/discourse.git' 108 | 109 | after_bundle_exec: 110 | - exec: 111 | cd: $home 112 | cmd: 113 | # Add the gems used in the script 114 | - echo "gem 'mysql2'" >> Gemfile 115 | - echo "gem 'ruby-bbcode-to-md', :github => 'nlalonde/ruby-bbcode-to-md'" >> Gemfile 116 | - su discourse -c 'bundle config unset deployment' 117 | - su discourse -c 'bundle install --no-deployment --path vendor/bundle --jobs $(($(nproc) - 1)) --without test development' 118 | - service mariadb start 119 | # imports the DB into mysql 120 | - sh /usr/local/bin/import_flarum_test.sh 121 | -------------------------------------------------------------------------------- /templates/offline-page.template.yml: -------------------------------------------------------------------------------- 1 | env: 2 | MIGRATE_ON_BOOT: 1 3 | PRECOMPILE_ON_BOOT: 1 4 | CREATE_DB_ON_BOOT: 1 5 | 6 | params: 7 | offline_page_repository: https://github.com/discourse/discourse-offline-page.git 8 | 9 | run: 10 | - file: 11 | path: "/etc/nginx/conf.d/outlets/server/30-offline-page.conf" 12 | contents: | 13 | error_page 502 /error_page.html; 14 | location /error_page.html { 15 | root /var/www/discourse-offline-page/html; 16 | internal; 17 | } 18 | 19 | - exec: 20 | cmd: git clone $offline_page_repository /var/www/discourse-offline-page 21 | raise_on_fail: false 22 | -------------------------------------------------------------------------------- /templates/postgres.10.template.yml: -------------------------------------------------------------------------------- 1 | params: 2 | db_synchronous_commit: "off" 3 | db_shared_buffers: "256MB" 4 | db_work_mem: "10MB" 5 | db_default_text_search_config: "pg_catalog.english" 6 | db_name: discourse 7 | db_user: discourse 8 | db_checkpoint_segments: 6 9 | db_logging_collector: off 10 | db_log_min_duration_statement: 100 11 | 12 | hooks: 13 | before_code: 14 | - replace: 15 | filename: /etc/service/unicorn/run 16 | from: "# postgres" 17 | to: sv start postgres || exit 1 18 | 19 | run: 20 | - exec: DEBIAN_FRONTEND=noninteractive apt-get purge -y postgresql-15 postgresql-client-15 postgresql-contrib-15 21 | - exec: apt-get update && apt-get install -y postgresql-10 postgresql-client-10 postgresql-contrib-10 22 | - exec: mkdir -p /shared/postgres_run 23 | - exec: chown postgres:postgres /shared/postgres_run 24 | - exec: chmod 775 /shared/postgres_run 25 | - exec: rm -fr /var/run/postgresql 26 | - exec: ln -s /shared/postgres_run /var/run/postgresql 27 | - exec: socat /dev/null UNIX-CONNECT:/shared/postgres_run/.s.PGSQL.5432 || exit 0 && echo postgres already running stop container ; exit 1 28 | - exec: rm -fr /shared/postgres_run/.s* 29 | - exec: rm -fr /shared/postgres_run/*.pid 30 | - exec: mkdir -p /shared/postgres_run/10-main.pg_stat_tmp 31 | - exec: chown postgres:postgres /shared/postgres_run/10-main.pg_stat_tmp 32 | - file: 33 | path: /etc/service/postgres/run 34 | chmod: "+x" 35 | contents: | 36 | #!/bin/sh 37 | exec 2>&1 38 | echo -1000 >/proc/self/oom_score_adj 39 | HOME=/var/lib/postgresql USER=postgres exec chpst -u postgres:postgres:ssl-cert -U postgres:postgres:ssl-cert /usr/lib/postgresql/10/bin/postmaster -D /etc/postgresql/10/main 40 | 41 | - file: 42 | path: /etc/runit/3.d/99-postgres 43 | chmod: "+x" 44 | contents: | 45 | #!/bin/bash 46 | sv stop postgres 47 | 48 | - exec: 49 | cmd: 50 | - chown -R root /var/lib/postgresql/10/main 51 | - "[ ! -e /shared/postgres_data ] && install -d -m 0755 -o postgres -g postgres /shared/postgres_data && sudo -E -u postgres /usr/lib/postgresql/10/bin/initdb -D /shared/postgres_data || exit 0" 52 | - chown -R postgres:postgres /shared/postgres_data 53 | - chown -R postgres:postgres /var/run/postgresql 54 | 55 | - replace: 56 | filename: "/etc/postgresql/10/main/postgresql.conf" 57 | from: "data_directory = '/var/lib/postgresql/10/main'" 58 | to: "data_directory = '/shared/postgres_data'" 59 | 60 | # listen on all interfaces 61 | - replace: 62 | filename: "/etc/postgresql/10/main/postgresql.conf" 63 | from: /#?listen_addresses *=.*/ 64 | to: "listen_addresses = '*'" 65 | 66 | # sync commit off is faster and less spiky, also marginally less safe 67 | - replace: 68 | filename: "/etc/postgresql/10/main/postgresql.conf" 69 | from: /#?synchronous_commit *=.*/ 70 | to: "synchronous_commit = $db_synchronous_commit" 71 | 72 | # default is 128MB which is way too small 73 | - replace: 74 | filename: "/etc/postgresql/10/main/postgresql.conf" 75 | from: /#?shared_buffers *=.*/ 76 | to: "shared_buffers = $db_shared_buffers" 77 | 78 | # default is 1MB which is too small 79 | - replace: 80 | filename: "/etc/postgresql/10/main/postgresql.conf" 81 | from: /#?work_mem *=.*/ 82 | to: "work_mem = $db_work_mem" 83 | 84 | # allow for other 85 | - replace: 86 | filename: "/etc/postgresql/10/main/postgresql.conf" 87 | from: /#?default_text_search_config *=.*/ 88 | to: "default_text_search_config = '$db_default_text_search_config'" 89 | 90 | # Necessary to enable backups 91 | - exec: 92 | cmd: 93 | - install -d -m 0755 -o postgres -g postgres /shared/postgres_backup 94 | 95 | - replace: 96 | filename: "/etc/postgresql/10/main/postgresql.conf" 97 | from: /#?checkpoint_segments *=.*/ 98 | to: "checkpoint_segments = $db_checkpoint_segments" 99 | 100 | - replace: 101 | filename: "/etc/postgresql/10/main/postgresql.conf" 102 | from: /#?logging_collector *=.*/ 103 | to: "logging_collector = $db_logging_collector" 104 | 105 | - replace: 106 | filename: "/etc/postgresql/10/main/postgresql.conf" 107 | from: /#?log_min_duration_statement *=.*/ 108 | to: "log_min_duration_statement = $db_log_min_duration_statement" 109 | 110 | - replace: 111 | filename: "/etc/postgresql/10/main/pg_hba.conf" 112 | from: /^#local +replication +postgres +peer$/ 113 | to: "local replication postgres peer" 114 | 115 | # allow all to connect in with md5 auth 116 | - replace: 117 | filename: "/etc/postgresql/10/main/pg_hba.conf" 118 | from: /^host.*all.*all.*127.*$/ 119 | to: "host all all 0.0.0.0/0 md5" 120 | 121 | # allow all to connect in with md5 auth (IPv6) 122 | - replace: 123 | filename: "/etc/postgresql/10/main/pg_hba.conf" 124 | from: /^host.*all.*all.*::1\/128.*$/ 125 | to: "host all all ::/0 md5" 126 | 127 | - exec: 128 | background: true 129 | # use fast shutdown for pg 130 | stop_signal: INT 131 | cmd: HOME=/var/lib/postgresql USER=postgres exec chpst -u postgres:postgres:ssl-cert -U postgres:postgres:ssl-cert /usr/lib/postgresql/10/bin/postmaster -D /etc/postgresql/10/main 132 | 133 | # give db a few secs to start up 134 | - exec: "sleep 5" 135 | 136 | - exec: su postgres -c 'createdb $db_name' || true 137 | - exec: su postgres -c 'psql $db_name -c "create user $db_user;"' || true 138 | - exec: su postgres -c 'psql $db_name -c "grant all privileges on database $db_name to $db_user;"' || true 139 | - exec: su postgres -c 'psql $db_name -c "alter schema public owner to $db_user;"' 140 | - exec: su postgres -c 'psql template1 -c "create extension if not exists hstore;"' 141 | - exec: su postgres -c 'psql template1 -c "create extension if not exists pg_trgm;"' 142 | - exec: su postgres -c 'psql $db_name -c "create extension if not exists hstore;"' 143 | - exec: su postgres -c 'psql $db_name -c "create extension if not exists pg_trgm;"' 144 | - exec: su postgres -c 'psql $db_name -c "create extension if not exists unaccent;"' 145 | - exec: 146 | stdin: | 147 | update pg_database set encoding = pg_char_to_encoding('UTF8') where datname = '$db_name' AND encoding = pg_char_to_encoding('SQL_ASCII'); 148 | cmd: sudo -u postgres psql $db_name 149 | raise_on_fail: false 150 | 151 | - file: 152 | path: /var/lib/postgresql/take-database-backup 153 | chown: postgres:postgres 154 | chmod: "+x" 155 | contents: | 156 | #!/bin/bash 157 | ID=db-$(date +%F_%T) 158 | FILENAME=/shared/postgres_backup/$ID.tar.gz 159 | pg_basebackup --format=tar --pgdata=- --wal-method=fetch --gzip --label=$ID > $FILENAME 160 | echo $FILENAME 161 | 162 | - file: 163 | path: /var/spool/cron/crontabs/postgres 164 | contents: | 165 | # m h dom mon dow command 166 | #MAILTO=? 167 | #0 */4 * * * /var/lib/postgresql/take-database-backup 168 | 169 | - exec: 170 | hook: postgres 171 | cmd: "echo postgres installed!" 172 | -------------------------------------------------------------------------------- /templates/postgres.12.template.yml: -------------------------------------------------------------------------------- 1 | params: 2 | db_synchronous_commit: "off" 3 | db_shared_buffers: "256MB" 4 | db_work_mem: "10MB" 5 | db_default_text_search_config: "pg_catalog.english" 6 | db_name: discourse 7 | db_user: discourse 8 | db_checkpoint_segments: 6 9 | db_logging_collector: off 10 | db_log_min_duration_statement: 100 11 | 12 | hooks: 13 | before_code: 14 | - replace: 15 | filename: /etc/service/unicorn/run 16 | from: "# postgres" 17 | to: sv start postgres || exit 1 18 | 19 | run: 20 | - exec: DEBIAN_FRONTEND=noninteractive apt-get purge -y postgresql-15 postgresql-client-15 postgresql-contrib-15 21 | - exec: apt-get update && apt-get install -y postgresql-12 postgresql-client-12 postgresql-contrib-12 22 | - exec: mkdir -p /shared/postgres_run 23 | - exec: chown postgres:postgres /shared/postgres_run 24 | - exec: chmod 775 /shared/postgres_run 25 | - exec: rm -fr /var/run/postgresql 26 | - exec: ln -s /shared/postgres_run /var/run/postgresql 27 | - exec: socat /dev/null UNIX-CONNECT:/shared/postgres_run/.s.PGSQL.5432 || exit 0 && echo postgres already running stop container ; exit 1 28 | - exec: rm -fr /shared/postgres_run/.s* 29 | - exec: rm -fr /shared/postgres_run/*.pid 30 | - exec: mkdir -p /shared/postgres_run/12-main.pg_stat_tmp 31 | - exec: chown postgres:postgres /shared/postgres_run/12-main.pg_stat_tmp 32 | - file: 33 | path: /etc/service/postgres/run 34 | chmod: "+x" 35 | contents: | 36 | #!/bin/sh 37 | exec 2>&1 38 | HOME=/var/lib/postgresql USER=postgres exec chpst -u postgres:postgres:ssl-cert -U postgres:postgres:ssl-cert /usr/lib/postgresql/12/bin/postmaster -D /etc/postgresql/12/main 39 | 40 | - file: 41 | path: /etc/runit/3.d/99-postgres 42 | chmod: "+x" 43 | contents: | 44 | #!/bin/bash 45 | sv stop postgres 46 | 47 | - exec: 48 | cmd: 49 | - chown -R root /var/lib/postgresql/12/main 50 | - "[ ! -e /shared/postgres_data ] && install -d -m 0755 -o postgres -g postgres /shared/postgres_data && sudo -E -u postgres /usr/lib/postgresql/12/bin/initdb -D /shared/postgres_data || exit 0" 51 | - chown -R postgres:postgres /shared/postgres_data 52 | - chown -R postgres:postgres /var/run/postgresql 53 | 54 | - replace: 55 | filename: "/etc/postgresql/12/main/postgresql.conf" 56 | from: "data_directory = '/var/lib/postgresql/12/main'" 57 | to: "data_directory = '/shared/postgres_data'" 58 | 59 | # listen on all interfaces 60 | - replace: 61 | filename: "/etc/postgresql/12/main/postgresql.conf" 62 | from: /#?listen_addresses *=.*/ 63 | to: "listen_addresses = '*'" 64 | 65 | # sync commit off is faster and less spiky, also marginally less safe 66 | - replace: 67 | filename: "/etc/postgresql/12/main/postgresql.conf" 68 | from: /#?synchronous_commit *=.*/ 69 | to: "synchronous_commit = $db_synchronous_commit" 70 | 71 | # default is 128MB which is way too small 72 | - replace: 73 | filename: "/etc/postgresql/12/main/postgresql.conf" 74 | from: /#?shared_buffers *=.*/ 75 | to: "shared_buffers = $db_shared_buffers" 76 | 77 | # default is 1MB which is too small 78 | - replace: 79 | filename: "/etc/postgresql/12/main/postgresql.conf" 80 | from: /#?work_mem *=.*/ 81 | to: "work_mem = $db_work_mem" 82 | 83 | # allow for other 84 | - replace: 85 | filename: "/etc/postgresql/12/main/postgresql.conf" 86 | from: /#?default_text_search_config *=.*/ 87 | to: "default_text_search_config = '$db_default_text_search_config'" 88 | 89 | # Necessary to enable backups 90 | - exec: 91 | cmd: 92 | - install -d -m 0755 -o postgres -g postgres /shared/postgres_backup 93 | 94 | - replace: 95 | filename: "/etc/postgresql/12/main/postgresql.conf" 96 | from: /#?checkpoint_segments *=.*/ 97 | to: "checkpoint_segments = $db_checkpoint_segments" 98 | 99 | - replace: 100 | filename: "/etc/postgresql/12/main/postgresql.conf" 101 | from: /#?logging_collector *=.*/ 102 | to: "logging_collector = $db_logging_collector" 103 | 104 | - replace: 105 | filename: "/etc/postgresql/12/main/postgresql.conf" 106 | from: /#?log_min_duration_statement *=.*/ 107 | to: "log_min_duration_statement = $db_log_min_duration_statement" 108 | 109 | - replace: 110 | filename: "/etc/postgresql/12/main/pg_hba.conf" 111 | from: /^#local +replication +postgres +peer$/ 112 | to: "local replication postgres peer" 113 | 114 | # allow all to connect in with md5 auth 115 | - replace: 116 | filename: "/etc/postgresql/12/main/pg_hba.conf" 117 | from: /^host.*all.*all.*127.*$/ 118 | to: "host all all 0.0.0.0/0 md5" 119 | 120 | # allow all to connect in with md5 auth (IPv6) 121 | - replace: 122 | filename: "/etc/postgresql/12/main/pg_hba.conf" 123 | from: /^host.*all.*all.*::1\/128.*$/ 124 | to: "host all all ::/0 md5" 125 | 126 | - exec: 127 | background: true 128 | # use fast shutdown for pg 129 | stop_signal: INT 130 | cmd: HOME=/var/lib/postgresql USER=postgres exec chpst -u postgres:postgres:ssl-cert -U postgres:postgres:ssl-cert /usr/lib/postgresql/12/bin/postmaster -D /etc/postgresql/12/main 131 | 132 | # give db a few secs to start up 133 | - exec: "sleep 5" 134 | 135 | - exec: su postgres -c 'createdb $db_name' || true 136 | - exec: su postgres -c 'psql $db_name -c "create user $db_user;"' || true 137 | - exec: su postgres -c 'psql $db_name -c "grant all privileges on database $db_name to $db_user;"' || true 138 | - exec: su postgres -c 'psql $db_name -c "alter schema public owner to $db_user;"' 139 | - exec: su postgres -c 'psql template1 -c "create extension if not exists hstore;"' 140 | - exec: su postgres -c 'psql template1 -c "create extension if not exists pg_trgm;"' 141 | - exec: su postgres -c 'psql $db_name -c "create extension if not exists hstore;"' 142 | - exec: su postgres -c 'psql $db_name -c "create extension if not exists pg_trgm;"' 143 | - exec: su postgres -c 'psql $db_name -c "create extension if not exists unaccent;"' 144 | - exec: 145 | stdin: | 146 | update pg_database set encoding = pg_char_to_encoding('UTF8') where datname = '$db_name' AND encoding = pg_char_to_encoding('SQL_ASCII'); 147 | cmd: sudo -u postgres psql $db_name 148 | raise_on_fail: false 149 | 150 | - file: 151 | path: /var/lib/postgresql/take-database-backup 152 | chown: postgres:postgres 153 | chmod: "+x" 154 | contents: | 155 | #!/bin/bash 156 | ID=db-$(date +%F_%T) 157 | FILENAME=/shared/postgres_backup/$ID.tar.gz 158 | pg_basebackup --format=tar --pgdata=- --wal-method=fetch --gzip --label=$ID > $FILENAME 159 | echo $FILENAME 160 | 161 | - file: 162 | path: /var/spool/cron/crontabs/postgres 163 | contents: | 164 | # m h dom mon dow command 165 | #MAILTO=? 166 | #0 */4 * * * /var/lib/postgresql/take-database-backup 167 | 168 | - exec: 169 | hook: postgres 170 | cmd: "echo postgres installed!" 171 | -------------------------------------------------------------------------------- /templates/postgres.13.template.yml: -------------------------------------------------------------------------------- 1 | params: 2 | db_synchronous_commit: "off" 3 | db_shared_buffers: "256MB" 4 | db_work_mem: "10MB" 5 | db_default_text_search_config: "pg_catalog.english" 6 | db_name: discourse 7 | db_user: discourse 8 | db_checkpoint_segments: 6 9 | db_logging_collector: off 10 | db_log_min_duration_statement: 100 11 | 12 | hooks: 13 | before_code: 14 | - replace: 15 | filename: /etc/service/unicorn/run 16 | from: "# postgres" 17 | to: | 18 | if [ -f /root/install_postgres ]; then 19 | /root/install_postgres 20 | rm /root/install_postgres 21 | fi 22 | sv start postgres || exit 1 23 | 24 | run: 25 | - exec: DEBIAN_FRONTEND=noninteractive apt-get purge -y postgresql-15 postgresql-client-15 postgresql-contrib-15 postgresql-15-pgvector 26 | - exec: apt-get update && apt-get install -y postgresql-13 postgresql-client-13 postgresql-contrib-13 postgresql-13-pgvector 27 | - file: 28 | path: /etc/service/postgres/run 29 | chmod: "+x" 30 | contents: | 31 | #!/bin/sh 32 | exec 2>&1 33 | HOME=/var/lib/postgresql USER=postgres exec thpoff chpst -u postgres:postgres:ssl-cert -U postgres:postgres:ssl-cert /usr/lib/postgresql/13/bin/postmaster -D /etc/postgresql/13/main 34 | 35 | - file: 36 | path: /etc/service/postgres/log/run 37 | chmod: "+x" 38 | contents: | 39 | #!/bin/sh 40 | mkdir -p /var/log/postgres 41 | exec svlogd /var/log/postgres 42 | 43 | - file: 44 | path: /etc/runit/3.d/99-postgres 45 | chmod: "+x" 46 | contents: | 47 | #!/bin/bash 48 | sv stop postgres 49 | 50 | - file: 51 | path: /root/install_postgres 52 | chmod: "+x" 53 | contents: | 54 | #!/bin/bash -x 55 | sed -i "s/^# $LANG/$LANG/" /etc/locale.gen 56 | locale-gen && update-locale 57 | mkdir -p /shared/postgres_run 58 | chown postgres:postgres /shared/postgres_run 59 | chmod 775 /shared/postgres_run 60 | rm -fr /var/run/postgresql 61 | ln -s /shared/postgres_run /var/run/postgresql 62 | if [ -e /shared/postgres_run/.s.PGSQL.5432 ]; then 63 | socat /dev/null UNIX-CONNECT:/shared/postgres_run/.s.PGSQL.5432 || exit 0 && echo postgres already running stop container ; exit 1 64 | fi 65 | rm -fr /shared/postgres_run/.s* 66 | rm -fr /shared/postgres_run/*.pid 67 | mkdir -p /shared/postgres_run/13-main.pg_stat_tmp 68 | chown postgres:postgres /shared/postgres_run/13-main.pg_stat_tmp 69 | chown -R root /var/lib/postgresql/13/main 70 | [ ! -e /shared/postgres_data ] && install -d -m 0755 -o postgres -g postgres /shared/postgres_data && sudo -E -u postgres /usr/lib/postgresql/13/bin/initdb -D /shared/postgres_data || exit 0 71 | chown -R postgres:postgres /shared/postgres_data 72 | chown -R postgres:postgres /var/run/postgresql 73 | # Necessary to enable backups 74 | install -d -m 0755 -o postgres -g postgres /shared/postgres_backup 75 | 76 | - replace: 77 | filename: "/etc/postgresql/13/main/postgresql.conf" 78 | from: "data_directory = '/var/lib/postgresql/13/main'" 79 | to: "data_directory = '/shared/postgres_data'" 80 | 81 | # listen on all interfaces 82 | - replace: 83 | filename: "/etc/postgresql/13/main/postgresql.conf" 84 | from: /#?listen_addresses *=.*/ 85 | to: "listen_addresses = '*'" 86 | 87 | # sync commit off is faster and less spiky, also marginally less safe 88 | - replace: 89 | filename: "/etc/postgresql/13/main/postgresql.conf" 90 | from: /#?synchronous_commit *=.*/ 91 | to: "synchronous_commit = $db_synchronous_commit" 92 | 93 | # default is 128MB which is way too small 94 | - replace: 95 | filename: "/etc/postgresql/13/main/postgresql.conf" 96 | from: /#?shared_buffers *=.*/ 97 | to: "shared_buffers = $db_shared_buffers" 98 | 99 | # default is 1MB which is too small 100 | - replace: 101 | filename: "/etc/postgresql/13/main/postgresql.conf" 102 | from: /#?work_mem *=.*/ 103 | to: "work_mem = $db_work_mem" 104 | 105 | # allow for other 106 | - replace: 107 | filename: "/etc/postgresql/13/main/postgresql.conf" 108 | from: /#?default_text_search_config *=.*/ 109 | to: "default_text_search_config = '$db_default_text_search_config'" 110 | 111 | - replace: 112 | filename: "/etc/postgresql/13/main/postgresql.conf" 113 | from: /#?checkpoint_segments *=.*/ 114 | to: "checkpoint_segments = $db_checkpoint_segments" 115 | 116 | - replace: 117 | filename: "/etc/postgresql/13/main/postgresql.conf" 118 | from: /#?logging_collector *=.*/ 119 | to: "logging_collector = $db_logging_collector" 120 | 121 | - replace: 122 | filename: "/etc/postgresql/13/main/postgresql.conf" 123 | from: /#?log_min_duration_statement *=.*/ 124 | to: "log_min_duration_statement = $db_log_min_duration_statement" 125 | 126 | - replace: 127 | filename: "/etc/postgresql/13/main/pg_hba.conf" 128 | from: /^#local +replication +postgres +peer$/ 129 | to: "local replication postgres peer" 130 | 131 | # allow all to connect in with md5 auth 132 | - replace: 133 | filename: "/etc/postgresql/13/main/pg_hba.conf" 134 | from: /^host.*all.*all.*127.*$/ 135 | to: "host all all 0.0.0.0/0 md5" 136 | 137 | # allow all to connect in with md5 auth (IPv6) 138 | - replace: 139 | filename: "/etc/postgresql/13/main/pg_hba.conf" 140 | from: /^host.*all.*all.*::1\/128.*$/ 141 | to: "host all all ::/0 md5" 142 | 143 | - exec: 144 | tag: db 145 | cmd: | 146 | if [ -f /root/install_postgres ]; then 147 | /root/install_postgres && rm -f /root/install_postgres 148 | elif [ -e /shared/postgres_run/.s.PGSQL.5432 ]; then 149 | socat /dev/null UNIX-CONNECT:/shared/postgres_run/.s.PGSQL.5432 || exit 0 && echo postgres already running stop container ; exit 1 150 | fi 151 | 152 | - exec: 153 | tag: db 154 | background: true 155 | # use fast shutdown for pg 156 | stop_signal: INT 157 | cmd: HOME=/var/lib/postgresql USER=postgres exec chpst -u postgres:postgres:ssl-cert -U postgres:postgres:ssl-cert /usr/lib/postgresql/13/bin/postmaster -D /etc/postgresql/13/main 158 | 159 | - file: 160 | path: /usr/local/bin/create_db 161 | chmod: +x 162 | contents: | 163 | #!/bin/bash 164 | su postgres -c 'createdb $db_name' || true 165 | su postgres -c 'psql $db_name -c "create user $db_user;"' || true 166 | su postgres -c 'psql $db_name -c "grant all privileges on database $db_name to $db_user;"' || true 167 | su postgres -c 'psql $db_name -c "alter schema public owner to $db_user;"' 168 | su postgres -c 'psql template1 -c "create extension if not exists hstore;"' 169 | su postgres -c 'psql template1 -c "create extension if not exists pg_trgm;"' 170 | su postgres -c 'psql template1 -c "create extension if not exists vector;"' 171 | su postgres -c 'psql template1 -c "alter extension vector update;"' || true 172 | su postgres -c 'psql $db_name -c "create extension if not exists hstore;"' 173 | su postgres -c 'psql $db_name -c "create extension if not exists pg_trgm;"' 174 | su postgres -c 'psql $db_name -c "create extension if not exists vector;"' 175 | su postgres -c 'psql $db_name -c "alter extension vector update;"' || true 176 | sudo -u postgres psql $db_name <<< "update pg_database set encoding = pg_char_to_encoding('UTF8') where datname = '$db_name' AND encoding = pg_char_to_encoding('SQL_ASCII');" || true 177 | 178 | - file: 179 | path: /var/lib/postgresql/take-database-backup 180 | chown: postgres:postgres 181 | chmod: "+x" 182 | contents: | 183 | #!/bin/bash 184 | ID=db-$(date +%F_%T) 185 | FILENAME=/shared/postgres_backup/$ID.tar.gz 186 | pg_basebackup --format=tar --pgdata=- --wal-method=fetch --gzip --label=$ID > $FILENAME 187 | echo $FILENAME 188 | 189 | - file: 190 | path: /var/spool/cron/crontabs/postgres 191 | contents: | 192 | # m h dom mon dow command 193 | #MAILTO=? 194 | #0 */4 * * * /var/lib/postgresql/take-database-backup 195 | 196 | - exec: 197 | tag: db 198 | hook: postgres 199 | cmd: 200 | # give db a few secs to start up 201 | - "sleep 5" 202 | - /usr/local/bin/create_db 203 | - "echo postgres installed!" 204 | -------------------------------------------------------------------------------- /templates/postgres.15.template.yml: -------------------------------------------------------------------------------- 1 | params: 2 | db_synchronous_commit: "off" 3 | db_shared_buffers: "256MB" 4 | db_work_mem: "10MB" 5 | db_default_text_search_config: "pg_catalog.english" 6 | db_name: discourse 7 | db_user: discourse 8 | db_checkpoint_segments: 6 9 | db_logging_collector: off 10 | db_log_min_duration_statement: 100 11 | 12 | hooks: 13 | before_code: 14 | - replace: 15 | filename: /etc/service/unicorn/run 16 | from: "# postgres" 17 | to: | 18 | if [ -f /root/install_postgres ]; then 19 | /root/install_postgres 20 | rm /root/install_postgres 21 | fi 22 | sv start postgres || exit 1 23 | 24 | run: 25 | - file: 26 | path: /etc/service/postgres/run 27 | chmod: "+x" 28 | contents: | 29 | #!/bin/sh 30 | exec 2>&1 31 | HOME=/var/lib/postgresql USER=postgres exec thpoff chpst -u postgres:postgres:ssl-cert -U postgres:postgres:ssl-cert /usr/lib/postgresql/15/bin/postmaster -D /etc/postgresql/15/main 32 | 33 | - file: 34 | path: /etc/service/postgres/log/run 35 | chmod: "+x" 36 | contents: | 37 | #!/bin/sh 38 | mkdir -p /var/log/postgres 39 | exec svlogd /var/log/postgres 40 | 41 | - file: 42 | path: /etc/runit/3.d/99-postgres 43 | chmod: "+x" 44 | contents: | 45 | #!/bin/bash 46 | sv stop postgres 47 | 48 | - file: 49 | path: /root/install_postgres 50 | chmod: "+x" 51 | contents: | 52 | #!/bin/bash 53 | sed -i "s/^# $LANG/$LANG/" /etc/locale.gen 54 | locale-gen && update-locale 55 | mkdir -p /shared/postgres_run 56 | chown postgres:postgres /shared/postgres_run 57 | chmod 775 /shared/postgres_run 58 | rm -fr /var/run/postgresql 59 | ln -s /shared/postgres_run /var/run/postgresql 60 | if [ -e /shared/postgres_run/.s.PGSQL.5432 ]; then 61 | socat /dev/null UNIX-CONNECT:/shared/postgres_run/.s.PGSQL.5432 || exit 0 && echo postgres already running stop container ; exit 1 62 | fi 63 | rm -fr /shared/postgres_run/.s* 64 | rm -fr /shared/postgres_run/*.pid 65 | mkdir -p /shared/postgres_run/15-main.pg_stat_tmp 66 | chown postgres:postgres /shared/postgres_run/15-main.pg_stat_tmp 67 | chown -R root /var/lib/postgresql/15/main 68 | if [ ! -e /shared/postgres_data ]; then 69 | install -d -m 0755 -o postgres -g postgres /shared/postgres_data 70 | sudo -E -u postgres /usr/lib/postgresql/15/bin/initdb -D /shared/postgres_data 71 | chown -R postgres:postgres /shared/postgres_data 72 | chown -R postgres:postgres /var/run/postgresql 73 | elif [ -f /root/upgrade_postgres ]; then 74 | unset RETCODE 75 | /root/upgrade_postgres || RETCODE=$? 76 | [ -z "${RETCODE}" ] && rm /root/upgrade_postgres || exit $RETCODE 77 | fi 78 | # Necessary to enable backups 79 | install -d -m 0755 -o postgres -g postgres /shared/postgres_backup 80 | 81 | - file: 82 | path: /root/upgrade_postgres 83 | chmod: "+x" 84 | contents: | 85 | #!/bin/bash 86 | PG_MAJOR_OLD=`cat /shared/postgres_data/PG_VERSION` 87 | 88 | if [ ! "15" = "$PG_MAJOR_OLD" ]; then 89 | echo Upgrading PostgreSQL from version ${PG_MAJOR_OLD} to 15 90 | free_disk=$(df -P -B1 /shared | tail -n 1 | awk '{print $4}') 91 | required=$(($(du -sb /shared/postgres_data | awk '{print $1}') * 2)) 92 | 93 | if [ "$free_disk" -lt "$required" ]; then 94 | echo 95 | echo ------------------------------------------------------------------------------------- 96 | echo "WARNING: Upgrading PostgreSQL would require an additional $(numfmt --to=si $(($required - $free_disk))) of disk space" 97 | echo "Please free up some space, or expand your disk, before continuing." 98 | echo 99 | echo 'To avoid upgrading change "templates/postgres.template.yml" TO "templates/postgres.13.template.yml" in containers/app.yml' 100 | echo 101 | echo 'You can run "./launcher start app" to restart your app in the meanwhile.' 102 | echo ------------------------------------------------------------------------------------- 103 | exit 1 104 | fi 105 | 106 | if [ -d /shared/postgres_data_old ]; then 107 | mv /shared/postgres_data_old /shared/postgres_data_older 108 | fi 109 | 110 | rm -fr /shared/postgres_data_new 111 | install -d -m 0755 -o postgres -g postgres /shared/postgres_data_new && sudo -u postgres /usr/lib/postgresql/15/bin/initdb -D /shared/postgres_data_new || exit 0 112 | apt-get update 113 | apt-get install -y postgresql-${PG_MAJOR_OLD} postgresql-${PG_MAJOR_OLD}-pgvector 114 | chown -R postgres:postgres /var/lib/postgresql/15 115 | /etc/init.d/postgresql stop 116 | rm -fr /shared/postgres_data/postmaster.pid 117 | cd ~postgres 118 | cp -pr /etc/postgresql/${PG_MAJOR_OLD}/main/* /shared/postgres_data 119 | echo >> /shared/postgres_data/postgresql.conf 120 | echo "data_directory = '/shared/postgres_data'" >> /shared/postgres_data/postgresql.conf 121 | SUCCESS=true 122 | sudo -u postgres /usr/lib/postgresql/15/bin/pg_upgrade -d /shared/postgres_data -D /shared/postgres_data_new -b /usr/lib/postgresql/${PG_MAJOR_OLD}/bin -B /usr/lib/postgresql/15/bin || SUCCESS=false 123 | 124 | if [[ "$SUCCESS" == 'false' ]]; then 125 | echo ------------------------------------------------------------------------------------- 126 | echo UPGRADE OF POSTGRES FAILED 127 | echo 128 | echo Please visit https://meta.discourse.org/t/postgresql-15-update/349515 for support. 129 | echo 130 | echo You can run "./launcher start app" to restart your app in the meanwhile 131 | echo ------------------------------------------------------------------------------------- 132 | exit 1 133 | fi 134 | 135 | mv /shared/postgres_data /shared/postgres_data_old 136 | mv /shared/postgres_data_new /shared/postgres_data 137 | 138 | echo ------------------------------------------------------------------------------------- 139 | echo UPGRADE OF POSTGRES COMPLETE 140 | echo 141 | echo Old ${PG_MAJOR_OLD} database is stored at /shared/postgres_data_old 142 | echo 143 | echo To complete the upgrade, rebuild again using: 144 | echo 145 | echo ./launcher rebuild app 146 | echo ------------------------------------------------------------------------------------- 147 | # Magic exit status to denote no failure 148 | exit 77 149 | fi 150 | 151 | - replace: 152 | filename: "/etc/postgresql/15/main/postgresql.conf" 153 | from: "data_directory = '/var/lib/postgresql/15/main'" 154 | to: "data_directory = '/shared/postgres_data'" 155 | 156 | # listen on all interfaces 157 | - replace: 158 | filename: "/etc/postgresql/15/main/postgresql.conf" 159 | from: /#?listen_addresses *=.*/ 160 | to: "listen_addresses = '*'" 161 | 162 | # sync commit off is faster and less spiky, also marginally less safe 163 | - replace: 164 | filename: "/etc/postgresql/15/main/postgresql.conf" 165 | from: /#?synchronous_commit *=.*/ 166 | to: "synchronous_commit = $db_synchronous_commit" 167 | 168 | # default is 128MB which is way too small 169 | - replace: 170 | filename: "/etc/postgresql/15/main/postgresql.conf" 171 | from: /#?shared_buffers *=.*/ 172 | to: "shared_buffers = $db_shared_buffers" 173 | 174 | # default is 1MB which is too small 175 | - replace: 176 | filename: "/etc/postgresql/15/main/postgresql.conf" 177 | from: /#?work_mem *=.*/ 178 | to: "work_mem = $db_work_mem" 179 | 180 | # allow for other 181 | - replace: 182 | filename: "/etc/postgresql/15/main/postgresql.conf" 183 | from: /#?default_text_search_config *=.*/ 184 | to: "default_text_search_config = '$db_default_text_search_config'" 185 | 186 | - replace: 187 | filename: "/etc/postgresql/15/main/postgresql.conf" 188 | from: /#?checkpoint_segments *=.*/ 189 | to: "checkpoint_segments = $db_checkpoint_segments" 190 | 191 | - replace: 192 | filename: "/etc/postgresql/15/main/postgresql.conf" 193 | from: /#?logging_collector *=.*/ 194 | to: "logging_collector = $db_logging_collector" 195 | 196 | - replace: 197 | filename: "/etc/postgresql/15/main/postgresql.conf" 198 | from: /#?log_min_duration_statement *=.*/ 199 | to: "log_min_duration_statement = $db_log_min_duration_statement" 200 | 201 | - replace: 202 | filename: "/etc/postgresql/15/main/pg_hba.conf" 203 | from: /^#local +replication +postgres +peer$/ 204 | to: "local replication postgres peer" 205 | 206 | # allow all to connect in with md5/scram auth 207 | - replace: 208 | filename: "/etc/postgresql/15/main/pg_hba.conf" 209 | from: /^host.*all.*all.*127.*$/ 210 | to: "host all all 0.0.0.0/0 md5" 211 | 212 | # allow all to connect in with md5/scram auth (IPv6) 213 | - replace: 214 | filename: "/etc/postgresql/15/main/pg_hba.conf" 215 | from: /^host.*all.*all.*::1\/128.*$/ 216 | to: "host all all ::/0 md5" 217 | 218 | - exec: 219 | tag: db 220 | cmd: | 221 | if [ -f /root/install_postgres ]; then 222 | /root/install_postgres && rm -f /root/install_postgres 223 | elif [ -e /shared/postgres_run/.s.PGSQL.5432 ]; then 224 | socat /dev/null UNIX-CONNECT:/shared/postgres_run/.s.PGSQL.5432 || exit 0 && echo postgres already running stop container ; exit 1 225 | fi 226 | 227 | - exec: 228 | tag: db 229 | background: true 230 | # use fast shutdown for pg 231 | stop_signal: INT 232 | cmd: HOME=/var/lib/postgresql USER=postgres exec chpst -u postgres:postgres:ssl-cert -U postgres:postgres:ssl-cert /usr/lib/postgresql/15/bin/postmaster -D /etc/postgresql/15/main 233 | 234 | - file: 235 | path: /usr/local/bin/create_db 236 | chmod: +x 237 | contents: | 238 | #!/bin/bash 239 | su postgres -c 'createdb $db_name' || true 240 | su postgres -c 'psql $db_name -c "create user $db_user;"' || true 241 | su postgres -c 'psql $db_name -c "grant all privileges on database $db_name to $db_user;"' || true 242 | su postgres -c 'psql $db_name -c "alter schema public owner to $db_user;"' 243 | su postgres -c 'psql template1 -c "create extension if not exists hstore;"' 244 | su postgres -c 'psql template1 -c "create extension if not exists pg_trgm;"' 245 | su postgres -c 'psql template1 -c "create extension if not exists vector;"' 246 | su postgres -c 'psql template1 -c "alter extension vector update;"' || true 247 | su postgres -c 'psql $db_name -c "create extension if not exists hstore;"' 248 | su postgres -c 'psql $db_name -c "create extension if not exists pg_trgm;"' 249 | su postgres -c 'psql $db_name -c "create extension if not exists vector;"' 250 | su postgres -c 'psql $db_name -c "alter extension vector update;"' || true 251 | sudo -u postgres psql $db_name <<< "update pg_database set encoding = pg_char_to_encoding('UTF8') where datname = '$db_name' AND encoding = pg_char_to_encoding('SQL_ASCII');" || true 252 | 253 | - file: 254 | path: /var/lib/postgresql/take-database-backup 255 | chown: postgres:postgres 256 | chmod: "+x" 257 | contents: | 258 | #!/bin/bash 259 | ID=db-$(date +%F_%T) 260 | FILENAME=/shared/postgres_backup/$ID.tar.gz 261 | pg_basebackup --format=tar --pgdata=- --wal-method=fetch --gzip --label=$ID > $FILENAME 262 | echo $FILENAME 263 | 264 | - file: 265 | path: /var/spool/cron/crontabs/postgres 266 | contents: | 267 | # m h dom mon dow command 268 | #MAILTO=? 269 | #0 */4 * * * /var/lib/postgresql/take-database-backup 270 | 271 | - exec: 272 | tag: db 273 | hook: postgres 274 | cmd: 275 | # give db a few secs to start up 276 | - "sleep 5" 277 | - /usr/local/bin/create_db 278 | - "echo postgres installed!" 279 | -------------------------------------------------------------------------------- /templates/postgres.9.5.template.yml: -------------------------------------------------------------------------------- 1 | params: 2 | db_synchronous_commit: "off" 3 | db_shared_buffers: "256MB" 4 | db_work_mem: "10MB" 5 | db_default_text_search_config: "pg_catalog.english" 6 | db_name: discourse 7 | db_user: discourse 8 | db_checkpoint_segments: 6 9 | db_logging_collector: off 10 | db_log_min_duration_statement: 100 11 | 12 | hooks: 13 | before_code: 14 | - replace: 15 | filename: /etc/service/unicorn/run 16 | from: "# postgres" 17 | to: sv start postgres || exit 1 18 | 19 | run: 20 | - exec: DEBIAN_FRONTEND=noninteractive apt-get purge -y postgresql-15 postgresql-client-15 postgresql-contrib-15 21 | - exec: apt-get update && apt-get install -y postgresql-9.5 postgresql-client-9.5 postgresql-contrib-9.5 22 | - exec: mkdir -p /shared/postgres_run 23 | - exec: chown postgres:postgres /shared/postgres_run 24 | - exec: chmod 775 /shared/postgres_run 25 | - exec: rm -fr /var/run/postgresql 26 | - exec: ln -s /shared/postgres_run /var/run/postgresql 27 | - exec: socat /dev/null UNIX-CONNECT:/shared/postgres_run/.s.PGSQL.5432 || exit 0 && echo postgres already running stop container ; exit 1 28 | - exec: rm -fr /shared/postgres_run/.s* 29 | - exec: rm -fr /shared/postgres_run/*.pid 30 | - exec: mkdir -p /shared/postgres_run/9.5-main.pg_stat_tmp 31 | - exec: chown postgres:postgres /shared/postgres_run/9.5-main.pg_stat_tmp 32 | - file: 33 | path: /etc/service/postgres/run 34 | chmod: "+x" 35 | contents: | 36 | #!/bin/sh 37 | exec 2>&1 38 | echo -1000 >/proc/self/oom_score_adj 39 | HOME=/var/lib/postgresql USER=postgres exec chpst -u postgres:postgres:ssl-cert -U postgres:postgres:ssl-cert /usr/lib/postgresql/9.5/bin/postmaster -D /etc/postgresql/9.5/main 40 | 41 | - file: 42 | path: /etc/runit/3.d/99-postgres 43 | chmod: "+x" 44 | contents: | 45 | #!/bin/bash 46 | sv stop postgres 47 | 48 | - exec: 49 | cmd: 50 | - chown -R root /var/lib/postgresql/9.5/main 51 | - "[ ! -e /shared/postgres_data ] && install -d -m 0755 -o postgres -g postgres /shared/postgres_data && sudo -E -u postgres /usr/lib/postgresql/9.5/bin/initdb -D /shared/postgres_data || exit 0" 52 | - chown -R postgres:postgres /shared/postgres_data 53 | - chown -R postgres:postgres /var/run/postgresql 54 | 55 | - replace: 56 | filename: "/etc/postgresql/9.5/main/postgresql.conf" 57 | from: "data_directory = '/var/lib/postgresql/9.5/main'" 58 | to: "data_directory = '/shared/postgres_data'" 59 | 60 | # listen on all interfaces 61 | - replace: 62 | filename: "/etc/postgresql/9.5/main/postgresql.conf" 63 | from: /#?listen_addresses *=.*/ 64 | to: "listen_addresses = '*'" 65 | 66 | # sync commit off is faster and less spiky, also marginally less safe 67 | - replace: 68 | filename: "/etc/postgresql/9.5/main/postgresql.conf" 69 | from: /#?synchronous_commit *=.*/ 70 | to: "synchronous_commit = $db_synchronous_commit" 71 | 72 | # default is 128MB which is way too small 73 | - replace: 74 | filename: "/etc/postgresql/9.5/main/postgresql.conf" 75 | from: /#?shared_buffers *=.*/ 76 | to: "shared_buffers = $db_shared_buffers" 77 | 78 | # default is 1MB which is too small 79 | - replace: 80 | filename: "/etc/postgresql/9.5/main/postgresql.conf" 81 | from: /#?work_mem *=.*/ 82 | to: "work_mem = $db_work_mem" 83 | 84 | # allow for other 85 | - replace: 86 | filename: "/etc/postgresql/9.5/main/postgresql.conf" 87 | from: /#?default_text_search_config *=.*/ 88 | to: "default_text_search_config = '$db_default_text_search_config'" 89 | 90 | # Necessary to enable backups 91 | - exec: 92 | cmd: 93 | - install -d -m 0755 -o postgres -g postgres /shared/postgres_backup 94 | 95 | - replace: 96 | filename: "/etc/postgresql/9.5/main/postgresql.conf" 97 | from: /#?checkpoint_segments *=.*/ 98 | to: "checkpoint_segments = $db_checkpoint_segments" 99 | 100 | - replace: 101 | filename: "/etc/postgresql/9.5/main/postgresql.conf" 102 | from: /#?logging_collector *=.*/ 103 | to: "logging_collector = $db_logging_collector" 104 | 105 | - replace: 106 | filename: "/etc/postgresql/9.5/main/postgresql.conf" 107 | from: /#?log_min_duration_statement *=.*/ 108 | to: "log_min_duration_statement = $db_log_min_duration_statement" 109 | 110 | - replace: 111 | filename: "/etc/postgresql/9.5/main/pg_hba.conf" 112 | from: /^#local +replication +postgres +peer$/ 113 | to: "local replication postgres peer" 114 | 115 | # allow all to connect in with md5 auth 116 | - replace: 117 | filename: "/etc/postgresql/9.5/main/pg_hba.conf" 118 | from: /^host.*all.*all.*127.*$/ 119 | to: "host all all 0.0.0.0/0 md5" 120 | 121 | # allow all to connect in with md5 auth (IPv6) 122 | - replace: 123 | filename: "/etc/postgresql/9.5/main/pg_hba.conf" 124 | from: /^host.*all.*all.*::1\/128.*$/ 125 | to: "host all all ::/0 md5" 126 | 127 | - exec: 128 | background: true 129 | # use fast shutdown for pg 130 | stop_signal: INT 131 | cmd: HOME=/var/lib/postgresql USER=postgres exec chpst -u postgres:postgres:ssl-cert -U postgres:postgres:ssl-cert /usr/lib/postgresql/9.5/bin/postmaster -D /etc/postgresql/9.5/main 132 | 133 | # give db a few secs to start up 134 | - exec: "sleep 5" 135 | 136 | - exec: su postgres -c 'createdb $db_name' || true 137 | - exec: su postgres -c 'psql $db_name -c "create user $db_user;"' || true 138 | - exec: su postgres -c 'psql $db_name -c "grant all privileges on database $db_name to $db_user;"' || true 139 | - exec: su postgres -c 'psql $db_name -c "alter schema public owner to $db_user;"' 140 | - exec: su postgres -c 'psql template1 -c "create extension if not exists hstore;"' 141 | - exec: su postgres -c 'psql template1 -c "create extension if not exists pg_trgm;"' 142 | - exec: su postgres -c 'psql $db_name -c "create extension if not exists hstore;"' 143 | - exec: su postgres -c 'psql $db_name -c "create extension if not exists pg_trgm;"' 144 | - exec: su postgres -c 'psql $db_name -c "create extension if not exists unaccent;"' 145 | - exec: 146 | stdin: | 147 | update pg_database set encoding = pg_char_to_encoding('UTF8') where datname = '$db_name' AND encoding = pg_char_to_encoding('SQL_ASCII'); 148 | cmd: sudo -u postgres psql $db_name 149 | raise_on_fail: false 150 | 151 | - file: 152 | path: /var/lib/postgresql/take-database-backup 153 | chown: postgres:postgres 154 | chmod: "+x" 155 | contents: | 156 | #!/bin/bash 157 | ID=db-$(date +%F_%T) 158 | FILENAME=/shared/postgres_backup/$ID.tar.gz 159 | pg_basebackup --format=tar --pgdata=- --xlog --gzip --label=$ID > $FILENAME 160 | echo $FILENAME 161 | 162 | - file: 163 | path: /var/spool/cron/crontabs/postgres 164 | contents: | 165 | # m h dom mon dow command 166 | #MAILTO=? 167 | #0 */4 * * * /var/lib/postgresql/take-database-backup 168 | 169 | - exec: 170 | hook: postgres 171 | cmd: "echo postgres installed!" 172 | -------------------------------------------------------------------------------- /templates/postgres.template.yml: -------------------------------------------------------------------------------- 1 | base_image: discourse/base:2.0.20250226-0128 2 | params: 3 | db_synchronous_commit: "off" 4 | db_shared_buffers: "256MB" 5 | db_work_mem: "10MB" 6 | db_default_text_search_config: "pg_catalog.english" 7 | db_name: discourse 8 | db_user: discourse 9 | db_checkpoint_segments: 6 10 | db_logging_collector: off 11 | db_log_min_duration_statement: 100 12 | 13 | hooks: 14 | before_code: 15 | - replace: 16 | filename: /etc/service/unicorn/run 17 | from: "# postgres" 18 | to: | 19 | if [ -f /root/install_postgres ]; then 20 | /root/install_postgres 21 | rm /root/install_postgres 22 | fi 23 | sv start postgres || exit 1 24 | 25 | run: 26 | - file: 27 | path: /etc/service/postgres/run 28 | chmod: "+x" 29 | contents: | 30 | #!/bin/sh 31 | exec 2>&1 32 | HOME=/var/lib/postgresql USER=postgres exec thpoff chpst -u postgres:postgres:ssl-cert -U postgres:postgres:ssl-cert /usr/lib/postgresql/15/bin/postmaster -D /etc/postgresql/15/main 33 | 34 | - file: 35 | path: /etc/service/postgres/log/run 36 | chmod: "+x" 37 | contents: | 38 | #!/bin/sh 39 | mkdir -p /var/log/postgres 40 | exec svlogd /var/log/postgres 41 | 42 | - file: 43 | path: /etc/runit/3.d/99-postgres 44 | chmod: "+x" 45 | contents: | 46 | #!/bin/bash 47 | sv stop postgres 48 | 49 | - file: 50 | path: /root/install_postgres 51 | chmod: "+x" 52 | contents: | 53 | #!/bin/bash 54 | run_upgrade_postgres() { 55 | if [ -f /root/upgrade_postgres ]; then 56 | unset RETCODE 57 | /root/upgrade_postgres || RETCODE=$? 58 | [ -z "${RETCODE}" ] && rm /root/upgrade_postgres || exit $RETCODE 59 | fi 60 | exit 0 61 | } 62 | sed -i "s/^# $LANG/$LANG/" /etc/locale.gen 63 | locale-gen && update-locale 64 | mkdir -p /shared/postgres_run 65 | chown postgres:postgres /shared/postgres_run 66 | chmod 775 /shared/postgres_run 67 | rm -fr /var/run/postgresql 68 | ln -s /shared/postgres_run /var/run/postgresql 69 | if [ -e /shared/postgres_run/.s.PGSQL.5432 ]; then 70 | socat /dev/null UNIX-CONNECT:/shared/postgres_run/.s.PGSQL.5432 || run_upgrade_postgres && echo postgres already running stop container ; exit 1 71 | fi 72 | rm -fr /shared/postgres_run/.s* 73 | rm -fr /shared/postgres_run/*.pid 74 | mkdir -p /shared/postgres_run/15-main.pg_stat_tmp 75 | chown postgres:postgres /shared/postgres_run/15-main.pg_stat_tmp 76 | chown -R root /var/lib/postgresql/15/main 77 | if [ ! -e /shared/postgres_data ]; then 78 | install -d -m 0755 -o postgres -g postgres /shared/postgres_data 79 | sudo -E -u postgres /usr/lib/postgresql/15/bin/initdb -D /shared/postgres_data 80 | chown -R postgres:postgres /shared/postgres_data 81 | chown -R postgres:postgres /var/run/postgresql 82 | fi 83 | run_upgrade_postgres 84 | # Necessary to enable backups 85 | install -d -m 0755 -o postgres -g postgres /shared/postgres_backup 86 | 87 | - file: 88 | path: /root/upgrade_postgres 89 | chmod: "+x" 90 | contents: | 91 | #!/bin/bash 92 | PG_MAJOR_OLD=`cat /shared/postgres_data/PG_VERSION` 93 | 94 | if [ ! "15" = "$PG_MAJOR_OLD" ]; then 95 | echo Upgrading PostgreSQL from version ${PG_MAJOR_OLD} to 15 96 | free_disk=$(df -P -B1 /shared | tail -n 1 | awk '{print $4}') 97 | required=$(($(du -sb /shared/postgres_data | awk '{print $1}') * 2)) 98 | 99 | if [ "$free_disk" -lt "$required" ]; then 100 | echo 101 | echo ------------------------------------------------------------------------------------- 102 | echo "WARNING: Upgrading PostgreSQL would require an additional $(numfmt --to=si $(($required - $free_disk))) of disk space" 103 | echo "Please free up some space, or expand your disk, before continuing." 104 | echo 105 | echo 'To avoid upgrading change "templates/postgres.template.yml" TO "templates/postgres.13.template.yml" in containers/app.yml' 106 | echo 107 | echo 'You can run "./launcher start app" to restart your app in the meanwhile.' 108 | echo ------------------------------------------------------------------------------------- 109 | exit 1 110 | fi 111 | 112 | if [ -d /shared/postgres_data_old ]; then 113 | mv /shared/postgres_data_old /shared/postgres_data_older 114 | fi 115 | 116 | rm -fr /shared/postgres_data_new 117 | install -d -m 0755 -o postgres -g postgres /shared/postgres_data_new && sudo -u postgres /usr/lib/postgresql/15/bin/initdb -D /shared/postgres_data_new || exit 0 118 | apt-get update 119 | apt-get install -y postgresql-${PG_MAJOR_OLD} postgresql-${PG_MAJOR_OLD}-pgvector 120 | chown -R postgres:postgres /var/lib/postgresql/15 121 | /etc/init.d/postgresql stop 122 | rm -fr /shared/postgres_data/postmaster.pid 123 | cd ~postgres 124 | cp -pr /etc/postgresql/${PG_MAJOR_OLD}/main/* /shared/postgres_data 125 | echo >> /shared/postgres_data/postgresql.conf 126 | echo "data_directory = '/shared/postgres_data'" >> /shared/postgres_data/postgresql.conf 127 | SUCCESS=true 128 | sudo -u postgres /usr/lib/postgresql/15/bin/pg_upgrade -d /shared/postgres_data -D /shared/postgres_data_new -b /usr/lib/postgresql/${PG_MAJOR_OLD}/bin -B /usr/lib/postgresql/15/bin || SUCCESS=false 129 | 130 | if [[ "$SUCCESS" == 'false' ]]; then 131 | echo ------------------------------------------------------------------------------------- 132 | echo UPGRADE OF POSTGRES FAILED 133 | echo 134 | echo Please visit https://meta.discourse.org/t/postgresql-15-update/349515 for support. 135 | echo 136 | echo You can run "./launcher start app" to restart your app in the meanwhile 137 | echo ------------------------------------------------------------------------------------- 138 | exit 1 139 | fi 140 | 141 | mv /shared/postgres_data /shared/postgres_data_old 142 | mv /shared/postgres_data_new /shared/postgres_data 143 | 144 | echo ------------------------------------------------------------------------------------- 145 | echo UPGRADE OF POSTGRES COMPLETE 146 | echo 147 | echo Old ${PG_MAJOR_OLD} database is stored at /shared/postgres_data_old 148 | echo 149 | echo To complete the upgrade, rebuild again using: 150 | echo 151 | echo ./launcher rebuild app 152 | echo ------------------------------------------------------------------------------------- 153 | # Magic exit status to denote no failure 154 | exit 77 155 | fi 156 | 157 | - replace: 158 | filename: "/etc/postgresql/15/main/postgresql.conf" 159 | from: "data_directory = '/var/lib/postgresql/15/main'" 160 | to: "data_directory = '/shared/postgres_data'" 161 | 162 | # listen on all interfaces 163 | - replace: 164 | filename: "/etc/postgresql/15/main/postgresql.conf" 165 | from: /#?listen_addresses *=.*/ 166 | to: "listen_addresses = '*'" 167 | 168 | # sync commit off is faster and less spiky, also marginally less safe 169 | - replace: 170 | filename: "/etc/postgresql/15/main/postgresql.conf" 171 | from: /#?synchronous_commit *=.*/ 172 | to: "synchronous_commit = $db_synchronous_commit" 173 | 174 | # default is 128MB which is way too small 175 | - replace: 176 | filename: "/etc/postgresql/15/main/postgresql.conf" 177 | from: /#?shared_buffers *=.*/ 178 | to: "shared_buffers = $db_shared_buffers" 179 | 180 | # default is 1MB which is too small 181 | - replace: 182 | filename: "/etc/postgresql/15/main/postgresql.conf" 183 | from: /#?work_mem *=.*/ 184 | to: "work_mem = $db_work_mem" 185 | 186 | # allow for other 187 | - replace: 188 | filename: "/etc/postgresql/15/main/postgresql.conf" 189 | from: /#?default_text_search_config *=.*/ 190 | to: "default_text_search_config = '$db_default_text_search_config'" 191 | 192 | - replace: 193 | filename: "/etc/postgresql/15/main/postgresql.conf" 194 | from: /#?checkpoint_segments *=.*/ 195 | to: "checkpoint_segments = $db_checkpoint_segments" 196 | 197 | - replace: 198 | filename: "/etc/postgresql/15/main/postgresql.conf" 199 | from: /#?logging_collector *=.*/ 200 | to: "logging_collector = $db_logging_collector" 201 | 202 | - replace: 203 | filename: "/etc/postgresql/15/main/postgresql.conf" 204 | from: /#?log_min_duration_statement *=.*/ 205 | to: "log_min_duration_statement = $db_log_min_duration_statement" 206 | 207 | - replace: 208 | filename: "/etc/postgresql/15/main/pg_hba.conf" 209 | from: /^#local +replication +postgres +peer$/ 210 | to: "local replication postgres peer" 211 | 212 | # allow all to connect in with md5/scram auth 213 | - replace: 214 | filename: "/etc/postgresql/15/main/pg_hba.conf" 215 | from: /^host.*all.*all.*127.*$/ 216 | to: "host all all 0.0.0.0/0 md5" 217 | 218 | # allow all to connect in with md5/scram auth (IPv6) 219 | - replace: 220 | filename: "/etc/postgresql/15/main/pg_hba.conf" 221 | from: /^host.*all.*all.*::1\/128.*$/ 222 | to: "host all all ::/0 md5" 223 | 224 | - exec: 225 | tag: db 226 | cmd: | 227 | if [ -f /root/install_postgres ]; then 228 | /root/install_postgres && rm -f /root/install_postgres 229 | elif [ -e /shared/postgres_run/.s.PGSQL.5432 ]; then 230 | socat /dev/null UNIX-CONNECT:/shared/postgres_run/.s.PGSQL.5432 || exit 0 && echo postgres already running stop container ; exit 1 231 | fi 232 | 233 | - exec: 234 | tag: db 235 | background: true 236 | # use fast shutdown for pg 237 | stop_signal: INT 238 | cmd: HOME=/var/lib/postgresql USER=postgres exec chpst -u postgres:postgres:ssl-cert -U postgres:postgres:ssl-cert /usr/lib/postgresql/15/bin/postmaster -D /etc/postgresql/15/main 239 | 240 | - file: 241 | path: /usr/local/bin/create_db 242 | chmod: +x 243 | contents: | 244 | #!/bin/bash 245 | su postgres -c 'createdb $db_name' || true 246 | su postgres -c 'psql $db_name -c "create user $db_user;"' || true 247 | su postgres -c 'psql $db_name -c "grant all privileges on database $db_name to $db_user;"' || true 248 | su postgres -c 'psql $db_name -c "alter schema public owner to $db_user;"' 249 | su postgres -c 'psql template1 -c "create extension if not exists hstore;"' 250 | su postgres -c 'psql template1 -c "create extension if not exists pg_trgm;"' 251 | su postgres -c 'psql template1 -c "create extension if not exists vector;"' 252 | su postgres -c 'psql template1 -c "alter extension vector update;"' || true 253 | su postgres -c 'psql $db_name -c "create extension if not exists hstore;"' 254 | su postgres -c 'psql $db_name -c "create extension if not exists pg_trgm;"' 255 | su postgres -c 'psql $db_name -c "create extension if not exists vector;"' 256 | su postgres -c 'psql $db_name -c "alter extension vector update;"' || true 257 | sudo -u postgres psql $db_name <<< "update pg_database set encoding = pg_char_to_encoding('UTF8') where datname = '$db_name' AND encoding = pg_char_to_encoding('SQL_ASCII');" || true 258 | 259 | - file: 260 | path: /var/lib/postgresql/take-database-backup 261 | chown: postgres:postgres 262 | chmod: "+x" 263 | contents: | 264 | #!/bin/bash 265 | ID=db-$(date +%F_%T) 266 | FILENAME=/shared/postgres_backup/$ID.tar.gz 267 | pg_basebackup --format=tar --pgdata=- --wal-method=fetch --gzip --label=$ID > $FILENAME 268 | echo $FILENAME 269 | 270 | - file: 271 | path: /var/spool/cron/crontabs/postgres 272 | contents: | 273 | # m h dom mon dow command 274 | #MAILTO=? 275 | #0 */4 * * * /var/lib/postgresql/take-database-backup 276 | 277 | - exec: 278 | tag: db 279 | hook: postgres 280 | cmd: 281 | # give db a few secs to start up 282 | - "sleep 5" 283 | - /usr/local/bin/create_db 284 | - "echo postgres installed!" 285 | -------------------------------------------------------------------------------- /templates/redis.template.yml: -------------------------------------------------------------------------------- 1 | base_image: discourse/base:2.0.20250226-0128 2 | params: 3 | redis_io_threads: "1" 4 | 5 | run: 6 | - file: 7 | path: /etc/service/redis/run 8 | chmod: "+x" 9 | contents: | 10 | #!/bin/sh 11 | exec 2>&1 12 | exec thpoff chpst -u redis -U redis /usr/bin/redis-server /etc/redis/redis.conf 13 | - file: 14 | path: /etc/service/redis/log/run 15 | chmod: "+x" 16 | contents: | 17 | #!/bin/sh 18 | mkdir -p /var/log/redis 19 | exec svlogd /var/log/redis 20 | - file: 21 | path: /etc/runit/3.d/10-redis 22 | chmod: "+x" 23 | contents: | 24 | #!/bin/bash 25 | sv stop redis 26 | 27 | - replace: 28 | filename: "/etc/redis/redis.conf" 29 | from: "daemonize yes" 30 | to: "" 31 | - replace: 32 | filename: "/etc/redis/redis.conf" 33 | from: /^pidfile.*$/ 34 | to: "" 35 | 36 | - exec: 37 | tag: db 38 | cmd: 39 | - install -d -m 0755 -o redis -g redis /shared/redis_data 40 | 41 | - replace: 42 | filename: "/etc/redis/redis.conf" 43 | from: /^logfile.*$/ 44 | to: "logfile \"\"" 45 | 46 | - replace: 47 | filename: "/etc/redis/redis.conf" 48 | from: /^bind .*$/ 49 | to: "" 50 | 51 | - replace: 52 | filename: "/etc/redis/redis.conf" 53 | from: /^dir .*$/ 54 | to: "dir /shared/redis_data" 55 | 56 | - replace: 57 | filename: "/etc/redis/redis.conf" 58 | from: /^protected-mode yes/ 59 | to: "protected-mode no" 60 | 61 | - replace: 62 | filename: "/etc/redis/redis.conf" 63 | from: "# io-threads 4" 64 | to: "io-threads $redis_io_threads" 65 | 66 | - exec: 67 | cmd: echo redis installed 68 | hook: redis 69 | - exec: cat /etc/redis/redis.conf | grep logfile 70 | 71 | - exec: 72 | background: true 73 | tag: db 74 | cmd: exec chpst -u redis -U redis /usr/bin/redis-server /etc/redis/redis.conf 75 | - exec: 76 | tag: db 77 | cmd: sleep 10 78 | 79 | # we can not migrate without redis, launch it if needed 80 | hooks: 81 | before_db_migrate: 82 | - exec: 83 | background: true 84 | cmd: exec chpst -u redis -U redis /usr/bin/redis-server /etc/redis/redis.conf 85 | - exec: sleep 10 86 | after_code: 87 | - replace: 88 | filename: /etc/service/unicorn/run 89 | from: "# redis" 90 | to: | 91 | if [ ! -d /shared/redis_data ]; then 92 | install -d -m 0755 -o redis -g redis /shared/redis_data 93 | fi 94 | sv start redis || exit 1 95 | -------------------------------------------------------------------------------- /templates/sshd.template.yml: -------------------------------------------------------------------------------- 1 | # This file is deprecated; you can remove it from your app.yml 2 | # TODO(2026-01-01): Remove this file 3 | run: 4 | - exec: |- 5 | echo "Deprecation warning: sshd is no longer supported" 6 | echo "Remove templates/sshd.template.yml from your containers/*.yml files" 7 | -------------------------------------------------------------------------------- /templates/syslog.template.yml: -------------------------------------------------------------------------------- 1 | run: 2 | - exec: echo rsyslog template is included in base image, remove 3 | 4 | -------------------------------------------------------------------------------- /templates/web.china.template.yml: -------------------------------------------------------------------------------- 1 | hooks: 2 | before_code: 3 | - exec: 4 | cmd: 5 | - su discourse -c 'git config --global url."https://mirror.ghproxy.com/https://github.com/".insteadOf "https://github.com/"' 6 | 7 | before_web: 8 | - exec: 9 | cmd: 10 | - su discourse -c 'gem sources --add https://mirrors.tuna.tsinghua.edu.cn/rubygems/ --remove https://rubygems.org/' 11 | 12 | before_yarn: 13 | - exec: 14 | cmd: 15 | - su discourse -c 'pnpm config set registry https://registry.npmmirror.com --global' 16 | 17 | before_bundle_exec: 18 | - exec: 19 | cmd: 20 | - su discourse -c 'bundle config mirror.https://rubygems.org https://mirrors.tuna.tsinghua.edu.cn/rubygems' 21 | -------------------------------------------------------------------------------- /templates/web.ipv6.template.yml: -------------------------------------------------------------------------------- 1 | # This file is deprecated; you can remove it from your app.yml 2 | # TODO(2026-01-01): Remove this file 3 | run: 4 | - exec: |- 5 | echo "Deprecation warning: IPv6 is enabled by default when possible" 6 | echo "Remove templates/web.ipv6.template.yml from your containers/*.yml files" 7 | -------------------------------------------------------------------------------- /templates/web.letsencrypt.ssl.template.yml: -------------------------------------------------------------------------------- 1 | env: 2 | LETSENCRYPT_DIR: "/shared/letsencrypt" 3 | DISCOURSE_FORCE_HTTPS: true 4 | 5 | hooks: 6 | after_ssl: 7 | - exec: 8 | cmd: 9 | - if [ -z "$LETSENCRYPT_ACCOUNT_EMAIL" ]; then echo "LETSENCRYPT_ACCOUNT_EMAIL ENV variable is required and has not been set."; exit 1; fi 10 | - /bin/bash -c "if [[ ! \"$LETSENCRYPT_ACCOUNT_EMAIL\" =~ ([^@]+)@([^\.]+) ]]; then echo \"LETSENCRYPT_ACCOUNT_EMAIL is not a valid email address\"; exit 1; fi" 11 | 12 | - exec: 13 | cmd: 14 | - cd /root && git clone --branch 3.0.6 --depth 1 https://github.com/acmesh-official/acme.sh.git && cd /root/acme.sh 15 | - touch /var/spool/cron/crontabs/root 16 | - install -d -m 0755 -g root -o root $LETSENCRYPT_DIR 17 | - cd /root/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --install --log "${LETSENCRYPT_DIR}/acme.sh.log" 18 | - cd /root/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --upgrade --auto-upgrade 19 | - cd /root/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --set-default-ca --server letsencrypt 20 | 21 | - file: 22 | path: "/etc/nginx/letsencrypt.conf" 23 | contents: | 24 | user www-data; 25 | worker_processes auto; 26 | daemon on; 27 | 28 | events { 29 | worker_connections 768; 30 | # multi_accept on; 31 | } 32 | 33 | http { 34 | sendfile on; 35 | tcp_nopush on; 36 | tcp_nodelay on; 37 | keepalive_timeout 65; 38 | types_hash_max_size 2048; 39 | 40 | access_log /var/log/nginx/access.letsencrypt.log; 41 | error_log /var/log/nginx/error.letsencrypt.log; 42 | 43 | server { 44 | listen 80; 45 | listen [::]:80; 46 | 47 | location ~ /.well-known { 48 | root /var/www/discourse/public; 49 | allow all; 50 | } 51 | } 52 | } 53 | 54 | - file: 55 | path: /etc/runit/1.d/letsencrypt 56 | chmod: "+x" 57 | contents: | 58 | #!/bin/bash 59 | /usr/sbin/nginx -c /etc/nginx/letsencrypt.conf 60 | 61 | issue_cert() { 62 | LE_WORKING_DIR="${LETSENCRYPT_DIR}" $$ENV_LETSENCRYPT_DIR/acme.sh --issue $2 -d $$ENV_DISCOURSE_HOSTNAME --keylength $1 -w /var/www/discourse/public 63 | } 64 | 65 | cert_exists() { 66 | [[ "$(cd $$ENV_LETSENCRYPT_DIR/$$ENV_DISCOURSE_HOSTNAME$1 && openssl verify -CAfile <(openssl x509 -in ca.cer) fullchain.cer | grep "OK")" ]] 67 | } 68 | 69 | ######################################################## 70 | # RSA cert 71 | ######################################################## 72 | issue_cert "4096" 73 | 74 | if ! cert_exists ""; then 75 | # Try to issue the cert again if something goes wrong 76 | issue_cert "4096" "--force" 77 | fi 78 | 79 | LE_WORKING_DIR="${LETSENCRYPT_DIR}" $$ENV_LETSENCRYPT_DIR/acme.sh \ 80 | --installcert \ 81 | -d $$ENV_DISCOURSE_HOSTNAME \ 82 | --fullchainpath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.cer \ 83 | --keypath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.key \ 84 | --reloadcmd "sv reload nginx" 85 | 86 | ######################################################## 87 | # ECDSA cert 88 | ######################################################## 89 | issue_cert "ec-256" 90 | 91 | if ! cert_exists "_ecc"; then 92 | # Try to issue the cert again if something goes wrong 93 | issue_cert "ec-256" "--force" 94 | fi 95 | 96 | LE_WORKING_DIR="${LETSENCRYPT_DIR}" $$ENV_LETSENCRYPT_DIR/acme.sh \ 97 | --installcert --ecc \ 98 | -d $$ENV_DISCOURSE_HOSTNAME \ 99 | --fullchainpath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.cer \ 100 | --keypath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.key \ 101 | --reloadcmd "sv reload nginx" 102 | 103 | if cert_exists "" || cert_exists "_ecc"; then 104 | grep -q 'force_https' "/var/www/discourse/config/discourse.conf" || echo "force_https = 'true'" >> "/var/www/discourse/config/discourse.conf" 105 | fi 106 | 107 | /usr/sbin/nginx -c /etc/nginx/letsencrypt.conf -s stop 108 | 109 | - replace: 110 | filename: /shared/letsencrypt/account.conf 111 | from: /#?ACCOUNT_EMAIL=.+/ 112 | to: | 113 | ACCOUNT_EMAIL=$$ENV_LETSENCRYPT_ACCOUNT_EMAIL 114 | 115 | - replace: 116 | filename: "/etc/nginx/conf.d/outlets/server/20-https.conf" 117 | from: /ssl_certificate.+/ 118 | to: | 119 | ssl_certificate /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.cer; 120 | ssl_certificate /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.cer; 121 | 122 | - replace: 123 | filename: "/etc/nginx/conf.d/outlets/server/20-https.conf" 124 | from: /ssl_certificate_key.+/ 125 | to: | 126 | ssl_certificate_key /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.key; 127 | ssl_certificate_key /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.key; 128 | -------------------------------------------------------------------------------- /templates/web.onion.template.yml: -------------------------------------------------------------------------------- 1 | # Adds another server on port 80 for hidden service hosting 2 | 3 | run: 4 | - exec: 5 | cmd: 6 | # Check DISCOURSE_ONION variable has been configured 7 | - if [ -z "$DISCOURSE_ONION" ]; then echo "DISCOURSE_ONION ENV variable is required and has not been set."; exit 1; fi 8 | 9 | - exec: 10 | cmd: 11 | # Copy default nginx file 12 | - "cp $home/config/nginx.sample.conf /etc/nginx/conf.d/onion.conf" 13 | 14 | # Remove duplicate entries that would crash the server 15 | - replace: 16 | filename: "/etc/nginx/conf.d/onion.conf" 17 | from: /upstream[^\}]+\}/m 18 | to: "" 19 | 20 | - replace: 21 | filename: "/etc/nginx/conf.d/onion.conf" 22 | from: /map[^\}]+\}/m 23 | to: "" 24 | 25 | - replace: 26 | filename: "/etc/nginx/conf.d/onion.conf" 27 | from: /types[^\}]+\}/m 28 | to: "" 29 | 30 | - replace: 31 | filename: "/etc/nginx/conf.d/onion.conf" 32 | from: /proxy_cache_path.*$/ 33 | to: "" 34 | 35 | - replace: 36 | filename: "/etc/nginx/conf.d/onion.conf" 37 | from: /log_format.*$/ 38 | to: "" 39 | 40 | - replace: 41 | filename: "/etc/nginx/conf.d/onion.conf" 42 | from: /server_name.+$/ 43 | to: server_name $$ENV_DISCOURSE_ONION; 44 | 45 | # Apply the same replacements done on web.template.yml to the nginx file 46 | - replace: 47 | filename: "/etc/nginx/conf.d/onion.conf" 48 | from: /client_max_body_size.+$/ 49 | to: client_max_body_size $upload_size ; 50 | -------------------------------------------------------------------------------- /templates/web.ratelimited.template.yml: -------------------------------------------------------------------------------- 1 | params: 2 | reqs_per_second: 12 3 | burst_per_second: 12 4 | reqs_per_minute: 200 5 | burst_per_minute: 100 6 | conn_per_ip: 20 7 | 8 | run: 9 | - file: 10 | path: "/etc/nginx/conf.d/outlets/before-server/30-ratelimited.conf" 11 | contents: | 12 | limit_req_zone $binary_remote_addr zone=flood:10m rate=$reqs_per_secondr/s; 13 | limit_req_zone $binary_remote_addr zone=bot:10m rate=$reqs_per_minuter/m; 14 | limit_req_status 429; 15 | limit_conn_zone $binary_remote_addr zone=connperip:10m; 16 | limit_conn_status 429; 17 | 18 | - file: 19 | path: "/etc/nginx/conf.d/outlets/discourse/30-ratelimited.conf" 20 | contents: | 21 | limit_conn connperip $conn_per_ip; 22 | limit_req zone=flood burst=$burst_per_second nodelay; 23 | limit_req zone=bot burst=$burst_per_minute nodelay; 24 | -------------------------------------------------------------------------------- /templates/web.socketed.template.yml: -------------------------------------------------------------------------------- 1 | run: 2 | - file: 3 | path: /etc/runit/1.d/remove-old-socket 4 | chmod: "+x" 5 | contents: | 6 | #!/bin/bash 7 | rm -f /shared/nginx.http*.sock 8 | - file: 9 | path: /etc/runit/3.d/remove-old-socket 10 | chmod: "+x" 11 | contents: | 12 | #!/bin/bash 13 | rm -rf /shared/nginx.http*.sock 14 | - replace: 15 | filename: "/etc/nginx/conf.d/outlets/server/10-http.conf" 16 | from: /listen 80;(\nlisten \[::\]:80;)?/ 17 | to: | 18 | listen unix:/shared/nginx.http.sock; 19 | set_real_ip_from unix:; 20 | - replace: 21 | filename: "/etc/nginx/conf.d/outlets/server/20-https.conf" 22 | from: /listen 443 ssl;(\nlisten \[::\]:443 ssl;)?/ 23 | to: | 24 | listen unix:/shared/nginx.https.sock ssl; 25 | set_real_ip_from unix:; 26 | - replace: 27 | filename: "/etc/nginx/conf.d/outlets/before-server/20-redirect-http-to-https.conf" 28 | from: /listen 80;(\nlisten \[::\]:80;)?/ 29 | to: | 30 | listen unix:/shared/nginx.http.sock; 31 | set_real_ip_from unix:; 32 | -------------------------------------------------------------------------------- /templates/web.ssl.template.yml: -------------------------------------------------------------------------------- 1 | run: 2 | - exec: 3 | cmd: 4 | - "mkdir -p /shared/ssl/" 5 | - file: 6 | path: "/etc/nginx/conf.d/outlets/before-server/20-redirect-http-to-https.conf" 7 | contents: | 8 | server { 9 | listen 80; 10 | return 301 https://$$ENV_DISCOURSE_HOSTNAME$request_uri; 11 | } 12 | - file: 13 | path: "/etc/nginx/conf.d/outlets/server/10-http.conf" 14 | contents: "" 15 | - file: 16 | hook: ssl 17 | path: "/etc/nginx/conf.d/outlets/server/20-https.conf" 18 | contents: | 19 | listen 443 ssl; 20 | http2 on; 21 | 22 | ssl_protocols TLSv1.2 TLSv1.3; 23 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; 24 | ssl_prefer_server_ciphers off; 25 | 26 | ssl_certificate /shared/ssl/ssl.crt; 27 | ssl_certificate_key /shared/ssl/ssl.key; 28 | 29 | ssl_session_tickets off; 30 | ssl_session_timeout 1d; 31 | ssl_session_cache shared:SSL:1m; 32 | 33 | add_header Strict-Transport-Security 'max-age=31536000'; 34 | 35 | if ($http_host != $$ENV_DISCOURSE_HOSTNAME) { 36 | rewrite (.*) https://$$ENV_DISCOURSE_HOSTNAME$1 permanent; 37 | } 38 | - file: 39 | path: "/etc/nginx/conf.d/outlets/discourse/20-https.conf" 40 | contents: | 41 | add_header Strict-Transport-Security 'max-age=31536000'; 42 | - exec: 43 | cmd: 44 | - |- 45 | if [ -f "/proc/net/if_inet6" ] ; then 46 | sed -i 's/listen 80;/listen 80;\nlisten [::]:80;/g' /etc/nginx/conf.d/outlets/before-server/20-redirect-http-to-https.conf 47 | sed -i 's/listen 443 ssl;/listen 443 ssl;\nlisten [::]:443 ssl;/g' /etc/nginx/conf.d/outlets/server/20-https.conf 48 | fi 49 | -------------------------------------------------------------------------------- /templates/web.template.yml: -------------------------------------------------------------------------------- 1 | base_image: discourse/base:2.0.20250226-0128 2 | env: 3 | # You can have redis on a different box 4 | RAILS_ENV: 'production' 5 | UNICORN_WORKERS: 3 6 | UNICORN_SIDEKIQS: 1 7 | # stop heap doubling in size so aggressively, this conserves memory 8 | RUBY_GC_HEAP_GROWTH_MAX_SLOTS: 40000 9 | RUBY_GC_HEAP_INIT_SLOTS: 400000 10 | RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR: 1.5 11 | 12 | DISCOURSE_DB_SOCKET: /var/run/postgresql 13 | DISCOURSE_DB_HOST: 14 | DISCOURSE_DB_PORT: 15 | 16 | params: 17 | version: tests-passed 18 | home: /var/www/discourse 19 | upload_size: 10m 20 | nginx_worker_connections: 4000 21 | 22 | run: 23 | - exec: thpoff echo "thpoff is installed!" 24 | - exec: 25 | tag: precompile 26 | cmd: 27 | - /usr/local/bin/ruby -e 'if ENV["DISCOURSE_SMTP_ADDRESS"] == "smtp.example.com"; puts "Aborting! Mail is not configured!"; exit 1; end' 28 | - /usr/local/bin/ruby -e 'if ENV["DISCOURSE_HOSTNAME"] == "discourse.example.com"; puts "Aborting! Domain is not configured!"; exit 1; end' 29 | - /usr/local/bin/ruby -e 'if (ENV["DISCOURSE_CDN_URL"] || "")[0..1] == "//"; puts "Aborting! CDN must have a protocol specified. Once fixed you should rebake your posts now to correct all posts."; exit 1; end' 30 | # TODO: move to base image (anacron can not be fired up using rc.d) 31 | - exec: rm -f /etc/cron.d/anacron 32 | - file: 33 | path: /etc/cron.d/anacron 34 | contents: | 35 | SHELL=/bin/sh 36 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 37 | 38 | 30 7 * * * root /usr/sbin/anacron -s >/dev/null 39 | - file: 40 | path: /etc/runit/1.d/copy-env 41 | chmod: "+x" 42 | contents: | 43 | #!/bin/bash 44 | env > ~/boot_env 45 | conf=/var/www/discourse/config/discourse.conf 46 | 47 | # find DISCOURSE_ env vars, strip the leader, lowercase the key 48 | /usr/local/bin/ruby -e 'ENV.each{|k,v| puts "#{$1.downcase} = '\''#{v}'\''" if k =~ /^DISCOURSE_(.*)/}' > $conf 49 | 50 | - file: 51 | path: /etc/service/unicorn/run 52 | chmod: "+x" 53 | contents: | 54 | #!/bin/bash 55 | exec 2>&1 56 | # redis 57 | # postgres 58 | cd $home 59 | chown -R discourse:www-data /shared/log/rails 60 | # before precompile 61 | if [[ -z "$PRECOMPILE_ON_BOOT" ]]; then 62 | PRECOMPILE_ON_BOOT=1 63 | fi 64 | if [ -f /usr/local/bin/create_db ] && [ "$CREATE_DB_ON_BOOT" = "1" ]; then /usr/local/bin/create_db; fi; 65 | if [ "$MIGRATE_ON_BOOT" = "1" ]; then su discourse -c 'bundle exec rake db:migrate'; fi 66 | if [ "$PRECOMPILE_ON_BOOT" = "1" ]; then SKIP_EMBER_CLI_COMPILE=1 su discourse -c 'bundle exec rake assets:precompile'; fi 67 | LD_PRELOAD=$RUBY_ALLOCATOR HOME=/home/discourse USER=discourse exec thpoff chpst -u discourse:www-data -U discourse:www-data bundle exec config/unicorn_launcher -E production -c config/unicorn.conf.rb 68 | 69 | - file: 70 | path: /etc/service/nginx/run 71 | chmod: "+x" 72 | contents: | 73 | #!/bin/sh 74 | exec 2>&1 75 | exec /usr/sbin/nginx 76 | 77 | - file: 78 | path: /etc/runit/3.d/01-nginx 79 | chmod: "+x" 80 | contents: | 81 | #!/bin/bash 82 | sv stop nginx 83 | 84 | - file: 85 | path: /etc/runit/3.d/02-unicorn 86 | chmod: "+x" 87 | contents: | 88 | #!/bin/bash 89 | sv stop unicorn 90 | 91 | - exec: 92 | cd: $home 93 | hook: code 94 | cmd: 95 | - sudo -H -E -u discourse git clean -f 96 | # TODO Remove the special handling of shallow clones when everyone uses images without that clone type 97 | - |- 98 | sudo -H -E -u discourse bash -c ' 99 | set -o errexit 100 | if [ $(git rev-parse --is-shallow-repository) == "true" ]; then 101 | git remote set-branches --add origin main 102 | git remote set-branches origin $version 103 | git fetch --depth 1 origin $version 104 | else 105 | git fetch --tags --prune-tags --prune --force origin 106 | fi 107 | ' 108 | - |- 109 | sudo -H -E -u discourse bash -c ' 110 | set -o errexit 111 | if [[ $(git symbolic-ref --short HEAD) == $version ]] ; then 112 | git pull 113 | else 114 | git -c advice.detachedHead=false checkout $version 115 | fi 116 | ' 117 | - sudo -H -E -u discourse git config user.discourse-version $version 118 | - mkdir -p tmp 119 | - chown discourse:www-data tmp 120 | - mkdir -p tmp/pids 121 | - mkdir -p tmp/sockets 122 | - touch tmp/.gitkeep 123 | - mkdir -p /shared/log/rails 124 | - bash -c "touch -a /shared/log/rails/{production,production_errors,unicorn.stdout,unicorn.stderr,sidekiq}.log" 125 | - bash -c "ln -s /shared/log/rails/{production,production_errors,unicorn.stdout,unicorn.stderr,sidekiq}.log $home/log" 126 | - bash -c "mkdir -p /shared/{uploads,backups}" 127 | - bash -c "ln -s /shared/{uploads,backups} $home/public" 128 | - bash -c "mkdir -p /shared/tmp/{backups,restores}" 129 | - bash -c "ln -s /shared/tmp/{backups,restores} $home/tmp" 130 | - chown -R discourse:www-data /shared/log/rails /shared/uploads /shared/backups /shared/tmp 131 | # scrub broken symlinks from plugins that have been removed 132 | - "[ ! -d public/plugins ] || find public/plugins/ -maxdepth 1 -xtype l -delete" 133 | 134 | - exec: 135 | cmd: 136 | - "cp $home/config/nginx.sample.conf /etc/nginx/conf.d/discourse.conf" 137 | - "rm /etc/nginx/sites-enabled/default" 138 | - "mkdir -p /var/nginx/cache" 139 | 140 | # Stop building the container if the Nginx outlets are missing 141 | - "grep -q 'outlets/before-server' /etc/nginx/conf.d/discourse.conf || ( >&2 echo 'The \"before-server\" Nginx outlet is missing. This version of discourse_docker is not compatible with the chosen Discourse version.' ; exit 1 )" 142 | - "grep -q 'outlets/server' /etc/nginx/conf.d/discourse.conf || ( >&2 echo 'The \"server\" Nginx outlet is missing. This version of discourse_docker is not compatible with the chosen Discourse version.' ; exit 1 )" 143 | - "grep -q 'outlets/discourse' /etc/nginx/conf.d/discourse.conf || ( >&2 echo 'The \"discourse\" Nginx outlet is missing. This version of discourse_docker is not compatible with the chosen Discourse version.' ; exit 1 )" 144 | 145 | # Create placeholders for the Nginx outlets 146 | - "mkdir -p /etc/nginx/conf.d/outlets/before-server" 147 | - "touch /etc/nginx/conf.d/outlets/before-server/20-redirect-http-to-https.conf" 148 | - "touch /etc/nginx/conf.d/outlets/before-server/30-ratelimited.conf" 149 | - "mkdir -p /etc/nginx/conf.d/outlets/server" 150 | - "touch /etc/nginx/conf.d/outlets/server/10-http.conf" 151 | - "touch /etc/nginx/conf.d/outlets/server/20-https.conf" 152 | - "touch /etc/nginx/conf.d/outlets/server/30-offline-page.conf" 153 | - "mkdir -p /etc/nginx/conf.d/outlets/discourse" 154 | - "touch /etc/nginx/conf.d/outlets/discourse/20-https.conf" 155 | - "touch /etc/nginx/conf.d/outlets/discourse/30-ratelimited.conf" 156 | 157 | - replace: 158 | filename: /etc/nginx/nginx.conf 159 | from: pid /run/nginx.pid; 160 | to: daemon off; 161 | 162 | - replace: 163 | filename: "/etc/nginx/nginx.conf" 164 | from: /worker_connections.+$/ 165 | to: worker_connections $nginx_worker_connections; 166 | 167 | - replace: 168 | filename: "/etc/nginx/conf.d/discourse.conf" 169 | from: /client_max_body_size.+$/ 170 | to: client_max_body_size $upload_size; 171 | 172 | - exec: 173 | cmd: 174 | # Move `listen 80` to an outlet 175 | - sed -i 's#listen 80;##g' /etc/nginx/conf.d/discourse.conf 176 | - |- 177 | if [ -f "/proc/net/if_inet6" ]; then 178 | echo "listen 80;\nlisten [::]:80;" > /etc/nginx/conf.d/outlets/server/10-http.conf 179 | else 180 | echo "listen 80;" > /etc/nginx/conf.d/outlets/server/10-http.conf 181 | fi 182 | 183 | - exec: 184 | cmd: echo "done configuring web" 185 | hook: web_config 186 | 187 | - exec: 188 | cd: $home 189 | hook: web 190 | cmd: 191 | # install bundler version to match Gemfile.lock 192 | - gem install bundler --conservative -v $(awk '/BUNDLED WITH/ { getline; gsub(/ /,""); print $0 }' Gemfile.lock) 193 | - find $home ! -user discourse -exec chown discourse {} \+ 194 | 195 | - exec: 196 | cd: $home 197 | hook: yarn 198 | cmd: 199 | - |- 200 | if [ -f yarn.lock ]; then 201 | if [ -d node_modules/.pnpm ]; then 202 | echo "This version of Discourse uses yarn, but pnpm node_modules are preset. Cleaning up..." 203 | find ./node_modules ./app/assets/javascripts/*/node_modules -mindepth 1 -maxdepth 1 -exec rm -rf {} + 204 | fi 205 | su discourse -c 'yarn install --frozen-lockfile && yarn cache clean' 206 | else 207 | su discourse -c 'CI=1 pnpm install --frozen-lockfile && pnpm prune' 208 | fi 209 | 210 | - exec: 211 | cd: $home 212 | hook: bundle_exec 213 | cmd: 214 | - su discourse -c 'bundle install --jobs $(($(nproc) - 1)) --retry 3' 215 | - su discourse -c 'bundle clean' 216 | 217 | - exec: 218 | cd: $home 219 | cmd: 220 | - su discourse -c 'LOAD_PLUGINS=0 bundle exec rake plugin:pull_compatible_all' 221 | hook: plugin_compatibility 222 | raise_on_fail: false 223 | 224 | - exec: 225 | cd: $home 226 | tag: migrate 227 | hook: db_migrate 228 | cmd: 229 | - su discourse -c 'bundle exec rake db:migrate' 230 | - exec: 231 | cd: $home 232 | tag: build 233 | hook: assets_precompile_build 234 | cmd: 235 | - su discourse -c 'bundle exec rake assets:precompile:build' 236 | - exec: 237 | cd: $home 238 | tag: precompile 239 | hook: assets_precompile 240 | cmd: 241 | - su discourse -c 'SKIP_EMBER_CLI_COMPILE=1 bundle exec rake themes:update assets:precompile' 242 | 243 | - replace: 244 | tag: precompile 245 | filename: /etc/service/unicorn/run 246 | from: "# before precompile" 247 | to: "PRECOMPILE_ON_BOOT=0" 248 | 249 | - file: 250 | path: /usr/local/bin/discourse 251 | chmod: +x 252 | contents: | 253 | #!/bin/bash 254 | (cd /var/www/discourse && RAILS_ENV=production sudo -H -E -u discourse bundle exec script/discourse "$@") 255 | 256 | - file: 257 | path: /usr/local/bin/rails 258 | chmod: +x 259 | contents: | 260 | #!/bin/bash 261 | (cd /var/www/discourse && RAILS_ENV=production sudo -H -E -u discourse bundle exec script/rails "$@") 262 | 263 | - file: 264 | path: /usr/local/bin/rake 265 | chmod: +x 266 | contents: | 267 | #!/bin/bash 268 | (cd /var/www/discourse && RAILS_ENV=production sudo -H -E -u discourse bundle exec bin/rake "$@") 269 | 270 | - file: 271 | path: /usr/local/bin/rbtrace 272 | chmod: +x 273 | contents: | 274 | #!/bin/bash 275 | (cd /var/www/discourse && RAILS_ENV=production sudo -H -E -u discourse bundle exec rbtrace "$@") 276 | 277 | - file: 278 | path: /usr/local/bin/stackprof 279 | chmod: +x 280 | contents: | 281 | #!/bin/bash 282 | (cd /var/www/discourse && RAILS_ENV=production sudo -H -E -u discourse bundle exec stackprof "$@") 283 | 284 | - file: 285 | path: /etc/update-motd.d/10-web 286 | chmod: +x 287 | contents: | 288 | #!/bin/bash 289 | echo 290 | echo Use: rails, rake or discourse to execute commands in production 291 | echo 292 | 293 | - file: 294 | path: /etc/logrotate.d/rails 295 | contents: | 296 | /shared/log/rails/*.log 297 | { 298 | rotate 7 299 | dateext 300 | daily 301 | missingok 302 | delaycompress 303 | compress 304 | sharedscripts 305 | postrotate 306 | sv 1 unicorn 307 | endscript 308 | } 309 | 310 | - file: 311 | path: /etc/logrotate.d/nginx 312 | contents: | 313 | /var/log/nginx/*.log { 314 | daily 315 | missingok 316 | rotate 7 317 | compress 318 | delaycompress 319 | create 0644 www-data www-data 320 | sharedscripts 321 | postrotate 322 | sv 1 nginx 323 | endscript 324 | } 325 | 326 | # move state out of the container this fancy is done to support rapid rebuilds of containers, 327 | # we store anacron and logrotate state outside the container to ensure its maintained across builds 328 | # later move this snipped into an initialization script 329 | # we also ensure all the symlinks we need to /shared are in place in the correct structure 330 | # this allows us to bootstrap on one machine and then run on another 331 | - file: 332 | path: /etc/runit/1.d/00-ensure-links 333 | chmod: +x 334 | contents: | 335 | #!/bin/bash 336 | if [[ ! -L /var/lib/logrotate ]]; then 337 | rm -fr /var/lib/logrotate 338 | mkdir -p /shared/state/logrotate 339 | ln -s /shared/state/logrotate /var/lib/logrotate 340 | fi 341 | if [[ ! -L /var/spool/anacron ]]; then 342 | rm -fr /var/spool/anacron 343 | mkdir -p /shared/state/anacron-spool 344 | ln -s /shared/state/anacron-spool /var/spool/anacron 345 | fi 346 | if [[ ! -d /shared/log/rails ]]; then 347 | mkdir -p /shared/log/rails 348 | chown -R discourse:www-data /shared/log/rails 349 | fi 350 | if [[ ! -d /shared/uploads ]]; then 351 | mkdir -p /shared/uploads 352 | chown -R discourse:www-data /shared/uploads 353 | fi 354 | if [[ ! -d /shared/backups ]]; then 355 | mkdir -p /shared/backups 356 | chown -R discourse:www-data /shared/backups 357 | fi 358 | 359 | rm -rf /shared/tmp/{backups,restores} 360 | mkdir -p /shared/tmp/{backups,restores} 361 | chown -R discourse:www-data /shared/tmp/{backups,restores} 362 | - file: 363 | path: /etc/runit/1.d/01-cleanup-web-pids 364 | chmod: +x 365 | contents: | 366 | #!/bin/bash 367 | /bin/rm -f /var/www/discourse/tmp/pids/*.pid 368 | # change login directory to Discourse home 369 | - file: 370 | path: /root/.bash_profile 371 | chmod: 644 372 | contents: | 373 | cd $home 374 | 375 | - file: 376 | path: /usr/local/etc/ImageMagick-7/policy.xml 377 | contents: | 378 | 379 | 381 | 382 | 383 | 386 | ]> 387 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | ## Smoke tests for `discourse-setup` 2 | 3 | These are not **real** tests, but do test that `discourse-setup` can produce or modify 4 | the YML file as expected. 5 | 6 | Tests will not run if yml files exist already. If the tests succeed, the container files are deleted. 7 | 8 | ### `standalone` tests 9 | 10 | - run the first time to do an initial creation of `app.yml` and that the values get set as expected. 11 | - run again to change the values 12 | 13 | ### `two-container` tests 14 | 15 | - run with `--two-container` switch to create separate data and web containers 16 | - run again (not requiring the `--two-container` switch) and update values as expected 17 | 18 | ### `update-old-templates` tests 19 | 20 | - updates a very old (Sep 6, 2016) standalone.yml 21 | - updates a pretty old (Apr 13, 2018) web_only.yml 22 | 23 | The tests won't run if `app.yml` or `web_only.yml` exist. 24 | 25 | ### `run-all-tests` 26 | 27 | Runs all three of the above tests and prints an error if `app.yml` or `web_only.yml` exist. 28 | 29 | -------------------------------------------------------------------------------- /tests/run-all-tests: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 3 | 4 | $DIR/standalone 5 | $DIR/two-container 6 | $DIR/update-old-templates 7 | 8 | if [ -f $DIR/../containers/app.yml ] || [ -f $DIR/../containers/web_only.yml ] 9 | then 10 | echo Some test failed. Sad. 11 | fi 12 | -------------------------------------------------------------------------------- /tests/standalone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 3 | YML=$DIR/../containers/app.yml 4 | if [ -f $YML ] 5 | then 6 | echo "cannot run test if $YML exists." 7 | exit 8 | fi 9 | 10 | check_value () { 11 | VAR=$1 12 | VAL=$2 13 | YML=$3 14 | if ! [[ $(grep $VAR $YML |sed -e "s/ $VAR: //") == "$VAL" ]] 15 | then 16 | echo $VAR is NOT $VAL 17 | echo TEST FAILED. Aborting. 18 | exit 1 19 | fi 20 | } 21 | 22 | hostname='test.myhost.com' 23 | developer='admin@mail.myhost.com' 24 | smtp_address='smtp.myhostn.com' 25 | smtp_port='' 26 | smtp_user='smtpuser' 27 | smtp_pass='smtp-pw' 28 | notification='' 29 | letsencrypt='le@myhost.com' 30 | maxmind='' 31 | $DIR/../discourse-setup --skip-connection-test --skip-rebuild <containers/app.yml > $YML 24 | 25 | hostname='new.myhost.com' 26 | developer='new@mail.myhost.com' 27 | smtp_address='new.myhostn.com' 28 | smtp_port='2525' 29 | smtp_user='newuser' 30 | smtp_pass='new-smtp-pw' 31 | notification='somuser@otherhost.com' 32 | smtp_domain=otherhost.com # NOTE: script uses notification hostnme 33 | letsencrypt='le-new@myhost.com' 34 | maxmind='maxthisone' 35 | $DIR/../discourse-setup --skip-connection-test --skip-rebuild < $YML 62 | hostname='new.myhost.com' 63 | developer='new-admin@mail.myhost.com' 64 | smtp_address='new.myhostn.com' 65 | smtp_port='2525' 66 | smtp_user='newuser' 67 | smtp_pass='new-smtp-pw' 68 | notification='somuser@otherhost.com' 69 | smtp_domain=otherhost.com # NOTE: script uses notification hostnme 70 | letsencrypt='le-new@myhost.com' 71 | maxmind='maxthisone' 72 | $DIR/../discourse-setup --skip-connection-test --skip-rebuild <