mirror of
https://github.com/sartoopjj/thefeed.git
synced 2026-05-18 07:34:35 +03:00
872 lines
33 KiB
Bash
872 lines
33 KiB
Bash
#!/bin/bash
|
|
|
|
red='\033[0;31m'
|
|
green='\033[0;32m'
|
|
blue='\033[0;34m'
|
|
yellow='\033[0;33m'
|
|
plain='\033[0m'
|
|
|
|
GITHUB_REPO="sartoopjj/thefeed"
|
|
INSTALL_DIR="/opt/thefeed"
|
|
DATA_DIR="${INSTALL_DIR}/data"
|
|
SERVICE_FILE="/etc/systemd/system/thefeed-server.service"
|
|
|
|
# check root
|
|
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error:${plain} Please run this script with root privilege" && exit 1
|
|
|
|
# Check OS and set release variable
|
|
if [[ -f /etc/os-release ]]; then
|
|
source /etc/os-release
|
|
release=$ID
|
|
elif [[ -f /usr/lib/os-release ]]; then
|
|
source /usr/lib/os-release
|
|
release=$ID
|
|
else
|
|
echo -e "${red}Failed to check the system OS, please contact the author!${plain}" >&2
|
|
exit 1
|
|
fi
|
|
echo -e "OS: ${green}$release${plain}"
|
|
|
|
arch() {
|
|
case "$(uname -m)" in
|
|
x86_64 | x64 | amd64) echo 'amd64' ;;
|
|
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
|
|
*) echo -e "${red}Unsupported CPU architecture: $(uname -m)${plain}" && exit 1 ;;
|
|
esac
|
|
}
|
|
|
|
echo -e "Arch: ${green}$(arch)${plain}"
|
|
|
|
install_base() {
|
|
echo -e "${green}Installing base dependencies...${plain}"
|
|
case "${release}" in
|
|
ubuntu | debian | armbian)
|
|
apt-get update && apt-get install -y -q curl tar ca-certificates
|
|
;;
|
|
fedora | amzn | rhel | almalinux | rocky | ol)
|
|
dnf -y update && dnf install -y -q curl tar ca-certificates
|
|
;;
|
|
centos)
|
|
if [[ "${VERSION_ID}" =~ ^7 ]]; then
|
|
yum -y update && yum install -y curl tar ca-certificates
|
|
else
|
|
dnf -y update && dnf install -y -q curl tar ca-certificates
|
|
fi
|
|
;;
|
|
arch | manjaro | parch)
|
|
pacman -Syu --noconfirm curl tar ca-certificates
|
|
;;
|
|
alpine)
|
|
apk update && apk add curl tar ca-certificates bash
|
|
;;
|
|
*)
|
|
apt-get update && apt-get install -y -q curl tar ca-certificates
|
|
;;
|
|
esac
|
|
}
|
|
|
|
get_latest_version() {
|
|
local version
|
|
version=$(curl -Ls "https://api.github.com/repos/${GITHUB_REPO}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
|
if [[ -z "$version" ]]; then
|
|
echo -e "${yellow}Trying with IPv4...${plain}" >&2
|
|
version=$(curl -4 -Ls "https://api.github.com/repos/${GITHUB_REPO}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
|
fi
|
|
echo "$version"
|
|
}
|
|
|
|
_fetch_releases() {
|
|
local body
|
|
body=$(curl -Ls "https://api.github.com/repos/${GITHUB_REPO}/releases?per_page=20")
|
|
if [[ -z "$body" ]]; then
|
|
body=$(curl -4 -Ls "https://api.github.com/repos/${GITHUB_REPO}/releases?per_page=20")
|
|
fi
|
|
echo "$body"
|
|
}
|
|
|
|
# Normalise GitHub JSON (pretty or minified) to one release object per line.
|
|
_split_releases() {
|
|
_fetch_releases | tr -d '\n' | sed 's/{/\n{/g'
|
|
}
|
|
|
|
get_latest_prerelease() {
|
|
# GitHub's pretty-printed JSON has "prerelease": true (with space). After
|
|
# tr -d '\n' the spaces stay, so the grep needs to allow whitespace.
|
|
_split_releases \
|
|
| grep -E '"prerelease":[[:space:]]*true' \
|
|
| head -1 \
|
|
| sed -E 's/.*"tag_name":[[:space:]]*"([^"]+)".*/\1/'
|
|
}
|
|
|
|
list_versions() {
|
|
echo -e "${green}Recent thefeed releases (most recent first):${plain}"
|
|
local line tag label
|
|
while IFS= read -r line; do
|
|
case "$line" in
|
|
*'"tag_name"'*) ;;
|
|
*) continue ;;
|
|
esac
|
|
tag=$(echo "$line" | sed -E 's/.*"tag_name":[[:space:]]*"([^"]+)".*/\1/')
|
|
if echo "$line" | grep -qE '"prerelease":[[:space:]]*true'; then
|
|
label="[pre-release]"
|
|
else
|
|
label="[stable]"
|
|
fi
|
|
printf " %-15s %s\n" "$tag" "$label"
|
|
done < <(_split_releases)
|
|
echo ""
|
|
echo -e "Install one with: ${blue}sudo bash install.sh --version <tag>${plain}"
|
|
echo -e "Or: ${blue}sudo bash install.sh <tag>${plain} (positional)"
|
|
}
|
|
|
|
download_binary() {
|
|
local version="$1"
|
|
local arch_name
|
|
arch_name=$(arch)
|
|
local binary_name="thefeed-server-linux-${arch_name}"
|
|
local url="https://github.com/${GITHUB_REPO}/releases/download/${version}/${binary_name}"
|
|
|
|
echo -e "${green}Downloading thefeed-server ${version} for linux/${arch_name}...${plain}"
|
|
mkdir -p "$INSTALL_DIR"
|
|
|
|
curl -4fLo "${INSTALL_DIR}/thefeed-server" "$url"
|
|
if [[ $? -ne 0 ]]; then
|
|
echo -e "${red}Failed to download binary from:${plain}"
|
|
echo -e "${red} ${url}${plain}"
|
|
echo -e "${yellow}Please check that the version exists and your server can reach GitHub${plain}"
|
|
exit 1
|
|
fi
|
|
|
|
chmod 755 "${INSTALL_DIR}/thefeed-server"
|
|
echo -e "${green}Binary installed to ${INSTALL_DIR}/thefeed-server${plain}"
|
|
}
|
|
|
|
setup_channels() {
|
|
echo -e "\n${green}Setting up Telegram channels...${plain}"
|
|
echo "# Telegram channel usernames (one per line)" > "$DATA_DIR/channels.txt.tmp"
|
|
echo "# Lines starting with # are comments" >> "$DATA_DIR/channels.txt.tmp"
|
|
|
|
echo ""
|
|
echo -e "${yellow}Enter Telegram channel usernames (one per line, empty line to finish):${plain}"
|
|
while true; do
|
|
read -rp " Channel: " channel
|
|
if [[ -z "$channel" ]]; then
|
|
break
|
|
fi
|
|
channel="${channel#@}"
|
|
echo "@$channel" >> "$DATA_DIR/channels.txt.tmp"
|
|
echo -e " ${green}Added @${channel}${plain}"
|
|
done
|
|
mv "$DATA_DIR/channels.txt.tmp" "$DATA_DIR/channels.txt"
|
|
}
|
|
|
|
setup_x_accounts() {
|
|
echo -e "\n${green}Setting up X accounts...${plain}"
|
|
echo "# X usernames (one per line, without @)" > "$DATA_DIR/x_accounts.txt.tmp"
|
|
echo "# Lines starting with # are comments" >> "$DATA_DIR/x_accounts.txt.tmp"
|
|
|
|
echo ""
|
|
echo -e "${yellow}Enter X usernames (one per line, empty line to finish):${plain}"
|
|
while true; do
|
|
read -rp " X: " account
|
|
if [[ -z "$account" ]]; then
|
|
break
|
|
fi
|
|
account="${account#@}"
|
|
account="${account#x/}"
|
|
echo "$account" >> "$DATA_DIR/x_accounts.txt.tmp"
|
|
echo -e " ${green}Added ${account}${plain}"
|
|
done
|
|
mv "$DATA_DIR/x_accounts.txt.tmp" "$DATA_DIR/x_accounts.txt"
|
|
}
|
|
|
|
# Helper: update or add a key=value in the env file
|
|
env_set() {
|
|
local key="$1" val="$2"
|
|
if grep -q "^${key}=" "$DATA_DIR/thefeed.env" 2>/dev/null; then
|
|
sed -i "s|^${key}=.*|${key}=${val}|" "$DATA_DIR/thefeed.env"
|
|
else
|
|
echo "${key}=${val}" >> "$DATA_DIR/thefeed.env"
|
|
fi
|
|
}
|
|
|
|
# Helper: read a key from the env file (empty string if missing)
|
|
env_get() {
|
|
local key="$1"
|
|
grep "^${key}=" "$DATA_DIR/thefeed.env" 2>/dev/null | head -1 | cut -d= -f2-
|
|
}
|
|
|
|
setup_config() {
|
|
mkdir -p "$DATA_DIR"
|
|
|
|
local is_update=false cur_allow_manage=""
|
|
if [[ -f "$DATA_DIR/thefeed.env" ]]; then
|
|
is_update=true
|
|
cur_allow_manage=$(env_get THEFEED_ALLOW_MANAGE)
|
|
fi
|
|
|
|
# --- Channels ---
|
|
if [[ -f "$DATA_DIR/channels.txt" ]]; then
|
|
local ch_count
|
|
ch_count=$(grep -c '^@' "$DATA_DIR/channels.txt" 2>/dev/null || echo 0)
|
|
echo -e "${yellow}Telegram channels configured: ${ch_count}${plain}"
|
|
read -rp "Change Telegram channels? [y/N]: " change_ch
|
|
if [[ "$change_ch" == "y" || "$change_ch" == "Y" ]]; then
|
|
setup_channels
|
|
fi
|
|
else
|
|
setup_channels
|
|
fi
|
|
|
|
# --- X accounts ---
|
|
if [[ -f "$DATA_DIR/x_accounts.txt" ]]; then
|
|
local x_count
|
|
x_count=$(grep -cv '^#\|^$' "$DATA_DIR/x_accounts.txt" 2>/dev/null || echo 0)
|
|
echo -e "${yellow}X accounts configured: ${x_count}${plain}"
|
|
read -rp "Change X accounts? [y/N]: " change_x
|
|
if [[ "$change_x" == "y" || "$change_x" == "Y" ]]; then
|
|
setup_x_accounts
|
|
fi
|
|
else
|
|
setup_x_accounts
|
|
fi
|
|
|
|
# --- Server settings ---
|
|
echo -e "\n${green}═══════════════════════════════════════${plain}"
|
|
echo -e "${green} Server Configuration${plain}"
|
|
echo -e "${green}═══════════════════════════════════════${plain}"
|
|
echo ""
|
|
|
|
local cur_domain cur_key cur_limit cur_listen cur_fetch_interval
|
|
if $is_update; then
|
|
cur_domain=$(env_get THEFEED_DOMAIN)
|
|
cur_key=$(env_get THEFEED_KEY)
|
|
cur_limit=$(env_get THEFEED_MSG_LIMIT)
|
|
cur_listen=$(env_get THEFEED_LISTEN)
|
|
cur_fetch_interval=$(env_get THEFEED_FETCH_INTERVAL)
|
|
fi
|
|
|
|
local domain=""
|
|
while true; do
|
|
if [[ -n "$cur_domain" ]]; then
|
|
read -rp "DNS domain [${cur_domain}]: " domain
|
|
domain="${domain:-$cur_domain}"
|
|
else
|
|
read -rp "DNS domain (e.g., t.example.com): " domain
|
|
fi
|
|
if [[ -n "$domain" ]]; then break; fi
|
|
echo -e "${red}Domain cannot be empty${plain}"
|
|
done
|
|
|
|
local passkey=""
|
|
while true; do
|
|
if [[ -n "$cur_key" ]]; then
|
|
read -rp "Encryption passphrase [keep current]: " passkey
|
|
passkey="${passkey:-$cur_key}"
|
|
else
|
|
read -rp "Encryption passphrase: " passkey
|
|
fi
|
|
if [[ -n "$passkey" ]]; then break; fi
|
|
echo -e "${red}Passphrase cannot be empty${plain}"
|
|
done
|
|
|
|
local msg_limit=""
|
|
read -rp "Max messages per channel [${cur_limit:-15}]: " msg_limit
|
|
msg_limit="${msg_limit:-${cur_limit:-15}}"
|
|
|
|
local fetch_interval=""
|
|
while true; do
|
|
read -rp "Fetch cycle interval, minutes (min 3) [${cur_fetch_interval:-10}]: " fetch_interval
|
|
fetch_interval="${fetch_interval:-${cur_fetch_interval:-10}}"
|
|
if [[ "$fetch_interval" =~ ^[0-9]+$ ]] && [[ "$fetch_interval" -ge 3 ]]; then break; fi
|
|
echo -e "${red}Must be an integer ≥ 3${plain}"
|
|
done
|
|
|
|
echo ""
|
|
echo -e "${yellow}Allow remote management (send messages, add/remove channels)?${plain}"
|
|
echo -e " If enabled, anyone with the passphrase can manage channels."
|
|
local allow_manage=""
|
|
if [[ "$cur_allow_manage" == "1" ]]; then
|
|
read -rp "Enable remote management? [Y/n]: " allow_manage
|
|
if [[ "$allow_manage" == "n" || "$allow_manage" == "N" ]]; then
|
|
allow_manage="0"
|
|
else
|
|
allow_manage="1"
|
|
fi
|
|
else
|
|
read -rp "Enable remote management? [y/N]: " allow_manage
|
|
if [[ "$allow_manage" == "y" || "$allow_manage" == "Y" ]]; then
|
|
allow_manage="1"
|
|
else
|
|
allow_manage="0"
|
|
fi
|
|
fi
|
|
|
|
# --- Media relays ---
|
|
# Each relay is independent: the same file can be served by DNS, GitHub,
|
|
# and any future relay simultaneously. Enabling a relay just gives
|
|
# clients another way to fetch the bytes.
|
|
echo ""
|
|
echo -e "${green}═══════════════════════════════════════${plain}"
|
|
echo -e "${green} Media relays${plain}"
|
|
echo -e "${green}═══════════════════════════════════════${plain}"
|
|
local cur_dns_enabled cur_dns_size cur_dns_ttl cur_dns_comp
|
|
local cur_gh_enabled cur_gh_token cur_gh_repo cur_gh_branch cur_gh_size cur_gh_ttl
|
|
if $is_update; then
|
|
cur_dns_enabled=$(env_get THEFEED_DNS_MEDIA_ENABLED)
|
|
cur_dns_size=$(env_get THEFEED_DNS_MEDIA_MAX_SIZE_KB)
|
|
cur_dns_ttl=$(env_get THEFEED_DNS_MEDIA_CACHE_TTL_MIN)
|
|
cur_dns_comp=$(env_get THEFEED_DNS_MEDIA_COMPRESSION)
|
|
cur_gh_enabled=$(env_get THEFEED_GITHUB_RELAY_ENABLED)
|
|
cur_gh_token=$(env_get THEFEED_GITHUB_RELAY_TOKEN)
|
|
cur_gh_repo=$(env_get THEFEED_GITHUB_RELAY_REPO)
|
|
cur_gh_branch=$(env_get THEFEED_GITHUB_RELAY_BRANCH)
|
|
cur_gh_size=$(env_get THEFEED_GITHUB_RELAY_MAX_SIZE_KB)
|
|
cur_gh_ttl=$(env_get THEFEED_GITHUB_RELAY_TTL_MIN)
|
|
fi
|
|
|
|
# DNS relay (slow path, off by default).
|
|
echo ""
|
|
echo -e "${yellow}DNS relay${plain} — files served block-by-block over DNS. Slower, works"
|
|
echo -e " in censored networks. Default 100 KB cap."
|
|
local dns_default="N" dns_prompt="[y/N]"
|
|
if [[ "$cur_dns_enabled" == "1" ]]; then dns_default="Y" dns_prompt="[Y/n]"; fi
|
|
local dns_enabled_in=""
|
|
read -rp "Enable DNS relay? $dns_prompt: " dns_enabled_in
|
|
if [[ -z "$dns_enabled_in" ]]; then dns_enabled_in="$dns_default"; fi
|
|
local dns_enabled="0"
|
|
if [[ "$dns_enabled_in" == "y" || "$dns_enabled_in" == "Y" ]]; then dns_enabled="1"; fi
|
|
|
|
local dns_max_size="${cur_dns_size:-100}"
|
|
local dns_ttl="${cur_dns_ttl:-600}"
|
|
local dns_comp="${cur_dns_comp:-gzip}"
|
|
if [[ "$dns_enabled" == "1" ]]; then
|
|
read -rp "DNS relay max file size in KB [${dns_max_size}]: " in
|
|
dns_max_size="${in:-$dns_max_size}"
|
|
read -rp "DNS relay TTL in minutes [${dns_ttl}]: " in
|
|
dns_ttl="${in:-$dns_ttl}"
|
|
read -rp "DNS relay compression (none|gzip|deflate) [${dns_comp}]: " in
|
|
dns_comp="${in:-$dns_comp}"
|
|
fi
|
|
|
|
# GitHub relay (fast path, default off — needs a token).
|
|
echo ""
|
|
echo -e "${yellow}GitHub relay${plain} — files uploaded to a repo and pulled by clients over"
|
|
echo -e " plain HTTPS. Faster + bigger files; needs a personal access token."
|
|
local gh_default="N" gh_prompt="[y/N]"
|
|
if [[ "$cur_gh_enabled" == "1" ]]; then gh_default="Y"; gh_prompt="[Y/n]"; fi
|
|
local gh_enabled_in=""
|
|
read -rp "Enable GitHub relay? $gh_prompt: " gh_enabled_in
|
|
if [[ -z "$gh_enabled_in" ]]; then gh_enabled_in="$gh_default"; fi
|
|
local gh_enabled="0"
|
|
if [[ "$gh_enabled_in" == "y" || "$gh_enabled_in" == "Y" ]]; then gh_enabled="1"; fi
|
|
|
|
local gh_token="" gh_repo="" gh_branch="${cur_gh_branch:-main}"
|
|
local gh_max_size="${cur_gh_size:-15360}"
|
|
local gh_ttl="${cur_gh_ttl:-600}"
|
|
if [[ "$gh_enabled" == "1" ]]; then
|
|
if [[ -n "$cur_gh_token" ]]; then
|
|
read -rp "GitHub token (PAT, contents:write) [keep current]: " gh_token
|
|
gh_token="${gh_token:-$cur_gh_token}"
|
|
else
|
|
read -rp "GitHub token (PAT, contents:write): " gh_token
|
|
fi
|
|
while true; do
|
|
if [[ -n "$cur_gh_repo" ]]; then
|
|
read -rp "GitHub repo (owner/repo) [${cur_gh_repo}]: " gh_repo
|
|
gh_repo="${gh_repo:-$cur_gh_repo}"
|
|
else
|
|
read -rp "GitHub repo (owner/repo): " gh_repo
|
|
fi
|
|
if [[ "$gh_repo" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then break; fi
|
|
echo -e "${red}Invalid repo. Format: owner/repo${plain}"
|
|
done
|
|
read -rp "GitHub relay branch [${gh_branch}]: " in
|
|
gh_branch="${in:-$gh_branch}"
|
|
read -rp "GitHub relay max file size in KB [${gh_max_size}]: " in
|
|
gh_max_size="${in:-$gh_max_size}"
|
|
read -rp "GitHub relay TTL in minutes [${gh_ttl}]: " in
|
|
gh_ttl="${in:-$gh_ttl}"
|
|
fi
|
|
|
|
# --- Telegram mode ---
|
|
local cur_no_tg=""
|
|
if $is_update; then
|
|
cur_no_tg=$(env_get THEFEED_NO_TELEGRAM)
|
|
fi
|
|
echo ""
|
|
echo -e "${green}═══════════════════════════════════════${plain}"
|
|
echo -e "${green} Telegram Mode Selection${plain}"
|
|
echo -e "${green}═══════════════════════════════════════${plain}"
|
|
echo ""
|
|
echo -e " ${yellow}1)${plain} Without Telegram ${green}(recommended)${plain}"
|
|
echo -e " - No Telegram credentials stored on server"
|
|
echo -e " - Reads public channels only"
|
|
echo ""
|
|
echo -e " ${yellow}2)${plain} With Telegram"
|
|
echo -e " - Needs API ID, API hash, and phone number"
|
|
echo -e " - Required for private channels and sending messages"
|
|
echo ""
|
|
local mode_default="1"
|
|
[[ "$cur_no_tg" != "1" && "$is_update" == "true" && -n "$(env_get TELEGRAM_API_ID)" && "$(env_get TELEGRAM_API_ID)" != "0" ]] && mode_default="2"
|
|
local mode_in=""
|
|
while true; do
|
|
read -rp "Choose mode [1/2] (default: ${mode_default}): " mode_in
|
|
mode_in="${mode_in:-$mode_default}"
|
|
[[ "$mode_in" == "1" || "$mode_in" == "2" ]] && break
|
|
echo -e "${red}Enter 1 or 2${plain}"
|
|
done
|
|
|
|
local api_id="" api_hash="" phone="" listen_addr=""
|
|
if [[ "$mode_in" == "1" ]]; then
|
|
api_id="0"
|
|
api_hash="none"
|
|
phone="none"
|
|
read -rp "DNS listen address [${cur_listen:-0.0.0.0:53}]: " listen_addr
|
|
listen_addr="${listen_addr:-${cur_listen:-0.0.0.0:53}}"
|
|
|
|
cat > "$DATA_DIR/thefeed.env" <<ENVEOF
|
|
THEFEED_DOMAIN=${domain}
|
|
THEFEED_KEY=${passkey}
|
|
THEFEED_ALLOW_MANAGE=${allow_manage}
|
|
THEFEED_MSG_LIMIT=${msg_limit}
|
|
THEFEED_X_RSS_INSTANCES=https://nitter.net,http://nitter.net
|
|
TELEGRAM_API_ID=${api_id}
|
|
TELEGRAM_API_HASH=${api_hash}
|
|
TELEGRAM_PHONE=${phone}
|
|
THEFEED_LISTEN=${listen_addr}
|
|
THEFEED_FETCH_INTERVAL=${fetch_interval}
|
|
THEFEED_NO_TELEGRAM=1
|
|
THEFEED_DNS_MEDIA_ENABLED=${dns_enabled}
|
|
THEFEED_DNS_MEDIA_MAX_SIZE_KB=${dns_max_size}
|
|
THEFEED_DNS_MEDIA_CACHE_TTL_MIN=${dns_ttl}
|
|
THEFEED_DNS_MEDIA_COMPRESSION=${dns_comp}
|
|
THEFEED_GITHUB_RELAY_ENABLED=${gh_enabled}
|
|
THEFEED_GITHUB_RELAY_TOKEN=${gh_token}
|
|
THEFEED_GITHUB_RELAY_REPO=${gh_repo}
|
|
THEFEED_GITHUB_RELAY_BRANCH=${gh_branch}
|
|
THEFEED_GITHUB_RELAY_MAX_SIZE_KB=${gh_max_size}
|
|
THEFEED_GITHUB_RELAY_TTL_MIN=${gh_ttl}
|
|
ENVEOF
|
|
chmod 600 "$DATA_DIR/thefeed.env"
|
|
echo -e "${green}Config saved to ${DATA_DIR}/thefeed.env${plain}"
|
|
return 0
|
|
fi
|
|
|
|
# With Telegram
|
|
local cur_api_id cur_api_hash cur_phone
|
|
if $is_update; then
|
|
cur_api_id=$(env_get TELEGRAM_API_ID)
|
|
cur_api_hash=$(env_get TELEGRAM_API_HASH)
|
|
cur_phone=$(env_get TELEGRAM_PHONE)
|
|
[[ "$cur_api_id" == "0" ]] && cur_api_id=""
|
|
[[ "$cur_api_hash" == "none" ]] && cur_api_hash=""
|
|
[[ "$cur_phone" == "none" ]] && cur_phone=""
|
|
fi
|
|
|
|
while true; do
|
|
if [[ -n "$cur_api_id" && "$cur_api_id" != "0" ]]; then
|
|
read -rp "Telegram API ID [${cur_api_id}]: " api_id
|
|
api_id="${api_id:-$cur_api_id}"
|
|
else
|
|
read -rp "Telegram API ID: " api_id
|
|
fi
|
|
if [[ "$api_id" =~ ^[0-9]+$ ]]; then break; fi
|
|
echo -e "${red}API ID must be a number${plain}"
|
|
done
|
|
|
|
while true; do
|
|
if [[ -n "$cur_api_hash" && "$cur_api_hash" != "none" ]]; then
|
|
read -rp "Telegram API Hash [keep current]: " api_hash
|
|
api_hash="${api_hash:-$cur_api_hash}"
|
|
else
|
|
read -rp "Telegram API Hash: " api_hash
|
|
fi
|
|
if [[ -n "$api_hash" ]]; then break; fi
|
|
echo -e "${red}API Hash cannot be empty${plain}"
|
|
done
|
|
|
|
while true; do
|
|
if [[ -n "$cur_phone" && "$cur_phone" != "none" ]]; then
|
|
read -rp "Telegram phone number [${cur_phone}]: " phone
|
|
phone="${phone:-$cur_phone}"
|
|
else
|
|
read -rp "Telegram phone number (e.g., +1234567890): " phone
|
|
fi
|
|
if [[ -n "$phone" ]]; then break; fi
|
|
echo -e "${red}Phone number cannot be empty${plain}"
|
|
done
|
|
|
|
read -rp "DNS listen address [${cur_listen:-0.0.0.0:53}]: " listen_addr
|
|
listen_addr="${listen_addr:-${cur_listen:-0.0.0.0:53}}"
|
|
|
|
cat > "$DATA_DIR/thefeed.env" <<ENVEOF
|
|
THEFEED_DOMAIN=${domain}
|
|
THEFEED_KEY=${passkey}
|
|
THEFEED_ALLOW_MANAGE=${allow_manage}
|
|
THEFEED_MSG_LIMIT=${msg_limit}
|
|
THEFEED_X_RSS_INSTANCES=https://nitter.net,http://nitter.net
|
|
TELEGRAM_API_ID=${api_id}
|
|
TELEGRAM_API_HASH=${api_hash}
|
|
TELEGRAM_PHONE=${phone}
|
|
THEFEED_LISTEN=${listen_addr}
|
|
THEFEED_FETCH_INTERVAL=${fetch_interval}
|
|
THEFEED_DNS_MEDIA_ENABLED=${dns_enabled}
|
|
THEFEED_DNS_MEDIA_MAX_SIZE_KB=${dns_max_size}
|
|
THEFEED_DNS_MEDIA_CACHE_TTL_MIN=${dns_ttl}
|
|
THEFEED_DNS_MEDIA_COMPRESSION=${dns_comp}
|
|
THEFEED_GITHUB_RELAY_ENABLED=${gh_enabled}
|
|
THEFEED_GITHUB_RELAY_TOKEN=${gh_token}
|
|
THEFEED_GITHUB_RELAY_REPO=${gh_repo}
|
|
THEFEED_GITHUB_RELAY_BRANCH=${gh_branch}
|
|
THEFEED_GITHUB_RELAY_MAX_SIZE_KB=${gh_max_size}
|
|
THEFEED_GITHUB_RELAY_TTL_MIN=${gh_ttl}
|
|
ENVEOF
|
|
chmod 600 "$DATA_DIR/thefeed.env"
|
|
echo -e "${green}Config saved to ${DATA_DIR}/thefeed.env${plain}"
|
|
chmod 700 "$DATA_DIR"
|
|
}
|
|
|
|
telegram_login() {
|
|
echo -e "\n${green}═══════════════════════════════════════${plain}"
|
|
echo -e "${green} Telegram Login (one-time)${plain}"
|
|
echo -e "${green}═══════════════════════════════════════${plain}"
|
|
echo -e "${yellow}This will authenticate with Telegram and save the session.${plain}"
|
|
echo ""
|
|
|
|
set -a
|
|
source "$DATA_DIR/thefeed.env"
|
|
set +a
|
|
|
|
"$INSTALL_DIR/thefeed-server" \
|
|
--login-only \
|
|
--data-dir "$DATA_DIR" \
|
|
--domain "$THEFEED_DOMAIN" \
|
|
--key "$THEFEED_KEY" \
|
|
--api-id "$TELEGRAM_API_ID" \
|
|
--api-hash "$TELEGRAM_API_HASH" \
|
|
--phone "$TELEGRAM_PHONE"
|
|
|
|
if [[ $? -ne 0 ]]; then
|
|
echo -e "${red}Telegram login failed${plain}"
|
|
echo -e "${yellow}You can retry later with:${plain}"
|
|
echo -e " sudo bash install.sh --login"
|
|
return 1
|
|
fi
|
|
|
|
chmod 600 "$DATA_DIR/session.json"
|
|
echo -e "${green}Telegram login successful, session saved.${plain}"
|
|
}
|
|
|
|
install_service() {
|
|
echo -e "${green}Installing systemd service...${plain}"
|
|
|
|
set -a
|
|
source "$DATA_DIR/thefeed.env"
|
|
set +a
|
|
|
|
local extra_flags=""
|
|
if [[ "${THEFEED_NO_TELEGRAM:-}" == "1" ]]; then
|
|
extra_flags="--no-telegram"
|
|
fi
|
|
if [[ "${THEFEED_ALLOW_MANAGE:-}" == "1" ]]; then
|
|
extra_flags="${extra_flags} --allow-manage"
|
|
fi
|
|
# All --dns-media-* and --github-relay-* settings come from THEFEED_*
|
|
# env vars, so the binary picks them up via EnvironmentFile alone.
|
|
|
|
cat > "$SERVICE_FILE" <<SVCEOF
|
|
[Unit]
|
|
Description=thefeed DNS-based Telegram Feed Server
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
WorkingDirectory=${INSTALL_DIR}
|
|
EnvironmentFile=${DATA_DIR}/thefeed.env
|
|
ExecStart=${INSTALL_DIR}/thefeed-server \\
|
|
--data-dir ${DATA_DIR} \\
|
|
--domain \${THEFEED_DOMAIN} \\
|
|
--key \${THEFEED_KEY} \\
|
|
--x-accounts ${DATA_DIR}/x_accounts.txt \\
|
|
--x-rss-instances \${THEFEED_X_RSS_INSTANCES} \\
|
|
--api-id \${TELEGRAM_API_ID} \\
|
|
--api-hash \${TELEGRAM_API_HASH} \\
|
|
--phone \${TELEGRAM_PHONE} \\
|
|
--listen \${THEFEED_LISTEN} ${extra_flags}
|
|
|
|
Restart=on-failure
|
|
RestartSec=10
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
|
|
# Security hardening
|
|
ProtectHome=true
|
|
PrivateTmp=true
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
SVCEOF
|
|
|
|
systemctl daemon-reload
|
|
echo -e "${green}Service installed: thefeed-server${plain}"
|
|
}
|
|
|
|
start_service() {
|
|
echo -e "${green}Enabling and starting service...${plain}"
|
|
systemctl enable thefeed-server
|
|
systemctl start thefeed-server
|
|
echo ""
|
|
echo -e "${green}Service status:${plain}"
|
|
systemctl status thefeed-server --no-pager || true
|
|
}
|
|
|
|
show_usage() {
|
|
echo ""
|
|
echo -e "┌─────────────────────────────────────────────────────────┐"
|
|
echo -e "│ ${blue}thefeed service management:${plain} │"
|
|
echo -e "│ │"
|
|
echo -e "│ ${blue}systemctl status thefeed-server${plain} - Status │"
|
|
echo -e "│ ${blue}systemctl restart thefeed-server${plain} - Restart │"
|
|
echo -e "│ ${blue}systemctl stop thefeed-server${plain} - Stop │"
|
|
echo -e "│ ${blue}journalctl -u thefeed-server -f${plain} - Live logs │"
|
|
echo -e "│ │"
|
|
echo -e "│ All data in: ${blue}${INSTALL_DIR}/${plain} │"
|
|
echo -e "│ ${blue}Config:${plain} ${DATA_DIR}/thefeed.env │"
|
|
echo -e "│ ${blue}Channels:${plain} ${DATA_DIR}/channels.txt │"
|
|
echo -e "│ ${blue}X acct:${plain} ${DATA_DIR}/x_accounts.txt │"
|
|
echo -e "│ ${blue}Session:${plain} ${DATA_DIR}/session.json │"
|
|
echo -e "│ ${blue}Binary:${plain} ${INSTALL_DIR}/thefeed-server │"
|
|
echo -e "│ │"
|
|
echo -e "│ ${yellow}Quick commands (copy-paste):${plain} │"
|
|
echo -e "│ Update: ${blue}curl -Ls URL | sudo bash${plain} │"
|
|
echo -e "│ Uninstall: ${blue}curl -Ls URL | sudo bash -s -- --uninstall${plain} │"
|
|
echo -e "│ Re-login: ${blue}curl -Ls URL | sudo bash -s -- --login${plain} │"
|
|
echo -e "│ │"
|
|
echo -e "│ ${red}⚠ NEVER share your passphrase publicly!${plain} │"
|
|
echo -e "│ ${red}Anyone with it can read ALL your messages.${plain} │"
|
|
echo -e "│ ${red}--password only protects the web UI on your PC.${plain} │"
|
|
echo -e "└─────────────────────────────────────────────────────────┘"
|
|
echo ""
|
|
echo -e "Full update command:"
|
|
echo -e " ${blue}curl -Ls https://raw.githubusercontent.com/${GITHUB_REPO}/main/scripts/install.sh | sudo bash${plain}"
|
|
echo ""
|
|
echo -e "Full uninstall command:"
|
|
echo -e " ${blue}curl -Ls https://raw.githubusercontent.com/${GITHUB_REPO}/main/scripts/install.sh | sudo bash -s -- --uninstall${plain}"
|
|
echo ""
|
|
}
|
|
|
|
install_thefeed() {
|
|
local version="$1"
|
|
local channel="${2:-stable}" # "stable" or "pre"
|
|
|
|
# When invoked via `curl | bash`, stdin is the pipe, not a terminal —
|
|
# so every read in setup_config returns empty and the user is never
|
|
# prompted. Re-open stdin from /dev/tty so prompts work as expected.
|
|
if [[ ! -t 0 ]] && [[ -e /dev/tty ]]; then
|
|
exec </dev/tty
|
|
fi
|
|
|
|
if [[ -z "$version" ]]; then
|
|
if [[ "$channel" == "pre" ]]; then
|
|
version=$(get_latest_prerelease)
|
|
if [[ -z "$version" ]]; then
|
|
echo -e "${red}No pre-release found on GitHub${plain}"
|
|
echo -e "${yellow}Run: bash install.sh --list to see available versions${plain}"
|
|
exit 1
|
|
fi
|
|
echo -e "${yellow}Channel:${plain} ${blue}pre-release${plain}"
|
|
else
|
|
version=$(get_latest_version)
|
|
if [[ -z "$version" ]]; then
|
|
echo -e "${red}Failed to fetch latest version from GitHub${plain}"
|
|
echo -e "${yellow}Please check your network or specify a version: bash install.sh --version v1.0.0${plain}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
if [[ "$version" =~ ^[0-9] ]]; then
|
|
version="v${version}"
|
|
fi
|
|
echo -e "Version: ${green}${version}${plain}"
|
|
|
|
# Check current version
|
|
if [[ -f "${INSTALL_DIR}/thefeed-server" ]]; then
|
|
local current_version
|
|
current_version=$("${INSTALL_DIR}/thefeed-server" --version 2>&1 | awk '{print $2}' || echo "unknown")
|
|
echo -e "Current: ${yellow}${current_version}${plain}"
|
|
if [[ "$current_version" == "$version" ]]; then
|
|
echo -e "${yellow}Already running ${version}. Reinstalling anyway...${plain}"
|
|
fi
|
|
fi
|
|
|
|
# Stop existing service
|
|
if systemctl is-active thefeed-server &>/dev/null; then
|
|
echo -e "${yellow}Stopping existing service...${plain}"
|
|
systemctl stop thefeed-server
|
|
fi
|
|
|
|
# Download
|
|
download_binary "$version"
|
|
|
|
setup_config
|
|
while IFS= read -r v; do
|
|
unset "$v"
|
|
done < <(env | awk -F= '/^THEFEED_|^TELEGRAM_/{print $1}')
|
|
set -a
|
|
source "$DATA_DIR/thefeed.env"
|
|
set +a
|
|
if [[ "${THEFEED_NO_TELEGRAM:-}" != "1" ]]; then
|
|
# Only prompt for Telegram login if credentials changed or no session exists
|
|
if [[ ! -f "$DATA_DIR/session.json" ]]; then
|
|
telegram_login
|
|
else
|
|
read -rp "Re-authenticate with Telegram? [y/N]: " relogin
|
|
if [[ "$relogin" == "y" || "$relogin" == "Y" ]]; then
|
|
telegram_login
|
|
fi
|
|
fi
|
|
fi
|
|
install_service
|
|
start_service
|
|
|
|
echo -e "\n${green}═══════════════════════════════════════${plain}"
|
|
echo -e "${green} thefeed ${version} installed successfully!${plain}"
|
|
echo -e "${green}═══════════════════════════════════════${plain}"
|
|
show_usage
|
|
}
|
|
|
|
login_only() {
|
|
if [[ ! -f "$DATA_DIR/thefeed.env" ]]; then
|
|
echo -e "${red}Config not found. Run install first: bash install.sh${plain}"
|
|
exit 1
|
|
fi
|
|
if [[ ! -f "${INSTALL_DIR}/thefeed-server" ]]; then
|
|
echo -e "${red}Binary not found. Run install first: bash install.sh${plain}"
|
|
exit 1
|
|
fi
|
|
telegram_login
|
|
echo -e "${green}Restarting service...${plain}"
|
|
systemctl restart thefeed-server || true
|
|
}
|
|
|
|
uninstall_thefeed() {
|
|
echo -e "${yellow}Uninstalling thefeed...${plain}"
|
|
|
|
systemctl stop thefeed-server 2>/dev/null || true
|
|
systemctl disable thefeed-server 2>/dev/null || true
|
|
rm -f "$SERVICE_FILE"
|
|
systemctl daemon-reload
|
|
|
|
local remove_data=""
|
|
if [[ -t 0 ]]; then
|
|
read -rp "Remove all data (config, session, binary)? [y/N]: " remove_data
|
|
else
|
|
# When piped (curl | bash), stdin is not a terminal — default to keeping data
|
|
echo -e "${yellow}Non-interactive mode: keeping data. Delete manually with: rm -rf ${INSTALL_DIR}${plain}"
|
|
fi
|
|
if [[ "$remove_data" == "y" || "$remove_data" == "Y" ]]; then
|
|
rm -rf "$INSTALL_DIR"
|
|
echo -e "${green}All data removed${plain}"
|
|
else
|
|
rm -f "${INSTALL_DIR}/thefeed-server"
|
|
echo -e "${green}Binary removed (data preserved in ${DATA_DIR})${plain}"
|
|
fi
|
|
|
|
echo -e "${green}thefeed uninstalled successfully${plain}"
|
|
}
|
|
|
|
show_help() {
|
|
echo -e "thefeed install script"
|
|
echo ""
|
|
echo -e "Usage: bash $0 [OPTION]"
|
|
echo ""
|
|
echo -e "Options:"
|
|
echo -e " ${green}(no args)${plain} Install or update to latest stable version"
|
|
echo -e " ${green}--version <tag>${plain} Install a specific version (rollback, beta, rc)"
|
|
echo -e " ${green}-v <tag>${plain} Short form of --version"
|
|
echo -e " ${green}<tag>${plain} Positional form, e.g. bash install.sh v1.0.0"
|
|
echo -e " ${green}--pre${plain} Install the latest pre-release (beta/rc)"
|
|
echo -e " ${green}--list${plain} List recent releases with stable/pre labels"
|
|
echo -e " ${green}--login${plain} Re-authenticate with Telegram"
|
|
echo -e " ${green}--uninstall${plain} Remove thefeed"
|
|
echo -e " ${green}--help${plain} Show this help"
|
|
echo ""
|
|
echo -e "Examples:"
|
|
echo -e " Roll back: ${blue}sudo bash install.sh --version v0.9.2${plain}"
|
|
echo -e " Install beta: ${blue}sudo bash install.sh --pre${plain}"
|
|
echo -e " Specific tag: ${blue}sudo bash install.sh --version v1.2.0-rc1${plain}"
|
|
echo -e " See available: ${blue}sudo bash install.sh --list${plain}"
|
|
echo ""
|
|
echo -e "No-Telegram mode (recommended for most users):"
|
|
echo -e " Reads public Telegram channels without needing Telegram credentials."
|
|
echo -e " Safer because no phone number or API keys are stored on the server."
|
|
echo ""
|
|
echo -e "Quick commands:"
|
|
echo -e " Install/Update: ${blue}curl -Ls https://raw.githubusercontent.com/${GITHUB_REPO}/main/scripts/install.sh | sudo bash${plain}"
|
|
echo -e " Install beta: ${blue}curl -Ls https://raw.githubusercontent.com/${GITHUB_REPO}/main/scripts/install.sh | sudo bash -s -- --pre${plain}"
|
|
echo -e " Roll back: ${blue}curl -Ls https://raw.githubusercontent.com/${GITHUB_REPO}/main/scripts/install.sh | sudo bash -s -- --version v0.9.2${plain}"
|
|
echo -e " Uninstall: ${blue}curl -Ls https://raw.githubusercontent.com/${GITHUB_REPO}/main/scripts/install.sh | sudo bash -s -- --uninstall${plain}"
|
|
}
|
|
|
|
# Main
|
|
echo -e "${green}Running thefeed installer...${plain}"
|
|
|
|
# Flags: --version <tag> / -v <tag> / positional <tag>, --pre, --list,
|
|
# --login, --uninstall, --help. No args = latest stable.
|
|
REQUEST_VERSION=""
|
|
REQUEST_CHANNEL="stable"
|
|
ACTION="install"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--help|-h)
|
|
ACTION="help"; shift ;;
|
|
--login)
|
|
ACTION="login"; shift ;;
|
|
--uninstall)
|
|
ACTION="uninstall"; shift ;;
|
|
--list)
|
|
ACTION="list"; shift ;;
|
|
--pre|--prerelease|--beta)
|
|
REQUEST_CHANNEL="pre"; shift ;;
|
|
--version|-v)
|
|
shift
|
|
if [[ -z "${1:-}" ]]; then
|
|
echo -e "${red}--version requires a tag argument (e.g. --version v1.0.0)${plain}"
|
|
exit 1
|
|
fi
|
|
REQUEST_VERSION="$1"; shift ;;
|
|
--version=*)
|
|
REQUEST_VERSION="${1#*=}"; shift ;;
|
|
--)
|
|
shift; break ;;
|
|
-*)
|
|
echo -e "${red}Unknown flag: $1${plain}"
|
|
echo -e "Run ${blue}bash $0 --help${plain} for usage"
|
|
exit 1 ;;
|
|
*)
|
|
# Positional tag, e.g. bash install.sh v1.0.0
|
|
if [[ -z "$REQUEST_VERSION" ]]; then
|
|
REQUEST_VERSION="$1"
|
|
fi
|
|
shift ;;
|
|
esac
|
|
done
|
|
|
|
case "$ACTION" in
|
|
help)
|
|
show_help; exit 0 ;;
|
|
login)
|
|
login_only; exit 0 ;;
|
|
uninstall)
|
|
uninstall_thefeed; exit 0 ;;
|
|
list)
|
|
list_versions; exit 0 ;;
|
|
install)
|
|
install_base
|
|
install_thefeed "$REQUEST_VERSION" "$REQUEST_CHANNEL"
|
|
exit 0 ;;
|
|
esac
|