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/internal/photoprism/get"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/i18n"
|
"github.com/photoprism/photoprism/pkg/i18n"
|
||||||
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed embed/video.mp4
|
//go:embed embed/video.mp4
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"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
|
// 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/acl"
|
||||||
"github.com/photoprism/photoprism/internal/auth/session"
|
"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) {
|
func TestAuth(t *testing.T) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"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.
|
// 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/entity"
|
||||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
"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.
|
// 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/form"
|
||||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CloseableResponseRecorder struct {
|
type CloseableResponseRecorder struct {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||||
"github.com/photoprism/photoprism/internal/thumb"
|
"github.com/photoprism/photoprism/internal/thumb"
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"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"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAddVideoCacheHeader(t *testing.T) {
|
func TestAddVideoCacheHeader(t *testing.T) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
swagger "github.com/swaggo/gin-swagger"
|
swagger "github.com/swaggo/gin-swagger"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed swagger.json
|
//go:embed swagger.json
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/i18n"
|
"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
|
// 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/internal/photoprism/get"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/i18n"
|
"github.com/photoprism/photoprism/pkg/i18n"
|
||||||
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
"github.com/photoprism/photoprism/pkg/rnd"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
"github.com/photoprism/photoprism/internal/entity"
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
"github.com/photoprism/photoprism/internal/form"
|
"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"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"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.
|
// 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/internal/config"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOAuthToken(t *testing.T) {
|
func TestOAuthToken(t *testing.T) {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/i18n"
|
"github.com/photoprism/photoprism/pkg/i18n"
|
||||||
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OAuthUserinfo should return information about the authenticated user,
|
// 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/photoprism/get"
|
||||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/i18n"
|
"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.
|
// 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/internal/thumb/avatar"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/i18n"
|
"github.com/photoprism/photoprism/pkg/i18n"
|
||||||
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
"github.com/photoprism/photoprism/pkg/rnd"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
"github.com/photoprism/photoprism/pkg/time/unix"
|
"github.com/photoprism/photoprism/pkg/time/unix"
|
||||||
"github.com/photoprism/photoprism/pkg/txt"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/form"
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"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"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/i18n"
|
"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.
|
// 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/internal/photoprism/get"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/i18n"
|
"github.com/photoprism/photoprism/pkg/i18n"
|
||||||
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
"github.com/photoprism/photoprism/pkg/rnd"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/auth/acl"
|
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"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"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/i18n"
|
"github.com/photoprism/photoprism/pkg/i18n"
|
||||||
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
"github.com/photoprism/photoprism/pkg/rnd"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/media/video"
|
"github.com/photoprism/photoprism/pkg/media/video"
|
||||||
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
"github.com/photoprism/photoprism/pkg/rnd"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/media/video"
|
"github.com/photoprism/photoprism/pkg/media/video"
|
||||||
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetVideo(t *testing.T) {
|
func TestGetVideo(t *testing.T) {
|
||||||
|
|||||||
@@ -145,10 +145,8 @@ func startAction(ctx *cli.Context) error {
|
|||||||
auto.Start(conf)
|
auto.Start(conf)
|
||||||
|
|
||||||
// Wait for signal to initiate server shutdown.
|
// Wait for signal to initiate server shutdown.
|
||||||
quit := make(chan os.Signal)
|
signal.Notify(server.Signal, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)
|
||||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)
|
sig := <-server.Signal
|
||||||
|
|
||||||
sig := <-quit
|
|
||||||
|
|
||||||
// Stop all background activity.
|
// Stop all background activity.
|
||||||
auto.Shutdown()
|
auto.Shutdown()
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ func statusAction(ctx *cli.Context) error {
|
|||||||
client := &http.Client{Timeout: 10 * time.Second}
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
|
||||||
// Connect to unix socket?
|
// Connect to unix socket?
|
||||||
if unixSocket := conf.HttpSocket(); unixSocket != "" {
|
if unixSocket := conf.HttpSocket(); unixSocket != nil {
|
||||||
client.Transport = &http.Transport{
|
client.Transport = &http.Transport{
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
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/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConfig_CdnUrl(t *testing.T) {
|
func TestConfig_CdnUrl(t *testing.T) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -8,7 +9,8 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/config/ttl"
|
"github.com/photoprism/photoprism/internal/config/ttl"
|
||||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"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 (
|
const (
|
||||||
@@ -47,13 +49,13 @@ func (c *Config) ProxyProtoHeaders() map[string]string {
|
|||||||
h := make(map[string]string, p+1)
|
h := make(map[string]string, p+1)
|
||||||
|
|
||||||
if p == 0 {
|
if p == 0 {
|
||||||
h[header.ForwardedProto] = header.ProtoHttps
|
h[header.ForwardedProto] = scheme.Https
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range c.options.ProxyProtoHeaders {
|
for k, v := range c.options.ProxyProtoHeaders {
|
||||||
if l := len(c.options.ProxyProtoHttps); l == 0 {
|
if l := len(c.options.ProxyProtoHttps); l == 0 {
|
||||||
h[v] = header.ProtoHttps
|
h[v] = scheme.Https
|
||||||
} else if l > k {
|
} else if l > k {
|
||||||
h[v] = c.options.ProxyProtoHttps[k]
|
h[v] = c.options.ProxyProtoHttps[k]
|
||||||
} else {
|
} else {
|
||||||
@@ -138,16 +140,44 @@ func (c *Config) HttpPort() int {
|
|||||||
return c.options.HttpPort
|
return c.options.HttpPort
|
||||||
}
|
}
|
||||||
|
|
||||||
// HttpSocket tries to parse the HttpHost as a Unix socket path and returns an empty string otherwise.
|
// HttpSocket tries to parse the HttpHost as a Unix socket URL and returns it, or nil if it fails.
|
||||||
func (c *Config) HttpSocket() string {
|
func (c *Config) HttpSocket() *url.URL {
|
||||||
if c.options.HttpSocket != "" {
|
if c.options.HttpSocket != nil {
|
||||||
// Do nothing.
|
// Return cached resource URI.
|
||||||
|
return c.options.HttpSocket
|
||||||
} else if host := c.options.HttpHost; !strings.HasPrefix(host, "unix:") {
|
} else if host := c.options.HttpHost; !strings.HasPrefix(host, "unix:") {
|
||||||
return ""
|
return nil
|
||||||
} else if strings.Contains(host, "/") {
|
|
||||||
c.options.HttpSocket = strings.TrimPrefix(host, "unix:")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
return c.options.HttpSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/config/ttl"
|
"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) {
|
func TestConfig_HttpServerHost(t *testing.T) {
|
||||||
@@ -20,10 +24,62 @@ func TestConfig_HttpServerHost(t *testing.T) {
|
|||||||
|
|
||||||
func TestConfig_HttpSocket(t *testing.T) {
|
func TestConfig_HttpSocket(t *testing.T) {
|
||||||
c := NewConfig(CliTestContext())
|
c := NewConfig(CliTestContext())
|
||||||
|
assert.Nil(t, c.HttpSocket())
|
||||||
|
|
||||||
assert.Equal(t, "", c.HttpSocket())
|
t.Run("Empty", func(t *testing.T) {
|
||||||
c.options.HttpHost = "unix:/tmp/photoprism.sock"
|
c.options.HttpSocket = nil
|
||||||
assert.Equal(t, "/tmp/photoprism.sock", c.HttpSocket())
|
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) {
|
func TestConfig_HttpServerPort(t *testing.T) {
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/ffmpeg"
|
"github.com/photoprism/photoprism/internal/ffmpeg"
|
||||||
"github.com/photoprism/photoprism/internal/thumb"
|
"github.com/photoprism/photoprism/internal/thumb"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/i18n"
|
"github.com/photoprism/photoprism/pkg/i18n"
|
||||||
"github.com/photoprism/photoprism/pkg/media"
|
"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"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -616,7 +617,7 @@ var Flags = CliFlags{
|
|||||||
Flag: &cli.StringSliceFlag{
|
Flag: &cli.StringSliceFlag{
|
||||||
Name: "proxy-proto-https",
|
Name: "proxy-proto-https",
|
||||||
Usage: "forwarded HTTPS protocol `NAME`",
|
Usage: "forwarded HTTPS protocol `NAME`",
|
||||||
Value: cli.NewStringSlice(header.ProtoHttps),
|
Value: cli.NewStringSlice(scheme.Https),
|
||||||
EnvVars: EnvVars("PROXY_PROTO_HTTPS"),
|
EnvVars: EnvVars("PROXY_PROTO_HTTPS"),
|
||||||
}}, {
|
}}, {
|
||||||
Flag: &cli.BoolFlag{
|
Flag: &cli.BoolFlag{
|
||||||
@@ -678,7 +679,7 @@ var Flags = CliFlags{
|
|||||||
Name: "http-host",
|
Name: "http-host",
|
||||||
Aliases: []string{"ip"},
|
Aliases: []string{"ip"},
|
||||||
Value: "0.0.0.0",
|
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"),
|
EnvVars: EnvVars("HTTP_HOST"),
|
||||||
}}, {
|
}}, {
|
||||||
Flag: &cli.IntFlag{
|
Flag: &cli.IntFlag{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package config
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -144,7 +145,7 @@ type Options struct {
|
|||||||
HttpVideoMaxAge int `yaml:"HttpVideoMaxAge" json:"HttpVideoMaxAge" flag:"http-video-maxage"`
|
HttpVideoMaxAge int `yaml:"HttpVideoMaxAge" json:"HttpVideoMaxAge" flag:"http-video-maxage"`
|
||||||
HttpHost string `yaml:"HttpHost" json:"-" flag:"http-host"`
|
HttpHost string `yaml:"HttpHost" json:"-" flag:"http-host"`
|
||||||
HttpPort int `yaml:"HttpPort" json:"-" flag:"http-port"`
|
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"`
|
DatabaseDriver string `yaml:"DatabaseDriver" json:"-" flag:"database-driver"`
|
||||||
DatabaseDsn string `yaml:"DatabaseDsn" json:"-" flag:"database-dsn"`
|
DatabaseDsn string `yaml:"DatabaseDsn" json:"-" flag:"database-dsn"`
|
||||||
DatabaseName string `yaml:"DatabaseName" json:"-" flag:"database-name"`
|
DatabaseName string `yaml:"DatabaseName" json:"-" flag:"database-name"`
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/i18n"
|
"github.com/photoprism/photoprism/pkg/i18n"
|
||||||
"github.com/photoprism/photoprism/pkg/list"
|
"github.com/photoprism/photoprism/pkg/list"
|
||||||
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
"github.com/photoprism/photoprism/pkg/rnd"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
"github.com/photoprism/photoprism/pkg/time/unix"
|
"github.com/photoprism/photoprism/pkg/time/unix"
|
||||||
"github.com/photoprism/photoprism/pkg/txt"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/form"
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/i18n"
|
"github.com/photoprism/photoprism/pkg/i18n"
|
||||||
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
"github.com/photoprism/photoprism/pkg/rnd"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
"github.com/photoprism/photoprism/pkg/txt"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/auth/acl"
|
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"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/rnd"
|
||||||
"github.com/photoprism/photoprism/pkg/time/unix"
|
"github.com/photoprism/photoprism/pkg/time/unix"
|
||||||
"github.com/photoprism/photoprism/pkg/txt/report"
|
"github.com/photoprism/photoprism/pkg/txt/report"
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/ai/face"
|
"github.com/photoprism/photoprism/internal/ai/face"
|
||||||
@@ -14,6 +12,7 @@ import (
|
|||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
"github.com/photoprism/photoprism/pkg/media/colors"
|
"github.com/photoprism/photoprism/pkg/media/colors"
|
||||||
"github.com/photoprism/photoprism/pkg/media/projection"
|
"github.com/photoprism/photoprism/pkg/media/projection"
|
||||||
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
"github.com/photoprism/photoprism/pkg/rnd"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/entity"
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
"github.com/photoprism/photoprism/pkg/media"
|
"github.com/photoprism/photoprism/pkg/media"
|
||||||
"github.com/photoprism/photoprism/pkg/media/video"
|
"github.com/photoprism/photoprism/pkg/media/video"
|
||||||
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPhoto_Ids(t *testing.T) {
|
func TestPhoto_Ids(t *testing.T) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
"github.com/photoprism/photoprism/internal/entity"
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"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) {
|
func TestMediaFile_Ok(t *testing.T) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"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.
|
// 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/api"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"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.
|
// 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/api"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"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.
|
// registerWebAppRoutes adds routes for the web user interface.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/api"
|
"github.com/photoprism/photoprism/internal/api"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"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.
|
// 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"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/acme/autocert"
|
"golang.org/x/crypto/acme/autocert"
|
||||||
@@ -16,7 +17,10 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"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
|
// 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")
|
c.String(http.StatusOK, "OK")
|
||||||
})
|
})
|
||||||
|
|
||||||
// Start web server.
|
// Web server configuration.
|
||||||
var tlsErr error
|
var tlsErr error
|
||||||
var tlsManager *autocert.Manager
|
var tlsManager *autocert.Manager
|
||||||
var server *http.Server
|
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 listener net.Listener
|
||||||
var unixAddr *net.UnixAddr
|
var unixAddr *net.UnixAddr
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Create a Unix socket and attach the server to it.
|
// Check if the Unix socket already exists and delete it if the force flag is set.
|
||||||
if unixAddr, err = net.ResolveUnixAddr("unix", unixSocket); err != nil {
|
if fs.SocketExists(unixSocket.Path) {
|
||||||
log.Errorf("server: invalid unix socket (%s)", err)
|
if txt.Bool(unixSocket.Query().Get("force")) == false {
|
||||||
|
Fail("server: %s socket %s already exists", clean.Log(unixSocket.Scheme), clean.Log(unixSocket.Path))
|
||||||
return
|
return
|
||||||
} else if listener, err = net.ListenUnix("unix", unixAddr); err != nil {
|
} else if removeErr := os.Remove(unixSocket.Path); removeErr != nil {
|
||||||
log.Errorf("server: failed to listen on unix socket (%s)", err)
|
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(unixSocket.Scheme, unixAddr); err != nil {
|
||||||
|
Fail("server: failed to listen on %s socket (%s)", clean.Log(unixSocket.Scheme), err)
|
||||||
return
|
return
|
||||||
} else {
|
} 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:
|
// Listen on Unix socket, which should be automatically closed and removed after use:
|
||||||
// https://pkg.go.dev/net#UnixListener.SetUnlinkOnClose.
|
// https://pkg.go.dev/net#UnixListener.SetUnlinkOnClose.
|
||||||
server = &http.Server{
|
server = &http.Server{
|
||||||
Addr: unixSocket,
|
Addr: listener.Addr().String(),
|
||||||
Handler: router,
|
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)
|
go StartHttp(server, listener)
|
||||||
}
|
}
|
||||||
} else if tlsManager, tlsErr = AutoTLS(conf); tlsErr == nil {
|
} 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))
|
log.Infof("server: listening on %s [%s]", server.Addr, time.Since(start))
|
||||||
|
|
||||||
|
// Start Web server.
|
||||||
go StartAutoTLS(server, tlsManager, conf)
|
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")
|
log.Infof("server: starting in tls mode")
|
||||||
|
|
||||||
tlsSocket := fmt.Sprintf("%s:%d", conf.HttpHost(), conf.HttpPort())
|
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))
|
log.Infof("server: listening on %s [%s]", server.Addr, time.Since(start))
|
||||||
|
|
||||||
|
// Start Web server.
|
||||||
go StartTLS(server, publicCert, privateKey)
|
go StartTLS(server, publicCert, privateKey)
|
||||||
} else {
|
} else {
|
||||||
log.Infof("server: %s", tlsErr)
|
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())
|
tcpSocket := fmt.Sprintf("%s:%d", conf.HttpHost(), conf.HttpPort())
|
||||||
|
|
||||||
if listener, err := net.Listen("tcp", tcpSocket); err != nil {
|
if listener, err := net.Listen("tcp", tcpSocket); err != nil {
|
||||||
log.Errorf("server: %s", err)
|
Fail("server: %s", err)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
server = &http.Server{
|
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))
|
log.Infof("server: listening on %s [%s]", server.Addr, time.Since(start))
|
||||||
|
|
||||||
|
// Start Web server.
|
||||||
go StartHttp(server, listener)
|
go StartHttp(server, listener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"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.
|
// 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/internal/workers/auto"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"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"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"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"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"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/rnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/auth/acl"
|
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
"github.com/photoprism/photoprism/internal/entity"
|
"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"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/service/hub/places"
|
"github.com/photoprism/photoprism/internal/service/hub/places"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Status string
|
type Status string
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/form"
|
"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"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package clean
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"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.
|
// 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
|
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 {
|
func FileExists(fileName string) bool {
|
||||||
if fileName == "" {
|
if fileName == "" {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -1,10 +1,29 @@
|
|||||||
package fs
|
package fs
|
||||||
|
|
||||||
import "os"
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
// File and directory permissions.
|
// File and directory permissions.
|
||||||
var (
|
var (
|
||||||
ModeDir os.FileMode = 0o777
|
ModeDir os.FileMode = 0o777
|
||||||
|
ModeSocket os.FileMode = 0o666
|
||||||
ModeFile os.FileMode = 0o666
|
ModeFile os.FileMode = 0o666
|
||||||
ModeBackup os.FileMode = 0o600
|
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"
|
"strings"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"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.
|
// 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/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContentType(t *testing.T) {
|
func TestContentType(t *testing.T) {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/media"
|
"github.com/photoprism/photoprism/pkg/media"
|
||||||
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Info represents video file information.
|
// Info represents video file information.
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInfo(t *testing.T) {
|
func TestInfo(t *testing.T) {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
"github.com/photoprism/photoprism/pkg/header"
|
|
||||||
"github.com/photoprism/photoprism/pkg/media"
|
"github.com/photoprism/photoprism/pkg/media"
|
||||||
|
"github.com/photoprism/photoprism/pkg/net/header"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProbeFile(t *testing.T) {
|
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