Compare commits

...

15 Commits
0.9 ... 0.9.2

Author SHA1 Message Date
yusing
f997423fd7 fix error formatting 2025-02-04 07:04:49 +08:00
yusing
1871ef3d38 clearer error message when config reload failed 2025-02-04 07:04:27 +08:00
yusing
7c56c88dd4 fix server not being restarted after config reload 2025-02-04 07:04:15 +08:00
yusing
4d7422dd90 adjusted and simplified default config and compose.yml 2025-02-04 07:04:05 +08:00
yusing
eccabc0588 remove incorrectly added pnpn lockfile 2025-02-04 07:04:05 +08:00
yusing
0c7b188587 api: fix search icon returning null when no match 2025-02-02 03:31:52 +08:00
yusing
4c97b79adf log prometheus enabled 2025-02-02 03:21:39 +08:00
yusing
8ae9573b07 add timeout to notification context 2025-02-01 14:42:21 +08:00
yusing
43fce6e739 fix two tests 2025-02-01 14:41:22 +08:00
Yuzerion
78900772bb Feat/ntfy (#57)
* implement ntfy notification

* fix notification fields order

* fix schema for ntfy

---------

Co-authored-by: yusing <yusing@6uo.me>
2025-02-01 13:07:44 +08:00
yusing
c16a0444ca fix main.go and update next release doc 2025-02-01 12:51:52 +08:00
yusing
0d518166ee api: move prometheus handler inside api handler /v1/metrics 2025-02-01 02:09:43 +08:00
yusing
6ae391a3c9 make POST and JSON as notification defaults 2025-01-31 14:56:55 +08:00
yusing
357897a0cd remove schema stuff from code 2025-01-31 05:21:32 +08:00
yusing
10a0a8fe09 update readme 2025-01-31 03:33:20 +08:00
35 changed files with 267 additions and 761 deletions

View File

@@ -42,8 +42,8 @@ GODOXY_HTTPS_ADDR=:443
# API listening address
GODOXY_API_ADDR=127.0.0.1:8888
# Prometheus Metrics listening address (uncomment to enable)
#GODOXY_PROMETHEUS_ADDR=:8889
# Prometheus Metrics
GODOXY_PROMETHEUS_ENABLED=true
# Debug mode
GODOXY_DEBUG=false

View File

@@ -1,10 +1,10 @@
{
"yaml.schemas": {
"https://github.com/yusing/go-proxy/raw/v0.8/schemas/config.schema.json": [
"https://github.com/yusing/go-proxy/raw/v0.9/schemas/config.schema.json": [
"config.example.yml",
"config.yml"
],
"https://github.com/yusing/go-proxy/raw/v0.8/schemas/routes.schema.json": [
"https://github.com/yusing/go-proxy/raw/v0.9/schemas/routes.schema.json": [
"providers.example.yml"
]
}

View File

@@ -11,9 +11,7 @@
A lightweight, easy-to-use, and [performant](https://github.com/yusing/go-proxy/wiki/Benchmarks) reverse proxy with a Web UI and dashboard.
**v0.9 will be out soon, with a brand new [WebUI](next-release.md)!!! Below one is the old one**
![Screenshot](screenshots/webui.png)
![Screenshot](https://github.com/user-attachments/assets/4bb371f4-6e4c-425c-89b2-b9e962bdd46f)
_Join our [Discord](https://discord.gg/umReR62nRd) for help and discussions_
@@ -45,6 +43,7 @@ _Join our [Discord](https://discord.gg/umReR62nRd) for help and discussions_
- Auto hot-reload on container state / config file changes
- **idlesleeper**: stop containers on idle, wake it up on traffic _(optional, see [screenshots](#idlesleeper))_
- HTTP(s) reserve proxy
- OpenID Connect support
- [HTTP middleware support](https://github.com/yusing/go-proxy/wiki/Middlewares)
- [Custom error pages support](https://github.com/yusing/go-proxy/wiki/Middlewares#custom-error-pages)
- TCP and UDP port forwarding
@@ -79,7 +78,7 @@ Setup DNS Records point to machine which runs `GoDoxy`, e.g.
docker run --rm -v .:/setup ghcr.io/yusing/go-proxy /app/godoxy setup
```
3. _(Optional)_ setup WebUI login
3. _(Optional)_ setup WebUI login (skip if you use OIDC)
- set random JWT secret
@@ -99,9 +98,7 @@ Setup DNS Records point to machine which runs `GoDoxy`, e.g.
5. Start the container `docker compose up -d`
6. You may now do some extra configuration
- With text editor (e.g. Visual Studio Code)
- With Web UI via `https://gp.y.z`
6. You may now do some extra configuration on WebUI `https://gp.y.z`
[🔼Back to top](#table-of-content)
@@ -109,15 +106,15 @@ Setup DNS Records point to machine which runs `GoDoxy`, e.g.
1. Make `config` directory then grab `config.example.yml` into `config/config.yml`
`mkdir -p config && wget https://raw.githubusercontent.com/yusing/go-proxy/v0.8/config.example.yml -O config/config.yml`
`mkdir -p config && wget https://raw.githubusercontent.com/yusing/go-proxy/v0.9/config.example.yml -O config/config.yml`
2. Grab `.env.example` into `.env`
`wget https://raw.githubusercontent.com/yusing/go-proxy/v0.8/.env.example -O .env`
`wget https://raw.githubusercontent.com/yusing/go-proxy/v0.9/.env.example -O .env`
3. Grab `compose.example.yml` into `compose.yml`
`wget https://raw.githubusercontent.com/yusing/go-proxy/v0.8/compose.example.yml -O compose.yml`
`wget https://raw.githubusercontent.com/yusing/go-proxy/v0.9/compose.example.yml -O compose.yml`
### Folder structrue

View File

@@ -107,15 +107,15 @@ _加入我們的 [Discord](https://discord.gg/umReR62nRd) 獲取幫助和討論_
1. 建立 `config` 目錄,然後將 `config.example.yml` 下載到 `config/config.yml`
`mkdir -p config && wget https://raw.githubusercontent.com/yusing/go-proxy/v0.8/config.example.yml -O config/config.yml`
`mkdir -p config && wget https://raw.githubusercontent.com/yusing/go-proxy/v0.9/config.example.yml -O config/config.yml`
2. 將 `.env.example` 下載到 `.env`
`wget https://raw.githubusercontent.com/yusing/go-proxy/v0.8/.env.example -O .env`
`wget https://raw.githubusercontent.com/yusing/go-proxy/v0.9/.env.example -O .env`
3. 將 `compose.example.yml` 下載到 `compose.yml`
`wget https://raw.githubusercontent.com/yusing/go-proxy/v0.8/compose.example.yml -O compose.yml`
`wget https://raw.githubusercontent.com/yusing/go-proxy/v0.9/compose.example.yml -O compose.yml`
### 資料夾結構

View File

@@ -128,8 +128,7 @@ func main() {
}
cfg.Start(&config.StartServersOptions{
Proxy: true,
Metrics: true,
Proxy: true,
})
if err := auth.Initialize(); err != nil {
logging.Fatal().Err(err).Msg("failed to initialize authentication")

View File

@@ -28,15 +28,13 @@ services:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./config:/app/config
- ./logs:/app/logs
- ./error_pages:/app/error_pages
# (Optional) choose one of below to enable https
# 1. use existing certificate
# To use autocert, certs will be stored in "./certs".
# You can also use a docker volume to store it
- ./certs:/app/certs
# remove "./certs:/app/certs" and uncomment below to use existing certificate
# - /path/to/certs/cert.crt:/app/certs/cert.crt
# - /path/to/certs/priv.key:/app/certs/priv.key
# 2. use autocert, certs will be stored in ./certs
# you can also use a docker volume to store it
# - ./certs:/app/certs

View File

@@ -1,78 +1,42 @@
# Autocert (choose one below and uncomment to enable)
#
# 1. use existing cert
#
# autocert:
# provider: local
#
# cert_path: certs/cert.crt # optional, uncomment only if you need to change it
# key_path: certs/priv.key # optional, uncomment only if you need to change it
#
# 2. cloudflare
#
# autocert:
# provider: cloudflare
# email: abc@gmail.com # ACME Email
# domains: # a list of domains for cert registration
# - "*.y.z" # remember to use double quotes to surround wildcard domain
# email: abc@gmail.com # ACME Email
# domains: # a list of domains for cert registration
# - "*.domain.com"
# - "domain.com"
# options:
# auth_token: c1234565789-abcdefghijklmnopqrst # your zone API token
#
# 3. other providers, check docs/dns_providers.md for more
# auth_token: c1234565789-abcdefghijklmnopqrst # your zone API token
# 3. other providers, see https://github.com/yusing/go-proxy/wiki/Supported-DNS%E2%80%9001-Providers#supported-dns-01-providers
entrypoint:
middlewares:
# this part blocks all non-LAN HTTP traffic
# remove if you don't want this
- use: CIDRWhitelist
allow:
- "127.0.0.1"
- "10.0.0.0/8"
- "172.16.0.0/12"
- "192.168.0.0/16"
status: 403
message: "Forbidden"
# end of CIDRWhitelist
# Below define an example of middleware config
# 1. block non local IP connections
# 2. redirect HTTP to HTTPS
#
# middlewares:
# - use: CIDRWhitelist
# allow:
# - "127.0.0.1"
# - "10.0.0.0/8"
# - "172.16.0.0/12"
# - "192.168.0.0/16"
# status: 403
# message: "Forbidden"
# - use: RedirectHTTP
# this part redirects HTTP to HTTPS
# remove if you don't want this
- use: RedirectHTTP
# access_log:
# buffer_size: 1024
# path: /var/log/example.log
# filters:
# status_codes:
# values:
# - 200-299
# - 101
# method:
# values:
# - GET
# host:
# values:
# - example.y.z
# headers:
# negative: true
# values:
# - foo=bar
# - baz
# cidr:
# values:
# - 192.168.10.0/24
# fields:
# headers:
# default: keep
# config:
# foo: redact
# query:
# default: drop
# config:
# foo: keep
# cookies:
# default: redact
# config:
# foo: keep
# below enables access log
access_log:
format: combined
path: /app/logs/entrypoint.log
providers:
# include files are standalone yaml files under `config/` directory
@@ -84,6 +48,7 @@ providers:
docker:
# $DOCKER_HOST implies environment variable `DOCKER_HOST` or unix:///var/run/docker.sock by default
local: $DOCKER_HOST
# explicit only mode
# only containers with explicit aliases will be proxied
# add "!" after provider name to enable explicit only mode
@@ -106,28 +71,10 @@ providers:
# - name: discord
# provider: webhook
# url: https://discord.com/api/webhooks/...
# template: discord
# # payload: | # discord template implies the following
# # {
# # "embeds": [
# # {
# # "title": $title,
# # "fields": $fields,
# # "color": "$color"
# # }
# # ]
# # }
# if match_domains not defined
# any host = alias+[any domain] will match
# i.e. https://app1.y.z will match alias app1 for any domain y.z
# but https://app1.node1.y.z will only match alias "app.node1"
#
# if match_domains defined
# only host = alias+[one of match_domains] will match
# i.e. match_domains = [node1.my.app, my.site]
# https://app1.my.app, https://app1.my.net, etc. will not match even if app1 exists
# only https://*.node1.my.app and https://*.my.site will match
#
# template: discord # this means use payload template from internal/notif/templates/discord.json
# Check https://github.com/yusing/go-proxy/wiki/Certificates-and-domain-matching#domain-matching
# for explaination of `match_domains`
#
# match_domains:
# - my.site

View File

@@ -3,10 +3,13 @@ package api
import (
"net/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
v1 "github.com/yusing/go-proxy/internal/api/v1"
"github.com/yusing/go-proxy/internal/api/v1/auth"
"github.com/yusing/go-proxy/internal/api/v1/favicon"
"github.com/yusing/go-proxy/internal/common"
config "github.com/yusing/go-proxy/internal/config/types"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
@@ -36,6 +39,11 @@ func NewHandler(cfg config.ConfigInstance) http.Handler {
mux.HandleFunc("GET", "/v1/favicon", auth.RequireAuth(favicon.GetFavIcon))
mux.HandleFunc("POST", "/v1/homepage/set", auth.RequireAuth(v1.SetHomePageOverrides))
if common.PrometheusEnabled {
mux.Handle("GET /v1/metrics", promhttp.Handler())
logging.Info().Msg("prometheus metrics enabled")
}
defaultAuth := auth.GetDefaultAuth()
if defaultAuth != nil {
mux.HandleFunc("GET", "/v1/auth/redirect", defaultAuth.RedirectLoginPage)

View File

@@ -71,6 +71,9 @@ func List(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
U.RespondError(w, err)
return
}
if icons == nil {
icons = []string{}
}
U.RespondJSON(w, r, icons)
case ListTasks:
U.RespondJSON(w, r, task.DebugTaskList())

View File

@@ -26,10 +26,6 @@ const (
MiddlewareComposeBasePath = ConfigBasePath + "/middlewares"
SchemasBasePath = "schemas"
ConfigSchemaPath = SchemasBasePath + "/config.schema.json"
FileProviderSchemaPath = SchemasBasePath + "/providers.schema.json"
ComposeFileName = "compose.yml"
ComposeExampleFileName = "compose.example.yml"
@@ -38,7 +34,6 @@ const (
var RequiredDirectories = []string{
ConfigBasePath,
SchemasBasePath,
ErrorPagesBasePath,
MiddlewareComposeBasePath,
}

View File

@@ -38,11 +38,7 @@ var (
APIHTTPPort,
APIHTTPURL = GetAddrEnv("API_ADDR", "127.0.0.1:8888", "http")
MetricsHTTPAddr,
MetricsHTTPHost,
MetricsHTTPPort,
MetricsHTTPURL = GetAddrEnv("PROMETHEUS_ADDR", "", "http")
PrometheusEnabled = MetricsHTTPURL != ""
PrometheusEnabled = GetEnvBool("PROMETHEUS_ENABLED", false)
APIJWTSecret = decodeJWTKey(GetEnvString("API_JWT_SECRET", ""))
APIJWTTokenTTL = GetDurationEnv("API_JWT_TOKEN_TTL", time.Hour)

View File

@@ -15,7 +15,6 @@ import (
"github.com/yusing/go-proxy/internal/entrypoint"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/metrics"
"github.com/yusing/go-proxy/internal/net/http/server"
"github.com/yusing/go-proxy/internal/notif"
proxy "github.com/yusing/go-proxy/internal/route/provider"
@@ -104,7 +103,6 @@ func OnConfigChange(ev []events.Event) {
}
if err := Reload(); err != nil {
logging.Warn().Msg("using last config")
// recovered in event queue
panic(err)
}
@@ -119,14 +117,14 @@ func Reload() E.Error {
err := newCfg.load()
if err != nil {
newCfg.task.Finish(err)
return err
return E.New("using last config").With(err)
}
// cancel all current subtasks -> wait
// -> replace config -> start new subtasks
instance.task.Finish("config changed")
instance = newCfg
instance.Start()
instance.Start(StartAllServers)
return nil
}
@@ -182,9 +180,11 @@ func (cfg *Config) StartProxyProviders() {
}
type StartServersOptions struct {
Proxy, API, Metrics bool
Proxy, API bool
}
var StartAllServers = &StartServersOptions{true, true}
func (cfg *Config) StartServers(opts ...*StartServersOptions) {
if len(opts) == 0 {
opts = append(opts, &StartServersOptions{})
@@ -207,14 +207,6 @@ func (cfg *Config) StartServers(opts ...*StartServersOptions) {
Handler: api.NewHandler(cfg),
})
}
if opt.Metrics && common.PrometheusEnabled {
server.StartServer(cfg.task, server.Options{
Name: "metrics",
CertProvider: cfg.AutoCertProvider(),
HTTPAddr: common.MetricsHTTPAddr,
Handler: metrics.NewHandler(),
})
}
}
func (cfg *Config) load() E.Error {

View File

@@ -75,8 +75,10 @@ func (err *nestedError) Error() string {
lines := make([]string, 0, 1+len(err.Extras))
if err.Err != nil {
lines = append(lines, makeLine(err.Err.Error(), 0))
lines = append(lines, makeLines(err.Extras, 1)...)
} else {
lines = append(lines, makeLines(err.Extras, 0)...)
}
lines = append(lines, makeLines(err.Extras, 1)...)
return strutils.JoinLines(lines)
}

View File

@@ -4,14 +4,13 @@ import (
"testing"
"github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
loadbalance "github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
. "github.com/yusing/go-proxy/internal/utils/testing"
)
func TestRebalance(t *testing.T) {
t.Parallel()
t.Run("zero", func(t *testing.T) {
lb := New(new(loadbalance.Config))
lb := New(new(types.Config))
for range 10 {
lb.AddServer(types.TestNewServer(0))
}
@@ -19,7 +18,7 @@ func TestRebalance(t *testing.T) {
ExpectEqual(t, lb.sumWeight, maxWeight)
})
t.Run("less", func(t *testing.T) {
lb := New(new(loadbalance.Config))
lb := New(new(types.Config))
lb.AddServer(types.TestNewServer(float64(maxWeight) * .1))
lb.AddServer(types.TestNewServer(float64(maxWeight) * .2))
lb.AddServer(types.TestNewServer(float64(maxWeight) * .3))
@@ -30,7 +29,7 @@ func TestRebalance(t *testing.T) {
ExpectEqual(t, lb.sumWeight, maxWeight)
})
t.Run("more", func(t *testing.T) {
lb := New(new(loadbalance.Config))
lb := New(new(types.Config))
lb.AddServer(types.TestNewServer(float64(maxWeight) * .1))
lb.AddServer(types.TestNewServer(float64(maxWeight) * .2))
lb.AddServer(types.TestNewServer(float64(maxWeight) * .3))

View File

@@ -1,6 +1,8 @@
package notif
import (
"io"
"net/http"
"net/url"
"strings"
@@ -45,3 +47,23 @@ func (base *ProviderBase) GetURL() string {
func (base *ProviderBase) GetToken() string {
return base.Token
}
func (base *ProviderBase) GetMethod() string {
return http.MethodPost
}
func (base *ProviderBase) GetMIMEType() string {
return "application/json"
}
func (base *ProviderBase) SetHeaders(logMsg *LogMessage, headers http.Header) {
// no-op by default
}
func (base *ProviderBase) makeRespError(resp *http.Response) error {
body, err := io.ReadAll(resp.Body)
if err == nil {
return E.Errorf("%s status %d: %s", base.Name, resp.StatusCode, body)
}
return E.Errorf("%s status %d", base.Name, resp.StatusCode)
}

View File

@@ -5,9 +5,9 @@ import "fmt"
type Color uint
const (
Red Color = 0xff0000
Green Color = 0x00ff00
Blue Color = 0x0000ff
ColorError Color = 0xff0000
ColorSuccess Color = 0x00ff00
ColorInfo Color = 0x0000ff
)
func (c Color) HexString() string {

View File

@@ -38,6 +38,8 @@ func (cfg *NotificationConfig) UnmarshalMap(m map[string]any) (err E.Error) {
cfg.Provider = &Webhook{}
case ProviderGotify:
cfg.Provider = &GotifyClient{}
case ProviderNtfy:
cfg.Provider = &Ntfy{}
default:
return ErrUnknownNotifProvider.
Subject(cfg.ProviderName).

View File

@@ -14,10 +14,15 @@ type (
logCh chan *LogMessage
providers F.Set[Provider]
}
LogField struct {
Name string `json:"name"`
Value string `json:"value"`
}
LogFields []LogField
LogMessage struct {
Level zerolog.Level
Title string
Extras map[string]any
Extras LogFields
Color Color
}
)
@@ -48,6 +53,10 @@ func Notify(msg *LogMessage) {
}
}
func (f *LogFields) Add(name, value string) {
*f = append(*f, LogField{Name: name, Value: value})
}
func (disp *Dispatcher) RegisterProvider(cfg *NotificationConfig) {
disp.providers.Add(cfg.Provider)
}

View File

@@ -3,32 +3,22 @@ package notif
import (
"bytes"
"encoding/json"
"fmt"
)
func formatMarkdown(extras map[string]interface{}) string {
func formatMarkdown(extras LogFields) string {
msg := bytes.NewBufferString("")
for k, v := range extras {
for _, field := range extras {
msg.WriteString("#### ")
msg.WriteString(k)
msg.WriteString(field.Name)
msg.WriteRune('\n')
msg.WriteString(fmt.Sprintf("%v", v))
msg.WriteString(field.Value)
msg.WriteRune('\n')
}
return msg.String()
}
func formatDiscord(extras map[string]interface{}) (string, error) {
fieldsMap := make([]map[string]any, len(extras))
i := 0
for k, extra := range extras {
fieldsMap[i] = map[string]any{
"name": k,
"value": extra,
}
i++
}
fields, err := json.Marshal(fieldsMap)
func formatDiscord(extras LogFields) (string, error) {
fields, err := json.Marshal(extras)
if err != nil {
return "", err
}

View File

@@ -24,16 +24,6 @@ func (client *GotifyClient) GetURL() string {
return client.URL + gotifyMsgEndpoint
}
// GetMethod implements Provider.
func (client *GotifyClient) GetMethod() string {
return http.MethodPost
}
// GetMIMEType implements Provider.
func (client *GotifyClient) GetMIMEType() string {
return "application/json"
}
// MakeBody implements Provider.
func (client *GotifyClient) MakeBody(logMsg *LogMessage) (io.Reader, error) {
var priority int
@@ -71,7 +61,7 @@ func (client *GotifyClient) makeRespError(resp *http.Response) error {
var errm model.Error
err := json.NewDecoder(resp.Body).Decode(&errm)
if err != nil {
return fmt.Errorf(ProviderGotify+" status %d, but failed to decode err response: %w", resp.StatusCode, err)
return fmt.Errorf("%s status %d, but failed to decode err response: %w", client.Name, resp.StatusCode, err)
}
return fmt.Errorf(ProviderGotify+" status %d %s: %s", resp.StatusCode, errm.Error, errm.ErrorDescription)
return fmt.Errorf("%s status %d %s: %s", client.Name, resp.StatusCode, errm.Error, errm.ErrorDescription)
}

89
internal/notif/ntfy.go Normal file
View File

@@ -0,0 +1,89 @@
package notif
import (
"bytes"
"io"
"net/http"
"strings"
"github.com/rs/zerolog"
E "github.com/yusing/go-proxy/internal/error"
)
// See https://docs.ntfy.sh/publish
type Ntfy struct {
ProviderBase
Topic string `json:"topic"`
Style NtfyStyle `json:"style"`
}
type NtfyStyle string
const (
NtfyStyleMarkdown NtfyStyle = "markdown"
NtfyStylePlain NtfyStyle = "plain"
)
func (n *Ntfy) Validate() E.Error {
if n.URL == "" {
return E.New("url is required")
}
if n.Topic == "" {
return E.New("topic is required")
}
if n.Topic[0] == '/' {
return E.New("topic should not start with a slash")
}
switch n.Style {
case "":
n.Style = NtfyStyleMarkdown
case NtfyStyleMarkdown, NtfyStylePlain:
default:
return E.Errorf("invalid style, expecting %q or %q, got %q", NtfyStyleMarkdown, NtfyStylePlain, n.Style)
}
return nil
}
func (n *Ntfy) GetURL() string {
if n.URL[len(n.URL)-1] == '/' {
return n.URL + n.Topic
}
return n.URL + "/" + n.Topic
}
func (n *Ntfy) GetMIMEType() string {
return ""
}
func (n *Ntfy) GetToken() string {
return n.Token
}
func (n *Ntfy) MakeBody(logMsg *LogMessage) (io.Reader, error) {
switch n.Style {
case NtfyStyleMarkdown:
return strings.NewReader(formatMarkdown(logMsg.Extras)), nil
default:
return &bytes.Buffer{}, nil
}
}
func (n *Ntfy) SetHeaders(logMsg *LogMessage, headers http.Header) {
headers.Set("Title", logMsg.Title)
switch logMsg.Level {
// warning (or other unspecified) uses default priority
case zerolog.FatalLevel:
headers.Set("Priority", "urgent")
case zerolog.ErrorLevel:
headers.Set("Priority", "high")
case zerolog.InfoLevel:
headers.Set("Priority", "low")
case zerolog.DebugLevel:
headers.Set("Priority", "min")
}
if n.Style == NtfyStyleMarkdown {
headers.Set("Markdown", "yes")
}
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"io"
"net/http"
"time"
E "github.com/yusing/go-proxy/internal/error"
gphttp "github.com/yusing/go-proxy/internal/net/http"
@@ -21,6 +22,7 @@ type (
GetMIMEType() string
MakeBody(logMsg *LogMessage) (io.Reader, error)
SetHeaders(logMsg *LogMessage, headers http.Header)
makeRespError(resp *http.Response) error
}
@@ -30,6 +32,7 @@ type (
const (
ProviderGotify = "gotify"
ProviderNtfy = "ntfy"
ProviderWebhook = "webhook"
)
@@ -38,6 +41,10 @@ func notifyProvider(ctx context.Context, provider Provider, msg *LogMessage) err
if err != nil {
return E.PrependSubject(provider.GetName(), err)
}
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
@@ -52,6 +59,7 @@ func notifyProvider(ctx context.Context, provider Provider, msg *LogMessage) err
if provider.GetToken() != "" {
req.Header.Set("Authorization", "Bearer "+provider.GetToken())
}
provider.SetHeaders(msg, req.Header)
resp, err := http.DefaultClient.Do(req)
if err != nil {

View File

@@ -92,12 +92,12 @@ func (webhook *Webhook) GetMIMEType() string {
func (webhook *Webhook) makeRespError(resp *http.Response) error {
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("webhook status %d, failed to read body: %w", resp.StatusCode, err)
return fmt.Errorf("%s status %d, failed to read body: %w", webhook.Name, resp.StatusCode, err)
}
if len(body) > 0 {
return fmt.Errorf("webhook status %d: %s", resp.StatusCode, body)
return fmt.Errorf("%s status %d: %s", webhook.Name, resp.StatusCode, body)
}
return fmt.Errorf("webhook status %d", resp.StatusCode)
return fmt.Errorf("%s status %d", webhook.Name, resp.StatusCode)
}
func (webhook *Webhook) MakeBody(logMsg *LogMessage) (io.Reader, error) {

View File

@@ -41,7 +41,7 @@ func TestHTTPConfigDeserialize(t *testing.T) {
if err != nil {
ExpectNoError(t, err)
}
ExpectDeepEqual(t, cfg.HTTPConfig, &tt.expected)
ExpectDeepEqual(t, cfg.HTTPConfig, tt.expected)
})
}
}

View File

@@ -12,7 +12,7 @@ import (
)
var (
branch = common.GetEnvString("BRANCH", "v0.8")
branch = common.GetEnvString("BRANCH", "v0.9")
baseURL = "https://github.com/yusing/go-proxy/raw/" + branch
requiredConfigs = []Config{
{common.ConfigBasePath, true, false, ""},

View File

@@ -198,33 +198,33 @@ func (mon *monitor) checkUpdateHealth() error {
status = health.StatusUnhealthy
}
if result.Healthy != (mon.status.Swap(status) == health.StatusHealthy) {
extras := map[string]any{
"Service Name": mon.service,
"Time": strutils.FormatTime(time.Now()),
extras := notif.LogFields{
{Name: "Service Name", Value: mon.service},
{Name: "Time", Value: strutils.FormatTime(time.Now())},
}
if !result.Healthy {
extras["Last Seen"] = strutils.FormatLastSeen(GetLastSeen(mon.service))
extras.Add("Last Seen", strutils.FormatLastSeen(GetLastSeen(mon.service)))
}
if !mon.url.Load().Nil() {
extras["Service URL"] = mon.url.Load().String()
extras.Add("Service URL", mon.url.Load().String())
}
if result.Detail != "" {
extras["Detail"] = result.Detail
extras.Add("Detail", result.Detail)
}
if result.Healthy {
logger.Info().Msg("service is up")
extras["Ping"] = fmt.Sprintf("%d ms", result.Latency.Milliseconds())
extras.Add("Ping", fmt.Sprintf("%d ms", result.Latency.Milliseconds()))
notif.Notify(&notif.LogMessage{
Title: "✅ Service is up ✅",
Extras: extras,
Color: notif.Green,
Color: notif.ColorSuccess,
})
} else {
logger.Warn().Msg("service went down")
notif.Notify(&notif.LogMessage{
Title: "❌ Service went down ❌",
Extras: extras,
Color: notif.Red,
Color: notif.ColorError,
})
}
}

View File

@@ -1,176 +1,6 @@
GoDoxy v0.9.0 expected changes
GoDoxy v0.9.1 expected changes
- **new** Brand new rewritten WebUI
- View logs directly from WebUI
- Edit dashboard app config (e.g. icon, name, category, etc.)
- Toggle show / hide apps
- Health bubbles, latency, etc. rich info on dashboard items
- UI config editor
![{7829FA41-5733-4BAD-8183-CDF093CEC6F2}](https://github.com/user-attachments/assets/4bb371f4-6e4c-425c-89b2-b9e962bdd46f)
![{29A4608C-607F-43C9-A542-15EC6B9D024E}](https://github.com/user-attachments/assets/8469cfaf-dc37-4b6e-9f29-c44eea91bb82)
![{83118DF5-9D46-4D00-9CEF-C0F6C8D18C4B}](https://github.com/user-attachments/assets/856140f0-78bb-4a76-98f2-ad47544a3515)
- **new** Support selfh.st icons: `@selfhst/<reference>.<format>` _(e.g. `@selfhst/adguard-home.webp`)_
- also uses the display name on https://selfh.st/icons/ as default for our dashboard!
- **new** GoDoxy server side favicon retreiving and caching
- deliver smooth dashboard experience by caching favicons
- correct icon can show without setting `homepage.icon` by parsing it from app's root path "/", selecting `link[rel=icon]` from HTML as default icon
- **Thanks [polds](https://github.com/polds)**
Optionally allow a user to specify a “warm-up” endpoint to start the container, returning a 403 if the endpoint isnt hit and the container has been stopped.
This can help prevent bots from starting random containers, or allow health check systems to run some probes. Or potentially lock the start endpoints behind a different authentication mechanism, etc.
Sample service showing this:
```yaml
hello-world:
image: nginxdemos/hello
container_name: hello-world
restart: "no"
ports:
- "9100:80"
labels:
proxy.aliases: hello-world
proxy.#1.port: 9100
proxy.idle_timeout: 45s
proxy.wake_timeout: 30s
proxy.stop_method: stop
proxy.stop_timeout: 10s
proxy.stop_signal: SIGTERM
proxy.start_endpoint: "/start"
```
Hitting `/` on this service when the container is down:
```curl
$ curl -sv -X GET -H "Host: hello-world.godoxy.local" http://localhost/
* Host localhost:80 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:80...
* Connected to localhost (::1) port 80
> GET / HTTP/1.1
> Host: hello-world.godoxy.local
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 403 Forbidden
< Content-Type: text/plain; charset=utf-8
< X-Content-Type-Options: nosniff
< Date: Wed, 08 Jan 2025 02:04:51 GMT
< Content-Length: 71
<
Forbidden: Container can only be started via configured start endpoint
* Connection #0 to host localhost left intact
```
Hitting `/start` when the container is down:
```curl
curl -sv -X GET -H "Host: hello-world.godoxy.local" -H "X-Goproxy-Check-Redirect: skip" http://localhost/start
* Host localhost:80 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:80...
* Connected to localhost (::1) port 80
> GET /start HTTP/1.1
> Host: hello-world.godoxy.local
> User-Agent: curl/8.7.1
> Accept: */*
> X-Goproxy-Check-Redirect: skip
>
* Request completely sent off
< HTTP/1.1 200 OK
< Date: Wed, 08 Jan 2025 02:13:39 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact
```
- **Thanks [polds](https://github.com/polds)**
Support WebUI authentication via OIDC by setting these environment variables:
- `GODOXY_OIDC_ISSUER_URL` e.g.:
- Pocket ID: `https://pocker-id.yourdomain.com`
- Authentik: `https://authentik.yourdomain.com/application/o/<application_slug>/` **The ending slash is required**
- `GODOXY_OIDC_LOGOUT_URL` _(if your issuer supports it, e.g.)_
- Authentik: `https://authentik.yourdomain.com/application/o/<application_slug>/end-session`
- `GODOXY_OIDC_CLIENT_ID`
- `GODOXY_OIDC_CLIENT_SECRET`
- `GODOXY_OIDC_REDIRECT_URL`
- `GODOXY_OIDC_SCOPES` _(optional)_
- `GODOXY_OIDC_ALLOWED_USERS`
- `GODOXY_OIDC_ALLOWED_GROUPS` _(optional)_
- Use OpenID Connect to authenticate GoDoxy's WebUI and all your services (SSO)
```yaml
# default
labels:
proxy.app.middlewares.oidc:
# with overridden allowed users
labels:
proxy.app.middlewares.oidc.allowed_users: user1, user2
# with overridden allowed groups
labels:
proxy.app.middlewares.oidc.allowed_groups: group1, group2
# with both overridden (can use inline YAML string for less typing)
labels:
proxy.app.middlewares.oidc: |
allowed_users: [user1, user2]
allowed_groups: [group1, group2]
```
- Caddyfile like rules (experimental)
```yaml
proxy.goaccess.rules: |
- name: default
do: |
rewrite / /index.html
serve /var/www/goaccess
- name: ws
on: |
header Connection Upgrade
header Upgrade websocket
do: bypass # do nothing, pass to reverse proxy
proxy.app.rules: |
- name: default
do: bypass # do nothing, pass to reverse proxy
- name: block POST and PUT
on: method POST | method PUT
do: error 403 Forbidden
```
- config reload will now cause a server full restart (i.e. proxy, api, prometheus, etc), eliminating some incorrect behaviors
- drop support of inline yaml string list without hyphen `-` prefix, e.g.
```yaml
# old
proxy.app.middlewares.request.hide_headers: |
X-Header1
X-Header2
# new
proxy.app.middlewares.request.hide_headers: |
- X-Header1
- X-Header2
```
- autocert now supports hot-reload
- middleware compose now supports cross-referencing, e.g.
```yaml
foo:
- use: RedirectHTTP
bar: # in the same file or different file
- use: foo@file
```
- changed default `ResponseHeaderTimeout` to `60s`
- allow customizing `ResponseHeaderTimeout` for each app, e.g.
```yaml
proxy.<app>.response_header_timeout: 3m
```
- Fixes
- bug: cert renewal failure no longer causes renew schdueler to stuck forever
- bug: access log writes to closed file after config reload
- Support Ntfy notifications
- Prometheus metrics server now inside API server under `/v1/metrics`
- `GODOXY_PROMETHEUS_ADDR` removed
- `GODOXY_PROMETHEUS_ENABLED` added, default `false`

View File

@@ -1,6 +1,6 @@
{
"name": "godoxy-schemas",
"version": "0.9.0-22",
"version": "0.9.1-1",
"description": "JSON Schema and typescript types for GoDoxy configuration",
"license": "MIT",
"repository": {

391
pnpm-lock.yaml generated
View File

@@ -1,391 +0,0 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
typescript-json-schema:
specifier: ^0.65.1
version: 0.65.1
packages:
'@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
'@jridgewell/sourcemap-codec@1.5.0':
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@tsconfig/node10@1.0.11':
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
'@tsconfig/node12@1.0.11':
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
'@tsconfig/node14@1.0.3':
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
'@tsconfig/node16@1.0.4':
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@types/node@18.19.74':
resolution: {integrity: sha512-HMwEkkifei3L605gFdV+/UwtpxP6JSzM+xFk2Ia6DNFSwSVBRh9qp5Tgf4lNFOMfPVuU0WnkcWpXZpgn5ufO4A==}
acorn-walk@8.3.4:
resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
engines: {node: '>=0.4.0'}
acorn@8.14.0:
resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
engines: {node: '>=0.4.0'}
hasBin: true
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
arg@4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
diff@4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
path-equal@1.2.5:
resolution: {integrity: sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g==}
path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
safe-stable-stringify@2.5.0:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
ts-node@10.9.2:
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
hasBin: true
peerDependencies:
'@swc/core': '>=1.2.50'
'@swc/wasm': '>=1.2.50'
'@types/node': '*'
typescript: '>=2.7'
peerDependenciesMeta:
'@swc/core':
optional: true
'@swc/wasm':
optional: true
typescript-json-schema@0.65.1:
resolution: {integrity: sha512-tuGH7ff2jPaUYi6as3lHyHcKpSmXIqN7/mu50x3HlYn0EHzLpmt3nplZ7EuhUkO0eqDRc9GqWNkfjgBPIS9kxg==}
hasBin: true
typescript@5.5.4:
resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==}
engines: {node: '>=14.17'}
hasBin: true
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
yn@3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
snapshots:
'@cspotcode/source-map-support@0.8.1':
dependencies:
'@jridgewell/trace-mapping': 0.3.9
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.0': {}
'@jridgewell/trace-mapping@0.3.9':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
'@tsconfig/node10@1.0.11': {}
'@tsconfig/node12@1.0.11': {}
'@tsconfig/node14@1.0.3': {}
'@tsconfig/node16@1.0.4': {}
'@types/json-schema@7.0.15': {}
'@types/node@18.19.74':
dependencies:
undici-types: 5.26.5
acorn-walk@8.3.4:
dependencies:
acorn: 8.14.0
acorn@8.14.0: {}
ansi-regex@5.0.1: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
arg@4.1.3: {}
balanced-match@1.0.2: {}
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
cliui@8.0.1:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
concat-map@0.0.1: {}
create-require@1.1.1: {}
diff@4.0.2: {}
emoji-regex@8.0.0: {}
escalade@3.2.0: {}
fs.realpath@1.0.0: {}
get-caller-file@2.0.5: {}
glob@7.2.3:
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
inflight@1.0.6:
dependencies:
once: 1.4.0
wrappy: 1.0.2
inherits@2.0.4: {}
is-fullwidth-code-point@3.0.0: {}
make-error@1.3.6: {}
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
once@1.4.0:
dependencies:
wrappy: 1.0.2
path-equal@1.2.5: {}
path-is-absolute@1.0.1: {}
require-directory@2.1.1: {}
safe-stable-stringify@2.5.0: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
ts-node@10.9.2(@types/node@18.19.74)(typescript@5.5.4):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 18.19.74
acorn: 8.14.0
acorn-walk: 8.3.4
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
typescript: 5.5.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
typescript-json-schema@0.65.1:
dependencies:
'@types/json-schema': 7.0.15
'@types/node': 18.19.74
glob: 7.2.3
path-equal: 1.2.5
safe-stable-stringify: 2.5.0
ts-node: 10.9.2(@types/node@18.19.74)(typescript@5.5.4)
typescript: 5.5.4
yargs: 17.7.2
transitivePeerDependencies:
- '@swc/core'
- '@swc/wasm'
typescript@5.5.4: {}
undici-types@5.26.5: {}
v8-compile-cache-lib@3.0.1: {}
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrappy@1.0.2: {}
y18n@5.0.8: {}
yargs-parser@21.1.1: {}
yargs@17.7.2:
dependencies:
cliui: 8.0.1
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1
yn@3.1.1: {}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
import { URL } from "../types";
export declare const NOTIFICATION_PROVIDERS: readonly ["webhook", "gotify"];
export declare const NOTIFICATION_PROVIDERS: readonly ["webhook", "gotify", "ntfy"];
export type NotificationProvider = (typeof NOTIFICATION_PROVIDERS)[number];
export type NotificationConfig = {
name: string;
@@ -9,9 +9,17 @@ export interface GotifyConfig extends NotificationConfig {
provider: "gotify";
token: string;
}
export declare const NTFY_MSG_STYLES: string[];
export type NtfyStyle = (typeof NTFY_MSG_STYLES)[number];
export interface NtfyConfig extends NotificationConfig {
provider: "ntfy";
topic: string;
token?: string;
style?: NtfyStyle;
}
export declare const WEBHOOK_TEMPLATES: readonly ["", "discord"];
export declare const WEBHOOK_METHODS: readonly ["POST", "GET", "PUT"];
export declare const WEBHOOK_MIME_TYPES: readonly ["application/json", "application/x-www-form-urlencoded", "text/plain"];
export declare const WEBHOOK_MIME_TYPES: readonly ["application/json", "application/x-www-form-urlencoded", "text/plain", "text/markdown"];
export declare const WEBHOOK_COLOR_MODES: readonly ["hex", "dec"];
export type WebhookTemplate = (typeof WEBHOOK_TEMPLATES)[number];
export type WebhookMethod = (typeof WEBHOOK_METHODS)[number];

View File

@@ -1,9 +1,11 @@
export const NOTIFICATION_PROVIDERS = ["webhook", "gotify"];
export const NOTIFICATION_PROVIDERS = ["webhook", "gotify", "ntfy"];
export const NTFY_MSG_STYLES = ["markdown", "plain"];
export const WEBHOOK_TEMPLATES = ["", "discord"];
export const WEBHOOK_METHODS = ["POST", "GET", "PUT"];
export const WEBHOOK_MIME_TYPES = [
"application/json",
"application/x-www-form-urlencoded",
"text/plain",
"text/markdown",
];
export const WEBHOOK_COLOR_MODES = ["hex", "dec"];

View File

@@ -1,6 +1,6 @@
import { URL } from "../types";
export const NOTIFICATION_PROVIDERS = ["webhook", "gotify"] as const;
export const NOTIFICATION_PROVIDERS = ["webhook", "gotify", "ntfy"] as const;
export type NotificationProvider = (typeof NOTIFICATION_PROVIDERS)[number];
@@ -17,12 +17,23 @@ export interface GotifyConfig extends NotificationConfig {
token: string;
}
export const NTFY_MSG_STYLES = ["markdown", "plain"];
export type NtfyStyle = (typeof NTFY_MSG_STYLES)[number];
export interface NtfyConfig extends NotificationConfig {
provider: "ntfy";
topic: string;
token?: string;
style?: NtfyStyle;
}
export const WEBHOOK_TEMPLATES = ["", "discord"] as const;
export const WEBHOOK_METHODS = ["POST", "GET", "PUT"] as const;
export const WEBHOOK_MIME_TYPES = [
"application/json",
"application/x-www-form-urlencoded",
"text/plain",
"text/markdown",
] as const;
export const WEBHOOK_COLOR_MODES = ["hex", "dec"] as const;

View File

@@ -1,5 +1,5 @@
import { URI, URL } from "../types";
import { GotifyConfig, WebhookConfig } from "./notification";
import { GotifyConfig, NtfyConfig, WebhookConfig } from "./notification";
export type Providers = {
/** List of route definition files to include
*
@@ -21,7 +21,7 @@ export type Providers = {
* @minItems 1
* @examples require(".").notificationExamples
*/
notification?: (WebhookConfig | GotifyConfig)[];
notification?: (WebhookConfig | GotifyConfig | NtfyConfig)[];
};
export declare const includeExamples: readonly ["file1.yml", "file2.yml"];
export declare const dockerExamples: readonly [{

View File

@@ -1,5 +1,5 @@
import { URI, URL } from "../types";
import { GotifyConfig, WebhookConfig } from "./notification";
import { GotifyConfig, NtfyConfig, WebhookConfig } from "./notification";
export type Providers = {
/** List of route definition files to include
@@ -20,7 +20,7 @@ export type Providers = {
* @minItems 1
* @examples require(".").notificationExamples
*/
notification?: (WebhookConfig | GotifyConfig)[];
notification?: (WebhookConfig | GotifyConfig | NtfyConfig)[];
};
export const includeExamples = ["file1.yml", "file2.yml"] as const;