多租户更改

This commit is contained in:
richjohnrambo
2026-08-11 15:59:57 +08:00
parent 6c800de67d
commit c2076721fc
453 changed files with 102349 additions and 47 deletions
+265
View File
@@ -0,0 +1,265 @@
# Using Docker to run Tinode
All images are available at https://hub.docker.com/r/tinode/
1. [Install Docker](https://docs.docker.com/install/) 1.8 or above. The provided dockerfiles are dependent on [Docker networking](https://docs.docker.com/network/) which may not work with the older Docker.
2. Create a bridge network. It's used to connect Tinode container with the database container.
```
$ docker network create tinode-net
```
3. Decide which database backend you want to use: MySQL, PostgreSQL, MongoDB or RethinkDB. Run the selected database container, attaching it to `tinode-net` network:
1. **MySQL**: If you've decided to use MySQL backend, run the official MySQL Docker container:
```
$ docker run --name mysql --network tinode-net --restart always --env MYSQL_ALLOW_EMPTY_PASSWORD=yes -d mysql:5.7
```
See [instructions](https://hub.docker.com/_/mysql/) for more options. MySQL 5.7 or above is required.
2. **PostgreSQL**: If you've decided to use PostgreSQL backend, run the official PostgreSQL Docker container:
```
$ docker run --name postgres --network tinode-net --restart always --env POSTGRES_PASSWORD=postgres -d postgres:13
```
See [instructions](https://hub.docker.com/_/postgres/) for more options. PostgresSQL 13 or above is required.
The name `rethinkdb`, `mysql`, `mongodb` or `postgres` in the `--name` assignment is important. It's used by other containers as a database's host name.
3. **MongoDB**: If you've decided to use MongoDB backend, run the official MongoDB Docker container and initialise it as single node replica set (you can change "rs0" if you wish):
```
$ docker run --name mongodb --network tinode-net --restart always -d mongo:latest --replSet "rs0"
$ docker exec -it mongodb mongosh
# And inside mongo shell:
> rs.initiate( {"_id": "rs0", "members": [ {"_id": 0, "host": "mongodb:27017"} ]} )
> quit()
```
See [instructions](https://hub.docker.com/_/mongo/) for more options. MongoDB 4.2 or above is required.
4. **RethinkDB**: If you've decided to use RethinkDB backend, run the official RethinkDB Docker container:
```
$ docker run --name rethinkdb --network tinode-net --restart always -d rethinkdb:2.3
```
See [instructions](https://hub.docker.com/_/rethinkdb/) for more options.
4. Run the Tinode container for the appropriate database:
1. **MySQL**:
```
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net tinode/tinode-mysql:latest
```
2. **PostgreSQL**:
```
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net tinode/tinode-postgres:latest
```
3. **MongoDB**:
```
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net tinode/tinode-mongodb:latest
```
4. **RethinkDB**:
```
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net tinode/tinode-rethinkdb:latest
```
You can also run Tinode with the `tinode/tinode` image (which has all of the above DB adapters compiled in). You will need to specify the database adapter via `STORE_USE_ADAPTER` environment variable. E.g. for `mysql`, the command line will look like
```
$ docker run -p 6060:6060 -d -e STORE_USE_ADAPTER mysql --name tinode-srv --network tinode-net tinode/tinode:latest
```
See [below](#supported-environment-variables) for more options.
The port mapping `-p 5678:1234` tells Docker to map container's port 1234 to host's port 5678 making server accessible at http://localhost:5678/. The container will initialize the database with test data on the first run.
You may replace `:latest` with a different tag. See all all available tags here:
* [MySQL tags](https://hub.docker.com/r/tinode/tinode-mysql/tags/)
* [PostgreSQL tags](https://hub.docker.com/r/tinode/tinode-postgresql/tags/) (beta version)
* [MongoDB tags](https://hub.docker.com/r/tinode/tinode-mongodb/tags/)
* [RethinkDB tags](https://hub.docker.com/r/tinode/tinode-rethink/tags/)
* [All bundle tags](https://hub.docker.com/r/tinode/tinode/tags/)
5. Test the installation by pointing your browser to [http://localhost:6060/](http://localhost:6060/).
## Optional
### External config file
The container comes with a built-in config file which can be customized with values from the environment variables (see [Supported environment variables](#supported_environment_variables) below). If changes are extensive it may be more convenient to replace the built-in config file with a custom one. In that case map the config file located on your host (e.g. `/users/jdoe/new_tinode.conf`) to container (e.g. `/tinode.conf`) using [Docker volumes](https://docs.docker.com/storage/volumes/) `--volume /users/jdoe/new_tinode.conf:/tinode.conf` then instruct the container to use the new config `--env EXT_CONFIG=/tinode.conf`:
```
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net \
--volume /users/jdoe/new_tinode.conf:/tinode.conf \
--env EXT_CONFIG=/tinode.conf \
tinode/tinode-mysql:latest
```
When `EXT_CONFIG` is set, most other environment variables are ignored. Consult [the table](#supported-environment-variables) below for a full list.
### Resetting or upgrading the database
The database schema may change from time to time. An error `Invalid database version 101. Expected 103` means the schema has changed and needs to be updated, in this case from version 101 to version 103. You need to either reset or upgrade the database to continue:
Shut down the Tinode container and remove it:
```
$ docker stop tinode-srv && docker rm tinode-srv
```
then repeat step 4 adding `--env RESET_DB=true` to reset or `--env UPGRADE_DB=true` to upgrade.
Also, the database is automatically created if missing.
### Enable push notifications
Tinode uses Google Firebase Cloud Messaging (FCM) to send pushes.
Follow [instructions](../docs/faq.md#q-how-to-setup-fcm-push-notifications) for obtaining the required FCM credentials.
* Download and save the [FCM service account credentials](https://cloud.google.com/docs/authentication/production) file.
* Obtain values for `apiKey`, `messagingSenderId`, `projectId`, `appId`, `messagingVapidKey`.
Assuming your Firebase credentials file is named `myproject-1234-firebase-adminsdk-abc12-abcdef012345.json`
and it's saved at `/Users/jdoe/`, web API key is `AIRaNdOmX4ULR-X6ranDomzZ2bHdRanDomq2tbQ`, Sender ID `141421356237`,
Project ID `myproject-1234`, App ID `1:141421356237:web:abc7de1234fab56cd78abc`, VAPID key (a.k.a. "Web Push certificates")
is `83_Or_So_Random_Looking_Characters`, start the container with the following parameters (using MySQL container as an example):
```
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net \
-v /Users/jdoe:/config \
--env FCM_CRED_FILE=/config/myproject-1234-firebase-adminsdk-abc12-abcdef012345.json \
--env FCM_API_KEY=AIRaNdOmX4ULR-X6ranDomzZ2bHdRanDomq2tbQ \
--env FCM_APP_ID=1:141421356237:web:abc7de1234fab56cd78abc \
--env FCM_PROJECT_ID=myproject-1234 \
--env FCM_SENDER_ID=141421356237 \
--env FCM_VAPID_KEY=83_Or_So_Random_Looking_Characters \
tinode/tinode-mysql:latest
```
### Configure video calling
Tinode uses [WebRTC](https://webrtc.org/) for video and audio calls. WebRTC needs [Interactive Communication Establishment (ICE)](https://tools.ietf.org/id/draft-ietf-ice-rfc5245bis-13.html) [TURN(S)](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT) and/or [STUN](https://en.wikipedia.org/wiki/STUN) servers to traverse [NAT](https://en.wikipedia.org/wiki/Network_address_translation), otherwise calls may not work. Tinode does not include TURN(S) or STUN out of the box. You need to obtain and configure your own service. Once you setup your TURN(S) and/or STUN service, save its configuration to a file, for example `/Users/jdoe/turn-config.json` and provide path to this file when starting the container:
```
$ docker run -p 6060:6060 -d --name tinode-srv --network tinode-net \
-v /Users/jdoe:/config \
--env ICE_SERVERS_FILE=/config/turn-config.json \
< ... other config parameters ... >
tinode/tinode-mysql:latest
```
The config file uses the following format:
```json
[
{
"urls": [
"stun:stun.example.com"
]
},
{
"username": "user-name-to-use-for-authentication-with-the-server",
"credential": "your-password",
"urls": [
"turn:turn.example.com:80?transport=udp",
"turn:turn.example.com:3478?transport=tcp",
"turns:turn.example.com:443?transport=tcp",
]
}
]
```
[XIRSYS](https://xirsys.com/) offers a free tier for developers. We are in no way affiliated with XIRSYS. We do not endorse or otherwise take any responsibility for your use of their services.
### Run the chatbot
See [instructions](../chatbot/python/).
The chatbot password is generated only when the database is initialized or reset. It's saved to `/botdata` directory in the container. If you want to keep the data available between container changes, such as image upgrades, make sure the `/botdata` is a mounted volume (i.e. you always launch the container with `--volume botdata:/botdata` option).
## Supported environment variables
You can specify the following environment variables when issuing `docker run` command:
| Variable | Type | Default | Purpose |
| --- | --- | --- | --- |
| `ACC_GC_ENABLED`[^2] | bool | `false` | Enable/diable automatic deletion of unfinished account registrations. |
| `AUTH_TOKEN_KEY`[^2] | string | `wfaY2RgF2S1OQI/ZlK+LS​rp1KB2jwAdGAIHQ7JZn+Kc=` | base64-encoded 32 random bytes used as salt for authentication tokens. |
| `AWS_ACCESS_KEY_ID`[^2] | string | | AWS Access Key ID when using `s3` media handler. |
| `AWS_CORS_ORIGINS`[^2] | string | `["*"]` | Allowed origins ([CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin)) URL for downloads. Generally use your server URL and its aliases. |
| `AWS_REGION`[^2] | string | | AWS Region when using `s3` media handler |
| `AWS_S3_BUCKET`[^2] | string | | Name of the AWS S3 bucket when using `s3` media handler. |
| `AWS_S3_ENDPOINT`[^2] | string | | An endpoint URL (hostname only or fully qualified URI) to override the default endpoint; can be of any S3-compatible service, such as `minio-api.x.io` |
| `AWS_SECRET_ACCESS_KEY`[^2] | string | | AWS [Secret Access Key](https://aws.amazon.com/blogs/security/wheres-my-secret-access-key/) when using `s3` media handler. |
| `CLUSTER_SELF` | string | | Node name if the server is running in a Tinode cluster. |
| `DEBUG_EMAIL_VERIFICATION_CODE`[^2] | string | | Enable dummy email verification code, e.g. `123456`. Disabled by default (empty string). |
| `DEFAULT_COUNTRY_CODE`[^2] | string | `US` | 2-letter country code to assign to sessions by default when the country isn't specified by the client explicitly and it's impossible to infer it. |
| `EXT_CONFIG`[^1] | string | | Path to external config file to use instead of the built-in one. If this parameter is used, most other variables are ignored[^1]. |
| `EXT_STATIC_DIR` | string | | Path to external directory containing static data (e.g. Tinode Webapp files). |
| `FCM_CRED_FILE`[^2] | string | | Path to JSON file with FCM server-side service account credentials which will be used to send push notifications. |
| `FCM_API_KEY` | string | | Firebase API key; required for receiving push notifications in the web client. |
| `FCM_APP_ID` | string | | Firebase web app ID; required for receiving push notifications in the web client. |
| `FCM_PROJECT_ID` | string | | Firebase project ID; required for receiving push notifications in the web client. |
| `FCM_SENDER_ID` | string | | Firebase FCM sender ID; required for receiving push notifications in the web client. |
| `FCM_VAPID_KEY` | string | | Also called 'Web Client certificate' in the FCM console; required by the web client to receive push notifications. |
| `FCM_INCLUDE_ANDROID_NOTIFICATION`[^2] | boolean | true | If true, pushes a data + notification message, otherwise a data-only message. [More info](https://firebase.google.com/docs/cloud-messaging/concept-options). |
| `FCM_MEASUREMENT_ID` | string | | Google Analytics ID of the form `G-123ABCD789`. |
| `FS_CORS_ORIGINS`[^2] | string | `["*"]` | Cors origins when media is served from the file system. See `AWS_CORS_ORIGINS` for details. |
| `ICE_SERVERS_FILE`[^2] | string | | Path to JSON file with configuration of ICE servers to be used for video calls. |
| `MEDIA_HANDLER`[^2] | string | `fs` | Handler of large files, either `fs` or `s3`. |
| `MYSQL_DSN`[^2] | string | <code>'root@tcp(mysql)/tinode?&#8203;parseTime=true&#8203;&collation=utf8mb4_0900_ai_ci'</code> | MySQL [DSN](https://github.com/go-sql-driver/mysql#dsn-data-source-name). |
| `PLUGIN_PYTHON_CHAT_BOT_ENABLED`[^2] | bool | `false` | Enable calling into the plugin provided by Python chatbot. |
| `POSTGRES_DSN`[^2] | string | <code>'postgresql://postgres:postgres@&#8203;localhost:5432/tinode?&#8203;sslmode=disable&#8203;&connect_timeout=10'</code> | PostgreSQL [DSN](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING). |
| `RESET_DB` | bool | `false` | Drop and recreate the database. |
| `SAMPLE_DATA` | string | _see comment →_ | File with sample data to load. Default `data.json` when resetting or generating new DB, none when upgrading. Use `` (empty string) to disable. |
| `SMTP_AUTH_MECHANISM`[^2] | string | `"plain"` | SMTP authentication mechanism to use; one of "login", "cram-md5", "plain". |
| `SMTP_DOMAINS`[^2] | string | | White list of email domains; when non-empty, accept registrations with emails from these domains only (email verification). |
| `SMTP_HELO_HOST`[^2] | string | _see comment →_ | FQDN to use in SMTP HELO/EHLO command; if missing, the hostname from `SMTP_HOST_URL` is used. |
| `SMTP_HOST_URL`[^2] | string | `'http://localhost:6060/'` | URL of the host where the webapp is running (email verification). |
| `SMTP_LOGIN`[^2] | string | | Optional login to use for authentication with the SMTP server (email verification). |
| `SMTP_PASSWORD`[^2] | string | | Optional password to use for authentication with the SMTP server (email verification). |
| `SMTP_PORT`[^2] | number | | Port number of the SMTP server to use for sending verification emails, e.g. `25` or `587`. |
| `SMTP_SENDER` | string | | [RFC 5322](https://tools.ietf.org/html/rfc5322) email address to use in the `FROM` field of verification emails, e.g. `'"John Doe" <jdoe@example.com>'`. |
| `SMTP_SERVER`[^2] | string | | Name of the SMTP server to use for sending verification emails, e.g. `smtp.gmail.com`. If SMTP_SERVER is not defined, email verification will be disabled. |
| `STORE_USE_ADAPTER`[^2] | string | | DB adapter name (specify with `tinode/tinode` container only). |
| `TEL_HOST_URL`[^2] | string | `'http://localhost:6060/'` | URL of the host where the webapp is running for phone verification. |
| `TEL_SENDER`[^2] | string | | Sender name to pass to SMS sending service. |
| `TLS_CONTACT_ADDRESS`[^2] | string | | Optional email to use as contact for [LetsEncrypt](https://letsencrypt.org/) certificates, e.g. `jdoe@example.com`. |
| `TLS_DOMAIN_NAME`[^2] | string | | If non-empty, enables TLS (http**s**) and configures domain name of your container, e.g. `www.example.com`. In order for TLS to work you have to expose your HTTPS port to the Internet and correctly configure DNS. It WILL FAIL with `localhost` or unroutable IPs. |
| `TNPG_AUTH_TOKEN` | string | | Tinode Push Gateway authentication token. |
| `TNPG_ORG`[^2] | string | | Tinode Push Gateway organization name as registered at https://console.tinode.co |
| `UID_ENCRYPTION_KEY`[^2] | string | `la6YsO+bNX/+XIkOqc5Svw==` | base64-encoded 16 random bytes used as an encryption key for user IDs. |
| `UPGRADE_DB` | bool | `false` | Upgrade database schema, if necessary. |
| `WAIT_FOR` | string | | If non-empty, waits for the specified database `host:port` to be available before starting the server. |
[^1]: If set, variables marked with the footnote `[2]` are ignored.
[^2]: Ignored if `EXT_CONFIG` is set.
A convenient way to generate a desired number of random bytes and base64-encode them on Linux and Mac:
```
$ openssl rand -base64 <desired length>
```
## Metrics Exporter
See [monitoring/exporter/README](../monitoring/exporter/README.md) for information on the Exporter.
Container is also available as a part of the Tinode docker distribution: `tinode/exporter`.
Run it with
```
$ docker run -p 6222:6222 -d --name tinode-exporter --network tinode-net \
--env SERVE_FOR=<prometheus|influxdb> \
--env TINODE_ADDR=<tinode metrics endpoint> \
... <monitoring service specific vars> \
tinode/exporter:latest
```
Available variables:
| Variable | Type | Default | Function |
| --- | --- | --- | --- |
| `SERVE_FOR` | string | `` | Monitoring service: `prometheus` or `influxdb` |
| `TINODE_ADDR` | string | `http://localhost/stats/expvar/` | Tinode metrics path |
| `INFLUXDB_VERSION` | string | `1.7` | InfluxDB version (`1.7` or `2.0`) |
| `INFLUXDB_ORGANIZATION` | string | `org` | InfluxDB organization |
| `INFLUXDB_PUSH_INTERVAL` | int | `60` | Exporter metrics push interval in seconds |
| `INFLUXDB_PUSH_ADDRESS` | string | `https://mon.tinode.co/intake` | InfluxDB backend url |
| `INFLUXDB_AUTH_TOKEN` | string | `` | InfluxDB auth token |
| `PROM_NAMESPACE` | string | `tinode` | Prometheus namespace |
| `PROM_METRICS_PATH` | string | `/metrics` | Exporter webserver path that Prometheus server scrapes |
+39
View File
@@ -0,0 +1,39 @@
# Dockerfile builds an image with a chatbot (Tino) for Tinode.
FROM python:3.13-slim
ARG VERSION=0.25
ARG LOGIN_AS=
ARG TINODE_HOST=tinode-srv:16060
ENV VERSION=$VERSION
ARG BINVERS=$VERSION
LABEL maintainer="Tinode Team <info@tinode.co>"
LABEL name="TinodeChatbot"
LABEL version=$VERSION
RUN mkdir -p /usr/src/bot
WORKDIR /usr/src/bot
# Volume with login cookie. Not created automatically.
# VOLUME /botdata
# Get tarball with the chatbot code and data.
ADD https://github.com/tinode/chat/releases/download/v${BINVERS}/py-chatbot.tar.gz .
# Unpack chatbot, delete archive
RUN tar -xzf py-chatbot.tar.gz \
&& rm py-chatbot.tar.gz
RUN pip install --no-cache-dir -r requirements.txt
# Healthcheck: try to connect to the gRPC port (40051)
HEALTHCHECK --interval=1m --timeout=3s --start-period=15s \
CMD python -c "import socket; s=socket.create_connection(('localhost', 40051), timeout=1); s.close()" || exit 1
# Use docker's command line parameter `-e LOGIN_AS=user:password` to login as someone other than Tino.
CMD ["/bin/sh", "-c", "python chatbot.py --login-basic=${LOGIN_AS} --login-cookie=/botdata/.tn-cookie --host=${TINODE_HOST} >> /var/log/chatbot.log"]
# Plugin port
EXPOSE 40051
+68
View File
@@ -0,0 +1,68 @@
# Docker compose for end-to-end setup.
These reference docker-compose files will run Tinode with the MySql backend either as [a single-instance](single-instance.yml) or [a 3-node cluster](cluster.yml) setup.
```
docker-compose -f <name of the file> [-f <name of override file>] up -d
```
By default, this command starts up a mysql instance, Tinode server(s) and Tinode exporter(s).
Tinode server(s) is(are) configured similar to [Tinode Demo/Sandbox](../../README.md#demosandbox) and
maps its web port to the host's port 6060 (6061, 6062). Tinode exporter(s) serve(s) metrics for InfluxDB.
Reference configuration for the following databases is also available in the override files:
* [PostgreSQL 15.2](https://hub.docker.com/_/postgres/tags)
* [MongoDB 4.2.3](https://hub.docker.com/_/mongo/tags)
* [RethinkDB 2.4.2](https://hub.docker.com/_/rethinkdb/tags)
## Commands
### Full stack
To bring up the full stack, you can use the following commands:
* MySql:
- Single-instance setup: `docker-compose -f single-instance.yml up -d`
- Cluster: `docker-compose -f cluster.yml up -d`
* PostgreSQL:
- Single-instance setup: `docker-compose -f single-instance.yml -f single-instance.postgres.yml up -d`
- Cluster: `docker-compose -f cluster.yml -f cluster.postgres.yml up -d`
* MongoDb:
- Single-instance setup: `docker-compose -f single-instance.yml -f single-instance.mongodb.yml up -d`
- Cluster: `docker-compose -f cluster.yml -f cluster.mongodb.yml up -d`
* RethinkDb:
- Single-instance setup: `docker-compose -f single-instance.yml -f single-instance.rethinkdb.yml up -d`
- Cluster: `docker-compose -f cluster.yml -f cluster.rethinkdb.yml up -d`
You can run individual/separate components of the setup by providing their names to the `docker-compose` command.
E.g. to start the Tinode server in the single-instance MySql setup,
```
docker-compose -f single-instance.yml up -d tinode-0
```
### Database resets and/or version upgrades
To reset the database or upgrade the database version, you can set `RESET_DB` or `UPGRADE_DB` environment variable to true when starting Tinode with docker-compose.
E.g. for upgrading the database in MongoDb cluster setup, use:
```
UPGRADE_DB=true docker-compose -f cluster.yml -f cluster.mongodb.yml up -d tinode-0
```
For resetting the database in RethinkDb single-instance setup, run:
```
RESET_DB=true docker-compose -f single-instance.yml -f single-instance.rethinkdb.yml up -d tinode-0
```
## Troubleshooting
Print out and verify your docker-compose configuration by running:
```
docker-compose -f <name of the file> [-f <name of override file>] config
```
If the Tinode server(s) are failing, you can print the job's stdout/stderr with:
```
docker logs tinode-<instance number>
```
Additionally, you can examine the jobs `tinode.log` file. To download it from the container, run:
```
docker cp tinode-<instance number>:/var/log/tinode.log .
```
+37
View File
@@ -0,0 +1,37 @@
FROM alpine:3.14
ARG VERSION=0.16.4
ENV VERSION=$VERSION
LABEL maintainer="Tinode Team <info@tinode.co>"
LABEL name="TinodeMetricExporter"
LABEL version=$VERSION
ENV SERVE_FOR=""
ENV WAIT_FOR=""
ENV TINODE_ADDR=http://localhost/stats/expvar/
ENV INSTANCE="exporter-instance"
ENV INFLUXDB_VERSION=1.7
ENV INFLUXDB_ORGANIZATION="org"
ENV INFLUXDB_PUSH_INTERVAL=60
ENV INFLUXDB_PUSH_ADDRESS=""
ENV INFLUXDB_AUTH_TOKEN=""
ENV PROM_NAMESPACE="tinode"
ENV PROM_METRICS_PATH="/metrics"
WORKDIR /opt/tinode
RUN apk add --no-cache bash
# Fetch exporter build from Github.
ADD https://github.com/tinode/chat/releases/download/v$VERSION/exporter.linux-amd64 ./exporter
COPY entrypoint.sh .
RUN chmod +x exporter && chmod +x entrypoint.sh
ENTRYPOINT ./entrypoint.sh
EXPOSE 6222
Regular → Executable
+74
View File
@@ -0,0 +1,74 @@
#!/bin/bash
# Check if environment variables (provided as argument list) are set.
function check_vars() {
local varnames=( "$@" )
for varname in "${varnames[@]}"
do
eval value=\$${varname}
if [ -z "$value" ] ; then
echo "$varname env var must be specified."
exit 1
fi
done
}
# Make sure the system uses /etc/hosts when resolving domain names
# (needed for docker-compose's `extra_hosts` param to work correctly).
# See https://github.com/gliderlabs/docker-alpine/issues/367,
# https://github.com/golang/go/issues/35305 for details.
echo "hosts: files dns" > /etc/nsswitch.conf
# Accept http requests at.
LISTEN_AT=":6222"
# Required env vars.
common_vars=( TINODE_ADDR INSTANCE SERVE_FOR )
influx_varnames=( INFLUXDB_VERSION INFLUXDB_ORGANIZATION INFLUXDB_PUSH_INTERVAL \
INFLUXDB_PUSH_ADDRESS INFLUXDB_AUTH_TOKEN )
prometheus_varnames=( PROM_NAMESPACE PROM_METRICS_PATH )
check_vars "${common_vars[@]}"
# Common arguments.
args=("--tinode_addr=${TINODE_ADDR}" "--instance=${INSTANCE}" "--listen_at=${LISTEN_AT}" "--serve_for=${SERVE_FOR}")
# Platform-specific arguments.
case "$SERVE_FOR" in
"prometheus")
check_vars "${prometheus_varnames[@]}"
args+=("--prom_namespace=${PROM_NAMESPACE}" "--prom_metrics_path=${PROM_METRICS_PATH}")
if [ ! -z "$PROM_TIMEOUT" ]; then
args+=("--prom_timeout=${PROM_TIMEOUT}")
fi
;;
"influxdb")
check_vars "${influxdb_varnames[@]}"
args+=("--influx_db_version=${INFLUXDB_VERSION}" \
"--influx_organization=${INFLUXDB_ORGANIZATION}" \
"--influx_push_interval=${INFLUXDB_PUSH_INTERVAL}" \
"--influx_push_addr=${INFLUXDB_PUSH_ADDRESS}" \
"--influx_auth_token=${INFLUXDB_AUTH_TOKEN}")
if [ ! -z "$INFLUXDB_BUCKET" ]; then
args+=("--influx_bucket=${INFLUXDB_BUCKET}")
fi
;;
*)
echo "\$SERVE_FOR must be set to either 'prometheus' or 'influxdb'"
exit 1
;;
esac
# Wait for Tinode server if needed.
if [ ! -z "$WAIT_FOR" ] ; then
IFS=':' read -ra TND <<< "$WAIT_FOR"
if [ ${#TND[@]} -ne 2 ]; then
echo "\$WAIT_FOR (${WAIT_FOR}) env var should be in form HOST:PORT"
exit 1
fi
until nc -z -v -w5 ${TND[0]} ${TND[1]}; do echo "waiting for ${WAIT_FOR}..."; sleep 5; done
fi
./exporter "${args[@]}"
+179
View File
@@ -0,0 +1,179 @@
# Docker file builds an image with a tinode chat server.
#
# In order to run the image you have to link it to a running database container. For example, to
# to use RethinkDB (named 'rethinkdb') and map the port where the tinode server accepts connections:
#
# $ docker run -p 6060:6060 -d --link rethinkdb \
# --env UID_ENCRYPTION_KEY=base64+encoded+16+bytes= \
# --env API_KEY_SALT=base64+encoded+32+bytes \
# --env AUTH_TOKEN_KEY=base64+encoded+32+bytes \
# tinode-server
FROM alpine:3.22
ARG VERSION=0.25
ENV VERSION=$VERSION
ARG BINVERS=$VERSION
LABEL maintainer="Tinode Team <info@tinode.co>"
LABEL name="TinodeChatServer"
LABEL version=$VERSION
# Build-time options.
# Database selector. Builds for MySQL by default.
# Alternatively use one of: postgres mongodb rethinkdb for a corresponsing
# DB backend or alldbs to build a generic Tinode docker image, for example:
# `--build-arg TARGET_DB=postgres` to build for PostgreSQL.
ARG TARGET_DB=mysql
ENV TARGET_DB=$TARGET_DB
# Runtime options.
# Specifies the database host:port pair to wait for before running Tinode.
# Ignored if empty.
ENV WAIT_FOR=
# An option to reset database.
ENV RESET_DB=false
# An option to upgrade database.
ENV UPGRADE_DB=false
# Option to skip DB initialization when it's missing.
ENV NO_DB_INIT=false
# Load sample data to database from data.json.
ARG SAMPLE_DATA=data.json
ENV SAMPLE_DATA=$SAMPLE_DATA
# Default country code to use in communication.
ENV DEFAULT_COUNTRY_CODE=US
# The MySQL DSN connection.
ENV MYSQL_DSN='root@tcp(mysql)/tinode?parseTime=true&collation=utf8mb4_0900_ai_ci'
# The PostgreSQL DSN connection.
ENV POSTGRES_DSN='postgresql://postgres:postgres@localhost:5432/tinode?sslmode=disable&connect_timeout=10'
# Disable chatbot plugin by default.
ENV PLUGIN_PYTHON_CHAT_BOT_ENABLED=false
# Default handler for large files
ENV MEDIA_HANDLER=fs
# Whitelisted domains for file and S3 large media handler.
ENV FS_CORS_ORIGINS='["*"]'
ENV AWS_CORS_ORIGINS='["*"]'
# AWS S3 parameters
ENV AWS_ACCESS_KEY_ID=
ENV AWS_SECRET_ACCESS_KEY=
ENV AWS_REGION=
ENV AWS_S3_BUCKET=
ENV AWS_S3_ENDPOINT=
# Default externally-visible hostname for email verification.
ENV SMTP_HOST_URL='http://localhost:6060'
# Email parameters decalarations.
ENV SMTP_SERVER=
ENV SMTP_PORT=
ENV SMTP_SENDER=
ENV SMTP_LOGIN=
ENV SMTP_PASSWORD=
ENV SMTP_AUTH_MECHANISM=
ENV SMTP_HELO_HOST=
ENV EMAIL_VERIFICATION_REQUIRED=
ENV DEBUG_EMAIL_VERIFICATION_CODE=
# Whitelist of permitted email domains for email verification (empty list means all domains are permitted)
ENV SMTP_DOMAINS=''
# Various encryption and salt keys. Replace with your own in production.
# Salt used to generate the API key. Don't change it unless you also change the
# API key in the webapp & Android.
ENV API_KEY_SALT=T713/rYYgW7g4m3vG6zGRh7+FM1t0T8j13koXScOAj4=
# Key used to sign authentication tokens.
ENV AUTH_TOKEN_KEY=wfaY2RgF2S1OQI/ZlK+LSrp1KB2jwAdGAIHQ7JZn+Kc=
# Key to initialize UID generator
ENV UID_ENCRYPTION_KEY=la6YsO+bNX/+XIkOqc5Svw==
# Disable TLS by default.
ENV TLS_ENABLED=false
ENV TLS_DOMAIN_NAME=
ENV TLS_CONTACT_ADDRESS=
# Disable push notifications by default.
ENV FCM_PUSH_ENABLED=false
# Declare FCM-related vars
ENV FCM_API_KEY=
ENV FCM_APP_ID=
ENV FCM_SENDER_ID=
ENV FCM_PROJECT_ID=
ENV FCM_VAPID_KEY=
ENV FCM_MEASUREMENT_ID=
# Enable Android-specific notifications by default.
ENV FCM_INCLUDE_ANDROID_NOTIFICATION=true
# Disable push notifications via Tinode Push Gateway.
ENV TNPG_PUSH_ENABLED=false
# Tinode Push Gateway authentication token.
ENV TNPG_AUTH_TOKEN=
# Tinode Push Gateway organization name as registered at console.tinode.co
ENV TNPG_ORG=
# Video calls configuration.
ENV WEBRTC_ENABLED=false
ENV ICE_SERVERS_FILE=
# Use the target db by default.
# When TARGET_DB is "alldbs", it is the user's responsibility
# to set STORE_USE_ADAPTER to the desired db adapter correctly.
ENV STORE_USE_ADAPTER=$TARGET_DB
# Url path for exposing the server's internal status. E.g. '/status'
ENV SERVER_STATUS_PATH=''
# Garbage collection of unfinished account registrations.
ENV ACC_GC_ENABLED=false
# Install root certificates, they are needed for email validator to work
# with the TLS SMTP servers like Gmail or Mailjet. Also add bash and grep.
RUN apk update && \
apk add --no-cache ca-certificates bash grep
WORKDIR /opt/tinode
# Copy config template to the container.
COPY config.template .
COPY entrypoint.sh .
# Get the desired Tinode build.
ADD https://github.com/tinode/chat/releases/download/v$BINVERS/tinode-$TARGET_DB.linux-amd64.tar.gz .
# Unpack the Tinode archive.
RUN tar -xzf tinode-$TARGET_DB.linux-amd64.tar.gz \
&& rm tinode-$TARGET_DB.linux-amd64.tar.gz
# Create directory for chatbot data.
RUN mkdir /botdata
# Make scripts runnable
RUN chmod +x entrypoint.sh
RUN chmod +x credentials.sh
# Healthcheck: check if port 6060 is open
HEALTHCHECK --interval=1m --timeout=3s --start-period=30s \
CMD nc -z localhost 6060 || exit 1
# Generate config from template and run the server.
ENTRYPOINT ["./entrypoint.sh"]
# HTTP, gRPC, cluster ports
EXPOSE 6060 16060 12000-12003
+208
View File
@@ -0,0 +1,208 @@
{
"listen": ":6060",
"api_path": "/",
"cache_control": 39600,
"static_mount": "/",
"grpc_listen": ":16060",
"grpc_keepalive_enabled": true,
"api_key_salt": "$API_KEY_SALT",
"max_message_size": 4194304,
"max_subscriber_count": 128,
"max_tag_count": 16,
"expvar": "/stats/expvar/",
"server_status": "$SERVER_STATUS_PATH",
"use_x_forwarded_for": true,
"default_country_code": "$DEFAULT_COUNTRY_CODE",
"media": {
"use_handler": "$MEDIA_HANDLER",
"max_size": 33554432,
"gc_period": 60,
"gc_block_size": 100,
"handlers": {
"fs": {
"upload_dir": "uploads",
"cache_control": "max-age=86400",
"cors_origins": $FS_CORS_ORIGINS
},
"s3":{
"access_key_id": "$AWS_ACCESS_KEY_ID",
"secret_access_key": "$AWS_SECRET_ACCESS_KEY",
"region": "$AWS_REGION",
"bucket": "$AWS_S3_BUCKET",
"endpoint": "$AWS_S3_ENDPOINT",
"presign_ttl": 3600,
"cache_control": "max-age=86400",
"cors_origins": $AWS_CORS_ORIGINS
}
}
},
"tls": {
"enabled": $TLS_ENABLED,
"http_redirect": ":80",
"strict_max_age": 604800,
"autocert": {
"cache": "/etc/letsencrypt/live/$TLS_DOMAIN_NAME",
"email": "$TLS_CONTACT_ADDRESS",
"domains": ["$TLS_DOMAIN_NAME"]
}
},
"auth_config": {
"logical_names": [],
"basic": {
"add_to_tags": true,
"min_login_length": 3,
"min_password_length": 6
},
"token": {
"expire_in": 1209600,
"serial_num": 1,
"key": "$AUTH_TOKEN_KEY"
},
"code": {
"expire_in": 900,
"max_retries": 3,
"code_length": 6
}
},
"store_config": {
"uid_key": "$UID_ENCRYPTION_KEY",
"max_results": 1024,
"use_adapter": "$STORE_USE_ADAPTER",
"adapters": {
"mysql": {
"database": "tinode",
"dsn": "$MYSQL_DSN"
},
"postgres": {
"database": "tinode",
"dsn": "$POSTGRES_DSN"
},
"rethinkdb": {
"database": "tinode",
"addresses": "rethinkdb"
},
"mongodb": {
"database": "tinode",
"addresses": "mongodb",
"replica_set": "rs0"
}
}
},
"acc_validation": {
"email": {
"add_to_tags": true,
"required": [$EMAIL_VERIFICATION_REQUIRED],
"config": {
"host_url": "$SMTP_HOST_URL",
"smtp_server": "$SMTP_SERVER",
"smtp_port": "$SMTP_PORT",
"sender": "$SMTP_SENDER",
"login": "$SMTP_LOGIN",
"sender_password": "$SMTP_PASSWORD",
"auth_mechanism": "$SMTP_AUTH_MECHANISM",
"smtp_helo_host": "$SMTP_HELO_HOST",
"languages": ["en", "es", "fr", "ru", "vi", "zh"],
"validation_templ": "./templ/email-validation-{{.Language}}.templ",
"reset_secret_templ": "./templ/email-password-reset-{{.Language}}.templ",
"max_retries": 3,
"domains": [$SMTP_DOMAINS],
"debug_response": "$DEBUG_EMAIL_VERIFICATION_CODE"
}
},
"tel": {
"add_to_tags": true,
"config": {
"host_url": "$TEL_HOST_URL",
"languages": ["en", "es", "fr", "pt", "ru", "vi", "zh"],
"sender": "$TEL_SENDER",
"universal_templ": "./templ/sms-universal-{{.Language}}.templ",
"max_retries": 3,
"debug_response": "$DEBUG_TEL_VERIFICATION_CODE"
}
}
},
"acc_gc_config": {
"enabled": $ACC_GC_ENABLED,
"gc_period": 3600,
"gc_block_size": 10,
"gc_min_account_age": 48
},
"push": [
{
"name":"tnpg",
"config": {
"enabled": $TNPG_PUSH_ENABLED,
"token": "$TNPG_AUTH_TOKEN",
"org": "$TNPG_ORG"
}
},
{
"name":"fcm",
"config": {
"enabled": $FCM_PUSH_ENABLED,
"project_id": "$FCM_PROJECT_ID",
"credentials_file": "$FCM_CRED_FILE",
"time_to_live": 3600,
"android": {
"enabled": $FCM_INCLUDE_ANDROID_NOTIFICATION,
"icon": "ic_logo_push",
"icon_color": "#3949AB",
"click_action": ".MessageActivity",
"msg": {
"title_loc_key": "new_message",
"title": "",
"body_loc_key": "",
"body": ""
},
"sub": {
"title_loc_key": "new_chat",
"body_loc_key": ""
}
}
}
}
],
"webrtc": {
"enabled": $WEBRTC_ENABLED,
"call_establishment_timeout": 30,
"ice_servers_file": "$ICE_SERVERS_FILE"
},
"cluster_config": {
"self": "",
"nodes": [
{"name": "tinode-0", "addr": "tinode-0:12000"},
{"name": "tinode-1", "addr": "tinode-1:12001"},
{"name": "tinode-2", "addr": "tinode-2:12002"}
],
"failover": {
"enabled": true,
"heartbeat": 100,
"vote_after": 8,
"node_fail_after": 16
}
},
"plugins": [
{
"enabled": $PLUGIN_PYTHON_CHAT_BOT_ENABLED,
"name": "python_chat_bot",
"timeout": 20000,
"filters": {
"account": "C"
},
"failure_code": 0,
"failure_text": null,
"service_addr": "tcp://localhost:40051"
}
]
}
+146
View File
@@ -0,0 +1,146 @@
#!/bin/bash
# If EXT_CONFIG is set, use it as a config file.
if [ ! -z "$EXT_CONFIG" ] ; then
CONFIG="$EXT_CONFIG"
# Enable push notifications.
if [ ! -z "$FCM_SENDER_ID" ] ; then
FCM_PUSH_ENABLED=true
fi
else
CONFIG=working.config
# Remove the old config.
rm -f working.config
# The 'alldbs' is not a valid adapter name.
if [ "$TARGET_DB" = "alldbs" ] ; then
TARGET_DB=
fi
# Enable email verification if $SMTP_SERVER is defined.
if [ ! -z "$SMTP_SERVER" ] ; then
EMAIL_VERIFICATION_REQUIRED='"auth"'
fi
# Enable TLS (httpS).
if [ ! -z "$TLS_DOMAIN_NAME" ] ; then
TLS_ENABLED=true
fi
# Enable push notifications.
if [ ! -z "$FCM_CRED_FILE" ] ; then
FCM_PUSH_ENABLED=true
fi
if [ ! -z "$TNPG_AUTH_TOKEN" ] ; then
TNPG_PUSH_ENABLED=true
fi
if [ ! -z "$ICE_SERVERS_FILE" ] ; then
WEBRTC_ENABLED=true
fi
# Generate a new 'working.config' from template and environment
while IFS='' read -r line || [[ -n $line ]] ; do
while [[ "$line" =~ (\$[A-Z_][A-Z_0-9]*) ]] ; do
LHS=${BASH_REMATCH[1]}
RHS="$(eval echo "\"$LHS\"")"
line=${line//$LHS/"$RHS"}
done
echo "$line" >> working.config
done < config.template
fi
# If external static dir is defined, use it.
# Otherwise, fall back to "./static".
if [ ! -z "$EXT_STATIC_DIR" ] ; then
STATIC_DIR=$EXT_STATIC_DIR
else
STATIC_DIR="./static"
fi
# Do not load data when upgrading database.
if [ "$UPGRADE_DB" = "true" ] ; then
SAMPLE_DATA=
fi
# If push notifications are enabled, generate client-side firebase config file.
if [ ! -z "$FCM_PUSH_ENABLED" ] || [ ! -z "$TNPG_PUSH_ENABLED" ] ; then
# Write client config to $STATIC_DIR/firebase-init.js
cat > $STATIC_DIR/firebase-init.js <<- EOM
const FIREBASE_INIT = {
apiKey: "$FCM_API_KEY",
appId: "$FCM_APP_ID",
messagingSenderId: "$FCM_SENDER_ID",
projectId: "$FCM_PROJECT_ID",
messagingVapidKey: "$FCM_VAPID_KEY",
measurementId: "$FCM_MEASUREMENT_ID"
};
EOM
else
# Create an empty firebase-init.js
echo "" > $STATIC_DIR/firebase-init.js
fi
if [ ! -z "$IOS_UNIV_LINKS_APP_ID" ] ; then
# Write config to $STATIC_DIR/apple-app-site-association config file.
# See https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html for details.
cat > $STATIC_DIR/apple-app-site-association <<- EOM
{
"applinks": {
"apps": [],
"details": [
{
"appID": "$IOS_UNIV_LINKS_APP_ID",
"paths": [ "*" ]
}
]
}
}
EOM
fi
# Wait for database if needed.
if [ ! -z "$WAIT_FOR" ] ; then
IFS=':' read -ra DB <<< "$WAIT_FOR"
if [ ${#DB[@]} -ne 2 ]; then
echo "\$WAIT_FOR (${WAIT_FOR}) env var should be in form HOST:PORT"
exit 1
fi
until nc -z -v -w5 ${DB[0]} ${DB[1]}; do echo "waiting for ${WAIT_FOR}..."; sleep 3; done
fi
# Initialize the database if it has not been initialized yet or if data reset/upgrade has been requested.
init_stdout=./init-db-stdout.txt
./init-db \
--reset=${RESET_DB} \
--upgrade=${UPGRADE_DB} \
--config=${CONFIG} \
--data=${SAMPLE_DATA} \
--no_init=${NO_DB_INIT} \
1>${init_stdout}
if [ $? -ne 0 ]; then
echo "./init-db failed. Quitting."
exit 1
fi
# If sample data was provided, try to find Tino password.
if [ ! -z "$SAMPLE_DATA" ] ; then
grep "usr;tino;" $init_stdout > /botdata/tino-password
fi
if [ -s /botdata/tino-password ] ; then
# Convert Tino's authentication credentials into a cookie file.
# /botdata/tino-password could be empty if DB was not updated. In such a case the
# /botdata/.tn-cookie will not be modified.
./credentials.sh /botdata/.tn-cookie < /botdata/tino-password
fi
args=("--config=${CONFIG}" "--static_data=$STATIC_DIR" "--cluster_self=$CLUSTER_SELF" "--pprof_url=$PPROF_URL")
# Run the tinode server.
./tinode "${args[@]}" 2>> /var/log/tinode.log