Compare commits

...

21 Commits

Author SHA1 Message Date
KawaiiPunk 0168577d65 chore: publish 0.1.2+v4.1.4 release
continuous-integration/drone/push Build is failing Details
2023-07-10 14:15:15 +01:00
decentral1se 270edfe034
fix: use RAILS_ENV in setup script [ci skip] 2023-02-17 10:03:58 +01:00
decentral1se 6fa8d78593
feat: new assets script 2023-02-17 10:03:50 +01:00
decentral1se e7898a5eea
docs: more commands, less quotes [ci skip] 2023-02-17 09:51:21 +01:00
decentral1se 0178ee3560
feat: new setup script. fix admin script 2023-02-17 09:46:05 +01:00
decentral1se 435aa16638
fix: migrate latest hometown changes [ci skip] 2023-02-15 20:16:16 +01:00
decentral1se f72d56ddc8
docs: woring [ci skip] 2023-02-15 02:00:37 +01:00
decentral1se d0f8203f56
docs: fix link
continuous-integration/drone/push Build is failing Details
2023-02-15 01:50:59 +01:00
decentral1se b961666e6b
docs: missing setup cmd
continuous-integration/drone/push Build is failing Details
2023-02-15 01:48:33 +01:00
decentral1se 910958b6a8
docs: tips & tricks migration
continuous-integration/drone/push Build is failing Details
2023-02-15 01:47:06 +01:00
decentral1se c65ea6663e
docs: more setup steps
continuous-integration/drone/push Build is failing Details
2023-02-15 01:37:57 +01:00
decentral1se 2f8dcad8aa
wip: v4.x & major config refactor
continuous-integration/drone/push Build is failing Details
2023-02-15 01:30:24 +01:00
3wc 4e9990f351 Switch to self-hosted stack-ssh-deploy image [mass update]
continuous-integration/drone/push Build is passing Details
2023-01-21 11:49:56 -08:00
3wc 6c6bffb1bb Generate secrets for Drone
continuous-integration/drone/push Build is passing Details
2023-01-20 22:29:40 -08:00
3wc 5b483be18a Add drone configs / secrets [mass update]
continuous-integration/drone/push Build is failing Details
2023-01-20 21:32:06 -08:00
3wc b91ddc8928 Fix CI by adding networks: [mass update]
continuous-integration/drone/push Build is failing Details
2023-01-20 11:58:41 -08:00
3wc 6652157b9b Automatically generate catalogue on release [mass update]
continuous-integration/drone/push Build is failing Details
Re: coop-cloud/recipes-catalogue-json#4
2023-01-20 10:27:11 -08:00
3wc a287cd4227 Update abra syntax in examples (finally) [mass update]
continuous-integration/drone/push Build is failing Details
2023-01-19 16:02:28 -08:00
3wc fbf19b5492 Switch to <recipe>.example.com
continuous-integration/drone/push Build is failing Details
2022-11-24 10:53:27 -08:00
decentral1se dd9e6adee0
use domain env var
continuous-integration/drone/push Build is failing Details
2022-03-27 21:10:51 +02:00
knoflook 01ecc19e35 fix command order
closes #9
2022-02-23 19:07:14 +00:00
6 changed files with 250 additions and 195 deletions

View File

@ -3,10 +3,13 @@ kind: pipeline
name: deploy to swarm-test.autonomic.zone
steps:
- name: deployment
image: decentral1se/stack-ssh-deploy:latest
image: git.coopcloud.tech/coop-cloud/stack-ssh-deploy:latest
settings:
host: swarm-test.autonomic.zone
stack: mastodon
generate_secrets: true
networks:
- proxy
purge: true
deploy_key:
from_secret: drone_ssh_swarm_test
@ -14,16 +17,28 @@ steps:
DOMAIN: mastodon.swarm-test.autonomic.zone
STACK_NAME: mastodon
LETS_ENCRYPT_ENV: production
ENTRYPOINT_CONF_VERSION: v1
SECRET_KEY_BASE_VERSION: v1
SECRET_OTP_SECRET_VERSION: v1
SECRET_VAPID_PRIVATE_KEY_VERSION: v1
SECRET_DB_PASSWORD_VERSION: v1
SECRET_SMTP_PASSWORD_VERSION: v1
trigger:
branch:
- main
---
kind: pipeline
name: recipe release
name: generate recipe catalogue
steps:
- name: release a new version
image: thecoopcloud/drone-abra:latest
image: plugins/downstream
settings:
command: recipe mastodon release
deploy_key:
from_secret: abra_bot_deploy_key
server: https://build.coopcloud.tech
token:
from_secret: drone_abra-bot_token
fork: true
repositories:
- coop-cloud/auto-recipes-catalogue-json
trigger:
event: tag

