Files
linux-scripts/deploy-freshrss.sh
T
chiefgeek a1a17e81a1 Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
2026-05-25 03:31:08 +02:00

399 lines
12 KiB
Bash

#!/usr/bin/env bash
#########################################################################################
#### deploy-freshrss.sh — Deploy FreshRSS with Docker Compose + Nginx reverse proxy ####
#### Checks for existing configs, never overwrites. Safe to run on existing setups. ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### License: MIT ####
#### Version 1.00 ####
#### ####
#### Usage: ####
#### sudo ./deploy-freshrss.sh --domain rss.example.com ####
#### sudo ./deploy-freshrss.sh --domain rss.example.com --port 8081 ####
#### sudo ./deploy-freshrss.sh --dry-run --domain rss.example.com ####
#### sudo ./deploy-freshrss.sh --remove ####
#### ####
#### See --help for all options. ####
#########################################################################################
set -euo pipefail
DOMAIN=""
PORT="8080"
INSTALL_DIR="/opt/freshrss"
DB_PASSWORD=""
TZ="UTC"
CRON_MIN="*/15"
DRY_RUN=false
REMOVE=false
SKIP_NGINX=false
SKIP_SSL=false
# Colors
if [[ -t 1 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BOLD='\033[1m'
RESET='\033[0m'
else
RED="" GREEN="" YELLOW="" BOLD="" RESET=""
fi
log() { echo -e "${GREEN}[OK]${RESET} $*"; }
warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; }
err() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
info() { echo -e "${BOLD}[INFO]${RESET} $*"; }
usage() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Deploy FreshRSS with Docker Compose, PostgreSQL, and Nginx reverse proxy.
Installs:
1. Docker Compose stack (FreshRSS + PostgreSQL) in /opt/freshrss/
2. Nginx reverse proxy config in /etc/nginx/conf.d/
3. Let's Encrypt SSL certificate (optional)
Options:
--domain DOMAIN Domain/subdomain for FreshRSS (required for install)
--port PORT Local port for FreshRSS container (default: 8080)
--install-dir PATH Installation directory (default: /opt/freshrss)
--db-password PASS PostgreSQL password (default: auto-generated)
--timezone TZ Timezone (default: UTC)
--cron-min PATTERN Feed update schedule (default: */15)
--skip-nginx Skip Nginx config (manual reverse proxy)
--skip-ssl Skip Let's Encrypt certificate
--dry-run Show what would be done without making changes
--remove Remove FreshRSS deployment
-h, --help Show this help
Examples:
$(basename "$0") --domain rss.example.com
$(basename "$0") --domain rss.example.com --port 8081 --timezone America/Chicago
$(basename "$0") --domain rss.example.com --skip-ssl
$(basename "$0") --dry-run --domain rss.example.com
$(basename "$0") --remove
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--domain) DOMAIN="$2"; shift ;;
--port) PORT="$2"; shift ;;
--install-dir) INSTALL_DIR="$2"; shift ;;
--db-password) DB_PASSWORD="$2"; shift ;;
--timezone) TZ="$2"; shift ;;
--cron-min) CRON_MIN="$2"; shift ;;
--skip-nginx) SKIP_NGINX=true ;;
--skip-ssl) SKIP_SSL=true ;;
--dry-run) DRY_RUN=true ;;
--remove) REMOVE=true ;;
-h|--help) usage; exit 0 ;;
*) err "Unknown option: $1"; usage; exit 1 ;;
esac
shift
done
if [[ $EUID -ne 0 ]]; then
err "Must run as root (sudo)"
exit 1
fi
# -- Remove mode --
if [[ "$REMOVE" == "true" ]]; then
info "Removing FreshRSS deployment..."
echo ""
if [[ -f "${INSTALL_DIR}/docker-compose.yml" ]]; then
if [[ "$DRY_RUN" == "true" ]]; then
info "Would run: docker compose down in ${INSTALL_DIR}"
else
cd "$INSTALL_DIR"
docker compose down 2>/dev/null || true
log "Stopped FreshRSS containers"
fi
fi
# Remove nginx config
for f in /etc/nginx/conf.d/freshrss.conf /etc/nginx/sites-enabled/freshrss.conf /etc/nginx/sites-available/freshrss.conf; do
if [[ -f "$f" ]]; then
if [[ "$DRY_RUN" == "true" ]]; then
info "Would remove: $f"
else
rm -f "$f"
log "Removed $f"
fi
fi
done
if [[ "$DRY_RUN" != "true" ]] && command -v nginx &>/dev/null; then
nginx -t 2>/dev/null && systemctl reload nginx 2>/dev/null && log "Reloaded Nginx"
fi
echo ""
if [[ "$DRY_RUN" != "true" ]]; then
log "Containers stopped and Nginx config removed."
info "Data preserved at ${INSTALL_DIR}/ - remove manually if desired:"
echo " rm -rf ${INSTALL_DIR}"
fi
exit 0
fi
# -- Validation --
if [[ -z "$DOMAIN" ]]; then
err "Domain is required: --domain rss.example.com"
exit 1
fi
if ! command -v docker &>/dev/null; then
err "Docker is not installed. Install Docker first."
exit 1
fi
if ! docker compose version &>/dev/null 2>&1; then
err "Docker Compose v2 is not available. Install docker-compose-plugin."
exit 1
fi
# Generate DB password if not provided
if [[ -z "$DB_PASSWORD" ]]; then
DB_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=' | head -c 24)
fi
# -- Install mode --
info "Deploying FreshRSS..."
echo ""
info "Domain: ${DOMAIN}"
info "Port: ${PORT}"
info "Install dir: ${INSTALL_DIR}"
info "Timezone: ${TZ}"
info "Feed cron: ${CRON_MIN}"
echo ""
# 1. Create directory
if [[ -d "$INSTALL_DIR" ]]; then
info "Directory ${INSTALL_DIR} already exists"
else
if [[ "$DRY_RUN" == "true" ]]; then
info "Would create: ${INSTALL_DIR}"
else
mkdir -p "$INSTALL_DIR"
log "Created ${INSTALL_DIR}"
fi
fi
# 2. Docker Compose file
COMPOSE_FILE="${INSTALL_DIR}/docker-compose.yml"
if [[ -f "$COMPOSE_FILE" ]]; then
info "docker-compose.yml already exists - skipping (delete to recreate)"
else
if [[ "$DRY_RUN" == "true" ]]; then
info "Would create: ${COMPOSE_FILE}"
else
cat > "$COMPOSE_FILE" <<YAML
services:
freshrss:
image: freshrss/freshrss:latest
container_name: freshrss
restart: unless-stopped
ports:
- "${PORT}:80"
volumes:
- ./data:/var/www/FreshRSS/data
- ./extensions:/var/www/FreshRSS/extensions
environment:
- TZ=${TZ}
- CRON_MIN=${CRON_MIN}
depends_on:
freshrss-db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80"]
interval: 30s
timeout: 10s
retries: 3
freshrss-db:
image: postgres:16-alpine
container_name: freshrss-db
restart: unless-stopped
volumes:
- ./postgres-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=freshrss
- POSTGRES_USER=freshrss
- POSTGRES_PASSWORD=${DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U freshrss"]
interval: 10s
timeout: 5s
retries: 5
YAML
log "Created ${COMPOSE_FILE}"
fi
fi
# 3. Start containers
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^freshrss$'; then
info "FreshRSS container is already running"
else
if [[ "$DRY_RUN" == "true" ]]; then
info "Would run: docker compose up -d"
else
cd "$INSTALL_DIR"
docker compose up -d
log "Started FreshRSS containers"
fi
fi
# 4. Nginx reverse proxy
if [[ "$SKIP_NGINX" == "true" ]]; then
info "Skipping Nginx config (--skip-nginx)"
elif ! command -v nginx &>/dev/null; then
warn "Nginx not installed - skipping reverse proxy config"
SKIP_NGINX=true
else
NGINX_CONF=""
# Detect config directory style
if [[ -d /etc/nginx/conf.d ]]; then
NGINX_CONF="/etc/nginx/conf.d/freshrss.conf"
elif [[ -d /etc/nginx/sites-available ]]; then
NGINX_CONF="/etc/nginx/sites-available/freshrss.conf"
else
warn "Could not detect Nginx config directory - skipping"
SKIP_NGINX=true
fi
if [[ "$SKIP_NGINX" != "true" && -n "$NGINX_CONF" ]]; then
if [[ -f "$NGINX_CONF" ]]; then
info "Nginx config already exists at ${NGINX_CONF} - skipping"
else
if [[ "$DRY_RUN" == "true" ]]; then
info "Would create: ${NGINX_CONF}"
else
if [[ "$SKIP_SSL" == "true" ]]; then
# HTTP only
cat > "$NGINX_CONF" <<NGINX
server {
listen 80;
server_name ${DOMAIN};
location / {
proxy_pass http://127.0.0.1:${PORT};
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 \$scheme;
}
}
NGINX
else
# HTTPS with placeholder (certbot will fill in)
cat > "$NGINX_CONF" <<NGINX
server {
listen 80;
server_name ${DOMAIN};
return 301 https://\$host\$request_uri;
}
server {
listen 443 ssl http2;
server_name ${DOMAIN};
ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;
location / {
proxy_pass http://127.0.0.1:${PORT};
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 \$scheme;
}
}
NGINX
fi
log "Created ${NGINX_CONF}"
# Symlink if sites-available style
if [[ "$NGINX_CONF" == *sites-available* ]]; then
ln -sf "$NGINX_CONF" /etc/nginx/sites-enabled/freshrss.conf
log "Symlinked to sites-enabled"
fi
fi
fi
fi
fi
# 5. SSL certificate
if [[ "$SKIP_SSL" != "true" && "$SKIP_NGINX" != "true" ]]; then
if [[ -d "/etc/letsencrypt/live/${DOMAIN}" ]]; then
info "SSL certificate already exists for ${DOMAIN}"
elif command -v certbot &>/dev/null; then
if [[ "$DRY_RUN" == "true" ]]; then
info "Would run: certbot certonly --nginx -d ${DOMAIN}"
else
certbot certonly --nginx -d "$DOMAIN" --non-interactive --agree-tos --register-unsafely-without-email || {
warn "Certbot failed - configure SSL manually"
warn "Run: certbot certonly --nginx -d ${DOMAIN}"
}
fi
else
warn "certbot not installed - configure SSL manually"
fi
fi
# 6. Reload Nginx
if [[ "$SKIP_NGINX" != "true" && "$DRY_RUN" != "true" ]]; then
if nginx -t 2>/dev/null; then
systemctl reload nginx
log "Reloaded Nginx"
else
warn "Nginx config test failed - check config manually"
fi
fi
# -- Summary --
echo ""
echo -e "${BOLD}Deployment summary:${RESET}"
echo " Docker Compose: ${INSTALL_DIR}/docker-compose.yml"
echo " FreshRSS: http://127.0.0.1:${PORT}"
if [[ "$SKIP_NGINX" != "true" ]]; then
if [[ "$SKIP_SSL" == "true" ]]; then
echo " Public URL: http://${DOMAIN}"
else
echo " Public URL: https://${DOMAIN}"
fi
echo " Nginx config: ${NGINX_CONF:-/etc/nginx/conf.d/freshrss.conf}"
fi
echo " Database: PostgreSQL (freshrss-db container)"
echo " Feed updates: Every ${CRON_MIN} minutes"
echo " Data directory: ${INSTALL_DIR}/data/"
echo ""
echo -e "${BOLD}Next steps:${RESET}"
if [[ "$SKIP_SSL" == "true" ]]; then
echo " 1. Open http://${DOMAIN} and complete the setup wizard"
else
echo " 1. Open https://${DOMAIN} and complete the setup wizard"
fi
echo " 2. Database config in wizard:"
echo " Type: PostgreSQL"
echo " Host: freshrss-db"
echo " Database: freshrss"
echo " User: freshrss"
echo " Password: (saved in ${INSTALL_DIR}/docker-compose.yml)"
echo " 3. Create your admin account"
echo " 4. Add your first feed: https://mylinux.work/index.xml"
echo ""
info "Remove with: $(basename "$0") --remove"