Install & upgrade troubleshooting

What to do when the installer stops, an upgrade does not take, or the service will not come back up. Search the exact error text — every entry here is a failure path that really exists in the installer or that a release actually changed.

Start here

The installer stopped. Where do I look?

The installer prints a red ✗ line naming what failed. That line is the thing to search for on this page.

If the install finished but the service is unhealthy, the service's own log is the next place:

sudo journalctl -u jt-proxense -n 50 --no-pager

Check which version is actually installed:

jt-proxense version

Install fails

This installer must run as root

The one-liner needs sudo. It creates a system user, writes a systemd unit and installs into /opt.

curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh | sudo bash

Note the sudo goes on bash, not on curl.

No supported package manager (apt / dnf / yum / pacman / zypper) found

The installer resolves system packages through one of those five. On a distribution it does not recognise, install the prerequisites by hand and then re-run it — it skips what is already present:

git, python3 (3.10+), python3-pip, python3-venv, systemd
Python 3.10+ required (found ...)

Most common on RHEL / Rocky / AlmaLinux 8, which ship Python 3.6 as python3:

sudo dnf install python3.11 python3.11-pip
curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh | sudo bash

The installer picks the newest interpreter it finds and points the systemd unit at it, so you do not need to change the system default.

/opt/jt-proxense is non-empty but not a git checkout. Refusing to overwrite.

Something is already in the target directory that the installer did not put there — an old manual copy, or an extracted archive. It refuses rather than deleting your files.

Move it aside and re-run:

sudo mv /opt/jt-proxense /opt/jt-proxense.old
curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh | sudo bash

Or install somewhere else:

curl -fsSL … | sudo JT_PROXENSE_INSTALL_DIR=/opt/jtp bash

Your data is not in that directory — users, audit log and settings live in /var/lib/jt-proxense and /etc/jt-proxense, and survive this.

Smoke test failed — a runtime module did not import

Python dependencies installed but one of them will not load. See the exact module name in the line above the failure, then:

sudo python3 -m pip install --upgrade -r /opt/jt-proxense/requirements.txt
sudo systemctl restart jt-proxense

On Debian 12+ / Ubuntu 24.04 the base environment is marked externally managed (PEP 668). The installer detects this and adds --break-system-packages automatically; if you are installing by hand you need the same flag.

git clone or curl fails / the host cannot reach GitHub

The installer needs github.com and raw.githubusercontent.com. Behind a proxy, export it for the whole command — a proxy set only for your shell will not reach the sudo environment:

curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh \
  | sudo https_proxy=http://proxy:3128 bash

For a fully air-gapped host, clone the repository elsewhere, copy it to /opt/jt-proxense, and run install.sh from inside it.

Upgrade fails

How do I upgrade? Is git pull enough?

Re-run the same one-liner. It is idempotent and it is the upgrade path:

curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh | sudo bash

A bare git pull is not enough. It fetches code but does not reinstall the systemd unit, refresh Python dependencies, or apply the file ownership and permission repairs — so you end up running new code under an old unit. If you have already done that, just run the one-liner afterwards; it repairs the rest.

Which version does the installer put on? Can I pin one?

Since v1.1.0 it installs the newest release tag, not the tip of main — so the same command run on two different days gives the same code, and there is something to roll back to.

Pin a specific release, or deliberately follow the development branch:

# a specific release
curl -fsSL … | sudo JT_PROXENSE_REF=v1.1.0 bash

# the development branch
curl -fsSL … | sudo JT_PROXENSE_REF=main bash
Your local changes would be overwritten / dubious ownership

The installer resets the checkout to the release it is installing, so edits made inside /opt/jt-proxense are discarded by design. Keep changes in config.yaml, which is never overwritten.

If git refuses with detected dubious ownership, it is because the checkout is root-owned (deliberate since v1.1.0 — the service must not be able to modify its own code). Run git as root, or let the installer do it:

sudo git -C /opt/jt-proxense status
It says it upgraded, but the UI still shows the old version

