Skip to content

Feat/UI redesign production docker#12

Merged
iamfarhad merged 3 commits into
mainfrom
feat/ui-redesign-production-docker
Jul 18, 2026
Merged

Feat/UI redesign production docker#12
iamfarhad merged 3 commits into
mainfrom
feat/ui-redesign-production-docker

Conversation

@iamfarhad

Copy link
Copy Markdown
Owner

No description provided.

farhadzand and others added 3 commits July 18, 2026 16:44
Android Chrome renames text/plain downloads whose extension it doesn't
associate with that MIME type, so "x.conf" landed in Downloads as
"x.conf.txt" - which the WireGuard app refuses to import. Switch both
download surfaces (the frontend Download .conf blob and the subscription
endpoint's attachment response) to application/octet-stream; the OpenAPI
spec documents the change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three real-world failures from a node install session:

- A pasted join token with trailing newlines silently answered the next
  prompts, leaving required settings (node name, WG interface address)
  empty in .env. Required prompts now re-ask until non-empty, strip
  pasted CRs, and validate formats (host:port, numeric port, IPv4/CIDR).
- The control-plane address is the panel's NODE_AGENT_PORT (48443 by
  default), but the prompt didn't say so and accepted anything - users
  naturally enter the WireGuard port. The prompt now spells it out and
  probes the address over TCP, warning + confirming before accepting an
  unreachable one.
- "ERROR: problem running iptables/ufw-init" (classically a kernel
  upgraded without a reboot) aborted the whole install at the firewall
  step under set -e. Both installers now warn with the exact manual
  rule and continue instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A real node install failed with the agent looping on "register rejected:
405 Method Not Allowed" + nginx HTML: the control-plane address was given
as the panel's web port (443), where Caddy routes /agent/register to the
SPA's nginx. Attack the mistake from every side:

- The join-token response now carries panel_addr (panel domain +
  NODE_AGENT_PORT) and the join-token dialog displays it with a copy
  button as "enter exactly this", so admins never guess the port.
- install-node.sh probes https://<addr>/agent/register after the TCP
  check; an HTML answer means "this is the web UI" and the installer
  says so and re-prompts.
- The agent itself detects an HTML/405 register response and explains
  the NODE_AGENT_PORT mix-up instead of dumping the nginx error page.

Also rewrite `wgpanel uninstall`, which only ran `docker compose down -v`
on the panel project (and silently exited non-zero unless you typed
"yes"): it now tears down the self-node stack too, deletes /opt/wgpanel
and /opt/wgpanel-node, removes the CLI itself, and prints exactly what
it is about to destroy before asking for confirmation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@iamfarhad
iamfarhad merged commit 988cb31 into main Jul 18, 2026
3 checks passed

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request improves the node onboarding process by surfacing the correct control plane address (panel_addr) in the UI and validating it during installation. It also updates subscription configuration downloads to use application/octet-stream to prevent Android Chrome from renaming .conf files to .conf.txt, and enhances the uninstallation script. The review feedback highlights a potential issue with using /dev/tcp in bash on systems where it is disabled, a directory context issue when running docker compose down in the uninstaller, and a case-sensitivity issue when checking the Content-Type header.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread deploy/install-node.sh
Comment on lines +101 to +119
if ! timeout 5 bash -c ": </dev/tcp/${PANEL_ADDR%:*}/${PANEL_ADDR##*:}" 2>/dev/null; then
warn "Cannot reach ${PANEL_ADDR} over TCP. Check the address: the port must be the"
warn "panel's node-agent port (NODE_AGENT_PORT in the panel's .env, 48443 by default),"
warn "not the WireGuard port, and it must be open in the panel server's firewall."
read -rp "Use ${PANEL_ADDR} anyway? [y/N]: " CONFIRM
[[ "${CONFIRM,,}" == y* ]] && break
continue
fi
# Reachable is not enough: the panel's WEB port (443) also answers TCP. The real
# agent endpoint replies with plain text/JSON, never HTML - an HTML answer means
# this is the web UI and registration would die with an nginx "405 Not Allowed".
if curl -skm 5 "https://${PANEL_ADDR}/agent/register" 2>/dev/null | grep -qiE '<html|<!doctype'; then
warn "${PANEL_ADDR} answers like the panel's WEB UI, not the node-agent API."
warn "Enter the panel's node-agent port instead: NODE_AGENT_PORT in the panel"
warn "server's /opt/wgpanel/.env (48443 by default) - e.g. ${PANEL_ADDR%:*}:48443."
read -rp "Use ${PANEL_ADDR} anyway? [y/N]: " CONFIRM
[[ "${CONFIRM,,}" == y* ]] && break
continue
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Debian (and by extension, some Ubuntu configurations) disables the /dev/tcp built-in redirection in bash at compile-time for security reasons. When /dev/tcp is disabled, the command bash -c ": </dev/tcp/..." will always fail, causing the script to incorrectly warn that the control plane is unreachable even when it is perfectly healthy.

