mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
These changes allow you to force the re-creation of existing Unix domain sockets and set the permissions of sockets after they have been created. The flag or variable value for this must be formatted as follows: --http-host="unix:/var/run/photoprism.sock?force=true&mode=660" Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -11,8 +11,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
//go:embed embed/video.mp4
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// Auth checks if the user is authorized to access a resource with the given permission
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||
"github.com/photoprism/photoprism/internal/auth/session"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package api
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// ClientIP returns the client IP address from the request context or a placeholder if it is unknown.
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// AddCountHeader adds the actual result count to the response.
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
type CloseableResponseRecorder struct {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
func TestAddVideoCacheHeader(t *testing.T) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
swagger "github.com/swaggo/gin-swagger"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
//go:embed swagger.json
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// OAuthAuthorize should gather consent and authorization from resource owners when using the
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// OAuthToken creates a new access token for clients that authenticate with valid OAuth2 client credentials.
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
func TestOAuthToken(t *testing.T) {
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// OAuthUserinfo should return information about the authenticated user,
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// OIDCLogin redirects a browser to the login page of the configured OpenID Connect provider, if any.
|
||||
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/thumb/avatar"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
"github.com/photoprism/photoprism/pkg/time/unix"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// CreateSession creates a new client session and returns it as JSON if authentication was successful.
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/media/video"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/media/video"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
func TestGetVideo(t *testing.T) {
|
||||
|
||||
@@ -145,10 +145,8 @@ func startAction(ctx *cli.Context) error {
|
||||
auto.Start(conf)
|
||||
|
||||
// Wait for signal to initiate server shutdown.
|
||||
quit := make(chan os.Signal)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)
|
||||
|
||||
sig := <-quit
|
||||
signal.Notify(server.Signal, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)
|
||||
sig := <-server.Signal
|
||||
|
||||
// Stop all background activity.
|
||||
auto.Shutdown()
|
||||
|
||||
@@ -35,10 +35,10 @@ func statusAction(ctx *cli.Context) error {
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
|
||||
// Connect to unix socket?
|
||||
if unixSocket := conf.HttpSocket(); unixSocket != "" {
|
||||
if unixSocket := conf.HttpSocket(); unixSocket != nil {
|
||||
client.Transport = &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return net.Dial("unix", unixSocket)
|
||||
return net.Dial(unixSocket.Scheme, unixSocket.Path)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
func TestConfig_CdnUrl(t *testing.T) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -8,7 +9,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/config/ttl"
|
||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/scheme"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -47,13 +49,13 @@ func (c *Config) ProxyProtoHeaders() map[string]string {
|
||||
h := make(map[string]string, p+1)
|
||||
|
||||
if p == 0 {
|
||||
h[header.ForwardedProto] = header.ProtoHttps
|
||||
h[header.ForwardedProto] = scheme.Https
|
||||
return h
|
||||
}
|
||||
|
||||
for k, v := range c.options.ProxyProtoHeaders {
|
||||
if l := len(c.options.ProxyProtoHttps); l == 0 {
|
||||
h[v] = header.ProtoHttps
|
||||
h[v] = scheme.Https
|
||||
} else if l > k {
|
||||
h[v] = c.options.ProxyProtoHttps[k]
|
||||
} else {
|
||||
@@ -138,16 +140,44 @@ func (c *Config) HttpPort() int {
|
||||
return c.options.HttpPort
|
||||
}
|
||||
|
||||
// HttpSocket tries to parse the HttpHost as a Unix socket path and returns an empty string otherwise.
|
||||
func (c *Config) HttpSocket() string {
|
||||
if c.options.HttpSocket != "" {
|
||||
// Do nothing.
|
||||
// HttpSocket tries to parse the HttpHost as a Unix socket URL and returns it, or nil if it fails.
|
||||
func (c *Config) HttpSocket() *url.URL {
|
||||
if c.options.HttpSocket != nil {
|
||||
// Return cached resource URI.
|
||||
return c.options.HttpSocket
|
||||
} else if host := c.options.HttpHost; !strings.HasPrefix(host, "unix:") {
|
||||
return ""
|
||||
} else if strings.Contains(host, "/") {
|
||||
c.options.HttpSocket = strings.TrimPrefix(host, "unix:")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse socket resource URI.
|
||||
socket, err := url.Parse(c.options.HttpHost)
|
||||
|
||||
// Return nil if parsing failed, or it's not a Unix domain socket URI.
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if socket.Scheme == scheme.HttpUnix {
|
||||
socket.Scheme = scheme.Unix
|
||||
}
|
||||
|
||||
if socket.Scheme != scheme.Unix || socket.Host == "" && socket.Path == "" {
|
||||
return nil
|
||||
} else if socket.Host != "" && socket.Path == "" {
|
||||
// Create a path from the host if an absolute socket path is not specified,
|
||||
socket.Path = fs.Abs(socket.Host)
|
||||
socket.Host = ""
|
||||
}
|
||||
|
||||
// Should never happen.
|
||||
if socket.Path == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cache parsed resource URI.
|
||||
c.options.HttpSocket = socket
|
||||
|
||||
// Return parsed resource URI.
|
||||
return c.options.HttpSocket
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config/ttl"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/net/scheme"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
func TestConfig_HttpServerHost(t *testing.T) {
|
||||
@@ -20,10 +24,62 @@ func TestConfig_HttpServerHost(t *testing.T) {
|
||||
|
||||
func TestConfig_HttpSocket(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.Nil(t, c.HttpSocket())
|
||||
|
||||
assert.Equal(t, "", c.HttpSocket())
|
||||
c.options.HttpHost = "unix:/tmp/photoprism.sock"
|
||||
assert.Equal(t, "/tmp/photoprism.sock", c.HttpSocket())
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
c.options.HttpSocket = nil
|
||||
c.options.HttpHost = ""
|
||||
|
||||
result := c.HttpSocket()
|
||||
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
c.options.HttpSocket = nil
|
||||
c.options.HttpHost = "unix:http.sock"
|
||||
|
||||
result := c.HttpSocket()
|
||||
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
t.Run("UnixHost", func(t *testing.T) {
|
||||
c.options.HttpSocket = nil
|
||||
c.options.HttpHost = "unix://http.sock"
|
||||
|
||||
result := c.HttpSocket()
|
||||
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, scheme.Unix, result.Scheme)
|
||||
assert.Contains(t, result.Path, "/internal/config/http.sock")
|
||||
assert.False(t, txt.Bool(result.Query().Get("force")))
|
||||
assert.Equal(t, fs.ModeSocket, fs.ParseMode(result.Query().Get("mode"), fs.ModeSocket))
|
||||
})
|
||||
t.Run("UnixPath", func(t *testing.T) {
|
||||
c.options.HttpSocket = nil
|
||||
c.options.HttpHost = "unix:/var/run/photoprism.sock?force=false&mode=0640"
|
||||
|
||||
result := c.HttpSocket()
|
||||
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, scheme.Unix, result.Scheme)
|
||||
assert.Equal(t, "/var/run/photoprism.sock", result.Path)
|
||||
assert.Equal(t, "false", result.Query().Get("force"))
|
||||
assert.False(t, txt.Bool(result.Query().Get("force")))
|
||||
assert.Equal(t, os.FileMode(0o640), fs.ParseMode(result.Query().Get("mode"), fs.ModeSocket))
|
||||
})
|
||||
t.Run("Force", func(t *testing.T) {
|
||||
c.options.HttpSocket = nil
|
||||
c.options.HttpHost = "unix:/tmp/photoprism.sock?force=true&mode=660"
|
||||
|
||||
result := c.HttpSocket()
|
||||
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, scheme.Unix, result.Scheme)
|
||||
assert.Equal(t, "/tmp/photoprism.sock", result.Path)
|
||||
assert.Equal(t, "true", result.Query().Get("force"))
|
||||
assert.True(t, txt.Bool(result.Query().Get("force")))
|
||||
assert.Equal(t, os.FileMode(0o660), fs.ParseMode(result.Query().Get("mode"), 0o000))
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfig_HttpServerPort(t *testing.T) {
|
||||
|
||||
@@ -13,9 +13,10 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/ffmpeg"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
"github.com/photoprism/photoprism/pkg/media"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/scheme"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
@@ -616,7 +617,7 @@ var Flags = CliFlags{
|
||||
Flag: &cli.StringSliceFlag{
|
||||
Name: "proxy-proto-https",
|
||||
Usage: "forwarded HTTPS protocol `NAME`",
|
||||
Value: cli.NewStringSlice(header.ProtoHttps),
|
||||
Value: cli.NewStringSlice(scheme.Https),
|
||||
EnvVars: EnvVars("PROXY_PROTO_HTTPS"),
|
||||
}}, {
|
||||
Flag: &cli.BoolFlag{
|
||||
@@ -678,7 +679,7 @@ var Flags = CliFlags{
|
||||
Name: "http-host",
|
||||
Aliases: []string{"ip"},
|
||||
Value: "0.0.0.0",
|
||||
Usage: "Web server `IP` address or Unix domain socket, e.g. unix:/var/run/photoprism.sock",
|
||||
Usage: "Web server `IP` address or Unix domain socket, e.g. unix:/var/run/photoprism.sock?force=true&mode=660",
|
||||
EnvVars: EnvVars("HTTP_HOST"),
|
||||
}}, {
|
||||
Flag: &cli.IntFlag{
|
||||
|
||||
@@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@@ -144,7 +145,7 @@ type Options struct {
|
||||
HttpVideoMaxAge int `yaml:"HttpVideoMaxAge" json:"HttpVideoMaxAge" flag:"http-video-maxage"`
|
||||
HttpHost string `yaml:"HttpHost" json:"-" flag:"http-host"`
|
||||
HttpPort int `yaml:"HttpPort" json:"-" flag:"http-port"`
|
||||
HttpSocket string `yaml:"-" json:"-" flag:"-"`
|
||||
HttpSocket *url.URL `yaml:"-" json:"-" flag:"-"`
|
||||
DatabaseDriver string `yaml:"DatabaseDriver" json:"-" flag:"database-driver"`
|
||||
DatabaseDsn string `yaml:"DatabaseDsn" json:"-" flag:"database-dsn"`
|
||||
DatabaseName string `yaml:"DatabaseName" json:"-" flag:"database-name"`
|
||||
|
||||
@@ -16,9 +16,9 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
"github.com/photoprism/photoprism/pkg/list"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
"github.com/photoprism/photoprism/pkg/time/unix"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
"github.com/photoprism/photoprism/pkg/time/unix"
|
||||
"github.com/photoprism/photoprism/pkg/txt/report"
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/ai/face"
|
||||
@@ -14,6 +12,7 @@ import (
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/media/colors"
|
||||
"github.com/photoprism/photoprism/pkg/media/projection"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
|
||||
@@ -4,13 +4,12 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/pkg/media"
|
||||
"github.com/photoprism/photoprism/pkg/media/video"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
func TestPhoto_Ids(t *testing.T) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
func TestMediaFile_Ok(t *testing.T) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// Api is a middleware that sets additional response headers when serving REST API requests.
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/api"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// registerStaticRoutes adds routes for serving static content and templates.
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/api"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// registerWebAppRoutes adds routes for the web user interface.
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/api"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// Security is a middleware that adds security-related headers to the server's response.
|
||||
|
||||
23
internal/server/shutdown.go
Normal file
23
internal/server/shutdown.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Signal channel for initiating the shutdown.
|
||||
var Signal = make(chan os.Signal)
|
||||
|
||||
// Fail reports an error and shuts down the server.
|
||||
func Fail(err string, params ...interface{}) {
|
||||
if err != "" {
|
||||
log.Errorf(err, params...)
|
||||
}
|
||||
|
||||
Shutdown()
|
||||
}
|
||||
|
||||
// Shutdown gracefully stops the server.
|
||||
func Shutdown() {
|
||||
Signal <- syscall.SIGINT
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
@@ -16,7 +17,10 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
// Start the REST API server using the configuration provided
|
||||
@@ -97,33 +101,58 @@ func Start(ctx context.Context, conf *config.Config) {
|
||||
c.String(http.StatusOK, "OK")
|
||||
})
|
||||
|
||||
// Start web server.
|
||||
// Web server configuration.
|
||||
var tlsErr error
|
||||
var tlsManager *autocert.Manager
|
||||
var server *http.Server
|
||||
|
||||
if unixSocket := conf.HttpSocket(); unixSocket != "" {
|
||||
// Listen on a Unix domain socket instead of a TCP port?
|
||||
if unixSocket := conf.HttpSocket(); unixSocket != nil {
|
||||
var listener net.Listener
|
||||
var unixAddr *net.UnixAddr
|
||||
var err error
|
||||
|
||||
// Create a Unix socket and attach the server to it.
|
||||
if unixAddr, err = net.ResolveUnixAddr("unix", unixSocket); err != nil {
|
||||
log.Errorf("server: invalid unix socket (%s)", err)
|
||||
// Check if the Unix socket already exists and delete it if the force flag is set.
|
||||
if fs.SocketExists(unixSocket.Path) {
|
||||
if txt.Bool(unixSocket.Query().Get("force")) == false {
|
||||
Fail("server: %s socket %s already exists", clean.Log(unixSocket.Scheme), clean.Log(unixSocket.Path))
|
||||
return
|
||||
} else if removeErr := os.Remove(unixSocket.Path); removeErr != nil {
|
||||
Fail("server: %s socket %s already exists and cannot be deleted", clean.Log(unixSocket.Scheme), clean.Log(unixSocket.Path))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create a Unix socket and listen on it.
|
||||
if unixAddr, err = net.ResolveUnixAddr(unixSocket.Scheme, unixSocket.Path); err != nil {
|
||||
Fail("server: invalid %s socket (%s)", clean.Log(unixSocket.Scheme), err)
|
||||
return
|
||||
} else if listener, err = net.ListenUnix("unix", unixAddr); err != nil {
|
||||
log.Errorf("server: failed to listen on unix socket (%s)", err)
|
||||
} else if listener, err = net.ListenUnix(unixSocket.Scheme, unixAddr); err != nil {
|
||||
Fail("server: failed to listen on %s socket (%s)", clean.Log(unixSocket.Scheme), err)
|
||||
return
|
||||
} else {
|
||||
// Update socket permissions?
|
||||
if mode := unixSocket.Query().Get("mode"); mode == "" {
|
||||
// Skip, no socket mode was specified.
|
||||
} else if modeErr := os.Chmod(unixSocket.Path, fs.ParseMode(mode, fs.ModeSocket)); modeErr != nil {
|
||||
log.Warnf(
|
||||
"server: failed to change permissions of %s socket %s (%s)",
|
||||
clean.Log(unixSocket.Scheme),
|
||||
clean.Log(unixSocket.Path),
|
||||
modeErr,
|
||||
)
|
||||
}
|
||||
|
||||
// Listen on Unix socket, which should be automatically closed and removed after use:
|
||||
// https://pkg.go.dev/net#UnixListener.SetUnlinkOnClose.
|
||||
server = &http.Server{
|
||||
Addr: unixSocket,
|
||||
Addr: listener.Addr().String(),
|
||||
Handler: router,
|
||||
}
|
||||
|
||||
log.Infof("server: listening on %s [%s]", unixSocket, time.Since(start))
|
||||
log.Infof("server: listening on %s [%s]", unixSocket.Path, time.Since(start))
|
||||
|
||||
// Start Web server.
|
||||
go StartHttp(server, listener)
|
||||
}
|
||||
} else if tlsManager, tlsErr = AutoTLS(conf); tlsErr == nil {
|
||||
@@ -140,8 +169,10 @@ func Start(ctx context.Context, conf *config.Config) {
|
||||
}
|
||||
|
||||
log.Infof("server: listening on %s [%s]", server.Addr, time.Since(start))
|
||||
|
||||
// Start Web server.
|
||||
go StartAutoTLS(server, tlsManager, conf)
|
||||
} else if publicCert, privateKey := conf.TLS(); unixSocket == "" && publicCert != "" && privateKey != "" {
|
||||
} else if publicCert, privateKey := conf.TLS(); publicCert != "" && privateKey != "" {
|
||||
log.Infof("server: starting in tls mode")
|
||||
|
||||
tlsSocket := fmt.Sprintf("%s:%d", conf.HttpHost(), conf.HttpPort())
|
||||
@@ -156,6 +187,8 @@ func Start(ctx context.Context, conf *config.Config) {
|
||||
}
|
||||
|
||||
log.Infof("server: listening on %s [%s]", server.Addr, time.Since(start))
|
||||
|
||||
// Start Web server.
|
||||
go StartTLS(server, publicCert, privateKey)
|
||||
} else {
|
||||
log.Infof("server: %s", tlsErr)
|
||||
@@ -163,7 +196,7 @@ func Start(ctx context.Context, conf *config.Config) {
|
||||
tcpSocket := fmt.Sprintf("%s:%d", conf.HttpHost(), conf.HttpPort())
|
||||
|
||||
if listener, err := net.Listen("tcp", tcpSocket); err != nil {
|
||||
log.Errorf("server: %s", err)
|
||||
Fail("server: %s", err)
|
||||
return
|
||||
} else {
|
||||
server = &http.Server{
|
||||
@@ -173,6 +206,7 @@ func Start(ctx context.Context, conf *config.Config) {
|
||||
|
||||
log.Infof("server: listening on %s [%s]", server.Addr, time.Since(start))
|
||||
|
||||
// Start Web server.
|
||||
go StartHttp(server, listener)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// Static is a middleware that adds static content-related headers to the server's response.
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/workers/auto"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/service/hub/places"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
type Status string
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package clean
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// ContentType normalizes media content type strings, see https://en.wikipedia.org/wiki/Media_type.
|
||||
|
||||
27
pkg/fs/fs.go
27
pkg/fs/fs.go
@@ -42,7 +42,32 @@ const (
|
||||
HomePath = Home + PathSeparator
|
||||
)
|
||||
|
||||
// FileExists returns true if file exists and is not a directory.
|
||||
// Stat returns the os.FileInfo for the given file path, or an error if it does not exist.
|
||||
func Stat(filePath string) (os.FileInfo, error) {
|
||||
if filePath == "" {
|
||||
return nil, fmt.Errorf("empty filepath")
|
||||
}
|
||||
|
||||
return os.Stat(filePath)
|
||||
}
|
||||
|
||||
// SocketExists returns true if the specified socket exists and is not a regular file or directory.
|
||||
func SocketExists(socketName string) bool {
|
||||
if socketName == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if path exists and is a socket.
|
||||
if info, err := os.Stat(socketName); err != nil {
|
||||
return false
|
||||
} else if mode := info.Mode(); info.IsDir() || mode.IsRegular() || mode.Type() != os.ModeSocket {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// FileExists returns true if specified file exists and is not a directory.
|
||||
func FileExists(fileName string) bool {
|
||||
if fileName == "" {
|
||||
return false
|
||||
|
||||
@@ -1,10 +1,29 @@
|
||||
package fs
|
||||
|
||||
import "os"
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// File and directory permissions.
|
||||
var (
|
||||
ModeDir os.FileMode = 0o777
|
||||
ModeSocket os.FileMode = 0o666
|
||||
ModeFile os.FileMode = 0o666
|
||||
ModeBackup os.FileMode = 0o600
|
||||
)
|
||||
|
||||
// ParseMode parses and returns a filesystem permission mode,
|
||||
// or the specified default mode if it could not be parsed.
|
||||
func ParseMode(s string, defaultMode os.FileMode) os.FileMode {
|
||||
if s == "" {
|
||||
return defaultMode
|
||||
}
|
||||
mode, err := strconv.ParseUint(s, 8, 32)
|
||||
|
||||
if err != nil || mode <= 0 {
|
||||
return defaultMode
|
||||
}
|
||||
|
||||
return os.FileMode(mode)
|
||||
}
|
||||
|
||||
42
pkg/fs/mode_test.go
Normal file
42
pkg/fs/mode_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseMode(t *testing.T) {
|
||||
t.Run("Default", func(t *testing.T) {
|
||||
mode := ParseMode("", ModeSocket)
|
||||
assert.Equal(t, ModeSocket, mode)
|
||||
assert.Equal(t, os.FileMode(0o666), mode)
|
||||
})
|
||||
t.Run("777", func(t *testing.T) {
|
||||
mode := ParseMode("777", ModeSocket)
|
||||
assert.Equal(t, os.ModePerm, mode)
|
||||
assert.Equal(t, os.FileMode(0o777), mode)
|
||||
})
|
||||
t.Run("0777", func(t *testing.T) {
|
||||
mode := ParseMode("0777", ModeSocket)
|
||||
assert.Equal(t, os.ModePerm, mode)
|
||||
assert.Equal(t, os.FileMode(0o777), mode)
|
||||
})
|
||||
t.Run("0770", func(t *testing.T) {
|
||||
mode := ParseMode("0770", ModeSocket)
|
||||
assert.Equal(t, os.FileMode(0o770), mode)
|
||||
})
|
||||
t.Run("0666", func(t *testing.T) {
|
||||
mode := ParseMode("0666", ModeSocket)
|
||||
assert.Equal(t, os.FileMode(0o666), mode)
|
||||
})
|
||||
t.Run("0660", func(t *testing.T) {
|
||||
mode := ParseMode("0660", ModeSocket)
|
||||
assert.Equal(t, os.FileMode(0o660), mode)
|
||||
})
|
||||
t.Run("660", func(t *testing.T) {
|
||||
mode := ParseMode("660", ModeSocket)
|
||||
assert.Equal(t, os.FileMode(0o660), mode)
|
||||
})
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// ContentType returns a normalized video content type strings based on the video file type and codec.
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
func TestContentType(t *testing.T) {
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/media"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
// Info represents video file information.
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/media"
|
||||
"github.com/photoprism/photoprism/pkg/net/header"
|
||||
)
|
||||
|
||||
func TestProbeFile(t *testing.T) {
|
||||
|
||||
8
pkg/net/scheme/http.go
Normal file
8
pkg/net/scheme/http.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package scheme
|
||||
|
||||
const (
|
||||
Http = "http"
|
||||
Https = "https"
|
||||
HttpUnix = Http + "+" + Unix
|
||||
Websocket = "wss"
|
||||
)
|
||||
25
pkg/net/scheme/scheme.go
Normal file
25
pkg/net/scheme/scheme.go
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Package scheme provides abstractions and naming constants for URI/URL resource strings.
|
||||
|
||||
Copyright (c) 2018 - 2025 PhotoPrism UG. All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under Version 3 of the GNU Affero General Public License (the "AGPL"):
|
||||
<https://docs.photoprism.app/license/agpl>
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
The AGPL is supplemented by our Trademark and Brand Guidelines,
|
||||
which describe how our Brand Assets may be used:
|
||||
<https://www.photoprism.app/trademark>
|
||||
|
||||
Feel free to send an email to hello@photoprism.app if you have questions,
|
||||
want to support our work, or just want to say hello.
|
||||
|
||||
Additional information can be found in our Developer Guide:
|
||||
<https://docs.photoprism.app/developer-guide/>
|
||||
*/
|
||||
package scheme
|
||||
21
pkg/net/scheme/scheme_test.go
Normal file
21
pkg/net/scheme/scheme_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package scheme
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestScheme(t *testing.T) {
|
||||
t.Run("HTTP", func(t *testing.T) {
|
||||
assert.Equal(t, "http", Http)
|
||||
assert.Equal(t, "http+unix", HttpUnix)
|
||||
assert.Equal(t, "https", Https)
|
||||
assert.Equal(t, "wss", Websocket)
|
||||
})
|
||||
t.Run("Unix", func(t *testing.T) {
|
||||
assert.Equal(t, "unix", Unix)
|
||||
assert.Equal(t, "unixgram", Unixgram)
|
||||
assert.Equal(t, "unixpacket", Unixpacket)
|
||||
})
|
||||
}
|
||||
7
pkg/net/scheme/unix.go
Normal file
7
pkg/net/scheme/unix.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package scheme
|
||||
|
||||
const (
|
||||
Unix = "unix"
|
||||
Unixgram = "unixgram"
|
||||
Unixpacket = "unixpacket"
|
||||
)
|
||||
Reference in New Issue
Block a user