#!/usr/bin/env bash
# Copyright (c) 2026 HostFog Ltd. All rights reserved.
# Proprietary and confidential. Use is subject to the HostFog licence (LICENSE).
# Unauthorised copying, modification or distribution is prohibited.
# HostFog server agent — the ONLY thing the panel may run as root (via sudo).
# Every argument is validated; secrets and bulk data (passwords, zone files, scripts, uploads) arrive on stdin.
# The functions live in lib/*.sh next to this file (installed to /usr/local/hostfog/bin/lib, root-only).
set -euo pipefail
umask 022
export LC_ALL=C PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# Never run inside php-fpm.service. Two reasons:
#  1. PHP-FPM's unit can sandbox its children (ProtectSystem/ReadOnlyPaths): /etc and /usr are then read-only for
#     anything it starts, so saving nginx.conf fails ("Read-only file system") and useradd can't create
#     /etc/group.lock ("cannot lock /etc/group; try again later"). A --scope keeps that sandbox, so we ask systemd
#     (PID 1) to start the agent as a short-lived service of its own, with the real, writable filesystem.
#  2. When PHP restarts, systemd stops every process in its cgroup, which would kill the agent half-way through a change.
# stdin/stdout/stderr and the exit code are passed straight through (--pipe --wait). KillMode=process keeps the
# background jobs some commands start (updates, package installs) running after the agent itself returns.
# /etc/hostfog/agent-scope is created by server-fixes.sh when systemd-run works on this server.
if [[ -z "${HF_SCOPED:-}" && -e /etc/hostfog/agent-scope ]] && grep -q 'fpm\.service' /proc/self/cgroup 2>/dev/null; then
  exec systemd-run --quiet --collect --pipe --wait --service-type=exec \
    -p KillMode=process -p Nice=0 -E HF_SCOPED=1 -E LC_ALL=C \
    -- "$(readlink -f "$0")" "$@"
fi

AGENT_LIB="$(dirname "$(readlink -f "$0")")/lib"
for _f in "$AGENT_LIB"/[0-9][0-9]-*.sh; do
  # shellcheck source=/dev/null
  source "$_f"
done
unset _f

[[ $EUID -eq 0 ]] || die "must run as root"
cmd="${1:-}"; shift || true
args() { local min="$1" max="$2"; shift 2; [[ $# -ge $min && $# -le $max ]] || die "wrong number of arguments"; }

# serialise everything that changes config (read-only and streaming commands skip the lock)
case "$cmd" in
  fm|dns-check|backup-get|export-get|db-export|stats|usage|usage-files|mail-queue|disk-usage|resources|sysinfo|service-list|fw-list|f2b-list|php-list|php-available|php-ext-list|log-tail|portals-status|health-check|wp-scan|wp-info|cache-flags|tunnel-status|tunnel-key|backup-list|\
  updates-log|pkg-log|pkg-search|ssl-info|site-info|bandwidth|weblog-stats|mail-usage|mail-list|mail-sieve-get|db-sizes|db-list|\
  ftp-list|git-info|node-list|node-versions|conf-list|conf-get|conf-history|limits-status|py-list|py-versions|term-start|term-reap|import-list|import-fetch|import-scan|import-files|import-mail|import-db|import-clean|job-import|job-offsite|backup-contents|backup-browse|backup-file|backup-verify|offsite-test|offsite-put|offsite-put-panel|offsite-list|offsite-get|offsite-prune|self-update-log|malware-scan|waf-status|waf-log|job-scan|move-pack|move-get|move-receive|move-clean|job-move|pull-files|pull-db|pull-mail|job-pull|redis-info|docker-status|docker-list|docker-run|dnssec-ds|dns-cluster-status|rb-list|rb-version|sw-status|sw-available|pg-list|pg-export|node-detect) ;;
  *) exec 9>/run/hostfog-agent.lock; flock -w 300 9 || die "server is busy, try again";;
esac

case "$cmd" in
  # accounts
  account-create)    args 1 2 "$@"; account_create "$@";;
  pw-selftest)       args 0 0 "$@"; pw_selftest;;
  logs-secure-all)   args 0 0 "$@"; logs_secure_all;;
  apache-start)      args 0 0 "$@"; apache_start && ok;;
  panel-ssl)         args 0 1 "$@"; panel_ssl "$@";;
  mail-name)         args 1 1 "$@"; mail_name_set "$@";;
  portals-on)        args 0 3 "$@"; portals_on "$@";;
  portals-off)       args 0 0 "$@"; portals_off;;
  status-page-on)    args 0 1 "$@"; status_page_on "$@";;
  status-page-off)   args 0 0 "$@"; status_page_off;;
  portals-status)    args 0 0 "$@"; portals_status;;
  portals-web)       args 0 0 "$@"; portals_web; ok;;
  mail-autoconfig)   args 0 0 "$@"; mail_autoconfig_web;;
  dns-check)         args 1 2 "$@"; dns_check "$@";;
  account-passwd)    args 1 1 "$@"; account_passwd "$@";;
  account-quota)     args 2 3 "$@"; account_quota "$@";;
  usage-files)       args 1 1 "$@"; usage_files "$@";;
  account-suspend)   args 1 1 "$@"; account_suspend "$@";;
  account-unsuspend) args 1 1 "$@"; account_unsuspend "$@";;
  account-delete)    args 1 2 "$@"; account_delete "$@";;
  pull-files)        args 7 7 "$@"; pull_files "$@";;
  pull-db)           args 6 6 "$@"; pull_db "$@";;
  pull-mail)         args 6 6 "$@"; pull_mail "$@";;
  job-pull)          args 1 1 "$@"; job_run pull "$@";;
  move-pack)         args 2 2 "$@"; move_pack "$@";;
  move-get)          args 1 1 "$@"; move_get "$@";;
  move-receive)      args 1 1 "$@"; move_receive "$@";;
  move-unpack)       args 2 2 "$@"; move_unpack "$@";;
  move-clean)        args 1 1 "$@"; move_clean "$@";;
  job-move)          args 1 1 "$@"; job_run move "$@";;
  limits-set)        args 4 4 "$@"; limits_set "$@";;
  limits-mode)       args 1 1 "$@"; limits_mode "$@";;
  limits-status)     limits_status;;
  limits-off-finish) limits_off_finish;;
  usage)             args 1 1 "$@"; usage "$@";;
  disk-usage)        args 1 1 "$@"; disk_usage "$@";;
  resources)         resources;;
  bandwidth)         args 2 2 "$@"; bandwidth "$@";;
  weblog-stats)      args 3 3 "$@"; weblog_stats "$@";;
  # websites & SSL
  domain-add)        args 2 3 "$@"; domain_add "$@";;
  alias-add)         args 3 3 "$@"; alias_add "$@";;
  domain-delete)     args 2 3 "$@"; domain_delete "$@";;
  site-set)          args 4 4 "$@"; site_set "$@";;
  site-maint)        args 3 3 "$@"; site_maint "$@";;
  static-rescan)     args 0 1 "$@"; static_rescan "$@";;
  site-info)         args 2 2 "$@"; site_info "$@";;
  ssl-issue)         args 3 3 "$@"; ssl_issue "$@";;
  ssl-wildcard)      args 3 3 "$@"; ssl_wildcard "$@";;
  ssl-custom)        args 2 2 "$@"; ssl_custom "$@";;
  ssl-remove)        args 2 2 "$@"; ssl_remove "$@";;
  ssl-renew)         args 2 2 "$@"; ssl_renew "$@";;
  ssl-info)          args 1 1 "$@"; ssl_info "$@";;
  rules-write)       args 2 2 "$@"; rules_write "$@";;
  migrate-sites)     migrate_sites;;
  # PHP
  php-version)       args 2 2 "$@"; php_set_version "$@";;
  php-ini)           args 1 1 "$@"; php_set_ini "$@";;
  php-install)       args 1 1 "$@"; php_install "$@";;
  log-tail)          args 3 4 "$@"; log_tail "$@";;
  health-check)      args 0 0 "$@"; health_check;;
  health-fix)        args 1 1 "$@"; health_fix "$@";;
  wp-scan)           args 1 1 "$@"; wp_scan "$@";;
  cache-purge)       args 2 2 "$@"; cache_purge "$@";;
  tunnel-enable)     args 1 1 "$@"; tunnel_enable "$@";;
  tunnel-disable)    args 0 0 "$@"; tunnel_disable;;
  tunnel-sync)       args 0 0 "$@"; tunnel_sync;;
  tunnel-status)     args 0 0 "$@"; tunnel_status;;
  tunnel-key)        args 0 0 "$@"; tunnel_key;;
  cache-flags)       args 0 0 "$@"; cache_flags;;
  wp-info)           args 2 2 "$@"; wp_info "$@";;
  img-optimise)      args 4 4 "$@"; img_optimise "$@";;
  img-webp-remove)   args 2 2 "$@"; img_webp_remove "$@";;
  wp-action)         args 3 4 "$@"; wp_action "$@";;
  php-list)          php_list;;
  php-available)     php_available;;
  php-repo)          php_repo;;
  php-ext-list)      args 1 1 "$@"; php_ext_list "$@";;
  php-ext-set)       args 3 3 "$@"; php_ext_set "$@";;
  # databases
  db-create)         args 3 3 "$@"; db_create "$@";;
  db-new)            args 2 2 "$@"; db_new "$@";;
  db-drop)           args 2 2 "$@"; db_drop "$@";;
  db-delete)         args 3 3 "$@"; db_delete "$@";;
  db-passwd|dbuser-passwd) args 2 2 "$@"; dbuser_passwd "$@";;
  dbuser-create)     args 2 2 "$@"; dbuser_create "$@";;
  dbuser-delete)     args 2 2 "$@"; dbuser_delete "$@";;
  db-grant)          args 4 4 "$@"; db_grant "$@";;
  db-revoke)         args 3 3 "$@"; db_revoke "$@";;
  db-list)           args 1 1 "$@"; db_list "$@";;
  db-sizes)          args 1 1 "$@"; db_sizes "$@";;
  db-export)         args 2 2 "$@"; db_export "$@";;
  db-import)         args 2 2 "$@"; db_import "$@";;
  pg-create)         args 2 2 "$@"; pg_create "$@";;
  pg-passwd)         args 2 2 "$@"; pg_passwd "$@";;
  pg-drop)           args 2 2 "$@"; pg_drop "$@";;
  pg-list)           args 1 1 "$@"; pg_list "$@";;
  pg-export)         args 2 2 "$@"; pg_export "$@";;
  pg-import)         args 2 2 "$@"; pg_import "$@";;
  mysql-remote-set)  args 1 1 "$@"; mysql_remote_set "$@";;
  pma-sso)           args 1 1 "$@"; pma_sso "$@";;
  pma-cleanup)       pma_cleanup;;
  # email
  mail-domain-on)    args 2 2 "$@"; mail_domain_on "$@";;
  mail-domain-off)   args 2 2 "$@"; mail_domain_disable "$@";;
  mail-add)          args 3 3 "$@"; mail_add "$@";;
  mail-passwd)       args 2 2 "$@"; mail_passwd "$@";;
  mail-quota)        args 3 3 "$@"; mail_quota "$@";;
  mail-del)          args 2 2 "$@"; mail_del "$@";;
  mail-usage)        args 2 2 "$@"; mail_usage "$@";;
  mail-list)         args 2 2 "$@"; mail_list "$@";;
  webmail-domain)    args 3 4 "$@"; webmail_domain "$@";;
  mail-junk-list)    args 3 3 "$@"; mail_junk_list "$@";;
  mail-junk-release) args 3 3 "$@"; mail_junk_release "$@";;
  mail-sieve-set)    args 2 2 "$@"; mail_sieve_set "$@";;
  mail-sieve-get)    args 2 2 "$@"; mail_sieve_get "$@";;
  webmail-sso)       args 2 2 "$@"; webmail_sso "$@";;
  fwd-set)           args 3 3 "$@"; fwd_set "$@";;
  fwd-del)           args 2 2 "$@"; fwd_del "$@";;
  # DNS
  remote-domain-add) args 2 2 "$@"; remote_domain_add "$@";;
  remote-domain-del) args 2 2 "$@"; remote_domain_del "$@";;
  dns-write)         args 2 2 "$@"; dns_write "$@";;
  dns-delete)        args 2 2 "$@"; dns_delete "$@";;
  # files, FTP, cron, keys, git
  fm)                [[ $# -ge 2 ]] || die "usage"; fm "$@";;
  ftp-add)           args 4 4 "$@"; ftp_add "$@";;
  ftp-passwd)        args 2 2 "$@"; ftp_passwd "$@";;
  ftp-quota)         args 3 3 "$@"; ftp_quota "$@";;
  ftp-del)           args 2 2 "$@"; ftp_del "$@";;
  ftp-list)          args 1 1 "$@"; ftp_list "$@";;
  cron-write)        args 1 1 "$@"; cron_write "$@";;
  sshkeys-write)     args 1 1 "$@"; sshkeys_write "$@";;
  git-clone)         args 3 4 "$@"; git_clone "$@";;
  git-init)          args 2 2 "$@"; git_init "$@";;
  git-pull)          args 2 2 "$@"; git_pull "$@";;
  git-checkout)      args 3 3 "$@"; git_checkout "$@";;
  git-info)          args 2 2 "$@"; git_info "$@";;
  git-deploy)        args 3 3 "$@"; git_deploy "$@";;
  git-delete)        args 2 2 "$@"; git_delete "$@";;
  # software
  wp-install)        args 2 3 "$@"; wp_install "$@";;
  app-install)       args 4 4 "$@"; app_install "$@";;
  node-versions)     node_versions;;
  node-install)      args 1 1 "$@"; node_install "$@";;
  sw-status)         args 0 0 "$@"; sw_status;;
  sw-available)      args 0 1 "$@"; sw_available "$@";;
  sw-set)            args 2 2 "$@"; sw_set "$@";;
  node-set)          args 2 2 "$@"; node_app_set "$@";;
  node-ctl)          args 3 3 "$@"; node_app_ctl "$@";;
  node-npm)          args 2 2 "$@"; node_app_npm "$@";;
  node-delete)       args 2 2 "$@"; node_app_delete "$@";;
  node-build)        args 2 2 "$@"; node_app_build "$@";;
  node-detect)       args 2 2 "$@"; node_detect "$@";;
  node-list)         args 1 1 "$@"; node_app_list "$@";;
  mail-limit)        args 2 2 "$@"; mail_limit "$@";;
  mail-pause)        args 2 2 "$@"; mail_pause "$@";;
  mail-queue)        args 0 0 "$@"; mail_queue_list "$@";;
  mail-queue-do)     args 2 2 "$@"; mail_queue_do "$@";;
  app-remove)        args 3 3 "$@"; app_remove "$@";;
  list-set)          args 2 2 "$@"; mlist_set "$@";;
  list-del)          args 2 2 "$@"; mlist_del "$@";;
  dnssec)            args 3 3 "$@"; dnssec_set "$@";;
  dnssec-ds)         args 2 2 "$@"; dnssec_ds "$@";;
  dns-cluster-set)   dns_cluster_set;;
  dns-cluster-status) dns_cluster_status;;
  dns-secondary-setup) args 1 1 "$@"; dns_secondary_setup "$@";;
  docker-install)    docker_install;;
  docker-status)     docker_status;;
  docker-set)        args 5 5 "$@"; docker_app_set "$@";;
  docker-run)        args 2 2 "$@"; docker_run "$@";;
  docker-ctl)        args 3 3 "$@"; docker_app_ctl "$@";;
  docker-list)       args 1 1 "$@"; docker_app_list "$@";;
  redis-on)          args 2 2 "$@"; redis_on "$@";;
  redis-off)         args 1 1 "$@"; redis_off "$@";;
  redis-info)        args 1 1 "$@"; redis_info "$@";;
  redis-flush)       args 1 1 "$@"; redis_flush "$@";;
  redis-wp)          args 4 4 "$@"; redis_wp "$@";;
  rb-set)            args 2 2 "$@"; rb_app_set "$@";;
  rb-setup)          args 2 2 "$@"; rb_app_setup "$@";;
  rb-ctl)            args 3 3 "$@"; rb_app_ctl "$@";;
  rb-delete)         args 2 2 "$@"; rb_app_delete "$@";;
  rb-list)           args 1 1 "$@"; rb_app_list "$@";;
  rb-version)        ok "$(rb_version)";;
  py-versions)       py_versions;;
  py-set)            args 2 2 "$@"; py_app_set "$@";;
  py-setup)          args 2 2 "$@"; py_app_setup "$@";;
  py-ctl)            args 3 3 "$@"; py_app_ctl "$@";;
  py-delete)         args 2 2 "$@"; py_app_delete "$@";;
  py-list)           args 1 1 "$@"; py_app_list "$@";;
  term-start)        args 1 1 "$@"; term_start "$@";;
  term-reap)         args 2 2 "$@"; term_reap "$@";;
  # backups
  staging-detect)    args 2 2 "$@"; staging_detect "$@";;
  staging-copy)      args 3 5 "$@"; staging_copy "$@";;
  site-clone)        args 3 5 "$@"; site_clone "$@";;
  staging-push)      args 4 6 "$@"; staging_push "$@";;
  import-list)       import_list;;
  import-fetch)      args 2 2 "$@"; import_fetch "$@";;
  import-scan)       args 2 2 "$@"; import_scan "$@";;
  import-files)      args 2 2 "$@"; import_files "$@";;
  import-mail)       args 2 2 "$@"; import_mail "$@";;
  import-db)         args 2 2 "$@"; import_db "$@";;
  import-syshash)    args 1 1 "$@"; import_syshash "$@";;
  import-mailhash)   args 2 2 "$@"; import_mailhash "$@";;
  import-clean)      args 1 2 "$@"; import_clean "$@";;
  job-import)        args 1 1 "$@"; job_import "$@";;
  self-update)       args 1 1 "$@"; self_update "$@";;
  # website security
  malware-scan)      args 1 1 "$@"; malware_scan "$@";;
  malware-quarantine) args 2 2 "$@"; malware_quarantine "$@";;
  malware-restore)   args 2 2 "$@"; malware_restore "$@";;
  malware-purge)     args 2 2 "$@"; malware_purge "$@";;
  malware-delete)    args 2 2 "$@"; malware_delete "$@";;
  job-scan)          args 1 1 "$@"; job_run scan "$@";;
  waf-init)          args 0 0 "$@"; waf_init;;
  waf-set)           args 1 1 "$@"; waf_set "$@";;
  waf-status)        args 0 0 "$@"; waf_status;;
  waf-site)          args 3 3 "$@"; waf_site "$@";;
  waf-skip)          args 4 4 "$@"; waf_skip "$@";;
  waf-log)           args 2 3 "$@"; waf_log "$@";;
  self-update-log)   args 0 0 "$@"; self_update_log;;
  job-offsite)       args 1 1 "$@"; job_run offsite "$@";;
  backup-create)     args 1 1 "$@"; backup_create "$@";;
  backup-list)       args 1 1 "$@"; backup_list "$@";;
  backup-delete)     args 2 2 "$@"; backup_delete "$@";;
  backup-get)        args 2 2 "$@"; backup_get "$@";;
  backup-restore)    args 2 3 "$@"; backup_restore "$@";;
  backup-prune)      args 1 1 "$@"; backup_prune "$@";;
  backup-contents)   args 2 2 "$@"; backup_contents "$@";;
  backup-browse)     args 2 3 "$@"; backup_browse "$@";;
  backup-verify)     args 2 2 "$@"; backup_verify "$@";;
  backup-file)       args 3 3 "$@"; backup_file "$@";;
  offsite-test)      offsite_test;;
  offsite-put)       args 2 2 "$@"; offsite_put "$@";;
  offsite-put-panel) args 1 1 "$@"; offsite_put_panel "$@";;
  backup-export)     args 2 2 "$@"; backup_export "$@";;
  export-get)        args 2 2 "$@"; export_get "$@";;
  export-prune)      args 0 0 "$@"; export_prune "$@";;
  offsite-list)      args 1 1 "$@"; offsite_list "$@";;
  offsite-get)       args 2 2 "$@"; offsite_get "$@";;
  offsite-prune)     args 1 1 "$@"; offsite_prune "$@";;
  # server
  service-list)      service_list;;
  service-restart)   args 1 1 "$@"; service_restart "$@";;
  fw-list)           fw_list;;
  fw-allow)          args 2 2 "$@"; fw_allow "$@";;
  fw-block)          args 1 1 "$@"; fw_block "$@";;
  fw-unblock)        args 1 1 "$@"; fw_unblock "$@";;
  fw-delete)         args 1 1 "$@"; fw_delete "$@";;
  f2b-list)          f2b_list;;
  f2b-unban)         args 1 1 "$@"; f2b_unban "$@";;
  sysinfo)           sysinfo;;
  updates-check)     updates_check;;
  updates-apply)     updates_apply;;
  updates-log)       updates_log;;
  server-reboot)     server_reboot;;
  pkg-search)        args 1 1 "$@"; pkg_search "$@";;
  pkg-install)       args 1 1 "$@"; pkg_run install "$@";;
  pkg-remove)        args 1 1 "$@"; pkg_run remove "$@";;
  pkg-log)           pkg_log;;
  conf-list)         conf_list;;
  conf-get)          args 1 1 "$@"; conf_get "$@";;
  conf-set)          args 1 1 "$@"; conf_set "$@";;
  conf-history)      args 1 1 "$@"; conf_history "$@";;
  conf-restore)      args 2 2 "$@"; conf_restore "$@";;
  stats)             stats;;
  *) die "unknown command";;
esac
