From 71ab9b7f14118f02dd18cd733bd4e0ad19ece590 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Sat, 05 Apr 2025 20:21:32 +0800 Subject: [PATCH] config shall no longer be a global variable --- .golangci.yaml | 1 - acl.go | 2 +- config.go | 12 ++++++------ database.go | 2 +- fedauth.go | 2 +- git2d_deploy.go | 4 ++-- git_hooks_deploy.go | 8 ++++---- git_hooks_handle_linux.go | 10 +++++----- git_hooks_handle_other.go | 10 +++++----- git_init.go | 4 ++-- http_handle_branches.go | 6 +++--- http_handle_group_index.go | 6 +++--- http_handle_index.go | 4 ++-- http_handle_login.go | 4 ++-- http_handle_repo_index.go | 6 +++--- http_handle_repo_raw.go | 6 +++--- http_handle_repo_tree.go | 6 +++--- http_handle_repo_upload_pack.go | 4 ++-- http_server.go | 28 +++++++++++++--------------- irc.go | 20 ++++++++++---------- lmtp_server.go | 21 +++++++++++---------- main.go | 78 +++++++++++++++++++++++++++-------------------------- remote_url.go | 8 ++++---- server.go | 5 +++++ ssh_handle_receive_pack.go | 8 ++++---- ssh_handle_upload_pack.go | 6 +++--- ssh_server.go | 10 +++++----- ssh_utils.go | 4 ++-- diff --git a/.golangci.yaml b/.golangci.yaml index 73eff0c3bd457c4ba6bb22f715a075f98e14eccf..1c8c972ffaad3f066695f3f5975dcf28223e7302 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -5,7 +5,6 @@ - tenv - depguard - err113 # dynamically defined errors are fine for our purposes - forcetypeassert # type assertion failures are usually programming errors - - gochecknoglobals # doesn't matter since this isn't a library - gochecknoinits # we use inits sparingly for good reasons - godox # they're just used as markers for where needs improvements - ireturn # doesn't work well with how we use generics diff --git a/acl.go b/acl.go index 5f242d57b9099e1f617b856b726cdb8286209d83..44cd04b644a5473d8ac825112cf4a9af520a1680 100644 --- a/acl.go +++ b/acl.go @@ -13,7 +13,7 @@ // getRepoInfo returns the filesystem path and direct access permission for a // given repo and a provided ssh public key. // // TODO: Revamp. -func getRepoInfo(ctx context.Context, groupPath []string, repoName, sshPubkey string) (repoID int, fsPath string, access bool, contribReq, userType string, userID int, err error) { +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 = database.QueryRow(ctx, ` WITH RECURSIVE group_path_cte AS ( -- Start: match the first name in the path where parent_group IS NULL diff --git a/config.go b/config.go index 6220d6ff1b9bd1c64ebc9bdab2026c2024aab8e1..1bbc3a104b89cd54b6aff7cb7a2092dfd4e46b89 100644 --- a/config.go +++ b/config.go @@ -16,7 +16,7 @@ // config holds the global configuration used by this instance. There is // currently no synchronization mechanism, so it must not be modified after // request handlers are spawned. -var config struct { +type Config struct { HTTP struct { Net string `scfg:"net"` Addr string `scfg:"addr"` @@ -76,7 +76,7 @@ // TODO: Currently, it returns an error when the user specifies any unknown // 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 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 @@ } defer configFile.Close() decoder := scfg.NewDecoder(bufio.NewReader(configFile)) - if err = decoder.Decode(&config); err != nil { + if err = decoder.Decode(&s.config); err != nil { return err } - if config.DB.Type != "postgres" { + if s.config.DB.Type != "postgres" { return errors.New("unsupported database type") } - if database, err = pgxpool.New(context.Background(), config.DB.Conn); err != nil { + if database, err = pgxpool.New(context.Background(), s.config.DB.Conn); err != nil { return err } - globalData["forge_title"] = config.General.Title + globalData["forge_title"] = s.config.General.Title return nil } diff --git a/database.go b/database.go index a3f5ab8e2f06bd43f345be2b93c1007fa2273f83..18e753fee640e9c8b1cee19eacbacd085223fe9b 100644 --- a/database.go +++ b/database.go @@ -24,7 +24,7 @@ // 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 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 = database.Query(ctx, query, args...); err != nil { diff --git a/fedauth.go b/fedauth.go index 808fba505060535f675f4dd378b70a0e8532639c..46290e502c5b60b2027a6cab0ab06dfb6c206c51 100644 --- a/fedauth.go +++ b/fedauth.go @@ -17,7 +17,7 @@ ) // 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 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 diff --git a/git2d_deploy.go b/git2d_deploy.go index 09332a8d438cefa0f75fc4337bcb89adf265ff4b..f3a4cc9f6f3d8c146084d596085f4260e162e9ca 100644 --- a/git2d_deploy.go +++ b/git2d_deploy.go @@ -9,7 +9,7 @@ "io/fs" "os" ) -func deployGit2D() (err error) { +func (s *server) deployGit2D() (err error) { var srcFD fs.File var dstFD *os.File @@ -18,7 +18,7 @@ return err } defer srcFD.Close() - if dstFD, err = os.OpenFile(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_hooks_deploy.go b/git_hooks_deploy.go index c9039fecdb0aa83769105098af8fb855f31eb3cb..ea11d0c6c81bbbd1504e9e3559f60ff0043a71ae 100644 --- a/git_hooks_deploy.go +++ b/git_hooks_deploy.go @@ -14,7 +14,7 @@ // 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 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 @@ return err } defer srcFD.Close() - if dstFD, err = os.OpenFile(filepath.Join(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 @@ } // Go's embed filesystems do not store permissions; but in any case, // they would need to be 0o755: - if err = os.Chmod(filepath.Join(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(config.Hooks.Execs, "hookc"), filepath.Join(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 8c8d34b99bb68a64570a49c3d1eaee784e1516b0..37afba106405c98ab1da3c6031e3ed685daf31b0 100644 --- a/git_hooks_handle_linux.go +++ b/git_hooks_handle_linux.go @@ -34,7 +34,7 @@ ) // hooksHandler handles a connection from hookc via the // unix socket. -func hooksHandler(conn net.Conn) { +func (s *server) hooksHandler(conn net.Conn) { var ctx context.Context var cancel context.CancelFunc var ucred *syscall.Ucred @@ -187,7 +187,7 @@ writeRedError(sshStderr, "Invalid federated user identifier %#v does not contain a colon", fedUserID) return 1 } - ok, err := fedauth(ctx, packPass.userID, service, username, packPass.pubkey) + ok, err := s.fedauth(ctx, packPass.userID, service, username, packPass.pubkey) if err != nil { writeRedError(sshStderr, "Failed to verify federated user identifier %#v: %v", fedUserID, err) return 1 @@ -247,7 +247,7 @@ if err != nil { writeRedError(sshStderr, "Error creating merge request: %v", err) return 1 } - mergeRequestWebURL := fmt.Sprintf("%s/contrib/%d/", genHTTPRemoteURL(packPass.groupPath, packPass.repoName), newMRLocalID) + mergeRequestWebURL := fmt.Sprintf("%s/contrib/%d/", s.genHTTPRemoteURL(packPass.groupPath, packPass.repoName), newMRLocalID) fmt.Fprintln(sshStderr, ansiec.Blue+"Created merge request at", mergeRequestWebURL+ansiec.Reset) select { @@ -342,13 +342,13 @@ // serveGitHooks handles connections on the specified network listener and // 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 serveGitHooks(listener net.Listener) error { +func (s *server) serveGitHooks(listener net.Listener) error { for { conn, err := listener.Accept() if err != nil { return err } - go hooksHandler(conn) + go s.hooksHandler(conn) } } diff --git a/git_hooks_handle_other.go b/git_hooks_handle_other.go index fdeca8303b360e783a3a5325371d9ab2a05e2d45..6d5b08daeace239046700415bc590c38f4c1ffef 100644 --- a/git_hooks_handle_other.go +++ b/git_hooks_handle_other.go @@ -29,7 +29,7 @@ var errGetFD = errors.New("unable to get file descriptor") // hooksHandler handles a connection from hookc via the // unix socket. -func hooksHandler(conn net.Conn) { +func (s *server) hooksHandler(conn net.Conn) { var ctx context.Context var cancel context.CancelFunc var err error @@ -165,7 +165,7 @@ writeRedError(sshStderr, "Invalid federated user identifier %#v does not contain a colon", fedUserID) return 1 } - ok, err := fedauth(ctx, packPass.userID, service, username, packPass.pubkey) + ok, err := s.fedauth(ctx, packPass.userID, service, username, packPass.pubkey) if err != nil { writeRedError(sshStderr, "Failed to verify federated user identifier %#v: %v", fedUserID, err) return 1 @@ -225,7 +225,7 @@ if err != nil { writeRedError(sshStderr, "Error creating merge request: %v", err) return 1 } - mergeRequestWebURL := fmt.Sprintf("%s/contrib/%d/", genHTTPRemoteURL(packPass.groupPath, packPass.repoName), newMRLocalID) + mergeRequestWebURL := fmt.Sprintf("%s/contrib/%d/", s.genHTTPRemoteURL(packPass.groupPath, packPass.repoName), newMRLocalID) fmt.Fprintln(sshStderr, ansiec.Blue+"Created merge request at", mergeRequestWebURL+ansiec.Reset) select { @@ -320,13 +320,13 @@ // serveGitHooks handles connections on the specified network listener and // 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 serveGitHooks(listener net.Listener) error { +func (s *server) serveGitHooks(listener net.Listener) error { for { conn, err := listener.Accept() if err != nil { return err } - go hooksHandler(conn) + go s.hooksHandler(conn) } } diff --git a/git_init.go b/git_init.go index f1a283ef330b34bf1e87d4518da7cb408525c2b3..1800c5acf2ec026d306b60f357abb730cfe95f5e 100644 --- a/git_init.go +++ b/git_init.go @@ -11,7 +11,7 @@ ) // gitInit initializes a bare git repository with the forge-deployed hooks // directory as the hooksPath. -func 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 @@ if gitConf, err = repo.Config(); err != nil { return err } - gitConf.Raw.SetOption("core", gitFmtConfig.NoSubsection, "hooksPath", 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/http_handle_branches.go b/http_handle_branches.go index 48ba5ab0a641a6512e6dcc66f7c7346211c5e985..d386b8236e980ab53497e6a83b9dab57b87384f3 100644 --- a/http_handle_branches.go +++ b/http_handle_branches.go @@ -13,7 +13,7 @@ "github.com/go-git/go-git/v5/plumbing/storer" ) // httpHandleRepoBranches provides the branches page in repos. -func 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 @@ -37,8 +37,8 @@ }) } params["branches"] = branches - params["http_clone_url"] = genHTTPRemoteURL(groupPath, repoName) - params["ssh_clone_url"] = genSSHRemoteURL(groupPath, repoName) + params["http_clone_url"] = s.genHTTPRemoteURL(groupPath, repoName) + params["ssh_clone_url"] = s.genSSHRemoteURL(groupPath, repoName) params["notes"] = notes renderTemplate(writer, "repo_branches", params) diff --git a/http_handle_group_index.go b/http_handle_group_index.go index 568a38e76e61569171e03723f265afd73d5b21f6..16120a89e91d2ee1711aed6e601765019e425cc0 100644 --- a/http_handle_group_index.go +++ b/http_handle_group_index.go @@ -17,7 +17,7 @@ // 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 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 @@ -111,7 +111,7 @@ errorPage500(writer, params, "Error creating repo: "+err.Error()) return } - filePath := filepath.Join(config.Git.RepoDir, strconv.Itoa(newRepoID)+".git") + filePath := filepath.Join(s.config.Git.RepoDir, strconv.Itoa(newRepoID)+".git") _, err = database.Exec( request.Context(), @@ -126,7 +126,7 @@ errorPage500(writer, params, "Error updating repo path: "+err.Error()) return } - if err = gitInit(filePath); err != nil { + if err = s.gitInit(filePath); err != nil { errorPage500(writer, params, "Error initializing repo: "+err.Error()) return } diff --git a/http_handle_index.go b/http_handle_index.go index 5d2dc3e0aaac8d8717227f15e897f9b6e7c67720..755e7c4bb53f65d730b1fe51cecfca90fb28df91 100644 --- a/http_handle_index.go +++ b/http_handle_index.go @@ -12,11 +12,11 @@ ) // httpHandleIndex provides the main index page which includes a list of groups // and some global information such as SSH keys. -func 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 - groups, err = queryNameDesc(request.Context(), "SELECT name, COALESCE(description, '') FROM groups WHERE parent_group IS NULL") + groups, err = s.queryNameDesc(request.Context(), "SELECT name, COALESCE(description, '') FROM groups WHERE parent_group IS NULL") if err != nil { errorPage500(writer, params, "Error querying groups: "+err.Error()) return diff --git a/http_handle_login.go b/http_handle_login.go index 56c0a82c1474585b6bd5d81298db3afd52a438d2..ea1dbae6454d8ee7026f76170cbcac798393bdf7 100644 --- a/http_handle_login.go +++ b/http_handle_login.go @@ -16,7 +16,7 @@ "github.com/jackc/pgx/v5" ) // httpHandleLogin provides the login page for local users. -func 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 @@ -71,7 +71,7 @@ return } now = time.Now() - expiry = now.Add(time.Duration(config.HTTP.CookieExpiry) * time.Second) + expiry = now.Add(time.Duration(s.config.HTTP.CookieExpiry) * time.Second) cookie = http.Cookie{ Name: "session", diff --git a/http_handle_repo_index.go b/http_handle_repo_index.go index ef1b76ec09336173a27cef1b21f2181c7a5c6eac..c253fa95d5839df554b5d43ad245cd16cfa1cbc1 100644 --- a/http_handle_repo_index.go +++ b/http_handle_repo_index.go @@ -20,18 +20,18 @@ Message string } // httpHandleRepoIndex provides the front page of a repo using git2d. -func 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, _, _, _, _, _ := getRepoInfo(req.Context(), groupPath, repoName, "") // TODO: Don't use getRepoInfo + _, repoPath, _, _, _, _, _ := s.getRepoInfo(req.Context(), groupPath, repoName, "") // TODO: Don't use getRepoInfo var notes []string if strings.Contains(repoName, "\n") || sliceContainsNewlines(groupPath) { notes = append(notes, "Path contains newlines; HTTP Git access impossible") } - client, err := git2c.NewClient(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_raw.go b/http_handle_repo_raw.go index 3a4e152fbb9af4a6dd2c7d2762af394fa7d15c61..570030fb33f035f6a5adb917e05ff2c1be226ccd 100644 --- a/http_handle_repo_raw.go +++ b/http_handle_repo_raw.go @@ -15,16 +15,16 @@ ) // httpHandleRepoRaw serves raw files, or directory listings that point to raw // files. -func 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) pathSpec := strings.TrimSuffix(rawPathSpec, "/") params["path_spec"] = pathSpec - _, repoPath, _, _, _, _, _ := getRepoInfo(request.Context(), groupPath, repoName, "") + _, repoPath, _, _, _, _, _ := s.getRepoInfo(request.Context(), groupPath, repoName, "") - client, err := git2c.NewClient(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 9cdd9cd332b0d44081319fc6a07de1279b4df55e..7af6e3e75b0eb1844a6f860eaeb89ea91f3f89cb 100644 --- a/http_handle_repo_tree.go +++ b/http_handle_repo_tree.go @@ -16,16 +16,16 @@ // httpHandleRepoTree provides a friendly, syntax-highlighted view of // individual files, and provides directory views that link to these files. // // TODO: Do not highlight files that are too large. -func 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) pathSpec := strings.TrimSuffix(rawPathSpec, "/") params["path_spec"] = pathSpec - _, repoPath, _, _, _, _, _ := getRepoInfo(request.Context(), groupPath, repoName, "") + _, repoPath, _, _, _, _, _ := s.getRepoInfo(request.Context(), groupPath, repoName, "") - client, err := git2c.NewClient(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 e201193b9b8e038ffd6bdcc7b00e34fc059119e0..3d9170cefe91ffabb06a3d9455dc8afcc7dfb356 100644 --- a/http_handle_repo_upload_pack.go +++ b/http_handle_repo_upload_pack.go @@ -14,7 +14,7 @@ ) // httpHandleUploadPack handles incoming Git fetch/pull/clone's over the Smart // HTTP protocol. -func 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 @@ -67,7 +67,7 @@ writer.Header().Set("Transfer-Encoding", "chunked") writer.WriteHeader(http.StatusOK) cmd = exec.Command("git", "upload-pack", "--stateless-rpc", repoPath) - cmd.Env = append(os.Environ(), "LINDENII_FORGE_HOOKS_SOCKET_PATH="+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_server.go b/http_server.go index 3f8e36cc2d9dec50674c927ba77c2f0c7fb0f1d6..5c78533e524bb8a70d22a4dfe7bedbd5489f5888 100644 --- a/http_server.go +++ b/http_server.go @@ -15,15 +15,13 @@ "github.com/jackc/pgx/v5" "go.lindenii.runxiyu.org/forge/misc" ) -type forgeHTTPRouter struct{} - // ServeHTTP handles all incoming HTTP requests and routes them to the correct // location. // // TODO: This function is way too large. -func (router *forgeHTTPRouter) ServeHTTP(writer http.ResponseWriter, request *http.Request) { +func (s *server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { var remoteAddr string - if config.HTTP.ReverseProxy { + if s.config.HTTP.ReverseProxy { remoteAddrs, ok := request.Header["X-Forwarded-For"] if ok && len(remoteAddrs) == 1 { remoteAddr = remoteAddrs[0] @@ -75,7 +73,7 @@ } } if len(segments) == 0 { - httpHandleIndex(writer, request, params) + s.httpHandleIndex(writer, request, params) return } @@ -100,7 +98,7 @@ if segments[0] == "-" { switch segments[1] { case "login": - httpHandleLogin(writer, request, params) + s.httpHandleLogin(writer, request, params) return case "users": httpHandleUsers(writer, request, params) @@ -137,7 +135,7 @@ case sepIndex == -1: if misc.RedirectDir(writer, request) { return } - httpHandleGroupIndex(writer, request, params) + s.httpHandleGroupIndex(writer, request, params) case len(segments) == sepIndex+1: errorPage404(writer, params) return @@ -159,7 +157,7 @@ errorPage500(writer, params, err.Error()) } return case "git-upload-pack": - if err = httpHandleUploadPack(writer, request, params); err != nil { + if err = s.httpHandleUploadPack(writer, request, params); err != nil { errorPage500(writer, params, err.Error()) } return @@ -185,15 +183,15 @@ for _, part := range segments[:sepIndex+3] { repoURLRoot = repoURLRoot + url.PathEscape(part) + "/" } params["repo_url_root"] = repoURLRoot - params["repo_patch_mailing_list"] = repoURLRoot[1:len(repoURLRoot)-1] + "@" + config.LMTP.Domain - params["http_clone_url"] = genHTTPRemoteURL(groupPath, moduleName) - params["ssh_clone_url"] = genSSHRemoteURL(groupPath, moduleName) + 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) if len(segments) == sepIndex+3 { if misc.RedirectDir(writer, request) { return } - httpHandleRepoIndex(writer, request, params) + s.httpHandleRepoIndex(writer, request, params) return } @@ -212,12 +210,12 @@ } if len(segments) < sepIndex+5 && misc.RedirectDir(writer, request) { return } - httpHandleRepoTree(writer, request, params) + s.httpHandleRepoTree(writer, request, params) case "branches": if misc.RedirectDir(writer, request) { return } - httpHandleRepoBranches(writer, request, params) + s.httpHandleRepoBranches(writer, request, params) return case "raw": if misc.AnyContain(segments[sepIndex+4:], "/") { @@ -232,7 +230,7 @@ } if len(segments) < sepIndex+5 && misc.RedirectDir(writer, request) { return } - httpHandleRepoRaw(writer, request, params) + s.httpHandleRepoRaw(writer, request, params) case "log": if len(segments) > sepIndex+4 { errorPage400(writer, params, "Too many parameters") diff --git a/irc.go b/irc.go index 868fc052d85fe98c9fd2bc58645232922756dc46..69cc9f0328aeacab25616d6d4f476e4374bdcc3c 100644 --- a/irc.go +++ b/irc.go @@ -21,13 +21,13 @@ content T errorBack chan error } -func ircBotSession() error { +func (s *server) ircBotSession() error { var err error var underlyingConn net.Conn - if config.IRC.TLS { - underlyingConn, err = tls.Dial(config.IRC.Net, 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(config.IRC.Net, config.IRC.Addr) + underlyingConn, err = net.Dial(s.config.IRC.Net, s.config.IRC.Addr) } if err != nil { return err @@ -41,11 +41,11 @@ slog.Debug("irc tx", "line", s) return conn.WriteString(s + "\r\n") } - _, err = logAndWriteLn("NICK " + config.IRC.Nick) + _, err = logAndWriteLn("NICK " + s.config.IRC.Nick) if err != nil { return err } - _, err = logAndWriteLn("USER " + config.IRC.User + " 0 * :" + config.IRC.Gecos) + _, err = logAndWriteLn("USER " + s.config.IRC.User + " 0 * :" + s.config.IRC.Gecos) if err != nil { return err } @@ -86,7 +86,7 @@ c, ok := msg.Source.(irc.Client) if !ok { slog.Error("unable to convert source of JOIN to client") } - if c.Nick != config.IRC.Nick { + if c.Nick != s.config.IRC.Nick { continue } default: @@ -134,12 +134,12 @@ return <-ech } // TODO: Delay and warnings? -func ircBotLoop() { - ircSendBuffered = make(chan string, config.IRC.SendQ) +func (s *server) ircBotLoop() { + ircSendBuffered = make(chan string, s.config.IRC.SendQ) ircSendDirectChan = make(chan errorBack[string]) for { - err := ircBotSession() + err := s.ircBotSession() slog.Error("irc session error", "error", err) } } diff --git a/lmtp_server.go b/lmtp_server.go index fc3d92d5e51fcb8c98dcf5328dd1bc7c59113e59..e97ca55e37a2f5c704b1049ba7d02e63b2d869da 100644 --- a/lmtp_server.go +++ b/lmtp_server.go @@ -27,6 +27,7 @@ from string to []string ctx context.Context cancel context.CancelFunc + s server } func (session *lmtpSession) Reset() { @@ -62,13 +63,13 @@ } return session, nil } -func serveLMTP(listener net.Listener) error { +func (s *server) serveLMTP(listener net.Listener) error { smtpServer := smtp.NewServer(&lmtpHandler{}) smtpServer.LMTP = true - smtpServer.Domain = config.LMTP.Domain - smtpServer.Addr = config.LMTP.Socket - smtpServer.WriteTimeout = time.Duration(config.LMTP.WriteTimeout) * time.Second - smtpServer.ReadTimeout = time.Duration(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) } @@ -84,9 +85,9 @@ data []byte n int64 ) - n, err = io.CopyN(&buf, r, config.LMTP.MaxSize) + n, err = io.CopyN(&buf, r, session.s.config.LMTP.MaxSize) switch { - case n == 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) @@ -107,7 +108,7 @@ } switch strings.ToLower(email.Header.Get("Auto-Submitted")) { case "auto-generated", "auto-replied": - // Disregard automatic emails like OOO replies. + // Disregard automatic emails like OOO repliesession.s. slog.Info("ignoring automatic message", "from", session.from, "to", strings.Join(session.to, ","), @@ -132,10 +133,10 @@ _ = from for _, to := range to { - if !strings.HasSuffix(to, "@"+config.LMTP.Domain) { + if !strings.HasSuffix(to, "@"+session.s.config.LMTP.Domain) { continue } - localPart := to[:len(to)-len("@"+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 index de014173b56cd77b283007035ab044cefb55319a..452782595c8275a7ae11daad7fdde5a44413bd55 100644 --- a/main.go +++ b/main.go @@ -24,11 +24,13 @@ "path to configuration file", ) flag.Parse() - if err := loadConfig(*configPath); err != nil { + s := server{} + + if err := s.loadConfig(*configPath); err != nil { slog.Error("loading configuration", "error", err) os.Exit(1) } - if err := deployHooks(); err != nil { + if err := s.deployHooks(); err != nil { slog.Error("deploying hooks", "error", err) os.Exit(1) } @@ -36,14 +38,14 @@ if err := loadTemplates(); err != nil { slog.Error("loading templates", "error", err) os.Exit(1) } - if err := deployGit2D(); err != nil { + if err := s.deployGit2D(); err != nil { slog.Error("deploying git2d", "error", err) os.Exit(1) } // Launch Git2D go func() { - cmd := exec.Command(config.Git.DaemonPath, config.Git.Socket) //#nosec G204 + 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 { @@ -53,14 +55,14 @@ }() // UNIX socket listener for hooks { - hooksListener, err := net.Listen("unix", config.Hooks.Socket) + hooksListener, err := net.Listen("unix", s.config.Hooks.Socket) if errors.Is(err, syscall.EADDRINUSE) { - slog.Warn("removing existing socket", "path", config.Hooks.Socket) - if err = syscall.Unlink(config.Hooks.Socket); err != nil { - slog.Error("removing existing socket", "path", config.Hooks.Socket, "error", err) + 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", config.Hooks.Socket); err != nil { + if hooksListener, err = net.Listen("unix", s.config.Hooks.Socket); err != nil { slog.Error("listening hooks", "error", err) os.Exit(1) } @@ -68,9 +70,9 @@ } else if err != nil { slog.Error("listening hooks", "error", err) os.Exit(1) } - slog.Info("listening hooks on unix", "path", config.Hooks.Socket) + slog.Info("listening hooks on unix", "path", s.config.Hooks.Socket) go func() { - if err = serveGitHooks(hooksListener); err != nil { + if err = s.serveGitHooks(hooksListener); err != nil { slog.Error("serving hooks", "error", err) os.Exit(1) } @@ -79,14 +81,14 @@ } // UNIX socket listener for LMTP { - lmtpListener, err := net.Listen("unix", config.LMTP.Socket) + lmtpListener, err := net.Listen("unix", s.config.LMTP.Socket) if errors.Is(err, syscall.EADDRINUSE) { - slog.Warn("removing existing socket", "path", config.LMTP.Socket) - if err = syscall.Unlink(config.LMTP.Socket); err != nil { - slog.Error("removing existing socket", "path", config.LMTP.Socket, "error", err) + 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", config.LMTP.Socket); err != nil { + if lmtpListener, err = net.Listen("unix", s.config.LMTP.Socket); err != nil { slog.Error("listening LMTP", "error", err) os.Exit(1) } @@ -94,9 +96,9 @@ } else if err != nil { slog.Error("listening LMTP", "error", err) os.Exit(1) } - slog.Info("listening LMTP on unix", "path", config.LMTP.Socket) + slog.Info("listening LMTP on unix", "path", s.config.LMTP.Socket) go func() { - if err = serveLMTP(lmtpListener); err != nil { + if err = s.serveLMTP(lmtpListener); err != nil { slog.Error("serving LMTP", "error", err) os.Exit(1) } @@ -105,14 +107,14 @@ } // SSH listener { - sshListener, err := net.Listen(config.SSH.Net, config.SSH.Addr) - if errors.Is(err, syscall.EADDRINUSE) && config.SSH.Net == "unix" { - slog.Warn("removing existing socket", "path", config.SSH.Addr) - if err = syscall.Unlink(config.SSH.Addr); err != nil { - slog.Error("removing existing socket", "path", config.SSH.Addr, "error", err) + 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(config.SSH.Net, config.SSH.Addr); err != nil { + if sshListener, err = net.Listen(s.config.SSH.Net, s.config.SSH.Addr); err != nil { slog.Error("listening SSH", "error", err) os.Exit(1) } @@ -120,9 +122,9 @@ } else if err != nil { slog.Error("listening SSH", "error", err) os.Exit(1) } - slog.Info("listening SSH on", "net", config.SSH.Net, "addr", config.SSH.Addr) + slog.Info("listening SSH on", "net", s.config.SSH.Net, "addr", s.config.SSH.Addr) go func() { - if err = serveSSH(sshListener); err != nil { + if err = s.serveSSH(sshListener); err != nil { slog.Error("serving SSH", "error", err) os.Exit(1) } @@ -131,14 +133,14 @@ } // HTTP listener { - httpListener, err := net.Listen(config.HTTP.Net, config.HTTP.Addr) - if errors.Is(err, syscall.EADDRINUSE) && config.HTTP.Net == "unix" { - slog.Warn("removing existing socket", "path", config.HTTP.Addr) - if err = syscall.Unlink(config.HTTP.Addr); err != nil { - slog.Error("removing existing socket", "path", config.HTTP.Addr, "error", err) + 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(config.HTTP.Net, config.HTTP.Addr); err != nil { + if httpListener, err = net.Listen(s.config.HTTP.Net, s.config.HTTP.Addr); err != nil { slog.Error("listening HTTP", "error", err) os.Exit(1) } @@ -147,12 +149,12 @@ slog.Error("listening HTTP", "error", err) os.Exit(1) } 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, + 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", config.HTTP.Net, "addr", config.HTTP.Addr) + 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) @@ -162,7 +164,7 @@ }() } // IRC bot - go ircBotLoop() + go s.ircBotLoop() select {} } diff --git a/remote_url.go b/remote_url.go index f227dbf5ce953b1110bdbf6ba6b3219e1c960483..9f30993a271085907e18a62a03a531ad9f6fe2bd 100644 --- a/remote_url.go +++ b/remote_url.go @@ -14,12 +14,12 @@ // We don't use path.Join because it collapses multiple slashes into one. // genSSHRemoteURL generates SSH remote URLs from a given group path and repo // name. -func genSSHRemoteURL(groupPath []string, repoName string) string { - return strings.TrimSuffix(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 genHTTPRemoteURL(groupPath []string, repoName string) string { - return strings.TrimSuffix(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/server.go b/server.go new file mode 100644 index 0000000000000000000000000000000000000000..8f35913ab8b538743d699ed26227890b7181157c --- /dev/null +++ b/server.go @@ -0,0 +1,5 @@ +package main + +type server struct { + config Config +} diff --git a/ssh_handle_receive_pack.go b/ssh_handle_receive_pack.go index f8777fadd91a9b968679dd21a950283da88d040e..ed7ef40476b031b8c92fc1a44f44e65fadf4ce8e 100644 --- a/ssh_handle_receive_pack.go +++ b/ssh_handle_receive_pack.go @@ -34,8 +34,8 @@ // packPasses contains hook cookies mapped to their packPass. var packPasses = cmap.Map[string, packPass]{} // sshHandleRecvPack handles attempts to push to repos. -func sshHandleRecvPack(session gliderSSH.Session, pubkey, repoIdentifier string) (err error) { - groupPath, repoName, repoID, repoPath, directAccess, contribReq, userType, userID, err := getRepoInfo2(session.Context(), repoIdentifier, pubkey) +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 } @@ -55,7 +55,7 @@ return errors.New("repository has no core section in config") } hooksPath := repoConfCore.OptionAll("hooksPath") - if len(hooksPath) != 1 || hooksPath[0] != config.Hooks.Execs { + if len(hooksPath) != 1 || hooksPath[0] != s.config.Hooks.Execs { return errors.New("repository has hooksPath set to an unexpected value") } @@ -114,7 +114,7 @@ // 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="+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 d732a72f591eab38ce1653038791aabf8f718081..7f2a52c50699bf4189ed0ae9bc134c6e0221d52c 100644 --- a/ssh_handle_upload_pack.go +++ b/ssh_handle_upload_pack.go @@ -13,14 +13,14 @@ ) // sshHandleUploadPack handles clones/fetches. It just uses git-upload-pack // and has no ACL checks. -func 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 = getRepoInfo2(session.Context(), repoIdentifier, pubkey); err != nil { + 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="+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 c1b8c8a3b8ac851df9ba002201754733a0eada40..9cb3062e1d8d01852d37755923e4a5f3eb4d9992 100644 --- a/ssh_server.go +++ b/ssh_server.go @@ -25,13 +25,13 @@ // 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 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(config.SSH.Key); err != nil { + if hostKeyBytes, err = os.ReadFile(s.config.SSH.Key); err != nil { return err } @@ -52,7 +52,7 @@ clientPubkeyStr = strings.TrimSuffix(misc.BytesToString(goSSH.MarshalAuthorizedKey(clientPubkey)), "\n") } 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(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() @@ -67,13 +67,13 @@ if len(cmd) > 2 { fmt.Fprintln(session.Stderr(), "Too many arguments\r") return } - err = sshHandleUploadPack(session, clientPubkeyStr, cmd[1]) + err = s.sshHandleUploadPack(session, clientPubkeyStr, cmd[1]) case "git-receive-pack": if len(cmd) > 2 { fmt.Fprintln(session.Stderr(), "Too many arguments\r") return } - err = sshHandleRecvPack(session, clientPubkeyStr, cmd[1]) + err = s.sshHandleRecvPack(session, clientPubkeyStr, cmd[1]) default: fmt.Fprintln(session.Stderr(), "Unsupported command: "+cmd[0]+"\r") return diff --git a/ssh_utils.go b/ssh_utils.go index c906ab3514bcb5477ecd295765498e5203ee161d..02069dd9d524fcb1864a172925b00fdd0d7e2576 100644 --- a/ssh_utils.go +++ b/ssh_utils.go @@ -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 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 @@ -64,7 +64,7 @@ moduleName = segments[sepIndex+2] repoName = moduleName switch moduleType { case "repos": - _1, _2, _3, _4, _5, _6, _7 := getRepoInfo(ctx, groupPath, moduleName, sshPubkey) + _1, _2, _3, _4, _5, _6, _7 := s.getRepoInfo(ctx, groupPath, moduleName, sshPubkey) return groupPath, repoName, _1, _2, _3, _4, _5, _6, _7 default: return []string{}, "", 0, "", false, "", "", 0, errIllegalSSHRepoPath -- 2.48.1