Skip to main content
Version: 2.1.0

Reinstalling a Server That Has Customers On It

INSTALL.sh was written for an empty machine. Running it again on a server that already has hosting accounts used to be unsafe: it regenerated the MySQL root password, copied the stock configuration over the operator's own, rm -rf'd directories the running server had written to, and — because it saw its own panel holding port 2083 — usually refused to start at all.

It is now safe to point at a server that matters, and it will say what it is about to do before it does it.

The three ways to run it

bash INSTALL.sh --dry-run # report what would change; change nothing
bash INSTALL.sh --adopt # update an existing installation in place
bash INSTALL.sh --reinstall # re-run every step, keeping the data

Running plain bash INSTALL.sh on a machine that already has HiTechCloud on it now stops with an error that names those three, rather than treating the machine as empty.

Other flags:

FlagWhat it does
--forceProceed past the refusals: a checkout with local changes, a version downgrade, a MySQL root password that no longer authenticates. Read what is being refused first.
--no-backupSkip the pre-flight backup. On a server with customer data this is not advisable and the run says so.

Start with the dry run

bash INSTALL.sh --dry-run

It changes nothing at all — no packages, no services, no files. It reports the version, account, site and database counts it found, every configuration file a real run would create or rewrite, and a unified diff of each one it would replace. That report is what makes it reasonable to point the installer at a production server.

The pre-flight backup

Before an --adopt or --reinstall writes anything, it runs deploy/adopt-server.sh, which copies the three things a reinstall cannot regenerate:

  • the panel database — accounts, domains, sites and plans, as a compressed SQL dump. Everything else on the server is derived from it; it is derived from nothing.
  • /etc/hitechcloud — credentials, the per-site records under native/sites, the issued certificates, DNS zone files, and the operator's own hitechcloud.config.
  • the account list — every hosting account with its uid and gid. An account recreated with a different uid leaves every file in its home directory owned by nobody, which is the failure here that is hardest to undo.

It lands in /var/backups/hitechcloud/preflight-<timestamp>/, the path is printed, and /var/backups/hitechcloud/latest points at the most recent one. Set HITECHCLOUD_BACKUP_ROOT to put it somewhere else.

Customer content/home, the customers' own MySQL databases, the mail spool — is deliberately not copied: it is far too large for a pre-flight tarball, and nothing in the installer writes to any of it. What happens instead is that it gets counted, before and after.

Nothing was lost

The backup includes a manifest.env with the account, site, domain, database, zone, certificate and home-directory counts as they were before the run. At the end, the installer re-counts and compares:

bash deploy/adopt-server.sh --compare /var/backups/hitechcloud/latest/manifest.env

A count that went up is somebody using the server while you worked. A count that went down fails the run and names what went missing, so "nothing was lost" is something you can check rather than something you have to assume.

You can run adopt-server.sh on its own at any time:

bash deploy/adopt-server.sh --survey # report only
bash deploy/adopt-server.sh --backup # take a backup, print its path

Running it on the demo server

The demo server is redeployed through CI/CD, not from a shell on the box. In GitHub Actions, run the Deploy to demo workflow with workflow_dispatch and pick a mode:

ModeWhat happens
deployThe normal path, and what every push to main does: build here, copy there, re-render the sites, restart. It does not run INSTALL.sh.
dry-runSurveys the server and runs INSTALL.sh --dry-run on it. Changes nothing. The report goes into the run summary.
reinstallThe data-safe INSTALL.sh path: pre-flight backup, every step idempotent, then the count comparison. The backup path appears in the run summary.

Run dry-run first and read its summary. Then run reinstall.

Restoring

Everything the run replaced is in the backup directory, under replaced/ with its original path. To go further back:

cd /var/backups/hitechcloud/latest
gzip -dc panel.sql.gz | mysql # the panel database
tar -C / -xzf etc-hitechcloud.tar.gz # /etc/hitechcloud
systemctl restart hitechcloud hitechadmin

What changed, and why

Each of these was a step that assumed the machine was empty.

StepBeforeNow
Port check2083 and 2087 held by the panel itself counted as a conflict, so the installer refused to run on any working serverPorts held by hitechcloud and hitechadmin are treated as ours
Repository updategit reset --hard discarded local changes in /opt/hitechcloudStops and names the changed files unless --force
CredentialsA missing credentials.env meant a new MySQL root password on a live databaseThe working password is recovered from the option files; if none works, the run refuses
MySQL root passwordALTER USER ... || true, then the new password was written to the client files whether or not it had workedOnly set when it is not already in force, and proven to authenticate before anything that depends on it is written
/root/.my.cnf, mysql.cnfRewritten every runWritten only when the content differs; a socket-authenticated root no longer gets a password line that breaks it
Feature sets, stock configcp -a over the operator's filesCopied through a comparison, with the previous content kept
hitechcloud.configCarefully preserved, then overwritten a few lines later by the shipped copyExcluded from both copies
services.jsonThe shipped copy replaced the one generated from this host's units, putting every admin Services row back to "Inactive"Excluded
hitechcloudclirm -rf took the generated aliases.txt and the ImunifyAV scripts with itrsync --delete with the same exclusions the deploy pipeline uses
panel.env, admin.envRewritten wholesale, deleting anything an operator had addedMerged: our keys are set, everything else is kept
systemd unitsOverwritten every runWritten only when they differ, previous copy kept
Panel binaryThe mirror's "latest" installed over whatever was running, downgrade includedStaged, questioned, and refused if it is older than the running build
Service restartenable --now does nothing to an already-running service, so a new binary was never picked upRestarted when the binary on disk actually changed
RedisRestarted on every run, dropping every panel sessionRestarted only when its configuration changed
Administrator accountA missing sqlite3 made the count read 0 and added a second administratorRefuses to guess when the table cannot be read
Hosting accountChecked /etc/passwd onlyChecks the panel's users table too, so a half-created account is reported rather than duplicated
Multi-WebServer HostingA failure was a warningFatal on a server with websites — a site whose configuration did not regenerate is a site that is down
Web configurationNothing checked it afterwardsBoth nginx configurations are tested; if either is rejected the previous set is restored from a snapshot and the run stops

What it still will not do

  • It does not migrate the panel database schema. It loads the schema when there is none, and leaves an existing one alone.
  • It does not back up /home or the customers' own databases. Use hitechcloudcli user-backup for those.
  • --force turns off every refusal listed above. There is a reason for each of them; read the message before you reach for it.