diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..606d96c --- /dev/null +++ b/.env.example @@ -0,0 +1,19 @@ +# these are the defaults -- to override any of them, simply add a line to a new +# file named '.env'. (this new file should only contain the variables you wish +# to override; the rest should be left alone.) + +# name of the non-root user running mastodon +MASTODON_USER=mastodon + +# default paths +MASTODON_ROOT="/home/$MASTODON_USER" # absolute path to home dir of above user +UTILS_ROOT="$MASTODON_ROOT/utils" # this repository +APP_ROOT="$MASTODON_ROOT/live" # Mastodon source +BACKUPS_ROOT="$MASTODON_ROOT/backups" # backups destination +LOGS_ROOT="$MASTODON_ROOT/logs" # logs destination + +# paths to rbenv and nvm installations (both are automatically installed by +# install.sh to these default directories, but the specific environment +# variables are still required) +RBENV_ROOT="$MASTODON_ROOT/.rbenv" # rbenv (w/ ruby-build plugin) directory +NVM_DIR="$MASTODON_ROOT/.nvm" # nvm directory diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml index 2fed67d..ec2076a 100644 --- a/.github/workflows/super-linter.yml +++ b/.github/workflows/super-linter.yml @@ -30,6 +30,7 @@ jobs: env: DEFAULT_BRANCH: "main" FILTER_REGEX_EXCLUDE: .*etc/.* + VALIDATE_ENV: false VALIDATE_MARKDOWN: false VALIDATE_NATURAL_LANGUAGE: false GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index b55dd24..3a97e0e 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,9 @@ mkdir -p /home/mastodon git clone https://github.com/jakejarvis/mastodon-utils.git /home/mastodon/utils cd /home/mastodon/utils +# override default environment variables if necessary: +cp .env.example .env + # install Mastodon on fresh Ubuntu box: ./scripts/install.sh @@ -37,13 +40,14 @@ cd /home/mastodon/utils ## Scripts - [`init.sh`](init.sh): A small helper that runs at the very beginning of each script below to initialize `nvm`/`rbenv` and set consistent environment variables. + - **Optional:** The default values of each config variable can be seen in [`.env.example`](.env.example). Create a new file named `.env` in the root of this repository (probably at `/home/mastodon/utils/.env`) to override any or all of them. - **Optional:** To make your life easier, you can also source this script from the `.bashrc` of the `mastodon` user and/or whichever user you regularly SSH in as: ```sh [ -s /home/mastodon/utils/init.sh ] && \. /home/mastodon/utils/init.sh >/dev/null 2>&1 ``` -- [`version.sh`](scripts/version.sh): Tests `init.sh` by printing the versions of Mastodon, rbenv, nvm, Ruby, Node, and Yarn. +- [`version.sh`](scripts/version.sh): A quick and easy way to test `init.sh` and `.env` by printing the version numbers of Mastodon, rbenv, nvm, Ruby, Node, and Yarn. #### Periodic tasks @@ -56,7 +60,7 @@ cd /home/mastodon/utils **The following scripts are highly opinionated, catastrophically destructive, and very specific to me.** Check them out line-by-line instead of running them. -- [`install.sh`](scripts/install.sh): Assumes an absolutely clean install of Ubuntu and installs Mastodon ***with all of the quirks from this repo.*** Configure `MASTODON_USER` and other paths in [`init.sh`](init.sh) first if necessary. [Get the far less dangerous version of `install.sh` here instead.](https://github.com/jakejarvis/mastodon-installer/blob/main/install.sh) +- [`install.sh`](scripts/install.sh): Assumes an absolutely clean install of Ubuntu and installs Mastodon ***with all of the quirks from this repo.*** Configure `MASTODON_USER` and other paths in `.env` first (see [`.env.example`](.env.example)) if necessary. [Get the far less dangerous version of `install.sh` here instead.](https://github.com/jakejarvis/mastodon-installer/blob/main/install.sh) - [`upgrade.sh`](scripts/upgrade.sh): Upgrades Mastodon server (latest version if vanilla Mastodon, latest commit if `glitch-soc`) and ***re-applies all customizations***. [Get the far less dangerous version of `upgrade.sh` here instead.](https://github.com/jakejarvis/mastodon-installer/blob/main/upgrade.sh) - [`customize.sh`](scripts/customize.sh): Applies ***every Git patch below***, sets defaults (mostly for logged-out visitors) and removes unused files. diff --git a/init.sh b/init.sh index 07e7e18..624c71b 100755 --- a/init.sh +++ b/init.sh @@ -1,16 +1,26 @@ #!/bin/bash -# user running mastodon -export MASTODON_USER=mastodon +# load custom environment variables +MASTODON_ENV_PATH="$(dirname "$(realpath "${BASH_SOURCE[0]}")")/.env" +if [ -s "$MASTODON_ENV_PATH" ]; then + set -a + # shellcheck disable=SC1090 + source "$MASTODON_ENV_PATH" + set +a +else + echo "⚠ Missing .env file at '$MASTODON_ENV_PATH'. Falling back to defaults from '$MASTODON_ENV_PATH.example'." +fi -# default paths -export MASTODON_ROOT="/home/$MASTODON_USER" # home dir of the user above -export UTILS_ROOT="$MASTODON_ROOT/utils" # this repository -export APP_ROOT="$MASTODON_ROOT/live" # actual Mastodon files -export BACKUPS_ROOT="$MASTODON_ROOT/backups" # backups destination -export LOGS_ROOT="$MASTODON_ROOT/logs" # logs destintation -export RBENV_ROOT="$MASTODON_ROOT/.rbenv" # rbenv (w/ ruby-build plugin) directory -export NVM_DIR="$MASTODON_ROOT/.nvm" # nvm directory +# fall back to default env variables & re-export them +export MASTODON_USER="${MASTODON_USER:="mastodon"}" +export MASTODON_ROOT="${MASTODON_ROOT:="/home/$MASTODON_USER"}" +export UTILS_ROOT="${UTILS_ROOT:="$MASTODON_ROOT/utils"}" +export APP_ROOT="${APP_ROOT:="$MASTODON_ROOT/live"}" +export BACKUPS_ROOT="${BACKUPS_ROOT:="$MASTODON_ROOT/backups"}" +export LOGS_ROOT="${LOGS_ROOT:="$MASTODON_ROOT/logs"}" +export RBENV_ROOT="${RBENV_ROOT:="$MASTODON_ROOT/.rbenv"}" +export NVM_DIR="${NVM_DIR:="$MASTODON_ROOT/.nvm"}" +export MY_NAME_IS_JAKE_JARVIS="${MY_NAME_IS_JAKE_JARVIS:="false"}" # automatically detect glitch-soc # shellcheck disable=SC2155 @@ -20,9 +30,9 @@ export MASTODON_IS_GLITCH=$(test -d "$APP_ROOT/app/javascript/flavours/glitch" & # initialize rbenv if [ -s "$RBENV_ROOT/bin/rbenv" ]; then - eval "$($RBENV_ROOT/bin/rbenv init -)" + eval "$("$RBENV_ROOT"/bin/rbenv init -)" else - echo "⚠️ Couldn't find rbenv in '$RBENV_ROOT', double check the paths set in '$UTILS_ROOT/init.sh'..." + echo "⚠ rbenv wasn't found in '$RBENV_ROOT'. You might need to override RBENV_ROOT in '$MASTODON_ENV_PATH'..." fi # initialize nvm @@ -30,17 +40,17 @@ if [ -s "$NVM_DIR/nvm.sh" ]; then # shellcheck disable=SC1091 source "$NVM_DIR/nvm.sh" else - echo "⚠️ Couldn't find nvm.sh in '$NVM_DIR', double check the paths set in '$UTILS_ROOT/init.sh'..." + echo "⚠ nvm wasn't found in '$NVM_DIR'. You might need to override NVM_DIR in '$MASTODON_ENV_PATH'..." fi # check for Mastodon in set location if [ ! -d "$APP_ROOT" ]; then - echo "⚠️ Couldn't find Mastodon at '$APP_ROOT', double check the paths set in '$UTILS_ROOT/init.sh'..." + echo "⚠ Mastodon wasn't found at '$APP_ROOT'. You might need to override APP_ROOT in '$MASTODON_ENV_PATH'..." fi # clone this repo if it doesn't exist in the proper location # if [ ! -d "$UTILS_ROOT" ]; then -# echo "⚠️ Couldn't find mastodon-utils at '$UTILS_ROOT', cloning it for you..." +# echo "⚠ mastodon-utils wasn't found in '$UTILS_ROOT'. Cloning it for you..." # as_mastodon git clone https://github.com/jakejarvis/mastodon-utils.git "$UTILS_ROOT" # fi @@ -48,9 +58,9 @@ fi # run a given command as MASTODON_USER (`as_mastodon whoami`) as_mastodon() { - # crazy bandaids to make sure node & ruby are always available to MASTODON_USER # support quotes in args: https://stackoverflow.com/a/68898864/1438024 - CMD=$( + # shellcheck disable=SC2155 + local CMD=$( ( PS4='+' exec 2>&1 @@ -58,10 +68,14 @@ as_mastodon() { true "$@" ) | sed 's/^+*true //' ) + + # crazy bandaids to make sure ruby & node are always available to MASTODON_USER if [ -s "$RBENV_ROOT/bin/rbenv" ]; then + # prepend rbenv setup script to given command CMD="eval \"\$(\"$RBENV_ROOT\"/bin/rbenv init - bash)\"; $CMD" fi if [ -s "$NVM_DIR/nvm.sh" ]; then + # prepend nvm setup script to given command CMD="source \"$NVM_DIR/nvm.sh\"; $CMD" fi @@ -73,9 +87,15 @@ as_mastodon() { fi } -# run 'bin/tootctl' as MASTODON_USER in APP_ROOT from anywhere (`tootctl version`) +# run 'bin/tootctl' as $MASTODON_USER in $APP_ROOT from anywhere (`tootctl version`) tootctl() { - (cd "$APP_ROOT" && as_mastodon RAILS_ENV=production ruby ./bin/tootctl "$@") + # native tootctl *must* be run while in the mastodon source directory + if [ -d "$APP_ROOT" ]; then + (cd "$APP_ROOT" && as_mastodon RAILS_ENV=production ruby ./bin/tootctl "$@") + else + echo "⚠ Can't run tootctl because Mastodon wasn't found at '$APP_ROOT'. You might need to override APP_ROOT in '$MASTODON_ENV_PATH'..." + return 1 + fi } # --- diff --git a/scripts/backup.sh b/scripts/backup.sh index 5432d5d..b83de5b 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -14,46 +14,51 @@ echo -e "\n===== backup.sh: started at $(date '+%Y-%m-%d %H:%M:%S') =====\n" . "$(dirname "${BASH_SOURCE[0]}")"/../init.sh if [ "$(systemctl is-active mastodon-web.service)" = "active" ]; then - echo "⚠️ Mastodon is currently running." + echo "⚠ Mastodon is currently running." echo "We'll start the backup anyways, but if it's a critical one, stop all Mastodon" echo "services first with 'systemctl stop mastodon-*' and run this again." echo "" fi if [ ! -d "$BACKUPS_ROOT" ]; then - as_mastodon mkdir -p "$BACKUPS_ROOT" + sudo mkdir -p "$BACKUPS_ROOT" fi -TEMP_DIR=$(as_mastodon mktemp -d) +# TODO: ugly, do better +TEMP_DIR=$(sudo mktemp -d) +sudo chmod 777 "$TEMP_DIR" -echo "Backing up Postgres..." -as_mastodon pg_dump -Fc mastodon_production -f "$TEMP_DIR/postgres.dump" +echo "* Backing up Postgres..." +sudo -u postgres pg_dump -Fc mastodon_production -f "$TEMP_DIR/postgres.dump" -echo "Backing up Redis..." +echo "* Backing up Redis..." sudo cp /var/lib/redis/dump.rdb "$TEMP_DIR/redis.rdb" -echo "Backing up secrets..." +echo "* Backing up secrets..." sudo cp "$APP_ROOT/.env.production" "$TEMP_DIR/env.production" -echo "Backing up certs..." +echo "* Backing up certs..." sudo mkdir -p "$TEMP_DIR/certs" sudo cp -r /etc/letsencrypt/{archive,live,renewal} "$TEMP_DIR/certs/" -echo "Compressing..." +echo "* Compressing..." ARCHIVE_DEST="$BACKUPS_ROOT/mastodon-$(date "+%Y.%m.%d-%H.%M.%S").tar.gz" sudo tar --owner=0 --group=0 -czvf "$ARCHIVE_DEST" -C "$TEMP_DIR" . sudo chown "$MASTODON_USER":"$MASTODON_USER" "$ARCHIVE_DEST" -echo "Removing temp files..." +echo "* Fixing permissions..." +sudo chown -R "$MASTODON_USER":"$MASTODON_USER" "$BACKUPS_ROOT" + +echo "* Removing temp files..." sudo rm -rf --preserve-root "$TEMP_DIR" -echo "Saved to $ARCHIVE_DEST" +echo "* Saved archive to '$ARCHIVE_DEST'" if [ -s /usr/local/bin/linode-cli ]; then - echo "Uploading to S3..." + echo "* Uploading to S3..." sudo /usr/local/bin/linode-cli obj put "$ARCHIVE_DEST" jarvis-backup fi -echo "🎉 done! (keep this archive safe!)" +echo "* 🎉 done! (keep this archive safe!)" echo -e "\n===== backup.sh: finished at $(date '+%Y-%m-%d %H:%M:%S') =====\n" diff --git a/scripts/customize.sh b/scripts/customize.sh index 6fed08b..b6891f9 100755 --- a/scripts/customize.sh +++ b/scripts/customize.sh @@ -7,7 +7,7 @@ set -euo pipefail # shellcheck disable=SC1091 . "$(dirname "${BASH_SOURCE[0]}")"/../init.sh -# re-detect glitch-soc +# re-detect glitch-soc (answer might have changed since first sourcing init.sh) MASTODON_IS_GLITCH="$(test -d "$APP_ROOT/app/javascript/flavours/glitch" && echo true || echo false)" # --- @@ -41,8 +41,8 @@ if [ "$MASTODON_IS_GLITCH" = true ]; then "public/riot-glitch.png" ) - for f in "${removePaths[@]}"; do - as_mastodon rm -rf --preserve-root "$APP_ROOT/$f" + for r in "${removePaths[@]}"; do + as_mastodon rm -rf --preserve-root "$APP_ROOT/$r" done fi diff --git a/scripts/install.sh b/scripts/install.sh index 7e17558..5cc9000 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -3,10 +3,7 @@ # exit when any step fails set -euo pipefail -# :) -MY_NAME_IS_JAKE_JARVIS="false" - -# can't say you weren't warned +# 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" @@ -19,7 +16,7 @@ fi # check for existing installation if [ -d "$APP_ROOT" ]; then - echo "⚠️ $APP_ROOT already exists. Are you sure Mastodon isn't already installed?" + echo "⚠ $APP_ROOT already exists. Are you sure Mastodon isn't already installed?" exit 255 fi @@ -128,6 +125,7 @@ as_mastodon git checkout "$(as_mastodon git tag -l | grep -v 'rc[0-9]*$' | sort # as_mastodon git checkout glitch-soc/main # apply customizations +# shellcheck disable=SC1091 . "$UTILS_ROOT"/scripts/customize.sh # install ruby diff --git a/scripts/upgrade.sh b/scripts/upgrade.sh index 9eb0885..b6d4b3b 100755 --- a/scripts/upgrade.sh +++ b/scripts/upgrade.sh @@ -3,10 +3,7 @@ # exit when any step fails set -euo pipefail -# :) -MY_NAME_IS_JAKE_JARVIS="false" - -# can't say you weren't warned +# 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/upgrade.sh" @@ -23,53 +20,55 @@ as_mastodon git fetch --all as_mastodon git stash push --include-untracked --message "pre-upgrade changes" if [ -d "$APP_ROOT/app/javascript/flavours/glitch" ]; then # glitch-soc (uses latest commits) - echo "Pulling latest glitch-soc commits..." + echo "* Pulling latest glitch-soc commits..." as_mastodon git checkout glitch-soc/main else # vanilla (uses latest release) - echo "Pulling latest Mastodon release..." + echo "* Pulling latest Mastodon release..." as_mastodon git checkout "$(as_mastodon git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)" fi # re-apply customizations +echo "* Applying patches..." +# shellcheck disable=SC1091 . "$UTILS_ROOT"/scripts/customize.sh +# refresh dependencies +echo "* Updating dependencies..." # set new ruby version as_mastodon RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install --skip-existing as_mastodon rbenv global "$(as_mastodon cat "$APP_ROOT"/.ruby-version)" - # set new node version as_mastodon nvm install as_mastodon nvm use as_mastodon npm install --global yarn - -# update dependencies +# install deps as_mastodon bundle install --jobs "$(getconf _NPROCESSORS_ONLN)" as_mastodon yarn install --pure-lockfile # compile new assets -echo "Compiling new assets..." +echo "* Compiling new assets..." as_mastodon RAILS_ENV=production bundle exec rails assets:precompile # run migrations: # https://docs.joinmastodon.org/admin/upgrading/ -echo "Running pre-deploy database migrations..." +echo "* Running pre-deploy database migrations..." # note: DB_PORT is hard-coded because we need the raw DB, and .env.production might be pointing at pgbouncer as_mastodon DB_PORT=5432 SKIP_POST_DEPLOYMENT_MIGRATIONS=true RAILS_ENV=production bundle exec rails db:migrate # restart mastodon -echo "Restarting services (round 1/2)..." +echo "* Restarting services (round 1/2)..." sudo systemctl restart mastodon-web mastodon-sidekiq mastodon-streaming # clear caches & run post-deployment db migration -echo "Clearing cache..." +echo "* Clearing cache..." as_mastodon RAILS_ENV=production ruby "$APP_ROOT/bin/tootctl" cache clear -echo "Running post-deploy database migrations..." +echo "* Running post-deploy database migrations..." # note: DB_PORT is hard-coded because we need the raw DB, and .env.production might be pointing at pgbouncer as_mastodon DB_PORT=5432 RAILS_ENV=production bundle exec rails db:migrate # restart mastodon again -echo "Restarting services (round 2/2)..." +echo "* Restarting services (round 2/2)..." sudo systemctl restart mastodon-web mastodon-sidekiq mastodon-streaming -echo "🎉 done!" +echo "* 🎉 done!" diff --git a/scripts/version.sh b/scripts/version.sh index 2a14919..57153ac 100755 --- a/scripts/version.sh +++ b/scripts/version.sh @@ -13,3 +13,4 @@ echo "* Ruby: $(ruby --version)" echo "* Node.js: $(node --version)" echo "* Yarn: $(yarn --version)" echo "* Mastodon: $(tootctl version)" +echo "* whoami: $(as_mastodon whoami)"