View File

@ -1,6 +1,6 @@
TYPE=mastodon
DOMAIN=mastodon.swarm-test.autonomic.zone
DOMAIN=mastodon.example.com
# Enables WEB_DOMAIN if set (FOR FUTURE USE)
# USER_DOMAIN=
@ -72,7 +72,7 @@ ES_PORT=9200
# Secrets
# =======
SECRET_KEY_BASE_VERSION=v1
SECRET_SECRET_KEY_BASE_VERSION=v1
SECRET_OTP_SECRET_VERSION=v1
SECRET_VAPID_PRIVATE_KEY_VERSION=v1
SECRET_DB_PASSWORD_VERSION=v1
@ -169,8 +169,3 @@ DEFAULT_LOCALE=en
# SAML_UID_ATTRIBUTE=
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=
# Hidden services (Not Supported)
# ===============================
# http_proxy= # yes, this should be lowercase
# ALLOW_ACCESS_TO_HIDDEN_SERVICE=

View File

@ -1,62 +1,63 @@
# Mastodon
Your self-hosted, globally interconnected microblogging community
> Your self-hosted, globally interconnected microblogging community
<!-- metadata -->
* **Category**: Apps
* **Status**:
* **Status**: 1
* **Image**: [`tootsuite/mastodon`](https://hub.docker.com/r/tootsuite/mastodon)
* **Healthcheck**:
* **Backups**:
* **Email**:
* **Tests**:
* **SSO**:
* **Healthcheck**: No
* **Backups**: No
* **Email**: Yes
* **Tests**: No
* **SSO**: Yes
<!-- endmetadata -->
## Basic usage
## Quick start
Mastodon expects secrets to be formatted in a very specific way, so please
choose "No" when prompted to generate secrets for `abra app new mastodon`. The
secrets must be generated outside of `abra` and that is achieved in step 2. See
the [`abra.sh`](./abra.sh) for more.
1. Set up Docker Swarm and [`abra`]
1. Deploy [`coop-cloud/traefik`]
1. `abra app new mastodon`
1. Follow the [secrets setup docs](#secrets-setup)
1. `abra app YOURAPPDOMAIN config` - be sure to change `DOMAIN` to something that resolves to your Docker swarm box
1. `abra app YOURAPPDOMAIN setup` to setup the database and create the admin account (services will stop flapping shortly after)
1. `abra app cmd <domain> secrets --local`
1. `abra app secret insert <domain> smtp_password v1 <password>`
1. `abra app config <domain>` (uncomment SMTP details)
1. `abra app deploy <domain>`
1. `abra app cmd <domain> streaming setup`
[`abra`]: https://git.autonomic.zone/autonomic-cooperative/abra
[`coop-cloud/traefik`]: https://git.autonomic.zone/coop-cloud/traefik
## Secrets setup
Because Mastodon expects secrets generated by specific tools, we don't support that in `abra` yet. However, you can run these commands yourself using the underlying Docker CLI. You can then load them in as secrets to the swarm using `abra` though and then they will be picked up on the deployment.
First, generate the `SECRET_KEY_BASE` and `OTP_SECRET` and store them in your local shell environment, you'll need them for subsequent commands.
Then, on your host (outside of the containers), you'll need to fix permissions
for the volume (see [#10](https://git.coopcloud.tech/coop-cloud/mastodon/issues/10)):
```
$ SECRET_KEY_BASE=$(docker run --rm tootsuite/mastodon:v3.4.0 bundle exec rake secret)
$ OTP_SECRET=$(docker run --rm tootsuite/mastodon:v3.4.0 bundle exec rake secret)
$ abra app YOURAPPDOMAIN secret insert secret_key_base v1 $SECRET_KEY_BASE
$ abra app YOURAPPDOMAIN secret insert otp_secret v1 $OTP_SECRET
chown -R 991:991 /var/lib/docker/volumes/<domain>_app/_data
```
Then you need to generate the `VAPID_{PUBLIC/PRIVATE}_KEY` values using the `SECRET_KEY_BASE`/`OTP_SECRET`:
And finally, within the `app` container, create an admin account:
```
$ docker run \
-e SECRET_KEY_BASE=$SECRET_KEY_BASE \
-e OTP_SECRET=$OTP_SECRET \
--rm tootsuite/mastodon:v3.4.0 \
bundle exec rake mastodon:webpush:generate_vapid_key
abra app cmd <domain> admin -- <username> <email>
```
Once you see the values generated, you can load the `VAPID_PUBLIC_KEY` into your `.env` file and `VAPID_PRIVATE_KEY` into a secret.
## Tips & tricks
### Auto-complete is not working?
Check the sidekiq logs (`/sidekiq/retries`), is a bunch of stuff failing? What
is the error?
If it looks anything like `blocked by: [FORBIDDEN/12/index read-only / allow
delete (api)];` then it might mean that your elastic search service has put
itself into "read-only" state. This could be due to running close to no free
disk space one time. ES doesn't undo this state, even when you have more free
disk space once more, so you need to handle this manually:
```
$ abra app YOURDOMAIN secret insert vapid_private_key v1 YOURVAPIDPRIVATEKEY
abra app run <domain> es bash
curl -XPUT -H "Content-Type: application/json" http://localhost:9200/_all/_settings -d '{"index.blocks.read_only_allow_delete": null}'
```
And finally, to end your whirlwind secrets loading adventure, get the `DB_PASS` and `SMTP_PASSWORD` loaded.
```
$ abra app YOURAPPDOMAIN secret generate db_password v1
$ abra app YOURDOMAIN secret insert smtp_password v1 YOURSMTPPASSWORD
```
Then head back to the sidekiq retries panel and retry one job. You should see
the ticket of retries go down by one if if passed. Then you can "retry all" and
they should get scheduled & run.

73
abra.sh
View File

@ -1 +1,72 @@
export ENTRYPOINT_CONF_VERSION=v1
export ENTRYPOINT_CONF_VERSION=v2
assets() {
export OTP_SECRET=$(cat /run/secrets/otp_secret)
export SECRET_KEY_BASE=$(cat /run/secrets/secret_key_base)
export DB_PASS=$(cat /run/secrets/db_password)
RAILS_ENV=production bundle exec rails assets:precompile
}
admin() {
export OTP_SECRET=$(cat /run/secrets/otp_secret)
export SECRET_KEY_BASE=$(cat /run/secrets/secret_key_base)
export DB_PASS=$(cat /run/secrets/db_password)
RAILS_ENV=production bin/tootctl accounts create "$1" --email "$2" --confirmed --role Owner
}
setup() {
export OTP_SECRET=$(cat /run/secrets/otp_secret)
export SECRET_KEY_BASE=$(cat /run/secrets/secret_key_base)
export DB_PASS=$(cat /run/secrets/db_password)
RAILS_ENV=production bundle exec rake db:setup
}
secrets() {
set -e
docker context use default > /dev/null 2>&1
MASTO_VERSION="v4.1.0"
echo "Generating secrets for a new Mastodon deployment..."
echo ""
SECRET_KEY_BASE=$(docker run --rm tootsuite/mastodon:$MASTO_VERSION bundle exec rake secret)
abra app secret insert "$APP_NAME" secret_key_base v1 "$SECRET_KEY_BASE"
echo "SECRET_KEY_BASE = $SECRET_KEY_BASE"
echo ""
OTP_SECRET=$(docker run --rm tootsuite/mastodon:$MASTO_VERSION bundle exec rake secret)
abra app secret insert "$APP_NAME" otp_secret v1 "$OTP_SECRET"
echo "OTP_SECRET = $OTP_SECRET"
echo ""
docker run \
-e SECRET_KEY_BASE="$SECRET_KEY_BASE" \
-e OTP_SECRET="$OTP_SECRET" \
--rm tootsuite/mastodon:$MASTO_VERSION \
bundle exec rake mastodon:webpush:generate_vapid_key \
> /tmp/key.txt
VAPID_PRIVATE_KEY=$(grep -oP "VAPID_PRIVATE_KEY=\K.+" "/tmp/key.txt")
VAPID_PUBLIC_KEY=$(grep -oP "VAPID_PUBLIC_KEY=\K.+" "/tmp/key.txt")
rm -rf /tmp/key.txt
echo "VAPID_PUBLIC_KEY = $VAPID_PUBLIC_KEY"
echo "!IMPORTANT! you MUST insert this VAPID_PUBLIC_KEY into your app .env config !IMPORTANT!"
echo ""
abra app secret insert "$APP_NAME" vapid_private_key v1 "$VAPID_PRIVATE_KEY"
echo "VAPID_PRIVATE_KEY = $VAPID_PRIVATE_KEY"
echo ""
abra app secret generate "$APP_NAME" db_password v1
echo ""
echo "don't forget to insert your smtp_password! your deployment won't work without it"
echo "run \"abra app secret insert $APP_NAME smtp_password v1 YOURSMTPPASSWORD\""
echo ""
}

View File

@ -3,11 +3,12 @@ version: "3.8"
services:
app:
image: tootsuite/mastodon:v3.4.6
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
image: tootsuite/mastodon:v4.1.4
command: |
bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
networks: &bothNetworks
- proxy
- internal_network
- internal
deploy:
update_config:
failure_action: rollback
@ -19,7 +20,7 @@ services:
- "traefik.http.routers.${STACK_NAME}_web.rule=Host(`${DOMAIN}`)"
- "traefik.http.routers.${STACK_NAME}_web.entrypoints=web-secure"
- "traefik.http.routers.${STACK_NAME}_web.tls.certresolver=${LETS_ENCRYPT_ENV}"
- "coop-cloud.${STACK_NAME}.version=0.1.1+3.4.6"
- "coop-cloud.${STACK_NAME}.version=0.1.2+v4.1.4"
configs: &configs
- source: entrypoint_sh
target: /usr/local/bin/entrypoint.sh
@ -34,130 +35,91 @@ services:
- smtp_password
- vapid_private_key
environment: &env
- DB_HOST
- DB_USER
- DB_NAME
- DB_PASS_FILE=/run/secrets/db_password
- DB_PORT
- REDIS_HOST
- REDIS_PORT
- REDIS_URL
- REDIS_NAMESPACE
- ALLOW_ACCESS_TO_HIDDEN_SERVICE
- ALTERNATE_DOMAINS
- AUTHORIZED_FETCH
- CACHE_REDIS_HOST
- CACHE_REDIS_NAMESPACE
- CACHE_REDIS_PORT
- CACHE_REDIS_URL
- CACHE_REDIS_NAMESPACE
- DB_HOST
- DB_NAME
- DB_PORT
- DB_USER
- DEFAULT_LOCALE
- EMAIL_DOMAIN_ALLOWLIST
- EMAIL_DOMAIN_DENYLIST
- ES_ENABLED
- ES_HOST
- ES_PORT
- STATSD_ADDR
- STATSD_NAMESPACE
- VAPID_PRIVATE_KEY_FILE=/run/secrets/vapid_private_key
- VAPID_PUBLIC_KEY
- OTP_SECRET_FILE=/run/secrets/otp_secret
- SECRET_KEY_BASE_FILE=/run/secrets/secret_key_base
- LOCAL_DOMAIN
- WEB_DOMAIN
- ALTERNATE_DOMAINS
- AUTHORIZED_FETCH
- LIMITED_FEDERATION_MODE
- RAILS_ENV
- RAILS_SERVE_STATIC_FILES
- SINGLE_USER_MODE
- EMAIL_DOMAIN_ALLOWLIST
- EMAIL_DOMAIN_DENYLIST
- DEFAULT_LOCALE
- MAX_SESSION_ACTIVATIONS
- USER_ACTIVE_DAYS
- SMTP_SERVER
- SMTP_PORT
- SMTP_LOGIN
- SMTP_PASSWORD_FILE=/run/secrets/smtp_password
- SMTP_FROM_ADDRESS
- SMTP_DOMAIN
- SMTP_DELIVERY_METHOD
- SMTP_AUTH_METHOD
- SMTP_CA_FILE
- SMTP_OPENSSL_VERIFY_MODE
- SMTP_ENABLE_STARTTLS_AUTO
- SMTP_TLS
- SMTP_SSL
- PAPERCLIP_ROOT_PATH
- PAPERCLIP_ROOT_URL
- OAUTH_REDIRECT_AT_SIGN_IN
- LDAP_ENABLED
- LDAP_HOST
- LDAP_PORT
- LDAP_METHOD
- LDAP_BASE
- LDAP_BIND_DN
- LDAP_PASSWORD
- LDAP_UID
- LDAP_SEARCH_FILTER
- LDAP_ENABLED
- LDAP_HOST
- LDAP_MAIL
- LDAP_METHOD
- LDAP_PASSWORD
- LDAP_PORT
- LDAP_SEARCH_FILTER
- LDAP_UID
- LDAP_UID_CONVERSTION_ENABLED
- SAML_ENABLED
- LIMITED_FEDERATION_MODE
- LOCAL_DOMAIN
- MAX_SESSION_ACTIVATIONS
- OAUTH_REDIRECT_AT_SIGN_IN
- OTP_SECRET_FILE=/run/secrets/otp_secret
- PAPERCLIP_ROOT_PATH
- PAPERCLIP_ROOT_URL
- RAILS_ENV
- RAILS_SERVE_STATIC_FILES
- REDIS_HOST
- REDIS_NAMESPACE
- REDIS_PORT
- REDIS_URL
- SAML_ACS_URL
- SAML_ISSUER
- SAML_IDP_SSO_TARGET_URL
- SAML_IDP_CERT
- SAML_IDP_CERT_FINGERPRINT
- SAML_NAME_IDENTIFIER_FORMAT
- SAML_CERT
- SAML_PRIVATE_KEY
- SAML_SECURITY_WANT_ASSERTION_SIGNED
- SAML_SECURITY_WANT_ASSERTION_ENCRYPTED
- SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED
- SAML_ATTRIBUTES_STATEMENTS_UID
- SAML_ATTRIBUTES_STATEMENTS_EMAIL
- SAML_ATTRIBUTES_STATEMENTS_FULL_NAME
- SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME
- SAML_ATTRIBUTES_STATEMENTS_FULL_NAME
- SAML_ATTRIBUTES_STATEMENTS_LAST_NAME
- SAML_UID_ATTRIBUTE
- SAML_ATTRIBUTES_STATEMENTS_UID
- SAML_ATTRIBUTES_STATEMENTS_VERIFIED
- SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL
- http_proxy # yes, this should be lowercase
- ALLOW_ACCESS_TO_HIDDEN_SERVICE
db:
image: postgres:9.6-alpine
networks: &internalNetwork
- internal_network
volumes:
- postgres:/var/lib/postgresql/data
secrets:
- db_password
environment:
- POSTGRES_DB=${DB_NAME}
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
- POSTGRES_USER=${DB_USER}
redis:
image: redis:6.2-alpine
networks: *internalNetwork
healthcheck:
test: ["CMD", "redis-cli", "ping"]
volumes:
- redis:/data
es:
image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.8.17
environment:
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- "cluster.name=es-mastodon"
- "discovery.type=single-node"
- "bootstrap.memory_lock=true"
networks:
- internal_network
volumes:
- es:/usr/share/elasticsearch/data
ulimits:
memlock:
soft: -1
hard: -1
- SAML_CERT
- SAML_ENABLED
- SAML_IDP_CERT
- SAML_IDP_CERT_FINGERPRINT
- SAML_IDP_SSO_TARGET_URL
- SAML_ISSUER
- SAML_NAME_IDENTIFIER_FORMAT
- SAML_PRIVATE_KEY
- SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED
- SAML_SECURITY_WANT_ASSERTION_ENCRYPTED
- SAML_SECURITY_WANT_ASSERTION_SIGNED
- SAML_UID_ATTRIBUTE
- SECRET_KEY_BASE_FILE=/run/secrets/secret_key_base
- SINGLE_USER_MODE
- SMTP_AUTH_METHOD
- SMTP_CA_FILE
- SMTP_DELIVERY_METHOD
- SMTP_DOMAIN
- SMTP_ENABLE_STARTTLS_AUTO
- SMTP_FROM_ADDRESS
- SMTP_LOGIN
- SMTP_OPENSSL_VERIFY_MODE
- SMTP_PASSWORD_FILE=/run/secrets/smtp_password
- SMTP_PORT
- SMTP_SERVER
- SMTP_SSL
- SMTP_TLS
- STATSD_ADDR
- STATSD_NAMESPACE
- USER_ACTIVE_DAYS
- VAPID_PRIVATE_KEY_FILE=/run/secrets/vapid_private_key
- VAPID_PUBLIC_KEY
- WEB_DOMAIN
streaming:
image: tootsuite/mastodon:v3.4.6
image: tootsuite/mastodon:v4.1.4
command: node ./streaming
configs: *configs
entrypoint: *entrypoint
@ -175,10 +137,10 @@ services:
- "traefik.http.routers.${STACK_NAME}_streaming.entrypoints=web-secure"
- "traefik.http.routers.${STACK_NAME}_streaming.tls.certresolver=${LETS_ENCRYPT_ENV}"
environment: *env
volumes: *appVolume # used to make sure this volume is created
volumes: *appVolume
sidekiq:
image: tootsuite/mastodon:v3.4.6
image: tootsuite/mastodon:v4.1.4
secrets: *secrets
command: bundle exec sidekiq
configs: *configs
@ -191,9 +153,46 @@ services:
volumes: *appVolume
environment: *env
db:
image: postgres:15.3-alpine
networks: &internalNetwork
- internal
volumes:
- postgres:/var/lib/postgresql/data
secrets:
- db_password
environment:
- POSTGRES_DB=${DB_NAME}
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
- POSTGRES_USER=${DB_USER}
redis:
image: redis:7.0-alpine
networks: *internalNetwork
healthcheck:
test: ["CMD", "redis-cli", "ping"]
volumes:
- redis:/data
es:
image: docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2
environment:
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- "cluster.name=es-mastodon"
- "discovery.type=single-node"
- "bootstrap.memory_lock=true"
networks:
- internal
volumes:
- es:/usr/share/elasticsearch/data
ulimits:
memlock:
soft: -1
hard: -1
secrets:
secret_key_base:
name: ${STACK_NAME}_secret_key_base_${SECRET_KEY_BASE_VERSION}
name: ${STACK_NAME}_secret_key_base_${SECRET_SECRET_KEY_BASE_VERSION}
external: true
otp_secret:
name: ${STACK_NAME}_otp_secret_${SECRET_OTP_SECRET_VERSION}
@ -215,10 +214,9 @@ volumes:
es:
networks:
internal:
proxy:
external: true
internal_network:
internal: true
configs:
entrypoint_sh:

View File

@ -2,31 +2,6 @@
set -eu
file_env() {
local var="$1"
local fileVar="${var}_FILE"
local def="${2:-}"
export DB_PASS=$(cat /run/secrets/db_password)
if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
exit 1
fi
local val="$def"
if [ "${!var:-}" ]; then
val="${!var}"
elif [ "${!fileVar:-}" ]; then
val="$(< "${!fileVar}")"
fi
export "$var"="$val"
unset "$fileVar"
}
file_env "DB_PASS"
file_env "OTP_SECRET"
file_env "SECRET_KEY_BASE"
file_env "SMTP_PASSWORD"
file_env "VAPID_PRIVATE_KEY"
/usr/bin/tini -- "$@"
/usr/bin/tini -s -- "$@"