Skip to content

Fix orphaned Apache vhosts and the 502 they cause (#17) - #20

Merged
Tux82 merged 6 commits into
mainfrom
fix/issue-17-vhost-orphans
Jul 25, 2026
Merged

Fix orphaned Apache vhosts and the 502 they cause (#17)#20
Tux82 merged 6 commits into
mainfrom
fix/issue-17-vhost-orphans

Conversation

@Tux82

@Tux82 Tux82 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Fixes the root cause behind #17 (all sites 502 after an apt upgrade) plus everything else that turned up while reproducing it.

The outage chain

A domain deleted at some earlier point left its vhost in sites-enabled pointing at a DocumentRoot and log directory that no longer existed. Apache kept serving on its already-loaded config, so nothing looked wrong — until apt upgrade restarted it. A missing log directory is fatal at startup, Apache refused to come up, and cloudflared had no origin to reach. Hence 502 on every site at once rather than one.

delete_account.sh enumerated a user's domains by globbing /home/<user>/*/www. With the home dir already gone, the glob found nothing, remove_domain.sh never ran, and the vhost was orphaned.

Changes

commit fix
4742bb8 delete_account.sh — enumerate domains from the filesystem, the panel DB, and vhost ownership
cc0b564 remove_domain.sh / add_domain.shconfigtest before reload; never escalate a failed reload to a restart; stop overwriting a live site's index.php on recreate
7b6046b new inetp rebuild_vhosts — regenerate missing vhosts from the DB, report/disable orphans
4b31edd dns_check.sh — accept a bare domain
9d18224 status.sh — version parse, sort -V update comparison, ASCII fallback for non-UTF-8 consoles
e7b09c0 update_ssh_port.sh — port lives in sshd_config.d/99-inetpanel.conf, verify it took effect, full revert on failure

The reload || restart fallback deserves calling out: a reload that fails is harmless (the good config keeps serving), but the restart that followed it took the whole box down. That ordering turned a single bad vhost into a total outage.

Verification

Run against a live 1.24.4 box with 35 domains:

  • all 8 scripts pass bash -n
  • status.sh reproduced the exact garbage block from 502 Bad Gateway on a Cloudflare-tunneled site #17, and the fix prints Version: 1.24.4 / Update: up to date
  • status.sh renders correctly under both LANG=C.UTF-8 and LC_ALL=C
  • dns_check.sh accepts example.com, --domain example.org, and --verbose example.net
  • rebuild_vhosts --check dry-ran clean: 35 skipped, 0 missing, 0 orphaned
  • the vhost template rebuild_vhosts generates reproduces an existing vhost byte for byte (port and FPM socket path both match)

update_ssh_port.sh is the exception — it rewrites sshd config, so I did not exercise it on a live box. It wants a test where a lockout doesn't matter.

Notes

  • No version bump. The post-commit hook from install-hooks.sh wasn't installed in this clone, so Version.php is untouched — the release commit can own that.
  • CRLF left alone. TiCore/*.php, src/*.php and themes/default/*.php still have CRLF committed while .gitattributes asks for LF, so they show as modified on a fresh clone. Unrelated to this work and a big diff, so I left it. Worth a git add --renormalize . on its own commit.

Tux82 and others added 6 commits July 25, 2026 10:56
delete_account.sh found a user's domains by globbing /home/<user>/*/www.
If that directory was already gone — a partial delete, a manual rm, or
delete_user.sh --force removing the home dir first — the glob found
nothing, remove_domain.sh never ran, and the Apache vhost survived
pointing at a DocumentRoot and ErrorLog dir that no longer existed.

Apache keeps serving on its already-loaded config, so nothing looks wrong
until the next full restart (an apt upgrade, a reboot), at which point a
missing log directory is fatal and Apache refuses to start at all —
taking every site on the box down with it. Reported in #17, where an
apt upgrade turned a long-dead vhost into a 502 across all three sites.

Take the union of the filesystem, the panel DB, and the vhosts whose
DocumentRoot lives under the user's home.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
remove_domain.sh ran `systemctl reload apache2 || systemctl restart
apache2`, which is backwards: a reload that fails leaves the running
(good) config serving, but a restart on a broken config takes every site
on the box down. add_domain.sh reloaded with no validation at all, so a
bad vhost could sit latent until something restarted Apache.

Both now run apache2ctl configtest first. add_domain.sh additionally
disables the vhost it just wrote if validation fails, so a bad add can
never become a boot-time outage.

Also stop clobbering live sites: add_domain.sh supports recreating an
existing domain (see the vhost cleanup near the top), but unconditionally
copied the welcome placeholder over www/index.php — data loss for anyone
re-adding a domain to regenerate its vhost. Only write it into an empty
web root now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rebuild_pools.sh regenerates missing PHP-FPM pools from the panel DB, but
there was no equivalent for Apache. A domain whose vhost went missing had
to be rebuilt by hand, and add_domain.sh was the obvious tool to reach
for — which would overwrite the site's index.php.

Two jobs, both driven by the panel database:

  MISSING  — a domain the panel knows about with no vhost in
             sites-available, regenerated from add_domain.sh's template.
             Existing vhosts are never touched.

  ORPHANED — an enabled vhost whose DocumentRoot or ErrorLog directory no
             longer exists. Reported by default; --disable-orphans
             a2dissites them.

--check reports without modifying anything. Nothing is reloaded unless
apache2ctl configtest passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The arg loop discarded anything that wasn't --domain, so the documented-
looking `inetp dns_check example.com` failed with "Domain is required
(--domain)" — confusing enough that it cost a round trip in #17 while
diagnosing an outage.

Positional domains now work; --domain still does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`grep "APP_VERSION" Version.php | grep -oP "'[^']+'"` matched every line
mentioning APP_VERSION — the doc comments, the User-Agent string, and the
self-update regex — so CURRENT_VER became a multi-line blob that printed
as garbage under "Panel":

      Version:    1.24.4
    User-Agent: iNetPanel/
    available
    latest
    ...

and, never equalling panel_latest_ver, made every run claim an update was
available. Match only the constant declaration, and compare with sort -V
so a stale panel_latest_ver can't read as newer.

Also fall back to ASCII for the box rules and status dots when the
terminal isn't UTF-8 — in the Proxmox/noVNC console these rendered as a
row of '?' characters.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The port was written straight into /etc/ssh/sshd_config, which is
ucf-managed on Debian: upgrading openssh-server prompts whether to keep
your version or take the maintainer's, and taking the maintainer's
silently reverts the port — locking you out on the next reconnect, with
no clue why. Write to /etc/ssh/sshd_config.d/99-inetpanel.conf instead
when the main config includes that directory (bookworm does by default),
falling back to the old behaviour when it doesn't.

While here:
  - detect the current port from `sshd -T` rather than only grepping the
    main file, so a port set in a drop-in is seen
  - confirm sshd actually came back on the new port before reporting
    success — reporting a change that didn't take effect is how people
    get locked out
  - back up and fully restore both files on the revert path

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Tux82
Tux82 merged commit e7b09c0 into main Jul 25, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant