aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRunxi Yu <me@runxiyu.org>2025-04-05 20:52:04 +0800
committerRunxi Yu <me@runxiyu.org>2025-04-05 20:52:04 +0800
commitb4b0d966340ad9c892f8b8912eebc6118eed7482 (patch)
tree0512a6dd5cfc21fbd91a07d31f972aa2a1cf844d
parentgit2c: git2c.go -> client.go, a more sensible name (diff)
downloadforge-b4b0d966340ad9c892f8b8912eebc6118eed7482.tar.gz
forge-b4b0d966340ad9c892f8b8912eebc6118eed7482.tar.zst
forge-b4b0d966340ad9c892f8b8912eebc6118eed7482.zip
Use cmd/forge for the entry point
-rw-r--r--Makefile2
-rw-r--r--acl.go6
-rw-r--r--cmd/forge/main.go32
-rw-r--r--config.go14
-rw-r--r--database.go6
-rw-r--r--fedauth.go6
-rw-r--r--git2d_deploy.go6
-rw-r--r--git_format_patch.go2
-rw-r--r--git_hooks_deploy.go10
-rw-r--r--git_hooks_handle_linux.go16
-rw-r--r--git_hooks_handle_other.go2
-rw-r--r--git_init.go6
-rw-r--r--git_misc.go6
-rw-r--r--git_plumbing.go2
-rw-r--r--git_ref.go2
-rw-r--r--http_auth.go6
-rw-r--r--http_error_page.go2
-rw-r--r--http_handle_branches.go4
-rw-r--r--http_handle_group_index.go18
-rw-r--r--http_handle_index.go4
-rw-r--r--http_handle_login.go10
-rw-r--r--http_handle_repo_commit.go2
-rw-r--r--http_handle_repo_contrib_index.go6
-rw-r--r--http_handle_repo_contrib_one.go6
-rw-r--r--http_handle_repo_index.go6
-rw-r--r--http_handle_repo_info.go6
-rw-r--r--http_handle_repo_log.go2
-rw-r--r--http_handle_repo_raw.go6
-rw-r--r--http_handle_repo_tree.go6
-rw-r--r--http_handle_repo_upload_pack.go8
-rw-r--r--http_handle_users.go2
-rw-r--r--http_server.go14
-rw-r--r--http_template.go2
-rw-r--r--http_template_funcs.go2
-rw-r--r--irc.go32
-rw-r--r--iter.go2
-rw-r--r--lmtp_handle_patch.go4
-rw-r--r--lmtp_server.go22
-rw-r--r--main.go187
-rw-r--r--remote_url.go10
-rw-r--r--resources.go2
-rw-r--r--server.go199
-rw-r--r--ssh_handle_receive_pack.go12
-rw-r--r--ssh_handle_upload_pack.go6
-rw-r--r--ssh_server.go14
-rw-r--r--ssh_utils.go4
-rw-r--r--users.go6
-rw-r--r--version.go2
48 files changed, 372 insertions, 360 deletions
diff --git a/Makefile b/Makefile
index 0d464a4..eb2df23 100644
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,7 @@ VERSION = $(shell git describe --tags --always --dirty)
SOURCE_FILES = $(shell git ls-files)
forge: source.tar.gz hookc/hookc git2d/git2d $(SOURCE_FILES)
- CGO_ENABLED=0 go build -o forge -ldflags '-extldflags "-f no-PIC -static" -X "main.VERSION=$(VERSION)"' -tags 'osusergo netgo static_build'
+ CGO_ENABLED=0 go build -o forge -ldflags '-extldflags "-f no-PIC -static" -X "main.VERSION=$(VERSION)"' -tags 'osusergo netgo static_build' ./cmd/forge
utils/colb:
diff --git a/acl.go b/acl.go
index dfe128a..c4bf29f 100644
--- a/acl.go
+++ b/acl.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"context"
@@ -13,8 +13,8 @@ import (
// given repo and a provided ssh public key.
//
// TODO: Revamp.
-func (s *server) getRepoInfo(ctx context.Context, groupPath []string, repoName, sshPubkey string) (repoID int, fsPath string, access bool, contribReq, userType string, userID int, err error) {
- err = s.database.QueryRow(ctx, `
+func (s *Server) getRepoInfo(ctx context.Context, groupPath []string, repoName, sshPubkey string) (repoID int, fsPath string, access bool, contribReq, userType string, userID int, err error) {
+ err = s.Database.QueryRow(ctx, `
WITH RECURSIVE group_path_cte AS (
-- Start: match the first name in the path where parent_group IS NULL
SELECT
diff --git a/cmd/forge/main.go b/cmd/forge/main.go
new file mode 100644
index 0000000..102f4da
--- /dev/null
+++ b/cmd/forge/main.go
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
+
+package main
+
+import (
+ "flag"
+ "log/slog"
+ "os"
+
+ "go.lindenii.runxiyu.org/forge"
+)
+
+func main() {
+ configPath := flag.String(
+ "config",
+ "/etc/lindenii/forge.scfg",
+ "path to configuration file",
+ )
+ flag.Parse()
+
+ s := forge.Server{}
+
+ s.Setup()
+
+ if err := s.LoadConfig(*configPath); err != nil {
+ slog.Error("loading configuration", "error", err)
+ os.Exit(1)
+ }
+
+ s.Run()
+}
diff --git a/config.go b/config.go
index ec8daf8..84633ea 100644
--- a/config.go
+++ b/config.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"bufio"
@@ -67,7 +67,7 @@ type Config struct {
} `scfg:"db"`
}
-// loadConfig loads a configuration file from the specified path and unmarshals
+// LoadConfig loads a configuration file from the specified path and unmarshals
// it to the global [config] struct. This may race with concurrent reads from
// [config]; additional synchronization is necessary if the configuration is to
// be made reloadable.
@@ -76,7 +76,7 @@ type Config struct {
// configuration patterns, but silently ignores fields in the [config] struct
// that is not present in the user's configuration file. We would prefer the
// exact opposite behavior.
-func (s *server) loadConfig(path string) (err error) {
+func (s *Server) LoadConfig(path string) (err error) {
var configFile *os.File
if configFile, err = os.Open(path); err != nil {
return err
@@ -84,19 +84,19 @@ func (s *server) loadConfig(path string) (err error) {
defer configFile.Close()
decoder := scfg.NewDecoder(bufio.NewReader(configFile))
- if err = decoder.Decode(&s.config); err != nil {
+ if err = decoder.Decode(&s.Config); err != nil {
return err
}
- if s.config.DB.Type != "postgres" {
+ if s.Config.DB.Type != "postgres" {
return errors.New("unsupported database type")
}
- if s.database, err = pgxpool.New(context.Background(), s.config.DB.Conn); err != nil {
+ if s.Database, err = pgxpool.New(context.Background(), s.Config.DB.Conn); err != nil {
return err
}
- s.globalData["forge_title"] = s.config.General.Title
+ s.GlobalData["forge_title"] = s.Config.General.Title
return nil
}
diff --git a/database.go b/database.go
index 1ea0753..eafad33 100644
--- a/database.go
+++ b/database.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"context"
@@ -18,10 +18,10 @@ import (
// queryNameDesc is a helper function that executes a query and returns a
// list of nameDesc results. The query must return two string arguments, i.e. a
// name and a description.
-func (s *server) queryNameDesc(ctx context.Context, query string, args ...any) (result []nameDesc, err error) {
+func (s *Server) queryNameDesc(ctx context.Context, query string, args ...any) (result []nameDesc, err error) {
var rows pgx.Rows
- if rows, err = s.database.Query(ctx, query, args...); err != nil {
+ if rows, err = s.Database.Query(ctx, query, args...); err != nil {
return nil, err
}
defer rows.Close()
diff --git a/fedauth.go b/fedauth.go
index 43cb4e3..fd84047 100644
--- a/fedauth.go
+++ b/fedauth.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"bufio"
@@ -17,7 +17,7 @@ import (
// fedauth checks whether a user's SSH public key matches the remote username
// they claim to have on the service. If so, the association is recorded.
-func (s *server) fedauth(ctx context.Context, userID int, service, remoteUsername, pubkey string) (bool, error) {
+func (s *Server) fedauth(ctx context.Context, userID int, service, remoteUsername, pubkey string) (bool, error) {
var err error
matched := false
@@ -77,7 +77,7 @@ func (s *server) fedauth(ctx context.Context, userID int, service, remoteUsernam
}
var txn pgx.Tx
- if txn, err = s.database.Begin(ctx); err != nil {
+ if txn, err = s.Database.Begin(ctx); err != nil {
return false, err
}
defer func() {
diff --git a/git2d_deploy.go b/git2d_deploy.go
index ba63a1b..5a5f336 100644
--- a/git2d_deploy.go
+++ b/git2d_deploy.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"io"
@@ -9,7 +9,7 @@ import (
"os"
)
-func (s *server) deployGit2D() (err error) {
+func (s *Server) deployGit2D() (err error) {
var srcFD fs.File
var dstFD *os.File
@@ -18,7 +18,7 @@ func (s *server) deployGit2D() (err error) {
}
defer srcFD.Close()
- if dstFD, err = os.OpenFile(s.config.Git.DaemonPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755); err != nil {
+ 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()
diff --git a/git_format_patch.go b/git_format_patch.go
index 79a7474..5628ce1 100644
--- a/git_format_patch.go
+++ b/git_format_patch.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"bytes"
diff --git a/git_hooks_deploy.go b/git_hooks_deploy.go
index 0cfb4f9..eeda995 100644
--- a/git_hooks_deploy.go
+++ b/git_hooks_deploy.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"errors"
@@ -14,7 +14,7 @@ import (
// 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) {
+func (s *Server) deployHooks() (err error) {
err = func() (err error) {
var srcFD fs.File
var dstFD *os.File
@@ -24,7 +24,7 @@ func (s *server) deployHooks() (err error) {
}
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 {
+ 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()
@@ -41,14 +41,14 @@ func (s *server) deployHooks() (err error) {
// 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 {
+ 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 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
}
diff --git a/git_hooks_handle_linux.go b/git_hooks_handle_linux.go
index 097f236..1101302 100644
--- a/git_hooks_handle_linux.go
+++ b/git_hooks_handle_linux.go
@@ -3,7 +3,7 @@
//
//go:build linux
-package main
+package forge
import (
"bytes"
@@ -34,7 +34,7 @@ var (
// hooksHandler handles a connection from hookc via the
// unix socket.
-func (s *server) hooksHandler(conn net.Conn) {
+func (s *Server) hooksHandler(conn net.Conn) {
var ctx context.Context
var cancel context.CancelFunc
var ucred *syscall.Ucred
@@ -77,7 +77,7 @@ func (s *server) hooksHandler(conn net.Conn) {
{
var ok bool
- packPass, ok = s.packPasses.Load(misc.BytesToString(cookie))
+ packPass, ok = s.PackPasses.Load(misc.BytesToString(cookie))
if !ok {
if _, err = conn.Write([]byte{1}); err != nil {
return
@@ -233,12 +233,12 @@ func (s *server) hooksHandler(conn net.Conn) {
var newMRLocalID int
if packPass.userID != 0 {
- err = s.database.QueryRow(ctx,
+ err = s.Database.QueryRow(ctx,
"INSERT INTO merge_requests (repo_id, creator, source_ref, status) VALUES ($1, $2, $3, 'open') RETURNING repo_local_id",
packPass.repoID, packPass.userID, strings.TrimPrefix(refName, "refs/heads/"),
).Scan(&newMRLocalID)
} else {
- err = s.database.QueryRow(ctx,
+ err = s.Database.QueryRow(ctx,
"INSERT INTO merge_requests (repo_id, source_ref, status) VALUES ($1, $2, 'open') RETURNING repo_local_id",
packPass.repoID, strings.TrimPrefix(refName, "refs/heads/"),
).Scan(&newMRLocalID)
@@ -251,7 +251,7 @@ func (s *server) hooksHandler(conn net.Conn) {
fmt.Fprintln(sshStderr, ansiec.Blue+"Created merge request at", mergeRequestWebURL+ansiec.Reset)
select {
- case s.ircSendBuffered <- "PRIVMSG #chat :New merge request at " + mergeRequestWebURL:
+ case s.IrcSendBuffered <- "PRIVMSG #chat :New merge request at " + mergeRequestWebURL:
default:
slog.Error("IRC SendQ exceeded")
}
@@ -259,7 +259,7 @@ func (s *server) hooksHandler(conn net.Conn) {
var existingMRUser int
var isAncestor bool
- err = s.database.QueryRow(ctx,
+ err = s.Database.QueryRow(ctx,
"SELECT COALESCE(creator, 0) FROM merge_requests WHERE source_ref = $1 AND repo_id = $2",
strings.TrimPrefix(refName, "refs/heads/"), packPass.repoID,
).Scan(&existingMRUser)
@@ -342,7 +342,7 @@ func (s *server) hooksHandler(conn net.Conn) {
// treats incoming connections as those from git hook handlers by spawning
// sessions. The listener must be a SOCK_STREAM UNIX domain socket. The
// function itself blocks.
-func (s *server) serveGitHooks(listener net.Listener) error {
+func (s *Server) serveGitHooks(listener net.Listener) error {
for {
conn, err := listener.Accept()
if err != nil {
diff --git a/git_hooks_handle_other.go b/git_hooks_handle_other.go
index 687bd8f..4a4328f 100644
--- a/git_hooks_handle_other.go
+++ b/git_hooks_handle_other.go
@@ -3,7 +3,7 @@
//
//go:build !linux
-package main
+package forge
import (
"bytes"
diff --git a/git_init.go b/git_init.go
index 1800c5a..b448451 100644
--- a/git_init.go
+++ b/git_init.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"github.com/go-git/go-git/v5"
@@ -11,7 +11,7 @@ import (
// gitInit initializes a bare git repository with the forge-deployed hooks
// directory as the hooksPath.
-func (s *server) gitInit(repoPath string) (err error) {
+func (s *Server) gitInit(repoPath string) (err error) {
var repo *git.Repository
var gitConf *gitConfig.Config
@@ -23,7 +23,7 @@ func (s *server) gitInit(repoPath string) (err error) {
return err
}
- gitConf.Raw.SetOption("core", gitFmtConfig.NoSubsection, "hooksPath", s.config.Hooks.Execs)
+ gitConf.Raw.SetOption("core", gitFmtConfig.NoSubsection, "hooksPath", s.Config.Hooks.Execs)
gitConf.Raw.SetOption("receive", gitFmtConfig.NoSubsection, "advertisePushOptions", "true")
if err = repo.SetConfig(gitConf); err != nil {
diff --git a/git_misc.go b/git_misc.go
index 17f834c..8ba10e1 100644
--- a/git_misc.go
+++ b/git_misc.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"context"
@@ -22,8 +22,8 @@ import (
// TODO: This should be deprecated in favor of doing it in the relevant
// request/router context in the future, as it cannot cover the nuance of
// fields needed.
-func (s *server) openRepo(ctx context.Context, groupPath []string, repoName string) (repo *git.Repository, description string, repoID int, fsPath string, err error) {
- err = s.database.QueryRow(ctx, `
+func (s *Server) openRepo(ctx context.Context, groupPath []string, repoName string) (repo *git.Repository, description string, repoID int, fsPath string, err error) {
+ err = s.Database.QueryRow(ctx, `
WITH RECURSIVE group_path_cte AS (
-- Start: match the first name in the path where parent_group IS NULL
SELECT
diff --git a/git_plumbing.go b/git_plumbing.go
index 74c80ac..440de7c 100644
--- a/git_plumbing.go
+++ b/git_plumbing.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"bytes"
diff --git a/git_ref.go b/git_ref.go
index 921359e..476dde0 100644
--- a/git_ref.go
+++ b/git_ref.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"github.com/go-git/go-git/v5"
diff --git a/http_auth.go b/http_auth.go
index 5f0dc66..5ba278b 100644
--- a/http_auth.go
+++ b/http_auth.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"net/http"
@@ -9,14 +9,14 @@ import (
// getUserFromRequest returns the user ID and username associated with the
// session cookie in a given [http.Request].
-func (s *server) getUserFromRequest(request *http.Request) (id int, username string, err error) {
+func (s *Server) getUserFromRequest(request *http.Request) (id int, username string, err error) {
var sessionCookie *http.Cookie
if sessionCookie, err = request.Cookie("session"); err != nil {
return
}
- err = s.database.QueryRow(
+ err = s.Database.QueryRow(
request.Context(),
"SELECT user_id, COALESCE(username, '') FROM users u JOIN sessions s ON u.id = s.user_id WHERE s.session_id = $1;",
sessionCookie.Value,
diff --git a/http_error_page.go b/http_error_page.go
index 00ef04b..0cce72e 100644
--- a/http_error_page.go
+++ b/http_error_page.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"net/http"
diff --git a/http_handle_branches.go b/http_handle_branches.go
index 01a162a..96c4ac7 100644
--- a/http_handle_branches.go
+++ b/http_handle_branches.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"net/http"
@@ -14,7 +14,7 @@ import (
)
// httpHandleRepoBranches provides the branches page in repos.
-func (s *server) httpHandleRepoBranches(writer http.ResponseWriter, _ *http.Request, params map[string]any) {
+func (s *Server) httpHandleRepoBranches(writer http.ResponseWriter, _ *http.Request, params map[string]any) {
var repo *git.Repository
var repoName string
var groupPath []string
diff --git a/http_handle_group_index.go b/http_handle_group_index.go
index 46f1f6a..cc33860 100644
--- a/http_handle_group_index.go
+++ b/http_handle_group_index.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"errors"
@@ -17,7 +17,7 @@ import (
// httpHandleGroupIndex provides index pages for groups, which includes a list
// of its subgroups and repos, as well as a form for group maintainers to
// create repos.
-func (s *server) httpHandleGroupIndex(writer http.ResponseWriter, request *http.Request, params map[string]any) {
+func (s *Server) httpHandleGroupIndex(writer http.ResponseWriter, request *http.Request, params map[string]any) {
var groupPath []string
var repos []nameDesc
var subgroups []nameDesc
@@ -28,7 +28,7 @@ func (s *server) httpHandleGroupIndex(writer http.ResponseWriter, request *http.
groupPath = params["group_path"].([]string)
// The group itself
- err = s.database.QueryRow(request.Context(), `
+ err = s.Database.QueryRow(request.Context(), `
WITH RECURSIVE group_path_cte AS (
SELECT
id,
@@ -69,7 +69,7 @@ func (s *server) httpHandleGroupIndex(writer http.ResponseWriter, request *http.
// ACL
var count int
- err = s.database.QueryRow(request.Context(), `
+ err = s.Database.QueryRow(request.Context(), `
SELECT COUNT(*)
FROM user_group_roles
WHERE user_id = $1
@@ -96,7 +96,7 @@ func (s *server) httpHandleGroupIndex(writer http.ResponseWriter, request *http.
}
var newRepoID int
- err := s.database.QueryRow(
+ err := s.Database.QueryRow(
request.Context(),
`INSERT INTO repos (name, description, group_id, contrib_requirements)
VALUES ($1, $2, $3, $4)
@@ -111,9 +111,9 @@ func (s *server) httpHandleGroupIndex(writer http.ResponseWriter, request *http.
return
}
- filePath := filepath.Join(s.config.Git.RepoDir, strconv.Itoa(newRepoID)+".git")
+ filePath := filepath.Join(s.Config.Git.RepoDir, strconv.Itoa(newRepoID)+".git")
- _, err = s.database.Exec(
+ _, err = s.Database.Exec(
request.Context(),
`UPDATE repos
SET filesystem_path = $1
@@ -137,7 +137,7 @@ func (s *server) httpHandleGroupIndex(writer http.ResponseWriter, request *http.
// Repos
var rows pgx.Rows
- rows, err = s.database.Query(request.Context(), `
+ rows, err = s.Database.Query(request.Context(), `
SELECT name, COALESCE(description, '')
FROM repos
WHERE group_id = $1
@@ -162,7 +162,7 @@ func (s *server) httpHandleGroupIndex(writer http.ResponseWriter, request *http.
}
// Subgroups
- rows, err = s.database.Query(request.Context(), `
+ rows, err = s.Database.Query(request.Context(), `
SELECT name, COALESCE(description, '')
FROM groups
WHERE parent_group = $1
diff --git a/http_handle_index.go b/http_handle_index.go
index 755e7c4..a519a5a 100644
--- a/http_handle_index.go
+++ b/http_handle_index.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"net/http"
@@ -12,7 +12,7 @@ import (
// httpHandleIndex provides the main index page which includes a list of groups
// and some global information such as SSH keys.
-func (s *server) httpHandleIndex(writer http.ResponseWriter, request *http.Request, params map[string]any) {
+func (s *Server) httpHandleIndex(writer http.ResponseWriter, request *http.Request, params map[string]any) {
var err error
var groups []nameDesc
diff --git a/http_handle_login.go b/http_handle_login.go
index 10bfdcd..e02ba10 100644
--- a/http_handle_login.go
+++ b/http_handle_login.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"crypto/rand"
@@ -16,7 +16,7 @@ import (
)
// httpHandleLogin provides the login page for local users.
-func (s *server) httpHandleLogin(writer http.ResponseWriter, request *http.Request, params map[string]any) {
+func (s *Server) httpHandleLogin(writer http.ResponseWriter, request *http.Request, params map[string]any) {
var username, password string
var userID int
var passwordHash string
@@ -35,7 +35,7 @@ func (s *server) httpHandleLogin(writer http.ResponseWriter, request *http.Reque
username = request.PostFormValue("username")
password = request.PostFormValue("password")
- err = s.database.QueryRow(request.Context(),
+ err = s.Database.QueryRow(request.Context(),
"SELECT id, COALESCE(password, '') FROM users WHERE username = $1",
username,
).Scan(&userID, &passwordHash)
@@ -71,7 +71,7 @@ func (s *server) httpHandleLogin(writer http.ResponseWriter, request *http.Reque
}
now = time.Now()
- expiry = now.Add(time.Duration(s.config.HTTP.CookieExpiry) * time.Second)
+ expiry = now.Add(time.Duration(s.Config.HTTP.CookieExpiry) * time.Second)
cookie = http.Cookie{
Name: "session",
@@ -85,7 +85,7 @@ func (s *server) httpHandleLogin(writer http.ResponseWriter, request *http.Reque
http.SetCookie(writer, &cookie)
- _, err = s.database.Exec(request.Context(), "INSERT INTO sessions (user_id, session_id) VALUES ($1, $2)", userID, cookieValue)
+ _, err = s.Database.Exec(request.Context(), "INSERT INTO sessions (user_id, session_id) VALUES ($1, $2)", userID, cookieValue)
if err != nil {
errorPage500(writer, params, "Error inserting session: "+err.Error())
return
diff --git a/http_handle_repo_commit.go b/http_handle_repo_commit.go
index a398dc2..88ade4b 100644
--- a/http_handle_repo_commit.go
+++ b/http_handle_repo_commit.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"fmt"
diff --git a/http_handle_repo_contrib_index.go b/http_handle_repo_contrib_index.go
index e0c8478..f729cbe 100644
--- a/http_handle_repo_contrib_index.go
+++ b/http_handle_repo_contrib_index.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"net/http"
@@ -18,12 +18,12 @@ type idTitleStatus struct {
}
// httpHandleRepoContribIndex provides an index to merge requests of a repo.
-func (s *server) httpHandleRepoContribIndex(writer http.ResponseWriter, request *http.Request, params map[string]any) {
+func (s *Server) httpHandleRepoContribIndex(writer http.ResponseWriter, request *http.Request, params map[string]any) {
var rows pgx.Rows
var result []idTitleStatus
var err error
- if rows, err = s.database.Query(request.Context(),
+ if rows, err = s.Database.Query(request.Context(),
"SELECT repo_local_id, COALESCE(title, 'Untitled'), status FROM merge_requests WHERE repo_id = $1",
params["repo_id"],
); err != nil {
diff --git a/http_handle_repo_contrib_one.go b/http_handle_repo_contrib_one.go
index 0df7491..9a261a4 100644
--- a/http_handle_repo_contrib_one.go
+++ b/http_handle_repo_contrib_one.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"net/http"
@@ -14,7 +14,7 @@ import (
// httpHandleRepoContribOne provides an interface to each merge request of a
// repo.
-func (s *server) httpHandleRepoContribOne(writer http.ResponseWriter, request *http.Request, params map[string]any) {
+func (s *Server) httpHandleRepoContribOne(writer http.ResponseWriter, request *http.Request, params map[string]any) {
var mrIDStr string
var mrIDInt int
var err error
@@ -33,7 +33,7 @@ func (s *server) httpHandleRepoContribOne(writer http.ResponseWriter, request *h
}
mrIDInt = int(mrIDInt64)
- if err = s.database.QueryRow(request.Context(),
+ if err = s.Database.QueryRow(request.Context(),
"SELECT COALESCE(title, ''), status, source_ref, COALESCE(destination_branch, '') FROM merge_requests WHERE repo_id = $1 AND repo_local_id = $2",
params["repo_id"], mrIDInt,
).Scan(&title, &status, &srcRefStr, &dstBranchStr); err != nil {
diff --git a/http_handle_repo_index.go b/http_handle_repo_index.go
index c6338cf..edba57b 100644
--- a/http_handle_repo_index.go
+++ b/http_handle_repo_index.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"net/http"
@@ -19,13 +19,13 @@ type commitDisplay struct {
}
// httpHandleRepoIndex provides the front page of a repo using git2d.
-func (s *server) httpHandleRepoIndex(w http.ResponseWriter, req *http.Request, params map[string]any) {
+func (s *Server) httpHandleRepoIndex(w http.ResponseWriter, req *http.Request, params map[string]any) {
repoName := params["repo_name"].(string)
groupPath := params["group_path"].([]string)
_, repoPath, _, _, _, _, _ := s.getRepoInfo(req.Context(), groupPath, repoName, "") // TODO: Don't use getRepoInfo
- client, err := git2c.NewClient(s.config.Git.Socket)
+ client, err := git2c.NewClient(s.Config.Git.Socket)
if err != nil {
errorPage500(w, params, err.Error())
return
diff --git a/http_handle_repo_info.go b/http_handle_repo_info.go
index b7b7438..e2080ac 100644
--- a/http_handle_repo_info.go
+++ b/http_handle_repo_info.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"fmt"
@@ -16,12 +16,12 @@ import (
// HTTP protocol.
//
// TODO: Reject access from web browsers.
-func (s *server) httpHandleRepoInfo(writer http.ResponseWriter, request *http.Request, params map[string]any) (err error) {
+func (s *Server) httpHandleRepoInfo(writer http.ResponseWriter, request *http.Request, params map[string]any) (err error) {
groupPath := params["group_path"].([]string)
repoName := params["repo_name"].(string)
var repoPath string
- if err := s.database.QueryRow(request.Context(), `
+ if err := s.Database.QueryRow(request.Context(), `
WITH RECURSIVE group_path_cte AS (
-- Start: match the first name in the path where parent_group IS NULL
SELECT
diff --git a/http_handle_repo_log.go b/http_handle_repo_log.go
index 5c69836..b104491 100644
--- a/http_handle_repo_log.go
+++ b/http_handle_repo_log.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"net/http"
diff --git a/http_handle_repo_raw.go b/http_handle_repo_raw.go
index 570030f..7e19e02 100644
--- a/http_handle_repo_raw.go
+++ b/http_handle_repo_raw.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"fmt"
@@ -15,7 +15,7 @@ import (
// httpHandleRepoRaw serves raw files, or directory listings that point to raw
// files.
-func (s *server) httpHandleRepoRaw(writer http.ResponseWriter, request *http.Request, params map[string]any) {
+func (s *Server) httpHandleRepoRaw(writer http.ResponseWriter, request *http.Request, params map[string]any) {
repoName := params["repo_name"].(string)
groupPath := params["group_path"].([]string)
rawPathSpec := params["rest"].(string)
@@ -24,7 +24,7 @@ func (s *server) httpHandleRepoRaw(writer http.ResponseWriter, request *http.Req
_, repoPath, _, _, _, _, _ := s.getRepoInfo(request.Context(), groupPath, repoName, "")
- client, err := git2c.NewClient(s.config.Git.Socket)
+ client, err := git2c.NewClient(s.Config.Git.Socket)
if err != nil {
errorPage500(writer, params, err.Error())
return
diff --git a/http_handle_repo_tree.go b/http_handle_repo_tree.go
index 7af6e3e..e8e5ff8 100644
--- a/http_handle_repo_tree.go
+++ b/http_handle_repo_tree.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"html/template"
@@ -16,7 +16,7 @@ import (
// individual files, and provides directory views that link to these files.
//
// TODO: Do not highlight files that are too large.
-func (s *server) httpHandleRepoTree(writer http.ResponseWriter, request *http.Request, params map[string]any) {
+func (s *Server) httpHandleRepoTree(writer http.ResponseWriter, request *http.Request, params map[string]any) {
repoName := params["repo_name"].(string)
groupPath := params["group_path"].([]string)
rawPathSpec := params["rest"].(string)
@@ -25,7 +25,7 @@ func (s *server) httpHandleRepoTree(writer http.ResponseWriter, request *http.Re
_, repoPath, _, _, _, _, _ := s.getRepoInfo(request.Context(), groupPath, repoName, "")
- client, err := git2c.NewClient(s.config.Git.Socket)
+ client, err := git2c.NewClient(s.Config.Git.Socket)
if err != nil {
errorPage500(writer, params, err.Error())
return
diff --git a/http_handle_repo_upload_pack.go b/http_handle_repo_upload_pack.go
index a6580a7..4c7291b 100644
--- a/http_handle_repo_upload_pack.go
+++ b/http_handle_repo_upload_pack.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"io"
@@ -14,7 +14,7 @@ import (
// httpHandleUploadPack handles incoming Git fetch/pull/clone's over the Smart
// HTTP protocol.
-func (s *server) httpHandleUploadPack(writer http.ResponseWriter, request *http.Request, params map[string]any) (err error) {
+func (s *Server) httpHandleUploadPack(writer http.ResponseWriter, request *http.Request, params map[string]any) (err error) {
var groupPath []string
var repoName string
var repoPath string
@@ -24,7 +24,7 @@ func (s *server) httpHandleUploadPack(writer http.ResponseWriter, request *http.
groupPath, repoName = params["group_path"].([]string), params["repo_name"].(string)
- if err := s.database.QueryRow(request.Context(), `
+ if err := s.Database.QueryRow(request.Context(), `
WITH RECURSIVE group_path_cte AS (
-- Start: match the first name in the path where parent_group IS NULL
SELECT
@@ -67,7 +67,7 @@ func (s *server) httpHandleUploadPack(writer http.ResponseWriter, request *http.
writer.WriteHeader(http.StatusOK)
cmd = exec.Command("git", "upload-pack", "--stateless-rpc", repoPath)
- cmd.Env = append(os.Environ(), "LINDENII_FORGE_HOOKS_SOCKET_PATH="+s.config.Hooks.Socket)
+ cmd.Env = append(os.Environ(), "LINDENII_FORGE_HOOKS_SOCKET_PATH="+s.Config.Hooks.Socket)
if stdout, err = cmd.StdoutPipe(); err != nil {
return err
}
diff --git a/http_handle_users.go b/http_handle_users.go
index e02d4b2..d379624 100644
--- a/http_handle_users.go
+++ b/http_handle_users.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"net/http"
diff --git a/http_server.go b/http_server.go
index 13ee492..fdb55b4 100644
--- a/http_server.go
+++ b/http_server.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"errors"
@@ -19,9 +19,9 @@ import (
// location.
//
// TODO: This function is way too large.
-func (s *server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
+func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
var remoteAddr string
- if s.config.HTTP.ReverseProxy {
+ if s.Config.HTTP.ReverseProxy {
remoteAddrs, ok := request.Header["X-Forwarded-For"]
if ok && len(remoteAddrs) == 1 {
remoteAddr = remoteAddrs[0]
@@ -50,7 +50,7 @@ func (s *server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
params["url_segments"] = segments
params["dir_mode"] = dirMode
- params["global"] = s.globalData
+ params["global"] = s.GlobalData
var userID int // 0 for none
userID, params["username"], err = s.getUserFromRequest(request)
params["user_id"] = userID
@@ -87,10 +87,10 @@ func (s *server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
switch segments[1] {
case "static":
- s.staticHandler.ServeHTTP(writer, request)
+ s.StaticHandler.ServeHTTP(writer, request)
return
case "source":
- s.sourceHandler.ServeHTTP(writer, request)
+ s.SourceHandler.ServeHTTP(writer, request)
return
}
}
@@ -183,7 +183,7 @@ func (s *server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
repoURLRoot = repoURLRoot + url.PathEscape(part) + "/"
}
params["repo_url_root"] = repoURLRoot
- params["repo_patch_mailing_list"] = repoURLRoot[1:len(repoURLRoot)-1] + "@" + s.config.LMTP.Domain
+ params["repo_patch_mailing_list"] = repoURLRoot[1:len(repoURLRoot)-1] + "@" + s.Config.LMTP.Domain
params["http_clone_url"] = s.genHTTPRemoteURL(groupPath, moduleName)
params["ssh_clone_url"] = s.genSSHRemoteURL(groupPath, moduleName)
diff --git a/http_template.go b/http_template.go
index 9aa15cb..f60f026 100644
--- a/http_template.go
+++ b/http_template.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"log/slog"
diff --git a/http_template_funcs.go b/http_template_funcs.go
index 526841f..616afe2 100644
--- a/http_template_funcs.go
+++ b/http_template_funcs.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"net/url"
diff --git a/irc.go b/irc.go
index 49fa28f..298930f 100644
--- a/irc.go
+++ b/irc.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"crypto/tls"
@@ -16,13 +16,13 @@ type errorBack[T any] struct {
errorBack chan error
}
-func (s *server) ircBotSession() error {
+func (s *Server) ircBotSession() error {
var err error
var underlyingConn net.Conn
- if s.config.IRC.TLS {
- underlyingConn, err = tls.Dial(s.config.IRC.Net, s.config.IRC.Addr, nil)
+ if s.Config.IRC.TLS {
+ underlyingConn, err = tls.Dial(s.Config.IRC.Net, s.Config.IRC.Addr, nil)
} else {
- underlyingConn, err = net.Dial(s.config.IRC.Net, s.config.IRC.Addr)
+ underlyingConn, err = net.Dial(s.Config.IRC.Net, s.Config.IRC.Addr)
}
if err != nil {
return err
@@ -36,11 +36,11 @@ func (s *server) ircBotSession() error {
return conn.WriteString(s + "\r\n")
}
- _, err = logAndWriteLn("NICK " + s.config.IRC.Nick)
+ _, err = logAndWriteLn("NICK " + s.Config.IRC.Nick)
if err != nil {
return err
}
- _, err = logAndWriteLn("USER " + s.config.IRC.User + " 0 * :" + s.config.IRC.Gecos)
+ _, err = logAndWriteLn("USER " + s.Config.IRC.User + " 0 * :" + s.Config.IRC.Gecos)
if err != nil {
return err
}
@@ -81,7 +81,7 @@ func (s *server) ircBotSession() error {
if !ok {
slog.Error("unable to convert source of JOIN to client")
}
- if c.Nick != s.config.IRC.Nick {
+ if c.Nick != s.Config.IRC.Nick {
continue
}
default:
@@ -93,18 +93,18 @@ func (s *server) ircBotSession() error {
select {
case err = <-readLoopError:
return err
- case line := <-s.ircSendBuffered:
+ case line := <-s.IrcSendBuffered:
_, err = logAndWriteLn(line)
if err != nil {
select {
- case s.ircSendBuffered <- line:
+ case s.IrcSendBuffered <- line:
default:
slog.Error("unable to requeue message", "line", line)
}
writeLoopAbort <- struct{}{}
return err
}
- case lineErrorBack := <-s.ircSendDirectChan:
+ case lineErrorBack := <-s.IrcSendDirectChan:
_, err = logAndWriteLn(lineErrorBack.content)
lineErrorBack.errorBack <- err
if err != nil {
@@ -117,10 +117,10 @@ func (s *server) ircBotSession() error {
// ircSendDirect sends an IRC message directly to the connection and bypasses
// the buffering system.
-func (s *server) ircSendDirect(line string) error {
+func (s *Server) ircSendDirect(line string) error {
ech := make(chan error, 1)
- s.ircSendDirectChan <- errorBack[string]{
+ s.IrcSendDirectChan <- errorBack[string]{
content: line,
errorBack: ech,
}
@@ -129,9 +129,9 @@ func (s *server) ircSendDirect(line string) error {
}
// TODO: Delay and warnings?
-func (s *server) ircBotLoop() {
- s.ircSendBuffered = make(chan string, s.config.IRC.SendQ)
- s.ircSendDirectChan = make(chan errorBack[string])
+func (s *Server) ircBotLoop() {
+ s.IrcSendBuffered = make(chan string, s.Config.IRC.SendQ)
+ s.IrcSendDirectChan = make(chan errorBack[string])
for {
err := s.ircBotSession()
diff --git a/iter.go b/iter.go
index d4c7175..e237118 100644
--- a/iter.go
+++ b/iter.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import "iter"
diff --git a/lmtp_handle_patch.go b/lmtp_handle_patch.go
index ab846aa..bf1b94c 100644
--- a/lmtp_handle_patch.go
+++ b/lmtp_handle_patch.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"bytes"
@@ -19,7 +19,7 @@ import (
"go.lindenii.runxiyu.org/forge/misc"
)
-func (s *server) lmtpHandlePatch(session *lmtpSession, groupPath []string, repoName string, mbox io.Reader) (err error) {
+func (s *Server) lmtpHandlePatch(session *lmtpSession, groupPath []string, repoName string, mbox io.Reader) (err error) {
var diffFiles []*gitdiff.File
var preamble string
if diffFiles, preamble, err = gitdiff.Parse(mbox); err != nil {
diff --git a/lmtp_server.go b/lmtp_server.go
index 8191766..863a5c0 100644
--- a/lmtp_server.go
+++ b/lmtp_server.go
@@ -2,7 +2,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
// SPDX-FileCopyrightText: Copyright (c) 2024 Robin Jarry <robin@jarry.cc>
-package main
+package forge
import (
"bytes"
@@ -27,7 +27,7 @@ type lmtpSession struct {
to []string
ctx context.Context
cancel context.CancelFunc
- s server
+ s Server
}
func (session *lmtpSession) Reset() {
@@ -63,13 +63,13 @@ func (*lmtpHandler) NewSession(_ *smtp.Conn) (smtp.Session, error) {
return session, nil
}
-func (s *server) serveLMTP(listener net.Listener) error {
+func (s *Server) serveLMTP(listener net.Listener) error {
smtpServer := smtp.NewServer(&lmtpHandler{})
smtpServer.LMTP = true
- smtpServer.Domain = s.config.LMTP.Domain
- smtpServer.Addr = s.config.LMTP.Socket
- smtpServer.WriteTimeout = time.Duration(s.config.LMTP.WriteTimeout) * time.Second
- smtpServer.ReadTimeout = time.Duration(s.config.LMTP.ReadTimeout) * time.Second
+ smtpServer.Domain = s.Config.LMTP.Domain
+ smtpServer.Addr = s.Config.LMTP.Socket
+ smtpServer.WriteTimeout = time.Duration(s.Config.LMTP.WriteTimeout) * time.Second
+ smtpServer.ReadTimeout = time.Duration(s.Config.LMTP.ReadTimeout) * time.Second
smtpServer.EnableSMTPUTF8 = true
return smtpServer.Serve(listener)
}
@@ -85,9 +85,9 @@ func (session *lmtpSession) Data(r io.Reader) error {
n int64
)
- n, err = io.CopyN(&buf, r, session.s.config.LMTP.MaxSize)
+ n, err = io.CopyN(&buf, r, session.s.Config.LMTP.MaxSize)
switch {
- case n == session.s.config.LMTP.MaxSize:
+ case n == session.s.Config.LMTP.MaxSize:
err = errors.New("Message too big.")
// drain whatever is left in the pipe
_, _ = io.Copy(io.Discard, r)
@@ -133,10 +133,10 @@ func (session *lmtpSession) Data(r io.Reader) error {
_ = from
for _, to := range to {
- if !strings.HasSuffix(to, "@"+session.s.config.LMTP.Domain) {
+ if !strings.HasSuffix(to, "@"+session.s.Config.LMTP.Domain) {
continue
}
- localPart := to[:len(to)-len("@"+session.s.config.LMTP.Domain)]
+ localPart := to[:len(to)-len("@"+session.s.Config.LMTP.Domain)]
var segments []string
segments, err = misc.PathToSegments(localPart)
if err != nil {
diff --git a/main.go b/main.go
deleted file mode 100644
index 2e1e094..0000000
--- a/main.go
+++ /dev/null
@@ -1,187 +0,0 @@
-// SPDX-License-Identifier: AGPL-3.0-only
-// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-
-package main
-
-import (
- "errors"
- "flag"
- "io/fs"
- "log"
- "log/slog"
- "net"
- "net/http"
- "os"
- "os/exec"
- "syscall"
- "time"
-)
-
-func main() {
- configPath := flag.String(
- "config",
- "/etc/lindenii/forge.scfg",
- "path to configuration file",
- )
- flag.Parse()
-
- s := server{}
-
- 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
- }
-
- if err := s.loadConfig(*configPath); err != nil {
- slog.Error("loading configuration", "error", err)
- os.Exit(1)
- }
- if err := s.deployHooks(); err != nil {
- slog.Error("deploying hooks", "error", err)
- os.Exit(1)
- }
- if err := 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)
- }
-
- // 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 {}
-}
diff --git a/remote_url.go b/remote_url.go
index 9f30993..453ddeb 100644
--- a/remote_url.go
+++ b/remote_url.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"net/url"
@@ -14,12 +14,12 @@ import (
// genSSHRemoteURL generates SSH remote URLs from a given group path and repo
// name.
-func (s *server) genSSHRemoteURL(groupPath []string, repoName string) string {
- return strings.TrimSuffix(s.config.SSH.Root, "/") + "/" + misc.SegmentsToURL(groupPath) + "/-/repos/" + url.PathEscape(repoName)
+func (s *Server) genSSHRemoteURL(groupPath []string, repoName string) string {
+ return strings.TrimSuffix(s.Config.SSH.Root, "/") + "/" + misc.SegmentsToURL(groupPath) + "/-/repos/" + url.PathEscape(repoName)
}
// genHTTPRemoteURL generates HTTP remote URLs from a given group path and repo
// name.
-func (s *server) genHTTPRemoteURL(groupPath []string, repoName string) string {
- return strings.TrimSuffix(s.config.HTTP.Root, "/") + "/" + misc.SegmentsToURL(groupPath) + "/-/repos/" + url.PathEscape(repoName)
+func (s *Server) genHTTPRemoteURL(groupPath []string, repoName string) string {
+ return strings.TrimSuffix(s.Config.HTTP.Root, "/") + "/" + misc.SegmentsToURL(groupPath) + "/-/repos/" + url.PathEscape(repoName)
}
diff --git a/resources.go b/resources.go
index 5ecb218..ffe1008 100644
--- a/resources.go
+++ b/resources.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"embed"
diff --git a/server.go b/server.go
index c995968..ce69380 100644
--- a/server.go
+++ b/server.go
@@ -1,34 +1,201 @@
-package main
+package forge
import (
+ "errors"
+ "io/fs"
+ "log"
+ "log/slog"
+ "net"
"net/http"
+ "os"
+ "os/exec"
+ "syscall"
+ "time"
"github.com/jackc/pgx/v5/pgxpool"
"go.lindenii.runxiyu.org/lindenii-common/cmap"
goSSH "golang.org/x/crypto/ssh"
)
-type server struct {
- config Config
+type Server struct {
+ Config Config
- // database serves as the primary database handle for this entire application.
+ // Database serves as the primary Database handle for this entire application.
// Transactions or single reads may be used from it. A [pgxpool.Pool] is
// necessary to safely use pgx concurrently; pgx.Conn, etc. are insufficient.
- database *pgxpool.Pool
+ Database *pgxpool.Pool
- sourceHandler http.Handler
- staticHandler http.Handler
+ SourceHandler http.Handler
+ StaticHandler http.Handler
- ircSendBuffered chan string
- ircSendDirectChan chan errorBack[string]
+ IrcSendBuffered chan string
+ IrcSendDirectChan chan errorBack[string]
- // globalData is passed as "global" when rendering HTML templates.
- globalData map[string]any
+ // GlobalData is passed as "global" when rendering HTML templates.
+ GlobalData map[string]any
- serverPubkeyString string
- serverPubkeyFP string
- serverPubkey goSSH.PublicKey
+ ServerPubkeyString string
+ ServerPubkeyFP string
+ ServerPubkey goSSH.PublicKey
- // packPasses contains hook cookies mapped to their packPass.
- packPasses cmap.Map[string, packPass]
+ // PackPasses contains hook cookies mapped to their packPass.
+ PackPasses cmap.Map[string, packPass]
+}
+
+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 := 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)
+ }
+
+ // 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 {}
}
diff --git a/ssh_handle_receive_pack.go b/ssh_handle_receive_pack.go
index 33262e4..724c3fd 100644
--- a/ssh_handle_receive_pack.go
+++ b/ssh_handle_receive_pack.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"errors"
@@ -30,7 +30,7 @@ type packPass struct {
}
// sshHandleRecvPack handles attempts to push to repos.
-func (s *server) sshHandleRecvPack(session gliderSSH.Session, pubkey, repoIdentifier string) (err error) {
+func (s *Server) sshHandleRecvPack(session gliderSSH.Session, pubkey, repoIdentifier string) (err error) {
groupPath, repoName, repoID, repoPath, directAccess, contribReq, userType, userID, err := s.getRepoInfo2(session.Context(), repoIdentifier, pubkey)
if err != nil {
return err
@@ -51,7 +51,7 @@ func (s *server) sshHandleRecvPack(session gliderSSH.Session, pubkey, repoIdenti
}
hooksPath := repoConfCore.OptionAll("hooksPath")
- if len(hooksPath) != 1 || hooksPath[0] != s.config.Hooks.Execs {
+ if len(hooksPath) != 1 || hooksPath[0] != s.Config.Hooks.Execs {
return errors.New("repository has hooksPath set to an unexpected value")
}
@@ -91,7 +91,7 @@ func (s *server) sshHandleRecvPack(session gliderSSH.Session, pubkey, repoIdenti
fmt.Fprintln(session.Stderr(), "Error while generating cookie:", err)
}
- s.packPasses.Store(cookie, packPass{
+ s.PackPasses.Store(cookie, packPass{
session: session,
pubkey: pubkey,
directAccess: directAccess,
@@ -104,13 +104,13 @@ func (s *server) sshHandleRecvPack(session gliderSSH.Session, pubkey, repoIdenti
contribReq: contribReq,
userType: userType,
})
- defer s.packPasses.Delete(cookie)
+ defer s.PackPasses.Delete(cookie)
// The Delete won't execute until proc.Wait returns unless something
// horribly wrong such as a panic occurs.
proc := exec.CommandContext(session.Context(), "git-receive-pack", repoPath)
proc.Env = append(os.Environ(),
- "LINDENII_FORGE_HOOKS_SOCKET_PATH="+s.config.Hooks.Socket,
+ "LINDENII_FORGE_HOOKS_SOCKET_PATH="+s.Config.Hooks.Socket,
"LINDENII_FORGE_HOOKS_COOKIE="+cookie,
)
proc.Stdin = session
diff --git a/ssh_handle_upload_pack.go b/ssh_handle_upload_pack.go
index 7f2a52c..45ecfd8 100644
--- a/ssh_handle_upload_pack.go
+++ b/ssh_handle_upload_pack.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"fmt"
@@ -13,14 +13,14 @@ import (
// sshHandleUploadPack handles clones/fetches. It just uses git-upload-pack
// and has no ACL checks.
-func (s *server) sshHandleUploadPack(session glider_ssh.Session, pubkey, repoIdentifier string) (err error) {
+func (s *Server) sshHandleUploadPack(session glider_ssh.Session, pubkey, repoIdentifier string) (err error) {
var repoPath string
if _, _, _, repoPath, _, _, _, _, err = s.getRepoInfo2(session.Context(), repoIdentifier, pubkey); err != nil {
return err
}
proc := exec.CommandContext(session.Context(), "git-upload-pack", repoPath)
- proc.Env = append(os.Environ(), "LINDENII_FORGE_HOOKS_SOCKET_PATH="+s.config.Hooks.Socket)
+ proc.Env = append(os.Environ(), "LINDENII_FORGE_HOOKS_SOCKET_PATH="+s.Config.Hooks.Socket)
proc.Stdin = session
proc.Stdout = session
proc.Stderr = session.Stderr()
diff --git a/ssh_server.go b/ssh_server.go
index afb0d95..ed303b9 100644
--- a/ssh_server.go
+++ b/ssh_server.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"fmt"
@@ -19,13 +19,13 @@ import (
// serveSSH serves SSH on a [net.Listener]. The listener should generally be a
// TCP listener, although AF_UNIX SOCK_STREAM listeners may be appropriate in
// rare cases.
-func (s *server) serveSSH(listener net.Listener) error {
+func (s *Server) serveSSH(listener net.Listener) error {
var hostKeyBytes []byte
var hostKey goSSH.Signer
var err error
var server *gliderSSH.Server
- if hostKeyBytes, err = os.ReadFile(s.config.SSH.Key); err != nil {
+ if hostKeyBytes, err = os.ReadFile(s.Config.SSH.Key); err != nil {
return err
}
@@ -33,9 +33,9 @@ func (s *server) serveSSH(listener net.Listener) error {
return err
}
- s.serverPubkey = hostKey.PublicKey()
- s.serverPubkeyString = misc.BytesToString(goSSH.MarshalAuthorizedKey(s.serverPubkey))
- s.serverPubkeyFP = goSSH.FingerprintSHA256(s.serverPubkey)
+ s.ServerPubkey = hostKey.PublicKey()
+ s.ServerPubkeyString = misc.BytesToString(goSSH.MarshalAuthorizedKey(s.ServerPubkey))
+ s.ServerPubkeyFP = goSSH.FingerprintSHA256(s.ServerPubkey)
server = &gliderSSH.Server{
Handler: func(session gliderSSH.Session) {
@@ -46,7 +46,7 @@ func (s *server) serveSSH(listener net.Listener) error {
}
slog.Info("incoming ssh", "addr", session.RemoteAddr().String(), "key", clientPubkeyStr, "command", session.RawCommand())
- fmt.Fprintln(session.Stderr(), ansiec.Blue+"Lindenii Forge "+VERSION+", source at "+strings.TrimSuffix(s.config.HTTP.Root, "/")+"/-/source/"+ansiec.Reset+"\r")
+ fmt.Fprintln(session.Stderr(), ansiec.Blue+"Lindenii Forge "+VERSION+", source at "+strings.TrimSuffix(s.Config.HTTP.Root, "/")+"/-/source/"+ansiec.Reset+"\r")
cmd := session.Command()
diff --git a/ssh_utils.go b/ssh_utils.go
index 02069dd..8f04209 100644
--- a/ssh_utils.go
+++ b/ssh_utils.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"context"
@@ -18,7 +18,7 @@ var errIllegalSSHRepoPath = errors.New("illegal SSH repo path")
// getRepoInfo2 also fetches repo information... it should be deprecated and
// implemented in individual handlers.
-func (s *server) getRepoInfo2(ctx context.Context, sshPath, sshPubkey string) (groupPath []string, repoName string, repoID int, repoPath string, directAccess bool, contribReq, userType string, userID int, err error) {
+func (s *Server) getRepoInfo2(ctx context.Context, sshPath, sshPubkey string) (groupPath []string, repoName string, repoID int, repoPath string, directAccess bool, contribReq, userType string, userID int, err error) {
var segments []string
var sepIndex int
var moduleType, moduleName string
diff --git a/users.go b/users.go
index 1b31f3a..3f57a35 100644
--- a/users.go
+++ b/users.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
-package main
+package forge
import (
"context"
@@ -12,10 +12,10 @@ import (
// addUserSSH adds a new user solely based on their SSH public key.
//
// TODO: Audit all users of this function.
-func (s *server) addUserSSH(ctx context.Context, pubkey string) (userID int, err error) {
+func (s *Server) addUserSSH(ctx context.Context, pubkey string) (userID int, err error) {
var txn pgx.Tx
- if txn, err = s.database.Begin(ctx); err != nil {
+ if txn, err = s.Database.Begin(ctx); err != nil {
return
}
defer func() {
diff --git a/version.go b/version.go
index c5e5aab..92fc82b 100644
--- a/version.go
+++ b/version.go
@@ -1,3 +1,3 @@
-package main
+package forge
var VERSION = "unknown" //nolint:gochecknoglobals