Sanitized mirror from private repository - 2026-04-18 11:19:59 UTC
This commit is contained in:
21
docs/services/mastodon/LICENSE
Normal file
21
docs/services/mastodon/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Vish
|
||||
|
||||
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.
|
||||
160
docs/services/mastodon/README.md
Normal file
160
docs/services/mastodon/README.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Mastodon Production Scripts
|
||||
|
||||
Production-ready Mastodon deployment scripts for self-hosting.
|
||||
|
||||
## Installation Options
|
||||
|
||||
### Option 1: Docker (Multi-Platform)
|
||||
```bash
|
||||
curl -fsSL https://git.vish.gg/Vish/mastodon-production/raw/branch/main/install.sh | sudo bash -s -- --domain mastodon.example.com --email admin@example.com
|
||||
```
|
||||
Supports: Ubuntu, Debian, Fedora, Rocky/Alma/RHEL 8+, Arch, openSUSE
|
||||
|
||||
### Option 2: Bare-Metal (Rocky Linux 10)
|
||||
```bash
|
||||
# Set your configuration
|
||||
export DOMAIN="mastodon.example.com"
|
||||
export ADMIN_USER="admin"
|
||||
export ADMIN_EMAIL="admin@example.com"
|
||||
export SMTP_SERVER="smtp.gmail.com"
|
||||
export SMTP_PORT="587"
|
||||
export SMTP_USER="your@gmail.com"
|
||||
export SMTP_PASS="REDACTED_PASSWORD"
|
||||
export SMTP_FROM="notifications@example.com"
|
||||
|
||||
# Run installer
|
||||
curl -sSL https://git.vish.gg/Vish/mastodon-production/raw/branch/main/install-baremetal.sh | bash
|
||||
```
|
||||
|
||||
## Scripts
|
||||
|
||||
| Script | Description |
|
||||
|--------|-------------|
|
||||
| `install.sh` | Docker-based installer (multi-platform) |
|
||||
| `install-baremetal.sh` | Bare-metal installer for Rocky Linux 10 |
|
||||
| `verify-mastodon.sh` | Health check / verification script |
|
||||
| `fix-mastodon.sh` | Diagnose and auto-fix common issues |
|
||||
| `backup-mastodon.sh` | Backup script for migration |
|
||||
| `update-mastodon.sh` | Update to latest Mastodon version |
|
||||
|
||||
### Verify Installation
|
||||
|
||||
```bash
|
||||
./verify-mastodon.sh
|
||||
```
|
||||
|
||||
Checks:
|
||||
- All services (postgresql, valkey, nginx, mastodon-*)
|
||||
- API endpoints (instance, streaming)
|
||||
- Database connectivity and stats
|
||||
- Federation endpoints (webfinger, nodeinfo)
|
||||
- Configuration files
|
||||
|
||||
### Fix Common Issues
|
||||
|
||||
```bash
|
||||
./fix-mastodon.sh
|
||||
```
|
||||
|
||||
Automatically fixes:
|
||||
- Stopped services
|
||||
- File permissions
|
||||
- SELinux contexts
|
||||
- Service startup issues
|
||||
|
||||
## Bare-Metal Architecture (Rocky Linux 10)
|
||||
|
||||
```
|
||||
Internet → Cloudflare → Reverse Proxy (443) → Rocky VM (3000)
|
||||
↓
|
||||
nginx
|
||||
↓
|
||||
┌─────────────────┼─────────────────┐
|
||||
↓ ↓ ↓
|
||||
Puma (3001) Streaming (4000) Sidekiq
|
||||
↓ ↓ ↓
|
||||
└─────────────────┼─────────────────┘
|
||||
↓
|
||||
PostgreSQL + Valkey
|
||||
```
|
||||
|
||||
### Services (Bare-Metal)
|
||||
|
||||
| Service | Port | Description |
|
||||
|---------|------|-------------|
|
||||
| nginx | 3000 | External reverse proxy |
|
||||
| mastodon-web | 3001 | Puma web server |
|
||||
| mastodon-streaming | 4000 | WebSocket streaming |
|
||||
| mastodon-sidekiq | - | Background jobs |
|
||||
| postgresql | 5432 | Database |
|
||||
| valkey | 6379 | Redis cache |
|
||||
|
||||
## Backup & Restore
|
||||
|
||||
### Create Backup
|
||||
```bash
|
||||
/home/mastodon/scripts/backup-mastodon.sh
|
||||
```
|
||||
|
||||
Creates a complete backup including:
|
||||
- PostgreSQL database dump
|
||||
- `.env.production` (secrets)
|
||||
- User uploads (avatars, headers, media)
|
||||
- Restore instructions
|
||||
|
||||
### Restore
|
||||
See `RESTORE.md` included in backup archive.
|
||||
|
||||
## Update Mastodon
|
||||
|
||||
```bash
|
||||
# Update to latest version
|
||||
/home/mastodon/scripts/update-mastodon.sh
|
||||
|
||||
# Update to specific version
|
||||
/home/mastodon/scripts/update-mastodon.sh v4.6.0
|
||||
```
|
||||
|
||||
## Maintenance Commands
|
||||
|
||||
```bash
|
||||
# Service status
|
||||
systemctl status mastodon-web mastodon-sidekiq mastodon-streaming
|
||||
|
||||
# Restart all services
|
||||
systemctl restart mastodon-web mastodon-sidekiq mastodon-streaming
|
||||
|
||||
# View logs
|
||||
journalctl -u mastodon-web -f
|
||||
journalctl -u mastodon-sidekiq -f
|
||||
|
||||
# Access tootctl
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl --help'
|
||||
|
||||
# Create new user
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts create USERNAME --email=EMAIL --confirmed'
|
||||
|
||||
# Make user admin/owner
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts modify USERNAME --role Owner'
|
||||
|
||||
# Clear media cache
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl media remove --days=7'
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Bare-Metal
|
||||
- Rocky Linux 10 (fresh install)
|
||||
- 4GB+ RAM recommended
|
||||
- 20GB+ disk space
|
||||
- Domain with DNS configured
|
||||
- SMTP credentials for email
|
||||
|
||||
### Docker
|
||||
- Any supported Linux distribution
|
||||
- Docker and Docker Compose
|
||||
- Domain with DNS configured
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
140
docs/services/mastodon/USER_MANAGEMENT.md
Normal file
140
docs/services/mastodon/USER_MANAGEMENT.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# User Management Guide
|
||||
|
||||
## Creating New Users
|
||||
|
||||
### Method 1: Command Line (Recommended for Admins)
|
||||
|
||||
```bash
|
||||
# Create a new user (confirmed = skip email verification)
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts create USERNAME --email=user@example.com --confirmed'
|
||||
|
||||
# Approve the user (if approval mode is enabled)
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts approve USERNAME'
|
||||
|
||||
# Optional: Give them a role
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts modify USERNAME --role Moderator'
|
||||
# Roles: Owner, Admin, Moderator (or leave blank for regular user)
|
||||
```
|
||||
|
||||
### Method 2: Web Registration
|
||||
|
||||
1. Go to https://your-domain.com
|
||||
2. Click "Create account"
|
||||
3. Fill in username, email, password
|
||||
4. Admin approves in Settings → Administration → Pending accounts (if approval required)
|
||||
|
||||
### Method 3: Invite Links
|
||||
|
||||
1. Login as admin
|
||||
2. Go to Settings → Invites
|
||||
3. Click "Generate invite link"
|
||||
4. Share the link with your partner/friends
|
||||
|
||||
## Example: Adding Your Partner
|
||||
|
||||
```bash
|
||||
# Create account for partner
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts create partner --email=partner@example.com --confirmed'
|
||||
|
||||
# Save the generated password! It will be displayed like:
|
||||
# New password: "REDACTED_PASSWORD"
|
||||
|
||||
# Approve the account
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts approve partner'
|
||||
|
||||
# Optional: Make them an admin too
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts modify partner --role Admin'
|
||||
```
|
||||
|
||||
## User Limits
|
||||
|
||||
**There is NO hard limit on users.**
|
||||
|
||||
Your only constraints are server resources:
|
||||
- **RAM**: Each active user session uses some memory
|
||||
- **Storage**: Media uploads (avatars, images, videos) take disk space
|
||||
- **CPU**: More users = more background jobs
|
||||
|
||||
For a small personal instance (2-10 users), a VM with 4GB RAM and 20GB storage is more than enough.
|
||||
|
||||
## Managing Existing Users
|
||||
|
||||
### List all users
|
||||
```bash
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts list'
|
||||
```
|
||||
|
||||
### Reset a user's password
|
||||
```bash
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts modify USERNAME --reset-password'
|
||||
```
|
||||
|
||||
### Disable/Enable a user
|
||||
```bash
|
||||
# Disable
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts modify USERNAME --disable'
|
||||
|
||||
# Enable
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts modify USERNAME --enable'
|
||||
```
|
||||
|
||||
### Delete a user
|
||||
```bash
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts delete USERNAME'
|
||||
```
|
||||
|
||||
### Change user role
|
||||
```bash
|
||||
# Make admin
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts modify USERNAME --role Admin'
|
||||
|
||||
# Make moderator
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts modify USERNAME --role Moderator'
|
||||
|
||||
# Remove all roles (regular user)
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/tootctl accounts modify USERNAME --role ""'
|
||||
```
|
||||
|
||||
## Registration Settings
|
||||
|
||||
Control how new users can join via the admin panel:
|
||||
|
||||
1. Login as admin
|
||||
2. Go to **Settings → Administration → Server Settings → Registrations**
|
||||
3. Choose:
|
||||
- **Open**: Anyone can sign up
|
||||
- **Approval required**: Admin must approve new accounts
|
||||
- **Closed**: No new registrations (invite-only)
|
||||
|
||||
## User Roles
|
||||
|
||||
| Role | Permissions |
|
||||
|------|-------------|
|
||||
| **Owner** | Full access, can't be demoted |
|
||||
| **Admin** | Full admin panel access, manage users, server settings |
|
||||
| **Moderator** | Handle reports, suspend users, manage content |
|
||||
| **User** | Regular user, no admin access |
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
# Create user
|
||||
bin/tootctl accounts create USERNAME --email=EMAIL --confirmed
|
||||
|
||||
# Approve user
|
||||
bin/tootctl accounts approve USERNAME
|
||||
|
||||
# Make admin
|
||||
bin/tootctl accounts modify USERNAME --role Admin
|
||||
|
||||
# Reset password
|
||||
bin/tootctl accounts modify USERNAME --reset-password
|
||||
|
||||
# Delete user
|
||||
bin/tootctl accounts delete USERNAME
|
||||
```
|
||||
|
||||
All commands require the prefix:
|
||||
```bash
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production ...'
|
||||
```
|
||||
131
docs/services/mastodon/backup-mastodon.sh
Executable file
131
docs/services/mastodon/backup-mastodon.sh
Executable file
@@ -0,0 +1,131 @@
|
||||
#!/bin/bash
|
||||
# Mastodon Backup Script
|
||||
# Creates a complete backup for migration to another server
|
||||
# Run as root
|
||||
|
||||
set -e
|
||||
|
||||
BACKUP_DIR="${BACKUP_DIR:-/home/mastodon/backups}"
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_NAME="mastodon_backup_${TIMESTAMP}"
|
||||
BACKUP_PATH="${BACKUP_DIR}/${BACKUP_NAME}"
|
||||
|
||||
echo "=========================================="
|
||||
echo "Mastodon Backup Script"
|
||||
echo "Backup location: ${BACKUP_PATH}"
|
||||
echo "=========================================="
|
||||
|
||||
# Create backup directory
|
||||
mkdir -p "${BACKUP_PATH}"
|
||||
|
||||
# 1. Backup PostgreSQL database
|
||||
echo "[1/5] Backing up PostgreSQL database..."
|
||||
sudo -u postgres pg_dump -Fc mastodon_production > "${BACKUP_PATH}/database.dump"
|
||||
echo " Database backup: $(du -h ${BACKUP_PATH}/database.dump | cut -f1)"
|
||||
|
||||
# 2. Backup .env.production (contains secrets)
|
||||
echo "[2/5] Backing up configuration..."
|
||||
cp /home/mastodon/live/.env.production "${BACKUP_PATH}/.env.production"
|
||||
|
||||
# 3. Backup user uploads (avatars, headers, media)
|
||||
echo "[3/5] Backing up user uploads (this may take a while)..."
|
||||
if [ -d /home/mastodon/live/public/system ]; then
|
||||
tar -czf "${BACKUP_PATH}/system.tar.gz" -C /home/mastodon/live/public system
|
||||
echo " System files: $(du -h ${BACKUP_PATH}/system.tar.gz | cut -f1)"
|
||||
else
|
||||
echo " No system directory found (fresh install)"
|
||||
fi
|
||||
|
||||
# 4. Backup custom files (if any)
|
||||
echo "[4/5] Backing up custom files..."
|
||||
mkdir -p "${BACKUP_PATH}/custom"
|
||||
|
||||
# Custom CSS/branding
|
||||
if [ -f /home/mastodon/live/app/javascript/styles/custom.scss ]; then
|
||||
cp /home/mastodon/live/app/javascript/styles/custom.scss "${BACKUP_PATH}/custom/"
|
||||
fi
|
||||
|
||||
# Site uploads (favicon, thumbnail, etc)
|
||||
if [ -d /home/mastodon/live/public/site_uploads ]; then
|
||||
cp -r /home/mastodon/live/public/site_uploads "${BACKUP_PATH}/custom/"
|
||||
fi
|
||||
|
||||
# 5. Export user data
|
||||
echo "[5/5] Exporting instance data..."
|
||||
sudo -u mastodon bash -c "cd ~/live && export PATH=\"\$HOME/.rbenv/bin:\$PATH\" && eval \"\$(rbenv init -)\" && RAILS_ENV=production bin/tootctl accounts export > /dev/null 2>&1" || true
|
||||
|
||||
# Create restore instructions
|
||||
cat > "${BACKUP_PATH}/RESTORE.md" << 'RESTORE'
|
||||
# Mastodon Restore Instructions
|
||||
|
||||
## On the new server:
|
||||
|
||||
1. Run the install script first (without creating admin user)
|
||||
2. Stop all Mastodon services:
|
||||
```
|
||||
systemctl stop mastodon-web mastodon-sidekiq mastodon-streaming
|
||||
```
|
||||
|
||||
3. Restore the database:
|
||||
```
|
||||
sudo -u postgres dropdb mastodon_production
|
||||
sudo -u postgres createdb -O mastodon mastodon_production
|
||||
sudo -u postgres pg_restore -d mastodon_production database.dump
|
||||
```
|
||||
|
||||
4. Restore .env.production:
|
||||
```
|
||||
cp .env.production /home/mastodon/live/.env.production
|
||||
chown mastodon:mastodon /home/mastodon/live/.env.production
|
||||
chmod 600 /home/mastodon/live/.env.production
|
||||
```
|
||||
|
||||
5. Restore user uploads:
|
||||
```
|
||||
cd /home/mastodon/live/public
|
||||
tar -xzf /path/to/backup/system.tar.gz
|
||||
chown -R mastodon:mastodon system
|
||||
```
|
||||
|
||||
6. Update LOCAL_DOMAIN in .env.production if domain changed
|
||||
|
||||
7. Run migrations (in case of version upgrade):
|
||||
```
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bundle exec rails db:migrate'
|
||||
```
|
||||
|
||||
8. Recompile assets:
|
||||
```
|
||||
sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bundle exec rails assets:precompile'
|
||||
```
|
||||
|
||||
9. Fix SELinux contexts:
|
||||
```
|
||||
chcon -R -t httpd_sys_content_t /home/mastodon/live/public
|
||||
```
|
||||
|
||||
10. Start services:
|
||||
```
|
||||
systemctl start mastodon-web mastodon-sidekiq mastodon-streaming
|
||||
```
|
||||
RESTORE
|
||||
|
||||
# Create final archive
|
||||
echo ""
|
||||
echo "Creating final archive..."
|
||||
cd "${BACKUP_DIR}"
|
||||
tar -czf "${BACKUP_NAME}.tar.gz" "${BACKUP_NAME}"
|
||||
rm -rf "${BACKUP_NAME}"
|
||||
|
||||
FINAL_SIZE=$(du -h "${BACKUP_DIR}/${BACKUP_NAME}.tar.gz" | cut -f1)
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "✅ Backup Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Backup file: ${BACKUP_DIR}/${BACKUP_NAME}.tar.gz"
|
||||
echo "Size: ${FINAL_SIZE}"
|
||||
echo ""
|
||||
echo "To download: scp root@server:${BACKUP_DIR}/${BACKUP_NAME}.tar.gz ."
|
||||
echo ""
|
||||
222
docs/services/mastodon/fix-mastodon.sh
Executable file
222
docs/services/mastodon/fix-mastodon.sh
Executable file
@@ -0,0 +1,222 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# Mastodon Fix/Repair Script
|
||||
# Diagnoses and fixes common issues
|
||||
# =============================================================================
|
||||
# Run as root
|
||||
|
||||
echo "=========================================="
|
||||
echo "Mastodon Fix/Repair Tool"
|
||||
echo "=========================================="
|
||||
|
||||
# Check root
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "This script must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FIXED=0
|
||||
ERRORS=0
|
||||
|
||||
# 1. Check and fix service status
|
||||
echo ""
|
||||
echo "[1/7] Checking services..."
|
||||
|
||||
services=("postgresql" "valkey" "nginx" "mastodon-web" "mastodon-sidekiq" "mastodon-streaming")
|
||||
for svc in "${services[@]}"; do
|
||||
if systemctl is-active --quiet $svc 2>/dev/null; then
|
||||
echo " ✓ $svc is running"
|
||||
elif systemctl list-unit-files | grep -q "^${svc}.service"; then
|
||||
echo " ✗ $svc is not running, attempting to start..."
|
||||
systemctl start $svc 2>/dev/null
|
||||
sleep 2
|
||||
if systemctl is-active --quiet $svc; then
|
||||
echo " ✓ $svc started successfully"
|
||||
FIXED=$((FIXED + 1))
|
||||
else
|
||||
echo " ✗ Failed to start $svc"
|
||||
echo " Check logs: journalctl -u $svc -n 50"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# 2. Check file permissions
|
||||
echo ""
|
||||
echo "[2/7] Checking file permissions..."
|
||||
|
||||
# Check .env.production
|
||||
if [ -f /home/mastodon/live/.env.production ]; then
|
||||
OWNER=$(stat -c '%U' /home/mastodon/live/.env.production)
|
||||
PERMS=$(stat -c '%a' /home/mastodon/live/.env.production)
|
||||
|
||||
if [ "$OWNER" != "mastodon" ]; then
|
||||
echo " ✗ Fixing .env.production ownership..."
|
||||
chown mastodon:mastodon /home/mastodon/live/.env.production
|
||||
FIXED=$((FIXED + 1))
|
||||
fi
|
||||
|
||||
if [ "$PERMS" != "600" ]; then
|
||||
echo " ✗ Fixing .env.production permissions..."
|
||||
chmod 600 /home/mastodon/live/.env.production
|
||||
FIXED=$((FIXED + 1))
|
||||
fi
|
||||
|
||||
echo " ✓ .env.production permissions OK"
|
||||
fi
|
||||
|
||||
# Check live directory ownership
|
||||
if [ -d /home/mastodon/live ]; then
|
||||
LIVE_OWNER=$(stat -c '%U' /home/mastodon/live)
|
||||
if [ "$LIVE_OWNER" != "mastodon" ]; then
|
||||
echo " ✗ Fixing /home/mastodon/live ownership..."
|
||||
chown -R mastodon:mastodon /home/mastodon/live
|
||||
FIXED=$((FIXED + 1))
|
||||
else
|
||||
echo " ✓ /home/mastodon/live ownership OK"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. Check database connection
|
||||
echo ""
|
||||
echo "[3/7] Checking database..."
|
||||
|
||||
if sudo -u postgres psql -c "SELECT 1" mastodon_production > /dev/null 2>&1; then
|
||||
echo " ✓ Database connection successful"
|
||||
else
|
||||
echo " ✗ Cannot connect to database"
|
||||
|
||||
# Try to fix common issues
|
||||
if ! systemctl is-active --quiet postgresql; then
|
||||
echo " Attempting to start PostgreSQL..."
|
||||
systemctl start postgresql
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
# Check if database exists
|
||||
if ! sudo -u postgres psql -lqt | cut -d \| -f 1 | grep -qw mastodon_production; then
|
||||
echo " Database does not exist!"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4. Check Redis/Valkey connection
|
||||
echo ""
|
||||
echo "[4/7] Checking cache server..."
|
||||
|
||||
if valkey-cli ping > /dev/null 2>&1; then
|
||||
echo " ✓ Valkey connection successful"
|
||||
elif redis-cli ping > /dev/null 2>&1; then
|
||||
echo " ✓ Redis connection successful"
|
||||
else
|
||||
echo " ✗ Cannot connect to cache server"
|
||||
|
||||
if systemctl is-active --quiet valkey; then
|
||||
echo " Valkey is running but not responding"
|
||||
elif systemctl is-active --quiet redis; then
|
||||
echo " Redis is running but not responding"
|
||||
else
|
||||
echo " Attempting to start Valkey..."
|
||||
systemctl start valkey 2>/dev/null || systemctl start redis 2>/dev/null
|
||||
sleep 2
|
||||
FIXED=$((FIXED + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# 5. Check nginx configuration
|
||||
echo ""
|
||||
echo "[5/7] Checking nginx configuration..."
|
||||
|
||||
if nginx -t 2>/dev/null; then
|
||||
echo " ✓ Nginx configuration is valid"
|
||||
else
|
||||
echo " ✗ Nginx configuration has errors"
|
||||
nginx -t
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
# 6. Check SELinux contexts (Rocky/RHEL)
|
||||
echo ""
|
||||
echo "[6/7] Checking SELinux..."
|
||||
|
||||
if command -v getenforce &> /dev/null; then
|
||||
SELINUX_MODE=$(getenforce)
|
||||
echo " SELinux mode: $SELINUX_MODE"
|
||||
|
||||
if [ "$SELINUX_MODE" = "Enforcing" ]; then
|
||||
# Fix common SELinux issues
|
||||
if [ -d /home/mastodon/live/public ]; then
|
||||
echo " Ensuring correct SELinux contexts..."
|
||||
chcon -R -t httpd_sys_content_t /home/mastodon/live/public 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo " SELinux not present"
|
||||
fi
|
||||
|
||||
# 7. Check API endpoints
|
||||
echo ""
|
||||
echo "[7/7] Checking API endpoints..."
|
||||
|
||||
sleep 1
|
||||
|
||||
# Test instance API
|
||||
if curl -sf http://127.0.0.1:3000/api/v1/instance > /dev/null 2>&1; then
|
||||
echo " ✓ Instance API responding"
|
||||
else
|
||||
echo " ✗ Instance API not responding"
|
||||
|
||||
# Check if it's a startup timing issue
|
||||
echo " Waiting for services to fully start..."
|
||||
sleep 5
|
||||
|
||||
if curl -sf http://127.0.0.1:3000/api/v1/instance > /dev/null 2>&1; then
|
||||
echo " ✓ Instance API now responding"
|
||||
else
|
||||
echo " ✗ Instance API still not responding"
|
||||
echo " Check logs: journalctl -u mastodon-web -n 50"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test streaming API
|
||||
if curl -sf http://127.0.0.1:4000/api/v1/streaming/health > /dev/null 2>&1; then
|
||||
echo " ✓ Streaming API healthy"
|
||||
else
|
||||
echo " ✗ Streaming API not responding"
|
||||
echo " Attempting to restart streaming service..."
|
||||
systemctl restart mastodon-streaming
|
||||
sleep 3
|
||||
if curl -sf http://127.0.0.1:4000/api/v1/streaming/health > /dev/null 2>&1; then
|
||||
echo " ✓ Streaming API now healthy"
|
||||
FIXED=$((FIXED + 1))
|
||||
else
|
||||
echo " ✗ Streaming API still not responding"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
if [ $ERRORS -eq 0 ]; then
|
||||
if [ $FIXED -eq 0 ]; then
|
||||
echo "✅ All checks passed! No issues found."
|
||||
else
|
||||
echo "✅ Fixed $FIXED issue(s). All checks now pass."
|
||||
echo ""
|
||||
echo "You may want to restart services:"
|
||||
echo " systemctl restart mastodon-web mastodon-sidekiq mastodon-streaming"
|
||||
fi
|
||||
else
|
||||
echo "⚠️ Found $ERRORS error(s) that need manual attention."
|
||||
echo ""
|
||||
echo "Common fixes:"
|
||||
echo " - Check logs: journalctl -u mastodon-web -f"
|
||||
echo " - Restart all: systemctl restart mastodon-{web,sidekiq,streaming}"
|
||||
echo " - Check .env: cat /home/mastodon/live/.env.production"
|
||||
echo " - Run migrations: sudo -u mastodon bash -lc 'cd ~/live && RAILS_ENV=production bin/rails db:migrate'"
|
||||
fi
|
||||
echo "=========================================="
|
||||
|
||||
exit $ERRORS
|
||||
340
docs/services/mastodon/install-baremetal.sh
Executable file
340
docs/services/mastodon/install-baremetal.sh
Executable file
@@ -0,0 +1,340 @@
|
||||
#!/bin/bash
|
||||
# Mastodon v4.5.4 Bare-Metal Install Script for Rocky Linux 10
|
||||
# Usage: curl -sSL https://git.vish.gg/Vish/pihole-baremetal/raw/branch/main/mastodon/install-mastodon.sh | bash
|
||||
# Run as root on a fresh Rocky Linux 10 VM
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration - Edit these before running
|
||||
DOMAIN="${DOMAIN:-mastodon.example.com}"
|
||||
ADMIN_USER="${ADMIN_USER:-admin}"
|
||||
ADMIN_EMAIL="${ADMIN_EMAIL:-admin@example.com}"
|
||||
SMTP_SERVER="${SMTP_SERVER:-smtp.gmail.com}"
|
||||
SMTP_PORT="${SMTP_PORT:-587}"
|
||||
SMTP_USER="${SMTP_USER:-}"
|
||||
SMTP_PASS="REDACTED_PASSWORD"
|
||||
SMTP_FROM="${SMTP_FROM:-notifications@example.com}"
|
||||
|
||||
echo "=========================================="
|
||||
echo "Mastodon v4.5.4 Installation Script"
|
||||
echo "Target Domain: $DOMAIN"
|
||||
echo "=========================================="
|
||||
|
||||
# Check if running as root
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "This script must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install system dependencies
|
||||
echo "[1/12] Installing system dependencies..."
|
||||
dnf install -y epel-release
|
||||
dnf install -y git curl wget gcc make autoconf bison openssl-devel \
|
||||
libyaml-devel libffi-devel readline-devel zlib-devel gdbm-devel ncurses-devel \
|
||||
libxml2-devel libxslt-devel libicu-devel libidn-devel jemalloc-devel \
|
||||
ImageMagick ImageMagick-devel nginx postgresql-server postgresql-contrib \
|
||||
valkey certbot python3-certbot-nginx meson ninja-build \
|
||||
libpng-devel libjpeg-turbo-devel libwebp-devel libtiff-devel \
|
||||
expat-devel gobject-introspection-devel glib2-devel
|
||||
|
||||
# Install Node.js 20
|
||||
echo "[2/12] Installing Node.js 20..."
|
||||
curl -fsSL https://rpm.nodesource.com/setup_20.x | bash -
|
||||
dnf install -y nodejs
|
||||
|
||||
# Enable corepack for Yarn
|
||||
corepack enable
|
||||
|
||||
# Build libvips from source (not in Rocky 10 repos)
|
||||
echo "[3/12] Building libvips from source..."
|
||||
cd /tmp
|
||||
wget https://github.com/libvips/libvips/releases/download/v8.16.1/vips-8.16.1.tar.xz
|
||||
tar xf vips-8.16.1.tar.xz
|
||||
cd vips-8.16.1
|
||||
meson setup build --prefix=/usr --buildtype=release
|
||||
cd build && ninja && ninja install
|
||||
ldconfig
|
||||
cd /tmp && rm -rf vips-8.16.1*
|
||||
|
||||
# Initialize PostgreSQL
|
||||
echo "[4/12] Setting up PostgreSQL..."
|
||||
postgresql-setup --initdb
|
||||
systemctl enable --now postgresql
|
||||
|
||||
# Create mastodon database user and database
|
||||
sudo -u postgres psql -c "CREATE USER mastodon CREATEDB;"
|
||||
sudo -u postgres psql -c "CREATE DATABASE mastodon_production OWNER mastodon;"
|
||||
|
||||
# Start Valkey (Redis)
|
||||
echo "[5/12] Starting Valkey..."
|
||||
systemctl enable --now valkey
|
||||
|
||||
# Create mastodon user
|
||||
echo "[6/12] Creating mastodon user..."
|
||||
useradd -m -s /bin/bash mastodon || true
|
||||
|
||||
# Install Ruby via rbenv
|
||||
echo "[7/12] Installing Ruby 3.4.7..."
|
||||
sudo -u mastodon bash << 'RUBY_INSTALL'
|
||||
cd ~
|
||||
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
|
||||
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
|
||||
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
|
||||
export PATH="$HOME/.rbenv/bin:$PATH"
|
||||
eval "$(rbenv init -)"
|
||||
|
||||
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
|
||||
RUBY_CONFIGURE_OPTS="--with-jemalloc" rbenv install 3.4.7
|
||||
rbenv global 3.4.7
|
||||
gem install bundler
|
||||
RUBY_INSTALL
|
||||
|
||||
# Clone Mastodon
|
||||
echo "[8/12] Cloning Mastodon v4.5.4..."
|
||||
sudo -u mastodon bash << 'CLONE'
|
||||
cd ~
|
||||
git clone https://github.com/mastodon/mastodon.git live
|
||||
cd live
|
||||
git checkout v4.5.4
|
||||
CLONE
|
||||
|
||||
# Install dependencies
|
||||
echo "[9/12] Installing Ruby and Node dependencies..."
|
||||
sudo -u mastodon bash << 'DEPS'
|
||||
export PATH="$HOME/.rbenv/bin:$PATH"
|
||||
eval "$(rbenv init -)"
|
||||
cd ~/live
|
||||
bundle config deployment 'true'
|
||||
bundle config without 'development test'
|
||||
bundle install -j$(nproc)
|
||||
yarn install --immutable
|
||||
DEPS
|
||||
|
||||
# Generate secrets and create .env.production
|
||||
echo "[10/12] Generating secrets and configuration..."
|
||||
SECRET_KEY=$(openssl rand -hex 64)
|
||||
OTP_SECRET=$(openssl rand -hex 64)
|
||||
VAPID_KEYS=$(sudo -u mastodon bash -c 'cd ~/live && export PATH="$HOME/.rbenv/bin:$PATH" && eval "$(rbenv init -)" && RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key 2>/dev/null')
|
||||
VAPID_PRIVATE=$(echo "$VAPID_KEYS" | grep VAPID_PRIVATE_KEY | cut -d= -f2)
|
||||
VAPID_PUBLIC=$(echo "$VAPID_KEYS" | grep VAPID_PUBLIC_KEY | cut -d= -f2)
|
||||
|
||||
AR_KEY=$(openssl rand -hex 32)
|
||||
AR_DETERMINISTIC=$(openssl rand -hex 32)
|
||||
AR_SALT=$(openssl rand -hex 32)
|
||||
|
||||
cat > /home/mastodon/live/.env.production << ENVFILE
|
||||
LOCAL_DOMAIN=$DOMAIN
|
||||
SINGLE_USER_MODE=false
|
||||
SECRET_KEY_BASE=$SECRET_KEY
|
||||
OTP_SECRET=$OTP_SECRET
|
||||
VAPID_PRIVATE_KEY=$VAPID_PRIVATE
|
||||
VAPID_PUBLIC_KEY=$VAPID_PUBLIC
|
||||
DB_HOST=/var/run/postgresql
|
||||
DB_USER=mastodon
|
||||
DB_NAME=mastodon_production
|
||||
DB_PASS=
|
||||
"REDACTED_PASSWORD"
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
SMTP_SERVER=$SMTP_SERVER
|
||||
SMTP_PORT=$SMTP_PORT
|
||||
SMTP_LOGIN=$SMTP_USER
|
||||
SMTP_PASSWORD="REDACTED_PASSWORD"
|
||||
SMTP_FROM_ADDRESS=$SMTP_FROM
|
||||
SMTP_AUTH_METHOD=plain
|
||||
SMTP_OPENSSL_VERIFY_MODE=none
|
||||
SMTP_ENABLE_STARTTLS=auto
|
||||
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=$AR_KEY
|
||||
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=$AR_DETERMINISTIC
|
||||
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=$AR_SALT
|
||||
TRUSTED_PROXY_IP=127.0.0.1,::1,192.168.0.0/16
|
||||
ENVFILE
|
||||
|
||||
chown mastodon:mastodon /home/mastodon/live/.env.production
|
||||
chmod 600 /home/mastodon/live/.env.production
|
||||
|
||||
# Run migrations and seed
|
||||
echo "[11/12] Running database migrations..."
|
||||
sudo -u mastodon bash << 'MIGRATE'
|
||||
export PATH="$HOME/.rbenv/bin:$PATH"
|
||||
eval "$(rbenv init -)"
|
||||
cd ~/live
|
||||
RAILS_ENV=production bundle exec rails db:migrate
|
||||
RAILS_ENV=production bundle exec rails db:seed
|
||||
RAILS_ENV=production bundle exec rails assets:precompile
|
||||
MIGRATE
|
||||
|
||||
# Create systemd services
|
||||
echo "[12/12] Creating systemd services..."
|
||||
cat > /etc/systemd/system/mastodon-web.service << 'SERVICE'
|
||||
[Unit]
|
||||
Description=mastodon-web
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=mastodon
|
||||
WorkingDirectory=/home/mastodon/live
|
||||
Environment="RAILS_ENV=production"
|
||||
Environment="PORT=3001"
|
||||
ExecStart=/bin/bash -lc 'cd /home/mastodon/live && exec bundle exec puma -C config/puma.rb'
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICE
|
||||
|
||||
cat > /etc/systemd/system/mastodon-sidekiq.service << 'SERVICE'
|
||||
[Unit]
|
||||
Description=mastodon-sidekiq
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=mastodon
|
||||
WorkingDirectory=/home/mastodon/live
|
||||
Environment="RAILS_ENV=production"
|
||||
Environment="MALLOC_ARENA_MAX=2"
|
||||
ExecStart=/bin/bash -lc 'cd /home/mastodon/live && exec bundle exec sidekiq -c 25'
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICE
|
||||
|
||||
cat > /etc/systemd/system/mastodon-streaming.service << 'SERVICE'
|
||||
[Unit]
|
||||
Description=mastodon-streaming
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=mastodon
|
||||
WorkingDirectory=/home/mastodon/live
|
||||
Environment="NODE_ENV=production"
|
||||
Environment="PORT=4000"
|
||||
Environment="STREAMING_CLUSTER_NUM=1"
|
||||
ExecStart=/usr/bin/node ./streaming
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICE
|
||||
|
||||
# Nginx config
|
||||
cat > /etc/nginx/conf.d/mastodon.conf << 'NGINX'
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
upstream backend {
|
||||
server 127.0.0.1:3001 fail_timeout=0;
|
||||
}
|
||||
|
||||
upstream streaming {
|
||||
server 127.0.0.1:4000 fail_timeout=0;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 3000;
|
||||
listen [::]:3000;
|
||||
server_name _;
|
||||
|
||||
keepalive_timeout 70;
|
||||
sendfile on;
|
||||
client_max_body_size 99m;
|
||||
|
||||
root /home/mastodon/live/public;
|
||||
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
|
||||
|
||||
location / {
|
||||
try_files $uri @proxy;
|
||||
}
|
||||
|
||||
location ~ ^/(assets|avatars|emoji|headers|packs|shortcuts|sounds|system)/ {
|
||||
add_header Cache-Control "public, max-age=2419200, must-revalidate";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location ^~ /api/v1/streaming {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_pass http://streaming;
|
||||
proxy_buffering off;
|
||||
proxy_redirect off;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
tcp_nodelay on;
|
||||
}
|
||||
|
||||
location @proxy {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_pass http://backend;
|
||||
proxy_buffering on;
|
||||
proxy_redirect off;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
tcp_nodelay on;
|
||||
}
|
||||
|
||||
error_page 404 500 501 502 503 504 /500.html;
|
||||
}
|
||||
NGINX
|
||||
|
||||
# SELinux and firewall
|
||||
setsebool -P httpd_can_network_connect 1
|
||||
setsebool -P httpd_read_user_content 1
|
||||
chcon -R -t httpd_sys_content_t /home/mastodon/live/public
|
||||
chmod 755 /home/mastodon /home/mastodon/live /home/mastodon/live/public
|
||||
firewall-cmd --permanent --add-port=3000/tcp
|
||||
firewall-cmd --reload
|
||||
|
||||
# Add localhost to Rails hosts
|
||||
echo 'Rails.application.config.hosts << "localhost"' >> /home/mastodon/live/config/environments/production.rb
|
||||
echo 'Rails.application.config.hosts << "127.0.0.1"' >> /home/mastodon/live/config/environments/production.rb
|
||||
chown mastodon:mastodon /home/mastodon/live/config/environments/production.rb
|
||||
|
||||
# Enable and start services
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now mastodon-web mastodon-sidekiq mastodon-streaming nginx
|
||||
|
||||
# Create admin user
|
||||
echo ""
|
||||
echo "Creating admin user..."
|
||||
ADMIN_PASS="REDACTED_PASSWORD" -u mastodon bash -c "cd ~/live && export PATH=\"\$HOME/.rbenv/bin:\$PATH\" && eval \"\$(rbenv init -)\" && RAILS_ENV=production bin/tootctl accounts create $ADMIN_USER --email=$ADMIN_EMAIL --confirmed 2>&1 | grep 'New password' | awk '{print \$3}'")
|
||||
sudo -u mastodon bash -c "cd ~/live && export PATH=\"\$HOME/.rbenv/bin:\$PATH\" && eval \"\$(rbenv init -)\" && RAILS_ENV=production bin/tootctl accounts modify $ADMIN_USER --role Owner"
|
||||
sudo -u mastodon bash -c "cd ~/live && export PATH=\"\$HOME/.rbenv/bin:\$PATH\" && eval \"\$(rbenv init -)\" && RAILS_ENV=production bin/tootctl accounts approve $ADMIN_USER"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "✅ Mastodon Installation Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Domain: $DOMAIN"
|
||||
echo "Admin User: $ADMIN_USER"
|
||||
echo "Admin Email: $ADMIN_EMAIL"
|
||||
echo "Admin Password: "REDACTED_PASSWORD"
|
||||
echo ""
|
||||
echo "Listening on port 3000 (HTTP)"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Configure your reverse proxy to forward HTTPS to port 3000"
|
||||
echo "2. Login and change your password"
|
||||
echo "3. Configure instance settings in Administration panel"
|
||||
echo ""
|
||||
723
docs/services/mastodon/install.sh
Normal file
723
docs/services/mastodon/install.sh
Normal file
@@ -0,0 +1,723 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# Mastodon Production Installer
|
||||
# =============================================================================
|
||||
# Self-hosted Mastodon instance - production ready with Docker
|
||||
#
|
||||
# Supported: Ubuntu, Debian, Fedora, Rocky/Alma/RHEL 8+, Arch, openSUSE
|
||||
# Deploys via Docker Compose
|
||||
#
|
||||
# Usage:
|
||||
# curl -fsSL <url>/install.sh | sudo bash
|
||||
#
|
||||
# Options:
|
||||
# --domain <domain> Your domain (required)
|
||||
# --email <email> Admin email / Let's Encrypt
|
||||
# --no-ssl Skip SSL (local testing only)
|
||||
# --single-user Single user mode
|
||||
# --s3 Enable S3 storage configuration
|
||||
# =============================================================================
|
||||
|
||||
set -o pipefail
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1" >&2; exit 1; }
|
||||
|
||||
# Configuration
|
||||
INSTALL_DIR="/opt/mastodon"
|
||||
DATA_DIR="/opt/mastodon-data"
|
||||
DOMAIN=""
|
||||
ADMIN_EMAIL=""
|
||||
ENABLE_SSL=true
|
||||
SINGLE_USER_MODE=false
|
||||
ENABLE_S3=false
|
||||
|
||||
# Parse arguments
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
--domain) DOMAIN="$2"; shift 2 ;;
|
||||
--email) ADMIN_EMAIL="$2"; shift 2 ;;
|
||||
--no-ssl) ENABLE_SSL=false; shift ;;
|
||||
--single-user) SINGLE_USER_MODE=true; shift ;;
|
||||
--s3) ENABLE_S3=true; shift ;;
|
||||
--help|-h)
|
||||
echo "Mastodon Production Installer"
|
||||
echo ""
|
||||
echo "Usage: install.sh [options]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --domain <domain> Your domain (e.g., mastodon.example.com)"
|
||||
echo " --email <email> Admin email for Let's Encrypt"
|
||||
echo " --no-ssl Skip SSL (testing only)"
|
||||
echo " --single-user Single user mode"
|
||||
echo " --s3 Configure S3 storage"
|
||||
exit 0
|
||||
;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Check root
|
||||
[ "$(id -u)" -ne 0 ] && error "Run as root: sudo bash install.sh"
|
||||
|
||||
# Detect OS
|
||||
detect_os() {
|
||||
if [ -f /etc/os-release ]; then
|
||||
. /etc/os-release
|
||||
OS=$ID
|
||||
OS_VERSION=${VERSION_ID:-}
|
||||
else
|
||||
error "Cannot detect OS"
|
||||
fi
|
||||
log "Detected: $OS $OS_VERSION"
|
||||
}
|
||||
|
||||
# Wait for package manager locks
|
||||
wait_for_lock() {
|
||||
case $OS in
|
||||
ubuntu|debian|linuxmint|pop)
|
||||
while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do
|
||||
sleep 2
|
||||
done
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Install Docker
|
||||
install_docker() {
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
success "Docker already installed"
|
||||
systemctl enable --now docker 2>/dev/null || true
|
||||
return
|
||||
fi
|
||||
|
||||
log "Installing Docker..."
|
||||
|
||||
case $OS in
|
||||
ubuntu|debian|linuxmint|pop)
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
wait_for_lock
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq ca-certificates curl gnupg
|
||||
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
DOCKER_OS=$OS
|
||||
case "$OS" in linuxmint|pop) DOCKER_OS="ubuntu" ;; esac
|
||||
|
||||
curl -fsSL https://download.docker.com/linux/$DOCKER_OS/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg 2>/dev/null
|
||||
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
|
||||
CODENAME=${VERSION_CODENAME:-jammy}
|
||||
case "$OS" in linuxmint|pop) CODENAME="jammy" ;; esac
|
||||
[ "$OS" = "debian" ] && case "$CODENAME" in trixie|sid) CODENAME="bookworm" ;; esac
|
||||
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$DOCKER_OS $CODENAME stable" > /etc/apt/sources.list.d/docker.list
|
||||
|
||||
wait_for_lock
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
;;
|
||||
|
||||
fedora)
|
||||
dnf install -y -q dnf-plugins-core
|
||||
dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
|
||||
dnf install -y -q docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
;;
|
||||
|
||||
rocky|almalinux|rhel|centos)
|
||||
dnf install -y -q dnf-plugins-core || yum install -y yum-utils
|
||||
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 2>/dev/null || \
|
||||
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
|
||||
dnf install -y -q docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin || \
|
||||
yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
;;
|
||||
|
||||
arch|manjaro|endeavouros)
|
||||
pacman -Sy --noconfirm docker docker-compose
|
||||
;;
|
||||
|
||||
opensuse*|sles)
|
||||
zypper install -y docker docker-compose
|
||||
;;
|
||||
|
||||
*)
|
||||
error "Unsupported OS: $OS"
|
||||
;;
|
||||
esac
|
||||
|
||||
systemctl enable --now docker
|
||||
success "Docker installed"
|
||||
}
|
||||
|
||||
# Generate secrets
|
||||
generate_secrets() {
|
||||
SECRET_KEY_BASE=$(openssl rand -hex 64)
|
||||
OTP_SECRET=$(openssl rand -hex 64)
|
||||
|
||||
# Generate VAPID keys
|
||||
VAPID_KEYS=$(docker run --rm tootsuite/mastodon:latest bundle exec rake mastodon:webpush:generate_vapid_key 2>/dev/null || echo "")
|
||||
if [ -n "$VAPID_KEYS" ]; then
|
||||
VAPID_PRIVATE_KEY=$(echo "$VAPID_KEYS" | grep VAPID_PRIVATE_KEY | cut -d= -f2)
|
||||
VAPID_PUBLIC_KEY=$(echo "$VAPID_KEYS" | grep VAPID_PUBLIC_KEY | cut -d= -f2)
|
||||
else
|
||||
VAPID_PRIVATE_KEY=$(openssl rand -hex 32)
|
||||
VAPID_PUBLIC_KEY=$(openssl rand -hex 32)
|
||||
fi
|
||||
|
||||
POSTGRES_PASSWORD="REDACTED_PASSWORD" rand -hex 32)
|
||||
REDIS_PASSWORD="REDACTED_PASSWORD" rand -hex 32)
|
||||
}
|
||||
|
||||
# Get domain interactively
|
||||
get_domain() {
|
||||
if [ -z "$DOMAIN" ]; then
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Domain Configuration"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "Enter your domain for Mastodon (e.g., mastodon.example.com)"
|
||||
echo "A domain is REQUIRED for Mastodon to work properly."
|
||||
echo ""
|
||||
read -p "Domain: " DOMAIN
|
||||
if [ -z "$DOMAIN" ]; then
|
||||
error "Domain is required for Mastodon"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$ADMIN_EMAIL" ]; then
|
||||
read -p "Admin email: " ADMIN_EMAIL
|
||||
if [ -z "$ADMIN_EMAIL" ]; then
|
||||
warn "No email provided - SSL may not work"
|
||||
ADMIN_EMAIL="admin@$DOMAIN"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Create directories
|
||||
create_directories() {
|
||||
log "Creating directories..."
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
mkdir -p "$DATA_DIR"/{postgres,redis,mastodon/{public/system,live}}
|
||||
mkdir -p "$DATA_DIR"/caddy/{data,config}
|
||||
chmod -R 755 "$DATA_DIR"
|
||||
success "Directories created"
|
||||
}
|
||||
|
||||
# Create .env file
|
||||
create_env() {
|
||||
log "Creating environment configuration..."
|
||||
|
||||
local protocol="https"
|
||||
[ "$ENABLE_SSL" != true ] && protocol="http"
|
||||
|
||||
cat > "$INSTALL_DIR/.env.production" << EOF
|
||||
# Federation
|
||||
LOCAL_DOMAIN=$DOMAIN
|
||||
SINGLE_USER_MODE=$SINGLE_USER_MODE
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD="REDACTED_PASSWORD"
|
||||
|
||||
# PostgreSQL
|
||||
DB_HOST=db
|
||||
DB_USER=mastodon
|
||||
DB_NAME=mastodon
|
||||
DB_PASS="REDACTED_PASSWORD"
|
||||
DB_PORT=5432
|
||||
|
||||
# Secrets
|
||||
SECRET_KEY_BASE=$SECRET_KEY_BASE
|
||||
OTP_SECRET=$OTP_SECRET
|
||||
VAPID_PRIVATE_KEY=$VAPID_PRIVATE_KEY
|
||||
VAPID_PUBLIC_KEY=$VAPID_PUBLIC_KEY
|
||||
|
||||
# Web
|
||||
WEB_DOMAIN=$DOMAIN
|
||||
ALTERNATE_DOMAINS=
|
||||
|
||||
# Email (configure for production)
|
||||
SMTP_SERVER=smtp.mailgun.org
|
||||
SMTP_PORT=587
|
||||
SMTP_LOGIN=
|
||||
SMTP_PASSWORD=
|
||||
"REDACTED_PASSWORD"
|
||||
SMTP_AUTH_METHOD=plain
|
||||
SMTP_OPENSSL_VERIFY_MODE=none
|
||||
SMTP_ENABLE_STARTTLS=auto
|
||||
|
||||
# File storage
|
||||
# For S3 storage, uncomment and configure:
|
||||
# S3_ENABLED=true
|
||||
# S3_BUCKET=your-bucket
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
# AWS_SECRET_ACCESS_KEY=
|
||||
# S3_REGION=us-east-1
|
||||
# S3_PROTOCOL=https
|
||||
# S3_HOSTNAME=s3.amazonaws.com
|
||||
|
||||
# Elasticsearch (optional, for full-text search)
|
||||
# ES_ENABLED=true
|
||||
# ES_HOST=elasticsearch
|
||||
# ES_PORT=9200
|
||||
|
||||
# Performance
|
||||
RAILS_ENV=production
|
||||
NODE_ENV=production
|
||||
RAILS_LOG_LEVEL=warn
|
||||
TRUSTED_PROXY_IP=172.16.0.0/12
|
||||
|
||||
# IP and session
|
||||
IP_RETENTION_PERIOD=31556952
|
||||
SESSION_RETENTION_PERIOD=31556952
|
||||
EOF
|
||||
|
||||
chmod 600 "$INSTALL_DIR/.env.production"
|
||||
success "Environment configuration created"
|
||||
}
|
||||
|
||||
# Create docker-compose.yml
|
||||
create_compose() {
|
||||
log "Creating Docker Compose file..."
|
||||
|
||||
cat > "$INSTALL_DIR/docker-compose.yml" << 'EOF'
|
||||
services:
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
container_name: mastodon-db
|
||||
shm_size: 256mb
|
||||
environment:
|
||||
POSTGRES_USER: mastodon
|
||||
POSTGRES_PASSWORD: "REDACTED_PASSWORD"
|
||||
POSTGRES_DB: mastodon
|
||||
volumes:
|
||||
- ./data/postgres:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "mastodon"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- internal
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: mastodon-redis
|
||||
command: redis-server --requirepass REDACTED_PASSWORD
|
||||
volumes:
|
||||
- ./data/redis:/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:"REDACTED_PASSWORD" "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- internal
|
||||
|
||||
web:
|
||||
image: tootsuite/mastodon:latest
|
||||
container_name: mastodon-web
|
||||
env_file: .env.production
|
||||
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
|
||||
volumes:
|
||||
- ./data/mastodon/public/system:/mastodon/public/system
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider --proxy=off localhost:3000/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
networks:
|
||||
- internal
|
||||
- external
|
||||
|
||||
streaming:
|
||||
image: tootsuite/mastodon:latest
|
||||
container_name: mastodon-streaming
|
||||
env_file: .env.production
|
||||
command: node ./streaming
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
networks:
|
||||
- internal
|
||||
- external
|
||||
|
||||
sidekiq:
|
||||
image: tootsuite/mastodon:latest
|
||||
container_name: mastodon-sidekiq
|
||||
env_file: .env.production
|
||||
command: bundle exec sidekiq
|
||||
volumes:
|
||||
- ./data/mastodon/public/system:/mastodon/public/system
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "ps aux | grep '[s]idekiq 6' || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
networks:
|
||||
- internal
|
||||
- external
|
||||
|
||||
caddy:
|
||||
image: caddy:2-alpine
|
||||
container_name: mastodon-caddy
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- ./data/caddy/data:/data
|
||||
- ./data/caddy/config:/config
|
||||
- ./data/mastodon/public:/mastodon/public:ro
|
||||
depends_on:
|
||||
- web
|
||||
- streaming
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- external
|
||||
|
||||
watchtower:
|
||||
image: containrrr/watchtower:latest
|
||||
container_name: mastodon-watchtower
|
||||
environment:
|
||||
WATCHTOWER_CLEANUP: "true"
|
||||
WATCHTOWER_SCHEDULE: "0 0 4 * * *"
|
||||
WATCHTOWER_LABEL_ENABLE: "false"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
internal:
|
||||
internal: true
|
||||
external:
|
||||
EOF
|
||||
|
||||
# Extract DB_PASS for compose
|
||||
echo "DB_PASS="REDACTED_PASSWORD" > "$INSTALL_DIR/.env"
|
||||
echo "REDIS_PASSWORD="REDACTED_PASSWORD" >> "$INSTALL_DIR/.env"
|
||||
|
||||
success "Docker Compose file created"
|
||||
}
|
||||
|
||||
# Create Caddyfile
|
||||
create_caddyfile() {
|
||||
log "Creating Caddy configuration..."
|
||||
|
||||
if [ "$ENABLE_SSL" = true ]; then
|
||||
cat > "$INSTALL_DIR/Caddyfile" << EOF
|
||||
$DOMAIN {
|
||||
encode gzip
|
||||
|
||||
handle_path /system/* {
|
||||
file_server {
|
||||
root /mastodon/public
|
||||
}
|
||||
}
|
||||
|
||||
handle /api/v1/streaming/* {
|
||||
reverse_proxy streaming:4000
|
||||
}
|
||||
|
||||
handle /* {
|
||||
reverse_proxy web:3000
|
||||
}
|
||||
|
||||
header {
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||
X-Frame-Options "SAMEORIGIN"
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-XSS-Protection "1; mode=block"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
}
|
||||
|
||||
log {
|
||||
output stdout
|
||||
}
|
||||
}
|
||||
EOF
|
||||
else
|
||||
cat > "$INSTALL_DIR/Caddyfile" << EOF
|
||||
:80 {
|
||||
encode gzip
|
||||
|
||||
handle_path /system/* {
|
||||
file_server {
|
||||
root /mastodon/public
|
||||
}
|
||||
}
|
||||
|
||||
handle /api/v1/streaming/* {
|
||||
reverse_proxy streaming:4000
|
||||
}
|
||||
|
||||
handle /* {
|
||||
reverse_proxy web:3000
|
||||
}
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
success "Caddy configuration created"
|
||||
}
|
||||
|
||||
# Initialize database
|
||||
init_database() {
|
||||
log "Initializing database..."
|
||||
cd "$INSTALL_DIR"
|
||||
|
||||
# Start database first
|
||||
docker compose up -d db redis
|
||||
sleep 10
|
||||
|
||||
# Run migrations
|
||||
docker compose run --rm web bundle exec rails db:setup SAFETY_ASSURED=1 2>/dev/null || \
|
||||
docker compose run --rm web bundle exec rails db:migrate SAFETY_ASSURED=1
|
||||
|
||||
# Precompile assets
|
||||
docker compose run --rm web bundle exec rails assets:precompile
|
||||
|
||||
success "Database initialized"
|
||||
}
|
||||
|
||||
# Create management script
|
||||
create_management_script() {
|
||||
log "Creating management script..."
|
||||
|
||||
cat > /usr/local/bin/mastodon << 'EOF'
|
||||
#!/bin/bash
|
||||
cd /opt/mastodon || exit 1
|
||||
|
||||
case "${1:-help}" in
|
||||
start) docker compose up -d ;;
|
||||
stop) docker compose down ;;
|
||||
restart) docker compose restart ${2:-} ;;
|
||||
status) docker compose ps ;;
|
||||
logs) docker compose logs -f ${2:-} ;;
|
||||
update)
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
docker compose run --rm web bundle exec rails db:migrate
|
||||
docker compose run --rm web bundle exec rails assets:precompile
|
||||
docker compose restart
|
||||
;;
|
||||
edit) ${EDITOR:-nano} /opt/mastodon/.env.production ;;
|
||||
|
||||
admin)
|
||||
if [ -z "$2" ]; then
|
||||
echo "Usage: mastodon admin <username>"
|
||||
exit 1
|
||||
fi
|
||||
docker compose run --rm web bin/tootctl accounts create "$2" --email "${3:-admin@localhost}" --confirmed --role Owner
|
||||
;;
|
||||
|
||||
reset-password)
|
||||
if [ -z "$2" ]; then
|
||||
echo "Usage: mastodon reset-password <username>"
|
||||
exit 1
|
||||
fi
|
||||
docker compose run --rm web bin/tootctl accounts modify "$2" --reset-password
|
||||
;;
|
||||
|
||||
tootctl)
|
||||
shift
|
||||
docker compose run --rm web bin/tootctl "$@"
|
||||
;;
|
||||
|
||||
console)
|
||||
docker compose run --rm web bin/rails console
|
||||
;;
|
||||
|
||||
shell)
|
||||
docker compose run --rm web /bin/bash
|
||||
;;
|
||||
|
||||
backup)
|
||||
timestamp=$(date +"%Y%m%d_%H%M%S")
|
||||
backup_dir="/opt/mastodon-data/backups"
|
||||
mkdir -p "$backup_dir"
|
||||
|
||||
echo "Backing up database..."
|
||||
docker compose exec -T db pg_dump -U mastodon mastodon > "$backup_dir/mastodon_db_$timestamp.sql"
|
||||
|
||||
echo "Backing up media..."
|
||||
tar -czf "$backup_dir/mastodon_media_$timestamp.tar.gz" -C /opt/mastodon-data mastodon/public/system
|
||||
|
||||
echo "Backup complete: $backup_dir"
|
||||
ls -la "$backup_dir"/*$timestamp*
|
||||
;;
|
||||
|
||||
cleanup)
|
||||
echo "Cleaning up old media..."
|
||||
docker compose run --rm web bin/tootctl media remove --days=7
|
||||
docker compose run --rm web bin/tootctl preview_cards remove --days=30
|
||||
docker compose run --rm web bin/tootctl statuses remove --days=90
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Mastodon Management"
|
||||
echo ""
|
||||
echo "Usage: mastodon <command>"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " start Start all services"
|
||||
echo " stop Stop all services"
|
||||
echo " restart [service] Restart services"
|
||||
echo " status Show status"
|
||||
echo " logs [service] View logs"
|
||||
echo " update Update and migrate"
|
||||
echo " edit Edit configuration"
|
||||
echo " admin <user> Create admin user"
|
||||
echo " reset-password <u> Reset user password"
|
||||
echo " tootctl <args> Run tootctl command"
|
||||
echo " console Rails console"
|
||||
echo " shell Bash shell"
|
||||
echo " backup Backup database and media"
|
||||
echo " cleanup Clean old media/statuses"
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
|
||||
chmod +x /usr/local/bin/mastodon
|
||||
success "Management script created"
|
||||
}
|
||||
|
||||
# Configure firewall
|
||||
configure_firewall() {
|
||||
log "Configuring firewall..."
|
||||
|
||||
if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld 2>/dev/null; then
|
||||
firewall-cmd --permanent --add-service=http 2>/dev/null || true
|
||||
firewall-cmd --permanent --add-service=https 2>/dev/null || true
|
||||
firewall-cmd --reload 2>/dev/null || true
|
||||
success "Firewall configured (firewalld)"
|
||||
elif command -v ufw >/dev/null 2>&1 && ufw status | grep -q "active"; then
|
||||
ufw allow 80/tcp 2>/dev/null || true
|
||||
ufw allow 443/tcp 2>/dev/null || true
|
||||
success "Firewall configured (ufw)"
|
||||
else
|
||||
warn "No active firewall detected"
|
||||
fi
|
||||
}
|
||||
|
||||
# Deploy
|
||||
deploy() {
|
||||
log "Deploying Mastodon..."
|
||||
cd "$INSTALL_DIR"
|
||||
|
||||
# Copy data directory reference
|
||||
ln -sf "$DATA_DIR" "$INSTALL_DIR/data" 2>/dev/null || true
|
||||
mkdir -p "$INSTALL_DIR/data"
|
||||
ln -sf "$DATA_DIR/postgres" "$INSTALL_DIR/data/postgres"
|
||||
ln -sf "$DATA_DIR/redis" "$INSTALL_DIR/data/redis"
|
||||
ln -sf "$DATA_DIR/mastodon" "$INSTALL_DIR/data/mastodon"
|
||||
ln -sf "$DATA_DIR/caddy" "$INSTALL_DIR/data/caddy"
|
||||
|
||||
docker compose pull
|
||||
|
||||
# Initialize database
|
||||
init_database
|
||||
|
||||
# Start all services
|
||||
docker compose up -d
|
||||
|
||||
# Wait for services
|
||||
log "Waiting for services to start..."
|
||||
sleep 15
|
||||
|
||||
success "Mastodon deployed!"
|
||||
}
|
||||
|
||||
# Show completion message
|
||||
show_complete() {
|
||||
local protocol="https"
|
||||
[ "$ENABLE_SSL" != true ] && protocol="http"
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Mastodon Installation Complete!"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "Access:"
|
||||
echo " Web Interface: ${protocol}://${DOMAIN}"
|
||||
echo ""
|
||||
echo "Create your admin account:"
|
||||
echo " mastodon admin yourusername your@email.com"
|
||||
echo ""
|
||||
echo "Then reset password to get initial password:"
|
||||
echo " mastodon reset-password yourusername"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " mastodon status - Show service status"
|
||||
echo " mastodon logs - View logs"
|
||||
echo " mastodon update - Update Mastodon"
|
||||
echo " mastodon backup - Backup database"
|
||||
echo " mastodon cleanup - Clean old media"
|
||||
echo " mastodon tootctl - Run tootctl commands"
|
||||
echo ""
|
||||
echo "Config: $INSTALL_DIR/.env.production"
|
||||
echo "Data: $DATA_DIR"
|
||||
echo ""
|
||||
echo "⚠️ Configure email in .env.production for:"
|
||||
echo " - Email notifications"
|
||||
echo " - Password resets"
|
||||
echo " - Account confirmations"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Main
|
||||
main() {
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Mastodon Production Installer"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
detect_os
|
||||
get_domain
|
||||
generate_secrets
|
||||
install_docker
|
||||
create_directories
|
||||
create_env
|
||||
create_compose
|
||||
create_caddyfile
|
||||
create_management_script
|
||||
configure_firewall
|
||||
deploy
|
||||
show_complete
|
||||
}
|
||||
|
||||
main "$@"
|
||||
105
docs/services/mastodon/update-mastodon.sh
Executable file
105
docs/services/mastodon/update-mastodon.sh
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/bin/bash
|
||||
# Mastodon Update Script
|
||||
# Updates Mastodon to the latest stable version (or specified version)
|
||||
# Run as root
|
||||
|
||||
set -e
|
||||
|
||||
TARGET_VERSION="${1:-}"
|
||||
MASTODON_DIR="/home/mastodon/live"
|
||||
|
||||
echo "=========================================="
|
||||
echo "Mastodon Update Script"
|
||||
echo "=========================================="
|
||||
|
||||
# Check current version
|
||||
CURRENT_VERSION=$(cd $MASTODON_DIR && git describe --tags 2>/dev/null || echo "unknown")
|
||||
echo "Current version: $CURRENT_VERSION"
|
||||
|
||||
# Get latest version if not specified
|
||||
if [ -z "$TARGET_VERSION" ]; then
|
||||
echo "Fetching latest version..."
|
||||
cd $MASTODON_DIR
|
||||
sudo -u mastodon git fetch --tags
|
||||
TARGET_VERSION=$(git tag -l 'v*' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)
|
||||
fi
|
||||
|
||||
echo "Target version: $TARGET_VERSION"
|
||||
|
||||
if [ "$CURRENT_VERSION" = "$TARGET_VERSION" ]; then
|
||||
echo "Already at version $TARGET_VERSION. Nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
read -p "Proceed with update? (y/N) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Update cancelled."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create backup first
|
||||
echo ""
|
||||
echo "[1/7] Creating backup before update..."
|
||||
/home/mastodon/scripts/backup-mastodon.sh || echo "Backup script not found, skipping..."
|
||||
|
||||
# Stop services
|
||||
echo ""
|
||||
echo "[2/7] Stopping Mastodon services..."
|
||||
systemctl stop mastodon-web mastodon-sidekiq mastodon-streaming
|
||||
|
||||
# Update code
|
||||
echo ""
|
||||
echo "[3/7] Updating Mastodon code..."
|
||||
cd $MASTODON_DIR
|
||||
sudo -u mastodon git fetch --all
|
||||
sudo -u mastodon git checkout $TARGET_VERSION
|
||||
|
||||
# Update Ruby dependencies
|
||||
echo ""
|
||||
echo "[4/7] Updating Ruby dependencies..."
|
||||
sudo -u mastodon bash -lc "cd ~/live && bundle install"
|
||||
|
||||
# Update Node dependencies
|
||||
echo ""
|
||||
echo "[5/7] Updating Node dependencies..."
|
||||
sudo -u mastodon bash -lc "cd ~/live && yarn install --immutable"
|
||||
|
||||
# Run database migrations
|
||||
echo ""
|
||||
echo "[6/7] Running database migrations..."
|
||||
sudo -u mastodon bash -lc "cd ~/live && RAILS_ENV=production bundle exec rails db:migrate"
|
||||
|
||||
# Precompile assets
|
||||
echo ""
|
||||
echo "[7/7] Precompiling assets (this may take a few minutes)..."
|
||||
sudo -u mastodon bash -lc "cd ~/live && RAILS_ENV=production bundle exec rails assets:precompile"
|
||||
|
||||
# Fix SELinux contexts
|
||||
chcon -R -t httpd_sys_content_t /home/mastodon/live/public
|
||||
|
||||
# Start services
|
||||
echo ""
|
||||
echo "Starting Mastodon services..."
|
||||
systemctl start mastodon-web mastodon-sidekiq mastodon-streaming
|
||||
|
||||
# Verify
|
||||
sleep 5
|
||||
echo ""
|
||||
echo "Checking service status..."
|
||||
systemctl is-active mastodon-web mastodon-sidekiq mastodon-streaming
|
||||
|
||||
NEW_VERSION=$(cd $MASTODON_DIR && git describe --tags 2>/dev/null || echo "unknown")
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "✅ Update Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Previous version: $CURRENT_VERSION"
|
||||
echo "New version: $NEW_VERSION"
|
||||
echo ""
|
||||
echo "Please verify your instance is working correctly."
|
||||
echo "Check the release notes for any manual steps:"
|
||||
echo "https://github.com/mastodon/mastodon/releases/tag/$TARGET_VERSION"
|
||||
echo ""
|
||||
185
docs/services/mastodon/verify-mastodon.sh
Executable file
185
docs/services/mastodon/verify-mastodon.sh
Executable file
@@ -0,0 +1,185 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# Mastodon Health Check / Verification Script
|
||||
# =============================================================================
|
||||
# Run as root
|
||||
|
||||
echo "=========================================="
|
||||
echo "Mastodon Health Check"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
FAILED=0
|
||||
WARN=0
|
||||
|
||||
# Load domain from .env if available
|
||||
if [ -f /home/mastodon/live/.env.production ]; then
|
||||
DOMAIN=$(grep "^LOCAL_DOMAIN=" /home/mastodon/live/.env.production | cut -d= -f2)
|
||||
echo "Domain: ${DOMAIN:-unknown}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[Service Status]"
|
||||
services=("postgresql" "valkey" "nginx" "mastodon-web" "mastodon-sidekiq" "mastodon-streaming")
|
||||
for svc in "${services[@]}"; do
|
||||
STATUS=$(systemctl is-active $svc 2>/dev/null || echo "not-found")
|
||||
if [ "$STATUS" = "active" ]; then
|
||||
echo " ✓ $svc: running"
|
||||
elif [ "$STATUS" = "not-found" ]; then
|
||||
echo " - $svc: not installed"
|
||||
else
|
||||
echo " ✗ $svc: $STATUS"
|
||||
FAILED=1
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "[API Endpoints]"
|
||||
|
||||
# Instance API
|
||||
INSTANCE=$(curl -sf http://127.0.0.1:3000/api/v1/instance 2>/dev/null)
|
||||
if [ -n "$INSTANCE" ]; then
|
||||
VERSION=$(echo "$INSTANCE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('version','unknown'))" 2>/dev/null)
|
||||
USERS=$(echo "$INSTANCE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('stats',{}).get('user_count',0))" 2>/dev/null)
|
||||
echo " ✓ Instance API: responding (v$VERSION, $USERS users)"
|
||||
else
|
||||
echo " ✗ Instance API: not responding"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# Streaming API
|
||||
STREAMING=$(curl -sf http://127.0.0.1:4000/api/v1/streaming/health 2>/dev/null)
|
||||
if [ -n "$STREAMING" ]; then
|
||||
echo " ✓ Streaming API: healthy"
|
||||
else
|
||||
echo " ✗ Streaming API: not responding"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# Nginx proxy
|
||||
NGINX_CHECK=$(curl -sf -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/ 2>/dev/null)
|
||||
if [ "$NGINX_CHECK" = "200" ] || [ "$NGINX_CHECK" = "302" ]; then
|
||||
echo " ✓ Nginx proxy: working (HTTP $NGINX_CHECK)"
|
||||
else
|
||||
echo " ✗ Nginx proxy: not working (HTTP $NGINX_CHECK)"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[Database]"
|
||||
if systemctl is-active --quiet postgresql; then
|
||||
DB_SIZE=$(sudo -u postgres psql -t -c "SELECT pg_size_pretty(pg_database_size('mastodon_production'));" 2>/dev/null | xargs)
|
||||
ACCOUNTS=$(sudo -u postgres psql -t -d mastodon_production -c "SELECT COUNT(*) FROM accounts;" 2>/dev/null | xargs)
|
||||
STATUSES=$(sudo -u postgres psql -t -d mastodon_production -c "SELECT COUNT(*) FROM statuses;" 2>/dev/null | xargs)
|
||||
echo " ✓ PostgreSQL: running (DB: ${DB_SIZE:-unknown})"
|
||||
echo " Accounts: ${ACCOUNTS:-0}, Statuses: ${STATUSES:-0}"
|
||||
else
|
||||
echo " ✗ PostgreSQL: not running"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[Cache]"
|
||||
if systemctl is-active --quiet valkey; then
|
||||
VALKEY_INFO=$(valkey-cli INFO server 2>/dev/null | grep valkey_version | cut -d: -f2 | tr -d '\r')
|
||||
echo " ✓ Valkey: running (v${VALKEY_INFO:-unknown})"
|
||||
elif systemctl is-active --quiet redis; then
|
||||
REDIS_INFO=$(redis-cli INFO server 2>/dev/null | grep redis_version | cut -d: -f2 | tr -d '\r')
|
||||
echo " ✓ Redis: running (v${REDIS_INFO:-unknown})"
|
||||
else
|
||||
echo " ✗ Valkey/Redis: not running"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[Sidekiq Jobs]"
|
||||
# Check sidekiq process
|
||||
SIDEKIQ_PID=$(pgrep -f "sidekiq.*live" 2>/dev/null)
|
||||
if [ -n "$SIDEKIQ_PID" ]; then
|
||||
SIDEKIQ_MEM=$(ps -p $SIDEKIQ_PID -o rss= 2>/dev/null | awk '{printf "%.0fMB", $1/1024}')
|
||||
echo " ✓ Sidekiq: running (PID: $SIDEKIQ_PID, Mem: $SIDEKIQ_MEM)"
|
||||
else
|
||||
echo " ✗ Sidekiq: not running"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[Federation]"
|
||||
# Check webfinger
|
||||
if [ -n "$DOMAIN" ]; then
|
||||
WF_CHECK=$(curl -sf -H "Accept: application/jrd+json" "http://127.0.0.1:3000/.well-known/webfinger?resource=acct:test@$DOMAIN" 2>/dev/null | head -c 50)
|
||||
if [ -n "$WF_CHECK" ]; then
|
||||
echo " ✓ Webfinger: responding"
|
||||
else
|
||||
echo " - Webfinger: no test account (may be normal)"
|
||||
fi
|
||||
|
||||
# Check host-meta
|
||||
HOSTMETA=$(curl -sf "http://127.0.0.1:3000/.well-known/host-meta" 2>/dev/null | head -c 50)
|
||||
if [ -n "$HOSTMETA" ]; then
|
||||
echo " ✓ Host-meta: configured"
|
||||
else
|
||||
echo " ✗ Host-meta: not responding"
|
||||
WARN=1
|
||||
fi
|
||||
|
||||
# Check nodeinfo
|
||||
NODEINFO=$(curl -sf "http://127.0.0.1:3000/nodeinfo/2.0" 2>/dev/null)
|
||||
if [ -n "$NODEINFO" ]; then
|
||||
echo " ✓ NodeInfo: available"
|
||||
else
|
||||
echo " ✗ NodeInfo: not responding"
|
||||
WARN=1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[Storage]"
|
||||
if [ -d /home/mastodon/live/public/system ]; then
|
||||
MEDIA_SIZE=$(du -sh /home/mastodon/live/public/system 2>/dev/null | cut -f1)
|
||||
echo " Media storage: ${MEDIA_SIZE:-empty}"
|
||||
else
|
||||
echo " Media storage: not yet created"
|
||||
fi
|
||||
|
||||
DISK_USAGE=$(df -h /home 2>/dev/null | tail -1 | awk '{print $5}')
|
||||
echo " Disk usage (/home): ${DISK_USAGE:-unknown}"
|
||||
|
||||
echo ""
|
||||
echo "[Configuration]"
|
||||
if [ -f /home/mastodon/live/.env.production ]; then
|
||||
echo " ✓ .env.production exists"
|
||||
|
||||
# Check critical settings
|
||||
SECRET_KEY=$(grep "^SECRET_KEY_BASE=" /home/mastodon/live/.env.production | cut -d= -f2)
|
||||
if [ -n "$SECRET_KEY" ] && [ ${#SECRET_KEY} -gt 50 ]; then
|
||||
echo " ✓ SECRET_KEY_BASE: configured"
|
||||
else
|
||||
echo " ✗ SECRET_KEY_BASE: missing or invalid"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
VAPID_KEY=$(grep "^VAPID_PRIVATE_KEY=" /home/mastodon/live/.env.production | cut -d= -f2)
|
||||
if [ -n "$VAPID_KEY" ]; then
|
||||
echo " ✓ VAPID keys: configured"
|
||||
else
|
||||
echo " ✗ VAPID keys: missing"
|
||||
WARN=1
|
||||
fi
|
||||
else
|
||||
echo " ✗ .env.production: not found"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
if [ $FAILED -eq 0 ] && [ $WARN -eq 0 ]; then
|
||||
echo "✅ All checks passed!"
|
||||
elif [ $FAILED -eq 0 ]; then
|
||||
echo "⚠️ Passed with warnings"
|
||||
else
|
||||
echo "❌ Some checks failed"
|
||||
fi
|
||||
echo "=========================================="
|
||||
|
||||
exit $FAILED
|
||||
Reference in New Issue
Block a user