Compare commits
3 Commits
ff97f14229
...
9d1abc3e90
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d1abc3e90 | ||
|
|
9f2d5fc7fb | ||
|
|
47c3b10be9 |
43
BUILDING_WILD_API.md
Normal file
43
BUILDING_WILD_API.md
Normal 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.
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
137
internal/api/v1/handlers_dnsmasq.go
Normal file
137
internal/api/v1/handlers_dnsmasq.go
Normal 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"
|
||||
}
|
||||
@@ -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"`
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user