All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
1.5.2 - 2026-11-03
- Releases are prepared by tagpr: a release
pull request carries the CHANGELOG section and the refreshed
CREDITS, and merging it tags the release. The GitHub release notes are now this file's section for the version instead of a list of commits. - Workflows default to a read-only
GITHUB_TOKEN; each job lists the permissions it needs. - The documentation site builds with Hugo 0.168.0 and the thulite 3 theme (#103), and pull requests that touch it now build it.
- Dependency updates:
github/codeql-action/upload-sarif4.39.1 anddocker/login-action4.7.0.
1.5.1 - 2026-10-23
Security release. No changes to roji's behavior.
- Built with Go 1.27.3, which adds a denylist to
crypto/x509so that certificate verification rejects known compromised intermediate certificates (CVE-2026-111943). It covers systems whose trust store has not been updated to distrust them. v1.5.0 was built with Go 1.27.2.
- Dependency updates:
golang.org/x/text0.43.0,goreleaser/goreleaser-action7.3.0, andgithub/codeql-action/upload-sarif4.39.0. - The documentation site build pins Hugo to 0.167.0 instead of
latest. Hugo 0.168.0 removed a config key the site uses, which failed the deploy of an unchanged site. Moving to the replacement is tracked in #103.
1.5.0 - 2026-10-12
roji run tells the command the hostname it was given. v1.4.0 passed only the
port, which left a server that needs its own origin with a hostname to be typed
by hand.
-
roji runpasses the hostname and the URL of the route to the command, the way it passes the port:{host}and{url}in the arguments are replaced, andROJI_HOSTandROJI_URLare set. A server that has to know its own origin no longer needs the hostname written into the command, where it would only be right for one machine'sdomain.roji run api -- npx wrangler dev --port {port} --host {host} --upstream-protocol https
- The release build pins GoReleaser to an exact version instead of
~> v2. The.tar.gzand.zipbytes depend on the GoReleaser that packs them, so rebuilding a tag reproduced the binaries and not the archive checksums once GoReleaser had moved on.
1.4.0 - 2026-10-12
Dev servers on the host. roji has routed containers and static directories;
a vite dev or wrangler dev started outside Docker had no way in. It gets a
hostname now, either from the config file or by being started through
roji run, which also picks its port.
-
proxy_sitesin the config file gives a hostname to a server running on the host itself, such asvite devorwrangler dev, the way a container gets one. WebSocket, SSE, gRPC and the forwarding headers behave as they do for containers. When nothing is listening on the target, the request gets a page naming the address and what to start. See Dev Servers on the Host.proxy_sites: - host: app target: http://localhost:5173
-
roji run <name> -- <command>starts a dev server on a free port and routeshttps://<name>.<domain>to it for as long as the command runs. The port is passed asPORTand replaces{port}in the arguments, so projects that share a default port no longer collide and none needs a port in its config.roji run app -- npx vite --port {port} -
roji config reloadapplies changes tostatic_sitesandproxy_sitesto the running roji, which until now took the dashboard's button or a call to the API. It lists every declared site and says why any was left out, so a site that did not make it is not mistaken for one that did. The static sites guide has documented this command since v1.0.0; it now exists./_api/config/reloadreturns the same list undersites, along with the file it read and the base domain it judged the sites against. -
Sites declared in the config file can be published through the tunnel with
tunnel: true, under bothproxy_sitesandstatic_sites. Static sites had no way to opt in before and were always private. Through the tunnel a static site does not serve or list dotfiles (.env,.git/),.well-known/excepted. -
roji doctorchecks the declared sites: a static site whose root is not a directory, a proxy site nothing is listening behind, a hostname roji's certificate does not cover, a hostname that is the dashboard's, a hostname declared twice, and a site that asks for the tunnel but has no public name.
-
Breaking: routes in
/_api/routesand the/_api/eventsstream carry akindfield (docker,staticorlocal) in place of theisStaticboolean. -
Pages a static site generates, such as the directory listing, no longer link to the dashboard when the request came through the tunnel.
-
A static site declared on the dashboard's hostname or the base domain is no longer registered. The dashboard answers on those names first, so such a site was listed and never reachable.
-
Connecting to a backend gives up after 10 seconds instead of waiting out the operating system's connect timeout.
-
--helpand errors about a command's arguments were printed in English whatever the language. They are translated like the rest of the CLI. -
A static site protected with Basic authentication served its files to an
OPTIONSrequest without credentials. The check letsOPTIONSthrough for CORS preflight, and the static file server answered it like aGET. Static sites now answer onlyGETandHEAD. -
A published container with a warning (no exposed port, a hostname conflict) answered tunnel requests with the warning page, which names the service and the dashboard. It now gets the same bare 404 as every other refusal.
-
A config reload cleared the declared sites before adding them back. A request arriving in between for a static site's hostname was answered by the Docker route the site was hiding, if there was one. The set is now replaced in one step.
-
The tunnel guard and the proxy each resolve a request's route. When the route changed in between β a config reload, a container stopping β a request the guard had approved could be answered by a route that never opted in. The proxy now checks the route it is about to serve.
- Built with Go 1.27.2, which fixes three denial-of-service vulnerabilities in
the standard library that roji's listeners are exposed to: crafted HTTP
Rangeheaders (CVE-2026-78667), excessive HTTP/2SETTINGSframes (CVE-2026-78669), and repeated ECH extension references in a TLS handshake (CVE-2026-97031). v1.3.0 was built with Go 1.27.1.
- Dependency updates:
github.com/moby/moby/api1.56.1,github.com/moby/moby/client0.6.2, golangci-lint v2.14.0, andgolang.org/x/net0.60.0. The last is not linked into the roji binary β the development tools ingo.modpull it in β and was raised for the vulnerabilities reported against 0.59.0.
1.3.0 - 2026-09-30
Distribution release. The installer runs the binary it downloads with sudo
and puts a CA into the system trust store, yet it never checked what it had
downloaded. It does now, and the releases carry what it checks against. The
binary also ships the licenses of everything linked into it.
-
The installer verifies the downloaded archive before installing anything. It compares the archive with the release's
checksums.txt, and when the GitHub CLI is installed and logged in, it also checks the build provenance withgh attestation verify. Either failure stops the installation. Releases from this one on carry that attestation; older ones are checked against the checksums only. See Installation. -
--version X.Y.Z(orROJI_VERSION=X.Y.Zon thebashside of the pipe) installs a given release instead of the latest one. -
roji creditsprints roji's license, the licenses of the Go modules linked into the binary, and the notices for other bundled software such as Petite Vue. The same texts ship asCREDITSandTHIRD_PARTY_NOTICESin the release archives and the Docker image. The one-liner installer keeps only the binary, so the command is how to read them after installing that way.
- The install one-liner points at
maininstead of a release tag. The tag in the URL never decided which version was installed β the installer always fetched the latest release β so--versionis now the way to pin one. The documented command also passes--proto '=https' --tlsv1.2to curl.
-
When the GitHub API rate limit was hit, the installer reported roji as already up to date and exited without upgrading. It now reads the latest version from the
releases/latestredirect, which has no such limit, and stops with an error if it cannot determine it. -
The installer left its temporary directory behind when extracting the archive failed.
- GoReleaser creates the release as a draft, and the workflow publishes it only
after the archives are attested. Until then
releases/latestkeeps pointing at the previous version, so the installer never picks up a release it cannot verify. - The installer runs nothing but its final
main "$@"line at the top level, so a download cut off midway does not execute a partial script. - HTTP/2 for the HTTPS listener and for gRPC backends is configured through
net/http'sProtocolsinstead of the deprecatedgolang.org/x/net/http2APIs. - Dependency updates:
github.com/moby/moby/client0.6.0,golang.org/x/net0.59.0, Go 1.27.1.
1.2.0 - 2026-08-14
Feature release. v1.1.0 confined roji to loopback, which left nothing for the times something outside has to reach the machine. A Cloudflare Tunnel fills that in, one container at a time.
-
Publish selected routes to the internet through a Cloudflare Tunnel, with a
tunnel:block in the configuration file and aroji.tunnellabel on each container that opts in. roji picks up every container on its network without being asked, so publishing is never the default.The tunnel gets a listener of its own on
127.0.0.1:{tunnel.port}(8080 unless set). cloudflared connects from 127.0.0.1 like any local browser, so nothing in a request distinguishes the two; the port it arrived on does, and that is what makes the guard possible. Requests reaching it are refused unless they name a route that opted in. The dashboard, the base domain,/_api/*and/_assets/*are refused outright β/_api/projects/{name}/upstarts containers without credentials β and static sites have no opt-in spelling, so none are published. Every refusal answers with the same bare 404, since wording them apart would say which hostnames exist.A route published as
web.dev.localhostanswers toweb.example.com. Only the suffix changes: the routing key stays local, and so does theHostthe backend receives, which is what a dev server's allowed-hosts list expects.With
auto_start: trueroji runs cloudflared as a child process and stops it on shutdown. It is not restarted after a crash β roji is not a process manager, and a tunnel that keeps failing to authenticate should not retry in silence. Pointing*.{domain}at the tunnel stays manual, since roji holds no Cloudflare credentials. See Public Access. -
roji doctorchecks that cloudflared is installed, logged in, and that the named tunnel exists, and prints the DNS command it cannot run for you. -
The dashboard marks published routes with a π badge and their public URL.
1.1.1 - 2026-08-07
Bug fix release. Three routing defects that share a shape: a route was stored but could not be reached, and nothing said so.
-
Two backends claiming the same hostname and the same path prefix both joined the routing table without a warning. One answered by sort order and the other was unreachable while still looking healthy on the dashboard. The collision check asked whether a backend had a prefix rather than whether its routing key β hostname and prefix together β was already taken. The same pair without a prefix has always been reported.
-
Router.AddBackenddid not update a route carrying a path prefix. Prefixed routes were appended rather than replaced, so re-adding a container left a second, stale entry behind (three adds gave three entries). Prefix-less routes replaced correctly. -
roji config reloadsilently dropped a static site whose hostname a container had since taken, and it stayed gone until roji restarted, though nothing about the configuration had changed. Reload clears the static sites and registers them again, and registration refused a hostname a Docker route held.A static site declared in the config file now keeps its hostname, which is what request handling and startup ordering already did β only registration disagreed. A Docker route hidden that way is reported in the log. See Static Sites.
1.1.0 - 2026-08-07
Security and correctness release. roji now listens on loopback only, which is a breaking change for anyone reaching it from another device. Alongside that, six routing and WebSocket defects found by review, and the linting that would have caught several of them.
-
Breaking: roji listens on
127.0.0.1and::1instead of every interface. Until now anyone on the same network segment β a shared office LAN, a cafe network, a guest VLAN β could reach ports 80 and 443 on the developer's machine, and through them every container on the roji network plus the dashboard. The Docker Compose endpoints (/_api/projects/{name}/up|down|restart) have no authentication, so that was container control rather than information disclosure.To go back to the previous behavior, or to reach roji from a phone on the same network, set
bindto the addresses you want or to an empty string for all of them. See Configuration. -
WebSocket upgrades go through
httputil.ReverseProxyrather than a hand-rolled dialer. Two consequences beyond the fixes below:permessage-deflateis negotiated end to end, where roji previously stripped the offer and left every connection uncompressed; and a backend that refuses the upgrade now has its own response β status, headers and body β reach the client instead of a plain-text error.
bindsetting, as a comma-separated list, withROJI_BINDand--bind. A loopback address that cannot be bound is skipped with a warning, so a machine with IPv6 disabled still starts on127.0.0.1; any other configured address is required, since nothing substitutes for it.roji doctorreports which addresses roji listens on.
- Path prefix routes matched without a segment boundary, so a
/apiroute also captured/apifooand/api-docs. The request reached the backend with a relative path and was rejected with 400; a prefix producing a valid-looking remainder would have been misrouted silently. - Stripping a path prefix rewrote only the decoded path, so
/api/files/a%2Fbreached the backend as/files/a/bβ one path segment turned into two. Only routes carrying aroji.pathlabel were affected. - WebSocket upgrades reached the backend with the backend's own address in
Host, while ordinary requests on the same route carried the hostname the client used. Vite and webpack check the upgrade request'sHostagainst their allowed-hosts configuration, so HMR could be refused on a page that loaded fine. - WebSocket upgrades dropped the client's headers,
CookieandAuthorizationamong them. A WebSocket behind session or token authentication was refused the upgrade while the page itself loaded. roji.path=/produced a path prefix of/rather than no prefix at all, which left a prefix-less route on the same hostname unreachable and unannounced by the hostname collision check.roji.pathwas normalized withfilepath.Clean, which rewrites slashes to the OS separator. On Windows/apibecame\apiand matched nothing, so path-based routing failed entirely.roji logdid not notice a log rotation. The server rotates by renaming, so the handle a follower holds keeps pointing at the renamed file; roji watched its size, which never shrinks. The follower silently tailed a detached file and printed nothing more.roji log -n 0printed no history in follow mode, though the flag documents0as "show every line", androji log -nwith a negative count panicked.- Directory listing links were HTML-escaped but not URL-escaped, so a file whose
name contained
?or#could not be opened from the listing.
go fixmodernizers applied,gofmtrun over the files that had drifted, andgolangci-lintadded to CI along with agofmtcheck. The linter runs once perGOOS, because theserviceandcertgenpackages are split by build tag and a single Linux run never compiles the macOS and Windows files.
1.0.8 - 2026-08-01
Bug fix release. The roji routes and roji health commands ignored the
resolved configuration when calling the local API, so both failed on any setup
that did not use the built-in defaults.
roji routesandroji healthnow honor the configureddomain,https_portanddashboardvalues from the config file andROJI_*environment variables. Neither command calledconfig.Load(), and instead read globals that are only populated when the root command parses its own flags:roji routesrequestedhttps://roji.:0/_api/routesand always failed to connectroji healthrequested a hardcodedhttps://localhost/_api/health, which returns 404 wheneverdomainis notlocalhost, because the API is only served on the dashboard virtual host
- Extract the local API client into a new
apiclientpackage, shared by the CLI commands and theroji doctorport check. Requests connect to localhost and carry the dashboard hostname in theHostheader, so no*.localhostDNS resolution is required.
1.0.7 - 2026-07-13
Dependency and security maintenance release. Clears all open security alerts (2 code scanning) and the failing govulncheck / Trivy checks, all rooted in the Go 1.26.4 standard library.
- Upgrade Go to 1.26.5, fixing 2 standard library vulnerabilities:
- CVE-2026-39822 (HIGH): Symlink-following in
os.Rootallows directory traversal - CVE-2026-42505 (MEDIUM): Information disclosure in
crypto/tlsEncrypted Client Hello
- CVE-2026-39822 (HIGH): Symlink-following in
- Upgrade the
golangbuilder image 1.26.4-alpine β 1.26.5-alpine (#57), so the released binary is built with the patched standard library
- Upgrade
golang.org/x/net0.56.0 β 0.57.0 (#58), which also pullsgolang.org/x/text0.38.0 β 0.40.0
1.0.6 - 2026-06-25
Dependency and security maintenance release. Resolves all 14 open security alerts (Dependabot + code scanning). The fixed issues are in build/website tooling (Hugo, vite, esbuild, Babel) rather than the roji proxy runtime.
- Upgrade
github.com/gohugoio/hugo0.161.0 β 0.163.3, fixing 5 advisories:- CVE-2026-50133: XSS via unescaped code-fence language in the default code block renderer
- CVE-2026-50134: XSS via
text/htmlcontent files - CVE-2026-50135:
security.http.urlsallow-list bypass via HTTP redirects - GHSA-q76j-gcg9-vxc6: Symlink confinement bypass in
os.ReadFile - GHSA-c3wq-j5vh-68rc: Symlink confinement bypass in
resources.Get
- Upgrade
viteandesbuild(website), fixing:- GHSA-jqfw-vq24-v9c3 (HIGH):
viteserver.fs.denybypass on Windows alternate paths - launch-editor: NTLMv2 hash disclosure via UNC path handling on Windows
esbuildarbitrary file read when running the dev server on Windows
- GHSA-jqfw-vq24-v9c3 (HIGH):
- Upgrade
@babel/core7.29.0 β 7.29.7 (website), fixing CVE-2026-49356: arbitrary file read viasourceMappingURLcomment - Remove unused
@changesets/cli/@changesets/changelog-githubdev dependencies (website). They were inherited from the Doks theme starter and are not used (this project uses GoReleaser + a hand-maintained CHANGELOG); removing them drops the only paths to the vulnerablejs-yaml(GHSA-h67p-54hq-rp68: quadratic-complexity DoS in merge key handling)
golang.org/x/net0.55.0 β 0.56.0golang.org/x/text0.37.0 β 0.38.0github.com/moby/moby/api1.54.2 β 1.55.0github.com/moby/moby/client0.4.1 β 0.5.0
actions/checkoutv6 β v7
1.0.5 - 2026-06-08
- Upgrade Go to 1.26.4, fixing 2 standard library vulnerabilities:
- GO-2026-5039 / CVE-2026-42504 (HIGH): Decoding a maliciously-crafted MIME header containing many invalid encoded-words in
net/textproto - GO-2026-5037: Issue in
crypto/x509
- GO-2026-5039 / CVE-2026-42504 (HIGH): Decoding a maliciously-crafted MIME header containing many invalid encoded-words in
codecov/codecov-actionv6 β v7
1.0.4 - 2026-05-26
- Upgrade
golang.org/x/net0.54.0 β 0.55.0, fixing GO-2026-5026: Punycode label decoding bypass in theidnapackage.ToASCII/ToUnicodeincorrectly accept Punycode-encoded labels that decode to ASCII-only labels, enabling a hostname privilege-escalation bypass. Reachable in roji because thehttputil.ReverseProxyinproxy/handler.govalidates upstream hosts throughgolang.org/x/net/http2βhttpgutsβidna.ToASCII.
brace-expansion5.0.5 β 5.0.6 (website)
1.0.3 - 2026-05-18
- Upgrade Go to 1.26.3, fixing 5 standard library vulnerabilities:
- GO-2026-4982: Bypass of meta content URL escaping causes XSS in
html/template - GO-2026-4980: Escaper bypass leads to XSS in
html/template - GO-2026-4976: Issue in
net/http/httputil - GO-2026-4971: Panic in
DialandLookupPortwhen handling NUL byte on Windows innet - GO-2026-4918: Issue in
net/http/internal/http2viagolang.org/x/net
- GO-2026-4982: Bypass of meta content URL escaping causes XSS in
- Upgrade
go.opentelemetry.io/otel1.40.0 β 1.41.0, fixing CVE-2026-29181 (HIGH): multi-valuebaggageheader extraction causes excessive allocations (remote DoS amplification)
golang.org/x/net0.53.0 β 0.54.0github.com/moby/moby/client0.4.0 β 0.4.1github.com/gohugoio/hugo0.159.2 β 0.161.0 (website)@babel/plugin-transform-modules-systemjs7.29.0 β 7.29.4 (website)postcss8.5.8 β 8.5.10 (website)
actions/upload-pages-artifactv4 β v5
1.0.2 - 2026-04-13
- Migrate Docker client from
github.com/docker/dockertogithub.com/moby/moby/client v0.4.0, fixing CVE-2026-34040 (AuthZ bypass in Docker daemon) - Upgrade Go to 1.26.2, fixing 5 standard library vulnerabilities:
- GO-2026-4947: Unexpected work during chain building in
crypto/x509 - GO-2026-4946: Inefficient policy validation in
crypto/x509 - GO-2026-4870: TLS 1.3 KeyUpdate DoS in
crypto/tls - GO-2026-4866: Case-sensitive name constraints Auth Bypass in
crypto/x509 - GO-2026-4865: XSS in
html/template
- GO-2026-4947: Unexpected work during chain building in
golang.org/x/net0.52.0 β 0.53.0golang.org/x/text0.35.0 β 0.36.0github.com/fatih/color1.18.0 β 1.19.0github.com/gohugoio/hugo0.149.1 β 0.159.2 (website)
actions/configure-pagesv5 β v6codecov/codecov-actionv5 β v6actions/deploy-pagesv4 β v5docker/login-actionv3 β v4docker/metadata-actionv5 β v6
1.0.1 - 2026-03-16
- Upgrade Go to 1.26.1, fixing 3 standard library vulnerabilities:
- GO-2026-4601: Incorrect parsing of IPv6 host literals in
net/url - GO-2026-4602: FileInfo can escape from a Root in
os - GO-2026-4603: URLs in meta content attribute actions are not escaped in
html/template
- GO-2026-4601: Incorrect parsing of IPv6 host literals in
golang.org/x/net0.51.0 β 0.52.0golang.org/x/text0.34.0 β 0.35.0golang.org/x/sys0.41.0 β 0.42.0
docker/build-push-actionv6 β v7docker/setup-buildx-actionv3 β v4actions/checkout,actions/setup-node,actions/upload-pages-artifactupdated to latest versions
1.0.0 - 2026-02-17
- Docker Mode removed:
install-docker.shhas been deleted. roji now only supports Native Mode (standalone binary). Existing Docker Mode users should migrate manually by stopping the container, removing the installation, and re-running the native installer. ROJI_DOMAINdefault unified:docker-compose.ymlnow defaults todev.localhost(waslocalhost), matching Native Mode behavior.
- Homebrew tap support: Install on macOS with
brew install kan/roji/roji- GoReleaser auto-generates and pushes Formula to
kan/homebrew-rojion release
- GoReleaser auto-generates and pushes Formula to
- Recent Projects delete: Remove projects from history via dashboard or API
DELETE /_api/projects/{name}/deleteendpoint- Remove button on inactive projects in dashboard
- Confirmation dialog before deletion
- Internationalization (i18n): Japanese language support
- CLI commands: All user-facing messages translated via
i18n.T()/i18n.Tf() - Dashboard: Client-side translation with
Accept-Languageheader detection - Not Found / Warning pages: Server-side Go template translation
- Static file directory listing: Translated UI elements
- install.sh: Language detection via
LANG/LC_ALL/LC_MESSAGES - Doctor checks: All check names, messages, and details translated
- Lightweight i18n package (
i18n/) with JSON message files embedded viaembed.FS - Language auto-detection: CLI uses
LANGenv var, HTTP usesAccept-Languageheader - Fallback chain: current language β English β key itself
- CLI commands: All user-facing messages translated via
- README.md overhaul: Complete rewrite for v1.0.0
- New Quick Start section for fastest path to first service
- New Getting Started tutorial (multi-service project walkthrough)
- New API Reference with all 16 endpoints
- Expanded Troubleshooting with platform-specific guides (WSL, macOS, Linux)
- Expanded CLI Reference with all commands and flags
- Consolidated Configuration section with full config file example
- Added missing
ROJI_HTTP_PORT/ROJI_HTTPS_PORTenvironment variables to docs
install.shnow shows an error and migration instructions when Docker Mode is detected, instead of offering automatic migrationdocker-compose.ymlheader updated to clarify it is for development and testing only- Documented
roji.selfas a reserved internal label
install-docker.sh(Docker Mode installer)- Docker Mode (Legacy) section from README.md
- TLS Certificates manual installation section (replaced by
roji ca install) --migrateflag frominstall.sh
0.9.1 - 2026-02-06
- Go Update: Update to Go 1.25.7 to address crypto/tls vulnerability (CVE fix)
0.9.0 - 2026-02-06
- Docker Compose Operations: Control projects from dashboard and API
POST /_api/projects/{name}/up- Start project (docker compose up -d)POST /_api/projects/{name}/down- Stop project (docker compose down)POST /_api/projects/{name}/restart- Restart all servicesGET /_api/projects/{name}/logs- Stream logs via F300 SSE- Dashboard buttons: Start/Stop/Restart on active and inactive projects
- Stop button on individual routes in the routes list
- Confirmation dialog before destructive operations
- CORS support for cross-subdomain API calls
- Not Found Page Project Detection: Auto-start inactive projects
- Detects hostname matching an inactive project
- Shows "Start" button to bring up the project directly
- Supports both
service.domainandservice-project.domainhostname patterns
- Basic Authentication: Protect routes with username/password
- Docker labels:
roji.auth.basic.user,roji.auth.basic.pass,roji.auth.basic.realm - Config file support for static sites
- CORS preflight (OPTIONS) requests bypass authentication
- Dashboard shows auth badge (π) for protected routes
- Config validation warns on unknown auth keys
- Docker labels:
- Static File Hosting: Serve static files without Docker
- Configure via
static_sitesin config.yaml - Directory listing with dark mode support
index.htmlauto-serving- Directory traversal protection
- Basic authentication support
- Dashboard "Reload Config" button for hot-reload
- Configure via
- Service Management: Run roji as system service
roji service install/uninstall/start/stop/restart/status- Linux: systemd service (
/etc/systemd/system/roji.service) - macOS: launchd agent (
~/Library/LaunchAgents/com.roji.agent.plist) - Windows: NSSM-based Windows Service
- Log File Output: Service mode logs to file
- Linux/WSL:
~/.local/share/roji/roji.log - macOS:
~/Library/Logs/roji.log - Auto-rotation at 10MB
- Linux/WSL:
- Config File Validation: Comprehensive validation on load and in doctor
- Unknown keys warning (typo detection)
- Type validation for all fields
- Missing required fields check
roji doctorshows config validation results
- New Native Mode Installer:
install.shnow installs native binary- Downloads pre-built binary from GitHub Releases
- Interactive install location selection (
~/.local/binor/usr/local/bin) - Auto-detects and migrates from Docker Mode
- Runs
doctor --fix,ca install, andservice installautomatically - Legacy Docker installer available as
install-docker.sh
- Dashboard now displays authentication status for each route
- Dashboard active projects now have Start/Stop/Restart operation buttons
- Dashboard inactive projects now have a Start button alongside copy command
- Dashboard routes list now includes Stop button for Docker Compose projects
- Static site index status shown in dashboard (π enabled / π disabled)
roji doctorport check now detects if roji itself is using the ports- Not Found page and Warning page now use absolute URLs for dashboard assets
certutil.exenot found on some WSL configurations
0.8.0 - 2026-01-24
- Native Mode: Run roji as a standalone binary without Docker
- Single binary execution on host system
- Direct access to Docker socket from host
- Simplified deployment for local development
- Configuration File: YAML-based configuration support
- Default location:
~/.config/roji/config.yaml - XDG Base Directory compliant paths
- Settings priority: CLI > Environment > Config file > Defaults
- Default location:
roji configCommand: Manage configuration filesroji config show- Display current configurationroji config path- Show configuration file pathsroji config init- Create default configuration fileroji config edit- Open configuration in editor
roji doctorCommand: Environment diagnostics with auto-fix- Docker daemon and socket checks
- Network existence verification
- Port availability checks
- CA certificate existence and installation status
- Server certificate validity and domain matching
- DNS resolution checks
--fixflag for automatic remediation--jsonflag for machine-readable output
roji caCommand: CA certificate managementroji ca status- Check installation statusroji ca install- Install CA to system trust storeroji ca uninstall- Remove CA from trust storeroji ca export- Export CA certificate (PEM/DER)- Platform support: macOS (Keychain), Linux (system CA store), Windows (certutil), WSL (Windows user store)
--userflag for user-level installation (no sudo)--windowsflag for WSL to Windows installation
- Makefile: Common development tasks
make build- Build binary to ./bin/rojimake test- Run all testsmake doctor- Build and run doctor
- Dashboard host default corrected to
roji.{domain}(was incorrectly{domain}) *.localhostDNS resolution failure now passes in doctor (Chrome auto-resolves per RFC 6761)- CA certificate names now include unique identifier for easier management
- Certificate domain mismatch detection in
roji doctor - Auto-regeneration of server certificate when domain changes
- Server startup now detects domain mismatch and regenerates certificate with helpful message
- XDG paths: config in
~/.config/roji/, data in~/.local/share/roji/ - CA installer interface with platform-specific implementations
- Doctor check interface for extensible diagnostics
- Certificate domain validation using x509 DNSNames
0.7.0 - 2026-01-15
- WebSocket Proxy Support: Full bidirectional WebSocket proxying
- Automatic detection via
Upgrade: websocketheader - Bidirectional message forwarding between client and backend
- Support for text and binary messages
- Proper connection cleanup on disconnect
- Automatic detection via
- gRPC Proxy Support: HTTP/2 based gRPC proxying
- Automatic detection via
Content-Type: application/grpc - HTTP/2 transport for backend connections (h2c)
- gRPC-compatible error responses with
Grpc-Statusheader - Streaming support with immediate flush
- Automatic detection via
- Log Export: Export request logs from dashboard
- JSON and CSV download formats
- Filter by host, service, method, and time range
- Export buttons added to dashboard UI
/_api/logs/exportendpoint
- HTTPS server now explicitly configured with HTTP/2 support (required for gRPC)
- Request log panel now includes JSON/CSV export buttons
- WebSocket proxy using
gorilla/websocketlibrary - HTTP/2 transport via
golang.org/x/net/http2package - LogFilter type for flexible log querying
- CSV escaping for safe export of paths with special characters
0.6.1 - 2026-01-10
- Dashboard crash when no containers connected: Fixed JavaScript error "Cannot read properties of null (reading 'forEach')" that occurred when accessing the dashboard with no containers in the roji network. Routes are now properly initialized as an empty array instead of null.
0.6.0 - 2026-01-10
- Dark Mode: Automatic theme switching based on system preferences
- Manual toggle button (sun/moon icon) in dashboard
- Settings persisted in localStorage
- FOUC prevention (theme applied before page render)
- Applied to all pages (dashboard, not found, warning)
- Request Log Viewer: Real-time request logging in dashboard
- Ring buffer storing last 100 requests
- Real-time streaming via SSE
- Filtering by hostname (dropdown) and path (text input)
- Shows method, path, status code, latency, and hostname
- Multiple Network Support: Monitor multiple Docker networks simultaneously
- Comma-separated network names in
ROJI_NETWORK - Network badge on each route in dashboard
- Click network badge to filter routes
- Comma-separated network names in
- Container Restart Button: Restart containers directly from dashboard
- Restart button on each route card
- Confirmation dialog before restart
/_api/containers/{id}/restartAPI endpoint
- Request Mocking: Define mock responses via container labels
roji.mock.{METHOD}.{PATH}for response bodyroji.mock.status.{METHOD}.{PATH}for status code- Supports JSON and text responses
- Useful for frontend development without backend
- Upgrade Support in install.sh: Seamless upgrades via one-liner
- Automatic detection of existing installation
- Version comparison with GitHub API
- Interactive upgrade menu (upgrade/keep/reinstall)
- Automatic upgrade when running non-interactively
- Configuration backup before upgrade
- Configuration migration for new settings
- Rollback instructions
- Integration Tests: Docker Compose based testing
- Real container lifecycle testing
- Route detection and proxy verification
- Warning case testing (no port, hostname conflicts)
- GitHub Actions integration
- E2E Tests: Full HTTP/HTTPS server testing
- Real HTTP request verification
- TLS certificate validation
- SSE connection testing
- Dashboard accessibility testing
ROJI_NETWORKnow supports comma-separated values for multiple networks- Dashboard now shows network badges for each route
- Request log panel added below routes in dashboard
- CSS variables for theme management (
theme.css) - LogBuffer ring buffer for efficient request log storage
- SSE endpoint
/_api/logsfor real-time log streaming - Network-aware container discovery and routing
- Docker API
ContainerRestartintegration
0.5.0 - 2026-01-08
- Misconfigured Container Warnings: Dashboard now shows containers with configuration issues
- Containers without exposed ports are displayed with warning badge
- Warning page with context-aware fix suggestions
- Hostname conflict detection when multiple services use the same hostname
- Warning Page: Dedicated error page for misconfigured routes
- Shows specific issue and affected service
- Provides tailored fix instructions based on warning type
- Direct link back to dashboard
- GitHub Link: Added GitHub repository link with Octocat icon to dashboard header
- Multi-service Hostname Format: Changed from
service.project.localhosttoservice-project.localhost- Keeps hostname at single subdomain level
- Ensures wildcard SSL certificate (
*.localhost) compatibility - Prevents certificate errors for multi-service projects
- Warning type detection in proxy handler for template switching
- Hostname conflict check in Router.AddBackend with warning propagation
- Inline SVG for GitHub Octocat icon (no external dependencies)
0.4.0 - 2026-01-07
- Live Dashboard: Real-time route updates via Server-Sent Events (SSE)
- Automatic updates when containers start/stop without page refresh
- Connection status indicator (Live/Disconnected)
- Auto-reconnection on connection loss
- Browser Notifications: Optional desktop notifications for route changes
- Notification settings persisted in localStorage
- "Route added" and "Route removed" notifications
- Project History: Track active and recent Docker Compose projects
- Automatically detects Docker Compose projects
- Shows project name, working directory, and service count
- Displays last active time for stopped projects
- One-click copy of restart commands
- Data persisted in
/data/projects.json
- Dashboard Redirect: Base domain now redirects to dashboard host
https://dev.localhost/βhttps://roji.dev.localhost/- Preserves path and query strings
- Ensures consistent bookmarks and URL bar history
- Version Info: Hover tooltip on dashboard showing build metadata
- Commit hash, build date, and built by information
- Data Persistence: New
/datavolume for storing project history - API Endpoints:
/_api/events- Server-Sent Events stream for real-time updates/_api/projects- Active and inactive project listing
- Dashboard now accessible at
https://roji.dev.localhost(primary) - Base domain
https://dev.localhostredirects to dashboard host - Environment variable
ROJI_DASHBOARDdefault changed toroji.{domain} - Improved dashboard UI with Petite Vue framework (~6KB)
- Enhanced version display with build information tooltip
- Updated install.sh to:
- Create
/datadirectory for project history - Mount
/datavolume in generated docker-compose.yml - Set
ROJI_DASHBOARDtoroji.dev.localhost - Add
ROJI_DATA_DIRenvironment variable
- Create
examples/directory (install.sh now generates docker-compose.yml)
- SSE test race condition that occasionally caused test failures
- Dashboard styling improvements for better visual consistency
- Petite Vue (~6KB) for reactive UI without build step
- SSE Pub/Sub pattern in Router for efficient event broadcasting
- Graceful SSE reconnection handling
- CSS transitions for smooth route add/remove animations
- Project metadata collection from Docker Compose labels
0.3.0 - 2026-01-05
- GoReleaser integration for automated releases
- Multi-platform binary builds (Linux/macOS/Windows, amd64/arm64)
- Multi-architecture Docker images (amd64/arm64)
- Build metadata embedding (version, commit, date, built by)
roji versioncommand with accurate version information- GitHub Release automation
- Semantic versioning tags (v1.2.3 β 1.2.3, 1.2, 1, latest)
- Switched from manual Docker builds to GoReleaser
- Improved release workflow automation
0.2.0 - 2026-01-04
- Security enhancements:
- Distroless base image (gcr.io/distroless/static:nonroot)
- X-Forwarded header spoofing protection
- Path traversal prevention for roji.path label
- GitHub Actions security scanning (govulncheck, Trivy, hadolint)
- Performance improvements:
- SSE support (FlushInterval = -1)
- Shared Transport for connection pooling
- Docker Events auto-reconnection
- Robustness improvements:
- Explicit server timeouts
- Docker API timeout handling
roji healthcommand for Distroless compatibility
- Updated Docker client library to v28.5.2
- Migrated to Distroless image for better security
0.1.0 - 2026-01-03
- Initial release
- Auto-discovery of Docker containers on shared network
- TLS certificate auto-generation
- Label-based configuration (roji.host, roji.port, roji.path)
- Dynamic route updates via Docker Events
- Dashboard for viewing routes
- CLI commands: routes, version, health
- Health check endpoints (/_api/health, /healthz)
- Status API (/_api/status) with certificate expiration monitoring
- One-liner installation script (install.sh)
- HTTP to HTTPS redirection
- Path-based routing support
- Cobra-based CLI structure