mirror of
https://github.com/anotherhadi/iknowyou.git
synced 2026-04-12 00:47:26 +02:00
init
This commit is contained in:
62
back/config/builtin.go
Normal file
62
back/config/builtin.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package config
|
||||
|
||||
import "gopkg.in/yaml.v3"
|
||||
|
||||
// BuiltinProfile is a hardcoded, read-only profile with optional tool config overrides.
|
||||
type BuiltinProfile struct {
|
||||
Notes string
|
||||
Profile Profile
|
||||
Tools map[string]map[string]any
|
||||
}
|
||||
|
||||
var BuiltinProfiles = map[string]BuiltinProfile{
|
||||
"default": {
|
||||
Notes: "Standard profile. All tools are active with default settings.",
|
||||
Profile: Profile{},
|
||||
},
|
||||
"hard": {
|
||||
Notes: "Aggressive profile. All tools are active, including those that may send notifications to the target.",
|
||||
Profile: Profile{},
|
||||
Tools: map[string]map[string]any{
|
||||
"user-scanner": {"allow_loud": true},
|
||||
"github-recon": {"deepscan": true},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func ApplyBuiltinToolOverride(profileName, toolName string, dst any) error {
|
||||
builtin, ok := BuiltinProfiles[profileName]
|
||||
if !ok || builtin.Tools == nil {
|
||||
return nil
|
||||
}
|
||||
overrides, hasOverride := builtin.Tools[toolName]
|
||||
if !hasOverride {
|
||||
return nil
|
||||
}
|
||||
b, err := yaml.Marshal(overrides)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return yaml.Unmarshal(b, dst)
|
||||
}
|
||||
|
||||
func ActiveToolsForProfile(p Profile, allToolNames []string) []string {
|
||||
active := allToolNames
|
||||
if len(p.Enabled) > 0 {
|
||||
active = p.Enabled
|
||||
}
|
||||
if len(p.Disabled) > 0 {
|
||||
blacklist := make(map[string]struct{}, len(p.Disabled))
|
||||
for _, n := range p.Disabled {
|
||||
blacklist[n] = struct{}{}
|
||||
}
|
||||
var filtered []string
|
||||
for _, n := range active {
|
||||
if _, skip := blacklist[n]; !skip {
|
||||
filtered = append(filtered, n)
|
||||
}
|
||||
}
|
||||
active = filtered
|
||||
}
|
||||
return active
|
||||
}
|
||||
144
back/config/config.go
Normal file
144
back/config/config.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Tools map[string]yaml.Node `yaml:"tools" json:"tools"`
|
||||
Profiles map[string]Profile `yaml:"profiles" json:"profiles"`
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
Notes string `yaml:"notes,omitempty" json:"notes,omitempty"`
|
||||
Tools map[string]yaml.Node `yaml:"tools" json:"tools"`
|
||||
Enabled []string `yaml:"enabled" json:"enabled"`
|
||||
Disabled []string `yaml:"disabled" json:"disabled"`
|
||||
}
|
||||
|
||||
func (c *Config) DecodeEffective(toolName, profileName string, dst any) error {
|
||||
if node, ok := c.Tools[toolName]; ok {
|
||||
if err := node.Decode(dst); err != nil {
|
||||
return fmt.Errorf("config: decoding global config for tool %q: %w", toolName, err)
|
||||
}
|
||||
}
|
||||
|
||||
if profileName != "" {
|
||||
// Builtin profiles have their overrides defined in Go, not in YAML.
|
||||
if _, isBuiltin := BuiltinProfiles[profileName]; isBuiltin {
|
||||
return ApplyBuiltinToolOverride(profileName, toolName, dst)
|
||||
}
|
||||
p, ok := c.Profiles[profileName]
|
||||
if !ok {
|
||||
return fmt.Errorf("config: unknown profile %q", profileName)
|
||||
}
|
||||
if node, ok := p.Tools[toolName]; ok {
|
||||
if err := node.Decode(dst); err != nil {
|
||||
return fmt.Errorf("config: decoding profile %q override for tool %q: %w", profileName, toolName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) ActiveTools(profileName string, allToolNames []string) ([]string, error) {
|
||||
if profileName == "" {
|
||||
return allToolNames, nil
|
||||
}
|
||||
if builtin, ok := BuiltinProfiles[profileName]; ok {
|
||||
return ActiveToolsForProfile(builtin.Profile, allToolNames), nil
|
||||
}
|
||||
p, ok := c.Profiles[profileName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("config: unknown profile %q", profileName)
|
||||
}
|
||||
return ActiveToolsForProfile(p, allToolNames), nil
|
||||
}
|
||||
|
||||
// IsReadonly reports whether the config file at path cannot be written to.
|
||||
// Returns false if the file does not exist (it can still be created).
|
||||
func IsReadonly(path string) bool {
|
||||
f, err := os.OpenFile(path, os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return os.IsPermission(err)
|
||||
}
|
||||
f.Close()
|
||||
return false
|
||||
}
|
||||
|
||||
func Load(path string) (*Config, error) {
|
||||
f, err := os.Open(path)
|
||||
if os.IsNotExist(err) {
|
||||
log.Printf("config: %q not found, starting with empty config", path)
|
||||
return Default(), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("config: open %q: %w", path, err)
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
var cfg Config
|
||||
dec := yaml.NewDecoder(f)
|
||||
dec.KnownFields(true)
|
||||
if err := dec.Decode(&cfg); err != nil {
|
||||
return nil, fmt.Errorf("config: decode: %w", err)
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func Default() *Config {
|
||||
return &Config{
|
||||
Tools: make(map[string]yaml.Node),
|
||||
Profiles: make(map[string]Profile),
|
||||
}
|
||||
}
|
||||
|
||||
func Save(path string, cfg *Config) error {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("config: create %q: %w", path, err)
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
enc := yaml.NewEncoder(f)
|
||||
enc.SetIndent(2)
|
||||
if err := enc.Encode(cfg); err != nil {
|
||||
return fmt.Errorf("config: encode: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MergeNodePatch merges patch key-values into an existing yaml.Node (mapping).
|
||||
// If existing is a zero value, it starts from an empty mapping.
|
||||
func MergeNodePatch(existing yaml.Node, patch map[string]any) (yaml.Node, error) {
|
||||
var m map[string]any
|
||||
if existing.Kind != 0 {
|
||||
if err := existing.Decode(&m); err != nil {
|
||||
return yaml.Node{}, fmt.Errorf("config: decode node: %w", err)
|
||||
}
|
||||
}
|
||||
if m == nil {
|
||||
m = make(map[string]any)
|
||||
}
|
||||
for k, v := range patch {
|
||||
m[k] = v
|
||||
}
|
||||
|
||||
b, err := yaml.Marshal(m)
|
||||
if err != nil {
|
||||
return yaml.Node{}, fmt.Errorf("config: marshal: %w", err)
|
||||
}
|
||||
var doc yaml.Node
|
||||
if err := yaml.Unmarshal(b, &doc); err != nil {
|
||||
return yaml.Node{}, fmt.Errorf("config: unmarshal: %w", err)
|
||||
}
|
||||
if doc.Kind == yaml.DocumentNode && len(doc.Content) == 1 {
|
||||
return *doc.Content[0], nil
|
||||
}
|
||||
return doc, nil
|
||||
}
|
||||
64
back/config/env/env.go
vendored
Normal file
64
back/config/env/env.go
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Port int
|
||||
ConfigPath string
|
||||
FrontDir string // when set, serves the Astro static build at "/"
|
||||
SearchTTL time.Duration
|
||||
CleanupInterval time.Duration
|
||||
Demo bool // when true, disables searches and config mutations
|
||||
}
|
||||
|
||||
func Load() (*Config, error) {
|
||||
cfg := &Config{
|
||||
Port: 8080,
|
||||
ConfigPath: "/etc/iky/config.yaml",
|
||||
SearchTTL: 48 * time.Hour,
|
||||
CleanupInterval: time.Hour,
|
||||
}
|
||||
|
||||
if v := os.Getenv("IKY_PORT"); v != "" {
|
||||
p, err := strconv.Atoi(v)
|
||||
if err != nil || p < 1 || p > 65535 {
|
||||
return nil, fmt.Errorf("env: IKY_PORT %q is not a valid port number", v)
|
||||
}
|
||||
cfg.Port = p
|
||||
}
|
||||
|
||||
if v := os.Getenv("IKY_CONFIG"); v != "" {
|
||||
cfg.ConfigPath = v
|
||||
}
|
||||
|
||||
if v := os.Getenv("IKY_FRONT_DIR"); v != "" {
|
||||
cfg.FrontDir = v
|
||||
}
|
||||
|
||||
if v := os.Getenv("IKY_SEARCH_TTL"); v != "" {
|
||||
d, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("env: IKY_SEARCH_TTL %q is not a valid duration", v)
|
||||
}
|
||||
cfg.SearchTTL = d
|
||||
}
|
||||
|
||||
if v := os.Getenv("IKY_CLEANUP_INTERVAL"); v != "" {
|
||||
d, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("env: IKY_CLEANUP_INTERVAL %q is not a valid duration", v)
|
||||
}
|
||||
cfg.CleanupInterval = d
|
||||
}
|
||||
|
||||
if v := os.Getenv("IKY_DEMO"); v == "true" || v == "1" {
|
||||
cfg.Demo = true
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
Reference in New Issue
Block a user