Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bf6b8bb28 | ||
|
|
cb82e2690c | ||
|
|
ab1f9220a3 | ||
|
|
4c5d40e4c9 | ||
|
|
c33065151e | ||
|
|
ab01d0f04e | ||
|
|
42c3c6eb29 | ||
|
|
da7a1f656f | ||
|
|
63719ca0a0 | ||
|
|
cd27d47f4b | ||
|
|
60c5ccf34b | ||
|
|
d819de2626 | ||
|
|
79cb082879 | ||
|
|
632bf8d0b6 | ||
|
|
5e1d1698ff | ||
|
|
396497fff7 | ||
|
|
94bfe029d5 | ||
|
|
7f736eb93e | ||
|
|
414e283b46 | ||
|
|
a52e628f7b | ||
|
|
3eea97109e | ||
|
|
1950fc518f | ||
|
|
b93d654aca | ||
|
|
91594faf28 | ||
|
|
6c2aa0c3c2 | ||
|
|
db613f81cc | ||
|
|
51769c4094 | ||
|
|
cb768ca3f8 | ||
|
|
433e8e5b99 | ||
|
|
6cb42fbca1 | ||
|
|
406c172230 | ||
|
|
b4fbe81bb4 | ||
|
|
28f211bfef | ||
|
|
891257cce8 | ||
|
|
4cae237b36 | ||
|
|
c684a39191 | ||
|
|
797e4640df | ||
|
|
9684629549 | ||
|
|
a2825880bc | ||
|
|
577cd0fcea | ||
|
|
4b86085a8c | ||
|
|
0ee99e10c8 | ||
|
|
35f173e17c | ||
|
|
87f8af9b97 | ||
|
|
4dd215d3d8 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,8 @@
|
|||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
.idea/
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
server/docs/
|
server/docs/
|
||||||
server/site/
|
server/site/
|
||||||
tools/fbsend/fbsend
|
tools/fbsend/fbsend
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ builds:
|
|||||||
binary: ntfy
|
binary: ntfy
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0 # explicitly disable, since we don't need go-sqlite3
|
- CGO_ENABLED=0 # explicitly disable, since we don't need go-sqlite3
|
||||||
|
tags: [noserver] # don't include server files
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
- "-X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
||||||
goos: [windows]
|
goos: [windows]
|
||||||
@@ -71,6 +72,7 @@ builds:
|
|||||||
binary: ntfy
|
binary: ntfy
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0 # explicitly disable, since we don't need go-sqlite3
|
- CGO_ENABLED=0 # explicitly disable, since we don't need go-sqlite3
|
||||||
|
tags: [noserver] # don't include server files
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
- "-X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
||||||
goos: [darwin]
|
goos: [darwin]
|
||||||
|
|||||||
122
Makefile
122
Makefile
@@ -1,66 +1,72 @@
|
|||||||
|
MAKEFLAGS := --jobs=1
|
||||||
VERSION := $(shell git describe --tag)
|
VERSION := $(shell git describe --tag)
|
||||||
|
|
||||||
.PHONY:
|
.PHONY:
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "Typical commands (more see below):"
|
@echo "Typical commands (more see below):"
|
||||||
@echo " make build - Build web app, documentation and server/client (sloowwww)"
|
@echo " make build - Build web app, documentation and server/client (sloowwww)"
|
||||||
@echo " make cli-linux-amd64 - Build server/client binary (amd64, no web app or docs)"
|
@echo " make cli-linux-amd64 - Build server/client binary (amd64, no web app or docs)"
|
||||||
@echo " make install-linux-amd64 - Install ntfy binary to /usr/bin/ntfy (amd64)"
|
@echo " make install-linux-amd64 - Install ntfy binary to /usr/bin/ntfy (amd64)"
|
||||||
@echo " make web - Build the web app"
|
@echo " make web - Build the web app"
|
||||||
@echo " make docs - Build the documentation"
|
@echo " make docs - Build the documentation"
|
||||||
@echo " make check - Run all tests, vetting/formatting checks and linters"
|
@echo " make check - Run all tests, vetting/formatting checks and linters"
|
||||||
@echo
|
@echo
|
||||||
@echo "Build everything:"
|
@echo "Build everything:"
|
||||||
@echo " make build - Build web app, documentation and server/client"
|
@echo " make build - Build web app, documentation and server/client"
|
||||||
@echo " make clean - Clean build/dist folders"
|
@echo " make clean - Clean build/dist folders"
|
||||||
@echo
|
@echo
|
||||||
@echo "Build server & client (not release version):"
|
@echo "Build server & client (using GoReleaser, not release version):"
|
||||||
@echo " make cli - Build server & client (all architectures)"
|
@echo " make cli - Build server & client (all architectures)"
|
||||||
@echo " make cli-linux-amd64 - Build server & client (Linux, amd64 only)"
|
@echo " make cli-linux-amd64 - Build server & client (Linux, amd64 only)"
|
||||||
@echo " make cli-linux-armv6 - Build server & client (Linux, armv6 only)"
|
@echo " make cli-linux-armv6 - Build server & client (Linux, armv6 only)"
|
||||||
@echo " make cli-linux-armv7 - Build server & client (Linux, armv7 only)"
|
@echo " make cli-linux-armv7 - Build server & client (Linux, armv7 only)"
|
||||||
@echo " make cli-linux-arm64 - Build server & client (Linux, arm64 only)"
|
@echo " make cli-linux-arm64 - Build server & client (Linux, arm64 only)"
|
||||||
@echo " make cli-windows-amd64 - Build client (Windows, amd64 only)"
|
@echo " make cli-windows-amd64 - Build client (Windows, amd64 only)"
|
||||||
@echo " make cli-darwin-amd64 - Build client (macOS, amd64 only)"
|
@echo " make cli-darwin-all - Build client (macOS, arm64+amd64 universal binary)"
|
||||||
|
@echo
|
||||||
|
@echo "Build server & client (without GoReleaser):"
|
||||||
|
@echo " make cli-linux-server - Build client & server (no GoReleaser, current arch, Linux)"
|
||||||
|
@echo " make cli-darwin-server - Build client & server (no GoReleaser, current arch, macOS)"
|
||||||
|
@echo " make cli-client - Build client only (no GoReleaser, current arch, Linux/macOS/Windows)"
|
||||||
@echo
|
@echo
|
||||||
@echo "Build web app:"
|
@echo "Build web app:"
|
||||||
@echo " make web - Build the web app"
|
@echo " make web - Build the web app"
|
||||||
@echo " make web-deps - Install web app dependencies (npm install the universe)"
|
@echo " make web-deps - Install web app dependencies (npm install the universe)"
|
||||||
@echo " make web-build - Actually build the web app"
|
@echo " make web-build - Actually build the web app"
|
||||||
@echo
|
@echo
|
||||||
@echo "Build documentation:"
|
@echo "Build documentation:"
|
||||||
@echo " make docs - Build the documentation"
|
@echo " make docs - Build the documentation"
|
||||||
@echo " make docs-deps - Install Python dependencies (pip3 install)"
|
@echo " make docs-deps - Install Python dependencies (pip3 install)"
|
||||||
@echo " make docs-build - Actually build the documentation"
|
@echo " make docs-build - Actually build the documentation"
|
||||||
@echo
|
@echo
|
||||||
@echo "Test/check:"
|
@echo "Test/check:"
|
||||||
@echo " make test - Run tests"
|
@echo " make test - Run tests"
|
||||||
@echo " make race - Run tests with -race flag"
|
@echo " make race - Run tests with -race flag"
|
||||||
@echo " make coverage - Run tests and show coverage"
|
@echo " make coverage - Run tests and show coverage"
|
||||||
@echo " make coverage-html - Run tests and show coverage (as HTML)"
|
@echo " make coverage-html - Run tests and show coverage (as HTML)"
|
||||||
@echo " make coverage-upload - Upload coverage results to codecov.io"
|
@echo " make coverage-upload - Upload coverage results to codecov.io"
|
||||||
@echo
|
@echo
|
||||||
@echo "Lint/format:"
|
@echo "Lint/format:"
|
||||||
@echo " make fmt - Run 'go fmt'"
|
@echo " make fmt - Run 'go fmt'"
|
||||||
@echo " make fmt-check - Run 'go fmt', but don't change anything"
|
@echo " make fmt-check - Run 'go fmt', but don't change anything"
|
||||||
@echo " make vet - Run 'go vet'"
|
@echo " make vet - Run 'go vet'"
|
||||||
@echo " make lint - Run 'golint'"
|
@echo " make lint - Run 'golint'"
|
||||||
@echo " make staticcheck - Run 'staticcheck'"
|
@echo " make staticcheck - Run 'staticcheck'"
|
||||||
@echo
|
@echo
|
||||||
@echo "Releasing:"
|
@echo "Releasing:"
|
||||||
@echo " make release - Create a release"
|
@echo " make release - Create a release"
|
||||||
@echo " make release-snapshot - Create a test release"
|
@echo " make release-snapshot - Create a test release"
|
||||||
@echo
|
@echo
|
||||||
@echo "Install locally (requires sudo):"
|
@echo "Install locally (requires sudo):"
|
||||||
@echo " make install-linux-amd64 - Copy amd64 binary from dist/ to /usr/bin/ntfy"
|
@echo " make install-linux-amd64 - Copy amd64 binary from dist/ to /usr/bin/ntfy"
|
||||||
@echo " make install-linux-armv6 - Copy armv6 binary from dist/ to /usr/bin/ntfy"
|
@echo " make install-linux-armv6 - Copy armv6 binary from dist/ to /usr/bin/ntfy"
|
||||||
@echo " make install-linux-armv7 - Copy armv7 binary from dist/ to /usr/bin/ntfy"
|
@echo " make install-linux-armv7 - Copy armv7 binary from dist/ to /usr/bin/ntfy"
|
||||||
@echo " make install-linux-arm64 - Copy arm64 binary from dist/ to /usr/bin/ntfy"
|
@echo " make install-linux-arm64 - Copy arm64 binary from dist/ to /usr/bin/ntfy"
|
||||||
@echo " make install-linux-deb-amd64 - Install .deb from dist/ (amd64 only)"
|
@echo " make install-linux-deb-amd64 - Install .deb from dist/ (amd64 only)"
|
||||||
@echo " make install-linux-deb-armv6 - Install .deb from dist/ (armv6 only)"
|
@echo " make install-linux-deb-armv6 - Install .deb from dist/ (armv6 only)"
|
||||||
@echo " make install-linux-deb-armv7 - Install .deb from dist/ (armv7 only)"
|
@echo " make install-linux-deb-armv7 - Install .deb from dist/ (armv7 only)"
|
||||||
@echo " make install-linux-deb-arm64 - Install .deb from dist/ (arm64 only)"
|
@echo " make install-linux-deb-arm64 - Install .deb from dist/ (arm64 only)"
|
||||||
|
|
||||||
|
|
||||||
# Building everything
|
# Building everything
|
||||||
@@ -68,7 +74,7 @@ help:
|
|||||||
clean: .PHONY
|
clean: .PHONY
|
||||||
rm -rf dist build server/docs server/site
|
rm -rf dist build server/docs server/site
|
||||||
|
|
||||||
build: web docs server
|
build: web docs cli
|
||||||
|
|
||||||
update: web-deps-update cli-deps-update docs-deps-update
|
update: web-deps-update cli-deps-update docs-deps-update
|
||||||
|
|
||||||
@@ -131,6 +137,36 @@ cli-windows-amd64: cli-deps-static-sites
|
|||||||
cli-darwin-all: cli-deps-static-sites
|
cli-darwin-all: cli-deps-static-sites
|
||||||
goreleaser build --snapshot --rm-dist --debug --id ntfy_darwin_all
|
goreleaser build --snapshot --rm-dist --debug --id ntfy_darwin_all
|
||||||
|
|
||||||
|
cli-linux-server: cli-deps-static-sites
|
||||||
|
# This is a target to build the CLI (including the server) manually.
|
||||||
|
# Use this for development, if you really don't want to install GoReleaser ...
|
||||||
|
mkdir -p dist/ntfy_linux_server server/docs
|
||||||
|
CGO_ENABLED=1 go build \
|
||||||
|
-o dist/ntfy_linux_server/ntfy \
|
||||||
|
-tags sqlite_omit_load_extension,osusergo,netgo \
|
||||||
|
-ldflags \
|
||||||
|
"-linkmode=external -extldflags=-static -s -w -X main.version=$(VERSION) -X main.commit=$(shell git rev-parse --short HEAD) -X main.date=$(shell date +%s)"
|
||||||
|
|
||||||
|
cli-darwin-server: cli-deps-static-sites
|
||||||
|
# This is a target to build the CLI (including the server) manually.
|
||||||
|
# Use this for macOS/iOS development, so you have a local server to test with.
|
||||||
|
mkdir -p dist/ntfy_darwin_server server/docs
|
||||||
|
CGO_ENABLED=1 go build \
|
||||||
|
-o dist/ntfy_darwin_server/ntfy \
|
||||||
|
-tags sqlite_omit_load_extension,osusergo,netgo \
|
||||||
|
-ldflags \
|
||||||
|
"-linkmode=external -s -w -X main.version=$(VERSION) -X main.commit=$(shell git rev-parse --short HEAD) -X main.date=$(shell date +%s)"
|
||||||
|
|
||||||
|
cli-client: cli-deps-static-sites
|
||||||
|
# This is a target to build the CLI (excluding the server) manually. This should work on Linux/macOS/Windows.
|
||||||
|
# Use this for development, if you really don't want to install GoReleaser ...
|
||||||
|
mkdir -p dist/ntfy_client server/docs
|
||||||
|
CGO_ENABLED=0 go build \
|
||||||
|
-o dist/ntfy_client/ntfy \
|
||||||
|
-tags noserver \
|
||||||
|
-ldflags \
|
||||||
|
"-X main.version=$(VERSION) -X main.commit=$(shell git rev-parse --short HEAD) -X main.date=$(shell date +%s)"
|
||||||
|
|
||||||
cli-deps: cli-deps-static-sites cli-deps-all cli-deps-gcc
|
cli-deps: cli-deps-static-sites cli-deps-all cli-deps-gcc
|
||||||
|
|
||||||
cli-deps-gcc: cli-deps-gcc-armv6-armv7 cli-deps-gcc-arm64
|
cli-deps-gcc: cli-deps-gcc-armv6-armv7 cli-deps-gcc-arm64
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build !noserver
|
||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -26,7 +28,7 @@ var cmdAccess = &cli.Command{
|
|||||||
Usage: "Grant/revoke access to a topic, or show access",
|
Usage: "Grant/revoke access to a topic, or show access",
|
||||||
UsageText: "ntfy access [USERNAME [TOPIC [PERMISSION]]]",
|
UsageText: "ntfy access [USERNAME [TOPIC [PERMISSION]]]",
|
||||||
Flags: flagsAccess,
|
Flags: flagsAccess,
|
||||||
Before: initConfigFileInputSource("config", flagsAccess),
|
Before: initConfigFileInputSourceFunc("config", flagsAccess),
|
||||||
Action: execUserAccess,
|
Action: execUserAccess,
|
||||||
Category: categoryServer,
|
Category: categoryServer,
|
||||||
Description: `Manage the access control list for the ntfy server.
|
Description: `Manage the access control list for the ntfy server.
|
||||||
21
cmd/app.go
21
cmd/app.go
@@ -2,10 +2,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/urfave/cli/v2/altsrc"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,21 +27,3 @@ func New() *cli.App {
|
|||||||
Commands: commands,
|
Commands: commands,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// initConfigFileInputSource is like altsrc.InitInputSourceWithContext and altsrc.NewYamlSourceFromFlagFunc, but checks
|
|
||||||
// if the config flag is exists and only loads it if it does. If the flag is set and the file exists, it fails.
|
|
||||||
func initConfigFileInputSource(configFlag string, flags []cli.Flag) cli.BeforeFunc {
|
|
||||||
return func(context *cli.Context) error {
|
|
||||||
configFile := context.String(configFlag)
|
|
||||||
if context.IsSet(configFlag) && !util.FileExists(configFile) {
|
|
||||||
return fmt.Errorf("config file %s does not exist", configFile)
|
|
||||||
} else if !context.IsSet(configFlag) && !util.FileExists(configFile) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
inputSource, err := altsrc.NewYamlSourceFromFile(configFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return altsrc.ApplyInputSourceValues(context, inputSource, flags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
52
cmd/config_loader.go
Normal file
52
cmd/config_loader.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"github.com/urfave/cli/v2/altsrc"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
"heckel.io/ntfy/util"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// initConfigFileInputSourceFunc is like altsrc.InitInputSourceWithContext and altsrc.NewYamlSourceFromFlagFunc, but checks
|
||||||
|
// if the config flag is exists and only loads it if it does. If the flag is set and the file exists, it fails.
|
||||||
|
func initConfigFileInputSourceFunc(configFlag string, flags []cli.Flag) cli.BeforeFunc {
|
||||||
|
return func(context *cli.Context) error {
|
||||||
|
configFile := context.String(configFlag)
|
||||||
|
if context.IsSet(configFlag) && !util.FileExists(configFile) {
|
||||||
|
return fmt.Errorf("config file %s does not exist", configFile)
|
||||||
|
} else if !context.IsSet(configFlag) && !util.FileExists(configFile) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
inputSource, err := newYamlSourceFromFile(configFile, flags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return altsrc.ApplyInputSourceValues(context, inputSource, flags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newYamlSourceFromFile creates a new Yaml InputSourceContext from a filepath.
|
||||||
|
//
|
||||||
|
// This function also maps aliases, so a .yml file can contain short options, or options with underscores
|
||||||
|
// instead of dashes. See https://github.com/binwiederhier/ntfy/issues/255.
|
||||||
|
func newYamlSourceFromFile(file string, flags []cli.Flag) (altsrc.InputSourceContext, error) {
|
||||||
|
var rawConfig map[interface{}]interface{}
|
||||||
|
b, err := os.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := yaml.Unmarshal(b, &rawConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, f := range flags {
|
||||||
|
flagName := f.Names()[0]
|
||||||
|
for _, flagAlias := range f.Names()[1:] {
|
||||||
|
if _, ok := rawConfig[flagAlias]; ok {
|
||||||
|
rawConfig[flagName] = rawConfig[flagAlias]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return altsrc.NewMapInputSource(file, rawConfig), nil
|
||||||
|
}
|
||||||
38
cmd/config_loader_test.go
Normal file
38
cmd/config_loader_test.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewYamlSourceFromFile(t *testing.T) {
|
||||||
|
filename := filepath.Join(t.TempDir(), "server.yml")
|
||||||
|
contents := `
|
||||||
|
# Normal options
|
||||||
|
listen-https: ":10443"
|
||||||
|
|
||||||
|
# Note the underscore!
|
||||||
|
listen_http: ":1080"
|
||||||
|
|
||||||
|
# OMG this is allowed now ...
|
||||||
|
K: /some/file.pem
|
||||||
|
`
|
||||||
|
require.Nil(t, os.WriteFile(filename, []byte(contents), 0600))
|
||||||
|
|
||||||
|
ctx, err := newYamlSourceFromFile(filename, flagsServe)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
listenHTTPS, err := ctx.String("listen-https")
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, ":10443", listenHTTPS)
|
||||||
|
|
||||||
|
listenHTTP, err := ctx.String("listen-http") // No underscore!
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, ":1080", listenHTTP)
|
||||||
|
|
||||||
|
keyFile, err := ctx.String("key-file") // Long option!
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, "/some/file.pem", keyFile)
|
||||||
|
}
|
||||||
@@ -1,17 +1,20 @@
|
|||||||
|
//go:build !noserver
|
||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"github.com/urfave/cli/v2/altsrc"
|
|
||||||
"heckel.io/ntfy/server"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"github.com/urfave/cli/v2/altsrc"
|
||||||
|
"heckel.io/ntfy/server"
|
||||||
|
"heckel.io/ntfy/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -20,41 +23,41 @@ func init() {
|
|||||||
|
|
||||||
var flagsServe = []cli.Flag{
|
var flagsServe = []cli.Flag{
|
||||||
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: "/etc/ntfy/server.yml", DefaultText: "/etc/ntfy/server.yml", Usage: "config file"},
|
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: "/etc/ntfy/server.yml", DefaultText: "/etc/ntfy/server.yml", Usage: "config file"},
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "base-url", Aliases: []string{"B"}, EnvVars: []string{"NTFY_BASE_URL"}, Usage: "externally visible base URL for this host (e.g. https://ntfy.sh)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "base-url", Aliases: []string{"base_url", "B"}, EnvVars: []string{"NTFY_BASE_URL"}, Usage: "externally visible base URL for this host (e.g. https://ntfy.sh)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: server.DefaultListenHTTP, Usage: "ip:port used to as HTTP listen address"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"listen_http", "l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: server.DefaultListenHTTP, Usage: "ip:port used to as HTTP listen address"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-https", Aliases: []string{"L"}, EnvVars: []string{"NTFY_LISTEN_HTTPS"}, Usage: "ip:port used to as HTTPS listen address"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-https", Aliases: []string{"listen_https", "L"}, EnvVars: []string{"NTFY_LISTEN_HTTPS"}, Usage: "ip:port used to as HTTPS listen address"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-unix", Aliases: []string{"U"}, EnvVars: []string{"NTFY_LISTEN_UNIX"}, Usage: "listen on unix socket path"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-unix", Aliases: []string{"listen_unix", "U"}, EnvVars: []string{"NTFY_LISTEN_UNIX"}, Usage: "listen on unix socket path"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "key-file", Aliases: []string{"K"}, EnvVars: []string{"NTFY_KEY_FILE"}, Usage: "private key file, if listen-https is set"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "key-file", Aliases: []string{"key_file", "K"}, EnvVars: []string{"NTFY_KEY_FILE"}, Usage: "private key file, if listen-https is set"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "cert-file", Aliases: []string{"E"}, EnvVars: []string{"NTFY_CERT_FILE"}, Usage: "certificate file, if listen-https is set"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "cert-file", Aliases: []string{"cert_file", "E"}, EnvVars: []string{"NTFY_CERT_FILE"}, Usage: "certificate file, if listen-https is set"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "firebase-key-file", Aliases: []string{"F"}, EnvVars: []string{"NTFY_FIREBASE_KEY_FILE"}, Usage: "Firebase credentials file; if set additionally publish to FCM topic"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "firebase-key-file", Aliases: []string{"firebase_key_file", "F"}, EnvVars: []string{"NTFY_FIREBASE_KEY_FILE"}, Usage: "Firebase credentials file; if set additionally publish to FCM topic"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-file", Aliases: []string{"C"}, EnvVars: []string{"NTFY_CACHE_FILE"}, Usage: "cache file used for message caching"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-file", Aliases: []string{"cache_file", "C"}, EnvVars: []string{"NTFY_CACHE_FILE"}, Usage: "cache file used for message caching"}),
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "cache-duration", Aliases: []string{"b"}, EnvVars: []string{"NTFY_CACHE_DURATION"}, Value: server.DefaultCacheDuration, Usage: "buffer messages for this time to allow `since` requests"}),
|
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "cache-duration", Aliases: []string{"cache_duration", "b"}, EnvVars: []string{"NTFY_CACHE_DURATION"}, Value: server.DefaultCacheDuration, Usage: "buffer messages for this time to allow `since` requests"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"auth_file", "H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-default-access", Aliases: []string{"p"}, EnvVars: []string{"NTFY_AUTH_DEFAULT_ACCESS"}, Value: "read-write", Usage: "default permissions if no matching entries in the auth database are found"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-default-access", Aliases: []string{"auth_default_access", "p"}, EnvVars: []string{"NTFY_AUTH_DEFAULT_ACCESS"}, Value: "read-write", Usage: "default permissions if no matching entries in the auth database are found"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-cache-dir", EnvVars: []string{"NTFY_ATTACHMENT_CACHE_DIR"}, Usage: "cache directory for attached files"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-cache-dir", Aliases: []string{"attachment_cache_dir"}, EnvVars: []string{"NTFY_ATTACHMENT_CACHE_DIR"}, Usage: "cache directory for attached files"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-total-size-limit", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT"}, DefaultText: "5G", Usage: "limit of the on-disk attachment cache"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-total-size-limit", Aliases: []string{"attachment_total_size_limit", "A"}, EnvVars: []string{"NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT"}, DefaultText: "5G", Usage: "limit of the on-disk attachment cache"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-file-size-limit", Aliases: []string{"Y"}, EnvVars: []string{"NTFY_ATTACHMENT_FILE_SIZE_LIMIT"}, DefaultText: "15M", Usage: "per-file attachment size limit (e.g. 300k, 2M, 100M)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-file-size-limit", Aliases: []string{"attachment_file_size_limit", "Y"}, EnvVars: []string{"NTFY_ATTACHMENT_FILE_SIZE_LIMIT"}, DefaultText: "15M", Usage: "per-file attachment size limit (e.g. 300k, 2M, 100M)"}),
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "attachment-expiry-duration", Aliases: []string{"X"}, EnvVars: []string{"NTFY_ATTACHMENT_EXPIRY_DURATION"}, Value: server.DefaultAttachmentExpiryDuration, DefaultText: "3h", Usage: "duration after which uploaded attachments will be deleted (e.g. 3h, 20h)"}),
|
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "attachment-expiry-duration", Aliases: []string{"attachment_expiry_duration", "X"}, EnvVars: []string{"NTFY_ATTACHMENT_EXPIRY_DURATION"}, Value: server.DefaultAttachmentExpiryDuration, DefaultText: "3h", Usage: "duration after which uploaded attachments will be deleted (e.g. 3h, 20h)"}),
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: server.DefaultKeepaliveInterval, Usage: "interval of keepalive messages"}),
|
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"keepalive_interval", "k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: server.DefaultKeepaliveInterval, Usage: "interval of keepalive messages"}),
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "manager-interval", Aliases: []string{"m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: server.DefaultManagerInterval, Usage: "interval of for message pruning and stats printing"}),
|
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "manager-interval", Aliases: []string{"manager_interval", "m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: server.DefaultManagerInterval, Usage: "interval of for message pruning and stats printing"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-root", EnvVars: []string{"NTFY_WEB_ROOT"}, Value: "app", Usage: "sets web root to landing page (home) or web app (app)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-root", Aliases: []string{"web_root"}, EnvVars: []string{"NTFY_WEB_ROOT"}, Value: "app", Usage: "sets web root to landing page (home), web app (app) or disabled (disable)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-addr", EnvVars: []string{"NTFY_SMTP_SENDER_ADDR"}, Usage: "SMTP server address (host:port) for outgoing emails"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-addr", Aliases: []string{"smtp_sender_addr"}, EnvVars: []string{"NTFY_SMTP_SENDER_ADDR"}, Usage: "SMTP server address (host:port) for outgoing emails"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-user", EnvVars: []string{"NTFY_SMTP_SENDER_USER"}, Usage: "SMTP user (if e-mail sending is enabled)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-user", Aliases: []string{"smtp_sender_user"}, EnvVars: []string{"NTFY_SMTP_SENDER_USER"}, Usage: "SMTP user (if e-mail sending is enabled)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-pass", EnvVars: []string{"NTFY_SMTP_SENDER_PASS"}, Usage: "SMTP password (if e-mail sending is enabled)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-pass", Aliases: []string{"smtp_sender_pass"}, EnvVars: []string{"NTFY_SMTP_SENDER_PASS"}, Usage: "SMTP password (if e-mail sending is enabled)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-from", EnvVars: []string{"NTFY_SMTP_SENDER_FROM"}, Usage: "SMTP sender address (if e-mail sending is enabled)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-from", Aliases: []string{"smtp_sender_from"}, EnvVars: []string{"NTFY_SMTP_SENDER_FROM"}, Usage: "SMTP sender address (if e-mail sending is enabled)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-listen", EnvVars: []string{"NTFY_SMTP_SERVER_LISTEN"}, Usage: "SMTP server address (ip:port) for incoming emails, e.g. :25"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-listen", Aliases: []string{"smtp_server_listen"}, EnvVars: []string{"NTFY_SMTP_SERVER_LISTEN"}, Usage: "SMTP server address (ip:port) for incoming emails, e.g. :25"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-domain", EnvVars: []string{"NTFY_SMTP_SERVER_DOMAIN"}, Usage: "SMTP domain for incoming e-mail, e.g. ntfy.sh"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-domain", Aliases: []string{"smtp_server_domain"}, EnvVars: []string{"NTFY_SMTP_SERVER_DOMAIN"}, Usage: "SMTP domain for incoming e-mail, e.g. ntfy.sh"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-addr-prefix", EnvVars: []string{"NTFY_SMTP_SERVER_ADDR_PREFIX"}, Usage: "SMTP email address prefix for topics to prevent spam (e.g. 'ntfy-')"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-addr-prefix", Aliases: []string{"smtp_server_addr_prefix"}, EnvVars: []string{"NTFY_SMTP_SERVER_ADDR_PREFIX"}, Usage: "SMTP email address prefix for topics to prevent spam (e.g. 'ntfy-')"}),
|
||||||
altsrc.NewIntFlag(&cli.IntFlag{Name: "global-topic-limit", Aliases: []string{"T"}, EnvVars: []string{"NTFY_GLOBAL_TOPIC_LIMIT"}, Value: server.DefaultTotalTopicLimit, Usage: "total number of topics allowed"}),
|
altsrc.NewIntFlag(&cli.IntFlag{Name: "global-topic-limit", Aliases: []string{"global_topic_limit", "T"}, EnvVars: []string{"NTFY_GLOBAL_TOPIC_LIMIT"}, Value: server.DefaultTotalTopicLimit, Usage: "total number of topics allowed"}),
|
||||||
altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-subscription-limit", EnvVars: []string{"NTFY_VISITOR_SUBSCRIPTION_LIMIT"}, Value: server.DefaultVisitorSubscriptionLimit, Usage: "number of subscriptions per visitor"}),
|
altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-subscription-limit", Aliases: []string{"visitor_subscription_limit"}, EnvVars: []string{"NTFY_VISITOR_SUBSCRIPTION_LIMIT"}, Value: server.DefaultVisitorSubscriptionLimit, Usage: "number of subscriptions per visitor"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-attachment-total-size-limit", EnvVars: []string{"NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT"}, Value: "100M", Usage: "total storage limit used for attachments per visitor"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-attachment-total-size-limit", Aliases: []string{"visitor_attachment_total_size_limit"}, EnvVars: []string{"NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT"}, Value: "100M", Usage: "total storage limit used for attachments per visitor"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-attachment-daily-bandwidth-limit", EnvVars: []string{"NTFY_VISITOR_ATTACHMENT_DAILY_BANDWIDTH_LIMIT"}, Value: "500M", Usage: "total daily attachment download/upload bandwidth limit per visitor"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-attachment-daily-bandwidth-limit", Aliases: []string{"visitor_attachment_daily_bandwidth_limit"}, EnvVars: []string{"NTFY_VISITOR_ATTACHMENT_DAILY_BANDWIDTH_LIMIT"}, Value: "500M", Usage: "total daily attachment download/upload bandwidth limit per visitor"}),
|
||||||
altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-request-limit-burst", EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_BURST"}, Value: server.DefaultVisitorRequestLimitBurst, Usage: "initial limit of requests per visitor"}),
|
altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-request-limit-burst", Aliases: []string{"visitor_request_limit_burst"}, EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_BURST"}, Value: server.DefaultVisitorRequestLimitBurst, Usage: "initial limit of requests per visitor"}),
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "visitor-request-limit-replenish", EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_REPLENISH"}, Value: server.DefaultVisitorRequestLimitReplenish, Usage: "interval at which burst limit is replenished (one per x)"}),
|
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "visitor-request-limit-replenish", Aliases: []string{"visitor_request_limit_replenish"}, EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_REPLENISH"}, Value: server.DefaultVisitorRequestLimitReplenish, Usage: "interval at which burst limit is replenished (one per x)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-request-limit-exempt-hosts", EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_EXEMPT_HOSTS"}, Value: "", Usage: "hostnames and/or IP addresses of hosts that will be exempt from the visitor request limit"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-request-limit-exempt-hosts", Aliases: []string{"visitor_request_limit_exempt_hosts"}, EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_EXEMPT_HOSTS"}, Value: "", Usage: "hostnames and/or IP addresses of hosts that will be exempt from the visitor request limit"}),
|
||||||
altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-email-limit-burst", EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_BURST"}, Value: server.DefaultVisitorEmailLimitBurst, Usage: "initial limit of e-mails per visitor"}),
|
altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-email-limit-burst", Aliases: []string{"visitor_email_limit_burst"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_BURST"}, Value: server.DefaultVisitorEmailLimitBurst, Usage: "initial limit of e-mails per visitor"}),
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "visitor-email-limit-replenish", EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_REPLENISH"}, Value: server.DefaultVisitorEmailLimitReplenish, Usage: "interval at which burst limit is replenished (one per x)"}),
|
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "visitor-email-limit-replenish", Aliases: []string{"visitor_email_limit_replenish"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_REPLENISH"}, Value: server.DefaultVisitorEmailLimitReplenish, Usage: "interval at which burst limit is replenished (one per x)"}),
|
||||||
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "behind-proxy", Aliases: []string{"P"}, EnvVars: []string{"NTFY_BEHIND_PROXY"}, Value: false, Usage: "if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting)"}),
|
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "behind-proxy", Aliases: []string{"behind_proxy", "P"}, EnvVars: []string{"NTFY_BEHIND_PROXY"}, Value: false, Usage: "if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting)"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmdServe = &cli.Command{
|
var cmdServe = &cli.Command{
|
||||||
@@ -64,7 +67,7 @@ var cmdServe = &cli.Command{
|
|||||||
Action: execServe,
|
Action: execServe,
|
||||||
Category: categoryServer,
|
Category: categoryServer,
|
||||||
Flags: flagsServe,
|
Flags: flagsServe,
|
||||||
Before: initConfigFileInputSource("config", flagsServe),
|
Before: initConfigFileInputSourceFunc("config", flagsServe),
|
||||||
Description: `Run the ntfy server and listen for incoming requests
|
Description: `Run the ntfy server and listen for incoming requests
|
||||||
|
|
||||||
The command will load the configuration from /etc/ntfy/server.yml. Config options can
|
The command will load the configuration from /etc/ntfy/server.yml. Config options can
|
||||||
@@ -142,12 +145,14 @@ func execServe(c *cli.Context) error {
|
|||||||
return errors.New("if set, base-url must start with http:// or https://")
|
return errors.New("if set, base-url must start with http:// or https://")
|
||||||
} else if !util.InStringList([]string{"read-write", "read-only", "write-only", "deny-all"}, authDefaultAccess) {
|
} else if !util.InStringList([]string{"read-write", "read-only", "write-only", "deny-all"}, authDefaultAccess) {
|
||||||
return errors.New("if set, auth-default-access must start set to 'read-write', 'read-only', 'write-only' or 'deny-all'")
|
return errors.New("if set, auth-default-access must start set to 'read-write', 'read-only', 'write-only' or 'deny-all'")
|
||||||
} else if !util.InStringList([]string{"app", "home"}, webRoot) {
|
} else if !util.InStringList([]string{"app", "home", "disable"}, webRoot) {
|
||||||
return errors.New("if set, web-root must be 'home' or 'app'")
|
return errors.New("if set, web-root must be 'home' or 'app'")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default auth permissions
|
|
||||||
webRootIsApp := webRoot == "app"
|
webRootIsApp := webRoot == "app"
|
||||||
|
enableWeb := webRoot != "disable"
|
||||||
|
|
||||||
|
// Default auth permissions
|
||||||
authDefaultRead := authDefaultAccess == "read-write" || authDefaultAccess == "read-only"
|
authDefaultRead := authDefaultAccess == "read-write" || authDefaultAccess == "read-only"
|
||||||
authDefaultWrite := authDefaultAccess == "read-write" || authDefaultAccess == "write-only"
|
authDefaultWrite := authDefaultAccess == "read-write" || authDefaultAccess == "write-only"
|
||||||
|
|
||||||
@@ -227,6 +232,7 @@ func execServe(c *cli.Context) error {
|
|||||||
conf.VisitorEmailLimitBurst = visitorEmailLimitBurst
|
conf.VisitorEmailLimitBurst = visitorEmailLimitBurst
|
||||||
conf.VisitorEmailLimitReplenish = visitorEmailLimitReplenish
|
conf.VisitorEmailLimitReplenish = visitorEmailLimitReplenish
|
||||||
conf.BehindProxy = behindProxy
|
conf.BehindProxy = behindProxy
|
||||||
|
conf.EnableWeb = enableWeb
|
||||||
s, err := server.New(conf)
|
s, err := server.New(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build !noserver
|
||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -22,7 +24,7 @@ var cmdUser = &cli.Command{
|
|||||||
Usage: "Manage/show users",
|
Usage: "Manage/show users",
|
||||||
UsageText: "ntfy user [list|add|remove|change-pass|change-role] ...",
|
UsageText: "ntfy user [list|add|remove|change-pass|change-role] ...",
|
||||||
Flags: flagsUser,
|
Flags: flagsUser,
|
||||||
Before: initConfigFileInputSource("config", flagsUser),
|
Before: initConfigFileInputSourceFunc("config", flagsUser),
|
||||||
Category: categoryServer,
|
Category: categoryServer,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
{
|
{
|
||||||
@@ -227,7 +227,7 @@ The easiest way to configure a private instance is to set `auth-default-access`
|
|||||||
|
|
||||||
=== "/etc/ntfy/server.yml"
|
=== "/etc/ntfy/server.yml"
|
||||||
``` yaml
|
``` yaml
|
||||||
auth-file "/var/lib/ntfy/user.db"
|
auth-file: "/var/lib/ntfy/user.db"
|
||||||
auth-default-access: "deny-all"
|
auth-default-access: "deny-all"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -775,6 +775,11 @@ Each config option can be set in the config file `/etc/ntfy/server.yml` (e.g. `l
|
|||||||
CLI option (e.g. `--listen-http :80`. Here's a list of all available options. Alternatively, you can set an environment
|
CLI option (e.g. `--listen-http :80`. Here's a list of all available options. Alternatively, you can set an environment
|
||||||
variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
|
variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
All config options can also be defined in the `server.yml` file using underscores instead of dashes, e.g.
|
||||||
|
`cache_duration` and `cache-duration` are both supported. This is to support stricter YAML parsers that do
|
||||||
|
not support dashes.
|
||||||
|
|
||||||
| Config option | Env variable | Format | Default | Description |
|
| Config option | Env variable | Format | Default | Description |
|
||||||
|--------------------------------------------|-------------------------------------------------|-----------------------------------------------------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|--------------------------------------------|-------------------------------------------------|-----------------------------------------------------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `base-url` | `NTFY_BASE_URL` | *URL* | - | Public facing base URL of the service (e.g. `https://ntfy.sh`) |
|
| `base-url` | `NTFY_BASE_URL` | *URL* | - | Public facing base URL of the service (e.g. `https://ntfy.sh`) |
|
||||||
@@ -802,7 +807,7 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
|
|||||||
| `smtp-server-addr-prefix` | `NTFY_SMTP_SERVER_ADDR_PREFIX` | `[ip]:port` | - | Optional prefix for the e-mail addresses to prevent spam, e.g. `ntfy-` |
|
| `smtp-server-addr-prefix` | `NTFY_SMTP_SERVER_ADDR_PREFIX` | `[ip]:port` | - | Optional prefix for the e-mail addresses to prevent spam, e.g. `ntfy-` |
|
||||||
| `keepalive-interval` | `NTFY_KEEPALIVE_INTERVAL` | *duration* | 45s | Interval in which keepalive messages are sent to the client. This is to prevent intermediaries closing the connection for inactivity. Note that the Android app has a hardcoded timeout at 77s, so it should be less than that. |
|
| `keepalive-interval` | `NTFY_KEEPALIVE_INTERVAL` | *duration* | 45s | Interval in which keepalive messages are sent to the client. This is to prevent intermediaries closing the connection for inactivity. Note that the Android app has a hardcoded timeout at 77s, so it should be less than that. |
|
||||||
| `manager-interval` | `$NTFY_MANAGER_INTERVAL` | *duration* | 1m | Interval in which the manager prunes old messages, deletes topics and prints the stats. |
|
| `manager-interval` | `$NTFY_MANAGER_INTERVAL` | *duration* | 1m | Interval in which the manager prunes old messages, deletes topics and prints the stats. |
|
||||||
| `web-root` | `NTFY_WEB_ROOT` | `app` or `home` | `app` | Sets web root to landing page (home) or web app (app) |
|
| `web-root` | `NTFY_WEB_ROOT` | `app`, `home` or `disable` | `app` | Sets web root to landing page (home), web app (app) or disables the web app entirely (disable) |
|
||||||
| `global-topic-limit` | `NTFY_GLOBAL_TOPIC_LIMIT` | *number* | 15,000 | Rate limiting: Total number of topics before the server rejects new topics. |
|
| `global-topic-limit` | `NTFY_GLOBAL_TOPIC_LIMIT` | *number* | 15,000 | Rate limiting: Total number of topics before the server rejects new topics. |
|
||||||
| `visitor-subscription-limit` | `NTFY_VISITOR_SUBSCRIPTION_LIMIT` | *number* | 30 | Rate limiting: Number of subscriptions per visitor (IP address) |
|
| `visitor-subscription-limit` | `NTFY_VISITOR_SUBSCRIPTION_LIMIT` | *number* | 30 | Rate limiting: Number of subscriptions per visitor (IP address) |
|
||||||
| `visitor-attachment-total-size-limit` | `NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT` | *size* | 100M | Rate limiting: Total storage limit used for attachments per visitor, for all attachments combined. Storage is freed after attachments expire. See `attachment-expiry-duration`. |
|
| `visitor-attachment-total-size-limit` | `NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT` | *size* | 100M | Rate limiting: Total storage limit used for attachments per visitor, for all attachments combined. Storage is freed after attachments expire. See `attachment-expiry-duration`. |
|
||||||
@@ -839,42 +844,42 @@ DESCRIPTION:
|
|||||||
ntfy serve --listen-http :8080 # Starts server with alternate port
|
ntfy serve --listen-http :8080 # Starts server with alternate port
|
||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
--config value, -c value config file (default: /etc/ntfy/server.yml) [$NTFY_CONFIG_FILE]
|
--config value, -c value config file (default: /etc/ntfy/server.yml) [$NTFY_CONFIG_FILE]
|
||||||
--base-url value, -B value externally visible base URL for this host (e.g. https://ntfy.sh) [$NTFY_BASE_URL]
|
--base-url value, --base_url value, -B value externally visible base URL for this host (e.g. https://ntfy.sh) [$NTFY_BASE_URL]
|
||||||
--listen-http value, -l value ip:port used to as HTTP listen address (default: ":80") [$NTFY_LISTEN_HTTP]
|
--listen-http value, --listen_http value, -l value ip:port used to as HTTP listen address (default: ":80") [$NTFY_LISTEN_HTTP]
|
||||||
--listen-https value, -L value ip:port used to as HTTPS listen address [$NTFY_LISTEN_HTTPS]
|
--listen-https value, --listen_https value, -L value ip:port used to as HTTPS listen address [$NTFY_LISTEN_HTTPS]
|
||||||
--listen-unix value, -U value listen on unix socket path [$NTFY_LISTEN_UNIX]
|
--listen-unix value, --listen_unix value, -U value listen on unix socket path [$NTFY_LISTEN_UNIX]
|
||||||
--key-file value, -K value private key file, if listen-https is set [$NTFY_KEY_FILE]
|
--key-file value, --key_file value, -K value private key file, if listen-https is set [$NTFY_KEY_FILE]
|
||||||
--cert-file value, -E value certificate file, if listen-https is set [$NTFY_CERT_FILE]
|
--cert-file value, --cert_file value, -E value certificate file, if listen-https is set [$NTFY_CERT_FILE]
|
||||||
--firebase-key-file value, -F value Firebase credentials file; if set additionally publish to FCM topic [$NTFY_FIREBASE_KEY_FILE]
|
--firebase-key-file value, --firebase_key_file value, -F value Firebase credentials file; if set additionally publish to FCM topic [$NTFY_FIREBASE_KEY_FILE]
|
||||||
--cache-file value, -C value cache file used for message caching [$NTFY_CACHE_FILE]
|
--cache-file value, --cache_file value, -C value cache file used for message caching [$NTFY_CACHE_FILE]
|
||||||
--cache-duration since, -b since buffer messages for this time to allow since requests (default: 12h0m0s) [$NTFY_CACHE_DURATION]
|
--cache-duration since, --cache_duration since, -b since buffer messages for this time to allow since requests (default: 12h0m0s) [$NTFY_CACHE_DURATION]
|
||||||
--auth-file value, -H value auth database file used for access control [$NTFY_AUTH_FILE]
|
--auth-file value, --auth_file value, -H value auth database file used for access control [$NTFY_AUTH_FILE]
|
||||||
--auth-default-access value, -p value default permissions if no matching entries in the auth database are found (default: "read-write") [$NTFY_AUTH_DEFAULT_ACCESS]
|
--auth-default-access value, --auth_default_access value, -p value default permissions if no matching entries in the auth database are found (default: "read-write") [$NTFY_AUTH_DEFAULT_ACCESS]
|
||||||
--attachment-cache-dir value cache directory for attached files [$NTFY_ATTACHMENT_CACHE_DIR]
|
--attachment-cache-dir value, --attachment_cache_dir value cache directory for attached files [$NTFY_ATTACHMENT_CACHE_DIR]
|
||||||
--attachment-total-size-limit value, -A value limit of the on-disk attachment cache (default: 5G) [$NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT]
|
--attachment-total-size-limit value, --attachment_total_size_limit value, -A value limit of the on-disk attachment cache (default: 5G) [$NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT]
|
||||||
--attachment-file-size-limit value, -Y value per-file attachment size limit (e.g. 300k, 2M, 100M) (default: 15M) [$NTFY_ATTACHMENT_FILE_SIZE_LIMIT]
|
--attachment-file-size-limit value, --attachment_file_size_limit value, -Y value per-file attachment size limit (e.g. 300k, 2M, 100M) (default: 15M) [$NTFY_ATTACHMENT_FILE_SIZE_LIMIT]
|
||||||
--attachment-expiry-duration value, -X value duration after which uploaded attachments will be deleted (e.g. 3h, 20h) (default: 3h) [$NTFY_ATTACHMENT_EXPIRY_DURATION]
|
--attachment-expiry-duration value, --attachment_expiry_duration value, -X value duration after which uploaded attachments will be deleted (e.g. 3h, 20h) (default: 3h) [$NTFY_ATTACHMENT_EXPIRY_DURATION]
|
||||||
--keepalive-interval value, -k value interval of keepalive messages (default: 45s) [$NTFY_KEEPALIVE_INTERVAL]
|
--keepalive-interval value, --keepalive_interval value, -k value interval of keepalive messages (default: 45s) [$NTFY_KEEPALIVE_INTERVAL]
|
||||||
--manager-interval value, -m value interval of for message pruning and stats printing (default: 1m0s) [$NTFY_MANAGER_INTERVAL]
|
--manager-interval value, --manager_interval value, -m value interval of for message pruning and stats printing (default: 1m0s) [$NTFY_MANAGER_INTERVAL]
|
||||||
--web-root value sets web root to landing page (home) or web app (app) (default: "app") [$NTFY_WEB_ROOT]
|
--web-root value, --web_root value sets web root to landing page (home), web app (app) or disabled (disable) (default: "app") [$NTFY_WEB_ROOT]
|
||||||
--smtp-sender-addr value SMTP server address (host:port) for outgoing emails [$NTFY_SMTP_SENDER_ADDR]
|
--smtp-sender-addr value, --smtp_sender_addr value SMTP server address (host:port) for outgoing emails [$NTFY_SMTP_SENDER_ADDR]
|
||||||
--smtp-sender-user value SMTP user (if e-mail sending is enabled) [$NTFY_SMTP_SENDER_USER]
|
--smtp-sender-user value, --smtp_sender_user value SMTP user (if e-mail sending is enabled) [$NTFY_SMTP_SENDER_USER]
|
||||||
--smtp-sender-pass value SMTP password (if e-mail sending is enabled) [$NTFY_SMTP_SENDER_PASS]
|
--smtp-sender-pass value, --smtp_sender_pass value SMTP password (if e-mail sending is enabled) [$NTFY_SMTP_SENDER_PASS]
|
||||||
--smtp-sender-from value SMTP sender address (if e-mail sending is enabled) [$NTFY_SMTP_SENDER_FROM]
|
--smtp-sender-from value, --smtp_sender_from value SMTP sender address (if e-mail sending is enabled) [$NTFY_SMTP_SENDER_FROM]
|
||||||
--smtp-server-listen value SMTP server address (ip:port) for incoming emails, e.g. :25 [$NTFY_SMTP_SERVER_LISTEN]
|
--smtp-server-listen value, --smtp_server_listen value SMTP server address (ip:port) for incoming emails, e.g. :25 [$NTFY_SMTP_SERVER_LISTEN]
|
||||||
--smtp-server-domain value SMTP domain for incoming e-mail, e.g. ntfy.sh [$NTFY_SMTP_SERVER_DOMAIN]
|
--smtp-server-domain value, --smtp_server_domain value SMTP domain for incoming e-mail, e.g. ntfy.sh [$NTFY_SMTP_SERVER_DOMAIN]
|
||||||
--smtp-server-addr-prefix value SMTP email address prefix for topics to prevent spam (e.g. 'ntfy-') [$NTFY_SMTP_SERVER_ADDR_PREFIX]
|
--smtp-server-addr-prefix value, --smtp_server_addr_prefix value SMTP email address prefix for topics to prevent spam (e.g. 'ntfy-') [$NTFY_SMTP_SERVER_ADDR_PREFIX]
|
||||||
--global-topic-limit value, -T value total number of topics allowed (default: 15000) [$NTFY_GLOBAL_TOPIC_LIMIT]
|
--global-topic-limit value, --global_topic_limit value, -T value total number of topics allowed (default: 15000) [$NTFY_GLOBAL_TOPIC_LIMIT]
|
||||||
--visitor-subscription-limit value number of subscriptions per visitor (default: 30) [$NTFY_VISITOR_SUBSCRIPTION_LIMIT]
|
--visitor-subscription-limit value, --visitor_subscription_limit value number of subscriptions per visitor (default: 30) [$NTFY_VISITOR_SUBSCRIPTION_LIMIT]
|
||||||
--visitor-attachment-total-size-limit value total storage limit used for attachments per visitor (default: "100M") [$NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT]
|
--visitor-attachment-total-size-limit value, --visitor_attachment_total_size_limit value total storage limit used for attachments per visitor (default: "100M") [$NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT]
|
||||||
--visitor-attachment-daily-bandwidth-limit value total daily attachment download/upload bandwidth limit per visitor (default: "500M") [$NTFY_VISITOR_ATTACHMENT_DAILY_BANDWIDTH_LIMIT]
|
--visitor-attachment-daily-bandwidth-limit value, --visitor_attachment_daily_bandwidth_limit value total daily attachment download/upload bandwidth limit per visitor (default: "500M") [$NTFY_VISITOR_ATTACHMENT_DAILY_BANDWIDTH_LIMIT]
|
||||||
--visitor-request-limit-burst value initial limit of requests per visitor (default: 60) [$NTFY_VISITOR_REQUEST_LIMIT_BURST]
|
--visitor-request-limit-burst value, --visitor_request_limit_burst value initial limit of requests per visitor (default: 60) [$NTFY_VISITOR_REQUEST_LIMIT_BURST]
|
||||||
--visitor-request-limit-replenish value interval at which burst limit is replenished (one per x) (default: 5s) [$NTFY_VISITOR_REQUEST_LIMIT_REPLENISH]
|
--visitor-request-limit-replenish value, --visitor_request_limit_replenish value interval at which burst limit is replenished (one per x) (default: 5s) [$NTFY_VISITOR_REQUEST_LIMIT_REPLENISH]
|
||||||
--visitor-request-limit-exempt-hosts value hostnames and/or IP addresses of hosts that will be exempt from the visitor request limit [$NTFY_VISITOR_REQUEST_LIMIT_EXEMPT_HOSTS]
|
--visitor-request-limit-exempt-hosts value, --visitor_request_limit_exempt_hosts value hostnames and/or IP addresses of hosts that will be exempt from the visitor request limit [$NTFY_VISITOR_REQUEST_LIMIT_EXEMPT_HOSTS]
|
||||||
--visitor-email-limit-burst value initial limit of e-mails per visitor (default: 16) [$NTFY_VISITOR_EMAIL_LIMIT_BURST]
|
--visitor-email-limit-burst value, --visitor_email_limit_burst value initial limit of e-mails per visitor (default: 16) [$NTFY_VISITOR_EMAIL_LIMIT_BURST]
|
||||||
--visitor-email-limit-replenish value interval at which burst limit is replenished (one per x) (default: 1h0m0s) [$NTFY_VISITOR_EMAIL_LIMIT_REPLENISH]
|
--visitor-email-limit-replenish value, --visitor_email_limit_replenish value interval at which burst limit is replenished (one per x) (default: 1h0m0s) [$NTFY_VISITOR_EMAIL_LIMIT_REPLENISH]
|
||||||
--behind-proxy, -P if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting) (default: false) [$NTFY_BEHIND_PROXY]
|
--behind-proxy, --behind_proxy, -P if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting) (default: false) [$NTFY_BEHIND_PROXY]
|
||||||
--help, -h show help (default: false)
|
--help, -h show help (default: false)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ Typical commands (more see below):
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to build the **ntfy binary including web app and docs for all supported architectures** (amd64, armv7, and amd64),
|
If you want to build the **ntfy binary including web app and docs for all supported architectures** (amd64, armv7, and arm64),
|
||||||
you can simply run `make build`:
|
you can simply run `make build`:
|
||||||
|
|
||||||
``` shell
|
``` shell
|
||||||
@@ -162,13 +162,14 @@ To build only the `ntfy` binary **without the web app or documentation**, use th
|
|||||||
|
|
||||||
``` shell
|
``` shell
|
||||||
$ make
|
$ make
|
||||||
Build server & client (not release version):
|
Build server & client (using GoReleaser, not release version):
|
||||||
make cli - Build server & client (all architectures)
|
make cli - Build server & client (all architectures)
|
||||||
make cli-linux-amd64 - Build server & client (Linux, amd64 only)
|
make cli-linux-amd64 - Build server & client (Linux, amd64 only)
|
||||||
make cli-linux-armv6 - Build server & client (Linux, armv6 only)
|
make cli-linux-armv6 - Build server & client (Linux, armv6 only)
|
||||||
make cli-linux-armv7 - Build server & client (Linux, armv7 only)
|
make cli-linux-armv7 - Build server & client (Linux, armv7 only)
|
||||||
make cli-linux-arm64 - Build server & client (Linux, arm64 only)
|
make cli-linux-arm64 - Build server & client (Linux, arm64 only)
|
||||||
make cli-windows-amd64 - Build client (Windows, amd64 only)
|
make cli-windows-amd64 - Build client (Windows, amd64 only)
|
||||||
|
make cli-darwin-all - Build client (macOS, arm64+amd64 universal binary)
|
||||||
```
|
```
|
||||||
|
|
||||||
So if you're on an amd64/x86_64-based machine, you may just want to run `make cli-linux-amd64` during testing. On a modern
|
So if you're on an amd64/x86_64-based machine, you may just want to run `make cli-linux-amd64` during testing. On a modern
|
||||||
@@ -199,8 +200,10 @@ server/server.go:85:13: pattern docs: no matching files found
|
|||||||
|
|
||||||
This is because we use `go:embed` to embed the documentation and web app, so the Go code expects files to be
|
This is because we use `go:embed` to embed the documentation and web app, so the Go code expects files to be
|
||||||
present at `server/docs` and `server/site`. If they are not, you'll see the above error. The `cli-deps-static-sites`
|
present at `server/docs` and `server/site`. If they are not, you'll see the above error. The `cli-deps-static-sites`
|
||||||
target creates dummy files that ensures that you'll be able to build.
|
target creates dummy files that ensure that you'll be able to build.
|
||||||
|
|
||||||
|
While not officially supported (or released), you can build and run the server **on macOS** as well. Simply run
|
||||||
|
`make cli-darwin-server` to build a binary, or `go run main.go serve` (see above) to run it.
|
||||||
|
|
||||||
### Build the web app
|
### Build the web app
|
||||||
The sources for the web app live in `web/`. As long as you have `npm` installed (see above), building the web app
|
The sources for the web app live in `web/`. As long as you have `npm` installed (see above), building the web app
|
||||||
@@ -284,9 +287,13 @@ Then either follow the steps for building with or without Firebase.
|
|||||||
I do build the ntfy Android app using IntelliJ IDEA (Android Studio), so I don't know if these Gradle commands will
|
I do build the ntfy Android app using IntelliJ IDEA (Android Studio), so I don't know if these Gradle commands will
|
||||||
work without issues. Please give me feedback if it does/doesn't work for you.
|
work without issues. Please give me feedback if it does/doesn't work for you.
|
||||||
|
|
||||||
Without Firebase, you may want to still change the default `app_base_url` in [strings.xml](https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/res/values/strings.xml)
|
Without Firebase, you may want to still change the default `app_base_url` in [values.xml](https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/res/values/values.xml)
|
||||||
if you're self-hosting the server. Then run:
|
if you're self-hosting the server. Then run:
|
||||||
```
|
```
|
||||||
|
# Remove Google dependencies (FCM)
|
||||||
|
sed -i -e '/google-services/d' build.gradle
|
||||||
|
sed -i -e '/google-services/d' app/build.gradle
|
||||||
|
|
||||||
# To build an unsigned .apk (app/build/outputs/apk/fdroid/*.apk)
|
# To build an unsigned .apk (app/build/outputs/apk/fdroid/*.apk)
|
||||||
./gradlew assembleFdroidRelease
|
./gradlew assembleFdroidRelease
|
||||||
|
|
||||||
@@ -303,7 +310,7 @@ To build your own version with Firebase, you must:
|
|||||||
|
|
||||||
* Create a Firebase/FCM account
|
* Create a Firebase/FCM account
|
||||||
* Place your account file at `app/google-services.json`
|
* Place your account file at `app/google-services.json`
|
||||||
* And change `app_base_url` in [strings.xml](https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/res/values/strings.xml)
|
* And change `app_base_url` in [values.xml](https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/res/values/values.xml)
|
||||||
* Then run:
|
* Then run:
|
||||||
```
|
```
|
||||||
# To build an unsigned .apk (app/build/outputs/apk/play/*.apk)
|
# To build an unsigned .apk (app/build/outputs/apk/play/*.apk)
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ There are a million ways to use ntfy, but here are some inspirations. I try to c
|
|||||||
<a href="https://github.com/binwiederhier/ntfy/tree/main/examples">examples on GitHub</a>, so be sure to check
|
<a href="https://github.com/binwiederhier/ntfy/tree/main/examples">examples on GitHub</a>, so be sure to check
|
||||||
those out, too.
|
those out, too.
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
Many of these examples were contributed by ntfy users. If you have other examples of how you use ntfy, please
|
||||||
|
[create a pull request](https://github.com/binwiederhier/ntfy/pulls), and I'll happily include it. Also note, that
|
||||||
|
I cannot guarantee that all of these examples are functional. Many of them I have not tried myself.
|
||||||
|
|
||||||
## A long process is done: backups, copying data, pipelines, ...
|
## A long process is done: backups, copying data, pipelines, ...
|
||||||
I started adding notifications pretty much all of my scripts. Typically, I just chain the <tt>curl</tt> call
|
I started adding notifications pretty much all of my scripts. Typically, I just chain the <tt>curl</tt> call
|
||||||
directly to the command I'm running. The following example will either send <i>Laptop backup succeeded</i>
|
directly to the command I'm running. The following example will either send <i>Laptop backup succeeded</i>
|
||||||
@@ -98,7 +103,8 @@ One of my co-workers uses the following Ansible task to let him know when things
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Watchtower notifications (shoutrrr)
|
## Watchtower notifications (shoutrrr)
|
||||||
You can use `shoutrrr` generic webhook support to send watchtower notifications to your ntfy topic.
|
You can use [shoutrrr](https://github.com/containrrr/shoutrrr) generic webhook support to send
|
||||||
|
[Watchtower](https://github.com/containrrr/watchtower/) notifications to your ntfy topic.
|
||||||
|
|
||||||
Example docker-compose.yml:
|
Example docker-compose.yml:
|
||||||
```yml
|
```yml
|
||||||
@@ -122,7 +128,6 @@ GitHub have been hopeless. In case it ever becomes available, I want to know imm
|
|||||||
``` cron
|
``` cron
|
||||||
# Check github/ntfy user
|
# Check github/ntfy user
|
||||||
*/6 * * * * if curl -s https://api.github.com/users/ntfy | grep "Not Found"; then curl -d "github.com/ntfy is available" -H "Tags: tada" -H "Prio: high" ntfy.sh/my-alerts; fi
|
*/6 * * * * if curl -s https://api.github.com/users/ntfy | grep "Not Found"; then curl -d "github.com/ntfy is available" -H "Tags: tada" -H "Prio: high" ntfy.sh/my-alerts; fi
|
||||||
~
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Download notifications (Sonarr, Radarr, Lidarr, Readarr, Prowlarr, SABnzbd)
|
## Download notifications (Sonarr, Radarr, Lidarr, Readarr, Prowlarr, SABnzbd)
|
||||||
@@ -132,11 +137,10 @@ Some simple bash scripts to achieve this are kindly provided in [nickexyz's repo
|
|||||||
## Node-RED
|
## Node-RED
|
||||||
You can use the HTTP request node to send messages with [Node-RED](https://nodered.org), some examples:
|
You can use the HTTP request node to send messages with [Node-RED](https://nodered.org), some examples:
|
||||||
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Example: Send a message (click to expand)</summary>
|
<summary>Example: Send a message (click to expand)</summary>
|
||||||
|
|
||||||
```
|
``` json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "c956e688cc74ad8e",
|
"id": "c956e688cc74ad8e",
|
||||||
@@ -225,7 +229,7 @@ You can use the HTTP request node to send messages with [Node-RED](https://noder
|
|||||||
<details>
|
<details>
|
||||||
<summary>Example: Send a picture (click to expand)</summary>
|
<summary>Example: Send a picture (click to expand)</summary>
|
||||||
|
|
||||||
```
|
``` json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "d135a13eadeb9d6d",
|
"id": "d135a13eadeb9d6d",
|
||||||
@@ -341,8 +345,8 @@ You can use the HTTP request node to send messages with [Node-RED](https://noder
|
|||||||
|
|
||||||
## Gatus service health check
|
## Gatus service health check
|
||||||
|
|
||||||
An example for a custom alert with <a href="https://github.com/TwiN/gatus">Gatus</a>
|
An example for a custom alert with [Gatus](https://github.com/TwiN/gatus):
|
||||||
```
|
``` yaml
|
||||||
alerting:
|
alerting:
|
||||||
custom:
|
custom:
|
||||||
url: "https://ntfy.sh"
|
url: "https://ntfy.sh"
|
||||||
@@ -366,3 +370,18 @@ alerting:
|
|||||||
TRIGGERED: "warning"
|
TRIGGERED: "warning"
|
||||||
RESOLVED: "white_check_mark"
|
RESOLVED: "white_check_mark"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Jellyseerr/Overseerr webhook
|
||||||
|
Here is an example for [jellyseerr](https://github.com/Fallenbagel/jellyseerr)/[overseerr](https://overseerr.dev/) webhook
|
||||||
|
JSON payload. Remember to change the `https://requests.example.com` to your jellyseerr/overseerr URL.
|
||||||
|
|
||||||
|
``` json
|
||||||
|
{
|
||||||
|
"topic": "requests",
|
||||||
|
"title": "{{event}}",
|
||||||
|
"message": "{{subject}}\n{{message}}\n\nRequested by: {{requestedBy_username}}\n\nStatus: {{media_status}}\nRequest Id: {{request_id}}",
|
||||||
|
"priority": 4,
|
||||||
|
"attach": "{{image}}",
|
||||||
|
"click": "https://requests.example.com/{{media_type}}/{{media_tmdbid}}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -26,37 +26,37 @@ deb/rpm packages.
|
|||||||
|
|
||||||
=== "x86_64/amd64"
|
=== "x86_64/amd64"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.22.0/ntfy_1.22.0_linux_x86_64.tar.gz
|
wget https://github.com/binwiederhier/ntfy/releases/download/v1.23.0/ntfy_1.23.0_linux_x86_64.tar.gz
|
||||||
tar zxvf ntfy_1.22.0_linux_x86_64.tar.gz
|
tar zxvf ntfy_1.23.0_linux_x86_64.tar.gz
|
||||||
sudo cp -a ntfy_1.22.0_linux_x86_64/ntfy /usr/bin/ntfy
|
sudo cp -a ntfy_1.23.0_linux_x86_64/ntfy /usr/bin/ntfy
|
||||||
sudo mkdir /etc/ntfy && sudo cp ntfy_1.22.0_linux_x86_64/{client,server}/*.yml /etc/ntfy
|
sudo mkdir /etc/ntfy && sudo cp ntfy_1.23.0_linux_x86_64/{client,server}/*.yml /etc/ntfy
|
||||||
sudo ntfy serve
|
sudo ntfy serve
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "armv6"
|
=== "armv6"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.22.0/ntfy_1.22.0_linux_armv6.tar.gz
|
wget https://github.com/binwiederhier/ntfy/releases/download/v1.23.0/ntfy_1.23.0_linux_armv6.tar.gz
|
||||||
tar zxvf ntfy_1.22.0_linux_armv6.tar.gz
|
tar zxvf ntfy_1.23.0_linux_armv6.tar.gz
|
||||||
sudo cp -a ntfy_1.22.0_linux_armv6/ntfy /usr/bin/ntfy
|
sudo cp -a ntfy_1.23.0_linux_armv6/ntfy /usr/bin/ntfy
|
||||||
sudo mkdir /etc/ntfy && sudo cp ntfy_1.22.0_linux_armv6/{client,server}/*.yml /etc/ntfy
|
sudo mkdir /etc/ntfy && sudo cp ntfy_1.23.0_linux_armv6/{client,server}/*.yml /etc/ntfy
|
||||||
sudo ntfy serve
|
sudo ntfy serve
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "armv7/armhf"
|
=== "armv7/armhf"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.22.0/ntfy_1.22.0_linux_armv7.tar.gz
|
wget https://github.com/binwiederhier/ntfy/releases/download/v1.23.0/ntfy_1.23.0_linux_armv7.tar.gz
|
||||||
tar zxvf ntfy_1.22.0_linux_armv7.tar.gz
|
tar zxvf ntfy_1.23.0_linux_armv7.tar.gz
|
||||||
sudo cp -a ntfy_1.22.0_linux_armv7/ntfy /usr/bin/ntfy
|
sudo cp -a ntfy_1.23.0_linux_armv7/ntfy /usr/bin/ntfy
|
||||||
sudo mkdir /etc/ntfy && sudo cp ntfy_1.22.0_linux_armv7/{client,server}/*.yml /etc/ntfy
|
sudo mkdir /etc/ntfy && sudo cp ntfy_1.23.0_linux_armv7/{client,server}/*.yml /etc/ntfy
|
||||||
sudo ntfy serve
|
sudo ntfy serve
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "arm64"
|
=== "arm64"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.22.0/ntfy_1.22.0_linux_arm64.tar.gz
|
wget https://github.com/binwiederhier/ntfy/releases/download/v1.23.0/ntfy_1.23.0_linux_arm64.tar.gz
|
||||||
tar zxvf ntfy_1.22.0_linux_arm64.tar.gz
|
tar zxvf ntfy_1.23.0_linux_arm64.tar.gz
|
||||||
sudo cp -a ntfy_1.22.0_linux_arm64/ntfy /usr/bin/ntfy
|
sudo cp -a ntfy_1.23.0_linux_arm64/ntfy /usr/bin/ntfy
|
||||||
sudo mkdir /etc/ntfy && sudo cp ntfy_1.22.0_linux_arm64/{client,server}/*.yml /etc/ntfy
|
sudo mkdir /etc/ntfy && sudo cp ntfy_1.23.0_linux_arm64/{client,server}/*.yml /etc/ntfy
|
||||||
sudo ntfy serve
|
sudo ntfy serve
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ Manually installing the .deb file:
|
|||||||
|
|
||||||
=== "x86_64/amd64"
|
=== "x86_64/amd64"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.22.0/ntfy_1.22.0_linux_amd64.deb
|
wget https://github.com/binwiederhier/ntfy/releases/download/v1.23.0/ntfy_1.23.0_linux_amd64.deb
|
||||||
sudo dpkg -i ntfy_*.deb
|
sudo dpkg -i ntfy_*.deb
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
@@ -111,7 +111,7 @@ Manually installing the .deb file:
|
|||||||
|
|
||||||
=== "armv6"
|
=== "armv6"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.22.0/ntfy_1.22.0_linux_armv6.deb
|
wget https://github.com/binwiederhier/ntfy/releases/download/v1.23.0/ntfy_1.23.0_linux_armv6.deb
|
||||||
sudo dpkg -i ntfy_*.deb
|
sudo dpkg -i ntfy_*.deb
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
@@ -119,7 +119,7 @@ Manually installing the .deb file:
|
|||||||
|
|
||||||
=== "armv7/armhf"
|
=== "armv7/armhf"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.22.0/ntfy_1.22.0_linux_armv7.deb
|
wget https://github.com/binwiederhier/ntfy/releases/download/v1.23.0/ntfy_1.23.0_linux_armv7.deb
|
||||||
sudo dpkg -i ntfy_*.deb
|
sudo dpkg -i ntfy_*.deb
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
@@ -127,7 +127,7 @@ Manually installing the .deb file:
|
|||||||
|
|
||||||
=== "arm64"
|
=== "arm64"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.22.0/ntfy_1.22.0_linux_arm64.deb
|
wget https://github.com/binwiederhier/ntfy/releases/download/v1.23.0/ntfy_1.23.0_linux_arm64.deb
|
||||||
sudo dpkg -i ntfy_*.deb
|
sudo dpkg -i ntfy_*.deb
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
@@ -137,28 +137,28 @@ Manually installing the .deb file:
|
|||||||
|
|
||||||
=== "x86_64/amd64"
|
=== "x86_64/amd64"
|
||||||
```bash
|
```bash
|
||||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.22.0/ntfy_1.22.0_linux_amd64.rpm
|
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.23.0/ntfy_1.23.0_linux_amd64.rpm
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "armv6"
|
=== "armv6"
|
||||||
```bash
|
```bash
|
||||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.22.0/ntfy_1.22.0_linux_armv6.rpm
|
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.23.0/ntfy_1.23.0_linux_armv6.rpm
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "armv7/armhf"
|
=== "armv7/armhf"
|
||||||
```bash
|
```bash
|
||||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.22.0/ntfy_1.22.0_linux_armv7.rpm
|
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.23.0/ntfy_1.23.0_linux_armv7.rpm
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "arm64"
|
=== "arm64"
|
||||||
```bash
|
```bash
|
||||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.22.0/ntfy_1.22.0_linux_arm64.rpm
|
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.23.0/ntfy_1.23.0_linux_arm64.rpm
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
```
|
```
|
||||||
@@ -184,21 +184,23 @@ If run as `root`, ntfy will look for its config at `/etc/ntfy/client.yml`. For a
|
|||||||
`~/Library/Application Support/ntfy/client.yml` (sample included in the tarball).
|
`~/Library/Application Support/ntfy/client.yml` (sample included in the tarball).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl https://github.com/binwiederhier/ntfy/releases/download/v1.22.0/ntfy_v1.22.0_macOS_all.tar.gz > ntfy_v1.22.0_macOS_all.tar.gz
|
curl https://github.com/binwiederhier/ntfy/releases/download/v1.23.0/ntfy_v1.23.0_macOS_all.tar.gz > ntfy_v1.23.0_macOS_all.tar.gz
|
||||||
tar zxvf ntfy_v1.22.0_macOS_all.tar.gz
|
tar zxvf ntfy_v1.23.0_macOS_all.tar.gz
|
||||||
sudo cp -a ntfy_v1.22.0_macOS_all/ntfy /usr/local/bin/ntfy
|
sudo cp -a ntfy_v1.23.0_macOS_all/ntfy /usr/local/bin/ntfy
|
||||||
mkdir ~/Library/Application\ Support/ntfy
|
mkdir ~/Library/Application\ Support/ntfy
|
||||||
cp ntfy_v1.22.0_macOS_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
|
cp ntfy_v1.23.0_macOS_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
|
||||||
ntfy --help
|
ntfy --help
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
If there is a desire to install ntfy via [Homebrew](https://brew.sh/), please create a
|
If there is a desire to install ntfy via [Homebrew](https://brew.sh/), please create a
|
||||||
[GitHub issue](https://github.com/binwiederhier/ntfy/issues) to let me know.
|
[GitHub issue](https://github.com/binwiederhier/ntfy/issues) to let me know. Also, you can build and run the
|
||||||
|
ntfy server on macOS as well, though I don't officially support that. Check out the [build instructions](develop.md)
|
||||||
|
for details.
|
||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on Windows as well.
|
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on Windows as well.
|
||||||
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v1.22.0/ntfy_v1.22.0-next_windows_x86_64.zip),
|
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v1.23.0/ntfy_v1.23.0-next_windows_x86_64.zip),
|
||||||
extract it and place the `ntfy.exe` binary somewhere in your `%Path%`.
|
extract it and place the `ntfy.exe` binary somewhere in your `%Path%`.
|
||||||
|
|
||||||
The default path for the client config file is at `%AppData%\ntfy\client.yml` (not created automatically, sample in the ZIP file).
|
The default path for the client config file is at `%AppData%\ntfy\client.yml` (not created automatically, sample in the ZIP file).
|
||||||
|
|||||||
@@ -2,19 +2,38 @@
|
|||||||
Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases)
|
Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases)
|
||||||
and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases).
|
and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases).
|
||||||
|
|
||||||
<!--
|
## ntfy server v1.23.0
|
||||||
|
Released May 21, 2022
|
||||||
## ntfy server v1.23.0 (UNRELEASED)
|
|
||||||
|
|
||||||
**Features:**
|
**Features:**
|
||||||
|
|
||||||
* [Windows](https://ntfy.sh/docs/install/#windows) and [macOS](https://ntfy.sh/docs/install/#macos) builds for the [ntfy CLI](https://ntfy.sh/docs/subscribe/cli/) ([#112](https://github.com/binwiederhier/ntfy/issues/112))
|
* [Windows](https://ntfy.sh/docs/install/#windows) and [macOS](https://ntfy.sh/docs/install/#macos) builds for the [ntfy CLI](https://ntfy.sh/docs/subscribe/cli/) ([#112](https://github.com/binwiederhier/ntfy/issues/112))
|
||||||
|
* Ability to disable the web app entirely ([#238](https://github.com/binwiederhier/ntfy/issues/238)/[#249](https://github.com/binwiederhier/ntfy/pull/249), thanks to [@Curid](https://github.com/Curid))
|
||||||
|
* Add APNs config to Firebase messages to support [iOS app](https://github.com/binwiederhier/ntfy/issues/4) ([#247](https://github.com/binwiederhier/ntfy/pull/247), thanks to [@Copephobia](https://github.com/Copephobia))
|
||||||
|
|
||||||
|
**Bugs:**
|
||||||
|
|
||||||
|
* Support underscores in server.yml config options ([#255](https://github.com/binwiederhier/ntfy/issues/255), thanks to [@ajdelgado](https://github.com/ajdelgado))
|
||||||
|
* Force MAKEFLAGS to --jobs=1 in `Makefile` ([#257](https://github.com/binwiederhier/ntfy/pull/257), thanks to [@oddlama](https://github.com/oddlama))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
|
||||||
|
* Typo in install instructions ([#252](https://github.com/binwiederhier/ntfy/pull/252)/[#251](https://github.com/binwiederhier/ntfy/issues/251), thanks to [@oddlama](https://github.com/oddlama))
|
||||||
|
* Fix typo in private server example ([#262](https://github.com/binwiederhier/ntfy/pull/262), thanks to [@MayeulC](https://github.com/MayeulC))
|
||||||
|
* [Examples](examples.md) for [jellyseerr](https://github.com/Fallenbagel/jellyseerr)/[overseerr](https://overseerr.dev/) ([#264](https://github.com/binwiederhier/ntfy/pull/264), thanks to [@Fallenbagel](https://github.com/Fallenbagel))
|
||||||
|
|
||||||
**Additional translations:**
|
**Additional translations:**
|
||||||
|
|
||||||
* Portuguese/Brazil (thanks to [@tiagotriques](https://hosted.weblate.org/user/tiagotriques/))
|
* Portuguese/Brazil (thanks to [@tiagotriques](https://hosted.weblate.org/user/tiagotriques/) and [@pireshenrique22](https://hosted.weblate.org/user/pireshenrique22/))
|
||||||
|
|
||||||
## ntfy Android app v1.13.0 (UNRELEASED)
|
Thank you to the many translators, who helped translate the new strings so quickly. I am humbled and amazed by your help.
|
||||||
|
|
||||||
|
## ntfy Android app v1.13.0
|
||||||
|
Released May 11, 2022
|
||||||
|
|
||||||
|
This release brings a slightly altered design for the detail view, featuring a card layout to make notifications more easily
|
||||||
|
distinguishable from one another. It also ships per-topic settings that allow overriding minimum priority, auto delete threshold
|
||||||
|
and custom icons. Aside from that, we've got tons of bug fixes as usual.
|
||||||
|
|
||||||
**Features:**
|
**Features:**
|
||||||
|
|
||||||
@@ -40,8 +59,6 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
|
|||||||
Thanks to [@cmeis](https://github.com/cmeis), [@StoyanDimitrov](https://github.com/StoyanDimitrov), [@Fallenbagel](https://github.com/Fallenbagel) for testing, and
|
Thanks to [@cmeis](https://github.com/cmeis), [@StoyanDimitrov](https://github.com/StoyanDimitrov), [@Fallenbagel](https://github.com/Fallenbagel) for testing, and
|
||||||
to [@Joeharrison94](https://github.com/Joeharrison94) for the input. And thank you very much to all the translators for catching up so quickly.
|
to [@Joeharrison94](https://github.com/Joeharrison94) for the input. And thank you very much to all the translators for catching up so quickly.
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
## ntfy server v1.22.0
|
## ntfy server v1.22.0
|
||||||
Released May 7, 2022
|
Released May 7, 2022
|
||||||
|
|
||||||
|
|||||||
26
go.mod
26
go.mod
@@ -4,23 +4,23 @@ go 1.17
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/firestore v1.6.1 // indirect
|
cloud.google.com/go/firestore v1.6.1 // indirect
|
||||||
cloud.google.com/go/storage v1.22.0 // indirect
|
cloud.google.com/go/storage v1.22.1 // indirect
|
||||||
firebase.google.com/go v3.13.0+incompatible
|
firebase.google.com/go v3.13.0+incompatible
|
||||||
github.com/BurntSushi/toml v1.1.0 // indirect
|
github.com/BurntSushi/toml v1.1.0 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
github.com/emersion/go-smtp v0.15.0
|
github.com/emersion/go-smtp v0.15.0
|
||||||
github.com/gabriel-vasile/mimetype v1.4.0
|
github.com/gabriel-vasile/mimetype v1.4.0
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.12
|
github.com/mattn/go-sqlite3 v1.14.13
|
||||||
github.com/olebedev/when v0.0.0-20211212231525-59bd4edcf9d6
|
github.com/olebedev/when v0.0.0-20211212231525-59bd4edcf9d6
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/urfave/cli/v2 v2.6.0
|
github.com/urfave/cli/v2 v2.7.1
|
||||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
|
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898
|
||||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
|
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
||||||
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171
|
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171
|
||||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306
|
||||||
google.golang.org/api v0.79.0
|
google.golang.org/api v0.80.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,23 +31,25 @@ require (
|
|||||||
cloud.google.com/go/compute v1.6.1 // indirect
|
cloud.google.com/go/compute v1.6.1 // indirect
|
||||||
cloud.google.com/go/iam v0.3.0 // indirect
|
cloud.google.com/go/iam v0.3.0 // indirect
|
||||||
github.com/AlekSi/pointer v1.2.0 // indirect
|
github.com/AlekSi/pointer v1.2.0 // indirect
|
||||||
|
github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac // indirect
|
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/google/go-cmp v0.5.8 // indirect
|
github.com/google/go-cmp v0.5.8 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
|
||||||
github.com/googleapis/go-type-adapters v1.0.0 // indirect
|
github.com/googleapis/go-type-adapters v1.0.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
go.opencensus.io v0.23.0 // indirect
|
go.opencensus.io v0.23.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
|
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
|
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 // indirect
|
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect
|
||||||
google.golang.org/grpc v1.46.0 // indirect
|
google.golang.org/grpc v1.46.2 // indirect
|
||||||
google.golang.org/protobuf v1.28.0 // indirect
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
48
go.sum
48
go.sum
@@ -56,8 +56,9 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
|||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
cloud.google.com/go/storage v1.22.0 h1:NUV0NNp9nkBuW66BFRLuMgldN60C57ET3dhbwLIYio8=
|
|
||||||
cloud.google.com/go/storage v1.22.0/go.mod h1:GbaLEoMqbVm6sx3Z0R++gSiBlgMv6yUi2q1DeGFKQgE=
|
cloud.google.com/go/storage v1.22.0/go.mod h1:GbaLEoMqbVm6sx3Z0R++gSiBlgMv6yUi2q1DeGFKQgE=
|
||||||
|
cloud.google.com/go/storage v1.22.1 h1:F6IlQJZrZM++apn9V5/VfS3gbTUYg98PS3EMQAzqtfg=
|
||||||
|
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4=
|
firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4=
|
||||||
firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs=
|
firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs=
|
||||||
@@ -70,6 +71,8 @@ github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
|
|||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
|
github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0 h1:R/qAiUxFT3mNgQaNqJe0IVznjKRNm23ohAIh9lgtlzc=
|
||||||
|
github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0/go.mod h1:v3ZDlfVAL1OrkKHbGSFFK60k0/7hruHPDq2XMs9Gu6U=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
@@ -187,13 +190,16 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe
|
|||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||||
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
|
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
|
||||||
github.com/googleapis/gax-go/v2 v2.3.0 h1:nRJtk3y8Fm770D42QV6T90ZnvFZyk7agSo3Q+Z9p3WI=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
|
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
|
||||||
github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA=
|
github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA=
|
||||||
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
|
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
@@ -211,8 +217,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
|||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
|
github.com/mattn/go-sqlite3 v1.14.13 h1:1tj15ngiFfcZzii7yd82foL+ks+ouQcj8j/TPq3fk1I=
|
||||||
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.13/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/olebedev/when v0.0.0-20211212231525-59bd4edcf9d6 h1:oDSPaYiL2dbjcArLrFS8ANtwgJMyOLzvQCZon+XmFsk=
|
github.com/olebedev/when v0.0.0-20211212231525-59bd4edcf9d6 h1:oDSPaYiL2dbjcArLrFS8ANtwgJMyOLzvQCZon+XmFsk=
|
||||||
github.com/olebedev/when v0.0.0-20211212231525-59bd4edcf9d6/go.mod h1:DPucAeQGDPUzYUt+NaWw6qsF5SFapWWToxEiVDh2aV0=
|
github.com/olebedev/when v0.0.0-20211212231525-59bd4edcf9d6/go.mod h1:DPucAeQGDPUzYUt+NaWw6qsF5SFapWWToxEiVDh2aV0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
@@ -233,8 +239,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/urfave/cli/v2 v2.6.0 h1:yj2Drkflh8X/zUrkWlWlUjZYHyWN7WMmpVxyxXIUyv8=
|
github.com/urfave/cli/v2 v2.7.1 h1:DsAOFeI9T0vmUW4LiGR5mhuCIn5kqGIE4WMU2ytmH00=
|
||||||
github.com/urfave/cli/v2 v2.6.0/go.mod h1:oDzoM7pVwz6wHn5ogWgFUU1s4VJayeQS+aEZDqXIEJs=
|
github.com/urfave/cli/v2 v2.7.1/go.mod h1:TYFbtzt/azQoJOrGH5mDfZtS0jIkl/OeFwlRWPR9KRM=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
@@ -254,8 +260,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8=
|
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0=
|
||||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@@ -332,8 +338,9 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
|||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
|
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
|
||||||
|
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -365,8 +372,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
|
||||||
|
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -422,8 +430,10 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
|
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
|
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
|
||||||
@@ -498,8 +508,9 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
|
|
||||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
|
||||||
|
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
@@ -538,8 +549,9 @@ google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc
|
|||||||
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
|
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
|
||||||
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
|
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
|
||||||
google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
|
google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
|
||||||
google.golang.org/api v0.79.0 h1:vaOcm0WdXvhGkci9a0+CcQVZqSRjN8ksSBlWv99f8Pg=
|
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
|
||||||
google.golang.org/api v0.79.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
|
google.golang.org/api v0.80.0 h1:IQWaGVCYnsm4MO3hh+WtSXMzMzuyFx/fuR8qkN3A0Qo=
|
||||||
|
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
@@ -623,9 +635,12 @@ google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX
|
|||||||
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||||
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||||
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||||
|
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||||
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 h1:q1kiSVscqoDeqTF27eQ2NnLLDmqF0I373qQNXYMy0fo=
|
|
||||||
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||||
|
google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||||
|
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I=
|
||||||
|
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
@@ -654,8 +669,9 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K
|
|||||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||||
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||||
google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8=
|
|
||||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||||
|
google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ=
|
||||||
|
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ type Config struct {
|
|||||||
VisitorEmailLimitBurst int
|
VisitorEmailLimitBurst int
|
||||||
VisitorEmailLimitReplenish time.Duration
|
VisitorEmailLimitReplenish time.Duration
|
||||||
BehindProxy bool
|
BehindProxy bool
|
||||||
|
EnableWeb bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig instantiates a default new server config
|
// NewConfig instantiates a default new server config
|
||||||
@@ -126,5 +127,6 @@ func NewConfig() *Config {
|
|||||||
VisitorEmailLimitBurst: DefaultVisitorEmailLimitBurst,
|
VisitorEmailLimitBurst: DefaultVisitorEmailLimitBurst,
|
||||||
VisitorEmailLimitReplenish: DefaultVisitorEmailLimitReplenish,
|
VisitorEmailLimitReplenish: DefaultVisitorEmailLimitReplenish,
|
||||||
BehindProxy: false,
|
BehindProxy: false,
|
||||||
|
EnableWeb: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/emersion/go-smtp"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
"heckel.io/ntfy/auth"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@@ -28,6 +23,12 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/emersion/go-smtp"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"heckel.io/ntfy/auth"
|
||||||
|
"heckel.io/ntfy/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server is the main server, providing the UI and API for ntfy
|
// Server is the main server, providing the UI and API for ntfy
|
||||||
@@ -263,23 +264,23 @@ func (s *Server) handle(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||||
if r.Method == http.MethodGet && r.URL.Path == "/" {
|
if r.Method == http.MethodGet && r.URL.Path == "/" {
|
||||||
return s.handleHome(w, r)
|
return s.ensureWebEnabled(s.handleHome)(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && r.URL.Path == "/example.html" {
|
} else if r.Method == http.MethodGet && r.URL.Path == "/example.html" {
|
||||||
return s.handleExample(w, r)
|
return s.ensureWebEnabled(s.handleExample)(w, r, v)
|
||||||
} else if r.Method == http.MethodHead && r.URL.Path == "/" {
|
} else if r.Method == http.MethodHead && r.URL.Path == "/" {
|
||||||
return s.handleEmpty(w, r, v)
|
return s.ensureWebEnabled(s.handleEmpty)(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && r.URL.Path == webConfigPath {
|
} else if r.Method == http.MethodGet && r.URL.Path == webConfigPath {
|
||||||
return s.handleWebConfig(w, r)
|
return s.ensureWebEnabled(s.handleWebConfig)(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && r.URL.Path == userStatsPath {
|
} else if r.Method == http.MethodGet && r.URL.Path == userStatsPath {
|
||||||
return s.handleUserStats(w, r, v)
|
return s.handleUserStats(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && staticRegex.MatchString(r.URL.Path) {
|
} else if r.Method == http.MethodGet && staticRegex.MatchString(r.URL.Path) {
|
||||||
return s.handleStatic(w, r)
|
return s.ensureWebEnabled(s.handleStatic)(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && docsRegex.MatchString(r.URL.Path) {
|
} else if r.Method == http.MethodGet && docsRegex.MatchString(r.URL.Path) {
|
||||||
return s.handleDocs(w, r)
|
return s.ensureWebEnabled(s.handleDocs)(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && fileRegex.MatchString(r.URL.Path) && s.config.AttachmentCacheDir != "" {
|
} else if r.Method == http.MethodGet && fileRegex.MatchString(r.URL.Path) && s.config.AttachmentCacheDir != "" {
|
||||||
return s.limitRequests(s.handleFile)(w, r, v)
|
return s.limitRequests(s.handleFile)(w, r, v)
|
||||||
} else if r.Method == http.MethodOptions {
|
} else if r.Method == http.MethodOptions {
|
||||||
return s.handleOptions(w, r)
|
return s.ensureWebEnabled(s.handleOptions)(w, r, v)
|
||||||
} else if (r.Method == http.MethodPut || r.Method == http.MethodPost) && r.URL.Path == "/" {
|
} else if (r.Method == http.MethodPut || r.Method == http.MethodPost) && r.URL.Path == "/" {
|
||||||
return s.limitRequests(s.transformBodyJSON(s.authWrite(s.handlePublish)))(w, r, v)
|
return s.limitRequests(s.transformBodyJSON(s.authWrite(s.handlePublish)))(w, r, v)
|
||||||
} else if (r.Method == http.MethodPut || r.Method == http.MethodPost) && topicPathRegex.MatchString(r.URL.Path) {
|
} else if (r.Method == http.MethodPut || r.Method == http.MethodPost) && topicPathRegex.MatchString(r.URL.Path) {
|
||||||
@@ -297,21 +298,21 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
|
|||||||
} else if r.Method == http.MethodGet && authPathRegex.MatchString(r.URL.Path) {
|
} else if r.Method == http.MethodGet && authPathRegex.MatchString(r.URL.Path) {
|
||||||
return s.limitRequests(s.authRead(s.handleTopicAuth))(w, r, v)
|
return s.limitRequests(s.authRead(s.handleTopicAuth))(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && (topicPathRegex.MatchString(r.URL.Path) || externalTopicPathRegex.MatchString(r.URL.Path)) {
|
} else if r.Method == http.MethodGet && (topicPathRegex.MatchString(r.URL.Path) || externalTopicPathRegex.MatchString(r.URL.Path)) {
|
||||||
return s.handleTopic(w, r)
|
return s.ensureWebEnabled(s.handleTopic)(w, r, v)
|
||||||
}
|
}
|
||||||
return errHTTPNotFound
|
return errHTTPNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) error {
|
func (s *Server) handleHome(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||||
if s.config.WebRootIsApp {
|
if s.config.WebRootIsApp {
|
||||||
r.URL.Path = webAppIndex
|
r.URL.Path = webAppIndex
|
||||||
} else {
|
} else {
|
||||||
r.URL.Path = webHomeIndex
|
r.URL.Path = webHomeIndex
|
||||||
}
|
}
|
||||||
return s.handleStatic(w, r)
|
return s.handleStatic(w, r, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleTopic(w http.ResponseWriter, r *http.Request) error {
|
func (s *Server) handleTopic(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||||
unifiedpush := readBoolParam(r, false, "x-unifiedpush", "unifiedpush", "up") // see PUT/POST too!
|
unifiedpush := readBoolParam(r, false, "x-unifiedpush", "unifiedpush", "up") // see PUT/POST too!
|
||||||
if unifiedpush {
|
if unifiedpush {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
@@ -320,7 +321,7 @@ func (s *Server) handleTopic(w http.ResponseWriter, r *http.Request) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.URL.Path = webAppIndex
|
r.URL.Path = webAppIndex
|
||||||
return s.handleStatic(w, r)
|
return s.handleStatic(w, r, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleEmpty(_ http.ResponseWriter, _ *http.Request, _ *visitor) error {
|
func (s *Server) handleEmpty(_ http.ResponseWriter, _ *http.Request, _ *visitor) error {
|
||||||
@@ -334,12 +335,12 @@ func (s *Server) handleTopicAuth(w http.ResponseWriter, _ *http.Request, _ *visi
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleExample(w http.ResponseWriter, _ *http.Request) error {
|
func (s *Server) handleExample(w http.ResponseWriter, _ *http.Request, _ *visitor) error {
|
||||||
_, err := io.WriteString(w, exampleSource)
|
_, err := io.WriteString(w, exampleSource)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleWebConfig(w http.ResponseWriter, r *http.Request) error {
|
func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visitor) error {
|
||||||
appRoot := "/"
|
appRoot := "/"
|
||||||
if !s.config.WebRootIsApp {
|
if !s.config.WebRootIsApp {
|
||||||
appRoot = "/app"
|
appRoot = "/app"
|
||||||
@@ -367,13 +368,13 @@ func (s *Server) handleUserStats(w http.ResponseWriter, r *http.Request, v *visi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleStatic(w http.ResponseWriter, r *http.Request) error {
|
func (s *Server) handleStatic(w http.ResponseWriter, r *http.Request, _ *visitor) error {
|
||||||
r.URL.Path = webSiteDir + r.URL.Path
|
r.URL.Path = webSiteDir + r.URL.Path
|
||||||
util.Gzip(http.FileServer(http.FS(webFsCached))).ServeHTTP(w, r)
|
util.Gzip(http.FileServer(http.FS(webFsCached))).ServeHTTP(w, r)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleDocs(w http.ResponseWriter, r *http.Request) error {
|
func (s *Server) handleDocs(w http.ResponseWriter, r *http.Request, _ *visitor) error {
|
||||||
util.Gzip(http.FileServer(http.FS(docsStaticCached))).ServeHTTP(w, r)
|
util.Gzip(http.FileServer(http.FS(docsStaticCached))).ServeHTTP(w, r)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -904,7 +905,7 @@ func parseSince(r *http.Request, poll bool) (sinceMarker, error) {
|
|||||||
return sinceNoMessages, errHTTPBadRequestSinceInvalid
|
return sinceNoMessages, errHTTPBadRequestSinceInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleOptions(w http.ResponseWriter, _ *http.Request) error {
|
func (s *Server) handleOptions(w http.ResponseWriter, _ *http.Request, _ *visitor) error {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, POST")
|
w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, POST")
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests
|
w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "*") // CORS, allow auth via JS // FIXME is this terrible?
|
w.Header().Set("Access-Control-Allow-Headers", "*") // CORS, allow auth via JS // FIXME is this terrible?
|
||||||
@@ -1118,6 +1119,15 @@ func (s *Server) limitRequests(next handleFunc) handleFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) ensureWebEnabled(next handleFunc) handleFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||||
|
if !s.config.EnableWeb {
|
||||||
|
return errHTTPNotFound
|
||||||
|
}
|
||||||
|
return next(w, r, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// transformBodyJSON peeks the request body, reads the JSON, and converts it to headers
|
// transformBodyJSON peeks the request body, reads the JSON, and converts it to headers
|
||||||
// before passing it on to the next handler. This is meant to be used in combination with handlePublish.
|
// before passing it on to the next handler. This is meant to be used in combination with handlePublish.
|
||||||
func (s *Server) transformBodyJSON(next handleFunc) handleFunc {
|
func (s *Server) transformBodyJSON(next handleFunc) handleFunc {
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
# ntfy server config file
|
# ntfy server config file
|
||||||
|
#
|
||||||
|
# Please refer to the documentation at https://ntfy.sh/docs/config/ for details.
|
||||||
|
# All options also support underscores (_) instead of dashes (-) to comply with the YAML spec.
|
||||||
|
|
||||||
# Public facing base URL of the service (e.g. https://ntfy.sh or https://ntfy.example.com)
|
# Public facing base URL of the service (e.g. https://ntfy.sh or https://ntfy.example.com)
|
||||||
# This setting is currently only used by the attachments and e-mail sending feature (outgoing mail only).
|
# This setting is currently only used by the attachments and e-mail sending feature (outgoing mail only).
|
||||||
@@ -127,7 +130,8 @@
|
|||||||
# manager-interval: "1m"
|
# manager-interval: "1m"
|
||||||
|
|
||||||
# Defines if the root route (/) is pointing to the landing page (as on ntfy.sh) or the
|
# Defines if the root route (/) is pointing to the landing page (as on ntfy.sh) or the
|
||||||
# web app. If you self-host, you don't want to change this. Can be "app" (default) or "home".
|
# web app. If you self-host, you don't want to change this.
|
||||||
|
# Can be "app" (default), "home" or "disable" to disable the web app entirely.
|
||||||
#
|
#
|
||||||
# web-root: app
|
# web-root: app
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,18 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
firebase "firebase.google.com/go"
|
firebase "firebase.google.com/go"
|
||||||
"firebase.google.com/go/messaging"
|
"firebase.google.com/go/messaging"
|
||||||
"fmt"
|
|
||||||
"google.golang.org/api/option"
|
"google.golang.org/api/option"
|
||||||
"heckel.io/ntfy/auth"
|
"heckel.io/ntfy/auth"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
fcmMessageLimit = 4000
|
fcmMessageLimit = 4000
|
||||||
|
fcmApnsBodyMessageLimit = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
// maybeTruncateFCMMessage performs best-effort truncation of FCM messages.
|
// maybeTruncateFCMMessage performs best-effort truncation of FCM messages.
|
||||||
@@ -34,6 +36,20 @@ func maybeTruncateFCMMessage(m *messaging.Message) *messaging.Message {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// maybeTruncateAPNSBodyMessage truncates the body for APNS.
|
||||||
|
//
|
||||||
|
// The "body" of the push notification can contain the entire message, which would count doubly for the overall length
|
||||||
|
// of the APNS payload. I set a limit of 100 characters before truncating the notification "body" with ellipsis.
|
||||||
|
// The message would not be changed (unless truncated for being too long). Note: if the payload is too large (>4KB),
|
||||||
|
// APNS will simply reject / discard the notification, meaning it will never arrive on the iOS device.
|
||||||
|
func maybeTruncateAPNSBodyMessage(s string) string {
|
||||||
|
if len(s) >= fcmApnsBodyMessageLimit {
|
||||||
|
over := len(s) - fcmApnsBodyMessageLimit + 3 // len("...")
|
||||||
|
return s[:len(s)-over] + "..."
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
func createFirebaseSubscriber(credentialsFile string, auther auth.Auther) (subscriber, error) {
|
func createFirebaseSubscriber(credentialsFile string, auther auth.Auther) (subscriber, error) {
|
||||||
fb, err := firebase.NewApp(context.Background(), nil, option.WithCredentialsFile(credentialsFile))
|
fb, err := firebase.NewApp(context.Background(), nil, option.WithCredentialsFile(credentialsFile))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -55,6 +71,7 @@ func createFirebaseSubscriber(credentialsFile string, auther auth.Auther) (subsc
|
|||||||
|
|
||||||
func toFirebaseMessage(m *message, auther auth.Auther) (*messaging.Message, error) {
|
func toFirebaseMessage(m *message, auther auth.Auther) (*messaging.Message, error) {
|
||||||
var data map[string]string // Mostly matches https://ntfy.sh/docs/subscribe/api/#json-message-format
|
var data map[string]string // Mostly matches https://ntfy.sh/docs/subscribe/api/#json-message-format
|
||||||
|
var apnsConfig *messaging.APNSConfig
|
||||||
switch m.Event {
|
switch m.Event {
|
||||||
case keepaliveEvent, openEvent:
|
case keepaliveEvent, openEvent:
|
||||||
data = map[string]string{
|
data = map[string]string{
|
||||||
@@ -95,6 +112,22 @@ func toFirebaseMessage(m *message, auther auth.Auther) (*messaging.Message, erro
|
|||||||
data["attachment_expires"] = fmt.Sprintf("%d", m.Attachment.Expires)
|
data["attachment_expires"] = fmt.Sprintf("%d", m.Attachment.Expires)
|
||||||
data["attachment_url"] = m.Attachment.URL
|
data["attachment_url"] = m.Attachment.URL
|
||||||
}
|
}
|
||||||
|
apnsData := make(map[string]interface{})
|
||||||
|
for k, v := range data {
|
||||||
|
apnsData[k] = v
|
||||||
|
}
|
||||||
|
apnsConfig = &messaging.APNSConfig{
|
||||||
|
Payload: &messaging.APNSPayload{
|
||||||
|
CustomData: apnsData,
|
||||||
|
Aps: &messaging.Aps{
|
||||||
|
MutableContent: true,
|
||||||
|
Alert: &messaging.ApsAlert{
|
||||||
|
Title: m.Title,
|
||||||
|
Body: maybeTruncateAPNSBodyMessage(m.Message),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// If anonymous read for a topic is not allowed, we cannot send the message along
|
// If anonymous read for a topic is not allowed, we cannot send the message along
|
||||||
// via Firebase. Instead, we send a "poll_request" message, asking the client to poll.
|
// via Firebase. Instead, we send a "poll_request" message, asking the client to poll.
|
||||||
@@ -116,5 +149,6 @@ func toFirebaseMessage(m *message, auther auth.Auther) (*messaging.Message, erro
|
|||||||
Topic: m.Topic,
|
Topic: m.Topic,
|
||||||
Data: data,
|
Data: data,
|
||||||
Android: androidConfig,
|
Android: androidConfig,
|
||||||
|
APNS: apnsConfig,
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"heckel.io/ntfy/auth"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@@ -18,6 +15,10 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"heckel.io/ntfy/auth"
|
||||||
|
"heckel.io/ntfy/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServer_PublishAndPoll(t *testing.T) {
|
func TestServer_PublishAndPoll(t *testing.T) {
|
||||||
@@ -162,6 +163,40 @@ func TestServer_StaticSites(t *testing.T) {
|
|||||||
require.Contains(t, rr.Body.String(), "</html>")
|
require.Contains(t, rr.Body.String(), "</html>")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServer_WebEnabled(t *testing.T) {
|
||||||
|
conf := newTestConfig(t)
|
||||||
|
conf.EnableWeb = false
|
||||||
|
s := newTestServer(t, conf)
|
||||||
|
|
||||||
|
rr := request(t, s, "GET", "/", "", nil)
|
||||||
|
require.Equal(t, 404, rr.Code)
|
||||||
|
|
||||||
|
rr = request(t, s, "GET", "/example.html", "", nil)
|
||||||
|
require.Equal(t, 404, rr.Code)
|
||||||
|
|
||||||
|
rr = request(t, s, "GET", "/config.js", "", nil)
|
||||||
|
require.Equal(t, 404, rr.Code)
|
||||||
|
|
||||||
|
rr = request(t, s, "GET", "/static/css/home.css", "", nil)
|
||||||
|
require.Equal(t, 404, rr.Code)
|
||||||
|
|
||||||
|
conf2 := newTestConfig(t)
|
||||||
|
conf2.EnableWeb = true
|
||||||
|
s2 := newTestServer(t, conf2)
|
||||||
|
|
||||||
|
rr = request(t, s2, "GET", "/", "", nil)
|
||||||
|
require.Equal(t, 200, rr.Code)
|
||||||
|
|
||||||
|
rr = request(t, s2, "GET", "/example.html", "", nil)
|
||||||
|
require.Equal(t, 200, rr.Code)
|
||||||
|
|
||||||
|
rr = request(t, s2, "GET", "/config.js", "", nil)
|
||||||
|
require.Equal(t, 200, rr.Code)
|
||||||
|
|
||||||
|
rr = request(t, s2, "GET", "/static/css/home.css", "", nil)
|
||||||
|
require.Equal(t, 200, rr.Code)
|
||||||
|
}
|
||||||
|
|
||||||
func TestServer_PublishLargeMessage(t *testing.T) {
|
func TestServer_PublishLargeMessage(t *testing.T) {
|
||||||
c := newTestConfig(t)
|
c := newTestConfig(t)
|
||||||
c.AttachmentCacheDir = "" // Disable attachments
|
c.AttachmentCacheDir = "" // Disable attachments
|
||||||
@@ -1303,7 +1338,7 @@ func firebaseServiceAccountFile(t *testing.T) string {
|
|||||||
return os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT_FILE")
|
return os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT_FILE")
|
||||||
} else if os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT") != "" {
|
} else if os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT") != "" {
|
||||||
filename := filepath.Join(t.TempDir(), "firebase.json")
|
filename := filepath.Join(t.TempDir(), "firebase.json")
|
||||||
require.NotNil(t, os.WriteFile(filename, []byte(os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT")), 0600))
|
require.NotNil(t, os.WriteFile(filename, []byte(os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT")), 0o600))
|
||||||
return filename
|
return filename
|
||||||
}
|
}
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
|
|||||||
2336
web/package-lock.json
generated
2336
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@
|
|||||||
"publish_dialog_progress_uploading": "Изпращане…",
|
"publish_dialog_progress_uploading": "Изпращане…",
|
||||||
"publish_dialog_progress_uploading_detail": "Изпращане {{loaded}}/{{total}} ({{percent}}%)…",
|
"publish_dialog_progress_uploading_detail": "Изпращане {{loaded}}/{{total}} ({{percent}}%)…",
|
||||||
"publish_dialog_message_published": "Известието е публикувано",
|
"publish_dialog_message_published": "Известието е публикувано",
|
||||||
"publish_dialog_attachment_limits_file_and_quota_reached": "надвишава ограничението и квотата от {{fileSizeLimit}}, оставащи {{remainingBytes}}",
|
"publish_dialog_attachment_limits_file_and_quota_reached": "надвишава ограничението от {{fileSizeLimit}} за размер на файл и квотата, остават {{remainingBytes}}",
|
||||||
"publish_dialog_message_label": "Съобщение",
|
"publish_dialog_message_label": "Съобщение",
|
||||||
"publish_dialog_message_placeholder": "Въведете съобщение",
|
"publish_dialog_message_placeholder": "Въведете съобщение",
|
||||||
"publish_dialog_other_features": "Други възможности:",
|
"publish_dialog_other_features": "Други възможности:",
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
"message_bar_type_message": "Въведете съобщение",
|
"message_bar_type_message": "Въведете съобщение",
|
||||||
"message_bar_error_publishing": "Грешка при изпращане на известието",
|
"message_bar_error_publishing": "Грешка при изпращане на известието",
|
||||||
"notifications_copied_to_clipboard": "Копирано в междинната памет",
|
"notifications_copied_to_clipboard": "Копирано в междинната памет",
|
||||||
"notifications_attachment_link_expired": "препратката за изтегляне е невалидна",
|
"notifications_attachment_link_expired": "препратката за изтегляне е с изтекла давност",
|
||||||
"nav_button_settings": "Настройки",
|
"nav_button_settings": "Настройки",
|
||||||
"nav_button_documentation": "Ръководство",
|
"nav_button_documentation": "Ръководство",
|
||||||
"nav_button_subscribe": "Абониране за тема",
|
"nav_button_subscribe": "Абониране за тема",
|
||||||
@@ -59,27 +59,27 @@
|
|||||||
"notifications_actions_open_url_title": "Към {{url}}",
|
"notifications_actions_open_url_title": "Към {{url}}",
|
||||||
"notifications_click_copy_url_button": "Копиране на препратка",
|
"notifications_click_copy_url_button": "Копиране на препратка",
|
||||||
"notifications_click_open_button": "Отваряне",
|
"notifications_click_open_button": "Отваряне",
|
||||||
"notifications_click_copy_url_title": "Копира препратката в междинната памет",
|
"notifications_click_copy_url_title": "Копиране на препратката в междинната памет",
|
||||||
"notifications_none_for_topic_title": "Липсват известия в темата",
|
"notifications_none_for_topic_title": "Липсват известия в темата",
|
||||||
"notifications_none_for_any_title": "Липсват известия",
|
"notifications_none_for_any_title": "Липсват известия",
|
||||||
"notifications_none_for_topic_description": "За да изпратите известия в тази тема, просто изпратете PUT или POST към адреса ѝ.",
|
"notifications_none_for_topic_description": "За да изпратите известия в тази тема, просто направете PUT или POST към адреса ѝ.",
|
||||||
"notifications_none_for_any_description": "За да изпратите известия в тема, просто изпратете PUT или POST към адреса ѝ. Ето пример с една от вашите теми.",
|
"notifications_none_for_any_description": "За да изпратите известия в тема, просто направете PUT или POST към адреса ѝ. Ето пример с една от вашите теми.",
|
||||||
"notifications_no_subscriptions_description": "Щракнете върху „{{linktext}}“, за да създадете тема или да се абонирате. След това като изпратите съобщения чрез метода PUT или POST ще ги получавате тук.",
|
"notifications_no_subscriptions_description": "Щракнете върху „{{linktext}}“, за да създадете тема или да се абонирате. След това като изпратите съобщения чрез метода PUT или POST ще ги получите тук.",
|
||||||
"notifications_more_details": "За допълнителна информация посетете <websiteLink>страницата</websiteLink> или <docsLink>документацията</docsLink>.",
|
"notifications_more_details": "За допълнителна информация посетете <websiteLink>страницата</websiteLink> или <docsLink>документацията</docsLink>.",
|
||||||
"publish_dialog_priority_min": "Мин. приоритет",
|
"publish_dialog_priority_min": "Мин. приоритет",
|
||||||
"publish_dialog_attachment_limits_file_reached": "надвишава ограничението от {{fileSizeLimit}}",
|
"publish_dialog_attachment_limits_file_reached": "надвишава ограничението от {{fileSizeLimit}} за размер на файл",
|
||||||
"publish_dialog_base_url_label": "Адрес на услугата",
|
"publish_dialog_base_url_label": "Адрес на услугата",
|
||||||
"publish_dialog_base_url_placeholder": "Адрес на услугата, напр. https://example.com",
|
"publish_dialog_base_url_placeholder": "Адрес на услугата, напр. https://example.com",
|
||||||
"publish_dialog_topic_placeholder": "Име на темата, напр. phils_alerts",
|
"publish_dialog_topic_placeholder": "Име на темата, напр. phils_alerts",
|
||||||
"publish_dialog_priority_low": "Нисък приоритет",
|
"publish_dialog_priority_low": "Нисък приоритет",
|
||||||
"publish_dialog_attachment_limits_quota_reached": "надвишава ограничението, оставащи {{remainingBytes}}",
|
"publish_dialog_attachment_limits_quota_reached": "надвишава квотата, остават {{remainingBytes}}",
|
||||||
"publish_dialog_priority_high": "Висок приоритет",
|
"publish_dialog_priority_high": "Висок приоритет",
|
||||||
"publish_dialog_priority_default": "Подразбиран приоритет",
|
"publish_dialog_priority_default": "Подразбиран приоритет",
|
||||||
"publish_dialog_title_placeholder": "Заглавие на известието, напр. Предупреждение за диска",
|
"publish_dialog_title_placeholder": "Заглавие на известието, напр. Предупреждение за диска",
|
||||||
"publish_dialog_tags_label": "Етикети",
|
"publish_dialog_tags_label": "Етикети",
|
||||||
"publish_dialog_email_label": "Адрес на електронна поща",
|
"publish_dialog_email_label": "Адрес на електронна поща",
|
||||||
"publish_dialog_priority_max": "Макс. приоритет",
|
"publish_dialog_priority_max": "Макс. приоритет",
|
||||||
"publish_dialog_tags_placeholder": "Разделени със запетая етикети, напр. внимание, диск",
|
"publish_dialog_tags_placeholder": "Разделени със запетая етикети, напр. warning, srv1-backup",
|
||||||
"publish_dialog_click_label": "Адрес",
|
"publish_dialog_click_label": "Адрес",
|
||||||
"publish_dialog_topic_label": "Име на темата",
|
"publish_dialog_topic_label": "Име на темата",
|
||||||
"publish_dialog_title_label": "Заглавие",
|
"publish_dialog_title_label": "Заглавие",
|
||||||
@@ -130,14 +130,14 @@
|
|||||||
"prefs_users_dialog_username_label": "Потребител, напр. phil",
|
"prefs_users_dialog_username_label": "Потребител, напр. phil",
|
||||||
"prefs_users_dialog_button_add": "Добавяне",
|
"prefs_users_dialog_button_add": "Добавяне",
|
||||||
"error_boundary_title": "О, не, ntfy се срина",
|
"error_boundary_title": "О, не, ntfy се срина",
|
||||||
"error_boundary_description": "Това очевидно не трябва да се случва. Много съжаляваме!<br/>Ако имате минута, <githubLink>докладвайте в GitHub</githubLink>, или ни уведомете в <discordLink>Discord</discordLink> или <matrixLink>Matrix</matrixLink>.",
|
"error_boundary_description": "Това очевидно не трябва да се случва. Много съжаляваме!<br/>Ако имате минута, <githubLink>докладвайте в GitHub</githubLink> или ни уведомете в <discordLink>Discord</discordLink> или <matrixLink>Matrix</matrixLink>.",
|
||||||
"error_boundary_stack_trace": "Следа от стека",
|
"error_boundary_stack_trace": "Следа от стека",
|
||||||
"error_boundary_gathering_info": "Събиране на допълнителна информация…",
|
"error_boundary_gathering_info": "Събиране на допълнителна информация…",
|
||||||
"notifications_loading": "Зареждане на известия…",
|
"notifications_loading": "Зареждане на известия…",
|
||||||
"error_boundary_button_copy_stack_trace": "Копиране на следата от стека",
|
"error_boundary_button_copy_stack_trace": "Копиране на следата от стека",
|
||||||
"prefs_users_description": "Добавяйте и премахвайте потребители за защитените теми. Имайте предвид, че потребителското име и паролата се съхраняват в местната памет на мрежовия четец.",
|
"prefs_users_description": "Добавяйте и премахвайте потребители за защитените теми. Имайте предвид, че потребителското име и паролата се съхраняват в местната памет на мрежовия четец.",
|
||||||
"prefs_notifications_sound_description_none": "Известията не са съпроводени със звук",
|
"prefs_notifications_sound_description_none": "Известията не са съпроводени със звук",
|
||||||
"prefs_notifications_sound_description_some": "Известията са съпроводени със звука „{{sound}}“",
|
"prefs_notifications_sound_description_some": "При пристигане известията са съпроводени от звука „{{sound}}“",
|
||||||
"prefs_notifications_delete_after_never_description": "Известията никога не се премахват автоматично",
|
"prefs_notifications_delete_after_never_description": "Известията никога не се премахват автоматично",
|
||||||
"prefs_notifications_delete_after_three_hours_description": "Известията се премахват автоматично след три часа",
|
"prefs_notifications_delete_after_three_hours_description": "Известията се премахват автоматично след три часа",
|
||||||
"priority_min": "минимален",
|
"priority_min": "минимален",
|
||||||
@@ -149,8 +149,43 @@
|
|||||||
"prefs_notifications_delete_after_one_day_description": "Известията се премахват автоматично след един ден",
|
"prefs_notifications_delete_after_one_day_description": "Известията се премахват автоматично след един ден",
|
||||||
"prefs_notifications_min_priority_description_max": "Показват се известията с приоритет 5 (най-висок)",
|
"prefs_notifications_min_priority_description_max": "Показват се известията с приоритет 5 (най-висок)",
|
||||||
"prefs_notifications_delete_after_one_month_description": "Известията се премахват автоматично след един месец",
|
"prefs_notifications_delete_after_one_month_description": "Известията се премахват автоматично след един месец",
|
||||||
"prefs_notifications_min_priority_description_any": "Показват се всички известия, независимо от приоритета им",
|
"prefs_notifications_min_priority_description_any": "Показват се всички известия, независимо от приоритета",
|
||||||
"prefs_notifications_min_priority_description_x_or_higher": "Показват се известията с приоритет {{number}} ({{name}}) или по-висок",
|
"prefs_notifications_min_priority_description_x_or_higher": "Показват се известията с приоритет {{number}} ({{name}}) или по-висок",
|
||||||
"notifications_actions_http_request_title": "Изпращане на HTTP {{method}} до {{url}}",
|
"notifications_actions_http_request_title": "Изпращане на HTTP {{method}} до {{url}}",
|
||||||
"notifications_actions_not_supported": "Действието не се поддържа от приложението за уеб"
|
"notifications_actions_not_supported": "Действието не се поддържа от приложението за интернет",
|
||||||
|
"action_bar_show_menu": "Показване на менюто",
|
||||||
|
"action_bar_logo_alt": "Логотип на ntfy",
|
||||||
|
"action_bar_toggle_mute": "Заглушаване или пускне на известията",
|
||||||
|
"action_bar_toggle_action_menu": "Отваряне или затваряне на менюто с действията",
|
||||||
|
"nav_button_muted": "Известията са заглушени",
|
||||||
|
"notifications_list": "Списък с известия",
|
||||||
|
"notifications_list_item": "Известие",
|
||||||
|
"notifications_delete": "Изтриване",
|
||||||
|
"notifications_mark_read": "Отбелязване като прочетено",
|
||||||
|
"nav_button_connecting": "свързване",
|
||||||
|
"message_bar_show_dialog": "Показване на диалога за публикуване",
|
||||||
|
"message_bar_publish": "Публикуване на съобщение",
|
||||||
|
"notifications_priority_x": "Приоритет {{priority}}",
|
||||||
|
"notifications_new_indicator": "Ново известие",
|
||||||
|
"notifications_attachment_image": "Прикачено изображение",
|
||||||
|
"notifications_attachment_file_image": "файл на изображение",
|
||||||
|
"notifications_attachment_file_video": "файл на видео",
|
||||||
|
"notifications_attachment_file_audio": "файл на аудио",
|
||||||
|
"notifications_attachment_file_app": "Инсталационен файл на приложение за Android",
|
||||||
|
"notifications_attachment_file_document": "друг документ",
|
||||||
|
"publish_dialog_emoji_picker_show": "Избор на емоция",
|
||||||
|
"publish_dialog_topic_reset": "Нулиране на тема",
|
||||||
|
"publish_dialog_click_reset": "Премахване на адрес",
|
||||||
|
"publish_dialog_email_reset": "Премахване на препращането към ел. поща",
|
||||||
|
"publish_dialog_delay_reset": "Премахва забавянето на изпращането",
|
||||||
|
"publish_dialog_attached_file_remove": "Премахване на прикачения файл",
|
||||||
|
"emoji_picker_search_clear": "Изчистване на търсенето",
|
||||||
|
"subscribe_dialog_subscribe_base_url_label": "Адрес на услугата",
|
||||||
|
"prefs_notifications_sound_play": "Възпроизвеждане на избрания звук",
|
||||||
|
"publish_dialog_attach_reset": "Премахване на адреса на файла за прикачане",
|
||||||
|
"prefs_users_delete_button": "Премахване на потребител",
|
||||||
|
"prefs_users_table": "Таблица с потребители",
|
||||||
|
"prefs_users_edit_button": "Промяна на потребител",
|
||||||
|
"error_boundary_unsupported_indexeddb_title": "Поверително разглеждане не се поддържа",
|
||||||
|
"error_boundary_unsupported_indexeddb_description": "За да работи интернет-приложението ntfy се нуждае от IndexedDB, а мрежовият четец не поддържа IndexedDB в режим на поверително разглеждане.<br/><br/>Въпреки това, няма смисъл да използвате интернет-приложението ntfy в режим на поверително разглеждане, тъй като всичко се пази в хранилището на четеца. Можете да прочетете повече по <githubLink>проблема в GitHub</githubLink> или да се свържете с нас в <discordLink>Discord</discordLink> или <matrixLink>Matrix</matrixLink>."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,5 +152,40 @@
|
|||||||
"prefs_users_description": "Zde můžete přidávat/odebírat uživatele pro chráněná témata. Upozorňujeme, že uživatelské jméno a heslo jsou uloženy v místním úložišti prohlížeče.",
|
"prefs_users_description": "Zde můžete přidávat/odebírat uživatele pro chráněná témata. Upozorňujeme, že uživatelské jméno a heslo jsou uloženy v místním úložišti prohlížeče.",
|
||||||
"error_boundary_gathering_info": "Získejte více informací …",
|
"error_boundary_gathering_info": "Získejte více informací …",
|
||||||
"prefs_appearance_language_title": "Jazyk",
|
"prefs_appearance_language_title": "Jazyk",
|
||||||
"prefs_appearance_title": "Vzhled"
|
"prefs_appearance_title": "Vzhled",
|
||||||
|
"action_bar_show_menu": "Zobrazit nabídku",
|
||||||
|
"action_bar_logo_alt": "logo ntfy",
|
||||||
|
"action_bar_toggle_mute": "Ztlumení/zrušení ztlumení oznámení",
|
||||||
|
"action_bar_toggle_action_menu": "Otevřít/zavřít nabídku akcí",
|
||||||
|
"message_bar_show_dialog": "Zobrazit okno pro odesílání oznámení",
|
||||||
|
"message_bar_publish": "Odeslat zprávu",
|
||||||
|
"nav_button_muted": "Oznámení ztlumena",
|
||||||
|
"nav_button_connecting": "připojování",
|
||||||
|
"notifications_list": "Seznam oznámení",
|
||||||
|
"notifications_list_item": "Oznámení",
|
||||||
|
"notifications_mark_read": "Označit jako přečtené",
|
||||||
|
"notifications_delete": "Smazat",
|
||||||
|
"notifications_new_indicator": "Nové oznámení",
|
||||||
|
"notifications_attachment_image": "Obrázek přílohy",
|
||||||
|
"notifications_attachment_file_image": "soubor s obrázkem",
|
||||||
|
"notifications_attachment_file_video": "video soubor",
|
||||||
|
"notifications_attachment_file_audio": "zvukový soubor",
|
||||||
|
"notifications_attachment_file_app": "Soubor s aplikací pro Android",
|
||||||
|
"publish_dialog_emoji_picker_show": "Vybrat emoji",
|
||||||
|
"publish_dialog_topic_reset": "Obnovení tématu",
|
||||||
|
"publish_dialog_click_reset": "Odebrat URL kliknutím",
|
||||||
|
"publish_dialog_email_reset": "Odebrat přeposlání e-mailu",
|
||||||
|
"publish_dialog_attach_reset": "Odebrat URL přílohy",
|
||||||
|
"publish_dialog_attached_file_remove": "Odebrat přiložený soubor",
|
||||||
|
"emoji_picker_search_clear": "Vyčistit vyhledávání",
|
||||||
|
"prefs_users_edit_button": "Upravit uživatele",
|
||||||
|
"prefs_users_delete_button": "Odstranit uživatele",
|
||||||
|
"error_boundary_unsupported_indexeddb_title": "Soukromé prohlížení není podporováno",
|
||||||
|
"error_boundary_unsupported_indexeddb_description": "Webová aplikace ntfy potřebuje ke svému fungování databázi IndexedDB a váš prohlížeč v režimu soukromého prohlížení databázi IndexedDB nepodporuje.<br/><br/>To je sice nepříjemné, ale používat webovou aplikaci ntfy v režimu soukromého prohlížení stejně nemá smysl, protože vše je uloženo v úložišti prohlížeče. Více se o tom můžete dočíst <githubLink>v tomto tématu na GitHubu</githubLink>, nebo se na nás obrátit pomocí služeb <discordLink>Discord</discordLink> nebo <matrixLink>Matrix</matrixLink>.",
|
||||||
|
"notifications_priority_x": "Priorita {{priority}}",
|
||||||
|
"subscribe_dialog_subscribe_base_url_label": "URL služby",
|
||||||
|
"prefs_notifications_sound_play": "Přehrát vybraný zvuk",
|
||||||
|
"prefs_users_table": "Tabulka uživatelů",
|
||||||
|
"notifications_attachment_file_document": "jiný dokument",
|
||||||
|
"publish_dialog_delay_reset": "Odebrat odložené doručení"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,5 +152,40 @@
|
|||||||
"prefs_notifications_delete_after_one_week_description": "Benachrichtigungen werden nach einer Woche automatisch gelöscht",
|
"prefs_notifications_delete_after_one_week_description": "Benachrichtigungen werden nach einer Woche automatisch gelöscht",
|
||||||
"priority_min": "min",
|
"priority_min": "min",
|
||||||
"notifications_actions_not_supported": "Diese Aktion wird in der Web-App nicht unterstützt",
|
"notifications_actions_not_supported": "Diese Aktion wird in der Web-App nicht unterstützt",
|
||||||
"notifications_actions_http_request_title": "Sende HTTP {{method}} an {{url}}"
|
"notifications_actions_http_request_title": "Sende HTTP {{method}} an {{url}}",
|
||||||
|
"action_bar_show_menu": "Menü anzeigen",
|
||||||
|
"action_bar_toggle_mute": "Stummschaltung der Benachrichtigungen an/aus",
|
||||||
|
"message_bar_show_dialog": "Dialog zur Veröffentlichung anzeigen",
|
||||||
|
"message_bar_publish": "Benachrichtigung veröffentlichen",
|
||||||
|
"nav_button_connecting": "verbinde",
|
||||||
|
"notifications_list": "Benachrichtigungsliste",
|
||||||
|
"notifications_mark_read": "Als gelesen markieren",
|
||||||
|
"notifications_delete": "Löschen",
|
||||||
|
"notifications_priority_x": "Priorität {{priority}}",
|
||||||
|
"notifications_attachment_file_image": "Bilddatei",
|
||||||
|
"notifications_attachment_image": "Bild des Anhangs",
|
||||||
|
"notifications_attachment_file_video": "Videodatei",
|
||||||
|
"notifications_attachment_file_audio": "Audiodatei",
|
||||||
|
"notifications_attachment_file_app": "Android App-Datei",
|
||||||
|
"notifications_attachment_file_document": "anderes Dokument",
|
||||||
|
"publish_dialog_attached_file_remove": "Angehängte Datei entfernen",
|
||||||
|
"emoji_picker_search_clear": "Suche leeren",
|
||||||
|
"subscribe_dialog_subscribe_base_url_label": "Service URL",
|
||||||
|
"prefs_notifications_sound_play": "Gewählten Sound abspielen",
|
||||||
|
"prefs_users_table": "Benutzertabelle",
|
||||||
|
"prefs_users_edit_button": "Benutzer bearbeiten",
|
||||||
|
"prefs_users_delete_button": "Benutzer löschen",
|
||||||
|
"error_boundary_unsupported_indexeddb_title": "Private Browser-Tabs werden nicht unterstützt",
|
||||||
|
"publish_dialog_delay_reset": "Verzögerte Zustellung entfernen",
|
||||||
|
"error_boundary_unsupported_indexeddb_description": "Die ntfy Web-App benötigt eine IndexedDB für eine korrekte Funktion, und Dein Browser unterstützt in privaten Tabs keinen IndexedDB.<br/><br/>Das ist zwar ärgerlich, eine Nutzung von ntfy in einem privaten Tab macht aber auch wenig Sinn da alle Daten im Browser gespeichert werden. Weitere Informationen gibt es <githubLink>in diesem GitHub-Issue</githubLink>, oder im Chat bei <discordLink>Discord</discordLink> oder <matrixLink>Matrix</matrixLink>.",
|
||||||
|
"action_bar_toggle_action_menu": "Aktionsmenü öffnen/schließen",
|
||||||
|
"notifications_new_indicator": "Neue Benachrichtigung",
|
||||||
|
"publish_dialog_email_reset": "Email-Weiterleitung entfernen",
|
||||||
|
"action_bar_logo_alt": "ntfy Logo",
|
||||||
|
"nav_button_muted": "Benachrichtigungen stummgeschaltet",
|
||||||
|
"notifications_list_item": "Benachrichtigung",
|
||||||
|
"publish_dialog_emoji_picker_show": "Emoji wählen",
|
||||||
|
"publish_dialog_topic_reset": "Thema zurücksetzen",
|
||||||
|
"publish_dialog_attach_reset": "angehängte URL entfernen",
|
||||||
|
"publish_dialog_click_reset": "Klick-URL entfernen"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,5 +152,40 @@
|
|||||||
"prefs_notifications_delete_after_one_week_description": "Las notificaciones se eliminan automáticamente después de una semana",
|
"prefs_notifications_delete_after_one_week_description": "Las notificaciones se eliminan automáticamente después de una semana",
|
||||||
"priority_low": "baja",
|
"priority_low": "baja",
|
||||||
"notifications_actions_not_supported": "Acción no soportada en la aplicación web",
|
"notifications_actions_not_supported": "Acción no soportada en la aplicación web",
|
||||||
"notifications_actions_http_request_title": "Enviar HTTP {{method}} a {{url}}"
|
"notifications_actions_http_request_title": "Enviar HTTP {{method}} a {{url}}",
|
||||||
|
"error_boundary_unsupported_indexeddb_description": "La aplicación web ntfy necesita IndexedDB para funcionar y su navegador no soporta IndexedDB en modo de navegación privada. <br/> <br/> Si bien esto es desafortunado, tampoco tiene mucho sentido usar la aplicación web ntfy en modo de navegación privada de todos modos, porque todo está almacenado en el almacenamiento del navegador. Puede leer más sobre esto <githubLink>en este issue de GitHub</githubLink>, o hablar con nosotros en <discordLink>Discord</discordLink> o <matrixLink>Matrix</matrixLink>.",
|
||||||
|
"action_bar_show_menu": "Mostrar menú",
|
||||||
|
"action_bar_logo_alt": "logo de ntfy",
|
||||||
|
"action_bar_toggle_action_menu": "Abrir/cerrar el menú de acción",
|
||||||
|
"message_bar_show_dialog": "Mostrar diálogo de publicación",
|
||||||
|
"message_bar_publish": "Publicar mensaje",
|
||||||
|
"nav_button_muted": "Notificaciones silenciadas",
|
||||||
|
"nav_button_connecting": "conectando",
|
||||||
|
"notifications_list": "Lista de notificaciones",
|
||||||
|
"notifications_list_item": "Notificación",
|
||||||
|
"notifications_mark_read": "Marcar como leído",
|
||||||
|
"notifications_delete": "Eliminar",
|
||||||
|
"notifications_priority_x": "Prioridad {{priority}}",
|
||||||
|
"notifications_new_indicator": "Nueva notificación",
|
||||||
|
"notifications_attachment_image": "Imagen adjunta",
|
||||||
|
"notifications_attachment_file_image": "archivo de imagen",
|
||||||
|
"notifications_attachment_file_video": "archivo de video",
|
||||||
|
"notifications_attachment_file_audio": "archivo de audio",
|
||||||
|
"notifications_attachment_file_app": "Archivo de aplicación de Android",
|
||||||
|
"notifications_attachment_file_document": "otro documento",
|
||||||
|
"action_bar_toggle_mute": "Silenciar/reactivar notificaciones",
|
||||||
|
"publish_dialog_emoji_picker_show": "Elige un emoji",
|
||||||
|
"publish_dialog_topic_reset": "Restablecer tópico",
|
||||||
|
"publish_dialog_click_reset": "Eliminar URL de clic",
|
||||||
|
"publish_dialog_email_reset": "Eliminar el reenvío de correo electrónico",
|
||||||
|
"publish_dialog_attach_reset": "Eliminar la URL del archivo adjunto",
|
||||||
|
"publish_dialog_delay_reset": "Eliminar entrega retrasada",
|
||||||
|
"publish_dialog_attached_file_remove": "Eliminar el archivo adjunto",
|
||||||
|
"emoji_picker_search_clear": "Limpiar búsqueda",
|
||||||
|
"subscribe_dialog_subscribe_base_url_label": "URL del servicio",
|
||||||
|
"prefs_notifications_sound_play": "Reproducir el sonido seleccionado",
|
||||||
|
"prefs_users_table": "Tabla de usuarios",
|
||||||
|
"prefs_users_edit_button": "Editar usuario",
|
||||||
|
"prefs_users_delete_button": "Eliminar usuario",
|
||||||
|
"error_boundary_unsupported_indexeddb_title": "Navegación privada no soportada"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,5 +152,40 @@
|
|||||||
"priority_default": "bawaan",
|
"priority_default": "bawaan",
|
||||||
"priority_min": "min",
|
"priority_min": "min",
|
||||||
"notifications_actions_not_supported": "Tindakan tidak didukung di aplikasi web",
|
"notifications_actions_not_supported": "Tindakan tidak didukung di aplikasi web",
|
||||||
"notifications_actions_http_request_title": "Kirim {{method}} HTTP ke {{url}}"
|
"notifications_actions_http_request_title": "Kirim {{method}} HTTP ke {{url}}",
|
||||||
|
"action_bar_show_menu": "Tampilkan menu",
|
||||||
|
"action_bar_logo_alt": "logo ntfy",
|
||||||
|
"action_bar_toggle_mute": "Bisu/suarakan notifikasi",
|
||||||
|
"action_bar_toggle_action_menu": "Buka/tutup menu tindakan",
|
||||||
|
"message_bar_show_dialog": "Tampilkan dialog publikasi",
|
||||||
|
"message_bar_publish": "Publikasikan pesan",
|
||||||
|
"nav_button_muted": "Notifikasi dibisukan",
|
||||||
|
"nav_button_connecting": "menghubungkan",
|
||||||
|
"notifications_list": "Daftar notifikasi",
|
||||||
|
"notifications_list_item": "Notifikasi",
|
||||||
|
"notifications_mark_read": "Tandai sebagai dibaca",
|
||||||
|
"notifications_delete": "Hapus",
|
||||||
|
"notifications_priority_x": "Prioritas {{priority}}",
|
||||||
|
"notifications_new_indicator": "Notifikasi baru",
|
||||||
|
"notifications_attachment_image": "Lampiran gambar",
|
||||||
|
"notifications_attachment_file_image": "file gambar",
|
||||||
|
"notifications_attachment_file_video": "file",
|
||||||
|
"notifications_attachment_file_audio": "file audio",
|
||||||
|
"notifications_attachment_file_app": "file aplikasi Android",
|
||||||
|
"notifications_attachment_file_document": "dokumen lainnya",
|
||||||
|
"publish_dialog_emoji_picker_show": "Pilih emoji",
|
||||||
|
"publish_dialog_topic_reset": "Atur ulang topik",
|
||||||
|
"publish_dialog_click_reset": "Hapus URL klik",
|
||||||
|
"publish_dialog_email_reset": "Hapus terusan email",
|
||||||
|
"publish_dialog_attach_reset": "Hapus URL lampiran",
|
||||||
|
"publish_dialog_delay_reset": "Hapus pengiriman telat",
|
||||||
|
"publish_dialog_attached_file_remove": "Hapus file yang dilampirkan",
|
||||||
|
"emoji_picker_search_clear": "Hapus pencarian",
|
||||||
|
"subscribe_dialog_subscribe_base_url_label": "URL layanan",
|
||||||
|
"prefs_notifications_sound_play": "Mainkan suara yang dipilih",
|
||||||
|
"prefs_users_table": "Tabel pengguna",
|
||||||
|
"prefs_users_edit_button": "Edit pengguna",
|
||||||
|
"prefs_users_delete_button": "Hapus pengguna",
|
||||||
|
"error_boundary_unsupported_indexeddb_description": "Aplikasi web ntfy membutuhkan IndexedDB untuk berfungsi, dan peramban Anda tidak mendukung IndexedDB dalam mode penjelajahan pribadi.<br/><br/>Meskipun ini disayangkan, penggunaan aplikasi web ntfy juga tidak masuk akal di mode penjelajahan pribadi, karena semuanya disimpan di penyimpanan peramban. Anda dapat membaca lebih lanjut tentangnya <githubLink>di masalah GitHub ini</githubLink>, atau berbicara dengan kami di <discordLink>Discord</discordLink> atau <matrixLink>Matrix</matrixLink>.",
|
||||||
|
"error_boundary_unsupported_indexeddb_title": "Penjelajahan privat tidak didukung"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
"publish_dialog_message_label": "メッセージ",
|
"publish_dialog_message_label": "メッセージ",
|
||||||
"publish_dialog_email_label": "メール",
|
"publish_dialog_email_label": "メール",
|
||||||
"notifications_none_for_any_title": "まだ通知を受信していません。",
|
"notifications_none_for_any_title": "まだ通知を受信していません。",
|
||||||
"publish_dialog_priority_max": "優先度最高",
|
"publish_dialog_priority_max": "優先度 最高",
|
||||||
"publish_dialog_button_cancel_sending": "送信をキャンセル",
|
"publish_dialog_button_cancel_sending": "送信をキャンセル",
|
||||||
"publish_dialog_attach_label": "添付URL",
|
"publish_dialog_attach_label": "添付URL",
|
||||||
"notifications_none_for_any_description": "トピックに通知を送信するには、トピックURLにPUTまたはPOSTしてください。トピックのひとつを利用した例を示します。",
|
"notifications_none_for_any_description": "トピックに通知を送信するには、トピックURLにPUTまたはPOSTしてください。トピックのひとつを利用した例を示します。",
|
||||||
@@ -60,14 +60,14 @@
|
|||||||
"publish_dialog_email_placeholder": "通知を転送するアドレス, 例) phil@example.com",
|
"publish_dialog_email_placeholder": "通知を転送するアドレス, 例) phil@example.com",
|
||||||
"notifications_more_details": "詳しい情報は、<websiteLink>ウェブサイト</websiteLink> または <docsLink>ドキュメント</docsLink> を参照してください。",
|
"notifications_more_details": "詳しい情報は、<websiteLink>ウェブサイト</websiteLink> または <docsLink>ドキュメント</docsLink> を参照してください。",
|
||||||
"publish_dialog_attachment_limits_file_reached": "ファイルサイズ制限 {{fileSizeLimit}} を超えました",
|
"publish_dialog_attachment_limits_file_reached": "ファイルサイズ制限 {{fileSizeLimit}} を超えました",
|
||||||
"publish_dialog_priority_min": "優先度最低",
|
"publish_dialog_priority_min": "優先度 最低",
|
||||||
"publish_dialog_priority_low": "優先度低",
|
"publish_dialog_priority_low": "優先度 低",
|
||||||
"publish_dialog_priority_default": "優先度通常",
|
"publish_dialog_priority_default": "優先度 通常",
|
||||||
"publish_dialog_base_url_label": "サービスURL",
|
"publish_dialog_base_url_label": "サービスURL",
|
||||||
"publish_dialog_other_features": "他の機能:",
|
"publish_dialog_other_features": "他の機能:",
|
||||||
"notifications_loading": "通知を読み込み中…",
|
"notifications_loading": "通知を読み込み中…",
|
||||||
"publish_dialog_attachment_limits_quota_reached": "クォータを超過しました、残り{{remainingBytes}}",
|
"publish_dialog_attachment_limits_quota_reached": "クォータを超過しました、残り{{remainingBytes}}",
|
||||||
"publish_dialog_priority_high": "優先度高",
|
"publish_dialog_priority_high": "優先度 高",
|
||||||
"publish_dialog_topic_placeholder": "トピック名の例 phil_alerts",
|
"publish_dialog_topic_placeholder": "トピック名の例 phil_alerts",
|
||||||
"publish_dialog_title_placeholder": "通知タイトル 例: ディスクスペース警告",
|
"publish_dialog_title_placeholder": "通知タイトル 例: ディスクスペース警告",
|
||||||
"publish_dialog_message_placeholder": "メッセージ本文を入力してください",
|
"publish_dialog_message_placeholder": "メッセージ本文を入力してください",
|
||||||
@@ -152,5 +152,40 @@
|
|||||||
"priority_low": "低",
|
"priority_low": "低",
|
||||||
"priority_min": "最低",
|
"priority_min": "最低",
|
||||||
"notifications_actions_not_supported": "このアクションはWebアプリではサポートされていません",
|
"notifications_actions_not_supported": "このアクションはWebアプリではサポートされていません",
|
||||||
"notifications_actions_http_request_title": "{{url}}にHTTP {{method}}を送信"
|
"notifications_actions_http_request_title": "{{url}}にHTTP {{method}}を送信",
|
||||||
|
"prefs_users_edit_button": "ユーザーを編集",
|
||||||
|
"publish_dialog_attached_file_remove": "添付ファイルを削除",
|
||||||
|
"error_boundary_unsupported_indexeddb_description": "nfty webアプリは動作にIndexedDBを使用しますが、あなたのブラウザはプライベートブラウジングモード時にIndexedDBをサポートしていません。<br/><br/>これは残念なことですが、ntfy webアプリは全ての情報をブラウザストレージに保存して動作するため、プライベートブラウジングモードで利用するのはあまり意味がないかも知れません。詳細については <githubLink>GitHub issue</githubLink>を参照するか、<discordLink>Discord</discordLink>や<matrixLink>Matrix</matrixLink>の議論に参加してください。",
|
||||||
|
"action_bar_show_menu": "メニューを表示",
|
||||||
|
"action_bar_logo_alt": "ntfyロゴ",
|
||||||
|
"action_bar_toggle_mute": "通知をミュート/解除",
|
||||||
|
"action_bar_toggle_action_menu": "動作メニューを開く/閉じる",
|
||||||
|
"message_bar_show_dialog": "送信ダイアログを表示",
|
||||||
|
"message_bar_publish": "メッセージを送信",
|
||||||
|
"nav_button_muted": "ミュートされた通知",
|
||||||
|
"nav_button_connecting": "接続中",
|
||||||
|
"notifications_list": "通知一覧",
|
||||||
|
"notifications_new_indicator": "新しい通知",
|
||||||
|
"notifications_list_item": "通知",
|
||||||
|
"notifications_mark_read": "既読にする",
|
||||||
|
"notifications_delete": "削除",
|
||||||
|
"notifications_priority_x": "優先度 {{priority}}",
|
||||||
|
"notifications_attachment_image": "添付画像",
|
||||||
|
"notifications_attachment_file_image": "画像ファイル",
|
||||||
|
"notifications_attachment_file_video": "動画ファイル",
|
||||||
|
"notifications_attachment_file_audio": "音声ファイル",
|
||||||
|
"notifications_attachment_file_app": "Androidアプリファイル",
|
||||||
|
"notifications_attachment_file_document": "その他文書",
|
||||||
|
"publish_dialog_emoji_picker_show": "絵文字",
|
||||||
|
"publish_dialog_topic_reset": "トピックをリセット",
|
||||||
|
"publish_dialog_click_reset": "クリックURLを削除",
|
||||||
|
"publish_dialog_email_reset": "メール転送を削除",
|
||||||
|
"publish_dialog_attach_reset": "添付URLを削除",
|
||||||
|
"publish_dialog_delay_reset": "配信遅延を削除",
|
||||||
|
"emoji_picker_search_clear": "検索をクリア",
|
||||||
|
"subscribe_dialog_subscribe_base_url_label": "サーバーURL",
|
||||||
|
"prefs_notifications_sound_play": "選択されたサウンドを再生",
|
||||||
|
"prefs_users_table": "ユーザー一覧",
|
||||||
|
"prefs_users_delete_button": "ユーザーを削除",
|
||||||
|
"error_boundary_unsupported_indexeddb_title": "プライベートブラウジングはサポートされていません"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,5 +153,39 @@
|
|||||||
"prefs_notifications_sound_description_none": "Notificações não reproduzem nenhum som quando chegam",
|
"prefs_notifications_sound_description_none": "Notificações não reproduzem nenhum som quando chegam",
|
||||||
"prefs_notifications_sound_description_some": "Notificações reproduzem som {{sound}} quando chegam",
|
"prefs_notifications_sound_description_some": "Notificações reproduzem som {{sound}} quando chegam",
|
||||||
"prefs_notifications_min_priority_description_x_or_higher": "Mostrar notificações se prioridade for {{number}} ({{name}}) ou acima",
|
"prefs_notifications_min_priority_description_x_or_higher": "Mostrar notificações se prioridade for {{number}} ({{name}}) ou acima",
|
||||||
"prefs_notifications_delete_after_three_hours_description": "Notificações são automaticamente excluídas após três horas"
|
"prefs_notifications_delete_after_three_hours_description": "Notificações são automaticamente excluídas após três horas",
|
||||||
|
"publish_dialog_attach_reset": "Remover URL do anexo",
|
||||||
|
"publish_dialog_emoji_picker_show": "Escolher emoji",
|
||||||
|
"publish_dialog_attached_file_remove": "Remover arquivo anexado",
|
||||||
|
"emoji_picker_search_clear": "Limpar",
|
||||||
|
"subscribe_dialog_subscribe_base_url_label": "URL de subscrição",
|
||||||
|
"notifications_list": "Lista de notificações",
|
||||||
|
"message_bar_show_dialog": "Mostrar caixa de publicação",
|
||||||
|
"publish_dialog_topic_reset": "Resetar tópico",
|
||||||
|
"publish_dialog_delay_reset": "Remover entrega adiada da notificação",
|
||||||
|
"nav_button_connecting": "Conectando",
|
||||||
|
"publish_dialog_email_reset": "Remover encaminhar email",
|
||||||
|
"prefs_notifications_sound_play": "Reproduzir som selecionado",
|
||||||
|
"action_bar_show_menu": "Mostrar menu",
|
||||||
|
"action_bar_toggle_mute": "Habilita/Desabilita notificações",
|
||||||
|
"action_bar_toggle_action_menu": "Abrir/fechar menu de ação",
|
||||||
|
"action_bar_logo_alt": "nfty logo",
|
||||||
|
"message_bar_publish": "Publicar mensagem",
|
||||||
|
"nav_button_muted": "Notificações desabilitadas",
|
||||||
|
"notifications_list_item": "Notificação",
|
||||||
|
"notifications_mark_read": "Marcar como lido",
|
||||||
|
"notifications_delete": "Excluir",
|
||||||
|
"notifications_priority_x": "Prioridade {{priority}}",
|
||||||
|
"notifications_new_indicator": "Nova notificação",
|
||||||
|
"notifications_attachment_image": "Imagem anexada",
|
||||||
|
"notifications_attachment_file_image": "Arquivo de imagem",
|
||||||
|
"notifications_attachment_file_video": "Arquivo de vídeo",
|
||||||
|
"notifications_attachment_file_audio": "Arquivo de áudio",
|
||||||
|
"notifications_attachment_file_app": "Arquivo apk android",
|
||||||
|
"notifications_attachment_file_document": "Outros documentos",
|
||||||
|
"publish_dialog_click_reset": "Remover URL clicável",
|
||||||
|
"prefs_users_table": "Tabela de usuários",
|
||||||
|
"prefs_users_edit_button": "Editar usuário",
|
||||||
|
"prefs_users_delete_button": "Excluir usuário",
|
||||||
|
"error_boundary_unsupported_indexeddb_title": "Navegação anônima não suportada"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,5 +152,40 @@
|
|||||||
"prefs_notifications_delete_after_never_description": "Bildirimler asla otomatik olarak silinmez",
|
"prefs_notifications_delete_after_never_description": "Bildirimler asla otomatik olarak silinmez",
|
||||||
"priority_high": "yüksek",
|
"priority_high": "yüksek",
|
||||||
"notifications_actions_not_supported": "Eylem, web uygulamasında desteklenmiyor",
|
"notifications_actions_not_supported": "Eylem, web uygulamasında desteklenmiyor",
|
||||||
"notifications_actions_http_request_title": "{{url}} adresine HTTP {{method}} gönder"
|
"notifications_actions_http_request_title": "{{url}} adresine HTTP {{method}} gönder",
|
||||||
|
"action_bar_show_menu": "Menüyü göster",
|
||||||
|
"action_bar_logo_alt": "ntfy logosu",
|
||||||
|
"action_bar_toggle_action_menu": "Eylem menüsünü aç/kapat",
|
||||||
|
"message_bar_show_dialog": "Yayınla iletişim kutusunu göster",
|
||||||
|
"message_bar_publish": "Mesaj yayınla",
|
||||||
|
"nav_button_connecting": "bağlanıyor",
|
||||||
|
"notifications_list": "Bildirimler listesi",
|
||||||
|
"notifications_list_item": "Bildirim",
|
||||||
|
"notifications_delete": "Sil",
|
||||||
|
"notifications_attachment_image": "Ek resmi",
|
||||||
|
"notifications_attachment_file_image": "resim dosyası",
|
||||||
|
"notifications_attachment_file_video": "video dosyası",
|
||||||
|
"notifications_attachment_file_audio": "ses dosyası",
|
||||||
|
"notifications_attachment_file_app": "Android uygulama dosyası",
|
||||||
|
"notifications_attachment_file_document": "diğer belge",
|
||||||
|
"publish_dialog_emoji_picker_show": "Emoji seç",
|
||||||
|
"publish_dialog_topic_reset": "Konuyu sıfırla",
|
||||||
|
"publish_dialog_attach_reset": "Ek URL'sini kaldır",
|
||||||
|
"publish_dialog_delay_reset": "Gecikmeli teslimatı kaldır",
|
||||||
|
"publish_dialog_attached_file_remove": "Ekli dosyayı kaldır",
|
||||||
|
"emoji_picker_search_clear": "Aramayı temizle",
|
||||||
|
"subscribe_dialog_subscribe_base_url_label": "Hizmet URL'si",
|
||||||
|
"prefs_notifications_sound_play": "Seçilen sesi çal",
|
||||||
|
"error_boundary_unsupported_indexeddb_description": "ntfy web uygulamasının çalışması için IndexedDB'ye ihtiyacı var ve tarayıcınız gizli tarama modunda IndexedDB'yi desteklemiyor.<br/><br/>Bu talihsiz olsa da, ntfy web uygulamasını gizli tarama modunda kullanmak pek mantıklı değildir, çünkü her şey tarayıcı deposunda saklanır. <githubLink>Bu GitHub sorununda</githubLink> bununla ilgili daha fazla bilgi edinebilir veya <discordLink>Discord</discordLink> veya <matrixLink>Matrix</matrixLink> üzerinden bizimle konuşabilirsiniz.",
|
||||||
|
"notifications_new_indicator": "Yeni bildirim",
|
||||||
|
"action_bar_toggle_mute": "Bildirimleri sesini kapat/aç",
|
||||||
|
"publish_dialog_click_reset": "Tıklama URL'sini kaldır",
|
||||||
|
"prefs_users_table": "Kullanıcılar tablosu",
|
||||||
|
"error_boundary_unsupported_indexeddb_title": "Gizli tarama desteklenmiyor",
|
||||||
|
"nav_button_muted": "Bildirimler sessize alındı",
|
||||||
|
"notifications_mark_read": "Okundu olarak işaretle",
|
||||||
|
"notifications_priority_x": "Öncelik {{priority}}",
|
||||||
|
"publish_dialog_email_reset": "E-posta yönlendirmesini kaldır",
|
||||||
|
"prefs_users_edit_button": "Kullanıcıyı düzenle",
|
||||||
|
"prefs_users_delete_button": "Kullanıcı sil"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -458,7 +458,7 @@ const Language = () => {
|
|||||||
<MenuItem value="id">Bahasa Indonesia</MenuItem>
|
<MenuItem value="id">Bahasa Indonesia</MenuItem>
|
||||||
<MenuItem value="ja">日本語</MenuItem>
|
<MenuItem value="ja">日本語</MenuItem>
|
||||||
<MenuItem value="nb_NO">Norsk bokmål</MenuItem>
|
<MenuItem value="nb_NO">Norsk bokmål</MenuItem>
|
||||||
<MenuItem value="pt_BR">Português</MenuItem>
|
<MenuItem value="pt_BR">Português (Brasil)</MenuItem>
|
||||||
<MenuItem value="ru">Русский</MenuItem>
|
<MenuItem value="ru">Русский</MenuItem>
|
||||||
<MenuItem value="tr">Türkçe</MenuItem>
|
<MenuItem value="tr">Türkçe</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
Reference in New Issue
Block a user