Skip to content

feat(chief): support sub-path hosting via base_url - #211

Open
ssFari wants to merge 9 commits into
BlankOn:mainfrom
ssFari:feat/issue-161-base-url
Open

feat(chief): support sub-path hosting via base_url#211
ssFari wants to merge 9 commits into
BlankOn:mainfrom
ssFari:feat/issue-161-base-url

Conversation

@ssFari

@ssFari ssFari commented May 1, 2026

Copy link
Copy Markdown

feat: allow irgsh-chief to run under a sub-path with base_url (v2)

Description Thank you for the detailed feedback on the previous iteration. I have refactored the implementation to address all blockers and design concerns. This version ensures full backward compatibility with workers while providing a robust sub-path support for the dashboard.

Addresssed Feedback & Improvements

  1. Clean utils/config.yaml (Blocker Add clone task #1)

    • Reverted all accidental local edits (indentation, redis host, signing keys, etc.) to match upstream exactly.
    • Only added optional commented examples for base_url and public_url under the chief section.
    • Restored iso.public_base_url to prevent regression.
  2. Dual-Mux Routing for Worker Compatibility (Blocker CI/CD #2)

    • Implemented Option 3: The inner mux is now registered at both the root (/) and the prefix (BaseURL/).
    • Result: Workers can still reach /api/v1/* at the root without any config changes, while the dashboard and user routes are accessible via the sub-path.
  3. Dedicated PublicURL & Proper Normalization (Design SQL Schema #3 & Fix urfave cli #4)

    • Added a separate public_url field in ChiefConfig to distinguish between internal listen addresses and external-facing
      URLs.
    • Created a normalizeChiefConfig function that trims trailing slashes from Address, PublicURL, and BaseURL to prevent
      double-slash issues (//logs/...).
  4. Strict BaseURL Validation (Design Working chainned signature through endpoint. #5)

    • Added a custom Regex validator (^/[A-Za-z0-9_-/]*$) registered in applyDefaults.
    • This catches invalid characters (spaces, query strings, etc.) during configuration load.
  5. Enhanced Testing & Refactoring (Nit Minimal complete builder worker #6 & Repo initialization. Working package injection.=[ #7)

    • Refactored internal/config/config_test.go to provide a complete valid config, ensuring the validator doesn't swallow
      unrelated errors.
    • Extracted and unit-tested normalizeChiefConfig directly.
    • Simplified the normalization logic using the suggested collapsed branch.
  6. Visibility & Maintainability (Nit irgsh-cli is now part of irgsh-go #8 & Improve docs #9)

    • Added a log.Println warning when a notification is sent without a logBaseURL, making misconfigurations visible.
    • Introduced a baseurl template helper in the dashboard. All internal links now use this helper, ensuring sub-path
      consistency across the UI.
      Verification Results
    • Unit Tests: All tests passed (go test ./internal/config/... ./internal/chief/usecase/...).
    • Manual E2E: Verified that curl http://localhost:8080/api/v1/version works (Worker path) AND http://localhost:8080/irgsh/ serves the dashboard (User path).
    • Log Links: Verified log links in the dashboard correctly point to /irgsh/logs/....

Fixes

@atqamz atqamz changed the title Feat/issue 161 base url feat(chief): support sub-path hosting via base_url May 5, 2026

@atqamz atqamz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Workflow note

This is a follow-up to #210, which was closed and re-opened as a new PR. For future iterations: push additional commits to the same branch (fix/allow-base-url-chief) instead of closing and opening a new PR. This keeps all review history in one thread, prior comments stay anchored to their lines, and reviewers don't need to cross-reference two PRs to follow the conversation.

Also renamed the PR title from Feat/issue 161 base url to feat(chief): support sub-path hosting via base_url for better readability in the PR list.


Prior review items — all addressed

All 9 items from the #210 review (2 blockers, 3 design, 4 nits) have been correctly addressed. Good work.

# Item Status
B1 utils/config.yaml scope creep Fixed — only 2 commented lines added
B2 Sub-path routing breaks workers Fixed — dual-mux: inner mux at both / and BaseURL/
D3 Notification URL used internal Address Fixed — new public_url field with Address fallback
D4 Address not normalized Fixed — normalizeChiefConfig trims all three fields
D5 Thin BaseURL validation Fixed — regex validator ^/[A-Za-z0-9_\-/]*$
N6 Test swallowed validator error Fixed — full valid config in tests + direct normalizeChiefConfig unit test
N7 Redundant normalization branch Fixed — collapsed to suggested form
N8 Silent log URL omission Fixed — log.Println warning added
N9 Dashboard links not prefix-aware Fixed — baseurl template helper introduced

New issues

A — public_url + base_url double-prefix footgun (blocker)

In cmd/builder/builder.go, cmd/iso/iso.go, cmd/repo/repo.go:

logBaseURL := irgshConfig.Chief.PublicURL
if logBaseURL == "" {
    logBaseURL = irgshConfig.Chief.Address
}
logBaseURL += irgshConfig.Chief.BaseURL

An operator who sets public_url: 'https://domain.com/irgsh' (the full externally-reachable URL, which is the natural thing to write) AND base_url: '/irgsh' gets:

https://domain.com/irgsh/irgsh/logs/...

The current config comments don't hint at this constraint. Pick one of:

  1. Document the convention: public_url is the origin only (no sub-path); base_url is appended automatically. Add a comment in utils/config.yaml next to the public_url example: # origin only — base_url is appended automatically.
  2. Drop the append when public_url is set: If the operator provided public_url, assume it's already the complete external URL and don't append BaseURL.

Either option works; option 1 is a one-line doc fix, option 2 changes the logic. Without one of these, operators deploying behind a reverse proxy will get broken notification links that are hard to diagnose.

B — RegisterValidation error swallowed (nit)

internal/config/config.go:

validate.RegisterValidation("baseurl", func(fl validator.FieldLevel) bool { ... })
return validate.Struct(cfg)

RegisterValidation returns an error if the tag name is empty or the function is nil. The return value is ignored. One line:

if err := validate.RegisterValidation("baseurl", ...); err != nil {
    return err
}

C — Regex compiled on every config load (nit)

regexp.MustCompile runs inside the closure on every call to applyDefaults. Lift to package level:

var baseURLRegex = regexp.MustCompile(`^/[A-Za-z0-9_\-/]*$`)

Reference from the closure instead of compiling each time.

D — Dual-mux: /api/v1/* accessible at both paths — add a comment (nit)

The dual-mux means /irgsh/api/v1/version also responds (worker routes reachable via prefix too). This is intentional for Option 3, but a future reader will see the double-registration and wonder if it's a bug. One comment in setupRoutes:

// rootMux forwards prefixed browser traffic to mux while also keeping mux
// reachable at root so workers can call /api/v1/* without config changes.

E — Stray blank-line removals (nit)

The diff removes the blank line between sendBuildNotification/sendISONotification/sendRepoNotification and the following Build/BuildISO/Repo function in all three worker files. Unrelated to this feature; restore them.

F — TestBaseURLValidation may not fire the baseurl validator (nit)

The minimal IrgshConfig in the test omits Repo, ISO, Storage, Monitoring. If those structs have validate:"required" fields, applyDefaults may return a different validation error before reaching the baseurl check — valid BaseURL cases would still pass (error for wrong reason), but invalid cases might report the wrong field. CI is green so it works today.

Suggest adding one assertion that a bad BaseURL produces an error mentioning BaseURL or baseurl, to confirm the right validator fires.


All prior blockers resolved — the core approach is solid. Issue A is the only item blocking merge; B–F can be cleaned up in the same PR.

ssFari pushed a commit to ssFari/irgsh-go that referenced this pull request Jun 3, 2026
@ssFari

ssFari commented Jun 3, 2026

Copy link
Copy Markdown
Author

I have addressed all the feedback items (A-F) from the latest review. Thank you for the thorough review!

ssFari added a commit to ssFari/irgsh-go that referenced this pull request Jun 3, 2026
@ssFari
ssFari force-pushed the feat/issue-161-base-url branch from ef94a68 to 268004a Compare June 3, 2026 07:08
@ssFari
ssFari force-pushed the feat/issue-161-base-url branch from 268004a to 009a922 Compare June 3, 2026 07:09
@ssFari

ssFari commented Jun 3, 2026

Copy link
Copy Markdown
Author

Thanks again for the thorough review. All six new items (A–F) are addressed in ef94a68. Details below for quick re-verification:

A — public_url + base_url double-prefix (blocker) — Went with option 2: when public_url is set it is treated as the complete external URL and base_url is not appended; otherwise we fall back to address + base_url. See cmd/builder/builder.go:29-31, cmd/iso/iso.go:37-39, cmd/repo/repo.go:30-32. The convention is documented in utils/config.yaml:21 (# full external URL, ignores base_url if set). Note the dual-mux also serves /logs/... at both root and the prefix, so log links resolve regardless.

B — RegisterValidation error swallowed — Now returned: internal/config/config.go:180-184.

C — Regex recompiled per load — Lifted to package-level baseURLRegex: internal/config/config.go:14, referenced from the closure.

D — Dual-mux needs a comment — Added at cmd/chief/main.go:166-167 explaining why mux is registered at both / and BaseURL/.

E — Stray blank-line removals — Restored before Build/BuildISO/Repo in all three worker files.

F — Test may not fire the baseurl validatorTestBaseURLValidation now exercises invalid cases (space, query string, control char, protocol) and asserts the error mentions baseurl: internal/config/config_test.go:90-128.

Verification: go build ./... clean; go test ./internal/config/... ./internal/chief/usecase/... pass. Ready for re-review.

@herpiko

herpiko commented Jun 3, 2026

Copy link
Copy Markdown
Member

Hi @ssFari, thanks for contributing.

Can we wrap this within irgshConfig struct? So we don't have to put conditional check here and there.

func sendBuildNotification(taskUUID, status string, jobInfo notification.JobNotificationInfo) {
	notification.SendJobNotification(
		irgshConfig.Notification.WebhookURL,
		irgshConfig.FullBaseURL,
		"Build",
		taskUUID,
		status,

@ssFari

ssFari commented Jun 3, 2026

Copy link
Copy Markdown
Author

Thanks @herpiko, good call. Done in 607f66b.

The public_url/base_url resolution now happens once at config load time into IrgshConfig.FullBaseURL (internal/config/config.gocomputeFullBaseURL, called right after normalizeChiefConfig). The three notification senders just reference it now, no more conditional in each:

func sendBuildNotification(taskUUID, status string, jobInfo notification.JobNotificationInfo) {
	notification.SendJobNotification(
		irgshConfig.Notification.WebhookURL,
		irgshConfig.FullBaseURL,
		"Build",
		taskUUID,
		status,
		jobInfo,
	)
}

Same change applied to sendISONotification and sendRepoNotification. Added TestComputeFullBaseURL covering both branches (public_url set vs. address+base_url fallback). go build ./... clean and go test ./internal/config/... passes.

Compute the externally-reachable log base URL once at config load time
into IrgshConfig.FullBaseURL, instead of repeating the public_url/base_url
conditional in builder, iso, and repo notification senders.
@ssFari
ssFari force-pushed the feat/issue-161-base-url branch from 7a9a3fc to 607f66b Compare June 3, 2026 08:09
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.

Allow irgsh-chief API to work with base URL

3 participants