Add listen to Unix websocket (#484)
This commit is contained in:
@@ -43,6 +43,7 @@ sqlite.journal-mode: WAL
|
||||
|
||||
# HTTP server configuration
|
||||
# Host to bind to. Default: 0.0.0.0
|
||||
# Use an IP address for network binding. Use a path for Unix socket binding (e.g. /run/opengist.sock)
|
||||
http.host: 0.0.0.0
|
||||
|
||||
# Port to bind to. Default: 6157
|
||||
@@ -51,6 +52,9 @@ http.port: 6157
|
||||
# Enable or disable git operations (clone, pull, push) via HTTP (either `true` or `false`). Default: true
|
||||
http.git-enabled: true
|
||||
|
||||
# File permissions for Unix socket (octal format). Default: 0666
|
||||
unix-socket-permissions: 0666
|
||||
|
||||
# Enable or disable the metrics endpoint (either `true` or `false`). Default: false
|
||||
metrics.enabled: false
|
||||
|
||||
|
||||
@@ -4,48 +4,49 @@ aside: false
|
||||
|
||||
# Configuration Cheat Sheet
|
||||
|
||||
| YAML Config Key | Environment Variable | Default value | Description |
|
||||
|-----------------------|-------------------------------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| log-level | OG_LOG_LEVEL | `warn` | Set the log level to one of the following: `debug`, `info`, `warn`, `error`, `fatal`. |
|
||||
| log-output | OG_LOG_OUTPUT | `stdout,file` | Set the log output to one or more of the following: `stdout`, `file`. |
|
||||
| external-url | OG_EXTERNAL_URL | none | Public URL to access to Opengist. |
|
||||
| opengist-home | OG_OPENGIST_HOME | home directory | Path to the directory where Opengist stores its data. |
|
||||
| secret-key | OG_SECRET_KEY | randomized 32 bytes | Secret key used for session store & encrypt MFA data on database. |
|
||||
| db-uri | OG_DB_URI | `opengist.db` | URI of the database. |
|
||||
| index | OG_INDEX | `bleve` | Define the code indexer (either `bleve`, `meilisearch`, or empty for no index). |
|
||||
| index.meili.host | OG_MEILI_HOST | none | Set the host for the Meiliseach server. |
|
||||
| index.meili.api-key | OG_MEILI_API_KEY | none | Set the API key for the Meiliseach server. |
|
||||
| git.default-branch | OG_GIT_DEFAULT_BRANCH | none | Default branch name used by Opengist when initializing Git repositories. If not set, uses the Git default branch name. More info [here](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup#_new_default_branch) |
|
||||
| sqlite.journal-mode | OG_SQLITE_JOURNAL_MODE | `WAL` | Set the journal mode for SQLite. More info [here](https://www.sqlite.org/pragma.html#pragma_journal_mode) |
|
||||
| http.host | OG_HTTP_HOST | `0.0.0.0` | The host on which the HTTP server should bind. |
|
||||
| http.port | OG_HTTP_PORT | `6157` | The port on which the HTTP server should listen. |
|
||||
| http.git-enabled | OG_HTTP_GIT_ENABLED | `true` | Enable or disable git operations (clone, pull, push) via HTTP. (`true` or `false`) |
|
||||
| metrics.enabled | OG_METRICS_ENABLED | `false` | Enable or disable Prometheus metrics endpoint at `/metrics` (`true` or `false`) |
|
||||
| ssh.git-enabled | OG_SSH_GIT_ENABLED | `true` | Enable or disable git operations (clone, pull, push) via SSH. (`true` or `false`) |
|
||||
| ssh.host | OG_SSH_HOST | `0.0.0.0` | The host on which the SSH server should bind. |
|
||||
| ssh.port | OG_SSH_PORT | `2222` | The port on which the SSH server should listen. |
|
||||
| ssh.external-domain | OG_SSH_EXTERNAL_DOMAIN | none | Public domain for the Git SSH connection, if it has to be different from the HTTP one. If not set, uses the URL from the request. |
|
||||
| ssh.keygen-executable | OG_SSH_KEYGEN_EXECUTABLE | `ssh-keygen` | Path to the SSH key generation executable. |
|
||||
| github.client-key | OG_GITHUB_CLIENT_KEY | none | The client key for the GitHub OAuth application. |
|
||||
| github.secret | OG_GITHUB_SECRET | none | The secret for the GitHub OAuth application. |
|
||||
| gitlab.client-key | OG_GITLAB_CLIENT_KEY | none | The client key for the GitLab OAuth application. |
|
||||
| gitlab.secret | OG_GITLAB_SECRET | none | The secret for the GitLab OAuth application. |
|
||||
| gitlab.url | OG_GITLAB_URL | `https://gitlab.com/` | The URL of the GitLab instance. |
|
||||
| gitlab.name | OG_GITLAB_NAME | `GitLab` | The name of the GitLab instance. It is displayed in the OAuth login button. |
|
||||
| gitea.client-key | OG_GITEA_CLIENT_KEY | none | The client key for the Gitea OAuth application. |
|
||||
| gitea.secret | OG_GITEA_SECRET | none | The secret for the Gitea OAuth application. |
|
||||
| gitea.url | OG_GITEA_URL | `https://gitea.com/` | The URL of the Gitea instance. |
|
||||
| gitea.name | OG_GITEA_NAME | `Gitea` | The name of the Gitea instance. It is displayed in the OAuth login button. |
|
||||
| oidc.provider-name | OG_OIDC_PROVIDER_NAME | none | The name of the OIDC provider |
|
||||
| oidc.client-key | OG_OIDC_CLIENT_KEY | none | The client key for the OpenID application. |
|
||||
| oidc.secret | OG_OIDC_SECRET | none | The secret for the OpenID application. |
|
||||
| oidc.discovery-url | OG_OIDC_DISCOVERY_URL | none | Discovery endpoint of the OpenID provider. |
|
||||
| ldap.url | OG_LDAP_URL | none | URL of the LDAP instance; if not set, LDAP authentication is disabled |
|
||||
| ldap.bind-dn | OG_LDAP_BIND_DN | none | Bind DN to authenticate against the LDAP. e.g: cn=read-only-admin,dc=example,dc=com |
|
||||
| ldap.bind-credentials | OG_LDAP_BIND_CREDENTIALS | none | The password for the Bind DN. |
|
||||
| ldap.search-base | OG_LDAP_SEARCH_BASE | none | The Base DN to start search from. e.g: ou=People,dc=example,dc=com |
|
||||
| ldap.search-filter | OG_LDAP_SEARCH_FILTER | none | The filter to search against (the format string %s will be replaced with the username). e.g: (uid=%s) |
|
||||
| custom.name | OG_CUSTOM_NAME | none | The name of your instance, to be displayed in the tab title |
|
||||
| custom.logo | OG_CUSTOM_LOGO | none | Path to an image, relative to $opengist-home/custom. |
|
||||
| custom.favicon | OG_CUSTOM_FAVICON | none | Path to an image, relative to $opengist-home/custom. |
|
||||
| custom.static-links | OG_CUSTOM_STATIC_LINK_#_(PATH,NAME) | none | Path and name to custom links, more info [here](custom-links.md). |
|
||||
| YAML Config Key | Environment Variable | Default value | Description |
|
||||
|-------------------------|-------------------------------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| log-level | OG_LOG_LEVEL | `warn` | Set the log level to one of the following: `debug`, `info`, `warn`, `error`, `fatal`. |
|
||||
| log-output | OG_LOG_OUTPUT | `stdout,file` | Set the log output to one or more of the following: `stdout`, `file`. |
|
||||
| external-url | OG_EXTERNAL_URL | none | Public URL to access to Opengist. |
|
||||
| opengist-home | OG_OPENGIST_HOME | home directory | Path to the directory where Opengist stores its data. |
|
||||
| secret-key | OG_SECRET_KEY | randomized 32 bytes | Secret key used for session store & encrypt MFA data on database. |
|
||||
| db-uri | OG_DB_URI | `opengist.db` | URI of the database. |
|
||||
| index | OG_INDEX | `bleve` | Define the code indexer (either `bleve`, `meilisearch`, or empty for no index). |
|
||||
| index.meili.host | OG_MEILI_HOST | none | Set the host for the Meiliseach server. |
|
||||
| index.meili.api-key | OG_MEILI_API_KEY | none | Set the API key for the Meiliseach server. |
|
||||
| git.default-branch | OG_GIT_DEFAULT_BRANCH | none | Default branch name used by Opengist when initializing Git repositories. If not set, uses the Git default branch name. More info [here](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup#_new_default_branch) |
|
||||
| sqlite.journal-mode | OG_SQLITE_JOURNAL_MODE | `WAL` | Set the journal mode for SQLite. More info [here](https://www.sqlite.org/pragma.html#pragma_journal_mode) |
|
||||
| http.host | OG_HTTP_HOST | `0.0.0.0` | The host on which the HTTP server should bind. Use an IP address for network binding. Use a path for Unix socket binding (e.g. /run/opengist.sock) |
|
||||
| http.port | OG_HTTP_PORT | `6157` | The port on which the HTTP server should listen. |
|
||||
| http.git-enabled | OG_HTTP_GIT_ENABLED | `true` | Enable or disable git operations (clone, pull, push) via HTTP. (`true` or `false`) |
|
||||
| unix-socket-permissions | OG_UNIX_SOCKET_PERMISSIONS | `0666` | File permissions for Unix socket (octal format). |
|
||||
| metrics.enabled | OG_METRICS_ENABLED | `false` | Enable or disable Prometheus metrics endpoint at `/metrics` (`true` or `false`) |
|
||||
| ssh.git-enabled | OG_SSH_GIT_ENABLED | `true` | Enable or disable git operations (clone, pull, push) via SSH. (`true` or `false`) |
|
||||
| ssh.host | OG_SSH_HOST | `0.0.0.0` | The host on which the SSH server should bind. |
|
||||
| ssh.port | OG_SSH_PORT | `2222` | The port on which the SSH server should listen. |
|
||||
| ssh.external-domain | OG_SSH_EXTERNAL_DOMAIN | none | Public domain for the Git SSH connection, if it has to be different from the HTTP one. If not set, uses the URL from the request. |
|
||||
| ssh.keygen-executable | OG_SSH_KEYGEN_EXECUTABLE | `ssh-keygen` | Path to the SSH key generation executable. |
|
||||
| github.client-key | OG_GITHUB_CLIENT_KEY | none | The client key for the GitHub OAuth application. |
|
||||
| github.secret | OG_GITHUB_SECRET | none | The secret for the GitHub OAuth application. |
|
||||
| gitlab.client-key | OG_GITLAB_CLIENT_KEY | none | The client key for the GitLab OAuth application. |
|
||||
| gitlab.secret | OG_GITLAB_SECRET | none | The secret for the GitLab OAuth application. |
|
||||
| gitlab.url | OG_GITLAB_URL | `https://gitlab.com/` | The URL of the GitLab instance. |
|
||||
| gitlab.name | OG_GITLAB_NAME | `GitLab` | The name of the GitLab instance. It is displayed in the OAuth login button. |
|
||||
| gitea.client-key | OG_GITEA_CLIENT_KEY | none | The client key for the Gitea OAuth application. |
|
||||
| gitea.secret | OG_GITEA_SECRET | none | The secret for the Gitea OAuth application. |
|
||||
| gitea.url | OG_GITEA_URL | `https://gitea.com/` | The URL of the Gitea instance. |
|
||||
| gitea.name | OG_GITEA_NAME | `Gitea` | The name of the Gitea instance. It is displayed in the OAuth login button. |
|
||||
| oidc.provider-name | OG_OIDC_PROVIDER_NAME | none | The name of the OIDC provider |
|
||||
| oidc.client-key | OG_OIDC_CLIENT_KEY | none | The client key for the OpenID application. |
|
||||
| oidc.secret | OG_OIDC_SECRET | none | The secret for the OpenID application. |
|
||||
| oidc.discovery-url | OG_OIDC_DISCOVERY_URL | none | Discovery endpoint of the OpenID provider. |
|
||||
| ldap.url | OG_LDAP_URL | none | URL of the LDAP instance; if not set, LDAP authentication is disabled |
|
||||
| ldap.bind-dn | OG_LDAP_BIND_DN | none | Bind DN to authenticate against the LDAP. e.g: cn=read-only-admin,dc=example,dc=com |
|
||||
| ldap.bind-credentials | OG_LDAP_BIND_CREDENTIALS | none | The password for the Bind DN. |
|
||||
| ldap.search-base | OG_LDAP_SEARCH_BASE | none | The Base DN to start search from. e.g: ou=People,dc=example,dc=com |
|
||||
| ldap.search-filter | OG_LDAP_SEARCH_FILTER | none | The filter to search against (the format string %s will be replaced with the username). e.g: (uid=%s) |
|
||||
| custom.name | OG_CUSTOM_NAME | none | The name of your instance, to be displayed in the tab title |
|
||||
| custom.logo | OG_CUSTOM_LOGO | none | Path to an image, relative to $opengist-home/custom. |
|
||||
| custom.favicon | OG_CUSTOM_FAVICON | none | Path to an image, relative to $opengist-home/custom. |
|
||||
| custom.static-links | OG_CUSTOM_STATIC_LINK_#_(PATH,NAME) | none | Path and name to custom links, more info [here](custom-links.md). |
|
||||
|
||||
@@ -36,11 +36,12 @@ var CmdStart = cli.Command{
|
||||
|
||||
Initialize(ctx)
|
||||
|
||||
go server.NewServer(os.Getenv("OG_DEV") == "1", path.Join(config.GetHomeDir(), "sessions"), false).Start()
|
||||
server := server.NewServer(os.Getenv("OG_DEV") == "1", path.Join(config.GetHomeDir(), "sessions"), false)
|
||||
go server.Start()
|
||||
go ssh.Start()
|
||||
|
||||
<-stopCtx.Done()
|
||||
shutdown()
|
||||
shutdown(server)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -130,7 +131,7 @@ func Initialize(ctx *cli.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func shutdown() {
|
||||
func shutdown(server *server.Server) {
|
||||
log.Info().Msg("Shutting down database...")
|
||||
if err := db.Close(); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to close database")
|
||||
@@ -141,6 +142,8 @@ func shutdown() {
|
||||
index.Close()
|
||||
}
|
||||
|
||||
server.Stop()
|
||||
|
||||
log.Info().Msg("Shutdown complete")
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,8 @@ type config struct {
|
||||
HttpPort string `yaml:"http.port" env:"OG_HTTP_PORT"`
|
||||
HttpGit bool `yaml:"http.git-enabled" env:"OG_HTTP_GIT_ENABLED"`
|
||||
|
||||
UnixSocketPermissions string `yaml:"unix-socket-permissions" env:"OG_UNIX_SOCKET_PERMISSIONS"`
|
||||
|
||||
SshGit bool `yaml:"ssh.git-enabled" env:"OG_SSH_GIT_ENABLED"`
|
||||
SshHost string `yaml:"ssh.host" env:"OG_SSH_HOST"`
|
||||
SshPort string `yaml:"ssh.port" env:"OG_SSH_PORT"`
|
||||
@@ -113,6 +115,8 @@ func configWithDefaults() (*config, error) {
|
||||
c.HttpPort = "6157"
|
||||
c.HttpGit = true
|
||||
|
||||
c.UnixSocketPermissions = "0666"
|
||||
|
||||
c.SshGit = true
|
||||
c.SshHost = "0.0.0.0"
|
||||
c.SshPort = "2222"
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/thomiceli/opengist/internal/validator"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -45,7 +51,19 @@ func NewServer(isDev bool, sessionsPath string, ignoreCsrf bool) *Server {
|
||||
return s
|
||||
}
|
||||
|
||||
func isSocketPath(host string) bool {
|
||||
return strings.Contains(host, "/") || strings.Contains(host, "\\")
|
||||
}
|
||||
|
||||
func (s *Server) Start() {
|
||||
if isSocketPath(config.C.HttpHost) {
|
||||
s.startUnixSocket()
|
||||
} else {
|
||||
s.startHTTP()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) startHTTP() {
|
||||
addr := config.C.HttpHost + ":" + config.C.HttpPort
|
||||
|
||||
log.Info().Msg("Starting HTTP server on http://" + addr)
|
||||
@@ -54,12 +72,106 @@ func (s *Server) Start() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) startUnixSocket() {
|
||||
socketPath := config.C.HttpHost
|
||||
if socketPath == "" {
|
||||
socketPath = "/tmp/opengist.sock"
|
||||
}
|
||||
|
||||
if dir := filepath.Dir(socketPath); dir != "." {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
log.Warn().Err(err).Str("dir", dir).Msg("Failed to create socket directory")
|
||||
}
|
||||
}
|
||||
if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) {
|
||||
log.Warn().Err(err).Str("socket", socketPath).Msg("Failed to remove existing socket file")
|
||||
}
|
||||
|
||||
pidPath := strings.TrimSuffix(socketPath, filepath.Ext(socketPath)) + ".pid"
|
||||
if err := s.createPidFile(pidPath); err != nil {
|
||||
log.Warn().Err(err).Str("pid-file", pidPath).Msg("Failed to create PID file")
|
||||
}
|
||||
|
||||
listener, err := net.Listen("unix", socketPath)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to start Unix socket server")
|
||||
}
|
||||
s.echo.Listener = listener
|
||||
|
||||
if config.C.UnixSocketPermissions != "" {
|
||||
if perm, err := strconv.ParseUint(config.C.UnixSocketPermissions, 8, 32); err == nil {
|
||||
if err := os.Chmod(socketPath, os.FileMode(perm)); err != nil {
|
||||
log.Warn().Err(err).Str("socket", socketPath).Str("permissions", config.C.UnixSocketPermissions).Msg("Failed to set socket permissions")
|
||||
}
|
||||
} else {
|
||||
log.Warn().Err(err).Str("permissions", config.C.UnixSocketPermissions).Msg("Invalid socket permissions format")
|
||||
}
|
||||
}
|
||||
|
||||
log.Info().Str("socket", socketPath).Msg("Starting Unix socket server")
|
||||
log.Info().Str("pid-file", pidPath).Msg("PID file created")
|
||||
server := new(http.Server)
|
||||
if err := s.echo.StartServer(server); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatal().Err(err).Msg("Failed to start Unix socket server")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Stop() {
|
||||
if isSocketPath(config.C.HttpHost) {
|
||||
s.stopUnixSocket()
|
||||
} else {
|
||||
s.stopHTTP()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) stopHTTP() {
|
||||
log.Info().Msg("Stopping HTTP server...")
|
||||
if err := s.echo.Close(); err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to stop HTTP server")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) stopUnixSocket() {
|
||||
log.Info().Msg("Stopping Unix socket server...")
|
||||
|
||||
var socketPath string
|
||||
if s.echo.Listener != nil {
|
||||
if unixListener, ok := s.echo.Listener.(*net.UnixListener); ok {
|
||||
socketPath = unixListener.Addr().String()
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.echo.Close(); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to stop Unix socket server")
|
||||
}
|
||||
|
||||
if socketPath != "" {
|
||||
if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) {
|
||||
log.Error().Err(err).Str("socket", socketPath).Msg("Failed to remove socket file")
|
||||
} else {
|
||||
log.Info().Str("socket", socketPath).Msg("Socket file removed")
|
||||
}
|
||||
|
||||
pidPath := strings.TrimSuffix(socketPath, filepath.Ext(socketPath)) + ".pid"
|
||||
if err := os.Remove(pidPath); err != nil && !os.IsNotExist(err) {
|
||||
log.Error().Err(err).Str("pid-file", pidPath).Msg("Failed to remove PID file")
|
||||
} else {
|
||||
log.Info().Str("pid-file", pidPath).Msg("PID file removed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) createPidFile(pidPath string) error {
|
||||
pid := os.Getpid()
|
||||
pidContent := fmt.Sprintf("%d\n", pid)
|
||||
|
||||
if err := os.WriteFile(pidPath, []byte(pidContent), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.echo.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
#!/bin/sh
|
||||
set -euo pipefail
|
||||
|
||||
# Start background processes
|
||||
make watch_frontend &
|
||||
make watch_backend &
|
||||
FRONTEND_PID=$!
|
||||
|
||||
trap 'kill $(jobs -p)' EXIT
|
||||
make watch_backend &
|
||||
BACKEND_PID=$!
|
||||
|
||||
# Function for graceful shutdown
|
||||
cleanup() {
|
||||
echo "Shutting down gracefully..."
|
||||
kill -TERM $FRONTEND_PID $BACKEND_PID 2>/dev/null || true
|
||||
wait $FRONTEND_PID $BACKEND_PID 2>/dev/null || true
|
||||
echo "Shutdown complete"
|
||||
}
|
||||
|
||||
# Set up trap for graceful shutdown
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
# Wait for background processes
|
||||
wait
|
||||
Reference in New Issue
Block a user