Check what the server actually runs first:

jt-proxense version
sudo systemctl status jt-proxense --no-pager | head -5

If that is the new version, your browser is holding the previous index.html. The page detects a stale bundle and reloads itself, but a hard refresh settles it immediately: Ctrl+Shift+R (⌘+Shift+R on macOS).

How do I roll back to the previous version?

Install the earlier tag. Your database and settings are outside the install directory and are not touched:

curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh \
  | sudo JT_PROXENSE_REF=v1.1.0 bash

Database migrations are forward-only. Rolling back across a release that added one is not supported — take a copy of /var/lib/jt-proxense/jt-proxense.db before a major upgrade if you want a way back.

The service will not start

REFUSING TO START: config.yaml could not be parsed

The file is not valid YAML. The daemon refuses rather than falling back to defaults, because those defaults turn authentication off and bind every interface — a broken file must never quietly open an instance up.

Restore the copy taken before the last change:

ls -lt /opt/jt-proxense/config_backups/
sudo cp /opt/jt-proxense/config_backups/config_YYYYMMDD_HHMMSS.yaml \
        /opt/jt-proxense/config.yaml
sudo chown jt-proxense:jt-proxense /opt/jt-proxense/config.yaml
sudo chmod 600 /opt/jt-proxense/config.yaml
sudo systemctl restart jt-proxense

A setting this version does not recognise is not an error — since v1.1.1 it is ignored with a warning naming the key, and everything around it is kept.

systemd: Failed to start / Unit ... failed with a ReadWritePaths error

Since v1.1.0 the unit names each writable path individually, and systemd refuses to start a unit whose ReadWritePaths mentions something that does not exist. Re-running the installer creates them:

curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh | sudo bash

The paths are /opt/jt-proxense/config.yaml, /opt/jt-proxense/config_backups, /opt/jt-proxense/.ssh, /var/lib/jt-proxense and /etc/jt-proxense. This happens if the unit was updated by hand rather than through the installer.

Address already in use / port 8098 is taken

Find what holds it, then either stop that or move jt-proxense:

sudo ss -tlnp | grep 8098

To move it, edit server.http_port in /opt/jt-proxense/config.yaml and restart. On a fresh install you can set it up front:

curl -fsSL … | sudo JT_PROXENSE_PORT=8099 bash
The page loads but has no styling, or the layout is flat

The browser got the JavaScript but not the stylesheet, usually because the files are not readable by the service account after a manual copy:

sudo chmod 644 /opt/jt-proxense/dist/index.html \
                /opt/jt-proxense/dist/assets/index-*.js \
                /opt/jt-proxense/dist/assets/index-*.css
sudo systemctl restart jt-proxense

Re-running the installer fixes this too, and is the safer choice.

Adding a cluster from the UI fails to save

The service writes config.yaml and takes a backup first, so both must be writable by the service account. Editing the file with a root shell redirect changes its owner:

sudo chown jt-proxense:jt-proxense /opt/jt-proxense/config.yaml
sudo chmod 600 /opt/jt-proxense/config.yaml
sudo chown -R jt-proxense:jt-proxense /opt/jt-proxense/config_backups
sudo systemctl restart jt-proxense

Cannot sign in

Too many attempts, please try again later

That IP hit the login rate limit: five failed attempts in five minutes. It clears itself after the cool-down, or immediately:

sudo jt-proxense unlock            # list what is locked
sudo jt-proxense unlock --all      # clear every lockout

This only clears the counter. It never changes a password.

Forgot the admin password, or lost the authenticator

The CLI works without the service running — that is what it is for:

sudo jt-proxense reset-password admin
sudo jt-proxense user reset-totp admin
Completely locked out after an auth misconfiguration

Turn authentication off, fix the configuration, turn it back on:

sudo jt-proxense auth disable       # also binds to 127.0.0.1 only
sudo systemctl restart jt-proxense

Re-enable it with sudo jt-proxense auth set-local.

After upgrading to v1.1.0 or later

Everyone gets locked out after a few failed logins