Since curl is already installed and used in the subsequent check, we can use curl to check both connectivity and the response content in a single request, which is more portable, robust, and efficient.

Suggested change
if ! timeout 5 bash -c ": </dev/tcp/${PANEL_ADDR%:*}/${PANEL_ADDR##*:}" 2>/dev/null; then
warn "Cannot reach ${PANEL_ADDR} over TCP. Check the address: the port must be the"
warn "panel's node-agent port (NODE_AGENT_PORT in the panel's .env, 48443 by default),"
warn "not the WireGuard port, and it must be open in the panel server's firewall."
read -rp "Use ${PANEL_ADDR} anyway? [y/N]: " CONFIRM
[[ "${CONFIRM,,}" == y* ]] && break
continue
fi
# Reachable is not enough: the panel's WEB port (443) also answers TCP. The real
# agent endpoint replies with plain text/JSON, never HTML - an HTML answer means
# this is the web UI and registration would die with an nginx "405 Not Allowed".
if curl -skm 5 "https://${PANEL_ADDR}/agent/register" 2>/dev/null | grep -qiE '<html|<!doctype'; then
warn "${PANEL_ADDR} answers like the panel's WEB UI, not the node-agent API."
warn "Enter the panel's node-agent port instead: NODE_AGENT_PORT in the panel"
warn "server's /opt/wgpanel/.env (48443 by default) - e.g. ${PANEL_ADDR%:*}:48443."
read -rp "Use ${PANEL_ADDR} anyway? [y/N]: " CONFIRM
[[ "${CONFIRM,,}" == y* ]] && break
continue
fi
local response
if ! response=$(curl -skm 5 "https://${PANEL_ADDR}/agent/register" 2>/dev/null); then
warn "Cannot reach ${PANEL_ADDR} over HTTPS. Check the address: the port must be the"
warn "panel's node-agent port (NODE_AGENT_PORT in the panel's .env, 48443 by default),"
warn "not the WireGuard port, and it must be open in the panel server's firewall."
read -rp "Use ${PANEL_ADDR} anyway? [y/N]: " CONFIRM
[[ "${CONFIRM,,}" == y* ]] && break
continue
fi
# Reachable is not enough: the panel's WEB port (443) also answers TCP. The real
# agent endpoint replies with plain text/JSON, never HTML - an HTML answer means
# this is the web UI and registration would die with an nginx "405 Not Allowed".
if echo "$response" | grep -qiE '<html|<!doctype'; then
warn "${PANEL_ADDR} answers like the panel's WEB UI, not the node-agent API."
warn "Enter the panel's node-agent port instead: NODE_AGENT_PORT in the panel"
warn "server's /opt/wgpanel/.env (48443 by default) - e.g. ${PANEL_ADDR%:*}:48443."
read -rp "Use ${PANEL_ADDR} anyway? [y/N]: " CONFIRM
[[ "${CONFIRM,,}" == y* ]] && break
continue
fi

Comment thread deploy/wgpanel
Comment on lines +334 to +335
docker compose down -v --remove-orphans \
|| warn "Panel teardown failed - finish it manually: cd ${INSTALL_DIR} && docker compose down -v"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since wgpanel is installed globally as /usr/local/bin/wgpanel, users can run the uninstall command from any directory. Running docker compose down without changing to the $INSTALL_DIR directory will fail because Docker Compose won't be able to find the docker-compose.yml file.

  (cd "$INSTALL_DIR" && docker compose down -v --remove-orphans) \
    || warn "Panel teardown failed - finish it manually: cd ${INSTALL_DIR} && docker compose down -v"

Comment on lines +116 to +118
if resp.StatusCode == http.StatusMethodNotAllowed ||
strings.Contains(resp.Header.Get("Content-Type"), "text/html") ||
bytes.Contains(bytes.ToLower(body), []byte("<html")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The Content-Type header value can be mixed-case (e.g., Text/HTML or text/HTML). Performing a case-sensitive strings.Contains check on the raw header value might fail to detect HTML responses from some web servers. It is safer to convert the header value to lowercase before checking.

Suggested change
if resp.StatusCode == http.StatusMethodNotAllowed ||
strings.Contains(resp.Header.Get("Content-Type"), "text/html") ||
bytes.Contains(bytes.ToLower(body), []byte("<html")) {
if resp.StatusCode == http.StatusMethodNotAllowed ||
strings.Contains(strings.ToLower(resp.Header.Get("Content-Type")), "text/html") ||
bytes.Contains(bytes.ToLower(body), []byte("<html")) {

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