This product is in Beta โ€” not production ready. Looking for testers โ€” please apply  ยท  ๐Ÿž Report a bug โ†’
Documentation โ€” under the hood

Every script, explained.

The panel never runs shell commands directly. Everything privileged goes through these scripts โ€” idempotent, validated, loud on failure. This is the complete list, generated from their own headers.

๐Ÿ‘ฅAccounts & quotas (10)

Everything about the humans (and resellers) on the box: creating accounts, suspending them, syncing passwords, and keeping disk and bandwidth inside the lines.

ScriptWhat it does
bandwidth-collect.shbandwidth-collect.sh โ€” tally per-domain bytes from OLS vhost access logs into panel DB (bandwidth_daily). IDEMPOTENT: sums today's dated lines and upserts, so re-runs and hourly cron are safe. Skips the panel's own access.log.
bandwidth-enforce.shbandwidth-enforce.sh โ€” suspend active non-admin users at/over their monthly bandwidth quota (cPanel-style). Monthly usage = SUM(bandwidth_daily) for the current calendar month. Sets users.bw_suspended=1 so the monthly reset knows this was A
bandwidth-reset.shbandwidth-reset.sh โ€” monthly (1st, 00:05): lift ONLY automatic bandwidth suspensions (bw_suspended=1). Manual suspends are untouched โ€” the panel clears the flag on any manual suspend/unsuspend, so human decisions always stick.
create-account.shcreate-account.sh <username> [php_version] [password] [max_children] password (optional): sets the SYSTEM password via chpasswd so the main account can FTP immediately (vsftpd local_enable). Panel password changes re-sync it via password-sy
delete-account.shdelete-account.sh <username> โ€” full teardown of a hosting account. Idempotent: every step tolerates already-gone pieces. ORDER: system artifacts first (vhosts, zones, pools, crontab, mail maps), home dir + user last. NEVER touches panel DB
disk-usage.shdisk-usage.sh โ€” print "label<TAB>MB" lines for the cleanup page.
password-sync.shpassword-sync.sh <username> <newpass> โ€” re-sync the SYSTEM password after a panel password change (same credential by design; piped to chpasswd, never argv).
quota-check.shquota-check.sh โ€” per-user disk report (home + virtual mail), flags over-quota. Consistency rule: Helpers::quota() measures the same two trees, so the panel's hard gate and this report can never disagree about who's over.
quota-enforce.shquota-enforce.sh โ€” hard disk-quota backstop (kernel quotas unavailable: LXC and VPS mounts are host-owned, so no usrquota). Two locks, both self-healing: over quota (active, non-admin) -> FTP dropped from userlist (530s) AND the FPM pool fi
suspend-account.shsuspend-account.sh <username> <suspend|unsuspend> Shells stay nologin either way (accounts are FTP-only; only the password lock changes).

๐Ÿ’พBackups (20)

Nightly full backups, week-long incrementals, encrypted copies, and offsite mirrors โ€” plus every way to get data back.

