diff --git a/.env.example b/.env.example index 5d31b49..5289a16 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,7 @@ AUTH_HEADER_USER=X-authentik-username AUTH_HEADER_EMAIL=X-authentik-email FORGEJO_BASE_URL= FORGEJO_TOKEN= +FORGEJO_GIT_USERNAME=oauth2 FORGEJO_ORG= CADDY_ROUTE_ENABLED=false CADDY_ADMIN_URL=http://localhost:2019 diff --git a/README.md b/README.md index f0e7e03..b5f62e7 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Important: - `DB_PROVISION_ENABLED=true` provisions a dedicated PostgreSQL role/database per app. - `DB_PROVISION_ADMIN_URL` is the admin DSN used for provisioning (defaults to `DATABASE_URL`). - `FORGEJO_BASE_URL` + `FORGEJO_TOKEN` enable repository creation. +- `FORGEJO_GIT_USERNAME` is used with `FORGEJO_TOKEN` for non-interactive HTTPS git clone/fetch (default `oauth2`). - `FORGEJO_ORG` switches creation from personal repos to org repos. - `UNIX_SOCKET_PATH` enables unix socket listener in addition to `LISTEN_ADDR`. - `CADDY_ROUTE_ENABLED=true` enables route provisioning. diff --git a/internal/app/config.go b/internal/app/config.go index dd83274..f51c430 100644 --- a/internal/app/config.go +++ b/internal/app/config.go @@ -19,6 +19,7 @@ func loadConfig() (Config, error) { AuthHeaderEmail: getenv("AUTH_HEADER_EMAIL", "X-authentik-email"), ForgejoBaseURL: strings.TrimRight(getenv("FORGEJO_BASE_URL", ""), "/"), ForgejoToken: getenv("FORGEJO_TOKEN", ""), + ForgejoGitUsername: getenv("FORGEJO_GIT_USERNAME", "oauth2"), ForgejoOrg: getenv("FORGEJO_ORG", ""), DBProvisionAdminURL: getenv("DB_PROVISION_ADMIN_URL", ""), DBProvisionEnabled: getenvBool("DB_PROVISION_ENABLED", true), diff --git a/internal/app/deploy_service.go b/internal/app/deploy_service.go index b61ab7b..ce25b94 100644 --- a/internal/app/deploy_service.go +++ b/internal/app/deploy_service.go @@ -8,6 +8,7 @@ import ( "fmt" "log" "net/http" + "net/url" "os" "path/filepath" "strconv" @@ -117,13 +118,14 @@ func (a *App) buildProjectBinary(p Project) error { func (a *App) syncProjectRepoFromMain(p Project, projectDir string) error { repoDir := filepath.Join(projectDir, "repo") + repoCloneURL := a.repoURLForGitAuth(p.RepoURL) if _, err := os.Stat(repoDir); os.IsNotExist(err) { - if err := runCmd("git", "clone", "--branch", "main", "--single-branch", p.RepoURL, repoDir); err != nil { + if err := runCmd("git", "clone", "--branch", "main", "--single-branch", repoCloneURL, repoDir); err != nil { return err } return nil } - if err := runCmd("git", "-C", repoDir, "remote", "set-url", "origin", p.RepoURL); err != nil { + if err := runCmd("git", "-C", repoDir, "remote", "set-url", "origin", repoCloneURL); err != nil { return err } if err := runCmd("git", "-C", repoDir, "fetch", "origin", "main", "--prune"); err != nil { @@ -134,3 +136,23 @@ func (a *App) syncProjectRepoFromMain(p Project, projectDir string) error { } return nil } + +func (a *App) repoURLForGitAuth(repoURL string) string { + if repoURL == "" || a.cfg.ForgejoBaseURL == "" || a.cfg.ForgejoToken == "" { + return repoURL + } + ru, err := url.Parse(repoURL) + if err != nil || ru.Scheme != "https" { + return repoURL + } + fu, err := url.Parse(a.cfg.ForgejoBaseURL) + if err != nil { + return repoURL + } + if !strings.EqualFold(ru.Hostname(), fu.Hostname()) { + return repoURL + } + u := *ru + u.User = url.UserPassword(a.cfg.ForgejoGitUsername, a.cfg.ForgejoToken) + return u.String() +} diff --git a/internal/app/types.go b/internal/app/types.go index 3f83f19..741097e 100644 --- a/internal/app/types.go +++ b/internal/app/types.go @@ -16,6 +16,7 @@ type Config struct { AuthHeaderEmail string ForgejoBaseURL string ForgejoToken string + ForgejoGitUsername string ForgejoOrg string DBProvisionAdminURL string DBProvisionEnabled bool