diff --git a/README.md b/README.md index 5c7ca1d..2d09dcb 100644 --- a/README.md +++ b/README.md @@ -1036,7 +1036,7 @@ Master-Slave Replication (optional): |-----------|------| | **mtproxymax.sh** | Single bash script: CLI, TUI, config manager | | **telemt** | Rust MTProto engine running inside Docker | -| **Telegram bot service** | Independent systemd service polling Bot API | +| **Telegram bot service** | Independent service polling Bot API (systemd or OpenRC) | | **Replication sync service** | systemd timer pushing config to slave servers | | **Prometheus endpoint** | `/metrics` on port 9090 (localhost only) | diff --git a/mtproxymax.sh b/mtproxymax.sh index c398b7f..bad095a 100644 --- a/mtproxymax.sh +++ b/mtproxymax.sh @@ -35,6 +35,9 @@ FLEET_DATA_DIR="${FLEET_DATA_DIR:-${INSTALL_DIR}/fleet_data}" SSL_CONF_FILE="${SSL_CONF_FILE:-${INSTALL_DIR}/ssl.conf}" SSL_DIR="${SSL_DIR:-${INSTALL_DIR}/ssl}" CLOUD_BACKUP_FILE="${CLOUD_BACKUP_FILE:-${INSTALL_DIR}/cloud_backup.conf}" +SYSTEMD_DIR="${SYSTEMD_DIR:-/etc/systemd/system}" +INITD_DIR="${INITD_DIR:-/etc/init.d}" +RUNLEVELS_DIR="${RUNLEVELS_DIR:-/etc/runlevels}" SCANNER_SHIELD_SET="mtp_scanners" CONTAINER_NAME="mtproxymax" DOCKER_IMAGE_BASE="mtproxymax-telemt" @@ -522,6 +525,29 @@ detect_os() { fi } +# Detect the host init system: systemd | openrc | none +detect_init_system() { + if command -v systemctl &>/dev/null; then + echo "systemd" + elif [ -x /sbin/openrc-run ] || command -v rc-service &>/dev/null; then + echo "openrc" + else + echo "none" + fi +} + +# Register an OpenRC service in a runlevel, returning 0 only if it really landed +# there. `rc-update add` is not trusted on its own: its exit status is swallowed +# and, more importantly, a failure must never be reported to the user as success. +# Tested with -e rather than -L: rc-update creates a symlink to the init script we +# just wrote, so -e is true exactly when the service is genuinely runnable, and it +# stays verifiable on hosts that cannot create symlinks. +openrc_enable_service() { + local svc="$1" runlevel="${2:-default}" + rc-update add "$svc" "$runlevel" 2>/dev/null || true + [ -e "${RUNLEVELS_DIR}/${runlevel}/${svc}" ] +} + # Check dependencies check_dependencies() { local missing=() @@ -10039,11 +10065,10 @@ self_update() { # Always regenerate and restart Telegram bot service to apply latest daemon code if [ "${TELEGRAM_ENABLED:-}" = "true" ]; then telegram_generate_service_script - if command -v systemctl &>/dev/null && [ -f /etc/systemd/system/mtproxymax-telegram.service ]; then - log_info "Restarting Telegram bot service..." - systemctl restart mtproxymax-telegram.service 2>/dev/null \ - && log_success "Telegram bot service restarted" \ - || log_warn "Telegram restart failed — run: systemctl restart mtproxymax-telegram.service" + if telegram_restart_service; then + log_success "Telegram bot service restarted" + else + log_warn "Telegram bot service is not installed — run: mtproxymax telegram setup" fi fi @@ -11097,8 +11122,12 @@ telegram_setup_wizard() { # Send proxy links telegram_notify_proxy_started &>/dev/null & - # Setup systemd service for bot polling - setup_telegram_service + # Setup the bot polling service (systemd or OpenRC) + if ! setup_telegram_service; then + echo "" + log_warn "The bot is configured, but its background service is NOT running." + log_warn "Bot commands and alerts will not work until it is started (see the hint above)." + fi press_any_key } @@ -12120,12 +12149,66 @@ TELEGRAM_SCRIPT chmod +x "$script_path" } +# Stop the Telegram bot service on whichever init system is present +telegram_stop_service() { + case "$(detect_init_system)" in + systemd) systemctl stop mtproxymax-telegram.service 2>/dev/null || true ;; + openrc) rc-service mtproxymax-telegram stop 2>/dev/null || true ;; + esac + return 0 +} + +# Restart the Telegram bot service; nonzero when it is not installed or fails +telegram_restart_service() { + case "$(detect_init_system)" in + systemd) + [ -f "${SYSTEMD_DIR}/mtproxymax-telegram.service" ] || return 1 + systemctl restart mtproxymax-telegram.service 2>/dev/null + ;; + openrc) + [ -f "${INITD_DIR}/mtproxymax-telegram" ] || return 1 + rc-service mtproxymax-telegram restart 2>/dev/null + ;; + *) return 1 ;; + esac +} + +# Remove the Telegram bot service definition from the host init system +telegram_remove_service() { + case "$(detect_init_system)" in + systemd) + systemctl stop mtproxymax-telegram.service 2>/dev/null || true + systemctl disable mtproxymax-telegram.service 2>/dev/null || true + rm -f "${SYSTEMD_DIR}/mtproxymax-telegram.service" + systemctl daemon-reload 2>/dev/null || true + ;; + openrc) + rc-service mtproxymax-telegram stop 2>/dev/null || true + rc-update del mtproxymax-telegram default 2>/dev/null || true + rm -f "${INITD_DIR}/mtproxymax-telegram" + ;; + esac + return 0 +} + +# True when the Telegram bot service is actually running (not merely enabled) +telegram_service_running() { + case "$(detect_init_system)" in + systemd) systemctl is-active --quiet mtproxymax-telegram.service 2>/dev/null ;; + openrc) rc-service mtproxymax-telegram status >/dev/null 2>&1 ;; + *) return 1 ;; + esac +} + setup_telegram_service() { telegram_generate_service_script - # Create systemd service - if command -v systemctl &>/dev/null; then - cat > /etc/systemd/system/mtproxymax-telegram.service << 'SERVICE_EOF' + local init_system + init_system=$(detect_init_system) + + case "$init_system" in + systemd) + cat > "${SYSTEMD_DIR}/mtproxymax-telegram.service" << 'SERVICE_EOF' [Unit] Description=MTProxyMax Telegram Bot Service After=network-online.target docker.service @@ -12145,10 +12228,84 @@ SERVICE_EOF systemctl daemon-reload systemctl enable mtproxymax-telegram.service 2>/dev/null - systemctl restart mtproxymax-telegram.service 2>/dev/null - log_success "Telegram bot service started" + if systemctl restart mtproxymax-telegram.service 2>/dev/null; then + log_success "Telegram bot service started (systemd)" + else + log_warn "Telegram bot service failed to start — check: journalctl -u mtproxymax-telegram.service" + return 1 + fi + ;; + openrc) + # supervise-daemon keeps the bot alive across crashes, mirroring the + # systemd unit's Restart=on-failure / RestartSec=10. The daemon runs its + # own foreground poll loop — and is the only scheduler for the periodic + # tasks (quota/expiry enforcement, sweep, proxy auto-restart) — so it can + # be supervised directly instead of backgrounding itself. + # + # `need docker` is deliberate: the daemon drives the proxy container, so + # starting it without docker would only emit bogus "proxy down" alerts. + # `use` is soft — it orders dns/logger only when those services exist. + # + # respawn_max=0 means "always respawn", which is slightly broader than the + # systemd unit's Restart=on-failure: supervise-daemon restarts even after a + # clean exit. The daemon only exits on error or a signal, so this is the + # behaviour we want, but it is a real difference between the two backends. + cat > "${INITD_DIR}/mtproxymax-telegram" << OPENRC_EOF +#!/sbin/openrc-run +# MTProxyMax Telegram Bot Service +# Auto-generated — do not edit manually + +name="mtproxymax-telegram" +description="MTProxyMax Telegram Bot Service" + +supervisor=supervise-daemon +command="/bin/bash" +command_args="${INSTALL_DIR}/mtproxymax-telegram.sh" +respawn_delay=10 +respawn_max=0 + +# NOTE: two pid files exist and they are NOT interchangeable. This one belongs to +# the supervision layer; the bot daemon separately writes its own PID to +# ${INSTALL_DIR}/mtproxymax-telegram.pid. Killing the PID in this file stops the +# supervisor, not the bot. +pidfile="/run/\${RC_SVCNAME}.pid" +output_log="/var/log/mtproxymax-telegram.log" +error_log="/var/log/mtproxymax-telegram.err" + +depend() { + need net docker + use dns logger +} + +start_pre() { + # Generated by 'mtproxymax telegram setup'; absent until that wizard runs. + if [ ! -f "\${command_args}" ]; then + eerror "\${command_args} not found." + eerror "Run: ${INSTALL_DIR}/mtproxymax telegram setup" + return 1 fi } +OPENRC_EOF + + chmod +x "${INITD_DIR}/mtproxymax-telegram" + if ! openrc_enable_service mtproxymax-telegram default; then + log_warn "Could not enable the bot service for boot — run: rc-update add mtproxymax-telegram default" + fi + if rc-service mtproxymax-telegram restart 2>/dev/null; then + log_success "Telegram bot service started (OpenRC)" + else + log_warn "Telegram bot service failed to start — check: rc-service mtproxymax-telegram status" + return 1 + fi + ;; + *) + log_warn "No supported init system found (systemd or OpenRC)." + log_warn "The bot daemon was generated at ${INSTALL_DIR}/mtproxymax-telegram.sh but is NOT running." + echo -e " ${DIM}Start it manually: nohup ${INSTALL_DIR}/mtproxymax-telegram.sh >/var/log/mtproxymax-telegram.log 2>&1 &${NC}" + return 1 + ;; + esac +} # ── Section 14b: Replication / HA ──────────────────────────── @@ -12558,7 +12715,7 @@ setup_replication_service() { return 1 fi - cat > /etc/systemd/system/mtproxymax-sync.service << 'REPL_SERVICE_EOF' + cat > "${SYSTEMD_DIR}/mtproxymax-sync.service" << 'REPL_SERVICE_EOF' [Unit] Description=MTProxyMax Replication Sync After=network-online.target docker.service @@ -12571,7 +12728,7 @@ StandardOutput=journal StandardError=journal REPL_SERVICE_EOF - cat > /etc/systemd/system/mtproxymax-sync.timer << REPL_TIMER_EOF + cat > "${SYSTEMD_DIR}/mtproxymax-sync.timer" << REPL_TIMER_EOF [Unit] Description=MTProxyMax Replication Sync Timer @@ -12600,8 +12757,8 @@ stop_replication_service() { remove_replication_service() { stop_replication_service - rm -f /etc/systemd/system/mtproxymax-sync.service - rm -f /etc/systemd/system/mtproxymax-sync.timer + rm -f "${SYSTEMD_DIR}/mtproxymax-sync.service" + rm -f "${SYSTEMD_DIR}/mtproxymax-sync.timer" rm -f "${INSTALL_DIR}/mtproxymax-sync.sh" command -v systemctl &>/dev/null && systemctl daemon-reload 2>/dev/null || true } @@ -13203,7 +13360,7 @@ run_installer() { } # Setup autostart - setup_autostart + setup_autostart || true # Telegram setup offer echo "" @@ -13228,9 +13385,28 @@ run_installer() { show_main_menu } +# Remove the main autostart service definition from the host init system +main_service_remove() { + case "$(detect_init_system)" in + systemd) + systemctl stop mtproxymax.service 2>/dev/null || true + systemctl disable mtproxymax.service 2>/dev/null || true + rm -f "${SYSTEMD_DIR}/mtproxymax.service" + systemctl daemon-reload 2>/dev/null || true + ;; + openrc) + rc-service mtproxymax stop 2>/dev/null || true + rc-update del mtproxymax default 2>/dev/null || true + rm -f "${INITD_DIR}/mtproxymax" + ;; + esac + return 0 +} + setup_autostart() { - if command -v systemctl &>/dev/null; then - cat > /etc/systemd/system/mtproxymax.service << 'AUTOSTART_EOF' + case "$(detect_init_system)" in + systemd) + cat > "${SYSTEMD_DIR}/mtproxymax.service" << 'AUTOSTART_EOF' [Unit] Description=MTProxyMax Telegram Proxy After=network-online.target docker.service @@ -13250,7 +13426,55 @@ AUTOSTART_EOF systemctl daemon-reload systemctl enable mtproxymax.service 2>/dev/null log_success "Auto-start enabled (systemd)" - fi + ;; + openrc) + # Type=oneshot + RemainAfterExit=yes wraps the manager's own start/stop, + # so a plain start/stop script is the faithful equivalent (no supervisor). + cat > "${INITD_DIR}/mtproxymax" << OPENRC_EOF +#!/sbin/openrc-run +# MTProxyMax Telegram Proxy +# Auto-generated — do not edit manually + +name="mtproxymax" +description="MTProxyMax Telegram Proxy" + +depend() { + need net + need docker +} + +start() { + ebegin "Starting MTProxyMax" + /usr/local/bin/mtproxymax start + eend \$? +} + +stop() { + ebegin "Stopping MTProxyMax" + /usr/local/bin/mtproxymax stop + eend \$? +} + +status() { + /usr/local/bin/mtproxymax status +} +OPENRC_EOF + + chmod +x "${INITD_DIR}/mtproxymax" + if openrc_enable_service mtproxymax default; then + log_success "Auto-start enabled (OpenRC)" + else + log_warn "Could not enable auto-start — run: rc-update add mtproxymax default" + return 1 + fi + ;; + *) + log_warn "No supported init system found (systemd or OpenRC)." + log_warn "Auto-start on boot is NOT enabled." + echo -e " ${DIM}Add it to your init system manually, or start at boot with: ${INSTALL_DIR}/mtproxymax start${NC}" + return 1 + ;; + esac } show_install_summary() { @@ -13352,15 +13576,8 @@ uninstall() { echo "" log_info "Removing services..." - systemctl stop mtproxymax-telegram.service 2>/dev/null || true - systemctl disable mtproxymax-telegram.service 2>/dev/null || true - rm -f /etc/systemd/system/mtproxymax-telegram.service - - systemctl stop mtproxymax.service 2>/dev/null || true - systemctl disable mtproxymax.service 2>/dev/null || true - rm -f /etc/systemd/system/mtproxymax.service - - systemctl daemon-reload 2>/dev/null || true + telegram_remove_service + main_service_remove log_info "Removing geo-blocking rules..." geoblock_remove_all @@ -15478,11 +15695,15 @@ cli_main() { setup) check_root; telegram_setup_wizard ;; test) telegram_test_message ;; status|"") - if [ "$TELEGRAM_ENABLED" = "true" ]; then + if [ "$TELEGRAM_ENABLED" != "true" ]; then + echo -e " ${BOLD}Telegram:${NC} $(draw_status disabled 'Disabled')" + elif telegram_service_running; then echo -e " ${BOLD}Telegram:${NC} $(draw_status running 'Enabled')" echo -e " ${DIM}Interval: every ${TELEGRAM_INTERVAL}h | Alerts: ${TELEGRAM_ALERTS_ENABLED} | Label: ${TELEGRAM_SERVER_LABEL}${NC}" else - echo -e " ${BOLD}Telegram:${NC} $(draw_status disabled 'Disabled')" + echo -e " ${BOLD}Telegram:${NC} $(draw_status warning 'Enabled (service not running)')" + echo -e " ${DIM}Interval: every ${TELEGRAM_INTERVAL}h | Alerts: ${TELEGRAM_ALERTS_ENABLED} | Label: ${TELEGRAM_SERVER_LABEL}${NC}" + echo -e " ${DIM}The bot service is configured but not running — run: mtproxymax telegram setup${NC}" fi ;; interval) @@ -15550,7 +15771,7 @@ cli_main() { check_root TELEGRAM_ENABLED="false" save_settings - systemctl stop mtproxymax-telegram.service 2>/dev/null || true + telegram_stop_service log_success "Telegram disabled" ;; remove) @@ -15559,8 +15780,7 @@ cli_main() { TELEGRAM_BOT_TOKEN="" TELEGRAM_CHAT_ID="" save_settings - systemctl stop mtproxymax-telegram.service 2>/dev/null || true - systemctl disable mtproxymax-telegram.service 2>/dev/null || true + telegram_remove_service log_success "Telegram bot removed" ;; *) log_error "Usage: mtproxymax telegram [setup|test|status|interval|label|alerts|disable|remove]"; return 1 ;; @@ -17236,13 +17456,16 @@ show_telegram_menu() { 4) if [ "$TELEGRAM_ENABLED" = "true" ]; then TELEGRAM_ENABLED="false" - systemctl stop mtproxymax-telegram.service 2>/dev/null || true + telegram_stop_service log_success "Telegram disabled" else if [ -n "$TELEGRAM_BOT_TOKEN" ] && [ -n "$TELEGRAM_CHAT_ID" ]; then TELEGRAM_ENABLED="true" - setup_telegram_service - log_success "Telegram enabled" + if setup_telegram_service; then + log_success "Telegram enabled" + else + log_warn "Telegram bot service could not be started" + fi else log_warn "Run setup wizard first" fi @@ -18055,7 +18278,8 @@ show_info_telegram() { echo "" echo -e " ${BOLD}What does the bot do?${NC}" echo -e " Control your proxy from your phone via Telegram. The bot runs" - echo -e " as a separate systemd service and responds to commands." + echo -e " as a separate background service (systemd or OpenRC) and responds" + echo -e " to commands." echo "" echo -e " ${BOLD}Available commands:${NC}" echo -e " /mp_status Check proxy status, uptime, traffic" diff --git a/tests/test_telegram_service_openrc.sh b/tests/test_telegram_service_openrc.sh new file mode 100644 index 0000000..c3ce0ae --- /dev/null +++ b/tests/test_telegram_service_openrc.sh @@ -0,0 +1,318 @@ +#!/bin/bash +# Regression tests for Telegram bot / autostart service installation across init systems. +# +# Covers the non-systemd path added for Alpine/OpenRC: the Telegram bot service and the +# main autostart service must be installed via OpenRC instead of silently doing nothing, +# and a host with neither init system must fail loudly rather than reporting success. +set -o pipefail + +if [ "${BASH_VERSINFO[0]:-0}" -lt 4 ]; then + echo "SKIP: bash 4+ required (got ${BASH_VERSION:-unknown})" >&2 + exit 0 +fi + +TEST_TMPDIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'mtp_test_XXXXXX') +INSTALL_DIR="$TEST_TMPDIR/install" +SETTINGS_FILE="$INSTALL_DIR/settings.conf" +INITD_DIR="$TEST_TMPDIR/etc/init.d" +SYSTEMD_DIR="$TEST_TMPDIR/etc/systemd/system" +RUNLEVELS_DIR="$TEST_TMPDIR/etc/runlevels" +mkdir -p "$INSTALL_DIR" "$INITD_DIR" "$SYSTEMD_DIR" "$RUNLEVELS_DIR" + +MTPROXYMAX_SOURCE_ONLY=true source "$(dirname "${BASH_SOURCE[0]}")/../mtproxymax.sh" +set +e +trap 'rm -rf "$TEST_TMPDIR"' EXIT + +TESTS_RUN=0 +TESTS_FAILED=0 + +CMD_LOG="$TEST_TMPDIR/cmd.log" +WARN_LOG="$TEST_TMPDIR/warn.log" +SUCCESS_LOG="$TEST_TMPDIR/success.log" +: > "$CMD_LOG" +: > "$WARN_LOG" +: > "$SUCCESS_LOG" + +check_root() { :; } +log_success() { echo "$*" >> "$SUCCESS_LOG"; } +log_info() { :; } +log_warn() { echo "$*" >> "$WARN_LOG"; } +log_error() { echo "$*" >> "$WARN_LOG"; } + +# Shadow the init-system binaries so nothing touches the host. +RC_SERVICE_STATUS=0 +SYSTEMCTL_STATUS=0 +# When 0, `rc-update add` silently fails to create the runlevel symlink, which is +# how a genuine boot-enable failure presents. Success must not be reported then. +RC_UPDATE_LINKS=1 +systemctl() { echo "systemctl $*" >> "$CMD_LOG"; return "$SYSTEMCTL_STATUS"; } +rc-update() { + echo "rc-update $*" >> "$CMD_LOG" + if [ "$1" = "add" ] && [ "$RC_UPDATE_LINKS" = "1" ]; then + mkdir -p "$RUNLEVELS_DIR/$3" + ln -sfn "$INITD_DIR/$2" "$RUNLEVELS_DIR/$3/$2" + fi + return 0 +} +rc-service() { echo "rc-service $*" >> "$CMD_LOG"; return "$RC_SERVICE_STATUS"; } + +FAKE_INIT="openrc" +detect_init_system() { echo "$FAKE_INIT"; } + +assert_eq() { + local name="$1" want="$2" got="$3" + TESTS_RUN=$((TESTS_RUN + 1)) + if [ "$got" = "$want" ]; then + printf ' PASS %s\n' "$name" + else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf ' FAIL %s (got=%q want=%q)\n' "$name" "$got" "$want" + fi +} + +assert_file_contains() { + local name="$1" needle="$2" file="$3" + TESTS_RUN=$((TESTS_RUN + 1)) + if grep -qF -- "$needle" "$file" 2>/dev/null; then + printf ' PASS %s\n' "$name" + else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf ' FAIL %s (missing %q in %s)\n' "$name" "$needle" "$file" + fi +} + +assert_log_contains() { + local name="$1" needle="$2" + TESTS_RUN=$((TESTS_RUN + 1)) + if grep -qF -- "$needle" "$CMD_LOG" 2>/dev/null; then + printf ' PASS %s\n' "$name" + else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf ' FAIL %s (missing %q in command log)\n' "$name" "$needle" + fi +} + +assert_warn_contains() { + local name="$1" needle="$2" + TESTS_RUN=$((TESTS_RUN + 1)) + if grep -qF -- "$needle" "$WARN_LOG" 2>/dev/null; then + printf ' PASS %s\n' "$name" + else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf ' FAIL %s (missing %q in warning log)\n' "$name" "$needle" + fi +} + +assert_success_contains() { + local name="$1" needle="$2" + TESTS_RUN=$((TESTS_RUN + 1)) + if grep -qF -- "$needle" "$SUCCESS_LOG" 2>/dev/null; then + printf ' PASS %s\n' "$name" + else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf ' FAIL %s (missing %q in success log)\n' "$name" "$needle" + fi +} + +reset_state() { + FAKE_INIT="${1:-openrc}" + RC_SERVICE_STATUS=0 + SYSTEMCTL_STATUS=0 + RC_UPDATE_LINKS=1 + : > "$CMD_LOG" + : > "$WARN_LOG" + : > "$SUCCESS_LOG" + rm -f "$INITD_DIR/mtproxymax" "$INITD_DIR/mtproxymax-telegram" + rm -f "$SYSTEMD_DIR/mtproxymax.service" "$SYSTEMD_DIR/mtproxymax-telegram.service" + rm -rf "$RUNLEVELS_DIR" +} + +TELEGRAM_INIT="$INITD_DIR/mtproxymax-telegram" +MAIN_INIT="$INITD_DIR/mtproxymax" +TELEGRAM_UNIT="$SYSTEMD_DIR/mtproxymax-telegram.service" +MAIN_UNIT="$SYSTEMD_DIR/mtproxymax.service" + +echo "Telegram service init-system tests" + +# ── OpenRC: telegram bot service ───────────────────────────── +reset_state openrc +setup_telegram_service +assert_eq "openrc setup succeeds" "0" "$?" + +assert_file_contains "init script declares supervise-daemon" "supervisor=supervise-daemon" "$TELEGRAM_INIT" +assert_file_contains "init script respawns after 10s" "respawn_delay=10" "$TELEGRAM_INIT" +assert_file_contains "init script respawns indefinitely" "respawn_max=0" "$TELEGRAM_INIT" +assert_file_contains "init script needs net and docker" "need net docker" "$TELEGRAM_INIT" +assert_file_contains "init script points at generated daemon" \ + "command_args=\"${INSTALL_DIR}/mtproxymax-telegram.sh\"" "$TELEGRAM_INIT" +assert_file_contains "init script separates stderr" \ + "error_log=\"/var/log/mtproxymax-telegram.err\"" "$TELEGRAM_INIT" +assert_file_contains "start_pre guards on the generated daemon" \ + "if [ ! -f \"\${command_args}\" ]; then" "$TELEGRAM_INIT" + +if [ -x "$TELEGRAM_INIT" ]; then + assert_eq "init script is executable" "yes" "yes" +else + assert_eq "init script is executable" "yes" "no" +fi + +# Runtime variables must survive generation unexpanded. +assert_file_contains "RC_SVCNAME stays literal" 'pidfile="/run/${RC_SVCNAME}.pid"' "$TELEGRAM_INIT" +if grep -qF 'pidfile="/run/.pid"' "$TELEGRAM_INIT" 2>/dev/null; then + assert_eq "RC_SVCNAME not expanded at generation time" "literal" "expanded" +else + assert_eq "RC_SVCNAME not expanded at generation time" "literal" "literal" +fi + +assert_log_contains "service added to default runlevel" "rc-update add mtproxymax-telegram default" +assert_log_contains "service restarted via rc-service" "rc-service mtproxymax-telegram restart" + +# ── OpenRC: restart / stop / remove / status helpers ───────── +reset_state openrc +setup_telegram_service >/dev/null +: > "$CMD_LOG" + +telegram_restart_service +assert_eq "restart succeeds when unit installed" "0" "$?" +assert_log_contains "restart uses rc-service" "rc-service mtproxymax-telegram restart" + +telegram_stop_service +assert_log_contains "stop uses rc-service" "rc-service mtproxymax-telegram stop" + +telegram_remove_service +assert_log_contains "remove stops the service" "rc-service mtproxymax-telegram stop" +assert_log_contains "remove deletes from runlevel" "rc-update del mtproxymax-telegram default" +if [ -f "$TELEGRAM_INIT" ]; then + assert_eq "remove deletes init script" "deleted" "kept" +else + assert_eq "remove deletes init script" "deleted" "deleted" +fi + +# restart must report failure once the service definition is gone. +telegram_restart_service +assert_eq "restart fails when unit absent" "1" "$?" + +reset_state openrc +setup_telegram_service >/dev/null +RC_SERVICE_STATUS=0 +if telegram_service_running; then + assert_eq "status true when rc-service succeeds" "true" "true" +else + assert_eq "status true when rc-service succeeds" "true" "false" +fi +RC_SERVICE_STATUS=1 +if telegram_service_running; then + assert_eq "status false when rc-service fails" "false" "true" +else + assert_eq "status false when rc-service fails" "false" "false" +fi + +# ── OpenRC: start failure must not be reported as success ──── +reset_state openrc +RC_SERVICE_STATUS=1 +setup_telegram_service +assert_eq "openrc start failure returns nonzero" "1" "$?" +assert_warn_contains "openrc start failure warns" "rc-service mtproxymax-telegram status" + +# ── OpenRC: boot-enable failure must not be reported as success ── +reset_state openrc +setup_telegram_service +assert_success_contains "setup reports the bot as started" "Telegram bot service started (OpenRC)" +if [ -e "$RUNLEVELS_DIR/default/mtproxymax-telegram" ]; then + assert_eq "boot-enable registers the service in the runlevel" "yes" "yes" +else + assert_eq "boot-enable registers the service in the runlevel" "yes" "no" +fi + +reset_state openrc +RC_UPDATE_LINKS=0 +setup_telegram_service +assert_eq "boot-enable failure still starts the bot" "0" "$?" +assert_warn_contains "boot-enable failure warns" "Could not enable the bot service for boot" + +# ── No init system: loud failure, no silent no-op ──────────── +reset_state none +setup_telegram_service +assert_eq "no-init-system setup returns nonzero" "1" "$?" +if [ -f "$TELEGRAM_INIT" ]; then + assert_eq "no-init-system writes no init script" "absent" "present" +else + assert_eq "no-init-system writes no init script" "absent" "absent" +fi +assert_warn_contains "no-init-system warns it is not running" "is NOT running" + +reset_state none +if telegram_service_running; then + assert_eq "status false with no init system" "false" "true" +else + assert_eq "status false with no init system" "false" "false" +fi + +# ── systemd behaviour unchanged, but now path-injectable ───── +reset_state systemd +setup_telegram_service +assert_eq "systemd setup succeeds" "0" "$?" +assert_file_contains "systemd unit written to SYSTEMD_DIR" "MTProxyMax Telegram Bot Service" "$TELEGRAM_UNIT" +assert_file_contains "systemd unit restarts on failure" "Restart=on-failure" "$TELEGRAM_UNIT" +assert_log_contains "systemd enables the unit" "systemctl enable mtproxymax-telegram.service" +assert_log_contains "systemd restarts the unit" "systemctl restart mtproxymax-telegram.service" + +reset_state systemd +setup_telegram_service >/dev/null +: > "$CMD_LOG" +telegram_remove_service +assert_log_contains "systemd remove disables the unit" "systemctl disable mtproxymax-telegram.service" +if [ -f "$TELEGRAM_UNIT" ]; then + assert_eq "systemd remove deletes unit file" "deleted" "kept" +else + assert_eq "systemd remove deletes unit file" "deleted" "deleted" +fi + +# ── Autostart service ──────────────────────────────────────── +reset_state openrc +setup_autostart +assert_eq "autostart openrc succeeds" "0" "$?" +assert_file_contains "autostart script starts the manager" "/usr/local/bin/mtproxymax start" "$MAIN_INIT" +assert_file_contains "autostart script stops the manager" "/usr/local/bin/mtproxymax stop" "$MAIN_INIT" +assert_file_contains "autostart script requires docker" "need docker" "$MAIN_INIT" +assert_log_contains "autostart added to default runlevel" "rc-update add mtproxymax default" +if [ -x "$MAIN_INIT" ]; then + assert_eq "autostart script is executable" "yes" "yes" +else + assert_eq "autostart script is executable" "yes" "no" +fi + +reset_state openrc +RC_UPDATE_LINKS=0 +setup_autostart +assert_eq "autostart boot-enable failure returns nonzero" "1" "$?" +assert_warn_contains "autostart boot-enable failure warns" "Could not enable auto-start" + +reset_state none +setup_autostart +assert_eq "autostart with no init system returns nonzero" "1" "$?" +if [ -f "$MAIN_INIT" ]; then + assert_eq "autostart writes no init script when unsupported" "absent" "present" +else + assert_eq "autostart writes no init script when unsupported" "absent" "absent" +fi + +reset_state systemd +setup_autostart +assert_eq "autostart systemd succeeds" "0" "$?" +assert_file_contains "autostart unit written to SYSTEMD_DIR" "MTProxyMax Telegram Proxy" "$MAIN_UNIT" +assert_log_contains "autostart unit enabled" "systemctl enable mtproxymax.service" + +reset_state systemd +setup_autostart >/dev/null +: > "$CMD_LOG" +main_service_remove +assert_log_contains "autostart remove disables the unit" "systemctl disable mtproxymax.service" +if [ -f "$MAIN_UNIT" ]; then + assert_eq "autostart remove deletes unit file" "deleted" "kept" +else + assert_eq "autostart remove deletes unit file" "deleted" "deleted" +fi + +printf '\n%d tests, %d failures\n' "$TESTS_RUN" "$TESTS_FAILED" +[ "$TESTS_FAILED" -eq 0 ]