This commit is contained in:
2026-10-20 02:43:27 +01:00
commit 545a9b6b88
21 changed files with 3654 additions and 0 deletions
+133
View File
@@ -0,0 +1,133 @@
// 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
}