134 lines
4.8 KiB
Go
134 lines
4.8 KiB
Go
// Copyright (C) 2026 Dcntrlized
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published
|
|
// by the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
// Package config is the complete, environment-driven runtime configuration of
|
|
// the license bot. The binary takes no arguments: the container image carries
|
|
// no secrets and the deployment decides everything through the environment.
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// Config is the complete runtime configuration of the bot.
|
|
type Config struct {
|
|
// GiteaURL is the instance base URL the bot calls, e.g. http://gitea:3000
|
|
// on an instance whose Gitea shares this Docker daemon. Deliberately
|
|
// configured here instead of taken from webhook payloads: the payload's
|
|
// clone_url follows the instance ROOT_URL, which may be stale.
|
|
GiteaURL string
|
|
// PublicURL is the instance URL a *human* should follow, used only for the
|
|
// links in the bot's PR comments. On an instance reached over the internal
|
|
// alias (GITEA_URL=http://gitea:3000) that alias is not resolvable from a
|
|
// browser, so the comment must link the public name instead. Defaults to
|
|
// GiteaURL.
|
|
PublicURL string
|
|
// Token is a bot-account token with write access to the target repositories.
|
|
// It lives only in this process's environment -- never in a workflow.
|
|
Token string
|
|
// WebhookSecret verifies X-Gitea-Signature on incoming webhook deliveries.
|
|
// Webhooks are the only trigger, so this is required.
|
|
WebhookSecret string
|
|
// Repos is the allowlist of owner/name repositories the bot may touch.
|
|
Repos []string
|
|
// Listen is the HTTP listen address (webhook + health route).
|
|
Listen string
|
|
// BotName/BotEmail identify the licensing commit's committer.
|
|
BotName string
|
|
BotEmail string
|
|
// Holder is passed to addlicense -c.
|
|
Holder string
|
|
// AddlicenseBin is the addlicense executable (in PATH by default).
|
|
AddlicenseBin string
|
|
// WorkRoot is where per-PR git worktrees are created.
|
|
WorkRoot string
|
|
// DryRun inspects and comments but never commits or pushes.
|
|
DryRun bool
|
|
// CommentMarker identifies the bot's own PR comments so they can be
|
|
// replaced (deleted and reposted, never edited in place: Gitea keeps a
|
|
// comment's position, so an edited message stays buried in the timeline).
|
|
CommentMarker string
|
|
// StatusContext is the commit-status context the bot posts and the merge
|
|
// gate requires. It must match the branch-protection string byte for byte.
|
|
StatusContext string
|
|
}
|
|
|
|
// FromEnv builds the configuration from the process environment.
|
|
func FromEnv() (Config, error) {
|
|
c := Config{
|
|
GiteaURL: strings.TrimRight(env("GITEA_URL", ""), "/"),
|
|
PublicURL: strings.TrimRight(env("GITEA_PUBLIC_URL", ""), "/"),
|
|
Token: os.Getenv("GITEA_TOKEN"),
|
|
WebhookSecret: os.Getenv("WEBHOOK_SECRET"),
|
|
Repos: splitList(os.Getenv("REPOS")),
|
|
Listen: env("LISTEN", ":8080"),
|
|
BotName: env("BOT_NAME", "Dcntrlized Auto CopyLeft Bot"),
|
|
BotEmail: env("BOT_EMAIL", "[email protected]"),
|
|
Holder: env("HOLDER", "Dcntrlized"),
|
|
AddlicenseBin: env("ADDLICENSE_BIN", "addlicense"),
|
|
WorkRoot: env("WORK_ROOT", os.TempDir()),
|
|
DryRun: truthy(os.Getenv("DRY_RUN")),
|
|
CommentMarker: env("COMMENT_MARKER", "<!-- dcntrlized-copyleft-bot -->"),
|
|
StatusContext: env("STATUS_CONTEXT", "license-bot / addlicense"),
|
|
}
|
|
|
|
if c.Token == "" {
|
|
return c, fmt.Errorf("GITEA_TOKEN is required")
|
|
}
|
|
if len(c.Repos) == 0 {
|
|
return c, fmt.Errorf("REPOS is required (comma-separated owner/name list)")
|
|
}
|
|
if c.WebhookSecret == "" {
|
|
return c, fmt.Errorf("WEBHOOK_SECRET is required: webhooks are the only trigger")
|
|
}
|
|
if c.PublicURL == "" {
|
|
c.PublicURL = c.GiteaURL
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
// Allows reports whether repo (owner/name) is on the allowlist.
|
|
func (c Config) Allows(repo string) bool {
|
|
for _, r := range c.Repos {
|
|
if strings.EqualFold(r, repo) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func env(key, def string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return def
|
|
}
|
|
|
|
func truthy(v string) bool {
|
|
return v == "1" || strings.EqualFold(v, "true") || strings.EqualFold(v, "yes")
|
|
}
|
|
|
|
func splitList(v string) []string {
|
|
var out []string
|
|
for _, p := range strings.Split(v, ",") {
|
|
if p = strings.TrimSpace(p); p != "" {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
return out
|
|
}
|