Repository: debiki/talkyard-prod-one
Branch: main
Commit: 674690bec601
Files: 39
Total size: 117.6 KB
Directory structure:
gitextract_culawk36/
├── .gitignore
├── .gitmodules
├── LICENSE-MIT.txt
├── README.md
├── conf/
│ ├── app/
│ │ └── play-framework.conf
│ └── web/
│ ├── maint-msg.html
│ └── sites-enabled/
│ └── talkyard-servers.conf
├── debug.yml
├── docker-compose.yml
├── docs/
│ ├── copy-backups-elsewhere.md
│ ├── how-restore-backup.md
│ ├── multisite-talkyard.adoc
│ ├── release-channels.md
│ ├── risk-free-upgrades.md
│ ├── troubleshooting.md
│ └── upgrade-v0-to-v1.md
├── mem/
│ ├── 1.7g.yml
│ ├── 2g.yml
│ ├── 4g.yml
│ ├── 7.5G.yml
│ └── 8G.yml
├── scripts/
│ ├── backup.sh
│ ├── delete-old-backups.sh
│ ├── find-admin-login-link.sh
│ ├── impl/
│ │ ├── check-talkyard-backups.sh
│ │ ├── docker-compose.wrong-app-ip.yml
│ │ ├── recreate-web.sh
│ │ └── unjson.sh
│ ├── old/
│ │ └── Vagrantfile
│ ├── prepare-os.sh
│ ├── schedule-automatic-upgrades.sh
│ ├── schedule-daily-backups.sh
│ ├── tests/
│ │ ├── install-all.sh
│ │ ├── install-docker-compose.sh
│ │ ├── test-delete-backups.sh
│ │ └── test-generate-backups.sh
│ └── upgrade-if-needed.sh
├── view-logs
└── view-stats
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
secrets/
# Maintenance log. Various scripts write to.
talkyard-maint.log
================================================
FILE: .gitmodules
================================================
[submodule "versions"]
path = versions
url = https://github.com/debiki/talkyard-versions.git
================================================
FILE: LICENSE-MIT.txt
================================================
The things in this repository are licensed under the MIT license, see below. The actual Talkyard source code is in another repository, under a different license.
---
Copyright (c) 2016-2024 Kaj Magnus Lindberg
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
Installing Talkyard
================
NOTICE: This Git branch is a PREVIEW of the upcoming Talkyard v1 (epoch 1).
You need to edit `.env` and set `RELEASE_BRANCH=tyse-v1-dev` (even though the
comment juts above in `.env` says "don't use").
Do not use this preview of v1 "for real", in production, yet.
Also, don't install v0 — because then you'd want to upgrade to v1 pretty soon,
a waste of time.
Instead: **Wait 1 or 2 weeks until v1 has been released** — that should be in the beginning
of April 2026, or end of Mars. Then install v1.
Feedback welcome! You can post in: https://forum.talkyard.io
------
Here you'll learn how to install Talkyard v1 on a single server, for production use:
Debian 12 or 13 with at least 2 GB RAM.
(Old Talkyard v0 docs are
here.)
Docker-based installation.
Automated upgrades and backups.
Automatic HTTPS certs.
Multi-site support.
You should be familiar with Linux, Bash, Git and Docker.
Or use our hosting, see https://www.talkyard.io.
Ask questions and report problems in **[the forum](http://www.talkyard.io/forum/latest/support)**.
**Installation overview:** You'll rent a virtual private server (VPS), download
and install Talkyard, sign up for a send-emails service, and configure email settings.
Then optionally configure OpenAuth login for Google, Facebook, Twitter, GitHub.
And off-site backups.
Dockerfiles, build scripts and source code are in another repo: https://github.com/debiki/talkyard.
See `./docker-compose.yml` (in this repo) for details and links.
Directories
----------------
You'll install Talkyard in `/opt/talkyard-v1/`.
Talkyard uses these directories:
(following the Linux File System Hierarchy Standard)
- `/opt/talkyard-v1/`: Various scripts, and `docker-compose.yml`.
This is a Git repo — you can check in your changes to Git,
but only if you can resolve Git conflicts!
- `/opt/talkyard-v1/conf`:
Configuration, mounted read-only in Docker containers.
- `/opt/talkyard-v1/secrets`:
Docker secrets, e.g. database password.
- `/var/lib/docker/`:
Database storage, uploaded files (in Docker volumes).
Docker images, log files.
- `/var/opt/backups/talkyard/v1/`:
Backups.
Preparations
----------------
1.
Provision a Debian 12 or 13 server, or Ubuntu 24.04,
with at least 20 GB disk and 2 GB RAM,
for example at
[Upcloud](https://upcloud.com/).
Point a domain name, say, `forum.your-website.com`, to the server IP address.
1.
Update the OS, then install Git and some stuff:
apt-get update
apt-get upgrade
apt-get -y install git locales
apt-get -y install rng-tools # better generation of random numbers
apt-get -y install jq # to view logs
apt-get -y install tree ncdu vim # nice to have
locale-gen en_US.UTF-8 # installs English
export LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 # starts using English (warnings are harmless)
1.
Create big empty files that you can delete if your server runs out of disk:
fallocate --length 250MiB /balloon-1-delete-if-disk-full
fallocate --length 250MiB /balloon-2-delete-if-disk-full
fallocate --length 250MiB /var/balloon-3-delete-if-disk-full
fallocate --length 250MiB /var/balloon-4-delete-if-disk-full
1.
Install Docker.
Read: https://docs.docker.com/engine/install/debian/ and follow the instructions.
Or use their convenience script: https://docs.docker.com/engine/install/debian/#install-using-the-convenience-script.
Afterwards, also install the Docker Compose plugin:
sudo apt-get install docker-compose-plugin
(Optionally, add: `{ "log-driver": "local" }` to `/etc/docker/daemon.json`,
so Docker will delete old logs for all your Docker containers, and save disk.
But you don't need to — Talkyard uses that logging driver by default in any case.)
### Advanced
If you want to, and know what you're doing:
**Swap:** Comment out any swap from `/etc/fstab`, and run: `swapoff -a`.
**Disks:** Mount `/var/` and `/var/opt/backups/(talkyard/)` on their own disks
(so the host OS and Talkyard won't stop working just because some disk
gets full).
**Firewall:** Install a firewall, for example, firewalld, see: https://firewalld.org.
Note that ufw (another Linux firewall) is incompatible with Docker
— Docker can bypass `ufw` rules, see:
https://docs.docker.com/engine/network/packet-filtering-firewalls/#docker-and-ufw.
There is, however, [ufw-docker](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#recommended-mitigations).
You can see the IP addresses of the Docker containers in the `.env` file. The IP
of the `web` container, which runs Nginx and listens on ports 80 and 443,
is set on the `FE_PUB_NET_WEB_IP=...` line, and this is the only
container that should be reachable from outside. The egress proxy should be able
to connect to the Internet (but no one should be able to connect to it). Its
IP address is set on the `EG_PUB_NET_EGRESSP_IP=...` line.
More about Firewalld and Docker:
https://firewalld.org/2024/04/strictly-filtering-docker-containers
If you use Google Cloud Engine: GCE already has a firewall.
Installation instructions
----------------
(There's a troubleshooting document here: ./docs/troubleshooting.md )
1.
Download installation scripts: (you need to install in
`/opt/talkyard-v1/` for the backup scripts to work)
sudo -i # become root
cd /opt/
git clone https://github.com/debiki/talkyard-prod-one.git talkyard-v1
cd talkyard-v1
# Make sure you'll install v1:
git checkout --track origin/ty-prod-one-v1
1.
Prepare the OS: install tools, enable automatic security updates, simplify troubleshooting,
and make ElasticSearch work: (Consider reading the script first...)
./scripts/prepare-os.sh 2>&1 | tee -a talkyard-maint.log
If you don't want to run the whole script, you at least need to:
- Copy the sysctl `net.core.somaxconn` and `vm.max_map_count` settings in the script to your
`/etc/sysctl.conf` config file — otherwise, the full-text-search-engine (ElasticSearch)
won't work. Afterwards, run `sysctl --system` to reload the system configuration.
1. Edit config values:
```
nano conf/app/play-framework.conf # fill in values in the Required Settings section
nano secrets/postgres_password.txt # type a database password on a single line, nothing else!
# Don't let anyone see the password.
chmod 0600 secrets/postgres_password.txt
```
Note:
- Set `talkyard.secure=true`, so HTTPS will work — unless you're testing
on localhost; then set `talkyard.secure=false`.
- If you don't edit `play.http.secret.key` in `play-framework.conf`,
the server won't start.
- A PostgreSQL database user, named *talkyard*, gets created automatically,
by the *rdb* Docker container, with the password you specified in
`secrets/postgres_password.txt`.
You don't need to do anything.
1. Depending on how much RAM your server has (run `free -mh` to find out),
choose one of these files:
mem/1.7g.yml, mem/2g.yml, mem/3.6g.yml, ... and so on,
and copy it to ./docker-compose.override.yml. For example, for
a server with 4 GB RAM:
cp mem/4g.yml docker-compose.override.yml
1. Install and start the latest version. Might take a few minutes
the first time (to download Docker images).
# This script also installs, although named "upgrade–...".
./scripts/upgrade-if-needed.sh 2>&1 | tee -a talkyard-maint.log
(This creates a new Docker network — you can choose the IP range; see the
section *Docker Networks* below.)
Type `docker compose ps` — you should now see a list
of Docker containers in state Up (means they're running).
1. Schedule daily backups, deletion of old backups, and automatic upgrades:
./scripts/schedule-daily-backups.sh 2>&1 | tee -a talkyard-maint.log
./scripts/schedule-automatic-upgrades.sh 2>&1 | tee -a talkyard-maint.log
1. Open a web browser; go to `https://talkyard.your website.com` — note: **https**
not http.
Your browser should show a warning about the connection _not_ being secure.
Talkyard and LetsEncrypt will now start generating a HTTPS certificate for you.
Wait 20 seconds, reload the page, and thereafter HTTPS should work.
1. In the browser, click _Continue_ and create an admin account
with the email address you specified when you edited `play-framework.conf` earlier
(see above).
Follow the getting-started guide.
Everything will restart automatically on server reboot.
Next steps:
- Edit `/opt/talkyard-v1/conf/web/sites-enabled/talkyard-servers.conf` and redirect
from HTTP to HTTPS.
- Sign up for a send-email-service — see the section just below.
- Send an email to `hello at talkyard.io` so we get your address, and can
inform you about security issues and major software
upgrades that might require you to do something manually.
Or subscribe to the Announcements category over at https://www.talkyard.io/forum/.
- Copy backups off-site, regularly. See the Backups section below.
- Configure Gmail, Facebook, Twitter, GitHub login,
by creating OpenAuth apps over at their sites, and adding API keys and secrets
to `play-framework.conf`. See below, just after the next section, about email.
- Optionally, create more Talkyard sites hosted by this same Talkyard installation,
see [docs/multisite-talkyard.adoc](docs/multisite-talkyard.adoc).
Configuring email
----------------
If you don't have a mail server already, then sign up for a transactional email
service, for example Mailgun, Elastic Email, SendGrid, Mailjet or Amazon SES.
(Signing up, and verifying your sender email address and domain, is a bit complicated
— nothing you do in five minutes.)
Then, configure email settings in `/opt/talkyard/conf/play-framework.conf`,
that is, fill in these values:
```
talkyard.smtp.host="..."
talkyard.smtp.port="587"
talkyard.smtp.requireStartTls=true
#talkyard.smtp.tlsPort="465"
#talkyard.smtp.connectWithTls=true
talkyard.smtp.checkServerIdentity=true
talkyard.smtp.user="..."
talkyard.smtp.password="..."
talkyard.smtp.fromAddress="support@your-organization.com"
```
(Google Cloud Engine blocks outgoing ports 587 and 465 (at least it did in the past).
Probably you email provider has made other ports available for you to use,
e.g. Amazon SES: ports 2587 and 2465.)
OpenAuth login
----------------
You want login with Facebook, Gmail and maybe Twitter and GitHub to work? Here's how.
However, we haven't written easy to follow instructions for this yet.
Send us an email: `hello at talkyard.io`, mention OpenAuth, and we'll hurry up.
(There are very very brief instructions in this the markdown source but they might be out of date,
or there might be typos,
so they're hidden unless you are a tech person who knows how to view the source.)
Viewing log files
----------------
Change directory to `/opt/talkyard-v1/`. Then:
- The application server, to view its logs: `./view-logs -f --tail 50 app`
(where `-f --tail NN` is optional).
You can also: `docker compose logs -f --tail 50 app`, but then you'll see
hard to read json. `view-logs` uses `jq` to parse & make readable the json.
- The web server: `docker compose logs -f --tail 50 web` (not json).
- The database: `docker compose logs -f --tail 50 rdb` (not json).
- The search engine: `./view-logs search`.
Upgrading to newer versions
----------------
If you followed the instructions above — that is, if you ran these scripts:
`./scripts/prepare-os.sh` and `./scripts/schedule-automatic-upgrades.sh`
— then your server should keep itself up-to-date, and ought to require no maintenance.
In a few cases you might have to do something manually, when upgrading.
Like, running `git pull` and editing config files, maybe running a shell script.
For us to be able to tell you about this, please send us an email at
`hello at talkyard.io`.
If you didn't run `./scripts/schedule-automatic-upgrades.sh`, you can upgrade
manually like so:
sudo -i
cd /opt/talkyard-v1/
./scripts/upgrade-if-needed.sh 2>&1 | tee -a talkyard-maint.log
Backups
----------------
### Importing a backup
See [docs/how-restore-backups.md](./docs/how-restore-backup.md).
You can log in to Postgres like so:
sudo docker compose exec rdb psql postgres postgres # as user 'postgres'
sudo docker compose exec rdb psql talkyard talkyard # as user 'talkyard'
### Backing up, manually
You should have configured automatic backups already, see the Installation
Instructions section above. In any case, you can backup manually like so:
sudo -i
cd /opt/talkyard-v1/
./scripts/backup.sh manual 2>&1 | tee -a talkyard-maint.log
New backups should appear in `/var/opt/backups/talkyard/v1/archives/`.
### Copy backups elsewhere
You should copy the backups to a safety off-site backup server, regularly.
Otherwise, if your main server suddenly disappears, or someone breaks into it
and ransomware-encrypts everything — you'd lose all data.
See [docs/copy-backups-elsewhere.md](./docs/copy-backups-elsewhere.md).
Docker networks
----------------
Talkyard creates its own Docker subnets, for security reasons, and assigns static
IPs to the containers.
Without static IPs, then, if a container restarts, Docker might give it a new IP,
and the other containers then couldn't find it it. —
Unless they're also restarted, so all things that have cached the old stale IP,
pick up the new IP instead.
You can choose the network IP ranges. Open the `.env` file, scroll down and you'll see:
```
FE_PUB_NET_SUBNET=...
FE_INT_NET_SUBNET=...
BE_INT_NET_SUBNET=...
...
```
Tips
----------------
If you start running out of disk, one reason can be old patches for automatic operating system security updates.
You can delete them to free up disk:
```
sudo apt autoremove --purge
```
License (MIT)
----------------
```
Copyright (c) 2016-2025 Kaj Magnus Lindberg.
Licensed under the MIT license, see `LICENSE-MIT.txt` — and this is for the
instructions and scripts in this repository only, not for Talkyard source code
or things in other repositories.
```
================================================
FILE: conf/app/play-framework.conf
================================================
# Play Framework configuration file
# Required settings
# ---------------------
# Fill in your email address. Later on, sign up with this email, to become the site owner.
talkyard.becomeOwnerEmailAddress="your-email@your.website.org"
# The address to your Talkyard site, e.g. "forum.your-organization.org" or
# "comments.your.blog".
# This address is used when Talkyard generates links back to itself, e.g. links in emails.
# And so your Talkyard site can know that incoming HTTP requests are indeed meant for it.
# (If the hostname is something else, Talkyard will instead reply that there is no such site.)
talkyard.hostname="localhost"
# If testing with Vagrant on localhost.
#talkyard.port=8080
# Set to true to use HTTPS.
talkyard.secure=false
# Replace "change_this" with say 80 random characters. The value is secret.
# The server will refuse to start until you've changed this.
play.http.secret.key="change_this"
# Email server
# ---------------------
talkyard.smtp.host=""
talkyard.smtp.user=""
talkyard.smtp.password=""
talkyard.smtp.fromAddress="support@your-organization.com"
## You can use STARTTLS:
talkyard.smtp.port="587"
talkyard.smtp.requireStartTls=true
## or connect directly with TLS:
#talkyard.smtp.tlsPort="465"
#talkyard.smtp.connectWithTls=true
## but don't try both at the same time.
## If you're running your own email server, it needs a TLS certificate
## for this to work. You can use LetsEncrypt to get a cert.
talkyard.smtp.checkServerIdentity=true
# Spam detection
# ---------------------
talkyard.googleApiKey=""
talkyard.akismetApiKey=""
talkyard.securityComplaintsEmailAddress="support@example.com"
# Backup and restore
# ---------------------
# How large SQL dump you can restore/import.
# (Also see TY_NGX_LIMIT_REQ_BODY_SIZE in docker-compose.yml)
talkyard.maxImportDumpBytes=25100100
# This is for importing additional Talkyard sites to this server. It would
# then host many Talkyard sites, each one with its own unique hostname.
talkyard.mayImportSite=false
# Other parts of the system
# ---------------------
talkyard.redis.host="cache"
talkyard.nginx.host="web"
talkyard.postgresql.host="rdb"
talkyard.postgresql.port="5432"
talkyard.postgresql.database="talkyard"
talkyard.postgresql.user="talkyard"
# Password in docker secret: /run/secrets/postgres_password
# Advanced
# ---------------------
talkyard.featureFlags=""
talkyard.maxGroupMentionNotifications=50
#talkyard.createSiteHostname=""
#talkyard.cdnOrigin="//your-cdn.example.com"
# From environment variables
# ---------------------
# For changes to environment variables to take effect, the relevant container (typically
# the app container) needs to be deleted (docker compose rm app) and recreated.
talkyard.becomeOwnerEmailAddress=${?BECOME_OWNER_EMAIL_ADDRESS}
talkyard.hostname=${?TALKYARD_HOSTNAME}
talkyard.port=${?TALKYARD_PORT}
talkyard.secure=${?TALKYARD_SECURE}
talkyard.createSiteHostname=${?CREATE_SITE_HOSTNAME}
talkyard.maintenanceApiSecret=${?TY_SYSMAINT_API_SECRET}
talkyard.emailWebhooksApiSecret=${?TY_EMAIL_WEBHOOKS_API_SECRET}
talkyard.featureFlags=${?TY_FEATURE_FLAGS}
play.http.secret.key=${?PLAY_SECRET_KEY}
# Testing
# ---------------------
talkyard.e2eTestPassword=${?E2E_TEST_PASSWORD}
talkyard.forbiddenPassword=${?FORBIDDEN_PASSWORD}
talkyard.mayFastForwardTime=${?MAY_FAST_FORWARD_TIME}
# Authentication
# ---------------------
# OpenAuth login (i.e. login via Google, Facebook, etc).
talkyard.authn {
# Google provider
google.authorizationURL="https://accounts.google.com/o/oauth2/auth"
google.accessTokenURL="https://accounts.google.com/o/oauth2/token"
google.scope="profile email"
#google.clientID="..."
#google.clientSecret="..."
# Facebook provider
facebook.authorizationURL="https://graph.facebook.com/v3.0/oauth/authorize"
facebook.accessTokenURL="https://graph.facebook.com/v3.0/oauth/access_token"
facebook.scope="email"
#facebook.clientID="..."
#facebook.clientSecret="..."
# Twitter provider
twitter.requestTokenURL="https://twitter.com/oauth/request_token"
twitter.accessTokenURL="https://twitter.com/oauth/access_token"
twitter.authorizationURL="https://twitter.com/oauth/authenticate"
#twitter.consumerKey="..."
#twitter.consumerSecret="..."
# GitHub
github.authorizationURL="https://github.com/login/oauth/authorize"
github.accessTokenURL="https://github.com/login/oauth/access_token"
github.scope="user:email"
#github.clientID="..."
#github.clientSecret="..."
}
================================================
FILE: conf/web/maint-msg.html
================================================
Then reload this page. We're upgrading the server.
================================================ FILE: conf/web/sites-enabled/talkyard-servers.conf ================================================ ## Nginx `server {}` blocks for your Talkyard forum. ## ## There's 1) a HTTP server, 2) a HTTPS server with auto generated LetsEncrypt certs. ## ## To redirect HTTP to HTTPS: ## Comment out the 'include /etc/nginx/...' lines in the HTTP server (not the HTTPS server). ## Comment in the 'return 302 ...' line. ## ## There's also 3) an out commented server block that shows how you can ## add your own server with a custom cert, e.g. a wildcard cert. ## ## Note: If you comment-in/add-more server blocks, don't include 'backlog=8192' ## in their listen directives — otherwise there'll be a "duplicate listen options" ## Nginx error. The backlog should be the same as net.core.somaxconn in /etc/sysctl.conf, ## namely 8192, set in /opt/talkyard-v1/scripts/prepare-os.sh [BACKLGSZ] ## — but one may specify this in only one place. ## ## HTTP Server. ## Replies to HTTPS cert challenges, can redirect to HTTPS. ## server { listen 80 backlog=8192; # about backlog: see above [BACKLGSZ] ## Using ipv6 here, can prevent Nginx from starting, if the host OS has disabled ipv6, ## Nginx then won't start and says: [ipv6_probl] # [emerg] socket() [::]:80 failed (97: Address family not supported by protocol) #listen [::]:80 backlog=8192; server_name _; ## For generating HTTPS certs via LetsEncrypt, HTTP-01 challenge. location /.well-known/acme-challenge { content_by_lua_block { ngx.log(ngx.INFO, "Replying to ACME HTTP-01 challenge" .. ", server name: " .. ngx.var.server_name .. ", host: " .. ngx.var.http_host .. " [TyNACMEHTTP01]") require("resty.acme.autossl").serve_http_challenge() } } ## To redirect to HTTPS, comment out these two includes, and comment in ## "location / { return 302 ... }" below. include /etc/nginx/server-limits.conf; include /etc/nginx/server-locations.conf; ## Redirect from HTTP to HTTPS. ## Use temp redirects (302) not permanent (301) in case you'll want to allow ## http in the future, for some reason. #location / { # return 302 https://$http_host$request_uri; #} } ## HTTPS Server with LetsEncrypt auto generated certs. ## server { listen 443 ssl default_server backlog=8192; # [BACKLGSZ] #listen [::]:443 ssl default_server backlog=8192; # [ipv6_probl] http2 on; server_name _; ## Required, or Nginx won't start. Gets used until we've gotten a LetsEncrypt cert ## (sth like 10 seconds after first HTTPS request to the server addr). ssl_certificate /etc/nginx/generated/https-cert-self-signed-fallback.pem; ssl_certificate_key /etc/nginx/generated/https-cert-self-signed-fallback.key; ssl_certificate_by_lua_block { require("resty.acme.autossl").ssl_certificate() } ## For generating HTTPS certs via LetsEncrypt, TLS-ALPN-01 challenge ## (which works over HTTPS, unlike the HTTP-01 challenge). ## Disabled in nginx.conf, because experimental in the lua-resty-acme plugin. #location /.well-known/acme-challenge { # content_by_lua_block { # ngx.log(ngx.INFO, "Replying to ACME TLS-ALPN-01 challenge") # -- Cannot access here?: # -- ", server name: " .. ngx.var.server_name .. " [TyNACMEALPN01]") # require("resty.acme.autossl").serve_tls_alpn_challenge() # } #} include /etc/nginx/server-ssl.conf; include /etc/nginx/server-limits.conf; include /etc/nginx/server-locations.conf; } ## HTTPS Server with custom (e.g. wildcard) HTTPS cert ## ---------------------------------------------------- ## Redirect port 80 to 443: (without generating any LetsEncrypt cert, not needed) ## #server { # listen 80; # backlog=8192; # [BACKLGSZ] # server_name ~^(.*)\.example\.com$; # return 302 https://$http_host$request_uri; #} ## HTTPS Server with custom cert ## Docs: http://nginx.org/en/docs/http/configuring_https_servers.html ## #server { # ## Comment out 'backlog=...' if you also use a LetsEncrypt auto cert server (above). # ## Nginx won't start if 'backlog=...' is present at two places. # listen 443 ssl; # backlog=8192; # [BACKLGSZ] # #listen [::]:443 ssl; # backlog=8192; # [ipv6_probl] # http2 on; # # server_name ~^(.*)\.example\.com$; # # ssl_certificate /etc/certbot/live/example.com-0001/fullchain.pem; # ssl_certificate_key /etc/certbot/live/example.com-0001/privkey.pem; # # include /etc/nginx/server-ssl.conf; # include /etc/nginx/server-limits.conf; # include /etc/nginx/server-locations.conf; #} ================================================ FILE: debug.yml ================================================ # Opens ports to Docker containers so you can connect and debug. # Only reachable locally (since listens on 127.0.0.1), so, access via an SSH tunnel. services: app: ports: - '127.0.0.1:9000:9000' # Play Framework's HTTP listen port, for bypasssing Nginx (to debug) - '127.0.0.1:9443:9443' # Play Framework's HTTPS port - '127.0.0.1:9999:9999' # Java debugger port - '127.0.0.1:3333:3333' # JMX cache: ports: - '127.0.0.1:6379:6379' rdb: ports: - '127.0.0.1:5432:5432' search: ports: - '127.0.0.1:9200:9200' ================================================ FILE: docker-compose.yml ================================================ # Talkyard, discussion forum software, v1. Production installation on a single server. # # Dockerfiles for the Docker images are in another Git repo: # https://github.com/debiki/talkyard, at: images/(service-name)/Dockerfile # # There's an image build script: https://github.com/debiki/talkyard/blob/main/Makefile, # the `prod-images` and `tag-and-push-latest-images` targets. # Docker Compose file spec: # https://github.com/compose-spec/compose-spec/blob/main/spec.md # You can override this in docker-compose.override.yml. # Volume names get prefixed by the COMPOSE_PROJECT_NAME = 'talkyard-v1_', from .env. volumes: web-generated: pub-files: priv-files: redis-data: pg-data: es-data: es-logs: # See: https://docs.docker.com/compose/how-tos/use-secrets/ secrets: postgres_password: file: ./secrets/postgres_password.txt #backup_password: # file: ./secrets/backup_password.txt # The netw names get prefixed with the COMPOSE_PROJECT_NAME. networks: # Frontend public network. Connects Nginx to the Internet. fe_pub_net: ipam: config: - subnet: ${FE_PUB_NET_SUBNET} # So Nginx can talk to Redis and the app server. fe_int_net: internal: true ipam: config: - subnet: ${FE_INT_NET_SUBNET} # Backend network: the app server and various databases. Is an internal network, # not directly connected to the Internet. # See: https://docs.docker.com/reference/compose-file/networks/#internal be_int_net: internal: true ipam: config: - subnet: ${BE_INT_NET_SUBNET} # So the app server can connect to the egress proxy. eg_int_net: internal: true ipam: config: - subnet: ${EG_INT_NET_SUBNET} # So the egress proxy can access the Internet, e.g. to send webhooks or fetch link previews. eg_pub_net: ipam: config: - subnet: ${EG_PUB_NET_SUBNET} # See: https://github.com/compose-spec/compose-spec/blob/main/spec.md#logging x-logging: &default_logging # This driver rotates logs, so won't run out of disk. driver: local services: web: image: ${DOCKER_REG_ORG}/talkyard-web:${PINNED_VERSION_TAG:-${VERSION_TAG}} # dockerfile: https://github.com/debiki/talkyard/blob/main/images/web/Dockerfile restart: always volumes: # Nginx `server {...}` blocks for your Talkyard forum. - ./conf/web/sites-enabled:/etc/nginx/sites-enabled:ro # A LetsEncrypt ACME account key gets generated on startup and saved here. # Once done, you could make this volume read-only: append ':ro' to the next line. - web-generated:/etc/nginx/generated # Don't, however, mount priv-files here. - pub-files:/var/talkyard/v1/pub-files:ro # Optionally, add a wildcard cert volume here: # talkyard-wildcard-certs:/etc/wildcard-certs:ro # Fetch certs in a Docker container using e.g. LEGO https://github.com/go-acme/lego # that mounts the same volume, run in a cron job, # and edit: ./conf/web/sites-enabled/talkyard-servers.conf # add e.g.: ssl_certificate /etc/wildcard-certs/...something.crt; # and: ssl_certificate_key ... ports: - '80:80' - '443:443' networks: fe_pub_net: ipv4_address: ${FE_PUB_NET_WEB_IP} fe_int_net: ipv4_address: ${FE_INT_NET_WEB_IP} depends_on: - app - cache logging: *default_logging environment: TY_NGX_ERROR_LOG_LEVEL: 'info' # or 'notice' or 'debug' # TY_NGX_ACCESS_LOG_CONFIG: 'tyalogfmt' ## Max uploaded file size, e.g. uploaded images or backups to restore: # TY_NGX_LIMIT_REQ_BODY_SIZE: '25m' ## To let any CDN of yours bypass Ty's Nginx rate limits: # X_PULL_KEY: '...' # CDN_PULL_KEY: '...' security_opt: - no-new-privileges=true # When cap_drop is ALL, it gets processed before cap_add, see: # https://stackoverflow.com/a/63219871 # and: # https://github.com/moby/moby/blob/1c39b1c44c973f18f39bd684c6aba57bb96510fe/oci/caps/utils.go#L120 cap_drop: - ALL cap_add: # Without CHOWN: # nginx: [emerg] chown("/opt/nginx/proxy-cache", 100) failed (1: Operation not permitted) - CHOWN # To bypass file read, write, and execute permission checks: # (DAC means "discretionary access control", and DAC_OVERRIDE) # Without DAC_OVERRIDE: # nginx: [emerg] mkdir() "/var/cache/nginx/client_temp" failed (13: Permission denied) - DAC_OVERRIDE # To change the group and user id of a process: (so Nginx won't need to run as root) - SETGID - SETUID # To bind to lower ports. Maybe listen on 8080 instead, and map port 80:8080? - NET_BIND_SERVICE app: image: ${DOCKER_REG_ORG}/talkyard-app:${PINNED_VERSION_TAG:-${VERSION_TAG}} # dockerfile: https://github.com/debiki/talkyard/blob/main/images/app/Dockerfile.prod restart: always stdin_open: true # otherwise Play Framework exits volumes: - ./conf/app/play-framework.conf:/opt/talkyard/app/conf/app-prod-override.conf:ro # see [4WDKPU2] in github.com/debiki/talkyard - pub-files:/var/talkyard/v1/pub-files - priv-files:/var/talkyard/v1/priv-files # So backups can be downloaded via the admin web interface. But read-only, # so evil bugs cannot destroy all backups. # - /var/opt/backups/talkyard/v1/:/var/opt/backups/talkyard/v1/:ro secrets: - postgres_password networks: fe_int_net: ipv4_address: ${FE_INT_NET_APP_IP} be_int_net: ipv4_address: ${BE_INT_NET_APP_IP} eg_int_net: ipv4_address: ${EG_INT_NET_APP_IP} depends_on: # Start also if other services are unhealthy, so can show admin troubleshooting tips. - rendr - cache - rdb - search - egressp logging: *default_logging environment: # The `TALKYARD_SECURE: '${TALKYARD_SECURE}'` syntax doesn't work — results in # the env vars getting set to an empty string, but Play Framework wants a bool # and refuses to start. - TALKYARD_SECURE # The app always looks here, not configurable. [postgres_pw_path] # POSTGRES_PASSWORD_FILE=/tmp/postgres_password - TALKYARD_HOSTNAME - BECOME_OWNER_EMAIL_ADDRESS # [egressp_conf] - 'http_proxy=http://egressp:4750' - 'https_proxy=http://egressp:4750' - "no_proxy=''" security_opt: - no-new-privileges=true cap_drop: - ALL cap_add: - CHOWN # to make secrets readable to 'appuser' - FOWNER # - DAC_OVERRIDE - SETUID # to switch to 'appuser' - SETGID # 'appgroup' rendr: image: ${DOCKER_REG_ORG}/talkyard-rendr:${PINNED_VERSION_TAG:-${VERSION_TAG}} # dockerfile: https://github.com/debiki/talkyard/blob/main/images/rendr/Dockerfile networks: be_int_net: ipv4_address: ${BE_INT_NET_RENDR_IP} logging: *default_logging security_opt: - no-new-privileges=true cap_drop: - ALL cache: image: ${DOCKER_REG_ORG}/talkyard-cache:${PINNED_VERSION_TAG:-${VERSION_TAG}} # dockerfile: https://github.com/debiki/talkyard/blob/main/images/cache/Dockerfile restart: always volumes: - redis-data:/data networks: fe_int_net: ipv4_address: ${FE_INT_NET_CACHE_IP} logging: *default_logging sysctls: net.core.somaxconn: 511 security_opt: - no-new-privileges=true cap_drop: - ALL cap_add: # [enough_caps]? See: https://github.com/docker-library/postgres/issues/649 - CHOWN # [redis_cap_chown] - SETGID - SETUID - SETPCAP rdb: image: ${DOCKER_REG_ORG}/talkyard-rdb:${PINNED_VERSION_TAG:-${VERSION_TAG}} # dockerfile: https://github.com/debiki/talkyard/blob/main/images/rdb/Dockerfile restart: always secrets: - postgres_password volumes: - pg-data:/var/lib/postgresql networks: be_int_net: ipv4_address: ${BE_INT_NET_RDB_IP} logging: *default_logging environment: POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password security_opt: - no-new-privileges=true cap_drop: - ALL cap_add: # [enough_caps]? See: https://github.com/docker-library/postgres/issues/649 - CHOWN - DAC_READ_SEARCH - DAC_OVERRIDE # to copy Postgres secret - FOWNER - SETGID - SETUID - SYS_NICE search: image: ${DOCKER_REG_ORG}/talkyard-search:${PINNED_VERSION_TAG:-${VERSION_TAG}} # dockerfile: https://github.com/debiki/talkyard/blob/main/images/search/Dockerfile restart: always volumes: - es-data:/usr/share/elasticsearch/data # Deprecation logs aren't written to stdout, but to files in this dir, # auto rotated, max 5G in total. # See: https://www.elastic.co/docs/deploy-manage/monitor/logging-configuration/elasticsearch-deprecation-logs - es-logs:/usr/share/elasticsearch/logs networks: be_int_net: ipv4_address: ${BE_INT_NET_SEARCH_IP} logging: *default_logging environment: bootstrap.memory_lock: 'true' ES_JAVA_OPTS: '-Xms512m -Xmx512m' ulimits: memlock: soft: -1 hard: -1 nofile: soft: 65536 hard: 65536 security_opt: - no-new-privileges=true cap_drop: - ALL cap_add: # [enough_caps]? # SYS_CHROOT? See https://github.com/nasa-jpl/ASSESS/blob/master/docker-compose.yml - SETGID - SETUID # Egress proxy. # Stops Server Side Request Forgery (SSRF). egressp: image: ${DOCKER_REG_ORG}/talkyard-egressp:${PINNED_VERSION_TAG:-${VERSION_TAG}} restart: always networks: eg_int_net: ipv4_address: ${EG_INT_NET_EGRESSP_IP} eg_pub_net: ipv4_address: ${EG_PUB_NET_EGRESSP_IP} logging: *default_logging read_only: true security_opt: - no-new-privileges=true cap_drop: - ALL backup: image: ${DOCKER_REG_ORG}/talkyard-backup:${PINNED_VERSION_TAG:-${VERSION_TAG}} # dockerfile: https://github.com/debiki/talkyard/blob/main/images/backup/Dockerfile profiles: [backup] # prevents auto-start on 'up' # If under heavy load, run the backup script at 25% priority. (Default is 1024 shares.) cpu_shares: 256 # Don't let the backup container use more than 60% CPU (even if the system is idle), # otherwise on a small VM the kernel might panic [100_kernel_panic]. cpu_quota: 60000 cpu_period: 100000 secrets: # Will appear at /run/secrets/postgres_password. [backup_pg_client_secret] - postgres_password #- backup_password volumes: # Here we save the backups. - /var/opt/backups/talkyard/v1:/var/opt/backups/talkyard/v1 # So can backup uploaded files and Redis. - pub-files:/var/talkyard/v1/pub-files:ro - priv-files:/var/talkyard/v1/priv-files:ro - redis-data:/var/talkyard/v1/redis-data:ro # So can backup config files. - .:/opt/talkyard-v1:ro logging: *default_logging depends_on: # So can backup database. - rdb networks: be_int_net: security_opt: - no-new-privileges=true cap_drop: - ALL cap_add: # So 'root' can access Redis' dump.rdb file, which is owned by user 'redis' 999. - CAP_DAC_READ_SEARCH # vim: et ts=2 sw=2 ================================================ FILE: docs/copy-backups-elsewhere.md ================================================ Take regular off-site backups ====================== After you've installed Talkyard, you should regularly copy the backups to an off-site backup server. Here's a way to do that. **Note:** Not yet tested in Talkyard v1, but should work — only the backup archives path has changed (from `/opt/talkyard-backups/archives/` to `/var/opt/backups/talkyard/v1/archives/`). ### Create SSH key On the backup server, preferably located in another datacenter, create a SSH key: ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_remotebackup -C "Automated remote backup" You can perhaps skip the passphrase, since the backup server will have read-only rsync access only, all backups will be available on the backup server anyway. So a passhprase doesn't give any additional security. ### Enable restricted rsync, rrsync On the Talkyard server, enable rrsync: zcat /usr/share/doc/rsync/scripts/rrsync.gz > /usr/local/bin/rrsync chmod ugo+x /usr/local/bin/rrsync ### rsync keys Then create a backup user with an `authorized_keys` file that allows restricted rsync: # (still on the Talkyard server) useradd --create-home remotebackup su - remotebackup mkdir .ssh echo 'command="/usr/local/bin/rrsync -ro /var/opt/backups/talkyard/v1/archives/",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding' >> .ssh/authorized_keys Copy the public key on the backup server: # on the backup server: cat ~/.ssh/id_remotebackup.pub # copy the output Append the public key to the last line in `authorized_keys` on the Talkyard server: # as user remotebackup: (!) nano ~/.ssh/authorized_keys # append a space and then the stuff you just copied to the last line (which is the only line, if the file was just created). # Do not paste it on a new line. The result should be that the `authorized_keys` file looks like: (and it's a really long line) command=..... ssh-rsa AAAA................ Automated remote backup ### Test Now, on the backup server, test copying backups: # replace 'SERVERADDRESS' with your Talkyard server address rsync -e "ssh -i $HOME/.ssh/id_remotebackup" -av remotebackup@SERVERADDRESS:/ $HOME/talkyard-backups/ ### Schedule copying-of-backups If the above test works, then schedule a cron job to copy backups regularly. Do this on the backup server: # again, replace 'SERVERADDRESS' with your Talkyard server address crontab -l | { cat; echo '@hourly rsync -e "ssh -i .ssh/id_remotebackup" -av remotebackup@SERVERADDRESS:/ talkyard-backups/ >> cron.log 2>&1'; } | crontab - Now you'll have fresh backups of your forum in ~/talkyard-backups/, in case the Talkyard server disappears. (Why do we run the rsync client read-only on the backup server? Well, because if we were to let the Talkyard server connect and write to the backup server, then someone who breaks in to the Talkyard server could ransomware-encrypt all backups (that is, encrypt everything and tell you "give me money, only then will I unencrypt your data so you can read it again"). But when the Talkyard server doesn't have access to the backup server, this cannot happen. Note that it should be easier to make the backup server safe, because it doesn't need to run the whole Talkyard tech stack.) ### Get an email, if backups stop working *NOT YET IMPLEMENTED* ` [BADBKPEML]`, the following does not yet work: On the remote backup server, copy the contents of the script [scripts/check-talkyard-backups.sh](../scripts/check-talkyard-backups.sh) to your home directory. Edit the script and fill in email server (SMTP) credentials. Then, test run the script: cd $HOME ./check-talkyard-backups.sh --send-email-if-bad talkyard-backups/ And test send an email: ./check-talkyard-backups.sh --send-test-email If seems to work, run daily via Cron: crontab -l | { cat; echo '@daily ./check-talkyard-backups.sh --send-email-if-bad talkyard-backups/ >> cron.log 2>&1'; } | crontab - ================================================ FILE: docs/how-restore-backup.md ================================================ How restore a Talkyard backup ============================================= **Notice:** Not yet updated for Talkyard v1, for example, steps for decrypting encrypted backups are missing. If your server disappeared, and you want to restore a backup on a new server. Or if you're upgrading from Talkyard epoch v0 to v1. Then you can do as follows. Start installing Talkyard on that new server, following the instructions in https://github.com/debiki/talkyard-prod-one/blob/ty-prod-one-v1/README.md — but stop at step 8: "Edit config values". Instead, we'll copy config files from the backup: On the new server, as root, run the commands below: Replace `BACKUP_ARCHIVES_DIR` and `DB_BACKUP_FILE` etcetera below, with the actual path and file names. ``` sudo -i # become root cd /opt/talkyard-v1 echo "$(date -I): Restoring backup ..." >> talkyard-maint.log # Restore config files and HTTPS certs # ------------------------------ # First, let's "backup" the new conf, in case you'd like to diff old vs default. mkdir -p default-conf/data mv conf docker-compose.* .env default-conf/ mv data/certbot data/sites-enabled-auto-gen default-conf/data/ # Then restore the old config. mkdir old-conf mkdir data tar xf /BACKUP_ARCHIVES_DIR/CONFIG_BACKUP_FILE.tar.gz -C old-conf mv old-conf/.env ./ mv old-conf/docker-compose.* ./ mv old-conf/conf ./conf # Stop any App server # ------------------------------ # This shouldn't be needed — you didn't start the Talkyard server yet? # You stopped at step 8 as mentioned above? # Anyway, if the Talkyard app server is running, stop it: # (Otherwise the restore will fail because of active database connections.) docker compose stop app # Restore the database, PostgreSQL # ------------------------------ # First, start PostgreSQL. docker compose up -d rdb # NOTE: Overwrites any existing database (!). zcat /BACKUP_ARCHIVES_DIR/DB_BACKUP_FILE.sql.gz \ | docker exec -i $(docker compose ps -q rdb) psql postgres postgres \ | tee -a talkyard-maint.log # Restore Redis? # ------------------------------ # Not needed, it's a cache. (Maybe write something about Redis later.) # Restore uploaded files # ------------------------------ # Test if works! [ty_v1] docker compose run --rm \ -v /BACKUP_ARCHIVES_DIR/UPLOADS_BACKUP_DIR.d:/uploads:ro \ app \ rsync -a /uploads/ /var/talkyard/v1/pub-files/uploads/ ``` ### Memory Next, configure memory: Run `free -m` to find out how many megabytes memory your machine has. Look at docker-compose.override.yml to see how much memory Talkyard has been configure to use — and optionally, replace that file with another more suitable one from `./mem/*`, e.g.: `cp mem/4g.yml docker-compose.override.yml`. ### Start Talkyard Now, time to start everything: ``` docker compose up -d docker compose logs -f --tail 999 ``` Also, think about if you need to 1) update your DNS server with the IP address to your new Talkyard server. Or maybe 2) change the hostname of the Talkyard server — you'd then edit Nginx config in `conf/app/play-framework.conf`, and `conf/web/sites-enabled/talkyard-servers.conf`, plus generate a LetsEncrypt cert (see: `https://github.com/debiki/talkyard-prod-one/blob/ty-prod-one-v1/docs/setup-https.md`). ### Backups and automatic upgrades Continue with step ?? in the installation instructions in README.md, https://github.com/debiki/talkyard-prod-one/blob/ty-prod-one-v1/README.md, that is, this step: *"Schedule deletion of old log files, daily backups and deletion old backups, and automatic upgrades"*. Also, look at the *Next steps* just below, in README.md — you'll want to configure off-site backups? ================================================ FILE: docs/multisite-talkyard.adoc ================================================ Multisite Talkyard ====================================================================== One Talkyard server can host many Talkyard sites: different forums and blogs, with different owners and admins. If you've installed Talkyard as usual, for a single site, then, you can enable Multisite Talkyard. Let's say your main Talkyard site is at: `main-talkyard.example.com`. Then, . Add DNS record(s) for the new sites you'll create. Could be a wildcard A or CNAME record: ``` main-talkyard.example.com 3600 IN A 11.22.33.44 *.multi-ty.example.com 3600 IN A main-talkyard.example.com. ``` In `/opt/talkyard/conf/play-framework.conf`, scroll down to the Advanced section; add these settings: ``` talkyard.createSiteHostname="main-talkyard.example.com" talkyard.baseDomain="multi-ty.example.com" ``` where `main-talkyard.example.com` is the address to your already working Talkyard site, the firt site you installed. Now, you can go to: `https://main-talkyard.example.com/-/create-site`, or `https://main-talkyard.example.com/-/create-site/blog-comments`, and create a new Talkyard site. Its address will be: `https://something.multi-ty.example.com`. HTTPS should work automatically — Talkyard and LetsEncrypt generates certs for you. Only the very first time someone (you) accesses a new site, there'll be a connection-not-secure error, and you'll need to wait 10 – 20 seconds and reload the page. Once you've created an additional Talkyard site, you can, if you want to, change its address to something else, by going here: `https://something.multi-ty.example.com/-/admin/settings/site` and clicking *Change address*. ================================================ FILE: docs/release-channels.md ================================================ Release Channels ====================================================================== Later, you can choose between: 1. Getting new features and bug fixes more often — the `regular` release branch. 2. Getting only important bug fixes — the `lts` (Long Term Stable) branch. You choose this by editing `/opt/talkyard-v1/.env` and setting `RELEASE_BRANCH=regular` or `...=lts`. Currently only the `regular` branch exists. It's the default, so you don't need to do anything. (This is inspired by Kubernetes' release channels: https://cloud.google.com/kubernetes-engine/docs/concepts/release-channels) ================================================ FILE: docs/risk-free-upgrades.md ================================================ Risk-Free Upgrades (Blue-Green, Google Cloud) ========================= You can use this method for major OS upgrades or major Talkyard migrations, e.g. v0 to v1. For routine updates, we recommend just following the installation instructions, that is, a daily cron job that calls `scripts/upgrade-if-needed.sh`. **Important:** Read/skim all steps before you start. Especially read the **Make backups work again** and **Rolling back** sections at the end. Prerequisites ------------------------- - These docs are for Google Cloud. (No docs written for AWS or anything else.) - Familiarity with Google Cloud, e.g. how to SSH into a VM. - SSH access to your off-site backup server, if any. - Know how to edit your hosts file (on your laptop). - Know how to open your browser's Dev Tools and switch to the Network tab to check IP addresses. Instructions ------------------------- 1. **Before** 1. **Enable Maintenance Mode** which also makes the server read-only. SSH into your server, and: ``` cd /opt/talkyard sudo docker-compose exec rdb psql talkyard talkyard -c \ 'update system_settings_t set maintenance_until_unix_secs_c = 1;' ``` Now the forum should show an Under Maintenance message: (screenshot) 1. **Clone the server** 1. In Google Cloud, go to **Virtual Machines > VM Instances**, and find your VM. 1. Click the three dots **⋮** next to your VM, then click **Create new machine image**. (screenshot) 1. Once created, go to **Machine Images**, find the new image, click **⋮** then click **Create instance**. 1. Launch the VM in same region and zone as the old VM, with the same machine type. 1. **Verify & Upgrade** 1. Let's see if the new VM works. Find the IP of the new VM, and add it to your laptop's `/etc/hosts`: ``` 11.22.33.44 your-forum.example.com ``` 1. In a browser, go to `https://your-forum.example.com` and open Dev Tools. Switch to the Network tab. Verify that you're hitting the IP of the new VM (and not the _old_ VM). 1. **Upgrade.** SSH into the new VM. Upgrade the forum or the OS. 1. Do some manual testing in the browser, including: - Visit: `your-forum.example.com/-/build-info` do you see the new (upgraded) Talkyard version number? - Post a test topic in a hidden category (e.g. staff-only). - Visit: `your-forum.example.com/-/last-errors` — you see any errors? 1. **Switch Traffic** 1. Move your forum's IP address from the old VM to the new VM: Go to **VPC Network > IP addresses**, find the forum's public IP, click **⋮** and **Reassign to another resource**, select the new VM. 1. Remove the `/etc/hosts` entry (on your laptop). Reload web page, look in Dev Tools, the Network tab, and verify you're hitting the public IP address of the forum — which now points to the _new_ VM. 1. **Afterwards** 1. **Disable Maintenance Mode.** On the new VM: ``` cd /opt/talkyard sudo docker-compose exec rdb psql talkyard talkyard -c \ 'update system_settings_t set maintenance_until_unix_secs_c = null;' ``` Reload the web page. Did the maintenance message disappear? 1. **Shut down** the old VM, but don't delete it (wait a month). Reload the web page — still works? 1. **Make backups work again** If you copy backups off-site using rsync (as described in `copy-backups-elsewhere.md`), this now fails with a _"REMOTE HOST IDENTIFICATION HAS CHANGED"_ warning, because Google Cloud will have given the new VM a different SSH host key (even though it's a machine image of the old). Therefore: 1. SSH into the remote backup server. Remove the old key, accept the new: ``` ssh-keygen -R your-forum.example.com # removes old key ssh your-forum.example.com # accepts new. Type 'yes' when prompted ``` 1. Type `crontab -l` to list cron jobs (on the remote backup server). Copy-paste the line that `rsync`s the Talkyard backups, and run it manually, verify works fine. Also, make sure you've configured Google Cloud to backup the new VM regularly (if you want to do that in addition to Talkyard's own backups). ### Rolling back If there's any problem, either don't reassign the IP, or move it back to the old VM. Disable Maintenance Mode on the **old** VM using the SQL command from the _Disable Maintenance Mode_ step above. You can figure out what went wrong on the new VM without any stress. (Unless, of course, something bad happens _after_ you've completely migrated to the new VM. That's why it's good to run some tests before switching over.) Related reading ------------------------- - About machine images: https://cloud.google.com/compute/docs/machine-images#disk-backup - Reassigning external IP addresses: https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address#IP_assign - Reassigning an external IP programatically: - https://cloud.google.com/sdk/gcloud/reference/compute/instances/delete-access-config - https://cloud.google.com/sdk/gcloud/reference/compute/instances/add-access-config ================================================ FILE: docs/troubleshooting.md ================================================ Troubleshooting Talkyard ======================== Installation Problems --------------------- ### Error: talkyard_web_1, Read timed out If, when you run: /scripts/upgrade-if-needed.sh 2>&1 | tee -a talkyard-maint.log you're getting this error: Creating talkyard_web_1 ... ERROR: for talkyard_web_1 UnixHTTPConnectionPool(host='localhost', port=None): Read timed out. (read timeout=...) ERROR: for web UnixHTTPConnectionPool(host='localhost', port=None): Read timed out. (read timeout=...) An HTTP request took too long to complete. Retry with --verbose to obtain debug information. If you encounter this issue regularly because of slow network conditions, consider setting COMPOSE_HTTP_TIMEOUT to a higher value (current value: 240). then the reason can be that the server has too little memory — which apparently can cause Nginx (OpenResty) to run out of memory and crash. Now you might wonder, why would Nginx use that much memory? — I think it's OpenResty (an Nginx distribution) that just-in-time compiles lots of Lua code, and then uses lots of memory. Old: Troubleshooting and debugging ---------------- (Ignore this section; it's not completed and hard to understand.) ? save Java crash dumps in ./play-crash + tips about how to run jmap? or view in jvisualvm + Idea? jmap -heap PID How to connect VisualVM Tips about how to view logs: all logs, app specific logs. How to jump into a Docker container. How to connect a debugger: open Docker port, then connect via SSH tunnel (assuming a firewall blocks the port on the host). If using Google Compute Engine, then ssh tunnel: gcloud compute ssh server-name --ssh-flag=-L9999:127.0.0.1:9999 --ssh-flag=-N How to open console in Chrome, view messages & post to the E.D. help forum. View CPU & memory usage: `./scripts/stats.sh` ================================================ FILE: docs/upgrade-v0-to-v1.md ================================================ Upgrading from Talkyard v0 to v1 ================================ **NOTICE**: This is a **draft**, not yet finished or tested. --- Talkyrad v1 is a major new version of Talkyard — a new epoch. Previous versions have been v0.YYYY.NNN, newer versions will be v1.YYYY.NNN (e.g. v1.2025.001). To upgrade, you'll install Talkyrad v1 side-by-side with v0, backup v0, shut down v0, restore the backup to v1, and start v1. Why upgrade? ------------------------- - Talkyard v1 upgrades all components to more recent versions (upgrades to PostgreSQL 18, ElasticSearch 9 (or 8), Redis 8, Debian 12 or 13). This is good to do, so you'll be using supported versions of the software. - Improvements to the maintenance scripts, e.g. optionally encrypted backups. - We'll start using Docker named volumes, instead of bind mounts. - We'll start using the Linux Filesystem Hierarch Standard, e.g. backups in `/var/opt/backups/talkyard/` instead of `/opt/talkyard-backups/`. - Talkyard v0 will stop getting new features, only bug fixes. How to upgrade ------------------------- ### Preparations Upgrade your Operating System to Debian 12 or 13 (Ubuntu 22 or 24 LTS should work too — they're based on Debian 12 and 13). Upgrade Docker to >= ???. Install Docker Compose v2, if you haven't already: apt-get install docker-compose-plugin (Talkyard v0 uses Docker Compose v1, but Talkyard v1 uses Docker Compose v2.) ### Phase 1: Backup and shut down v0 Make your Talkyard v0 server read-only: ``` cd /opt/talkyard # this is v0 docker-compose exec rdb psql ... ``` Take a backup, let's name it `beforeV1Upgrade`: ``` ./scripts/backup.sh beforeV1Upgrade ``` Now, shut down Talkyrad v0 — but don't delete anything! Just leave it as-is: ``` docker-compose down # (using Docker-Compose v1) ``` #### Verification Open a web browser and check that you the Talkyard site is inaccessible. ### Phase 2: Install and configure v1 Install Talkyard v1, as per the installation instructions in ../README.md . Copy configuration files from your v0 installation to v1 — with one change: ``` # Go to the Talkyard v1 installation dir cd /opt/talkyard-v1 # Back up default config mv conf conf.v1.default # Copy config from v0 to v1: cp -a /opt/talkyard/conf ./ # Move play-framework.conf to 'app/' sub dir: (for consistency with other containers) mkdir conf/app mv conf/play-framework.conf conf/app/play-framework.conf ``` Edit the config files: ``` ... TBD ... ``` #### Verification Start the new Talkyard v1 site: ``` cd /opt/talkyard-v1 docker compose up -d ``` See if you can access it in a browser. It'll be empty, since you haven't restored the database yet. ### Phase 3: Data migration #### Restore database Restore the database backup into Talkyrad v1: (and replace `...beforeV1Upgrade...sql.g` with the backup file name.) ``` cd /opt/talkyard-v1/ # note: v1 docker compose up -d rdb # (this is Docker Compose v2) # NOTE: Overwrites any existing database (!). zcat /opt/talkyard-backups/archives/...beforeV1Upgrade...sql.gz \ | docker exec -i $(docker compose ps -q rdb) psql postgres postgres \ | tee -a talkyard-maint.log ``` #### Copy uploaded files Copy uploaded files from v0 (located at `/opt/talkyard/data/uploads`) into the v1 named volume, using a temporary 'app' container. This container which automatically mounts the volume, as specified in docker-compose.yml. ``` # In /opt/talkyard-v1: docker compose run --rm \ -v /opt/talkyard/data/uploads:/uploads-v0:ro \ app \ rsync -a /uploads-v0/public/ /var/talkyard/v1/pub-files/uploads/ ``` #### Does it work? Start Talkyard v1 with your data restored: ``` # In /opt/talkyard-v1: docker compose up -d docker compose logs -f ``` Open a web browser and see if you can access your Talkyard site again. ### Phase 4: Last steps #### Make the site read-write ``` docker compose exec rdb psql ... ``` #### Reconfigure backups Reconfigure the off-site backup script so it backups `/var/opt/backups/` (instead of `/opt/talkyard-backups/`) — see the end of ../README.md. Wait a month or two. All fine? You can delete old v0. ================================================ FILE: mem/1.7g.yml ================================================ # For servers with 1.7 GB RAM. # E.g. a Google Compute Engine g1-small instance: 1 shared CPU & 1.7 GB mem. # (We don't make use of all RAM here, because ElasticSearch and Postgres wants # fairly much memory in the operating system file system cache, e.g. # ElasticSearch wants as much mem for the OS cache as for its own heap. # Also, there're Nginx and Redis containers too.) services: app: environment: # There's also stack memory and permanent-generation memory. PLAY_HEAP_MEMORY_MB: 256 search: environment: ES_JAVA_OPTS: '-Xms192m -Xmx192m' deploy: resources: limits: memory: 0.9G ================================================ FILE: mem/2g.yml ================================================ # For servers with 2 GB RAM. # (We don't make use of all RAM here, because ElasticSearch and Postgres wants # fairly much memory in the operating system file system cache, e.g. # ElasticSearch wants as much mem for the OS cache as for its own heap. # Also, there're Nginx and Redis containers too.) services: app: environment: # There's also stack memory and permanent-generation memory. PLAY_HEAP_MEMORY_MB: 512 search: environment: ES_JAVA_OPTS: '-Xms320m -Xmx320m' deploy: resources: limits: memory: 1G ================================================ FILE: mem/4g.yml ================================================ # For servers with 4 GB RAM. # (We don't make use of all RAM here, because ElasticSearch and Postgres wants # fairly much memory in the operating system file system cache, e.g. # ElasticSearch wants as much mem for the OS cache as for its own heap. # Also, there're Nginx and Redis containers too.) services: app: environment: # There's also stack memory and permanent-generation memory. PLAY_HEAP_MEMORY_MB: 1024 search: environment: ES_JAVA_OPTS: '-Xms512m -Xmx512m' deploy: # Let's restrict the container to 40% of total mem. resources: limits: memory: 1.6G ================================================ FILE: mem/7.5G.yml ================================================ # For servers with 7.5 GB RAM. # (We don't make use of all RAM here, because ElasticSearch and Postgres wants # fairly much memory in the operating system file system cache, e.g. # ElasticSearch wants as much mem for the OS cache as for its own heap. # Also, there's a Nginx and a Redis container too.) services: app: environment: # There's also stack memory and permanent-generation memory. PLAY_HEAP_MEMORY_MB: 1300 search: environment: ES_JAVA_OPTS: '-Xms1100m -Xmx1100m' deploy: # Let's restrict the container to 40% of total mem. resources: limits: memory: 3G ================================================ FILE: mem/8G.yml ================================================ # For servers with 8 GB RAM. # (We don't make use of all RAM here, because ElasticSearch and Postgres wants # fairly much memory in the operating system file system cache, e.g. # ElasticSearch wants as much mem for the OS cache as for its own heap. # Also, there's a Nginx and a Redis container too.) services: app: environment: # There's also stack memory and permanent-generation memory. PLAY_HEAP_MEMORY_MB: 1800 search: environment: ES_JAVA_OPTS: '-Xms1800m -Xmx1800m' deploy: # Let's restrict the container to 40% of total mem. resources: limits: memory: 3.2G ================================================ FILE: scripts/backup.sh ================================================ #!/bin/bash label="${1:-manual}" exec /usr/bin/docker compose run --rm backup \ /ty/backup.sh "$label" "$(hostname)" "$(date '+%FT%H%MZ' --utc)" ================================================ FILE: scripts/delete-old-backups.sh ================================================ #!/bin/bash exec /usr/bin/docker compose run --rm backup \ /ty/delete-old-backups.sh ================================================ FILE: scripts/find-admin-login-link.sh ================================================ #!/bin/bash # This prints admin one-time login links, and admin reset password links, # generated via: http://ty-server/-/admin-login # or via the Reset Password buttons. # # This is useful, if email hasn't yet been configured. Then one # can login as root, and run this script instead. # # Sync with this: [GETADMLNK] and [RSTPWDLNK]. dc="/usr/bin/docker compose" db_user="$1" if [ -z "$db_user" ]; then db_user="talkyard" fi # Set pager=off otherwise psql prints "More..." and waits for you to # hit Space. psql="psql -P pager=off $db_user $db_user" # Print admin emails, in case one doesn't remember one's admin email — e.g. # after migrating from Talkyard.net to self hosted: admin_addrs=$($dc exec rdb $psql -c " select site_id, primary_email_addr, username, full_name from users3 where is_admin -- Exclude System and Sysbot. and user_id >= 100 order by site_id asc, username asc ") echo echo "First, a tips:" echo "To generate admin login links, go to: https://your-talkyard-server/-/admin-login" echo "and type your admin email." echo echo "Here're the admin email addresses:" echo echo "$admin_addrs" echo # Print admin one time login links. echo "Looking in $db_user's database for admin login link emails" echo "and reset password emails ..." emails=$($dc exec rdb $psql -c " select -- Remove newlines, so can count and grep properly. -- (Need \\ not \, because Bash eats one.) regexp_replace(body_html, '[\\n\\r]+', ' ', 'g' ) || '\n' from emails_out3 where -- This is EmailType.ResetPassword = 22 and OneTimeLoginLink = 23. type in (22, 23) -- One day should be enough? and sent_on > now_utc() - interval '1 day' order by sent_on asc limit 22 ") # Sync with the email generating code [ADMLOGINEML]. name_urls=$(echo "$emails" | \ sed -nr 's#.*>(Hi [^<]+).*(https?://[^"]+).*$# \1 \2#p') if [ -z "$name_urls" ]; then echo echo "Found nothing." else how_many=$(echo "$name_urls" | wc --lines) echo echo "Found $how_many recent admin login or reset password links, most recent last:" echo echo "$name_urls" echo " ^---- this last link is the most recent" echo echo "Copy-paste the links into your browser address bar." echo "Each link works only once." fi echo ================================================ FILE: scripts/impl/check-talkyard-backups.sh ================================================ #!/bin/bash # Will finish this script later. Placed here in impl/ for now, so it won't # distract others who look in scripts/. function log_message { echo "`date --iso-8601=seconds --utc` check-backups: $1" } if [ $# -ne 2 ]; then echo "Usage: $0 --send-email-if-bad BACKUP_DIR" echo "Or: $0 --send-test-email" exit 1 fi backup_dir="$2" echo "Not yet implemented. Bye. [BADBKPEML]" exit # echo "Checking daily backups in $2:" # Find the most recent Postgres backup. # if [ older than two days — check both date in file name, and unix ctime? ] # then # problems=" ....." # fi # Get the backup's random value. # We can just look at the textual contents of the backup, to find out if they're # most likely ok — no need to restore the database into a real PostgreSQL # server. It'd be nice to do this too, optionally, though. # # good_row=$(zcat | grep "$random_value" | grep "$hostname" | grep 'postgres.sql') # if [ -z "$good_row" ] # then # problems=" ....." # fi # Have look in the uploads dir. # There should be this file: # touch $backup_test_dir/$when--$(hostname)--$random_value # if [ no such file ] # then # problems="$problems\n ... more problems....." # fi # if [ uploads backup is older than two days ] # then # problems=" ....." # fi # if [ "$problems" ] # then # Send an email with the "$problems". [BADBKPEML] # Need SMTP server addr, username, pwd, send-to address. # # Websearch for "how send email from linux server" to find out how. # # fi ================================================ FILE: scripts/impl/docker-compose.wrong-app-ip.yml ================================================ # Changes the 'app' container IP to the wrong IP [maint_app_ip], so 'web' # cannot connect to it. This makes 'web' respond quickly with a status 502, # which is good when in maintenance mode where we want to show a # "We're upgrading the server" message — instead of 'web' being able to connect # to 'app'; then it won't respond to the end user until half a minute (?) # has elapsed. services: web: extra_hosts: # Try connecting to a non-existing IP on a subnet we *can* access. # Because if 'web' tries to connect to something outside the subnet, seems # it justs "hang", maybe the connection would eventually timeout? # # 'web' is on the fe_int_net subnet: FE_INT_NET_SUBNET = 172.26.2.0/24 # and the correct app IP is: FE_INT_NET_APP_IP = 172.26.2.41 # see ../.env. # app: 172.26.2.127 # intentionally wrong ================================================ FILE: scripts/impl/recreate-web.sh ================================================ #/bin/bash # This makes 'web' container config changes in docker-compose.yml take effect # (need to recreate the container). docker compose kill web docker compose rm -f web docker compose up -d web ================================================ FILE: scripts/impl/unjson.sh ================================================ #!/bin/bash # Makes json log messages human readable, by parsing the json and pretty-printing # the app specific interesting fields. if [ -z `which jq` ]; then echo "Please install 'jq' for Json pretty print, e.g.: sudo apt install jq" exit 1 fi # -r preserves backslashes, otherwise '\n' gets converted to just 'n', and we can no longer # pretty-print e.g. stacktraces with newlines. while read -r line do # Remove Docker's color codes. line=$(echo "$line" | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g") # We'll add our own colors, here're some codes: # (from http://misc.flogisoft.com/bash/tip_colors_and_formatting) # \e[34m = blue # \e[95m = light magenta # \e[32m = green # \e[33m = yellow # \e[90m = dark gray # \e[92m = light green # \e[39m = default if [[ "$line" =~ ^web_ ]] ; then echo -e "`echo "$line" | sed -r 's/^([^|]+\|)(.*)$/\\\\e[34m\1\\\\e[39m\2/'`" elif [[ "$line" =~ ^rdb_ ]] ; then echo -e "`echo "$line" | sed -r 's/^([^|]+\|)(.*)$/\\\\e[95m\1\\\\e[39m\2/'`" elif [[ "$line" =~ ^cache_ ]] ; then echo -e "`echo "$line" | sed -r 's/^([^|]+\|)(.*)$/\\\\e[32m\1\\\\e[39m\2/'`" elif [[ "$line" =~ ^search_ ]] ; then echo -e "`echo "$line" | sed -r 's/^([^|]+\|)(.*)$/\\\\e[33m\1\\\\e[39m\2/'`" elif [[ "$line" =~ ^gulp_ ]] ; then echo -e "`echo "$line" | sed -r 's/^([^|]+\|)(.*)$/\\\\e[90m\1\\\\e[39m\2/'`" elif [[ "$line" =~ ^app_ ]] ; then # The program 'jq' extracts the timestamp, severity, message etc fields from a json log message. # The -j flag removes surrounding quotes. json=$( egrep -o '\{".*\}' <<< "$line" ) if [ -n "$json" ]; then pretty_json=$(echo "$json" | jq -j '.severity, " ", .message, " kvs: ", .kvs' ) app="$(echo "$line" | sed -r 's/^([^|]+\|)(.*)$/\1/')" echo -e "\e[92m$app\e[39m $pretty_json" else # In dev mode, when compiling & reloading, Play Framework logs non-json messages. echo -e "`echo "$line" | sed -r 's/^([^|]+\|)(.*)$/\\\\e[92m\1\\\\e[39m\2/'`" fi else echo "$line" fi done ================================================ FILE: scripts/old/Vagrantfile ================================================ # -*- mode: ruby -*- # vi: set ft=ruby : #=========== # What is this? This file tells a program named Vagrant how a # Virtual Machine (VM) can be created and configured on your computer, # in which you can test install Talkyard. # # *** # MIGHT NO LONGER WORK? Was last tested with Ubuntu 18.04, but now we're # at Ubuntu 22.04 — "ubuntu/jammy64" below. Don't know if today's Vagrant # is compatible with this 4 years old file? # *** # # Tips: You'll need to add talkyard.port=8080 to play-framework.conf # (mentioned in README.md). # # Go and read about Vagrant here: https://www.vagrantup.com/ — click # "Getting Started" and read that page. Download and install Vagrant. # Also install VirtualBox (or some othervirtualization system), so that # there'll be something that can run the VM Vagrant will download for you. # Then: # # 1) Create an empty folder named `talkyard-prod-test`. Copy this file into it. # 2) cd into that folder, and type `vagrant up` # 3) Wait for Vagrant do download stuff, and the VM to start. # 4) Then type `vagrant ssh`, to open a shelll inside the VM. # 5) Follow the instructions in README.md (but now you have a server already, # i.e. the Vagrant VM which you are inside right now). # Note that, when editing /opt/talkyard/conf/play-framework.conf, you'll need to # comment in: talkyard.port=8080 # and set: talkyard.secure=false # since we'll access Talkyard via http://localhost:8080, no https cert. # 6) To stop the VM, type CTRL-D to exit Vagrant, then type `vagrant halt`. # # Advanced tips: To ssh into the VM and also expose a port on the host inside the VM, # do e.g.: vagrant ssh -- -R 5000:localhost:5000 # This is useful if you have a local test Docker registry running at localhost:5000 # on the host, and want to make it available in the VM also at localhost:5000. #=========== # All Vagrant configuration is done below. The "2" in Vagrant.configure # configures the configuration version (we support older styles for # backwards compatibility). Please don't change it unless you know what # you're doing. Vagrant.configure(2) do |config| # The most common configuration options are documented and commented below. # For a complete reference, please see the online documentation at # https://docs.vagrantup.com. # Every Vagrant development environment requires a box. You can search for # boxes at https://atlas.hashicorp.com/search. config.vm.box = "ubuntu/jammy64" # Disable automatic box update checking. If you disable this, then # boxes will only be checked for updates when the user runs # `vagrant box outdated`. This is not recommended. # config.vm.box_check_update = false # Create a forwarded port mapping which allows access to a specific port # within the machine from a port on the host machine. In the example below, # accessing "localhost:8080" will access port 80 on the guest machine. config.vm.network "forwarded_port", guest: 80, host: 8080 # Create a private network, which allows host-only access to the machine # using a specific IP. # config.vm.network "private_network", ip: "192.168.33.10" # Create a public network, which generally matched to bridged network. # Bridged networks make the machine appear as another physical device on # your network. # config.vm.network "public_network" # Share an additional folder to the guest VM. The first argument is # the path on the host to the actual folder. The second argument is # the path on the guest to mount the folder. And the optional third # argument is a set of non-required options. # config.vm.synced_folder "../data", "/vagrant_data" # Provider-specific configuration so you can fine-tune various # backing providers for Vagrant. These expose provider-specific options. # Example for VirtualBox: # config.vm.provider "virtualbox" do |vb| vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"] # # Display the VirtualBox GUI when booting the machine # vb.gui = true # # # Customize the amount of memory on the VM: vb.memory = "3000" end # # View the documentation for the provider you are using for more # information on available options. # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies # such as FTP and Heroku are also available. See the documentation at # https://docs.vagrantup.com/v2/push/atlas.html for more information. # config.push.define "atlas" do |push| # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME" # end # Enable provisioning with a shell script. Additional provisioners such as # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the # documentation for more information about their specific syntax and use. # config.vm.provision "shell", inline: <<-SHELL # sudo apt-get update # sudo apt-get install -y apache2 # SHELL end ================================================ FILE: scripts/prepare-os.sh ================================================ #!/bin/bash # This script makes ElasticSearch work, simplifies troubleshooting, # and configures automatic security updates, with reboots. function log_message { echo "`date --iso-8601=seconds --utc` prepare-os: $1" } echo echo log_message 'Configuring this Operating System:' did_what='' # Avoid harmless "warning: Setting locale failed" warnings from Perl: # (https://askubuntu.com/questions/162391/how-do-i-fix-my-locale-issue) locale-gen 'en_US.UTF-8' if ! grep -q 'LC_ALL=' /etc/default/locale; then echo 'Setting LC_ALL to en_US.UTF-8...' echo 'LC_ALL=en_US.UTF-8' >> /etc/default/locale export LC_ALL='en_US.UTF-8' did_what="Configured LC_ALL=en_US.UTF-8." fi if ! grep -q 'LANG=' /etc/default/locale; then echo 'Setting LANG to en_US.UTF-8...' echo 'LANG=en_US.UTF-8' >> /etc/default/locale export LANG='en_US.UTF-8' did_what="$did_what Configured LANG=en_US.UTF-8." fi # Append system config settings, so the ElasticSearch Docker container will work, # and so Nginx can handle more connections. [BACKLGSZ] if ! grep -q 'Talkyard' /etc/sysctl.conf; then log_message 'Amending the /etc/sysctl.conf config...' cat <<-EOF >> /etc/sysctl.conf ################################################################### # Talkyard settings # # Turn off swap, default = 60. vm.swappiness=0 # Up the max backlog queue size (num connections per port), default = 128. # Sync with conf/web/sites-enabled/talkyard-servers.conf. net.core.somaxconn=8192 # ElasticSearch wants this, default = 65530 # See: https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html vm.max_map_count=262144 EOF log_message 'Reloading the system config...' sysctl --system did_what="$did_what Added Talkyard settings to /etc/sysctl.conf." else log_message 'Talkyard settings found in /etc/sysctl.conf, leaving as is.' fi # Make Redis happier: # Redis doesn't want Transparent Huge Pages (THP) enabled, because that creates # latency and memory usage issues with Redis. Disable THP now directly, and also # after restart: (as recommended by Redis) if ! grep -q '\[always\]' /sys/kernel/mm/transparent_hugepage/enabled ; then echo "Transparent Huge Pages is [madvise] or [never], fine, Redis happy." else echo "Setting Transparent Huge Pages to [madvise], Redis wants this ..." echo madvise > /sys/kernel/mm/transparent_hugepage/enabled # We can use rc.local — also with Systemd, see: https://askubuntu.com/a/919598. rc_local_f="/etc/rc.local" if [ ! -f $rc_local_f ]; then echo "exit 0" >> $rc_local_f fi if ! grep -q 'transparent_hugepage/enabled' $rc_local_f ; then echo "Setting Transparent Huge Pages to [madvise] after reboot, in $rc_local_f..." # Insert ('i') before the last line ('$') in rc.local, which always? is # 'exit 0' in a new Ubuntu installation ... no, Debian now. sed -i -e '$i # For Talkyard and the Redis Docker container:\necho madvise > /sys/kernel/mm/transparent_hugepage/enabled\n' $rc_local_f fi did_what="$did_what Set Transparent Huge Pages to [madvise]." fi # Simplify troubleshooting: if ! grep -q 'HISTTIMEFORMAT' ~/.bashrc; then log_message 'Adding history settings to .bashrc...' cat <<-EOF >> ~/.bashrc ################################################################### export HISTCONTROL=ignoredups export HISTCONTROL=ignoreboth export HISTSIZE=10100 export HISTFILESIZE=10100 export HISTTIMEFORMAT='%F %T %z ' EOF did_what="$did_what Added HIST* settings to .bashrc." else log_message 'Probably sensible settings found in .bashrc, leaving as is.' fi # [ty_v1] Auto upgr: Ask if, and recommend that, auto reboot if needed after security # upgrades, and if Yes, then, add Automatic-Reboot also if there's already # a 20auto-upgrades file. Seems such a file exists by default, nowadays, Debian 12. # Automatically apply OS security patches. # The --force-confdef/old tells Apt to not overwrite any existing configuration, and to ask no questions. # See e.g.: https://askubuntu.com/a/104912/48382. # APT::Periodic::AutoremoveInterval "14"; = remove auto-installed dependencies that are no longer needed. # APT::Periodic::AutocleanInterval "14"; = remove downloaded installation archives that are nowadays out-of-date. # APT::Periodic::MinAge "8" = packages won't be deleted until they're these many days old (default is 2). # more docs: less /usr/lib/apt/apt.systemd.daily auto_upgr_f="/etc/apt/apt.conf.d/20auto-upgrades" if [ -f $auto_upgr_f ]; then log_message "There's already an auto upgrades config file: $auto_upgr_f." log_message "I'll leave it as is — I won't (re)configure automatic upgrades." log_message "---- It's contents: ------" cat $auto_upgr_f log_message "--------------------------" log_message "Consider adding the below line, if it's missing," log_message "so your server will reboot if needed, for upgrades to take effect:" echo echo 'Unattended-Upgrade::Automatic-Reboot "true";' echo else log_message 'Enabling automatic security updates and reboots...' did_what="$did_what Enabled automatic security updates and reboots." # About the packages we install: # apt-config-auto-update: Makes APT automatically update its package cache. # unattended-upgrades: Downloads and installs security upgrades automatically and unattended. DEBIAN_FRONTEND=noninteractive \ apt-get install -y \ -o Dpkg::Options::="--force-confdef" \ -o Dpkg::Options::="--force-confold" \ unattended-upgrades \ apt-config-auto-update cat <