The most likely upgrade surprise. X-Forwarded-For is now trusted only from loopback or an address you name. If your reverse proxy runs on another host, every request now carries the proxy's address, so all users share one rate-limit bucket — five failed logins from anyone locks out everyone for fifteen minutes. The audit log records the proxy as the source too.

Name the proxy in /opt/jt-proxense/config.yaml:

auth:
  trusted_proxies:
    - 192.0.2.10        # or a CIDR: 192.0.2.0/24

Then sudo systemctl restart jt-proxense and sudo jt-proxense unlock --all. The log says ignoring X-Forwarded-For from … once per peer while this is happening.

The UI stops working when served from another origin

Cross-origin access is closed by default from v1.1.0. If you serve the SPA from a different host or port than the API, name that origin:

server:
  cors_origins:
    - https://ui.example.net

A * entry is ignored on purpose: a wildcard combined with credentials lets any site your browser visits drive this API with your session.

If the UI and API are the same origin — the default, because the app serves its own SPA — you need none of this.

The log says: ignoring unrecognised setting(s)

Informational. Your config.yaml contains a key this version does not know — usually a typo, or an option from a different version. The key is dropped and everything else is honoured; check the spelling against config.example.yaml.

Before v1.1.1 this stopped the service from starting. If you are on v1.1.0 and the daemon refuses to start over a keyword argument, upgrade.

A user with a per-cluster role was refused before, and works now

Expected. Until v1.1.0 the role check read only the global (*) grant, so jt-proxense user grant bob cluster1 operator was ignored at the door. It is now honoured for endpoints under that cluster. Grants scoped to one cluster still do not apply to user administration or global settings.

Review who has what:

sudo jt-proxense user list

Uninstall

How do I remove it completely?

One line. It stops and disables the service, then deletes the install directory, /var/lib/jt-proxense (users, audit log, cluster secrets), /etc/jt-proxense (the master key) and the service user:

curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/uninstall.sh | sudo bash

It asks you to type remove first. Add --yes to skip that in a pipeline: … | sudo bash -s -- --yes.

This is irreversible. Export first if there is any chance you want the instance back — see the next entry.

I want to remove it but keep my settings and users

Export before uninstalling. The bundle holds config.yaml, the SQLite database (users, roles, audit log, notes) and the master key that decrypts the stored cluster secrets, in one passphrase-encrypted file:

sudo jt-proxense export-config /root/jt-proxense-backup.enc

Copy it somewhere off the host, then uninstall. To bring it back on any machine:

curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh | sudo bash
sudo systemctl stop jt-proxense
sudo jt-proxense import-config /root/jt-proxense-backup.enc --force
sudo systemctl restart jt-proxense

The bundle contains the master key, so treat it as a credential and delete it once imported.

The uninstaller says: run as root, or no terminal for confirmation

It needs sudo for the same reasons the installer does. The second message means it was piped with no TTY to read your confirmation from, which is what --yes is for:

curl -fsSL … /uninstall.sh | sudo bash -s -- --yes
Something was left behind after uninstalling

The uninstaller tolerates missing pieces, so a partial state clears with the same commands run by hand:

sudo systemctl stop jt-proxense; sudo systemctl disable jt-proxense
sudo rm -f /etc/systemd/system/jt-proxense.service /usr/local/bin/jt-proxense
sudo systemctl daemon-reload
sudo rm -rf /opt/jt-proxense /var/lib/jt-proxense /etc/jt-proxense
sudo userdel jt-proxense

If you installed somewhere else, substitute that path — the uninstaller honours JT_PROXENSE_INSTALL_DIR too.

Still stuck

Nothing here matches. How do I report it?

Open an issue with enough to reproduce it:

jt-proxense version
cat /etc/os-release | head -2
python3 -V
sudo journalctl -u jt-proxense -n 100 --no-pager

Redact PVE hostnames and API tokens before pasting. Never paste config.yaml as-is — it contains credentials.

Open an issue on GitHub

No entry matches that. Try fewer words, or open an issue.