Files
Alphaeus Mote 1f77473d8f feat(server): trust local proxies by default, seed built-in schedules
Proxy / base-URL:
- ORCHESTRAD_TRUSTED_PROXIES now defaults to "local", trusting reverse
  proxies in loopback + RFC1918 + link-local/ULA ranges out of the box, so
  X-Forwarded-* (client IP, scheme, host) is honored behind an edge proxy
  without extra config. New keywords: local/private, all/any, none.
- OIDC redirect URI derivation now uses the trust-gated request base URL
  instead of reading X-Forwarded-Proto directly, and audit client IP now
  trusts the middleware-rewritten RemoteAddr rather than the raw (spoofable)
  X-Forwarded-For header. Both honor forwarded values only from trusted
  peers.

Schedules:
- Seed eight built-in schedules on startup (every 5/15/30 min, hourly,
  every 6/12h, daily, weekly), idempotent by name, so operators have
  ready-made cadences in the Schedules page and the rule editor's schedule
  dropdown without hand-building one.

Test covers the trusted-proxy keyword expansion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 17:14:49 -04:00

81 lines
2.7 KiB
Go

// Package services - built-in schedule seeding
package services
import (
"database/sql"
"github.com/Grace-Solutions/OrchestrAD/internal/logging"
"github.com/Grace-Solutions/OrchestrAD/internal/models"
"github.com/Grace-Solutions/OrchestrAD/internal/repository"
)
// defaultSchedule describes one built-in schedule offered out of the box.
type defaultSchedule struct {
name string
kind string // "Easy" or "Cron"
interval int // for Easy
unit string // for Easy: Minutes/Hours/Days
cron string // for Cron (6-field, UTC)
desc string
}
var builtinSchedules = []defaultSchedule{
{name: "Every 5 minutes", kind: "Easy", interval: 5, unit: "Minutes", desc: "Built-in"},
{name: "Every 15 minutes", kind: "Easy", interval: 15, unit: "Minutes", desc: "Built-in"},
{name: "Every 30 minutes", kind: "Easy", interval: 30, unit: "Minutes", desc: "Built-in"},
{name: "Hourly", kind: "Easy", interval: 1, unit: "Hours", desc: "Built-in"},
{name: "Every 6 hours", kind: "Easy", interval: 6, unit: "Hours", desc: "Built-in"},
{name: "Every 12 hours", kind: "Easy", interval: 12, unit: "Hours", desc: "Built-in"},
{name: "Daily (00:00 UTC)", kind: "Cron", cron: "0 0 0 * * *", desc: "Built-in — every day at midnight UTC"},
{name: "Weekly (Sun 00:00 UTC)", kind: "Cron", cron: "0 0 0 * * 0", desc: "Built-in — every Sunday at midnight UTC"},
}
// EnsureDefaultSchedules idempotently seeds the built-in schedules so operators
// have ready-made cadences (in the Schedules page and the rule editor) without
// hand-building one. Existing schedules with the same name are left untouched;
// deleting a built-in schedule will not resurrect it within the same run but it
// reappears on next startup unless renamed.
func EnsureDefaultSchedules(db *sql.DB, logger *logging.Logger) {
repo := repository.NewScheduleRepository(db)
existing, _, err := repo.List(0, 500)
if err != nil {
logger.Warn("Schedules", "Could not list schedules for seeding: %v", err)
return
}
have := make(map[string]bool, len(existing))
for _, s := range existing {
have[s.Name] = true
}
seeded := 0
for _, d := range builtinSchedules {
if have[d.name] {
continue
}
desc := d.desc
sched := &models.Schedule{
Name: d.name,
Description: &desc,
IsEnabled: true,
ScheduleKind: d.kind,
TimezoneMode: "UTC",
}
if d.kind == "Easy" {
v, u := d.interval, d.unit
sched.EasyIntervalValue = &v
sched.EasyIntervalUnit = &u
} else {
c := d.cron
sched.CronExpression = &c
}
if err := repo.Create(sched); err != nil {
logger.Warn("Schedules", "Failed to seed built-in schedule %q: %v", d.name, err)
continue
}
seeded++
}
if seeded > 0 {
logger.Info("Schedules", "Seeded %d built-in schedule(s)", seeded)
}
}