Compare commits

...

5 Commits

Author SHA1 Message Date
wmantly 60dbfe5b9b Merge pull request #176 from theta42/release/v1.2.2
Release v1.2.2: fix load-balancing crash (wrong lua-resty-balancer module)
2026-07-21 16:22:32 -04:00
wmantly ecdced16fc Release 1.2.2: fix load-balancing crash (wrong lua-resty-balancer module)
Every request to a host with additional load-balancing targets 500'd:
targetinfo.lua required 'resty.balancer.round_robin', which does not
exist in the lua-resty-balancer rock actually installed by the
Dockerfile/install.sh. That rock provides resty.roundrobin instead,
with a different constructor (roundrobin:new(nodes), not
:new() + :reinit(nodes)).

Verified end-to-end in a rebuilt image: requests to a load-balanced
host now return 200 and alternate across both backend targets, with
no Lua errors in the OpenResty log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 16:21:03 -04:00
wmantly aecf275031 Merge pull request #174 from theta42/release/v1.2.1
Release v1.2.1: fix bootstrap admin lockout, refresh screenshots
2026-07-21 02:23:06 -04:00
wmantly 3f46a807e5 Correct version to 1.2.1: v1.2.0 was already tagged for load balancing
The load-balancing feature (merged in #172/#173) was tagged v1.2.0 on
GitHub, but nodejs/package.json and CHANGELOG.md were never bumped for
it. Backfill a 1.2.0 CHANGELOG entry and bump this release to 1.2.1
instead of 1.1.18, so package.json/CHANGELOG stay ahead of the highest
existing tag rather than behind it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 02:07:28 -04:00
wmantly bb1b84b56d Release 1.1.18: fix bootstrap admin lockout, refresh screenshots
models/user_redis.js hardcoded the bootstrap anti-lockout admin
username to 'proxyadmin2', while migrations/permission_bootstrap.js
grants the global-admin permission to conf.auth.adminUsers[0]. An
operator who customized adminUsers away from the default ended up
with a bootstrapped account that had no admin permissions -- a
silent lockout. user_redis.js now derives the bootstrap username the
same way permission_bootstrap.js does.

Also corrected a secrets.js.example comment that claimed the
bootstrap password defaults to the username itself (it actually
generates and logs a random password), and refreshed all README
screenshots against the current UI, including a new load-balancing
screenshot.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 02:04:26 -04:00
11 changed files with 41 additions and 13 deletions
+19
View File
@@ -6,6 +6,25 @@ correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`.
## [Unreleased]
## [1.2.2] - 2026-07-21
### Fixed
- Multi-target load balancing (added in 1.2.0) crashed every request to a load-balanced host: `ops/nginx_conf/targetinfo.lua` required a nonexistent `resty.balancer.round_robin` module. The `lua-resty-balancer` rock installed by the Dockerfile/`install.sh` doesn't provide that path — it provides `resty.roundrobin` (constructed as `roundrobin:new(nodes)`, not `:new()` + `:reinit(nodes)`). Fixed `targetinfo.lua` to use the real module and API; verified end-to-end that requests now round-robin across targets with no Lua errors.
## [1.2.1] - 2026-07-21
### Fixed
- The bootstrap anti-lockout admin account was always created as `proxyadmin2` regardless of `conf.auth.adminUsers`, while `migrations/permission_bootstrap.js` grants the global-admin permission to `conf.auth.adminUsers[0]`. If an operator customized `adminUsers` away from the default, the bootstrapped account and the permissioned account were two different (non-matching) usernames, so the anti-lockout account ended up with no admin access. `models/user_redis.js` now derives the bootstrap username from `conf.auth.adminUsers[0]` (falling back to `proxyadmin2`), matching `permission_bootstrap.js`.
- Corrected a `secrets.js.example` comment that claimed the bootstrap admin's password "defaults to the username itself" — it actually generates a random password printed to the container log on first boot.
### Changed
- Refreshed all README screenshots (hosts, per-host SSO auth, per-host basic auth) against the current UI, and added a new load-balancing screenshot for the multi-target feature.
## [1.2.0] - 2026-07-21
### Added
- Multi-target load balancing: hosts can now specify additional backend targets (`IP:port`, one per line) alongside the primary target; the proxy distributes requests across all of them round-robin via `lua-resty-balancer`. Fixes #47.
## [1.1.17] - 2026-07-20
### Fixed
+4
View File
@@ -31,6 +31,10 @@ management once basic auth is enabled:
[![Per-host basic auth](docs/images/host-auth-basic.png)](docs/images/host-auth-basic.png)
Multiple backend targets per host, load balanced round-robin:
[![Load balancing](docs/images/load-balancing.png)](docs/images/load-balancing.png)
## Why this over the alternatives
Nginx Proxy Manager, Traefik, and Caddy are all good reverse proxies with
Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

+5 -1
View File
@@ -87,7 +87,11 @@ class User extends Table{
User.register();
(async function(){
var defaultUser = 'proxyadmin2'
// Matches migrations/permission_bootstrap.js: the anti-lockout account is
// the first entry in conf.auth.adminUsers (default 'proxyadmin2'), NOT a
// hardcoded name -- otherwise an operator who customizes adminUsers ends
// up with a bootstrap account that has no admin permissions.
var defaultUser = (conf.auth && conf.auth.adminUsers && conf.auth.adminUsers[0]) || 'proxyadmin2';
// Optional: an orchestrator (e.g. theta-env's setup.sh) can set
// auth.localAdminPass in proxy-secrets.js to a generated password so this
// bootstrap account isn't left at a well-known default. Only used on first
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "proxy-api",
"version": "1.1.17",
"version": "1.2.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "proxy-api",
"version": "1.1.17",
"version": "1.2.2",
"license": "MIT",
"dependencies": {
"@fortawesome/fontawesome-free": "^7.3.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "proxy-api",
"version": "1.1.17",
"version": "1.2.2",
"author": [
{
"name": "William Mantly",
+2 -3
View File
@@ -62,7 +62,7 @@ function M.get(ngx, domain, targetInfo)
local json = require "cjson"
local redis = require "resty.redis"
local round_robin = require "resty.balancer.round_robin"
local roundrobin = require "resty.roundrobin"
if not domain then
return nil, 499
@@ -114,12 +114,11 @@ function M.get(ngx, domain, targetInfo)
local cache_key = domain .. "_" .. (res["updated_on"] or "0")
if not M.host_balancers[domain] or M.host_balancers[domain].key ~= cache_key then
local b = round_robin:new()
local nodes = {}
for _, t in ipairs(target_list) do
nodes[t] = 1
end
b:reinit(nodes)
local b = roundrobin:new(nodes)
M.host_balancers[domain] = { b = b, key = cache_key }
end
+8 -6
View File
@@ -67,12 +67,14 @@ module.exports = {
adminUsers: ['proxyadmin'],
groupRoleMap: {},
// Optional: the local anti-lockout admin's initial password, used
// ONLY the first time that account is created. Leave unset and it
// defaults to the username itself ("proxyadmin2") — fine for a quick
// local test, but change it (or set this) before exposing the proxy
// publicly. Once the account exists, this key is never read again;
// change the password via the app itself (or delete the Redis user
// to force it to be re-bootstrapped with a new value here).
// ONLY the first time that account is created. Leave unset and a
// random password is generated and printed to the container log on
// first boot — fine for a quick local test if you copy it from the
// log right away, but set this (or change the password afterward)
// before exposing the proxy publicly. Once the account exists, this
// key is never read again; change the password via the app itself
// (or delete the Redis user to force it to be re-bootstrapped with a
// new value here).
// localAdminPass: 'change-me',
},