1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
package main
import (
"errors"
"flag"
"log"
"net"
"net/http"
"os/exec"
"syscall"
"time"
"go.lindenii.runxiyu.org/lindenii-common/clog"
)
func main() {
configPath := flag.String(
"config",
"/etc/lindenii/forge.scfg",
"path to configuration file",
)
flag.Parse()
if err := loadConfig(*configPath); err != nil {
clog.Fatal(1, "Loading configuration: "+err.Error())
}
if err := deployHooks(); err != nil {
clog.Fatal(1, "Deploying hooks to filesystem: "+err.Error())
}
if err := loadTemplates(); err != nil {
clog.Fatal(1, "Loading templates: "+err.Error())
}
if err := deployGit2D(); err != nil {
clog.Fatal(1, "Deploying git2d: "+err.Error())
}
// Launch Git2D
go func() {
cmd := exec.Command(config.Git.DaemonPath, 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", config.Hooks.Socket)
if errors.Is(err, syscall.EADDRINUSE) {
clog.Warn("Removing existing socket " + config.Hooks.Socket)
if err = syscall.Unlink(config.Hooks.Socket); err != nil {
clog.Fatal(1, "Removing existing socket: "+err.Error())
}
if hooksListener, err = net.Listen("unix", config.Hooks.Socket); err != nil {
clog.Fatal(1, "Listening hooks: "+err.Error())
}
} else if err != nil {
clog.Fatal(1, "Listening hooks: "+err.Error())
}
clog.Info("Listening hooks on unix " + config.Hooks.Socket)
go func() {
if err = serveGitHooks(hooksListener); err != nil {
clog.Fatal(1, "Serving hooks: "+err.Error())
}
}()
}
// UNIX socket listener for LMTP
{
lmtpListener, err := net.Listen("unix", config.LMTP.Socket)
if errors.Is(err, syscall.EADDRINUSE) {
clog.Warn("Removing existing socket " + config.LMTP.Socket)
if err = syscall.Unlink(config.LMTP.Socket); err != nil {
clog.Fatal(1, "Removing existing socket: "+err.Error())
}
if lmtpListener, err = net.Listen("unix", config.LMTP.Socket); err != nil {
clog.Fatal(1, "Listening LMTP: "+err.Error())
}
} else if err != nil {
clog.Fatal(1, "Listening LMTP: "+err.Error())
}
clog.Info("Listening LMTP on unix " + config.LMTP.Socket)
go func() {
if err = serveLMTP(lmtpListener); err != nil {
clog.Fatal(1, "Serving LMTP: "+err.Error())
}
}()
}
// SSH listener
{
sshListener, err := net.Listen(config.SSH.Net, config.SSH.Addr)
if errors.Is(err, syscall.EADDRINUSE) && config.SSH.Net == "unix" {
clog.Warn("Removing existing socket " + config.SSH.Addr)
if err = syscall.Unlink(config.SSH.Addr); err != nil {
clog.Fatal(1, "Removing existing socket: "+err.Error())
}
if sshListener, err = net.Listen(config.SSH.Net, config.SSH.Addr); err != nil {
clog.Fatal(1, "Listening SSH: "+err.Error())
}
} else if err != nil {
clog.Fatal(1, "Listening SSH: "+err.Error())
}
clog.Info("Listening SSH on " + config.SSH.Net + " " + config.SSH.Addr)
go func() {
if err = serveSSH(sshListener); err != nil {
clog.Fatal(1, "Serving SSH: "+err.Error())
}
}()
}
// HTTP listener
{
httpListener, err := net.Listen(config.HTTP.Net, config.HTTP.Addr)
if errors.Is(err, syscall.EADDRINUSE) && config.HTTP.Net == "unix" {
clog.Warn("Removing existing socket " + config.HTTP.Addr)
if err = syscall.Unlink(config.HTTP.Addr); err != nil {
clog.Fatal(1, "Removing existing socket: "+err.Error())
}
if httpListener, err = net.Listen(config.HTTP.Net, config.HTTP.Addr); err != nil {
clog.Fatal(1, "Listening HTTP: "+err.Error())
}
} else if err != nil {
clog.Fatal(1, "Listening HTTP: "+err.Error())
}
server := http.Server{
Handler: &forgeHTTPRouter{},
ReadTimeout: time.Duration(config.HTTP.ReadTimeout) * time.Second,
WriteTimeout: time.Duration(config.HTTP.ReadTimeout) * time.Second,
IdleTimeout: time.Duration(config.HTTP.ReadTimeout) * time.Second,
} //exhaustruct:ignore
clog.Info("Listening HTTP on " + config.HTTP.Net + " " + config.HTTP.Addr)
go func() {
if err = server.Serve(httpListener); err != nil && !errors.Is(err, http.ErrServerClosed) {
clog.Fatal(1, "Serving HTTP: "+err.Error())
}
}()
}
// IRC bot
go ircBotLoop()
select {}
}
|