added git ssh support and ablity to download repo via zip, tar.gz, and bundle

This commit is contained in:
2026-05-17 20:09:55 +02:00
parent e7c64e583b
commit 5147c6bddb
13 changed files with 633 additions and 20 deletions
+61
View File
@@ -0,0 +1,61 @@
package handlers
import (
"fmt"
"net/http"
"xorm.io/xorm"
gitdomain "github.com/forgeo/forgebucket/internal/domain/git"
)
type ArchiveHandler struct {
db *xorm.Engine
}
func NewArchiveHandler(db *xorm.Engine) *ArchiveHandler {
return &ArchiveHandler{db: db}
}
var archiveFormats = map[string]struct {
contentType string
ext string
}{
"zip": {"application/zip", "zip"},
"tar.gz": {"application/x-tar", "tar.gz"},
"bundle": {"application/octet-stream", "bundle"},
}
func (h *ArchiveHandler) Download(w http.ResponseWriter, r *http.Request) {
repo, ok := resolveRepo(h.db, w, r)
if !ok {
return
}
format := r.URL.Query().Get("format")
if format == "" {
format = "zip"
}
meta, allowed := archiveFormats[format]
if !allowed {
jsonError(w, "format must be zip, tar.gz, or bundle", http.StatusBadRequest)
return
}
ref := r.URL.Query().Get("ref")
if ref == "" {
ref = repo.DefaultBranch
}
if ref == "" {
ref = "HEAD"
}
filename := fmt.Sprintf("%s-%s.%s", repo.Name, ref, meta.ext)
w.Header().Set("Content-Type", meta.contentType)
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
if err := gitdomain.ArchiveStream(repo.DiskPath, ref, format, w); err != nil {
// Headers already written — can't change status code; just log and close.
_ = err
}
}
+45
View File
@@ -0,0 +1,45 @@
package handlers
import (
"encoding/json"
"net/http"
"net/url"
"github.com/forgeo/forgebucket/internal/config"
)
type InstanceHandler struct {
cfg *config.Config
}
func NewInstanceHandler(cfg *config.Config) *InstanceHandler {
return &InstanceHandler{cfg: cfg}
}
// Get returns the public instance configuration needed by the frontend to
// construct clone URLs (SSH host, SSH port, instance name).
func (h *InstanceHandler) Get(w http.ResponseWriter, r *http.Request) {
sshHost := h.sshHost(r)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{ //nolint:errcheck
"sshHost": sshHost,
"sshPort": h.cfg.SSHPort,
"instanceName": h.cfg.InstanceName,
})
}
// sshHost extracts the hostname from InstanceURL. Falls back to the request
// host when InstanceURL is unset (common in local development).
func (h *InstanceHandler) sshHost(r *http.Request) string {
if h.cfg.InstanceURL != "" {
if u, err := url.Parse(h.cfg.InstanceURL); err == nil && u.Hostname() != "" {
return u.Hostname()
}
}
// Strip port from Host header if present.
host := r.Host
if u, err := url.Parse("http://" + host); err == nil {
return u.Hostname()
}
return host
}
+4
View File
@@ -79,6 +79,8 @@ func New(cfg *config.Config, engine *xorm.Engine, store sessions.Store, bus even
ociH := handlers.NewOCIRegistryHandler(engine, ociRegistry)
scanH := handlers.NewScanningHandler(engine, scanner)
vulnH := handlers.NewVulnScanHandler(engine, vulnScanner)
archiveH := handlers.NewArchiveHandler(engine)
instanceH := handlers.NewInstanceHandler(cfg)
// ── Git smart-HTTP transport ───────────────────────────────────────────────
// Regex constraint ensures only *.git paths match, so asset/SPA URLs
@@ -99,6 +101,7 @@ func New(cfg *config.Config, engine *xorm.Engine, store sessions.Store, bus even
// ── Public ────────────────────────────────────────────────────────────
r.Get("/explore/repos", exploreH.Repos)
r.Get("/explore/users", exploreH.Users)
r.Get("/instance", instanceH.Get)
// Generates a CSRF token + cookie. SPA calls this once on load.
r.Get("/csrf", func(w http.ResponseWriter, r *http.Request) {
@@ -177,6 +180,7 @@ func New(cfg *config.Config, engine *xorm.Engine, store sessions.Store, bus even
r.With(csrf).Put("/blob", repoH.UpdateBlob)
r.Get("/commits", repoH.Commits)
r.Get("/branches", repoH.Branches)
r.Get("/archive", archiveH.Download)
r.Get("/diff", repoH.Diff)
r.Route("/pulls", func(r chi.Router) {
r.Get("/", prH.List)