mirror of
https://github.com/DeBrosOfficial/network.git
synced 2026-01-30 03:43:04 +00:00
bug fixing
This commit is contained in:
parent
3d3b0d2ee6
commit
6101455f4a
45
migrations/009_dns_records_multi.sql
Normal file
45
migrations/009_dns_records_multi.sql
Normal file
@ -0,0 +1,45 @@
|
||||
-- Migration 009: Update DNS Records to Support Multiple Records per FQDN
|
||||
-- This allows round-robin A records and multiple NS records for the same domain
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- SQLite doesn't support DROP CONSTRAINT, so we recreate the table
|
||||
-- First, create the new table structure
|
||||
CREATE TABLE IF NOT EXISTS dns_records_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
fqdn TEXT NOT NULL, -- Fully qualified domain name (e.g., myapp.node-7prvNa.orama.network)
|
||||
record_type TEXT NOT NULL DEFAULT 'A',-- DNS record type: A, AAAA, CNAME, TXT, NS, SOA
|
||||
value TEXT NOT NULL, -- IP address or target value
|
||||
ttl INTEGER NOT NULL DEFAULT 300, -- Time to live in seconds
|
||||
priority INTEGER DEFAULT 0, -- Priority for MX/SRV records, or weight for round-robin
|
||||
namespace TEXT NOT NULL DEFAULT 'system', -- Namespace that owns this record
|
||||
deployment_id TEXT, -- Optional: deployment that created this record
|
||||
node_id TEXT, -- Optional: specific node ID for node-specific routing
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,-- Enable/disable without deleting
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by TEXT NOT NULL DEFAULT 'system', -- Wallet address or 'system' for auto-created records
|
||||
UNIQUE(fqdn, record_type, value) -- Allow multiple records of same type for same FQDN, but not duplicates
|
||||
);
|
||||
|
||||
-- Copy existing data if the old table exists
|
||||
INSERT OR IGNORE INTO dns_records_new (id, fqdn, record_type, value, ttl, namespace, deployment_id, node_id, is_active, created_at, updated_at, created_by)
|
||||
SELECT id, fqdn, record_type, value, ttl, namespace, deployment_id, node_id, is_active, created_at, updated_at, created_by
|
||||
FROM dns_records WHERE 1=1;
|
||||
|
||||
-- Drop old table and rename new one
|
||||
DROP TABLE IF EXISTS dns_records;
|
||||
ALTER TABLE dns_records_new RENAME TO dns_records;
|
||||
|
||||
-- Recreate indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_dns_records_fqdn ON dns_records(fqdn);
|
||||
CREATE INDEX IF NOT EXISTS idx_dns_records_fqdn_type ON dns_records(fqdn, record_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_dns_records_namespace ON dns_records(namespace);
|
||||
CREATE INDEX IF NOT EXISTS idx_dns_records_deployment ON dns_records(deployment_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_dns_records_node_id ON dns_records(node_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_dns_records_active ON dns_records(is_active);
|
||||
|
||||
-- Mark migration as applied
|
||||
INSERT OR IGNORE INTO schema_migrations(version) VALUES (9);
|
||||
|
||||
COMMIT;
|
||||
@ -341,6 +341,18 @@ func (o *Orchestrator) restartServices() error {
|
||||
|
||||
// Restart services to apply changes - use getProductionServices to only restart existing services
|
||||
services := utils.GetProductionServices()
|
||||
|
||||
// If this is a nameserver, also restart CoreDNS and Caddy
|
||||
if o.setup.IsNameserver() {
|
||||
nameserverServices := []string{"coredns", "caddy"}
|
||||
for _, svc := range nameserverServices {
|
||||
unitPath := filepath.Join("/etc/systemd/system", svc+".service")
|
||||
if _, err := os.Stat(unitPath); err == nil {
|
||||
services = append(services, svc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(services) == 0 {
|
||||
fmt.Printf(" ⚠️ No services found to restart\n")
|
||||
} else {
|
||||
|
||||
@ -142,11 +142,66 @@ func (b *Backend) parseValue(recordType, value string) (interface{}, error) {
|
||||
case "TXT":
|
||||
return []string{value}, nil
|
||||
|
||||
case "NS":
|
||||
return dns.Fqdn(value), nil
|
||||
|
||||
case "SOA":
|
||||
// SOA format: "mname rname serial refresh retry expire minimum"
|
||||
// Example: "ns1.dbrs.space. admin.dbrs.space. 2026012401 3600 1800 604800 300"
|
||||
return b.parseSOA(value)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported record type: %s", recordType)
|
||||
}
|
||||
}
|
||||
|
||||
// parseSOA parses a SOA record value string
|
||||
// Format: "mname rname serial refresh retry expire minimum"
|
||||
func (b *Backend) parseSOA(value string) (*dns.SOA, error) {
|
||||
parts := strings.Fields(value)
|
||||
if len(parts) < 7 {
|
||||
return nil, fmt.Errorf("invalid SOA format, expected 7 fields: %s", value)
|
||||
}
|
||||
|
||||
serial, err := parseUint32(parts[2])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid SOA serial: %w", err)
|
||||
}
|
||||
refresh, err := parseUint32(parts[3])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid SOA refresh: %w", err)
|
||||
}
|
||||
retry, err := parseUint32(parts[4])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid SOA retry: %w", err)
|
||||
}
|
||||
expire, err := parseUint32(parts[5])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid SOA expire: %w", err)
|
||||
}
|
||||
minttl, err := parseUint32(parts[6])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid SOA minimum: %w", err)
|
||||
}
|
||||
|
||||
return &dns.SOA{
|
||||
Ns: dns.Fqdn(parts[0]),
|
||||
Mbox: dns.Fqdn(parts[1]),
|
||||
Serial: serial,
|
||||
Refresh: refresh,
|
||||
Retry: retry,
|
||||
Expire: expire,
|
||||
Minttl: minttl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseUint32 parses a string to uint32
|
||||
func parseUint32(s string) (uint32, error) {
|
||||
var val uint32
|
||||
_, err := fmt.Sscanf(s, "%d", &val)
|
||||
return val, err
|
||||
}
|
||||
|
||||
// ping tests the RQLite connection
|
||||
func (b *Backend) ping() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
@ -205,6 +260,10 @@ func qTypeToString(qtype uint16) string {
|
||||
return "CNAME"
|
||||
case dns.TypeTXT:
|
||||
return "TXT"
|
||||
case dns.TypeNS:
|
||||
return "NS"
|
||||
case dns.TypeSOA:
|
||||
return "SOA"
|
||||
default:
|
||||
return dns.TypeToString[qtype]
|
||||
}
|
||||
@ -221,6 +280,10 @@ func stringToQType(s string) uint16 {
|
||||
return dns.TypeCNAME
|
||||
case "TXT":
|
||||
return dns.TypeTXT
|
||||
case "NS":
|
||||
return dns.TypeNS
|
||||
case "SOA":
|
||||
return dns.TypeSOA
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -150,6 +150,15 @@ func (p *RQLitePlugin) buildRR(qname string, record *DNSRecord) dns.RR {
|
||||
Hdr: header,
|
||||
Txt: record.ParsedValue.([]string),
|
||||
}
|
||||
case dns.TypeNS:
|
||||
return &dns.NS{
|
||||
Hdr: header,
|
||||
Ns: record.ParsedValue.(string),
|
||||
}
|
||||
case dns.TypeSOA:
|
||||
soa := record.ParsedValue.(*dns.SOA)
|
||||
soa.Hdr = header
|
||||
return soa
|
||||
default:
|
||||
p.logger.Warn("Unsupported record type",
|
||||
zap.Uint16("type", record.Type),
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
package installers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -191,23 +195,26 @@ func (ci *CoreDNSInstaller) Install() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Configure creates CoreDNS configuration files
|
||||
// Configure creates CoreDNS configuration files and seeds static DNS records into RQLite
|
||||
func (ci *CoreDNSInstaller) Configure(domain string, rqliteDSN string, ns1IP, ns2IP, ns3IP string) error {
|
||||
configDir := "/etc/coredns"
|
||||
if err := os.MkdirAll(configDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create config directory: %w", err)
|
||||
}
|
||||
|
||||
// Create Corefile
|
||||
corefile := ci.generateCorefile(domain, rqliteDSN, configDir)
|
||||
// Create Corefile (uses only RQLite plugin)
|
||||
corefile := ci.generateCorefile(domain, rqliteDSN)
|
||||
if err := os.WriteFile(filepath.Join(configDir, "Corefile"), []byte(corefile), 0644); err != nil {
|
||||
return fmt.Errorf("failed to write Corefile: %w", err)
|
||||
}
|
||||
|
||||
// Create zone file
|
||||
zonefile := ci.generateZoneFile(domain, ns1IP, ns2IP, ns3IP)
|
||||
if err := os.WriteFile(filepath.Join(configDir, "db."+domain), []byte(zonefile), 0644); err != nil {
|
||||
return fmt.Errorf("failed to write zone file: %w", err)
|
||||
// Seed static DNS records into RQLite
|
||||
fmt.Fprintf(ci.logWriter, " Seeding static DNS records into RQLite...\n")
|
||||
if err := ci.seedStaticRecords(domain, rqliteDSN, ns1IP, ns2IP, ns3IP); err != nil {
|
||||
// Don't fail on seed errors - RQLite might not be up yet
|
||||
fmt.Fprintf(ci.logWriter, " ⚠️ Could not seed DNS records (RQLite may not be ready): %v\n", err)
|
||||
} else {
|
||||
fmt.Fprintf(ci.logWriter, " ✓ Static DNS records seeded\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -263,14 +270,14 @@ rqlite:rqlite
|
||||
`
|
||||
}
|
||||
|
||||
// generateCorefile creates the CoreDNS configuration
|
||||
func (ci *CoreDNSInstaller) generateCorefile(domain, rqliteDSN, configDir string) string {
|
||||
// generateCorefile creates the CoreDNS configuration (RQLite only)
|
||||
func (ci *CoreDNSInstaller) generateCorefile(domain, rqliteDSN string) string {
|
||||
return fmt.Sprintf(`# CoreDNS configuration for %s
|
||||
# Uses RQLite for dynamic DNS records (deployments, ACME challenges)
|
||||
# Falls back to static zone file for base records (SOA, NS)
|
||||
# Uses RQLite for ALL DNS records (static + dynamic)
|
||||
# Static records (SOA, NS, A) are seeded into RQLite during installation
|
||||
|
||||
%s {
|
||||
# First try RQLite for dynamic records (TXT for ACME, A for deployments)
|
||||
# RQLite handles all records: SOA, NS, A, TXT (ACME), etc.
|
||||
rqlite {
|
||||
dsn %s
|
||||
refresh 5s
|
||||
@ -278,9 +285,6 @@ func (ci *CoreDNSInstaller) generateCorefile(domain, rqliteDSN, configDir string
|
||||
cache_size 10000
|
||||
}
|
||||
|
||||
# Fall back to static zone file for SOA/NS records
|
||||
file %s/db.%s
|
||||
|
||||
# Enable logging and error reporting
|
||||
log
|
||||
errors
|
||||
@ -293,44 +297,95 @@ func (ci *CoreDNSInstaller) generateCorefile(domain, rqliteDSN, configDir string
|
||||
cache 300
|
||||
errors
|
||||
}
|
||||
`, domain, domain, rqliteDSN, configDir, domain)
|
||||
`, domain, domain, rqliteDSN)
|
||||
}
|
||||
|
||||
// generateZoneFile creates the static DNS zone file
|
||||
func (ci *CoreDNSInstaller) generateZoneFile(domain, ns1IP, ns2IP, ns3IP string) string {
|
||||
return fmt.Sprintf(`$ORIGIN %s.
|
||||
$TTL 300
|
||||
// seedStaticRecords inserts static zone records into RQLite
|
||||
func (ci *CoreDNSInstaller) seedStaticRecords(domain, rqliteDSN, ns1IP, ns2IP, ns3IP string) error {
|
||||
// Generate serial based on current date
|
||||
serial := fmt.Sprintf("%d", time.Now().Unix())
|
||||
|
||||
@ IN SOA ns1.%s. admin.%s. (
|
||||
2024012401 ; Serial
|
||||
3600 ; Refresh
|
||||
1800 ; Retry
|
||||
604800 ; Expire
|
||||
300 ) ; Negative TTL
|
||||
// SOA record format: "mname rname serial refresh retry expire minimum"
|
||||
soaValue := fmt.Sprintf("ns1.%s. admin.%s. %s 3600 1800 604800 300", domain, domain, serial)
|
||||
|
||||
; Nameservers
|
||||
@ IN NS ns1.%s.
|
||||
@ IN NS ns2.%s.
|
||||
@ IN NS ns3.%s.
|
||||
// Define all static records
|
||||
records := []struct {
|
||||
fqdn string
|
||||
recordType string
|
||||
value string
|
||||
ttl int
|
||||
}{
|
||||
// SOA record
|
||||
{domain + ".", "SOA", soaValue, 300},
|
||||
|
||||
; Nameserver A records
|
||||
ns1 IN A %s
|
||||
ns2 IN A %s
|
||||
ns3 IN A %s
|
||||
// NS records
|
||||
{domain + ".", "NS", "ns1." + domain + ".", 300},
|
||||
{domain + ".", "NS", "ns2." + domain + ".", 300},
|
||||
{domain + ".", "NS", "ns3." + domain + ".", 300},
|
||||
|
||||
; Root domain points to all nodes (round-robin)
|
||||
@ IN A %s
|
||||
@ IN A %s
|
||||
@ IN A %s
|
||||
// Nameserver A records (glue)
|
||||
{"ns1." + domain + ".", "A", ns1IP, 300},
|
||||
{"ns2." + domain + ".", "A", ns2IP, 300},
|
||||
{"ns3." + domain + ".", "A", ns3IP, 300},
|
||||
|
||||
; Wildcard fallback (RQLite records take precedence for specific subdomains)
|
||||
* IN A %s
|
||||
* IN A %s
|
||||
* IN A %s
|
||||
`, domain, domain, domain, domain, domain, domain,
|
||||
ns1IP, ns2IP, ns3IP,
|
||||
ns1IP, ns2IP, ns3IP,
|
||||
ns1IP, ns2IP, ns3IP)
|
||||
// Root domain A records (round-robin)
|
||||
{domain + ".", "A", ns1IP, 300},
|
||||
{domain + ".", "A", ns2IP, 300},
|
||||
{domain + ".", "A", ns3IP, 300},
|
||||
|
||||
// Wildcard A records (round-robin)
|
||||
{"*." + domain + ".", "A", ns1IP, 300},
|
||||
{"*." + domain + ".", "A", ns2IP, 300},
|
||||
{"*." + domain + ".", "A", ns3IP, 300},
|
||||
}
|
||||
|
||||
// Build SQL statements
|
||||
var statements []string
|
||||
for _, r := range records {
|
||||
// Use INSERT OR REPLACE to handle updates
|
||||
stmt := fmt.Sprintf(
|
||||
`INSERT OR REPLACE INTO dns_records (fqdn, record_type, value, ttl, namespace, created_by) VALUES ('%s', '%s', '%s', %d, 'system', 'system')`,
|
||||
r.fqdn, r.recordType, r.value, r.ttl,
|
||||
)
|
||||
statements = append(statements, stmt)
|
||||
}
|
||||
|
||||
// Execute via RQLite HTTP API
|
||||
return ci.executeRQLiteStatements(rqliteDSN, statements)
|
||||
}
|
||||
|
||||
// executeRQLiteStatements executes SQL statements via RQLite HTTP API
|
||||
func (ci *CoreDNSInstaller) executeRQLiteStatements(rqliteDSN string, statements []string) error {
|
||||
// RQLite execute endpoint
|
||||
executeURL := rqliteDSN + "/db/execute?pretty&timings"
|
||||
|
||||
// Build request body
|
||||
body, err := json.Marshal(statements)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal statements: %w", err)
|
||||
}
|
||||
|
||||
// Create request
|
||||
req, err := http.NewRequest("POST", executeURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Execute with timeout
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("RQLite returned status %d: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// containsLine checks if a string contains a specific line
|
||||
|
||||
@ -457,15 +457,18 @@ func (h *DomainHandler) createDNSRecord(ctx context.Context, domain, deploymentI
|
||||
|
||||
// Create DNS A record
|
||||
dnsQuery := `
|
||||
INSERT INTO dns_records (fqdn, record_type, value, ttl, namespace, deployment_id, node_id, created_by, created_at)
|
||||
VALUES (?, 'A', ?, 300, ?, ?, ?, 'system', ?)
|
||||
ON CONFLICT(fqdn) DO UPDATE SET value = ?, updated_at = ?
|
||||
INSERT INTO dns_records (fqdn, record_type, value, ttl, namespace, deployment_id, node_id, created_by, created_at, updated_at)
|
||||
VALUES (?, 'A', ?, 300, ?, ?, ?, 'system', ?, ?)
|
||||
ON CONFLICT(fqdn, record_type, value) DO UPDATE SET
|
||||
deployment_id = excluded.deployment_id,
|
||||
node_id = excluded.node_id,
|
||||
updated_at = excluded.updated_at
|
||||
`
|
||||
|
||||
fqdn := domain + "."
|
||||
now := time.Now()
|
||||
|
||||
_, err = h.service.db.Exec(ctx, dnsQuery, fqdn, nodeIP, "", deploymentID, homeNodeID, now, nodeIP, now)
|
||||
_, err = h.service.db.Exec(ctx, dnsQuery, fqdn, nodeIP, "", deploymentID, homeNodeID, now, now)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to create DNS record", zap.Error(err))
|
||||
return
|
||||
|
||||
@ -324,7 +324,10 @@ func (s *DeploymentService) createDNSRecord(ctx context.Context, fqdn, recordTyp
|
||||
query := `
|
||||
INSERT INTO dns_records (fqdn, record_type, value, ttl, namespace, deployment_id, is_active, created_at, updated_at, created_by)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(fqdn) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
|
||||
ON CONFLICT(fqdn, record_type, value) DO UPDATE SET
|
||||
deployment_id = excluded.deployment_id,
|
||||
updated_at = excluded.updated_at,
|
||||
is_active = TRUE
|
||||
`
|
||||
|
||||
now := time.Now()
|
||||
|
||||
35
scripts/generate-source-archive.sh
Executable file
35
scripts/generate-source-archive.sh
Executable file
@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
# Generates a tarball of the current codebase for deployment
|
||||
# Output: /tmp/network-source.tar.gz
|
||||
#
|
||||
# Usage: ./scripts/generate-source-archive.sh
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
OUTPUT="/tmp/network-source.tar.gz"
|
||||
|
||||
echo "Generating source archive..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Remove root-level binaries before archiving (they'll be rebuilt on VPS)
|
||||
rm -f gateway cli node orama-cli-linux 2>/dev/null
|
||||
|
||||
tar czf "$OUTPUT" \
|
||||
--exclude='.git' \
|
||||
--exclude='node_modules' \
|
||||
--exclude='*.log' \
|
||||
--exclude='.DS_Store' \
|
||||
--exclude='bin/' \
|
||||
--exclude='dist/' \
|
||||
--exclude='coverage/' \
|
||||
--exclude='.claude/' \
|
||||
--exclude='testdata/' \
|
||||
--exclude='examples/' \
|
||||
--exclude='*.tar.gz' \
|
||||
.
|
||||
|
||||
echo "Archive created: $OUTPUT"
|
||||
echo "Size: $(du -h $OUTPUT | cut -f1)"
|
||||
Loading…
x
Reference in New Issue
Block a user