#!/usr/bin/env bash set -Eeuo pipefail RED='\033[0;31m' GREEN='\033[0;32m' BLUE='\033[0;34m' YELLOW='\033[1;33m' NC='\033[0m' APP_NAME="home-server" APP_USER="${HOMEIO_USER:-homeio}" APP_GROUP="${HOMEIO_GROUP:-${APP_USER}}" INSTALL_DIR="${HOMEIO_INSTALL_DIR:-/opt/home-server}" DATA_DIR="${HOMEIO_DATA_DIR:-/var/lib/home-server}" ENV_DIR="${HOMEIO_ENV_DIR:-/etc/home-server}" ENV_FILE="${HOMEIO_ENV_FILE:-${ENV_DIR}/home-server.env}" SERVICE_NAME="${HOMEIO_SERVICE_NAME:-home-server}" DBUS_SERVICE_NAME="${HOMEIO_DBUS_SERVICE_NAME:-home-server-dbus}" NGINX_SITE_NAME="${HOMEIO_NGINX_SITE_NAME:-home-server}" PURGE="false" ASSUME_YES="false" REMOVE_SYSTEM_USER="false" SERVICE_UNIT="${SERVICE_NAME}" if [[ "${SERVICE_UNIT}" != *.service ]]; then SERVICE_UNIT="${SERVICE_UNIT}.service" fi DBUS_SERVICE_UNIT="${DBUS_SERVICE_NAME}" if [[ "${DBUS_SERVICE_UNIT}" != *.service ]]; then DBUS_SERVICE_UNIT="${DBUS_SERVICE_UNIT}.service" fi print_status() { echo -e "${GREEN}[+]${NC} $1"; } print_error() { echo -e "${RED}[!]${NC} $1" >&2; } print_warn() { echo -e "${YELLOW}[*]${NC} $1"; } command_exists() { command -v "$1" >/dev/null 2>&1 } require_root() { [[ "${EUID}" -eq 0 ]] || { print_error "Run this uninstall script as root (for example: sudo bash uninstall.sh)."; exit 1; } } get_env_value() { local key="${1}" local file="${2}" [[ -f "${file}" ]] || return 1 grep -E "^${key}=" "${file}" | head -n 1 | cut -d '=' -f 2- } confirm() { local message="${1}" if [[ "${ASSUME_YES}" == "true" ]]; then return 0 fi local reply="" if [[ -t 0 ]]; then # Normal interactive mode read -r -p "${message} [y/N]: " reply elif read -r -p "${message} [y/N]: " reply 2>/dev/null /dev/null 2>&1 || true systemctl disable "${SERVICE_UNIT}" >/dev/null 2>&1 || true print_status "Stopping and disabling ${DBUS_SERVICE_UNIT}..." systemctl stop "${DBUS_SERVICE_UNIT}" >/dev/null 2>&1 || true systemctl disable "${DBUS_SERVICE_UNIT}" >/dev/null 2>&1 || true fi local unit_file="/etc/systemd/system/${SERVICE_UNIT}" if [[ -f "${unit_file}" ]]; then print_status "Removing systemd service files..." rm -f "${unit_file}" fi local dbus_unit_file="/etc/systemd/system/${DBUS_SERVICE_UNIT}" if [[ -f "${dbus_unit_file}" ]]; then rm -f "${dbus_unit_file}" fi if command_exists systemctl; then systemctl daemon-reload systemctl reset-failed >/dev/null 2>&1 || true fi rm -f /run/home-server/dbus-helper.sock >/dev/null 2>&1 || true rmdir /run/home-server >/dev/null 2>&1 || true } remove_reverse_proxy() { if ! command_exists nginx; then print_warn "nginx is not installed; skipping reverse proxy removal." return fi local nginx_conf="/etc/nginx/sites-available/${NGINX_SITE_NAME}.conf" local nginx_enabled="/etc/nginx/sites-enabled/${NGINX_SITE_NAME}.conf" local default_available="/etc/nginx/sites-available/default" local default_enabled="/etc/nginx/sites-enabled/default" local removed_site="false" if [[ -f "${nginx_conf}" || -L "${nginx_enabled}" ]]; then print_status "Removing nginx site ${NGINX_SITE_NAME}..." rm -f "${nginx_enabled}" >/dev/null 2>&1 || true rm -f "${nginx_conf}" >/dev/null 2>&1 || true removed_site="true" fi # Only hand port 80 back to the default site if this run actually took it # away. The restore used to be unconditional, so running the script against # a site name that was not installed still enabled Debian's default vhost — # and since that vhost listens with `default_server` while Homeio's listens # on a plain `listen 80`, nginx started answering every request by IP with # the "Welcome to nginx!" page and logged Homeio's block as a conflicting # server name it was ignoring. if [[ "${removed_site}" == "true" && -f "${default_available}" && ! -e "${default_enabled}" ]]; then print_status "Restoring nginx default site..." ln -sf "${default_available}" "${default_enabled}" fi if [[ "${removed_site}" == "true" ]]; then nginx -t >/dev/null 2>&1 && systemctl reload nginx >/dev/null 2>&1 || true fi } remove_app_files() { if [[ -d "${INSTALL_DIR}" ]]; then print_status "Removing app files from ${INSTALL_DIR}..." rm -rf "${INSTALL_DIR}" else print_warn "Install directory ${INSTALL_DIR} not found; skipping." fi } purge_database() { local db_name local db_user db_name="$(get_env_value HOMEIO_DB_NAME "${ENV_FILE}" || true)" db_user="$(get_env_value HOMEIO_DB_USER "${ENV_FILE}" || true)" [[ -n "${db_name}" ]] || db_name="home_server" [[ -n "${db_user}" ]] || db_user="home_server" if ! id -u postgres >/dev/null 2>&1; then print_warn "PostgreSQL OS user not found; skipping DB purge." return fi print_status "Dropping PostgreSQL database '${db_name}'..." runuser -u postgres -- dropdb --if-exists --force "${db_name}" >/dev/null 2>&1 || true print_status "Dropping PostgreSQL role '${db_user}'..." runuser -u postgres -- psql -v ON_ERROR_STOP=1 --set=db_user="${db_user}" >/dev/null 2>&1 <<'SQL' || true SELECT format('DROP ROLE %I', :'db_user') WHERE EXISTS (SELECT 1 FROM pg_roles WHERE rolname = :'db_user') \gexec SQL } purge_data_and_env() { if [[ -d "${DATA_DIR}" ]]; then print_status "Removing data directory ${DATA_DIR}..." rm -rf "${DATA_DIR}" fi if [[ -f "${ENV_FILE}" ]]; then print_status "Removing env file ${ENV_FILE}..." rm -f "${ENV_FILE}" fi if [[ -d "${ENV_DIR}" ]]; then rmdir "${ENV_DIR}" >/dev/null 2>&1 || true fi } remove_system_user_group() { if [[ "${REMOVE_SYSTEM_USER}" != "true" ]]; then return fi if id -u "${APP_USER}" >/dev/null 2>&1; then print_status "Removing system user ${APP_USER}..." userdel "${APP_USER}" >/dev/null 2>&1 || true fi if getent group "${APP_GROUP}" >/dev/null 2>&1; then print_status "Removing system group ${APP_GROUP}..." groupdel "${APP_GROUP}" >/dev/null 2>&1 || true fi } print_summary() { echo "" if [[ "${PURGE}" == "true" ]]; then echo -e "${GREEN}╭────────────────────────────────────────────────────╮${NC}" echo -e "${GREEN}│ Uninstall Complete (Purge) │${NC}" echo -e "${GREEN}├────────────────────────────────────────────────────┤${NC}" echo -e "${GREEN}│${NC} ${APP_NAME} has been completely removed. ${GREEN}│${NC}" echo -e "${GREEN}│${NC} All data, database, and configuration deleted.${GREEN}│${NC}" echo -e "${GREEN}╰────────────────────────────────────────────────────╯${NC}" else echo -e "${GREEN}╭────────────────────────────────────────────────────╮${NC}" echo -e "${GREEN}│ Uninstall Complete │${NC}" echo -e "${GREEN}├────────────────────────────────────────────────────┤${NC}" echo -e "${GREEN}│${NC} ${APP_NAME} has been removed. ${GREEN}│${NC}" echo -e "${GREEN}│${NC} ${GREEN}│${NC}" echo -e "${YELLOW}│${NC} ${YELLOW}Note:${NC} Database and data were preserved. ${GREEN}│${NC}" echo -e "${GREEN}│${NC} Run with ${BLUE}--purge${NC} to remove everything. ${GREEN}│${NC}" echo -e "${GREEN}╰────────────────────────────────────────────────────╯${NC}" fi echo "" } main() { parse_args "$@" require_root local mode="without purge" if [[ "${PURGE}" == "true" ]]; then mode="with purge" fi if ! confirm "Uninstall ${APP_NAME} (${mode})?"; then print_warn "Cancelled." exit 0 fi stop_and_remove_service remove_reverse_proxy remove_app_files if [[ "${PURGE}" == "true" ]]; then purge_database purge_data_and_env remove_system_user_group fi print_summary } main "$@"