Add dnsmasq endpoints.

This commit is contained in:
2025-10-12 00:35:03 +00:00
parent 47c3b10be9
commit 9f2d5fc7fb
6 changed files with 111 additions and 15 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 ## Development

View File

@@ -161,6 +161,7 @@ func (api *API) RegisterRoutes(r *mux.Router) {
r.HandleFunc("/api/v1/dnsmasq/status", api.DnsmasqStatus).Methods("GET") 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/config", api.DnsmasqGetConfig).Methods("GET")
r.HandleFunc("/api/v1/dnsmasq/restart", api.DnsmasqRestart).Methods("POST") 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") r.HandleFunc("/api/v1/dnsmasq/update", api.DnsmasqUpdate).Methods("POST")
} }

View File

@@ -49,6 +49,44 @@ func (api *API) DnsmasqRestart(w http.ResponseWriter, r *http.Request) {
}) })
} }
// 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 // DnsmasqUpdate regenerates and updates the dnsmasq configuration with all instances
func (api *API) DnsmasqUpdate(w http.ResponseWriter, r *http.Request) { func (api *API) DnsmasqUpdate(w http.ResponseWriter, r *http.Request) {
if err := api.updateDnsmasqForAllInstances(); err != nil { if err := api.updateDnsmasqForAllInstances(); err != nil {

View File

@@ -101,17 +101,31 @@ type NodeConfig struct {
} }
type InstanceConfig struct { type InstanceConfig struct {
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"` BaseDomain string `yaml:"baseDomain" json:"baseDomain"`
Domain string `yaml:"domain" json:"domain"` Domain string `yaml:"domain" json:"domain"`
InternalDomain string `yaml:"internalDomain" json:"internalDomain"` 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 { Backup struct {
Root string `yaml:"root" json:"root"` Root string `yaml:"root" json:"root"`
} `yaml:"backup" json:"backup"` } `yaml:"backup" json:"backup"`
DHCPRange string `yaml:"dhcpRange" json:"dhcpRange"` } `yaml:"cloud" json:"cloud"`
NFS struct {
Host string `yaml:"host" json:"host"`
MediaPath string `yaml:"mediaPath" json:"mediaPath"`
} `yaml:"nfs" json:"nfs"`
Cluster struct { Cluster struct {
Name string `yaml:"name" json:"name"` Name string `yaml:"name" json:"name"`
LoadBalancerIp string `yaml:"loadBalancerIp" json:"loadBalancerIp"` LoadBalancerIp string `yaml:"loadBalancerIp" json:"loadBalancerIp"`

View File

@@ -37,8 +37,8 @@ func (g *ConfigGenerator) Generate(cfg *config.GlobalConfig, clouds []config.Ins
resolution_section := "" resolution_section := ""
for _, cloud := range clouds { 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.Cloud.Domain, cloud.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.InternalDomain, cloud.Cloud.InternalDomain, cfg.Cluster.EndpointIP)
} }
template := `# Configuration file for dnsmasq. template := `# Configuration file for dnsmasq.