more optimizations
Build / Build (push) Successful in 32s

This commit is contained in:
wt committed 2025-07-07 22:40:06 +07:00
1 parent 952587df4f
commit efcb19aa2f
19 files changed
+276 -189

No files matched your search

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+35
View File
@@ -0,0 +1,35 @@
package api
import (
"dns_service/settings"
"dns_service/utils"
"encoding/json"
"net/http"
)
func Settings(w http.ResponseWriter, req *http.Request) {
switch req.Method {
case "GET":
respSettings, err := settings.GetSettings()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
utils.RespondWithJSON(w, respSettings, http.StatusOK)
case "POST":
var newSettings map[string]any
if err := json.NewDecoder(req.Body).Decode(&newSettings); err != nil {
http.Error(w, "invalid JSON format", http.StatusBadRequest)
return
}
respSettings, err := settings.SetSettings(newSettings)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
utils.RespondWithJSON(w, respSettings, http.StatusOK)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
+45
View File
@@ -0,0 +1,45 @@
package api
import (
"dns_service/utils"
"io"
"net/http"
"os"
)
func SettingsFile(w http.ResponseWriter, req *http.Request) {
configPath := os.Getenv("DNSMASQ_CONFIG")
if configPath == "" {
http.Error(w, "DNSMASQ_CONFIG environment variable not set", http.StatusInternalServerError)
return
}
switch req.Method {
case "GET":
data, err := os.ReadFile(configPath)
if err != nil {
http.Error(w, "failed to read dnsmasq config file", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/plain")
w.Write(data)
case "POST":
bodyBytes, err := io.ReadAll(req.Body)
if err != nil {
http.Error(w, "failed to read request body", http.StatusBadRequest)
return
}
if err := os.WriteFile(configPath, bodyBytes, 0644); err != nil {
http.Error(w, "failed to write dnsmasq config file", http.StatusInternalServerError)
return
}
if err := utils.RestartDnsmasq(); err != nil {
http.Error(w, "failed to restart dnsmasq", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/plain")
w.Write(bodyBytes)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
+23
View File
@@ -0,0 +1,23 @@
package api
import (
"embed"
"fmt"
"net/http"
"path"
)
//go:embed frontend/*
var Frontend embed.FS
func SpaHandler(fs http.FileSystem) http.Handler {
fileServer := http.FileServer(fs)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("/frontend" + r.URL.Path)
r.URL.Path = "/frontend" + path.Clean(r.URL.Path)
if _, err := fs.Open(path.Clean(r.URL.Path)); err != nil {
r.URL.Path = "/"
}
fileServer.ServeHTTP(w, r)
})
}
+4 -92
View File
@@ -1,111 +1,23 @@
package main
import (
"dns_service/settings"
"dns_service/utils"
"embed"
"encoding/json"
"fmt"
"io"
"dns_service/api"
"log"
"net/http"
"os"
"os/exec"
"path"
"github.com/joho/godotenv"
)
func Settings(w http.ResponseWriter, req *http.Request) {
if req.Method == "GET" {
resp_settings, err := settings.GetSettings()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
jsondata, err := json.Marshal(resp_settings)
if err != nil {
http.Error(w, "Error json marshal settings", http.StatusInternalServerError)
return
}
w.Header().Add("Content-Type", "application/json")
fmt.Fprintf(w, "%s", string(jsondata))
} else if req.Method == "POST" {
new_settings := make(map[string]any)
decoder := json.NewDecoder(req.Body)
decoder.Decode(&new_settings)
old_settings, err := settings.GetSettings()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resp_settings, err := settings.SetSettings(new_settings, old_settings)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
jsondata, err := json.Marshal(resp_settings)
if err != nil {
http.Error(w, "Error json marshal settings", http.StatusInternalServerError)
return
}
w.Header().Add("Content-Type", "application/json")
fmt.Println(new_settings, old_settings, resp_settings)
fmt.Fprintf(w, "%s", string(jsondata))
}
}
func SettingsFile(w http.ResponseWriter, req *http.Request) {
if req.Method == "GET" {
data, e := os.ReadFile(os.Getenv("DNSMASQ_CONFIG"))
if e != nil {
panic(e)
}
w.Header().Add("Content-Type", "text/plain")
fmt.Fprintf(w, "%s", string(data))
} else if req.Method == "POST" {
bodyBytes, err := io.ReadAll(req.Body)
if err != nil {
http.Error(w, "Error reading request body", http.StatusBadRequest)
return
}
err = os.WriteFile(os.Getenv("DNSMASQ_CONFIG"), bodyBytes, 0644)
if err != nil {
http.Error(w, "Error writing dnsmasq config file", http.StatusInternalServerError)
return
}
fmt.Println(string(bodyBytes))
utils.RestartDnsmasq()
exec.Command("rc", "restart", "dnsmasq").Run()
w.Header().Add("Content-Type", "text/plain")
fmt.Fprintf(w, "%s", string(bodyBytes))
}
}
//go:embed frontend/*
var frontend embed.FS
func spaHandler(fs http.FileSystem) http.Handler {
fileServer := http.FileServer(fs)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("/frontend" + r.URL.Path)
r.URL.Path = "/frontend" + path.Clean(r.URL.Path)
if _, err := fs.Open(path.Clean(r.URL.Path)); err != nil {
r.URL.Path = "/"
}
fileServer.ServeHTTP(w, r)
})
}
func main() {
err := godotenv.Load("/etc/dns_service/dns_service.conf")
if err != nil {
log.Fatal("Error loading .env file")
}
http.Handle("/", spaHandler(http.FS(frontend)))
http.HandleFunc("/api/settings", Settings)
http.HandleFunc("/api/settings/file", SettingsFile)
http.Handle("/", api.SpaHandler(http.FS(api.Frontend)))
http.HandleFunc("/api/settings", api.Settings)
http.HandleFunc("/api/settings/file", api.SettingsFile)
if os.Getenv("ENABLE_TLS") == "true" {
http.ListenAndServeTLS(
+36 -17
View File
@@ -2,32 +2,51 @@ package settings
import (
"dns_service/utils"
"fmt"
"os"
"strconv"
)
func GetSettings() (map[string]any, error) {
settings := make(map[string]any)
data, e := os.ReadFile(os.Getenv("DNSMASQ_CONFIG"))
if e != nil {
return settings, e
configPath := os.Getenv("DNSMASQ_CONFIG")
if configPath == "" {
return nil, fmt.Errorf("DNSMASQ_CONFIG environment variable not set")
}
data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read config file at %s: %w", configPath, err)
}
dnsmasqSettings := utils.DecodeSettings(string(data), "\n", "=")
if dnsmasqSettings["cache-size"] != "" {
cache_size, _ := strconv.ParseInt(dnsmasqSettings["cache-size"], 10, 64)
settings["cache_size"] = cache_size
settings := make(map[string]any)
// Parse cache size
if cacheSizeStr, ok := dnsmasqSettings["cache-size"]; ok && cacheSizeStr != "" {
cacheSize, err := strconv.ParseInt(cacheSizeStr, 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse cache-size '%s': %w", cacheSizeStr, err)
}
settings["cache_size"] = cacheSize
}
data, e = os.ReadFile(dnsmasqSettings["resolv-file"])
if e != nil {
return settings, e
// Read and parse resolv file
if resolvFile, ok := dnsmasqSettings["resolv-file"]; ok {
data, err := os.ReadFile(resolvFile)
if err != nil {
return nil, fmt.Errorf("failed to read resolv-file at %s: %w", resolvFile, err)
}
settings["resolv"] = utils.DecodeResolvs(string(data))
}
resolvSettings := utils.DecodeResolvs(string(data))
settings["resolv"] = resolvSettings
data, e = os.ReadFile(dnsmasqSettings["addn-hosts"])
if e != nil {
return settings, e
// Read and parse hosts file
if hostsFile, ok := dnsmasqSettings["addn-hosts"]; ok {
data, err := os.ReadFile(hostsFile)
if err != nil {
return nil, fmt.Errorf("failed to read addn-hosts file at %s: %w", hostsFile, err)
}
settings["hosts"] = utils.DecodeHosts(string(data))
}
hosts := utils.DecodeHosts(string(data))
settings["hosts"] = hosts
return settings, nil
}
+74 -39
View File
@@ -7,51 +7,86 @@ import (
"strings"
)
func SetSettings(new_settings map[string]any, old_settings map[string]any) (map[string]any, error) {
data, err := os.ReadFile(os.Getenv("DNSMASQ_CONFIG"))
// Helper to write file content
func writeFile(path, content string) error {
return os.WriteFile(path, []byte(content), 0644)
}
func SetSettings(new_settings map[string]any) (map[string]any, error) {
// 1. Get config path and read the main dnsmasq configuration
configPath := os.Getenv("DNSMASQ_CONFIG")
if configPath == "" {
return nil, fmt.Errorf("DNSMASQ_CONFIG environment variable not set")
}
data, err := os.ReadFile(configPath)
if err != nil {
return old_settings, err
return nil, fmt.Errorf("failed to read config file at %s: %w", configPath, err)
}
new_data := strings.Replace(
string(data),
"cache-size="+fmt.Sprintf("%v", old_settings["cache_size"]),
"cache-size="+fmt.Sprintf("%v", new_settings["cache_size"]),
1,
)
err = os.WriteFile(os.Getenv("DNSMASQ_CONFIG"), []byte(new_data), 0644)
if err != nil {
return old_settings, err
}
var resolvs string
new_resolvs := new_settings["resolv"].([]any)
for i := range new_resolvs {
resolvs += "nameserver " + new_resolvs[i].(string) + "\n"
}
var hosts string
new_hosts := new_settings["hosts"].(map[string]any)
for key, value := range new_hosts {
hosts += key
switch value.(type) {
case []any:
arrvalue := value.([]any)
for i := range arrvalue {
hosts += " " + arrvalue[i].(string)
dnsmasqSettings := utils.DecodeSettings(string(data), "\n", "=")
// 2. Update cache-size in the main config file
if newCacheSize, ok := new_settings["cache_size"]; ok {
oldCacheSize, oldOk := dnsmasqSettings["cache-size"]
// Replace the line if it exists, otherwise dnsmasq uses a default
if oldOk {
newConfigContent := strings.Replace(
string(data),
"cache-size="+oldCacheSize,
"cache-size="+fmt.Sprintf("%v", newCacheSize),
1,
)
if err := writeFile(configPath, newConfigContent); err != nil {
return nil, fmt.Errorf("failed to write updated config to %s: %w", configPath, err)
}
hosts += "\n"
break
case string:
hosts += " " + value.(string) + "\n"
}
}
fmt.Println(resolvs, hosts)
err = os.WriteFile("/etc/dnsmasq.conf.d/resolv.conf", []byte(resolvs), 0644)
if err != nil {
return old_settings, err
// 3. Update the resolv file
if newResolvs, ok := new_settings["resolv"].([]any); ok {
resolvFile, ok := dnsmasqSettings["resolv-file"]
if !ok {
return nil, fmt.Errorf("resolv-file path not found in dnsmasq config")
}
var b strings.Builder
for _, v := range newResolvs {
if s, ok := v.(string); ok {
fmt.Fprintf(&b, "nameserver %s\n", s)
}
}
if err := writeFile(resolvFile, b.String()); err != nil {
return nil, fmt.Errorf("failed to write to resolv file %s: %w", resolvFile, err)
}
}
err = os.WriteFile("/etc/dnsmasq.conf.d/hosts", []byte(hosts), 0644)
if err != nil {
return old_settings, err
// 4. Update the hosts file
if newHosts, ok := new_settings["hosts"].(map[string]any); ok {
hostsFile, ok := dnsmasqSettings["addn-hosts"]
if !ok {
return nil, fmt.Errorf("addn-hosts path not found in dnsmasq config")
}
var b strings.Builder
for key, value := range newHosts {
b.WriteString(key)
switch v := value.(type) {
case []any:
for _, ip := range v {
fmt.Fprintf(&b, " %s", ip)
}
b.WriteString("\n")
case string:
fmt.Fprintf(&b, " %s\n", v)
}
}
if err := writeFile(hostsFile, b.String()); err != nil {
return nil, fmt.Errorf("failed to write to hosts file %s: %w", hostsFile, err)
}
}
utils.RestartDnsmasq()
// 5. Restart dnsmasq to apply changes
if err := utils.RestartDnsmasq(); err != nil {
return nil, fmt.Errorf("failed to restart dnsmasq: %w", err)
}
return new_settings, nil
}
+12 -19
View File
@@ -4,28 +4,21 @@ import "strings"
func DecodeHosts(input string) map[string]any {
result := make(map[string]any)
pairs := strings.SplitSeq(input, "\n")
lines := strings.Split(input, "\n")
for pair := range pairs {
if pair == "" {
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) == 0 {
continue
}
kv := strings.Split(pair, " ")
if len(kv) == 2 {
key := strings.TrimSpace(kv[0])
value := strings.TrimSpace(kv[1])
if key != "" {
result[key] = value
}
continue
}
if len(kv) != 0 {
key := strings.TrimSpace(kv[0])
var value []string
for i := 1; i < len(kv); i++ {
value = append(value, kv[i])
}
result[key] = value
key := fields[0]
if len(fields) == 1 {
result[key] = []string{}
} else if len(fields) == 2 {
result[key] = fields[1]
} else {
result[key] = fields[1:]
}
}
return result
+8 -12
View File
@@ -4,19 +4,15 @@ import "strings"
func DecodeResolvs(input string) []string {
var result []string
pairs := strings.SplitSeq(input, "\n")
lines := strings.Split(input, "\n")
for pair := range pairs {
if pair == "" {
continue
}
kv := strings.SplitN(pair, " ", 2)
if len(kv) != 2 {
continue
}
value := strings.TrimSpace(kv[1])
if value != "" {
result = append(result, value)
for _, line := range lines {
trimmedLine := strings.TrimSpace(line)
if strings.HasPrefix(trimmedLine, "nameserver ") {
value := strings.TrimPrefix(trimmedLine, "nameserver ")
if value != "" {
result = append(result, value)
}
}
}
return result
+8 -6
View File
@@ -4,20 +4,22 @@ import "strings"
func DecodeSettings(input string, pairDelim string, kvDelim string) map[string]string {
result := make(map[string]string)
pairs := strings.SplitSeq(input, pairDelim)
pairs := strings.Split(input, pairDelim)
for pair := range pairs {
if pair == "" {
for _, pair := range pairs {
trimmedPair := strings.TrimSpace(pair)
if trimmedPair == "" {
continue
}
kv := strings.SplitN(pair, kvDelim, 2)
kv := strings.SplitN(trimmedPair, kvDelim, 2)
if len(kv) != 2 {
continue
}
key := strings.TrimSpace(kv[0])
value := strings.TrimSpace(kv[1])
if key != "" {
result[key] = value
result[key] = strings.TrimSpace(kv[1])
}
}
return result
+18
View File
@@ -0,0 +1,18 @@
package utils
import (
"encoding/json"
"net/http"
)
// Helper to write JSON responses
func RespondWithJSON(w http.ResponseWriter, data any, statusCode int) {
jsonData, err := json.Marshal(data)
if err != nil {
http.Error(w, "failed to marshal JSON", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
w.Write(jsonData)
}
+13 -4
View File
@@ -1,17 +1,26 @@
package utils
import (
"fmt"
"os"
"os/exec"
)
func RestartDnsmasq() {
func RestartDnsmasq() error {
var cmd *exec.Cmd
switch os.Getenv("SYSTEM_INITIALIZATION") {
case "rc":
exec.Command("rc", "restart", "dnsmasq").Run()
cmd = exec.Command("rc", "restart", "dnsmasq")
case "openrc":
exec.Command("rc-service", "dnsmasq", "restart").Run()
cmd = exec.Command("rc-service", "dnsmasq", "restart")
case "systemd":
exec.Command("systemctl", "restart", "dnsmasq").Run()
cmd = exec.Command("systemctl", "restart", "dnsmasq")
default:
return fmt.Errorf("unsupported system initialization: %s", os.Getenv("SYSTEM_INITIALIZATION"))
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to restart dnsmasq: %w", err)
}
return nil
}