Compare commits

..

3 Commits

Author SHA1 Message Date
Paul Payne
9d1abc3e90 Update env var name. 2025-10-12 00:41:04 +00:00
Paul Payne
9f2d5fc7fb Add dnsmasq endpoints. 2025-10-12 00:35:03 +00:00
Paul Payne
47c3b10be9 Dnsmasq management endpoints. 2025-10-11 23:01:26 +00:00
22 changed files with 401 additions and 77 deletions

43
BUILDING_WILD_API.md Normal file
View File

@@ -0,0 +1,43 @@
# Building the Wild Cloud Central API
## Dev Environment Requirements
- Go 1.21+
- GNU Make (for build automation)
## Principles
- A wild cloud instance is primarily data (YAML files for config, secrets, and manifests).
- Because a wild cloud instance is primarily data, a wild cloud instance can be managed by non-technical users through the webapp or by technical users by SSHing into the device (e.g. VSCode Remote SSH).
- Like v.PoC, we should only use gomplate templates for distinguishing between cloud instances. However, **within** a cloud instance, there should be no templating. The templates are compiled when being copied into the instances. This allows transparency and simple management by the user.
- Manage state and infrastructure idempotently.
- Cluster state should be the k8s cluster itself, not local files. It should be accessed via kubectl and talosctl.
- All wild cloud state should be stored on the filesystem in easy to read YAML files, and can be edited directly or through the webapp.
- All code should be simple and easy to understand.
- Avoid unnecessary complexity.
- Avoid unnecessary dependencies.
- Avoid unnecessary features.
- Avoid unnecessary abstractions.
- Avoid unnecessary comments.
- Avoid unnecessary configuration options.
- Avoid Helm. Use Kustomize.
- The API should be able to run on low-resource devices like a Raspberry Pi 4 (4GB RAM).
- The API should be able to manage multiple Wild Cloud instances on the LAN.
- The API should include functionality to manage a dnsmasq server on the same device. Currently, this is only used to resolve wild cloud domain names within the LAN to provide for private addresses on the LAN. The LAN router should be configured to use the Wild Central IP as its DNS server.
- The API is configurable to use various providers for:
- Wild Cloud Apps Directory provider (local FS, git repo, etc)
- DNS (built-in dnsmasq, external DNS server, etc)
### Coding Standards
- Use a standard Go project structure.
- Use Go modules.
- Use standard Go libraries wherever possible.
- Use popular, well-maintained libraries for common tasks (e.g. gorilla/mux for HTTP routing).
- Write unit tests for all functions and methods.
- Make and use common modules. For example, one module should handle all interactions with talosctl. Another modules should handle all interactions with kubectl.
- If the code is getting long and complex, break it into smaller modules.
### Features
- If WILD_CENTRAL_ENV environment variable is set to "development", the API should run in development mode.

View File

@@ -1,6 +1,6 @@
# Wild Central Daemon
# Wild Central API
The Wild Central Daemon is a lightweight service that runs on a local machine (e.g., a Raspberry Pi) to manage Wild Cloud instances on the local network. It provides an interface for users to interact with and manage their Wild Cloud environments.
The Wild Central API is a lightweight service that runs on a local machine (e.g., a Raspberry Pi) to manage Wild Cloud instances on the local network. It provides an interface for users to interact with and manage their Wild Cloud environments.
## Development

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
@@ -14,6 +15,7 @@ import (
"github.com/wild-cloud/wild-central/daemon/internal/config"
"github.com/wild-cloud/wild-central/daemon/internal/context"
"github.com/wild-cloud/wild-central/daemon/internal/dnsmasq"
"github.com/wild-cloud/wild-central/daemon/internal/instance"
"github.com/wild-cloud/wild-central/daemon/internal/operations"
"github.com/wild-cloud/wild-central/daemon/internal/secrets"
@@ -27,6 +29,7 @@ type API struct {
secrets *secrets.Manager
context *context.Manager
instance *instance.Manager
dnsmasq *dnsmasq.ConfigGenerator
broadcaster *operations.Broadcaster // SSE broadcaster for operation output
}
@@ -39,6 +42,13 @@ func NewAPI(dataDir, appsDir string) (*API, error) {
return nil, fmt.Errorf("failed to create instances directory: %w", err)
}
// Determine dnsmasq config path
dnsmasqConfigPath := "/etc/dnsmasq.d/wild-cloud.conf"
if os.Getenv("WILD_API_DNSMASQ_CONFIG_PATH") != "" {
dnsmasqConfigPath = os.Getenv("WILD_API_DNSMASQ_CONFIG_PATH")
log.Printf("Using custom dnsmasq config path: %s", dnsmasqConfigPath)
}
return &API{
dataDir: dataDir,
appsDir: appsDir,
@@ -46,6 +56,7 @@ func NewAPI(dataDir, appsDir string) (*API, error) {
secrets: secrets.NewManager(),
context: context.NewManager(dataDir),
instance: instance.NewManager(dataDir),
dnsmasq: dnsmasq.NewConfigGenerator(dnsmasqConfigPath),
broadcaster: operations.NewBroadcaster(),
}, nil
}
@@ -145,6 +156,13 @@ func (api *API) RegisterRoutes(r *mux.Router) {
r.HandleFunc("/api/v1/utilities/controlplane/ip", api.UtilitiesControlPlaneIP).Methods("GET")
r.HandleFunc("/api/v1/utilities/secrets/{secret}/copy", api.UtilitiesSecretCopy).Methods("POST")
r.HandleFunc("/api/v1/utilities/version", api.UtilitiesVersion).Methods("GET")
// dnsmasq management
r.HandleFunc("/api/v1/dnsmasq/status", api.DnsmasqStatus).Methods("GET")
r.HandleFunc("/api/v1/dnsmasq/config", api.DnsmasqGetConfig).Methods("GET")
r.HandleFunc("/api/v1/dnsmasq/restart", api.DnsmasqRestart).Methods("POST")
r.HandleFunc("/api/v1/dnsmasq/generate", api.DnsmasqGenerate).Methods("POST")
r.HandleFunc("/api/v1/dnsmasq/update", api.DnsmasqUpdate).Methods("POST")
}
// CreateInstance creates a new instance
@@ -168,10 +186,19 @@ func (api *API) CreateInstance(w http.ResponseWriter, r *http.Request) {
return
}
respondJSON(w, http.StatusCreated, map[string]string{
// Attempt to update dnsmasq configuration with all instances
// This is non-critical - include warning in response if it fails
response := map[string]interface{}{
"name": req.Name,
"message": "Instance created successfully",
})
}
if err := api.updateDnsmasqForAllInstances(); err != nil {
log.Printf("Warning: Could not update dnsmasq configuration: %v", err)
response["warning"] = fmt.Sprintf("dnsmasq update failed: %v. Use POST /api/v1/dnsmasq/update to retry.", err)
}
respondJSON(w, http.StatusCreated, response)
}
// ListInstances lists all instances

View File

