Lindenii Project Forge
Refactoring
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 { HTTP struct { Net string `scfg:"net"` Addr string `scfg:"addr"` CookieExpiry int `scfg:"cookie_expiry"` Root string `scfg:"root"` ReadTimeout uint32 `scfg:"read_timeout"` WriteTimeout uint32 `scfg:"write_timeout"` 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"` WriteTimeout uint32 `scfg:"write_timeout"` ReadTimeout uint32 `scfg:"read_timeout"` } `scfg:"lmtp"` Git struct { RepoDir string `scfg:"repo_dir"` Socket string `scfg:"socket"` DaemonPath string `scfg:"daemon_path"` } `scfg:"git"` SSH struct { Net string `scfg:"net"` Addr string `scfg:"addr"` Key string `scfg:"key"` Root string `scfg:"root"` } `scfg:"ssh"` IRC irc.Config `scfg:"irc"` 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 }
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> // Package database provides stubs and wrappers for databases. package database import ( "context" "github.com/jackc/pgx/v5/pgxpool" ) // Database is a wrapper around pgxpool.Pool to provide a common interface for // other packages in the forge. type Database struct { *pgxpool.Pool } // 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 } type Config struct { Type string `scfg:"type"` Conn string `scfg:"conn"` }
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 Config struct { Socket string `scfg:"socket"` Execs string `scfg:"execs"` }
type hookinfo struct {
type hookInfo struct {
session ssh.Session pubkey string directAccess bool repoPath string userID int userType string repoID int groupPath []string 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") }
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") }
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 }
package server 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
}
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> // The main entry point to the Lindenii Forge daemon. package main import (
"context"
"flag"
"go.lindenii.runxiyu.org/forge/forged/internal/unsorted"
"go.lindenii.runxiyu.org/forge/forged/internal/server"
) func main() { configPath := flag.String( "config", "/etc/lindenii/forge.scfg", "path to configuration file", ) flag.Parse()
s, err := unsorted.NewServer(*configPath)
s, err := server.New(context.Background(), *configPath)
if err != nil { panic(err) } panic(s.Run()) }