ScriptWhat it does
backup-account.shbackup-account.sh <username> โ€” full account backup (all domain dirs + home Maildir + virtual maildirs for the user's domains + full DB dump alongside). Layout: /home/<user>/<domain>/public_html, mail in /var/mail/virtual/<domain>.
backup-all.shbackup-all.sh โ€” loop all panel users
backup-crypt.shbackup-crypt.sh โ€” transparent backup encryption for panel backups. Model: per-user random keys (users.backup_pass), shown ONCE at enable time. Threat model: protects backups AT REST (stolen disk, leaked downloads, offsite copies) โ€” NOT agai
backup-dumps.shbackup-dumps.sh โ€” list database dump files (for selective DB restore).
backup-get.shbackup-get.sh <filename> โ€” stream a panel backup to stdout for download. Runs as root (backups are root-owned). Fails loudly on anything unexpected.
backup-inc.shbackup-inc.sh <username> โ€” rsync hardlinked-daily incremental (complements, never replaces, the tarball backups). Layout: /backup/skypanel/inc/<user>/<date>/ with --link-dest to yesterday, keep 7. Covers the home tree + the user's virtual m
backup-members.shbackup-members.sh <tarball> [prefix] โ€” list members (cap 300) for restore browsing.
backup-reconcile.shbackup-reconcile.sh โ€” register on-disk tarballs missing panel rows (pre-fix nightly backups, fetched offsite copies, hand-placed files). Owner parsed from the panel filename scheme (usernames can't contain dashes); unknown owners are report
backup-schedule.shbackup-schedule.sh <status|HH:MM|disable|enable> โ€” panel control over the nightly backup-all run (admin, Backups page). Edits only the backup-all line in /etc/cron.d/skypanel; everything else untouched. Disabled state is a commented line (r
db-restore.shdb-restore.sh <dbname> <dumpfile> โ€” restore ONE database from a full dump. Extracts the dump section between mysqldump's "Current Database" markers (plus the CREATE/USE preamble) into mysql. Refuses system schemas.
file-restore.shfile-restore.sh <username> <tarball> <member> โ€” extract ONE file from a backup. GUARDED: member must be an exact tar listing entry under home/<user>/ (no globs โ€” the panel passes one validated path); symlinks inside the tarball are never fo
inc-list.shinc-list.sh <username> โ€” list incremental snapshot dates (newest first).
inc-restore.shinc-restore.sh <username> <date> <relpath> โ€” restore one path from an incremental snapshot (date = YYYYMMDD dir). relpath is relative to the snapshot root, e.g. "home/admin/cloudknight.co.uk/public_html/index.html". Ownership repaired to th
mailbox-restore.shmailbox-restore.sh <domain> <localpart> <tarball> โ€” restore one mailbox (whole Maildir) from a backup into the live virtual mail store. The mailbox must still exist (use the Email page to recreate it first).
offsite-disable.shoffsite-disable.sh โ€” stop syncing and forget the remote (config stanza + panel settings rows). Offsite DATA is left alone (delete it at the provider if you mean it). The crypt password dies with the config โ€” fetching afterwards needs the pa
offsite-fetch.shoffsite-fetch.sh <filename> โ€” pull one file back from the encrypted remote into /backup/skypanel/ (decrypted on the fly by the crypt wrapper), so the existing browse/restore/download UI works unchanged. Filename is validated like the rest o
offsite-setup.shoffsite-setup.sh provider=v k=v ... โ€” configure the offsite backup remote (admin, Backups page). Providers: s3 (any S3-compatible: AWS, Backblaze B2, Wasabi, Hetzner, R2, MinIO) | sftp | local (USB mount etc.). Layout in rclone.conf: [offsi
offsite-status.shoffsite-status.sh โ€” TSV for the Backups page: configured, provider, target, last sync line, remote file count + size. Panel runs as nobody; this runs privileged (root-only rclone.conf). Never prints secrets.
offsite-sync.shoffsite-sync.sh โ€” nightly mirror of /backup/skypanel to the encrypted offsite remote (configured via offsite-setup.sh). rclone sync = offsite converges to local (deletes remote files deleted locally past retention โ€” the 14-day local prune i
restore-account.shrestore-account.sh <username> <filename> โ€” restore an account from a panel backup tarball. GUARDED: filename must be a plain *.tar.gz inside /backup/skypanel; only members under home/<user>/ are extracted, excluding the backup staging dir i

๐ŸŒWebsites & PHP (24)

Vhosts, per-user PHP pools and sandboxing, SSL, one-click apps, and the bot shield. The machinery behind every site on the server.

ScriptWhat it does
flarum-install.shflarum-install.sh <domain> <owner> <subdir> <db> <dbuser> <dbpass> <title> <user_id> <domain_id> <url> <admin_email> <admin_user> <admin_pass> One-click Flarum 2.x (modern forum) via its CLI installer โ€” database, settings and admin account
fpm-harden.shfpm-harden.sh [--all | username] โ€” re-apply pool hardening (disable_functions, open_basedir, memory cap, nice) by regenerating pool files via php-pool.sh. Idempotent: regenerating an already-hardened pool is a no-op reload. Weekly via cron
fpm-sandbox.shfpm-sandbox.sh off โ€” turn the systemd sandbox off for PHP-FPM (as root). On: read-only system except site write folders, own process list and /dev/shm, setuid and low-level calls blocked, mail() still works. Applied automatically on update; a version that won't start is put back without it.
fpm-status.shfpm-status.sh โ€” TSV per user pool for Admin > PHP pools (panel runs as nobody and can't read pool files; this runs privileged via ServiceManager). Columns: user, php-version, max_children, hardened(yes/no), memory_limit, nice
maintenance.shmaintenance.sh <domain> <on|off> โ€” park a site behind a holding page by swapping the vhost docRoot between public_html and a maintenance/ dir (no rewrite interplay, no .htaccess dependency). Holding page is branded per domain and created on
ols-throttle.shols-throttle.sh โ€” per-IP request-rate caps in the global perClientConnLimit block (fresh installs via the openlitespeed lib, existing boxes by hand). Connection COUNT was already capped (10000); request RATE was unlimited, so an RPS flood f
opcache-set.shopcache-set.sh <81|82|83> <on|off> โ€” toggle Zend OPcache for one PHP-FPM version.
php-pool-remove.shphp-pool-remove.sh <username> โ€” drop all FPM pools for a user. Idempotent.
php-pool.shphp-pool.sh <username> <81|82|83> [max_children] โ€” create (or move) a user's PHP-FPM pool. One pool per user in their CURRENT version only; switching versions removes the old pool and creates the new one. OLS talks to it via an `fcgi` handl
php-switch.shphp-switch.sh <domain> <8.1|8.2|8.3> [max_children] โ€” move a user's PHP-FPM pool to another version. The OLS handler (fcgi:fpm-<user>) is version-independent, so no vhost edit needed.
phpbb-install.shphpbb-install.sh <domain> <owner> <subdir> <db> <dbuser> <dbpass> <title> <user_id> <domain_id> <url> One-click phpBB (pinned 3.3.19) into a docroot subdir. Finish via the web installer (database page comes pre-filled from config.php โ€” just
preview-add.shpreview-add.sh <domain> <token> โ€” serve preview-<token>.<domain> from the domain's vhost (vhAliases, exact host). DNS wildcard (*.preview) is managed panel-side (DB row + zone re-render); this script only touches OLS. Idempotent. HTTP only
preview-remove.shpreview-remove.sh <domain> <token> โ€” drop the preview alias from the vhost. DNS wildcard stays (shared, harmless); token row dies panel-side.
ssl-issue.shssl-issue.sh <domain> <email>
ssl-remove.shssl-remove.sh <domain> โ€” drop the Let's Encrypt cert on domain delete so renewals stop failing against dead DNS. Refuses while any panel domain row still references the name (shared/sibling cases); mail-cert marker cleared when it points at
ssl-renew-hook.shssl-renew-hook.sh โ€” certbot --post-hook: web + ftp restarts (as before) plus re-sync of the mail cert (LE renews ~60d; without this mail silently falls back to... nothing breaks, but the synced files would go stale at expiry). Reads the dom
ssl-selfsigned.shssl-selfsigned.sh [cn] โ€” TLS for panel (:8090) + webmail (:8091) + phpmyadmin (:8092) with a self-signed cert. For LAN-only servers where Let's Encrypt can't reach. Idempotent. When going public, run certbot + scripts/ssl-issue.sh, which re
vhost-add.shvhost-add.sh <domain> <username> [php_version]
vhost-alias.shvhost-alias.sh <domain> <alias> โ€” park an extra domain on an existing vhost. Web-only (no mailboxes for aliases): creates the BIND zone for the alias and adds it to the vhost's vhAliases. Idempotent.
vhost-remove.shvhost-remove.sh <domain> <username> โ€” remove vhost, OLS registration, Exim maps, docroot. SAFE: docroot is deleted ONLY if it is exactly /home/<user>/<domain>/public_html. Missing pieces are skipped.
vhost-unalias.shvhost-unalias.sh <domain> <alias> โ€” detach a parked domain (vhost + its zone). Idempotent. The main domain and its files are untouched.
vvveb-install.shvvveb-install.sh <domain> <owner> <subdir> <db> <dbuser> <dbpass> <title> <user_id> <domain_id> <url> <admin_email> <admin_user> <admin_pass> One-click Vvveb CMS (website builder) via its CLI installer โ€” database, site and admin account are
wp-autoupdate.shwp-autoupdate.sh <on|off|status> <docroot> โ€” WordPress auto-update policy without wp-cli: a must-use plugin (plugins+translations auto, themes stay manual) + WP_AUTO_UPDATE_CORE=minor in wp-config.php (security releases only โ€” major version
wp-install.shwp-install.sh <domain> <owner> <subdir> <db> <dbuser> <dbpass> <title> <user_id> <domain_id> <url> One-click WordPress into a docroot subdir (empty = domain root). Finish by visiting /wp-admin (web installer creates the admin account there)

๐ŸงญDNS (7)

Authoritative zones, DNSSEC signing, and secondaries. The phone book of the operation.

ScriptWhat it does
dns-add-zone.shdns-add-zone.sh <domain> [ns1] [ns2] โ€” default zone (reseller NS supported)
dns-remove-zone.shdns-remove-zone.sh <domain> โ€” remove BIND zone file + registration, reconfig. Missing pieces are skipped (idempotent).
dns-save-zone.shdns-save-zone.sh <domain> โ€” validate staged zone, install, reload. Fails loudly on bad zones.
dns-secondary.shdns-secondary.sh <domain> <ip1>[,ip2] โ€” allow zone transfers to (and notify) secondary nameserver(s) for one zone. Empty ip list clears back to primary-only. Edits only that zone's stanza in zones.skypanel (never other zones), validates wit
dnssec-disable.shdnssec-disable.sh <domain> โ€” stop signing (stanza back to plain master). Keys are KEPT so re-enabling reuses them and the registrar DS stays valid.
dnssec-enable.shdnssec-enable.sh <domain> โ€” sign a zone with inline-signing under dnssec-policy default (BIND 9.18+: auto-dnssec is deprecated). BIND fully owns key lifecycle (CSK, automatic rollovers) โ€” the panel only triggers and reports the DS. Key dir
dnssec-status.shdnssec-status.sh <domain> โ€” "signed yes|no" + DS lines (for the panel UI).

โœ‰๏ธEmail (19)

Mailboxes, forwarders, lists, autoresponders, DKIM, spam filtering, and delivery scoring. The full mail stack, script by script.

ScriptWhat it does
autoreply-sync.shautoreply-sync.sh โ€” rebuild Exim vacation-reply data from the panel DB. AUTHORITATIVE (unlike email-reconcile.sh): disabled/deleted mailboxes vanish from the maps, so call this after every autoresponder/mailbox/domain change. Layout (taint-
dkim-gen.shdkim-gen.sh <domain> [selector] โ€” create a 2048-bit DKIM key for outbound signing. Private key stays root/Debian-exim-only; prints the DNS TXT to publish. Idempotent: existing keys are reused (rotating breaks published signatures).
dkim-status.shdkim-status.sh <domain> [selector] โ€” "signed yes|no" + TXT record (panel UI).
email-add.shemail-add.sh <address> <owner> โ€” register a virtual mailbox (Dovecot SQL + Exim virtual delivery). No system user involved: auth comes from the panel DB, mail lands in /var/mail/virtual.
email-filters-sync.shemail-filters-sync.sh โ€” rebuild sieve scripts + Exim filter map from the DB. Boxes WITH active rules get /var/lib/skypanel/sieve/<domain>/<box>.sieve (compiled) and a line in /etc/exim4/virtual/filtered, which routes their mail through Dove
email-reconcile.shemail-reconcile.sh โ€” repair virtual-mail maps/dirs from the panel DB + migrate legacy ~/Maildir mail into the right virtual boxes. IDEMPOTENT, additive-only: it never deletes map lines or mail (orphans are REPORTED for manual review). Migra
email-remove.shemail-remove.sh <address> โ€” drop a mailbox: Exim map lines + its virtual Maildir. Idempotent. The domain itself (and other boxes) is untouched.
exim-acl-patch.shexim-acl-patch.sh โ€” idempotently insert the weekend scan-skip block into the LIVE /etc/exim4/exim4.conf (which carries substituted secrets, so it is patched, never overwritten). Anchored on the stock spam-scan lines; a marker comment makes
forward-add.shforward-add.sh <from> <targets> โ€” register/refresh a forwarder line. <targets> is comma-separated; every target must be a plain address (no pipes/files โ€” Exim would never run them, but refuse loudly anyway). Self-forward (loop) is refused.
forward-remove.shforward-remove.sh <from> โ€” drop a forwarder line. Idempotent.
list-sync.shlist-sync.sh โ€” rebuild mailing-list maps from the panel DB. AUTHORITATIVE: removed lists/members vanish. Layout (taint-safe lookups only): /etc/exim4/virtual/lists โ€” "list@domain: /etc/exim4/virtual/listmembers/<domain>/<list>" .../
mail-autoconfig.shmail-autoconfig.sh <domain> <owner> โ€” drop client-setup XML into the docroot so Thunderbird/Outlook/Apple Mail/K-9 configure from email+password alone. No DNS, certs or vhosts needed: both clients fall back to domain-root paths (TB: /.well-
mail-cert-sync.shmail-cert-sync.sh <domain> โ€” point Dovecot + Exim at the domain's Let's Encrypt cert so mail clients (Thunderbird autodiscover etc.) stop hitting the self-signed IP cert. Single shared pair in /etc/ssl/skypanel/ (multi- domain SNI is future
mail-name.shmail-name.sh โ€” print Exim's primary_hostname (this box's mail identity). Used so system-mail forwarding (root@) targets the right domain.
mail-queue.shmail-queue.sh <summary|flush|frozen|thaw-frozen|drop-frozen|drop-all> โ€” Exim queue ops for the panel (runs as root). summary: counts; flush: one queue run; frozen: list frozen ids ( capped ); thaw-frozen: retry frozen; drop-frozen: delete f
mail-score.shmail-score.sh <domain> โ€” 0-100 deliverability score from fast local checks: SPF 25 / DKIM 25 / DMARC 20 / MX 15 / STARTTLS 15. Prints score=N plus one "<key>=<pts> <text>" line per check. Tight timeouts: must finish well inside the panel's
mail-sent.shmail-sent.sh <username> โ€” authenticated SMTP submissions TODAY across all of the user's mailbox addresses (what the rate limiter counts). Reads the live mainlog only (rotated logs excluded), so the number resets daily by nature. Prints an i
mail-usage.shmail-usage.sh <address> โ€” print mailbox usage in MB (one number, for the panel). Reads Dovecot's quota accounting (same numbers IMAP REPORTs), not du.
malware-scan.shmalware-scan.sh โ€” nightly docroot antivirus sweep. Runs daily via cron (off-peak; on-access scanning is deliberately NOT used โ€” fanotify is unavailable in LXC and taxes every request on small boxes). Uses clamdscan (daemon, multithreaded);

๐Ÿ—‚๏ธFiles & FTP (13)

The file manager backend and FTP provisioning: moving, renaming, zipping, and permissioning files as the right user.

ScriptWhat it does
file-chmod.shfile-chmod.sh <username> <relpath> <name> <mode> โ€” set permissions. GUARDED: mode must be exactly 3 octal digits; owner/group stay with the user (never root, never executable-by-others beyond 755). No setuid/setgid bits.
file-del.shfile-del.sh <username> <relpath> <name> โ€” delete one file or directory inside a user's home. GUARDED: no .., no absolute paths, never the home root itself, never a live domain root (use domain delete for those โ€” it tears down vhost/DNS/mail
file-get.shfile-get.sh <username> <relpath> <name> โ€” print one text file for the panel editor. GUARDED like file-del.sh. Refuses binaries (NUL byte) and files over 512KB.
file-mkdir.shfile-mkdir.sh <username> <relpath> <name> โ€” create one directory level. file-new.sh <username> <relpath> <name> โ€” create one empty file. Shared guards with file-del.sh (containment, no home root, validated names).
file-new.shfile-mkdir.sh <username> <relpath> <name> โ€” create one directory level. file-new.sh <username> <relpath> <name> โ€” create one empty file. Shared guards with file-del.sh (containment, no home root, validated names).
file-put.shfile-put.sh <username> <relpath> <tmpfile> <filename> โ€” place a panel-uploaded file into a user's home (relpath is relative to /home/<user>, e.g. "<domain>/public_html"). The web worker (nobody) can't write user homedirs, so PHP stages the
file-rename.shfile-rename.sh <username> <relpath> <oldname> <newname> โ€” rename within one dir. GUARDED like file-chmod.sh: both names validated, realpath must stay inside the home, no symlinks, target must not exist. Never touches home root.
file-unzip.shfile-unzip.sh <username> <relpath> <archive> โ€” extract a zip in place. GUARDED: archive validated; listing scanned for absolute paths / .. entries (zip-slip) before anything is written; result chowned to the user.
file-zip.shfile-zip.sh <username> <relpath> <archive> <name>... โ€” zip a selection into one archive in the same dir. GUARDED like file-rename.sh: all names validated, everything must resolve inside the home, no symlinks, archive must not exist yet. Ref
ftp-add.shftp-add.sh <login> <password> <homedir>
ftp-passwd.shftp-passwd.sh <login> <newpass> โ€” rotate one FTP suffix account's password. Underscore REQUIRED: main-account passwords sync via password-sync.sh instead.
ftp-remove.shftp-remove.sh <login> โ€” drop one FTP suffix account (system user + allow-list). Underscore REQUIRED: main accounts are never removed here (account delete owns those).
ftp-tls.shftp-tls.sh [domain] โ€” pin vsftpd to the LE cert and force TLS-only logins. Idempotent set-or-replace (never duplicates lines). Without this, FTP silently stays plaintext (ssl_enable=NO) even after LE is issued for the site โ€” clients fall ba

๐Ÿ›ก๏ธSecurity & firewall (6)

Watchers, tripwires, jails, and shields. The scripts that notice trouble and do something about it.

ScriptWhat it does
egress-watch.shegress-watch.sh [--seed] โ€” webshell canary: sample established outbound TCP, attribute to system users, alert ONCE per new (user, destination). Sampling (not tracing): short-lived connections between ticks are missed โ€” this catches beacons
fail2ban-panel.shfail2ban-panel.sh <filter-src> โ€” enable the panel-login jail on an existing box (fresh installs get it from the installer). Idempotent: skips present pieces, reloads fail2ban, verifies the jail is up.
integrity-check.shintegrity-check.sh [--baseline] โ€” tripwire-lite for the box's own brains: panel code, privileged scripts, and the configs that control mail, firewall and schedule. Anything changing outside the known writers (panel-update, ssl-issue, exim-a
ufw-manage.shufw-manage.sh <status|allow|deny|block|delete> [args] โ€” panel-safe UFW control. GUARDED: SSH (port 22) rules can never be deleted (lockout protection); ports strictly validated; IPs must parse; rule numbers numeric only.
user-abuse.shuser-abuse.sh โ€” every 5 min: snapshot per-user CPU/MEM/processes to /var/lib/skypanel/abuse.json (the admin Abuse board reads it โ€” no costly ps/du inside web requests) and alert on SUSTAINED offenders: >150% CPU (1.5 cores) or >25% RAM on t
waf-shield.shwaf-shield.sh <domain> <on|off> [--no-reload] โ€” toggle the bot-shield rewrite block in a vhost.conf (marked SHIELD-START/END, before the front controller). Blocks casual mass-scan traffic (traversal, sqli-in-query, shell paths, dotfile expo

โš™๏ธServices & monitoring (16)

Keeping daemons alive and knowing when they are not: health checks, watchdogs, disk janitors, and alerts.

ScriptWhat it does
app-daemon.shapp-daemon.sh <create|remove|start|stop|restart|on|off> <username> <name> [command] [workdir] Supervised user processes as systemd units (karnak-<user>-<name>.service). GUARDED: name strict; workdir must sit inside the user's home; the comm
app-log.shapp-log.sh <username> <name> [lines] โ€” journal tail for one supervised app.
app-status.shapp-status.sh <username> <name> โ€” "active|inactive enabled|disabled" for one daemon.
cron-monitor.shcron-monitor.sh โ€” dead-man alerts for user cron jobs. Every 5 min via cron. A job with expect_min set must FINISH (any exit) at least that often, plus grace_min slack. Overdue jobs mail the OWNER once per episode + panel-notify them; a fres
cron-sync.shcron-sync.sh <username> โ€” rebuild /var/spool/cron/crontabs/<user> from DB
cron-wrap.shcron-wrap.sh <jobid> <command...> โ€” run a user cron job with logging. Appends START/END/EXIT lines + capped output to ~/.skypanel-cron/<id>.log (panel-readable via ACL) so the Cron page shows last-run state.
disk-clean.shdisk-clean.sh <staging|sessions|releases> โ€” safe purges only. staging: orphaned upload fragments (>1 day). sessions: dead PHP sessions (>7 days; live ones are touched constantly). releases: keep newest TWO version dirs (current + rollback t
expiry-check.shexpiry-check.sh โ€” daily domain + SSL expiry watch. Alerts (mail + panel notify to the domain OWNER) when either drops to 30/14/7/1 days, once per band; above 30d the state clears silently. Caches both dates on domains for the Domains page.
push-chat.shpush-chat.sh <bot-token> โ€” list recent chats that messaged the bot (panel "Find my chat"). Message the bot anything first, then run: prints one "chat_id name" per line for the UI to offer. Token validated by shape only here; Telegram itself
push-digest.shpush-digest.sh [--test] โ€” phone pager for box-level urgents via a Telegram bot. Polls recent admin-scope notifications (user_id NULL) of urgent types and sends them with sendMessage. Default OFF (no settings = silent exit). No inbound ports
server-ip.shserver-ip.sh โ€” print the primary outward IPv4 (used for default DNS records)
service-control.shservice-control.sh <restart|start|stop|status> <service-key>
service-watchdog.shservice-watchdog.sh โ€” restart critical units that died. Runs every 5 min via cron. Logs recoveries to the panel log. Never touches ssh/cron themselves blindly: restarting those is safe (no config change), so they're included โ€” a dead cron i
session-kill.shsession-kill.sh <sid> โ€” destroy one panel session file. Strict basename handling: the sid can never escape the sessions dir.
session-list.shsession-list.sh โ€” list panel sessions (runs as root: the sessions dir is drwx-wx-wt, i.e. listable only by root by design). Tab-separated: sid, username, role, last-activity-epoch.
watchdog-status.shwatchdog-status.sh โ€” last-run age for the panel banners (Services + Notifications). Prints "age=<seconds>" or "age=never". Panel runs as nobody; the heartbeat file is world-readable by design (a timestamp, no secrets).

โฌ†๏ธUpdates & transfers (10)

Safe self-updates with rollback, version probes, and moving whole servers or cPanel accounts in.

ScriptWhat it does
build-panel-update.shbuild-panel-update.sh [version] โ€” assemble the self-update package the panel Update UI accepts, at /tmp/karnakpanel-panel-<ver>.tar.gz. panel-update.sh is strict and refuses anything else, so this mirrors its gates exactly. Layout at tarbal
cpmove-import.shcpmove-import.sh <tarball> <cpuser> <paneluser> <domain> โ€” import a cPanel full backup (cpmove-USER.tar.gz) into an EXISTING panel user + domain. DETACHED (runs longer than the panel sudo timeout): backgrounds into /var/log/karnakpanel/soft
panel-fetch-update.shpanel-fetch-update.sh โ€” one-click update import for the Update page. Downloads the LATEST signed panel-update package from the release feed into the panel staging dir (verified SHA256; signature itself is verified later by panel-update.sh b
panel-rollback.shpanel-rollback.sh [release] โ€” revert panel+scripts to a kept release (default: the newest release that ISN'T current). Restores the matching pre-upgrade DB dump when one exists. DETACHED like panel-update.sh; watch update.log.
panel-transfer-import.shpanel-transfer-import.sh <bundle.tar.gz> [--finish] [--force] โ€” restore a panel-transfer.sh bundle onto a FRESH box (run as root on the TARGET). VERSION GATE: bundle MANIFEST version must equal the local panel VERSION. Update the target fir
panel-transfer.shpanel-transfer.sh [--out DIR] โ€” export a server-transfer bundle (run as root on the SOURCE box). READ-ONLY: dumps config + databases, never mutates. Bundle holds everything small and machine-specific: panel DB dump, customer MySQL dumps, BI
panel-update.shpanel-update.sh <tarball> โ€” safe self-update for KarnakPanel itself. DETACHED (web timeout): all work backgrounds into /var/log/karnakpanel/update.log. Tarball layout (built by packaging the repo): MANIFEST โ€” VERSION=<x.y> line (
system-reboot.shsystem-reboot.sh [go|cancel] โ€” reboot (or cancel a pending reboot) DETACHED. The panel sudo wrapper kills scripts after 15s, but shutdown returns instantly: "go" schedules +1 min so the panel can flash back first. Without args it only REPOR
system-update.shsystem-update.sh [go] โ€” apt update + upgrade for the whole box, DETACHED. The panel sudo wrapper kills scripts after 15s, so the real work backgrounds into /var/log/karnakpanel/apt-upgrade.log (watch it in admin Logs). Without "go" it only
update-check.shupdate-check.sh โ€” daily "is there a newer release?" probe (admin dashboard + update banners). Compares local panel/VERSION against downloads/VERSION with sort -V; severity comes from the newest CHANGELOG section (mentions of "security" = ur

๐Ÿ“ฆInstaller modules (14 modules)

The fourteen library modules that turn a bare Ubuntu image into a hosting server, in order.

ScriptWhat it does
bind.shBIND9 authoritative
clamav.shClamAV antivirus for mail (OPTIONAL โ€” heavy).
common.shcommon: base packages, users, dirs
cron-ssl.shcron + certbot auto-renew
exim-dovecot-spamassassin.shExim4 + Dovecot + SpamAssassin (spamd)
fail2ban-ssh.shFail2ban + OpenSSH hardening
mariadb.shMariaDB
openlitespeed.shOpenLiteSpeed from official repo + default vhost for panel on :8090
panel-deploy.shDeploy panel code into /usr/local/skypanel/panel + wire OLS vhost docroot
php-fpm.shPHP-FPM per-user pools (customer PHP isolation). Ubuntu 24.04 / Debian 13 repos (Debian uses the Debian PHP repository, key pinned in the installer)
php-multiphp.shLSPHP multiple versions 8.3 8.4 8.5
phpmyadmin.shphpMyAdmin on :8092 (OpenLiteSpeed + LSPHP83 + local MariaDB)
roundcube.shRoundcube webmail on :8091 (OpenLiteSpeed + LSPHP83 + local Dovecot/Exim)
vsftpd.shvsftpd with virtual-user friendly defaults (system users chrooted)

๐ŸงฐPanel internals (12)

Small gears: logging, downloads stats, release checks, and the bits that glue everything together.

ScriptWhat it does
dev-snapshot.shdev-snapshot.sh [label] โ€” poor-man's versioning for a git-free repo (run on the DEV host after each feature). Copies the project tree (minus runtime) to ~/snapshots/<date>-<label>/ plus a sha256 manifest, keeps the last 10. Answers "what ch
download-digest.shdownload-digest.sh โ€” weekly append of download-stats.sh to a cumulative log (log rotation would otherwise eat history). Runs Mondays 06:15 via cron.
download-stats.shdownload-stats.sh โ€” install/download counts from the WEBSERVER's own logs. No beacons, no fingerprints: counts GETs of release artifacts served from /downloads (tarball + installer + VERSION), per file and per-day unique IPs. Run on the box
log-read.shlog-read.sh <service> [lines] โ€” tail a WHITELISTED log for the panel log viewer. Runs as root (most service logs are 640/600). Anything off-list is refused; line count is clamped so a 1MB OLS error.log can't blow up the web request.
panel-domain.shpanel-domain.sh <domain> [admin-email] โ€” onboard the PANEL's own domain at install time: DB rows + zone + vhost + autoconfig (mirrors DomainController::create for the admin user), then LE + services cert ONLY if the domain already resolves
phpini-set.shphpini-set.sh <81|82|83> <key> <value> โ€” change one allowlisted php.ini setting. Backs up first; validates with the FPM config test; rolls back on failure.
pma-gate.shpma-gate.sh <username> <hash|--revoke> โ€” manage the phpMyAdmin Basic-Auth gate. Only a HASH ever crosses here (generated panel-side); plaintext never hits argv/ps.
release-check.shrelease-check.sh <tarball> โ€” fail the cut if dev-only files ship. Local-only files (COMMANDS.md, internal docs, secrets, scaffolding) must NEVER reach users; the tar include-list is human-edited, so verify output.
ssh-addkey.shssh-addkey.sh <"type base64 [comment]"> โ€” append a root public key (panel SSH page). Strict shape check, duplicates skipped, perms fixed. Private keys and options (command=, ...) are refused โ€” this file is root login, keep it plain.
ssh-keygen.shssh-keygen.sh โ€” create a root ed25519 keypair for panel admins (no passphrase: it's for automated/owner use; passphrase-protected keys belong on YOUR machine โ€” see the SSH page how-to). Refuses if a keypair already exists (never silently re
ssh-password.shssh-password.sh <on|off> โ€” allow or forbid SSH password logins (keys-only mode). SAFETY (never regress): 'off' REFUSES unless /root/.ssh/authorized_keys holds at least one valid public key โ€” disabling passwords with no key = locked out. Res
ssh-status.shssh-status.sh โ€” TSV for Admin > SSH: password-auth, root-login, key count + fingerprints, panel-generated keypair present. Panel runs as nobody and can't read sshd_config/authorized_keys; this runs privileged.
๐Ÿž Report a bug