mirror of
https://github.com/yusing/godoxy.git
synced 2026-02-06 17:09:36 +01:00
Compare commits
96 Commits
refactor/e
...
compat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15b9635ee1 | ||
|
|
33d512f550 | ||
|
|
c7a70f630a | ||
|
|
12a8b9a1b4 | ||
|
|
4c62a4b365 | ||
|
|
5c30f4a859 | ||
|
|
c6e4e83fcd | ||
|
|
c54741aab6 | ||
|
|
fb6b692f55 | ||
|
|
1319f72805 | ||
|
|
52511f1618 | ||
|
|
ca2cbd3332 | ||
|
|
57f7b72923 | ||
|
|
54b3008cce | ||
|
|
d9abd65471 | ||
|
|
d5b9d2e6bc | ||
|
|
ec9cab05c7 | ||
|
|
6ff1346675 | ||
|
|
a75441aa8a | ||
|
|
32971b45c3 | ||
|
|
4bc7ce09c7 | ||
|
|
b5946b34b8 | ||
|
|
442e4a0972 | ||
|
|
d1c79acf87 | ||
|
|
df70f7f63c | ||
|
|
6707143aaf | ||
|
|
742a86d079 | ||
|
|
45f856b8d1 | ||
|
|
044c7bf8a0 | ||
|
|
e7b19c472b | ||
|
|
1ace5e641d | ||
|
|
83976646db | ||
|
|
8e1d75abd6 | ||
|
|
5c341d4745 | ||
|
|
9f245a62f2 | ||
|
|
ecc2547eb1 | ||
|
|
e2a48fcff9 | ||
|
|
6b09f54793 | ||
|
|
7b5ad3ab5e | ||
|
|
48790b4756 | ||
|
|
727a3e9452 | ||
|
|
f8f1b54aaf | ||
|
|
cb8f405e76 | ||
|
|
51704829c6 | ||
|
|
a1cc1d844d | ||
|
|
633deb85ca | ||
|
|
ee1c375fd9 | ||
|
|
2713f5282e | ||
|
|
f70cca0d00 | ||
|
|
3adefe4202 | ||
|
|
966c873013 | ||
|
|
b8c61c37dc | ||
|
|
22582cd32f | ||
|
|
a46573cab3 | ||
|
|
64e380cc40 | ||
|
|
2f68c7c386 | ||
|
|
e3001a70ed | ||
|
|
ea6543b3f9 | ||
|
|
1793dd629f | ||
|
|
edf8c6ea32 | ||
|
|
60cdffcf3c | ||
|
|
717ed04b38 | ||
|
|
80a6b21ff9 | ||
|
|
e48c3f57dd | ||
|
|
39f427555f | ||
|
|
280e455198 | ||
|
|
082991694b | ||
|
|
4ec9d818bf | ||
|
|
258d8921ad | ||
|
|
2b8d416625 | ||
|
|
b4e9613efe | ||
|
|
25605208e4 | ||
|
|
29036bed18 | ||
|
|
6d78f14cc7 | ||
|
|
4730753cb3 | ||
|
|
4fa2ef41aa | ||
|
|
732376328c | ||
|
|
110fe4b0aa | ||
|
|
45ba8d447a | ||
|
|
108417ca9d | ||
|
|
bd1ff9731d | ||
|
|
235af71343 | ||
|
|
059306c93b | ||
|
|
d938e24cf5 | ||
|
|
ab1881d02e | ||
|
|
90a4922b79 | ||
|
|
2022a0db82 | ||
|
|
4f8bb40d3d | ||
|
|
8ea296c99f | ||
|
|
ccefaf003d | ||
|
|
61236e0ace | ||
|
|
6a89ab77c8 | ||
|
|
9f1c279698 | ||
|
|
89cbcfee8c | ||
|
|
2da4bbedbf | ||
|
|
d73272b8e0 |
20
.github/workflows/docker-image-compat.yml
vendored
Normal file
20
.github/workflows/docker-image-compat.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Docker Image CI (compat)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- compat
|
||||
|
||||
jobs:
|
||||
build-compat:
|
||||
uses: ./.github/workflows/docker-image.yml
|
||||
with:
|
||||
image_name: ${{ github.repository_owner }}/godoxy
|
||||
tag: latest-compat
|
||||
target: main
|
||||
build-compat-agent:
|
||||
uses: ./.github/workflows/docker-image.yml
|
||||
with:
|
||||
image_name: ${{ github.repository_owner }}/godoxy-agent
|
||||
tag: latest-compat
|
||||
target: agent
|
||||
1
.github/workflows/docker-image-nightly.yml
vendored
1
.github/workflows/docker-image-nightly.yml
vendored
@@ -8,6 +8,7 @@ on:
|
||||
- "**" # matches every branch
|
||||
- "!dependabot/*"
|
||||
- "!main" # excludes main
|
||||
- "!compat" # excludes compat branch
|
||||
|
||||
jobs:
|
||||
build-nightly:
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
yusing@6uo.me.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -1,5 +1,5 @@
|
||||
# Stage 1: deps
|
||||
FROM golang:1.25.7-alpine AS deps
|
||||
FROM golang:1.25.6-alpine AS deps
|
||||
HEALTHCHECK NONE
|
||||
|
||||
# package version does not matter
|
||||
|
||||
@@ -161,7 +161,7 @@ Tips:
|
||||
srv.Serve(l)
|
||||
}
|
||||
|
||||
systeminfo.Poller.Start(t)
|
||||
systeminfo.Poller.Start()
|
||||
|
||||
task.WaitExit(3)
|
||||
}
|
||||
|
||||
20
agent/go.mod
20
agent/go.mod
@@ -1,11 +1,6 @@
|
||||
module github.com/yusing/godoxy/agent
|
||||
|
||||
go 1.25.7
|
||||
|
||||
exclude (
|
||||
github.com/moby/moby/api v1.53.0 // allow older daemon versions
|
||||
github.com/moby/moby/client v0.2.2 // allow older daemon versions
|
||||
)
|
||||
go 1.25.6
|
||||
|
||||
replace (
|
||||
github.com/shirou/gopsutil/v4 => ../internal/gopsutil
|
||||
@@ -19,8 +14,9 @@ replace (
|
||||
|
||||
exclude github.com/containerd/nerdctl/mod/tigron v0.0.0
|
||||
|
||||
exclude github.com/yusing/godoxy/internal/utils v0.0.0-20250927032450-e2aeef3a863f
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.15.0
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/pion/dtls/v3 v3.0.10
|
||||
@@ -36,6 +32,7 @@ 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 v1.15.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
@@ -44,6 +41,7 @@ require (
|
||||
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.2.0+incompatible // indirect
|
||||
github.com/docker/docker v28.5.2+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
|
||||
@@ -67,8 +65,9 @@ require (
|
||||
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/moby/moby/api v1.53.0 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/term v0.5.2 // 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
|
||||
@@ -76,6 +75,7 @@ require (
|
||||
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/pkg/errors v0.9.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.4.0 // indirect
|
||||
@@ -97,6 +97,7 @@ require (
|
||||
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/exporters/otlp/otlptrace v1.38.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
|
||||
@@ -104,6 +105,7 @@ require (
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
37
agent/go.sum
37
agent/go.sum
@@ -1,3 +1,5 @@
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
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=
|
||||
@@ -24,6 +26,8 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG
|
||||
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/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
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=
|
||||
@@ -39,6 +43,8 @@ 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.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM=
|
||||
github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
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=
|
||||
@@ -95,6 +101,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
|
||||
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/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||
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=
|
||||
@@ -128,15 +136,21 @@ github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
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/moby/moby/api v1.53.0 h1:PihqG1ncw4W+8mZs69jlwGXdaYBeb5brF6BL7mPIS/w=
|
||||
github.com/moby/moby/api v1.53.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
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/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
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=
|
||||
@@ -155,6 +169,7 @@ github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k
|
||||
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
|
||||
github.com/pires/go-proxyproto v0.9.2 h1:H1UdHn695zUVVmB0lQ354lOWHOy6TZSpzBl3tgN0s1U=
|
||||
github.com/pires/go-proxyproto v0.9.2/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
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=
|
||||
@@ -220,6 +235,10 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGN
|
||||
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/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
|
||||
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=
|
||||
@@ -228,6 +247,8 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W
|
||||
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.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
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=
|
||||
@@ -258,6 +279,12 @@ 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.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/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=
|
||||
@@ -268,5 +295,3 @@ 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=
|
||||
|
||||
@@ -102,7 +102,6 @@ func TestUDPServer_RejectInvalidClient(t *testing.T) {
|
||||
|
||||
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")
|
||||
|
||||
@@ -2,11 +2,11 @@ package agentproxy
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
route "github.com/yusing/godoxy/internal/route/types"
|
||||
)
|
||||
|
||||
@@ -53,7 +53,7 @@ func proxyConfigFromHeaders(h http.Header) (cfg Config, err error) {
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
err = sonic.Unmarshal(cfgJSON, &cfg)
|
||||
err = json.Unmarshal(cfgJSON, &cfg)
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (cfg *Config) SetAgentProxyConfigHeadersLegacy(h http.Header) {
|
||||
func (cfg *Config) SetAgentProxyConfigHeaders(h http.Header) {
|
||||
h.Set(HeaderXProxyHost, cfg.Host)
|
||||
h.Set(HeaderXProxyScheme, string(cfg.Scheme))
|
||||
cfgJSON, _ := sonic.Marshal(cfg.HTTPConfig)
|
||||
cfgJSON, _ := json.Marshal(cfg.HTTPConfig)
|
||||
cfgBase64 := base64.StdEncoding.EncodeToString(cfgJSON)
|
||||
h.Set(HeaderXProxyConfig, cfgBase64)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -8,7 +9,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
healthcheck "github.com/yusing/godoxy/internal/health/check"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
)
|
||||
@@ -73,7 +73,7 @@ func CheckHealth(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
sonic.ConfigDefault.NewEncoder(w).Encode(result)
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
func parseMsOrDefault(msStr string) time.Duration {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
@@ -51,7 +51,7 @@ func NewAgentHandler() http.Handler {
|
||||
Runtime: env.Runtime,
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
sonic.ConfigDefault.NewEncoder(w).Encode(agentInfo)
|
||||
json.NewEncoder(w).Encode(agentInfo)
|
||||
})
|
||||
mux.HandleEndpoint("GET", agent.EndpointHealth, CheckHealth)
|
||||
mux.HandleEndpoint("GET", agent.EndpointSystemInfo, metricsHandler.ServeHTTP)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.25.7-alpine AS builder
|
||||
FROM golang:1.25.6-alpine AS builder
|
||||
|
||||
HEALTHCHECK NONE
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module github.com/yusing/godoxy/cmd/bench_server
|
||||
|
||||
go 1.25.7
|
||||
go 1.25.6
|
||||
|
||||
@@ -181,6 +181,7 @@ func newApiHandler(debugMux *debugMux) *gin.Engine {
|
||||
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")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.25.7-alpine AS builder
|
||||
FROM golang:1.25.6-alpine AS builder
|
||||
|
||||
HEALTHCHECK NONE
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/yusing/godoxy/cmd/h2c_test_server
|
||||
|
||||
go 1.25.7
|
||||
go 1.25.6
|
||||
|
||||
require golang.org/x/net v0.49.0
|
||||
|
||||
|
||||
29
cmd/main.go
29
cmd/main.go
@@ -1,12 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
@@ -14,9 +14,12 @@ import (
|
||||
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"
|
||||
)
|
||||
@@ -48,6 +51,7 @@ func main() {
|
||||
parallel(
|
||||
dnsproviders.InitProviders,
|
||||
iconlist.InitCache,
|
||||
systeminfo.Poller.Start,
|
||||
middleware.LoadComposeFiles,
|
||||
)
|
||||
|
||||
@@ -62,20 +66,35 @@ func main() {
|
||||
|
||||
err := config.Load()
|
||||
if err != nil {
|
||||
var criticalErr config.CriticalError
|
||||
if errors.As(err, &criticalErr) {
|
||||
gperr.LogFatal("critical error in config", criticalErr)
|
||||
}
|
||||
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(true),
|
||||
})
|
||||
|
||||
// Local API Handler is used for unauthenticated access.
|
||||
if common.LocalAPIHTTPAddr != "" {
|
||||
server.StartServer(task.RootTask("local_api_server", false), server.Options{
|
||||
Name: "local_api",
|
||||
HTTPAddr: common.LocalAPIHTTPAddr,
|
||||
Handler: api.NewHandler(false),
|
||||
})
|
||||
}
|
||||
|
||||
listenDebugServer()
|
||||
|
||||
uptime.Poller.Start()
|
||||
config.WatchChanges()
|
||||
|
||||
close(done)
|
||||
|
||||
@@ -31,8 +31,8 @@ services:
|
||||
user: ${GODOXY_UID:-1000}:${GODOXY_GID:-1000}
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:rw
|
||||
- /app/node_modules/.cache:rw
|
||||
- /app/.next/cache # next image caching
|
||||
|
||||
# for lite variant, do not change uid/gid
|
||||
# - /var/cache/nginx:uid=101,gid=101
|
||||
# - /run:uid=101,gid=101
|
||||
|
||||
33
go.mod
33
go.mod
@@ -1,11 +1,6 @@
|
||||
module github.com/yusing/godoxy
|
||||
|
||||
go 1.25.7
|
||||
|
||||
exclude (
|
||||
github.com/moby/moby/api v1.53.0 // allow older daemon versions
|
||||
github.com/moby/moby/client v0.2.2 // allow older daemon versions
|
||||
)
|
||||
go 1.25.6
|
||||
|
||||
replace (
|
||||
github.com/coreos/go-oidc/v3 => ./internal/go-oidc
|
||||
@@ -19,9 +14,12 @@ replace (
|
||||
github.com/yusing/goutils/server => ./goutils/server
|
||||
)
|
||||
|
||||
exclude github.com/luthermonson/go-proxmox v0.3.0
|
||||
|
||||
require (
|
||||
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/docker/docker v28.5.2+incompatible // docker daemon
|
||||
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
|
||||
@@ -43,14 +41,12 @@ require (
|
||||
|
||||
require (
|
||||
github.com/bytedance/gopkg v0.1.3 // xxhash64 for fast hash
|
||||
github.com/bytedance/sonic v1.15.0 // fast json parsing
|
||||
github.com/bytedance/sonic v1.15.0 // indirect; fast json parsing
|
||||
github.com/docker/cli v29.2.0+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.1 // 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/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/luthermonson/go-proxmox v0.3.2
|
||||
github.com/oschwald/maxminddb-golang v1.13.1
|
||||
github.com/quic-go/quic-go v0.59.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
|
||||
@@ -121,11 +117,11 @@ require (
|
||||
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/pkg/errors v0.9.1 // 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 // indirect
|
||||
github.com/sony/gobreaker v1.0.0 // indirect
|
||||
@@ -150,7 +146,10 @@ require (
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
require github.com/moby/moby/api v1.53.0
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
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
|
||||
@@ -160,6 +159,7 @@ require (
|
||||
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/containerd/log v0.1.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
|
||||
@@ -169,11 +169,14 @@ require (
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/google/go-querystring v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
||||
github.com/klauspost/compress v1.18.3 // 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/moby/sys/atomicwriter v0.1.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/nrdcg/goinwx v0.12.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.107.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.107.0 // indirect
|
||||
@@ -183,14 +186,16 @@ require (
|
||||
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/samber/slog-zerolog/v2 v2.9.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
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
golang.org/x/arch v0.23.0 // indirect
|
||||
)
|
||||
|
||||
40
go.sum
40
go.sum
@@ -23,6 +23,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourceg
|
||||
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/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
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=
|
||||
@@ -65,6 +67,8 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG
|
||||
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/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
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=
|
||||
@@ -78,6 +82,8 @@ 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.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM=
|
||||
github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
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=
|
||||
@@ -159,6 +165,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
|
||||
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/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||
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=
|
||||
@@ -214,15 +222,21 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
|
||||
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/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/moby/moby/api v1.53.0 h1:PihqG1ncw4W+8mZs69jlwGXdaYBeb5brF6BL7mPIS/w=
|
||||
github.com/moby/moby/api v1.53.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
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/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
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=
|
||||
@@ -255,6 +269,7 @@ github.com/pires/go-proxyproto v0.9.2 h1:H1UdHn695zUVVmB0lQ354lOWHOy6TZSpzBl3tgN
|
||||
github.com/pires/go-proxyproto v0.9.2/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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
|
||||
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||
@@ -312,8 +327,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
||||
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/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/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=
|
||||
@@ -341,6 +356,10 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGN
|
||||
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/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
|
||||
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=
|
||||
@@ -349,6 +368,8 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W
|
||||
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.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
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=
|
||||
@@ -400,6 +421,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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-20210616094352-59db8d763f22/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -451,8 +473,8 @@ google.golang.org/api v0.263.0 h1:UFs7qn8gInIdtk1ZA6eXRXp5JDAnS4x9VRsRVCeKdbk=
|
||||
google.golang.org/api v0.263.0/go.mod h1:fAU1xtNNisHgOF5JooAs8rRaTkl2rT3uaoNGo9NS3R8=
|
||||
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/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
@@ -472,5 +494,3 @@ 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=
|
||||
|
||||
2
goutils
2
goutils
Submodule goutils updated: a270ef85af...e5fba76994
@@ -74,6 +74,8 @@ type ipLog struct {
|
||||
allowed bool
|
||||
}
|
||||
|
||||
type ContextKey struct{}
|
||||
|
||||
const cacheTTL = 1 * time.Minute
|
||||
|
||||
func (c *checkCache) Expired() bool {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package acl
|
||||
|
||||
import "net"
|
||||
|
||||
type ACL interface {
|
||||
IPAllowed(ip net.IP) bool
|
||||
WrapTCP(l net.Listener) net.Listener
|
||||
WrapUDP(l net.PacketConn) net.PacketConn
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package acl
|
||||
|
||||
import "context"
|
||||
|
||||
type ContextKey struct{}
|
||||
|
||||
func SetCtx(ctx interface{ SetValue(any, any) }, acl ACL) {
|
||||
ctx.SetValue(ContextKey{}, acl)
|
||||
}
|
||||
|
||||
func FromCtx(ctx context.Context) ACL {
|
||||
if acl, ok := ctx.Value(ContextKey{}).(ACL); ok {
|
||||
return acl
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2,12 +2,12 @@ package agentpool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/valyala/fasthttp"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
@@ -63,7 +63,7 @@ func (cfg *Agent) DoHealthCheck(timeout time.Duration, query string) (ret Health
|
||||
ret.Detail = fmt.Sprintf("HTTP %d %s", status, resp.Body())
|
||||
return ret, nil
|
||||
} else {
|
||||
err = sonic.Unmarshal(resp.Body(), &ret)
|
||||
err = json.Unmarshal(resp.Body(), &ret)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
@@ -2,10 +2,8 @@ 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"
|
||||
@@ -48,8 +46,6 @@ func NewHandler(requireAuth bool) *gin.Engine {
|
||||
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() && requireAuth {
|
||||
@@ -76,6 +72,7 @@ func NewHandler(requireAuth bool) *gin.Engine {
|
||||
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")
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
@@ -43,22 +42,22 @@ func GetContainer(c *gin.Context) {
|
||||
|
||||
defer dockerClient.Close()
|
||||
|
||||
cont, err := dockerClient.ContainerInspect(c.Request.Context(), id, client.ContainerInspectOptions{})
|
||||
cont, err := dockerClient.ContainerInspect(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to inspect container"))
|
||||
return
|
||||
}
|
||||
|
||||
var state ContainerState
|
||||
if cont.Container.State != nil {
|
||||
state = cont.Container.State.Status
|
||||
if cont.State != nil {
|
||||
state = cont.State.Status
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, &Container{
|
||||
Server: dockerCfg.URL,
|
||||
Name: cont.Container.Name,
|
||||
ID: cont.Container.ID,
|
||||
Image: cont.Container.Image,
|
||||
Name: cont.Name,
|
||||
ID: cont.ID,
|
||||
Image: cont.Image,
|
||||
State: state,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,9 +4,8 @@ import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
|
||||
_ "github.com/yusing/goutils/apitypes"
|
||||
@@ -40,12 +39,12 @@ func GetContainers(ctx context.Context, dockerClients DockerClients) ([]Containe
|
||||
errs := gperr.NewBuilder("failed to get containers")
|
||||
containers := make([]Container, 0)
|
||||
for server, dockerClient := range dockerClients {
|
||||
conts, err := dockerClient.ContainerList(ctx, client.ContainerListOptions{All: true})
|
||||
conts, err := dockerClient.ContainerList(ctx, container.ListOptions{All: true})
|
||||
if err != nil {
|
||||
errs.Add(err)
|
||||
continue
|
||||
}
|
||||
for _, cont := range conts.Items {
|
||||
for _, cont := range conts {
|
||||
containers = append(containers, Container{
|
||||
Server: server,
|
||||
Name: cont.Names[0],
|
||||
|
||||
@@ -4,9 +4,8 @@ import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
dockerSystem "github.com/docker/docker/api/types/system"
|
||||
"github.com/gin-gonic/gin"
|
||||
dockerSystem "github.com/moby/moby/api/types/system"
|
||||
"github.com/moby/moby/client"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
|
||||
@@ -65,13 +64,13 @@ func GetDockerInfo(ctx context.Context, dockerClients DockerClients) ([]dockerIn
|
||||
|
||||
i := 0
|
||||
for name, dockerClient := range dockerClients {
|
||||
info, err := dockerClient.Info(ctx, client.InfoOptions{})
|
||||
info, err := dockerClient.Info(ctx)
|
||||
if err != nil {
|
||||
errs.Add(err)
|
||||
continue
|
||||
}
|
||||
info.Info.Name = name
|
||||
dockerInfos[i] = toDockerInfo(info.Info)
|
||||
info.Name = name
|
||||
dockerInfos[i] = toDockerInfo(info)
|
||||
i++
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/api/pkg/stdcopy"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
@@ -73,7 +73,7 @@ func Logs(c *gin.Context) {
|
||||
}
|
||||
defer dockerClient.Close()
|
||||
|
||||
opts := client.ContainerLogsOptions{
|
||||
opts := container.LogsOptions{
|
||||
ShowStdout: queryParams.Stdout,
|
||||
ShowStderr: queryParams.Stderr,
|
||||
Since: queryParams.Since,
|
||||
|
||||
@@ -4,23 +4,17 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
type RestartRequest struct {
|
||||
ID string `json:"id" binding:"required"`
|
||||
client.ContainerRestartOptions
|
||||
}
|
||||
|
||||
// @x-id "restart"
|
||||
// @BasePath /api/v1
|
||||
// @Summary Restart container
|
||||
// @Description Restart container by container id
|
||||
// @Tags docker
|
||||
// @Produce json
|
||||
// @Param request body RestartRequest true "Request"
|
||||
// @Param request body StopRequest true "Request"
|
||||
// @Success 200 {object} apitypes.SuccessResponse
|
||||
// @Failure 400 {object} apitypes.ErrorResponse "Invalid request"
|
||||
// @Failure 403 {object} apitypes.ErrorResponse
|
||||
@@ -28,7 +22,7 @@ type RestartRequest struct {
|
||||
// @Failure 500 {object} apitypes.ErrorResponse
|
||||
// @Router /docker/restart [post]
|
||||
func Restart(c *gin.Context) {
|
||||
var req RestartRequest
|
||||
var req StopRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
|
||||
return
|
||||
@@ -48,7 +42,7 @@ func Restart(c *gin.Context) {
|
||||
|
||||
defer client.Close()
|
||||
|
||||
_, err = client.ContainerRestart(c.Request.Context(), req.ID, req.ContainerRestartOptions)
|
||||
err = client.ContainerRestart(c.Request.Context(), req.ID, req.StopOptions)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to restart container"))
|
||||
return
|
||||
|
||||
@@ -3,15 +3,15 @@ package dockerapi
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
type StartRequest struct {
|
||||
ID string `json:"id" binding:"required"`
|
||||
client.ContainerStartOptions
|
||||
container.StartOptions
|
||||
}
|
||||
|
||||
// @x-id "start"
|
||||
@@ -48,7 +48,7 @@ func Start(c *gin.Context) {
|
||||
|
||||
defer client.Close()
|
||||
|
||||
_, err = client.ContainerStart(c.Request.Context(), req.ID, req.ContainerStartOptions)
|
||||
err = client.ContainerStart(c.Request.Context(), req.ID, req.StartOptions)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to start container"))
|
||||
return
|
||||
|
||||
@@ -8,9 +8,8 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
"github.com/yusing/goutils/http/httpheaders"
|
||||
@@ -44,7 +43,7 @@ func Stats(c *gin.Context) {
|
||||
dockerCfg, ok := docker.GetDockerCfgByContainerID(id)
|
||||
if !ok {
|
||||
var route types.Route
|
||||
route, ok = entrypoint.FromCtx(c.Request.Context()).GetRoute(id)
|
||||
route, ok = routes.GetIncludeExcluded(id)
|
||||
if ok {
|
||||
cont := route.ContainerInfo()
|
||||
if cont == nil {
|
||||
@@ -68,7 +67,7 @@ func Stats(c *gin.Context) {
|
||||
defer dockerClient.Close()
|
||||
|
||||
if httpheaders.IsWebsocket(c.Request.Header) {
|
||||
stats, err := dockerClient.ContainerStats(c.Request.Context(), id, client.ContainerStatsOptions{Stream: true})
|
||||
stats, err := dockerClient.ContainerStats(c.Request.Context(), id, true)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to get container stats"))
|
||||
return
|
||||
@@ -102,7 +101,7 @@ func Stats(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
stats, err := dockerClient.ContainerStats(c.Request.Context(), id, client.ContainerStatsOptions{Stream: false})
|
||||
stats, err := dockerClient.ContainerStats(c.Request.Context(), id, false)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to get container stats"))
|
||||
return
|
||||
|
||||
@@ -3,15 +3,15 @@ package dockerapi
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
type StopRequest struct {
|
||||
ID string `json:"id" binding:"required"`
|
||||
client.ContainerStopOptions
|
||||
container.StopOptions
|
||||
}
|
||||
|
||||
// @x-id "stop"
|
||||
@@ -48,7 +48,7 @@ func Stop(c *gin.Context) {
|
||||
|
||||
defer client.Close()
|
||||
|
||||
_, err = client.ContainerStop(c.Request.Context(), req.ID, req.ContainerStopOptions)
|
||||
err = client.ContainerStop(c.Request.Context(), req.ID, req.StopOptions)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to stop container"))
|
||||
return
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/homepage/icons"
|
||||
iconfetch "github.com/yusing/godoxy/internal/homepage/icons/fetch"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
|
||||
_ "unsafe"
|
||||
@@ -73,11 +73,7 @@ func FavIcon(c *gin.Context) {
|
||||
//go:linkname GetFavIconFromAlias v1.GetFavIconFromAlias
|
||||
func GetFavIconFromAlias(ctx context.Context, alias string, variant icons.Variant) (iconfetch.Result, error) {
|
||||
// try with route.Icon
|
||||
ep := entrypoint.FromCtx(ctx)
|
||||
if ep == nil { // impossible, but just in case
|
||||
return iconfetch.FetchResultWithErrorf(http.StatusInternalServerError, "entrypoint not initialized")
|
||||
}
|
||||
r, ok := ep.HTTPRoutes().Get(alias)
|
||||
r, ok := routes.HTTP.Get(alias)
|
||||
if !ok {
|
||||
return iconfetch.FetchResultWithErrorf(http.StatusNotFound, "route not found")
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
"github.com/yusing/goutils/http/httpheaders"
|
||||
"github.com/yusing/goutils/http/websocket"
|
||||
|
||||
_ "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
// @x-id "health"
|
||||
@@ -23,16 +24,11 @@ import (
|
||||
// @Failure 500 {object} apitypes.ErrorResponse
|
||||
// @Router /health [get]
|
||||
func Health(c *gin.Context) {
|
||||
ep := entrypoint.FromCtx(c.Request.Context())
|
||||
if ep == nil { // impossible, but just in case
|
||||
c.JSON(http.StatusInternalServerError, apitypes.Error("entrypoint not initialized"))
|
||||
return
|
||||
}
|
||||
if httpheaders.IsWebsocket(c.Request.Header) {
|
||||
websocket.PeriodicWrite(c, 1*time.Second, func() (any, error) {
|
||||
return ep.GetHealthInfoSimple(), nil
|
||||
return routes.GetHealthInfoSimple(), nil
|
||||
})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, ep.GetHealthInfoSimple())
|
||||
c.JSON(http.StatusOK, routes.GetHealthInfoSimple())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/homepage"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
|
||||
_ "github.com/yusing/goutils/apitypes"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
// @x-id "categories"
|
||||
@@ -22,20 +21,15 @@ import (
|
||||
// @Failure 403 {object} apitypes.ErrorResponse
|
||||
// @Router /homepage/categories [get]
|
||||
func Categories(c *gin.Context) {
|
||||
ep := entrypoint.FromCtx(c.Request.Context())
|
||||
if ep == nil { // impossible, but just in case
|
||||
c.JSON(http.StatusInternalServerError, apitypes.Error("entrypoint not initialized"))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, HomepageCategories(ep))
|
||||
c.JSON(http.StatusOK, HomepageCategories())
|
||||
}
|
||||
|
||||
func HomepageCategories(ep entrypoint.Entrypoint) []string {
|
||||
func HomepageCategories() []string {
|
||||
check := make(map[string]struct{})
|
||||
categories := make([]string, 0)
|
||||
categories = append(categories, homepage.CategoryAll)
|
||||
categories = append(categories, homepage.CategoryFavorites)
|
||||
for _, r := range ep.HTTPRoutes().Iter {
|
||||
for _, r := range routes.HTTP.Iter {
|
||||
item := r.HomepageItem()
|
||||
if item.Category == "" {
|
||||
continue
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/homepage"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
"github.com/yusing/goutils/http/httpheaders"
|
||||
"github.com/yusing/goutils/http/websocket"
|
||||
@@ -53,30 +53,29 @@ func Items(c *gin.Context) {
|
||||
hostname = host
|
||||
}
|
||||
|
||||
ep := entrypoint.FromCtx(c.Request.Context())
|
||||
if httpheaders.IsWebsocket(c.Request.Header) {
|
||||
websocket.PeriodicWrite(c, 2*time.Second, func() (any, error) {
|
||||
return HomepageItems(ep, proto, hostname, &request), nil
|
||||
return HomepageItems(proto, hostname, &request), nil
|
||||
})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, HomepageItems(ep, proto, hostname, &request))
|
||||
c.JSON(http.StatusOK, HomepageItems(proto, hostname, &request))
|
||||
}
|
||||
}
|
||||
|
||||
func HomepageItems(ep entrypoint.Entrypoint, proto, hostname string, request *HomepageItemsRequest) homepage.Homepage {
|
||||
func HomepageItems(proto, hostname string, request *HomepageItemsRequest) homepage.Homepage {
|
||||
switch proto {
|
||||
case "http", "https":
|
||||
default:
|
||||
proto = "http"
|
||||
}
|
||||
|
||||
hp := homepage.NewHomepageMap(ep.HTTPRoutes().Size())
|
||||
hp := homepage.NewHomepageMap(routes.HTTP.Size())
|
||||
|
||||
if strings.Count(hostname, ".") > 1 {
|
||||
_, hostname, _ = strings.Cut(hostname, ".") // remove the subdomain
|
||||
}
|
||||
|
||||
for _, r := range ep.HTTPRoutes().Iter {
|
||||
for _, r := range routes.HTTP.Iter {
|
||||
if request.Provider != "" && r.ProviderName() != request.Provider {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
@@ -237,7 +236,7 @@ func marshalSystemInfo(ws *websocket.Manager, agentName string, systemInfo any)
|
||||
defer bufFromPool.release(bufFromPool.RawMessage)
|
||||
}
|
||||
|
||||
err := sonic.ConfigDefault.NewEncoder(buf).Encode(map[string]any{
|
||||
err := json.NewEncoder(buf).Encode(map[string]any{
|
||||
agentName: systemInfo,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
28
internal/api/v1/reload.go
Normal file
28
internal/api/v1/reload.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yusing/godoxy/internal/config"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
// @x-id "reload"
|
||||
// @BasePath /api/v1
|
||||
// @Summary Reload config
|
||||
// @Description Reload config
|
||||
// @Tags v1
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} apitypes.SuccessResponse
|
||||
// @Failure 403 {object} apitypes.ErrorResponse
|
||||
// @Failure 500 {object} apitypes.ErrorResponse
|
||||
// @Router /reload [post]
|
||||
func Reload(c *gin.Context) {
|
||||
if err := config.Reload(); err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to reload config"))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, apitypes.Success("config reloaded"))
|
||||
}
|
||||
@@ -4,11 +4,10 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/route"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
|
||||
_ "github.com/yusing/goutils/apitypes"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
type RoutesByProvider map[string][]route.Route
|
||||
@@ -25,10 +24,5 @@ type RoutesByProvider map[string][]route.Route
|
||||
// @Failure 500 {object} apitypes.ErrorResponse
|
||||
// @Router /route/by_provider [get]
|
||||
func ByProvider(c *gin.Context) {
|
||||
ep := entrypoint.FromCtx(c.Request.Context())
|
||||
if ep == nil { // impossible, but just in case
|
||||
c.JSON(http.StatusInternalServerError, apitypes.Error("entrypoint not initialized"))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, ep.RoutesByProvider())
|
||||
c.JSON(http.StatusOK, routes.ByProvider())
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
@@ -32,13 +32,7 @@ func Route(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ep := entrypoint.FromCtx(c.Request.Context())
|
||||
if ep == nil { // impossible, but just in case
|
||||
c.JSON(http.StatusInternalServerError, apitypes.Error("entrypoint not initialized"))
|
||||
return
|
||||
}
|
||||
|
||||
route, ok := ep.GetRoute(request.Which)
|
||||
route, ok := routes.GetIncludeExcluded(request.Which)
|
||||
if ok {
|
||||
c.JSON(http.StatusOK, route)
|
||||
return
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/route"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
"github.com/yusing/goutils/http/httpheaders"
|
||||
"github.com/yusing/goutils/http/websocket"
|
||||
@@ -32,16 +32,14 @@ func Routes(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ep := entrypoint.FromCtx(c.Request.Context())
|
||||
|
||||
provider := c.Query("provider")
|
||||
if provider == "" {
|
||||
c.JSON(http.StatusOK, slices.Collect(ep.IterRoutes))
|
||||
c.JSON(http.StatusOK, slices.Collect(routes.IterAll))
|
||||
return
|
||||
}
|
||||
|
||||
rts := make([]types.Route, 0, ep.NumRoutes())
|
||||
for r := range ep.IterRoutes {
|
||||
rts := make([]types.Route, 0, routes.NumAllRoutes())
|
||||
for r := range routes.IterAll {
|
||||
if r.ProviderName() == provider {
|
||||
rts = append(rts, r)
|
||||
}
|
||||
@@ -50,19 +48,17 @@ func Routes(c *gin.Context) {
|
||||
}
|
||||
|
||||
func RoutesWS(c *gin.Context) {
|
||||
ep := entrypoint.FromCtx(c.Request.Context())
|
||||
|
||||
provider := c.Query("provider")
|
||||
if provider == "" {
|
||||
websocket.PeriodicWrite(c, 3*time.Second, func() (any, error) {
|
||||
return slices.Collect(ep.IterRoutes), nil
|
||||
return slices.Collect(routes.IterAll), nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
websocket.PeriodicWrite(c, 3*time.Second, func() (any, error) {
|
||||
rts := make([]types.Route, 0, ep.NumRoutes())
|
||||
for r := range ep.IterRoutes {
|
||||
rts := make([]types.Route, 0, routes.NumAllRoutes())
|
||||
for r := range routes.IterAll {
|
||||
if r.ProviderName() == provider {
|
||||
rts = append(rts, r)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
@@ -109,7 +109,7 @@ type UserPassAuthCallbackRequest struct {
|
||||
|
||||
func (auth *UserPassAuth) PostAuthCallbackHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var creds UserPassAuthCallbackRequest
|
||||
err := sonic.ConfigDefault.NewDecoder(r.Body).Decode(&creds)
|
||||
err := json.NewDecoder(r.Body).Decode(&creds)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid request", http.StatusBadRequest)
|
||||
return
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package autocert
|
||||
|
||||
import "context"
|
||||
|
||||
type ContextKey struct{}
|
||||
|
||||
func SetCtx(ctx interface{ SetValue(any, any) }, p Provider) {
|
||||
ctx.SetValue(ContextKey{}, p)
|
||||
}
|
||||
|
||||
func FromCtx(ctx context.Context) Provider {
|
||||
if provider, ok := ctx.Value(ContextKey{}).(Provider); ok {
|
||||
return provider
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type Provider interface {
|
||||
Setup() error
|
||||
GetCert(*tls.ClientHelloInfo) (*tls.Certificate, error)
|
||||
ScheduleRenewalAll(task.Parent)
|
||||
ObtainCertAll() error
|
||||
|
||||
@@ -10,9 +10,11 @@ import (
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
config "github.com/yusing/godoxy/internal/config/types"
|
||||
"github.com/yusing/godoxy/internal/notif"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
"github.com/yusing/godoxy/internal/watcher"
|
||||
"github.com/yusing/godoxy/internal/watcher/events"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/server"
|
||||
"github.com/yusing/goutils/strings/ansi"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
@@ -58,30 +60,20 @@ func Load() error {
|
||||
|
||||
cfgWatcher = watcher.NewConfigFileWatcher(common.ConfigFileName)
|
||||
|
||||
initErr := state.InitFromFile(common.ConfigPath)
|
||||
if initErr != nil {
|
||||
// if error is critical, notify and return it without starting providers
|
||||
var criticalErr CriticalError
|
||||
if errors.As(initErr, &criticalErr) {
|
||||
logNotifyError("init", criticalErr.err)
|
||||
return criticalErr.err
|
||||
}
|
||||
}
|
||||
|
||||
// disable pool logging temporary since we already have pretty logging
|
||||
state.Entrypoint().DisablePoolsLog(true)
|
||||
routes.HTTP.DisableLog(true)
|
||||
routes.Stream.DisableLog(true)
|
||||
|
||||
defer func() {
|
||||
state.Entrypoint().DisablePoolsLog(false)
|
||||
routes.HTTP.DisableLog(false)
|
||||
routes.Stream.DisableLog(false)
|
||||
}()
|
||||
|
||||
initErr := state.InitFromFile(common.ConfigPath)
|
||||
err := errors.Join(initErr, state.StartProviders())
|
||||
if err != nil {
|
||||
logNotifyError("init", err)
|
||||
}
|
||||
|
||||
state.StartAPIServers()
|
||||
state.StartMetrics()
|
||||
|
||||
SetState(state)
|
||||
|
||||
// flush temporary log
|
||||
@@ -116,9 +108,7 @@ func Reload() gperr.Error {
|
||||
logNotifyError("start providers", err)
|
||||
return nil // continue
|
||||
}
|
||||
|
||||
newState.StartAPIServers()
|
||||
newState.StartMetrics()
|
||||
StartProxyServers()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -152,3 +142,16 @@ func OnConfigChange(ev []events.Event) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func StartProxyServers() {
|
||||
cfg := GetState()
|
||||
server.StartServer(cfg.Task(), server.Options{
|
||||
Name: "proxy",
|
||||
CertProvider: cfg.AutoCertProvider(),
|
||||
HTTPAddr: common.ProxyHTTPAddr,
|
||||
HTTPSAddr: common.ProxyHTTPSAddr,
|
||||
Handler: cfg.EntrypointHandler(),
|
||||
ACL: cfg.Value().ACL,
|
||||
SupportProxyProtocol: cfg.Value().Entrypoint.SupportProxyProtocol,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"iter"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -17,20 +18,14 @@ import (
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/rs/zerolog"
|
||||
acl "github.com/yusing/godoxy/internal/acl/types"
|
||||
"github.com/yusing/godoxy/internal/acl"
|
||||
"github.com/yusing/godoxy/internal/agentpool"
|
||||
"github.com/yusing/godoxy/internal/api"
|
||||
"github.com/yusing/godoxy/internal/autocert"
|
||||
autocertctx "github.com/yusing/godoxy/internal/autocert/types"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
config "github.com/yusing/godoxy/internal/config/types"
|
||||
"github.com/yusing/godoxy/internal/entrypoint"
|
||||
entrypointctx "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
homepage "github.com/yusing/godoxy/internal/homepage/types"
|
||||
"github.com/yusing/godoxy/internal/logging"
|
||||
"github.com/yusing/godoxy/internal/maxmind"
|
||||
"github.com/yusing/godoxy/internal/metrics/systeminfo"
|
||||
"github.com/yusing/godoxy/internal/metrics/uptime"
|
||||
"github.com/yusing/godoxy/internal/notif"
|
||||
route "github.com/yusing/godoxy/internal/route/provider"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
@@ -45,7 +40,7 @@ type state struct {
|
||||
|
||||
providers *xsync.Map[string, types.RouteProvider]
|
||||
autocertProvider *autocert.Provider
|
||||
entrypoint *entrypoint.Entrypoint
|
||||
entrypoint entrypoint.Entrypoint
|
||||
|
||||
task *task.Task
|
||||
|
||||
@@ -55,25 +50,14 @@ type state struct {
|
||||
tmpLog zerolog.Logger
|
||||
}
|
||||
|
||||
type CriticalError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e CriticalError) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
func (e CriticalError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func NewState() config.State {
|
||||
tmpLogBuf := bytes.NewBuffer(make([]byte, 0, 4096))
|
||||
return &state{
|
||||
providers: xsync.NewMap[string, types.RouteProvider](),
|
||||
task: task.RootTask("config", false),
|
||||
tmpLogBuf: tmpLogBuf,
|
||||
tmpLog: logging.NewLoggerWithFixedLevel(zerolog.InfoLevel, tmpLogBuf),
|
||||
providers: xsync.NewMap[string, types.RouteProvider](),
|
||||
entrypoint: entrypoint.NewEntrypoint(),
|
||||
task: task.RootTask("config", false),
|
||||
tmpLogBuf: tmpLogBuf,
|
||||
tmpLog: logging.NewLoggerWithFixedLevel(zerolog.InfoLevel, tmpLogBuf),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +73,7 @@ func SetState(state config.State) {
|
||||
|
||||
cfg := state.Value()
|
||||
config.ActiveState.Store(state)
|
||||
entrypoint.ActiveConfig.Store(&cfg.Entrypoint)
|
||||
homepage.ActiveConfig.Store(&cfg.Homepage)
|
||||
if autocertProvider := state.AutoCertProvider(); autocertProvider != nil {
|
||||
autocert.ActiveProvider.Store(autocertProvider.(*autocert.Provider))
|
||||
@@ -111,7 +96,7 @@ func (state *state) InitFromFile(filename string) error {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
state.Config = config.DefaultConfig()
|
||||
} else {
|
||||
return CriticalError{err}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return state.Init(data)
|
||||
@@ -120,7 +105,7 @@ func (state *state) InitFromFile(filename string) error {
|
||||
func (state *state) Init(data []byte) error {
|
||||
err := serialization.UnmarshalValidate(data, &state.Config, yaml.Unmarshal)
|
||||
if err != nil {
|
||||
return CriticalError{err}
|
||||
return err
|
||||
}
|
||||
|
||||
g := gperr.NewGroup("config load error")
|
||||
@@ -132,9 +117,7 @@ func (state *state) Init(data []byte) error {
|
||||
// these won't benefit from running on goroutines
|
||||
errs.Add(state.initNotification())
|
||||
errs.Add(state.initACL())
|
||||
if err := state.initEntrypoint(); err != nil {
|
||||
errs.Add(CriticalError{err})
|
||||
}
|
||||
errs.Add(state.initEntrypoint())
|
||||
errs.Add(state.loadRouteProviders())
|
||||
return errs.Error()
|
||||
}
|
||||
@@ -151,8 +134,8 @@ func (state *state) Value() *config.Config {
|
||||
return &state.Config
|
||||
}
|
||||
|
||||
func (state *state) Entrypoint() entrypointctx.Entrypoint {
|
||||
return state.entrypoint
|
||||
func (state *state) EntrypointHandler() http.Handler {
|
||||
return &state.entrypoint
|
||||
}
|
||||
|
||||
func (state *state) ShortLinkMatcher() config.ShortLinkMatcher {
|
||||
@@ -207,29 +190,6 @@ func (state *state) FlushTmpLog() {
|
||||
state.tmpLogBuf.Reset()
|
||||
}
|
||||
|
||||
func (state *state) StartAPIServers() {
|
||||
// API Handler needs to start after auth is initialized.
|
||||
server.StartServer(state.task.Subtask("api_server", false), server.Options{
|
||||
Name: "api",
|
||||
HTTPAddr: common.APIHTTPAddr,
|
||||
Handler: api.NewHandler(true),
|
||||
})
|
||||
|
||||
// Local API Handler is used for unauthenticated access.
|
||||
if common.LocalAPIHTTPAddr != "" {
|
||||
server.StartServer(state.task.Subtask("local_api_server", false), server.Options{
|
||||
Name: "local_api",
|
||||
HTTPAddr: common.LocalAPIHTTPAddr,
|
||||
Handler: api.NewHandler(false),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (state *state) StartMetrics() {
|
||||
systeminfo.Poller.Start(state.task)
|
||||
uptime.Poller.Start(state.task)
|
||||
}
|
||||
|
||||
// initACL initializes the ACL.
|
||||
func (state *state) initACL() error {
|
||||
if !state.ACL.Valid() {
|
||||
@@ -239,7 +199,7 @@ func (state *state) initACL() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
acl.SetCtx(state.task, state.ACL)
|
||||
state.task.SetValue(acl.ContextKey{}, state.ACL)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -247,7 +207,6 @@ func (state *state) initEntrypoint() error {
|
||||
epCfg := state.Config.Entrypoint
|
||||
matchDomains := state.MatchDomains
|
||||
|
||||
state.entrypoint = entrypoint.NewEntrypoint(state.task, &epCfg)
|
||||
state.entrypoint.SetFindRouteDomains(matchDomains)
|
||||
state.entrypoint.SetNotFoundRules(epCfg.Rules.NotFound)
|
||||
|
||||
@@ -261,8 +220,6 @@ func (state *state) initEntrypoint() error {
|
||||
}
|
||||
}
|
||||
|
||||
entrypointctx.SetCtx(state.task, state.entrypoint)
|
||||
|
||||
errs := gperr.NewBuilder("entrypoint error")
|
||||
errs.Add(state.entrypoint.SetMiddlewares(epCfg.Middlewares))
|
||||
errs.Add(state.entrypoint.SetAccessLogger(state.task, epCfg.AccessLog))
|
||||
@@ -339,7 +296,6 @@ func (state *state) initAutoCert() error {
|
||||
p.PrintCertExpiriesAll()
|
||||
|
||||
state.autocertProvider = p
|
||||
autocertctx.SetCtx(state.task, p)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
"github.com/yusing/godoxy/internal/acl"
|
||||
"github.com/yusing/godoxy/internal/autocert"
|
||||
"github.com/yusing/godoxy/internal/entrypoint"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
homepage "github.com/yusing/godoxy/internal/homepage/types"
|
||||
maxmind "github.com/yusing/godoxy/internal/maxmind/types"
|
||||
"github.com/yusing/godoxy/internal/notif"
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"iter"
|
||||
"net/http"
|
||||
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
"github.com/yusing/goutils/server"
|
||||
"github.com/yusing/goutils/synk"
|
||||
@@ -22,7 +21,7 @@ type State interface {
|
||||
|
||||
Value() *Config
|
||||
|
||||
Entrypoint() entrypoint.Entrypoint
|
||||
EntrypointHandler() http.Handler
|
||||
ShortLinkMatcher() ShortLinkMatcher
|
||||
AutoCertProvider() server.CertProvider
|
||||
|
||||
@@ -33,9 +32,6 @@ type State interface {
|
||||
StartProviders() error
|
||||
|
||||
FlushTmpLog()
|
||||
|
||||
StartAPIServers()
|
||||
StartMetrics()
|
||||
}
|
||||
|
||||
type ShortLinkMatcher interface {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/yusing/godoxy/internal/dnsproviders
|
||||
|
||||
go 1.25.7
|
||||
go 1.25.6
|
||||
|
||||
replace github.com/yusing/godoxy => ../..
|
||||
|
||||
@@ -23,12 +23,8 @@ require (
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/boombuler/barcode v1.1.0 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.15.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
@@ -53,7 +49,6 @@ require (
|
||||
github.com/gotify/server/v2 v2.8.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
@@ -78,7 +73,6 @@ require (
|
||||
github.com/sony/gobreaker v1.0.0 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/vultr/govultr/v3 v3.26.1 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
github.com/yusing/gointernals v0.1.16 // indirect
|
||||
@@ -89,7 +83,6 @@ require (
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
go.uber.org/ratelimit v0.3.1 // indirect
|
||||
golang.org/x/arch v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/mod v0.32.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
|
||||
@@ -37,18 +37,10 @@ github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx
|
||||
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/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.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||
github.com/bytedance/sonic/loader v0.5.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/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=
|
||||
@@ -119,8 +111,6 @@ github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2
|
||||
github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
|
||||
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/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=
|
||||
@@ -188,11 +178,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
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/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/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/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
@@ -221,8 +208,6 @@ 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/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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/docker/cli/cli/connhelper"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
"github.com/yusing/godoxy/internal/agentpool"
|
||||
@@ -198,7 +198,9 @@ func NewClient(cfg types.DockerProviderConfig, unique ...bool) (*SharedClient, e
|
||||
opt = append(opt, client.WithTLSClientConfig(cfg.TLS.CAFile, cfg.TLS.CertFile, cfg.TLS.KeyFile))
|
||||
}
|
||||
|
||||
client, err := client.New(opt...)
|
||||
opt = append(opt, client.WithAPIVersionNegotiation())
|
||||
|
||||
client, err := client.NewClientWithOpts(opt...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -11,9 +11,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
"github.com/yusing/godoxy/internal/agentpool"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
@@ -99,18 +98,18 @@ func UpdatePorts(ctx context.Context, c *types.Container) error {
|
||||
}
|
||||
defer dockerClient.Close()
|
||||
|
||||
inspect, err := dockerClient.ContainerInspect(ctx, c.ContainerID, client.ContainerInspectOptions{})
|
||||
inspect, err := dockerClient.ContainerInspect(ctx, c.ContainerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for port := range inspect.Container.Config.ExposedPorts {
|
||||
proto, portStr := nat.SplitProtoPort(port.String())
|
||||
for port := range inspect.Config.ExposedPorts {
|
||||
proto, portStr := nat.SplitProtoPort(string(port))
|
||||
portInt, _ := nat.ParsePort(portStr)
|
||||
if portInt == 0 {
|
||||
continue
|
||||
}
|
||||
c.PublicPortMapping[portInt] = container.PortSummary{
|
||||
c.PublicPortMapping[portInt] = container.Port{
|
||||
PublicPort: uint16(portInt), //nolint:gosec
|
||||
PrivatePort: uint16(portInt), //nolint:gosec
|
||||
Type: proto,
|
||||
@@ -211,8 +210,8 @@ func setPrivateHostname(c *types.Container, helper containerHelper) {
|
||||
}
|
||||
if c.Network != "" {
|
||||
v, hasNetwork := helper.NetworkSettings.Networks[c.Network]
|
||||
if hasNetwork && v.IPAddress.IsValid() {
|
||||
c.PrivateHostname = v.IPAddress.String()
|
||||
if hasNetwork && v.IPAddress != "" {
|
||||
c.PrivateHostname = v.IPAddress
|
||||
return
|
||||
}
|
||||
var hasComposeNetwork bool
|
||||
@@ -220,9 +219,9 @@ func setPrivateHostname(c *types.Container, helper containerHelper) {
|
||||
if proj := DockerComposeProject(c); proj != "" {
|
||||
newNetwork := fmt.Sprintf("%s_%s", proj, c.Network)
|
||||
v, hasComposeNetwork = helper.NetworkSettings.Networks[newNetwork]
|
||||
if hasComposeNetwork && v.IPAddress.IsValid() {
|
||||
if hasComposeNetwork && v.IPAddress != "" {
|
||||
c.Network = newNetwork // update network to the new one
|
||||
c.PrivateHostname = v.IPAddress.String()
|
||||
c.PrivateHostname = v.IPAddress
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -235,9 +234,9 @@ func setPrivateHostname(c *types.Container, helper containerHelper) {
|
||||
}
|
||||
// fallback to first network if no network is specified
|
||||
for k, v := range helper.NetworkSettings.Networks {
|
||||
if v.IPAddress.IsValid() {
|
||||
if v.IPAddress != "" {
|
||||
c.Network = k // update network to the first network
|
||||
c.PrivateHostname = v.IPAddress.String()
|
||||
c.PrivateHostname = v.IPAddress
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package docker
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/yusing/ds/ordered"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
|
||||
@@ -3,7 +3,7 @@ package docker
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
expect "github.com/yusing/goutils/testing"
|
||||
)
|
||||
|
||||
@@ -3,12 +3,12 @@ package docker
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
)
|
||||
|
||||
var listOptions = client.ContainerListOptions{
|
||||
var listOptions = container.ListOptions{
|
||||
// created|restarting|running|removing|paused|exited|dead
|
||||
// Filters: filters.NewArgs(
|
||||
// filters.Arg("status", "created"),
|
||||
@@ -31,7 +31,7 @@ func ListContainers(ctx context.Context, dockerCfg types.DockerProviderConfig) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return containers.Items, nil
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func IsErrConnectionFailed(err error) bool {
|
||||
|
||||
@@ -1,129 +1,47 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/logging/accesslog"
|
||||
"github.com/yusing/godoxy/internal/net/gphttp/middleware"
|
||||
"github.com/yusing/godoxy/internal/net/gphttp/middleware/errorpage"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
"github.com/yusing/godoxy/internal/route/rules"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/pool"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
|
||||
type HTTPRoutes interface {
|
||||
Get(alias string) (types.HTTPRoute, bool)
|
||||
}
|
||||
|
||||
type findRouteFunc func(HTTPRoutes, string) types.HTTPRoute
|
||||
|
||||
type Entrypoint struct {
|
||||
task *task.Task
|
||||
|
||||
cfg *Config
|
||||
|
||||
middleware *middleware.Middleware
|
||||
notFoundHandler http.Handler
|
||||
accessLogger accesslog.AccessLogger
|
||||
findRouteFunc findRouteFunc
|
||||
shortLinkMatcher *ShortLinkMatcher
|
||||
|
||||
streamRoutes *pool.Pool[types.StreamRoute]
|
||||
excludedRoutes *pool.Pool[types.Route]
|
||||
|
||||
// this only affects future http servers creation
|
||||
httpPoolDisableLog atomic.Bool
|
||||
|
||||
servers *xsync.Map[string, *httpServer] // listen addr -> server
|
||||
tcpListeners *xsync.Map[string, net.Listener] // listen addr -> listener
|
||||
udpListeners *xsync.Map[string, net.PacketConn] // listen addr -> listener
|
||||
middleware *middleware.Middleware
|
||||
notFoundHandler http.Handler
|
||||
accessLogger accesslog.AccessLogger
|
||||
findRouteFunc func(host string) types.HTTPRoute
|
||||
shortLinkTree *ShortLinkMatcher
|
||||
}
|
||||
|
||||
var _ entrypoint.Entrypoint = &Entrypoint{}
|
||||
// nil-safe
|
||||
var ActiveConfig atomic.Pointer[entrypoint.Config]
|
||||
|
||||
var emptyCfg Config
|
||||
|
||||
func NewEntrypoint(parent task.Parent, cfg *Config) *Entrypoint {
|
||||
if cfg == nil {
|
||||
cfg = &emptyCfg
|
||||
}
|
||||
|
||||
ep := &Entrypoint{
|
||||
task: parent.Subtask("entrypoint", false),
|
||||
cfg: cfg,
|
||||
findRouteFunc: findRouteAnyDomain,
|
||||
shortLinkMatcher: newShortLinkMatcher(),
|
||||
streamRoutes: pool.New[types.StreamRoute]("stream_routes"),
|
||||
excludedRoutes: pool.New[types.Route]("excluded_routes"),
|
||||
servers: xsync.NewMap[string, *httpServer](),
|
||||
tcpListeners: xsync.NewMap[string, net.Listener](),
|
||||
udpListeners: xsync.NewMap[string, net.PacketConn](),
|
||||
}
|
||||
ep.task.OnCancel("stop", func() {
|
||||
// servers stop on their own when context is cancelled
|
||||
var errs gperr.Group
|
||||
for _, listener := range ep.tcpListeners.Range {
|
||||
errs.Go(func() error {
|
||||
return listener.Close()
|
||||
})
|
||||
}
|
||||
for _, listener := range ep.udpListeners.Range {
|
||||
errs.Go(func() error {
|
||||
return listener.Close()
|
||||
})
|
||||
}
|
||||
if err := errs.Wait().Error(); err != nil {
|
||||
gperr.LogError("failed to stop entrypoint listeners", err)
|
||||
}
|
||||
})
|
||||
ep.task.OnFinished("cleanup", func() {
|
||||
ep.servers.Clear()
|
||||
ep.tcpListeners.Clear()
|
||||
ep.udpListeners.Clear()
|
||||
})
|
||||
return ep
|
||||
func init() {
|
||||
// make sure it's not nil
|
||||
ActiveConfig.Store(&entrypoint.Config{})
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) SupportProxyProtocol() bool {
|
||||
return ep.cfg.SupportProxyProtocol
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) DisablePoolsLog(v bool) {
|
||||
ep.httpPoolDisableLog.Store(v)
|
||||
// apply to all running http servers
|
||||
for _, srv := range ep.servers.Range {
|
||||
srv.routes.DisableLog(v)
|
||||
func NewEntrypoint() Entrypoint {
|
||||
return Entrypoint{
|
||||
findRouteFunc: findRouteAnyDomain,
|
||||
shortLinkTree: newShortLinkTree(),
|
||||
}
|
||||
// apply to other pools
|
||||
ep.streamRoutes.DisableLog(v)
|
||||
ep.excludedRoutes.DisableLog(v)
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) ShortLinkMatcher() *ShortLinkMatcher {
|
||||
return ep.shortLinkMatcher
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) HTTPRoutes() entrypoint.PoolLike[types.HTTPRoute] {
|
||||
return newHTTPPoolAdapter(ep)
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) StreamRoutes() entrypoint.PoolLike[types.StreamRoute] {
|
||||
return ep.streamRoutes
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) ExcludedRoutes() entrypoint.RWPoolLike[types.Route] {
|
||||
return ep.excludedRoutes
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) GetServer(addr string) (http.Handler, bool) {
|
||||
return ep.servers.Load(addr)
|
||||
return ep.shortLinkTree
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) SetFindRouteDomains(domains []string) {
|
||||
@@ -156,7 +74,7 @@ func (ep *Entrypoint) SetMiddlewares(mws []map[string]any) error {
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) SetNotFoundRules(rules rules.Rules) {
|
||||
ep.notFoundHandler = rules.BuildHandler(serveNotFound)
|
||||
ep.notFoundHandler = rules.BuildHandler(http.HandlerFunc(ep.serveNotFound))
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) SetAccessLogger(parent task.Parent, cfg *accesslog.RequestLoggerConfig) (err error) {
|
||||
@@ -173,39 +91,111 @@ func (ep *Entrypoint) SetAccessLogger(parent task.Parent, cfg *accesslog.Request
|
||||
return err
|
||||
}
|
||||
|
||||
func findRouteAnyDomain(routes HTTPRoutes, host string) types.HTTPRoute {
|
||||
func (ep *Entrypoint) FindRoute(s string) types.HTTPRoute {
|
||||
return ep.findRouteFunc(s)
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if ep.accessLogger != nil {
|
||||
rec := accesslog.GetResponseRecorder(w)
|
||||
w = rec
|
||||
defer func() {
|
||||
ep.accessLogger.LogRequest(r, rec.Response())
|
||||
accesslog.PutResponseRecorder(rec)
|
||||
}()
|
||||
}
|
||||
|
||||
route := ep.findRouteFunc(r.Host)
|
||||
switch {
|
||||
case route != nil:
|
||||
r = routes.WithRouteContext(r, route)
|
||||
if ep.middleware != nil {
|
||||
ep.middleware.ServeHTTP(route.ServeHTTP, w, r)
|
||||
} else {
|
||||
route.ServeHTTP(w, r)
|
||||
}
|
||||
case ep.tryHandleShortLink(w, r):
|
||||
return
|
||||
case ep.notFoundHandler != nil:
|
||||
ep.notFoundHandler.ServeHTTP(w, r)
|
||||
default:
|
||||
ep.serveNotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) tryHandleShortLink(w http.ResponseWriter, r *http.Request) (handled bool) {
|
||||
host := r.Host
|
||||
if before, _, ok := strings.Cut(host, ":"); ok {
|
||||
host = before
|
||||
}
|
||||
if strings.EqualFold(host, common.ShortLinkPrefix) {
|
||||
if ep.middleware != nil {
|
||||
ep.middleware.ServeHTTP(ep.shortLinkTree.ServeHTTP, w, r)
|
||||
} else {
|
||||
ep.shortLinkTree.ServeHTTP(w, r)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) serveNotFound(w http.ResponseWriter, r *http.Request) {
|
||||
// Why use StatusNotFound instead of StatusBadRequest or StatusBadGateway?
|
||||
// On nginx, when route for domain does not exist, it returns StatusBadGateway.
|
||||
// Then scraper / scanners will know the subdomain is invalid.
|
||||
// With StatusNotFound, they won't know whether it's the path, or the subdomain that is invalid.
|
||||
if served := middleware.ServeStaticErrorPageFile(w, r); !served {
|
||||
log.Error().
|
||||
Str("method", r.Method).
|
||||
Str("url", r.URL.String()).
|
||||
Str("remote", r.RemoteAddr).
|
||||
Msgf("not found: %s", r.Host)
|
||||
errorPage, ok := errorpage.GetErrorPageByStatus(http.StatusNotFound)
|
||||
if ok {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if _, err := w.Write(errorPage); err != nil {
|
||||
log.Err(err).Msg("failed to write error page")
|
||||
}
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func findRouteAnyDomain(host string) types.HTTPRoute {
|
||||
idx := strings.IndexByte(host, '.')
|
||||
if idx != -1 {
|
||||
target := host[:idx]
|
||||
if r, ok := routes.Get(target); ok {
|
||||
if r, ok := routes.HTTP.Get(target); ok {
|
||||
return r
|
||||
}
|
||||
}
|
||||
if r, ok := routes.Get(host); ok {
|
||||
if r, ok := routes.HTTP.Get(host); ok {
|
||||
return r
|
||||
}
|
||||
// try striping the trailing :port from the host
|
||||
if before, _, ok := strings.Cut(host, ":"); ok {
|
||||
if r, ok := routes.Get(before); ok {
|
||||
if r, ok := routes.HTTP.Get(before); ok {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findRouteByDomains(domains []string) func(routes HTTPRoutes, host string) types.HTTPRoute {
|
||||
return func(routes HTTPRoutes, host string) types.HTTPRoute {
|
||||
func findRouteByDomains(domains []string) func(host string) types.HTTPRoute {
|
||||
return func(host string) types.HTTPRoute {
|
||||
host, _, _ = strings.Cut(host, ":") // strip the trailing :port
|
||||
for _, domain := range domains {
|
||||
if target, ok := strings.CutSuffix(host, domain); ok {
|
||||
if r, ok := routes.Get(target); ok {
|
||||
if r, ok := routes.HTTP.Get(target); ok {
|
||||
return r
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to exact match
|
||||
if r, ok := routes.Get(host); ok {
|
||||
if r, ok := routes.HTTP.Get(host); ok {
|
||||
return r
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -10,11 +10,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
. "github.com/yusing/godoxy/internal/entrypoint"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/route"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
routeTypes "github.com/yusing/godoxy/internal/route/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
"github.com/yusing/goutils/task"
|
||||
@@ -50,15 +48,13 @@ func (t noopTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
}
|
||||
|
||||
func BenchmarkEntrypointReal(b *testing.B) {
|
||||
task := task.NewTestTask(b)
|
||||
ep := NewEntrypoint(task, nil)
|
||||
var ep Entrypoint
|
||||
req := http.Request{
|
||||
Method: "GET",
|
||||
URL: &url.URL{Path: "/", RawPath: "/"},
|
||||
Host: "test.domain.tld",
|
||||
}
|
||||
ep.SetFindRouteDomains([]string{})
|
||||
entrypoint.SetCtx(task, ep)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Length", "1")
|
||||
@@ -81,48 +77,48 @@ func BenchmarkEntrypointReal(b *testing.B) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
r, err := route.NewTestRoute(b, task, &route.Route{
|
||||
r := &route.Route{
|
||||
Alias: "test",
|
||||
Scheme: routeTypes.SchemeHTTP,
|
||||
Host: host,
|
||||
Port: route.Port{Proxy: portInt},
|
||||
HealthCheck: types.HealthCheckConfig{Disable: true},
|
||||
})
|
||||
}
|
||||
|
||||
require.NoError(b, err)
|
||||
require.False(b, r.ShouldExclude())
|
||||
err = r.Validate()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
err = r.Start(task.RootTask("test", false))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
var w noopResponseWriter
|
||||
|
||||
server, ok := ep.GetServer(common.ProxyHTTPAddr)
|
||||
if !ok {
|
||||
b.Fatal("server not found")
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
server.ServeHTTP(&w, &req)
|
||||
if w.statusCode != http.StatusOK {
|
||||
b.Fatalf("status code is not 200: %d", w.statusCode)
|
||||
}
|
||||
if string(w.written) != "1" {
|
||||
b.Fatalf("written is not 1: %s", string(w.written))
|
||||
}
|
||||
ep.ServeHTTP(&w, &req)
|
||||
// if w.statusCode != http.StatusOK {
|
||||
// b.Fatalf("status code is not 200: %d", w.statusCode)
|
||||
// }
|
||||
// if string(w.written) != "1" {
|
||||
// b.Fatalf("written is not 1: %s", string(w.written))
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEntrypoint(b *testing.B) {
|
||||
task := task.NewTestTask(b)
|
||||
ep := NewEntrypoint(task, nil)
|
||||
var ep Entrypoint
|
||||
req := http.Request{
|
||||
Method: "GET",
|
||||
URL: &url.URL{Path: "/", RawPath: "/"},
|
||||
Host: "test.domain.tld",
|
||||
}
|
||||
ep.SetFindRouteDomains([]string{})
|
||||
entrypoint.SetCtx(task, ep)
|
||||
|
||||
r, err := route.NewTestRoute(b, task, &route.Route{
|
||||
r := &route.Route{
|
||||
Alias: "test",
|
||||
Scheme: routeTypes.SchemeHTTP,
|
||||
Host: "localhost",
|
||||
@@ -132,23 +128,29 @@ func BenchmarkEntrypoint(b *testing.B) {
|
||||
HealthCheck: types.HealthCheckConfig{
|
||||
Disable: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
require.NoError(b, err)
|
||||
require.False(b, r.ShouldExclude())
|
||||
err := r.Validate()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
r.(types.ReverseProxyRoute).ReverseProxy().Transport = noopTransport{}
|
||||
err = r.Start(task.RootTask("test", false))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
rev, ok := routes.HTTP.Get("test")
|
||||
if !ok {
|
||||
b.Fatal("route not found")
|
||||
}
|
||||
rev.(types.ReverseProxyRoute).ReverseProxy().Transport = noopTransport{}
|
||||
|
||||
var w noopResponseWriter
|
||||
|
||||
server, ok := ep.GetServer(common.ProxyHTTPAddr)
|
||||
if !ok {
|
||||
b.Fatal("server not found")
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
server.ServeHTTP(&w, &req)
|
||||
ep.ServeHTTP(&w, &req)
|
||||
if w.statusCode != http.StatusOK {
|
||||
b.Fatalf("status code is not 200: %d", w.statusCode)
|
||||
}
|
||||
|
||||
@@ -5,13 +5,15 @@ import (
|
||||
|
||||
. "github.com/yusing/godoxy/internal/entrypoint"
|
||||
"github.com/yusing/godoxy/internal/route"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
|
||||
"github.com/yusing/goutils/task"
|
||||
expect "github.com/yusing/goutils/testing"
|
||||
)
|
||||
|
||||
func addRoute(ep *Entrypoint, alias string) {
|
||||
ep.AddRoute(&route.ReveseProxyRoute{
|
||||
var ep = NewEntrypoint()
|
||||
|
||||
func addRoute(alias string) {
|
||||
routes.HTTP.Add(&route.ReveseProxyRoute{
|
||||
Route: &route.Route{
|
||||
Alias: alias,
|
||||
Port: route.Port{
|
||||
@@ -23,28 +25,26 @@ func addRoute(ep *Entrypoint, alias string) {
|
||||
|
||||
func run(t *testing.T, match []string, noMatch []string) {
|
||||
t.Helper()
|
||||
ep := NewEntrypoint(task.NewTestTask(t), nil)
|
||||
t.Cleanup(routes.Clear)
|
||||
t.Cleanup(func() { ep.SetFindRouteDomains(nil) })
|
||||
|
||||
for _, test := range match {
|
||||
t.Run(test, func(t *testing.T) {
|
||||
found, ok := ep.HTTPRoutes().Get(test)
|
||||
expect.True(t, ok)
|
||||
found := ep.FindRoute(test)
|
||||
expect.NotNil(t, found)
|
||||
})
|
||||
}
|
||||
|
||||
for _, test := range noMatch {
|
||||
t.Run(test, func(t *testing.T) {
|
||||
found, ok := ep.HTTPRoutes().Get(test)
|
||||
expect.False(t, ok)
|
||||
found := ep.FindRoute(test)
|
||||
expect.Nil(t, found)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindRouteAnyDomain(t *testing.T) {
|
||||
ep := NewEntrypoint(task.NewTestTask(t), nil)
|
||||
addRoute(ep, "app1")
|
||||
addRoute("app1")
|
||||
|
||||
tests := []string{
|
||||
"app1.com",
|
||||
@@ -62,7 +62,6 @@ func TestFindRouteAnyDomain(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindRouteExactHostMatch(t *testing.T) {
|
||||
ep := NewEntrypoint(task.NewTestTask(t), nil)
|
||||
tests := []string{
|
||||
"app2.com",
|
||||
"app2.domain.com",
|
||||
@@ -76,20 +75,19 @@ func TestFindRouteExactHostMatch(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
addRoute(ep, test)
|
||||
addRoute(test)
|
||||
}
|
||||
|
||||
run(t, tests, testsNoMatch)
|
||||
}
|
||||
|
||||
func TestFindRouteByDomains(t *testing.T) {
|
||||
ep := NewEntrypoint(task.NewTestTask(t), nil)
|
||||
ep.SetFindRouteDomains([]string{
|
||||
".domain.com",
|
||||
".sub.domain.com",
|
||||
})
|
||||
|
||||
addRoute(ep, "app1")
|
||||
addRoute("app1")
|
||||
|
||||
tests := []string{
|
||||
"app1.domain.com",
|
||||
@@ -109,13 +107,12 @@ func TestFindRouteByDomains(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindRouteByDomainsExactMatch(t *testing.T) {
|
||||
ep := NewEntrypoint(task.NewTestTask(t), nil)
|
||||
ep.SetFindRouteDomains([]string{
|
||||
".domain.com",
|
||||
".sub.domain.com",
|
||||
})
|
||||
|
||||
addRoute(ep, "app1.foo.bar")
|
||||
addRoute("app1.foo.bar")
|
||||
|
||||
tests := []string{
|
||||
"app1.foo.bar", // exact match
|
||||
@@ -134,9 +131,8 @@ func TestFindRouteByDomainsExactMatch(t *testing.T) {
|
||||
|
||||
func TestFindRouteWithPort(t *testing.T) {
|
||||
t.Run("AnyDomain", func(t *testing.T) {
|
||||
ep := NewEntrypoint(task.NewTestTask(t), nil)
|
||||
addRoute(ep, "app1")
|
||||
addRoute(ep, "app2.com")
|
||||
addRoute("app1")
|
||||
addRoute("app2.com")
|
||||
|
||||
tests := []string{
|
||||
"app1:8080",
|
||||
@@ -152,13 +148,12 @@ func TestFindRouteWithPort(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("ByDomains", func(t *testing.T) {
|
||||
ep := NewEntrypoint(task.NewTestTask(t), nil)
|
||||
ep.SetFindRouteDomains([]string{
|
||||
".domain.com",
|
||||
})
|
||||
addRoute(ep, "app1")
|
||||
addRoute(ep, "app2")
|
||||
addRoute(ep, "app3.domain.com")
|
||||
addRoute("app1")
|
||||
addRoute("app2")
|
||||
addRoute("app3.domain.com")
|
||||
|
||||
tests := []string{
|
||||
"app1.domain.com:8080",
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
)
|
||||
|
||||
// httpPoolAdapter implements the PoolLike interface for the HTTP routes.
|
||||
type httpPoolAdapter struct {
|
||||
ep *Entrypoint
|
||||
}
|
||||
|
||||
func newHTTPPoolAdapter(ep *Entrypoint) httpPoolAdapter {
|
||||
return httpPoolAdapter{ep: ep}
|
||||
}
|
||||
|
||||
func (h httpPoolAdapter) Iter(yield func(alias string, route types.HTTPRoute) bool) {
|
||||
for addr, srv := range h.ep.servers.Range {
|
||||
// default routes are added to both HTTP and HTTPS servers, we don't need to iterate over them twice.
|
||||
if addr == common.ProxyHTTPSAddr {
|
||||
continue
|
||||
}
|
||||
for alias, route := range srv.routes.Iter {
|
||||
if !yield(alias, route) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h httpPoolAdapter) Get(alias string) (types.HTTPRoute, bool) {
|
||||
for addr, srv := range h.ep.servers.Range {
|
||||
if addr == common.ProxyHTTPSAddr {
|
||||
continue
|
||||
}
|
||||
if route, ok := srv.routes.Get(alias); ok {
|
||||
return route, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (h httpPoolAdapter) Size() (n int) {
|
||||
for addr, srv := range h.ep.servers.Range {
|
||||
if addr == common.ProxyHTTPSAddr {
|
||||
continue
|
||||
}
|
||||
n += srv.routes.Size()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
acl "github.com/yusing/godoxy/internal/acl/types"
|
||||
autocert "github.com/yusing/godoxy/internal/autocert/types"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
"github.com/yusing/godoxy/internal/logging/accesslog"
|
||||
"github.com/yusing/godoxy/internal/net/gphttp/middleware"
|
||||
"github.com/yusing/godoxy/internal/net/gphttp/middleware/errorpage"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
"github.com/yusing/goutils/pool"
|
||||
"github.com/yusing/goutils/server"
|
||||
)
|
||||
|
||||
// httpServer is a server that listens on a given address and serves HTTP routes.
|
||||
type HTTPServer interface {
|
||||
Listen(addr string, proto HTTPProto) error
|
||||
AddRoute(route types.HTTPRoute)
|
||||
DelRoute(route types.HTTPRoute)
|
||||
FindRoute(s string) types.HTTPRoute
|
||||
ServeHTTP(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
type httpServer struct {
|
||||
srv *server.Server
|
||||
ep *Entrypoint
|
||||
|
||||
stopFunc func(reason any)
|
||||
|
||||
addr string
|
||||
routes *pool.Pool[types.HTTPRoute]
|
||||
}
|
||||
|
||||
type HTTPProto string
|
||||
|
||||
const (
|
||||
HTTPProtoHTTP HTTPProto = "http"
|
||||
HTTPProtoHTTPS HTTPProto = "https"
|
||||
)
|
||||
|
||||
func NewHTTPServer(ep *Entrypoint) HTTPServer {
|
||||
return newHTTPServer(ep)
|
||||
}
|
||||
|
||||
func newHTTPServer(ep *Entrypoint) *httpServer {
|
||||
return &httpServer{ep: ep}
|
||||
}
|
||||
|
||||
// Listen starts the server and stop when entrypoint is stopped.
|
||||
func (srv *httpServer) Listen(addr string, proto HTTPProto) error {
|
||||
if srv.srv != nil {
|
||||
return errors.New("server already started")
|
||||
}
|
||||
|
||||
opts := server.Options{
|
||||
Name: addr,
|
||||
Handler: srv,
|
||||
ACL: acl.FromCtx(srv.ep.task.Context()),
|
||||
SupportProxyProtocol: srv.ep.cfg.SupportProxyProtocol,
|
||||
}
|
||||
|
||||
switch proto {
|
||||
case HTTPProtoHTTP:
|
||||
opts.HTTPAddr = addr
|
||||
case HTTPProtoHTTPS:
|
||||
opts.HTTPSAddr = addr
|
||||
opts.CertProvider = autocert.FromCtx(srv.ep.task.Context())
|
||||
}
|
||||
|
||||
task := srv.ep.task.Subtask("http_server", false)
|
||||
server, err := server.StartServer(task, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srv.stopFunc = task.FinishAndWait
|
||||
srv.addr = addr
|
||||
srv.srv = server
|
||||
srv.routes = pool.New[types.HTTPRoute](fmt.Sprintf("[%s] %s", proto, addr))
|
||||
srv.routes.DisableLog(srv.ep.httpPoolDisableLog.Load())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *httpServer) Close() {
|
||||
srv.stopFunc(nil)
|
||||
}
|
||||
|
||||
func (srv *httpServer) AddRoute(route types.HTTPRoute) {
|
||||
srv.routes.Add(route)
|
||||
}
|
||||
|
||||
func (srv *httpServer) DelRoute(route types.HTTPRoute) {
|
||||
srv.routes.Del(route)
|
||||
}
|
||||
|
||||
func (srv *httpServer) FindRoute(s string) types.HTTPRoute {
|
||||
return srv.ep.findRouteFunc(srv.routes, s)
|
||||
}
|
||||
|
||||
func (srv *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if srv.ep.accessLogger != nil {
|
||||
rec := accesslog.GetResponseRecorder(w)
|
||||
w = rec
|
||||
defer func() {
|
||||
srv.ep.accessLogger.LogRequest(r, rec.Response())
|
||||
accesslog.PutResponseRecorder(rec)
|
||||
}()
|
||||
}
|
||||
|
||||
route := srv.ep.findRouteFunc(srv.routes, r.Host)
|
||||
switch {
|
||||
case route != nil:
|
||||
r = routes.WithRouteContext(r, route)
|
||||
if srv.ep.middleware != nil {
|
||||
srv.ep.middleware.ServeHTTP(route.ServeHTTP, w, r)
|
||||
} else {
|
||||
route.ServeHTTP(w, r)
|
||||
}
|
||||
case srv.tryHandleShortLink(w, r):
|
||||
return
|
||||
case srv.ep.notFoundHandler != nil:
|
||||
srv.ep.notFoundHandler.ServeHTTP(w, r)
|
||||
default:
|
||||
serveNotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *httpServer) tryHandleShortLink(w http.ResponseWriter, r *http.Request) (handled bool) {
|
||||
host := r.Host
|
||||
if before, _, ok := strings.Cut(host, ":"); ok {
|
||||
host = before
|
||||
}
|
||||
if strings.EqualFold(host, common.ShortLinkPrefix) {
|
||||
if srv.ep.middleware != nil {
|
||||
srv.ep.middleware.ServeHTTP(srv.ep.shortLinkMatcher.ServeHTTP, w, r)
|
||||
} else {
|
||||
srv.ep.shortLinkMatcher.ServeHTTP(w, r)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func serveNotFound(w http.ResponseWriter, r *http.Request) {
|
||||
// Why use StatusNotFound instead of StatusBadRequest or StatusBadGateway?
|
||||
// On nginx, when route for domain does not exist, it returns StatusBadGateway.
|
||||
// Then scraper / scanners will know the subdomain is invalid.
|
||||
// With StatusNotFound, they won't know whether it's the path, or the subdomain that is invalid.
|
||||
if served := middleware.ServeStaticErrorPageFile(w, r); !served {
|
||||
log.Error().
|
||||
Str("method", r.Method).
|
||||
Str("url", r.URL.String()).
|
||||
Str("remote", r.RemoteAddr).
|
||||
Msgf("not found: %s", r.Host)
|
||||
errorPage, ok := errorpage.GetErrorPageByStatus(http.StatusNotFound)
|
||||
if ok {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if _, err := w.Write(errorPage); err != nil {
|
||||
log.Err(err).Msg("failed to write error page")
|
||||
}
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
)
|
||||
|
||||
// GetHealthInfo returns a map of route name to health info.
|
||||
//
|
||||
// The health info is for all routes, including excluded routes.
|
||||
func (ep *Entrypoint) GetHealthInfo() map[string]types.HealthInfo {
|
||||
healthMap := make(map[string]types.HealthInfo, ep.NumRoutes())
|
||||
for r := range ep.IterRoutes {
|
||||
healthMap[r.Name()] = getHealthInfo(r)
|
||||
}
|
||||
return healthMap
|
||||
}
|
||||
|
||||
// GetHealthInfoWithoutDetail returns a map of route name to health info without detail.
|
||||
//
|
||||
// The health info is for all routes, including excluded routes.
|
||||
func (ep *Entrypoint) GetHealthInfoWithoutDetail() map[string]types.HealthInfoWithoutDetail {
|
||||
healthMap := make(map[string]types.HealthInfoWithoutDetail, ep.NumRoutes())
|
||||
for r := range ep.IterRoutes {
|
||||
healthMap[r.Name()] = getHealthInfoWithoutDetail(r)
|
||||
}
|
||||
return healthMap
|
||||
}
|
||||
|
||||
// GetHealthInfoSimple returns a map of route name to health status.
|
||||
//
|
||||
// The health status is for all routes, including excluded routes.
|
||||
func (ep *Entrypoint) GetHealthInfoSimple() map[string]types.HealthStatus {
|
||||
healthMap := make(map[string]types.HealthStatus, ep.NumRoutes())
|
||||
for r := range ep.IterRoutes {
|
||||
healthMap[r.Name()] = getHealthInfoSimple(r)
|
||||
}
|
||||
return healthMap
|
||||
}
|
||||
|
||||
// RoutesByProvider returns a map of provider name to routes.
|
||||
//
|
||||
// The routes are all routes, including excluded routes.
|
||||
func (ep *Entrypoint) RoutesByProvider() map[string][]types.Route {
|
||||
rts := make(map[string][]types.Route)
|
||||
for r := range ep.IterRoutes {
|
||||
rts[r.ProviderName()] = append(rts[r.ProviderName()], r)
|
||||
}
|
||||
return rts
|
||||
}
|
||||
|
||||
func getHealthInfo(r types.Route) types.HealthInfo {
|
||||
mon := r.HealthMonitor()
|
||||
if mon == nil {
|
||||
return types.HealthInfo{
|
||||
HealthInfoWithoutDetail: types.HealthInfoWithoutDetail{
|
||||
Status: types.StatusUnknown,
|
||||
},
|
||||
Detail: "n/a",
|
||||
}
|
||||
}
|
||||
return types.HealthInfo{
|
||||
HealthInfoWithoutDetail: types.HealthInfoWithoutDetail{
|
||||
Status: mon.Status(),
|
||||
Uptime: mon.Uptime(),
|
||||
Latency: mon.Latency(),
|
||||
},
|
||||
Detail: mon.Detail(),
|
||||
}
|
||||
}
|
||||
|
||||
func getHealthInfoWithoutDetail(r types.Route) types.HealthInfoWithoutDetail {
|
||||
mon := r.HealthMonitor()
|
||||
if mon == nil {
|
||||
return types.HealthInfoWithoutDetail{
|
||||
Status: types.StatusUnknown,
|
||||
}
|
||||
}
|
||||
return types.HealthInfoWithoutDetail{
|
||||
Status: mon.Status(),
|
||||
Uptime: mon.Uptime(),
|
||||
Latency: mon.Latency(),
|
||||
}
|
||||
}
|
||||
|
||||
func getHealthInfoSimple(r types.Route) types.HealthStatus {
|
||||
mon := r.HealthMonitor()
|
||||
if mon == nil {
|
||||
return types.StatusUnknown
|
||||
}
|
||||
return mon.Status()
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
)
|
||||
|
||||
func (ep *Entrypoint) IterRoutes(yield func(r types.Route) bool) {
|
||||
for _, r := range ep.HTTPRoutes().Iter {
|
||||
if !yield(r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, r := range ep.streamRoutes.Iter {
|
||||
if !yield(r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, r := range ep.excludedRoutes.Iter {
|
||||
if !yield(r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) NumRoutes() int {
|
||||
return ep.HTTPRoutes().Size() + ep.streamRoutes.Size() + ep.excludedRoutes.Size()
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) GetRoute(alias string) (types.Route, bool) {
|
||||
if r, ok := ep.HTTPRoutes().Get(alias); ok {
|
||||
return r, true
|
||||
}
|
||||
if r, ok := ep.streamRoutes.Get(alias); ok {
|
||||
return r, true
|
||||
}
|
||||
if r, ok := ep.excludedRoutes.Get(alias); ok {
|
||||
return r, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) AddRoute(r types.Route) {
|
||||
if r.ShouldExclude() {
|
||||
ep.excludedRoutes.Add(r)
|
||||
r.Task().OnCancel("remove_route", func() {
|
||||
ep.excludedRoutes.Del(r)
|
||||
})
|
||||
return
|
||||
}
|
||||
switch r := r.(type) {
|
||||
case types.HTTPRoute:
|
||||
if err := ep.AddHTTPRoute(r); err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("route", r.Key()).
|
||||
Str("listen_url", r.ListenURL().String()).
|
||||
Msg("failed to add HTTP route")
|
||||
}
|
||||
ep.shortLinkMatcher.AddRoute(r.Key())
|
||||
r.Task().OnCancel("remove_route", func() {
|
||||
ep.delHTTPRoute(r)
|
||||
ep.shortLinkMatcher.DelRoute(r.Key())
|
||||
})
|
||||
case types.StreamRoute:
|
||||
ep.streamRoutes.Add(r)
|
||||
r.Task().OnCancel("remove_route", func() {
|
||||
ep.streamRoutes.Del(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// AddHTTPRoute adds a HTTP route to the entrypoint's server.
|
||||
//
|
||||
// If the server does not exist, it will be created, started and return any error.
|
||||
func (ep *Entrypoint) AddHTTPRoute(route types.HTTPRoute) error {
|
||||
if port := route.ListenURL().Port(); port == "" || port == "0" {
|
||||
host := route.ListenURL().Hostname()
|
||||
var httpAddr, httpsAddr string
|
||||
if host == "" {
|
||||
httpAddr = common.ProxyHTTPAddr
|
||||
httpsAddr = common.ProxyHTTPSAddr
|
||||
} else {
|
||||
httpAddr = net.JoinHostPort(host, strconv.Itoa(common.ProxyHTTPPort))
|
||||
httpsAddr = net.JoinHostPort(host, strconv.Itoa(common.ProxyHTTPSPort))
|
||||
}
|
||||
return errors.Join(ep.addHTTPRoute(route, httpAddr, HTTPProtoHTTP), ep.addHTTPRoute(route, httpsAddr, HTTPProtoHTTPS))
|
||||
}
|
||||
|
||||
return ep.addHTTPRoute(route, route.ListenURL().Host, HTTPProtoHTTPS)
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) addHTTPRoute(route types.HTTPRoute, addr string, proto HTTPProto) error {
|
||||
var err error
|
||||
srv, _ := ep.servers.LoadOrCompute(addr, func() (newSrv *httpServer, cancel bool) {
|
||||
newSrv = newHTTPServer(ep)
|
||||
err = newSrv.Listen(addr, proto)
|
||||
cancel = err != nil
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srv.AddRoute(route)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ep *Entrypoint) delHTTPRoute(route types.HTTPRoute) {
|
||||
addr := route.ListenURL().Host
|
||||
srv, _ := ep.servers.Load(addr)
|
||||
if srv != nil {
|
||||
srv.DelRoute(route)
|
||||
}
|
||||
// TODO: close if no servers left
|
||||
}
|
||||
@@ -14,7 +14,7 @@ type ShortLinkMatcher struct {
|
||||
subdomainRoutes *xsync.Map[string, struct{}]
|
||||
}
|
||||
|
||||
func newShortLinkMatcher() *ShortLinkMatcher {
|
||||
func newShortLinkTree() *ShortLinkMatcher {
|
||||
return &ShortLinkMatcher{
|
||||
fqdnRoutes: xsync.NewMap[string, string](),
|
||||
subdomainRoutes: xsync.NewMap[string, struct{}](),
|
||||
|
||||
@@ -6,15 +6,13 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
. "github.com/yusing/godoxy/internal/entrypoint"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
|
||||
func TestShortLinkMatcher_FQDNAlias(t *testing.T) {
|
||||
ep := NewEntrypoint(task.NewTestTask(t), nil)
|
||||
ep := NewEntrypoint()
|
||||
matcher := ep.ShortLinkMatcher()
|
||||
matcher.AddRoute("app.domain.com")
|
||||
|
||||
@@ -47,7 +45,7 @@ func TestShortLinkMatcher_FQDNAlias(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShortLinkMatcher_SubdomainAlias(t *testing.T) {
|
||||
ep := NewEntrypoint(task.NewTestTask(t), nil)
|
||||
ep := NewEntrypoint()
|
||||
matcher := ep.ShortLinkMatcher()
|
||||
matcher.SetDefaultDomainSuffix(".example.com")
|
||||
matcher.AddRoute("app")
|
||||
@@ -72,7 +70,7 @@ func TestShortLinkMatcher_SubdomainAlias(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShortLinkMatcher_NotFound(t *testing.T) {
|
||||
ep := NewEntrypoint(task.NewTestTask(t), nil)
|
||||
ep := NewEntrypoint()
|
||||
matcher := ep.ShortLinkMatcher()
|
||||
matcher.SetDefaultDomainSuffix(".example.com")
|
||||
matcher.AddRoute("app")
|
||||
@@ -95,7 +93,7 @@ func TestShortLinkMatcher_NotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShortLinkMatcher_AddDelRoute(t *testing.T) {
|
||||
ep := NewEntrypoint(task.NewTestTask(t), nil)
|
||||
ep := NewEntrypoint()
|
||||
matcher := ep.ShortLinkMatcher()
|
||||
matcher.SetDefaultDomainSuffix(".example.com")
|
||||
|
||||
@@ -133,7 +131,7 @@ func TestShortLinkMatcher_AddDelRoute(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShortLinkMatcher_NoDefaultDomainSuffix(t *testing.T) {
|
||||
ep := NewEntrypoint(task.NewTestTask(t), nil)
|
||||
ep := NewEntrypoint()
|
||||
matcher := ep.ShortLinkMatcher()
|
||||
// no SetDefaultDomainSuffix called
|
||||
|
||||
@@ -160,19 +158,15 @@ func TestShortLinkMatcher_NoDefaultDomainSuffix(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEntrypoint_ShortLinkDispatch(t *testing.T) {
|
||||
ep := NewEntrypoint(task.NewTestTask(t), nil)
|
||||
ep := NewEntrypoint()
|
||||
ep.ShortLinkMatcher().SetDefaultDomainSuffix(".example.com")
|
||||
ep.ShortLinkMatcher().AddRoute("app")
|
||||
|
||||
server := NewHTTPServer(ep)
|
||||
err := server.Listen("localhost:0", HTTPProtoHTTP)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("shortlink host", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/app", nil)
|
||||
req.Host = common.ShortLinkPrefix
|
||||
w := httptest.NewRecorder()
|
||||
server.ServeHTTP(w, req)
|
||||
ep.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
||||
assert.Equal(t, "https://app.example.com/", w.Header().Get("Location"))
|
||||
@@ -182,7 +176,7 @@ func TestEntrypoint_ShortLinkDispatch(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/app", nil)
|
||||
req.Host = common.ShortLinkPrefix + ":8080"
|
||||
w := httptest.NewRecorder()
|
||||
server.ServeHTTP(w, req)
|
||||
ep.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
||||
assert.Equal(t, "https://app.example.com/", w.Header().Get("Location"))
|
||||
@@ -192,7 +186,7 @@ func TestEntrypoint_ShortLinkDispatch(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/app", nil)
|
||||
req.Host = "app.example.com"
|
||||
w := httptest.NewRecorder()
|
||||
server.ServeHTTP(w, req)
|
||||
ep.ServeHTTP(w, req)
|
||||
|
||||
// Should not redirect, should try normal route lookup (which will 404)
|
||||
assert.NotEqual(t, http.StatusTemporaryRedirect, w.Code)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type ContextKey struct{}
|
||||
|
||||
func SetCtx(ctx interface{ SetValue(any, any) }, ep Entrypoint) {
|
||||
ctx.SetValue(ContextKey{}, ep)
|
||||
}
|
||||
|
||||
func FromCtx(ctx context.Context) Entrypoint {
|
||||
if ep, ok := ctx.Value(ContextKey{}).(Entrypoint); ok {
|
||||
return ep
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
)
|
||||
|
||||
type Entrypoint interface {
|
||||
SupportProxyProtocol() bool
|
||||
|
||||
DisablePoolsLog(v bool)
|
||||
|
||||
GetRoute(alias string) (types.Route, bool)
|
||||
AddRoute(r types.Route)
|
||||
IterRoutes(yield func(r types.Route) bool)
|
||||
NumRoutes() int
|
||||
RoutesByProvider() map[string][]types.Route
|
||||
|
||||
HTTPRoutes() PoolLike[types.HTTPRoute]
|
||||
StreamRoutes() PoolLike[types.StreamRoute]
|
||||
ExcludedRoutes() RWPoolLike[types.Route]
|
||||
|
||||
GetHealthInfo() map[string]types.HealthInfo
|
||||
GetHealthInfoWithoutDetail() map[string]types.HealthInfoWithoutDetail
|
||||
GetHealthInfoSimple() map[string]types.HealthStatus
|
||||
}
|
||||
|
||||
type PoolLike[Route types.Route] interface {
|
||||
Get(alias string) (Route, bool)
|
||||
Iter(yield func(alias string, r Route) bool)
|
||||
Size() int
|
||||
}
|
||||
|
||||
type RWPoolLike[Route types.Route] interface {
|
||||
PoolLike[Route]
|
||||
Add(r Route)
|
||||
Del(r Route)
|
||||
}
|
||||
@@ -2,13 +2,12 @@ package healthcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
httputils "github.com/yusing/goutils/http"
|
||||
@@ -44,7 +43,7 @@ func Docker(ctx context.Context, state *DockerHealthcheckState, timeout time.Dur
|
||||
defer cancel()
|
||||
|
||||
// the actual inspect response is intercepted and returned as RequestInterceptedError
|
||||
_, err := state.client.ContainerInspect(ctx, state.containerId, client.ContainerInspectOptions{})
|
||||
_, err := state.client.ContainerInspect(ctx, state.containerId)
|
||||
|
||||
var interceptedErr *httputils.RequestInterceptedError
|
||||
if !httputils.AsRequestInterceptedError(err, &interceptedErr) {
|
||||
@@ -106,7 +105,7 @@ func interceptDockerInspectResponse(resp *http.Response) (intercepted bool, err
|
||||
}
|
||||
|
||||
var state container.State
|
||||
err = sonic.Unmarshal(body, &state)
|
||||
err = json.Unmarshal(body, &state)
|
||||
release(body)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
@@ -2,12 +2,12 @@ package iconlist
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
@@ -55,20 +55,20 @@ func init() {
|
||||
|
||||
func InitCache() {
|
||||
m := make(IconMap)
|
||||
err := serialization.LoadFileIfExist(common.IconListCachePath, &m, sonic.Unmarshal)
|
||||
err := serialization.LoadFileIfExist(common.IconListCachePath, &m, json.Unmarshal)
|
||||
if err != nil {
|
||||
// backward compatible
|
||||
oldFormat := struct {
|
||||
Icons IconMap
|
||||
LastUpdate time.Time
|
||||
}{}
|
||||
err = serialization.LoadFileIfExist(common.IconListCachePath, &oldFormat, sonic.Unmarshal)
|
||||
err = serialization.LoadFileIfExist(common.IconListCachePath, &oldFormat, json.Unmarshal)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to load icons")
|
||||
} else {
|
||||
m = oldFormat.Icons
|
||||
// store it to disk immediately
|
||||
_ = serialization.SaveFile(common.IconListCachePath, &m, 0o644, sonic.Marshal)
|
||||
_ = serialization.SaveFile(common.IconListCachePath, &m, 0o644, json.Marshal)
|
||||
}
|
||||
} else if len(m) > 0 {
|
||||
log.Info().
|
||||
@@ -84,7 +84,7 @@ func InitCache() {
|
||||
|
||||
task.OnProgramExit("save_icons_cache", func() {
|
||||
icons := iconsCache.Load()
|
||||
_ = serialization.SaveFile(common.IconListCachePath, &icons, 0o644, sonic.Marshal)
|
||||
_ = serialization.SaveFile(common.IconListCachePath, &icons, 0o644, json.Marshal)
|
||||
})
|
||||
|
||||
go backgroundUpdateIcons()
|
||||
@@ -105,7 +105,7 @@ func backgroundUpdateIcons() {
|
||||
// swap old cache with new cache
|
||||
iconsCache.Store(newCache)
|
||||
// save it to disk
|
||||
err := serialization.SaveFile(common.IconListCachePath, &newCache, 0o644, sonic.Marshal)
|
||||
err := serialization.SaveFile(common.IconListCachePath, &newCache, 0o644, json.Marshal)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("failed to save icons")
|
||||
}
|
||||
@@ -270,7 +270,7 @@ func UpdateWalkxCodeIcons(m IconMap) error {
|
||||
}
|
||||
|
||||
data := make(map[string][]string)
|
||||
err = sonic.Unmarshal(body, &data)
|
||||
err = json.Unmarshal(body, &data)
|
||||
release(body)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -349,7 +349,7 @@ func UpdateSelfhstIcons(m IconMap) error {
|
||||
}
|
||||
|
||||
data := make([]SelfhStIcon, 0)
|
||||
err = sonic.Unmarshal(body, &data) //nolint:musttag
|
||||
err = json.Unmarshal(body, &data) //nolint:musttag
|
||||
release(body)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -2,11 +2,11 @@ package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/yusing/godoxy/internal/homepage/widgets"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
@@ -59,7 +59,7 @@ func jsonRequest[T any](ctx context.Context, client *Client, endpoint string, qu
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = sonic.ConfigDefault.NewDecoder(resp.Body).Decode(&result)
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@ package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
const endpointLogs = "/api/v2/log/main"
|
||||
@@ -45,7 +44,7 @@ func (l *LogEntry) Level() string {
|
||||
}
|
||||
|
||||
func (l *LogEntry) MarshalJSON() ([]byte, error) {
|
||||
return sonic.Marshal(map[string]any{
|
||||
return json.Marshal(map[string]any{
|
||||
"id": l.ID,
|
||||
"timestamp": l.Timestamp,
|
||||
"level": l.Level(),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package idlewatcher
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"iter"
|
||||
"strconv"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ type watcherDebug struct {
|
||||
|
||||
func (w watcherDebug) MarshalJSON() ([]byte, error) {
|
||||
state := w.state.Load()
|
||||
return sonic.Marshal(map[string]any{
|
||||
return json.Marshal(map[string]any{
|
||||
"name": w.Name(),
|
||||
"state": map[string]string{
|
||||
"status": string(state.status),
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package idlewatcher
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
type WakeEvent struct {
|
||||
@@ -40,7 +39,7 @@ func (w *Watcher) newWakeEvent(eventType WakeEventType, message string, err erro
|
||||
}
|
||||
|
||||
func (e *WakeEvent) WriteSSE(w io.Writer) error {
|
||||
data, err := sonic.Marshal(e)
|
||||
data, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ package provider
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
@@ -18,7 +17,7 @@ type DockerProvider struct {
|
||||
containerID string
|
||||
}
|
||||
|
||||
var startOptions = client.ContainerStartOptions{}
|
||||
var startOptions = container.StartOptions{}
|
||||
|
||||
func NewDockerProvider(dockerCfg types.DockerProviderConfig, containerID string) (idlewatcher.Provider, error) {
|
||||
client, err := docker.NewClient(dockerCfg)
|
||||
@@ -33,41 +32,34 @@ func NewDockerProvider(dockerCfg types.DockerProviderConfig, containerID string)
|
||||
}
|
||||
|
||||
func (p *DockerProvider) ContainerPause(ctx context.Context) error {
|
||||
_, err := p.client.ContainerPause(ctx, p.containerID, client.ContainerPauseOptions{})
|
||||
return err
|
||||
return p.client.ContainerPause(ctx, p.containerID)
|
||||
}
|
||||
|
||||
func (p *DockerProvider) ContainerUnpause(ctx context.Context) error {
|
||||
_, err := p.client.ContainerUnpause(ctx, p.containerID, client.ContainerUnpauseOptions{})
|
||||
return err
|
||||
return p.client.ContainerUnpause(ctx, p.containerID)
|
||||
}
|
||||
|
||||
func (p *DockerProvider) ContainerStart(ctx context.Context) error {
|
||||
_, err := p.client.ContainerStart(ctx, p.containerID, startOptions)
|
||||
return err
|
||||
return p.client.ContainerStart(ctx, p.containerID, startOptions)
|
||||
}
|
||||
|
||||
func (p *DockerProvider) ContainerStop(ctx context.Context, signal types.ContainerSignal, timeout int) error {
|
||||
_, err := p.client.ContainerStop(ctx, p.containerID, client.ContainerStopOptions{
|
||||
return p.client.ContainerStop(ctx, p.containerID, container.StopOptions{
|
||||
Signal: string(signal),
|
||||
Timeout: &timeout,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *DockerProvider) ContainerKill(ctx context.Context, signal types.ContainerSignal) error {
|
||||
_, err := p.client.ContainerKill(ctx, p.containerID, client.ContainerKillOptions{
|
||||
Signal: string(signal),
|
||||
})
|
||||
return err
|
||||
return p.client.ContainerKill(ctx, p.containerID, string(signal))
|
||||
}
|
||||
|
||||
func (p *DockerProvider) ContainerStatus(ctx context.Context) (idlewatcher.ContainerStatus, error) {
|
||||
status, err := p.client.ContainerInspect(ctx, p.containerID, client.ContainerInspectOptions{})
|
||||
status, err := p.client.ContainerInspect(ctx, p.containerID)
|
||||
if err != nil {
|
||||
return idlewatcher.ContainerStatusError, err
|
||||
}
|
||||
switch status.Container.State.Status {
|
||||
switch status.State.Status {
|
||||
case container.StateRunning:
|
||||
return idlewatcher.ContainerStatusRunning, nil
|
||||
case container.StateExited, container.StateDead, container.StateRestarting:
|
||||
@@ -75,12 +67,12 @@ func (p *DockerProvider) ContainerStatus(ctx context.Context) (idlewatcher.Conta
|
||||
case container.StatePaused:
|
||||
return idlewatcher.ContainerStatusPaused, nil
|
||||
}
|
||||
return idlewatcher.ContainerStatusError, idlewatcher.ErrUnexpectedContainerStatus.Subject(string(status.Container.State.Status))
|
||||
return idlewatcher.ContainerStatusError, idlewatcher.ErrUnexpectedContainerStatus.Subject(status.State.Status)
|
||||
}
|
||||
|
||||
func (p *DockerProvider) Watch(ctx context.Context) (eventCh <-chan watcher.Event, errCh <-chan gperr.Error) {
|
||||
return p.watcher.EventsWithOptions(ctx, watcher.DockerListOptions{
|
||||
Filters: watcher.NewDockerFilters(
|
||||
Filters: watcher.NewDockerFilter(
|
||||
watcher.DockerFilterContainer,
|
||||
watcher.DockerFilterContainerNameID(p.containerID),
|
||||
watcher.DockerFilterStart,
|
||||
|
||||
@@ -14,11 +14,11 @@ import (
|
||||
"github.com/yusing/ds/ordered"
|
||||
config "github.com/yusing/godoxy/internal/config/types"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/health/monitor"
|
||||
"github.com/yusing/godoxy/internal/idlewatcher/provider"
|
||||
idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types"
|
||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
"github.com/yusing/godoxy/internal/watcher/events"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
@@ -173,7 +173,7 @@ func NewWatcher(parent task.Parent, r types.Route, cfg *types.IdlewatcherConfig)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
depRoute, ok = entrypoint.FromCtx(parent.Context()).GetRoute(dep)
|
||||
depRoute, ok = routes.GetIncludeExcluded(dep)
|
||||
if !ok {
|
||||
depErrors.Addf("dependency %q not found", dep)
|
||||
continue
|
||||
|
||||
@@ -261,7 +261,7 @@ func loadNS[T store](ns namespace) T {
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := sonic.ConfigDefault.NewDecoder(file).Decode(&store); err != nil {
|
||||
if err := json.NewDecoder(file).Decode(&store); err != nil {
|
||||
log.Err(err).Msg("failed to decode store")
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ type MapStore[VT any] struct {
|
||||
|
||||
```go
|
||||
func (s MapStore[VT]) MarshalJSON() ([]byte, error) {
|
||||
return sonic.Marshal(xsync.ToPlainMap(s.Map))
|
||||
return json.Marshal(xsync.ToPlainMap(s.Map))
|
||||
}
|
||||
```
|
||||
|
||||
@@ -325,7 +325,7 @@ func (s MapStore[VT]) MarshalJSON() ([]byte, error) {
|
||||
```go
|
||||
func (s *MapStore[VT]) UnmarshalJSON(data []byte) error {
|
||||
tmp := make(map[string]VT)
|
||||
if err := sonic.Unmarshal(data, &tmp); err != nil {
|
||||
if err := json.Unmarshal(data, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Map = xsync.NewMap[string, VT](xsync.WithPresize(len(tmp)))
|
||||
@@ -349,7 +349,7 @@ The jsonstore package integrates with:
|
||||
Errors are logged but don't prevent store usage:
|
||||
|
||||
```go
|
||||
if err := sonic.Unmarshal(data, &tmp); err != nil {
|
||||
if err := json.Unmarshal(data, &tmp); err != nil {
|
||||
log.Err(err).
|
||||
Str("path", path).
|
||||
Msg("failed to load store")
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
@@ -66,7 +65,7 @@ func loadNS[T store](ns namespace) T {
|
||||
}
|
||||
} else {
|
||||
defer file.Close()
|
||||
if err := sonic.ConfigDefault.NewDecoder(file).Decode(&store); err != nil {
|
||||
if err := json.NewDecoder(file).Decode(&store); err != nil {
|
||||
log.Err(err).
|
||||
Str("path", path).
|
||||
Msg("failed to load store")
|
||||
@@ -84,7 +83,7 @@ func save() error {
|
||||
errs := gperr.NewBuilder("failed to save data stores")
|
||||
for ns, store := range stores {
|
||||
path := filepath.Join(storesPath, string(ns)+".json")
|
||||
if err := serialization.SaveFile(path, &store, 0o644, sonic.Marshal); err != nil {
|
||||
if err := serialization.SaveFile(path, &store, 0o644, json.Marshal); err != nil {
|
||||
errs.Add(err)
|
||||
}
|
||||
}
|
||||
@@ -114,12 +113,12 @@ func (s *MapStore[VT]) Initialize() {
|
||||
}
|
||||
|
||||
func (s MapStore[VT]) MarshalJSON() ([]byte, error) {
|
||||
return sonic.Marshal(xsync.ToPlainMap(s.Map))
|
||||
return json.Marshal(xsync.ToPlainMap(s.Map))
|
||||
}
|
||||
|
||||
func (s *MapStore[VT]) UnmarshalJSON(data []byte) error {
|
||||
tmp := make(map[string]VT)
|
||||
if err := sonic.Unmarshal(data, &tmp); err != nil {
|
||||
if err := json.Unmarshal(data, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Map = xsync.NewMap[string, VT](xsync.WithPresize(len(tmp)))
|
||||
@@ -135,10 +134,10 @@ func (obj *ObjectStore[Ptr]) Initialize() {
|
||||
}
|
||||
|
||||
func (obj ObjectStore[Ptr]) MarshalJSON() ([]byte, error) {
|
||||
return sonic.Marshal(obj.ptr)
|
||||
return json.Marshal(obj.ptr)
|
||||
}
|
||||
|
||||
func (obj *ObjectStore[Ptr]) UnmarshalJSON(data []byte) error {
|
||||
obj.Initialize()
|
||||
return sonic.Unmarshal(data, obj.ptr)
|
||||
return json.Unmarshal(data, obj.ptr)
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ package period
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
type Entries[T any] struct {
|
||||
@@ -75,7 +73,7 @@ type entriesJSON[T any] struct {
|
||||
}
|
||||
|
||||
func (e *Entries[T]) MarshalJSON() ([]byte, error) {
|
||||
return sonic.Marshal(entriesJSON[T]{
|
||||
return json.Marshal(entriesJSON[T]{
|
||||
Entries: e.Get(),
|
||||
Interval: e.interval,
|
||||
})
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/rs/zerolog/log"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/synk"
|
||||
@@ -98,7 +97,7 @@ func (p *Poller[T, AggregateT]) save() error {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = sonic.ConfigDefault.NewEncoder(f).Encode(p.period)
|
||||
err = json.NewEncoder(f).Encode(p.period)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -153,8 +152,8 @@ func (p *Poller[T, AggregateT]) pollWithTimeout(ctx context.Context) {
|
||||
p.lastResult.Store(data)
|
||||
}
|
||||
|
||||
func (p *Poller[T, AggregateT]) Start(parent task.Parent) {
|
||||
t := parent.Subtask("poller."+p.name, true)
|
||||
func (p *Poller[T, AggregateT]) Start() {
|
||||
t := task.RootTask("poller."+p.name, true)
|
||||
l := log.With().Str("name", p.name).Logger()
|
||||
err := p.load()
|
||||
if err != nil {
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
"github.com/shirou/gopsutil/v4/mem"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
@@ -82,12 +81,12 @@ var (
|
||||
|
||||
func TestSystemInfo(t *testing.T) {
|
||||
// Test marshaling
|
||||
data, err := sonic.Marshal(testInfo)
|
||||
data, err := json.Marshal(testInfo)
|
||||
expect.NoError(t, err)
|
||||
|
||||
// Test unmarshaling back
|
||||
var decoded SystemInfo
|
||||
err = sonic.Unmarshal(data, &decoded)
|
||||
err = json.Unmarshal(data, &decoded)
|
||||
expect.NoError(t, err)
|
||||
|
||||
// Compare original and decoded
|
||||
@@ -127,7 +126,7 @@ func TestSerialize(t *testing.T) {
|
||||
for _, query := range allQueries {
|
||||
t.Run(string(query), func(t *testing.T) {
|
||||
_, result := aggregate(entries, url.Values{"aggregate": []string{string(query)}})
|
||||
s, err := sonic.Marshal(result)
|
||||
s, err := json.Marshal(result)
|
||||
expect.NoError(t, err)
|
||||
var v []map[string]any
|
||||
expect.NoError(t, json.Unmarshal(s, &v))
|
||||
|
||||
@@ -2,23 +2,22 @@ package uptime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||
config "github.com/yusing/godoxy/internal/config/types"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/metrics/period"
|
||||
metricsutils "github.com/yusing/godoxy/internal/metrics/utils"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
)
|
||||
|
||||
type (
|
||||
StatusByAlias struct {
|
||||
Map map[string]types.HealthInfoWithoutDetail `json:"statuses"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Map map[string]routes.HealthInfoWithoutDetail `json:"statuses"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
} // @name RouteStatusesByAlias
|
||||
Status struct {
|
||||
Status types.HealthStatus `json:"status" swaggertype:"string" enums:"healthy,unhealthy,unknown,napping,starting"`
|
||||
@@ -42,13 +41,13 @@ var Poller = period.NewPoller("uptime", getStatuses, aggregateStatuses)
|
||||
|
||||
func getStatuses(ctx context.Context, _ StatusByAlias) (StatusByAlias, error) {
|
||||
return StatusByAlias{
|
||||
Map: entrypoint.FromCtx(ctx).GetHealthInfoWithoutDetail(),
|
||||
Map: routes.GetHealthInfoWithoutDetail(),
|
||||
Timestamp: time.Now().Unix(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Status) MarshalJSON() ([]byte, error) {
|
||||
return sonic.Marshal(map[string]any{
|
||||
return json.Marshal(map[string]any{
|
||||
"status": s.Status.String(),
|
||||
"latency": s.Latency,
|
||||
"timestamp": s.Timestamp,
|
||||
@@ -128,13 +127,11 @@ func (rs RouteStatuses) aggregate(limit int, offset int) Aggregated {
|
||||
up, down, idle, latency := rs.calculateInfo(statuses)
|
||||
|
||||
status := types.StatusUnknown
|
||||
if state := config.ActiveState.Load(); state != nil {
|
||||
r, ok := entrypoint.FromCtx(state.Context()).GetRoute(alias)
|
||||
if ok {
|
||||
mon := r.HealthMonitor()
|
||||
if mon != nil {
|
||||
status = mon.Status()
|
||||
}
|
||||
r, ok := routes.GetIncludeExcluded(alias)
|
||||
if ok {
|
||||
mon := r.HealthMonitor()
|
||||
if mon != nil {
|
||||
status = mon.Status()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,5 +149,5 @@ func (rs RouteStatuses) aggregate(limit int, offset int) Aggregated {
|
||||
}
|
||||
|
||||
func (result Aggregated) MarshalJSON() ([]byte, error) {
|
||||
return sonic.Marshal([]RouteAggregate(result))
|
||||
return json.Marshal([]RouteAggregate(result))
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ func TestEntrypointBypassRoute(t *testing.T) {
|
||||
expect.NoError(t, err)
|
||||
|
||||
expect.NoError(t, err)
|
||||
entry := entrypoint.NewEntrypoint(task.NewTestTask(t), nil)
|
||||
entry := entrypoint.NewEntrypoint()
|
||||
r := &route.Route{
|
||||
Alias: "test-route",
|
||||
Host: host,
|
||||
@@ -260,11 +260,7 @@ func TestEntrypointBypassRoute(t *testing.T) {
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "http://test-route.example.com", nil)
|
||||
server, ok := entry.GetServer(r.ListenURL().Host)
|
||||
if !ok {
|
||||
t.Fatal("server not found")
|
||||
}
|
||||
server.ServeHTTP(recorder, req)
|
||||
entry.ServeHTTP(recorder, req)
|
||||
expect.Equal(t, recorder.Code, http.StatusOK, "should bypass http redirect")
|
||||
expect.Equal(t, recorder.Body.String(), "test")
|
||||
expect.Equal(t, recorder.Header().Get("Test-Header"), "test-value")
|
||||
|
||||
@@ -3,6 +3,7 @@ package captcha
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -11,7 +12,6 @@ import (
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
@@ -70,7 +70,7 @@ func (p *HcaptchaProvider) Verify(r *http.Request) error {
|
||||
Success bool `json:"success"`
|
||||
Error []string `json:"error-codes"`
|
||||
}
|
||||
if err := sonic.ConfigDefault.NewDecoder(resp.Body).Decode(&respData); err != nil {
|
||||
if err := json.NewDecoder(resp.Body).Decode(&respData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
httputils "github.com/yusing/goutils/http"
|
||||
ioutils "github.com/yusing/goutils/io"
|
||||
)
|
||||
@@ -66,7 +66,7 @@ func (m *crowdsecMiddleware) finalize() error {
|
||||
// before implements RequestModifier.
|
||||
func (m *crowdsecMiddleware) before(w http.ResponseWriter, r *http.Request) (proceed bool) {
|
||||
// Build CrowdSec URL
|
||||
crowdsecURL, err := m.buildCrowdSecURL(r.Context())
|
||||
crowdsecURL, err := m.buildCrowdSecURL()
|
||||
if err != nil {
|
||||
Crowdsec.LogError(r).Err(err).Msg("failed to build CrowdSec URL")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
@@ -167,10 +167,10 @@ func (m *crowdsecMiddleware) before(w http.ResponseWriter, r *http.Request) (pro
|
||||
}
|
||||
|
||||
// buildCrowdSecURL constructs the CrowdSec server URL based on route or IP configuration
|
||||
func (m *crowdsecMiddleware) buildCrowdSecURL(ctx context.Context) (string, error) {
|
||||
func (m *crowdsecMiddleware) buildCrowdSecURL() (string, error) {
|
||||
// Try to get route first
|
||||
if m.Route != "" {
|
||||
if route, ok := entrypoint.FromCtx(ctx).GetRoute(m.Route); ok {
|
||||
if route, ok := routes.HTTP.Get(m.Route); ok {
|
||||
// Using route name
|
||||
targetURL := *route.TargetURL()
|
||||
targetURL.Path = m.Endpoint
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
httputils "github.com/yusing/goutils/http"
|
||||
"github.com/yusing/goutils/http/httpheaders"
|
||||
)
|
||||
@@ -46,7 +46,7 @@ func (m *forwardAuthMiddleware) setup() {
|
||||
|
||||
// before implements RequestModifier.
|
||||
func (m *forwardAuthMiddleware) before(w http.ResponseWriter, r *http.Request) (proceed bool) {
|
||||
route, ok := entrypoint.FromCtx(r.Context()).HTTPRoutes().Get(m.Route)
|
||||
route, ok := routes.HTTP.Get(m.Route)
|
||||
if !ok {
|
||||
ForwardAuth.LogWarn(r).Str("route", m.Route).Msg("forwardauth route not found")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"maps"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
@@ -152,7 +152,7 @@ func (m *Middleware) MarshalJSON() ([]byte, error) {
|
||||
commonOptions
|
||||
any
|
||||
}
|
||||
return sonic.MarshalIndent(map[string]any{
|
||||
return json.MarshalIndent(map[string]any{
|
||||
"name": m.name,
|
||||
"options": allOptions{
|
||||
commonOptions: m.commonOptions,
|
||||
|
||||
@@ -3,12 +3,12 @@ package middleware
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
@@ -24,7 +24,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
tmp := map[string]string{}
|
||||
err := sonic.Unmarshal(testHeadersRaw, &tmp)
|
||||
err := json.Unmarshal(testHeadersRaw, &tmp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package nettypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
urlPkg "net/url"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
type URL struct {
|
||||
@@ -47,7 +46,7 @@ func (u *URL) MarshalJSON() (text []byte, err error) {
|
||||
if u == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return sonic.Marshal(u.URL.String())
|
||||
return json.Marshal(u.URL.String())
|
||||
}
|
||||
|
||||
func (u *URL) Equals(other *URL) bool {
|
||||
|
||||
@@ -2,9 +2,9 @@ package notif
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
@@ -69,7 +69,7 @@ func (f FieldsBody) Format(format LogFormat) ([]byte, error) {
|
||||
}
|
||||
return msg.Bytes(), nil
|
||||
case LogFormatRawJSON:
|
||||
return sonic.Marshal(f)
|
||||
return json.Marshal(f)
|
||||
}
|
||||
return f.Format(LogFormatMarkdown)
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func (l ListBody) Format(format LogFormat) ([]byte, error) {
|
||||
}
|
||||
return msg.Bytes(), nil
|
||||
case LogFormatRawJSON:
|
||||
return sonic.Marshal(l)
|
||||
return json.Marshal(l)
|
||||
}
|
||||
return l.Format(LogFormatMarkdown)
|
||||
}
|
||||
@@ -97,7 +97,7 @@ func (m MessageBody) Format(format LogFormat) ([]byte, error) {
|
||||
case LogFormatPlain, LogFormatMarkdown:
|
||||
return []byte(m), nil
|
||||
case LogFormatRawJSON:
|
||||
return sonic.Marshal(m)
|
||||
return json.Marshal(m)
|
||||
}
|
||||
return []byte(m), nil
|
||||
}
|
||||
@@ -105,7 +105,7 @@ func (m MessageBody) Format(format LogFormat) ([]byte, error) {
|
||||
func (m MessageBodyBytes) Format(format LogFormat) ([]byte, error) {
|
||||
switch format {
|
||||
case LogFormatRawJSON:
|
||||
return sonic.Marshal(string(m))
|
||||
return json.Marshal(string(m))
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@@ -113,7 +113,7 @@ func (m MessageBodyBytes) Format(format LogFormat) ([]byte, error) {
|
||||
func (e errorBody) Format(format LogFormat) ([]byte, error) {
|
||||
switch format {
|
||||
case LogFormatRawJSON:
|
||||
return sonic.Marshal(e.Error)
|
||||
return json.Marshal(e.Error)
|
||||
case LogFormatPlain:
|
||||
return gperr.Plain(e.Error), nil
|
||||
case LogFormatMarkdown:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package notif
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/gotify/server/v2/model"
|
||||
"github.com/rs/zerolog"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
@@ -65,7 +65,7 @@ func (client *GotifyClient) MarshalMessage(logMsg *LogMessage) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
data, err := sonic.Marshal(msg)
|
||||
data, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -76,7 +76,7 @@ func (client *GotifyClient) MarshalMessage(logMsg *LogMessage) ([]byte, error) {
|
||||
// fmtError implements Provider.
|
||||
func (client *GotifyClient) fmtError(respBody io.Reader) error {
|
||||
var errm model.Error
|
||||
err := sonic.ConfigDefault.NewDecoder(respBody).Decode(&errm)
|
||||
err := json.NewDecoder(respBody).Decode(&errm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode err response: %w", err)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package notif
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
@@ -99,7 +99,7 @@ func (webhook *Webhook) fmtError(respBody io.Reader) error {
|
||||
}
|
||||
|
||||
func (webhook *Webhook) MarshalMessage(logMsg *LogMessage) ([]byte, error) {
|
||||
title, err := sonic.Marshal(logMsg.Title)
|
||||
title, err := json.Marshal(logMsg.Title)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func (webhook *Webhook) MarshalMessage(logMsg *LogMessage) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
if webhook.MIMEType == MimeTypeJSON {
|
||||
message, err = sonic.Marshal(string(message))
|
||||
message, err = json.Marshal(string(message))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -147,5 +147,5 @@ func validateJSONPayload(payload string) bool {
|
||||
"$color", "",
|
||||
)
|
||||
payload = replacer.Replace(payload)
|
||||
return sonic.Valid([]byte(payload))
|
||||
return json.Valid([]byte(payload))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package proxmox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -12,7 +13,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/luthermonson/go-proxmox"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
@@ -201,7 +201,7 @@ func (c *Client) Name() string {
|
||||
}
|
||||
|
||||
func (c *Client) MarshalJSON() ([]byte, error) {
|
||||
return sonic.Marshal(map[string]any{
|
||||
return json.Marshal(map[string]any{
|
||||
"version": c.Version,
|
||||
"cluster": map[string]any{
|
||||
"name": c.Cluster.Name,
|
||||
|
||||
@@ -2,10 +2,10 @@ package proxmox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/pool"
|
||||
)
|
||||
@@ -82,7 +82,7 @@ func (n *Node) String() string {
|
||||
}
|
||||
|
||||
func (n *Node) MarshalJSON() ([]byte, error) {
|
||||
return sonic.Marshal(map[string]any{
|
||||
return json.Marshal(map[string]any{
|
||||
"name": n.name,
|
||||
"id": n.id,
|
||||
})
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
// checkExists checks if the route already exists in the entrypoint.
|
||||
//
|
||||
// Context must be passed from the parent task that carries the entrypoint value.
|
||||
func checkExists(ctx context.Context, r types.Route) gperr.Error {
|
||||
func checkExists(r types.Route) gperr.Error {
|
||||
if r.UseLoadBalance() { // skip checking for load balanced routes
|
||||
return nil
|
||||
}
|
||||
@@ -21,9 +16,9 @@ func checkExists(ctx context.Context, r types.Route) gperr.Error {
|
||||
)
|
||||
switch r := r.(type) {
|
||||
case types.HTTPRoute:
|
||||
existing, ok = entrypoint.FromCtx(ctx).HTTPRoutes().Get(r.Key())
|
||||
existing, ok = routes.HTTP.Get(r.Key())
|
||||
case types.StreamRoute:
|
||||
existing, ok = entrypoint.FromCtx(ctx).StreamRoutes().Get(r.Key())
|
||||
existing, ok = routes.Stream.Get(r.Key())
|
||||
}
|
||||
if ok {
|
||||
return gperr.Errorf("route already exists: from provider %s and %s", existing.ProviderName(), r.ProviderName())
|
||||
|
||||
@@ -6,12 +6,12 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
config "github.com/yusing/godoxy/internal/config/types"
|
||||
"github.com/yusing/godoxy/internal/health/monitor"
|
||||
"github.com/yusing/godoxy/internal/logging/accesslog"
|
||||
gphttp "github.com/yusing/godoxy/internal/net/gphttp"
|
||||
"github.com/yusing/godoxy/internal/net/gphttp/middleware"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/task"
|
||||
@@ -120,19 +120,20 @@ func (s *FileServer) Start(parent task.Parent) gperr.Error {
|
||||
if s.UseHealthCheck() {
|
||||
s.HealthMon = monitor.NewMonitor(s)
|
||||
if err := s.HealthMon.Start(s.task); err != nil {
|
||||
l := log.With().Str("type", "fileserver").Str("name", s.Name()).Logger()
|
||||
gperr.LogWarn("health monitor error", err, &l)
|
||||
s.HealthMon = nil
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ep := entrypoint.FromCtx(parent.Context())
|
||||
if ep == nil {
|
||||
err := gperr.New("entrypoint not initialized")
|
||||
s.task.Finish(err)
|
||||
return err
|
||||
routes.HTTP.Add(s)
|
||||
if state := config.WorkingState.Load(); state != nil {
|
||||
state.ShortLinkMatcher().AddRoute(s.Alias)
|
||||
}
|
||||
ep.AddRoute(s)
|
||||
s.task.OnFinished("remove_route_from_http", func() {
|
||||
routes.HTTP.Del(s)
|
||||
if state := config.WorkingState.Load(); state != nil {
|
||||
state.ShortLinkMatcher().DelRoute(s.Alias)
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ package provider
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
expect "github.com/yusing/goutils/testing"
|
||||
@@ -26,7 +26,7 @@ func TestParseDockerLabels(t *testing.T) {
|
||||
Names: []string{"container"},
|
||||
Labels: labels,
|
||||
State: "running",
|
||||
Ports: []container.PortSummary{
|
||||
Ports: []container.Port{
|
||||
{Type: "tcp", PrivatePort: 1234, PublicPort: 1234},
|
||||
},
|
||||
}, types.DockerProviderConfig{URL: "unix:///var/run/docker.sock"}),
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
D "github.com/yusing/godoxy/internal/docker"
|
||||
"github.com/yusing/godoxy/internal/route"
|
||||
routeTypes "github.com/yusing/godoxy/internal/route/types"
|
||||
@@ -276,7 +275,7 @@ func TestPrivateIPLocalhost(t *testing.T) {
|
||||
NetworkSettings: &container.NetworkSettingsSummary{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
"network": {
|
||||
IPAddress: netip.MustParseAddr(testDockerIP),
|
||||
IPAddress: testDockerIP,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -294,7 +293,7 @@ func TestPrivateIPRemote(t *testing.T) {
|
||||
NetworkSettings: &container.NetworkSettingsSummary{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
"network": {
|
||||
IPAddress: netip.MustParseAddr(testDockerIP),
|
||||
IPAddress: testDockerIP,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -316,11 +315,11 @@ func TestStreamDefaultValues(t *testing.T) {
|
||||
NetworkSettings: &container.NetworkSettingsSummary{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
"network": {
|
||||
IPAddress: netip.MustParseAddr(privIP),
|
||||
IPAddress: privIP,
|
||||
},
|
||||
},
|
||||
},
|
||||
Ports: []container.PortSummary{
|
||||
Ports: []container.Port{
|
||||
{Type: "udp", PrivatePort: privPort, PublicPort: pubPort},
|
||||
},
|
||||
}
|
||||
@@ -373,7 +372,7 @@ func TestImplicitExcludeDatabase(t *testing.T) {
|
||||
t.Run("exposed port detection", func(t *testing.T) {
|
||||
r, ok := makeRoutes(&container.Summary{
|
||||
Names: dummyNames,
|
||||
Ports: []container.PortSummary{
|
||||
Ports: []container.Port{
|
||||
{Type: "tcp", PrivatePort: 5432, PublicPort: 5432},
|
||||
},
|
||||
})["a"]
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
"github.com/yusing/godoxy/agent/pkg/agentproxy"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
config "github.com/yusing/godoxy/internal/config/types"
|
||||
"github.com/yusing/godoxy/internal/health/monitor"
|
||||
"github.com/yusing/godoxy/internal/idlewatcher"
|
||||
"github.com/yusing/godoxy/internal/logging/accesslog"
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/yusing/godoxy/internal/net/gphttp/loadbalancer"
|
||||
"github.com/yusing/godoxy/internal/net/gphttp/middleware"
|
||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
route "github.com/yusing/godoxy/internal/route/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
@@ -158,22 +159,23 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
||||
|
||||
if r.HealthMon != nil {
|
||||
if err := r.HealthMon.Start(r.task); err != nil {
|
||||
gperr.LogWarn("health monitor error", err, &r.rp.Logger)
|
||||
r.HealthMon = nil
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ep := entrypoint.FromCtx(parent.Context())
|
||||
if ep == nil {
|
||||
err := gperr.New("entrypoint not initialized")
|
||||
r.task.Finish(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if r.UseLoadBalance() {
|
||||
r.addToLoadBalancer(parent, ep)
|
||||
r.addToLoadBalancer(parent)
|
||||
} else {
|
||||
ep.AddRoute(r)
|
||||
routes.HTTP.Add(r)
|
||||
if state := config.WorkingState.Load(); state != nil {
|
||||
state.ShortLinkMatcher().AddRoute(r.Alias)
|
||||
}
|
||||
r.task.OnCancel("remove_route", func() {
|
||||
routes.HTTP.Del(r)
|
||||
if state := config.WorkingState.Load(); state != nil {
|
||||
state.ShortLinkMatcher().DelRoute(r.Alias)
|
||||
}
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -185,12 +187,12 @@ func (r *ReveseProxyRoute) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
var lbLock sync.Mutex
|
||||
|
||||
func (r *ReveseProxyRoute) addToLoadBalancer(parent task.Parent, ep entrypoint.Entrypoint) {
|
||||
func (r *ReveseProxyRoute) addToLoadBalancer(parent task.Parent) {
|
||||
var lb *loadbalancer.LoadBalancer
|
||||
cfg := r.LoadBalance
|
||||
lbLock.Lock()
|
||||
|
||||
l, ok := ep.HTTPRoutes().Get(cfg.Link)
|
||||
l, ok := routes.HTTP.Get(cfg.Link)
|
||||
var linked *ReveseProxyRoute
|
||||
if ok {
|
||||
lbLock.Unlock()
|
||||
@@ -212,7 +214,16 @@ func (r *ReveseProxyRoute) addToLoadBalancer(parent task.Parent, ep entrypoint.E
|
||||
handler: lb,
|
||||
}
|
||||
linked.SetHealthMonitor(lb)
|
||||
ep.AddRoute(linked)
|
||||
routes.HTTP.AddKey(cfg.Link, linked)
|
||||
if state := config.WorkingState.Load(); state != nil {
|
||||
state.ShortLinkMatcher().AddRoute(cfg.Link)
|
||||
}
|
||||
r.task.OnFinished("remove_loadbalancer_route", func() {
|
||||
routes.HTTP.DelKey(cfg.Link)
|
||||
if state := config.WorkingState.Load(); state != nil {
|
||||
state.ShortLinkMatcher().DelRoute(cfg.Link)
|
||||
}
|
||||
})
|
||||
lbLock.Unlock()
|
||||
}
|
||||
r.loadBalancer = lb
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user