diff options
Diffstat (limited to '')
-rw-r--r-- | forged/internal/ansiec/doc.go (renamed from forged/internal/ansiec/ansiec.go) | 0 | ||||
-rw-r--r-- | forged/internal/bare/doc.go (renamed from forged/internal/bare/package.go) | 0 | ||||
-rw-r--r-- | forged/internal/config/config.go | 34 | ||||
-rw-r--r-- | forged/internal/database/database.go | 4 | ||||
-rw-r--r-- | forged/internal/git2c/client.go | 1 | ||||
-rw-r--r-- | forged/internal/git2c/doc.go | 2 | ||||
-rw-r--r-- | forged/internal/hooki/hooki.go | 45 | ||||
-rw-r--r-- | forged/internal/irc/bot.go | 1 | ||||
-rw-r--r-- | forged/internal/irc/doc.go | 2 | ||||
-rw-r--r-- | forged/internal/lmtp/config.go | 53 | ||||
-rw-r--r-- | forged/internal/misc/misc.go | 13 | ||||
-rw-r--r-- | forged/internal/misc/slices.go | 17 | ||||
-rw-r--r-- | forged/internal/misc/usock.go | 23 | ||||
-rw-r--r-- | forged/internal/server/server.go | 48 | ||||
-rw-r--r-- | forged/main.go | 5 |
15 files changed, 211 insertions, 37 deletions
diff --git a/forged/internal/ansiec/ansiec.go b/forged/internal/ansiec/doc.go index 542c564..542c564 100644 --- a/forged/internal/ansiec/ansiec.go +++ b/forged/internal/ansiec/doc.go diff --git a/forged/internal/bare/package.go b/forged/internal/bare/doc.go index 2f12f55..2f12f55 100644 --- a/forged/internal/bare/package.go +++ b/forged/internal/bare/doc.go diff --git a/forged/internal/config/config.go b/forged/internal/config/config.go index d3eb8e1..0166390 100644 --- a/forged/internal/config/config.go +++ b/forged/internal/config/config.go @@ -1,8 +1,14 @@ package config import ( + "bufio" + "log/slog" + "os" + "go.lindenii.runxiyu.org/forge/forged/internal/database" + "go.lindenii.runxiyu.org/forge/forged/internal/hooki" "go.lindenii.runxiyu.org/forge/forged/internal/irc" + "go.lindenii.runxiyu.org/forge/forged/internal/scfg" ) type Config struct { @@ -16,11 +22,8 @@ type Config struct { IdleTimeout uint32 `scfg:"idle_timeout"` ReverseProxy bool `scfg:"reverse_proxy"` } `scfg:"http"` - Hooks struct { - Socket string `scfg:"socket"` - Execs string `scfg:"execs"` - } `scfg:"hooks"` - LMTP struct { + Hooks hooki.Config `scfg:"hooks"` + LMTP struct { Socket string `scfg:"socket"` Domain string `scfg:"domain"` MaxSize int64 `scfg:"max_size"` @@ -42,9 +45,28 @@ type Config struct { General struct { Title string `scfg:"title"` } `scfg:"general"` - DB database.Config `scfg:"db"` + DB database.Config `scfg:"db"` Pprof struct { Net string `scfg:"net"` Addr string `scfg:"addr"` } `scfg:"pprof"` } + +func Open(path string) (config Config, err error) { + var configFile *os.File + + if configFile, err = os.Open(path); err != nil { + return config, err + } + defer configFile.Close() + + decoder := scfg.NewDecoder(bufio.NewReader(configFile)) + if err = decoder.Decode(&config); err != nil { + return config, err + } + for _, u := range decoder.UnknownDirectives() { + slog.Warn("unknown configuration directive", "directive", u) + } + + return config, err +} diff --git a/forged/internal/database/database.go b/forged/internal/database/database.go index 6674fe6..f353fe8 100644 --- a/forged/internal/database/database.go +++ b/forged/internal/database/database.go @@ -19,8 +19,8 @@ type Database struct { // Open opens a new database connection pool using the provided connection // string. It returns a Database instance and an error if any occurs. // It is run indefinitely in the background. -func Open(connString string) (Database, error) { - db, err := pgxpool.New(context.Background(), connString) +func Open(ctx context.Context, config Config) (Database, error) { + db, err := pgxpool.New(ctx, config.Conn) return Database{db}, err } diff --git a/forged/internal/git2c/client.go b/forged/internal/git2c/client.go index ed9390c..d8dc2ea 100644 --- a/forged/internal/git2c/client.go +++ b/forged/internal/git2c/client.go @@ -1,7 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> -// Package git2c provides routines to interact with the git2d backend daemon. package git2c import ( diff --git a/forged/internal/git2c/doc.go b/forged/internal/git2c/doc.go new file mode 100644 index 0000000..e14dae0 --- /dev/null +++ b/forged/internal/git2c/doc.go @@ -0,0 +1,2 @@ +// Package git2c provides routines to interact with the git2d backend daemon. +package git2c diff --git a/forged/internal/hooki/hooki.go b/forged/internal/hooki/hooki.go index ae26846..8e75bae 100644 --- a/forged/internal/hooki/hooki.go +++ b/forged/internal/hooki/hooki.go @@ -1,13 +1,26 @@ package hooki import ( - "go.lindenii.runxiyu.org/forge/forged/internal/cmap" + "fmt" + "net" + "github.com/gliderlabs/ssh" + "go.lindenii.runxiyu.org/forge/forged/internal/cmap" + "go.lindenii.runxiyu.org/forge/forged/internal/misc" ) -type Pool cmap.Map[string, hookinfo] +type Pool struct { + hookMap cmap.Map[string, hookInfo] + socketPath string + executablesPath string +} -type hookinfo struct { +type Config struct { + Socket string `scfg:"socket"` + Execs string `scfg:"execs"` +} + +type hookInfo struct { session ssh.Session pubkey string directAccess bool @@ -19,3 +32,29 @@ type hookinfo struct { repoName string contribReq string } + +func New(config Config) (pool Pool) { + pool.socketPath = config.Socket + pool.executablesPath = config.Execs + return +} + +func (pool *Pool) Run() error { + listener, _, err := misc.ListenUnixSocket(pool.socketPath) + if err != nil { + return fmt.Errorf("listen unix socket for hooks: %w", err) + } + + for { + conn, err := listener.Accept() + if err != nil { + return fmt.Errorf("accept conn: %w", err) + } + + go pool.handleConn(conn) + } +} + +func (pool *Pool) handleConn(conn net.Conn) { + panic("TODO: handle hook connection") +} diff --git a/forged/internal/irc/bot.go b/forged/internal/irc/bot.go index 1c6d32f..3ebac89 100644 --- a/forged/internal/irc/bot.go +++ b/forged/internal/irc/bot.go @@ -1,7 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> -// Package irc provides basic IRC bot functionality. package irc import ( diff --git a/forged/internal/irc/doc.go b/forged/internal/irc/doc.go new file mode 100644 index 0000000..dcfca82 --- /dev/null +++ b/forged/internal/irc/doc.go @@ -0,0 +1,2 @@ +// Package irc provides basic IRC bot functionality. +package irc diff --git a/forged/internal/lmtp/config.go b/forged/internal/lmtp/config.go new file mode 100644 index 0000000..ce22740 --- /dev/null +++ b/forged/internal/lmtp/config.go @@ -0,0 +1,53 @@ +package lmtp + +import ( + "fmt" + "net" + + "go.lindenii.runxiyu.org/forge/forged/internal/misc" +) + +type Pool struct { + socket string + domain string + maxSize int64 + writeTimeout uint32 + readTimeout uint32 +} + +type Config struct { + Socket string `scfg:"socket"` + Domain string `scfg:"domain"` + MaxSize int64 `scfg:"max_size"` + WriteTimeout uint32 `scfg:"write_timeout"` + ReadTimeout uint32 `scfg:"read_timeout"` +} + +func New(config Config) (pool Pool) { + pool.socket = config.Socket + pool.domain = config.Domain + pool.maxSize = config.MaxSize + pool.writeTimeout = config.WriteTimeout + pool.readTimeout = config.ReadTimeout + return pool +} + +func (pool *Pool) Run() error { + listener, _, err := misc.ListenUnixSocket(pool.socket) + if err != nil { + return fmt.Errorf("listen unix socket for LMTP: %w", err) + } + + for { + conn, err := listener.Accept() + if err != nil { + return fmt.Errorf("accept conn: %w", err) + } + + go pool.handleConn(conn) + } +} + +func (pool *Pool) handleConn(conn net.Conn) { + panic("TODO: handle LMTP connection") +} diff --git a/forged/internal/misc/misc.go b/forged/internal/misc/misc.go index 398020a..e9e10ab 100644 --- a/forged/internal/misc/misc.go +++ b/forged/internal/misc/misc.go @@ -3,16 +3,3 @@ // Package misc provides miscellaneous functions and other definitions. package misc - -import "strings" - -// sliceContainsNewlines returns true if and only if the given slice contains -// one or more strings that contains newlines. -func SliceContainsNewlines(s []string) bool { - for _, v := range s { - if strings.Contains(v, "\n") { - return true - } - } - return false -} diff --git a/forged/internal/misc/slices.go b/forged/internal/misc/slices.go new file mode 100644 index 0000000..3ad0211 --- /dev/null +++ b/forged/internal/misc/slices.go @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> + +package misc + +import "strings" + +// sliceContainsNewlines returns true if and only if the given slice contains +// one or more strings that contains newlines. +func SliceContainsNewlines(s []string) bool { + for _, v := range s { + if strings.Contains(v, "\n") { + return true + } + } + return false +} diff --git a/forged/internal/misc/usock.go b/forged/internal/misc/usock.go new file mode 100644 index 0000000..357fa43 --- /dev/null +++ b/forged/internal/misc/usock.go @@ -0,0 +1,23 @@ +package misc + +import ( + "errors" + "fmt" + "net" + "syscall" +) + +func ListenUnixSocket(path string) (listener net.Listener, replaced bool, err error) { + listener, err = net.Listen("unix", path) + if errors.Is(err, syscall.EADDRINUSE) { + replaced = true + if unlinkErr := syscall.Unlink(path); unlinkErr != nil { + return listener, false, fmt.Errorf("remove existing socket %q: %w", path, unlinkErr) + } + listener, err = net.Listen("unix", path) + } + if err != nil { + return listener, replaced, fmt.Errorf("listen on unix socket %q: %w", path, err) + } + return listener, replaced, nil +} diff --git a/forged/internal/server/server.go b/forged/internal/server/server.go index ec875f1..03570c3 100644 --- a/forged/internal/server/server.go +++ b/forged/internal/server/server.go @@ -4,29 +4,59 @@ import ( "context" "fmt" "html/template" + "log" "go.lindenii.runxiyu.org/forge/forged/internal/config" "go.lindenii.runxiyu.org/forge/forged/internal/database" "go.lindenii.runxiyu.org/forge/forged/internal/hooki" + "go.lindenii.runxiyu.org/forge/forged/internal/lmtp" "go.lindenii.runxiyu.org/forge/forged/internal/store" ) type Server struct { config config.Config - database database.Database - stores *store.Set - hookis *hooki.Pool + database database.Database + stores *store.Set + hookPool hooki.Pool + lmtpPool lmtp.Pool templates *template.Template } -func New(ctx context.Context, config config.Config) (*Server, error) { - database, err := database.Open(ctx, config.DB) +func New(ctx context.Context, configPath string) (server *Server, err error) { + server = &Server{} + + server.config, err = config.Open(configPath) if err != nil { - return nil, fmt.Errorf("open database: %w", err) + return server, fmt.Errorf("open config: %w", err) } - return &Server{ - database: database, - }, nil + // TODO: Should this belong here, or in Run()? + server.database, err = database.Open(ctx, server.config.DB) + if err != nil { + return server, fmt.Errorf("open database: %w", err) + } + + return server, nil +} + +func (s *Server) Run() error { + // TODO: Not running git2d because it should be run separately. + // This needs to be documented somewhere, hence a TODO here for now. + + go func() { + s.hookPool = hooki.New(s.config.Hooks) + if err := s.hookPool.Run(); err != nil { + log.Fatalf("run hook pool: %v", err) + } + }() + + go func() { + s.lmtpPool = lmtp.New(s.config.LMTP) + if err := s.lmtpPool.Run(); err != nil { + log.Fatalf("run LMTP pool: %v", err) + } + }() + + return nil } diff --git a/forged/main.go b/forged/main.go index fde15d1..e9609f3 100644 --- a/forged/main.go +++ b/forged/main.go @@ -5,9 +5,10 @@ package main import ( + "context" "flag" - "go.lindenii.runxiyu.org/forge/forged/internal/unsorted" + "go.lindenii.runxiyu.org/forge/forged/internal/server" ) func main() { @@ -18,7 +19,7 @@ func main() { ) flag.Parse() - s, err := unsorted.NewServer(*configPath) + s, err := server.New(context.Background(), *configPath) if err != nil { panic(err) } |