mirror of
https://github.com/yusing/godoxy.git
synced 2026-01-11 21:10:30 +01:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f997423fd7 | ||
|
|
1871ef3d38 | ||
|
|
7c56c88dd4 | ||
|
|
4d7422dd90 | ||
|
|
eccabc0588 | ||
|
|
0c7b188587 | ||
|
|
4c97b79adf | ||
|
|
8ae9573b07 | ||
|
|
43fce6e739 | ||
|
|
78900772bb | ||
|
|
c16a0444ca | ||
|
|
0d518166ee | ||
|
|
6ae391a3c9 | ||
|
|
357897a0cd | ||
|
|
10a0a8fe09 |
@@ -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
|
||||
4
.vscode/settings.example.json
vendored
4
.vscode/settings.example.json
vendored
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
17
README.md
17
README.md
@@ -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**
|
||||
|
||||

|
||||

|
||||
|
||||
_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
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
### 資料夾結構
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
89
internal/notif/ntfy.go
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, ""},
|
||||
|
||||
@@ -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(¬if.LogMessage{
|
||||
Title: "✅ Service is up ✅",
|
||||
Extras: extras,
|
||||
Color: notif.Green,
|
||||
Color: notif.ColorSuccess,
|
||||
})
|
||||
} else {
|
||||
logger.Warn().Msg("service went down")
|
||||
notif.Notify(¬if.LogMessage{
|
||||
Title: "❌ Service went down ❌",
|
||||
Extras: extras,
|
||||
Color: notif.Red,
|
||||
Color: notif.ColorError,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
180
next-release.md
180
next-release.md
@@ -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
|
||||

|
||||

|
||||

|
||||
- **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 isn’t hit and the container has been stopped.
|
||||
|
||||
This can help prevent bots from starting random containers, or allow health check systems to run some probes. 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`
|
||||
|
||||
@@ -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
391
pnpm-lock.yaml
generated
@@ -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
12
schemas/config/notification.d.ts
vendored
12
schemas/config/notification.d.ts
vendored
@@ -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];
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
4
schemas/config/providers.d.ts
vendored
4
schemas/config/providers.d.ts
vendored
@@ -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 [{
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user