@@ -0,0 +1,137 @@
package v1
import (
"fmt"
"log"
"net/http"
"github.com/wild-cloud/wild-central/daemon/internal/config"
)
// DnsmasqStatus returns the status of the dnsmasq service
func (api *API) DnsmasqStatus(w http.ResponseWriter, r *http.Request) {
status, err := api.dnsmasq.GetStatus()
if err != nil {
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get dnsmasq status: %v", err))
return
}
if status.Status != "active" {
w.WriteHeader(http.StatusServiceUnavailable)
}
respondJSON(w, http.StatusOK, status)
}
// DnsmasqGetConfig returns the current dnsmasq configuration
func (api *API) DnsmasqGetConfig(w http.ResponseWriter, r *http.Request) {
configContent, err := api.dnsmasq.ReadConfig()
if err != nil {
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to read dnsmasq config: %v", err))
return
}
respondJSON(w, http.StatusOK, map[string]interface{}{
"config_file": api.dnsmasq.GetConfigPath(),
"content": configContent,
})
}
// DnsmasqRestart restarts the dnsmasq service
func (api *API) DnsmasqRestart(w http.ResponseWriter, r *http.Request) {
if err := api.dnsmasq.RestartService(); err != nil {
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to restart dnsmasq: %v", err))
return
}
respondJSON(w, http.StatusOK, map[string]string{
"message": "dnsmasq service restarted successfully",
})
}
// DnsmasqGenerate generates the dnsmasq configuration without applying it (dry-run)
func (api *API) DnsmasqGenerate(w http.ResponseWriter, r *http.Request) {
// Get all instances
instanceNames, err := api.instance.ListInstances()
if err != nil {
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to list instances: %v", err))
return
}
// Load global config
globalConfigPath := api.getGlobalConfigPath()
globalCfg, err := config.LoadGlobalConfig(globalConfigPath)
if err != nil {
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to load global config: %v", err))
return
}
// Load all instance configs
var instanceConfigs []config.InstanceConfig
for _, name := range instanceNames {
instanceConfigPath := api.instance.GetInstanceConfigPath(name)
instanceCfg, err := config.LoadCloudConfig(instanceConfigPath)
if err != nil {
log.Printf("Warning: Could not load instance config for %s: %v", name, err)
continue
}
instanceConfigs = append(instanceConfigs, *instanceCfg)
}
// Generate config without writing or restarting
configContent := api.dnsmasq.Generate(globalCfg, instanceConfigs)
respondJSON(w, http.StatusOK, map[string]interface{}{
"message": "dnsmasq configuration generated (dry-run mode)",
"config": configContent,
})
}
// DnsmasqUpdate regenerates and updates the dnsmasq configuration with all instances
func (api *API) DnsmasqUpdate(w http.ResponseWriter, r *http.Request) {
if err := api.updateDnsmasqForAllInstances(); err != nil {
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to update dnsmasq: %v", err))
return
}
respondJSON(w, http.StatusOK, map[string]string{
"message": "dnsmasq configuration updated successfully",
})
}
// updateDnsmasqForAllInstances helper regenerates dnsmasq config from all instances
func (api *API) updateDnsmasqForAllInstances() error {
// Get all instances
instanceNames, err := api.instance.ListInstances()
if err != nil {
return fmt.Errorf("listing instances: %w", err)
}
// Load global config
globalConfigPath := api.getGlobalConfigPath()
globalCfg, err := config.LoadGlobalConfig(globalConfigPath)
if err != nil {
return fmt.Errorf("loading global config: %w", err)
}
// Load all instance configs
var instanceConfigs []config.InstanceConfig
for _, name := range instanceNames {
instanceConfigPath := api.instance.GetInstanceConfigPath(name)
instanceCfg, err := config.LoadCloudConfig(instanceConfigPath)
if err != nil {
log.Printf("Warning: Could not load instance config for %s: %v", name, err)
continue
}
instanceConfigs = append(instanceConfigs, *instanceCfg)
}
// Regenerate and write dnsmasq config
return api.dnsmasq.UpdateConfig(globalCfg, instanceConfigs)
}
// getGlobalConfigPath returns the path to the global config file
func (api *API) getGlobalConfigPath() string {
// This should match the structure from data.Paths
return api.dataDir + "/config.yaml"
}

View File

