Compare commits

...

1654 Commits
0.4.1 ... main

Author SHA1 Message Date
yusing
aaa3c9a8d8 fix(swagger): correct type names in swagger docs
Rename icon-related types in swagger docs:
- homepage.FetchResult → iconfetch.Result
- homepage.IconMetaSearch → iconlist.IconMetaSearch
- homepage.IconSource → icons.Source
- Shorten enum varnames (IconSourceAbsolute → SourceAbsolute, etc.)
- Add x-nullable: true to rules arrays
2026-01-10 15:57:56 +08:00
yusing
bc44de3196 feat(rules): add "on: default" rule syntax for default rule
- Add OnDefault rule type that matches when no other rules match
- Add validation to prevent multiple default rules
- Fix typo: extension → extensions in route config JSON tag
2026-01-10 15:53:26 +08:00
yusing
12b784d126 feat(serialization): add validation support for custom slice types
Enhanced the ConvertSlice function to include validation for destination slices that implement the CustomValidator interface. If validation fails, errors are collected and returned, ensuring data integrity during slice conversion.
2026-01-10 15:49:58 +08:00
yusing
71f6636cc3 refactor(serialization): optimize deserialization 2026-01-10 15:43:34 +08:00
yusing
cc1fe30045 refactor(scripts/wiki): rewrite markdown links when syncing impl docs to wiki
- Convert intra-repo README links to VitePress routes for SPA navigation
- Rewrite source file references (e.g., config.go:29) to GitHub blob links
- Makefile now passes REPO_URL to update-wiki for link rewriting
- Correct agent README.md file links from full to relative paths
- skip introduction.md when syncing
2026-01-10 13:54:22 +08:00
yusing
4ec352f1f6 refactor(homepage/icon): check service health before fetching icons and add retry logic
The icon fetching logic now checks if the target service is healthy before
attempting to fetch icons. If the health monitor reports an unhealthy status,
the function returns HTTP 503 Service Unavailable instead of proceeding.

Additionally, the icon cache lookup now includes infinite retry logic with a
15-second backoff interval, improving resilience during transient service
outages. Previously, failed lookups would not be retried.

The `route` interface was extended with a `HealthMonitor()` method to support
the health check functionality.
2026-01-09 21:48:35 +08:00
yusing
df530245bd fix(homepage/icon): set icons provider on init (introduced in 74f97a6621) 2026-01-09 21:41:32 +08:00
yusing
1a022bb3f4 refactor(route): improve References method to handle FQDN alias 2026-01-09 21:38:21 +08:00
yusing
2e57ca7743 fix(agent/stream) TCP/UDP server now handle stream headers with read deadlines 2026-01-09 21:22:51 +08:00
yusing
69d04f1b76 refactor(agent): remove goutils/server dependency and use direct HTTP server setup
- Replaced `goutils/server` helper library with manual HTTP server configuration for agent socketproxy
- Removed entire `agent/pkg/server/server.go` package (43 lines) that wrapped TLS/HTTP server functionality
- Added explicit TCP listener and integrated zerolog with server's error logging
- Cleaned up 17 unused indirect agent dependencies
2026-01-09 20:27:48 +08:00
yusing
74f97a6621 refactor(homepage): reorganize icons into dedicated package structure
Split the monolithic `internal/homepage` icons functionality into a structured package hierarchy:

- `internal/homepage/icons/` - Core types (URL, Key, Meta, Provider, Source, Variant)
- `internal/homepage/icons/fetch/` - Icon fetching logic (content.go, fetch.go, route.go)
- `internal/homepage/icons/list/` - Icon listing and search (list_icons.go, list_icons_test.go)

Moved icon-related code from `internal/homepage/`:
- `icon_url.go` → `icons/url.go` (+ url_test.go)
- `content.go` → `icons/fetch/content.go`
- `route.go` → `icons/fetch/route.go`
- `list_icons.go` → `icons/list/list_icons.go` (+ list_icons_test.go)

Updated all consumers to use the new package structure:
- `cmd/main.go`
- `internal/api/v1/favicon.go`
- `internal/api/v1/icons.go`
- `internal/idlewatcher/handle_http.go`
- `internal/route/route.go`
2026-01-09 12:06:54 +08:00
yusing
dc1b70d2d7 chore(deps): update dependencies 2026-01-09 10:57:24 +08:00
Yuzerion
6fac5d2d3e feat(agent): agent stream tunneling with TLS and dTLS (UDP) (#188)
* **New Features**
  * Multiplexed TLS port: HTTP API and a custom stream protocol can share one port via ALPN.
  * Agent-side TCP and DTLS/UDP stream tunneling with health-check support and runtime capability detection.
  * Agents now advertise per-agent stream support (TCP/UDP).

* **Documentation**
  * Added comprehensive stream protocol documentation.

* **Tests**
  * Extended integration and concurrency tests covering multiplexing, TCP/UDP streams, and health checks.

* **Chores**
  * Compose/template updated to expose both TCP and UDP ports.
2026-01-09 10:52:35 +08:00
yusing
4275cdae38 docs: enhance package README documentation 2026-01-09 10:27:55 +08:00
yusing
45c821fa98 docs: simplify agent/pkg/certs README 2026-01-09 10:13:32 +08:00
yusing
d4b7ae808f fix(route): allow hostname for stream routes; introduced in 3643add8a3 2026-01-09 10:09:59 +08:00
yusing
7687dca456 fix(middleware/redirect): use net.JoinHostPort for setting HTTPS host 2026-01-09 02:29:50 +08:00
yusing
45d6e3bab7 fix(stream): properly handle remote stream scheme IPv4/6 2026-01-09 01:49:22 +08:00
yusing
41eb8c2ffa fix(monitor): remove unnecssary return type 2026-01-09 00:42:07 +08:00
yusing
2e3ebefc4e fix(health/http): potential panic when error is tlsErr 2026-01-09 00:41:25 +08:00
yusing
5aa7dc09e5 fix(health): remove unnecessary containerId parameter 2026-01-09 00:40:08 +08:00
yusing
c7d4703622 docs: update README for autocert package to reflect changes in renewal scheduling and primary consumers 2026-01-09 00:06:27 +08:00
yusing
7e99f3465f docs: update goutils docs 2026-01-08 23:48:24 +08:00
yusing
e9d7edef12 docs: add per package README for implementation details (AI generated with human review) 2026-01-08 23:39:19 +08:00
yusing
13441286d1 docs(idlewatcher): update README to include loading page and SSE endpoint details
- Added information about the loading page (HTML + JS + CSS) and the SSE endpoint for wake events.
- Clarified the health monitor implementation and readiness tracking in the architecture overview.
- Correct state machine syntax.
2026-01-08 20:31:44 +08:00
yusing
86f35878fb feat(docs): add health check and monitor packages README; mermaid styling fix 2026-01-08 18:18:17 +08:00
yusing
7556a06716 feat(scriptsi): add script to sync implementation docs with wiki
- Introduced a new `update-wiki` script to automate the synchronization of implementation documentation from the repository to the wiki.
- Added necessary configuration files including `package.json`, `tsconfig.json`, and `.gitignore` for the new script.
- Updated the Makefile to include a target for running the `update-wiki` script.
2026-01-08 18:17:06 +08:00
yusing
7385761bdf fix(health): correct context handling, move NewMonitor, and improve docker health check errors
- Correct BaseContext nil check in Context() method
- Move NewMonitor from monitor.go to new.go
- Export ErrDockerHealthCheckFailedTooManyTimes and add ErrDockerHealthCheckNotAvailable
- Return ErrDockerHealthCheckNotAvailable when container has no health check configured
- Only log first docker health check failure and skip logging for ErrDockerHealthCheckNotAvailable
- Use mon.Context() instead of mon.task.Context() to avoid nil panic
2026-01-08 18:15:02 +08:00
yusing
581503e160 refactor: move internal/watcher/health to internal/health 2026-01-08 15:08:02 +08:00
yusing
243e7e9e95 refactor(health): restructure health check implementations into dedicated check package
- Move health check implementations from monitor/ to new check/ package
- Add h2c, tcp4/6, udp4/6 scheme support to agent health check API
- Add timeout URL parameter to agent health check endpoint
- Remove unused agent dependencies (dnsproviders, lego, various cloud SDKs)
- Use net.JoinHostPort instead of fmt.Sprintf for port joining
2026-01-08 14:54:33 +08:00
yusing
8b5cb947c8 refactor(agent): extract agent pool and HTTP utilities to dedicated package
Moved non-agent-specific logic from agent/pkg/agent/ to internal/agentpool/:
- pool.go: Agent pool management (Get, Add, Remove, List, Iter, etc.)
- http_requests.go: HTTP utilities (health checks, forwarding, websockets, reverse proxy)
- agent.go: Agent struct with HTTP client management

This separates general-purpose pool management from agent-specific configuration,
improving code organization and making the agent package focused on agent config only.
2026-01-08 12:02:21 +08:00
yusing
9ea9e62ee8 refactor: remove NoCopy struct; move RefCounter struct to goutils and update usage; remove internal/utils entirely 2026-01-07 17:17:12 +08:00
yusing
1ebba20216 fix(docker): add TLS check; correct dial handling and reconnection for custom docker provider; modernize pointer arithemetic with unsafe.Add 2026-01-07 15:28:53 +08:00
yusing
7bfb57ea30 fix(stream): nil panic for excluded routes 2026-01-07 15:24:28 +08:00
yusing
25ceb512b4 feat(route): add bind address support for TCP/UDP routes
- Introduced a new `Bind` field in the route configuration to specify the address to listen on for TCP and UDP routes.
- Defaulted the bind address to "0.0.0.0" if not provided.
- Enhanced validation to ensure the bind address is a valid IP.
- Updated stream initialization to use the correct network type (tcp4/tcp6 or udp4/udp6) based on the bind address.
- Refactored stream creation functions to accept the network type as a parameter.
2026-01-07 15:05:55 +08:00
yusing
9205af3a4f feat(api/cert): enhance certificate info retrieval
- Introduced a new method `GetCertInfos` to fetch details of all available certificates.
- Updated the `Info` handler to return an array of `CertInfo` instead of a single certificate.
- Improved error handling for cases with no available certificates.
- Refactored related error messages for clarity.
2026-01-07 10:54:33 +08:00
yusing
08f4d9e95f chore: update goutils 2026-01-07 10:24:08 +08:00
yusing
a44b9e352c refactor(docker): simplify flow of isLocal check 2026-01-06 16:38:49 +08:00
yusing
424398442b refactor: replace gperr.Builder with gperr.Group for concurrent error handling
- Updated various files to utilize gperr.Group for cleaner concurrency error handling.
- Removed sync.WaitGroup usage, simplifying the code structure.
- Ensured consistent error reporting across different components.
2026-01-06 16:29:35 +08:00
yusing
724617a2b3 chore(go.mod): update goquery comment and add description for x/sync package 2026-01-05 20:58:56 +08:00
yusing
61c8ac04e8 feat(autocert): add back inwx provider 2026-01-05 20:55:04 +08:00
yusing
cc27942c4d chore(deps): update dependencies 2026-01-05 20:47:56 +08:00
yusing
1c2515cb29 chore(docs): add README.md across multiple packages 2026-01-04 22:01:48 +08:00
yusing
45720db754 fix(Makefile): correct test command 2026-01-04 21:57:25 +08:00
yusing
1b9cfa6540 fix(autocert): forceRenewalDoneCh was never closed 2026-01-04 20:40:38 +08:00
yusing
f1d906ac11 fix(test): update test expectations 2026-01-04 20:31:11 +08:00
yusing
2835fd5fb0 fix(autocert): ensure extra certificate registration and renewal scheduling
Extra providers were not being properly initialized during NewProvider(),
causing certificate registration and renewal scheduling to be skipped.

- Add ConfigExtra type with idx field for provider indexing
- Add MergeExtraConfig() for inheriting main provider settings
- Add setupExtraProviders() for recursive extra provider initialization
- Refactor NewProvider to return error and call setupExtraProviders()
- Add provider-scoped logger with "main" or "extra[N]" name
- Add batch operations: ObtainCertIfNotExistsAll(), ObtainCertAll()
- Add ForceExpiryAll() with completion tracking via WaitRenewalDone()
- Add RenewMode (force/ifNeeded) for controlling renewal behavior
- Add PrintCertExpiriesAll() for logging all provider certificate expiries

Summary of staged changes:
- config.go: Added ConfigExtra type, MergeExtraConfig(), recursive validation with path uniqueness checking
- provider.go: Added provider indexing, scoped logger, batch cert operations, force renewal with completion tracking, RenewMode control
- setup.go: New file with setupExtraProviders() for proper extra provider initialization
- setup_test.go: New tests for extra provider setup
- multi_cert_test.go: New tests for multi-certificate functionality
- renew.go: Updated to use new provider API with error handling
- state.go: Updated to handle NewProvider error return
2026-01-04 20:30:58 +08:00
yusing
11d0c61b9c refactor(state): replace Entrypoint method with ShortLinkMatcher interface
- Cleaned up agent go.mod by removing unused indirect dependencies.
2026-01-04 12:43:05 +08:00
Yuzerion
c00854a124 feat(autocert): add multi-certificate support (#185)
Multi-certificate, SNI matching with exact map and suffix tree

Add support for multiple TLS certificates with SNI-based selection. The
root provider maintains a single centralized SNI matcher that uses an
exact match map for O(1) lookups, falling back to a suffix tree for
wildcard matching.

Key features:
- Add `Extra []Config` field to autocert.Config for additional certificates
- Each extra entry must specify unique `cert_path` and `key_path`
- Extra certs inherit main config (except `email` and `extra` fields)
- Extra certs participate in ACME obtain/renew cycles independently
- SNI selection precedence: exact match > wildcard match, main > extra
- Single centralized SNI matcher on root provider rebuilt after cert changes

The SNI matcher structure:
- Exact match map: O(1) lookup for exact domain matches
- Suffix tree: Efficient wildcard matching (e.g., *.example.com)

Implementation details:
- Provider.GetCert() now uses SNI from ClientHelloInfo for selection
- Main cert is returned as fallback when no SNI match is found
- Extra providers are created as child providers with merged configs
- SNI matcher is rebuilt after Setup() and after ObtainCert() completes
2026-01-04 00:37:26 +08:00
yusing
117dbb62f4 refactor(docker): accept unix and ssh scheme for providers 2026-01-03 20:06:31 +08:00
yusing
2c28bc116c fix(h2c_test_server): correct listening on message 2026-01-03 12:58:14 +08:00
yusing
1d90bec9ed refactor(benchmark): restart bench server after each run 2026-01-03 12:54:18 +08:00
yusing
b2df749cd1 refactor(io,reverseproxy): suppress "client disconnected" error; optimize CopyClose method 2026-01-03 12:41:11 +08:00
yusing
1916f73e78 refactor(route): modernize code with unsafe.Add 2026-01-03 12:40:55 +08:00
yusing
99ab9beb4a refactor(http/transport): increase MaxIdleConnsPerHost to 1000 2026-01-03 12:40:28 +08:00
yusing
5de064aa47 refactor(benchmark): replace whoami service with bench server
- Updated dev.compose.yml to define a new bench service that serves 4096 bytes of random data.
- Modified configurations for Traefik, Caddy, and Nginx to route traffic to the new bench service.
- Added Dockerfile and Go application for the bench server, including necessary Go modules.
- Updated benchmark script to target the new bench service endpoint.
2026-01-03 12:40:10 +08:00
yusing
880e11c414 refactor(http/reverseproxy): performance improvement
- Replaced req.Clone with req.WithContext and url/header/trailer cloning.
- Added conditional handling for "Expect" headers to manage 1xx responses with appropriate tracing.
2026-01-03 02:30:15 +08:00
yusing
0dfce823bf refactor(http): performance improvement
- Introduced a sync.Pool for ResponseRecorder to optimize memory usage.
- Updated ServeHTTP method to utilize the new GetResponseRecorder and PutResponseRecorder functions.
- Adjusted NewResponseRecorder to leverage the pooling mechanism.
2026-01-03 02:20:01 +08:00
yusing
c2583fc756 refactor(benchmark): update whoami service configuration to use FQDN alias 2026-01-03 02:10:00 +08:00
yusing
cf6246d58a refactor(benchmark): remove unused Docker socket configuration from benchmark service 2026-01-03 02:04:49 +08:00
yusing
fb040afe90 refactor(benchmark): benchmark script functionality and fairness 2026-01-03 00:57:50 +08:00
yusing
dc8abe943d feat(benchmark): enhance dev.compose.yml with benchmark services and scripts
- Added benchmark services (whoami, godoxy, traefik, caddy, nginx) to dev.compose.yml.
- Introduced a new benchmark.sh script for load testing using wrk and h2load.
- Updated Makefile to include a benchmark target for easy execution of the new script.
2026-01-03 00:28:59 +08:00
yusing
587b83cf14 fix(idlewatcher): pass context to ProxmoxProvider 2026-01-02 22:17:40 +08:00
yusing
a4658caf02 refactor(config): correct logic in InitFromFile 2026-01-02 21:56:34 +08:00
yusing
ef9ee0e169 feat(websocket): update goutils - deduplicate data to avoid unnecessary traffic 2026-01-02 18:04:08 +08:00
yusing
7eadec9752 chore: remove unused utils/deep_equal.go 2026-01-02 18:03:13 +08:00
yusing
dd35a4159f refactor(api/health): simplify health info type
- Updated health-related functions to return simplified health information.
- Introduced HealthStatusString type for correct swagger and schema generation.
- Refactored HealthJSON structure to utilize the new HealthStatusString type.
2026-01-02 18:02:49 +08:00
yusing
f28667e23e refactor: add context handling in various functions
- Modified functions to accept context.Context as a parameter for better context management.
- Updated Init methods in Proxmox and Config to use the provided context.
- Adjusted UpdatePorts and NewProxmoxProvider to utilize the context for operations.
2026-01-02 17:41:36 +08:00
yusing
8009da9e4d chore: go mod tidy 2026-01-02 15:49:03 +08:00
yusing
590743f1ef feat(entrypoint): implement short link #177
- Added ShortLinkMatcher to handle short link routing.
- Integrated short link handling in Entrypoint.
- Introduced tests for short link matching and dispatching.
- Configured default domain suffix for subdomain aliases.
2026-01-02 15:42:15 +08:00
yusing
1f4c30a48e fix(docker): update scheme validation to include 'tcp' in DockerProviderConfigDetailed 2026-01-02 10:55:42 +08:00
yusing
bae7387a5d feat(dev): add jotty and postgres-test services to dev.compose.yml 2026-01-02 01:20:05 +08:00
yusing
67fc48383d refactor(monitor): include detail in service down notification log 2026-01-02 01:17:47 +08:00
yusing
1406881071 feat(http/h2c): h2c test server with a Dockerfile
- Implemented a basic HTTP/2 server that responds with "ok" to requests.
- Updated dev.compose.yml to include a service for it
2026-01-02 01:17:28 +08:00
yusing
7976befda4 feat(http): enable HTTP/2 support in server configuration
- Added NextProtos to TLSConfig to prefer HTTP/2 and fallback to HTTP/1.1.
- Configured the server to handle HTTP/2 connections, with error logging for configuration failures.
2026-01-02 01:11:07 +08:00
yusing
8139311074 feat(healthcheck/http): implement h2c health check support and refactor request handling
- Added support for health checks using the h2c scheme.
- Refactored common header setting into a dedicated function.
- Updated CheckHealth method to differentiate between HTTP and h2c checks.
2026-01-02 00:46:48 +08:00
yusing
2690bf548d chore: update swagger add h2c scheme type 2026-01-01 18:56:11 +08:00
yusing
d3358ebd89 feat(http/reverseproxy): h2c support with scheme: h2c 2026-01-01 18:54:49 +08:00
yusing
fd74bfedf0 fix(agent): improve url handling to not break urls with encoded characters 2026-01-01 18:25:27 +08:00
yusing
a47170da39 feat(metrics): add IsExcluded field to RouteUptimeAggregate for enhanced status tracking
- updated swagger
2026-01-01 13:20:17 +08:00
yusing
89a4ca767d fix(homepage): improve alphabetical sorting by normalizing item names (#181)
- Updated the sorting function to use Title case for item names to ensure consistent alphabetical ordering.
2026-01-01 12:31:36 +08:00
yusing
3dbbde164b fix(route): enhance host parsing with port suffix support
- Added logic to strip the trailing :port from the host when searching for routes.
- Updated findRouteByDomains function to ensure consistent host formatting.
- Added related tests
2025-12-30 22:46:38 +08:00
yusing
e75eede332 chore(goutils): update subproject commit reference to 51a75d68 2025-12-30 22:01:01 +08:00
yusing
e4658a8f09 fix(route): update health monitor initialization to use implementation instance 2025-12-30 21:59:43 +08:00
yusing
e25ccdbd24 chore: upgrade dependencies 2025-12-30 21:56:54 +08:00
yusing
5087800fd7 fix(tests/metrics): correct syntax error 2025-12-30 21:53:10 +08:00
yusing
d7f33b7390 chore(.gitignore): add dev-data directory to ignore list 2025-12-30 21:52:04 +08:00
yusing
1978329314 feat(route): add CommandRoute for routing requests to specified routes
- Introduced CommandRoute to handle routing requests to other defined routes.
- Added validation to ensure a single argument is provided for the route.
- Implemented command handler to serve the specified route or return a 404 error if not found.
2025-12-30 21:49:47 +08:00
yusing
dba8441e8a refactor(routes): add excluded routes to health check and route list
- Updated route iteration to include all routes, including excluded ones.
- Renamed existing functions for clarity.
- Adjusted health info retrieval to reflect changes in route iteration.
- Improved route management by adding health monitoring capabilities for excluded routes.
2025-12-30 12:39:58 +08:00
yusing
44fc678496 refactor(docker): simplify docker host parsing 2025-12-29 10:38:43 +08:00
yusing
0b410311da fix(oidc): add trailing slash to OIDCAuthBasePath to work with paths like /authorize 2025-12-29 10:22:38 +08:00
yusing
dc39f0cb6e chore(swagger): add installation instruction for swaggo in Makefile 2025-12-23 17:18:59 +08:00
yusing
e232b9d122 chore(swagger): update swagger regarding new docker config structure 2025-12-23 17:18:13 +08:00
yusing
41f8d3cfc0 refactor(docker): update TLS config validation to require both CertFile and KeyFile exists or both empty 2025-12-23 12:25:26 +08:00
Yuzerion
5ab0392cd3 feat: docker over tls (#178) 2025-12-23 12:01:11 +08:00
yusing
09702266a9 feat(debug): implement debug server for development environment
- Added `listenDebugServer` function to handle debug requests.
- Introduced table based debug page with different functionalities.
- Updated Makefile to use `scc` for code analysis instead of `cloc`.
2025-12-22 16:57:47 +08:00
yusing
14f3ed95ea feat(auth): modernize block page styling 2025-12-22 15:48:55 +08:00
yusing
eb3aa21e37 fix(healthcheck): fix fileserver health check by removing zero port check 2025-12-22 12:04:09 +08:00
yusing
a6e86ea420 fix(auth): correct logic in AuthOrProceed when auth is disabled 2025-12-22 12:00:14 +08:00
yusing
dd96e09a7a refactor(docker): streamline label loading in loadDeleteIdlewatcherLabels function 2025-12-22 11:54:37 +08:00
yusing
4d08efbd4f chore(deps): upgrade dependencies 2025-12-22 11:54:13 +08:00
yusing
f67480d085 feat(oidc): make rate limit customizable; per oidc instance rate limit
- add env variables OIDC_RATE_LIMIT and OIDC_RATE_LIMIT_PERIOD
- default rate limit changed to 10 rps from 1 rps
- rate limit is no longer applied globally
2025-12-22 10:43:41 +08:00
yusing
736985b79d fix(auth): enforce HTML acceptance in OIDC login handler 2025-12-22 10:35:43 +08:00
yusing
1fb1ee0279 refactor(auth): enhance error handling in OIDC login and callback handlers with user-friendly pages 2025-12-22 10:35:07 +08:00
yusing
4b2a6023bb refactor(auth): update WriteBlockPage function to include action text and URL 2025-12-22 10:27:48 +08:00
yusing
5852053ef9 fix(config): remove duplicated reload error 2025-12-21 11:23:42 +08:00
yusing
c687795cd8 refactor(docker): remove unnecessary http client in NewClient method 2025-12-21 11:23:21 +08:00
yusing
93af695e95 refactor(list_icons): interning app category names to save memory 2025-12-20 20:43:21 +08:00
yusing
58325e60b4 refactor(docker): remove deprecated client.WithAPIVersionNegotiation() 2025-12-20 19:51:43 +08:00
yusing
b134b92704 feat(fileserver): implement spa support; add spa and index fields to config 2025-12-20 19:24:39 +08:00
yusing
376ac61279 fix(healthcheck): nil panic on health check 2025-12-20 11:07:42 +08:00
yusing
dca701e044 fix(healthcheck): nil panic on agents 2025-12-20 10:03:43 +08:00
yusing
4bb3af3671 feat(workflow): add cherry-pick workflow for tagging into compat branch 2025-12-18 23:24:48 +08:00
yusing
95efc127cf fix(idlewatcher): incorrect "dependency has positive idle timeout" error 2025-12-18 23:22:42 +08:00
yusing
6e55c4624b refactor(http): consolidate User-Agent header in health monitor 2025-12-18 00:25:47 +08:00
yusing
e9374364dd feat(reverse_proxy): add scheme mismatch handling for retry logic in reverse proxy 2025-12-18 00:24:46 +08:00
yusing
216679eb8d fix(docker): nil panic reading container names 2025-12-17 23:17:11 +08:00
yusing
505a3d3972 refactor(http): enhance health check error logic by treating all 5xx as unhealthy 2025-12-17 17:43:59 +08:00
yusing
27512b4d04 chore: upgrade dependencies 2025-12-17 17:43:53 +08:00
yusing
88d7255c7a fix(idlewatcher): directly serve the request on ready instead of redirecting 2025-12-17 11:46:52 +08:00
yusing
ea67095967 refactor(docker): streamline idlewatcher label handling
- Introduced a map for idlewatcher labels to simplify the loading of configuration values.
- Simplify logic to check for the presence of an idle timeout and handle dependencies.
2025-12-17 10:33:37 +08:00
yusing
86a46d191d feat(idlewatcher): add option to disable loading page 2025-12-17 10:33:33 +08:00
yusing
b7250b29e0 fix(idlewatcher): improve container readiness handling in wakeFromHTTP
- Updated the wakeFromHTTP method to send a 100 Continue response to prevent client wait-header timeout.
- Implemented logic for non-HTML requests to wait for the container to become ready, returning an error message if it times out, or redirecting if successful.
- Adjusted the waitForReady method to return true upon receiving a ready notification.
2025-12-17 10:30:06 +08:00
yusing
e44ecc0ccc fix(access_log): fix slice out-of-bound panic on log rotation 2025-12-16 17:20:59 +08:00
yusing
6f9f995100 fix(config): nil panic introduced in ff934a4bb2911f5fa3c23d8fe6fea252d881fdc3; remove duplicated log 2025-12-16 15:04:21 +08:00
yusing
496aec6bb6 refactor: simplify and optimize deserialization 2025-12-16 14:48:33 +08:00
yusing
4afed02fc2 refactor(pool): simplify and optimize SizedPool; remove sync pool 2025-12-16 14:08:38 +08:00
yusing
f7eb4b132a refactor(config): remove unused ActiveConfig 2025-12-16 11:57:09 +08:00
yusing
ff934a4bb2 fix(config): fix default values not applied 2025-12-16 11:55:47 +08:00
yusing
db0cbc6577 refactor(config): remove unnecessary indirection 2025-12-16 11:22:17 +08:00
yusing
de3f92246f feat(rules): add protocol matching functionality
- Introduced a new checker for HTTP protocols (http, https, h3) in the routing rules.
- Added corresponding test cases to validate protocol matching behavior in requests.
2025-12-16 10:22:00 +08:00
yusing
c143593284 fix(icons): add handling for dark icons for walkxcode 2025-12-15 15:42:59 +08:00
yusing
31bf889d4a refactor(favicon): enhance FindIcon function to support icon variants
- Updated FindIcon to accept an additional variant parameter for improved icon fetching.
- Adjusted FavIcon and GetFavIconFromAlias functions to utilize the new variant handling logic.
2025-12-15 14:54:05 +08:00
yusing
baa7e72ad6 refactor(icon): improve handling in WithVariant 2025-12-15 14:42:31 +08:00
yusing
f43e07fe60 fix(favicon): enhance variant handling in GetFavIconFromAlias function
- Added fallback logic to handle cases where the requested icon variant is unavailable.
- If variant not provided, do not call WithVariant.
2025-12-15 14:37:33 +08:00
yusing
d319ee99ad fix(favicon): correct icon cache key in FindIcon method 2025-12-15 14:31:16 +08:00
yusing
ab58559afc refactor(icon): add variant handling for absolute/relative icons in WithVariant method 2025-12-15 14:30:31 +08:00
yusing
a6bdbb5603 chore: update api swagger 2025-12-15 12:28:14 +08:00
yusing
a0c589c546 feat(favicon): add variant support for favicons
- Introduced a new Variant field in GetFavIconRequest to specify icon variants (light/dark).
- Updated GetFavIconFromAlias function to handle the variant when fetching favicons.
- Added WithVariant method in IconURL to manage icon variants effectively.
2025-12-15 12:28:03 +08:00
yusing
76b8252755 fix(socket-proxy): update golang version. fix Dockerfile 2025-12-10 17:56:04 +08:00
yusing
d547872a41 fix(ci): correct socket-proxy github workflow 2025-12-10 17:39:39 +08:00
yusing
8d4618cedf chore(deps): go mod tidy 2025-12-10 17:37:46 +08:00
yusing
2ba758939b chore(deps): upgrade dependencies 2025-12-10 17:27:00 +08:00
yusing
fdd37b777a fix(http): 'runtime error: comparing uncomparable type httputils.UnwrittenBody' 2025-12-10 17:23:13 +08:00
yusing
bc19a54976 chore(deps): upgrade dependencies in submodules 2025-12-08 14:17:14 +08:00
yusing
12d999809f fix(http): correct Unwrap method and enhance error handling in Hijack method
- Updated the Hijack method in LazyResponseModifier and ResponseModifier to return a wrapped error for unsupported hijacking.
- Added a nil check in LazyResponseModifier's Unwrap method to ensure safe access to the underlying ResponseWriter.
2025-12-08 14:06:58 +08:00
yusing
6771293336 fix(middleware): enhance response modification handling in ServeHTTP
- Replaced ResponseModifier with new LazyResponseModifier.
- Added logic to skip modification for non-HTML content.
2025-12-08 13:45:53 +08:00
yusing
d240c9dfee fix(io): limit buffer size to 16KB to avoid high memory usage and improve context propagation 2025-12-08 10:46:00 +08:00
yusing
c7eda38933 refactor(route): simplify context handling in RouteContext
- Removed unnecessary requestInternal struct and directly accessed the context field of http.Request.
- Simplified the initialization of ctxFieldOffset.
2025-12-05 18:26:34 +08:00
yusing
09caa888ad refactor(config): update config structures to use strutils.Redacted for sensitive fields
- Modified Config structs in various packages to replace string fields with strutils.Redacted to prevent logging sensitive information.
- Updated serialization methods to accommodate new data types.
- Adjusted API token handling in Proxmox configuration.
2025-12-05 18:26:16 +08:00
yusing
e41a487371 chore: remove go.work 2025-12-05 17:51:22 +08:00
yusing
7c08a8da2e Revert "ci: Add workflow to automatically merge main into compat on tag push"
This reverts commit 9930f3fa2e.
2025-12-05 16:29:45 +08:00
yusing
82df824490 chore: go mod tidy 2025-12-05 16:20:29 +08:00
yusing
2f341001c1 chore(Makefile): add socket-proxy to docker build test and update build command syntax 2025-12-05 16:10:14 +08:00
yusing
25ee8041da refactor(http,rules): move SharedData and ResponseModifier to httputils
- implemented dependency injection for rule auth handler
2025-12-05 16:06:36 +08:00
yusing
8687a57b6c fix(Dockerfile): exclude goutils in mod caching stage 2025-12-05 01:29:30 +08:00
yusing
3f4ed31e46 fix(middleware): skip modify response for websocket and event-stream requests in ServeHTTP 2025-12-05 01:18:27 +08:00
yusing
9930f3fa2e ci: Add workflow to automatically merge main into compat on tag push 2025-12-05 01:11:50 +08:00
yusing
2157545e17 fix(route): nil panic when used as an idlewatcher dependency 2025-12-05 01:10:48 +08:00
yusing
f721395ff0 refactor(healthcheck): agent health check 2025-12-05 00:45:24 +08:00
yusing
0dc7c59af1 refactor(deps): upgrade go to 1.25.5; isolate dependencies for reverseproxy, websocket and server modules 2025-12-05 00:36:16 +08:00
yusing
e3fe126a5c chore(example): introduce health check configuration defaults in example config 2025-12-04 18:08:26 +08:00
yusing
aa2575696d fix(http): handle 0 content length properly in some cases 2025-12-04 17:33:01 +08:00
yusing
c1f9c2c957 fix(middleware): skip modification for HEAD requests in ModifyHTML middleware 2025-12-04 17:27:26 +08:00
yusing
c098fef615 fix(http): enhance Content-Length handling in ResponseModifier
- Introduced origContentLength and bodyModified fields to track original content length and body modification status.
- Updated ContentLength and ContentLengthStr methods to return accurate content length based on body modification state.
- Adjusted Write and FlushRelease methods to ensure proper handling of Content-Length header.
- Modified middleware to use the new ContentLengthStr method.
2025-12-04 17:26:15 +08:00
yusing
9cdc985fb0 fix(tests): correct test expectations for middleware bypass and rules 2025-12-04 16:18:14 +08:00
yusing
2034738422 refactor(labels): refine wildcard expansion logic and tests
- Added multiple test cases for the ExpandWildcard function to cover various scenarios including basic wildcards, no wildcards, empty labels, and YAML configurations.
- Improved handling of nested maps and invalid YAML inputs.
- Ensured that explicit labels and reference aliases are correctly processed and expanded.
2025-12-04 16:16:43 +08:00
yusing
55a42b81de refactor(healthcheck): streamline health check configuration and defaults
- Moved health check constants from common package alongside type definition.
- Updated health check configuration to use struct directly instead of pointers.
- Introduced global default health check config
2025-12-04 15:19:10 +08:00
yusing
48627753d6 refactor(routes): simplify route exclusion check and health check defaults 2025-12-04 15:12:35 +08:00
yusing
09b514393d chore(deps): upgrade go version and dependencies 2025-12-04 12:18:12 +08:00
yusing
3b2ae5dbd6 refactor: move some utility functions to goutils and update references 2025-12-04 12:17:33 +08:00
yusing
fac3d67a51 chore; update goutils 2025-11-23 11:46:04 +08:00
yusing
cb642d7b32 fix(middleware): correct body mutation behavior in ServeHTTP
Refactor ServeHTTP to properly handle response body mutations by:
- Using ResponseModifier to capture response before modification
- Reading body content and allowing middleware to modify it
- Writing modified body back if changed during modification
- Ensuring proper order: RequestModifier before, ResponseModifier after next()

Previously, httputils.NewModifyResponseWriter did not correctly handle
body mutations. The new implementation captures the full response,
allows modification via modifyResponse, and properly writes back any
changes to the body.

Add BodyReader() and SetBody() methods to ResponseModifier to support
reading and replacing response body content.
2025-11-17 16:32:58 +08:00
yusing
9285977495 chore(deps): upgrade dependencies 2025-11-17 15:15:06 +08:00
yusing
e00cd8a35b fix(http): add Body field to response ModifyResponseWriter to avoid nil dereference panic 2025-11-17 15:12:13 +08:00
yusing
8ac459c038 fix(rules): ignore unsupported flush errors in ResponseModifier 2025-11-17 10:59:20 +08:00
yusing
1bcaf0dab5 fix(docker): revert to API version negotiation for Docker client 2025-11-15 10:52:03 +08:00
yusing
a291a49a0e fix(swagger): update netip.Addr definition type to string 2025-11-14 22:28:29 +08:00
yusing
28fdf3d2f4 fix(types/route): update HealthCheck JSON tag to be nullable for load-balancer routes 2025-11-14 22:16:30 +08:00
yusing
84b17baf46 refactor(rules): correct json format; remove MarshalJSON from []Rules 2025-11-14 20:53:35 +08:00
yusing
06ddb178f8 fix(proxmox): handle case when no nodes are available in AvailableNodeNames function 2025-11-14 10:59:58 +08:00
yusing
61fa7d2665 chore(debug): add debug logging for bypass rules and remove for route validation 2025-11-14 10:58:55 +08:00
yusing
615521ee1c chore: update submodule goutils 2025-11-14 10:33:11 +08:00
yusing
bbe308e821 ci: add Jenkinsfile for CI pipeline and configure SonarQube analysis 2025-11-13 23:39:23 +08:00
yusing
c156173757 refactor(docker): migrate from github.com/docker/docker to github.com/moby/moby 2025-11-13 23:03:27 +08:00
yusing
b1aae1cacf fix(rule): allow manifest.json in webui rule to make PWA work properly 2025-11-13 20:51:20 +08:00
yusing
f46552b477 fix(log): fix scrambled config log output 2025-11-13 20:49:38 +08:00
yusing
efe1350ffd chore: upgrade dependencies 2025-11-13 15:30:15 +08:00
yusing
219eedf3c5 fix(oidc): correct behavior when working with bypass rules
- Introduced a new handler for unknown paths in the OIDCProvider to prevent fallback to the default login page.
- Forced OIDC middleware to treat unknown path as logic path to redirect to login property when bypass rules is declared.
- Refactored OIDC path constants.
- Updated checkBypass middleware to enforce path prefixes for bypass rules, ensuring proper request handling.
2025-11-13 15:13:20 +08:00
yusing
f6dcc8f118 fix(idlewatcher): correct variable declaration for event data parsing in loading.js 2025-11-07 17:08:58 +08:00
yusing
4d6541c851 chore(deps): update Go version in Dockerfile and go mod tidy 2025-11-07 16:35:35 +08:00
yusing
c9db350cbc fix(route): add workaround for WebUI rule preset loading in validateRules function
- Introduced a temporary fix for loading the "webui.yml" rule preset when the container image is "godoxy-frontend".
- Added error handling for cases where the rule preset is not found.
- Marked the change with a FIXME comment to investigate the underlying issue in the future.
2025-11-07 16:16:58 +08:00
yusing
56374d595a fix(idlewatcher): correct target URL validation logic in NewWatcher function 2025-11-07 15:55:56 +08:00
yusing
d81521f293 refactor: improve HTTPS detection logic by using case-insensitive comparison for X-Forwarded-Proto header 2025-11-07 15:49:51 +08:00
yusing
e9ac3cd1a9 refactor: fix incorrect logic introduced in previous commits and improve error handling 2025-11-07 15:48:38 +08:00
yusing
d33ff2192a refactor(loadbalancer): implement sticky sessions and improve algorithm separation
- Refactor load balancer interface to separate server selection (ChooseServer) from request handling
- Add cookie-based sticky session support with configurable max-age and secure cookie handling
- Integrate idlewatcher requests with automatic sticky session assignment
- Improve algorithm implementations:
  * Replace fnv with xxhash3 for better performance in IP hash and server keys
  * Add proper bounds checking and error handling in all algorithms
  * Separate concerns between server selection and request processing
- Add Sticky and StickyMaxAge fields to LoadBalancerConfig
- Create dedicated sticky.go for session management utilities
2025-11-07 15:24:57 +08:00
yusing
910ef639a4 feat(idlewatcher): implement real-time SSE-based loading page with enhanced UX
This major overhaul of the idlewatcher system introduces a modern, real-time loading experience with Server-Sent Events (SSE) streaming and improved error handling.

- **Real-time Event Streaming**: New SSE endpoint (`/$godoxy/wake-events`) provides live updates during container wake process
- **Enhanced Loading Page**: Modern console-style interface with timestamped events and color-coded status messages
- **Improved Static Asset Management**: Dedicated paths for CSS, JS, and favicon to avoid conflicting with upstream assets
- **Event History Buffer**: Stores wake events for reconnecting clients and debugging

- Refactored HTTP request handling with cleaner static asset routing
- Added `WakeEvent` system with structured event types (starting, waking_dep, dep_ready, container_woke, waiting_ready, ready, error)
- Implemented thread-safe event broadcasting using xsync.Map for concurrent SSE connections
- Enhanced error handling with detailed logging and user-friendly error messages
- Simplified loading page template system with better asset path management
- Fixed race conditions in dependency waking and state management

- Removed `common.go` functions (canceled, waitStarted) - moved inline for better context
- Updated Waker interface to accept context parameter in Wake() method
- New static asset paths use `/$godoxy/` prefix to avoid conflicts

- Console-style output with Fira Code font for better readability
- Color-coded event types (yellow for starting, blue for dependencies, green for success, red for errors)
- Automatic page refresh when container becomes ready
- Improved visual design with better glassmorphism effects and responsive layout
- Real-time progress feedback during dependency wake and container startup

This change transforms the static loading page into a dynamic, informative experience that keeps users informed during the wake process while maintaining backward compatibility with existing routing behavior.
2025-11-07 14:58:33 +08:00
yusing
3cbd70f73a fix(health_check): correct agent routes health check logic 2025-11-07 10:21:15 +08:00
yusing
83d70d3bb2 fix(health_monitor): fix UpdateURL behavior in Docker and AgentProxied health monitor 2025-11-07 00:53:34 +08:00
yusing
bbb1b8497f fix(health_monitor): treat some errors as unhealth instead of actual error in RawHealthMonitor 2025-11-06 23:37:34 +08:00
yusing
d57d76dc65 feat(loading_page): move loading page css to style.css and serve as static asset 2025-11-06 20:32:40 +08:00
yusing
ef893974ea feat(route): implement PreferOver method for deterministic route replacement 2025-11-06 20:19:49 +08:00
yusing
b90f2409ab refactor(docker): set alias initially to have better debuggability 2025-11-06 20:15:03 +08:00
yusing
36e9b0d416 chore: upgrade go to 1.25.4 and dependencies 2025-11-06 20:13:01 +08:00
yusing
306cb7a20e fix(access_logger): fix stdout and path not working at the same time 2025-11-01 12:07:22 +08:00
yusing
e3915210aa fix(time): data race in DefaultTimeNow 2025-11-01 02:18:24 +08:00
yusing
e8fb202ea9 fix(docker): fix wildcard not working correctly with #N ref aliases 2025-11-01 02:10:09 +08:00
yusing
082b2f5da2 refactor(websocket): use only the first error and fix race condition 2025-11-01 01:28:12 +08:00
yusing
e670acb4b8 fix(access_logger): nil panic when stdout only, improve concurrency safety 2025-11-01 01:17:55 +08:00
yusing
77e486f4fe refactor(route): ensure validation and start only starts once, and lock error before finishing 2025-10-31 18:10:09 +08:00
yusing
3ccaba3163 fix(validation): prioritize pointer method for custom validation in serialization 2025-10-31 18:06:41 +08:00
yusing
705923960c feat(fileserver): add rules support for fileservers 2025-10-31 17:32:37 +08:00
yusing
ca737c8979 fix(modify-html): re-enable modifying HTML with chunked encoding 2025-10-31 17:30:23 +08:00
yusing
b6b5d4dbd7 fix(auth): handle nil defaultAuth to prevent nil panic before auth intializes 2025-10-31 17:15:03 +08:00
yusing
b2919fbaf6 feat(rules): supress some errors in rule execution 2025-10-31 17:13:09 +08:00
yusing
722c40d103 chore(examples): update example configurations with comments for certificate paths and lite variant 2025-10-30 11:45:06 +08:00
yusing
860d9c71b6 fix(pool,io): overlap memory on buffer splitting; hook in HookReadCloser should run after Close 2025-10-29 22:48:28 +08:00
yusing
e354d901c4 fix(monitor): safer approach to avoid nil panic in edge cases 2025-10-29 00:19:17 +08:00
yusing
921a8fb935 fix(monitor): handle missing container state in Docker health check 2025-10-28 23:49:59 +08:00
yusing
975354cdc1 chore(compose): comment out user for lite variant in example configuration 2025-10-28 23:15:54 +08:00
yusing
7d38bfd2d2 build: drop old image name support 2025-10-28 22:05:35 +08:00
yusing
5506cafa26 fix(rules): pages not loading correct for lite webui variant 2025-10-28 22:00:54 +08:00
yusing
9fd5bff81a fix(oidc): fix Webui OIDC loop 2025-10-28 21:54:46 +08:00
yusing
38041ca5b8 fix(pool): handle buffer capacity check in GetSized
- Added a check to ensure the buffer's capacity is sufficient before reusing it.
- Included a FIXME comment to address an unexpected condition in buffer allocation.
2025-10-28 21:48:27 +08:00
yusing
61be88c1d3 chore: upgrade dependencies 2025-10-28 21:40:23 +08:00
yusing
cb4dcb962e fix(http): nil panic in goutils http/intercept.go 2025-10-28 21:37:19 +08:00
yusing
1797a222cd fix(middlewares): correctly bypass middlewares with response rules 2025-10-28 20:44:46 +08:00
yusing
098fb7e62d fix(compose): update rootless compose example 2025-10-28 17:03:20 +08:00
yusing
d4dfec8293 refactor(http): proper ResponseWriter and headers handling across files 2025-10-28 14:43:10 +08:00
yusing
f29b69ff3b refactor(rules): remove Flush method and replace with http.NewResponseController in ResponseModifier 2025-10-27 17:46:23 +08:00
yusing
5e00e1c437 fix(middleware): correct and simplify HTML modification / buffer management logic, correct Accept-Encoding header 2025-10-27 15:08:29 +08:00
yusing
39c8cc2820 fix(auth): nil panic by handling in TryRefreshToken 2025-10-27 14:25:05 +08:00
yusing
56232dbd0e fix(monitor): nil panic in DockerHealthMonitor 2025-10-27 12:46:22 +08:00
yusing
baf774f927 fix(middleware): properly release buffer on error and not to reuse content for bytes.Buffer 2025-10-26 23:16:38 +08:00
yusing
a3c82209c6 refactor(api): disable caching completely 2025-10-26 21:33:58 +08:00
yusing
386d946bd2 feat(rules): support variables for error comand 2025-10-26 20:25:46 +08:00
yusing
ee9bf31d30 chore(compose): add comments for lite variant uid/gid configuration in example 2025-10-26 19:46:59 +08:00
yusing
2c87eebee3 chore(compose): remove host network_mode from example 2025-10-26 19:27:56 +08:00
yusing
5be784d567 chore(env): remove frontend port configuration from example files 2025-10-26 19:26:53 +08:00
yusing
a999c51bf8 fix(metrics): json marshaling 2025-10-26 16:57:16 +08:00
yusing
7ca722b256 fix(metrics): correct network data aggregation logic in system_info.go 2025-10-26 16:46:34 +08:00
yusing
51295be463 fix(json): ensure valid json 2025-10-26 16:38:08 +08:00
yusing
51fc5f017a feat(api): add sonic build tag in Makefile to let gin use sonic for json handling 2025-10-26 16:36:41 +08:00
yusing
e4996733fc fix(types): add placeholder field in VirtualMemoryStat for swagger 2025-10-26 16:04:28 +08:00
yusing
f76d86dfa2 feat(api): rules playground API
- updated swagger
2025-10-26 15:56:18 +08:00
yusing
8778f4ea73 fix(json): unmarshal error introduced in previous commit 2025-10-26 01:29:39 +08:00
yusing
6f75bb7593 refactor(api): replace apitypes module and fix swagger generation 2025-10-26 01:05:18 +08:00
yusing
964ba1eac1 chore: update dev environment configuration and base images
- Changed API_SECRET to API_JWT_SECRET in dev.compose.yml
- Updated base image from alpine to debian in dev.Dockerfile
- Upgraded golang version from 1.25.2 to 1.25.3 in Dockerfile
2025-10-25 23:31:53 +08:00
yusing
6e7b571946 feat(rules): add regex for image and font file paths in webui presets 2025-10-25 23:31:22 +08:00
yusing
fc7a81faf5 chore: upgrade dependencies 2025-10-25 23:27:35 +08:00
yusing
488ad160e7 fix(rules): ensure postform and form initialized, fix tests 2025-10-25 23:07:18 +08:00
yusing
1ec2872f3d feat(rules): replace go templates with custom variable expansion
- Replace template syntax ({{ .Request.Method }}) with $-prefixed variables ($req_method)
- Implement custom variable parser with static ($req_method, $status_code) and dynamic ($header(), $arg(), $form()) variables
- Replace templateOrStr interface with templateString struct and ExpandVars methods
- Add parser improvements for reliable quote handling
- Add new error types: ErrUnterminatedParenthesis, ErrUnexpectedVar, ErrExpectOneOrTwoArgs
- Update all tests and help text to use new variable syntax
- Add comprehensive unit and benchmark tests for variable expansion
2025-10-25 22:43:47 +08:00
yusing
9c3346dd9d fix(agent): correct usage of task in StartAgentServer and updated test expectations 2025-10-22 00:02:13 +08:00
yusing
203faa8e7e chore: update goutils 2025-10-22 00:01:27 +08:00
yusing
fbc853fa6a fix(script): incorrectly set DOCKER_SOCKET as rootless 2025-10-20 20:48:38 +08:00
yusing
3fefbdfded chore: update goutils 2025-10-20 20:46:31 +08:00
yusing
48be6def12 feat(autocert): add hostinger autocert provider
- upgraded dependencies and submodules
2025-10-19 10:48:00 +08:00
yusing
94d6b7a168 fix(pool): missing storeFullCap when allocating new buffer 2025-10-18 19:59:14 +08:00
yusing
1ca4b4939e perf(healthcheck): stop docker client from hogging resources in health checks 2025-10-18 19:35:32 +08:00
yusing
f8716d990e perf(pool): split bytes pool into tiered sized and unsized pools
- Remove BytesPoolWithMemory; split into UnsizedBytesPool and 11-tier SizedBytesPool
- Track buffer capacities with xsync Map to prevent capacity leaks
- Improve buffer reuse: split large buffers and put remainders back in pool
- Optimize small buffers to use unsized pool
- Expand test coverage and benchmarks for various allocation sizes
2025-10-18 17:38:01 +08:00
yusing
5a91db8d10 perf: use fasthttp for health checks; upgrade go to 1.25.3 2025-10-17 22:50:13 +08:00
yusing
3e73be60a1 fix(gotify): error if token not present 2025-10-16 10:25:38 +08:00
yusing
af9363209b fix(systeminfo): correct system info JSON format 2025-10-16 10:09:51 +08:00
yusing
ccc35b2a00 refactor: remove functional.Set wrapper 2025-10-16 10:08:25 +08:00
yusing
44536139c1 refactor: refine byte pools usage and fix memory leak in rules 2025-10-15 23:53:26 +08:00
yusing
2b4c39a79e perf(mem): reduced memory usage in metrics and task by string interning and deduplicating fields 2025-10-15 23:51:47 +08:00
yusing
ddf78aacba perf(logging): optimize multi-line message formatting
- Refactors the fmtMessage function to use strings.Builder
  - Simplifies multi-writer creation with a helper function
  - Updates the new console writer initialization pattern
  - Moves InitLogger function to the top
  - Fixed NewLoggerWithFixedLevel
2025-10-15 21:18:25 +08:00
yusing
f5a006ce81 refactor(task): fix onFinish not being called and simplify by replacing semaphore with channel 2025-10-15 15:07:19 +08:00
yusing
290af4e311 perf(mem): replace Scheme and ExcludedReason string with uint8 type to reduce mem usage 2025-10-15 14:35:44 +08:00
yusing
feafdf05f2 fix(validation): correct CustomValidator and strutils.Parser handling, add tests 2025-10-15 14:20:47 +08:00
yusing
b09bfd6c1e fix(serialization): use replace os.LookupEnv with env.LookupEnv 2025-10-15 00:12:17 +08:00
yusing
e13b18621d fix(favicon): add status code in error message 2025-10-15 00:11:38 +08:00
Yuzerion
53f3397b7a feat(rules): add post-request rules system with response manipulation (#160)
* Add comprehensive post-request rules support for response phase
* Enable response body, status, and header manipulation via set commands
* Refactor command handlers to support both request and response phases
* Implement response modifier system for post-request template execution
* Support response-based rule matching with status and header checks
* Add comprehensive benchmarks for matcher performance
* Refactor authentication and proxying commands for unified error handling
* Support negated conditions with !
* Enhance error handling, error formatting and validation
* Routes: add `rule_file` field with rule preset support
* Environment variable substitution: now supports variables without `GODOXY_` prefix

* new conditions:
  * `on resp_header <key> [<value>]`
  * `on status <status>`
* new commands:
  * `require_auth`
  * `set resp_header <key> <template>`
  * `set resp_body <template>`
  * `set status <code>`
  * `log <level> <path> <template>`
  * `notify <level> <provider> <title_template> <body_template>`
2025-10-14 23:53:06 +08:00
yusing
19968834d2 fix(autocert): added back timewebcloud provider 2025-10-13 07:12:38 +08:00
yusing
d41c6f8d77 fix(cookie): net/http: invalid Cookie.Domain .0.0.1:3000 2025-10-13 07:09:17 +08:00
yusing
dcc5ab8952 fix(entrypoint): 404 everything with match_domains 2025-10-13 06:45:29 +08:00
yusing
cc8858332d fix(agent): remove leftover pointer in tls.Config 2025-10-12 22:23:14 +08:00
yusing
82f02ea2bf fix(rules): nil panic when only having default rule 2025-10-12 22:21:01 +08:00
yusing
046ff8a020 chore: update config example about new not_found rule 2025-10-12 22:02:52 +08:00
yusing
dc9ae32e8f feat(entrypoint): add not found rule to customize 404 behavior 2025-10-12 21:04:49 +08:00
yusing
5640d5d454 fix(serialization): correctly handle json tag 2025-10-12 20:59:12 +08:00
yusing
c66de99fcb perf: further optimize http and body buffer handling 2025-10-12 20:57:51 +08:00
yusing
eef994082c fix(panic): nil panic in IterRoutes 2025-10-12 16:51:52 +08:00
yusing
8c670ab92e chore: update README.md and config.example.yml for new changes 2025-10-12 14:25:55 +08:00
yusing
d11ddb7c91 fix(ci): checkout submodules 2025-10-12 14:23:00 +08:00
yusing
78aea4b4d2 fix: gopsutil 2025-10-12 13:04:28 +08:00
yusing
80dd142861 refactor(rules): rename Static and Returning commands into Terminating and NonTerminating commands 2025-10-12 09:38:06 +08:00
yusing
92aa61e732 refactor(log): simplify access logger and disable stdout buffering
- Remove MultiWriter complexity and use single writer interface
  - Disable buffering for stdout logging to ensure immediate output
  - Replace slice-based closer/rotate support with type assertions
  - Simplify rotation result handling by passing result pointer
  - Update buffer size constants and improve memory management
  - Remove redundant stdout_logger.go and multi_writer.go files
  - Fix test cases to match new rotation API signature
2025-10-11 19:14:59 +08:00
yusing
848f26aa86 test(list-icon): fix tests regarding previous changes 2025-10-11 19:05:49 +08:00
yusing
81e500fcfc fix(log): stdout logging format 2025-10-11 18:30:38 +08:00
yusing
f417e0fa25 fix(notif): remove test logging 2025-10-11 18:13:19 +08:00
yusing
cb5a8e7b9d fix(acl): correct acl log handling and add country to summary 2025-10-11 17:03:08 +08:00
yusing
16cad11e89 fix(notif): format not being applied correctly 2025-10-11 16:54:52 +08:00
yusing
2bfbdbf519 refactor(acl): adjust summary format and add total count 2025-10-11 16:13:52 +08:00
yusing
d5e9a7b3b6 fix(notif): respect Method and empty content-type 2025-10-11 16:02:18 +08:00
yusing
7ea415078f fix(config): fix error and logging handling 2025-10-11 14:00:04 +08:00
yusing
e67704695b fix(acl): complete tcp and udp wrapper interface 2025-10-11 13:47:13 +08:00
yusing
804c7eec60 fix: env parsing 2025-10-11 13:37:18 +08:00
yusing
ea8be56bf8 fix(server): race condition on server stop 2025-10-11 13:26:10 +08:00
yusing
20c77edce5 chore: go mod tidy 2025-10-11 13:20:19 +08:00
yusing
4f2f0f58e2 fix(autocert): nil dereference 2025-10-11 13:12:45 +08:00
yusing
ac8ad149b8 fix(icons): list icon logic 2025-10-11 13:07:50 +08:00
yusing
14ec80c883 fix: Dockerfile mod caching 2025-10-11 13:01:52 +08:00
yusing
5de5f854ce fix: dockerfile 2025-10-11 12:56:55 +08:00
yusing
3d8994b42e chore: enhance example config 2025-10-11 12:46:54 +08:00
yusing
66043e4a26 refactor!: simplify lego generate script; add and drop some dns provider 2025-10-11 12:44:16 +08:00
yusing
d1e403e16f fix(docker): correct image in rootless docker compose 2025-10-11 11:27:14 +08:00
yusing
e72e20af69 fix(script): add missing domain input in setup.sh 2025-10-11 11:16:39 +08:00
yusing
ad6201c27a feat(dev): add parca in dev docker for profiling 2025-10-10 23:24:39 +08:00
yusing
c4c9e9300c chore: simplify dev docker and update Makefile accordingly 2025-10-10 23:24:14 +08:00
yusing
b23c3f1c3b refactor(icons): replace mutex-based cache with atomic synk.Value
- Remove sync.RWMutex and Cache struct in favor of atomic Value
  - Implement background goroutine for periodic icon updates
  - Add backward compatibility for old cache format
  - Improve concurrent access to icon cache
  - Simplify ListAvailableIcons()
2025-10-10 23:21:30 +08:00
yusing
38c0419483 feat(config): add temporary logging for failed reloads
- Add tmpLogBuf and tmpLog fields to capture config loading logs
  - Flush temporary logs only when reload succeeds
  - Extract NewLogger function for creating custom loggers
  - Update State interface to include FlushTmpLog method
2025-10-10 22:20:12 +08:00
yusing
357ce38b18 fix(idlewatcher): correctly restart on config reload 2025-10-10 21:57:36 +08:00
yusing
ef34c3ffdd fix(server): should wait for server to stop 2025-10-10 21:47:03 +08:00
yusing
2e411373a2 fix(config): failed reload should not start providers in new state 2025-10-10 21:46:02 +08:00
yusing
3dedd66ad1 test(rules): add tests for glob and regex, remove old path glob test 2025-10-10 21:39:21 +08:00
yusing
98f047d88a fix(rules): correct dollar sign handling 2025-10-10 21:37:54 +08:00
yusing
973a58e982 fix(import): remove unused import in rules/validate.go 2025-10-10 20:49:12 +08:00
yusing
4b55d1c607 fix: missing bracket in main.go 2025-10-10 20:46:47 +08:00
yusing
63eff4707c fix: submodules url 2025-10-10 20:40:33 +08:00
yusing
55a74c36b0 refactor(acl): optimize slice allocation in logNotifyLoop 2025-10-10 20:21:53 +08:00
yusing
fbabb7b7fb refactor(acl): default not to notify allowed and skip when total is 0 2025-10-10 20:20:09 +08:00
yusing
7a1841e9a5 fix(acl): add json tag for notify 2025-10-10 15:26:08 +08:00
yusing
d82bfd0ebd feat(acl): add periodic notification system for access summaries
- Add Notify configuration with To field and interval
  - Track allowed/blocked IP counts per address
  - Send periodic summary notifications with access statistics
  - Optimize logging with channel-based processing for concurrent safety
2025-10-10 15:24:48 +08:00
yusing
1f41c035ea feat(notification): add To field to LogMessage 2025-10-10 14:47:20 +08:00
yusing
c2c9f42fb3 feat(rules): glob and regex support, env var substitution
- optimized `remote` rule for ip matching
- updated descriptions
2025-10-10 14:43:48 +08:00
yusing
60cfff3435 refactor(idlewatcher): streamline loading screen favicon handling 2025-10-10 12:55:06 +08:00
yusing
c93a460043 refactor(config): add omitempty on some fields 2025-10-10 10:34:49 +08:00
yusing
9bf7a0beef revert(config): added back pointer for agent and notification config for correct unmarshaling 2025-10-10 10:07:49 +08:00
yusing
c89c737ecd fix(pool): variable shadowing 2025-10-10 10:01:42 +08:00
yusing
382fc61a9c chore: update submodules 2025-10-10 09:57:08 +08:00
yusing
b2de33e835 fix: Makefile 2025-10-10 09:55:21 +08:00
yusing
86644054e6 refactor: use goutils/env in socket-proxy 2025-10-10 09:54:16 +08:00
yusing
c2dcabe144 refactor(rules): remove 'caller' parameter in BuildHandler 2025-10-10 09:53:44 +08:00
yusing
c59ddc1df6 refactor: add ShouldExclude() bool to Route interface 2025-10-10 09:53:08 +08:00
yusing
f4db874fd6 refactor(proxmox): rename checkIPPrivate to privateIPOrNil 2025-10-10 09:52:38 +08:00
yusing
f334f5c13c feat(config): pretty print active config and routes by provider on load / reload 2025-10-10 09:51:32 +08:00
yusing
5acc4c3894 fix(homepage): logic error in ListAvailableIcons, ensure the cache is ready in GetHomepageMeta 2025-10-10 09:27:23 +08:00
yusing
a8aa82f687 chore: upgrade dependencies 2025-10-10 09:11:29 +08:00
yusing
0f3a1ac6e6 refactor: improved task lifecycle management 2025-10-10 09:07:47 +08:00
yusing
9fceda6729 fix: data race in strings.Title 2025-10-09 23:08:30 +08:00
yusing
becb49e864 fix(uptime): set to 0 instead of returning error on overflow check 2025-10-09 22:53:38 +08:00
yusing
3aed41e078 refactor: move version.go to goutils 2025-10-09 01:14:43 +08:00
yusing
8047067b2b refactor(utils): move utils/atomic to goutils 2025-10-09 01:07:47 +08:00
yusing
c3fa7c66a7 feat(entrypoint): added CatchAll and NotFound rules and handler 2025-10-09 01:03:16 +08:00
yusing
cab68807ee refactor(config): restructured with better concurrency and error handling, reduced cross referencing 2025-10-09 01:02:24 +08:00
yusing
d08be872a0 refactor(errors): simplify gperr.Builder usage 2025-10-09 00:28:22 +08:00
yusing
bb5f0cdf09 chore(go): upgrade to go1.25.2 2025-10-08 23:38:33 +08:00
yusing
a150f1a628 refactor(config): reduce references to config.GetInstance() 2025-10-07 21:49:00 +08:00
yusing
584db2efce refactor(docker): use atomic.Int instead of plain integer 2025-10-07 21:30:12 +08:00
yusing
c27bc0e129 refactor(docker): simplify docker client initialization in api 2025-10-07 21:26:52 +08:00
yusing
b46b464e65 refactor: add goutils as submodule, remove go.mod from internal/utils 2025-10-07 20:43:41 +08:00
yusing
52ec309f6b chore: go mod tidy 2025-10-05 20:41:37 +08:00
yusing
6051f75145 refactor(favicon): improve cache and error handling 2025-10-05 20:37:27 +08:00
yusing
f4f104d206 refactor: add go-oidc as submodule 2025-10-05 12:38:40 +08:00
yusing
448a2fbd6f chore: update gopsutil 2025-10-05 12:21:05 +08:00
yusing
74224c8e87 refactor(metrics): optimize and simplify system info; add gopsutil as submodule 2025-10-05 12:05:58 +08:00
yusing
ae57edfcb0 refactor(routes): remove unnecessary indirection 2025-10-03 23:28:03 +08:00
yusing
fc23e262d7 chore: update dependencies 2025-10-03 23:26:27 +08:00
yusing
11a3935e0c refactor(serialization): streamline custom validation logic in ValidateWithCustomValidator function 2025-10-03 23:20:14 +08:00
yusing
42e7adbf86 refactor(serialization): small optimization 2025-10-03 23:19:13 +08:00
yusing
1e0c7a15d8 refactor(metrics): optimize memory allocation in period entries
- Replace heap allocation with stack-allocated array in Entries.Get() method.
- Also refactor uptime module to use value types instead of pointer types.
2025-10-03 23:19:12 +08:00
yusing
ba8edb160f refactor(metrics): replace hardcoded time with contants, merge three tickers into one 2025-10-03 23:19:12 +08:00
Yuzerion
4852efcf9c feat: faster serialization (#157)
* refactor: improve deserialization performance

* refactor(serialization): simplify string conversion logic in Convert function

* fix(serialization): default value lookup

* refactor: add comment about concurrency in RegisterDefaultValueFactory

---------

Co-authored-by: yusing <yusing@6uo.me>
2025-10-02 20:30:31 +08:00
yusing
ef40793301 fix: Dockerfile 2025-10-01 19:56:38 +08:00
yusing
80862bcd2e chore(go.mod): mod tidy and exclude problematic v0.4.2 of goutils 2025-09-29 17:58:44 +08:00
yusing
45b16abd68 refactor(health): improve health status JSON unmarshalling 2025-09-29 17:52:03 +08:00
yusing
f411e17d80 feat(json): improve JSON performance with bytedance/sonic 2025-09-29 17:43:34 +08:00
yusing
024100aa8c fix(agent): failed to parse agent proxy config: unexpected end of JSON input 2025-09-28 20:30:02 +08:00
yusing
9d508c5950 feat(health): add random delay to health monitor to prevent thundering herd problem 2025-09-28 02:35:44 +08:00
yusing
2ff5e5c0b6 chore(deps): upgrade dependencies 2025-09-27 14:19:30 +08:00
yusing
2a05c6a630 refactor: move websocket package and some http utils to seperate repo 2025-09-27 14:16:42 +08:00
yusing
6776f20332 refactor: move task, error and testing utils to separte repo; apply gofumpt 2025-09-27 13:41:50 +08:00
yusing
5043ef778f refactor: remove gphttp.ServerError method 2025-09-27 12:47:51 +08:00
yusing
22bcf1201b refactor: move some io, http and string utils to separate repo 2025-09-27 12:46:41 +08:00
yusing
acecd827d6 refactor(synk): consolidate pool statistics tracking and replace GC tracking with dropped tracking 2025-09-27 11:35:38 +08:00
yusing
b2713a4b83 refactor(health): optimize health checking 2025-09-27 11:32:18 +08:00
yusing
e2aeef3a86 refactor(synk): replace runtime weak pointer functions with weak package and simplify buffer handling 2025-09-27 11:24:50 +08:00
yusing
9545482a44 refactor(pprof): remove memory profiling settings and enhance GC logging 2025-09-27 11:24:10 +08:00
yusing
d406b940d9 style: fix some golangci-lint warnings 2025-09-26 23:45:59 +08:00
yusing
dc1175ad69 refactor(http): remove and replace error helpers with standard http.Error 2025-09-26 23:39:00 +08:00
yusing
1409a4e8b9 chore(lint): update linting tool versions in trunk.yaml and enable fieldalignment check 2025-09-26 23:32:48 +08:00
yusing
8ec9752656 refactor(env): move env parsing to separate repo (cont. f7149453d6) 2025-09-26 21:41:57 +08:00
yusing
a932688ca3 chore(deps): upgrade gopsutils 2025-09-26 21:40:28 +08:00
yusing
55c1c918ba refactor: remove / throttle some debug logging 2025-09-26 21:00:35 +08:00
yusing
14e243d245 chore(deps): upgrade dependencies 2025-09-26 20:47:21 +08:00
yusing
f7149453d6 refactor(env): move env parsing to separate repo 2025-09-26 20:41:10 +08:00
yusing
00d137d05c refactor: remove obsolete args.go 2025-09-22 17:18:53 +08:00
yusing
f9affba9fc refactor(modules): replace github.com/yusing/go-proxy with github.com/yusing/godoxy 2025-09-22 16:44:59 +08:00
yusing
6b3bf84148 fix(stream): nil panic when logging error 2025-09-22 10:27:09 +08:00
yusing
62a667758d docs(README): add section for updating and uninstalling system agent 2025-09-21 13:19:27 +08:00
yusing
ddd27156fc refactor(Dockerfile): simplify development Dockerfile 2025-09-21 13:00:43 +08:00
yusing
af8e2d56b2 fix(agent): respect response header timeout and compression settings 2025-09-21 11:58:31 +08:00
yusing
74a215b894 feat(agentproxy): simplify configuration handling and related header management 2025-09-21 11:52:42 +08:00
yusing
ccdc0046fd refactor(agent): update version handling in AgentConfig to use pkg.Version type 2025-09-21 11:51:17 +08:00
yusing
2f7fdc4c51 feat(version): add comparison methods 2025-09-21 11:47:50 +08:00
yusing
de1f4da126 feat(ReverseProxy): add SSL/TLS configuration options and build TLS config method 2025-09-21 10:47:37 +08:00
yusing
a48ccb4423 refactor(server): improve proxy protocol handling 2025-09-19 11:59:34 +08:00
yusing
193fd9a249 docs(config): update config.example.yml with access control and proxy protocol comments 2025-09-19 10:47:35 +08:00
yusing
0bc4c4af77 fix(vscode): update schema URLs in settings.example.json 2025-09-19 10:41:27 +08:00
yusing
5fa1417add fix(server): set default logger in server start options if not provided 2025-09-19 10:31:00 +08:00
yusing
b763c92645 refactor(stream): update TCP and UDP stream listeners to support proxy protocol and ACL wrapping 2025-09-19 10:23:47 +08:00
yusing
09b14a47e9 refactor(config): add SupportProxyProtocol to Entrypoint config 2025-09-18 17:36:19 +08:00
yusing
83a69322fa refactor(server): enhance server start options and support for proxy protocol 2025-09-18 17:34:02 +08:00
yusing
3aba5a1911 refactor(agent): simplify ReverseProxy method by directly modifying request URL 2025-09-17 14:07:06 +08:00
yusing
ca805edfe0 fix(agent): incorrect uri in reverse proxy 2025-09-17 14:03:35 +08:00
yusing
7205bf47de feat(autocert): add DNS resolver options to Config and update provider initialization 2025-09-16 15:43:49 +08:00
yusing
b12999210f feat(docker): add tmpfs caching for Next.js in compose files 2025-09-14 21:24:01 +08:00
yusing
8b8969f033 fix(auth): change userpass to redirect to login and update documentation 2025-09-14 21:11:20 +08:00
yusing
025ebab1ce refactor(api): remove unused ErrorCode type 2025-09-14 20:50:07 +08:00
yusing
ea7bd0d19a fix(docker): update dev docker compose 2025-09-14 18:39:40 +08:00
yusing
f889f5c08d fix(oidc): simplify LoginHandler to always redirect to IdP 2025-09-14 14:33:28 +08:00
yusing
932c20f32d chore(docker): update .gitignore to exclude all .env files and modify dev.compose.yml to include env_file for development 2025-09-14 13:47:02 +08:00
yusing
2a08c55e39 feat(auth): add GET endpoint for logout and update documentation 2025-09-14 13:07:24 +08:00
yusing
93e1d17090 fix(auth): revert userpass PostAuthCallback to respond http 200 2025-09-14 11:19:37 +08:00
yusing
d72d403e2c docs(README): update README files to include new Star History section and replace outdated screenshots
- Added "Star History" section with a chart link.
- Replaced outdated screenshots with new "Routes" and "Servers" images.
- Removed references to deleted screenshots for better clarity.
2025-09-14 01:30:37 +08:00
yusing
b5d70a0592 docs(README): remove WebUI announcement from README 2025-09-14 01:15:36 +08:00
yusing
da71dcf058 fix(docker): simplify and fix logs api 2025-09-14 00:32:47 +08:00
yusing
6b17272347 chore(deps): upgraded dependencies 2025-09-14 00:19:53 +08:00
yusing
98afb02e7f fix(makefile): add dev-logs target and fix frontend lib path 2025-09-14 00:18:24 +08:00
yusing
103fd3b904 docs(swagger): updated swagger json and yaml 2025-09-14 00:17:43 +08:00
yusing
59917f52d7 feat(agent): add runtime configuration to agent env and script 2025-09-14 00:16:47 +08:00
yusing
24fb2e07e6 refactor(api: added all new endpoints and optionally set gin mode 2025-09-14 00:14:28 +08:00
yusing
8f1c02ca72 docs(README): update README files to include container runtime and ForwardAuth support 2025-09-14 00:13:39 +08:00
yusing
e359bc8fd9 fix(swagger): improve non-nullable property handling in Swagger JSON
- Updated set_non_nullable function to ensure required properties are processed correctly.
- Added logic to handle cases where 'required' is not present, maintaining existing functionality for non-nullable properties.
2025-09-14 00:12:35 +08:00
yusing
7b028adaa9 feat(api): add GetContainer endpoint for Docker container retrieval
- Implemented GetContainer function to retrieve container details by ID.
- Added error handling for missing ID, container not found, and client creation failures.
- Enhanced Container struct to support omitempty for state field in JSON responses.
- Updated API documentation with Swagger annotations for the new endpoint.
2025-09-14 00:12:23 +08:00
yusing
f3913e1f6f feat(api): add Docker container management endpoints
- Implemented Restart, Start, and Stop endpoints for managing Docker containers.
- Each endpoint includes request validation, error handling, and appropriate responses.
- Enhanced API documentation with Swagger annotations for all new routes.
2025-09-14 00:11:51 +08:00
yusing
b72f3bde53 refactor(routes): remove old HomepageCategories method 2025-09-14 00:11:32 +08:00
yusing
6077a1d70b feat(metrics): add AllSystemInfo endpoint for real-time system metrics
- Implemented AllSystemInfo function to retrieve and stream system information from agents via WebSocket.
- Introduced AllSystemInfoRequest struct for query parameter binding and validation.
- Enhanced error handling for invalid requests and WebSocket upgrades.
- Utilized goroutines for concurrent data fetching from multiple agents, with retry logic for robustness.
2025-09-14 00:10:55 +08:00
yusing
59cae0967a feat(api): updated docker logs api
- Refactored docker logs endpoint to use container ID directly.
2025-09-14 00:10:37 +08:00
yusing
1e1999b0af feat(agent): add ReverseProxy method and enhance Forward method
- Introduced ReverseProxy method to handle requests to the agent with context, method, and body.
- Updated Forward method to return *http.Response instead of byte data.
- Enhanced SystemInfo function to support querying by agent name in addition to agent address.
2025-09-14 00:09:07 +08:00
yusing
b64725f2f8 refactor(stats): change uptime type from string to int64 2025-09-14 00:07:56 +08:00
yusing
124069aaa4 refactor(metrics): optimize JSON marshaling and aggregation logic
- Updated JSON marshaling in SystemInfo to use quoted keys.
- Refactored aggregation logic to dynamically append entries.
- Adjusted test cases to reflect changes in data structure and ensure accurate serialization.
2025-09-14 00:07:34 +08:00
yusing
d56663d3f9 feat(metrics): enhance metrics handling with interval validation and historical data reconstruction
- Introduced addWithTime method for adding entries with specific timestamps.
- Added validateInterval and fixInterval methods to ensure correct interval settings.
- Updated JSON unmarshalling to respect entry timestamps and validate intervals post-load.
- Refactored poller to use a constant PollInterval for consistency across the codebase.
2025-09-14 00:06:30 +08:00
yusing
d1476edf91 test(middleware): update bypass and rule tests 2025-09-14 00:05:05 +08:00
yusing
4ed6c7c74d fix(rules): add swaggertype annotations for Rule fields 2025-09-14 00:04:14 +08:00
yusing
f31b1b5ed3 refactor(misc): enhance performance on bytes pool, entrypoint, access log and route context handling
- Introduced benchmark tests for Entrypoint and ReverseProxy to evaluate performance.
- Updated Entrypoint's ServeHTTP method to improve route context management.
- Added new test file for entrypoint benchmarks and refined existing tests for route handling.
2025-09-14 00:03:27 +08:00
yusing
e0d25e475c feat(docker): implement container ID to Docker host mapping 2025-09-14 00:01:00 +08:00
yusing
ef65481394 feat(routes): enhance route retrieval with search functionality
- Added SearchRoute method to Config for searching routes by alias.
- Updated Route function to check for excluded routes if the initial lookup fails, returning the found route or a 404 status accordingly.
2025-09-13 23:58:38 +08:00
yusing
1e9303b1ef refactor(docker): update ListContainers function to accept context and improve timeout handling 2025-09-13 23:55:47 +08:00
yusing
2c290a3916 feat(homepage): enhance homepage functionality with new item click tracking, sort methods and category management
- Added ItemClick endpoint to increment item click counts.
- Refactored Categories function to dynamically generate categories based on available items.
- Introduced sorting methods for homepage items and categories.
- Updated item configuration to include visibility, favorite status, and sort orders.
- Improved handling of item URLs and added support for websocket connections in item retrieval.
2025-09-13 23:52:54 +08:00
yusing
58a2dc73dd refactor(docker_watcher): rename docker_events to dockerEvents 2025-09-13 23:50:13 +08:00
yusing
1c080e067d refactor(routes): centralize route existence checking
- Removed All routes pool
2025-09-13 23:49:45 +08:00
yusing
2717dc963a feat(agent): add container runtime support and enhance agent configuration
- Introduced ContainerRuntime field in AgentConfig and AgentEnvConfig.
- Added IterAgents and NumAgents functions for agent pool management.
- Updated agent creation and verification endpoints to handle container runtime.
- Enhanced Docker Compose template to support different container runtimes.
- Added runtime endpoint to retrieve agent runtime information.
2025-09-13 23:44:03 +08:00
yusing
4509622dde fix(reverseproxy): properly suppress http2.errStreamClosed 2025-09-13 23:26:10 +08:00
yusing
60c13a797b refactor(config): parallelize route provider initialization 2025-09-13 23:25:29 +08:00
yusing
5e1da915dc refactor(agents): enhance VerifyNewAgent 2025-09-13 23:24:43 +08:00
yusing
3288624cf2 refactor(auth): change PostAuthCallbackHandler to redirect to home page instead of sending OK status 2025-09-13 23:21:58 +08:00
yusing
190d5e1ece fix(serialization): improve nil handling in mapUnmarshalValidate 2025-09-13 23:20:10 +08:00
yusing
0d2229cca0 refactor(xsync): replace functional map with xsync.Map, remove functional/map 2025-09-13 23:19:20 +08:00
yusing
493c0afdfa feat(websocket): implement Reader for reading binary data from the manager
- Removed Close method from Writer
2025-09-13 22:38:24 +08:00
yusing
99c1922342 feat(websocket): add deduplication support to PeriodicWrite function and introduce DeepEqual utility 2025-09-13 22:37:51 +08:00
yusing
a483e15a20 refactor(middlewares): remove xsync wrapper and replace strutils.SplitLine with bytes.Line 2025-09-13 22:33:21 +08:00
yusing
fbe82c3082 refactor(metrics): optimize JSON marshaling in SystemInfo and Aggregated structures for improved performance and memory management 2025-09-13 22:30:10 +08:00
yusing
24bcc2d2d2 fix(api): correct error formatting 2025-09-13 22:29:48 +08:00
yusing
d8c8cff8b7 fix(metrics): non ws response being encoded twice; simplified response handling 2025-09-13 22:29:17 +08:00
yusing
ef54d336a2 refactor(auth): remove GET method from /auth/callback endpoint and update Swagger documentation 2025-09-13 22:29:08 +08:00
yusing
0a5df1bd7f refactor(metrics): remove pointers from type parameter T to avoid unnecessary indirection 2025-09-13 22:28:57 +08:00
yusing
205928a741 refactor(real_ip): move header check before everything else 2025-09-13 22:23:00 +08:00
yusing
11d18091fd feat(route): add ExcludedReason field 2025-09-13 22:22:50 +08:00
yusing
3be72e5c68 fix(api): conditionally enable auth APIs based on auth configuration 2025-09-13 22:22:37 +08:00
yusing
a9847b6f81 refactor(homepage): improve icon search functionality and add case-insensitive string matching 2025-09-13 22:22:23 +08:00
yusing
04d823d616 feat(serialization): add 'd', 'w',' 'M' units support for time duration
- Updated Makefile to include `-checklinkname=0` in LDFLAGS
2025-09-12 11:41:59 +08:00
yusing
1be2ea44a2 cont: f7de703c15 2025-09-11 22:38:29 +08:00
yusing
978407ae7e chore(agent): upgrade dependencies 2025-09-11 22:19:22 +08:00
yusing
81f8bad77d breaking(dns_providers): drop support for serveral dns providers
- Dropped `namesilo`, `binarylane`,`edgeone`,`baiducloud`,`huaweicloud`,`tencentcloud`,`alidns`

- Introduce support for azion, conohav3, dyndnsfree, nicru, zoneedit
- dns providers dependencies upgrade
2025-09-11 22:14:30 +08:00
yusing
f7de703c15 feat(yaml): extend environment variable substitution to all YAML files
- returns error for unset environment variables
2025-09-11 22:04:13 +08:00
yusing
acf7490991 chore(deps): upgrade go dependencies 2025-09-10 23:46:17 +08:00
yusing
7770ce7025 fix(reverseproxy): improve error handling for HTTP proxy errors and add suppress some HTTP2 and HTTP/3 error codes 2025-09-10 23:20:23 +08:00
yusing
c9c5677b35 fix(notif): use markdown format if invalid 2025-09-10 22:59:11 +08:00
yusing
226ee2e5e5 fix(docker): correct environment variables in rootless setup 2025-09-10 10:01:05 +08:00
yusing
aec937a114 fix(makefile): remove GOARCH 2025-09-10 09:01:54 +08:00
yusing
bab9471bde feat(config): implement environment variable substitution in configuration file reading 2025-09-09 23:33:05 +08:00
yusing
4ebd1dbf32 feat(setup): enhance setup script for rootless Docker support and network configuration 2025-09-09 23:13:38 +08:00
yusing
82a4a61df0 feat(docker): add example configuration files for rootless Docker setup 2025-09-09 22:48:26 +08:00
yusing
9e56ea5db1 fix(docker): add healthcheck label to Dockerfile to prevent self checking 2025-09-09 22:36:26 +08:00
yusing
719682c99f refactor(websocket): enhance connection management by ensuring resources are released on context cancellation 2025-09-09 22:25:02 +08:00
yusing
f81a2b6607 fix(docker): treat containers from $DOCKER_HOST as local 2025-09-09 22:23:50 +08:00
yusing
f47ba0a9b5 feat(docs): update README files to include logo and improve table of contents formatting 2025-09-09 14:40:09 +08:00
yusing
52e949de85 feat: Add development environment configuration with Docker Compose and Dockerfile 2025-09-08 09:15:24 +08:00
yusing
abeb26b556 fix(monitor): prevent nil pointer dereference in Finish method 2025-09-08 09:02:19 +08:00
yusing
23d392d88b fix(route): improve error handling in route.Start method 2025-09-08 09:02:19 +08:00
yusing
d588664bfa fix: prevent panicking on misconfigurations 2025-09-08 09:02:19 +08:00
DeAndre Harris
41ce784a7f feat: Add per-route OIDC client ID and secret support (#145) 2025-09-08 08:16:30 +08:00
yusing
577169d03c refactor(idlewatcher): improve container readiness handling and health check logic
- Simplified the wakeFromHTTP and wakeFromStream methods by removing unnecessary loops and integrating direct checks for container readiness.
- Introduced a waitForReady method to streamline the waiting process for container readiness notifications.
- Enhanced the checkUpdateState method to include timeout detection for container startup.
- Added health check retries and logging for better monitoring of container state transitions.
2025-09-06 07:51:28 +08:00
yusing
b43274e9e6 refactor(idlewatcher): replace map with ordered.Map for deduplicating dependencies 2025-09-06 07:49:50 +08:00
yusing
d83c367e7f chore: update Go version to 1.25.1 in Dockerfile and module files 2025-09-06 07:48:57 +08:00
yusing
d9fbd53870 refactor(api): remove unused Swagger docs.go and clean up dependencies; Makefile update 2025-09-06 07:48:23 +08:00
yusing
7f54f50af8 docs(README): add announcement for new WebUI availability in nightly tag 2025-09-06 07:46:09 +08:00
yusing
8339c42470 refactor(middleware): simplify buffer allocation in themed middleware 2025-09-02 23:28:47 +08:00
yusing
ed39942d65 feat(api): implement caching middleware and allow favicons to be cached 2025-09-02 23:00:22 +08:00
yusing
998488f285 chore(trunk): update CLI and plugin versions, and bump linter dependencies 2025-09-02 22:59:00 +08:00
yusing
aac5016b78 refactor(httpheaders): replace strutils.SplitComma with strings.SplitSeq 2025-09-02 22:58:46 +08:00
yusing
d2b4d3e6e3 feat(auth): enhance cookieDomain function to support additional local domains 2025-09-02 22:58:24 +08:00
yusing
a2d4c468cd refactor(forwardauth): finalize middleware implementation with better headers handling 2025-09-02 22:58:13 +08:00
yusing
c550255458 feat(middledware): middleware-specific logging methods 2025-09-02 22:56:30 +08:00
yusing
6a3e28dfd7 fix(config): handle missing config file and middleware directory gracefully and log a warning 2025-09-02 22:55:43 +08:00
yusing
4513c221d5 refactor(modifyhtml): improved memory manangement and response body handling 2025-09-02 22:55:24 +08:00
yusing
245dba034e feat(io): introduce ReadAllBody and HookCloser for enhanced response handling and resource management 2025-09-02 22:53:54 +08:00
yusing
f39896fe30 refactor(handler): move version API out of auth and remove Swagger routes 2025-09-02 22:50:57 +08:00
yusing
b051987a1c refactor: apply renamed NewBytesPool with GetBytesPool 2025-09-02 22:50:57 +08:00
yusing
c128557c81 chore: update dependencies 2025-09-02 22:50:57 +08:00
yusing
6405325e56 Refactor(websocket): remove unused code 2025-09-02 22:50:57 +08:00
yusing
c3d2a90501 fix(websocket): ensure resources are properly released by closing the manager in PeriodicWrite function 2025-09-02 22:50:57 +08:00
yusing
31d49453a7 feat(pool): introduce BytesPoolWithMemory for optimized memory management and add benchmark for memory usage 2025-09-02 22:50:57 +08:00
yusing
04657420b8 refactor(websocket): enable compression for WebSocket connections to improve performance, removed buffer size to use HTTP buffer 2025-09-02 22:50:57 +08:00
FrozenFrog
2f0b8b6c09 Add TinyAuth forward-auth middleware implementation (#143)
* feat: add tinyauth middleware

---------

Co-authored-by: yusing <yusing@6uo.me>
2025-09-02 17:43:34 +08:00
yusing
5e15fd4bbe fix(fileserver): correct middleware handler to avoid self recursion 2025-08-19 22:26:38 +08:00
yusing
a5022e31a2 fix(auth,oidc): added GET method /auth/callback endpoint to fix OIDC 404 and update documentation accordingly 2025-08-19 22:26:30 +08:00
yusing
a057f0e956 fix(homepage): incorrect url
- fixed url being overridden
- fixed sub-subdomain being stripped
- fixed empty url for routes with FQDN aliases
2025-08-19 21:01:04 +08:00
yusing
dfe0014609 fix(route): handle subdomain removal in URL construction for homepage items 2025-08-18 20:46:59 +08:00
yusing
dfc2d5e35c feat(handler): add SkipOriginCheckMiddleware and configuration option to bypass origin checks 2025-08-17 20:46:26 +08:00
yusing
d3bfb2488b refactor(handler): simplifying code applying auth middleware 2025-08-17 20:41:46 +08:00
yusing
baf5b5eff1 fix(handler): fixed health check import and corrected code for new metrics api handler 2025-08-17 20:24:42 +08:00
yusing
1c7e3e42f8 refactor(websocket): simplify NewManagerWithUpgrade function to use context for custom upgrader 2025-08-17 20:23:57 +08:00
yusing
beb1913285 fix(agent): remove unused dependencies 2025-08-17 20:08:04 +08:00
yusing
e14d6baedb fix(agent): change request binding from query to JSON and update hostport formatting 2025-08-17 19:45:08 +08:00
yusing
cfb37d5bd0 refactor(agent): update AgentConfig struct to use exported fields and improve JSON serialization 2025-08-17 19:08:23 +08:00
yusing
f53d384533 refactor(agent): rename requests.go to http_requests.go 2025-08-17 19:07:50 +08:00
yusing
8360aa59d1 fix(websocket): allow localhost origin in WebSocket manager 2025-08-17 17:36:07 +08:00
yusing
6ec1016f29 feat(websocket): implement CORS support in WebSocket manager for origin validation 2025-08-17 17:14:10 +08:00
yusing
35b0dcb418 chore(todo): remove completed TODOs 2025-08-17 17:10:21 +08:00
yusing
353f818b41 fix(icons): update selfhstIcons URL to point to the raw GitHub content 2025-08-17 16:39:11 +08:00
yusing
b58cabf998 feat(makefile): add support for generating Swagger markdown and update output paths for API types 2025-08-17 16:31:18 +08:00
yusing
231c0c7665 feat(api): enhance Swagger documentation with detailed API info and update request parameters for uptime 2025-08-17 16:31:08 +08:00
yusing
9931c10fa6 chore(trunk): update dependencies and versions for CLI, plugins, runtimes, and linters 2025-08-17 15:50:27 +08:00
yusing
d56a6bc19d chore(golangci): disabled new linters godot, noinlineerr, tagalign, and wsl_v5 2025-08-17 15:50:16 +08:00
yusing
e0a110cad3 fix(websocket): ensure proper connection closure and stop ping check ticker in Close method 2025-08-17 14:53:25 +08:00
yusing
d1eb3470b5 fix(api): renew endpoint uses get instead of post, correct @Accept docs 2025-08-17 14:52:56 +08:00
yusing
e52c86e0b7 fix(api): missing health field for route in docs and generated api code 2025-08-17 14:33:53 +08:00
yusing
c19d82c876 feat(autocert): add EAB configuration support and corresponding tests 2025-08-17 11:45:26 +08:00
yusing
d2f317b44d docs(api): update swagger docs 2025-08-17 01:50:38 +08:00
yusing
ba9cb083cf fix(api): ErrorResponse no longer includes ANSI escape sequeneces 2025-08-17 01:50:26 +08:00
yusing
06669534cd fix(api): validate API response 2025-08-17 01:49:49 +08:00
yusing
07d6f36159 feat(docker): include full labels, mountpoints and image details 2025-08-17 01:47:58 +08:00
yusing
55018c8ab6 fix(route): update HealthJSON field to exclude from JSON serialization 2025-08-17 00:40:57 +08:00
yusing
0862920324 refactor(concurrency): simplify some waitgroup usage, remove extra wg.Done in wg.Go left in 11af9d1 in io.go 2025-08-17 00:23:54 +08:00
yusing
b32750d545 fix(websocket): websocket concurrent write during ping pong 2025-08-17 00:17:14 +08:00
yusing
a836920eca refactor(notif): replace RangeAllParalel with for loop and WaitGroup in dispatch 2025-08-16 23:58:59 +08:00
yusing
6b89cd9106 fix(api): added missing "x-id"s 2025-08-16 23:24:03 +08:00
yusing
11af9d107a refactor(concurrency): replaced manual WaitGroup management with new wg.Go() and removed redundant code. 2025-08-16 23:14:40 +08:00
yusing
7a9b8b3fb9 fix(api): corrected json marshaling for HealthInfo 2025-08-16 23:05:29 +08:00
yusing
90efa36193 refactor(api): better type safety, split homepage override apis, fixed favicon api docs 2025-08-16 19:57:14 +08:00
yusing
1e78a0a0a0 fix(api): wrong "accept" type in file set and validate API 2025-08-16 17:31:11 +08:00
yusing
52324fbef2 fix(tests): updated docker types in tests 2025-08-16 13:09:08 +08:00
yusing
8b40baa49f chore(dependencies): upgrade Go version and update related dependencies
- Updated Go version from 1.24.5 to 1.25.0 across multiple modules.
- Incremented versions for go-acme/lego from v4.25.1 to v4.25.2 and yusing/go-proxy from v0.16.1 to v0.16.2.
- Updated indirect dependencies including cloud.google.com/go/auth, golang.org/x/net, and others to their latest versions.
- Cleaned up and organized go.mod and go.sum files.
2025-08-16 13:08:39 +08:00
yusing
35a3e3fef6 refactor(api): restructured API for type safety, maintainability and docs generation
- These changes makes the API incombatible with previous versions
- Added new types for error handling, success responses, and health checks.
- Updated health check logic to utilize the new types for better clarity and structure.
- Refactored existing handlers to improve response consistency and error handling.
- Updated Makefile to include a new target for generating API types from Swagger.
- Updated "new agent" API to respond an encrypted cert pair
2025-08-16 13:04:05 +08:00
yusing
fce9ce21c9 chore(dependencies): update go-acme/lego, quic-go, and other dependencies
- Upgraded go-acme/lego from v4.24.0 to v4.25.1.
- Updated quic-go from v0.53.0 to v0.54.0.
- Incremented versions for yusing/go-proxy and related dependencies.
- Updated aws/smithy-go from v1.22.4 to v1.22.5 and baidubce/bce-sdk-go from v0.9.235 to v0.9.236.
- Updated barcode library from v1.0.2 to v1.1.0.
- Updated google.golang.org/api from v0.242.0 to v0.243.0 and grpc from v1.73.0 to v1.74.2.
- Cleaned up unused dependencies and updated indirect dependencies.
2025-07-27 21:49:11 +08:00
yusing
475e697490 fix(healthcheck): fix spamming notifications for unhealthy services
- Added a new `downNotificationSent` flag to track if a service down notification has been sent.
- Reset the notification state when a service comes back up.
- Updated logic to ensure notifications are sent only once after reaching the configured retry threshold for consecutive failures.
2025-07-27 21:47:29 +08:00
yusing
68ac4f952d fix(autocert): wrong path for last failure file 2025-07-21 09:39:17 +08:00
yusing
a2e6688056 chore: upgrade dependencies 2025-07-20 13:29:03 +08:00
yusing
e02cacdf2a feat(middleware): add themed middleware with customizable themes and styles
- Introduced a new themed middleware that allows for dynamic theme application.
- Added support for multiple themes: dark, dark-grey, solarized-dark, and custom CSS.
- Included CSS files for each theme and a font CSS template for font customization.
- Updated middleware registry to include the new themed middleware.
2025-07-20 12:59:25 +08:00
yusing
46c7ee4d84 feat(middleware): optimize HTML modification with byte pool 2025-07-20 12:47:32 +08:00
yusing
f39513483b refactor(middleware): improve common options handling in apply method 2025-07-20 12:46:30 +08:00
yusing
731121595c feat(autocert): enhance failure handling and cooldown logic
- Added last failure tracking to the Provider struct to manage certificate renewal failures.
- Implemented methods to get, update, and clear the last failure timestamp.
- Introduced cooldown durations to prevent immediate retries after failures.
- Updated ObtainCert and ScheduleRenewal methods to utilize the new failure handling logic.
2025-07-20 12:42:52 +08:00
yusing
8025af6067 feat(healthcheck): add retries before notifying (default: 3 times)
- Introduced NotifyFunc type for customizable notification handling in tests.
- Added Retries field to HealthCheckConfig for controlling notification thresholds.
- Implemented tests for notification behavior under various health check scenarios.
2025-07-20 12:03:45 +08:00
yusing
47910774dd fix(homepage): missing homepage.url option 2025-07-13 15:47:26 +08:00
yusing
b6bfd19cc2 fix(middleware): set Accept-Encoding to "" in modify_html middleware 2025-07-13 15:42:39 +08:00
yusing
e3b53a548d refactor(misc): small code refactor 2025-07-13 14:33:58 +08:00
yusing
a954ac8946 feat(middleware): introduct modify_html middleware 2025-07-13 14:01:36 +08:00
yusing
814ff33352 fix(tests): refactor and fix login in middleware test utils 2025-07-13 13:33:48 +08:00
yusing
b1d5c4b091 chore: upgrade go to 1.24.5 2025-07-13 13:10:19 +08:00
yusing
72dc783e23 fix(icons): update selfh.st icon json url 2025-07-01 02:14:27 +08:00
yusing
1c95bbba6e fix: Makefile 2025-07-01 02:14:12 +08:00
yusing
0c552c9cea chore: upgrade dependenocies 2025-07-01 02:14:06 +08:00
yusing
5631b1540a fix(notif): refactor and fix json payload validation and handling 2025-06-28 14:46:54 +08:00
yusing
24f949f053 fix(notif): stack overflow due to self recursion 2025-06-28 14:44:06 +08:00
yusing
9d712b91ff fix(rules): routes without default rule panics 2025-06-16 21:36:20 +08:00
yusing
4189ffa1db fix(docker): wildcard properties not working with FQDN aliases 2025-06-16 21:23:23 +08:00
yusing
e906b358fa tweak(entrypoint): introducing ResponseRecorder specialized for access logger for better throughput 2025-06-15 17:42:38 +08:00
yusing
f179de9231 fix(setup): update DNS provider docs link in setup script 2025-06-15 10:00:53 +08:00
yusing
1d546624de fix(serialization): call of reflect.Value.IsNil on string Value 2025-06-14 22:12:24 +08:00
yusing
ecc9d306d1 refactor(agent): move agent pool to agent package, rename route.Agent() to route.GetAgent() (cont. 7d17a01) 2025-06-14 22:04:10 +08:00
yusing
5ce1c7865e feat(agent): allow specifying agent for routes in route files 2025-06-14 20:05:11 +08:00
yusing
7d17a01de1 refactor(agent): move agent pool to agent package, rename route.Agent() to route.GetAgent() 2025-06-14 20:04:39 +08:00
yusing
cabb840a91 tweak(docker): add hint when specified network not found 2025-06-14 19:32:36 +08:00
yusing
4825f768f3 feat(docker): allow specifying docker network, handle error when no network available 2025-06-14 17:08:07 +08:00
yusing
5fdb023188 feat(docker): add network field to container info 2025-06-14 10:05:45 +08:00
yusing
4abf61a421 refactor(notif): enhance retry mechanism with exponential backoff and jitter; replace retry channel with a set for managing retry messages 2025-06-14 09:31:09 +08:00
yusing
96b7c3fcec chore: upgrade dependenocies 2025-06-13 23:06:34 +08:00
yusing
f8c57d930f fix(docker): wildcard labels not applying properly for YAML style values and alias without labels 2025-06-13 23:02:25 +08:00
yusing
880d66c75e docs: update links in config.example.yml to point to the new documentation site 2025-06-12 21:34:50 +08:00
yusing
4649c8d479 chore: update .gitignore to include .cursor directory 2025-06-12 21:08:00 +08:00
DarinDev1000
20021b3cae add GODOXY_API_JWT_SECURE=true to .env 2025-06-12 21:08:00 +08:00
yusing
cfa9201f82 fix(shutdown): change gracefulShutdown to call root.Finish directly instead of in a goroutine 2025-06-09 22:20:49 +08:00
yusing
b5328fe5e7 feat(idlesleep): support idlesleep for stream routes, rewritten and fixed stream implementation 2025-06-09 22:20:26 +08:00
yusing
25fbcc4ab9 fix(label): expand wildcard labels before unmarshaling and add corresponding test 2025-06-09 20:46:39 +08:00
yusing
421aaecba4 refactor: rename net/types to nettypes 2025-06-08 17:59:48 +08:00
yusing
01773976d1 fix(compose): rename container from godoxy to godoxy-proxy to prevent route conflict 2025-06-08 17:55:17 +08:00
yusing
2263d6063e fix(favicon): add GetFavIconFromAlias function and update favicon handling in idlewatcher 2025-06-08 15:13:38 +08:00
yusing
cfe0f6bb70 fix(route): remove x-properties routes during loading 2025-06-07 22:28:44 +08:00
yusing
a90d2b90d1 fix(systeminfo): safer time interval calculations and fix divided by zero crash 2025-06-07 19:42:53 +08:00
yusing
af9629424e fix(route): incorrect url for loadbalanced route 2025-06-07 19:13:31 +08:00
yusing
ee6cf29bc1 chore: upgrade dependenocies 2025-06-07 19:05:04 +08:00
yusing
c4a780e061 chore: updated to go1.24.4 2025-06-07 18:54:23 +08:00
yusing
09c244ef3c fix(route): add mutex lock for load balancer updates to prevent race conditions 2025-06-05 18:53:11 +08:00
yusing
bd0fe36c53 fix(idlewatcher): should not print idle_timeout fields on dependencies 2025-06-05 18:49:11 +08:00
yusing
d240da4393 fix(route): incorrect health status for idlewatcher dependencies 2025-06-05 18:40:40 +08:00
yusing
9470a14fe8 refactor(route): unify common fields into routes.go 2025-06-05 18:25:15 +08:00
yusing
d3568d9c35 fix: conflict error on load-balanced and excluded routes 2025-06-05 01:16:53 +08:00
yusing
44ef351840 fix(panic): Route.ProviderName before provider is set 2025-06-05 00:13:29 +08:00
yusing
a39d527fc1 feat(idlesleep): support container dependencies, including custom and docker depends_on, code refactor 2025-06-04 23:26:38 +08:00
yusing
22ab043e06 refactor(route): improve route handling 2025-06-04 23:17:41 +08:00
yusing
b670cdbd49 refactor(provider): improve route handling 2025-06-04 23:15:56 +08:00
yusing
45e34d691a tweak(healthcheck): allow custom base context 2025-06-04 23:14:46 +08:00
yusing
e82480a639 refactor: rename route/provider/types to provider 2025-06-04 23:13:42 +08:00
yusing
e39407886d fix: improved docker image parsing 2025-06-04 23:00:53 +08:00
yusing
3135e377a9 tweak(route): start routes in parallel 2025-06-03 23:32:59 +08:00
yusing
bdb3343a7c fix(healthcheck): handle cases for zero port 2025-06-03 22:56:00 +08:00
yusing
b411c6d504 feat(route): add api info for whether route is excluded 2025-06-03 22:48:35 +08:00
yusing
966a59b5c9 tweak: improve port and scheme detection 2025-06-03 22:41:31 +08:00
yusing
58db228e25 refactor(query): replace anonymous functions with sequence and for loop 2025-06-03 20:51:00 +08:00
yusing
e737737415 fix(idlewatcher): wake time outs before actual timeout 2025-06-02 23:26:47 +08:00
yusing
9087c4f195 feat(healthcheck): allow health checking for excluded routes 2025-06-02 23:19:30 +08:00
yusing
4705989f4b fix(websocket): 403 forbidden agent metrics 2025-06-01 00:31:56 +08:00
yusing
cb506120dd fix(serialization): remove debug stack from error message in mapUnmarshalValidate 2025-05-31 22:51:30 +08:00
yusing
88aaf956e5 fix(ci): fix wrong indentation for commented out gha cache in Docker workflow 2025-05-31 22:42:44 +08:00
yusing
ecfd018b0b fix(websocket): http: superfluous response.WriteHeader on websocket initiation failure 2025-05-31 22:29:02 +08:00
yusing
54bf84dcba fix(websocket): fix local address checks 2025-05-31 13:55:29 +08:00
yusing
57200bc1e9 refactor(io): enhance HTTP flusher handling 2025-05-31 13:54:50 +08:00
yusing
6f9bb410f5 fix(agent): use godoxy-to-agent latency for health check 2025-05-31 07:39:01 +08:00
yusing
e62e667b49 ci: remove gha cache in Docker workflow 2025-05-31 07:15:39 +08:00
yusing
abe81541db fix(login): login not working with user password 2025-05-31 07:11:19 +08:00
yusing
9e5d33714c chore: update dependencies 2025-05-30 20:50:31 +08:00
yusing
93a81fd558 fix(task): fix task tests 2025-05-29 20:42:36 +08:00
yusing
72923b8cfa refactor: remove Tracer from middleware implementations and related debugging functionality 2025-05-29 20:27:25 +08:00
yusing
24ba4c2a46 fix(oidc): update login handler to set redirect header for frontend requests 2025-05-29 20:08:52 +08:00
yusing
ed07bf42ce fix(routes): accidentally finished all routes when one route changed 2025-05-29 19:22:42 +08:00
yusing
371e756307 refactor: small refactor on maxmind code 2025-05-28 22:14:10 +08:00
yusing
32d8292b17 fix(oidc): apply rate limit to fix oocasional oauth state error due to race condition 2025-05-28 22:12:41 +08:00
yusing
717fd0e58c feat(net): add option to disable compression 2025-05-28 22:09:25 +08:00
yusing
2628d9e8a8 fix(task): refactor task module and fix reload stuck/error, fix some logic 2025-05-28 22:07:13 +08:00
yusing
c90795e614 fix(notification): unknown format: &{} 2025-05-26 20:03:17 +08:00
yusing
4a6bed7728 refactor: headers handling 2025-05-26 19:57:39 +08:00
yusing
216c03c5ff fix(task): revert to context based approach and fix tasks stuck, improve error handling 2025-05-26 00:32:59 +08:00
yusing
2e9f113224 fix(autocert): update test server configuration to use TLS with custom CA and add IPAddresses field 2025-05-25 18:50:18 +08:00
yusing
9d58977fa6 feat(autocert): add CACerts field to autocert Config for custom CA 2025-05-25 17:33:13 +08:00
yusing
8469b6406c tweak: consolidate bytes pool management and enhance CopyClose functionality for improved performance 2025-05-25 16:20:12 +08:00
yusing
b163771956 refactor: improve task management with xsync for concurrent access and enhance callback and subtasks handling as well as memory allocation 2025-05-25 15:13:30 +08:00
yusing
c1221e61d4 refactor: improve task management with xsync for concurrent access and enhance callback and subtasks handling as well as memory allocation 2025-05-25 15:01:44 +08:00
Yuzerion
4a8bd48ad5 fix: optimize memory usage, fix agent and code refactor (#118)
* refactor: simplify io code and make utils module independent

* fix(docker): agent and socket-proxy docker event flushing with modified reverse proxy handler

* refactor: remove unused code

* refactor: remove the use of logging module in most code

* refactor: streamline domain mismatch check in certState function

* tweak: use ecdsa p-256 for autocert

* fix(tests): update health check tests for invalid host and add case for port in host

* feat(acme): custom acme directory

* refactor: code refactor and improved context and error handling

* tweak: optimize memory usage under load

* fix(oidc): restore old user matching behavior

* docs: add ChatGPT assistant to README

---------

Co-authored-by: yusing <yusing@6uo.me>
2025-05-25 09:45:57 +08:00
yusing
ade93d49a3 docs: add ChatGPT assistant to README 2025-05-25 09:36:22 +08:00
yusing
82ee75daab fix(oidc): restore old user matching behavior 2025-05-25 09:14:54 +08:00
yusing
f0ab14cb1e tweak: optimize memory usage under load 2025-05-24 22:05:42 +08:00
yusing
5b7c392297 refactor: code refactor and improved context and error handling 2025-05-24 10:02:24 +08:00
yusing
1f1ae38e4d feat(acme): custom acme directory 2025-05-24 09:41:19 +08:00
yusing
22d44a6bb0 fix(tests): update health check tests for invalid host and add case for port in host 2025-05-24 08:42:10 +08:00
yusing
6a5cd1266b tweak: use ecdsa p-256 for autocert 2025-05-24 07:29:11 +08:00
yusing
1cf18657b6 refactor: streamline domain mismatch check in certState function 2025-05-24 07:20:58 +08:00
yusing
63c4bdc73d refactor: remove the use of logging module in most code 2025-05-23 23:28:33 +08:00
yusing
20a1649275 tweak: disable bytes pool for socket-proxy and agent 2025-05-23 23:01:27 +08:00
yusing
0f3b8e68ce refactor: remove unused code 2025-05-23 22:46:35 +08:00
yusing
5a3e3f19c7 fix(imports): due to previous refactor 2025-05-23 22:24:22 +08:00
yusing
df193a42fc fix(docker): agent and socket-proxy docker events not flushing with modified reverse proxy handler 2025-05-23 22:20:32 +08:00
yusing
f1e204f7fd refactor: simplify io code and make utils module independent 2025-05-23 22:19:14 +08:00
yusing
ff08c40403 fix(agent): docker handler 2025-05-23 21:12:46 +08:00
yusing
d8266f779f chore: update dependencies 2025-05-23 21:08:11 +08:00
yusing
9711867fbe tweak: optimize memory usage and allocation 2025-05-22 22:43:11 +08:00
yusing
fc8592ab45 fix(systeminfo): divide by zero on disk I/O speed calculation 2025-05-22 22:41:39 +08:00
yusing
3dbab118af refactor: docker event watcher 2025-05-19 23:15:47 +08:00
yusing
1f50ee7f2f tweak: replace coder/websocket with gorilla/websocket 2025-05-19 23:15:11 +08:00
yusing
cee6eaecff fix(healthcheck): retry on error and stop afte 5 trials 2025-05-18 22:16:12 +08:00
yusing
67a6b89ea5 fix(agent): improve install script command handling and add agent running check 2025-05-17 08:51:22 +08:00
yusing
78be9b1c71 fix(agent): update script url 2025-05-17 08:39:03 +08:00
yusing
26856b612a docs: replace broken/old links and update README_CHT key features section 2025-05-17 08:38:40 +08:00
yusing
36ceba3ae7 security: switch from RSA-2048 to ECDSA-P256 for agent certs and update certificate config and handling 2025-05-17 08:29:01 +08:00
yusing
f45f3fba79 refactor: logic refactor for setting xff header 2025-05-16 20:14:03 +08:00
yusing
4bbff323e3 chore: update dependencies 2025-05-16 07:19:33 +08:00
yusing
2e68baa93e tweak: optimize memory allocation and increase throughput 2025-05-16 07:15:45 +08:00
yusing
a162371ec5 feat: parallelize system info collection and refactor code 2025-05-14 21:38:28 +08:00
yusing
8f9c76daa5 chore: update dependencies 2025-05-14 21:00:53 +08:00
yusing
8b3e058885 fix: error formatting 2025-05-14 20:34:41 +08:00
yusing
023cbc81bc ci: update Docker CI workflows to exclude tags for socket-proxy and improve caching 2025-05-14 13:50:12 +08:00
yusing
b490e8c475 fix(acl): maxmind error even if configured, refactor 2025-05-14 13:44:43 +08:00
yusing
8e27886235 fix: incorrect unmarshal behavior for pointer primitives 2025-05-14 12:20:52 +08:00
yusing
7435b8e485 tests: add test for acl matchers 2025-05-13 20:11:16 +08:00
yusing
21724c037f fix: error formatting 2025-05-13 20:11:03 +08:00
yusing
44b4cff35e fix: acl matcher parsing, refactor 2025-05-13 19:40:43 +08:00
yusing
1e24765b17 fix: nil when printing error in edge cases 2025-05-13 19:40:04 +08:00
yusing
a1f2a84a16 fix(oidc): multiple state cookies being sent to frontend causing invalid oauth state 2025-05-12 14:19:18 +08:00
yusing
453262832a security: disallow tls1.0/1.1 2025-05-12 12:22:52 +08:00
yusing
99e975145c tweak default docker compose 2025-05-11 23:40:38 +08:00
yusing
e300170c51 fix: route autoconfiguration 2025-05-11 21:38:43 +08:00
yusing
1382137f20 tweak(cicd): attempt on better build caching 2025-05-11 07:00:34 +08:00
yusing
54d7508f5d style: gofmt and fix golangcl-ilint 2025-05-11 06:34:35 +08:00
yusing
71ca8c738e fix: middleware bypass 2025-05-11 06:33:22 +08:00
yusing
f1eefde964 fix(oidc): add timeout to oidc initialization 2025-05-11 05:58:18 +08:00
yusing
84e7a6591e fix(agent): health check logic 2025-05-11 00:05:01 +08:00
yusing
30c76cfc5f refactor: health check logic 2025-05-10 22:55:20 +08:00
yusing
a8ba42e360 fix: routes iter missing stream 2025-05-10 21:31:38 +08:00
yusing
cd291556fc fix(oid); redirect 2025-05-10 21:25:27 +08:00
yusing
0d41809630 fix(middleware): move bypass after finalize 2025-05-10 21:19:03 +08:00
yusing
53acf75c04 fix(homepage): item not hiding after config override 2025-05-10 18:14:10 +08:00
yusing
cf30fe6cfc feat(homepage): custom app sort order 2025-05-10 17:36:51 +08:00
yusing
55bbcae911 feat(api): refined list route api 2025-05-10 15:22:30 +08:00
yusing
b30c0d7dc0 feat(api): include agent version in response 2025-05-10 13:37:51 +08:00
yusing
198ae2cd02 refactor(api): restructure existing routes and remove unused debug endpoints and command line arguments 2025-05-10 13:12:41 +08:00
yusing
26938eb6ed feat(api): add new route for listing routes by provider 2025-05-10 12:58:37 +08:00
yusing
48823a860f fix(docker-compose): remove default proxy.exclude 2025-05-10 12:28:08 +08:00
yusing
985ff0a74d fix(deps): use dummy version for go-proxy module 2025-05-10 12:27:48 +08:00
yusing
43b493c60e fix(agent): docker handler 2025-05-10 12:26:50 +08:00
yusing
e0e0fab127 fix(agent): disable socket proxy by default 2025-05-10 12:26:06 +08:00
yusing
fc0dbd940c fix: Dockerfile caching 2025-05-10 12:12:39 +08:00
yusing
0208e6286f fix: docker socket handler 2025-05-10 11:24:28 +08:00
yusing
2c0b68c8c2 fix(build): Dockerfile 2025-05-10 10:50:26 +08:00
yusing
c05059765d style: coed cleanup and fix styling 2025-05-10 10:46:31 +08:00
yusing
a06787593c style: update golangci-lint and trunk configurations 2025-05-10 10:46:03 +08:00
yusing
8fe94d6d14 feat(socket-proxy): implement Docker socket proxy and related configurations
- Updated Dockerfile and Makefile for socket-proxy build.
- Modified go.mod to include necessary dependencies.
- Updated CI workflows for socket-proxy integration.
- Better module isolation
- Code refactor
2025-05-10 09:47:03 +08:00
yusing
4ddfb48b9d fix(setup): skipped autocert configuration 2025-05-09 14:31:32 +08:00
yusing
31dc112591 fix(middleware): middleware chain error handling
- Removed unnecessary initialization of befores and modResps in middlewareChain.
- modifyResponse should return immediately on error.
2025-05-09 12:29:50 +08:00
yusing
6797897814 fix(healthcheck): ensure detail is included on error 2025-05-09 12:26:31 +08:00
yusing
99eccd0b95 fix(monitor): reduce health check result initialization 2025-05-09 12:14:34 +08:00
yusing
0387739b94 fix(homepage): prioritize container name and alias as display name 2025-05-09 11:42:33 +08:00
yusing
ead27c72f1 fix(agent): typo for /distribution endpoint and update related configurations 2025-05-09 11:37:41 +08:00
yusing
455a85e6a0 feat(docker): add Docker socket proxy support and related configurations
- Introduced Docker socket proxy handling in the agent.
- Added environment variables for Docker socket configuration.
- Implemented new Docker handler with endpoint permissions based on environment settings.
- Added tests for Docker handler functionality.
- Updated go.mod to include gorilla/mux for routing.
2025-05-08 20:59:32 +08:00
yusing
8424fd9f1a chore: upgrade dependencies 2025-05-08 17:57:08 +08:00
yusing
75ee0e63bd fix(middleware): fix route bypass matching
- replace upstream headers approach with context value
2025-05-08 17:49:36 +08:00
yusing
1ce607029a Merge branch 'main' into dev 2025-05-07 23:27:02 +08:00
yusing
1e80ad2a44 fix(docker): host network_mode port selection 2025-05-07 23:26:51 +08:00
yusing
4daefa19d1 build: update Go version to 1.24.3 in Dockerfile and go.mod 2025-05-07 23:12:55 +08:00
yusing
491231e439 Merge branch 'main' into dev 2025-05-06 20:27:37 +08:00
yusing
c90ec8caa1 feat(container): add UpdatePorts method and support for host network mode 2025-05-06 20:27:25 +08:00
yusing
9eb674029e tweak(logging): rename write count variable and adjust buffer check interval 2025-05-05 20:59:43 +08:00
yusing
e41c6530ab chore: update dependencies and Makefile 2025-05-05 20:41:25 +08:00
yusing
afd35c183d test: fix failed tests after code changes 2025-05-05 20:41:25 +08:00
yusing
f190483b4e feat(rules.on): support route directive 2025-05-05 20:41:25 +08:00
yusing
7b0ed09772 fix(error): self referencing 2025-05-05 20:41:25 +08:00
yusing
4415bffc35 feat(rules.on): support & as logical AND 2025-05-05 20:41:25 +08:00
yusing
ddab2766b4 feat(middlewares): middleware bypass rules 2025-05-05 20:41:25 +08:00
yusing
ef95682116 feat(rules): compile path rules directly to glob 2025-05-05 20:41:25 +08:00
yusing
dd65a8d04b style: replace for loops with slices.Contains 2025-05-05 20:41:25 +08:00
yusing
aa23b5b595 test: add unit tests for FormatByteSize function 2025-05-05 20:41:25 +08:00
yusing
c55c6c84bc feat(health): add health check detail to health api 2025-05-05 20:41:25 +08:00
yusing
a45e5e17db chore: update dependencies and Makefile 2025-05-05 20:39:05 +08:00
yusing
b8c0961de3 test: fix failed tests after code changes 2025-05-05 20:05:47 +08:00
yusing
62d3d200e6 feat(rules.on): support route directive 2025-05-05 19:34:24 +08:00
yusing
bf32cafd90 fix(error): self referencing 2025-05-05 19:32:55 +08:00
yusing
1c182b5a7d feat(rules.on): support & as logical AND 2025-05-05 19:15:35 +08:00
yusing
ad60f377ba feat(middlewares): middleware bypass rules 2025-05-05 18:01:07 +08:00
yusing
75db09b1f3 feat(rules): compile path rules directly to glob 2025-05-05 14:42:55 +08:00
yusing
6dd849f480 style: replace for loops with slices.Contains 2025-05-05 13:36:08 +08:00
yusing
e2ae29795d test: add unit tests for FormatByteSize function 2025-05-05 13:27:51 +08:00
vSLY
92fa0f8168 Update README.md (#104)
Clarify setup process
2025-05-05 13:27:25 +08:00
yusing
b090598b68 feat(health): add health check detail to health api 2025-05-05 13:27:00 +08:00
vSLY
2cec88d3ce Update README.md (#104)
Clarify setup process
2025-05-05 00:45:29 +08:00
yusing
4df31263b5 fix(sensor): ignore "no data available" error 2025-05-05 00:33:43 +08:00
yusing
9eae809690 chore: move middleware trace to trace level 2025-05-04 23:58:47 +08:00
yusing
f1ba554a24 fix(notif): http 204 treated as error 2025-05-04 23:54:16 +08:00
yusing
f9a8aede20 feat: hCaptcha middleware 2025-05-04 17:21:12 +08:00
yusing
e275ee634c fix(http): content type detection 2025-05-04 16:18:46 +08:00
yusing
797d88772f fix: timeout waiting for maxmind db on shutdown 2025-05-04 16:04:17 +08:00
yusing
8ef8015a7f feat: improved icon and category lookup mechanism 2025-05-04 09:37:15 +08:00
yusing
5fce4b445b fix: error formatting 2025-05-04 07:12:28 +08:00
yusing
7552a706a7 chore: deps upgrade 2025-05-04 06:33:08 +08:00
yusing
e1bc6d1f44 fix: nil panic when formatting error 2025-05-04 06:33:00 +08:00
yusing
56850a9580 fix: update http error handling 2025-05-04 02:34:34 +08:00
yusing
5f780f4902 feat: improved port selection 2025-05-04 01:32:01 +08:00
yusing
ccb4639f43 breaking: move maxmind config to config.providers
- moved maxmind to separate module
- code refactored
- simplified test
2025-05-03 20:58:09 +08:00
yusing
ac1470d81d fix: remove incorrect comment from getOAuthRefreshToken function 2025-05-03 19:38:02 +08:00
yusing
efaabfa63a fix: access log field names 2025-05-03 19:32:02 +08:00
yusing
9043cf25c5 feat: push notification for config errors 2025-05-03 17:41:50 +08:00
yusing
98e90d7a0b refactor: improve error handling and response formatting in API 2025-05-03 17:41:10 +08:00
yusing
82c829de18 feat: notifications retry mechanism and improved error formatting 2025-05-03 14:30:40 +08:00
yusing
2fe4fef779 fix(oidc): enforce https redirection to prevent errors 2025-05-03 04:56:32 +08:00
yusing
91302ceed7 feat: simplify and optimize system info 2025-05-02 10:31:04 +08:00
yusing
7fa7b55b18 feat: notify for cert renewal result 2025-05-02 05:55:34 +08:00
yusing
69ee8495d8 refactor: notifications 2025-05-02 05:51:15 +08:00
yusing
28d9a72908 fix: icon not exists error on loading icon cache 2025-05-02 05:19:16 +08:00
yusing
770c698332 fix(idlewatcher): "unexpected container action" after unexpected EOF 2025-05-02 04:56:27 +08:00
yusing
cd4c843025 feat: light/dark variant icons and selfh.st tag as default category
- code refactor
- reduce memory usage
2025-05-02 03:38:50 +08:00
yusing
f0cf89060b chore(logging): move "adjusted buffer size" to debug level 2025-05-01 23:42:45 +08:00
yusing
f79a15bac6 update license 2025-05-01 07:29:48 +08:00
yusing
2b4a70a550 fix(docker): fixed retry mechanism 2025-05-01 06:48:38 +08:00
yusing
f06741428c fix(idlewatcher): log error and retry instead instead of stopping 2025-05-01 06:46:24 +08:00
yusing
16e6e72454 feat(access_log): dynamic buffer size 2025-05-01 05:57:02 +08:00
yusing
100d2c392f chore: memory optimization for access log 2025-04-30 18:30:46 +08:00
yusing
829eb08e37 feat: tunable rotate interval 2025-04-30 18:19:00 +08:00
yusing
53d54a09b0 fix: rotate result file size, add "saved" and omit empty values 2025-04-30 18:17:09 +08:00
yusing
62c551c7fe fix: tests 2025-04-30 17:42:51 +08:00
yusing
80e59bb481 fix: nil panic on unmarshaling zero value 2025-04-30 12:06:49 +08:00
yusing
7a5afc3612 fix; compose example 2025-04-30 04:03:11 +08:00
yusing
2c0349c11c chore: remove debug statement 2025-04-30 00:14:53 +08:00
yusing
8e3c2cc8d4 fix: issues when using socket-proxy 2025-04-29 23:56:15 +08:00
yusing
d35afdb3c9 security: exclude socket-proxy from proxying 2025-04-29 16:23:30 +08:00
yusing
ae093ebf40 docs: update wiki URL, add website URL 2025-04-29 15:22:31 +08:00
yusing
aa8af4185b chore: update schema url 2025-04-29 14:45:38 +08:00
yusing
0029cf69d6 fix: setup script and compose 2025-04-29 09:24:22 +08:00
Yuzerion
33e400a17e security: run in rootless by default and drop unnecessary caps (#101)
Co-authored-by: yusing <yusing@6uo.me>
2025-04-29 08:42:30 +08:00
yusing
1d22bcfed9 fix(access_log): file size calculation 2025-04-29 07:33:51 +08:00
yusing
978d82060e docs: move schema to frontend 2025-04-29 07:26:14 +08:00
yusing
7aa1215491 refactor: rename Deserialize to MapUnmarshalValidate 2025-04-29 07:26:14 +08:00
yusing
0b69589586 chore: disable unused last version parsing 2025-04-29 00:47:13 +08:00
yusing
bca3cd84d1 fix(accesslog): os: invalid use of WriteAt on file opened with O_APPEND 2025-04-29 00:46:30 +08:00
yusing
ce4bf2f646 fix(idlewatcher): not started for docker containers 2025-04-28 23:54:13 +08:00
yusing
c49016f22c fix: go.mod and deps upgrade 2025-04-28 11:32:01 +08:00
yusing
8da63daf02 refactor: simplify and remove duplicated code for icon caching 2025-04-28 11:22:49 +08:00
yusing
c5fd21552e fix(oidc): token not being refreshed when receiving simutaneous requests from the same session 2025-04-28 11:19:57 +08:00
yusing
27409abc24 fix: missing proxmox initialization 2025-04-28 05:08:14 +08:00
yusing
21c9e46274 fix: remove redundant event logging 2025-04-28 05:03:17 +08:00
yusing
22a12d3116 chore: remove redundant loadbalancer debug message 2025-04-28 04:57:26 +08:00
yusing
89d93dd878 chore: better error message 2025-04-28 00:48:20 +08:00
yusing
66853dfc52 fix: cloudflare realIP should defaults to be recursive 2025-04-27 23:53:04 +08:00
yusing
c72f66d64b feat(acl): add FORCE_RESOLVE_COUNTRY option to resolve country 2025-04-26 09:48:43 +08:00
yusing
59bc342a40 fix: notfications not being sent 2025-04-26 09:20:03 +08:00
yusing
e11579df10 chore(maxm): improved database update mechanism, fixed db being downloaded twice on first run 2025-04-26 09:08:03 +08:00
yusing
6a8f6fb4b5 chore(accesslog): reduce buffering for stdout 2025-04-26 08:29:55 +08:00
yusing
8f20bd3840 fix(acl): caching logic 2025-04-26 08:05:26 +08:00
yusing
f1abb745fe fix(tcp): return a dummy connection instead of nil 2025-04-26 07:57:20 +08:00
yusing
cb2990f6e8 chore: enrich example config 2025-04-26 07:40:55 +08:00
yusing
fb2f850311 fix(oidc): incorrect redirect url 2025-04-26 06:57:02 +08:00
yusing
2b9c0f09ee fix version checking 2025-04-26 06:50:43 +08:00
yusing
efe3eb4ce7 fix: autocert panic 2025-04-26 06:41:15 +08:00
yusing
a1c1a79976 fix: github workflow 2025-04-26 05:55:43 +08:00
yusing
90ba355d16 fix: Dockerfile 2025-04-26 05:51:37 +08:00
yusing
01179adfa8 fix: loosen agent version checking
- warn instead of error when version mismatch
- check for major version only
- better version parsing
2025-04-26 05:38:59 +08:00
yusing
e4be403bef fix(agent): reduce the size of agent binary by modules separation 2025-04-26 05:22:40 +08:00
yusing
e1cdf4da0f feat: add update functionality to agent script 2025-04-26 05:19:09 +08:00
yusing
5148cb3b8b refactor: remove unused constant CookieOauthSessionID, better error message 2025-04-26 03:55:16 +08:00
yusing
56c6a9f8fe chore: add groups scope to default OIDC scopes 2025-04-26 03:31:44 +08:00
yusing
be257b0532 refactor: change OIDCScopes to GetCommaSepEnv 2025-04-26 03:30:22 +08:00
yusing
0534bc38b2 fix(oidc): logout not working when user is denied 2025-04-26 03:26:45 +08:00
yusing
604e2481a6 fix(fileserver): being excluded 2025-04-26 01:45:47 +08:00
yusing
4f557043a5 fix(auth): login issue with user password authentication 2025-04-26 01:34:46 +08:00
yusing
03d609e4e1 fix: json marshaling 2025-04-26 01:31:22 +08:00
yusing
db6fc65876 fix(auth): adding missing HTTP methods 2025-04-26 00:50:03 +08:00
yusing
c6a05f7b35 docs: update demo url 2025-04-26 00:45:17 +08:00
yusing
9e4aa32120 deps: remove problematic sonic json library 2025-04-25 19:09:27 +08:00
yusing
759995972d docs: update README and config example for v0.11.0 2025-04-25 14:24:28 +08:00
yusing
03401488f6 chore(idlesleep): increase wake/sleep timeout to 3 minutes 2025-04-25 13:36:04 +08:00
yusing
1e790be70c chore: change ACL iso field to country 2025-04-25 13:25:45 +08:00
yusing
4410637f8b feat(autocert): added all available lego supported dns providers 2025-04-25 12:32:02 +08:00
yusing
3947152336 fix: uptime metrics 2025-04-25 11:26:24 +08:00
yusing
af8d2c74f6 revert(oidc): api breaking changes 2025-04-25 11:10:21 +08:00
yusing
e107f8d476 doc: checkout README from main 2025-04-25 10:49:05 +08:00
yusing
b427ff1f88 feat(acl): connection level ip/geo blocking
- fixed access log logic
- implement acl at connection level
- acl logging
- ip/cidr blocking
- geoblocking with MaxMind database
2025-04-25 10:47:52 +08:00
yusing
e513db62b0 refactor: move accesslog to logging/accesslog 2025-04-25 08:37:39 +08:00
yusing
2f33ee02d9 chore: replace gopkg.in/yaml.v3 with goccy/go-yaml 2025-04-25 08:36:54 +08:00
yusing
59490dcac0 refactor: move mock time to utils 2025-04-25 08:26:00 +08:00
yusing
5afa93a8f1 fix: json data not being loaded from disk correctly, logging 2025-04-25 08:26:00 +08:00
yusing
c8e9ed8440 fix: fatal and panic does not terminate the program 2025-04-25 08:26:00 +08:00
yusing
8363dfe257 fix: json marshal/unmarshal 2025-04-25 08:25:37 +08:00
yusing
080bbc18eb chore: completely drop prometheus support 2025-04-24 20:02:07 +08:00
yusing
1a0edc8bfe chore: deps upgrade 2025-04-24 16:39:05 +08:00
yusing
e8d1d524b9 fix: missing route handler iniitialization 2025-04-24 16:11:55 +08:00
yusing
edada22ac0 fix: tests 2025-04-24 15:45:34 +08:00
Yuzerion
76fb0cfdbb Feat/http3 (#84)
* chore(deps): update go-playground/validator to v10.26.0

* chore(deps): update Go version to 1.24.2 and dependencies, reorganize dependencies into categorized sections

* chore(deps): update Go version to 1.24.2 in Dockerfile

* refactor(agent): replace deprecated context import with standard context package

* feat(http3): add HTTP/3 support and refactor server handling code into utility functions

---------

Co-authored-by: yusing <yusing@6uo.me>
2025-04-24 15:37:12 +08:00
yusing
5df2553774 merge: better favicon handling 2025-04-24 15:34:47 +08:00
yusing
31812430f1 merge: access log rotation and enhancements 2025-04-24 15:29:18 +08:00
yusing
d668b03175 fix: tests 2025-04-24 15:09:46 +08:00
yusing
663a107c06 merge: main branch 2025-04-24 15:02:31 +08:00
yusing
806184e98b fix: redirectHTTP middleware redirect loop when behind another proxy 2025-04-24 09:27:10 +08:00
yusing
08ee82d7b0 fix(docker): docker clients not being cached correctly 2025-04-24 06:29:19 +08:00
yusing
bcc19167d4 feat: enhanced string utilities
- relative time formatting
- better relative duration formatting
2025-04-24 06:27:32 +08:00
yusing
858f65ee5a fix: update code for error handling changes, remove unused code 2025-04-24 06:24:28 +08:00
yusing
43566bbcfd feat: enhanced error handling library 2025-04-24 06:19:22 +08:00
yusing
ec8cca1245 feat: trie implementation 2025-04-24 05:56:03 +08:00
yusing
4a65de99a8 refactor: unify json load/saving with jsonstore 2025-04-24 05:49:32 +08:00
yusing
7461344004 fix: json store marshaling, api handler
- code clean up
- uncomment and simplify api auth handler
- fix redirect url for frontend
- proper redirect
2025-04-24 04:47:42 +08:00
yusing
b815c6fd69 feat(oidc): support token refreshing via offline_access scope
- refactored code
- moved api/v1/auth to auth/
- security enhancement
- env example update
- default jwt ttl changed to 24 hours
2025-04-23 17:50:22 +08:00
yusing
28c9a2e9d0 chore(oidc): remove debug logging 2025-04-23 02:02:17 +08:00
yusing
9e0bdd964c fix(oidc): rewrite login flow, fixed end_session_url retrieval and redirect issue 2025-04-22 19:29:19 +08:00
yusing
077641beaa refactor(oidc): simplify exchange method 2025-04-22 19:29:19 +08:00
yusing
ef483403da security: drop service headers 2025-04-22 19:28:58 +08:00
yusing
0a8aa2b215 fix(oidc): use XFH header from backend for cookie domain 2025-04-22 09:57:44 +08:00
yusing
5a984f5c0c chore: remove unused debugging printing 2025-04-22 09:54:19 +08:00
yusing
d60688c66f fix(route): error not being returned 2025-04-22 09:18:25 +08:00
yusing
23482da259 fix(route): panic on middleware error 2025-04-22 07:18:51 +08:00
yusing
62776229cb refactor(oidc): simplify initiialization flow, replace go-oidc with own forked version 2025-04-22 04:14:40 +08:00
yusing
36fab0cd50 fix(oidc): simplify and fix oidc middleware url handling 2025-04-22 03:55:43 +08:00
yusing
8f03662982 chore: upgrade go to 1.24.2 and dependencies 2025-04-22 03:21:42 +08:00
yusing
aad44031c4 chore: add rule files and debug dir to .gitignore 2025-04-22 03:19:05 +08:00
yusing
51813e6030 refactor(agent): replace deprecated context import with standard context package 2025-04-02 15:31:50 +08:00
yusing
f661907268 refactor(agent): streamline certificate and server handling in StartAgentServer function 2025-03-29 16:44:23 +08:00
yusing
be85633c32 fix(agent): fix agent host validatation and improve file path handling 2025-03-29 16:44:16 +08:00
yusing
392946fe33 fix(agent): fix generating incorrect cert values for shell command 2025-03-29 16:27:25 +08:00
yusing
671024965f fix(agent): initialize logger and start system info polling in main.go 2025-03-29 16:26:37 +08:00
yusing
8d9aef3cd5 fix: improve install-agent.sh script with better error handling, support for arm64 architecture 2025-03-29 16:26:08 +08:00
yusing
b5b4f0453a chore: remove unused AgentRegistrationPort environment variable from env.go 2025-03-29 16:10:33 +08:00
yusing
8ca6ac2752 fix: correct service section header in install-agent.sh 2025-03-29 15:47:29 +08:00
yusing
27f7e08e18 build trigger 2025-03-29 15:38:33 +08:00
yusing
f3e08dc9ea chore: remove outdated note about ongoing feature branch from README 2025-03-29 10:07:16 +08:00
yusing
e3797ea96b fix: update permissions in agent-binary workflow to allow write access for contents 2025-03-29 09:36:56 +08:00
yusing
146e7781be fix: limit redirect count when parsing html for favicon, fix url sanitize method 2025-03-29 09:35:12 +08:00
yusing
d2e2086540 fix: loading_page html 2025-03-29 08:21:26 +08:00
yusing
d105f866ff feat: enhance idlewaker loading page design and add favicon handling in waker_http, removed unnecessary checkings 2025-03-29 08:18:58 +08:00
yusing
1c001ed9df refactor: clean up logger and metric initialization flow 2025-03-29 02:59:40 +08:00
yusing
366c89164f chore: remove prometheus router metrics and related initialization code 2025-03-29 02:59:40 +08:00
yusing
36f13c61bb test: add tests for route validation scenarios 2025-03-29 02:59:40 +08:00
yusing
c8935102c3 feat: add validation for localhost routes to prevent usage of godoxy port causing self recursion 2025-03-29 02:59:40 +08:00
yusing
a9e4f82e30 refactor: move typescript stuff to 'schemas' directory 2025-03-28 09:28:02 +08:00
yusing
f966ca8b83 feat: update cookie security settings to use API_JWT_SECURE environment variable 2025-03-28 08:51:45 +08:00
yusing
2da7ea56d5 deps upgrade 2025-03-28 08:45:59 +08:00
yusing
232f720e77 refactor: use stretchr/testify, replace ExpectBytesEqual and ExpectDeepEqual with ExpectEqual in tests 2025-03-28 08:45:06 +08:00
yusing
2f476603d3 feat: implement experimental BackScanner for reading files backward and add tests for various scenarios 2025-03-28 08:14:06 +08:00
yusing
366fede517 feat: add websocket writer and error handling utilities 2025-03-28 08:14:06 +08:00
yusing
7ef8354eb0 feat: enhance route handling with agent support and refactor port selection mapping 2025-03-28 08:14:06 +08:00
yusing
fbb07011f1 refactor: update homepage item handling and improve JSON marshaling 2025-03-28 08:14:06 +08:00
yusing
a7da8ffb90 refactor: clean up code and fix race condition in idlewatcher 2025-03-28 08:14:06 +08:00
yusing
95fe294f7d feat: add AgentProvider implementation and integrate with provider types 2025-03-28 08:14:06 +08:00
yusing
cdb3ffe439 refactor: clean up code and enhance utilities with new functions 2025-03-28 08:14:06 +08:00
yusing
7707fc6f36 api: docker endpoints 2025-03-28 08:14:06 +08:00
yusing
765328affb api: system metrics endpoint 2025-03-28 08:14:06 +08:00
yusing
3c515b0258 feat: predefined docker image blacklist, avoid proxing service backends, refactor 2025-03-28 08:14:06 +08:00
yusing
c6f65ba69f feat: agent as docker provider, drop / reload routes when docker connection state changed, refactor 2025-03-28 08:14:06 +08:00
yusing
8c9a2b022b feat: agent health monitor 2025-03-28 08:14:06 +08:00
yusing
2e8248cd5b fix: race condition in health monitor 2025-03-28 08:14:06 +08:00
yusing
2b91d99ec6 refactor: remove unused old code 2025-03-28 08:14:06 +08:00
yusing
f7688a942a misc: schemas update 2025-03-28 08:14:06 +08:00
yusing
574056a7e3 metrics: metric utils 2025-03-28 07:47:58 +08:00
yusing
84e8dc0e06 refactor: improved config initialization flow, add agent config 2025-03-28 07:47:28 +08:00
yusing
fb8ce6c878 metrics: start polling uptime metrics 2025-03-28 07:42:31 +08:00
yusing
d961c11eb7 api: health endpoint support plain or ws based on request 2025-03-28 07:39:26 +08:00
yusing
90f8e82f14 refactor: error http handling 2025-03-28 07:39:26 +08:00
yusing
14bb66d12f env: remove LOG_STREAMING and DEBUG_MEM_LOGGER 2025-03-28 07:39:26 +08:00
yusing
7093985b57 refactor: clean up api handler 2025-03-28 07:39:26 +08:00
yusing
a557684542 api: manual cert renewal support, new api endpoint 2025-03-28 07:39:26 +08:00
yusing
b0876331e6 refactor: rename api/v1/file.go to config_file.go, updated error handling 2025-03-28 07:39:26 +08:00
yusing
cba7338d8d auth: support for end_session_endpoint discovery, remove OIDC_LOGOUT_URL 2025-03-28 07:39:26 +08:00
yusing
f72d9aee80 auth: implement block page on invalid credentials 2025-03-28 07:39:26 +08:00
yusing
480fb4818c api: allow authentication when on http 2025-03-28 07:39:26 +08:00
yusing
78a3c8a8e4 api: add DEBUG_DISABLE_AUTH for debugging 2025-03-28 07:39:26 +08:00
yusing
9cb7cc84ee refactor: move profiling code to pprof_*.go 2025-03-28 07:39:26 +08:00
yusing
2f24a1db41 api: generate random jwt secret if not present, remove unused imports 2025-03-28 07:39:26 +08:00
yusing
4a2cc70b52 refactor: rename module 'err' to 'gperr' and use gphttp error handling 2025-03-28 07:39:26 +08:00
yusing
3021672de5 refactor: move atomic.Value to value.go, improved handling for zero values 2025-03-28 07:39:26 +08:00
yusing
5d2df3550b refactor: remove forward auth, move module net/http to net/gphttp 2025-03-28 07:39:26 +08:00
yusing
c0c6e21a16 refactor: improved json loading flow and log messages 2025-03-28 07:39:26 +08:00
yusing
8c03c5e82e refactor: improved memlogger and remove html log formatting 2025-03-28 07:39:26 +08:00
yusing
dfd2f3962c refactor: move api/v1/utils to net/gphttp 2025-03-28 07:39:26 +08:00
yusing
d315710310 refactor: improved reverse proxy performance, reduce memory allocation calls 2025-03-28 07:39:26 +08:00
yusing
3424cc4e51 refactor: simplfy and move net/http/server to net/gphttp/server 2025-03-28 07:39:26 +08:00
yusing
361931ed96 refactor: rename module 'err' to 'gperr' in references 2025-03-28 07:39:26 +08:00
yusing
e4f6994dfc autocert: refactor and add pseudo provider for testing 2025-03-28 07:39:26 +08:00
yusing
827a27911c metrics: implement uptime and system metrics 2025-03-28 07:39:22 +08:00
yusing
1e39d0b186 refactor: improved init flow in main 2025-03-28 07:38:12 +08:00
yusing
fd223c7542 refactor: utils.WaitExit 2025-03-28 05:59:04 +08:00
yusing
40aa937f54 refactor: rename module 'err' to 'gperr' 2025-03-28 05:57:43 +08:00
yusing
47ab6b8a92 feat: godoxy agent 2025-03-28 03:36:35 +08:00
yusing
7420abf175 misc: update gitignore and trunk, remove next-release.md 2025-03-28 03:28:17 +08:00
yusing
e9a8194cf8 refactor: cleanup setup script 2025-03-28 03:26:04 +08:00
yusing
9006049d33 cicd: simplify and optimize Dockerfile, bump Go version to 1.24.1 2025-03-28 03:25:17 +08:00
yusing
39381a17de cicd: update github workflow 2025-03-28 03:24:02 +08:00
yusing
9460549eff docs: README update 2025-03-28 03:23:06 +08:00
yusing
5ea82645ef examples: add GODOXY_FRONTEND_PORT environment variable 2025-03-28 03:21:34 +08:00
yusing
597abc5b06 deps upgrade 2025-03-28 03:20:46 +08:00
yusing
350265e31f merge feat/godoxy-agent and update README 2025-03-28 03:13:59 +08:00
yusing
5680a306ff refactor: fix logout logic in oidc middleware 2025-03-28 02:19:46 +08:00
yusing
16cb09bda5 types: schema update 2025-03-28 02:11:00 +08:00
yusing
9a3c40f6a6 refactor: fix tests 2025-03-28 01:58:26 +08:00
yusing
821e4a225a middleware: use status 308 instead of 301 for redirectHTTP 2025-03-28 01:51:50 +08:00
yusing
939c99b0cf api: close connection on return 2025-03-28 01:44:11 +08:00
yusing
79b9c7011d deps upgrade 2025-03-28 01:33:06 +08:00
yusing
e7ff7402b4 fix Makefile 2025-03-23 00:05:10 +08:00
yusing
91f6369ba9 deps upgrade 2025-03-22 23:58:58 +08:00
yusing
17ef5cb9a5 security: sanitize uri 2025-03-22 23:58:37 +08:00
yusing
e8109f1b78 deps upgrade 2025-03-22 23:53:47 +08:00
yusing
f3840d56af security: sanitize path and uri 2025-03-22 23:53:33 +08:00
yusing
4a5e0b8d81 remove NEXT_PUBLIC_APP_BASE_DOMAIN 2025-03-21 05:55:11 +08:00
yusing
4ef29f027e deps upgrade 2025-03-17 05:40:42 +08:00
yusing
d4d2efe925 idlewatcher/waker: refactor 2025-03-08 07:59:10 +08:00
yusing
1078731f2d docker: refactor container related code 2025-03-08 07:10:53 +08:00
yusing
1739afae24 idlewatcher/waker: refactor, cleanup and fix 2025-03-08 07:06:57 +08:00
yusing
9f0c29c009 remove unused params 2025-03-08 04:47:24 +08:00
yusing
6220d02f32 api: log api error 2025-03-07 02:11:52 +08:00
yusing
c166b12515 upgrade go to 1.24.1, deps upgrade 2025-03-05 08:26:22 +08:00
yusing
189c870630 fix docker client panic introduced in last patch 2025-03-02 21:59:32 +08:00
yusing
cdead9ba8a fix makefile 2025-03-02 21:56:22 +08:00
yusing
21616f4d42 update agent docs 2025-03-01 17:06:30 +08:00
yusing
0a348278ca deps upgrade 2025-03-01 16:31:11 +08:00
yusing
98d0c9a4f6 update Makefile, removed old stuff 2025-03-01 16:31:03 +08:00
yusing
34a3739545 docker: fix docker client data race on Close() 2025-03-01 16:04:39 +08:00
yusing
7bb34b8788 fix redirectHTTP middleware test 2025-03-01 15:53:33 +08:00
yusing
f6dc432419 refactor: fix code formatting and return flow 2025-03-01 15:50:50 +08:00
yusing
9b2ee628aa fix docker client data race on Close(), remove SharedClient.IsConnected 2025-03-01 15:47:08 +08:00
yusing
357ad26a0e reduce docker client initiation 2025-03-01 15:39:25 +08:00
yusing
a3e705373c deps upgrade 2025-03-01 15:34:53 +08:00
yusing
71ad13256e fix redirectHTTP middleware, add bypass.user_agents option 2025-03-01 15:29:33 +08:00
yusing
68929631f2 fix redirectHTTP middleware, add bypass.user_agents option 2025-03-01 04:55:29 +08:00
yusing
9c04065c33 utils.io: revert last change 2025-03-01 04:32:24 +08:00
yusing
09db57db8f remove unused env 'IsProduction' 2025-02-27 05:29:54 +08:00
yusing
f9b7e64d53 oidc: use 'end_session_endpoint' from discovery, remove 'OIDC_LOGOUT_URL' 2025-02-27 05:27:38 +08:00
yusing
50262f2acc deps upgrade 2025-02-27 04:55:58 +08:00
yusing
a4d99b54af add a block page to oidc on invallid credentials, fix inifinite login redirect 2025-02-27 01:18:47 +08:00
yusing
485aa0f52b fix server initialization 2025-02-27 00:58:24 +08:00
yusing
f8b732c9b8 fix app url when using fqdn alias 2025-02-26 01:46:46 +08:00
yusing
ac72f77a74 homepage: refactor and fix overrides not being applied 2025-02-25 11:31:06 +08:00
yusing
626d48d151 fix dependabot messing with the nightly image 2025-02-25 10:26:45 +08:00
dependabot[bot]
07511281b8 Bump github.com/go-jose/go-jose/v4 from 4.0.4 to 4.0.5 (#67)
Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.0.4 to 4.0.5.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v4.0.4...v4.0.5)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-25 10:23:42 +08:00
yusing
7c11c9c91a update webui screenshot 2025-02-25 06:06:06 +08:00
yusing
2cabe4c416 update README and screenshots 2025-02-25 06:01:44 +08:00
yusing
dc88a037eb small memory usage optimization 2025-02-25 05:39:21 +08:00
yusing
2fe8531e51 deps upgrade 2025-02-25 05:39:07 +08:00
Yuzerion
fddd2651fc Update README.md 2025-02-25 05:38:18 +08:00
yusing
deb0781871 README and screenshots update 2025-02-25 05:34:38 +08:00
yusing
8114b04ab6 fix data race for system info 2025-02-25 04:29:17 +08:00
yusing
767560804d idlewatcher: refactor and fix data race 2025-02-25 04:29:07 +08:00
yusing
8074b93992 clarify setup script message 2025-02-25 03:44:44 +08:00
Yuzerion
588dd41244 Update README.md 2025-02-25 00:03:20 +08:00
yusing
61b0147a7c fix cloudflare real ip middleware data race 2025-02-24 19:50:04 +08:00
yusing
0d388a396c fix data race 2025-02-24 19:24:46 +08:00
yusing
135c79d2ad fix makefile for pprof 2025-02-24 18:54:16 +08:00
yusing
9925b042d8 bring back database check 2025-02-24 08:46:59 +08:00
yusing
1d16d514c7 fix empty homepage name, incorrect image parsing, refactor 2025-02-24 08:42:10 +08:00
yusing
bda547198e improved docker reconnect mechanism, removed redundant checkings, refactor 2025-02-24 07:50:23 +08:00
yusing
5f1b78ec84 allow agent without docker connected 2025-02-24 07:35:28 +08:00
yusing
b7e9a85be0 implement docker image blacklist 2025-02-24 06:47:07 +08:00
yusing
080c1cee4f replace deprecated docker types 2025-02-24 06:10:46 +08:00
yusing
baebede816 fix loggers 2025-02-24 05:44:48 +08:00
yusing
f455251645 metrics: fix metrics collection 2025-02-24 05:36:28 +08:00
yusing
8d06f7cf02 fix github action caching 2025-02-24 04:31:13 +08:00
yusing
4af2eaa6a3 fixed http server context handling 2025-02-24 04:20:00 +08:00
yusing
f5b8879b87 increase task timeout 2025-02-24 03:44:20 +08:00
yusing
7501fee448 cicd: fix workflow 2025-02-24 03:32:15 +08:00
yusing
b7b5090673 metrics: fix not using context 2025-02-24 03:28:47 +08:00
yusing
4f94a0f08a improved add agent mechanism 2025-02-24 03:28:23 +08:00
yusing
2281c8ac39 skip version check for dev versions 2025-02-24 03:27:50 +08:00
yusing
2cc152d0ab cicd: switch to use registry cache instead of gha cache 2025-02-24 02:52:15 +08:00
yusing
7b86bb262c fix setup script 2025-02-24 00:02:50 +08:00
yusing
ed2a4251f1 fix setup script 2025-02-24 00:00:05 +08:00
yusing
847811a52c rename go-proxy to godoxy 2025-02-23 14:27:25 +08:00
yusing
d25d5b734c fix agent install script 2025-02-23 14:24:41 +08:00
yusing
bc4792b7fd add quotes to agent install script 2025-02-23 13:54:47 +08:00
yusing
7850cbc4bf fix setup script 2025-02-23 13:48:40 +08:00
yusing
97fa648b2f fix setup script 2025-02-23 13:47:30 +08:00
yusing
c5cf867cd9 update default repo to main 2025-02-23 13:31:27 +08:00
yusing
03ea9bb760 update default image name 2025-02-23 13:28:35 +08:00
yusing
a1a5bf921e workflow update 2025-02-23 13:27:47 +08:00
yusing
3e1a7a0dc5 docker: clear routes on docker disconnect, reload routes on connection restore 2025-02-23 13:11:21 +08:00
yusing
2c21387ad9 implement system mode agent 2025-02-23 11:26:38 +08:00
yusing
5e8e4fa4a1 remove old code 2025-02-23 07:22:15 +08:00
yusing
a41107d021 fix markdown formatting 2025-02-23 06:40:02 +08:00
yusing
281523ee06 update default repo to main 2025-02-23 06:37:48 +08:00
yusing
2504510c61 update default image name 2025-02-23 06:36:20 +08:00
yusing
7153fc8bb5 workflow update 2025-02-23 06:28:34 +08:00
yusing
3af094d788 remove url from homepage item override 2025-02-23 01:38:32 +08:00
yusing
785ea71a20 add example to override app base domain in dashboard 2025-02-23 00:08:24 +08:00
yusing
05d2f77c0c refactor docker api code, deps upgrade 2025-02-22 04:51:07 +08:00
yusing
e22366e524 api: implement several docker apis 2025-02-20 18:03:54 +08:00
yusing
2b51c47846 reduce docker client initiation 2025-02-20 18:02:34 +08:00
yusing
dd6af9b8e0 debug: add option to disable auth 2025-02-20 17:45:47 +08:00
yusing
c66b17583f small refactor 2025-02-20 17:45:03 +08:00
yusing
3ce3520c45 middleware trace: fix incorrect log level 2025-02-20 17:44:47 +08:00
yusing
8d1e7f4331 fix sensor data not being returned to api 2025-02-19 15:30:22 +08:00
yusing
f0b04afa11 refactor and fix homepage override not correctly loaded 2025-02-19 14:58:52 +08:00
yusing
f1bfd13da3 fix cloudflare real ip middleware resolving local addresses 2025-02-19 00:36:44 +08:00
yusing
161cd84150 fix cloudflare real ip middleware resolving local addresses 2025-02-19 00:32:13 +08:00
yusing
da39593c15 add agent to schema 2025-02-18 21:11:24 +08:00
yusing
571f36e405 go.modL add comments explaining dependencies usage 2025-02-18 21:11:24 +08:00
yusing
a4b1200475 deps upgrade 2025-02-18 21:11:24 +08:00
yusing
43807dcba9 autocert: add porkbun cert provider 2025-02-18 21:11:24 +08:00
yusing
99a72451d9 fix access log rotation attempt 2025-02-18 21:11:20 +08:00
yusing
b8900999a4 deps upgrade 2025-02-18 16:39:25 +08:00
yusing
e6f77376b9 fix args.go affected from cherry-pick 2025-02-18 16:35:23 +08:00
yusing
b2a6a20f10 simplify setup with script 2025-02-18 05:43:33 +08:00
yusing
265b52dccb simplify setup with script 2025-02-18 05:39:15 +08:00
yusing
0c112e1db1 allow using auth without https 2025-02-18 04:15:47 +08:00
yusing
8eef7db1c6 trim and convert alias and host to lowercase 2025-02-18 04:07:21 +08:00
yusing
05cbf99237 trim and convert alias and host to lowercase 2025-02-18 02:32:31 +08:00
yusing
651a7cf83e enable auth by default with temporary random JWT 2025-02-18 02:27:45 +08:00
yusing
ee27237083 simplify access logger with bufio.Writer 2025-02-18 01:12:42 +08:00
yusing
72306e91a2 fix loadbalancing for agents 2025-02-17 17:39:38 +08:00
yusing
75d272be14 fix loadbalancing when two container have the same alias 2025-02-17 11:16:34 +08:00
yusing
a8a209f0b0 simplify some code and implement metrics storage 2025-02-17 07:18:59 +08:00
yusing
1b7b6196c5 reduce memory usage and improve performance 2025-02-17 05:05:53 +08:00
yusing
ed7937a026 system metrics aggregation 2025-02-16 23:38:19 +08:00
yusing
f2de4692ea fix system info timestamp 2025-02-16 04:18:19 +08:00
yusing
16b046bd44 add cert info and renewal api 2025-02-15 21:50:34 +08:00
yusing
7129e2cc9d remove unused code 2025-02-15 11:35:36 +08:00
yusing
01432fa778 improve initialization flow 2025-02-15 11:21:29 +08:00
yusing
9731d28ec3 fix server responding incorrect status code 2025-02-15 09:18:17 +08:00
yusing
99fbb31554 fix routes not started after adding agent 2025-02-15 09:10:42 +08:00
yusing
18d258aaa2 refactor and organize code 2025-02-15 05:44:47 +08:00
yusing
1af6dd9cf8 uses display name in uptime metrics, small refactor 2025-02-15 00:29:17 +08:00
yusing
0da183f084 update screenshots 2025-02-14 22:20:51 +08:00
yusing
205726b045 refactor 2025-02-14 22:04:45 +08:00
yusing
9cd5237bb8 remove add_agent command 2025-02-14 21:56:12 +08:00
yusing
964e94b3ba fix incorrect uptime history data 2025-02-14 21:30:49 +08:00
yusing
9f54f40f5a simplify setup process with WebUI 2025-02-14 20:14:16 +08:00
yusing
7047d37f70 fix system info metric crash on error 2025-02-14 12:36:45 +08:00
yusing
5b1d45a8fe fix http superfluous response.WriteHeader 2025-02-14 12:36:45 +08:00
yusing
a319957f3e fix duplicated routes not being shown 2025-02-14 12:36:45 +08:00
yusing
816166a30a fix feature not supported errors 2025-02-14 12:36:45 +08:00
yusing
5dd2ea776a remove unused code 2025-02-14 05:17:02 +08:00
yusing
3b94c7bb43 add buffering to docker watcher 2025-02-14 05:16:56 +08:00
yusing
f0198616ad improve error handling for system info metrics 2025-02-14 03:49:56 +08:00
yusing
267fd403da fix tcp/udp listening url 2025-02-14 03:28:58 +08:00
yusing
0a8bb7eae5 improved default port selection 2025-02-14 03:26:25 +08:00
yusing
409048c206 simplify code and fix metrics response 2025-02-14 02:19:58 +08:00
yusing
f84bd6a1e8 fix 5m period, fix websocket not responding on no data 2025-02-14 01:57:26 +08:00
yusing
d5c0e62be1 autocert: add porkbun cert provider 2025-02-13 23:48:35 +08:00
yusing
40c4344f73 poller error formatting 2025-02-13 23:31:00 +08:00
yusing
3bd8aca2d2 period: change "1m" to "1mo" to avoid confusion 2025-02-13 21:18:30 +08:00
yusing
a21bdedbc1 go.modL add comments explaining dependencies usage 2025-02-13 19:40:15 +08:00
yusing
797ebd7771 update next release md 2025-02-13 19:30:23 +08:00
yusing
04e9ecbc76 README.md: Update README.md 2025-02-13 19:26:23 +08:00
yusing
41d37579dc small refactor 2025-02-13 18:52:00 +08:00
yusing
10d23828a7 metrics: fix 5m period 2025-02-13 18:47:17 +08:00
yusing
19e3392825 improve reverse proxy and serverhandling
- buffer pool for IO copy
  - flush response after read, now works with event stream
  - fixed error handling for server
2025-02-13 18:39:35 +08:00
yusing
6bf4846ae8 poller: clear errors after logging 2025-02-13 17:23:00 +08:00
yusing
afcd37dac6 remove unnecessary transport.Clone 2025-02-13 17:14:57 +08:00
yusing
c2ff497cc9 revert readme 2025-02-13 17:07:17 +08:00
yusing
decd2c2ded fix various endpoints 2025-02-13 15:05:16 +08:00
yusing
02d1c9ce98 refactor header utils to httpheader package, cleanup api endpoints 2025-02-13 07:32:59 +08:00
yusing
5c9083a5df remove forwardAuth middleware 2025-02-13 07:19:40 +08:00
yusing
3c7fafa91f improved metrics implementation 2025-02-13 05:58:30 +08:00
yusing
fd50f8fcab fix check health for tcp/udp, refactor 2025-02-13 05:58:15 +08:00
yusing
1a93df5886 fix route port udp selection and healthcheck interval 2025-02-13 05:50:49 +08:00
yusing
bdc086c285 increase icon cache ttl to 3 days, remove pruned message when no icon pruned 2025-02-13 03:06:18 +08:00
yusing
82042e0b99 refactor, fix metrics and upgrade go to 1.24.0 2025-02-12 11:15:45 +08:00
yusing
c807b30c8f api: remove service health from prometheus, implement godoxy metrics 2025-02-12 05:30:34 +08:00
yusing
72dc76ec74 api: add system_info endpoint 2025-02-11 12:52:16 +08:00
yusing
71619042fd fix agent hot-reload issue and added list agents endpoint 2025-02-11 12:45:34 +08:00
yusing
429a77de8e refactor, fix reload error when using agents, and other small improvements 2025-02-11 12:15:51 +08:00
yusing
b1f72620dc refactor and properly set idlewaker error in JSON output 2025-02-11 10:14:32 +08:00
yusing
2a54aed135 fix incorrect RequestURI 2025-02-11 09:49:49 +08:00
yusing
040c1f6f78 fix query being duplicated 2025-02-11 09:44:23 +08:00
yusing
07bce90521 fixed some issues 2025-02-11 09:16:21 +08:00
yusing
508b093278 fix health monitor panic 2025-02-11 07:12:08 +08:00
yusing
9bed5bf872 fix agent json marshal 2025-02-11 06:27:27 +08:00
yusing
6d0a2cd301 fix serving wrong cert 2025-02-11 06:20:09 +08:00
yusing
e1ee08361d api: added network and sensors system info 2025-02-11 05:26:37 +08:00
yusing
3332ce34c5 simplify setup process 2025-02-11 05:05:56 +08:00
yusing
2c57e439d5 fixed a few stuff 2025-02-11 01:10:09 +08:00
yusing
73e2660e59 agent: add system-info endpoint 2025-02-11 01:10:09 +08:00
yusing
9120bbea34 change default agent name to hostname 2025-02-11 01:10:09 +08:00
yusing
58ea9750d7 Update next-release 2025-02-11 01:10:09 +08:00
yusing
a59ad97e5e Fix dockerfile and makefile 2025-02-11 01:10:09 +08:00
yusing
0a7b28caf5 refactor and remove unused code 2025-02-11 01:10:09 +08:00
yusing
eaf191e350 implement godoxy-agent 2025-02-11 01:10:09 +08:00
yusing
ecb89f80a0 update files for agent, deps upgrade 2025-02-11 01:10:07 +08:00
yusing
9626b65593 deps upgrade 2025-02-11 00:56:17 +08:00
yusing
c9b5516330 fix wildcard alias and some tests 2025-02-11 00:47:43 +08:00
yusing
4363ca88aa fix file server validation 2025-02-11 00:47:43 +08:00
Yuzerion
3353060ad4 Update README.md 2025-02-07 15:58:51 +08:00
yusing
ddc3b8575e fix startup panic when no notification provider is set 2025-02-07 03:07:21 +08:00
yusing
136a2ec89f remove some debug logging 2025-02-07 01:08:42 +08:00
yusing
021c68f2a7 update README 2025-02-06 18:31:49 +08:00
yusing
989a09274f restore notification 2025-02-06 18:25:39 +08:00
yusing
39c5886d7a make rules.name optional 2025-02-06 18:25:39 +08:00
Yuzerion
1a5f3735cf Feat/fileserver (#60)
* cleanup code for URL type

* fix makefile for trace mode

* refactor, merge Entry, RawEntry and Route into one. 

* Implement fileserver.

* refactor: rename HTTPRoute to ReverseProxyRoute to avoid confusion

* refactor: move metrics logger to middleware package

- fix prometheus metrics for load balanced routes
  - route will now fail when health monitor fail to start

* fix extra output of ls-* commands by defer initializaing stuff, speed up start time

* add test for path traversal attack, small fix on FileServer.Start method

* rename rule.on.bypass to pass

* refactor and fixed map-to-map  deserialization

* updated route loading logic

* schemas: add "add_prefix" option to modify_request middleware


* updated route JSONMarshalling

---------

Co-authored-by: yusing <yusing@6uo.me>
2025-02-06 18:23:10 +08:00
yusing
4d47eb0e91 update compose example 2025-02-06 05:59:21 +08:00
yusing
af7c59b5c2 add tests for rules.on 2025-02-06 05:50:03 +08:00
yusing
693bf68864 rules: updated help message, make values optional, fixes tests 2025-02-06 05:13:47 +08:00
Yuzerion
c9ddf3d165 Create FUNDING.yml 2025-02-06 04:44:19 +08:00
yusing
1549b56866 README: move auth docs to wiki 2025-02-06 03:12:34 +08:00
yusing
2cd1f22e68 add test for the previous commit 2025-02-06 02:33:30 +08:00
yusing
688f38943d fix single line yaml list treated as comma seperated list 2025-02-06 01:58:45 +08:00
yusing
043bbd7a11 readme and docker compose example amendment 2025-02-06 00:56:11 +08:00
yusing
f997423fd7 fix error formatting 2025-02-04 07:04:49 +08:00
yusing
1871ef3d38 clearer error message when config reload failed 2025-02-04 07:04:27 +08:00
yusing
7c56c88dd4 fix server not being restarted after config reload 2025-02-04 07:04:15 +08:00
yusing
4d7422dd90 adjusted and simplified default config and compose.yml 2025-02-04 07:04:05 +08:00
yusing
eccabc0588 remove incorrectly added pnpn lockfile 2025-02-04 07:04:05 +08:00
yusing
0c7b188587 api: fix search icon returning null when no match 2025-02-02 03:31:52 +08:00
yusing
4c97b79adf log prometheus enabled 2025-02-02 03:21:39 +08:00
yusing
8ae9573b07 add timeout to notification context 2025-02-01 14:42:21 +08:00
yusing
43fce6e739 fix two tests 2025-02-01 14:41:22 +08:00
Yuzerion
78900772bb Feat/ntfy (#57)
* implement ntfy notification

* fix notification fields order

* fix schema for ntfy

---------

Co-authored-by: yusing <yusing@6uo.me>
2025-02-01 13:07:44 +08:00
yusing
c16a0444ca fix main.go and update next release doc 2025-02-01 12:51:52 +08:00
yusing
0d518166ee api: move prometheus handler inside api handler /v1/metrics 2025-02-01 02:09:43 +08:00
yusing
6ae391a3c9 make POST and JSON as notification defaults 2025-01-31 14:56:55 +08:00
yusing
357897a0cd remove schema stuff from code 2025-01-31 05:21:32 +08:00
yusing
10a0a8fe09 update readme 2025-01-31 03:33:20 +08:00
yusing
98443be80c fix OIDC not working when ISSUE_URL points to GoDoxy itself 2025-01-30 10:39:26 +08:00
yusing
bf7f6e99c5 updated next release docs 2025-01-30 10:34:47 +08:00
yusing
b6e468e54e remove schema from dockerfile and code, dependencies upgrade 2025-01-30 00:43:25 +08:00
yusing
dfc634a362 http: increase default response header timeout to 60s, add option to customize it, schema update 2025-01-30 00:41:03 +08:00
yusing
d9b6b82f07 api: response error in json instead of html for better rendering flexibility 2025-01-29 11:50:08 +08:00
yusing
4ad6257dab fix deserialization 2025-01-29 11:49:28 +08:00
yusing
e3e3f1dfdc fixed some tests 2025-01-29 09:40:37 +08:00
yusing
60f83bb7bf rules: remove the requirement of "path must start with /" 2025-01-29 08:57:42 +08:00
yusing
bbc10cb105 fix serialization, added benchmark tests, updated next release docs 2025-01-26 15:08:10 +08:00
yusing
83ea19dd92 api: added validation endpoint 2025-01-26 14:47:33 +08:00
yusing
7ec42dce4d improved implementation of converting ANSI color to HTML 2025-01-26 14:46:43 +08:00
yusing
a9da7ce6fc small fix on Makefile and update dependencies 2025-01-26 14:45:19 +08:00
yusing
1586610a44 Cleaned up some validation code, stricter validation 2025-01-26 14:43:48 +08:00
yusing
254224c0e8 fix error formatting 2025-01-26 05:26:18 +08:00
yusing
9b66772a12 fix schemas 2025-01-25 12:50:16 +08:00
yusing
322878b0b7 fix schemas 2025-01-25 07:04:01 +08:00
yusing
9e181d25ce fix npm package 2025-01-25 02:36:22 +08:00
yusing
4c311fd78e fixed some schemas, packed it as a npm package 2025-01-24 10:42:50 +08:00
yusing
9936b3af5b improved homepage config implementation 2025-01-24 05:11:35 +08:00
yusing
648fd23a57 feat: oidc support OIDC_LOGOUT_URL 2025-01-24 00:34:50 +08:00
Peter Olds
7dd00d2424 feat: add a add_prefix middleware (#51)
This will allow you to translate:

`foo.mydomain.com` => `192.168.1.99:8000/foo` (for example)
2025-01-24 00:34:26 +08:00
Yuzerion
9e83fe7329 Update README.md 2025-01-24 00:28:38 +08:00
Yuzerion
166c9c75e9 Update next-release.md
added some screenshots
2025-01-24 00:25:36 +08:00
yusing
b9882f8985 updated implementation of (un)hiding items 2025-01-23 12:52:15 +08:00
yusing
37a166731d fixes some tests 2025-01-23 05:24:13 +08:00
yusing
66db583432 fix notification dispatcher panic when dispatching on program exit 2025-01-23 04:41:10 +08:00
yusing
f7eb80a6ea fix dashboard filter not working for edited apps 2025-01-23 04:29:39 +08:00
yusing
79f40f3d22 implement icon cache expiry, cleanup code and upgrade deps 2025-01-23 04:16:06 +08:00
yusing
ed3b26653c fix log wrapped incorrectly in WebUI, implement log SSR 2025-01-23 00:08:19 +08:00
yusing
2bb13129de fix: autocert scheduler using too high cpu usage 2025-01-22 10:45:57 +08:00
yusing
fc29e8f9fa fix typo 2025-01-22 08:32:51 +08:00
yusing
495c2c7390 fix makefile 2025-01-22 06:14:02 +08:00
yusing
b984386bab fix: high cpu usage 2025-01-22 05:44:04 +08:00
yusing
3781bb93e1 cleanup makefile and remove script, allow running as non-root user 2025-01-22 05:42:56 +08:00
yusing
3a4dc3f876 fixed dashboard not showing all apps 2025-01-21 12:56:21 +08:00
yusing
2c43f1412e fix OIDC middleware callback URL 2025-01-21 12:42:56 +08:00
yusing
5d3a93f103 idlewatcher: fix visiting unhealthy idle watched container causes panic 2025-01-21 10:37:09 +08:00
yusing
5faba1b5a9 fix svg content type 2025-01-21 10:07:53 +08:00
yusing
4e7bd3579b fix favicon content type 2025-01-21 09:36:17 +08:00
yusing
49da8a31d2 api: fix not getting correct icon 2025-01-21 09:31:51 +08:00
yusing
dd2b8f600d api: allow favicon endpoint to use url instead of alias 2025-01-21 06:48:56 +08:00
yusing
8b1a3a31ff simplify icon caching and homepage item override 2025-01-21 06:16:00 +08:00
yusing
d429374924 fix deserialization: reflect: indirection through nil pointer to embedded struct 2025-01-21 04:09:46 +08:00
yusing
dd0bbdc7b4 fix logs not printing correctly, removed unneccessary loggers 2025-01-20 17:42:54 +08:00
yusing
64e85c3076 feat: support selfh.st icons, support homepage config overriding 2025-01-20 17:42:17 +08:00
yusing
68771ce399 api: added some endpoints for dashboard filter to work 2025-01-20 06:17:18 +08:00
yusing
bcc7faa8e5 api: updated response message on invalid credential, add auth check endpoint 2025-01-20 02:14:21 +08:00
Yuzerion
fb0dc7dea0 Feat/OIDC middleware (#50)
* implement OIDC middleware

* auth code cleanup

* allow override allowed_user in middleware, fix typos

* fix tests and callbackURL

* update next release docs

* fix OIDC middleware not working with Authentik

* feat: add groups support for OIDC claims (#41)

Allow users to specify allowed groups in the env and use it to inspect the claims.

This performs a logical AND of users and groups (additive).

* merge feat/oidc-middleware (#49)

* api: enrich provider statistifcs

* fix: docker monitor now uses container status

* Feat/auto schemas (#48)

* use auto generated schemas

* go version bump and dependencies upgrade

* clarify some error messages

---------

Co-authored-by: yusing <yusing@6uo.me>

* cleanup some loadbalancer code

* api: cleanup websocket code

* api: add /v1/health/ws for health bubbles on dashboard

* feat: experimental memory logger and logs api for WebUI

---------

Co-authored-by: yusing <yusing@6uo.me>

---------

Co-authored-by: yusing <yusing@6uo.me>
Co-authored-by: Peter Olds <peter@olds.co>
2025-01-19 13:48:52 +08:00
yusing
0fad7b3411 feat: experimental memory logger and logs api for WebUI 2025-01-19 13:45:16 +08:00
yusing
1adba05065 api: add /v1/health/ws for health bubbles on dashboard 2025-01-19 04:34:20 +08:00
yusing
fe7740f1b0 api: cleanup websocket code 2025-01-19 04:33:55 +08:00
yusing
b253dce7e1 cleanup some loadbalancer code 2025-01-19 04:32:50 +08:00
Yuzerion
589b3a7a13 Feat/auto schemas (#48)
* use auto generated schemas

* go version bump and dependencies upgrade

* clarify some error messages

---------

Co-authored-by: yusing <yusing@6uo.me>
2025-01-19 00:37:17 +08:00
yusing
26d259b952 fix: docker monitor now uses container status 2025-01-15 09:16:36 +08:00
yusing
04e118c081 api: enrich provider statistifcs 2025-01-15 09:16:29 +08:00
yusing
2af2346e35 fix auth redirect 2025-01-13 08:41:09 +08:00
yusing
7cd44b5ad3 rename cookies to prevent conflict 2025-01-13 08:33:56 +08:00
yusing
81d96394b9 allow customizing OICD scopes 2025-01-13 08:30:46 +08:00
yusing
76fe5345d8 cleanup code, redirect to auth page when need 2025-01-13 07:15:29 +08:00
yusing
ef277ef57f fix: docker test and golangci-lint 2025-01-13 05:37:29 +08:00
Peter Olds
9a12dab600 fix: allow oauth_state token to be cross-domain (#40)
External OIDC providers won’t work with the current setup.
2025-01-13 05:27:06 +08:00
Yuzerion
51f6391ded feat: Add optional OIDC support (#39)
This allows the API to trigger an OAuth workflow to create the JWT for authentication. For now the workflow is triggered by manually visiting `/api/login/oidc` on the frontend app until the UI repo is updated to add support.

Co-authored-by: Peter Olds <peter@olds.co>
2025-01-13 04:49:46 +08:00
yusing
e10e6cfe4d updated ls-icon and icon fetching mechanism 2025-01-13 02:21:52 +08:00
yusing
d887a37f60 fix favicon on non http 200 2025-01-13 00:52:07 +08:00
yusing
1abd1e257f fix favicon path and try dashboard icon first then fallback to html parsing 2025-01-13 00:15:10 +08:00
yusing
137b0820b0 reset favicon cache on route reload 2025-01-12 22:32:17 +08:00
yusing
3f85d7f813 container now consider explicit if any proxy label defined 2025-01-12 22:31:43 +08:00
yusing
6b6dae129f fix route provider name 2025-01-12 13:49:47 +08:00
yusing
2c3672a7ea idlewatfcher: add proper Cache-Control Headers to response 2025-01-12 13:16:58 +08:00
yusing
645a58464c fix favicon redirection path 2025-01-12 13:14:31 +08:00
yusing
fcbb51dce7 fixed and improved favicon retrieving 2025-01-12 12:02:40 +08:00
yusing
c7c6a097f0 server side favicon retrieving and caching 2025-01-12 10:30:37 +08:00
yusing
0ce7f29976 fix proxy rules behavior and implemented a few more rules and commands, dependencies upgrade 2025-01-11 12:22:42 +08:00
yusing
f2df756c17 fix rule parser 2025-01-11 02:14:22 +08:00
yusing
28b5d44e11 fix: slice deserialization should return all errors 2025-01-11 01:39:03 +08:00
yusing
e7bb6bc798 fix bypass command 2025-01-10 06:48:41 +08:00
yusing
c572382f6a refactor query.go 2025-01-10 06:48:17 +08:00
yusing
e28c4a1b4d fix: rules escaped backslash 2025-01-09 19:59:53 +08:00
yusing
f5708fd539 add rule.on directives "cookie", "form", "postform" 2025-01-09 19:05:18 +08:00
yusing
5769abb626 fix: File.closeOnZero remove unnecessary for loop 2025-01-09 18:42:51 +08:00
yusing
4ebe0abba0 fix: bypass rules should not check first 2025-01-09 18:17:05 +08:00
yusing
8109c9ac4f small refactor 2025-01-09 14:09:48 +08:00
yusing
2ce1ceb460 remove old unused code 2025-01-09 14:09:48 +08:00
yusing
9d701ad671 add help messages to rules, updat url validation 2025-01-09 14:09:48 +08:00
yusing
4aee44fe11 fix rewrite omitting trailing slash, error msg update 2025-01-09 14:09:48 +08:00
yusing
adb41a80c5 support middleware cross referencing 2025-01-09 05:15:18 +08:00
yusing
642e6ebdc8 fix panic: Bad field name provided name 2025-01-09 04:44:55 +08:00
yusing
74828943a6 updated route rules implementation 2025-01-09 04:27:02 +08:00
yusing
f906e04581 fix access logger write on closed file after config reload 2025-01-09 04:26:31 +08:00
yusing
b3c47e759f fix incorrect reload behaviors, further organize code 2025-01-09 04:26:00 +08:00
yusing
8bbb5d2e09 fix fields not being validated (introduced in 577a536), drop support of list string not starting with hyphen 2025-01-09 04:21:32 +08:00
yusing
7fe03be73f fix: cert renewal failure cause scheduler stuck forver 2025-01-09 02:53:04 +08:00
yusing
abb0124011 readme and next release update 2025-01-08 14:03:40 +08:00
yusing
a98b2bb71a updated implementation of rules 2025-01-08 13:50:34 +08:00
yusing
bc1702e6cf refactoring: moved reverse_proxy to separate package to avoid import cycle 2025-01-08 13:50:34 +08:00
yusing
577a5366e8 remove unused old code 2025-01-08 13:50:34 +08:00
Peter Olds
7fedd5729e feat: Add optional StartEndpoint support for idle watcher
Optionally allow a user to specify a “warm-up” endpoint to start the container, returning a 403 if the endpoint isn’t hit and the container has been stopped.

This can help prevent bots from starting random containers, or allow health check systems to run some probes.
2025-01-08 11:01:10 +08:00
yusing
35c0463829 naive implementation of caddy like route rules, dependencies upgrade 2025-01-08 07:18:09 +08:00
yusing
1b40f81fcc remove next-release.md 2025-01-07 12:56:15 +08:00
yusing
afefd925ea api: updated list/get/set file endpoint 2025-01-07 10:57:53 +08:00
yusing
0850562bf9 fix nil panic on null entry 2025-01-06 04:58:11 +08:00
yusing
bc2335a54e update config example 2025-01-06 04:04:05 +08:00
yusing
5a9fc3ad18 healthcheck: should not include latency when ping failed 2025-01-06 04:03:59 +08:00
yusing
29f85db022 schema update and api /v1/schema 2025-01-06 00:49:29 +08:00
yusing
6034908a95 fix schemas 2025-01-05 15:03:03 +08:00
yusing
ef3dbc217b access log schema 2025-01-05 14:58:57 +08:00
yusing
01357617ae remove api ratelimiter 2025-01-05 12:13:20 +08:00
yusing
4775f4ea31 request/response middleware no longer canonicalize header key 2025-01-05 11:25:56 +08:00
yusing
ae7b27e1c9 fix udp not returning error correctly 2025-01-05 11:20:57 +08:00
yusing
70c8c4b4aa fix edge cases refCounter close channel twice 2025-01-05 09:15:03 +08:00
yusing
b7802f4e3e docs and makefile changes 2025-01-05 03:58:56 +08:00
yusing
6f35a6f5e9 api: also validate for middleware compose files 2025-01-05 03:29:03 +08:00
yusing
5e2ce9e1e6 fix stream task stuck on reload and udp mutex not unlocked properly 2025-01-05 03:26:31 +08:00
yusing
e04080bf1c update build files and dependencies 2025-01-05 03:16:59 +08:00
yusing
55134c8426 improved api error handling 2025-01-05 00:02:31 +08:00
yusing
0e886f5ddf fix alias not showing 2025-01-04 12:18:52 +08:00
yusing
1e97d1230a update config example, scheme and release readme 2025-01-04 11:07:38 +08:00
yusing
d44ce0ee6f dockerfile for local build, makefile update 2025-01-04 10:44:51 +08:00
yusing
c30d3f585f api: fix validation and http response 2025-01-04 09:01:52 +08:00
yusing
112859caa5 improved access log flushing 2025-01-04 05:08:23 +08:00
yusing
6b669fc540 api: homepage config json not longer include default url 2025-01-04 03:37:51 +08:00
yusing
cb9b7d55fd next release readme fix 2025-01-03 19:20:29 +08:00
yusing
c506db1ef4 refactor 2025-01-03 18:55:44 +08:00
yusing
65afc73f25 fix panic close on closed channel 2025-01-03 18:55:38 +08:00
yusing
7e60d1803c fix healthcheck last seen 2025-01-03 16:56:18 +08:00
yusing
3ecc0f95bf fixed some tests 2025-01-03 16:31:49 +08:00
yusing
c1db404c0d update next release readnme 2025-01-03 15:58:31 +08:00
yusing
b38bff41d8 support inline yaml for docker labels, serveral minor fixes 2025-01-03 15:35:40 +08:00
yusing
6e30d39b78 access logger support sharing the same file, tests added for concurrent logging 2025-01-03 14:10:09 +08:00
yusing
753e193d62 dependencies and tooling upgrade 2025-01-03 03:47:04 +08:00
yusing
4819972399 release filewatcher channels 2025-01-03 03:30:15 +08:00
yusing
ba8705fb84 fix shutdown stuck or panic 2025-01-03 03:30:15 +08:00
yusing
9f71fc2dd5 small refactor and update next-release readme 2025-01-03 03:30:15 +08:00
yusing
a587ada170 fix access logger high cpu usage, simplify some code 2025-01-03 03:30:15 +08:00
yusing
320e29ba84 fix loadbalancer panic on weight rebalance 2025-01-03 03:30:15 +08:00
yusing
cd74b76483 fix reload stuck 2025-01-03 03:30:07 +08:00
yusing
2fe0b888bd task package: replace waitgroup with channel, fix stuck 2025-01-02 11:12:13 +08:00
yusing
af14966b09 rewrite and fix reference counter 2025-01-02 09:59:31 +08:00
yusing
5fa0d47c0d more flexible domain matching 2025-01-01 17:05:43 +08:00
yusing
659ad29875 add timeout on task wait, temporary fix task stuck 2025-01-01 16:51:45 +08:00
yusing
a0a81240ce fix idlewatcher nil dereference 2025-01-01 14:25:44 +08:00
yusing
89f08f0da7 fix middleware loaded message 2025-01-01 06:22:20 +08:00
yusing
85c1a48d3a fix json marshal *route.Stream 2025-01-01 06:19:02 +08:00
yusing
846c1a104e small fix on task.finish 2025-01-01 06:16:33 +08:00
yusing
4dda54c9e6 access logger improvements 2025-01-01 06:09:35 +08:00
yusing
1ab34ed46f simplify task package implementation 2025-01-01 06:07:32 +08:00
yusing
e7aaa95ec5 dependencies upgrade 2024-12-24 01:37:15 +08:00
yusing
1042d12df6 fix notification dispatcher send on closed channel after disabling from config 2024-12-21 04:13:33 +08:00
yusing
751594860a fix docker health checker metrics missing from prometheus 2024-12-19 14:01:55 +08:00
yusing
84675b5c0f dependencies upgrade 2024-12-19 05:11:03 +08:00
yusing
e7be27413c small string split join optimization 2024-12-19 00:54:31 +08:00
yusing
654194b274 fix deserialization panics on empty map 2024-12-18 15:15:55 +08:00
yusing
36069cbe6d add host filter 2024-12-18 11:44:38 +08:00
yusing
34d5edd6b9 fix health lastSeen format 2024-12-18 10:49:33 +08:00
yusing
57a7c04a4c fix accesslog and serialization 2024-12-18 09:57:29 +08:00
yusing
87279688e6 fix middleware tracer and cloudflareRealIP 2024-12-18 09:03:12 +08:00
yusing
783b352e3b fixed json access logger 2024-12-18 08:01:58 +08:00
yusing
f683ab64ab fix realIP middleware not getting IP in some cases 2024-12-18 07:45:08 +08:00
yusing
942651dc16 add time field to json access log 2024-12-18 07:39:04 +08:00
yusing
2e86f8e6d8 add recursive option to cloudflareRealIP 2024-12-18 07:34:42 +08:00
yusing
c66694aa32 fix "do you mean" error formatting 2024-12-18 07:34:27 +08:00
yusing
f2a9ddd1a6 improved deserialization method 2024-12-18 07:18:18 +08:00
yusing
6aefe4d5d9 replace all schema check with go-playground/validator/v10 2024-12-18 04:48:29 +08:00
yusing
00f60a6e78 feature: accesslogger 2024-12-18 03:09:46 +08:00
yusing
34858a1ba0 fix prometheus metrics gone after route changes 2024-12-18 00:54:04 +08:00
yusing
4ae3d5344c go version 1.23.3 -> 1.23.4 2024-12-18 00:40:06 +08:00
yusing
276684f076 remove unnecessary encapsulation, setup branch updated to v0.8 2024-12-18 00:33:48 +08:00
yusing
2baeb6a572 dependencies upgrade 2024-12-17 10:40:31 +08:00
yusing
adb067a57f fix cert expiry date format 2024-12-17 10:35:59 +08:00
yusing
0995c8b839 fixed slice deserialization 2024-12-17 10:33:21 +08:00
yusing
0aa00ab226 replace Converter interface with string parser interface 2024-12-17 10:33:21 +08:00
yusing
c5d96f96e1 replace unnecessary Task interface with struct 2024-12-17 10:33:21 +08:00
yusing
4d94d12e9c fixed / suppressed (irrelevant) golangci-lint errors 2024-12-17 10:33:21 +08:00
yusing
d82594bf09 eliminate SonarCloud hardcoded IP complains 2024-12-17 10:33:21 +08:00
yusing
2f275ca81e add $upstream_name 2024-12-17 10:33:21 +08:00
yusing
59f4eaf3ea cleanup and simplify middleware implementations, refactor some other code 2024-12-17 10:33:21 +08:00
yusing
8a9cb2527e support deserialize into anonymous fields 2024-12-17 10:33:21 +08:00
yusing
e53d6d216d fix real ip should not modify XFF 2024-12-17 10:33:21 +08:00
yusing
ec78a92234 fix incorrect uppercase 2024-12-17 10:33:21 +08:00
yusing
f948d05b90 improved handling of visitor IPs for prometheus metrics 2024-12-17 10:33:21 +08:00
yusing
48430fd9c3 schema update and remove 'Origin' header from request 2024-12-13 15:48:15 +08:00
yusing
843d7b2231 refactor and dependencies upgrade 2024-12-13 15:22:31 +08:00
yusing
51b8806184 properly close docker client 2024-12-13 12:54:54 +08:00
yusing
68b2d79700 fix docker healthcheck formatting 2024-12-13 12:44:20 +08:00
yusing
17e8532e6f enrich health check result details 2024-12-13 12:35:59 +08:00
yusing
be81415a75 use docker healthcheck result if possible 2024-12-13 12:18:10 +08:00
yusing
b6c806a789 fix notif dispatcher nil panic 2024-12-13 00:46:45 +08:00
yusing
32871a8a3c dependencies upgrade 2024-12-11 07:21:07 +08:00
yusing
c6630a9f20 n8n compose example 2024-12-11 07:21:04 +08:00
yusing
2cbee10527 add $remote_host and $remote_port variables 2024-12-05 10:37:17 +08:00
yusing
aff8a3b401 fix modifyResponse middleware incorrect variable substitution 2024-12-05 10:31:48 +08:00
yusing
a9f6c4eb20 "visitor" prometheus metric 2024-12-05 08:54:48 +08:00
yusing
28d4373f67 fix potential issues with some websocket upstream servers 2024-12-04 06:09:52 +08:00
yusing
452bb0b0d7 http parameter tunings, dependencies upgrade 2024-12-04 06:02:54 +08:00
yusing
eabdd3de00 improved middleware variable subsititution 2024-12-04 01:58:17 +08:00
yusing
fcfb7a0105 README and example re-formatting 2024-12-03 11:51:13 +08:00
yusing
5d5c623f09 small refactor and fixes 2024-12-03 11:45:10 +08:00
yusing
cebc0c5405 support $resp_header(name) substitution 2024-12-03 11:09:30 +08:00
yusing
52d5e2f36d support x-properties 2024-12-03 10:28:47 +08:00
yusing
ef1863f810 support variables in modify request,response middlewares 2024-12-03 10:20:18 +08:00
yusing
cd749ac6a4 allow alias to match exact host 2024-12-02 05:01:55 +08:00
yusing
3f9d73d784 enable domain matching, removed unnecessary path_pattern check 2024-12-02 04:39:46 +08:00
yusing
58cfba7695 refactor and fix duplicate notification 2024-12-01 11:12:25 +08:00
yusing
d1cb7a5ce4 prevent generation of ACME key when not using autocert 2024-12-01 05:08:26 +08:00
yusing
863bb3f474 small update on reverse proxy and xforwarded middlewares 2024-12-01 05:04:57 +08:00
yusing
a4f44348ef fixed zero timeout causing health check to fail 2024-11-30 09:09:07 +08:00
yusing
51f9afb471 fixed redirectHTTP middleware 2024-11-30 08:58:30 +08:00
yusing
f8bdc7044c repalce redirect_to_https with entrypoint middleware 2024-11-30 08:50:23 +08:00
yusing
796a4a693a entrypoint middleware mutex 2024-11-30 08:50:02 +08:00
yusing
1c1ba1b55e [BREAKING] added entrypoint middleware support and config, config schema update 2024-11-30 08:02:03 +08:00
yusing
3af3a88f66 fix CIDRWhitelist status field 2024-11-30 08:01:15 +08:00
yusing
25eeabb9f9 [BREAKING] changed notification config format, support multiple notification providers, support webhook and markdown style notification 2024-11-30 06:44:49 +08:00
yusing
fb9de4c4ad added ping latency to healthcheck result 2024-11-30 06:43:47 +08:00
yusing
497879fb4b update serialization 2024-11-30 05:51:17 +08:00
yusing
6e9b5cc113 updated validation for middleware options 2024-11-30 04:00:55 +08:00
yusing
edc1ad952d updated deserialize method to support validation 2024-11-30 02:58:13 +08:00
yusing
4188bbc5bd small changes 2024-11-30 01:06:06 +08:00
yusing
10591452e4 schema update 2024-11-29 05:26:51 +08:00
yusing
c269bd04d3 updated autocert renew check logic 2024-11-29 05:26:05 +08:00
yusing
acdb324f7d autocert update:
- save ACME private key to reuse previous registered ACME account
- properly renew certificate with `Certificate.RenewWithOptions` instead of re-obtaining with `Certificate.Obtain`
2024-11-29 05:06:23 +08:00
yusing
d3842ec3c3 fixed loadbalancer panic 2024-11-28 07:15:27 +08:00
yusing
e1cac9f92f update validateSignal 2024-11-28 06:56:23 +08:00
yusing
4533cc592f fixed and updated tests 2024-11-28 06:52:26 +08:00
yusing
23614fe0d0 dependencies upgrade 2024-11-28 05:52:12 +08:00
yusing
d723403b6b modules reorganized and code refactor 2024-11-25 01:40:12 +08:00
yusing
f3b21e6bd9 refactor health module 2024-11-13 06:46:01 +08:00
yusing
6a2638c70c removed unnecessary PatchReverseProxy argument 2024-11-13 04:47:42 +08:00
yusing
b162dcbfbe updated incorrect metric help message 2024-11-13 04:46:31 +08:00
yusing
25a2de2a90 fixed stream route healthchecking wrong address 2024-11-11 06:48:10 +08:00
yusing
67b2286df0 fixed missing error subject 2024-11-11 06:47:47 +08:00
yusing
64728d10ad fixed incorrect healthcheck result in some cases, healthchecker now send user agent identifying godoxy 2024-11-11 06:37:05 +08:00
yusing
ae69019265 removed unnecessary mutex and locking, small refactor 2024-11-11 06:35:31 +08:00
yusing
c07f2ed722 fixed healthchecker start even if disabled, simplified label parsing 2024-11-11 06:34:12 +08:00
yusing
2951304647 fixed crash on invalid map value in docker labels 2024-11-11 06:17:23 +08:00
yusing
d936e24692 moved API request log to debug level 2024-11-11 01:32:55 +08:00
yusing
ba26e6a5d6 grafana dashboard template 2024-11-10 06:53:31 +08:00
yusing
6194bac4c4 metric unregistration on route removal, fixed multi-ips as visitor label detected from x headers 2024-11-10 06:47:59 +08:00
yusing
a1d1325ad6 updated health status impl 2024-11-10 06:35:56 +08:00
yusing
cceebff93a fixed CIDR whitelist shared its IP cache map when it should not 2024-11-10 03:25:33 +08:00
yusing
f97e3f65fe go version and deps update, fixed middlewares and metrics
- fixed "API JWT secret empty" warning output format
- fixed metrics initialized when it should not
- fixed middlewares.modifyRequest Host header not working properly
2024-11-08 06:14:08 +08:00
yusing
5214ae1760 uptime metrics 2024-11-07 11:44:01 +08:00
yusing
6be3aef367 changed req time elapsed to count on status code sent 2024-11-07 11:43:49 +08:00
yusing
6712e9b109 initial prometheus metrics support, simplfied some code 2024-11-06 12:24:12 +08:00
yusing
50a0686648 rebrand update Makefile 2024-11-06 05:06:17 +08:00
yusing
d47afa3081 removed extra ` from README 2024-11-06 05:05:46 +08:00
Yuzerion
1ddfe2fb92 Merge pull request #30 from codekoala/update-setup-instructions
Update setup instructions
2024-11-05 23:52:13 +08:00
Josh VanderLinden
3ae3d18566 Update setup instructions 2024-11-05 08:49:33 -05:00
yusing
5fdb171d65 rebrand changed startup message, built script and Dockerfile 2024-11-04 03:47:37 +08:00
yusing
99e43fe340 (non-breaking) rebrand changed environment variables 2024-11-04 03:29:26 +08:00
yusing
cf1ecbc826 added option to disable default app categories 2024-11-04 01:44:58 +08:00
yusing
5ff27b9e3d fixed extra output for ls-* commands 2024-11-04 01:32:26 +08:00
yusing
291304af75 readme update 2024-11-04 00:44:54 +08:00
yusing
b63ebfcb3b disabled auth by default (when no JWT secret is specified) 2024-11-04 00:32:19 +08:00
yusing
c6a9a816f6 copied default config into docker image, fixed ls-routes 2024-11-04 00:31:34 +08:00
yusing
f5cf716a91 rebrand as GoDoxy 2024-11-04 00:06:59 +08:00
yusing
6dbee61742 fixed wrong closing tag in example error page 2024-11-03 23:45:23 +08:00
yusing
d89d97b61f added predefined homepage categories 2024-11-03 11:44:30 +08:00
yusing
01b7ec2a99 API server now protected with rate limiter, fixed extra dot on URL on frontend 2024-11-03 10:11:27 +08:00
yusing
ddbee9ec19 added example error pages, removed compose examples 2024-11-03 09:56:47 +08:00
yusing
0bbadc6d6d removed default values 2024-11-03 07:34:16 +08:00
yusing
64584c73b2 rate limiter check host instead of full remote address 2024-11-03 07:16:59 +08:00
yusing
8df28628ec removed unnecessary pointer indirection, added rate limiter middleware 2024-11-03 07:07:30 +08:00
yusing
3bf520541b fixed stats websocket endpoint when no match_domains configured 2024-11-03 06:04:35 +08:00
yusing
a531896bd6 updated docker compose example 2024-11-03 05:27:27 +08:00
yusing
e005b42d18 readme indentation 2024-11-03 04:50:40 +08:00
yusing
1f6573b6da readme update 2024-11-03 04:43:17 +08:00
yusing
73af381c4c updated auto and manual setup guide 2024-11-03 04:38:09 +08:00
yusing
625bf4dfdc improved HTTP performance, especially when match_domains are used; api json string fix 2024-11-02 07:14:03 +08:00
yusing
46b4090629 moved env to .env.example, update setup method 2024-11-02 03:36:55 +08:00
yusing
91e012987e added option for jwt token ttl 2024-11-02 03:21:47 +08:00
yusing
a86d316d07 refactor and typo fixes 2024-11-02 03:14:47 +08:00
yusing
76454df5e6 remove useless dummytask 2024-11-02 03:14:25 +08:00
yusing
67b6e40f85 remove unused code 2024-11-02 03:04:15 +08:00
yusing
9889b5a8d3 correcting startup behavior 2024-11-02 03:00:14 +08:00
yusing
00fc75b61b update setup default branch 2024-10-31 03:07:03 +08:00
yusing
4ee93a1351 added logout endpoint 2024-10-31 00:58:35 +08:00
yusing
669d13b89a fixed crash on some base64 jwt secret 2024-10-30 11:09:52 +08:00
yusing
5fa86b5eb7 fixed auth internal server error 2024-10-30 10:19:30 +08:00
yusing
369cdf8c4f fixed config reload 2024-10-30 06:52:18 +08:00
yusing
0397f69853 fixed notification not being sent 2024-10-30 06:44:10 +08:00
yusing
81177926ff implemented login and jwt auth 2024-10-30 06:25:32 +08:00
yusing
e5bbb18414 migrated from logrus to zerolog, improved error formatting, fixed concurrent map write, fixed crash on rapid page refresh for idle containers, fixed infinite recursion on gotfiy error, fixed websocket connection problem when using idlewatcher 2024-10-29 11:34:58 +08:00
yusing
cfa74d69ae fixed out of range error on gotify message being sent 2024-10-22 16:46:12 +08:00
yusing
bee26f43d4 initial gotify support 2024-10-22 05:38:09 +08:00
yusing
a3ab32e9ab reload no longer be skipped when there're errors 2024-10-20 15:05:04 +08:00
yusing
c847fe4747 fixed schema and json tag, hide http://:0 2024-10-20 11:04:44 +08:00
yusing
a278711421 fixed loadbalancer with idlewatcher, fixed reload issue 2024-10-20 09:46:02 +08:00
yusing
01ffe0d97c simplified error messages 2024-10-19 14:25:28 +08:00
yusing
bd732dfa0a fixed homepage item not showing 2024-10-19 01:30:34 +08:00
yusing
8b8e1773e8 fixed loadbalanced routes with same alias cause conflict 2024-10-19 01:20:08 +08:00
yusing
b296fb2965 fixed healthcheck failed to disable and nil dereference 2024-10-19 00:13:55 +08:00
yusing
53557e38b6 Fixed a few issues:
- Incorrect name being shown on dashboard "Proxies page"
- Apps being shown when homepage.show is false
- Load balanced routes are shown on homepage instead of the load balancer
- Route with idlewatcher will now be removed on container destroy
- Idlewatcher panic
- Performance improvement
- Idlewatcher infinitely loading
- Reload stucked / not working properly
- Streams stuck on shutdown / reload
- etc...
Added:
- support idlewatcher for loadbalanced routes
- partial implementation for stream type idlewatcher
Issues:
- graceful shutdown
2024-10-18 16:47:01 +08:00
yusing
c0c61709ca fixed idlewatcher panic, dashboard app name, route not removing on container destroy 2024-10-16 00:57:10 +08:00
yusing
56b778f19c fixed json output for ls-routes and its API and homepage api 2024-10-15 16:23:46 +08:00
yusing
f4d532598c Improved healthcheck, idlewatcher support for loadbalanced routes, bug fixes 2024-10-15 15:34:27 +08:00
yusing
53fa28ae77 graceful shutdown and ref count related 2024-10-14 10:31:27 +08:00
yusing
f38b3abdbc improved health check 2024-10-14 10:02:53 +08:00
yusing
99207ae606 routes in loadbalance pool no longer listed in ls-route and its API, the loadbalancer is listed instead. improved context handling and grateful shutdown 2024-10-14 09:28:54 +08:00
yusing
d3b8cb8cba fixed possible memory leak for UDP stream 2024-10-14 08:57:46 +08:00
yusing
51c6eb4597 fixed problems related to reload and port selection 2024-10-14 08:55:49 +08:00
yusing
d47b672aa5 refactored some stuff, added healthcheck support, fixed 'include file' reload not showing in log 2024-10-12 13:56:38 +08:00
yusing
64e30f59e8 added missing json tags 2024-10-11 10:38:38 +08:00
yusing
cef7b3d396 updated tests for new behavior 2024-10-11 10:00:10 +08:00
yusing
7184c9cfe9 correcting some behaviors for $DOCKER_HOST, now uses container's private IP instead of localhost 2024-10-11 09:13:38 +08:00
yusing
da04a0dff4 added golangci-linting, refactor, simplified error msgs and fixed some error handling 2024-10-10 11:52:09 +08:00
yusing
d91b66ae87 performance improvement and small fix on loadbalancer 2024-10-09 18:10:51 +08:00
yusing
5c40f4aa84 added round_robin, least_conn and ip_hash load balance support, small refactoring 2024-10-09 10:39:07 +08:00
yusing
1797896fa6 fixed typos and formatting, fixed loading page not being shown (idlewaker) 2024-10-08 13:15:23 +08:00
yusing
d1c9e18c97 improved idlewatcher support for API-like services, fixed idlewaker proxying to zero port 2024-10-07 18:50:51 +08:00
yusing
ef83ed0596 improved idlewatcher and content type matching, update CI 2024-10-07 17:41:08 +08:00
yusing
d89155a6ee idlewatcher fixed idlewatcher incorrect respond haviour, keep url path 2024-10-07 16:44:18 +08:00
yusing
921ce23dde refactored http import name, fixed and simplified idlewatcher/idlewaker implementation, dependencies update 2024-10-07 12:45:07 +08:00
yusing
929b7f7059 get back aa6fafd5, accidentally reverted in 03cad9f3 2024-10-07 00:06:29 +08:00
yusing
de7805f281 fixed idlewatcher panics and incorrect behavior, update screenshot 2024-10-06 16:17:52 +08:00
yusing
03cad9f315 added package version api, dependencies upgrade 2024-10-06 09:23:41 +08:00
yusing
aa6fafd52f improved tracing for debug 2024-10-06 06:06:29 +08:00
yusing
01ff63a007 fix forward auth attempt#1 2024-10-06 03:18:06 +08:00
yusing
99746bad8e fix attempt#1: int64 not assignable to int 2024-10-06 02:02:13 +08:00
yusing
21b67e97af websocket fix attempt#2 2024-10-06 01:21:35 +08:00
yusing
668639e484 websocket fix attempt 2024-10-06 00:09:14 +08:00
yusing
e9b2079599 duration formatting update 2024-10-05 09:58:56 +08:00
yusing
5fb7d21c80 fixed that error message with sensitive info shouldn't be shown to end user 2024-10-05 03:42:09 +08:00
yusing
f5e00a6ef4 oops, adding back proxy.exclude=1 2024-10-04 19:07:48 +08:00
yusing
b06cbc0fee fixed dashboard stats update 2024-10-04 18:52:31 +08:00
yusing
abbcbad5e9 readme updates, docs moved to wiki 2024-10-04 11:27:11 +08:00
yusing
fab39a461f added ls-icons command 2024-10-04 10:04:18 +08:00
yusing
9c3edff92b databases without explicit alias(es) are now excluded by default 2024-10-04 09:17:45 +08:00
yusing
e8f4cd18a4 refactor: moved models/ to types/ 2024-10-04 08:47:53 +08:00
yusing
e566fd9b57 fixed homepage not respecting homepage.show field, disabled schema validation for included file 2024-10-04 08:36:32 +08:00
yusing
6211ddcdf0 show docker provider name instead of address in log 2024-10-04 07:21:49 +08:00
yusing
245f073350 tuned some http settings, refactor 2024-10-04 07:13:52 +08:00
yusing
dd629f516b omit EOF and contextCanceled error on non-debug mode 2024-10-04 06:55:43 +08:00
yusing
31080edd59 fixed event name missing 2024-10-04 06:51:26 +08:00
yusing
b679655cd5 fixed dashboard incorrect stats 2024-10-04 06:38:27 +08:00
yusing
ca3b062f89 updated schema for homepage fields 2024-10-04 01:00:06 +08:00
yusing
de6c1be51b improved homepage support, memory leak partial fix 2024-10-03 20:02:43 +08:00
yusing
4f09dbf044 replace - _ with whitespace for default homepage.name 2024-10-03 10:19:31 +08:00
yusing
e6b4630ce9 experimental homepage labels support 2024-10-03 10:10:14 +08:00
yusing
90bababd38 improved homepage labels 2024-10-03 04:00:02 +08:00
yusing
90130411f9 initial support of homepage labels 2024-10-03 02:53:05 +08:00
yusing
ae61a2335d added v1/list/match_domains 2024-10-03 02:13:34 +08:00
yusing
8329a8ea9c replacing label parser map with improved deserialization implementation, API host check now disabled when in debug mode 2024-10-03 01:50:49 +08:00
yusing
ef52ccb929 fixed api, fixed ListFiles function 2024-10-02 17:34:35 +08:00
yusing
ed9d8aab6f fixed docs 2024-10-02 17:33:41 +08:00
yusing
aa16287447 fixed route gone after container restart / Brename 2024-10-02 15:38:36 +08:00
yusing
a7a922308e fixed streams with zero port being served 2024-10-02 14:01:36 +08:00
yusing
ba13b81b0e fixed middleware implementation, added middleware tracing for easier debug 2024-10-02 13:55:41 +08:00
yusing
d172552fb0 fixed docs 2024-10-02 01:33:52 +08:00
yusing
2a8ab27fc1 fixed docs 2024-10-02 01:28:55 +08:00
yusing
e8c3e4c75f added cidr_whitelist middleware 2024-10-02 01:20:25 +08:00
yusing
ed887a5cfc fixed serialization and middleware compose 2024-10-02 01:04:34 +08:00
yusing
1bac96dc2a update docker test 2024-10-02 01:02:59 +08:00
yusing
c3b779a810 containers without port mapped will no longer be served 2024-10-01 17:18:17 +08:00
yusing
44cfd65f6c implement middleware compose 2024-10-01 16:38:07 +08:00
yusing
f5a36f94bb fixed error subject missing in some cases 2024-10-01 05:14:56 +08:00
yusing
e951194bee fixed route not being updated on restart, added experimental middleware compose support 2024-09-30 19:00:27 +08:00
yusing
478311fe9e fixed container routes not being loaded, added X-Forwarded-{Scheme,Proto,Host}, fixed containers with no mapping being served 2024-09-30 18:04:47 +08:00
yusing
48dd1397e8 remove sensitive info from debug logging 2024-09-30 16:32:58 +08:00
yusing
ebedbc931f enables add-x-forwarded by default, added hide-x-forwarded 2024-09-30 16:16:56 +08:00
yusing
9065d990e5 go-proxy ls-route now query api server first, then fallback to read from config file 2024-09-30 15:56:03 +08:00
yusing
b38d7595a7 fixed issue for container not being excluded on restart 2024-09-30 15:19:59 +08:00
yusing
860e914b90 added real_ip and cloudflare_real_ip middlewares, fixed that some middlewares does not work properly 2024-09-30 04:03:48 +08:00
yusing
ac3af49aa7 update compose example 2024-09-29 11:46:54 +08:00
yusing
415f169f48 added explicit only mode for docker provider, updated dependencies 2024-09-29 11:24:41 +08:00
yusing
e2b08d8667 ci speedup 2024-09-29 06:00:52 +08:00
yusing
91e7f4894a fixed some containers being excluded on restart 2024-09-28 12:34:06 +08:00
yusing
a78dba5191 added response message on invalid api request host 2024-09-28 11:55:26 +08:00
yusing
c7208c90c6 updated example 2024-09-28 11:51:47 +08:00
yusing
da6a2756fa custom error page enabled for default for non-exist routes and invalid host 2024-09-28 11:45:01 +08:00
yusing
9a6a66f5a8 fixing dockerfile 2024-09-28 09:58:08 +08:00
yusing
90487bfde6 restructured the project to comply community guideline, for others check release note 2024-09-28 09:51:34 +08:00
yusing
4120fd8d1c fixed unchecked integer conversion, fixed 'invalid host' bug, corrected error message 2024-09-28 01:20:18 +08:00
yusing
6f3a5ebe6e Update documentation for Docker labels and middlewares, now fields works for snake cases, camel cases, pascal cases 2024-09-27 23:44:45 +08:00
yusing
a935f200a3 removed config example from README, check config.example.yml for complete explaination 2024-09-27 09:59:36 +08:00
yusing
f474ae4f75 added support for a few middlewares, added match_domain option, changed index reference prefix from $ to #, etc. 2024-09-27 09:57:57 +08:00
yusing
345a4417a6 changing alias index prefix from '$' to '#', to avoid unquoted/unescaped dollar sign being treated as interpolation 2024-09-27 00:34:14 +08:00
yusing
8cca83723c added discord invite badge 2024-09-27 00:23:46 +08:00
Yuzerion
aa2fcd47c2 Update docker.md 2024-09-26 22:52:56 +08:00
Yuzerion
0580a7d3cd Update config.example.yml 2024-09-26 22:51:29 +08:00
Yuzerion
a43c242c66 fixing wrong format for config example 2024-09-26 22:50:56 +08:00
yusing
45d4b92fc6 Fixed missing error subject 2024-09-26 20:11:05 +08:00
yusing
72df9ff3e4 Initial abstract implementation of middlewares 2024-09-25 14:12:40 +08:00
yusing
48bf31fd0e refactor file names, readme updates, removed frontend submodule as it is being built independently 2024-09-25 11:22:25 +08:00
yusing
4ee5383f7d github ci fix attempt, speedup docker build on CI 2024-09-25 10:46:45 +08:00
yusing
33fb60a32d Refactor Docker CI workflow for multi-platform builds 2024-09-25 10:09:50 +08:00
yusing
d10d0e49fa Update default wake timeout to 30 seconds, fixed port selection, improved idlewatcher 2024-09-25 05:27:12 +08:00
yusing
dc3575c8fd Refactoring 2024-09-25 03:43:47 +08:00
yusing
17115cfb0b Refactor and fixed port and scheme assignment logic in FillMissingFields.
Fixed issue that when a container is stopped or network=host, it will be excluded.
2024-09-25 03:40:57 +08:00
yusing
498082f7e5 no longer exclude a stopped container that have user specified port 2024-09-25 00:56:33 +08:00
yusing
99216ffe59 fixed subdomain matching for sub-sub-subdomain and so on, now return 404 when subdomain is missing 2024-09-24 18:30:25 +08:00
yusing
f426dbc9cf removed save registration.json since it does not work properly 2024-09-24 18:18:37 +08:00
yusing
1c611cc9b9 example fix 2024-09-24 03:13:39 +08:00
yusing
dc43e26770 Format README for better clarity in setup instructions 2024-09-24 03:09:50 +08:00
yusing
79ae26f1b5 new simpler setup method, readme and doc update 2024-09-23 22:10:13 +08:00
yusing
109c2460fa fixed container being excluded in host network_mode 2024-09-23 20:34:46 +08:00
yusing
71e8e4a462 smarter scheme and port detection 2024-09-23 20:16:38 +08:00
yusing
8e2cc56afb Fixed nil dereferencing and added missing fields validation 2024-09-23 16:14:34 +08:00
yusing
6728bc39d2 fixed nil dereferencing 2024-09-23 07:19:47 +08:00
yusing
daca4b7735 shrink docker image size in half, adding back ForceColor for logrus 2024-09-23 05:34:50 +08:00
yusing
3b597eea29 support '0' listening port, readme update, showcase added 2024-09-23 04:09:56 +08:00
yusing
090b73d287 fixed tcp/udp I/O, deadlock, nil dereference; improved docker watcher, idlewatcher, loading page 2024-09-23 00:49:46 +08:00
yusing
96bce79e4b changed env GOPROXY_*_PORT to GOPROXY_*_ADDR, changed api server default to listen on localhost only, readme update 2024-09-22 06:06:24 +08:00
yusing
d9fd399e43 fix stuck loading in some scenerios for ls-* command line options 2024-09-22 05:01:36 +08:00
yusing
46281aa3b0 renamed ProxyEntry to RawEntry to avoid confusion with src/proxy/entry.go 2024-09-22 04:13:42 +08:00
yusing
d39b68bfd8 fixed possible resource leak 2024-09-22 04:11:02 +08:00
yusing
a11ce46028 added some docker compose examples; fixed defaults to wrong host; updated watcher behavior to retry connection every 3 secs until success or until cancelled 2024-09-22 04:00:08 +08:00
yusing
6388d9d44d fixed outputing error in ls-config, ls-routes, etc. 2024-09-21 18:47:38 +08:00
yusing
69361aea1b fixed host set to localhost even on remote docker, fixed one error in provider causing all routes not to load 2024-09-21 18:23:20 +08:00
yusing
26e2154c64 fixed startup crash for file provider 2024-09-21 17:22:17 +08:00
Yuzerion
a29bf880bc Update docker.md
Too sleepy...
2024-09-21 16:08:11 +08:00
Yuzerion
1f6d03bdbb Update compose.example.yml 2024-09-21 16:07:12 +08:00
Yuzerion
4a7d898b8e Update docker.md 2024-09-21 16:06:32 +08:00
Yuzerion
521b694aec Update docker.md 2024-09-21 15:56:39 +08:00
yusing
a351de7441 github CI fix attempt 2024-09-21 14:32:52 +08:00
yusing
ab2dc26b76 fixing udp stream listening on wrong port 2024-09-21 14:18:29 +08:00
yusing
9a81b13b67 fixing tcp/udp error on closing 2024-09-21 13:40:20 +08:00
yusing
626bd9666b check release 2024-09-21 12:45:56 +08:00
yusing
d7eab2ebcd fixing idlewatcher 2024-09-21 09:42:40 +08:00
yusing
e48b9bbb0a 新增繁中README (未完成) 2024-09-19 21:16:38 +08:00
yusing
339411530b v0.5.0-rc5: merge 2024-09-19 20:42:12 +08:00
yusing
4a2d42bfa9 v0.5.0-rc5: check release 2024-09-19 20:40:03 +08:00
Yuzerion
81da9ad83a small fix 2024-09-18 09:10:41 +08:00
yusing
be7a766cb2 v0.5.0-rc5: added proxy.exclude label, refactored some code 2024-09-17 17:56:41 +08:00
yusing
83d1d027c6 added TZ env to docker compose example 2024-09-17 12:36:13 +08:00
yusing
21fcceb391 v0.5.0-rc4: initial support for ovh, provider generator implementation update, replaced all interface{} to any 2024-09-17 12:06:58 +08:00
yusing
82f06374f7 v0.5.0-rc4: fixing autocert issue, cache ACME registration, added ls-config option 2024-09-17 08:41:36 +08:00
yusing
04fd6543fd README update for sonarcloud badges, simplify some test code, fixed some sonarlint issues 2024-09-17 04:51:26 +08:00
yusing
409a18df38 update default branch for setup script 2024-09-17 03:54:55 +08:00
yusing
4e5a8d0985 v0.5-rc3: version bump 2024-09-17 03:19:21 +08:00
yusing
16b507bc7c v0.5-rc3: update docker port detect mechanism, docker compose file and doc update 2024-09-17 03:11:04 +08:00
yusing
1120991019 v0.5-rc2: fixed port being overridden to 80 or 443 2024-09-17 00:30:26 +08:00
yusing
c0ebd9f8c0 v0.5-rc2: added reload cooldown, fixed auto reload, updated API 2024-09-17 00:10:25 +08:00
yusing
996b418ea9 v0.5-rc1: updated Dockerfile to conform latest format 2024-09-16 13:24:53 +08:00
yusing
4cddd4ff71 v0.5-rc1: schema fixes, provider file example update 2024-09-16 13:19:24 +08:00
yusing
7a0478164f v0.5: (BREAKING) replacing path with path_patterns, improved docker monitoring mechanism, bug fixes 2024-09-16 13:05:04 +08:00
yusing
2e7ba51521 v0.5: (BREAKING) new syntax for set_headers and hide_headers, updated label parser, error.Nil().String() will now return 'nil', better readme 2024-09-16 07:21:45 +08:00
yusing
5be8659a99 v0.5: (BREAKING) simplified config format, improved output formatting, fixed docker watcher 2024-09-16 03:48:39 +08:00
default
719693deb7 v0.5: (BREAKING) simplified config format, improved error output, updated proxy entry default value for 'port' 2024-08-14 02:41:11 +08:00
default
23e7d06081 v0.5: removed system service env, log output format fix 2024-08-13 06:00:22 +08:00
default
85fb637551 v0.5: fixed nil dereference for empty autocert config, fixed and simplified 'error' module, small readme and docs update 2024-08-13 04:59:34 +08:00
default
2fc82c3790 v0.5: remove binary build 2024-08-09 07:09:42 +08:00
default
a5a31a0d63 v0.5: readme and dockerfile update, removed old panel sources, added new frontend as submodule 2024-08-09 07:03:24 +08:00
default
73e481bc96 v0.5: dependencies update, EOF fix for PUT/POST /v1/file 2024-08-09 02:10:48 +08:00
default
93359110a2 preparing for v0.5 2024-08-01 10:06:42 +08:00
yusing
24778d1093 doc fix 2024-04-09 06:53:54 +00:00
yusing
830d0bdadd revert github workflow 2024-04-08 05:34:41 +00:00
yusing w
e12b356d0d Merge branch 'dev' into 'main'
Dev

See merge request yusing/go-proxy!3
2024-04-08 05:07:27 +00:00
yusing w
52549b6446 Dev 2024-04-08 05:07:27 +00:00
yusing
8694987ef9 version bump 2024-04-01 04:09:22 +00:00
yusing
b125b14bf6 added license 2024-04-01 04:08:48 +00:00
yusing
c782f365f9 readme update 2024-04-01 03:29:34 +00:00
yusing
72418a2056 added host network mode support, docs update, UDP fix 2024-04-01 03:23:30 +00:00
yusing
03bf425a38 fix selecting wrong port on remote docker provide 2024-03-31 21:33:09 +00:00
yusing
5fafa619ee version bump and binary doc fix 2024-03-31 16:28:48 +00:00
yusing
bebf99ed6c docker example update' 2024-03-31 11:45:23 +00:00
yusing
8483263d01 readme update 2024-03-31 11:32:16 +00:00
yusing
351bf84559 tcp/udp fix 2024-03-31 11:26:39 +00:00
yusing
cbe23d2ed1 tcp/udp fix 2024-03-31 07:04:08 +00:00
yusing
6e45f3683c docs fix 2024-03-30 00:39:05 +00:00
yusing
581894c05b binary setup script fix 2024-03-30 00:15:05 +00:00
yusing
2657b1f726 binary setup script fix 2024-03-29 23:07:17 +00:00
yusing
3505e8ff7e systemd service param update 2024-03-29 23:03:56 +00:00
yusing
2314e39291 workflow update 2024-03-29 22:53:20 +00:00
yusing
bd19f443d4 Merge branch 'main' of github.com:yusing/go-proxy 2024-03-29 22:43:13 +00:00
yusing
ce433f0c51 script update for auto version discovery, dockerfile fix for CI 2024-03-29 22:40:11 +00:00
Yuzerion
47877e5119 Merge pull request #12 from yusing/dev
0.4.4
2024-03-30 06:07:34 +08:00
yusing
486122f3d8 no timestamp, color and sorting in systemd mode 2024-03-29 21:45:29 +00:00
yusing
a0be1f11d3 script systemd auto restart on crash 2024-03-29 21:43:43 +00:00
yusing
662190e09e scripts fix 2024-03-29 21:29:06 +00:00
yusing
ce1e5da72e scripts fix 2024-03-29 21:22:51 +00:00
yusing
eb7e744a75 scripts fix 2024-03-29 21:20:18 +00:00
yusing
ac26baf97f scripts fix 2024-03-29 21:12:38 +00:00
yusing
5a8c11de16 docs update, added setup scripts 2024-03-29 21:02:21 +00:00
yusing
a8ecafcd09 workflow updte 2024-03-29 19:21:05 +00:00
Yuzerion
af37d1f29e Merge pull request #10 from yusing/test-go-workflow
Update go.yml
2024-03-30 03:06:16 +08:00
Yuzerion
8cfd24e6bd Update go.yml 2024-03-30 00:09:08 +08:00
Yuzerion
7bf5784016 Merge pull request #9 from yusing/test-go-workflow
Create go.yml
2024-03-30 00:01:49 +08:00
Yuzerion
25930a1a73 Create go.yml 2024-03-30 00:00:35 +08:00
Yuzerion
f20a1ff523 Merge pull request #7 from yusing/test-docker-image
Create docker-image.yml
2024-03-29 23:58:03 +08:00
Yuzerion
ba51796a64 Create docker-image.yml 2024-03-29 23:56:55 +08:00
yusing
c445d50221 smarter port selection 2024-03-29 13:55:28 +00:00
yusing
73dfc17a82 smarter port selection 2024-03-29 13:35:10 +00:00
yusing
fdab026a3b fix docker port discovery 2024-03-29 13:20:44 +00:00
yusing
c789c69c86 codemirror 5 fix for config edit 2024-03-29 13:13:26 +00:00
Yuzerion
2b298aa7fa Update README.md 2024-03-29 21:01:18 +08:00
yusing
d20e4d435a verify -> validate 2024-03-29 01:50:00 +00:00
yusing
15d9436d52 readme update 2024-03-29 01:47:13 +00:00
yusing
ca98b31458 fix default config value 2024-03-29 01:38:58 +00:00
yusing
77f957c7a8 makefile update 2024-03-29 01:31:51 +00:00
yusing
51493c9fdd makefile update 2024-03-29 01:28:44 +00:00
yusing
9b34dc994d added new file button in config editor, dockerfile fix 2024-03-29 01:24:47 +00:00
582 changed files with 79627 additions and 5511 deletions

75
.env.example Normal file
View File

@@ -0,0 +1,75 @@
# docker image tag (latest, nightly)
TAG=latest
# set timezone to get correct log timestamp
TZ=ETC/UTC
# container uid and gid (must match the owner of mounted directories)
GODOXY_UID=1000
GODOXY_GID=1000
# Set GODOXY_API_JWT_SECURE=false to allow http
GODOXY_API_JWT_SECURE=true
# API JWT Configuration (common)
# generate secret with `openssl rand -base64 32`
GODOXY_API_JWT_SECRET=
# the JWT token time-to-live
# leave empty to use default (24 hours)
# format: https://pkg.go.dev/time#Duration
GODOXY_API_JWT_TOKEN_TTL=
# API/WebUI user password login credentials (optional)
# These fields are not required for OIDC authentication
GODOXY_API_USER=admin
GODOXY_API_PASSWORD=password
# OIDC Configuration (optional)
# Uncomment and configure these values to enable OIDC authentication.
#
# GODOXY_OIDC_ISSUER_URL=https://accounts.google.com
# GODOXY_OIDC_CLIENT_ID=your-client-id
# GODOXY_OIDC_CLIENT_SECRET=your-client-secret
# GODOXY_OIDC_SCOPES=openid, profile, email, groups # you may also include `offline_access` if your Idp supports it (e.g. Authentik, Pocket ID)
#
# User definitions: Uncomment and configure these values to restrict access to specific users or groups.
# These two fields act as a logical AND operator. For example, given the following membership:
# user1, group1
# user2, group1
# user3, group2
# user1, group2
# You can allow access to user3 AND all users of group1 by providing:
# # GODOXY_OIDC_ALLOWED_USERS=user3
# # GODOXY_OIDC_ALLOWED_GROUPS=group1
#
# Comma-separated list of allowed users.
# GODOXY_OIDC_ALLOWED_USERS=user1,user2
# Optional: Comma-separated list of allowed groups.
# GODOXY_OIDC_ALLOWED_GROUPS=group1,group2
# Proxy listening address
GODOXY_HTTP_ADDR=:80
GODOXY_HTTPS_ADDR=:443
# Enable HTTP3
GODOXY_HTTP3_ENABLED=true
# API listening address
GODOXY_API_ADDR=127.0.0.1:8888
# Metrics
GODOXY_METRICS_DISABLE_CPU=false
GODOXY_METRICS_DISABLE_MEMORY=false
GODOXY_METRICS_DISABLE_DISK=false
GODOXY_METRICS_DISABLE_NETWORK=false
GODOXY_METRICS_DISABLE_SENSORS=false
# Frontend aliases (subdomains / FQDNs, e.g. godoxy, godoxy.domain.com)
GODOXY_FRONTEND_ALIASES=godoxy
# Docker socket
# /var/run/podman/podman.sock for podman
DOCKER_SOCKET=/var/run/docker.sock
SOCKET_PROXY_LISTEN_ADDR=127.0.0.1:2375
# Debug mode
GODOXY_DEBUG=false

15
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
# These are supported funding model platforms
github: yusing # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: yusingwysq # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

50
.github/workflows/agent-binary.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: GoDoxy agent binary
on:
push:
tags:
- v*
paths:
- "agent/**"
jobs:
build:
strategy:
matrix:
include:
- runner: ubuntu-latest
platform: linux/amd64
binary_name: godoxy-agent-linux-amd64
- runner: ubuntu-24.04-arm
platform: linux/arm64
binary_name: godoxy-agent-linux-arm64
name: Build ${{ matrix.platform }}
runs-on: ${{ matrix.runner }}
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Verify dependencies
run: go mod verify
- name: Build
run: |
make agent=1 NAME=${{ matrix.binary_name }} build
- name: Check binary
run: |
file bin/${{ matrix.binary_name }}
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.binary_name }}
path: bin/${{ matrix.binary_name }}
- name: Upload to release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
files: bin/${{ matrix.binary_name }}

View File

@@ -0,0 +1,24 @@
name: Docker Image CI (nightly)
on:
push:
branches:
- "*" # matches every branch that doesn't contain a '/'
- "*/*" # matches every branch containing a single '/'
- "**" # matches every branch
- "!dependabot/*"
- "!main" # excludes main
jobs:
build-nightly:
uses: ./.github/workflows/docker-image.yml
with:
image_name: ${{ github.repository_owner }}/godoxy
tag: nightly
target: main
build-nightly-agent:
uses: ./.github/workflows/docker-image.yml
with:
image_name: ${{ github.repository_owner }}/godoxy-agent
tag: nightly
target: agent

20
.github/workflows/docker-image-prod.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: Docker Image CI
on:
push:
tags:
- v*
jobs:
build-prod:
uses: ./.github/workflows/docker-image.yml
with:
image_name: ${{ github.repository_owner }}/godoxy
tag: latest
target: main
build-prod-agent:
uses: ./.github/workflows/docker-image.yml
with:
image_name: ${{ github.repository_owner }}/godoxy-agent
tag: latest
target: agent

View File

@@ -0,0 +1,22 @@
name: Docker Image CI (socket-proxy)
on:
push:
branches:
- main
paths:
- "socket-proxy/**"
- "socket-proxy.Dockerfile"
- ".github/workflows/docker-image-socket-proxy.yml"
tags-ignore:
- "**"
workflow_dispatch:
jobs:
build:
uses: ./.github/workflows/docker-image.yml
with:
image_name: ${{ github.repository_owner }}/socket-proxy
tag: latest
target: socket-proxy
dockerfile: socket-proxy.Dockerfile

158
.github/workflows/docker-image.yml vendored Normal file
View File

@@ -0,0 +1,158 @@
name: Docker Image CI
on:
workflow_call:
inputs:
tag:
required: true
type: string
image_name:
required: true
type: string
target:
required: true
type: string
dockerfile:
required: false
type: string
default: Dockerfile
env:
REGISTRY: ghcr.io
MAKE_ARGS: ${{ inputs.target }}=1
DIGEST_PATH: /tmp/digests/${{ inputs.target }}
DIGEST_NAME_SUFFIX: ${{ inputs.target }}
DOCKERFILE: ${{ inputs.dockerfile }}
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
platform: linux/amd64
- runner: ubuntu-24.04-arm
platform: linux/arm64
name: Build ${{ matrix.platform }}
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
id-token: write
attestations: write
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ inputs.image_name }}
tags: |
type=raw,value=${{ inputs.tag }},event=branch
type=ref,event=tag
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: ${{ matrix.platform }}
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
file: ${{ env.DOCKERFILE }}
outputs: type=image,name=${{ env.REGISTRY }}/${{ inputs.image_name }},push-by-digest=true,name-canonical=true,push=true
cache-from: |
type=registry,ref=${{ env.REGISTRY }}/${{ inputs.image_name }}:buildcache-${{ env.PLATFORM_PAIR }}
# type=gha,scope=${{ github.workflow }}-${{ env.PLATFORM_PAIR }}
cache-to: |
type=registry,ref=${{ env.REGISTRY }}/${{ inputs.image_name }}:buildcache-${{ env.PLATFORM_PAIR }},mode=max
# type=gha,scope=${{ github.workflow }}-${{ env.PLATFORM_PAIR }},mode=max
build-args: |
VERSION=${{ github.ref_name }}
MAKE_ARGS=${{ env.MAKE_ARGS }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.REGISTRY }}/${{ inputs.image_name }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
- name: Export digest
run: |
mkdir -p ${{ env.DIGEST_PATH }}
digest="${{ steps.build.outputs.digest }}"
touch "${{ env.DIGEST_PATH }}/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}-${{ env.DIGEST_NAME_SUFFIX }}
path: ${{ env.DIGEST_PATH }}/*
if-no-files-found: error
retention-days: 1
merge:
needs: build
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ env.DIGEST_PATH }}
pattern: digests-*-${{ env.DIGEST_NAME_SUFFIX }}
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ inputs.image_name }}
tags: |
type=raw,value=${{ inputs.tag }},event=branch
type=ref,event=tag
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create manifest list and push
id: push
working-directory: ${{ env.DIGEST_PATH }}
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY }}/${{ inputs.image_name }}@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ inputs.image_name }}:${{ steps.meta.outputs.version }}

View File

@@ -0,0 +1,39 @@
name: Cherry-pick into Compat
on:
push:
tags:
- v*
paths:
- ".github/workflows/merge-main-into-compat.yml"
jobs:
cherry-pick:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure git user
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Cherry-pick commits from last tag
run: |
git fetch origin compat
git checkout compat
CURRENT_TAG=${{ github.ref_name }}
PREV_TAG=$(git describe --tags --abbrev=0 $CURRENT_TAG^ 2>/dev/null || echo "")
if [ -z "$PREV_TAG" ]; then
echo "No previous tag found. Cherry-picking all commits up to $CURRENT_TAG"
git rev-list --reverse --no-merges $CURRENT_TAG | xargs -r git cherry-pick
else
echo "Cherry-picking commits from $PREV_TAG to $CURRENT_TAG"
git rev-list --reverse --no-merges $PREV_TAG..$CURRENT_TAG | xargs -r git cherry-pick
fi
- name: Push compat
run: |
git push origin compat

41
.gitignore vendored
View File

@@ -1,10 +1,43 @@
compose.yml
*.compose.yml
config/**
bin/go-proxy.bak
config
certs
config*/
!schemas/**
certs*/
bin/
error_pages/
!examples/error_pages/
profiles/
data/
debug/
logs/
log/
config-editor/
.vscode/settings.json
go.work.sum
!cmd/**/
!internal/**/
todo.md
.*.swp
.aider*
mtrace.json
.env
*.env
.cursorrules
.cursor/
.windsurfrules
test.Dockerfile
node_modules/
tsconfig.tsbuildinfo
!agent.compose.yml
!agent/pkg/**
dev-data/

15
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,15 @@
build-image:
image: docker
rules:
- if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
variables:
CI_REGISTRY_IMAGE: $CI_REGISTRY_IMAGE:latest
- if: $CI_COMMIT_REF_NAME != $CI_DEFAULT_BRANCH
variables:
CI_REGISTRY_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_BRANCH
before_script:
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER $CI_REGISTRY --password-stdin
script:
- echo building $CI_REGISTRY_IMAGE
- docker build --no-cache --build-arg VERSION=$CI_COMMIT_REF_NAME -t $CI_REGISTRY_IMAGE .
- docker push $CI_REGISTRY_IMAGE

12
.gitmodules vendored
View File

@@ -1,3 +1,9 @@
[submodule "templates/codemirror"]
path = templates/codemirror
url = https://github.com/codemirror/codemirror5.git
[submodule "internal/gopsutil"]
path = internal/gopsutil
url = https://github.com/godoxy-app/gopsutil.git
[submodule "internal/go-oidc"]
path = internal/go-oidc
url = https://github.com/godoxy-app/go-oidc.git
[submodule "goutils"]
path = goutils
url = https://github.com/yusing/goutils.git

151
.golangci.yml Normal file
View File

@@ -0,0 +1,151 @@
version: "2"
linters:
default: all
disable:
# - bodyclose
- containedctx
# - contextcheck
- cyclop
- depguard
# - dupl
- err113
- exhaustive
- exhaustruct
- funcorder
- forcetypeassert
- gochecknoglobals
- gochecknoinits
- gocognit
- goconst
- gocyclo
- godot
- gomoddirectives
- gosmopolitan
- ireturn
- lll
- maintidx
- makezero
- mnd
- nakedret
- nestif
- nlreturn
- nonamedreturns
- noinlineerr
- paralleltest
- revive
- rowserrcheck
- sqlclosecheck
- tagalign
- tagliatelle
- testpackage
- tparallel
- varnamelen
- wrapcheck
- wsl
- wsl_v5
settings:
errcheck:
exclude-functions:
- fmt.Fprintln
forbidigo:
forbid:
- pattern: ^print(ln)?$
funlen:
lines: -1
statements: 120
gocyclo:
min-complexity: 14
godox:
keywords:
- FIXME
gomoddirectives:
replace-allow-list:
- github.com/abbot/go-http-auth
- github.com/gorilla/mux
- github.com/mailgun/minheap
- github.com/mailgun/multibuf
- github.com/jaguilar/vt100
- github.com/cucumber/godog
- github.com/http-wasm/http-wasm-host-go
govet:
disable:
- shadow
enable-all: true
misspell:
locale: US
revive:
rules:
- name: struct-tag
- name: blank-imports
- name: context-as-argument
- name: context-keys-type
- name: error-return
- name: error-strings
- name: error-naming
- name: exported
disabled: true
- name: if-return
- name: increment-decrement
- name: var-naming
- name: var-declaration
- name: package-comments
disabled: true
- name: range
- name: receiver-naming
- name: time-naming
- name: unexported-return
- name: indent-error-flow
- name: errorf
- name: empty-block
- name: superfluous-else
- name: unused-parameter
disabled: true
- name: unreachable-code
- name: redefines-builtin-id
staticcheck:
checks:
- all
- -SA1019
dot-import-whitelist:
- github.com/yusing/godoxy/internal/utils/testing
tagalign:
align: false
sort: true
order:
- description
- json
- toml
- yaml
- yml
- label
- label-slice-as-struct
- file
- kv
- export
testifylint:
disable:
- suite-dont-use-pkg
- require-error
- go-require
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- gofumpt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

9
.trunk/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
*out
*logs
*actions
*notifications
*tools
plugins
user_trunk.yaml
user.yaml
tmp

42
.trunk/trunk.yaml Normal file
View File

@@ -0,0 +1,42 @@
# This file controls the behavior of Trunk: https://docs.trunk.io/cli
# To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml
version: 0.1
cli:
version: 1.25.0
# Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins)
plugins:
sources:
- id: trunk
ref: v1.7.2
uri: https://github.com/trunk-io/plugins
# Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes)
runtimes:
enabled:
- node@22.16.0
- python@3.10.8
- go@1.24.3
# This is the section where you manage your linters. (https://docs.trunk.io/check/configuration)
lint:
disabled:
- markdownlint
- yamllint
enabled:
- checkov@3.2.471
- golangci-lint2@2.5.0
- hadolint@2.14.0
- actionlint@1.7.7
- git-diff-check
- gofmt@1.20.4
- osv-scanner@2.2.2
- oxipng@9.1.5
- prettier@3.6.2
- shellcheck@0.11.0
- shfmt@3.6.0
- trufflehog@3.90.8
actions:
disabled:
- trunk-announce
- trunk-check-pre-push
- trunk-fmt-pre-commit
enabled:
- trunk-upgrade-available

11
.vscode/settings.example.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"yaml.schemas": {
"https://github.com/yusing/godoxy-webui/raw/refs/heads/main/types/godoxy/config.schema.json": [
"config.example.yml",
"config.yml"
],
"https://github.com/yusing/godoxy-webui/raw/refs/heads/main/types/godoxy/routes.schema.json": [
"providers.example.yml"
]
}
}

14
.vscode/settings.json vendored
View File

@@ -1,14 +0,0 @@
{
"go.inferGopath": false,
"yaml.schemas": {
"https://github.com/yusing/go-proxy/raw/main/schema/config.schema.json": [
"config.example.yml",
"config.yml",
"file:///config/workspace/go-proxy/config.example.yml"
],
"https://github.com/yusing/go-proxy/raw/main/schema/providers.schema.json": [
"providers.example.yml",
"*.providers.yml"
]
}
}

View File

@@ -1,22 +1,70 @@
FROM alpine:latest
# Stage 1: deps
FROM golang:1.25.5-alpine AS deps
HEALTHCHECK NONE
# package version does not matter
# trunk-ignore(hadolint/DL3018)
RUN apk add --no-cache tzdata make libcap-setcap
ENV GOPATH=/root/go
ENV GOCACHE=/root/.cache/go-build
WORKDIR /src
COPY goutils/go.mod goutils/go.sum ./goutils/
COPY internal/go-oidc/go.mod internal/go-oidc/go.sum ./internal/go-oidc/
COPY internal/gopsutil/go.mod internal/gopsutil/go.sum ./internal/gopsutil/
COPY go.mod go.sum ./
# remove godoxy stuff from go.mod first
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/root/go/pkg/mod \
sed -i '/^module github\.com\/yusing\/godoxy/!{/github\.com\/yusing\/godoxy/d}' go.mod && \
sed -i '/^module github\.com\/yusing\/goutils/!{/github\.com\/yusing\/goutils/d}' go.mod && \
go mod download -x
# Stage 2: builder
FROM deps AS builder
WORKDIR /src
COPY go.mod go.sum ./
COPY Makefile ./
COPY cmd ./cmd
COPY internal ./internal
COPY pkg ./pkg
COPY agent ./agent
COPY socket-proxy ./socket-proxy
COPY goutils ./goutils
ARG VERSION
ENV VERSION=${VERSION}
ARG MAKE_ARGS
ENV MAKE_ARGS=${MAKE_ARGS}
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/root/go/pkg/mod \
make ${MAKE_ARGS} docker=1 build
# Stage 3: Final image
FROM scratch
LABEL maintainer="yusing@6uo.me"
LABEL proxy.exclude=1
LABEL proxy.#1.healthcheck.disable=true
RUN apk add --no-cache bash tzdata
RUN mkdir /app
COPY bin/go-proxy entrypoint.sh /app/
COPY templates/ /app/templates
COPY config.example.yml /app/config/config.yml
# copy timezone data
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
RUN chmod +x /app/go-proxy /app/entrypoint.sh
ENV DOCKER_HOST unix:///var/run/docker.sock
ENV GOPROXY_DEBUG 0
ENV GOPROXY_REDIRECT_HTTP 1
# copy binary
COPY --from=builder /app/run /app/run
EXPOSE 80
EXPOSE 8080
EXPOSE 443
EXPOSE 8443
# copy certs
COPY --from=builder /etc/ssl/certs /etc/ssl/certs
ENV DOCKER_HOST=unix:///var/run/docker.sock
WORKDIR /app
ENTRYPOINT /app/entrypoint.sh
CMD ["/app/run"]

11
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,11 @@
node {
stage('SCM') {
checkout scm
}
stage('SonarQube Analysis') {
def scannerHome = tool 'SonarScanner';
withSonarQubeEnv() {
sh "${scannerHome}/bin/sonar-scanner"
}
}
}

45
LICENSE Normal file
View File

@@ -0,0 +1,45 @@
MIT License
Copyright (c) 2024 - present Yusing
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
internal/net/gphttp/reverseproxy/reverse_proxy_mod.go is copied from et/http/httputil/reverseproxy.go with modifications to adapt to this project.
Copyright 2011 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
---
internal/utils/io.go has a modified version of io.Copy with context and HTTP flusher handling.
Copyright 2009 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
---
internal/utils/strutils/split_join.go is copied from strings.Split and strings.Join with modifications to adapt to this project.
Copyright 2009 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.

197
Makefile
View File

@@ -1,35 +1,180 @@
.PHONY: all build up quick-restart restart logs get udp-server
shell := /bin/sh
export VERSION ?= $(shell git describe --tags --abbrev=0)
export BUILD_DATE ?= $(shell date -u +'%Y%m%d-%H%M')
export GOOS = linux
all: build quick-restart logs
REPO_URL ?= https://github.com/yusing/godoxy
WEBUI_DIR ?= ../godoxy-webui
DOCS_DIR ?= ${WEBUI_DIR}/wiki
GO_TAGS = sonic
LDFLAGS = -X github.com/yusing/goutils/version.version=${VERSION} -checklinkname=0
ifeq ($(agent), 1)
NAME = godoxy-agent
PWD = ${shell pwd}/agent
else ifeq ($(socket-proxy), 1)
NAME = godoxy-socket-proxy
PWD = ${shell pwd}/socket-proxy
else
NAME = godoxy
PWD = ${shell pwd}
endif
ifeq ($(trace), 1)
debug = 1
GODOXY_TRACE ?= 1
GODEBUG = gctrace=1 inittrace=1 schedtrace=3000
endif
ifeq ($(race), 1)
CGO_ENABLED = 1
GODOXY_DEBUG = 1
GO_TAGS += debug
BUILD_FLAGS += -race
else ifeq ($(debug), 1)
CGO_ENABLED = 1
GODOXY_DEBUG = 1
GO_TAGS += debug
# FIXME: BUILD_FLAGS += -asan -gcflags=all='-N -l'
else ifeq ($(pprof), 1)
CGO_ENABLED = 0
GORACE = log_path=logs/pprof strip_path_prefix=$(shell pwd)/ halt_on_error=1
GO_TAGS += pprof
VERSION := ${VERSION}-pprof
else
CGO_ENABLED = 0
LDFLAGS += -s -w
GO_TAGS += production
BUILD_FLAGS += -pgo=auto
endif
BUILD_FLAGS += -tags '$(GO_TAGS)' -ldflags='$(LDFLAGS)'
BIN_PATH := $(shell pwd)/bin/${NAME}
export NAME
export CGO_ENABLED
export GODOXY_DEBUG
export GODOXY_TRACE
export GODEBUG
export GORACE
export BUILD_FLAGS
ifeq ($(shell id -u), 0)
SETCAP_CMD = setcap
else
SETCAP_CMD = sudo setcap
endif
# CAP_NET_BIND_SERVICE: permission for binding to :80 and :443
POST_BUILD = $(SETCAP_CMD) CAP_NET_BIND_SERVICE=+ep ${BIN_PATH};
ifeq ($(docker), 1)
POST_BUILD += mkdir -p /app && mv ${BIN_PATH} /app/run;
endif
.PHONY: debug
test:
CGO_ENABLED=1 go test -v -race ${BUILD_FLAGS} ./internal/...
docker-build-test:
docker build -t godoxy .
docker build --build-arg=MAKE_ARGS=agent=1 -t godoxy-agent .
docker build --build-arg=MAKE_ARGS=socket-proxy=1 -t godoxy-socket-proxy .
go_ver := $(shell go version | cut -d' ' -f3 | cut -d'o' -f2)
files := $(shell find . -name go.mod -type f -or -name Dockerfile -type f)
gomod_paths := $(shell find . -name go.mod -type f | xargs dirname)
update-go:
for file in ${files}; do \
echo "updating $$file"; \
sed -i 's|go \([0-9]\+\.[0-9]\+\.[0-9]\+\)|go ${go_ver}|g' $$file; \
sed -i 's|FROM golang:.*-alpine|FROM golang:${go_ver}-alpine|g' $$file; \
done
for path in ${gomod_paths}; do \
echo "go mod tidy $$path"; \
cd ${PWD}/$$path && go mod tidy; \
done
update-deps:
for path in ${gomod_paths}; do \
echo "go get -u $$path"; \
cd ${PWD}/$$path && go get -u ./... && go mod tidy; \
done
mod-tidy:
for path in ${gomod_paths}; do \
echo "go mod tidy $$path"; \
cd ${PWD}/$$path && go mod tidy; \
done
build:
mkdir -p bin
CGO_ENABLED=0 GOOS=linux go build -pgo=auto -o bin/go-proxy src/go-proxy/*.go
mkdir -p $(shell dirname ${BIN_PATH})
go build -C ${PWD} ${BUILD_FLAGS} -o ${BIN_PATH} ./cmd
${POST_BUILD}
up:
docker compose up -d --build go-proxy
run:
cd ${PWD} && [ -f .env ] && godotenv -f .env go run ${BUILD_FLAGS} ./cmd
quick-restart: # quick restart without restarting the container
docker cp bin/go-proxy go-proxy:/app/go-proxy
docker cp templates/* go-proxy:/app/templates
docker cp entrypoint.sh go-proxy:/app/entrypoint.sh
docker exec -d go-proxy bash /app/entrypoint.sh restart
dev:
docker compose -f dev.compose.yml $(args)
restart:
docker kill go-proxy
docker compose up -d go-proxy
dev-build: build
docker compose -f dev.compose.yml up -t 0 -d app --force-recreate
logs:
tail -f log/go-proxy.log
benchmark:
@if [ -z "$(TARGET)" ]; then \
docker compose -f dev.compose.yml up -d --force-recreate godoxy traefik caddy nginx; \
else \
docker compose -f dev.compose.yml up -d --force-recreate $(TARGET); \
fi
sleep 1
@./scripts/benchmark.sh
get:
go get -d -u ./src/go-proxy
dev-run: build
cd dev-data && ${BIN_PATH}
udp-server:
docker run -it --rm \
-p 9999:9999/udp \
--label proxy.test-udp.scheme=udp \
--label proxy.test-udp.port=20003:9999 \
--network data_default \
--name test-udp \
$$(docker build -q -f udp-test-server.Dockerfile .)
mtrace:
${BIN_PATH} debug-ls-mtrace > mtrace.json
rapid-crash:
docker run --restart=always --name test_crash -p 80 debian:bookworm-slim /bin/cat &&\
sleep 3 &&\
docker rm -f test_crash
debug-list-containers:
bash -c 'echo -e "GET /containers/json HTTP/1.0\r\n" | sudo netcat -U /var/run/docker.sock | tail -n +9 | jq'
ci-test:
mkdir -p /tmp/artifacts
act -n --artifact-server-path /tmp/artifacts -s GITHUB_TOKEN="$$(gh auth token)"
cloc:
scc -w -i go --not-match '_test.go$$'
push-github:
git push origin $(shell git rev-parse --abbrev-ref HEAD)
gen-swagger:
# go install github.com/swaggo/swag/cmd/swag@latest
swag init --parseDependency --parseInternal --parseFuncBody -g handler.go -d internal/api -o internal/api/v1/docs
python3 scripts/fix-swagger-json.py
# we don't need this
rm internal/api/v1/docs/docs.go
gen-swagger-markdown: gen-swagger
# brew tap go-swagger/go-swagger && brew install go-swagger
swagger generate markdown -f internal/api/v1/docs/swagger.yaml --skip-validation --output ${DOCS_DIR}/src/API.md
gen-api-types: gen-swagger
# --disable-throw-on-error
bunx --bun swagger-typescript-api generate --sort-types --generate-union-enums --axios --add-readonly --route-types \
--responses -o ${WEBUI_DIR}/lib -n api.ts -p internal/api/v1/docs/swagger.json
bunx --bun prettier --config ${WEBUI_DIR}/.prettierrc --write ${WEBUI_DIR}/lib/api.ts
.PHONY: update-wiki
update-wiki:
DOCS_DIR=${DOCS_DIR} REPO_URL=${REPO_URL} bun --bun scripts/update-wiki/main.ts

607
README.md
View File

@@ -1,476 +1,219 @@
# go-proxy
<div align="center">
A simple auto docker reverse proxy for home use. **Written in _Go_**
<img src="assets/godoxy.png" width="200">
In the examples domain `x.y.z` is used, replace them with your domain
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=yusing_go-proxy&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=yusing_go-proxy)
![GitHub last commit](https://img.shields.io/github/last-commit/yusing/godoxy)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=yusing_go-proxy&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=go-proxy)
![Demo](https://img.shields.io/website?url=https%3A%2F%2Fdemo.godoxy.dev&label=Demo&link=https%3A%2F%2Fdemo.godoxy.dev)
[![Discord](https://dcbadge.limes.pink/api/server/umReR62nRd?style=flat)](https://discord.gg/umReR62nRd)
A lightweight, simple, and performant reverse proxy with WebUI.
<h5>
<a href="https://docs.godoxy.dev">Website</a> | <a href="https://docs.godoxy.dev/Home.html">Wiki</a> | <a href="https://discord.gg/umReR62nRd">Discord</a>
</h5>
<h5>EN | <a href="README_CHT.md">中文</a></h5>
<img src="screenshots/webui.jpg" style="max-width: 650">
Have questions? Ask [ChatGPT](https://chatgpt.com/g/g-6825390374b481919ad482f2e48936a1-godoxy-assistant)! (Thanks to [@ismesid](https://github.com/arevindh))
</div>
## Table of content
- [go-proxy](#go-proxy)
- [Table of content](#table-of-content)
- [Key Points](#key-points)
- [How to use](#how-to-use)
- [Binary](#binary)
- [Docker](#docker)
- [Use JSON Schema in VSCode](#use-json-schema-in-vscode)
- [Configuration](#configuration)
- [Labels (docker)](#labels-docker)
- [Environment variables](#environment-variables)
- [Config File](#config-file)
- [Fields](#fields)
- [Provider Kinds](#provider-kinds)
- [Provider File](#provider-file)
- [Supported DNS Challenge Providers](#supported-dns-challenge-providers)
- [Examples](#examples)
- [Single port configuration example](#single-port-configuration-example)
- [Multiple ports configuration example](#multiple-ports-configuration-example)
- [TCP/UDP configuration example](#tcpudp-configuration-example)
- [Load balancing Configuration Example](#load-balancing-configuration-example)
- [Troubleshooting](#troubleshooting)
- [Benchmarks](#benchmarks)
- [Known issues](#known-issues)
- [Memory usage](#memory-usage)
- [Build it yourself](#build-it-yourself)
<!-- TOC -->
## Key Points
- [Table of content](#table-of-content)
- [Running demo](#running-demo)
- [Key Features](#key-features)
- [Prerequisites](#prerequisites)
- [Setup](#setup)
- [How does GoDoxy work](#how-does-godoxy-work)
- [Update / Uninstall system agent](#update--uninstall-system-agent)
- [Screenshots](#screenshots)
- [idlesleeper](#idlesleeper)
- [Metrics and Logs](#metrics-and-logs)
- [Manual Setup](#manual-setup)
- [Folder structrue](#folder-structrue)
- [Build it yourself](#build-it-yourself)
- [Star History](#star-history)
- Fast (See [benchmarks](#benchmarks))
- Auto certificate obtaining and renewal (See [Config File](#config-file) and [Supported DNS Challenge Providers](#supported-dns-challenge-providers))
- Auto detect reverse proxies from docker
- Custom proxy entries with `config.yml` and additional provider files
- Subdomain matching + Path matching **(domain name doesn't matter)**
- HTTP(s) proxy + TCP/UDP Proxy
- HTTP(s) round robin load balance support (same subdomain and path across different hosts)
- Auto hot-reload on container `start` / `die` / `stop` or config file changes
- Simple panel to see all reverse proxies and health available on port [panel_port_http] (http) and port [panel_port_https] (https)
## Running demo
![panel screenshot](screenshots/panel.png)
- Config editor to edit config and provider files with validation
<https://demo.godoxy.dev>
**Validate and save file with Ctrl+S**
[![Deployed on Zeabur](https://zeabur.com/deployed-on-zeabur-dark.svg)](https://zeabur.com/referral?referralCode=yusing&utm_source=yusing&utm_campaign=oss)
![config editor screenshot](screenshots/config_editor.png)
## Key Features
## How to use
- **Simple**
- Effortless configuration with [simple labels](https://docs.godoxy.dev/Docker-labels-and-Route-Files) or WebUI
- [Simple multi-node setup](https://docs.godoxy.dev/Configurations#multi-docker-nodes-setup)
- Detailed error messages for easy troubleshooting.
- **ACL**: connection / request level access control
- IP/CIDR
- Country **(Maxmind account required)**
- Timezone **(Maxmind account required)**
- **Access logging**
- Periodic notification of access summaries for number of allowed and blocked connections
- **Advanced Automation**
- Automatic SSL certificate management with Let's Encrypt ([using DNS-01 Challenge](https://docs.godoxy.dev/DNS-01-Providers))
- Auto-configuration for Docker containers
- Hot-reloading of configurations and container state changes
- **Container Runtime Support**
- Docker
- Podman
- **Idle-sleep**: stop and wake containers based on traffic _(see [screenshots](#idlesleeper))_
- Docker containers
- Proxmox LXCs
- **Traffic Management**
- HTTP reserve proxy
- TCP/UDP port forwarding
- **OpenID Connect support**: SSO and secure your apps easily
- **ForwardAuth support**: integrate with any auth provider (e.g. TinyAuth)
- **Customization**
- [HTTP middlewares](https://docs.godoxy.dev/Middlewares)
- [Custom error pages support](https://docs.godoxy.dev/Custom-Error-Pages)
- **Web UI**
- App Dashboard
- Config Editor
- Uptime and System Metrics
- Docker Logs Viewer
- **Cross-Platform support**
- Supports **linux/amd64** and **linux/arm64**
- **Efficient and Performant**
- Written in **[Go](https://go.dev)**
1. Download and extract the latest release (or clone the repository if you want to try out experimental features)
## Prerequisites
2. Copy `config.example.yml` to `config/config.yml` and modify the content to fit your needs
Configure Wildcard DNS Record(s) to point to machine running `GoDoxy`, e.g.
3. (Optional) write your own `config/providers.yml` from `providers.example.yml`
- A Record: `*.domain.com` -> `10.0.10.1`
- AAAA Record (if you use IPv6): `*.domain.com` -> `::ffff:a00:a01`
4. See [Binary](#binary) or [docker](#docker)
## Setup
### Binary
> [!NOTE]
> GoDoxy is designed to be running in `host` network mode, do not change it.
>
> To change listening ports, modify `.env`.
1. (Optional) enabled HTTPS
1. Prepare a new directory for docker compose and config files.
- Use autocert feature by completing `autocert` in `config.yml`
2. Run setup script inside the directory, or [set up manually](#manual-setup)
- Use existing certificate
```shell
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/yusing/godoxy/main/scripts/setup.sh)"
```
Prepare your wildcard (`*.y.z`) SSL cert in `certs/`
3. Start the docker compose service from generated `compose.yml`:
- cert / chain / fullchain: `certs/cert.crt`
- private key: `certs/priv.key`
```shell
docker compose up -d
```
2. run the binary `bin/go-proxy`
4. You may now do some extra configuration on WebUI `https://godoxy.yourdomain.com`
3. enjoy
## How does GoDoxy work
### Docker
1. List all the containers
2. Read container name, labels and port configurations for each of them
3. Create a route if applicable (a route is like a "Virtual Host" in NPM)
4. Watch for container / config changes and update automatically
1. Copy content from [compose.example.yml](compose.example.yml) and create your own `compose.yml`
> [!NOTE]
> GoDoxy uses the label `proxy.aliases` as the subdomain(s), if unset it defaults to the `container_name` field in docker compose.
>
> For example, with the label `proxy.aliases: qbt` you can access your app via `qbt.domain.com`.
2. Add networks to make sure it is in the same network with other containers, or make sure `proxy.<alias>.host` is reachable
## Update / Uninstall system agent
3. (Optional) enable HTTPS
Update:
- Use autocert feature by completing `autocert` section in `config/config.yml` and mount `certs/` to `/app/certs` in order to store obtained certs
- Use existing certificate by mount your wildcard (`*.y.z`) SSL cert
- cert / chain / fullchain -> `/app/certs/cert.crt`
- private key -> `/app/certs/priv.key`
4. Start `go-proxy` with `docker compose up -d` or `make up`.
5. (Optional) If you are using ufw with vpn that drop all inbound traffic except vpn, run below to allow docker containers to connect to `go-proxy`
In case the network of your container is in subnet `172.16.0.0/16` (bridge),
and vpn network is under `100.64.0.0/10` (i.e. tailscale)
`sudo ufw allow from 172.16.0.0/16 to 100.64.0.0/10`
You can also list CIDRs of all docker bridge networks by:
`docker network inspect $(docker network ls | awk '$3 == "bridge" { print $1}') | jq -r '.[] | .Name + " " + .IPAM.Config[0].Subnet' -`
6. start your docker app, and visit <container_name>.y.z
7. check the logs with `docker compose logs` or `make logs` to see if there is any error, check panel at [panel port] for active proxies
## Use JSON Schema in VSCode
Modify `.vscode/settings.json` to fit your needs
```json
{
"yaml.schemas": {
"https://github.com/yusing/go-proxy/raw/main/schema/config.schema.json": [
"config.example.yml",
"config.yml"
],
"https://github.com/yusing/go-proxy/raw/main/schema/providers.schema.json": [
"providers.example.yml",
"*.providers.yml",
]
}
}
```bash
bash -c "$(curl -fsSL https://github.com/yusing/godoxy/raw/refs/heads/main/scripts/install-agent.sh)" -- update
```
## Configuration
Uninstall:
With container name, no label needs to be added *(most of the time)*.
### Labels (docker)
See [compose.example.yml](compose.example.yml) for more
- `proxy.aliases`: comma separated aliases for subdomain matching
- default: container name
- `proxy.*.<field>`: wildcard label for all aliases
Below labels has a **`proxy.<alias>`** prefix (i.e. `proxy.nginx.scheme: http`)
- `scheme`: proxy protocol
- default: `http`
- allowed: `http`, `https`, `tcp`, `udp`
- `host`: proxy host
- default: `container_name`
- `port`: proxy port
- default: first expose port (declared in `Dockerfile` or `docker-compose.yml`)
- `http(s)`: number in range og `0 - 65535`
- `tcp/udp`: `[<listeningPort>:]<targetPort>`
- `listeningPort`: number, when it is omitted (not suggested), a free port starting from 20000 will be used.
- `targetPort`: number, or predefined names (see [constants.go:14](src/go-proxy/constants.go#L14))
- `no_tls_verify`: whether skip tls verify when scheme is https
- default: `false`
- `path`: proxy path _(http(s) proxy only)_
- default: empty
- `path_mode`: mode for path handling
- default: empty
- allowed: empty, `forward`, `sub`
- `empty`: remove path prefix from URL when proxying
1. apps.y.z/webdav -> webdav:80
2. apps.y.z./webdav/path/to/file -> webdav:80/path/to/file
- `forward`: path remain unchanged
1. apps.y.z/webdav -> webdav:80/webdav
2. apps.y.z./webdav/path/to/file -> webdav:80/webdav/path/to/file
- `sub`: (experimental) remove path prefix from URL and also append path to HTML link attributes (`src`, `href` and `action`) and Javascript `fetch(url)` by response body substitution
e.g. apps.y.z/app1 -> webdav:80, `href="/app1/path/to/file"` -> `href="/path/to/file"`
- `load_balance`: enable load balance (docker only)
- allowed: `1`, `true`
### Environment variables
- `GOPROXY_DEBUG`: set to `1` or `true` to enable debug behaviors (i.e. output, etc.)
- `GOPROXY_REDIRECT_HTTP`: set to `0` or `false` to disable http to https redirect (only when certs are located)
### Config File
See [config.example.yml](config.example.yml) for more
#### Fields
- `autocert`: autocert configuration
- `email`: ACME Email
- `domains`: a list of domains for cert registration
- `provider`: DNS Challenge provider, see [Supported DNS Challenge Providers](#supported-dns-challenge-providers)
- `options`: provider specific options
- `providers`: reverse proxy providers configuration
- `kind`: provider kind (string), see [Provider Kinds](#provider-kinds)
- `value`: provider specific value
#### Provider Kinds
- `docker`: load reverse proxies from docker
values:
- `FROM_ENV`: value from environment
- full url to docker host (i.e. `tcp://host:2375`)
- `file`: load reverse proxies from provider file
value: relative path of file to `config/`
### Provider File
Fields are same as [docker labels](#labels-docker) starting from `scheme`
See [providers.example.yml](providers.example.yml) for examples
### Supported DNS Challenge Providers
- Cloudflare
- `auth_token`: your zone API token
Follow [this guide](https://cloudkul.com/blog/automcatic-renew-and-generate-ssl-on-your-website-using-lego-client/) to create a new token with `Zone.DNS` read and edit permissions
To add more provider support (**CloudDNS** as an example):
1. Fork this repo, modify [autocert.go](src/go-proxy/autocert.go#L305)
```go
var providersGenMap = map[string]ProviderGenerator{
"cloudflare": providerGenerator(cloudflare.NewDefaultConfig, cloudflare.NewDNSProviderConfig),
// add here, i.e.
"clouddns": providerGenerator(clouddns.NewDefaultConfig, clouddns.NewDNSProviderConfig),
}
```
2. Go to [https://go-acme.github.io/lego/dns/clouddns](https://go-acme.github.io/lego/dns/clouddns/) and check for required config
3. Build `go-proxy` with `make build`
4. Set required config in `config.yml` `autocert` -> `options` section
```shell
# From https://go-acme.github.io/lego/dns/clouddns/
CLOUDDNS_CLIENT_ID=bLsdFAks23429841238feb177a572aX \
CLOUDDNS_EMAIL=you@example.com \
CLOUDDNS_PASSWORD=b9841238feb177a84330f \
lego --email you@example.com --dns clouddns --domains my.example.org run
```
Should turn into:
```yaml
autocert:
...
options:
client_id: bLsdFAks23429841238feb177a572aX
email: you@example.com
password: b9841238feb177a84330f
```
5. Run and test if it works
6. Commit and create pull request
## Examples
### Single port configuration example
```yaml
# (default) https://<container_name>.y.z
whoami:
image: traefik/whoami
container_name: whoami # => whoami.y.z
# enable both subdomain and path matching:
whoami:
image: traefik/whoami
container_name: whoami
labels:
- proxy.aliases=whoami,apps
- proxy.apps.path=/whoami
# 1. visit https://whoami.y.z
# 2. visit https://apps.y.z/whoami
```bash
bash -c "$(curl -fsSL https://github.com/yusing/godoxy/raw/refs/heads/main/scripts/install-agent.sh)" -- uninstall
```
### Multiple ports configuration example
## Screenshots
```yaml
minio:
image: quay.io/minio/minio
container_name: minio
...
labels:
- proxy.aliases=minio,minio-console
- proxy.minio.port=9000
- proxy.minio-console.port=9001
### idlesleeper
# visit https://minio.y.z to access minio
# visit https://minio-console.y.z/whoami to access minio console
![idlesleeper](screenshots/idlesleeper.webp)
### Metrics and Logs
<div align="center">
<table>
<tr>
<td align="center"><img src="screenshots/routes.jpg" alt="Routes" width="350"/></td>
<td align="center"><img src="screenshots/servers.jpg" alt="Servers" width="350"/></td>
</tr>
<tr>
<td align="center"><b>Routes</b></td>
<td align="center"><b>Servers</b></td>
</tr>
</table>
</div>
## Manual Setup
1. Make `config` directory then grab `config.example.yml` into `config/config.yml`
`mkdir -p config && wget https://raw.githubusercontent.com/yusing/godoxy/main/config.example.yml -O config/config.yml`
2. Grab `.env.example` into `.env`
`wget https://raw.githubusercontent.com/yusing/godoxy/main/.env.example -O .env`
3. Grab `compose.example.yml` into `compose.yml`
`wget https://raw.githubusercontent.com/yusing/godoxy/main/compose.example.yml -O compose.yml`
### Folder structrue
```shell
├── certs
│ ├── cert.crt
│ └── priv.key
├── compose.yml
├── config
│ ├── config.yml
│ ├── middlewares
│ │ ├── middleware1.yml
│ │ ├── middleware2.yml
│ ├── provider1.yml
│ └── provider2.yml
├── data
│ ├── metrics # metrics data
│ │ ├── uptime.json
│ │ └── system_info.json
└── .env
```
### TCP/UDP configuration example
```yaml
# In the app
app-db:
image: postgres:15
container_name: app-db
...
labels:
# Optional (postgres is in the known image map)
- proxy.app-db.scheme=tcp
# Optional (first free port will be used for listening port)
- proxy.app-db.port=20000:postgres
# In go-proxy
go-proxy:
...
ports:
- 80:80
...
- 20000:20000/tcp
# or 20000-20010:20000-20010/tcp to declare large range at once
# access app-db via <*>.y.z:20000
```
## Load balancing Configuration Example
```yaml
nginx:
...
deploy:
mode: replicated
replicas: 3
labels:
- proxy.nginx.load_balance=1 # allowed: [1, true]
```
## Troubleshooting
Q: How to fix when it shows "no matching route for subdomain \<subdomain>"?
A: Make sure the container is running, and \<subdomain> matches any container name / alias
## Benchmarks
Benchmarked with `wrk` connecting `traefik/whoami`'s `/bench` endpoint
Remote benchmark (client running wrk and `go-proxy` server are different devices)
- Direct connection
```shell
root@yusing-pc:~# wrk -t 10 -c 200 -d 10s -H "Host: bench.6uo.me" --latency http://10.0.100.3:8003/bench
Running 10s test @ http://10.0.100.3:8003/bench
10 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 94.75ms 199.92ms 1.68s 91.27%
Req/Sec 4.24k 1.79k 18.79k 72.13%
Latency Distribution
50% 1.14ms
75% 120.23ms
90% 245.63ms
99% 1.03s
423444 requests in 10.10s, 50.88MB read
Socket errors: connect 0, read 0, write 0, timeout 29
Requests/sec: 41926.32
Transfer/sec: 5.04MB
```
- With reverse proxy
```shell
root@yusing-pc:~# wrk -t 10 -c 200 -d 10s -H "Host: bench.6uo.me" --latency http://10.0.1.7/bench
Running 10s test @ http://10.0.1.7/bench
10 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 79.35ms 169.79ms 1.69s 92.55%
Req/Sec 4.27k 1.90k 19.61k 75.81%
Latency Distribution
50% 1.12ms
75% 105.66ms
90% 200.22ms
99% 814.59ms
409836 requests in 10.10s, 49.25MB read
Socket errors: connect 0, read 0, write 0, timeout 18
Requests/sec: 40581.61
Transfer/sec: 4.88MB
```
Local benchmark (client running wrk and `go-proxy` server are under same proxmox host but different LXCs)
- Direct connection
```shell
root@http-benchmark-client:~# wrk -t 10 -c 200 -d 10s --latency http://10.0.100.1/bench
Running 10s test @ http://10.0.100.1/bench
10 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 434.08us 539.35us 8.76ms 85.28%
Req/Sec 67.71k 6.31k 87.21k 71.20%
Latency Distribution
50% 153.00us
75% 646.00us
90% 1.18ms
99% 2.38ms
6739591 requests in 10.01s, 809.85MB read
Requests/sec: 673608.15
Transfer/sec: 80.94MB
```
- With `go-proxy` reverse proxy
```shell
root@http-benchmark-client:~# wrk -t 10 -c 200 -d 10s -H "Host: bench.6uo.me" --latency http://10.0.1.7/bench
Running 10s test @ http://10.0.1.7/bench
10 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.23ms 0.96ms 11.43ms 72.09%
Req/Sec 17.48k 1.76k 21.48k 70.20%
Latency Distribution
50% 0.98ms
75% 1.76ms
90% 2.54ms
99% 4.24ms
1739079 requests in 10.01s, 208.97MB read
Requests/sec: 173779.44
Transfer/sec: 20.88MB
```
- With `traefik-v3`
```shell
root@traefik-benchmark:~# wrk -t10 -c200 -d10s -H "Host: benchmark.whoami" --latency http://127.0.0.1:8000/bench
Running 10s test @ http://127.0.0.1:8000/bench
10 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 2.81ms 10.36ms 180.26ms 98.57%
Req/Sec 11.35k 1.74k 13.76k 85.54%
Latency Distribution
50% 1.59ms
75% 2.27ms
90% 3.17ms
99% 37.91ms
1125723 requests in 10.01s, 109.50MB read
Requests/sec: 112499.59
Transfer/sec: 10.94MB
```
## Known issues
None
## Memory usage
It takes ~15 MB for 50 proxy entries
## Build it yourself
1. Install / Upgrade [go (>=1.22)](https://go.dev/doc/install) and `make` if not already
1. Clone the repository `git clone https://github.com/yusing/godoxy --depth=1`
2. Clear cache if you have built this before (go < 1.22) with `go clean -cache`
2. Install / Upgrade [go (>=1.22)](https://go.dev/doc/install) and `make` if not already
3. get dependencies with `make get`
3. Clear cache if you have built this before (go < 1.22) with `go clean -cache`
4. build binary with `make build`
4. get dependencies with `make get`
5. start your container with `make up` (docker) or `bin/go-proxy` (binary)
5. build binary with `make build`
[panel_port_http]: 8080
[panel_port_https]: 8443
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=yusing/godoxy&type=Date)](https://www.star-history.com/#yusing/godoxy&Date)
[🔼Back to top](#table-of-content)

200
README_CHT.md Normal file
View File

@@ -0,0 +1,200 @@
<div align="center">
<img src="assets/godoxy.png" width="200">
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=yusing_go-proxy&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=yusing_go-proxy)
![GitHub last commit](https://img.shields.io/github/last-commit/yusing/godoxy)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=yusing_go-proxy&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=yusing_go-proxy)
![Demo](https://img.shields.io/website?url=https%3A%2F%2Fdemo.godoxy.dev&label=Demo&link=https%3A%2F%2Fdemo.godoxy.dev)
[![Discord](https://dcbadge.limes.pink/api/server/umReR62nRd?style=flat)](https://discord.gg/umReR62nRd)
輕量、易用、 高效能,且帶有主頁和配置面板的反向代理
<h5>
<a href="https://docs.godoxy.dev">網站</a> | <a href="https://docs.godoxy.dev/Home.html">文檔</a> | <a href="https://discord.gg/umReR62nRd">Discord</a>
</h5>
<h5><a href="README.md">EN</a> | 中文</h5>
<img src="https://github.com/user-attachments/assets/4bb371f4-6e4c-425c-89b2-b9e962bdd46f" style="max-width: 650">
有疑問? 問 [ChatGPT](https://chatgpt.com/g/g-6825390374b481919ad482f2e48936a1-godoxy-assistant)!(鳴謝 [@ismesid](https://github.com/arevindh)
</div>
## 目錄
<!-- TOC -->
- [目錄](#目錄)
- [運行示例](#運行示例)
- [主要特點](#主要特點)
- [前置需求](#前置需求)
- [安裝](#安裝)
- [手動安裝](#手動安裝)
- [資料夾結構](#資料夾結構)
- [更新 / 卸載系統代理 (System Agent)](#更新--卸載系統代理-system-agent)
- [截圖](#截圖)
- [閒置休眠](#閒置休眠)
- [監控](#監控)
- [自行編譯](#自行編譯)
- [Star History](#star-history)
## 運行示例
<https://demo.godoxy.dev>
[![Deployed on Zeabur](https://zeabur.com/deployed-on-zeabur-dark.svg)](https://zeabur.com/referral?referralCode=yusing&utm_source=yusing&utm_campaign=oss)
## 主要特點
- **簡單易用**
- 透過 Docker[標籤](https://docs.godoxy.dev/Docker-labels-and-Route-Files)或 WebUI 輕鬆設定
- [簡單的多節點設置](https://docs.godoxy.dev/Configurations#multi-docker-nodes-setup)
- 詳細的錯誤訊息,便於故障排除
- **存取控制 (ACL)**:連線/請求層級存取控制
- IP/CIDR
- 國家 **(需要 Maxmind 帳戶)**
- 時區 **(需要 Maxmind 帳戶)**
- **存取日誌記錄**
- 定時發送摘要 (允許和拒絕的連線次數)
- **自動化**
- 使用 Let's Encrypt 自動管理 SSL 憑證 ([使用 DNS-01 驗證](https://docs.godoxy.dev/DNS-01-Providers))
- Docker 容器自動配置
- 設定檔與容器狀態變更時自動熱重載
- **容器運行時支援**
- Docker
- Podman
- **閒置休眠**:根據流量停止和喚醒容器 _(參見[截圖](#閒置休眠))_
- Docker 容器
- Proxmox LXC 容器
- **流量管理**
- HTTP 反向代理
- TCP/UDP 連接埠轉送
- **OpenID Connect 支援**:輕鬆實現單點登入 (SSO) 並保護您的應用程式
- **ForwardAuth 支援**:整合任何 auth provider (例如 TinyAuth)
- **客製化**
- [HTTP 中介軟體](https://docs.godoxy.dev/Middlewares)
- [支援自訂錯誤頁面](https://docs.godoxy.dev/Custom-Error-Pages)
- **網頁使用者介面 (Web UI)**
- 應用程式一覽
- 設定編輯器
- 執行時間與系統指標
- Docker 日誌檢視器
- **跨平台支援**
- 支援 **linux/amd64****linux/arm64**
- **高效能**
-**[Go](https://go.dev)** 語言編寫
## 前置需求
設置 DNS 記錄指向運行 `GoDoxy` 的機器,例如:
- A 記錄:`*.y.z` -> `10.0.10.1`
- AAAA 記錄:`*.y.z` -> `::ffff:a00:a01`
## 安裝
> [!NOTE]
> GoDoxy 僅在 `host` 網路模式下運作,請勿更改。
>
> 如需更改監聽埠,請修改 `.env`。
1. 準備一個新目錄用於 docker compose 和配置文件。
2. 在目錄內運行安裝腳本,或[手動安裝](#手動安裝)
```shell
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/yusing/godoxy/main/scripts/setup.sh)"
```
3. 現在可以在 WebUI `https://godoxy.yourdomain.com` 進行額外配置
### 手動安裝
1. 建立 `config` 目錄,然後將 `config.example.yml` 下載到 `config/config.yml`
`mkdir -p config && wget https://raw.githubusercontent.com/yusing/godoxy/main/config.example.yml -O config/config.yml`
2. 將 `.env.example` 下載到 `.env`
`wget https://raw.githubusercontent.com/yusing/godoxy/main/.env.example -O .env`
3. 將 `compose.example.yml` 下載到 `compose.yml`
`wget https://raw.githubusercontent.com/yusing/godoxy/main/compose.example.yml -O compose.yml`
### 資料夾結構
```shell
├── certs
│ ├── cert.crt
│ └── priv.key
├── compose.yml
├── config
│ ├── config.yml
│ ├── middlewares
│ │ ├── middleware1.yml
│ │ ├── middleware2.yml
│ ├── provider1.yml
│ └── provider2.yml
├── data
│ ├── metrics # metrics data
│ │ ├── uptime.json
│ │ └── system_info.json
└── .env
```
## 更新 / 卸載系統代理 (System Agent)
更新:
```bash
sudo /bin/bash -c "$(curl -fsSL https://github.com/yusing/godoxy/raw/refs/heads/main/scripts/install-agent.sh)" -- update
```
卸載:
```bash
sudo /bin/bash -c "$(curl -fsSL https://github.com/yusing/godoxy/raw/refs/heads/main/scripts/install-agent.sh)" -- uninstall
```
## 截圖
### 閒置休眠
![閒置休眠](screenshots/idlesleeper.webp)
### 監控
<div align="center">
<table>
<tr>
<td align="center"><img src="screenshots/routes.jpg" alt="Routes" width="350"/></td>
<td align="center"><img src="screenshots/servers.jpg" alt="Servers" width="350"/></td>
</tr>
<tr>
<td align="center"><b>路由</b></td>
<td align="center"><b>伺服器</b></td>
</tr>
</table>
</div>
## 自行編譯
1. 克隆儲存庫 `git clone https://github.com/yusing/godoxy --depth=1`
2. 如果尚未安裝,請安裝/升級 [go (>=1.22)](https://go.dev/doc/install) 和 `make`
3. 如果之前編譯過go < 1.22),請使用 `go clean -cache` 清除快取
4. 使用 `make get` 獲取依賴
5. 使用 `make build` 編譯二進制檔案
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=yusing/godoxy&type=Date)](https://www.star-history.com/#yusing/godoxy&Date)
[🔼 回到頂部](#目錄)

52
agent/cmd/README.md Normal file
View File

@@ -0,0 +1,52 @@
# agent/cmd
The main entry point for the GoDoxy Agent, a secure monitoring and proxy agent that runs alongside Docker containers.
## Overview
This package contains the `main.go` entry point for the GoDoxy Agent. The agent is a TLS-enabled server that provides:
- Secure Docker socket proxying with client certificate authentication
- HTTP proxy capabilities for container traffic
- System metrics collection and monitoring
- Health check endpoints
## Architecture
```mermaid
graph TD
A[main] --> B[Logger Init]
A --> C[Load CA Certificate]
A --> D[Load Server Certificate]
A --> E[Log Version Info]
A --> F[Start Agent Server]
A --> G[Start Socket Proxy]
A --> H[Start System Info Poller]
A --> I[Wait Exit]
F --> F1[TLS with mTLS]
F --> F2[Agent Handler]
G --> G1[Docker Socket Proxy]
```
## Main Function Flow
1. **Logger Setup**: Configures zerolog with console output
1. **Certificate Loading**: Loads CA and server certificates for TLS/mTLS
1. **Version Logging**: Logs agent version and configuration
1. **Agent Server**: Starts the main HTTPS server with agent handlers
1. **Socket Proxy**: Starts Docker socket proxy if configured
1. **System Monitoring**: Starts system info polling
1. **Graceful Shutdown**: Waits for exit signal (3 second timeout)
## Configuration
See `agent/pkg/env/README.md` for configuration options.
## Dependencies
- `agent/pkg/agent` - Core agent types and constants
- `agent/pkg/env` - Environment configuration
- `agent/pkg/server` - Server implementation
- `socketproxy/pkg` - Docker socket proxy
- `internal/metrics/systeminfo` - System metrics

167
agent/cmd/main.go Normal file
View File

@@ -0,0 +1,167 @@
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"net"
"net/http"
"os"
stdlog "log"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/agent/pkg/agent/stream"
"github.com/yusing/godoxy/agent/pkg/env"
"github.com/yusing/godoxy/agent/pkg/handler"
"github.com/yusing/godoxy/internal/metrics/systeminfo"
socketproxy "github.com/yusing/godoxy/socketproxy/pkg"
gperr "github.com/yusing/goutils/errs"
strutils "github.com/yusing/goutils/strings"
"github.com/yusing/goutils/task"
"github.com/yusing/goutils/version"
)
// TODO: support IPv6
func main() {
writer := zerolog.ConsoleWriter{
Out: os.Stderr,
TimeFormat: "01-02 15:04",
}
zerolog.TimeFieldFormat = writer.TimeFormat
log.Logger = zerolog.New(writer).Level(zerolog.InfoLevel).With().Timestamp().Logger()
ca := &agent.PEMPair{}
err := ca.Load(env.AgentCACert)
if err != nil {
log.Fatal().Err(err).Msg("init CA error")
}
caCert, err := ca.ToTLSCert()
if err != nil {
log.Fatal().Err(err).Msg("init CA error")
}
srv := &agent.PEMPair{}
srv.Load(env.AgentSSLCert)
if err != nil {
log.Fatal().Err(err).Msg("init SSL error")
}
srvCert, err := srv.ToTLSCert()
if err != nil {
log.Fatal().Err(err).Msg("init SSL error")
}
log.Info().Msgf("GoDoxy Agent version %s", version.Get())
log.Info().Msgf("Agent name: %s", env.AgentName)
log.Info().Msgf("Agent port: %d", env.AgentPort)
log.Info().Msgf("Agent runtime: %s", env.Runtime)
log.Info().Msg(`
Tips:
1. To change the agent name, you can set the AGENT_NAME environment variable.
2. To change the agent port, you can set the AGENT_PORT environment variable.
`)
t := task.RootTask("agent", false)
// One TCP listener on AGENT_PORT, then multiplex by TLS ALPN:
// - Stream ALPN: route to TCP stream tunnel handler (via http.Server.TLSNextProto)
// - Otherwise: route to HTTPS API handler
tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{Port: env.AgentPort})
if err != nil {
gperr.LogFatal("failed to listen on port", err)
}
caCertPool := x509.NewCertPool()
caCertPool.AddCert(caCert.Leaf)
muxTLSConfig := &tls.Config{
Certificates: []tls.Certificate{*srvCert},
ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
MinVersion: tls.VersionTLS12,
// Keep HTTP limited to HTTP/1.1 (matching current agent server behavior)
// and add the stream tunnel ALPN for multiplexing.
NextProtos: []string{"http/1.1", stream.StreamALPN},
}
if env.AgentSkipClientCertCheck {
muxTLSConfig.ClientAuth = tls.NoClientCert
}
// TLS listener feeds the HTTP server. ALPN stream connections are intercepted
// using http.Server.TLSNextProto.
tlsLn := tls.NewListener(tcpListener, muxTLSConfig)
streamSrv := stream.NewTCPServerHandler(t.Context())
httpSrv := &http.Server{
Handler: handler.NewAgentHandler(),
BaseContext: func(net.Listener) context.Context {
return t.Context()
},
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){
// When a client negotiates StreamALPN, net/http will call this hook instead
// of treating the connection as HTTP.
stream.StreamALPN: func(_ *http.Server, conn *tls.Conn, _ http.Handler) {
// ServeConn blocks until the tunnel finishes.
streamSrv.ServeConn(conn)
},
},
}
{
subtask := t.Subtask("agent-http", true)
t.OnCancel("stop_http", func() {
_ = streamSrv.Close()
_ = httpSrv.Close()
_ = tlsLn.Close()
})
go func() {
err := httpSrv.Serve(tlsLn)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Error().Err(err).Msg("agent HTTP server stopped with error")
}
subtask.Finish(err)
}()
log.Info().Int("port", env.AgentPort).Msg("HTTPS API server started (ALPN mux enabled)")
}
log.Info().Int("port", env.AgentPort).Msg("TCP stream handler started (via TLSNextProto)")
{
udpServer := stream.NewUDPServer(t.Context(), "udp", &net.UDPAddr{Port: env.AgentPort}, caCert.Leaf, srvCert)
subtask := t.Subtask("agent-stream-udp", true)
t.OnCancel("stop_stream_udp", func() {
_ = udpServer.Close()
})
go func() {
err := udpServer.Start()
subtask.Finish(err)
}()
log.Info().Int("port", env.AgentPort).Msg("UDP stream server started")
}
if socketproxy.ListenAddr != "" {
runtime := strutils.Title(string(env.Runtime))
log.Info().Msgf("%s socket listening on: %s", runtime, socketproxy.ListenAddr)
l, err := net.Listen("tcp", socketproxy.ListenAddr)
if err != nil {
gperr.LogFatal("failed to listen on port", err)
}
errLog := log.Logger.With().Str("level", "error").Str("component", "socketproxy").Logger()
srv := http.Server{
Handler: socketproxy.NewHandler(),
BaseContext: func(net.Listener) context.Context {
return t.Context()
},
ErrorLog: stdlog.New(&errLog, "", 0),
}
srv.Serve(l)
}
systeminfo.Poller.Start()
task.WaitExit(3)
}

104
agent/go.mod Normal file
View File

@@ -0,0 +1,104 @@
module github.com/yusing/godoxy/agent
go 1.25.5
replace (
github.com/shirou/gopsutil/v4 => ../internal/gopsutil
github.com/yusing/godoxy => ../
github.com/yusing/godoxy/socketproxy => ../socket-proxy
github.com/yusing/goutils => ../goutils
github.com/yusing/goutils/http/reverseproxy => ../goutils/http/reverseproxy
github.com/yusing/goutils/http/websocket => ../goutils/http/websocket
github.com/yusing/goutils/server => ../goutils/server
)
exclude github.com/containerd/nerdctl/mod/tigron v0.0.0
require (
github.com/bytedance/sonic v1.14.2
github.com/gin-gonic/gin v1.11.0
github.com/gorilla/websocket v1.5.3
github.com/pion/dtls/v3 v3.0.10
github.com/pion/transport/v3 v3.1.1
github.com/rs/zerolog v1.34.0
github.com/stretchr/testify v1.11.1
github.com/yusing/godoxy v0.23.1
github.com/yusing/godoxy/socketproxy v0.0.0-00010101000000-000000000000
github.com/yusing/goutils v0.7.0
)
require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic/loader v0.4.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/cli v29.1.3+incompatible // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/moby/api v1.52.0 // indirect
github.com/moby/moby/client v0.2.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pion/logging v0.2.4 // indirect
github.com/pion/transport/v4 v4.0.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/puzpuzpuz/xsync/v4 v4.2.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.58.0 // indirect
github.com/shirou/gopsutil/v4 v4.25.12 // indirect
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.69.0 // indirect
github.com/yusing/ds v0.3.1 // indirect
github.com/yusing/gointernals v0.1.16 // indirect
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260109021609-78fda75d1e58 // indirect
github.com/yusing/goutils/http/websocket v0.0.0-20260109021609-78fda75d1e58 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect
golang.org/x/arch v0.23.0 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.32.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

274
agent/go.sum Normal file
View File

@@ -0,0 +1,274 @@
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/diskfs/go-diskfs v1.7.0 h1:vonWmt5CMowXwUc79jWyGrf2DIMeoOjkLlMnQYGVOs8=
github.com/diskfs/go-diskfs v1.7.0/go.mod h1:LhQyXqOugWFRahYUSw47NyZJPezFzB9UELwhpszLP/k=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/docker/cli v29.1.3+incompatible h1:+kz9uDWgs+mAaIZojWfFt4d53/jv0ZUOOoSh5ZnH36c=
github.com/docker/cli v29.1.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/go-acme/lego/v4 v4.31.0 h1:gd4oUYdfs83PR1/SflkNdit9xY1iul2I4EystnU8NXM=
github.com/go-acme/lego/v4 v4.31.0/go.mod h1:m6zcfX/zcbMYDa8s6AnCMnoORWNP8Epnei+6NBCTUGs=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gotify/server/v2 v2.8.0 h1:E3UDDn/3rFZi1sjZfbuhXNnxJP3ACZhdcw/iySegPRA=
github.com/gotify/server/v2 v2.8.0/go.mod h1:6ci5adxcE2hf1v+2oowKiQmixOxXV8vU+CRLKP6sqZA=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU=
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/luthermonson/go-proxmox v0.3.2 h1:/zUg6FCl9cAABx0xU3OIgtDtClY0gVXxOCsrceDNylc=
github.com/luthermonson/go-proxmox v0.3.2/go.mod h1:oyFgg2WwTEIF0rP6ppjiixOHa5ebK1p8OaRiFhvICBQ=
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=
github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=
github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
github.com/moby/moby/client v0.2.1 h1:1Grh1552mvv6i+sYOdY+xKKVTvzJegcVMhuXocyDz/k=
github.com/moby/moby/client v0.2.1/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pion/dtls/v3 v3.0.10 h1:k9ekkq1kaZoxnNEbyLKI8DI37j/Nbk1HWmMuywpQJgg=
github.com/pion/dtls/v3 v3.0.10/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM=
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0=
github.com/puzpuzpuz/xsync/v4 v4.2.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.58.0 h1:ggY2pvZaVdB9EyojxL1p+5mptkuHyX5MOSv4dgWF4Ug=
github.com/quic-go/quic-go v0.58.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/slog-common v0.19.0 h1:fNcZb8B2uOLooeYwFpAlKjkQTUafdjfqKcwcC89G9YI=
github.com/samber/slog-common v0.19.0/go.mod h1:dTz+YOU76aH007YUU0DffsXNsGFQRQllPQh9XyNoA3M=
github.com/samber/slog-zerolog/v2 v2.9.0 h1:6LkOabJmZdNLaUWkTC3IVVA+dq7b/V0FM6lz6/7+THI=
github.com/samber/slog-zerolog/v2 v2.9.0/go.mod h1:gnQW9VnCfM34v2pRMUIGMsZOVbYLqY/v0Wxu6atSVGc=
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0=
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yusing/ds v0.3.1 h1:mCqTgTQD8RhiBpcysvii5kZ7ZBmqcknVsFubNALGLbY=
github.com/yusing/ds v0.3.1/go.mod h1:XhKV4l7cZwBbbl7lRzNC9zX27zvCM0frIwiuD40ULRk=
github.com/yusing/gointernals v0.1.16 h1:GrhZZdxzA+jojLEqankctJrOuAYDb7kY1C93S1pVR34=
github.com/yusing/gointernals v0.1.16/go.mod h1:B/0FVXt4WPmgzVy3ynzkqKi+BSGaJVmwCJBRXYapo34=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=

108
agent/pkg/agent/README.md Normal file
View File

@@ -0,0 +1,108 @@
# Agent Package
The `agent` package provides the client-side implementation for interacting with GoDoxy agents. It handles agent configuration, secure communication via TLS, and provides utilities for agent deployment and management.
## Architecture Overview
```mermaid
graph TD
subgraph GoDoxy Server
AP[Agent Pool] --> AC[AgentConfig]
end
subgraph Agent Communication
AC -->|HTTPS| AI[Agent Info API]
AC -->|TLS| ST[Stream Tunneling]
end
subgraph Deployment
G[Generator] --> DC[Docker Compose]
G --> IS[Install Script]
end
subgraph Security
NA[NewAgent] --> Certs[Certificates]
end
```
## File Structure
| File | Purpose |
| ---------------------------------------- | --------------------------------------------------------- |
| [`config.go`](config.go) | Core configuration, initialization, and API client logic. |
| [`new_agent.go`](new_agent.go) | Agent creation and certificate generation logic. |
| [`docker_compose.go`](docker_compose.go) | Generator for agent Docker Compose configurations. |
| [`bare_metal.go`](bare_metal.go) | Generator for bare metal installation scripts. |
| [`env.go`](env.go) | Environment configuration types and constants. |
| `common/` | Shared constants and utilities for agents. |
## Core Types
### [`AgentConfig`](config.go:29)
The primary struct used by the GoDoxy server to manage a connection to an agent. It stores the agent's address, metadata, and TLS configuration.
### [`AgentInfo`](config.go:45)
Contains basic metadata about the agent, including its version, name, and container runtime (Docker or Podman).
### [`PEMPair`](new_agent.go:53)
A utility struct for handling PEM-encoded certificate and key pairs, supporting encryption, decryption, and conversion to `tls.Certificate`.
## Agent Creation and Certificate Management
### Certificate Generation
The [`NewAgent`](new_agent.go:147) function creates a complete certificate infrastructure for an agent:
- **CA Certificate**: Self-signed root certificate with 1000-year validity.
- **Server Certificate**: For the agent's HTTPS server, signed by the CA.
- **Client Certificate**: For the GoDoxy server to authenticate with the agent.
All certificates use ECDSA with P-256 curve and SHA-256 signatures.
### Certificate Security
- Certificates are encrypted using AES-GCM with a provided encryption key.
- The [`PEMPair`](new_agent.go:53) struct provides methods for encryption, decryption, and conversion to `tls.Certificate`.
- Base64 encoding is used for certificate storage and transmission.
## Key Features
### 1. Secure Communication
All communication between the GoDoxy server and agents is secured using mutual TLS (mTLS). The [`AgentConfig`](config.go:29) handles the loading of CA and client certificates to establish secure connections.
### 2. Agent Discovery and Initialization
The [`Init`](config.go:231) and [`InitWithCerts`](config.go:110) methods allow the server to:
- Fetch agent metadata (version, name, runtime).
- Verify compatibility between server and agent versions.
- Test support for TCP and UDP stream tunneling.
### 3. Deployment Generators
The package provides interfaces and implementations for generating deployment artifacts:
- **Docker Compose**: Generates a `docker-compose.yml` for running the agent as a container via [`AgentComposeConfig.Generate()`](docker_compose.go:21).
- **Bare Metal**: Generates a shell script to install and run the agent as a systemd service via [`AgentEnvConfig.Generate()`](bare_metal.go:27).
### 4. Fake Docker Host
The package supports a "fake" Docker host scheme (`agent://<addr>`) to identify containers managed by an agent, allowing the GoDoxy server to route requests appropriately. See [`IsDockerHostAgent`](config.go:90) and [`GetAgentAddrFromDockerHost`](config.go:94).
## Usage Example
```go
cfg := &agent.AgentConfig{}
cfg.Parse("192.168.1.100:8081")
ctx := context.Background()
if err := cfg.Init(ctx); err != nil {
log.Fatal(err)
}
fmt.Printf("Connected to agent: %s (Version: %s)\n", cfg.Name, cfg.Version)
```

View File

@@ -0,0 +1,33 @@
package agent
import (
"bytes"
"text/template"
)
var (
installScript = `AGENT_NAME="{{.Name}}" \
AGENT_PORT="{{.Port}}" \
AGENT_CA_CERT="{{.CACert}}" \
AGENT_SSL_CERT="{{.SSLCert}}" \
{{ if eq .ContainerRuntime "nerdctl" -}}
DOCKER_SOCKET="/var/run/containerd/containerd.sock" \
RUNTIME="nerdctl" \
{{ else if eq .ContainerRuntime "podman" -}}
DOCKER_SOCKET="/var/run/podman/podman.sock" \
RUNTIME="podman" \
{{ else -}}
DOCKER_SOCKET="/var/run/docker.sock" \
RUNTIME="docker" \
{{ end -}}
bash -c "$(curl -fsSL https://raw.githubusercontent.com/yusing/godoxy/main/scripts/install-agent.sh)"`
installScriptTemplate = template.Must(template.New("install.sh").Parse(installScript))
)
func (c *AgentEnvConfig) Generate() (string, error) {
buf := bytes.NewBuffer(make([]byte, 0, 4096))
if err := installScriptTemplate.Execute(buf, c); err != nil {
return "", err
}
return buf.String(), nil
}

View File

@@ -0,0 +1,3 @@
package common
const CertsDNSName = "godoxy.agent"

364
agent/pkg/agent/config.go Normal file
View File

@@ -0,0 +1,364 @@
package agent
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/yusing/godoxy/agent/pkg/agent/common"
agentstream "github.com/yusing/godoxy/agent/pkg/agent/stream"
"github.com/yusing/godoxy/agent/pkg/certs"
gperr "github.com/yusing/goutils/errs"
httputils "github.com/yusing/goutils/http"
"github.com/yusing/goutils/version"
)
type AgentConfig struct {
AgentInfo
Addr string `json:"addr"`
IsTCPStreamSupported bool `json:"supports_tcp_stream"`
IsUDPStreamSupported bool `json:"supports_udp_stream"`
// for stream
caCert *x509.Certificate
clientCert *tls.Certificate
tlsConfig tls.Config
l zerolog.Logger
} // @name Agent
type AgentInfo struct {
Version version.Version `json:"version" swaggertype:"string"`
Name string `json:"name"`
Runtime ContainerRuntime `json:"runtime"`
}
// Deprecated. Replaced by EndpointInfo
const (
EndpointVersion = "/version"
EndpointName = "/name"
EndpointRuntime = "/runtime"
)
const (
EndpointInfo = "/info"
EndpointProxyHTTP = "/proxy/http"
EndpointHealth = "/health"
EndpointLogs = "/logs"
EndpointSystemInfo = "/system_info"
AgentHost = common.CertsDNSName
APIEndpointBase = "/godoxy/agent"
APIBaseURL = "https://" + AgentHost + APIEndpointBase
DockerHost = "https://" + AgentHost
FakeDockerHostPrefix = "agent://"
FakeDockerHostPrefixLen = len(FakeDockerHostPrefix)
)
func mustParseURL(urlStr string) *url.URL {
u, err := url.Parse(urlStr)
if err != nil {
panic(err)
}
return u
}
var (
AgentURL = mustParseURL(APIBaseURL)
HTTPProxyURL = mustParseURL(APIBaseURL + EndpointProxyHTTP)
HTTPProxyURLPrefixLen = len(APIEndpointBase + EndpointProxyHTTP)
)
func IsDockerHostAgent(dockerHost string) bool {
return strings.HasPrefix(dockerHost, FakeDockerHostPrefix)
}
func GetAgentAddrFromDockerHost(dockerHost string) string {
return dockerHost[FakeDockerHostPrefixLen:]
}
func (cfg *AgentConfig) FakeDockerHost() string {
return FakeDockerHostPrefix + cfg.Addr
}
func (cfg *AgentConfig) Parse(addr string) error {
cfg.Addr = addr
return nil
}
var serverVersion = version.Get()
// InitWithCerts initializes the agent config with the given CA, certificate, and key.
func (cfg *AgentConfig) InitWithCerts(ctx context.Context, ca, crt, key []byte) error {
clientCert, err := tls.X509KeyPair(crt, key)
if err != nil {
return err
}
cfg.clientCert = &clientCert
// create tls config
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(ca)
if !ok {
return errors.New("invalid ca certificate")
}
// Keep the CA leaf for stream client dialing.
if block, _ := pem.Decode(ca); block == nil || block.Type != "CERTIFICATE" {
return errors.New("invalid ca certificate")
} else if cert, err := x509.ParseCertificate(block.Bytes); err != nil {
return err
} else {
cfg.caCert = cert
}
cfg.tlsConfig = tls.Config{
Certificates: []tls.Certificate{clientCert},
RootCAs: caCertPool,
ServerName: common.CertsDNSName,
MinVersion: tls.VersionTLS12,
}
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
status, err := cfg.fetchJSON(ctx, EndpointInfo, &cfg.AgentInfo)
if err != nil {
return err
}
var streamUnsupportedErrs gperr.Builder
if status == http.StatusOK {
// test stream server connection
const fakeAddress = "localhost:8080" // it won't be used, just for testing
// test TCP stream support
err := agentstream.TCPHealthCheck(cfg.Addr, cfg.caCert, cfg.clientCert)
if err != nil {
streamUnsupportedErrs.Addf("failed to connect to stream server via TCP: %w", err)
} else {
cfg.IsTCPStreamSupported = true
}
// test UDP stream support
err = agentstream.UDPHealthCheck(cfg.Addr, cfg.caCert, cfg.clientCert)
if err != nil {
streamUnsupportedErrs.Addf("failed to connect to stream server via UDP: %w", err)
} else {
cfg.IsUDPStreamSupported = true
}
} else {
// old agent does not support EndpointInfo
// fallback with old logic
cfg.IsTCPStreamSupported = false
cfg.IsUDPStreamSupported = false
streamUnsupportedErrs.Adds("agent version is too old, does not support stream tunneling")
// get agent name
name, _, err := cfg.fetchString(ctx, EndpointName)
if err != nil {
return err
}
cfg.Name = name
// check agent version
agentVersion, _, err := cfg.fetchString(ctx, EndpointVersion)
if err != nil {
return err
}
cfg.Version = version.Parse(agentVersion)
// check agent runtime
runtime, status, err := cfg.fetchString(ctx, EndpointRuntime)
if err != nil {
return err
}
switch status {
case http.StatusOK:
switch runtime {
case "docker":
cfg.Runtime = ContainerRuntimeDocker
// case "nerdctl":
// cfg.Runtime = ContainerRuntimeNerdctl
case "podman":
cfg.Runtime = ContainerRuntimePodman
default:
return fmt.Errorf("invalid agent runtime: %s", runtime)
}
case http.StatusNotFound:
// backward compatibility, old agent does not have runtime endpoint
cfg.Runtime = ContainerRuntimeDocker
default:
return fmt.Errorf("failed to get agent runtime: HTTP %d %s", status, runtime)
}
}
cfg.l = log.With().Str("agent", cfg.Name).Logger()
if err := streamUnsupportedErrs.Error(); err != nil {
gperr.LogWarn("agent has limited/no stream tunneling support, TCP and UDP routes via agent will not work", err, &cfg.l)
}
if serverVersion.IsNewerThanMajor(cfg.Version) {
log.Warn().Msgf("agent %s major version mismatch: server: %s, agent: %s", cfg.Name, serverVersion, cfg.Version)
}
log.Info().Msgf("agent %q initialized", cfg.Name)
return nil
}
// Init initializes the agent config with the given context.
func (cfg *AgentConfig) Init(ctx context.Context) error {
filepath, ok := certs.AgentCertsFilepath(cfg.Addr)
if !ok {
return fmt.Errorf("invalid agent host: %s", cfg.Addr)
}
certData, err := os.ReadFile(filepath)
if err != nil {
return fmt.Errorf("failed to read agent certs: %w", err)
}
ca, crt, key, err := certs.ExtractCert(certData)
if err != nil {
return fmt.Errorf("failed to extract agent certs: %w", err)
}
return cfg.InitWithCerts(ctx, ca, crt, key)
}
// NewTCPClient creates a new TCP client for the agent.
//
// It returns an error if
// - the agent is not initialized
// - the agent does not support TCP stream tunneling
// - the agent stream server address is not initialized
func (cfg *AgentConfig) NewTCPClient(targetAddress string) (net.Conn, error) {
if cfg.caCert == nil || cfg.clientCert == nil {
return nil, errors.New("agent is not initialized")
}
if !cfg.IsTCPStreamSupported {
return nil, errors.New("agent does not support TCP stream tunneling")
}
return agentstream.NewTCPClient(cfg.Addr, targetAddress, cfg.caCert, cfg.clientCert)
}
// NewUDPClient creates a new UDP client for the agent.
//
// It returns an error if
// - the agent is not initialized
// - the agent does not support UDP stream tunneling
// - the agent stream server address is not initialized
func (cfg *AgentConfig) NewUDPClient(targetAddress string) (net.Conn, error) {
if cfg.caCert == nil || cfg.clientCert == nil {
return nil, errors.New("agent is not initialized")
}
if !cfg.IsUDPStreamSupported {
return nil, errors.New("agent does not support UDP stream tunneling")
}
return agentstream.NewUDPClient(cfg.Addr, targetAddress, cfg.caCert, cfg.clientCert)
}
func (cfg *AgentConfig) Transport() *http.Transport {
return &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
if addr != AgentHost+":443" {
return nil, &net.AddrError{Err: "invalid address", Addr: addr}
}
if network != "tcp" {
return nil, &net.OpError{Op: "dial", Net: network, Source: nil, Addr: nil}
}
return cfg.DialContext(ctx)
},
TLSClientConfig: &cfg.tlsConfig,
}
}
func (cfg *AgentConfig) TLSConfig() *tls.Config {
return &cfg.tlsConfig
}
var dialer = &net.Dialer{Timeout: 5 * time.Second}
func (cfg *AgentConfig) DialContext(ctx context.Context) (net.Conn, error) {
return dialer.DialContext(ctx, "tcp", cfg.Addr)
}
func (cfg *AgentConfig) String() string {
return cfg.Name + "@" + cfg.Addr
}
func (cfg *AgentConfig) do(ctx context.Context, method, endpoint string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, method, APIBaseURL+endpoint, body)
if err != nil {
return nil, err
}
client := http.Client{
Transport: cfg.Transport(),
}
return client.Do(req)
}
func (cfg *AgentConfig) fetchString(ctx context.Context, endpoint string) (string, int, error) {
resp, err := cfg.do(ctx, "GET", endpoint, nil)
if err != nil {
return "", 0, err
}
defer resp.Body.Close()
data, release, err := httputils.ReadAllBody(resp)
if err != nil {
return "", 0, err
}
ret := string(data)
release(data)
return ret, resp.StatusCode, nil
}
// fetchJSON fetches a JSON response from the agent and unmarshals it into the provided struct
//
// It will return the status code of the response, and error if any.
// If the status code is not http.StatusOK, out will be unchanged but error will still be nil.
func (cfg *AgentConfig) fetchJSON(ctx context.Context, endpoint string, out any) (int, error) {
resp, err := cfg.do(ctx, "GET", endpoint, nil)
if err != nil {
return 0, err
}
defer resp.Body.Close()
data, release, err := httputils.ReadAllBody(resp)
if err != nil {
return 0, err
}
defer release(data)
if resp.StatusCode != http.StatusOK {
return resp.StatusCode, nil
}
err = json.Unmarshal(data, out)
if err != nil {
return 0, err
}
return resp.StatusCode, nil
}

View File

@@ -0,0 +1,28 @@
package agent
import (
"bytes"
"text/template"
_ "embed"
)
var (
//go:embed templates/agent.compose.yml.tmpl
agentComposeYAML string
agentComposeYAMLTemplate = template.Must(template.New("agent.compose.yml.tmpl").Parse(agentComposeYAML))
)
const (
DockerImageProduction = "ghcr.io/yusing/godoxy-agent:latest"
DockerImageNightly = "ghcr.io/yusing/godoxy-agent:nightly"
)
func (c *AgentComposeConfig) Generate() (string, error) {
buf := bytes.NewBuffer(make([]byte, 0, 1024))
err := agentComposeYAMLTemplate.Execute(buf, c)
if err != nil {
return "", err
}
return buf.String(), nil
}

25
agent/pkg/agent/env.go Normal file
View File

@@ -0,0 +1,25 @@
package agent
type (
ContainerRuntime string
AgentEnvConfig struct {
Name string
Port int
CACert string
SSLCert string
ContainerRuntime ContainerRuntime
}
AgentComposeConfig struct {
Image string
*AgentEnvConfig
}
Generator interface {
Generate() (string, error)
}
)
const (
ContainerRuntimeDocker ContainerRuntime = "docker"
ContainerRuntimePodman ContainerRuntime = "podman"
// ContainerRuntimeNerdctl ContainerRuntime = "nerdctl"
)

View File

@@ -0,0 +1,245 @@
package agent
import (
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"io"
"math/big"
"strings"
"time"
"github.com/yusing/godoxy/agent/pkg/agent/common"
)
func toPEMPair(certDER []byte, key *ecdsa.PrivateKey) *PEMPair {
marshaledKey, err := marshalECPrivateKey(key)
if err != nil {
// This is a critical internal error during PEM encoding of a newly generated key.
// Panicking is acceptable here as it indicates a fundamental issue.
panic(fmt.Sprintf("failed to marshal EC private key for PEM encoding: %v", err))
}
return &PEMPair{
Cert: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}),
Key: pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: marshaledKey}),
}
}
func marshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error) {
derBytes, err := x509.MarshalECPrivateKey(key)
if err != nil {
return nil, fmt.Errorf("failed to marshal EC private key: %w", err)
}
return derBytes, nil
}
func b64Encode(data []byte) string {
return base64.StdEncoding.EncodeToString(data)
}
func b64Decode(data string) ([]byte, error) {
return base64.StdEncoding.DecodeString(data)
}
type PEMPair struct {
Cert, Key []byte
}
func (p *PEMPair) String() string {
return b64Encode(p.Cert) + ";" + b64Encode(p.Key)
}
func (p *PEMPair) Load(data string) (err error) {
parts := strings.Split(data, ";")
if len(parts) != 2 {
return errors.New("invalid PEM pair")
}
p.Cert, err = b64Decode(parts[0])
if err != nil {
return err
}
p.Key, err = b64Decode(parts[1])
if err != nil {
return err
}
return nil
}
func (p *PEMPair) Encrypt(encKey []byte) (PEMPair, error) {
cert, err := encrypt(p.Cert, encKey)
if err != nil {
return PEMPair{}, err
}
key, err := encrypt(p.Key, encKey)
if err != nil {
return PEMPair{}, err
}
return PEMPair{Cert: cert, Key: key}, nil
}
func (p *PEMPair) Decrypt(encKey []byte) (PEMPair, error) {
cert, err := decrypt(p.Cert, encKey)
if err != nil {
return PEMPair{}, err
}
key, err := decrypt(p.Key, encKey)
if err != nil {
return PEMPair{}, err
}
return PEMPair{Cert: cert, Key: key}, nil
}
func encrypt(data []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
return gcm.Seal(nonce, nonce, data, nil), nil
}
func decrypt(data []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := data[:gcm.NonceSize()]
ciphertext := data[gcm.NonceSize():]
return gcm.Open(nil, nonce, ciphertext, nil)
}
func (p *PEMPair) ToTLSCert() (*tls.Certificate, error) {
cert, err := tls.X509KeyPair(p.Cert, p.Key)
return &cert, err
}
func newSerialNumber() (*big.Int, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) // 128-bit random number
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, fmt.Errorf("failed to generate serial number: %w", err)
}
return serialNumber, nil
}
func NewAgent() (ca, srv, client *PEMPair, err error) {
caSerialNumber, err := newSerialNumber()
if err != nil {
return nil, nil, nil, err
}
// Create the CA's certificate
caTemplate := &x509.Certificate{
SerialNumber: caSerialNumber,
Subject: pkix.Name{
Organization: []string{"GoDoxy"},
CommonName: common.CertsDNSName,
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1000, 0, 0), // 1000 years
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 0,
MaxPathLenZero: true,
SignatureAlgorithm: x509.ECDSAWithSHA256,
}
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, nil, err
}
caDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey)
if err != nil {
return nil, nil, nil, err
}
ca = toPEMPair(caDER, caKey)
// Generate a new private key for the server certificate
serverKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, nil, err
}
serverSerialNumber, err := newSerialNumber()
if err != nil {
return nil, nil, nil, err
}
srvTemplate := &x509.Certificate{
SerialNumber: serverSerialNumber,
Issuer: caTemplate.Subject,
Subject: pkix.Name{
Organization: caTemplate.Subject.Organization,
OrganizationalUnit: []string{"Server"},
CommonName: common.CertsDNSName,
},
DNSNames: []string{common.CertsDNSName},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1000, 0, 0), // Add validity period
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
SignatureAlgorithm: x509.ECDSAWithSHA256,
}
srvCertDER, err := x509.CreateCertificate(rand.Reader, srvTemplate, caTemplate, &serverKey.PublicKey, caKey)
if err != nil {
return nil, nil, nil, err
}
srv = toPEMPair(srvCertDER, serverKey)
clientKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, nil, err
}
clientSerialNumber, err := newSerialNumber()
if err != nil {
return nil, nil, nil, err
}
clientTemplate := &x509.Certificate{
SerialNumber: clientSerialNumber,
Issuer: caTemplate.Subject,
Subject: pkix.Name{
Organization: caTemplate.Subject.Organization,
OrganizationalUnit: []string{"Client"},
CommonName: common.CertsDNSName,
},
DNSNames: []string{common.CertsDNSName},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1000, 0, 0),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
SignatureAlgorithm: x509.ECDSAWithSHA256,
}
clientCertDER, err := x509.CreateCertificate(rand.Reader, clientTemplate, caTemplate, &clientKey.PublicKey, caKey)
if err != nil {
return nil, nil, nil, err
}
client = toPEMPair(clientCertDER, clientKey)
return ca, srv, client, err
}

View File

@@ -0,0 +1,113 @@
package agent
import (
"crypto/rand"
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
"github.com/yusing/godoxy/agent/pkg/agent/common"
)
func TestNewAgent(t *testing.T) {
ca, srv, client, err := NewAgent()
require.NoError(t, err)
require.NotNil(t, ca)
require.NotNil(t, srv)
require.NotNil(t, client)
}
func TestPEMPair(t *testing.T) {
ca, srv, client, err := NewAgent()
require.NoError(t, err)
for i, p := range []*PEMPair{ca, srv, client} {
t.Run(fmt.Sprintf("load-%d", i), func(t *testing.T) {
var pp PEMPair
err := pp.Load(p.String())
require.NoError(t, err)
require.Equal(t, p.Cert, pp.Cert)
require.Equal(t, p.Key, pp.Key)
})
}
}
func TestPEMPairToTLSCert(t *testing.T) {
ca, srv, client, err := NewAgent()
require.NoError(t, err)
for i, p := range []*PEMPair{ca, srv, client} {
t.Run(fmt.Sprintf("toTLSCert-%d", i), func(t *testing.T) {
cert, err := p.ToTLSCert()
require.NoError(t, err)
require.NotNil(t, cert)
})
}
}
func TestServerClient(t *testing.T) {
ca, srv, client, err := NewAgent()
require.NoError(t, err)
srvTLS, err := srv.ToTLSCert()
require.NoError(t, err)
require.NotNil(t, srvTLS)
clientTLS, err := client.ToTLSCert()
require.NoError(t, err)
require.NotNil(t, clientTLS)
caPool := x509.NewCertPool()
require.True(t, caPool.AppendCertsFromPEM(ca.Cert))
srvTLSConfig := &tls.Config{
Certificates: []tls.Certificate{*srvTLS},
ClientCAs: caPool,
ClientAuth: tls.RequireAndVerifyClientCert,
}
clientTLSConfig := &tls.Config{
Certificates: []tls.Certificate{*clientTLS},
RootCAs: caPool,
ServerName: common.CertsDNSName,
}
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
server.TLS = srvTLSConfig
server.StartTLS()
defer server.Close()
httpClient := &http.Client{
Transport: &http.Transport{TLSClientConfig: clientTLSConfig},
}
resp, err := httpClient.Get(server.URL)
require.NoError(t, err)
require.Equal(t, resp.StatusCode, http.StatusOK)
}
func TestPEMPairEncryptDecrypt(t *testing.T) {
encKey := make([]byte, 32)
_, err := rand.Read(encKey)
require.NoError(t, err)
ca, _, _, err := NewAgent()
require.NoError(t, err)
encCA, err := ca.Encrypt(encKey)
require.NoError(t, err)
require.NotNil(t, encCA)
decCA, err := encCA.Decrypt(encKey)
require.NoError(t, err)
require.NotNil(t, decCA)
require.Equal(t, string(ca.Cert), string(decCA.Cert))
require.Equal(t, string(ca.Key), string(decCA.Key))
}

View File

@@ -0,0 +1,197 @@
# Stream proxy protocol
This package implements a small header-based handshake that allows an authenticated client to request forwarding to a `(host, port)` destination. It supports both TCP-over-TLS and UDP-over-DTLS transports.
## Overview
```mermaid
graph TD
subgraph Client
TC[TCPClient] -->|TLS| TSS[TCPServer]
UC[UDPClient] -->|DTLS| USS[UDPServer]
end
subgraph Stream Protocol
H[StreamRequestHeader]
end
TSS -->|Redirect| DST1[Destination TCP]
USS -->|Forward UDP| DST2[Destination UDP]
```
## Header
The on-wire header is a fixed-size binary blob:
- `Version` (8 bytes)
- `HostLength` (1 byte)
- `Host` (255 bytes, NUL padded)
- `PortLength` (1 byte)
- `Port` (5 bytes, NUL padded)
- `Flag` (1 byte, protocol flags)
- `Checksum` (4 bytes, big-endian CRC32)
Total: `headerSize = 8 + 1 + 255 + 1 + 5 + 1 + 4 = 275` bytes.
Checksum is `crc32.ChecksumIEEE(header[0:headerSize-4])`.
### Flags
The `Flag` field is a bitmask of protocol flags defined by `FlagType`:
| Flag | Value | Purpose |
| ---------------------- | ----- | ---------------------------------------------------------------------- |
| `FlagCloseImmediately` | `1` | Health check probe - server closes immediately after validating header |
See [`FlagType`](header.go:26) and [`FlagCloseImmediately`](header.go:28).
See [`StreamRequestHeader`](header.go:30).
## File Structure
| File | Purpose |
| ----------------------------------- | ------------------------------------------------------------ |
| [`header.go`](header.go) | Stream request header structure and validation. |
| [`tcp_client.go`](tcp_client.go:12) | TCP client implementation with TLS transport. |
| [`tcp_server.go`](tcp_server.go:13) | TCP server implementation for handling stream requests. |
| [`udp_client.go`](udp_client.go:13) | UDP client implementation with DTLS transport. |
| [`udp_server.go`](udp_server.go:17) | UDP server implementation for handling DTLS stream requests. |
| [`common.go`](common.go:11) | Connection manager and shared constants. |
## Constants
| Constant | Value | Purpose |
| ---------------------- | ------------------------- | ------------------------------------------------------- |
| `StreamALPN` | `"godoxy-agent-stream/1"` | TLS ALPN protocol for stream multiplexing. |
| `headerSize` | `275` bytes | Total size of the stream request header. |
| `dialTimeout` | `10s` | Timeout for establishing destination connections. |
| `readDeadline` | `10s` | Read timeout for UDP destination sockets. |
| `FlagCloseImmediately` | `1` | Flag for health check probe - server closes immediately |
See [`common.go`](common.go:11).
## Public API
### Types
#### `StreamRequestHeader`
Represents the on-wire protocol header used to negotiate a stream tunnel.
```go
type StreamRequestHeader struct {
Version [8]byte // Fixed to "0.1.0" with NUL padding
HostLength byte // Actual host name length (0-255)
Host [255]byte // NUL-padded host name
PortLength byte // Actual port string length (0-5)
Port [5]byte // NUL-padded port string
Flag FlagType // Protocol flags (e.g., FlagCloseImmediately)
Checksum [4]byte // CRC32 checksum of header without checksum
}
```
**Methods:**
- `NewStreamRequestHeader(host, port string) (*StreamRequestHeader, error)` - Creates a header for the given host and port. Returns error if host exceeds 255 bytes or port exceeds 5 bytes.
- `NewStreamHealthCheckHeader() *StreamRequestHeader` - Creates a header with `FlagCloseImmediately` set for health check probes.
- `Validate() bool` - Validates the version and checksum.
- `GetHostPort() (string, string)` - Extracts the host and port from the header.
- `ShouldCloseImmediately() bool` - Returns true if `FlagCloseImmediately` is set.
### TCP Functions
- [`NewTCPClient()`](tcp_client.go:26) - Creates a TLS client connection and sends the stream header.
- [`NewTCPServerHandler()`](tcp_server.go:24) - Creates a handler for ALPN-multiplexed connections (no listener).
- [`NewTCPServerFromListener()`](tcp_server.go:36) - Wraps an existing TLS listener.
- [`NewTCPServer()`](tcp_server.go:45) - Creates a fully-configured TCP server with TLS listener.
### UDP Functions
- [`NewUDPClient()`](udp_client.go:27) - Creates a DTLS client connection and sends the stream header.
- [`NewUDPServer()`](udp_server.go:26) - Creates a DTLS server listening on the given UDP address.
## Health Check Probes
The protocol supports health check probes using the `FlagCloseImmediately` flag. When a client sends a header with this flag set, the server validates the header and immediately closes the connection without establishing a destination tunnel.
This is useful for:
- Connectivity testing between agent and server
- Verifying TLS/DTLS handshake and mTLS authentication
- Monitoring stream protocol availability
**Usage:**
```go
header := stream.NewStreamHealthCheckHeader()
// Send header over TLS/DTLS connection
// Server will validate and close immediately
```
Both TCP and UDP servers silently handle health check probes without logging errors.
See [`NewStreamHealthCheckHeader()`](header.go:66) and [`FlagCloseImmediately`](header.go:28).
## TCP behavior
1. Client establishes a TLS connection to the stream server.
2. Client sends exactly one header as a handshake.
3. After the handshake, both sides proxy raw TCP bytes between client and destination.
Server reads the header using `io.ReadFull` to avoid dropping bytes.
See [`NewTCPClient()`](tcp_client.go:26) and [`(*TCPServer).redirect()`](tcp_server.go:116).
## UDP-over-DTLS behavior
1. Client establishes a DTLS connection to the stream server.
2. Client sends exactly one header as a handshake.
3. After the handshake, both sides proxy raw UDP datagrams:
- client -> destination: DTLS payload is written to destination `UDPConn`
- destination -> client: destination payload is written back to the DTLS connection
Responses do **not** include a header.
The UDP server uses a bidirectional forwarding model:
- One goroutine forwards from client to destination
- Another goroutine forwards from destination to client
The destination reader uses `readDeadline` to periodically wake up and check for context cancellation. Timeouts do not terminate the session.
See [`NewUDPClient()`](udp_client.go:27) and [`(*UDPServer).handleDTLSConnection()`](udp_server.go:89).
## Connection Management
Both `TCPServer` and `UDPServer` create a dedicated destination connection per incoming stream session and close it when the session ends (no destination connection reuse).
## Error Handling
| Error | Description |
| --------------------- | ----------------------------------------------- |
| `ErrInvalidHeader` | Header validation failed (version or checksum). |
| `ErrCloseImmediately` | Health check probe - server closed immediately. |
Errors from connection creation are propagated to the caller.
See [`header.go`](header.go:23).
## Integration
This package is used by the agent to provide stream tunneling capabilities. See the parent [`agent`](../README.md) package for integration details with the GoDoxy server.
### Certificate Requirements
Both TCP and UDP servers require:
- CA certificate for client verification
- Server certificate for TLS/DTLS termination
Both clients require:
- CA certificate for server verification
- Client certificate for mTLS authentication
### ALPN Protocol
The `StreamALPN` constant (`"godoxy-agent-stream/1"`) is used to multiplex stream tunnel traffic and HTTPS API traffic on the same port. Connections negotiating this ALPN are routed to the stream handler.

View File

@@ -0,0 +1,24 @@
package stream
import (
"time"
"github.com/pion/dtls/v3"
"github.com/yusing/goutils/synk"
)
const (
dialTimeout = 10 * time.Second
readDeadline = 10 * time.Second
)
// StreamALPN is the TLS ALPN protocol id used to multiplex the TCP stream tunnel
// and the HTTPS API on the same TCP port.
//
// When a client negotiates this ALPN, the agent will route the connection to the
// stream tunnel handler instead of the HTTP handler.
const StreamALPN = "godoxy-agent-stream/1"
var dTLSCipherSuites = []dtls.CipherSuiteID{dtls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}
var sizedPool = synk.GetSizedBytesPool()

View File

@@ -0,0 +1,117 @@
package stream
import (
"encoding/binary"
"errors"
"fmt"
"hash/crc32"
"reflect"
"unsafe"
)
const (
versionSize = 8
hostSize = 255
portSize = 5
flagSize = 1
checksumSize = 4 // crc32 checksum
headerSize = versionSize + 1 + hostSize + 1 + portSize + flagSize + checksumSize
)
var version = [versionSize]byte{'0', '.', '1', '.', '0', 0, 0, 0}
var ErrInvalidHeader = errors.New("invalid header")
var ErrCloseImmediately = errors.New("close immediately")
type FlagType uint8
const FlagCloseImmediately FlagType = 1 << iota
type StreamRequestHeader struct {
Version [versionSize]byte
HostLength byte
Host [hostSize]byte
PortLength byte
Port [portSize]byte
Flag FlagType
Checksum [checksumSize]byte
}
func init() {
if headerSize != reflect.TypeFor[StreamRequestHeader]().Size() {
panic("headerSize does not match the size of StreamRequestHeader")
}
}
func NewStreamRequestHeader(host, port string) (*StreamRequestHeader, error) {
if len(host) > hostSize {
return nil, fmt.Errorf("host is too long: max %d characters, got %d", hostSize, len(host))
}
if len(port) > portSize {
return nil, fmt.Errorf("port is too long: max %d characters, got %d", portSize, len(port))
}
header := &StreamRequestHeader{}
copy(header.Version[:], version[:])
header.HostLength = byte(len(host))
copy(header.Host[:], host)
header.PortLength = byte(len(port))
copy(header.Port[:], port)
header.updateChecksum()
return header, nil
}
func NewStreamHealthCheckHeader() *StreamRequestHeader {
header := &StreamRequestHeader{}
copy(header.Version[:], version[:])
header.Flag |= FlagCloseImmediately
header.updateChecksum()
return header
}
// ToHeader converts header byte array to a copy of itself as a StreamRequestHeader.
func ToHeader(buf *[headerSize]byte) StreamRequestHeader {
return *(*StreamRequestHeader)(unsafe.Pointer(buf))
}
func (h *StreamRequestHeader) GetHostPort() (string, string) {
return string(h.Host[:h.HostLength]), string(h.Port[:h.PortLength])
}
func (h *StreamRequestHeader) Validate() bool {
if h.Version != version {
return false
}
if h.HostLength > hostSize {
return false
}
if h.PortLength > portSize {
return false
}
return h.validateChecksum()
}
func (h *StreamRequestHeader) ShouldCloseImmediately() bool {
return h.Flag&FlagCloseImmediately != 0
}
func (h *StreamRequestHeader) updateChecksum() {
checksum := crc32.ChecksumIEEE(h.BytesWithoutChecksum())
binary.BigEndian.PutUint32(h.Checksum[:], checksum)
}
func (h *StreamRequestHeader) validateChecksum() bool {
checksum := crc32.ChecksumIEEE(h.BytesWithoutChecksum())
return checksum == binary.BigEndian.Uint32(h.Checksum[:])
}
func (h *StreamRequestHeader) BytesWithoutChecksum() []byte {
return (*[headerSize - checksumSize]byte)(unsafe.Pointer(h))[:]
}
func (h *StreamRequestHeader) Bytes() []byte {
return (*[headerSize]byte)(unsafe.Pointer(h))[:]
}

View File

@@ -0,0 +1,26 @@
package stream
import (
"testing"
)
func TestStreamRequestHeader_RoundTripAndChecksum(t *testing.T) {
h, err := NewStreamRequestHeader("example.com", "443")
if err != nil {
t.Fatalf("NewStreamRequestHeader: %v", err)
}
if !h.Validate() {
t.Fatalf("expected header to validate")
}
var buf [headerSize]byte
copy(buf[:], h.Bytes())
h2 := ToHeader(&buf)
if !h2.Validate() {
t.Fatalf("expected round-tripped header to validate")
}
host, port := h2.GetHostPort()
if host != "example.com" || port != "443" {
t.Fatalf("unexpected host/port: %q:%q", host, port)
}
}

View File

@@ -0,0 +1,122 @@
package stream
import (
"crypto/tls"
"crypto/x509"
"net"
"time"
"github.com/yusing/godoxy/agent/pkg/agent/common"
)
type TCPClient struct {
conn net.Conn
}
// NewTCPClient creates a new TCP client for the agent.
//
// It will establish a TLS connection and send a stream request header to the server.
//
// It returns an error if
// - the target address is invalid
// - the stream request header is invalid
// - the TLS configuration is invalid
// - the TLS connection fails
// - the stream request header is not sent
func NewTCPClient(serverAddr, targetAddress string, caCert *x509.Certificate, clientCert *tls.Certificate) (net.Conn, error) {
host, port, err := net.SplitHostPort(targetAddress)
if err != nil {
return nil, err
}
header, err := NewStreamRequestHeader(host, port)
if err != nil {
return nil, err
}
return newTCPClientWIthHeader(serverAddr, header, caCert, clientCert)
}
func TCPHealthCheck(serverAddr string, caCert *x509.Certificate, clientCert *tls.Certificate) error {
header := NewStreamHealthCheckHeader()
conn, err := newTCPClientWIthHeader(serverAddr, header, caCert, clientCert)
if err != nil {
return err
}
conn.Close()
return nil
}
func newTCPClientWIthHeader(serverAddr string, header *StreamRequestHeader, caCert *x509.Certificate, clientCert *tls.Certificate) (net.Conn, error) {
// Setup TLS configuration
caCertPool := x509.NewCertPool()
caCertPool.AddCert(caCert)
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{*clientCert},
RootCAs: caCertPool,
MinVersion: tls.VersionTLS12,
NextProtos: []string{StreamALPN},
ServerName: common.CertsDNSName,
}
// Establish TLS connection
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: dialTimeout}, "tcp", serverAddr, tlsConfig)
if err != nil {
return nil, err
}
// Send the stream header once as a handshake.
if _, err := conn.Write(header.Bytes()); err != nil {
_ = conn.Close()
return nil, err
}
return &TCPClient{
conn: conn,
}, nil
}
func (c *TCPClient) Read(p []byte) (n int, err error) {
return c.conn.Read(p)
}
func (c *TCPClient) Write(p []byte) (n int, err error) {
return c.conn.Write(p)
}
func (c *TCPClient) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *TCPClient) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *TCPClient) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *TCPClient) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *TCPClient) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
func (c *TCPClient) Close() error {
return c.conn.Close()
}
// ConnectionState exposes the underlying TLS connection state when the client is
// backed by *tls.Conn.
//
// This is primarily used by tests and diagnostics.
func (c *TCPClient) ConnectionState() tls.ConnectionState {
if tc, ok := c.conn.(*tls.Conn); ok {
return tc.ConnectionState()
}
return tls.ConnectionState{}
}

View File

@@ -0,0 +1,179 @@
package stream
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"io"
"net"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
ioutils "github.com/yusing/goutils/io"
)
type TCPServer struct {
ctx context.Context
listener net.Listener
}
// NewTCPServerHandler creates a TCP stream server that can serve already-accepted
// connections (e.g. handed off by an ALPN multiplexer).
//
// This variant does not require a listener. Use TCPServer.ServeConn to handle
// each incoming stream connection.
func NewTCPServerHandler(ctx context.Context) *TCPServer {
s := &TCPServer{ctx: ctx}
return s
}
// NewTCPServerFromListener creates a TCP stream server from an already-prepared
// listener.
//
// The listener is expected to yield connections that are already secured (e.g.
// a TLS/mTLS listener, or pre-handshaked *tls.Conn). This is used when the agent
// multiplexes HTTPS and stream-tunnel traffic on the same port.
func NewTCPServerFromListener(ctx context.Context, listener net.Listener) *TCPServer {
s := &TCPServer{
ctx: ctx,
listener: listener,
}
return s
}
func NewTCPServer(ctx context.Context, listener *net.TCPListener, caCert *x509.Certificate, serverCert *tls.Certificate) *TCPServer {
caCertPool := x509.NewCertPool()
caCertPool.AddCert(caCert)
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{*serverCert},
ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
MinVersion: tls.VersionTLS12,
NextProtos: []string{StreamALPN},
}
tcpListener := tls.NewListener(listener, tlsConfig)
return NewTCPServerFromListener(ctx, tcpListener)
}
func (s *TCPServer) Start() error {
if s.listener == nil {
return net.ErrClosed
}
context.AfterFunc(s.ctx, func() {
_ = s.listener.Close()
})
for {
conn, err := s.listener.Accept()
if err != nil {
if errors.Is(err, net.ErrClosed) && s.ctx.Err() != nil {
return s.ctx.Err()
}
return err
}
go s.handle(conn)
}
}
// ServeConn serves a single stream connection.
//
// The provided connection is expected to be already secured (TLS/mTLS) and to
// speak the stream protocol (i.e. the client will send the stream header first).
//
// This method blocks until the stream finishes.
func (s *TCPServer) ServeConn(conn net.Conn) {
s.handle(conn)
}
func (s *TCPServer) Addr() net.Addr {
if s.listener == nil {
return nil
}
return s.listener.Addr()
}
func (s *TCPServer) Close() error {
if s.listener == nil {
return nil
}
return s.listener.Close()
}
func (s *TCPServer) logger(clientConn net.Conn) *zerolog.Logger {
ev := log.With().Str("protocol", "tcp").
Str("remote", clientConn.RemoteAddr().String())
if s.listener != nil {
ev = ev.Str("addr", s.listener.Addr().String())
}
l := ev.Logger()
return &l
}
func (s *TCPServer) loggerWithDst(dstConn net.Conn, clientConn net.Conn) *zerolog.Logger {
ev := log.With().Str("protocol", "tcp").
Str("remote", clientConn.RemoteAddr().String()).
Str("dst", dstConn.RemoteAddr().String())
if s.listener != nil {
ev = ev.Str("addr", s.listener.Addr().String())
}
l := ev.Logger()
return &l
}
func (s *TCPServer) handle(conn net.Conn) {
defer conn.Close()
dst, err := s.redirect(conn)
if err != nil {
// Health check probe: close connection
if errors.Is(err, ErrCloseImmediately) {
s.logger(conn).Info().Msg("Health check received")
return
}
s.logger(conn).Err(err).Msg("failed to redirect connection")
return
}
defer dst.Close()
pipe := ioutils.NewBidirectionalPipe(s.ctx, conn, dst)
err = pipe.Start()
if err != nil {
s.loggerWithDst(dst, conn).Err(err).Msg("failed to start bidirectional pipe")
return
}
}
func (s *TCPServer) redirect(conn net.Conn) (net.Conn, error) {
// Read the stream header once as a handshake.
var headerBuf [headerSize]byte
_ = conn.SetReadDeadline(time.Now().Add(dialTimeout))
if _, err := io.ReadFull(conn, headerBuf[:]); err != nil {
return nil, err
}
_ = conn.SetReadDeadline(time.Time{})
header := ToHeader(&headerBuf)
if !header.Validate() {
return nil, ErrInvalidHeader
}
// Health check: close immediately if FlagCloseImmediately is set
if header.ShouldCloseImmediately() {
return nil, ErrCloseImmediately
}
// get destination connection
host, port := header.GetHostPort()
return s.createDestConnection(host, port)
}
func (s *TCPServer) createDestConnection(host, port string) (net.Conn, error) {
addr := net.JoinHostPort(host, port)
conn, err := net.DialTimeout("tcp", addr, dialTimeout)
if err != nil {
return nil, err
}
return conn, nil
}

View File

@@ -0,0 +1,26 @@
package stream_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/yusing/godoxy/agent/pkg/agent/stream"
)
func TestTCPHealthCheck(t *testing.T) {
certs := genTestCerts(t)
srv := startTCPServer(t, certs)
err := stream.TCPHealthCheck(srv.Addr.String(), certs.CaCert, certs.ClientCert)
require.NoError(t, err, "health check")
}
func TestUDPHealthCheck(t *testing.T) {
certs := genTestCerts(t)
srv := startUDPServer(t, certs)
err := stream.UDPHealthCheck(srv.Addr.String(), certs.CaCert, certs.ClientCert)
require.NoError(t, err, "health check")
}

View File

@@ -0,0 +1,94 @@
package stream_test
import (
"bufio"
"context"
"crypto/tls"
"crypto/x509"
"io"
"net"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/yusing/godoxy/agent/pkg/agent/common"
"github.com/yusing/godoxy/agent/pkg/agent/stream"
)
func TestTLSALPNMux_HTTPAndStreamShareOnePort(t *testing.T) {
certs := genTestCerts(t)
baseLn, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0})
require.NoError(t, err, "listen tcp")
defer baseLn.Close()
baseAddr := baseLn.Addr().String()
caCertPool := x509.NewCertPool()
caCertPool.AddCert(certs.CaCert)
serverTLS := &tls.Config{
Certificates: []tls.Certificate{*certs.SrvCert},
ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
MinVersion: tls.VersionTLS12,
NextProtos: []string{"http/1.1", stream.StreamALPN},
}
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
streamSrv := stream.NewTCPServerHandler(ctx)
defer func() { _ = streamSrv.Close() }()
tlsLn := tls.NewListener(baseLn, serverTLS)
defer func() { _ = tlsLn.Close() }()
// HTTP server
httpSrv := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("ok"))
}),
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){
stream.StreamALPN: func(_ *http.Server, conn *tls.Conn, _ http.Handler) {
streamSrv.ServeConn(conn)
},
},
}
go func() { _ = httpSrv.Serve(tlsLn) }()
defer func() { _ = httpSrv.Close() }()
// Stream destination
dstAddr, closeDst := startTCPEcho(t)
defer closeDst()
// HTTP client over the same port
clientTLS := &tls.Config{
Certificates: []tls.Certificate{*certs.ClientCert},
RootCAs: caCertPool,
MinVersion: tls.VersionTLS12,
NextProtos: []string{"http/1.1"},
ServerName: common.CertsDNSName,
}
hc, err := tls.Dial("tcp", baseAddr, clientTLS)
require.NoError(t, err, "dial https")
defer hc.Close()
_ = hc.SetDeadline(time.Now().Add(2 * time.Second))
_, err = hc.Write([]byte("GET / HTTP/1.1\r\nHost: godoxy-agent\r\n\r\n"))
require.NoError(t, err, "write http request")
r := bufio.NewReader(hc)
statusLine, err := r.ReadString('\n')
require.NoError(t, err, "read status line")
require.Contains(t, statusLine, "200", "expected 200")
// Stream client over the same port
client := NewTCPClient(t, baseAddr, dstAddr, certs)
defer client.Close()
_ = client.SetDeadline(time.Now().Add(2 * time.Second))
msg := []byte("ping over mux")
_, err = client.Write(msg)
require.NoError(t, err, "write stream payload")
buf := make([]byte, len(msg))
_, err = io.ReadFull(client, buf)
require.NoError(t, err, "read stream payload")
require.Equal(t, msg, buf)
}

View File

@@ -0,0 +1,201 @@
package stream_test
import (
"crypto/tls"
"fmt"
"io"
"sync"
"testing"
"time"
"github.com/pion/dtls/v3"
"github.com/stretchr/testify/require"
"github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/agent/pkg/agent/stream"
)
func TestTCPServer_FullFlow(t *testing.T) {
certs := genTestCerts(t)
dstAddr, closeDst := startTCPEcho(t)
defer closeDst()
srv := startTCPServer(t, certs)
client := NewTCPClient(t, srv.Addr.String(), dstAddr, certs)
defer client.Close()
// Ensure ALPN is negotiated as expected (required for multiplexing).
withState, ok := client.(interface{ ConnectionState() tls.ConnectionState })
require.True(t, ok, "tcp client should expose TLS connection state")
require.Equal(t, stream.StreamALPN, withState.ConnectionState().NegotiatedProtocol)
_ = client.SetDeadline(time.Now().Add(2 * time.Second))
msg := []byte("ping over tcp")
_, err := client.Write(msg)
require.NoError(t, err, "write to client")
buf := make([]byte, len(msg))
_, err = io.ReadFull(client, buf)
require.NoError(t, err, "read from client")
require.Equal(t, string(msg), string(buf), "unexpected echo")
}
func TestTCPServer_ConcurrentConnections(t *testing.T) {
certs := genTestCerts(t)
dstAddr, closeDst := startTCPEcho(t)
defer closeDst()
srv := startTCPServer(t, certs)
const nClients = 25
errs := make(chan error, nClients)
var wg sync.WaitGroup
wg.Add(nClients)
for i := range nClients {
go func() {
defer wg.Done()
client := NewTCPClient(t, srv.Addr.String(), dstAddr, certs)
defer client.Close()
_ = client.SetDeadline(time.Now().Add(2 * time.Second))
msg := fmt.Appendf(nil, "ping over tcp %d", i)
if _, err := client.Write(msg); err != nil {
errs <- fmt.Errorf("write to client: %w", err)
return
}
buf := make([]byte, len(msg))
if _, err := io.ReadFull(client, buf); err != nil {
errs <- fmt.Errorf("read from client: %w", err)
return
}
if string(msg) != string(buf) {
errs <- fmt.Errorf("unexpected echo: got=%q want=%q", string(buf), string(msg))
return
}
}()
}
wg.Wait()
close(errs)
for err := range errs {
require.NoError(t, err)
}
}
func TestUDPServer_RejectInvalidClient(t *testing.T) {
certs := genTestCerts(t)
// Generate a self-signed client cert that is NOT signed by the CA
_, _, invalidClientPEM, err := agent.NewAgent()
require.NoError(t, err, "generate invalid client certs")
invalidClientCert, err := invalidClientPEM.ToTLSCert()
require.NoError(t, err, "parse invalid client cert")
dstAddr, closeDst := startUDPEcho(t)
defer closeDst()
srv := startUDPServer(t, certs)
// Try to connect with a client cert from a different CA
_, err = stream.NewUDPClient(srv.Addr.String(), dstAddr, certs.CaCert, invalidClientCert)
require.Error(t, err, "expected error when connecting with client cert from different CA")
var handshakeErr *dtls.HandshakeError
require.ErrorAs(t, err, &handshakeErr, "expected handshake error")
}
func TestUDPServer_RejectClientWithoutCert(t *testing.T) {
certs := genTestCerts(t)
dstAddr, closeDst := startUDPEcho(t)
defer closeDst()
srv := startUDPServer(t, certs)
time.Sleep(time.Second)
// Try to connect without any client certificate
// Create a TLS cert without a private key to simulate no client cert
emptyCert := &tls.Certificate{}
_, err := stream.NewUDPClient(srv.Addr.String(), dstAddr, certs.CaCert, emptyCert)
require.Error(t, err, "expected error when connecting without client cert")
require.ErrorContains(t, err, "no certificate provided", "expected no cert error")
}
func TestUDPServer_FullFlow(t *testing.T) {
certs := genTestCerts(t)
dstAddr, closeDst := startUDPEcho(t)
defer closeDst()
srv := startUDPServer(t, certs)
client := NewUDPClient(t, srv.Addr.String(), dstAddr, certs)
defer client.Close()
_ = client.SetDeadline(time.Now().Add(2 * time.Second))
msg := []byte("ping over udp")
_, err := client.Write(msg)
require.NoError(t, err, "write to client")
buf := make([]byte, 2048)
n, err := client.Read(buf)
require.NoError(t, err, "read from client")
require.Equal(t, string(msg), string(buf[:n]), "unexpected echo")
}
func TestUDPServer_ConcurrentConnections(t *testing.T) {
certs := genTestCerts(t)
dstAddr, closeDst := startUDPEcho(t)
defer closeDst()
srv := startUDPServer(t, certs)
const nClients = 25
errs := make(chan error, nClients)
var wg sync.WaitGroup
wg.Add(nClients)
for i := range nClients {
go func() {
defer wg.Done()
client := NewUDPClient(t, srv.Addr.String(), dstAddr, certs)
defer client.Close()
_ = client.SetDeadline(time.Now().Add(5 * time.Second))
msg := fmt.Appendf(nil, "ping over udp %d", i)
if _, err := client.Write(msg); err != nil {
errs <- fmt.Errorf("write to client: %w", err)
return
}
buf := make([]byte, 2048)
n, err := client.Read(buf)
if err != nil {
errs <- fmt.Errorf("read from client: %w", err)
return
}
if string(msg) != string(buf[:n]) {
errs <- fmt.Errorf("unexpected echo: got=%q want=%q", string(buf[:n]), string(msg))
return
}
}()
}
wg.Wait()
close(errs)
for err := range errs {
require.NoError(t, err)
}
}

View File

@@ -0,0 +1,177 @@
package stream_test
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"io"
"net"
"testing"
"time"
"github.com/pion/transport/v3/udp"
"github.com/stretchr/testify/require"
"github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/agent/pkg/agent/stream"
)
// CertBundle holds all certificates needed for testing.
type CertBundle struct {
CaCert *x509.Certificate
SrvCert *tls.Certificate
ClientCert *tls.Certificate
}
// genTestCerts generates certificates for testing and returns them as a CertBundle.
func genTestCerts(t *testing.T) CertBundle {
t.Helper()
caPEM, srvPEM, clientPEM, err := agent.NewAgent()
require.NoError(t, err, "generate agent certs")
caCert, err := caPEM.ToTLSCert()
require.NoError(t, err, "parse CA cert")
srvCert, err := srvPEM.ToTLSCert()
require.NoError(t, err, "parse server cert")
clientCert, err := clientPEM.ToTLSCert()
require.NoError(t, err, "parse client cert")
return CertBundle{
CaCert: caCert.Leaf,
SrvCert: srvCert,
ClientCert: clientCert,
}
}
// startTCPEcho starts a TCP echo server and returns its address and close function.
func startTCPEcho(t *testing.T) (addr string, closeFn func()) {
t.Helper()
ln, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err, "listen tcp")
done := make(chan struct{})
go func() {
defer close(done)
for {
c, err := ln.Accept()
if err != nil {
return
}
go func(conn net.Conn) {
defer conn.Close()
_, _ = io.Copy(conn, conn)
}(c)
}
}()
return ln.Addr().String(), func() {
_ = ln.Close()
<-done
}
}
// startUDPEcho starts a UDP echo server and returns its address and close function.
func startUDPEcho(t *testing.T) (addr string, closeFn func()) {
t.Helper()
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
require.NoError(t, err, "listen udp")
uc := pc.(*net.UDPConn)
done := make(chan struct{})
go func() {
defer close(done)
buf := make([]byte, 65535)
for {
n, raddr, err := uc.ReadFromUDP(buf)
if err != nil {
return
}
_, _ = uc.WriteToUDP(buf[:n], raddr)
}
}()
return uc.LocalAddr().String(), func() {
_ = uc.Close()
<-done
}
}
// TestServer wraps a server with its startup goroutine for cleanup.
type TestServer struct {
Server interface{ Close() error }
Addr net.Addr
}
// startTCPServer starts a TCP server and returns a TestServer for cleanup.
func startTCPServer(t *testing.T, certs CertBundle) TestServer {
t.Helper()
tcpLn, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0})
require.NoError(t, err, "listen tcp")
ctx, cancel := context.WithCancel(t.Context())
srv := stream.NewTCPServer(ctx, tcpLn, certs.CaCert, certs.SrvCert)
errCh := make(chan error, 1)
go func() { errCh <- srv.Start() }()
t.Cleanup(func() {
cancel()
_ = srv.Close()
err := <-errCh
if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, net.ErrClosed) {
t.Logf("tcp server exit: %v", err)
}
})
return TestServer{
Server: srv,
Addr: srv.Addr(),
}
}
// startUDPServer starts a UDP server and returns a TestServer for cleanup.
func startUDPServer(t *testing.T, certs CertBundle) TestServer {
t.Helper()
ctx, cancel := context.WithCancel(t.Context())
srv := stream.NewUDPServer(ctx, "udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0}, certs.CaCert, certs.SrvCert)
errCh := make(chan error, 1)
go func() { errCh <- srv.Start() }()
time.Sleep(100 * time.Millisecond)
t.Cleanup(func() {
cancel()
_ = srv.Close()
err := <-errCh
if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, net.ErrClosed) && !errors.Is(err, udp.ErrClosedListener) {
t.Logf("udp server exit: %v", err)
}
})
return TestServer{
Server: srv,
Addr: srv.Addr(),
}
}
// NewTCPClient creates a TCP client connected to the server with test certificates.
func NewTCPClient(t *testing.T, serverAddr, targetAddress string, certs CertBundle) net.Conn {
t.Helper()
client, err := stream.NewTCPClient(serverAddr, targetAddress, certs.CaCert, certs.ClientCert)
require.NoError(t, err, "create tcp client")
return client
}
// NewUDPClient creates a UDP client connected to the server with test certificates.
func NewUDPClient(t *testing.T, serverAddr, targetAddress string, certs CertBundle) net.Conn {
t.Helper()
client, err := stream.NewUDPClient(serverAddr, targetAddress, certs.CaCert, certs.ClientCert)
require.NoError(t, err, "create udp client")
return client
}

View File

@@ -0,0 +1,118 @@
package stream
import (
"crypto/tls"
"crypto/x509"
"net"
"time"
"github.com/pion/dtls/v3"
"github.com/yusing/godoxy/agent/pkg/agent/common"
)
type UDPClient struct {
conn net.Conn
}
// NewUDPClient creates a new UDP client for the agent.
//
// It will establish a DTLS connection and send a stream request header to the server.
//
// It returns an error if
// - the target address is invalid
// - the stream request header is invalid
// - the DTLS configuration is invalid
// - the DTLS connection fails
// - the stream request header is not sent
func NewUDPClient(serverAddr, targetAddress string, caCert *x509.Certificate, clientCert *tls.Certificate) (net.Conn, error) {
host, port, err := net.SplitHostPort(targetAddress)
if err != nil {
return nil, err
}
header, err := NewStreamRequestHeader(host, port)
if err != nil {
return nil, err
}
return newUDPClientWIthHeader(serverAddr, header, caCert, clientCert)
}
func newUDPClientWIthHeader(serverAddr string, header *StreamRequestHeader, caCert *x509.Certificate, clientCert *tls.Certificate) (net.Conn, error) {
// Setup DTLS configuration
caCertPool := x509.NewCertPool()
caCertPool.AddCert(caCert)
dtlsConfig := &dtls.Config{
Certificates: []tls.Certificate{*clientCert},
RootCAs: caCertPool,
InsecureSkipVerify: false,
ExtendedMasterSecret: dtls.RequireExtendedMasterSecret,
ServerName: common.CertsDNSName,
CipherSuites: dTLSCipherSuites,
}
raddr, err := net.ResolveUDPAddr("udp", serverAddr)
if err != nil {
return nil, err
}
// Establish DTLS connection
conn, err := dtls.Dial("udp", raddr, dtlsConfig)
if err != nil {
return nil, err
}
// Send the stream header once as a handshake.
if _, err := conn.Write(header.Bytes()); err != nil {
_ = conn.Close()
return nil, err
}
return &UDPClient{
conn: conn,
}, nil
}
func UDPHealthCheck(serverAddr string, caCert *x509.Certificate, clientCert *tls.Certificate) error {
header := NewStreamHealthCheckHeader()
conn, err := newUDPClientWIthHeader(serverAddr, header, caCert, clientCert)
if err != nil {
return err
}
conn.Close()
return nil
}
func (c *UDPClient) Read(p []byte) (n int, err error) {
return c.conn.Read(p)
}
func (c *UDPClient) Write(p []byte) (n int, err error) {
return c.conn.Write(p)
}
func (c *UDPClient) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *UDPClient) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *UDPClient) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *UDPClient) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *UDPClient) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
func (c *UDPClient) Close() error {
return c.conn.Close()
}

View File

@@ -0,0 +1,208 @@
package stream
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"io"
"net"
"time"
"github.com/pion/dtls/v3"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
type UDPServer struct {
ctx context.Context
network string
laddr *net.UDPAddr
listener net.Listener
dtlsConfig *dtls.Config
}
func NewUDPServer(ctx context.Context, network string, laddr *net.UDPAddr, caCert *x509.Certificate, serverCert *tls.Certificate) *UDPServer {
caCertPool := x509.NewCertPool()
caCertPool.AddCert(caCert)
dtlsConfig := &dtls.Config{
Certificates: []tls.Certificate{*serverCert},
ClientCAs: caCertPool,
ClientAuth: dtls.RequireAndVerifyClientCert,
ExtendedMasterSecret: dtls.RequireExtendedMasterSecret,
CipherSuites: dTLSCipherSuites,
}
s := &UDPServer{
ctx: ctx,
network: network,
laddr: laddr,
dtlsConfig: dtlsConfig,
}
return s
}
func (s *UDPServer) Start() error {
listener, err := dtls.Listen(s.network, s.laddr, s.dtlsConfig)
if err != nil {
return err
}
s.listener = listener
context.AfterFunc(s.ctx, func() {
_ = s.listener.Close()
})
for {
conn, err := s.listener.Accept()
if err != nil {
// Expected error when context cancelled
if errors.Is(err, net.ErrClosed) && s.ctx.Err() != nil {
return s.ctx.Err()
}
return err
}
go s.handleDTLSConnection(conn)
}
}
func (s *UDPServer) Addr() net.Addr {
if s.listener != nil {
return s.listener.Addr()
}
return s.laddr
}
func (s *UDPServer) Close() error {
if s.listener != nil {
return s.listener.Close()
}
return nil
}
func (s *UDPServer) logger(clientConn net.Conn) *zerolog.Logger {
l := log.With().Str("protocol", "udp").
Str("addr", s.Addr().String()).
Str("remote", clientConn.RemoteAddr().String()).Logger()
return &l
}
func (s *UDPServer) loggerWithDst(clientConn net.Conn, dstConn *net.UDPConn) *zerolog.Logger {
l := log.With().Str("protocol", "udp").
Str("addr", s.Addr().String()).
Str("remote", clientConn.RemoteAddr().String()).
Str("dst", dstConn.RemoteAddr().String()).Logger()
return &l
}
func (s *UDPServer) handleDTLSConnection(clientConn net.Conn) {
defer clientConn.Close()
// Read the stream header once as a handshake.
var headerBuf [headerSize]byte
_ = clientConn.SetReadDeadline(time.Now().Add(dialTimeout))
if _, err := io.ReadFull(clientConn, headerBuf[:]); err != nil {
s.logger(clientConn).Err(err).Msg("failed to read stream header")
return
}
_ = clientConn.SetReadDeadline(time.Time{})
header := ToHeader(&headerBuf)
if !header.Validate() {
s.logger(clientConn).Error().Bytes("header", headerBuf[:]).Msg("invalid stream header received")
return
}
// Health check probe: close connection
if header.ShouldCloseImmediately() {
s.logger(clientConn).Info().Msg("Health check received")
return
}
host, port := header.GetHostPort()
dstConn, err := s.createDestConnection(host, port)
if err != nil {
s.logger(clientConn).Err(err).Msg("failed to get or create destination connection")
return
}
defer dstConn.Close()
go s.forwardFromDestination(dstConn, clientConn)
buf := sizedPool.GetSized(65535)
defer sizedPool.Put(buf)
for {
select {
case <-s.ctx.Done():
return
default:
n, err := clientConn.Read(buf)
// Per net.Conn contract, Read may return (n > 0, err == io.EOF).
// Always forward any bytes we got before acting on the error.
if n > 0 {
if _, werr := dstConn.Write(buf[:n]); werr != nil {
s.logger(clientConn).Err(werr).Msgf("failed to write %d bytes to destination", n)
return
}
}
if err != nil {
// Expected shutdown paths.
if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {
return
}
s.logger(clientConn).Err(err).Msg("failed to read from client")
return
}
}
}
}
func (s *UDPServer) createDestConnection(host, port string) (*net.UDPConn, error) {
addr := net.JoinHostPort(host, port)
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
dstConn, err := net.DialUDP("udp", nil, udpAddr)
if err != nil {
return nil, err
}
return dstConn, nil
}
func (s *UDPServer) forwardFromDestination(dstConn *net.UDPConn, clientConn net.Conn) {
buffer := sizedPool.GetSized(65535)
defer sizedPool.Put(buffer)
for {
select {
case <-s.ctx.Done():
return
default:
_ = dstConn.SetReadDeadline(time.Now().Add(readDeadline))
n, err := dstConn.Read(buffer)
if err != nil {
// The destination socket can be closed when the client disconnects (e.g. during
// the stream support probe in AgentConfig.StartWithCerts). Treat that as a
// normal exit and avoid noisy logs.
if errors.Is(err, net.ErrClosed) {
return
}
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue
}
s.loggerWithDst(clientConn, dstConn).Err(err).Msg("failed to read from destination")
return
}
if _, err := clientConn.Write(buffer[:n]); err != nil {
s.loggerWithDst(clientConn, dstConn).Err(err).Msgf("failed to write %d bytes to client", n)
return
}
}
}
}

View File

@@ -0,0 +1,67 @@
services:
agent:
image: "{{.Image}}"
container_name: godoxy-agent
restart: always
{{ if eq .ContainerRuntime "podman" -}}
ports:
- "{{.Port}}:{{.Port}}/tcp"
- "{{.Port}}:{{.Port}}/udp"
{{ else -}}
network_mode: host # do not change this
{{ end -}}
environment:
{{ if eq .ContainerRuntime "nerdctl" -}}
DOCKER_SOCKET: "/var/run/containerd/containerd.sock"
RUNTIME: "nerdctl"
{{ else if eq .ContainerRuntime "podman" -}}
DOCKER_SOCKET: "/var/run/podman/podman.sock"
RUNTIME: "podman"
{{ else -}}
DOCKER_SOCKET: "/var/run/docker.sock"
RUNTIME: "docker"
{{ end -}}
AGENT_NAME: "{{.Name}}"
AGENT_PORT: "{{.Port}}"
AGENT_CA_CERT: "{{.CACert}}"
AGENT_SSL_CERT: "{{.SSLCert}}"
# use agent as a docker socket proxy: [host]:port
# set LISTEN_ADDR to enable (e.g. 127.0.0.1:2375)
LISTEN_ADDR:
POST: false
ALLOW_RESTARTS: false
ALLOW_START: false
ALLOW_STOP: false
AUTH: false
BUILD: false
COMMIT: false
CONFIGS: false
CONTAINERS: false
DISTRIBUTION: false
EVENTS: true
EXEC: false
GRPC: false
IMAGES: false
INFO: false
NETWORKS: false
NODES: false
PING: true
PLUGINS: false
SECRETS: false
SERVICES: false
SESSION: false
SWARM: false
SYSTEM: false
TASKS: false
VERSION: true
VOLUMES: false
volumes:
{{ if eq .ContainerRuntime "podman" -}}
- /var/run/podman/podman.sock:/var/run/podman/podman.sock
{{ else if eq .ContainerRuntime "nerdctl" -}}
- /var/run/containerd/containerd.sock:/var/run/containerd/containerd.sock
- /var/lib/nerdctl:/var/lib/nerdctl:ro # required to read metadata like network info
{{ else -}}
- /var/run/docker.sock:/var/run/docker.sock
{{ end -}}
- ./data:/app/data

View File

@@ -0,0 +1,122 @@
# agent/pkg/agentproxy
Package for configuring HTTP proxy connections through the GoDoxy Agent using HTTP headers.
## Overview
This package provides types and functions for parsing and setting agent proxy configuration via HTTP headers. It supports both a modern base64-encoded JSON format and a legacy header-based format for backward compatibility.
## Architecture
```mermaid
graph LR
A[HTTP Request] --> B[ConfigFromHeaders]
B --> C{Modern Format?}
C -->|Yes| D[Parse X-Proxy-Config Base64 JSON]
C -->|No| E[Parse Legacy Headers]
D --> F[Config]
E --> F
F --> G[SetAgentProxyConfigHeaders]
G --> H[Modern Headers]
G --> I[Legacy Headers]
```
## Public Types
### Config
```go
type Config struct {
Scheme string // Proxy scheme (http or https)
Host string // Proxy host (hostname or hostname:port)
HTTPConfig // Extended HTTP configuration
}
```
The `HTTPConfig` embedded type (from `internal/route/types`) includes:
- `NoTLSVerify` - Skip TLS certificate verification
- `ResponseHeaderTimeout` - Timeout for response headers
- `DisableCompression` - Disable gzip compression
## Public Functions
### ConfigFromHeaders
```go
func ConfigFromHeaders(h http.Header) (Config, error)
```
Parses proxy configuration from HTTP request headers. Tries modern format first, falls back to legacy format if not present.
### proxyConfigFromHeaders
```go
func proxyConfigFromHeaders(h http.Header) (Config, error)
```
Parses the modern base64-encoded JSON format from `X-Proxy-Config` header.
### proxyConfigFromHeadersLegacy
```go
func proxyConfigFromHeadersLegacy(h http.Header) Config
```
Parses the legacy header format:
- `X-Proxy-Host` - Proxy host
- `X-Proxy-Https` - Whether to use HTTPS
- `X-Proxy-Skip-Tls-Verify` - Skip TLS verification
- `X-Proxy-Response-Header-Timeout` - Response timeout in seconds
### SetAgentProxyConfigHeaders
```go
func (cfg *Config) SetAgentProxyConfigHeaders(h http.Header)
```
Sets headers for modern format with base64-encoded JSON config.
### SetAgentProxyConfigHeadersLegacy
```go
func (cfg *Config) SetAgentProxyConfigHeadersLegacy(h http.Header)
```
Sets headers for legacy format with individual header fields.
## Header Constants
Modern headers:
- `HeaderXProxyScheme` - Proxy scheme
- `HeaderXProxyHost` - Proxy host
- `HeaderXProxyConfig` - Base64-encoded JSON config
Legacy headers (deprecated):
- `HeaderXProxyHTTPS`
- `HeaderXProxySkipTLSVerify`
- `HeaderXProxyResponseHeaderTimeout`
## Usage Example
```go
// Reading configuration from incoming request headers
func handleRequest(w http.ResponseWriter, r *http.Request) {
cfg, err := agentproxy.ConfigFromHeaders(r.Header)
if err != nil {
http.Error(w, "Invalid proxy config", http.StatusBadRequest)
return
}
// Use cfg.Scheme and cfg.Host to proxy the request
// ...
}
```
## Integration
This package is used by `agent/pkg/handler/proxy_http.go` to configure reverse proxy connections based on request headers.

View File

@@ -0,0 +1,73 @@
package agentproxy
import (
"encoding/base64"
"net/http"
"strconv"
"time"
"github.com/bytedance/sonic"
route "github.com/yusing/godoxy/internal/route/types"
)
type Config struct {
Scheme string `json:"scheme,omitempty"`
Host string `json:"host,omitempty"` // host or host:port
route.HTTPConfig
}
func ConfigFromHeaders(h http.Header) (Config, error) {
cfg, err := proxyConfigFromHeaders(h)
if cfg.Host == "" || err != nil {
cfg = proxyConfigFromHeadersLegacy(h)
}
return cfg, nil
}
func proxyConfigFromHeadersLegacy(h http.Header) (cfg Config) {
cfg.Host = h.Get(HeaderXProxyHost)
isHTTPS, _ := strconv.ParseBool(h.Get(HeaderXProxyHTTPS))
cfg.NoTLSVerify, _ = strconv.ParseBool(h.Get(HeaderXProxySkipTLSVerify))
responseHeaderTimeout, err := strconv.Atoi(h.Get(HeaderXProxyResponseHeaderTimeout))
if err != nil {
responseHeaderTimeout = 0
}
cfg.ResponseHeaderTimeout = time.Duration(responseHeaderTimeout) * time.Second
cfg.Scheme = "http"
if isHTTPS {
cfg.Scheme = "https"
}
return cfg
}
func proxyConfigFromHeaders(h http.Header) (cfg Config, err error) {
cfg.Scheme = h.Get(HeaderXProxyScheme)
cfg.Host = h.Get(HeaderXProxyHost)
cfgBase64 := h.Get(HeaderXProxyConfig)
cfgJSON, err := base64.StdEncoding.DecodeString(cfgBase64)
if err != nil {
return cfg, err
}
err = sonic.Unmarshal(cfgJSON, &cfg)
return cfg, err
}
func (cfg *Config) SetAgentProxyConfigHeadersLegacy(h http.Header) {
h.Set(HeaderXProxyHost, cfg.Host)
h.Set(HeaderXProxyHTTPS, strconv.FormatBool(cfg.Scheme == "https"))
h.Set(HeaderXProxySkipTLSVerify, strconv.FormatBool(cfg.NoTLSVerify))
h.Set(HeaderXProxyResponseHeaderTimeout, strconv.Itoa(int(cfg.ResponseHeaderTimeout.Round(time.Second).Seconds())))
}
func (cfg *Config) SetAgentProxyConfigHeaders(h http.Header) {
h.Set(HeaderXProxyHost, cfg.Host)
h.Set(HeaderXProxyScheme, string(cfg.Scheme))
cfgJSON, _ := sonic.Marshal(cfg.HTTPConfig)
cfgBase64 := base64.StdEncoding.EncodeToString(cfgJSON)
h.Set(HeaderXProxyConfig, cfgBase64)
}

View File

@@ -0,0 +1,14 @@
package agentproxy
const (
HeaderXProxyScheme = "X-Proxy-Scheme"
HeaderXProxyHost = "X-Proxy-Host"
HeaderXProxyConfig = "X-Proxy-Config"
)
// deprecated
const (
HeaderXProxyHTTPS = "X-Proxy-Https"
HeaderXProxySkipTLSVerify = "X-Proxy-Skip-Tls-Verify"
HeaderXProxyResponseHeaderTimeout = "X-Proxy-Response-Header-Timeout"
)

102
agent/pkg/certs/README.md Normal file
View File

@@ -0,0 +1,102 @@
# agent/pkg/certs
Certificate management package for creating and extracting certificate archives.
## Overview
This package provides utilities for packaging SSL certificates into ZIP archives and extracting them. It is used by the GoDoxy Agent to distribute certificates to clients in a convenient format.
## Architecture
```mermaid
graph LR
A[Raw Certs] --> B[ZipCert]
B --> C[ZIP Archive]
C --> D[ca.pem]
C --> E[cert.pem]
C --> F[key.pem]
G[ZIP Archive] --> H[ExtractCert]
H --> I[ca, crt, key]
```
## Public Functions
### ZipCert
```go
func ZipCert(ca, crt, key []byte) ([]byte, error)
```
Creates a ZIP archive containing three PEM files:
- `ca.pem` - CA certificate
- `cert.pem` - Server/client certificate
- `key.pem` - Private key
**Parameters:**
- `ca` - CA certificate in PEM format
- `crt` - Certificate in PEM format
- `key` - Private key in PEM format
**Returns:**
- ZIP archive bytes
- Error if packing fails
### ExtractCert
```go
func ExtractCert(data []byte) (ca, crt, key []byte, err error)
```
Extracts certificates from a ZIP archive created by `ZipCert`.
**Parameters:**
- `data` - ZIP archive bytes
**Returns:**
- `ca` - CA certificate bytes
- `crt` - Certificate bytes
- `key` - Private key bytes
- Error if extraction fails
### AgentCertsFilepath
```go
func AgentCertsFilepath(host string) (filepathOut string, ok bool)
```
Generates the file path for storing agent certificates.
**Parameters:**
- `host` - Agent hostname
**Returns:**
- Full file path within `certs/` directory
- `false` if host is invalid (contains path separators or special characters)
### isValidAgentHost
```go
func isValidAgentHost(host string) bool
```
Validates that a host string is safe for use in file paths.
## Constants
```go
const AgentCertsBasePath = "certs"
```
Base directory for storing certificate archives.
## File Format
The ZIP archive uses `zip.Store` compression (no compression) for fast creation and extraction. Each file is stored with its standard name (`ca.pem`, `cert.pem`, `key.pem`).

85
agent/pkg/certs/zip.go Normal file
View File

@@ -0,0 +1,85 @@
package certs
import (
"archive/zip"
"bytes"
"io"
"path/filepath"
strutils "github.com/yusing/goutils/strings"
)
const AgentCertsBasePath = "certs"
func writeFile(zipWriter *zip.Writer, name string, data []byte) error {
w, err := zipWriter.CreateHeader(&zip.FileHeader{
Name: name,
Method: zip.Store,
})
if err != nil {
return err
}
_, err = w.Write(data)
return err
}
func readFile(f *zip.File) ([]byte, error) {
r, err := f.Open()
if err != nil {
return nil, err
}
defer r.Close()
return io.ReadAll(r)
}
func ZipCert(ca, crt, key []byte) ([]byte, error) {
data := bytes.NewBuffer(make([]byte, 0, 6144))
zipWriter := zip.NewWriter(data)
defer zipWriter.Close()
if err := writeFile(zipWriter, "ca.pem", ca); err != nil {
return nil, err
}
if err := writeFile(zipWriter, "cert.pem", crt); err != nil {
return nil, err
}
if err := writeFile(zipWriter, "key.pem", key); err != nil {
return nil, err
}
if err := zipWriter.Close(); err != nil {
return nil, err
}
return data.Bytes(), nil
}
func isValidAgentHost(host string) bool {
return strutils.IsValidFilename(host + ".zip")
}
func AgentCertsFilepath(host string) (filepathOut string, ok bool) {
if !isValidAgentHost(host) {
return "", false
}
return filepath.Join(AgentCertsBasePath, host+".zip"), true
}
func ExtractCert(data []byte) (ca, crt, key []byte, err error) {
zipReader, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
if err != nil {
return nil, nil, nil, err
}
for _, file := range zipReader.File {
switch file.Name {
case "ca.pem":
ca, err = readFile(file)
case "cert.pem":
crt, err = readFile(file)
case "key.pem":
key, err = readFile(file)
}
if err != nil {
return nil, nil, nil, err
}
}
return ca, crt, key, nil
}

View File

@@ -0,0 +1,20 @@
package certs_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/yusing/godoxy/agent/pkg/certs"
)
func TestZipCert(t *testing.T) {
ca, crt, key := []byte("test1"), []byte("test2"), []byte("test3")
zipData, err := certs.ZipCert(ca, crt, key)
require.NoError(t, err)
ca2, crt2, key2, err := certs.ExtractCert(zipData)
require.NoError(t, err)
require.Equal(t, ca, ca2)
require.Equal(t, crt, crt2)
require.Equal(t, key, key2)
}

52
agent/pkg/env/README.md vendored Normal file
View File

@@ -0,0 +1,52 @@
# agent/pkg/env
Environment configuration package for the GoDoxy Agent.
## Overview
This package manages environment variable parsing and provides a centralized location for all agent configuration options. It is automatically initialized on import.
## Variables
| Variable | Type | Default | Description |
| -------------------------- | ---------------- | ---------------------- | --------------------------------------- |
| `DockerSocket` | string | `/var/run/docker.sock` | Path to Docker socket |
| `AgentName` | string | System hostname | Agent identifier |
| `AgentPort` | int | `8890` | Agent server port |
| `AgentSkipClientCertCheck` | bool | `false` | Skip mTLS certificate verification |
| `AgentCACert` | string | (empty) | Base64 Encoded CA certificate + key |
| `AgentSSLCert` | string | (empty) | Base64 Encoded server certificate + key |
| `Runtime` | ContainerRuntime | `docker` | Container runtime (docker or podman) |
## ContainerRuntime Type
```go
type ContainerRuntime string
const (
ContainerRuntimeDocker ContainerRuntime = "docker"
ContainerRuntimePodman ContainerRuntime = "podman"
)
```
## Public Functions
### DefaultAgentName
```go
func DefaultAgentName() string
```
Returns the system hostname as the default agent name. Falls back to `"agent"` if hostname cannot be determined.
### Load
```go
func Load()
```
Reloads all environment variables from the environment. Called automatically on package init, but can be called again to refresh configuration.
## Validation
The `Load()` function validates that `Runtime` is either `docker` or `podman`. An invalid runtime causes a fatal error.

49
agent/pkg/env/env.go vendored Normal file
View File

@@ -0,0 +1,49 @@
package env
import (
"os"
"github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/goutils/env"
"github.com/rs/zerolog/log"
)
func DefaultAgentName() string {
name, err := os.Hostname()
if err != nil {
return "agent"
}
return name
}
var (
AgentName string
AgentPort int
AgentSkipClientCertCheck bool
AgentCACert string
AgentSSLCert string
DockerSocket string
Runtime agent.ContainerRuntime
)
func init() {
Load()
}
func Load() {
DockerSocket = env.GetEnvString("DOCKER_SOCKET", "/var/run/docker.sock")
AgentName = env.GetEnvString("AGENT_NAME", DefaultAgentName())
AgentPort = env.GetEnvInt("AGENT_PORT", 8890)
AgentSkipClientCertCheck = env.GetEnvBool("AGENT_SKIP_CLIENT_CERT_CHECK", false)
AgentCACert = env.GetEnvString("AGENT_CA_CERT", "")
AgentSSLCert = env.GetEnvString("AGENT_SSL_CERT", "")
Runtime = agent.ContainerRuntime(env.GetEnvString("RUNTIME", "docker"))
switch Runtime {
case agent.ContainerRuntimeDocker, agent.ContainerRuntimePodman: //, agent.ContainerRuntimeNerdctl:
default:
log.Fatal().Str("runtime", string(Runtime)).Msg("invalid runtime")
}
}

127
agent/pkg/handler/README.md Normal file
View File

@@ -0,0 +1,127 @@
# agent/pkg/handler
HTTP request handler package for the GoDoxy Agent.
## Overview
This package provides the HTTP handler for the GoDoxy Agent server, including endpoints for:
- Version information
- Agent name and runtime
- Health checks
- System metrics (via SSE)
- HTTP proxy routing
- Docker socket proxying
## Architecture
```mermaid
graph TD
A[HTTP Request] --> B[NewAgentHandler]
B --> C{ServeMux Router}
C --> D[GET /version]
C --> E[GET /name]
C --> F[GET /runtime]
C --> G[GET /health]
C --> H[GET /system-info]
C --> I[GET /proxy/http/#123;path...#125;]
C --> J[ /#42; Docker Socket]
H --> K[Gin Router]
K --> L[WebSocket Upgrade]
L --> M[SystemInfo Poller]
```
## Public Types
### ServeMux
```go
type ServeMux struct{ *http.ServeMux }
```
Wrapper around `http.ServeMux` with agent-specific endpoint helpers.
**Methods:**
- `HandleEndpoint(method, endpoint string, handler http.HandlerFunc)` - Registers handler with API base path
- `HandleFunc(endpoint string, handler http.HandlerFunc)` - Registers GET handler with API base path
## Public Functions
### NewAgentHandler
```go
func NewAgentHandler() http.Handler
```
Creates and configures the HTTP handler for the agent server. Sets up:
- Gin-based metrics handler with WebSocket support for SSE
- All standard agent endpoints
- HTTP proxy endpoint
- Docker socket proxy fallback
## Endpoints
| Endpoint | Method | Description |
| ----------------------- | -------- | ------------------------------------ |
| `/version` | GET | Returns agent version |
| `/name` | GET | Returns agent name |
| `/runtime` | GET | Returns container runtime |
| `/health` | GET | Health check with scheme query param |
| `/system-info` | GET | System metrics via SSE or WebSocket |
| `/proxy/http/{path...}` | GET/POST | HTTP proxy with config from headers |
| `/*` | \* | Docker socket proxy |
## Sub-packages
### proxy_http.go
Handles HTTP proxy requests by reading configuration from request headers and proxying to the configured upstream.
**Key Function:**
- `ProxyHTTP(w, r)` - Proxies HTTP requests based on `X-Proxy-*` headers
### check_health.go
Handles health check requests for various schemes.
**Key Function:**
- `CheckHealth(w, r)` - Performs health checks with configurable scheme
**Supported Schemes:**
- `http`, `https` - HTTP health check
- `h2c` - HTTP/2 cleartext health check
- `tcp`, `udp`, `tcp4`, `udp4`, `tcp6`, `udp6` - TCP/UDP health check
- `fileserver` - File existence check
## Usage Example
```go
package main
import (
"net/http"
"github.com/yusing/godoxy/agent/pkg/handler"
)
func main() {
mux := http.NewServeMux()
mux.Handle("/", handler.NewAgentHandler())
http.ListenAndServe(":8890", mux)
}
```
## WebSocket Support
The handler includes a permissive WebSocket upgrader for internal use (no origin check). This enables real-time system metrics streaming via Server-Sent Events (SSE).
## Docker Socket Integration
All unmatched requests fall through to the Docker socket handler, allowing the agent to proxy Docker API calls when configured.

View File

@@ -0,0 +1,90 @@
package handler
import (
"net"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/bytedance/sonic"
healthcheck "github.com/yusing/godoxy/internal/health/check"
"github.com/yusing/godoxy/internal/types"
)
func CheckHealth(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
scheme := query.Get("scheme")
if scheme == "" {
http.Error(w, "missing scheme", http.StatusBadRequest)
return
}
timeout := parseMsOrDefault(query.Get("timeout"))
var (
result types.HealthCheckResult
err error
)
switch scheme {
case "fileserver":
path := query.Get("path")
if path == "" {
http.Error(w, "missing path", http.StatusBadRequest)
return
}
result, err = healthcheck.FileServer(path)
case "http", "https", "h2c": // path is optional
host := query.Get("host")
path := query.Get("path")
if host == "" {
http.Error(w, "missing host", http.StatusBadRequest)
return
}
url := url.URL{Scheme: scheme, Host: host}
if scheme == "h2c" {
result, err = healthcheck.H2C(r.Context(), &url, http.MethodHead, path, timeout)
} else {
result, err = healthcheck.HTTP(&url, http.MethodHead, path, timeout)
}
case "tcp", "udp", "tcp4", "udp4", "tcp6", "udp6":
host := query.Get("host")
if host == "" {
http.Error(w, "missing host", http.StatusBadRequest)
return
}
hasPort := strings.Contains(host, ":")
port := query.Get("port")
if port != "" && hasPort {
http.Error(w, "port and host with port cannot both be provided", http.StatusBadRequest)
return
}
if port != "" {
host = net.JoinHostPort(host, port)
}
url := url.URL{Scheme: scheme, Host: host}
result, err = healthcheck.Stream(r.Context(), &url, timeout)
}
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
sonic.ConfigDefault.NewEncoder(w).Encode(result)
}
func parseMsOrDefault(msStr string) time.Duration {
if msStr == "" {
return types.HealthCheckTimeoutDefault
}
timeoutMs, _ := strconv.ParseInt(msStr, 10, 64)
if timeoutMs == 0 {
return types.HealthCheckTimeoutDefault
}
return time.Duration(timeoutMs) * time.Millisecond
}

View File

@@ -0,0 +1,226 @@
package handler_test
import (
"encoding/json"
"net"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"testing"
"github.com/stretchr/testify/require"
"github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/agent/pkg/handler"
"github.com/yusing/godoxy/internal/types"
)
func TestCheckHealthHTTP(t *testing.T) {
tests := []struct {
name string
setupServer func() *httptest.Server
queryParams map[string]string
expectedStatus int
expectedHealthy bool
}{
{
name: "Valid",
setupServer: func() *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
},
queryParams: map[string]string{
"scheme": "http",
"host": "localhost",
"path": "/",
},
expectedStatus: http.StatusOK,
expectedHealthy: true,
},
{
name: "InvalidQuery",
setupServer: nil,
queryParams: map[string]string{
"scheme": "http",
},
expectedStatus: http.StatusBadRequest,
},
{
name: "ConnectionError",
setupServer: nil,
queryParams: map[string]string{
"scheme": "http",
"host": "localhost:12345",
},
expectedStatus: http.StatusOK,
expectedHealthy: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var server *httptest.Server
if tt.setupServer != nil {
server = tt.setupServer()
defer server.Close()
u, _ := url.Parse(server.URL)
tt.queryParams["scheme"] = u.Scheme
tt.queryParams["host"] = u.Host
tt.queryParams["path"] = u.Path
}
recorder := httptest.NewRecorder()
query := url.Values{}
for key, value := range tt.queryParams {
query.Set(key, value)
}
request := httptest.NewRequest(http.MethodGet, agent.APIEndpointBase+agent.EndpointHealth+"?"+query.Encode(), nil)
handler.CheckHealth(recorder, request)
require.Equal(t, recorder.Code, tt.expectedStatus)
if tt.expectedStatus == http.StatusOK {
var result types.HealthCheckResult
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &result))
require.Equal(t, result.Healthy, tt.expectedHealthy)
}
})
}
}
func TestCheckHealthFileServer(t *testing.T) {
tests := []struct {
name string
path string
expectedStatus int
expectedHealthy bool
expectedDetail string
}{
{
name: "ValidPath",
path: t.TempDir(),
expectedStatus: http.StatusOK,
expectedHealthy: true,
expectedDetail: "",
},
{
name: "InvalidPath",
path: "/invalid",
expectedStatus: http.StatusOK,
expectedHealthy: false,
expectedDetail: "stat /invalid: no such file or directory",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
query := url.Values{}
query.Set("scheme", "fileserver")
query.Set("path", tt.path)
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, agent.APIEndpointBase+agent.EndpointHealth+"?"+query.Encode(), nil)
handler.CheckHealth(recorder, request)
require.Equal(t, recorder.Code, tt.expectedStatus)
var result types.HealthCheckResult
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &result))
require.Equal(t, result.Healthy, tt.expectedHealthy)
require.Equal(t, result.Detail, tt.expectedDetail)
})
}
}
func TestCheckHealthTCPUDP(t *testing.T) {
tcp, err := net.Listen("tcp", "localhost:0")
require.NoError(t, err)
go func() {
conn, err := tcp.Accept()
require.NoError(t, err)
conn.Close()
}()
udp, err := net.ListenPacket("udp", "localhost:0")
require.NoError(t, err)
go func() {
buf := make([]byte, 1024)
n, addr, err := udp.ReadFrom(buf)
require.NoError(t, err)
require.Equal(t, string(buf[:n]), "ping")
_, _ = udp.WriteTo([]byte("pong"), addr)
udp.Close()
}()
tests := []struct {
name string
scheme string
host string
port int
expectedStatus int
expectedHealthy bool
}{
{
name: "ValidTCP",
scheme: "tcp",
host: "localhost",
port: tcp.Addr().(*net.TCPAddr).Port,
expectedStatus: http.StatusOK,
expectedHealthy: true,
},
{
name: "InvalidHost",
scheme: "tcp",
host: "",
port: 8080,
expectedStatus: http.StatusBadRequest,
expectedHealthy: false,
},
{
name: "ValidUDP",
scheme: "udp",
host: "localhost",
port: udp.LocalAddr().(*net.UDPAddr).Port,
expectedStatus: http.StatusOK,
expectedHealthy: true,
},
{
name: "InvalidHost",
scheme: "udp",
host: "",
port: 8080,
expectedStatus: http.StatusBadRequest,
expectedHealthy: false,
},
{
name: "Port in both host and port",
scheme: "tcp",
host: "localhost:1234",
port: 1234,
expectedStatus: http.StatusBadRequest,
expectedHealthy: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
query := url.Values{}
query.Set("scheme", tt.scheme)
query.Set("host", tt.host)
query.Set("port", strconv.Itoa(tt.port))
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, agent.APIEndpointBase+agent.EndpointHealth+"?"+query.Encode(), nil)
handler.CheckHealth(recorder, request)
require.Equal(t, recorder.Code, tt.expectedStatus)
if tt.expectedStatus == http.StatusOK {
var result types.HealthCheckResult
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &result))
require.Equal(t, result.Healthy, tt.expectedHealthy)
}
})
}
}

View File

@@ -0,0 +1,60 @@
package handler
import (
"net/http"
"github.com/bytedance/sonic"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/agent/pkg/env"
"github.com/yusing/godoxy/internal/metrics/systeminfo"
socketproxy "github.com/yusing/godoxy/socketproxy/pkg"
"github.com/yusing/goutils/version"
)
type ServeMux struct{ *http.ServeMux }
func (mux ServeMux) HandleEndpoint(method, endpoint string, handler http.HandlerFunc) {
mux.ServeMux.HandleFunc(method+" "+agent.APIEndpointBase+endpoint, handler)
}
func (mux ServeMux) HandleFunc(endpoint string, handler http.HandlerFunc) {
mux.ServeMux.HandleFunc(agent.APIEndpointBase+endpoint, handler)
}
var upgrader = &websocket.Upgrader{
// no origin check needed for internal websocket
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func NewAgentHandler() http.Handler {
gin.SetMode(gin.ReleaseMode)
mux := ServeMux{http.NewServeMux()}
metricsHandler := gin.Default()
{
metrics := metricsHandler.Group(agent.APIEndpointBase)
metrics.GET(agent.EndpointSystemInfo, func(c *gin.Context) {
c.Set("upgrader", upgrader)
systeminfo.Poller.ServeHTTP(c)
})
}
mux.HandleFunc(agent.EndpointProxyHTTP+"/{path...}", ProxyHTTP)
mux.HandleFunc(agent.EndpointInfo, func(w http.ResponseWriter, r *http.Request) {
agentInfo := agent.AgentInfo{
Version: version.Get(),
Name: env.AgentName,
Runtime: env.Runtime,
}
w.Header().Set("Content-Type", "application/json")
sonic.ConfigDefault.NewEncoder(w).Encode(agentInfo)
})
mux.HandleEndpoint("GET", agent.EndpointHealth, CheckHealth)
mux.HandleEndpoint("GET", agent.EndpointSystemInfo, metricsHandler.ServeHTTP)
mux.ServeMux.HandleFunc("/", socketproxy.DockerSocketHandler(env.DockerSocket))
return mux
}

View File

@@ -0,0 +1,72 @@
package handler
import (
"fmt"
"net/http"
"net/http/httputil"
"strings"
"time"
"github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/agent/pkg/agentproxy"
)
func NewTransport() *http.Transport {
return &http.Transport{
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
ResponseHeaderTimeout: 60 * time.Second,
WriteBufferSize: 16 * 1024, // 16KB
ReadBufferSize: 16 * 1024, // 16KB
}
}
func ProxyHTTP(w http.ResponseWriter, r *http.Request) {
cfg, err := agentproxy.ConfigFromHeaders(r.Header)
if err != nil {
http.Error(w, fmt.Sprintf("failed to parse agent proxy config: %s", err.Error()), http.StatusBadRequest)
return
}
transport := NewTransport()
if cfg.ResponseHeaderTimeout > 0 {
transport.ResponseHeaderTimeout = cfg.ResponseHeaderTimeout
}
if cfg.DisableCompression {
transport.DisableCompression = true
}
transport.TLSClientConfig, err = cfg.BuildTLSConfig(r.URL)
if err != nil {
http.Error(w, fmt.Sprintf("failed to build TLS client config: %s", err.Error()), http.StatusInternalServerError)
return
}
// Strip the {API_BASE}/proxy/http prefix while preserving URL escaping.
//
// NOTE: `r.URL.Path` is decoded. If we rewrite it without keeping `RawPath`
// in sync, Go may re-escape the path (e.g. turning "%5B" into "%255B"),
// which breaks urls with percent-encoded characters, like Next.js static chunk URLs.
prefix := agent.APIEndpointBase + agent.EndpointProxyHTTP
r.URL.Path = strings.TrimPrefix(r.URL.Path, prefix)
if r.URL.RawPath != "" {
if after, ok := strings.CutPrefix(r.URL.RawPath, prefix); ok {
r.URL.RawPath = after
} else {
// RawPath is no longer a valid encoding for Path; force Go to re-derive it.
r.URL.RawPath = ""
}
}
r.RequestURI = ""
rp := &httputil.ReverseProxy{
Director: func(r *http.Request) {
r.URL.Scheme = cfg.Scheme
r.URL.Host = cfg.Host
},
Transport: transport,
}
rp.ServeHTTP(w, r)
}

BIN
assets/godoxy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

73
cmd/README.md Normal file
View File

@@ -0,0 +1,73 @@
# cmd
Main entry point package for GoDoxy, a lightweight reverse proxy with WebUI for Docker containers.
## Overview
This package contains the `main.go` entry point that initializes and starts the GoDoxy server. It coordinates the initialization of all core components including configuration loading, API server, authentication, and monitoring services.
## Architecture
```mermaid
graph TD
A[main] --> B[Init Profiling]
A --> C[Init Logger]
A --> D[Parallel Init]
D --> D1[DNS Providers]
D --> D2[Icon Cache]
D --> D3[System Info Poller]
D --> D4[Middleware Compose Files]
A --> E[JWT Secret Setup]
A --> F[Create Directories]
A --> G[Load Config]
A --> H[Start Proxy Servers]
A --> I[Init Auth]
A --> J[Start API Server]
A --> K[Debug Server]
A --> L[Uptime Poller]
A --> M[Watch Changes]
A --> N[Wait Exit]
```
## Main Function Flow
The `main()` function performs the following initialization steps:
1. **Profiling Setup**: Initializes pprof endpoints for performance monitoring
1. **Logger Initialization**: Configures zerolog with memory logging
1. **Parallel Initialization**: Starts DNS providers, icon cache, system info poller, and middleware
1. **JWT Secret**: Ensures API JWT secret is set (generates random if not provided)
1. **Directory Preparation**: Creates required directories for logs, certificates, etc.
1. **Configuration Loading**: Loads YAML configuration and reports any errors
1. **Proxy Servers**: Starts HTTP/HTTPS proxy servers based on configuration
1. **Authentication**: Initializes authentication system with access control
1. **API Server**: Starts the REST API server with all configured routes
1. **Debug Server**: Starts the debug page server (development mode)
1. **Monitoring**: Starts uptime and system info polling
1. **Change Watcher**: Starts watching for Docker container and configuration changes
1. **Graceful Shutdown**: Waits for exit signal with configured timeout
## Configuration
The main configuration is loaded from `config/config.yml`. Required directories include:
- `logs/` - Log files
- `config/` - Configuration directory
- `certs/` - SSL certificates
- `proxy/` - Proxy-related files
## Environment Variables
- `API_JWT_SECRET` - Secret key for JWT authentication (optional, auto-generated if not set)
## Dependencies
- `internal/api` - REST API handlers
- `internal/auth` - Authentication and ACL
- `internal/config` - Configuration management
- `internal/dnsproviders` - DNS provider integration
- `internal/homepage` - WebUI dashboard
- `internal/logging` - Logging infrastructure
- `internal/metrics` - System metrics collection
- `internal/route` - HTTP routing and middleware
- `github.com/yusing/goutils/task` - Task lifecycle management

View File

@@ -0,0 +1,18 @@
FROM golang:1.25.5-alpine AS builder
HEALTHCHECK NONE
WORKDIR /src
COPY go.mod go.sum ./
COPY main.go ./
RUN go build -o bench_server main.go
FROM scratch
COPY --from=builder /src/bench_server /app/run
USER 1001:1001
CMD ["/app/run"]

3
cmd/bench_server/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/yusing/godoxy/cmd/bench_server
go 1.25.5

0
cmd/bench_server/go.sum Normal file
View File

34
cmd/bench_server/main.go Normal file
View File

@@ -0,0 +1,34 @@
package main
import (
"log"
"net/http"
"math/rand/v2"
)
var printables = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var random = make([]byte, 4096)
func init() {
for i := range random {
random[i] = printables[rand.IntN(len(printables))]
}
}
func main() {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write(random)
})
server := &http.Server{
Addr: ":80",
Handler: handler,
}
log.Println("Bench server listening on :80")
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("ListenAndServe: %v", err)
}
}

257
cmd/debug_page.go Normal file
View File

@@ -0,0 +1,257 @@
//go:build !production
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/yusing/godoxy/internal/api"
apiV1 "github.com/yusing/godoxy/internal/api/v1"
agentApi "github.com/yusing/godoxy/internal/api/v1/agent"
authApi "github.com/yusing/godoxy/internal/api/v1/auth"
certApi "github.com/yusing/godoxy/internal/api/v1/cert"
dockerApi "github.com/yusing/godoxy/internal/api/v1/docker"
fileApi "github.com/yusing/godoxy/internal/api/v1/file"
homepageApi "github.com/yusing/godoxy/internal/api/v1/homepage"
metricsApi "github.com/yusing/godoxy/internal/api/v1/metrics"
routeApi "github.com/yusing/godoxy/internal/api/v1/route"
"github.com/yusing/godoxy/internal/auth"
"github.com/yusing/godoxy/internal/idlewatcher"
idlewatcherTypes "github.com/yusing/godoxy/internal/idlewatcher/types"
)
type debugMux struct {
endpoints []debugEndpoint
mux http.ServeMux
}
type debugEndpoint struct {
name string
method string
path string
}
func newDebugMux() *debugMux {
return &debugMux{
endpoints: make([]debugEndpoint, 0),
mux: *http.NewServeMux(),
}
}
func (mux *debugMux) registerEndpoint(name, method, path string) {
mux.endpoints = append(mux.endpoints, debugEndpoint{name: name, method: method, path: path})
}
func (mux *debugMux) HandleFunc(name, method, path string, handler http.HandlerFunc) {
mux.registerEndpoint(name, method, path)
mux.mux.HandleFunc(method+" "+path, handler)
}
func (mux *debugMux) Finalize() {
mux.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, `
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, Apple Color Emoji, Segoe UI Emoji;
font-size: 16px;
line-height: 1.5;
color: #f8f9fa;
background-color: #121212;
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
width: 100%;
margin-top: 20px;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #333;
}
th {
background-color: #1e1e1e;
font-weight: 600;
color: #f8f9fa;
}
td {
color: #e9ecef;
}
.link {
color: #007bff;
text-decoration: none;
}
.link:hover {
text-decoration: underline;
}
.method {
color: #6c757d;
font-family: monospace;
}
.path {
color: #6c757d;
font-family: monospace;
}
</style>
</head>
<body>
<table>
<thead>
<tr>
<th>Name</th>
<th>Method</th>
<th>Path</th>
</tr>
</thead>
<tbody>`)
for _, endpoint := range mux.endpoints {
fmt.Fprintf(w, "<tr><td><a class='link' href=%q>%s</a></td><td class='method'>%s</td><td class='path'>%s</td></tr>", endpoint.path, endpoint.name, endpoint.method, endpoint.path)
}
fmt.Fprintln(w, `
</tbody>
</table>
</body>
</html>`)
})
}
func listenDebugServer() {
mux := newDebugMux()
mux.mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/svg+xml")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text x="50" y="50" text-anchor="middle" dominant-baseline="middle">🐙</text></svg>`))
})
mux.HandleFunc("Auth block page", "GET", "/auth/block", AuthBlockPageHandler)
mux.HandleFunc("Idlewatcher loading page", "GET", idlewatcherTypes.PathPrefix, idlewatcher.DebugHandler)
apiHandler := newApiHandler(mux)
mux.mux.HandleFunc("/api/v1/", apiHandler.ServeHTTP)
mux.Finalize()
go http.ListenAndServe(":7777", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Expires", "0")
mux.mux.ServeHTTP(w, r)
}))
}
func newApiHandler(debugMux *debugMux) *gin.Engine {
r := gin.New()
r.Use(api.ErrorHandler())
r.Use(api.ErrorLoggingMiddleware())
r.Use(api.NoCache())
registerGinRoute := func(router gin.IRouter, method, name string, path string, handler gin.HandlerFunc) {
if group, ok := router.(*gin.RouterGroup); ok {
debugMux.registerEndpoint(name, method, group.BasePath()+path)
} else {
debugMux.registerEndpoint(name, method, path)
}
router.Handle(method, path, handler)
}
registerGinRoute(r, "GET", "App version", "/api/v1/version", apiV1.Version)
v1 := r.Group("/api/v1")
if auth.IsEnabled() {
v1Auth := v1.Group("/auth")
{
registerGinRoute(v1Auth, "HEAD", "Auth check", "/check", authApi.Check)
registerGinRoute(v1Auth, "POST", "Auth login", "/login", authApi.Login)
registerGinRoute(v1Auth, "GET", "Auth callback", "/callback", authApi.Callback)
registerGinRoute(v1Auth, "POST", "Auth callback", "/callback", authApi.Callback)
registerGinRoute(v1Auth, "POST", "Auth logout", "/logout", authApi.Logout)
registerGinRoute(v1Auth, "GET", "Auth logout", "/logout", authApi.Logout)
}
}
{
// enable cache for favicon
registerGinRoute(v1, "GET", "Route favicon", "/favicon", apiV1.FavIcon)
registerGinRoute(v1, "GET", "Route health", "/health", apiV1.Health)
registerGinRoute(v1, "GET", "List icons", "/icons", apiV1.Icons)
registerGinRoute(v1, "POST", "Config reload", "/reload", apiV1.Reload)
registerGinRoute(v1, "GET", "Route stats", "/stats", apiV1.Stats)
route := v1.Group("/route")
{
registerGinRoute(route, "GET", "List routes", "/list", routeApi.Routes)
registerGinRoute(route, "GET", "Get route", "/:which", routeApi.Route)
registerGinRoute(route, "GET", "List providers", "/providers", routeApi.Providers)
registerGinRoute(route, "GET", "List routes by provider", "/by_provider", routeApi.ByProvider)
registerGinRoute(route, "POST", "Playground", "/playground", routeApi.Playground)
}
file := v1.Group("/file")
{
registerGinRoute(file, "GET", "List files", "/list", fileApi.List)
registerGinRoute(file, "GET", "Get file", "/content", fileApi.Get)
registerGinRoute(file, "PUT", "Set file", "/content", fileApi.Set)
registerGinRoute(file, "POST", "Set file", "/content", fileApi.Set)
registerGinRoute(file, "POST", "Validate file", "/validate", fileApi.Validate)
}
homepage := v1.Group("/homepage")
{
registerGinRoute(homepage, "GET", "List categories", "/categories", homepageApi.Categories)
registerGinRoute(homepage, "GET", "List items", "/items", homepageApi.Items)
registerGinRoute(homepage, "POST", "Set item", "/set/item", homepageApi.SetItem)
registerGinRoute(homepage, "POST", "Set items batch", "/set/items_batch", homepageApi.SetItemsBatch)
registerGinRoute(homepage, "POST", "Set item visible", "/set/item_visible", homepageApi.SetItemVisible)
registerGinRoute(homepage, "POST", "Set item favorite", "/set/item_favorite", homepageApi.SetItemFavorite)
registerGinRoute(homepage, "POST", "Set item sort order", "/set/item_sort_order", homepageApi.SetItemSortOrder)
registerGinRoute(homepage, "POST", "Set item all sort order", "/set/item_all_sort_order", homepageApi.SetItemAllSortOrder)
registerGinRoute(homepage, "POST", "Set item fav sort order", "/set/item_fav_sort_order", homepageApi.SetItemFavSortOrder)
registerGinRoute(homepage, "POST", "Set category order", "/set/category_order", homepageApi.SetCategoryOrder)
registerGinRoute(homepage, "POST", "Item click", "/item_click", homepageApi.ItemClick)
}
cert := v1.Group("/cert")
{
registerGinRoute(cert, "GET", "Get cert info", "/info", certApi.Info)
registerGinRoute(cert, "GET", "Renew cert", "/renew", certApi.Renew)
}
agent := v1.Group("/agent")
{
registerGinRoute(agent, "GET", "List agents", "/list", agentApi.List)
registerGinRoute(agent, "POST", "Create agent", "/create", agentApi.Create)
registerGinRoute(agent, "POST", "Verify agent", "/verify", agentApi.Verify)
}
metrics := v1.Group("/metrics")
{
registerGinRoute(metrics, "GET", "Get system info", "/system_info", metricsApi.SystemInfo)
registerGinRoute(metrics, "GET", "Get all system info", "/all_system_info", metricsApi.AllSystemInfo)
registerGinRoute(metrics, "GET", "Get uptime", "/uptime", metricsApi.Uptime)
}
docker := v1.Group("/docker")
{
registerGinRoute(docker, "GET", "Get container", "/container/:id", dockerApi.GetContainer)
registerGinRoute(docker, "GET", "List containers", "/containers", dockerApi.Containers)
registerGinRoute(docker, "GET", "Get docker info", "/info", dockerApi.Info)
registerGinRoute(docker, "GET", "Get docker logs", "/logs/:id", dockerApi.Logs)
registerGinRoute(docker, "POST", "Start docker container", "/start", dockerApi.Start)
registerGinRoute(docker, "POST", "Stop docker container", "/stop", dockerApi.Stop)
registerGinRoute(docker, "POST", "Restart docker container", "/restart", dockerApi.Restart)
}
}
return r
}
func AuthBlockPageHandler(w http.ResponseWriter, r *http.Request) {
auth.WriteBlockPage(w, http.StatusForbidden, "Forbidden", "Login", "/login")
}

7
cmd/debug_page_prod.go Normal file
View File

@@ -0,0 +1,7 @@
//go:build production
package main
func listenDebugServer() {
// no-op
}

View File

@@ -0,0 +1,18 @@
FROM golang:1.25.5-alpine AS builder
HEALTHCHECK NONE
WORKDIR /src
COPY go.mod go.sum ./
COPY main.go ./
RUN go build -o h2c_test_server main.go
FROM scratch
COPY --from=builder /src/h2c_test_server /app/run
USER 1001:1001
CMD ["/app/run"]

View File

@@ -0,0 +1,7 @@
module github.com/yusing/godoxy/cmd/h2c_test_server
go 1.25.5
require golang.org/x/net v0.48.0
require golang.org/x/text v0.32.0 // indirect

View File

@@ -0,0 +1,4 @@
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=

View File

@@ -0,0 +1,26 @@
package main
import (
"log"
"net/http"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
func main() {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
server := &http.Server{
Addr: ":80",
Handler: h2c.NewHandler(handler, &http2.Server{}),
}
log.Println("H2C server listening on :80")
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("ListenAndServe: %v", err)
}
}

89
cmd/main.go Executable file
View File

@@ -0,0 +1,89 @@
package main
import (
"os"
"sync"
"github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/api"
"github.com/yusing/godoxy/internal/auth"
"github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/config"
"github.com/yusing/godoxy/internal/dnsproviders"
iconlist "github.com/yusing/godoxy/internal/homepage/icons/list"
"github.com/yusing/godoxy/internal/logging"
"github.com/yusing/godoxy/internal/logging/memlogger"
"github.com/yusing/godoxy/internal/metrics/systeminfo"
"github.com/yusing/godoxy/internal/metrics/uptime"
"github.com/yusing/godoxy/internal/net/gphttp/middleware"
"github.com/yusing/godoxy/internal/route/rules"
gperr "github.com/yusing/goutils/errs"
"github.com/yusing/goutils/server"
"github.com/yusing/goutils/task"
"github.com/yusing/goutils/version"
)
func parallel(fns ...func()) {
var wg sync.WaitGroup
for _, fn := range fns {
wg.Go(fn)
}
wg.Wait()
}
func main() {
initProfiling()
logging.InitLogger(os.Stderr, memlogger.GetMemLogger())
log.Info().Msgf("GoDoxy version %s", version.Get())
log.Trace().Msg("trace enabled")
parallel(
dnsproviders.InitProviders,
iconlist.InitCache,
systeminfo.Poller.Start,
middleware.LoadComposeFiles,
)
if common.APIJWTSecret == nil {
log.Warn().Msg("API_JWT_SECRET is not set, using random key")
common.APIJWTSecret = common.RandomJWTKey()
}
for _, dir := range common.RequiredDirectories {
prepareDirectory(dir)
}
err := config.Load()
if err != nil {
gperr.LogWarn("errors in config", err)
}
config.StartProxyServers()
if err := auth.Initialize(); err != nil {
log.Fatal().Err(err).Msg("failed to initialize authentication")
}
rules.InitAuthHandler(auth.AuthOrProceed)
// API Handler needs to start after auth is initialized.
server.StartServer(task.RootTask("api_server", false), server.Options{
Name: "api",
HTTPAddr: common.APIHTTPAddr,
Handler: api.NewHandler(),
})
listenDebugServer()
uptime.Poller.Start()
config.WatchChanges()
task.WaitExit(config.Value().TimeoutShutdown)
}
func prepareDirectory(dir string) {
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err = os.MkdirAll(dir, 0o755); err != nil {
log.Fatal().Msgf("failed to create directory %s: %v", dir, err)
}
}
}

7
cmd/pprof_production.go Normal file
View File

@@ -0,0 +1,7 @@
//go:build !pprof
package main
func initProfiling() {
// no profiling in production
}

48
cmd/pprof_prof.go Normal file
View File

@@ -0,0 +1,48 @@
//go:build pprof
package main
import (
"net/http"
_ "net/http/pprof"
"runtime"
"runtime/debug"
"time"
"github.com/rs/zerolog/log"
strutils "github.com/yusing/goutils/strings"
)
func initProfiling() {
go func() {
log.Info().Msgf("pprof server started at http://localhost:7777/debug/pprof/")
log.Error().Err(http.ListenAndServe(":7777", nil)).Msg("pprof server failed")
}()
go func() {
ticker := time.NewTicker(time.Second * 10)
defer ticker.Stop()
var m runtime.MemStats
var gcStats debug.GCStats
for range ticker.C {
runtime.ReadMemStats(&m)
debug.ReadGCStats(&gcStats)
log.Info().Msgf("-----------------------------------------------------")
log.Info().Msgf("Timestamp: %s", time.Now().Format(time.RFC3339))
log.Info().Msgf(" Go Heap - In Use (Alloc/HeapAlloc): %s", strutils.FormatByteSize(m.Alloc))
log.Info().Msgf(" Go Heap - Reserved from OS (HeapSys): %s", strutils.FormatByteSize(m.HeapSys))
log.Info().Msgf(" Go Stacks - In Use (StackInuse): %s", strutils.FormatByteSize(m.StackInuse))
log.Info().Msgf(" Go Runtime - Other Sys (MSpanInuse, MCacheInuse, BuckHashSys, GCSys, OtherSys): %s", strutils.FormatByteSize(m.MSpanInuse+m.MCacheInuse+m.BuckHashSys+m.GCSys+m.OtherSys))
log.Info().Msgf(" Go Runtime - Total from OS (Sys): %s", strutils.FormatByteSize(m.Sys))
log.Info().Msgf(" Go Runtime - Freed from OS (HeapReleased): %s", strutils.FormatByteSize(m.HeapReleased))
log.Info().Msgf(" Number of Goroutines: %d", runtime.NumGoroutine())
log.Info().Msgf(" Number of completed GC cycles: %d", m.NumGC)
log.Info().Msgf(" Number of GCs: %d", gcStats.NumGC)
log.Info().Msgf(" Total GC time: %s", gcStats.PauseTotal)
log.Info().Msgf(" Last GC time: %s", gcStats.LastGC.Format(time.DateTime))
log.Info().Msg("-----------------------------------------------------")
}
}()
}

View File

@@ -1,46 +1,84 @@
version: '3'
---
services:
app:
build: .
container_name: go-proxy
restart: always
networks: # ^also add here
- default
# environment:
# - GOPROXY_DEBUG=1 # (optional, enable only for debug)
# - GOPROXY_REDIRECT_HTTP=0 # (optional, uncomment to disable http redirect (http -> https))
ports:
- 80:80 # http
# - 443:443 # optional, https
- 8080:8080 # http panel
# - 8443:8443 # optional, https panel
# optional, if you declared any tcp/udp proxy, set a range you want to use
# - 20000:20100/tcp
# - 20000:20100/udp
socket-proxy:
container_name: socket-proxy
image: ghcr.io/yusing/socket-proxy:latest
environment:
- ALLOW_START=1
- ALLOW_STOP=1
- ALLOW_RESTARTS=1
- CONTAINERS=1
- EVENTS=1
- INFO=1
- PING=1
- POST=1
- VERSION=1
volumes:
# use existing certificate
# - /path/to/cert.pem:/app/certs/cert.crt:ro
# - /path/to/privkey.pem:/app/certs/priv.key:ro
- ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock
restart: unless-stopped
tmpfs:
- /run
ports:
- ${SOCKET_PROXY_LISTEN_ADDR:-127.0.0.1:2375}:2375
frontend:
image: ghcr.io/yusing/godoxy-frontend:${TAG:-latest}
# lite variant
# image: ghcr.io/yusing/godoxy-frontend:${TAG:-latest}-lite
container_name: godoxy-frontend
restart: unless-stopped
env_file: .env
# comment out `user` for lite variant
user: ${GODOXY_UID:-1000}:${GODOXY_GID:-1000}
read_only: true
tmpfs:
- /app/.next/cache # next image caching
# use autocert feature
# - ./certs:/app/certs
# for lite variant, do not change uid/gid
# - /var/cache/nginx:uid=101,gid=101
# - /run:uid=101,gid=101
security_opt:
- no-new-privileges:true
cap_drop:
- all
depends_on:
- app
labels:
proxy.aliases: ${GODOXY_FRONTEND_ALIASES:-godoxy}
# proxy.#1.middlewares.cidr_whitelist: |
# status: 403
# message: IP not allowed
# allow:
# - 127.0.0.1
# - 10.0.0.0/8
# - 192.168.0.0/16
# - 172.16.0.0/12
app:
image: ghcr.io/yusing/godoxy:${TAG:-latest}
container_name: godoxy-proxy
restart: always
network_mode: host # do not change this
env_file: .env
user: ${GODOXY_UID:-1000}:${GODOXY_GID:-1000}
depends_on:
socket-proxy:
condition: service_started
security_opt:
- no-new-privileges:true
cap_drop:
- all
cap_add:
- NET_BIND_SERVICE
environment:
- DOCKER_HOST=tcp://${SOCKET_PROXY_LISTEN_ADDR:-127.0.0.1:2375}
volumes:
- ./config:/app/config
- ./logs:/app/logs
- ./error_pages:/app/error_pages:ro
- ./data:/app/data
# if local docker provider is used (by default)
- /var/run/docker.sock:/var/run/docker.sock:ro
# This path stores certs obtained from autocert and agent TLS client certs
- ./certs:/app/certs
# to use custom config and providers
# - ./config:/app/config
dns:
- 127.0.0.1 # workaround for "lookup: no such host"
extra_hosts:
# required if you use local docker provider and have containers in `host` network_mode
- host.docker.internal:host-gateway
logging:
driver: 'json-file'
options:
max-file: '1'
max-size: 128k
networks: # ^you may add other external networks
default:
driver: bridge
# mount existing certificate
# - /path/to/certs/cert.crt:/app/certs/cert.crt
# - /path/to/certs/priv.key:/app/certs/priv.key

View File

@@ -1,25 +1,164 @@
# uncomment to use autocert
autocert: # (optional, if you need autocert feature)
email: "user@domain.com" # (required) email for acme certificate
domains: # (required)
- "*.y.z" # domain for acme certificate, use wild card to allow all subdomains
provider: cloudflare # (required) dns challenge provider (string)
options: # provider specific options
auth_token: "YOUR_ZONE_API_TOKEN"
# Autocert (choose one below and uncomment to enable)
#
# 1. use existing cert
# autocert:
# provider: local
# cert_path: /path/to/cert.crt # default: /app/certs/cert.crt
# key_path: /path/to/priv.key # default: /app/certs/priv.key
# 2. cloudflare
# autocert:
# provider: cloudflare
# email: abc@gmail.com # ACME Email
# domains: # a list of domains for cert registration
# - "*.domain.com"
# - "domain.com"
# options:
# auth_token: c1234565789-abcdefghijklmnopqrst # your zone API token
# 3. other providers, see https://docs.godoxy.dev/DNS-01-Providers
# Access Control
# When enabled, it will be applied globally at connection level,
# all incoming connections (web, tcp and udp) will be checked against the ACL rules.
# acl:
# default: allow # or deny (default: allow)
# allow_local: true # or false (default: true)
# allow:
# - ip:1.2.3.4
# - cidr:1.2.3.4/32
# - country:US
# - timezone:Asia/Shanghai
# deny:
# - ip:1.2.3.4
# - cidr:1.2.3.4/32
# - country:US
# - timezone:Asia/Shanghai
# log: # warning: logging ACL can be slow based on the number of incoming connections and configured rules
# path: /app/logs/acl.log # (default: none)
# stdout: false # (default: false)
# keep: 30 days # (default: 30 days)
# log_allowed: false # (default: false)
# notify:
# interval: 1m # (default: 1m)
# to: [gotify, discord] # names under providers.notification
# include_allowed: false # (default: false)
entrypoint:
# Proxy Protocol: https://www.haproxy.com/blog/use-the-proxy-protocol-to-preserve-a-clients-ip-address
# When set to true, web entrypoint and all tcp routes will be wrapped with Proxy Protocol listener in order to preserve the client's IP address.
# Note that HTTP/3 with proxy protocol is not supported yet.
support_proxy_protocol: false
# Below define an example of middleware config
# 1. set security headers
# 2. block non local IP connections
# 3. redirect HTTP to HTTPS
#
middlewares:
- use: CloudflareRealIP
- use: ModifyResponse
set_headers:
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD
Access-Control-Allow-Headers: "*"
Access-Control-Allow-Origin: "*"
Access-Control-Max-Age: 180
Vary: "*"
X-XSS-Protection: 1; mode=block
Content-Security-Policy: "object-src 'self'; frame-ancestors 'self';"
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: same-origin
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
# - use: RedirectHTTP
# below enables access log
access_log:
format: combined
path: /app/logs/entrypoint.log
stdout: false # (default: false)
keep: 30 days # (default: 30 days)
# customize behavior for non-existent routes, e.g. pass over to another proxy
#
# rules:
# not_found:
# - name: default
# do: proxy http://other-proxy:8080
defaults:
healthcheck:
interval: 5s
timeout: 15s
retries: 3
providers:
local:
kind: docker
# include files are standalone yaml files under `config/` directory
#
# include:
# - file1.yml
# - file2.yml
docker:
# $DOCKER_HOST implies environment variable `DOCKER_HOST` or unix:///var/run/docker.sock by default
local: $DOCKER_HOST
# explicit only mode
# only containers with explicit aliases will be proxied
# add "!" after provider name to enable explicit only mode
#
# local!: $DOCKER_HOST
#
# add more docker providers if needed
# for value format, see https://docs.docker.com/reference/cli/dockerd/
value: FROM_ENV
# remote1:
# kind: docker
# value: ssh://user@10.0.1.1
# remote2:
# kind: docker
# value: tcp://10.0.1.1:2375
# provider1:
# kind: file
# value: provider1.yml
# provider2:
# kind: file
# value: provider2.yml
#
# remote-1: tcp://10.0.2.1:2375
# remote-2: ssh://root:1234@10.0.2.2
# notification providers
#
# notification:
# - name: ntfy
# provider: ntfy
# url: https://ntfy.domain.tld
# topic: godoxy
# - name: gotify
# provider: gotify
# url: https://gotify.domain.tld
# token: abcd
# - name: discord
# provider: webhook
# url: https://discord.com/api/webhooks/...
# template: discord # this means use payload template from internal/notif/templates/discord.json
# - name: pushover
# provider: webhook
# url: https://api.pushover.net/1/messages.json
# mime_type: application/x-www-form-urlencoded
# payload: '{"token": "your-app-token", "user": "your-user-key", "title": $title, "message": $message}'
# Proxmox providers (for idlesleep support for proxmox LXCs)
#
# proxmox:
# - url: https://pve.domain.com:8006/api2/json
# token_id: root@pam!abcdef
# secret: aaaa-bbbb-cccc-dddd
# no_tls_verify: true
# Match domains
# See https://docs.godoxy.dev/Certificates-and-domain-matching
#
# match_domains:
# - my.site
# - node1.my.app
# homepage config
homepage:
# use default app categories detected from alias or docker image name
use_default_categories: true
# Below are fixed options (non hot-reloadable)
# timeout for shutdown (in seconds)
timeout_shutdown: 5

7
dev.Dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates
WORKDIR /app
CMD ["/app/run"]

257
dev.compose.yml Normal file
View File

@@ -0,0 +1,257 @@
x-benchmark: &benchmark
restart: no
labels:
proxy.exclude: true
proxy.#1.healthcheck.disable: true
services:
app:
image: godoxy-dev
build:
context: .
dockerfile: dev.Dockerfile
container_name: godoxy-proxy-dev
restart: unless-stopped
env_file: dev.env
environment:
DOCKER_HOST: unix:///var/run/docker.sock
TZ: Asia/Hong_Kong
API_ADDR: 127.0.0.1:8999
API_USER: dev
API_PASSWORD: 1234
API_SKIP_ORIGIN_CHECK: true
API_JWT_TTL: 24h
DEBUG: true
API_JWT_SECRET: 1234567891234567
labels:
proxy.exclude: true
proxy.#1.healthcheck.disable: true
ipc: host
network_mode: host
volumes:
- ./bin/godoxy:/app/run:ro
- /var/run/docker.sock:/var/run/docker.sock
- ./dev-data/config:/app/config
- ./dev-data/certs:/app/certs
- ./dev-data/error_pages:/app/error_pages:ro
- ./dev-data/data:/app/data
- ./dev-data/logs:/app/logs
- ~/certs/myCA.pem:/etc/ssl/certs/ca.crt:ro
parca:
image: ghcr.io/parca-dev/parca:v0.24.2
container_name: godoxy-parca
restart: unless-stopped
command: [/parca, --config-path, /parca.yaml]
network_mode: host
# ports:
# - 7070:7070
configs:
- source: parca
target: /parca.yaml
labels:
proxy.#1.port: "7070"
tinyauth:
image: ghcr.io/steveiliop56/tinyauth:v3
container_name: tinyauth
restart: unless-stopped
environment:
- SECRET=12345678912345671234567891234567
- APP_URL=https://tinyauth.my.app
- USERS=user:$$2a$$10$$UdLYoJ5lgPsC0RKqYH/jMua7zIn0g9kPqWmhYayJYLaZQ/FTmH2/u # user:password
labels:
proxy.tinyauth.port: "3000"
jotty: # issue #182
image: ghcr.io/fccview/jotty:latest
container_name: jotty
user: "1000:1000"
tmpfs:
- /app/data:rw,uid=1000,gid=1000
- /app/config:rw,uid=1000,gid=1000
- /app/.next/cache:rw,uid=1000,gid=1000
restart: unless-stopped
environment:
- NODE_ENV=production
labels:
proxy.aliases: "jotty.my.app"
postgres-test:
image: postgres:18-alpine
container_name: postgres-test
restart: unless-stopped
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
h2c_test_server:
build:
context: cmd/h2c_test_server
dockerfile: Dockerfile
container_name: h2c_test
restart: unless-stopped
labels:
proxy.#1.scheme: h2c
proxy.#1.port: 80
bench: # returns 4096 bytes of random data
<<: *benchmark
build:
context: cmd/bench_server
dockerfile: Dockerfile
container_name: bench
godoxy:
<<: *benchmark
build: .
container_name: godoxy-benchmark
ports:
- 8080:80
configs:
- source: godoxy_config
target: /app/config/config.yml
- source: godoxy_provider
target: /app/config/providers.yml
traefik:
<<: *benchmark
image: traefik:latest
container_name: traefik
command:
- --api.insecure=true
- --entrypoints.web.address=:8081
- --providers.file.directory=/etc/traefik/dynamic
- --providers.file.watch=true
- --log.level=ERROR
ports:
- 8081:8081
configs:
- source: traefik_config
target: /etc/traefik/dynamic/routes.yml
caddy:
<<: *benchmark
image: caddy:latest
container_name: caddy
ports:
- 8082:80
configs:
- source: caddy_config
target: /etc/caddy/Caddyfile
tmpfs:
- /data
- /config
nginx:
<<: *benchmark
image: nginx:latest
container_name: nginx
command: nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
ports:
- 8083:80
configs:
- source: nginx_config
target: /etc/nginx/nginx.conf
configs:
godoxy_config:
content: |
providers:
include:
- providers.yml
godoxy_provider:
content: |
bench.domain.com:
host: bench
traefik_config:
content: |
http:
routers:
bench:
rule: "Host(`bench.domain.com`)"
entryPoints:
- web
service: bench
services:
bench:
loadBalancer:
servers:
- url: "http://bench:80"
caddy_config:
content: |
{
admin off
auto_https off
default_bind 0.0.0.0
servers {
protocols h1 h2c
}
}
http://bench.domain.com {
reverse_proxy bench:80
}
nginx_config:
content: |
worker_processes auto;
worker_rlimit_nofile 65535;
error_log /dev/null;
pid /var/run/nginx.pid;
events {
worker_connections 10240;
multi_accept on;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log off;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 10000;
upstream backend {
server bench:80;
keepalive 128;
}
server {
listen 80 default_server;
server_name _;
http2 on;
return 404;
}
server {
listen 80;
server_name bench.domain.com;
http2 on;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $$host;
proxy_set_header X-Real-IP $$remote_addr;
proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
proxy_buffering off;
}
}
}
parca:
content: |
object_storage:
bucket:
type: "FILESYSTEM"
config:
directory: "./data"
scrape_configs:
- job_name: "parca"
scrape_interval: "1s"
static_configs:
- targets: [ 'localhost:7777' ]

View File

@@ -1,11 +0,0 @@
#!/bin/bash
if [ "$1" == "restart" ]; then
echo "restarting"
killall go-proxy
fi
if [ "$GOPROXY_DEBUG" == "1" ]; then
/app/go-proxy 2> log/go-proxy.log &
tail -f /dev/null
else
/app/go-proxy
fi

View File

@@ -0,0 +1,27 @@
---
services:
n8n:
image: n8nio/n8n
container_name: n8n
restart: always
expose:
- 5678
labels:
proxy.n8n.middlewares.request.set_headers: |
SSLRedirect: true
STSSeconds: 315360000
browserXSSFilter: true
contentTypeNosniff: true
forceSTSHeader: true
SSLHost: ${DOMAIN_NAME}
STSIncludeSubdomains: true
STSPreload: true
environment:
- N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME}
- N8N_PORT=5678
- N8N_PROTOCOL=https
- NODE_ENV=production
- WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
volumes:
- ./data:/home/node/.n8n

View File

@@ -0,0 +1,288 @@
@import url("https://fonts.googleapis.com/css?family=Audiowide&display=swap");
html,
body {
margin: 0px;
overflow: hidden;
}
div {
position: absolute;
top: 0%;
left: 0%;
height: 100%;
width: 100%;
margin: 0px;
background: radial-gradient(circle, #240015 0%, #12000b 100%);
overflow: hidden;
}
.wrap {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
h2 {
position: absolute;
top: 50%;
left: 50%;
margin-top: 150px;
font-size: 32px;
text-transform: uppercase;
transform: translate(-50%, -50%);
display: block;
color: #12000a;
font-weight: 300;
font-family: Audiowide;
text-shadow: 0px 0px 4px #12000a;
animation: fadeInText 3s ease-in 3.5s forwards,
flicker4 5s linear 7.5s infinite, hueRotate 6s ease-in-out 3s infinite;
}
#svgWrap_1,
#svgWrap_2 {
position: absolute;
height: auto;
width: 600px;
max-width: 100%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#svgWrap_1,
#svgWrap_2,
div {
animation: hueRotate 6s ease-in-out 3s infinite;
}
#id1_1,
#id2_1,
#id3_1 {
stroke: #ff005d;
stroke-width: 3px;
fill: transparent;
filter: url(#glow);
}
#id1_2,
#id2_2,
#id3_2 {
stroke: #12000a;
stroke-width: 3px;
fill: transparent;
filter: url(#glow);
}
#id3_1 {
stroke-dasharray: 940px;
stroke-dashoffset: -940px;
animation: drawLine3 2.5s ease-in-out 0s forwards,
flicker3 4s linear 4s infinite;
}
#id2_1 {
stroke-dasharray: 735px;
stroke-dashoffset: -735px;
animation: drawLine2 2.5s ease-in-out 0.5s forwards,
flicker2 4s linear 4.5s infinite;
}
#id1_1 {
stroke-dasharray: 940px;
stroke-dashoffset: -940px;
animation: drawLine1 2.5s ease-in-out 1s forwards,
flicker1 4s linear 5s infinite;
}
@keyframes drawLine1 {
0% {
stroke-dashoffset: -940px;
}
100% {
stroke-dashoffset: 0px;
}
}
@keyframes drawLine2 {
0% {
stroke-dashoffset: -735px;
}
100% {
stroke-dashoffset: 0px;
}
}
@keyframes drawLine3 {
0% {
stroke-dashoffset: -940px;
}
100% {
stroke-dashoffset: 0px;
}
}
@keyframes flicker1 {
0% {
stroke: #ff005d;
}
1% {
stroke: transparent;
}
3% {
stroke: transparent;
}
4% {
stroke: #ff005d;
}
6% {
stroke: #ff005d;
}
7% {
stroke: transparent;
}
13% {
stroke: transparent;
}
14% {
stroke: #ff005d;
}
100% {
stroke: #ff005d;
}
}
@keyframes flicker2 {
0% {
stroke: #ff005d;
}
50% {
stroke: #ff005d;
}
51% {
stroke: transparent;
}
61% {
stroke: transparent;
}
62% {
stroke: #ff005d;
}
100% {
stroke: #ff005d;
}
}
@keyframes flicker3 {
0% {
stroke: #ff005d;
}
1% {
stroke: transparent;
}
10% {
stroke: transparent;
}
11% {
stroke: #ff005d;
}
40% {
stroke: #ff005d;
}
41% {
stroke: transparent;
}
45% {
stroke: transparent;
}
46% {
stroke: #ff005d;
}
100% {
stroke: #ff005d;
}
}
@keyframes flicker4 {
0% {
color: #ff005d;
text-shadow: 0px 0px 4px #ff005d;
}
30% {
color: #ff005d;
text-shadow: 0px 0px 4px #ff005d;
}
31% {
color: #12000a;
text-shadow: 0px 0px 4px #12000a;
}
32% {
color: #ff005d;
text-shadow: 0px 0px 4px #ff005d;
}
36% {
color: #ff005d;
text-shadow: 0px 0px 4px #ff005d;
}
37% {
color: #12000a;
text-shadow: 0px 0px 4px #12000a;
}
41% {
color: #12000a;
text-shadow: 0px 0px 4px #12000a;
}
42% {
color: #ff005d;
text-shadow: 0px 0px 4px #ff005d;
}
85% {
color: #ff005d;
text-shadow: 0px 0px 4px #ff005d;
}
86% {
color: #12000a;
text-shadow: 0px 0px 4px #12000a;
}
95% {
color: #12000a;
text-shadow: 0px 0px 4px #12000a;
}
96% {
color: #ff005d;
text-shadow: 0px 0px 4px #ff005d;
}
100% {
color: #ff005d;
text-shadow: 0px 0px 4px #ff005d;
}
}
@keyframes fadeInText {
1% {
color: #12000a;
text-shadow: 0px 0px 4px #12000a;
}
70% {
color: #ff005d;
text-shadow: 0px 0px 14px #ff005d;
}
100% {
color: #ff005d;
text-shadow: 0px 0px 4px #ff005d;
}
}
@keyframes hueRotate {
0% {
filter: hue-rotate(0deg);
}
50% {
filter: hue-rotate(-120deg);
}
100% {
filter: hue-rotate(0deg);
}
}

View File

@@ -0,0 +1,51 @@
{{/* Credit: https://codepen.io/code2rithik/pen/XWpVvYL */}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Page Not Found</title>
<link rel="stylesheet" href="/$gperrorpage/404.css" type="text/css">
<!-- <script src="/$gperrorpage/404.js"> </script> -->
</head>
<body>
<script>0</script>
<div></div>
<svg id="svgWrap_2" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 700 250">
<g>
<path id="id3_2"
d="M195.7 232.67h-37.1V149.7H27.76c-2.64 0-5.1-.5-7.36-1.49-2.27-.99-4.23-2.31-5.88-3.96-1.65-1.65-2.95-3.61-3.89-5.88s-1.42-4.67-1.42-7.22V29.62h36.82v82.98H158.6V29.62h37.1v203.05z" />
<path id="id2_2"
d="M470.69 147.71c0 8.31-1.06 16.17-3.19 23.58-2.12 7.41-5.12 14.28-8.99 20.6-3.87 6.33-8.45 11.99-13.74 16.99-5.29 5-11.07 9.28-17.35 12.81a85.146 85.146 0 0 1-20.04 8.14 83.637 83.637 0 0 1-21.67 2.83H319.3c-7.46 0-14.73-.94-21.81-2.83-7.08-1.89-13.76-4.6-20.04-8.14a88.292 88.292 0 0 1-17.35-12.81c-5.29-5-9.84-10.67-13.66-16.99-3.82-6.32-6.8-13.19-8.92-20.6-2.12-7.41-3.19-15.27-3.19-23.58v-33.13c0-12.46 2.34-23.88 7.01-34.27 4.67-10.38 10.92-19.33 18.76-26.83 7.83-7.5 16.87-13.36 27.12-17.56 10.24-4.2 20.93-6.3 32.07-6.3h66.41c7.36 0 14.58.94 21.67 2.83 7.08 1.89 13.76 4.6 20.04 8.14a88.292 88.292 0 0 1 17.35 12.81c5.29 5 9.86 10.67 13.74 16.99 3.87 6.33 6.87 13.19 8.99 20.6 2.13 7.41 3.19 15.27 3.19 23.58v33.14zm-37.1-33.13c0-7.27-1.32-13.88-3.96-19.82-2.64-5.95-6.16-11.04-10.55-15.29-4.39-4.25-9.46-7.5-15.22-9.77-5.76-2.27-11.8-3.35-18.13-3.26h-66.41c-6.14-.09-12.11.97-17.91 3.19-5.81 2.22-10.95 5.43-15.44 9.63-4.48 4.2-8.07 9.3-10.76 15.29-2.69 6-4.04 12.67-4.04 20.04v33.13c0 7.36 1.32 14.02 3.96 19.97 2.64 5.95 6.18 11.02 10.62 15.22 4.44 4.2 9.56 7.43 15.36 9.7 5.8 2.27 11.87 3.35 18.2 3.26h66.41c7.27 0 13.85-1.2 19.75-3.61s10.93-5.73 15.08-9.98 7.36-9.32 9.63-15.22c2.27-5.9 3.4-12.34 3.4-19.33v-33.15zm-16-26.91a17.89 17.89 0 0 1 2.83 6.73c.47 2.41.47 4.77 0 7.08-.47 2.31-1.39 4.48-2.76 6.51-1.37 2.03-3.14 3.75-5.31 5.17l-99.4 66.41c-1.61 1.23-3.26 2.08-4.96 2.55-1.7.47-3.45.71-5.24.71-3.02 0-5.9-.71-8.64-2.12-2.74-1.42-4.96-3.44-6.66-6.09a17.89 17.89 0 0 1-2.83-6.73c-.47-2.41-.5-4.77-.07-7.08.43-2.31 1.3-4.48 2.62-6.51 1.32-2.03 3.07-3.75 5.24-5.17l99.69-66.41a17.89 17.89 0 0 1 6.73-2.83c2.41-.47 4.77-.47 7.08 0 2.31.47 4.48 1.37 6.51 2.69 2.03 1.32 3.75 3.02 5.17 5.09z" />
<path id="id1_2"
d="M688.33 232.67h-37.1V149.7H520.39c-2.64 0-5.1-.5-7.36-1.49-2.27-.99-4.23-2.31-5.88-3.96-1.65-1.65-2.95-3.61-3.89-5.88s-1.42-4.67-1.42-7.22V29.62h36.82v82.98h112.57V29.62h37.1v203.05z" />
</g>
</svg>
<svg id="svgWrap_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 700 250">
<g>
<path id="id3_1"
d="M195.7 232.67h-37.1V149.7H27.76c-2.64 0-5.1-.5-7.36-1.49-2.27-.99-4.23-2.31-5.88-3.96-1.65-1.65-2.95-3.61-3.89-5.88s-1.42-4.67-1.42-7.22V29.62h36.82v82.98H158.6V29.62h37.1v203.05z" />
<path id="id2_1"
d="M470.69 147.71c0 8.31-1.06 16.17-3.19 23.58-2.12 7.41-5.12 14.28-8.99 20.6-3.87 6.33-8.45 11.99-13.74 16.99-5.29 5-11.07 9.28-17.35 12.81a85.146 85.146 0 0 1-20.04 8.14 83.637 83.637 0 0 1-21.67 2.83H319.3c-7.46 0-14.73-.94-21.81-2.83-7.08-1.89-13.76-4.6-20.04-8.14a88.292 88.292 0 0 1-17.35-12.81c-5.29-5-9.84-10.67-13.66-16.99-3.82-6.32-6.8-13.19-8.92-20.6-2.12-7.41-3.19-15.27-3.19-23.58v-33.13c0-12.46 2.34-23.88 7.01-34.27 4.67-10.38 10.92-19.33 18.76-26.83 7.83-7.5 16.87-13.36 27.12-17.56 10.24-4.2 20.93-6.3 32.07-6.3h66.41c7.36 0 14.58.94 21.67 2.83 7.08 1.89 13.76 4.6 20.04 8.14a88.292 88.292 0 0 1 17.35 12.81c5.29 5 9.86 10.67 13.74 16.99 3.87 6.33 6.87 13.19 8.99 20.6 2.13 7.41 3.19 15.27 3.19 23.58v33.14zm-37.1-33.13c0-7.27-1.32-13.88-3.96-19.82-2.64-5.95-6.16-11.04-10.55-15.29-4.39-4.25-9.46-7.5-15.22-9.77-5.76-2.27-11.8-3.35-18.13-3.26h-66.41c-6.14-.09-12.11.97-17.91 3.19-5.81 2.22-10.95 5.43-15.44 9.63-4.48 4.2-8.07 9.3-10.76 15.29-2.69 6-4.04 12.67-4.04 20.04v33.13c0 7.36 1.32 14.02 3.96 19.97 2.64 5.95 6.18 11.02 10.62 15.22 4.44 4.2 9.56 7.43 15.36 9.7 5.8 2.27 11.87 3.35 18.2 3.26h66.41c7.27 0 13.85-1.2 19.75-3.61s10.93-5.73 15.08-9.98 7.36-9.32 9.63-15.22c2.27-5.9 3.4-12.34 3.4-19.33v-33.15zm-16-26.91a17.89 17.89 0 0 1 2.83 6.73c.47 2.41.47 4.77 0 7.08-.47 2.31-1.39 4.48-2.76 6.51-1.37 2.03-3.14 3.75-5.31 5.17l-99.4 66.41c-1.61 1.23-3.26 2.08-4.96 2.55-1.7.47-3.45.71-5.24.71-3.02 0-5.9-.71-8.64-2.12-2.74-1.42-4.96-3.44-6.66-6.09a17.89 17.89 0 0 1-2.83-6.73c-.47-2.41-.5-4.77-.07-7.08.43-2.31 1.3-4.48 2.62-6.51 1.32-2.03 3.07-3.75 5.24-5.17l99.69-66.41a17.89 17.89 0 0 1 6.73-2.83c2.41-.47 4.77-.47 7.08 0 2.31.47 4.48 1.37 6.51 2.69 2.03 1.32 3.75 3.02 5.17 5.09z" />
<path id="id1_1"
d="M688.33 232.67h-37.1V149.7H520.39c-2.64 0-5.1-.5-7.36-1.49-2.27-.99-4.23-2.31-5.88-3.96-1.65-1.65-2.95-3.61-3.89-5.88s-1.42-4.67-1.42-7.22V29.62h36.82v82.98h112.57V29.62h37.1v203.05z" />
</g>
</svg>
<svg>
<defs>
<filter id="glow">
<fegaussianblur class="blur" result="coloredBlur" stddeviation="4"></fegaussianblur>
<femerge>
<femergenode in="coloredBlur"></femergenode>
<femergenode in="SourceGraphic"></femergenode>
</femerge>
</filter>
</defs>
</svg>
<h2>Page Not Found</h2>
</body>
</html>

File diff suppressed because it is too large Load Diff

217
go.mod Executable file → Normal file
View File

@@ -1,53 +1,190 @@
module github.com/yusing/go-proxy
module github.com/yusing/godoxy
go 1.22
go 1.25.5
require (
github.com/docker/cli v26.0.0+incompatible
github.com/docker/docker v26.0.0+incompatible
github.com/fsnotify/fsnotify v1.7.0
github.com/go-acme/lego/v4 v4.16.1
github.com/santhosh-tekuri/jsonschema v1.2.4
github.com/sirupsen/logrus v1.9.3
golang.org/x/net v0.22.0
gopkg.in/yaml.v3 v3.0.1
replace (
github.com/coreos/go-oidc/v3 => ./internal/go-oidc
github.com/shirou/gopsutil/v4 => ./internal/gopsutil
github.com/yusing/godoxy/agent => ./agent
github.com/yusing/godoxy/internal/dnsproviders => ./internal/dnsproviders
github.com/yusing/goutils => ./goutils
github.com/yusing/goutils/http/reverseproxy => ./goutils/http/reverseproxy
github.com/yusing/goutils/http/websocket => ./goutils/http/websocket
github.com/yusing/goutils/server => ./goutils/server
)
require (
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cloudflare/cloudflare-go v0.92.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/PuerkitoBio/goquery v1.11.0 // parsing HTML for extract fav icon; modify_html middleware
github.com/coreos/go-oidc/v3 v3.17.0 // oidc authentication
github.com/fsnotify/fsnotify v1.9.0 // file watcher
github.com/gin-gonic/gin v1.11.0 // api server
github.com/go-acme/lego/v4 v4.31.0 // acme client
github.com/go-playground/validator/v10 v10.30.1 // validator
github.com/gobwas/glob v0.2.3 // glob matcher for route rules
github.com/gorilla/websocket v1.5.3 // websocket for API and agent
github.com/gotify/server/v2 v2.8.0 // reference the Message struct for json response
github.com/lithammer/fuzzysearch v1.1.8 // fuzzy search for searching icons and filtering metrics
github.com/pires/go-proxyproto v0.8.1 // proxy protocol support
github.com/puzpuzpuz/xsync/v4 v4.2.0 // lock free map for concurrent operations
github.com/rs/zerolog v1.34.0 // logging
github.com/vincent-petithory/dataurl v1.0.0 // data url for fav icon
golang.org/x/crypto v0.46.0 // encrypting password with bcrypt
golang.org/x/net v0.48.0 // HTTP header utilities
golang.org/x/oauth2 v0.34.0 // oauth2 authentication
golang.org/x/sync v0.19.0 // errgroup and singleflight for concurrent operations
golang.org/x/time v0.14.0 // time utilities
)
require (
github.com/bytedance/gopkg v0.1.3 // xxhash64 for fast hash
github.com/bytedance/sonic v1.14.2 // fast json parsing
github.com/docker/cli v29.1.3+incompatible // needs docker/cli/cli/connhelper connection helper for docker client
github.com/goccy/go-yaml v1.19.2 // yaml parsing for different config files
github.com/golang-jwt/jwt/v5 v5.3.0 // jwt authentication
github.com/luthermonson/go-proxmox v0.3.2 // proxmox API client
github.com/moby/moby/api v1.52.0 // docker API
github.com/moby/moby/client v0.2.1 // docker client
github.com/oschwald/maxminddb-golang v1.13.1 // maxminddb for geoip database
github.com/quic-go/quic-go v0.58.0 // http3 support
github.com/shirou/gopsutil/v4 v4.25.12 // system information
github.com/spf13/afero v1.15.0 // afero for file system operations
github.com/stretchr/testify v1.11.1 // testing framework
github.com/valyala/fasthttp v1.69.0 // fast http for health check
github.com/yusing/ds v0.3.1 // data structures and algorithms
github.com/yusing/godoxy/agent v0.0.0-20260109022755-4275cdae3854
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20260109022755-4275cdae3854
github.com/yusing/gointernals v0.1.16
github.com/yusing/goutils v0.7.0
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260109021609-78fda75d1e58
github.com/yusing/goutils/http/websocket v0.0.0-20260109021609-78fda75d1e58
github.com/yusing/goutils/server v0.0.0-20260109021609-78fda75d1e58
)
require (
cloud.google.com/go/auth v0.18.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/buger/goterm v1.0.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/diskfs/go-diskfs v1.7.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/djherbis/times v1.6.0 // indirect
github.com/docker/go-connections v0.6.0
github.com/docker/go-units v0.5.0 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/gofrs/flock v0.13.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.9 // indirect
github.com/googleapis/gax-go/v2 v2.16.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
github.com/miekg/dns v1.1.58 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/jinzhu/copier v0.4.0 // indirect
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/magefile/mage v1.15.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.69 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nrdcg/goacmedns v0.2.0 // indirect
github.com/nrdcg/porkbun v0.4.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.19.0 // indirect
gotest.tools/v3 v3.5.1 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/ovh/go-ovh v1.9.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/samber/lo v1.52.0 // indirect
github.com/samber/slog-common v0.19.0 // indirect
github.com/samber/slog-zerolog/v2 v2.9.0 // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 // indirect
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect
github.com/sony/gobreaker v1.0.0 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0
go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect
go.uber.org/atomic v1.11.0
go.uber.org/ratelimit v0.3.1 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/tools v0.40.0 // indirect
google.golang.org/api v0.259.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.78.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
require (
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/bytedance/sonic/loader v0.4.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/go-resty/resty/v2 v2.17.1 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/google/go-querystring v1.2.0 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
github.com/linode/linodego v1.64.0 // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/nrdcg/goinwx v0.12.0 // indirect
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2 // indirect
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.2 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pion/dtls/v3 v3.0.10 // indirect
github.com/pion/logging v0.2.4 // indirect
github.com/pion/transport/v4 v4.0.1 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/pquerna/otp v1.5.0 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/vultr/govultr/v3 v3.26.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/arch v0.23.0 // indirect
)

582
go.sum Executable file → Normal file
View File

@@ -1,192 +1,480 @@
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cloudflare/cloudflare-go v0.86.0 h1:jEKN5VHNYNYtfDL2lUFLTRo+nOVNPFxpXTstVx0rqHI=
github.com/cloudflare/cloudflare-go v0.86.0/go.mod h1:wYW/5UP02TUfBToa/yKbQHV+r6h1NnJ1Je7XjuGM4Jw=
github.com/cloudflare/cloudflare-go v0.91.0 h1:L7IR+86qrZuEMSjGFg4cwRwtHqC8uCPmMUkP7BD4CPw=
github.com/cloudflare/cloudflare-go v0.91.0/go.mod h1:nUqvBUUDRxNzsDSQjbqUNWHEIYAoUlgRmcAzMKlFdKs=
github.com/cloudflare/cloudflare-go v0.92.0 h1:ltJvGvqZ4G6Fm2hHOYZ5RWpJQcrM0oDrsjjZydZhFJQ=
github.com/cloudflare/cloudflare-go v0.92.0/go.mod h1:nUqvBUUDRxNzsDSQjbqUNWHEIYAoUlgRmcAzMKlFdKs=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0=
cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0/go.mod h1:AW8VEadnhw9xox+VaVd9sP7NjzOAnaZBLRH6Tq3cJ38=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 h1:yzrctSl9GMIQ5lHu7jc8olOsGjWDCsBpJhWqfGa/YIM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0/go.mod h1:GE4m0rnnfwLGX0Y9A9A25Zx5N/90jneT5ABevqzhuFQ=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 h1:zLzoX5+W2l95UJoVwiyNS4dX8vHyQ6x2xRLoBBL9wMk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0/go.mod h1:wVEOJfGTj0oPAUGA1JuRAvz/lxXQsWW16axmHPP47Bk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 h1:h/33OxYLqBk0BYmEbSUy7MlvgQR/m1w1/7OJFKoPL1I=
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0/go.mod h1:rvh3imDA6EaQi+oM/GQHkQAOHbXPKJ7EWJvfjuw141Q=
github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs=
github.com/anchore/go-lzo v0.1.0/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/diskfs/go-diskfs v1.7.0 h1:vonWmt5CMowXwUc79jWyGrf2DIMeoOjkLlMnQYGVOs8=
github.com/diskfs/go-diskfs v1.7.0/go.mod h1:LhQyXqOugWFRahYUSw47NyZJPezFzB9UELwhpszLP/k=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v26.0.0+incompatible h1:90BKrx1a1HKYpSnnBFR6AgDq/FqkHxwlUyzJVPxD30I=
github.com/docker/cli v26.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v26.0.0+incompatible h1:Ng2qi+gdKADUa/VM+6b6YaY2nlZhk/lVJiKR/2bMudU=
github.com/docker/docker v26.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/docker/cli v29.1.3+incompatible h1:+kz9uDWgs+mAaIZojWfFt4d53/jv0ZUOOoSh5ZnH36c=
github.com/docker/cli v29.1.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1UgjJdAAhj+uPL68n7XASS6bU+07ZX1WJvVS2eyoeY=
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-acme/lego/v4 v4.16.1 h1:JxZ93s4KG0jL27rZ30UsIgxap6VGzKuREsSkkyzeoCQ=
github.com/go-acme/lego/v4 v4.16.1/go.mod h1:AVvwdPned/IWpD/ihHhMsKnveF7HHYAz/CmtXi7OZoE=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/go-acme/lego/v4 v4.31.0 h1:gd4oUYdfs83PR1/SflkNdit9xY1iul2I4EystnU8NXM=
github.com/go-acme/lego/v4 v4.31.0/go.mod h1:m6zcfX/zcbMYDa8s6AnCMnoORWNP8Epnei+6NBCTUGs=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4=
github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.9 h1:TOpi/QG8iDcZlkQlGlFUti/ZtyLkliXvHDcyUIMuFrU=
github.com/googleapis/enterprise-certificate-proxy v0.3.9/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y=
github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gotify/server/v2 v2.8.0 h1:E3UDDn/3rFZi1sjZfbuhXNnxJP3ACZhdcw/iySegPRA=
github.com/gotify/server/v2 v2.8.0/go.mod h1:6ci5adxcE2hf1v+2oowKiQmixOxXV8vU+CRLKP6sqZA=
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A=
github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU=
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/linode/linodego v1.64.0 h1:If6pULIwHuQytgogtpQaBdVLX7z2TTHUF5u1tj2TPiY=
github.com/linode/linodego v1.64.0/go.mod h1:GoiwLVuLdBQcAebxAVKVL3mMYUgJZR/puOUSla04xBE=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/luthermonson/go-proxmox v0.3.2 h1:/zUg6FCl9cAABx0xU3OIgtDtClY0gVXxOCsrceDNylc=
github.com/luthermonson/go-proxmox v0.3.2/go.mod h1:oyFgg2WwTEIF0rP6ppjiixOHa5ebK1p8OaRiFhvICBQ=
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI=
github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=
github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=
github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
github.com/moby/moby/client v0.2.1 h1:1Grh1552mvv6i+sYOdY+xKKVTvzJegcVMhuXocyDz/k=
github.com/moby/moby/client v0.2.1/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0=
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4=
github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0=
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2 h1:l0tH15ACQADZAzC+LZ+mo2tIX4H6uZu0ulrVmG5Tqz0=
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.2 h1:gzB4c6ztb38C/jYiqEaFC+mCGcWFHDji9e6jwymY9d4=
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.2/go.mod h1:l1qIPIq2uRV5WTSvkbhbl/ndbeOu7OCb3UZ+0+2ZSb8=
github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE=
github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pion/dtls/v3 v3.0.10 h1:k9ekkq1kaZoxnNEbyLKI8DI37j/Nbk1HWmMuywpQJgg=
github.com/pion/dtls/v3 v3.0.10/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis=
github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0=
github.com/puzpuzpuz/xsync/v4 v4.2.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.58.0 h1:ggY2pvZaVdB9EyojxL1p+5mptkuHyX5MOSv4dgWF4Ug=
github.com/quic-go/quic-go v0.58.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/slog-common v0.19.0 h1:fNcZb8B2uOLooeYwFpAlKjkQTUafdjfqKcwcC89G9YI=
github.com/samber/slog-common v0.19.0/go.mod h1:dTz+YOU76aH007YUU0DffsXNsGFQRQllPQh9XyNoA3M=
github.com/samber/slog-zerolog/v2 v2.9.0 h1:6LkOabJmZdNLaUWkTC3IVVA+dq7b/V0FM6lz6/7+THI=
github.com/samber/slog-zerolog/v2 v2.9.0/go.mod h1:gnQW9VnCfM34v2pRMUIGMsZOVbYLqY/v0Wxu6atSVGc=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 h1:ObX9hZmK+VmijreZO/8x9pQ8/P/ToHD/bdSb4Eg4tUo=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36/go.mod h1:LEsDu4BubxK7/cWhtlQWfuxwL4rf/2UEpxXz1o1EMtM=
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0=
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
github.com/vultr/govultr/v3 v3.26.1 h1:G/M0rMQKwVSmL+gb0UgETbW5mcQi0Vf/o/ZSGdBCxJw=
github.com/vultr/govultr/v3 v3.26.1/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusing/ds v0.3.1 h1:mCqTgTQD8RhiBpcysvii5kZ7ZBmqcknVsFubNALGLbY=
github.com/yusing/ds v0.3.1/go.mod h1:XhKV4l7cZwBbbl7lRzNC9zX27zvCM0frIwiuD40ULRk=
github.com/yusing/gointernals v0.1.16 h1:GrhZZdxzA+jojLEqankctJrOuAYDb7kY1C93S1pVR34=
github.com/yusing/gointernals v0.1.16/go.mod h1:B/0FVXt4WPmgzVy3ynzkqKi+BSGaJVmwCJBRXYapo34=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0=
go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao=
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM=
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.259.0 h1:90TaGVIxScrh1Vn/XI2426kRpBqHwWIzVBzJsVZ5XrQ=
google.golang.org/api v0.259.0/go.mod h1:LC2ISWGWbRoyQVpxGntWwLWN/vLNxxKBK9KuJRI8Te4=
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934=
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=

1
goutils Submodule

Submodule goutils added at 326c1f1eb3

282
internal/acl/README.md Normal file
View File

@@ -0,0 +1,282 @@
# ACL (Access Control List)
Access control at the TCP connection level with IP/CIDR, timezone, and country-based filtering.
## Overview
The ACL package provides network-level access control by wrapping TCP listeners and validating incoming connections against configurable allow/deny rules. It integrates with MaxMind GeoIP for geographic-based filtering and supports access logging with notification batching.
### Primary consumers
- `internal/entrypoint` - Wraps the main TCP listener for connection filtering
- Operators - Configure rules via YAML configuration
### Non-goals
- HTTP request-level filtering (handled by middleware)
- Authentication or authorization (see `internal/auth`)
- VPN or tunnel integration
### Stability
Stable internal package. The public API is the `Config` struct and its methods.
## Public API
### Exported types
```go
type Config struct {
Default string // "allow" or "deny" (default: "allow")
AllowLocal *bool // Allow private/loopback IPs (default: true)
Allow Matchers // Allow rules
Deny Matchers // Deny rules
Log *accesslog.ACLLoggerConfig // Access logging configuration
Notify struct {
To []string // Notification providers
Interval time.Duration // Notification frequency (default: 1m)
IncludeAllowed *bool // Include allowed in notifications (default: false)
}
}
```
```go
type Matcher struct {
match MatcherFunc
}
```
```go
type Matchers []Matcher
```
### Exported functions and methods
```go
func (c *Config) Validate() gperr.Error
```
Validates configuration and sets defaults. Must be called before `Start`.
```go
func (c *Config) Start(parent task.Parent) gperr.Error
```
Initializes the ACL, starts the logger and notification goroutines.
```go
func (c *Config) IPAllowed(ip net.IP) bool
```
Returns true if the IP is allowed based on configured rules. Performs caching and GeoIP lookup if needed.
```go
func (c *Config) WrapTCP(lis net.Listener) net.Listener
```
Wraps a `net.Listener` to filter connections by IP.
```go
func (matcher *Matcher) Parse(s string) error
```
Parses a matcher string in the format `{type}:{value}`. Supported types: `ip`, `cidr`, `tz`, `country`.
## Architecture
### Core components
```mermaid
graph TD
A[TCP Listener] --> B[TCPListener Wrapper]
B --> C{IP Allowed?}
C -->|Yes| D[Accept Connection]
C -->|No| E[Close Connection]
F[Config] --> G[Validate]
G --> H[Start]
H --> I[Matcher Evaluation]
I --> C
J[MaxMind] -.-> K[IP Lookup]
K -.-> I
L[Access Logger] -.-> M[Log & Notify]
M -.-> B
```
### Connection filtering flow
```mermaid
sequenceDiagram
participant Client
participant TCPListener
participant Config
participant MaxMind
participant Logger
Client->>TCPListener: Connection Request
TCPListener->>Config: IPAllowed(clientIP)
alt Loopback IP
Config-->>TCPListener: true
else Private IP (allow_local)
Config-->>TCPListener: true
else Cached Result
Config-->>TCPListener: Cached Result
else Evaluate Allow Rules
Config->>Config: Check Allow list
alt Matches
Config->>Config: Cache true
Config-->>TCPListener: Allowed
else Evaluate Deny Rules
Config->>Config: Check Deny list
alt Matches
Config->>Config: Cache false
Config-->>TCPListener: Denied
else Default Action
Config->>MaxMind: Lookup GeoIP
MaxMind-->>Config: IPInfo
Config->>Config: Apply default rule
Config->>Config: Cache result
Config-->>TCPListener: Result
end
end
end
alt Logging enabled
Config->>Logger: Log access attempt
end
```
### Matcher types
| Type | Format | Example |
| -------- | ----------------- | --------------------- |
| IP | `ip:address` | `ip:192.168.1.1` |
| CIDR | `cidr:network` | `cidr:192.168.0.0/16` |
| TimeZone | `tz:timezone` | `tz:Asia/Shanghai` |
| Country | `country:ISOCode` | `country:GB` |
## Configuration Surface
### Config sources
Configuration is loaded from `config/config.yml` under the `acl` key.
### Schema
```yaml
acl:
default: "allow" # "allow" or "deny"
allow_local: true # Allow private/loopback IPs
log:
log_allowed: false # Log allowed connections
notify:
to: ["gotify"] # Notification providers
interval: "1m" # Notification interval
include_allowed: false # Include allowed in notifications
```
### Hot-reloading
Configuration requires restart. The ACL does not support dynamic rule updates.
## Dependency and Integration Map
### Internal dependencies
- `internal/maxmind` - IP geolocation lookup
- `internal/logging/accesslog` - Access logging
- `internal/notif` - Notifications
- `internal/task/task.go` - Lifetime management
### Integration points
```go
// Entrypoint uses ACL to wrap the TCP listener
aclListener := config.ACL.WrapTCP(listener)
http.Server.Serve(aclListener, entrypoint)
```
## Observability
### Logs
- `ACL started` - Configuration summary on start
- `log_notify_loop` - Access attempts (allowed/denied)
Log levels: `Info` for startup, `Debug` for client closure.
### Metrics
No metrics are currently exposed.
## Security Considerations
- Loopback and private IPs are always allowed unless explicitly denied
- Cache TTL is 1 minute to limit memory usage
- Notification channel has a buffer of 100 to prevent blocking
- Failed connections are immediately closed without response
## Failure Modes and Recovery
| Failure | Behavior | Recovery |
| --------------------------------- | ------------------------------------- | --------------------------------------------- |
| Invalid matcher syntax | Validation fails on startup | Fix configuration syntax |
| MaxMind database unavailable | GeoIP lookups return unknown location | Default action applies; cache hit still works |
| Notification provider unavailable | Notification dropped | Error logged, continues operation |
| Cache full | No eviction, uses Go map | No action needed |
## Usage Examples
### Basic configuration
```go
aclConfig := &acl.Config{
Default: "allow",
AllowLocal: ptr(true),
Allow: acl.Matchers{
{match: matchIP(net.ParseIP("192.168.1.0/24"))},
},
Deny: acl.Matchers{
{match: matchISOCode("CN")},
},
}
if err := aclConfig.Validate(); err != nil {
log.Fatal(err)
}
if err := aclConfig.Start(parent); err != nil {
log.Fatal(err)
}
```
### Wrapping a TCP listener
```go
listener, err := net.Listen("tcp", ":443")
if err != nil {
log.Fatal(err)
}
// Wrap with ACL
aclListener := aclConfig.WrapTCP(listener)
// Use with HTTP server
server := &http.Server{}
server.Serve(aclListener)
```
### Creating custom matchers
```go
matcher := &acl.Matcher{}
err := matcher.Parse("country:US")
if err != nil {
log.Fatal(err)
}
// Use the matcher
allowed := matcher.match(ipInfo)
```

309
internal/acl/config.go Normal file
View File

@@ -0,0 +1,309 @@
package acl
import (
"fmt"
"math"
"net"
"sync/atomic"
"time"
"github.com/puzpuzpuz/xsync/v4"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/yusing/godoxy/internal/common"
"github.com/yusing/godoxy/internal/logging/accesslog"
"github.com/yusing/godoxy/internal/maxmind"
"github.com/yusing/godoxy/internal/notif"
gperr "github.com/yusing/goutils/errs"
strutils "github.com/yusing/goutils/strings"
"github.com/yusing/goutils/task"
)
type Config struct {
Default string `json:"default" validate:"omitempty,oneof=allow deny"` // default: allow
AllowLocal *bool `json:"allow_local"` // default: true
Allow Matchers `json:"allow"`
Deny Matchers `json:"deny"`
Log *accesslog.ACLLoggerConfig `json:"log"`
Notify struct {
To []string `json:"to"` // list of notification providers
Interval time.Duration `json:"interval"` // interval between notifications
IncludeAllowed *bool `json:"include_allowed"` // default: false
} `json:"notify"`
config
valErr gperr.Error
}
const defaultNotifyInterval = 1 * time.Minute
type config struct {
defaultAllow bool
allowLocal bool
ipCache *xsync.Map[string, *checkCache]
// will be nil if Notify.To is empty
// these are per IP, reset every Notify.Interval
allowedCount map[string]uint32
blockedCount map[string]uint32
// these are total, never reset
totalAllowedCount uint64
totalBlockedCount uint64
logAllowed bool
// will be nil if Log is nil
logger accesslog.AccessLogger
// will never tick if Notify.To is empty
notifyTicker *time.Ticker
notifyAllowed bool
// will be nil if both Log and Notify.To are empty
logNotifyCh chan ipLog
}
type checkCache struct {
*maxmind.IPInfo
allow bool
created time.Time
}
type ipLog struct {
info *maxmind.IPInfo
allowed bool
}
// could be nil
var ActiveConfig atomic.Pointer[Config]
const cacheTTL = 1 * time.Minute
func (c *checkCache) Expired() bool {
return c.created.Add(cacheTTL).Before(time.Now())
}
// TODO: add stats
const (
ACLAllow = "allow"
ACLDeny = "deny"
)
func (c *Config) Validate() gperr.Error {
switch c.Default {
case "", ACLAllow:
c.defaultAllow = true
case ACLDeny:
c.defaultAllow = false
default:
c.valErr = gperr.New("invalid default value").Subject(c.Default)
return c.valErr
}
if c.AllowLocal != nil {
c.allowLocal = *c.AllowLocal
} else {
c.allowLocal = true
}
if c.Notify.Interval < 0 {
c.Notify.Interval = defaultNotifyInterval
}
if c.Log != nil {
c.logAllowed = c.Log.LogAllowed
}
if !c.allowLocal && !c.defaultAllow && len(c.Allow) == 0 {
c.valErr = gperr.New("allow_local is false and default is deny, but no allow rules are configured")
return c.valErr
}
c.ipCache = xsync.NewMap[string, *checkCache]()
if c.Notify.IncludeAllowed != nil {
c.notifyAllowed = *c.Notify.IncludeAllowed
} else {
c.notifyAllowed = false
}
return nil
}
func (c *Config) Valid() bool {
return c != nil && c.valErr == nil
}
func (c *Config) Start(parent task.Parent) gperr.Error {
if c.Log != nil {
logger, err := accesslog.NewAccessLogger(parent, c.Log)
if err != nil {
return gperr.New("failed to start access logger").With(err)
}
c.logger = logger
}
if c.valErr != nil {
return c.valErr
}
if c.needLogOrNotify() {
c.logNotifyCh = make(chan ipLog, 100)
}
if c.needNotify() {
c.allowedCount = make(map[string]uint32)
c.blockedCount = make(map[string]uint32)
c.notifyTicker = time.NewTicker(c.Notify.Interval)
} else {
c.notifyTicker = time.NewTicker(time.Duration(math.MaxInt64)) // never tick
}
if c.needLogOrNotify() {
go c.logNotifyLoop(parent)
}
log.Info().
Str("default", c.Default).
Bool("allow_local", c.allowLocal).
Int("allow_rules", len(c.Allow)).
Int("deny_rules", len(c.Deny)).
Msg("ACL started")
return nil
}
func (c *Config) cacheRecord(info *maxmind.IPInfo, allow bool) {
if common.ForceResolveCountry && info.City == nil {
maxmind.LookupCity(info)
}
c.ipCache.Store(info.Str, &checkCache{
IPInfo: info,
allow: allow,
created: time.Now(),
})
}
func (c *Config) needLogOrNotify() bool {
return c.needLog() || c.needNotify()
}
func (c *Config) needLog() bool {
return c.logger != nil
}
func (c *Config) needNotify() bool {
return len(c.Notify.To) > 0
}
func (c *Config) getCachedCity(ip string) string {
record, ok := c.ipCache.Load(ip)
if ok {
if record.City != nil {
if record.City.Country.IsoCode != "" {
return record.City.Country.IsoCode
}
return record.City.Location.TimeZone
}
}
return "unknown location"
}
func (c *Config) logNotifyLoop(parent task.Parent) {
defer c.notifyTicker.Stop()
for {
select {
case <-parent.Context().Done():
return
case log := <-c.logNotifyCh:
if c.logger != nil {
if !log.allowed || c.logAllowed {
c.logger.LogACL(log.info, !log.allowed)
}
}
if c.needNotify() {
if log.allowed {
if c.notifyAllowed {
c.allowedCount[log.info.Str]++
c.totalAllowedCount++
}
} else {
c.blockedCount[log.info.Str]++
c.totalBlockedCount++
}
}
case <-c.notifyTicker.C: // will never tick when notify is disabled
total := len(c.allowedCount) + len(c.blockedCount)
if total == 0 {
continue
}
total++
fieldsBody := make(notif.ListBody, total)
i := 0
fieldsBody[i] = fmt.Sprintf("Total: allowed %d, blocked %d", c.totalAllowedCount, c.totalBlockedCount)
i++
for ip, count := range c.allowedCount {
fieldsBody[i] = fmt.Sprintf("%s (%s): allowed %d times", ip, c.getCachedCity(ip), count)
i++
}
for ip, count := range c.blockedCount {
fieldsBody[i] = fmt.Sprintf("%s (%s): blocked %d times", ip, c.getCachedCity(ip), count)
i++
}
notif.Notify(&notif.LogMessage{
Level: zerolog.InfoLevel,
Title: "ACL Summary for last " + strutils.FormatDuration(c.Notify.Interval),
Body: fieldsBody,
To: c.Notify.To,
})
clear(c.allowedCount)
clear(c.blockedCount)
}
}
}
// log and notify if needed
func (c *Config) logAndNotify(info *maxmind.IPInfo, allowed bool) {
if c.logNotifyCh != nil {
c.logNotifyCh <- ipLog{info: info, allowed: allowed}
}
}
func (c *Config) IPAllowed(ip net.IP) bool {
if ip == nil {
return false
}
// always allow loopback, not logged
if ip.IsLoopback() {
return true
}
if c.allowLocal && ip.IsPrivate() {
c.logAndNotify(&maxmind.IPInfo{IP: ip, Str: ip.String()}, true)
return true
}
ipStr := ip.String()
record, ok := c.ipCache.Load(ipStr)
if ok && !record.Expired() {
c.logAndNotify(record.IPInfo, record.allow)
return record.allow
}
ipAndStr := &maxmind.IPInfo{IP: ip, Str: ipStr}
if c.Allow.Match(ipAndStr) {
c.logAndNotify(ipAndStr, true)
c.cacheRecord(ipAndStr, true)
return true
}
if c.Deny.Match(ipAndStr) {
c.logAndNotify(ipAndStr, false)
c.cacheRecord(ipAndStr, false)
return false
}
c.logAndNotify(ipAndStr, c.defaultAllow)
c.cacheRecord(ipAndStr, c.defaultAllow)
return c.defaultAllow
}

112
internal/acl/matcher.go Normal file
View File

@@ -0,0 +1,112 @@
package acl
import (
"net"
"strings"
"github.com/yusing/godoxy/internal/maxmind"
gperr "github.com/yusing/goutils/errs"
)
type MatcherFunc func(*maxmind.IPInfo) bool
type Matcher struct {
match MatcherFunc
}
type Matchers []Matcher
const (
MatcherTypeIP = "ip"
MatcherTypeCIDR = "cidr"
MatcherTypeTimeZone = "tz"
MatcherTypeCountry = "country"
)
// TODO: use this error in the future
//
//nolint:unused
var errMatcherFormat = gperr.Multiline().AddLines(
"invalid matcher format, expect {type}:{value}",
"Available types: ip|cidr|tz|country",
"ip:127.0.0.1",
"cidr:127.0.0.0/8",
"tz:Asia/Shanghai",
"country:GB",
)
var (
errSyntax = gperr.New("syntax error")
errInvalidIP = gperr.New("invalid IP")
errInvalidCIDR = gperr.New("invalid CIDR")
)
func (matcher *Matcher) Parse(s string) error {
parts := strings.Split(s, ":")
if len(parts) != 2 {
return errSyntax
}
switch parts[0] {
case MatcherTypeIP:
ip := net.ParseIP(parts[1])
if ip == nil {
return errInvalidIP
}
matcher.match = matchIP(ip)
case MatcherTypeCIDR:
_, net, err := net.ParseCIDR(parts[1])
if err != nil {
return errInvalidCIDR
}
matcher.match = matchCIDR(net)
case MatcherTypeTimeZone:
matcher.match = matchTimeZone(parts[1])
case MatcherTypeCountry:
matcher.match = matchISOCode(parts[1])
default:
return errSyntax
}
return nil
}
func (matchers Matchers) Match(ip *maxmind.IPInfo) bool {
for _, m := range matchers {
if m.match(ip) {
return true
}
}
return false
}
func matchIP(ip net.IP) MatcherFunc {
return func(ip2 *maxmind.IPInfo) bool {
return ip.Equal(ip2.IP)
}
}
func matchCIDR(n *net.IPNet) MatcherFunc {
return func(ip *maxmind.IPInfo) bool {
return n.Contains(ip.IP)
}
}
func matchTimeZone(tz string) MatcherFunc {
return func(ip *maxmind.IPInfo) bool {
city, ok := maxmind.LookupCity(ip)
if !ok {
return false
}
return city.Location.TimeZone == tz
}
}
func matchISOCode(iso string) MatcherFunc {
return func(ip *maxmind.IPInfo) bool {
city, ok := maxmind.LookupCity(ip)
if !ok {
return false
}
return city.Country.IsoCode == iso
}
}

View File

@@ -0,0 +1,49 @@
package acl
import (
"net"
"reflect"
"testing"
maxmind "github.com/yusing/godoxy/internal/maxmind/types"
"github.com/yusing/godoxy/internal/serialization"
)
func TestMatchers(t *testing.T) {
strMatchers := []string{
"ip:127.0.0.1",
"cidr:10.0.0.0/8",
}
var mathers Matchers
err := serialization.Convert(reflect.ValueOf(strMatchers), reflect.ValueOf(&mathers), false)
if err != nil {
t.Fatal(err)
}
tests := []struct {
ip string
want bool
}{
{"127.0.0.1", true},
{"10.0.0.1", true},
{"127.0.0.2", false},
{"192.168.0.1", false},
{"11.0.0.1", false},
}
for _, test := range tests {
ip := net.ParseIP(test.ip)
if ip == nil {
t.Fatalf("invalid ip: %s", test.ip)
}
got := mathers.Match(&maxmind.IPInfo{
IP: ip,
Str: test.ip,
})
if got != test.want {
t.Errorf("mathers.Match(%s) = %v, want %v", test.ip, got, test.want)
}
}
}

View File

@@ -0,0 +1,75 @@
package acl
import (
"errors"
"io"
"net"
"time"
)
type TCPListener struct {
acl *Config
lis net.Listener
}
type noConn struct{}
func (noConn) Read(b []byte) (int, error) { return 0, io.EOF }
func (noConn) Write(b []byte) (int, error) { return 0, io.EOF }
func (noConn) Close() error { return nil }
func (noConn) LocalAddr() net.Addr { return nil }
func (noConn) RemoteAddr() net.Addr { return nil }
func (noConn) SetDeadline(t time.Time) error { return nil }
func (noConn) SetReadDeadline(t time.Time) error { return nil }
func (noConn) SetWriteDeadline(t time.Time) error { return nil }
func (c *Config) WrapTCP(lis net.Listener) net.Listener {
if c == nil {
return lis
}
return &TCPListener{
acl: c,
lis: lis,
}
}
func (s *TCPListener) Addr() net.Addr {
return s.lis.Addr()
}
func (s *TCPListener) Accept() (net.Conn, error) {
c, err := s.lis.Accept()
if err != nil {
return nil, err
}
addr, ok := c.RemoteAddr().(*net.TCPAddr)
if !ok {
// Not a TCPAddr, drop
c.Close()
return noConn{}, nil
}
if !s.acl.IPAllowed(addr.IP) {
c.Close()
return noConn{}, nil
}
return c, nil
}
type tcpListener interface {
SetDeadline(t time.Time) error
}
var _ tcpListener = (*net.TCPListener)(nil)
func (s *TCPListener) SetDeadline(t time.Time) error {
switch lis := s.lis.(type) {
case tcpListener:
return lis.SetDeadline(t)
default:
return errors.New("not a TCPListener")
}
}
func (s *TCPListener) Close() error {
return s.lis.Close()
}

View File

@@ -0,0 +1,105 @@
package acl
import (
"errors"
"net"
"time"
)
type UDPListener struct {
acl *Config
lis net.PacketConn
}
func (c *Config) WrapUDP(lis net.PacketConn) net.PacketConn {
if c == nil {
return lis
}
return &UDPListener{
acl: c,
lis: lis,
}
}
func (s *UDPListener) LocalAddr() net.Addr {
return s.lis.LocalAddr()
}
func (s *UDPListener) ReadFrom(p []byte) (int, net.Addr, error) {
for {
n, addr, err := s.lis.ReadFrom(p)
if err != nil {
return n, addr, err
}
udpAddr, ok := addr.(*net.UDPAddr)
if !ok {
// Not a UDPAddr, drop
continue
}
if !s.acl.IPAllowed(udpAddr.IP) {
// Drop packet from disallowed IP
continue
}
return n, addr, nil
}
}
func (s *UDPListener) WriteTo(p []byte, addr net.Addr) (int, error) {
for {
n, err := s.lis.WriteTo(p, addr)
if err != nil {
return n, err
}
udpAddr, ok := addr.(*net.UDPAddr)
if !ok {
// Not a UDPAddr, drop
continue
}
if !s.acl.IPAllowed(udpAddr.IP) {
// Drop packet to disallowed IP
continue
}
return n, nil
}
}
func (s *UDPListener) SetDeadline(t time.Time) error {
return s.lis.SetDeadline(t)
}
func (s *UDPListener) SetReadDeadline(t time.Time) error {
return s.lis.SetReadDeadline(t)
}
func (s *UDPListener) SetWriteDeadline(t time.Time) error {
return s.lis.SetWriteDeadline(t)
}
type udpListener interface {
SetReadBuffer(bytes int) error
SetWriteBuffer(bytes int) error
}
var _ udpListener = (*net.UDPConn)(nil)
func (s *UDPListener) SetReadBuffer(bytes int) error {
switch lis := s.lis.(type) {
case udpListener:
return lis.SetReadBuffer(bytes)
default:
return errors.New("not a UDPConn")
}
}
func (s *UDPListener) SetWriteBuffer(bytes int) error {
switch lis := s.lis.(type) {
case udpListener:
return lis.SetWriteBuffer(bytes)
default:
return errors.New("not a UDPConn")
}
}
func (s *UDPListener) Close() error {
return s.lis.Close()
}

View File

@@ -0,0 +1,281 @@
# Agent Pool
Thread-safe pool for managing remote Docker agent connections.
## Overview
The agentpool package provides a centralized pool for storing and retrieving remote agent configurations. It enables GoDoxy to connect to Docker hosts via agent connections instead of direct socket access, enabling secure remote container management.
### Primary consumers
- `internal/route/provider` - Creates agent-based route providers
- `internal/docker` - Manages agent-based Docker client connections
- Configuration loading during startup
### Non-goals
- Agent lifecycle management (handled by `agent/pkg/agent`)
- Agent health monitoring
- Agent authentication/authorization
### Stability
Stable internal package. The pool uses `xsync.Map` for lock-free concurrent access.
## Public API
### Exported types
```go
type Agent struct {
*agent.AgentConfig
httpClient *http.Client
fasthttpHcClient *fasthttp.Client
}
```
### Exported functions
```go
func Add(cfg *agent.AgentConfig) (added bool)
```
Adds an agent to the pool. Returns `true` if added, `false` if already exists. Uses `LoadOrCompute` to prevent duplicates.
```go
func Has(cfg *agent.AgentConfig) bool
```
Checks if an agent exists in the pool.
```go
func Remove(cfg *agent.AgentConfig)
```
Removes an agent from the pool.
```go
func RemoveAll()
```
Removes all agents from the pool. Called during configuration reload.
```go
func Get(agentAddrOrDockerHost string) (*Agent, bool)
```
Retrieves an agent by address or Docker host URL. Automatically detects if the input is an agent address or Docker host URL and resolves accordingly.
```go
func GetAgent(name string) (*Agent, bool)
```
Retrieves an agent by name. O(n) iteration over pool contents.
```go
func List() []*Agent
```
Returns all agents as a slice. Creates a new copy for thread safety.
```go
func Iter() iter.Seq2[string, *Agent]
```
Returns an iterator over all agents. Uses `xsync.Map.Range`.
```go
func Num() int
```
Returns the number of agents in the pool.
```go
func (agent *Agent) HTTPClient() *http.Client
```
Returns an HTTP client configured for the agent.
## Architecture
### Core components
```mermaid
graph TD
A[Agent Config] --> B[Add to Pool]
B --> C[xsync.Map Storage]
C --> D{Get Request}
D -->|By Address| E[Load from map]
D -->|By Docker Host| F[Resolve agent addr]
D -->|By Name| G[Iterate & match]
H[Docker Client] --> I[Get Agent]
I --> C
I --> J[HTTP Client]
J --> K[Agent Connection]
L[Route Provider] --> M[List Agents]
M --> C
```
### Thread safety model
The pool uses `xsync.Map[string, *Agent]` for concurrent-safe operations:
- `Add`: `LoadOrCompute` prevents race conditions and duplicates
- `Get`: Lock-free read operations
- `Iter`: Consistent snapshot iteration via `Range`
- `Remove`: Thread-safe deletion
### Test mode
When running tests (binary ends with `.test`), a test agent is automatically added:
```go
func init() {
if strings.HasSuffix(os.Args[0], ".test") {
agentPool.Store("test-agent", &Agent{
AgentConfig: &agent.AgentConfig{
Addr: "test-agent",
},
})
}
}
```
## Configuration Surface
No direct configuration. Agents are added via configuration loading from `config/config.yml`:
```yaml
providers:
agents:
- addr: agent.example.com:443
name: remote-agent
tls:
ca_file: /path/to/ca.pem
cert_file: /path/to/cert.pem
key_file: /path/to/key.pem
```
## Dependency and Integration Map
### Internal dependencies
- `agent/pkg/agent` - Agent configuration and connection settings
- `xsync/v4` - Concurrent map implementation
### External dependencies
- `valyala/fasthttp` - Fast HTTP client for agent communication
### Integration points
```go
// Docker package uses agent pool for remote connections
if agent.IsDockerHostAgent(host) {
a, ok := agentpool.Get(host)
if !ok {
panic(fmt.Errorf("agent %q not found", host))
}
opt := []client.Opt{
client.WithHost(agent.DockerHost),
client.WithHTTPClient(a.HTTPClient()),
}
}
```
## Observability
### Logs
No specific logging in the agentpool package. Client creation/destruction is logged in the docker package.
### Metrics
No metrics are currently exposed.
## Security Considerations
- TLS configuration is loaded from agent configuration
- Connection credentials are not stored in the pool after agent creation
- HTTP clients are created per-request to ensure credential freshness
## Failure Modes and Recovery
| Failure | Behavior | Recovery |
| -------------------- | -------------------- | ---------------------------- |
| Agent not found | Returns `nil, false` | Add agent to pool before use |
| Duplicate add | Returns `false` | Existing agent is preserved |
| Test mode activation | Test agent added | Only during test binaries |
## Performance Characteristics
- O(1) lookup by address
- O(n) iteration for name-based lookup
- Pre-sized to 10 entries via `xsync.WithPresize(10)`
- No locks required for read operations
- HTTP clients are created per-call to ensure fresh connections
## Usage Examples
### Adding an agent
```go
agentConfig := &agent.AgentConfig{
Addr: "agent.example.com:443",
Name: "my-agent",
}
added := agentpool.Add(agentConfig)
if !added {
log.Println("Agent already exists")
}
```
### Retrieving an agent
```go
// By address
agent, ok := agentpool.Get("agent.example.com:443")
if !ok {
log.Fatal("Agent not found")
}
// By Docker host URL
agent, ok := agentpool.Get("http://docker-host:2375")
if !ok {
log.Fatal("Agent not found")
}
// By name
agent, ok := agentpool.GetAgent("my-agent")
if !ok {
log.Fatal("Agent not found")
}
```
### Iterating over all agents
```go
for addr, agent := range agentpool.Iter() {
log.Printf("Agent: %s at %s", agent.Name, addr)
}
```
### Using with Docker client
```go
// When creating a Docker client with an agent host
if agent.IsDockerHostAgent(host) {
a, ok := agentpool.Get(host)
if !ok {
panic(fmt.Errorf("agent %q not found", host))
}
opt := []client.Opt{
client.WithHost(agent.DockerHost),
client.WithHTTPClient(a.HTTPClient()),
}
dockerClient, err := client.New(opt...)
}
```

View File

@@ -0,0 +1,54 @@
package agentpool
import (
"net"
"net/http"
"time"
"github.com/valyala/fasthttp"
"github.com/yusing/godoxy/agent/pkg/agent"
)
type Agent struct {
*agent.AgentConfig
httpClient *http.Client
fasthttpHcClient *fasthttp.Client
}
func newAgent(cfg *agent.AgentConfig) *Agent {
transport := cfg.Transport()
transport.MaxIdleConns = 100
transport.MaxIdleConnsPerHost = 100
transport.ReadBufferSize = 16384
transport.WriteBufferSize = 16384
return &Agent{
AgentConfig: cfg,
httpClient: &http.Client{
Transport: transport,
},
fasthttpHcClient: &fasthttp.Client{
DialTimeout: func(addr string, timeout time.Duration) (net.Conn, error) {
if addr != agent.AgentHost+":443" {
return nil, &net.AddrError{Err: "invalid address", Addr: addr}
}
return net.DialTimeout("tcp", cfg.Addr, timeout)
},
TLSConfig: cfg.TLSConfig(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 3 * time.Second,
DisableHeaderNamesNormalizing: true,
DisablePathNormalizing: true,
NoDefaultUserAgentHeader: true,
ReadBufferSize: 1024,
WriteBufferSize: 1024,
},
}
}
func (agent *Agent) HTTPClient() *http.Client {
return &http.Client{
Transport: agent.Transport(),
}
}

View File

@@ -0,0 +1,96 @@
package agentpool
import (
"context"
"fmt"
"io"
"net/http"
"time"
"github.com/bytedance/sonic"
"github.com/gorilla/websocket"
"github.com/valyala/fasthttp"
"github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/goutils/http/reverseproxy"
)
func (cfg *Agent) Do(ctx context.Context, method, endpoint string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, method, agent.APIBaseURL+endpoint, body)
if err != nil {
return nil, err
}
return cfg.httpClient.Do(req)
}
func (cfg *Agent) Forward(req *http.Request, endpoint string) (*http.Response, error) {
req.URL.Host = agent.AgentHost
req.URL.Scheme = "https"
req.URL.Path = agent.APIEndpointBase + endpoint
req.RequestURI = ""
resp, err := cfg.httpClient.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}
type HealthCheckResponse struct {
Healthy bool `json:"healthy"`
Detail string `json:"detail"`
Latency time.Duration `json:"latency"`
}
func (cfg *Agent) DoHealthCheck(timeout time.Duration, query string) (ret HealthCheckResponse, err error) {
req := fasthttp.AcquireRequest()
defer fasthttp.ReleaseRequest(req)
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseResponse(resp)
req.SetRequestURI(agent.APIBaseURL + agent.EndpointHealth + "?" + query)
req.Header.SetMethod(fasthttp.MethodGet)
req.Header.Set("Accept-Encoding", "identity")
req.SetConnectionClose()
start := time.Now()
err = cfg.fasthttpHcClient.DoTimeout(req, resp, timeout)
ret.Latency = time.Since(start)
if err != nil {
return ret, err
}
if status := resp.StatusCode(); status != http.StatusOK {
ret.Detail = fmt.Sprintf("HTTP %d %s", status, resp.Body())
return ret, nil
} else {
err = sonic.Unmarshal(resp.Body(), &ret)
if err != nil {
return ret, err
}
}
return ret, nil
}
func (cfg *Agent) Websocket(ctx context.Context, endpoint string) (*websocket.Conn, *http.Response, error) {
transport := cfg.Transport()
dialer := websocket.Dialer{
NetDialContext: transport.DialContext,
NetDialTLSContext: transport.DialTLSContext,
}
return dialer.DialContext(ctx, agent.APIBaseURL+endpoint, http.Header{
"Host": {agent.AgentHost},
})
}
// ReverseProxy reverse proxies the request to the agent
//
// It will create a new request with the same context, method, and body, but with the agent host and scheme, and the endpoint
// If the request has a query, it will be added to the proxy request's URL
func (cfg *Agent) ReverseProxy(w http.ResponseWriter, req *http.Request, endpoint string) {
rp := reverseproxy.NewReverseProxy("agent", agent.AgentURL, cfg.Transport())
req.URL.Host = agent.AgentHost
req.URL.Scheme = "https"
req.URL.Path = endpoint
req.RequestURI = ""
rp.ServeHTTP(w, req)
}

View File

@@ -0,0 +1,79 @@
package agentpool
import (
"iter"
"os"
"strings"
"github.com/puzpuzpuz/xsync/v4"
"github.com/yusing/godoxy/agent/pkg/agent"
)
var agentPool = xsync.NewMap[string, *Agent](xsync.WithPresize(10))
func init() {
if strings.HasSuffix(os.Args[0], ".test") {
agentPool.Store("test-agent", &Agent{
AgentConfig: &agent.AgentConfig{
Addr: "test-agent",
},
})
}
}
func Get(agentAddrOrDockerHost string) (*Agent, bool) {
if !agent.IsDockerHostAgent(agentAddrOrDockerHost) {
return getAgentByAddr(agentAddrOrDockerHost)
}
return getAgentByAddr(agent.GetAgentAddrFromDockerHost(agentAddrOrDockerHost))
}
func GetAgent(name string) (*Agent, bool) {
for _, agent := range agentPool.Range {
if agent.Name == name {
return agent, true
}
}
return nil, false
}
func Add(cfg *agent.AgentConfig) (added bool) {
_, loaded := agentPool.LoadOrCompute(cfg.Addr, func() (*Agent, bool) {
return newAgent(cfg), false
})
return !loaded
}
func Has(cfg *agent.AgentConfig) bool {
_, ok := agentPool.Load(cfg.Addr)
return ok
}
func Remove(cfg *agent.AgentConfig) {
agentPool.Delete(cfg.Addr)
}
func RemoveAll() {
agentPool.Clear()
}
func List() []*Agent {
agents := make([]*Agent, 0, agentPool.Size())
for _, agent := range agentPool.Range {
agents = append(agents, agent)
}
return agents
}
func Iter() iter.Seq2[string, *Agent] {
return agentPool.Range
}
func Num() int {
return agentPool.Size()
}
func getAgentByAddr(addr string) (agent *Agent, ok bool) {
agent, ok = agentPool.Load(addr)
return agent, ok
}

207
internal/api/handler.go Normal file
View File

@@ -0,0 +1,207 @@
package api
import (
"net/http"
"reflect"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/codec/json"
"github.com/gorilla/websocket"
"github.com/rs/zerolog/log"
apiV1 "github.com/yusing/godoxy/internal/api/v1"
agentApi "github.com/yusing/godoxy/internal/api/v1/agent"
authApi "github.com/yusing/godoxy/internal/api/v1/auth"
certApi "github.com/yusing/godoxy/internal/api/v1/cert"
dockerApi "github.com/yusing/godoxy/internal/api/v1/docker"
fileApi "github.com/yusing/godoxy/internal/api/v1/file"
homepageApi "github.com/yusing/godoxy/internal/api/v1/homepage"
metricsApi "github.com/yusing/godoxy/internal/api/v1/metrics"
routeApi "github.com/yusing/godoxy/internal/api/v1/route"
"github.com/yusing/godoxy/internal/auth"
"github.com/yusing/godoxy/internal/common"
apitypes "github.com/yusing/goutils/apitypes"
gperr "github.com/yusing/goutils/errs"
)
// @title GoDoxy API
// @version 1.0
// @description GoDoxy API
// @termsOfService https://github.com/yusing/godoxy/blob/main/LICENSE
// @contact.name Yusing
// @contact.url https://github.com/yusing/godoxy/issues
// @license.name MIT
// @license.url https://github.com/yusing/godoxy/blob/main/LICENSE
// @BasePath /api/v1
// @externalDocs.description GoDoxy Docs
// @externalDocs.url https://docs.godoxy.dev
func NewHandler() *gin.Engine {
if !common.IsDebug {
gin.SetMode("release")
}
r := gin.New()
r.Use(ErrorHandler())
r.Use(ErrorLoggingMiddleware())
r.Use(NoCache())
log.Debug().Msg("gin codec json.API: " + reflect.TypeOf(json.API).Name())
r.GET("/api/v1/version", apiV1.Version)
if auth.IsEnabled() {
v1Auth := r.Group("/api/v1/auth")
{
v1Auth.HEAD("/check", authApi.Check)
v1Auth.POST("/login", authApi.Login)
v1Auth.GET("/callback", authApi.Callback)
v1Auth.POST("/callback", authApi.Callback)
v1Auth.POST("/logout", authApi.Logout)
v1Auth.GET("/logout", authApi.Logout)
}
}
v1 := r.Group("/api/v1")
if auth.IsEnabled() {
v1.Use(AuthMiddleware())
}
if common.APISkipOriginCheck {
v1.Use(SkipOriginCheckMiddleware())
}
{
// enable cache for favicon
v1.GET("/favicon", apiV1.FavIcon)
v1.GET("/health", apiV1.Health)
v1.GET("/icons", apiV1.Icons)
v1.POST("/reload", apiV1.Reload)
v1.GET("/stats", apiV1.Stats)
route := v1.Group("/route")
{
route.GET("/list", routeApi.Routes)
route.GET("/:which", routeApi.Route)
route.GET("/providers", routeApi.Providers)
route.GET("/by_provider", routeApi.ByProvider)
route.POST("/playground", routeApi.Playground)
}
file := v1.Group("/file")
{
file.GET("/list", fileApi.List)
file.GET("/content", fileApi.Get)
file.PUT("/content", fileApi.Set)
file.POST("/content", fileApi.Set)
file.POST("/validate", fileApi.Validate)
}
homepage := v1.Group("/homepage")
{
homepage.GET("/categories", homepageApi.Categories)
homepage.GET("/items", homepageApi.Items)
homepage.POST("/set/item", homepageApi.SetItem)
homepage.POST("/set/items_batch", homepageApi.SetItemsBatch)
homepage.POST("/set/item_visible", homepageApi.SetItemVisible)
homepage.POST("/set/item_favorite", homepageApi.SetItemFavorite)
homepage.POST("/set/item_sort_order", homepageApi.SetItemSortOrder)
homepage.POST("/set/item_all_sort_order", homepageApi.SetItemAllSortOrder)
homepage.POST("/set/item_fav_sort_order", homepageApi.SetItemFavSortOrder)
homepage.POST("/set/category_order", homepageApi.SetCategoryOrder)
homepage.POST("/item_click", homepageApi.ItemClick)
}
cert := v1.Group("/cert")
{
cert.GET("/info", certApi.Info)
cert.GET("/renew", certApi.Renew)
}
agent := v1.Group("/agent")
{
agent.GET("/list", agentApi.List)
agent.POST("/create", agentApi.Create)
agent.POST("/verify", agentApi.Verify)
}
metrics := v1.Group("/metrics")
{
metrics.GET("/system_info", metricsApi.SystemInfo)
metrics.GET("/all_system_info", metricsApi.AllSystemInfo)
metrics.GET("/uptime", metricsApi.Uptime)
}
docker := v1.Group("/docker")
{
docker.GET("/container/:id", dockerApi.GetContainer)
docker.GET("/containers", dockerApi.Containers)
docker.GET("/info", dockerApi.Info)
docker.GET("/logs/:id", dockerApi.Logs)
docker.POST("/start", dockerApi.Start)
docker.POST("/stop", dockerApi.Stop)
docker.POST("/restart", dockerApi.Restart)
}
}
return r
}
func NoCache() gin.HandlerFunc {
return func(c *gin.Context) {
// skip cache if Cache-Control header is set
if c.Writer.Header().Get("Cache-Control") == "" {
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
c.Header("Pragma", "no-cache")
c.Header("Expires", "0")
}
c.Next()
}
}
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
err := auth.GetDefaultAuth().CheckToken(c.Request)
if err != nil {
c.JSON(http.StatusUnauthorized, apitypes.Error("Unauthorized", err))
c.Abort()
return
}
c.Next()
}
}
func SkipOriginCheckMiddleware() gin.HandlerFunc {
upgrader := &websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
return func(c *gin.Context) {
c.Set("upgrader", upgrader)
c.Next()
}
}
func ErrorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
if len(c.Errors) > 0 {
logger := log.With().Str("uri", c.Request.RequestURI).Logger()
for _, err := range c.Errors {
gperr.LogError("Internal error", err.Err, &logger)
}
if !c.IsWebsocket() {
c.JSON(http.StatusInternalServerError, apitypes.Error("Internal server error"))
}
}
}
}
func ErrorLoggingMiddleware() gin.HandlerFunc {
return gin.CustomRecoveryWithWriter(nil, func(c *gin.Context, err any) {
log.Error().Any("error", err).Str("uri", c.Request.RequestURI).Msg("Internal error")
if !c.IsWebsocket() {
c.JSON(http.StatusInternalServerError, apitypes.Error("Internal server error"))
}
})
}

197
internal/api/v1/README.md Normal file
View File

@@ -0,0 +1,197 @@
# API v1 Package
Implements the v1 REST API handlers for GoDoxy, exposing endpoints for managing routes, Docker containers, certificates, metrics, and system configuration.
## Overview
The `internal/api/v1` package implements the HTTP handlers that power GoDoxy's REST API. It uses the Gin web framework and provides endpoints for route management, container operations, certificate handling, system metrics, and configuration.
### Primary Consumers
- **WebUI**: The homepage dashboard and admin interface consume these endpoints
### Non-goals
- Authentication and authorization logic (delegated to `internal/auth`)
- Route proxying and request handling (handled by `internal/route`)
- Docker container lifecycle management (delegated to `internal/docker`)
- Certificate issuance and storage (handled by `internal/autocert`)
### Stability
This package is stable. Public API endpoints follow semantic versioning for request/response contracts. Internal implementation may change between minor versions.
## Public API
### Exported Types
Types are defined in `goutils/apitypes`:
| Type | Purpose |
| -------------------------- | -------------------------------- |
| `apitypes.ErrorResponse` | Standard error response format |
| `apitypes.SuccessResponse` | Standard success response format |
### Handler Subpackages
| Package | Purpose |
| ---------- | ---------------------------------------------- |
| `route` | Route listing, details, and playground testing |
| `docker` | Docker container management and monitoring |
| `cert` | Certificate information and renewal |
| `metrics` | System metrics and uptime information |
| `homepage` | Homepage items and category management |
| `file` | Configuration file read/write operations |
| `auth` | Authentication and session management |
| `agent` | Remote agent creation and management |
## Architecture
### Handler Organization
Package structure mirrors the API endpoint paths (e.g., `auth/login.go` handles `/auth/login`).
### Request Flow
```mermaid
sequenceDiagram
participant Client
participant GinRouter
participant Handler
participant Service
participant Response
Client->>GinRouter: HTTP Request
GinRouter->>Handler: Route to handler
Handler->>Service: Call service layer
Service-->>Handler: Data or error
Handler->>Response: Format JSON response
Response-->>Client: JSON or redirect
```
## Configuration Surface
API listening address is configured with `GODOXY_API_ADDR` environment variable.
## Dependency and Integration Map
### Internal Dependencies
| Package | Purpose |
| ----------------------- | --------------------------- |
| `internal/route/routes` | Route storage and iteration |
| `internal/docker` | Docker client management |
| `internal/config` | Configuration access |
| `internal/metrics` | System metrics collection |
| `internal/homepage` | Homepage item generation |
| `internal/agentpool` | Remote agent management |
| `internal/auth` | Authentication services |
### External Dependencies
| Package | Purpose |
| ------------------------------ | --------------------------- |
| `github.com/gin-gonic/gin` | HTTP routing and middleware |
| `github.com/gorilla/websocket` | WebSocket support |
| `github.com/moby/moby/client` | Docker API client |
## Observability
### Logs
Handlers log at `INFO` level for requests and `ERROR` level for failures. Logs include:
- Request path and method
- Response status code
- Error details (when applicable)
### Metrics
No dedicated metrics exposed by handlers. Request metrics collected by middleware.
## Security Considerations
- All endpoints (except `/api/v1/version`) require authentication
- Input validation using Gin binding tags
- Path traversal prevention in file operations
- WebSocket connections use same auth middleware as HTTP
## Failure Modes and Recovery
| Failure | Behavior |
| ----------------------------------- | ------------------------------------------ |
| Docker host unreachable | Returns partial results with errors logged |
| Certificate provider not configured | Returns 404 |
| Invalid request body | Returns 400 with error details |
| Authentication failure | Returns 302 redirect to login |
| Agent not found | Returns 404 |
## Usage Examples
### Listing All Routes via WebSocket
```go
import (
"github.com/gorilla/websocket"
)
func watchRoutes(provider string) error {
url := "ws://localhost:8888/api/v1/route/list"
if provider != "" {
url += "?provider=" + provider
}
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
return err
}
defer conn.Close()
for {
_, message, err := conn.ReadMessage()
if err != nil {
return err
}
// message contains JSON array of routes
processRoutes(message)
}
}
```
### Getting Container Status
```go
import (
"encoding/json"
"net/http"
)
type Container struct {
Server string `json:"server"`
Name string `json:"name"`
ID string `json:"id"`
Image string `json:"image"`
}
func listContainers() ([]Container, error) {
resp, err := http.Get("http://localhost:8888/api/v1/docker/containers")
if err != nil {
return nil, err
}
defer resp.Body.Close()
var containers []Container
if err := json.NewDecoder(resp.Body).Decode(&containers); err != nil {
return nil, err
}
return containers, nil
}
```
### Health Check
```bash
curl http://localhost:8888/health
```
)

Some files were not shown because too many files have changed in this diff Show More