#!/bin/bash # exit when any step fails set -euo pipefail # :) MY_NAME_IS_JAKE_JARVIS="false" # can't say you weren't warned if [ "$MY_NAME_IS_JAKE_JARVIS" != "pinky promise" ]; then echo "🚨 LISTEN UP!!!! YOU PROBABLY WANT THIS SCRIPT INSTEAD:" echo "https://github.com/jakejarvis/mastodon-installer/blob/main/install.sh" exit 69 fi # initialize path (and silence warnings about things not existing yet because that's why we're running the installer...) . "$(dirname "$(realpath "$0")")"/../init.sh >/dev/null 2>&1 # check for existing installation if [ -d "$APP_ROOT" ]; then echo "⚠️ $APP_ROOT already exists. Are you sure Mastodon isn't already installed?" exit 255 fi # ask for required info up-front read -p "Server FQDN? " MASTODON_DOMAIN read -p "Public domain? (the second part of usernames, usually the same) " MASTODON_USERNAME_DOMAIN read -p "Admin username? " MASTODON_ADMIN_USERNAME read -p "Admin email? " MASTODON_ADMIN_EMAIL # leave our mark INSTALLER_WUZ_HERE="# Generated by mastodon-installer @ $(date)" # set FQDN (especially necessary for sendmail) echo -e "\n$INSTALLER_WUZ_HERE 127.0.0.1 localhost $MASTODON_DOMAIN ::1 localhost $MASTODON_DOMAIN" | sudo tee -a /etc/hosts >/dev/null sudo hostnamectl set-hostname "$MASTODON_DOMAIN" # create non-root user named MASTODON_USER (unless it already exists) if ! id -u "$MASTODON_USER" >/dev/null 2>&1; then sudo adduser --disabled-login --gecos "Mastodon" "$MASTODON_USER" fi # install latest ubuntu updates sudo apt update sudo DEBIAN_FRONTEND=noninteractive apt upgrade -y sudo DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends \ curl \ wget \ gnupg \ apt-transport-https \ lsb-release \ ca-certificates # add official postgresql apt repository curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /usr/share/keyrings/postgresql-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/postgresql-archive-keyring.gpg] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" | sudo tee /etc/apt/sources.list.d/postgresql.list >/dev/null # add official redis apt repository curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list >/dev/null # add official nginx apt repository curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] https://nginx.org/packages/ubuntu/ $(lsb_release -cs) nginx" | sudo tee /etc/apt/sources.list.d/nginx.list >/dev/null # install prerequisites: # https://docs.joinmastodon.org/admin/install/#system-packages sudo apt update sudo DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends \ git-core \ g++ \ libpq-dev \ libxml2-dev \ libxslt1-dev \ imagemagick \ redis-server \ redis-tools \ postgresql \ postgresql-contrib \ libidn11-dev \ libicu-dev \ libreadline6-dev \ autoconf \ bison \ build-essential \ ffmpeg \ file \ gcc \ libffi-dev \ libgdbm-dev \ libjemalloc-dev \ libncurses5-dev \ libprotobuf-dev \ libssl-dev \ libyaml-dev \ pkg-config \ protobuf-compiler \ zlib1g-dev \ nginx \ python3 \ python3-venv \ libaugeas0 # install rbenv & ruby-build # https://github.com/rbenv/rbenv#basic-git-checkout # https://github.com/rbenv/ruby-build#clone-as-rbenv-plugin-using-git as_mastodon git clone https://github.com/rbenv/rbenv.git "$RBENV_ROOT" as_mastodon git clone https://github.com/rbenv/ruby-build.git "$RBENV_ROOT/plugins/ruby-build" eval "$("$RBENV_ROOT"/bin/rbenv init -)" # install nvm # https://github.com/nvm-sh/nvm#manual-install as_mastodon git clone https://github.com/nvm-sh/nvm.git "$NVM_DIR" && cd "$NVM_DIR" as_mastodon git checkout "$(as_mastodon git describe --abbrev=0 --tags --match "v[0-9]*" "$(as_mastodon git rev-list --tags --max-count=1)")" . "$NVM_DIR/nvm.sh" # clone vanilla Mastodon & checkout latest version: as_mastodon git clone https://github.com/mastodon/mastodon.git "$APP_ROOT" && cd "$APP_ROOT" as_mastodon git checkout "$(as_mastodon git describe --abbrev=0 --tags --match "v[0-9]*" "$(as_mastodon git rev-list --tags --max-count=1)")" # clone glitch-soc & checkout latest commit: # as_mastodon git clone https://github.com/glitch-soc/mastodon.git "$APP_ROOT" && cd "$APP_ROOT" # apply custom patches: as_mastodon git apply --reject --allow-binary-replacement "$UTILS_ROOT"/patches/*.patch # apply additional glitch-only patches: # as_mastodon git apply --reject --allow-binary-replacement "$UTILS_ROOT"/patches/glitch/*.patch # install ruby as_mastodon RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install --skip-existing as_mastodon rbenv global "$(as_mastodon cat "$APP_ROOT"/.ruby-version)" # install node & yarn as_mastodon bash -c "\. \"$NVM_DIR/nvm.sh\"; nvm install; nvm use; npm install --global yarn" # install npm and gem dependencies as_mastodon gem install bundler --no-document as_mastodon bundle config deployment "true" as_mastodon bundle config without "development test" as_mastodon bundle install --jobs "$(getconf _NPROCESSORS_ONLN)" as_mastodon yarn install --pure-lockfile --network-timeout 100000 # set up database w/ random alphanumeric password DB_PASSWORD=$(< /dev/urandom tr -dc A-Za-z0-9 | head -c32; echo) echo "CREATE USER '$MASTODON_USER' WITH PASSWORD '$DB_PASSWORD' CREATEDB" | sudo -u postgres psql -f - # populate .env.production config echo "$INSTALLER_WUZ_HERE LOCAL_DOMAIN=$MASTODON_USERNAME_DOMAIN WEB_DOMAIN=$MASTODON_DOMAIN DB_HOST=localhost DB_USER=$MASTODON_USER DB_NAME=mastodon_production DB_PASS=$DB_PASSWORD # without pgbouncer: DB_PORT=5432 # with pgbouncer: https://github.com/jakejarvis/mastodon-utils/wiki/Postgres-&-PgBouncer#pgbouncer # DB_PORT=6432 # PREPARED_STATEMENTS=false REDIS_HOST=localhost REDIS_PORT=6379 SECRET_KEY_BASE=$(as_mastodon RAILS_ENV=production bundle exec rake secret) OTP_SECRET=$(as_mastodon RAILS_ENV=production bundle exec rake secret) $(as_mastodon RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key) SINGLE_USER_MODE=false IP_RETENTION_PERIOD=31556952 SESSION_RETENTION_PERIOD=31556952 RAILS_LOG_LEVEL=warn WEB_CONCURRENCY=3 MAX_THREADS=10 STREAMING_CLUSTER_NUM=1 # uses linode, not brand name S3: https://cloud.linode.com/object-storage/buckets/create # AWS_ACCESS_KEY_ID=XXXXXXXX # AWS_SECRET_ACCESS_KEY=XXXXXXXX # S3_ENABLED=true # S3_BUCKET=my-bucket # S3_PROTOCOL=https # S3_HOSTNAME=us-east-1.linodeobjects.com # S3_ENDPOINT=https://us-east-1.linodeobjects.com # S3_ALIAS_HOST=my-bucket.us-east-1.linodeobjects.com # get SES credentials: https://us-east-1.console.aws.amazon.com/ses/home?region=us-east-1#/smtp # SMTP_SERVER=email-smtp.us-east-1.amazonaws.com # SMTP_PORT=587 # SMTP_FROM_ADDRESS=\"Mastodon \" # SMTP_LOGIN=XXXXXXXX # SMTP_PASSWORD=XXXXXXXX # https://github.com/jakejarvis/mastodon-utils/wiki/ElasticSearch # ES_ENABLED=true # ES_HOST=localhost # ES_PORT=9200 # ES_USER=optional # ES_PASS=optional # https://github.com/jakejarvis/mastodon-utils/wiki/Prometheus-&-Grafana # STATSD_ADDR=localhost:9125" | as_mastodon tee "$APP_ROOT/.env.production" >/dev/null # manually setup db as_mastodon RAILS_ENV=production bundle exec rails db:setup # precompile assets as_mastodon RAILS_ENV=production bundle exec rails assets:precompile # install latest certbot # https://certbot.eff.org/instructions?ws=nginx&os=pip sudo python3 -m venv /opt/certbot/ sudo /opt/certbot/bin/pip install --upgrade pip sudo /opt/certbot/bin/pip install certbot certbot-nginx sudo ln -s /opt/certbot/bin/certbot /usr/bin/certbot # ensure nginx hasn't started itself sudo systemctl stop nginx # order an ssl certificate from LE sudo certbot certonly \ --non-interactive \ --agree-tos \ --no-eff-email \ --domains "$MASTODON_DOMAIN" \ --email "$MASTODON_ADMIN_EMAIL" \ --standalone # configure nginx: copies conf files from this repo to /etc/nginx sudo mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak sudo cp "$UTILS_ROOT"/etc/nginx/nginx.conf /etc/nginx/nginx.conf sudo sed -i /etc/nginx/nginx.conf -e "s|user nginx;|user $MASTODON_USER;|g" sudo cp -f "$UTILS_ROOT"/etc/nginx/sites-available/*.conf /etc/nginx/sites-available/ sudo sed -i /etc/nginx/sites-available/mastodon.conf -e "s|mastodon.example.com|$MASTODON_DOMAIN|g" sudo sed -i /etc/nginx/sites-available/mastodon.conf -e "s|/home/mastodon/live|$APP_ROOT|g" sudo ln -sf /etc/nginx/sites-available/mastodon.conf /etc/nginx/sites-enabled/mastodon.conf # sudo ln -sf /etc/nginx/sites-available/default.conf /etc/nginx/sites-enabled/default.conf sudo cp -f "$UTILS_ROOT"/etc/nginx/modules/* /usr/lib/nginx/modules/ sudo nginx -t # configure mastodon systemd services sudo cp "$UTILS_ROOT"/etc/systemd/system/mastodon-*.service /etc/systemd/system/ # fix hard-coded paths and usernames in systemd files # (they already match the defaults from init.sh, so it's likely nothing will change) sudo sed -i /etc/systemd/system/mastodon-*.service -e "s|/home/mastodon/live|$APP_ROOT|g" sudo sed -i /etc/systemd/system/mastodon-*.service -e "s|/home/mastodon|$MASTODON_ROOT|g" sudo sed -i /etc/systemd/system/mastodon-*.service -e "s|User=mastodon|User=$MASTODON_USER|g" # start everything up! sudo systemctl daemon-reload sudo systemctl enable --now mastodon-web mastodon-sidekiq mastodon-streaming sudo systemctl start nginx # wait a bit to be safe sleep 5 # create admin account tootctl accounts create \ "$MASTODON_ADMIN_USERNAME" \ --email "$MASTODON_ADMIN_EMAIL" \ --role Owner \ --confirmed # set cleanup tasks to run weekly # https://docs.joinmastodon.org/admin/setup/#cleanup (sudo crontab -l; echo -e "\n$INSTALLER_WUZ_HERE @weekly bash -c \"$UTILS_ROOT/scripts/weekly_cleanup.sh >> $LOGS_ROOT/cron.log 2>&1\" ") | sudo crontab - echo "🎉 done! don't forget to fill in .env.production with credentials" echo "https://$MASTODON_DOMAIN/auth/sign_in"