@@ -101,17 +101,31 @@ type NodeConfig struct {
}
type InstanceConfig struct {
BaseDomain string `yaml:"baseDomain" json:"baseDomain"`
Domain string `yaml:"domain" json:"domain"`
InternalDomain string `yaml:"internalDomain" json:"internalDomain"`
Backup struct {
Root string `yaml:"root" json:"root"`
} `yaml:"backup" json:"backup"`
DHCPRange string `yaml:"dhcpRange" json:"dhcpRange"`
NFS struct {
Host string `yaml:"host" json:"host"`
MediaPath string `yaml:"mediaPath" json:"mediaPath"`
} `yaml:"nfs" json:"nfs"`
Cloud struct {
Router struct {
IP string `yaml:"ip" json:"ip"`
} `yaml:"router" json:"router"`
DNS struct {
IP string `yaml:"ip" json:"ip"`
ExternalResolver string `yaml:"externalResolver" json:"externalResolver"`
} `yaml:"dns" json:"dns"`
DHCPRange string `yaml:"dhcpRange" json:"dhcpRange"`
Dnsmasq struct {
Interface string `yaml:"interface" json:"interface"`
} `yaml:"dnsmasq" json:"dnsmasq"`
BaseDomain string `yaml:"baseDomain" json:"baseDomain"`
Domain string `yaml:"domain" json:"domain"`
InternalDomain string `yaml:"internalDomain" json:"internalDomain"`
NFS struct {
MediaPath string `yaml:"mediaPath" json:"mediaPath"`
Host string `yaml:"host" json:"host"`
StorageCapacity string `yaml:"storageCapacity" json:"storageCapacity"`
} `yaml:"nfs" json:"nfs"`
DockerRegistryHost string `yaml:"dockerRegistryHost" json:"dockerRegistryHost"`
Backup struct {
Root string `yaml:"root" json:"root"`
} `yaml:"backup" json:"backup"`
} `yaml:"cloud" json:"cloud"`
Cluster struct {
Name string `yaml:"name" json:"name"`
LoadBalancerIp string `yaml:"loadBalancerIp" json:"loadBalancerIp"`

View File

@@ -37,8 +37,8 @@ func (m *Manager) Initialize() error {
if err != nil {
return fmt.Errorf("failed to get current directory: %w", err)
}
if os.Getenv("WILD_CENTRAL_DATA") != "" {
dataDir = os.Getenv("WILD_CENTRAL_DATA")
if os.Getenv("WILD_API_DATA_DIR") != "" {
dataDir = os.Getenv("WILD_API_DATA_DIR")
} else {
dataDir = filepath.Join(cwd, "data")
}

View File

@@ -5,16 +5,31 @@ import (
"log"
"os"
"os/exec"
"strconv"
"strings"
"time"
"github.com/wild-cloud/wild-central/daemon/internal/config"
)
// ConfigGenerator handles dnsmasq configuration generation
type ConfigGenerator struct{}
type ConfigGenerator struct {
configPath string
}
// NewConfigGenerator creates a new dnsmasq config generator
func NewConfigGenerator() *ConfigGenerator {
return &ConfigGenerator{}
func NewConfigGenerator(configPath string) *ConfigGenerator {
if configPath == "" {
configPath = "/etc/dnsmasq.d/wild-cloud.conf"
}
return &ConfigGenerator{
configPath: configPath,
}
}
// GetConfigPath returns the dnsmasq config file path
func (g *ConfigGenerator) GetConfigPath() string {
return g.configPath
}
// Generate creates a dnsmasq configuration from the app config
@@ -22,8 +37,8 @@ func (g *ConfigGenerator) Generate(cfg *config.GlobalConfig, clouds []config.Ins
resolution_section := ""
for _, cloud := range clouds {
resolution_section += fmt.Sprintf("local=/%s/\naddress=/%s/%s\n", cloud.Domain, cloud.Domain, cfg.Cluster.EndpointIP)
resolution_section += fmt.Sprintf("local=/%s/\naddress=/%s/%s\n", cloud.InternalDomain, cloud.InternalDomain, cfg.Cluster.EndpointIP)
resolution_section += fmt.Sprintf("local=/%s/\naddress=/%s/%s\n", cloud.Cloud.Domain, cloud.Cloud.Domain, cfg.Cluster.EndpointIP)
resolution_section += fmt.Sprintf("local=/%s/\naddress=/%s/%s\n", cloud.Cloud.InternalDomain, cloud.Cloud.InternalDomain, cfg.Cluster.EndpointIP)
}
template := `# Configuration file for dnsmasq.
@@ -71,3 +86,91 @@ func (g *ConfigGenerator) RestartService() error {
}
return nil
}
// ServiceStatus represents the status of the dnsmasq service
type ServiceStatus struct {
Status string `json:"status"`
PID int `json:"pid"`
ConfigFile string `json:"config_file"`
InstancesConfigured int `json:"instances_configured"`
LastRestart time.Time `json:"last_restart"`
}
// GetStatus checks the status of the dnsmasq service
func (g *ConfigGenerator) GetStatus() (*ServiceStatus, error) {
status := &ServiceStatus{
ConfigFile: g.configPath,
}
// Check if service is active
cmd := exec.Command("systemctl", "is-active", "dnsmasq.service")
output, err := cmd.Output()
if err != nil {
status.Status = "inactive"
return status, nil
}
statusStr := strings.TrimSpace(string(output))
status.Status = statusStr
// Get PID if running
if statusStr == "active" {
cmd = exec.Command("systemctl", "show", "dnsmasq.service", "--property=MainPID")
output, err := cmd.Output()
if err == nil {
parts := strings.Split(strings.TrimSpace(string(output)), "=")
if len(parts) == 2 {
if pid, err := strconv.Atoi(parts[1]); err == nil {
status.PID = pid
}
}
}
// Get last restart time
cmd = exec.Command("systemctl", "show", "dnsmasq.service", "--property=ActiveEnterTimestamp")
output, err = cmd.Output()
if err == nil {
parts := strings.Split(strings.TrimSpace(string(output)), "=")
if len(parts) == 2 {
// Parse systemd timestamp format
if t, err := time.Parse("Mon 2006-01-02 15:04:05 MST", parts[1]); err == nil {
status.LastRestart = t
}
}
}
}
// Count instances in config
if data, err := os.ReadFile(g.configPath); err == nil {
// Count "local=/" occurrences (each instance has multiple)
count := strings.Count(string(data), "local=/")
// Each instance creates 2 "local=/" entries (domain and internal domain)
status.InstancesConfigured = count / 2
}
return status, nil
}
// ReadConfig reads the current dnsmasq configuration
func (g *ConfigGenerator) ReadConfig() (string, error) {
data, err := os.ReadFile(g.configPath)
if err != nil {
return "", fmt.Errorf("reading dnsmasq config: %w", err)
}
return string(data), nil
}
// UpdateConfig regenerates and writes the dnsmasq configuration for all instances
func (g *ConfigGenerator) UpdateConfig(cfg *config.GlobalConfig, instances []config.InstanceConfig) error {
// Generate fresh config from scratch
configContent := g.Generate(cfg, instances)
// Write config
log.Printf("Writing dnsmasq config to: %s", g.configPath)
if err := os.WriteFile(g.configPath, []byte(configContent), 0644); err != nil {
return fmt.Errorf("writing dnsmasq config: %w", err)
}
// Restart service to apply changes
return g.RestartService()
}

View File

@@ -554,7 +554,7 @@ func (m *Manager) Deploy(instanceName, serviceName, opID string, broadcaster *op
env := os.Environ()
env = append(env,
fmt.Sprintf("WILD_INSTANCE=%s", instanceName),
fmt.Sprintf("WILD_CENTRAL_DATA=%s", m.dataDir),
fmt.Sprintf("WILD_API_DATA_DIR=%s", m.dataDir),
fmt.Sprintf("KUBECONFIG=%s", kubeconfigPath),
)
fmt.Printf("[DEBUG] Environment configured: WILD_INSTANCE=%s, KUBECONFIG=%s\n", instanceName, kubeconfigPath)

View File

@@ -8,9 +8,9 @@ if [ -z "${WILD_INSTANCE}" ]; then
exit 1
fi
# Ensure WILD_CENTRAL_DATA is set
if [ -z "${WILD_CENTRAL_DATA}" ]; then
echo "❌ ERROR: WILD_CENTRAL_DATA is not set"
# Ensure WILD_API_DATA_DIR is set
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "❌ ERROR: WILD_API_DATA_DIR is not set"
exit 1
fi
@@ -20,7 +20,7 @@ if [ -z "${KUBECONFIG}" ]; then
exit 1
fi
INSTANCE_DIR="${WILD_CENTRAL_DATA}/instances/${WILD_INSTANCE}"
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
CLUSTER_SETUP_DIR="${INSTANCE_DIR}/setup/cluster-services"
CERT_MANAGER_DIR="${CLUSTER_SETUP_DIR}/cert-manager"
@@ -65,7 +65,7 @@ kubectl wait --for=condition=Available deployment/cert-manager-webhook -n cert-m
# Create Cloudflare API token secret
# Read token from Wild Central secrets file
echo "🔐 Creating Cloudflare API token secret..."
SECRETS_FILE="${WILD_CENTRAL_DATA}/instances/${WILD_INSTANCE}/secrets.yaml"
SECRETS_FILE="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}/secrets.yaml"
CLOUDFLARE_API_TOKEN=$(yq '.cloudflare.token' "$SECRETS_FILE" 2>/dev/null)
CLOUDFLARE_API_TOKEN=$(echo "$CLOUDFLARE_API_TOKEN")

View File

@@ -9,14 +9,14 @@ if [ -z "${WILD_INSTANCE}" ]; then
exit 1
fi
if [ -z "${WILD_CENTRAL_DATA}" ]; then
echo "❌ ERROR: WILD_CENTRAL_DATA environment variable is not set"
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "❌ ERROR: WILD_API_DATA_DIR environment variable is not set"
exit 1
fi
# Get the instance directory path
get_instance_dir() {
echo "${WILD_CENTRAL_DATA}/instances/${WILD_INSTANCE}"
echo "${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
}
# Get the secrets file path

View File

@@ -8,9 +8,9 @@ if [ -z "${WILD_INSTANCE}" ]; then
exit 1
fi
# Ensure WILD_CENTRAL_DATA is set
if [ -z "${WILD_CENTRAL_DATA}" ]; then
echo "❌ ERROR: WILD_CENTRAL_DATA is not set"
# Ensure WILD_API_DATA_DIR is set
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "❌ ERROR: WILD_API_DATA_DIR is not set"
exit 1
fi
@@ -20,7 +20,7 @@ if [ -z "${KUBECONFIG}" ]; then
exit 1
fi
INSTANCE_DIR="${WILD_CENTRAL_DATA}/instances/${WILD_INSTANCE}"
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
CLUSTER_SETUP_DIR="${INSTANCE_DIR}/setup/cluster-services"
COREDNS_DIR="${CLUSTER_SETUP_DIR}/coredns"

View File

@@ -8,9 +8,9 @@ if [ -z "${WILD_INSTANCE}" ]; then
exit 1
fi
# Ensure WILD_CENTRAL_DATA is set
if [ -z "${WILD_CENTRAL_DATA}" ]; then
echo "❌ ERROR: WILD_CENTRAL_DATA is not set"
# Ensure WILD_API_DATA_DIR is set
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "❌ ERROR: WILD_API_DATA_DIR is not set"
exit 1
fi
@@ -20,7 +20,7 @@ if [ -z "${KUBECONFIG}" ]; then
exit 1
fi
INSTANCE_DIR="${WILD_CENTRAL_DATA}/instances/${WILD_INSTANCE}"
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
CLUSTER_SETUP_DIR="${INSTANCE_DIR}/setup/cluster-services"
DOCKER_REGISTRY_DIR="${CLUSTER_SETUP_DIR}/docker-registry"

View File

@@ -8,9 +8,9 @@ if [ -z "${WILD_INSTANCE}" ]; then
exit 1
fi
# Ensure WILD_CENTRAL_DATA is set
if [ -z "${WILD_CENTRAL_DATA}" ]; then
echo "❌ ERROR: WILD_CENTRAL_DATA is not set"
# Ensure WILD_API_DATA_DIR is set
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "❌ ERROR: WILD_API_DATA_DIR is not set"
exit 1
fi
@@ -20,7 +20,7 @@ if [ -z "${KUBECONFIG}" ]; then
exit 1
fi
INSTANCE_DIR="${WILD_CENTRAL_DATA}/instances/${WILD_INSTANCE}"
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
CLUSTER_SETUP_DIR="${INSTANCE_DIR}/setup/cluster-services"
EXTERNALDNS_DIR="${CLUSTER_SETUP_DIR}/externaldns"
@@ -49,7 +49,7 @@ kubectl apply -k ${EXTERNALDNS_DIR}/kustomize
# Setup Cloudflare API token secret
echo "🔐 Creating Cloudflare API token secret..."
SECRETS_FILE="${WILD_CENTRAL_DATA}/instances/${WILD_INSTANCE}/secrets.yaml"
SECRETS_FILE="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}/secrets.yaml"
CLOUDFLARE_API_TOKEN=$(yq '.cloudflare.token' "$SECRETS_FILE" 2>/dev/null | tr -d '"')
if [ -z "$CLOUDFLARE_API_TOKEN" ] || [ "$CLOUDFLARE_API_TOKEN" = "null" ]; then

View File

@@ -8,9 +8,9 @@ if [ -z "${WILD_INSTANCE}" ]; then
exit 1
fi
# Ensure WILD_CENTRAL_DATA is set
if [ -z "${WILD_CENTRAL_DATA}" ]; then
echo "❌ ERROR: WILD_CENTRAL_DATA is not set"
# Ensure WILD_API_DATA_DIR is set
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "❌ ERROR: WILD_API_DATA_DIR is not set"
exit 1
fi
@@ -20,7 +20,7 @@ if [ -z "${KUBECONFIG}" ]; then
exit 1
fi
INSTANCE_DIR="${WILD_CENTRAL_DATA}/instances/${WILD_INSTANCE}"
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
CLUSTER_SETUP_DIR="${INSTANCE_DIR}/setup/cluster-services"
KUBERNETES_DASHBOARD_DIR="${CLUSTER_SETUP_DIR}/kubernetes-dashboard"

View File

@@ -8,9 +8,9 @@ if [ -z "${WILD_INSTANCE}" ]; then
exit 1
fi
# Ensure WILD_CENTRAL_DATA is set
if [ -z "${WILD_CENTRAL_DATA}" ]; then
echo "❌ ERROR: WILD_CENTRAL_DATA is not set"
# Ensure WILD_API_DATA_DIR is set
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "❌ ERROR: WILD_API_DATA_DIR is not set"
exit 1
fi
@@ -20,7 +20,7 @@ if [ -z "${KUBECONFIG}" ]; then
exit 1
fi
INSTANCE_DIR="${WILD_CENTRAL_DATA}/instances/${WILD_INSTANCE}"
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
CLUSTER_SETUP_DIR="${INSTANCE_DIR}/setup/cluster-services"
LONGHORN_DIR="${CLUSTER_SETUP_DIR}/longhorn"

View File

@@ -8,9 +8,9 @@ if [ -z "${WILD_INSTANCE}" ]; then
exit 1
fi
# Ensure WILD_CENTRAL_DATA is set
if [ -z "${WILD_CENTRAL_DATA}" ]; then
echo "❌ ERROR: WILD_CENTRAL_DATA is not set"
# Ensure WILD_API_DATA_DIR is set
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "❌ ERROR: WILD_API_DATA_DIR is not set"
exit 1
fi
@@ -20,7 +20,7 @@ if [ -z "${KUBECONFIG}" ]; then
exit 1
fi
INSTANCE_DIR="${WILD_CENTRAL_DATA}/instances/${WILD_INSTANCE}"
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
CLUSTER_SETUP_DIR="${INSTANCE_DIR}/setup/cluster-services"
METALLB_DIR="${CLUSTER_SETUP_DIR}/metallb"

View File

@@ -8,9 +8,9 @@ if [ -z "${WILD_INSTANCE}" ]; then
exit 1
fi
# Ensure WILD_CENTRAL_DATA is set
if [ -z "${WILD_CENTRAL_DATA}" ]; then
echo "❌ ERROR: WILD_CENTRAL_DATA is not set"
# Ensure WILD_API_DATA_DIR is set
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "❌ ERROR: WILD_API_DATA_DIR is not set"
exit 1
fi
@@ -20,7 +20,7 @@ if [ -z "${KUBECONFIG}" ]; then
exit 1
fi
INSTANCE_DIR="${WILD_CENTRAL_DATA}/instances/${WILD_INSTANCE}"
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
CONFIG_FILE="${INSTANCE_DIR}/config.yaml"
CLUSTER_SETUP_DIR="${INSTANCE_DIR}/setup/cluster-services"
NFS_DIR="${CLUSTER_SETUP_DIR}/nfs"

View File

@@ -8,9 +8,9 @@ if [ -z "${WILD_INSTANCE}" ]; then
exit 1
fi
# Ensure WILD_CENTRAL_DATA is set
if [ -z "${WILD_CENTRAL_DATA}" ]; then
echo "ERROR: WILD_CENTRAL_DATA is not set"
# Ensure WILD_API_DATA_DIR is set
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "ERROR: WILD_API_DATA_DIR is not set"
exit 1
fi
@@ -20,7 +20,7 @@ if [ -z "${KUBECONFIG}" ]; then
exit 1
fi
INSTANCE_DIR="${WILD_CENTRAL_DATA}/instances/${WILD_INSTANCE}"
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
CLUSTER_SETUP_DIR="${INSTANCE_DIR}/setup/cluster-services"
NFD_DIR="${CLUSTER_SETUP_DIR}/node-feature-discovery"

View File

@@ -8,9 +8,9 @@ if [ -z "${WILD_INSTANCE}" ]; then
exit 1
fi
# Ensure WILD_CENTRAL_DATA is set
if [ -z "${WILD_CENTRAL_DATA}" ]; then
echo "❌ ERROR: WILD_CENTRAL_DATA is not set"
# Ensure WILD_API_DATA_DIR is set
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "❌ ERROR: WILD_API_DATA_DIR is not set"
exit 1
fi
@@ -20,7 +20,7 @@ if [ -z "${KUBECONFIG}" ]; then
exit 1
fi
INSTANCE_DIR="${WILD_CENTRAL_DATA}/instances/${WILD_INSTANCE}"
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
CLUSTER_SETUP_DIR="${INSTANCE_DIR}/setup/cluster-services"
NVIDIA_PLUGIN_DIR="${CLUSTER_SETUP_DIR}/nvidia-device-plugin"

View File

@@ -8,9 +8,9 @@ if [ -z "${WILD_INSTANCE}" ]; then
exit 1
fi
# Ensure WILD_CENTRAL_DATA is set
if [ -z "${WILD_CENTRAL_DATA}" ]; then
echo "❌ ERROR: WILD_CENTRAL_DATA is not set"
# Ensure WILD_API_DATA_DIR is set
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "❌ ERROR: WILD_API_DATA_DIR is not set"
exit 1
fi
@@ -20,7 +20,7 @@ if [ -z "${KUBECONFIG}" ]; then
exit 1
fi
INSTANCE_DIR="${WILD_CENTRAL_DATA}/instances/${WILD_INSTANCE}"
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
CLUSTER_SETUP_DIR="${INSTANCE_DIR}/setup/cluster-services"
TRAEFIK_DIR="${CLUSTER_SETUP_DIR}/traefik"

View File

@@ -8,9 +8,9 @@ if [ -z "${WILD_INSTANCE}" ]; then
exit 1
fi
# Ensure WILD_CENTRAL_DATA is set
if [ -z "${WILD_CENTRAL_DATA}" ]; then
echo "❌ ERROR: WILD_CENTRAL_DATA is not set"
# Ensure WILD_API_DATA_DIR is set
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "❌ ERROR: WILD_API_DATA_DIR is not set"
exit 1
fi
@@ -20,7 +20,7 @@ if [ -z "${KUBECONFIG}" ]; then
exit 1
fi
INSTANCE_DIR="${WILD_CENTRAL_DATA}/instances/${WILD_INSTANCE}"
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
CLUSTER_SETUP_DIR="${INSTANCE_DIR}/setup/cluster-services"
UTILS_DIR="${CLUSTER_SETUP_DIR}/utils"

View File

@@ -19,7 +19,7 @@ func main() {
startTime = time.Now()
// Get data directory from environment or use default
dataDir := os.Getenv("WILD_CENTRAL_DATA")
dataDir := os.Getenv("WILD_API_DATA_DIR")
if dataDir == "" {
dataDir = "/var/lib/wild-central"
}