Hi… I am well aware that this diff view is very suboptimal. It will be fixed when the refactored server comes along!
Hooks, git2d: Simplify deployment logic
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
package forge
import (
"io"
"io/fs"
"os"
)
func (s *Server) deployGit2D() (err error) {
var srcFD fs.File
var dstFD *os.File
if srcFD, err = embeddedResourcesFS.Open("git2d/git2d"); err != nil {
return err
}
defer srcFD.Close()
if dstFD, err = os.OpenFile(s.config.Git.DaemonPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755); err != nil {
return err
}
defer dstFD.Close()
_, err = io.Copy(dstFD, srcFD)
return err
}
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
package forge
import (
"errors"
"io"
"io/fs"
"os"
"path/filepath"
)
// deployHooks deploys the git hooks client to the filesystem. The git hooks
// client is expected to be embedded in resourcesFS and must be pre-compiled
// during the build process; see the Makefile.
func (s *Server) deployHooks() (err error) {
err = func() (err error) {
var srcFD fs.File
var dstFD *os.File
if srcFD, err = embeddedResourcesFS.Open("hookc/hookc"); err != nil {
return err
}
defer srcFD.Close()
if dstFD, err = os.OpenFile(filepath.Join(s.config.Hooks.Execs, "hookc"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755); err != nil {
return err
}
defer dstFD.Close()
if _, err = io.Copy(dstFD, srcFD); err != nil {
return err
}
return nil
}()
if err != nil {
return err
}
// Go's embed filesystems do not store permissions; but in any case,
// they would need to be 0o755:
if err = os.Chmod(filepath.Join(s.config.Hooks.Execs, "hookc"), 0o755); err != nil {
return err
}
for _, hookName := range []string{
"pre-receive",
} {
if err = os.Symlink(filepath.Join(s.config.Hooks.Execs, "hookc"), filepath.Join(s.config.Hooks.Execs, hookName)); err != nil {
if !errors.Is(err, fs.ErrExist) {
return err
}
// TODO: Maybe check if it points to the right place?
}
}
return nil
}
package misc
import (
"io"
"io/fs"
"os"
)
func DeployBinary(src fs.File, dst string) (err error) {
var dstFile *os.File
if dstFile, err = os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755); err != nil {
return err
}
defer dstFile.Close()
_, err = io.Copy(dstFile, src)
return err
}
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> package misc import "strings"
func FirstOrPanic[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
// 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
}
package misc
func FirstOrPanic[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
func NoneOrPanic(err error) {
if err != nil {
panic(err)
}
}
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> package forge import ( "errors" "html/template" "io/fs" "log" "log/slog" "net" "net/http" "os" "os/exec"
"path/filepath"
"syscall" "time" "go.lindenii.runxiyu.org/forge/internal/database"
"go.lindenii.runxiyu.org/forge/internal/misc"
"go.lindenii.runxiyu.org/lindenii-common/cmap"
goSSH "golang.org/x/crypto/ssh"
)
type Server struct {
config Config
database database.Database
sourceHandler http.Handler
staticHandler http.Handler
ircSendBuffered chan string
ircSendDirectChan chan errorBack[string]
// globalData is passed as "global" when rendering HTML templates.
globalData map[string]any
serverPubkeyString string
serverPubkeyFP string
serverPubkey goSSH.PublicKey
// packPasses contains hook cookies mapped to their packPass.
packPasses cmap.Map[string, packPass]
templates *template.Template
}
func (s *Server) Setup() {
s.sourceHandler = http.StripPrefix(
"/-/source/",
http.FileServer(http.FS(embeddedSourceFS)),
)
staticFS, err := fs.Sub(embeddedResourcesFS, "static")
if err != nil {
panic(err)
}
s.staticHandler = http.StripPrefix("/-/static/", http.FileServer(http.FS(staticFS)))
s.globalData = map[string]any{
"server_public_key_string": &s.serverPubkeyString,
"server_public_key_fingerprint": &s.serverPubkeyFP,
"forge_version": version,
// Some other ones are populated after config parsing
}
}
func (s *Server) Run() {
if err := s.deployHooks(); err != nil {
slog.Error("deploying hooks", "error", err)
os.Exit(1)
}
if err := s.loadTemplates(); err != nil {
slog.Error("loading templates", "error", err)
os.Exit(1)
}
if err := s.deployGit2D(); err != nil {
slog.Error("deploying git2d", "error", err)
os.Exit(1)
}
misc.NoneOrPanic(s.loadTemplates())
misc.NoneOrPanic(misc.DeployBinary(misc.FirstOrPanic(embeddedResourcesFS.Open("git2d/git2d")), s.config.Git.DaemonPath))
misc.NoneOrPanic(misc.DeployBinary(misc.FirstOrPanic(embeddedResourcesFS.Open("hookc/hookc")), filepath.Join(s.config.Hooks.Execs, "pre-receive")))
misc.NoneOrPanic(os.Chmod(filepath.Join(s.config.Hooks.Execs, "pre-receive"), 0o755))
// Launch Git2D
go func() {
cmd := exec.Command(s.config.Git.DaemonPath, s.config.Git.Socket) //#nosec G204
cmd.Stderr = log.Writer()
cmd.Stdout = log.Writer()
if err := cmd.Run(); err != nil {
panic(err)
}
}()
// UNIX socket listener for hooks
{
hooksListener, err := net.Listen("unix", s.config.Hooks.Socket)
if errors.Is(err, syscall.EADDRINUSE) {
slog.Warn("removing existing socket", "path", s.config.Hooks.Socket)
if err = syscall.Unlink(s.config.Hooks.Socket); err != nil {
slog.Error("removing existing socket", "path", s.config.Hooks.Socket, "error", err)
os.Exit(1)
}
if hooksListener, err = net.Listen("unix", s.config.Hooks.Socket); err != nil {
slog.Error("listening hooks", "error", err)
os.Exit(1)
}
} else if err != nil {
slog.Error("listening hooks", "error", err)
os.Exit(1)
}
slog.Info("listening hooks on unix", "path", s.config.Hooks.Socket)
go func() {
if err = s.serveGitHooks(hooksListener); err != nil {
slog.Error("serving hooks", "error", err)
os.Exit(1)
}
}()
}
// UNIX socket listener for LMTP
{
lmtpListener, err := net.Listen("unix", s.config.LMTP.Socket)
if errors.Is(err, syscall.EADDRINUSE) {
slog.Warn("removing existing socket", "path", s.config.LMTP.Socket)
if err = syscall.Unlink(s.config.LMTP.Socket); err != nil {
slog.Error("removing existing socket", "path", s.config.LMTP.Socket, "error", err)
os.Exit(1)
}
if lmtpListener, err = net.Listen("unix", s.config.LMTP.Socket); err != nil {
slog.Error("listening LMTP", "error", err)
os.Exit(1)
}
} else if err != nil {
slog.Error("listening LMTP", "error", err)
os.Exit(1)
}
slog.Info("listening LMTP on unix", "path", s.config.LMTP.Socket)
go func() {
if err = s.serveLMTP(lmtpListener); err != nil {
slog.Error("serving LMTP", "error", err)
os.Exit(1)
}
}()
}
// SSH listener
{
sshListener, err := net.Listen(s.config.SSH.Net, s.config.SSH.Addr)
if errors.Is(err, syscall.EADDRINUSE) && s.config.SSH.Net == "unix" {
slog.Warn("removing existing socket", "path", s.config.SSH.Addr)
if err = syscall.Unlink(s.config.SSH.Addr); err != nil {
slog.Error("removing existing socket", "path", s.config.SSH.Addr, "error", err)
os.Exit(1)
}
if sshListener, err = net.Listen(s.config.SSH.Net, s.config.SSH.Addr); err != nil {
slog.Error("listening SSH", "error", err)
os.Exit(1)
}
} else if err != nil {
slog.Error("listening SSH", "error", err)
os.Exit(1)
}
slog.Info("listening SSH on", "net", s.config.SSH.Net, "addr", s.config.SSH.Addr)
go func() {
if err = s.serveSSH(sshListener); err != nil {
slog.Error("serving SSH", "error", err)
os.Exit(1)
}
}()
}
// HTTP listener
{
httpListener, err := net.Listen(s.config.HTTP.Net, s.config.HTTP.Addr)
if errors.Is(err, syscall.EADDRINUSE) && s.config.HTTP.Net == "unix" {
slog.Warn("removing existing socket", "path", s.config.HTTP.Addr)
if err = syscall.Unlink(s.config.HTTP.Addr); err != nil {
slog.Error("removing existing socket", "path", s.config.HTTP.Addr, "error", err)
os.Exit(1)
}
if httpListener, err = net.Listen(s.config.HTTP.Net, s.config.HTTP.Addr); err != nil {
slog.Error("listening HTTP", "error", err)
os.Exit(1)
}
} else if err != nil {
slog.Error("listening HTTP", "error", err)
os.Exit(1)
}
server := http.Server{
Handler: s,
ReadTimeout: time.Duration(s.config.HTTP.ReadTimeout) * time.Second,
WriteTimeout: time.Duration(s.config.HTTP.ReadTimeout) * time.Second,
IdleTimeout: time.Duration(s.config.HTTP.ReadTimeout) * time.Second,
} //exhaustruct:ignore
slog.Info("listening HTTP on", "net", s.config.HTTP.Net, "addr", s.config.HTTP.Addr)
go func() {
if err = server.Serve(httpListener); err != nil && !errors.Is(err, http.ErrServerClosed) {
slog.Error("serving HTTP", "error", err)
os.Exit(1)
}
}()
}
// IRC bot
go s.ircBotLoop()
select {}
}