Merge branch 'development' of github.com:freeflowuniverse/herolib into development

This commit is contained in:
2025-02-16 10:05:41 +03:00
10 changed files with 313 additions and 306 deletions

View File

@@ -0,0 +1,60 @@
#!/usr/bin/env -S v -n -w -cg -d use_openssl -enable-globals run
import freeflowuniverse.herolib.installers.infra.coredns as coredns_installer
import freeflowuniverse.herolib.osal.coredns
import freeflowuniverse.herolib.core.playbook
// coredns_installer.delete()!
mut installer := coredns_installer.get()!
// coredns_installer.fix()!
installer.start()!
mut script := "
!!dns.a_record
sub_domain: 'host1'
ip: '1.2.3.4'
ttl: 300
!!dns.aaaa_record
sub_domain: 'host1'
ip: '2001:db8::1'
ttl: 300
!!dns.mx_record
sub_domain: '*'
host: 'mail.example.com'
preference: 10
ttl: 300
!!dns.txt_record
sub_domain: '*'
text: 'v=spf1 mx ~all'
ttl: 300
!!dns.srv_record
service: 'ssh'
protocol: 'tcp'
host: 'host1'
target: 'sip.example.com'
port: 5060
priority: 10
weight: 100
ttl: 300
!!dns.ns_record
host: 'ns1.example.com'
ttl: 300
!!dns.soa_record
mbox: 'hostmaster.example.com'
ns: 'ns1.example.com'
refresh: 44
retry: 55
expire: 66
minttl: 100
ttl: 300
"
mut plbook := playbook.new(text: script)!
mut set := coredns.play_dns(mut plbook)!
set.set(key_prefix: 'dns:', domain: 'heroexample.com')!

View File

@@ -181,7 +181,7 @@ function os_update {
fi fi
#apt install apt-transport-https ca-certificates curl software-properties-common -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" --force-yes #apt install apt-transport-https ca-certificates curl software-properties-common -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" --force-yes
package_install "apt-transport-https ca-certificates curl wget software-properties-common tmux" package_install "apt-transport-https ca-certificates curl wget software-properties-common tmux"
package_install "rclone rsync mc redis-server screen net-tools git dnsutils htop ca-certificates screen lsb-release binutils pkg-config" package_install "rclone rsync mc redis-server screen net-tools git dnsutils htop ca-certificates screen lsb-release binutils pkg-config libssl-dev iproute2"
elif [[ "${OSNAME}" == "darwin"* ]]; then elif [[ "${OSNAME}" == "darwin"* ]]; then
if command -v brew >/dev/null 2>&1; then if command -v brew >/dev/null 2>&1; then

View File

@@ -24,7 +24,6 @@ fn startupcmd() ![]zinit.ZProcessNewArgs {
} }
fn running() !bool { fn running() !bool {
mut installer := get()!
mut conn := httpconnection.new(name: 'coredns', url: 'http://localhost:3334')! mut conn := httpconnection.new(name: 'coredns', url: 'http://localhost:3334')!
r := conn.get(prefix: 'health')! r := conn.get(prefix: 'health')!
if r.trim_space() == 'OK' { if r.trim_space() == 'OK' {
@@ -51,7 +50,7 @@ fn stop_post() ! {
// checks if a certain version or above is installed // checks if a certain version or above is installed
fn installed() !bool { fn installed() !bool {
res := os.execute('${osal.profile_path_source_and()!} coredns version') res := os.execute('/bin/bash -c "coredns --version"')
if res.exit_code != 0 { if res.exit_code != 0 {
return false return false
} }
@@ -73,39 +72,11 @@ fn ulist_get() !ulist.UList {
// uploads to S3 server if configured // uploads to S3 server if configured
fn upload() ! { fn upload() ! {
// installers.upload(
// cmdname: 'coredns'
// source: '${gitpath}/target/x86_64-unknown-linux-musl/release/coredns'
// )!
} }
fn install() ! { fn install() ! {
console.print_header('install coredns') console.print_header('install coredns')
build()! // because we need the plugins build()! // because we need the plugins
// mut url := ''
// if core.is_linux_arm()! {
// url = 'https://github.com/coredns/coredns/releases/download/v${version}/coredns_${version}_linux_arm64.tgz'
// } else if core.is_linux_intel()! {
// url = 'https://github.com/coredns/coredns/releases/download/v${version}/coredns_${version}_linux_amd64.tgz'
// } else if core.is_osx_arm()! {
// url = 'https://github.com/coredns/coredns/releases/download/v${version}/coredns_${version}_darwin_arm64.tgz'
// } else if core.is_osx_intel()! {
// url = 'https://github.com/coredns/coredns/releases/download/v${version}/coredns_${version}_darwin_amd64.tgz'
// } else {
// return error('unsported platform')
// }
// mut dest := osal.download(
// url: url
// minsize_kb: 13000
// expand_dir: '/tmp/coredns'
// )!
// mut binpath := dest.file_get('coredns')!
// osal.cmd_add(
// cmdname: 'coredns'
// source: binpath.path
// )!
} }
fn build() ! { fn build() ! {
@@ -132,10 +103,7 @@ fn build() ! {
mut path := pathlib.get_file(path: '${gitpath}/plugin.cfg', create: true)! mut path := pathlib.get_file(path: '${gitpath}/plugin.cfg', create: true)!
path.write(pluginsfile)! path.write(pluginsfile)!
cmd := ' cmd := 'bash -c "cd ${gitpath} && make"'
cd ${gitpath}
make
'
osal.execute_stdout(cmd)! osal.execute_stdout(cmd)!
// now copy to the default bin path // now copy to the default bin path
@@ -148,33 +116,12 @@ fn build() ! {
} }
fn destroy() ! { fn destroy() ! {
// mut systemdfactory := systemd.new()! for zprocess in startupcmd()! {
// systemdfactory.destroy("zinit")! mut sm := startupmanager_get(zprocess.startuptype)!
sm.delete(zprocess.name) or { return error('failed to delete coredns process: ${err}') }
}
// osal.process_kill_recursive(name:'zinit')! osal.execute_silent('sudo rm /usr/local/bin/coredns') or {
// osal.cmd_delete('zinit')! return error('failed to delete coredns bin: ${err}')
}
// osal.package_remove('
// podman
// conmon
// buildah
// skopeo
// runc
// ')!
// //will remove all paths where go/bin is found
// osal.profile_path_add_remove(paths2delete:"go/bin")!
// osal.rm("
// podman
// conmon
// buildah
// skopeo
// runc
// /var/lib/containers
// /var/lib/podman
// /var/lib/buildah
// /tmp/podman
// /tmp/conmon
// ")!
} }

View File

@@ -41,9 +41,10 @@ pub fn configure() ! {
mut path := pathlib.get_file(path: args.config_path, create: true)! mut path := pathlib.get_file(path: args.config_path, create: true)!
path.write(mycorefile)! path.write(mycorefile)!
if args.example { // this doesn't work for local machines, needs to be updated
example_configure()! // if args.example {
} // example_configure()!
// }
} }
pub fn example_configure() ! { pub fn example_configure() ! {

View File

@@ -58,11 +58,12 @@ transfer:transfer
hosts:hosts hosts:hosts
file:file file:file
secondary:secondary secondary:secondary
# etcd:etcd
redis:github.com/codysnider/coredns-redis
loop:loop loop:loop
forward:forward forward:forward
erratic:erratic erratic:erratic
whoami:whoami whoami:whoami
on:github.com/coredns/caddy/onevent on:github.com/coredns/caddy/onevent
sign:sign sign:sign
view:view view:view
redis:github.com/codysnider/coredns-redis

View File

@@ -61,6 +61,7 @@ fn install_() ! {
os.mv('${expand_dir}/go', go_dest)! os.mv('${expand_dir}/go', go_dest)!
os.rmdir_all(expand_dir)! os.rmdir_all(expand_dir)!
osal.profile_path_add_remove(paths2add: '${go_dest}/bin')! osal.profile_path_add_remove(paths2add: '${go_dest}/bin')!
os.setenv('PATH', '${go_dest}/bin:${os.getenv('PATH')}', true)
} }
fn build_() ! { fn build_() ! {

View File

@@ -1,7 +1,75 @@
// Input parameter structs for each record type module coredns
@[params]
struct SRVRecord { import freeflowuniverse.herolib.core.redisclient
// // Input parameter structs for each record type
// DNSRecordSet represents a set of DNS records
struct DNSRecordSet {
pub mut: pub mut:
redis ?&redisclient.Redis
records map[string]Record
}
pub struct Record {
pub mut:
a ?[]A_Record
aaaa ?[]AAAA_Record
txt ?[]TXT_Record
cname ?[]CNAME_Record
ns ?[]NS_Record
mx ?[]MX_Record
srv ?[]SRV_Record
caa ?[]CAA_Record
soa ?SOA_Record
}
@[params]
pub struct A_Record {
pub:
ip string @[required]
ttl int = 300
}
@[params]
pub struct AAAA_Record {
pub:
ip string @[required]
ttl int = 300
}
@[params]
pub struct TXT_Record {
pub:
text string @[required]
ttl int = 300
}
@[params]
pub struct CNAME_Record {
pub:
host string
ttl int = 300
}
@[params]
pub struct NS_Record {
pub:
host string @[required]
ttl int = 300
}
@[params]
pub struct MX_Record {
pub:
host string @[required]
preference int = 10
ttl int = 300
}
@[params]
pub struct SRV_Record {
pub:
target string @[required] target string @[required]
port int @[required] port int @[required]
priority int = 10 priority int = 10
@@ -10,46 +78,16 @@ pub mut:
} }
@[params] @[params]
struct TXTRecord { pub struct CAA_Record {
pub mut: pub:
text string @[required] flag u8
ttl int = 300 tag string
value string
} }
@[params] @[params]
struct MXRecord { pub struct SOA_Record {
pub mut: pub:
host string @[required]
preference int = 10
ttl int = 300
}
@[params]
struct ARecord {
pub mut:
name string @[required]
ip string @[required]
ttl int = 300
}
@[params]
struct AAAARecord {
pub mut:
name string @[required]
ip string @[required]
ttl int = 300
}
@[params]
struct NSRecord {
pub mut:
host string @[required]
ttl int = 300
}
@[params]
struct SOARecord {
pub mut:
mbox string @[required] mbox string @[required]
ns string @[required] ns string @[required]
refresh int = 44 refresh int = 44
@@ -58,16 +96,3 @@ pub mut:
minttl int = 100 minttl int = 100
ttl int = 300 ttl int = 300
} }
// DNSRecordSet represents a set of DNS records
struct DNSRecordSet {
pub mut:
srv []SRVRecord
txt []TXTRecord
mx []MXRecord
a []ARecord
aaaa []AAAARecord
ns []NSRecord
soa ?SOARecord
redis ?&redisclient.Redis
}

View File

@@ -15,20 +15,21 @@ pub fn play_dns(mut plbook playbook.PlayBook) !DNSRecordSet {
match action.name { match action.name {
'a_record' { 'a_record' {
recordset.add_a( recordset.add_a(
name: p.get('name')! sub_domain: p.get_default('sub_domain', '@')!
ip: p.get('ip')! ip: p.get('ip')!
ttl: p.get_int_default('ttl', 300)! ttl: p.get_int_default('ttl', 300)!
) )
} }
'aaaa_record' { 'aaaa_record' {
recordset.add_aaaa( recordset.add_aaaa(
name: p.get('name')! sub_domain: p.get_default('sub_domain', '@')!
ip: p.get('ip')! ip: p.get('ip')!
ttl: p.get_int_default('ttl', 300)! ttl: p.get_int_default('ttl', 300)!
) )
} }
'mx_record' { 'mx_record' {
recordset.add_mx( recordset.add_mx(
sub_domain: p.get_default('sub_domain', '@')!
host: p.get('host')! host: p.get('host')!
preference: p.get_int_default('preference', 10)! preference: p.get_int_default('preference', 10)!
ttl: p.get_int_default('ttl', 300)! ttl: p.get_int_default('ttl', 300)!
@@ -36,12 +37,16 @@ pub fn play_dns(mut plbook playbook.PlayBook) !DNSRecordSet {
} }
'txt_record' { 'txt_record' {
recordset.add_txt( recordset.add_txt(
text: p.get('text')! sub_domain: p.get_default('sub_domain', '@')!
ttl: p.get_int_default('ttl', 300)! text: p.get('text')!
ttl: p.get_int_default('ttl', 300)!
) )
} }
'srv_record' { 'srv_record' {
recordset.add_srv( recordset.add_srv(
host: p.get('host')!
protocol: p.get('protocol')!
service: p.get('service')!
target: p.get('target')! target: p.get('target')!
port: p.get_int('port')! port: p.get_int('port')!
priority: p.get_int_default('priority', 10)! priority: p.get_int_default('priority', 10)!
@@ -51,8 +56,9 @@ pub fn play_dns(mut plbook playbook.PlayBook) !DNSRecordSet {
} }
'ns_record' { 'ns_record' {
recordset.add_ns( recordset.add_ns(
host: p.get('host')! sub_domain: p.get_default('sub_domain', '@')!
ttl: p.get_int_default('ttl', 300)! host: p.get('host')!
ttl: p.get_int_default('ttl', 300)!
) )
} }
'soa_record' { 'soa_record' {
@@ -79,25 +85,30 @@ pub fn play_dns(mut plbook playbook.PlayBook) !DNSRecordSet {
// Example usage: // Example usage:
/* /*
!!dns.a_record !!dns.a_record
name: 'host1' sub_domain: 'host1'
ip: '1.2.3.4' ip: '1.2.3.4'
ttl: 300 ttl: 300
!!dns.aaaa_record !!dns.aaaa_record
name: 'host1' sub_domain: 'host1'
ip: '2001:db8::1' ip: '2001:db8::1'
ttl: 300 ttl: 300
!!dns.mx_record !!dns.mx_record
sub_domain: '*'
host: 'mail.example.com' host: 'mail.example.com'
preference: 10 preference: 10
ttl: 300 ttl: 300
!!dns.txt_record !!dns.txt_record
sub_domain: '*'
text: 'v=spf1 mx ~all' text: 'v=spf1 mx ~all'
ttl: 300 ttl: 300
!!dns.srv_record !!dns.srv_record
service: 'ssh'
protocol: 'tcp'
host: 'host1'
target: 'sip.example.com' target: 'sip.example.com'
port: 5060 port: 5060
priority: 10 priority: 10

View File

@@ -1,205 +1,166 @@
module coredns module coredns
import json
import freeflowuniverse.herolib.core.redisclient import freeflowuniverse.herolib.core.redisclient
import x.json2
// new_dns_record_set creates a new DNSRecordSet // new_dns_record_set creates a new DNSRecordSet
pub fn new_dns_record_set() DNSRecordSet { pub fn new_dns_record_set() DNSRecordSet {
return DNSRecordSet{ return DNSRecordSet{}
srv: []SRVRecord{} }
txt: []TXTRecord{}
mx: []MXRecord{} pub struct AddSRVRecordArgs {
a: []ARecord{} SRV_Record
aaaa: []AAAARecord{} pub:
ns: []NSRecord{} service string @[required]
} protocol string @[required]
host string @[required]
} }
// add_srv adds an SRV record to the set // add_srv adds an SRV record to the set
pub fn (mut rs DNSRecordSet) add_srv(args SRVRecord) { pub fn (mut rs DNSRecordSet) add_srv(args AddSRVRecordArgs) {
rs.srv << SRVRecord{ key := '_${args.service}._${args.protocol}.${args.host}'
target: args.target mut rec := rs.records[key] or { Record{} }
port: args.port if mut v := rec.srv {
priority: args.priority v << args.SRV_Record
weight: args.weight } else {
ttl: args.ttl rec.srv = [args.SRV_Record]
} }
rs.records[key] = rec
}
pub struct AddTXTRecordArgs {
TXT_Record
pub:
sub_domain string = '@'
} }
// add_txt adds a TXT record to the set // add_txt adds a TXT record to the set
pub fn (mut rs DNSRecordSet) add_txt(args TXTRecord) { pub fn (mut rs DNSRecordSet) add_txt(args AddTXTRecordArgs) {
rs.txt << TXTRecord{ mut rec := rs.records[args.sub_domain] or { Record{} }
text: args.text if mut v := rec.txt {
ttl: args.ttl v << args.TXT_Record
} } else {
rec.txt = [args.TXT_Record]
}
rs.records[args.sub_domain] = rec
}
pub struct AddMXRecordArgs {
MX_Record
pub:
sub_domain string = '@'
} }
// add_mx adds an MX record to the set // add_mx adds an MX record to the set
pub fn (mut rs DNSRecordSet) add_mx(args MXRecord) { pub fn (mut rs DNSRecordSet) add_mx(args AddMXRecordArgs) {
rs.mx << MXRecord{ mut rec := rs.records[args.sub_domain] or { Record{} }
host: args.host if mut v := rec.mx {
preference: args.preference v << args.MX_Record
ttl: args.ttl } else {
} rec.mx = [args.MX_Record]
}
rs.records[args.sub_domain] = rec
}
pub struct AddARecordArgs {
A_Record
pub:
sub_domain string = '@'
} }
// add_a adds an A record to the set // add_a adds an A record to the set
pub fn (mut rs DNSRecordSet) add_a(args ARecord) { pub fn (mut rs DNSRecordSet) add_a(args AddARecordArgs) {
rs.a << ARecord{ mut rec := rs.records[args.sub_domain] or { Record{} }
name: args.name if mut v := rec.a {
ip: args.ip v << args.A_Record
ttl: args.ttl } else {
} rec.a = [args.A_Record]
}
rs.records[args.sub_domain] = rec
}
pub struct AddAAAARecordArgs {
AAAA_Record
pub:
sub_domain string = '@'
} }
// add_aaaa adds an AAAA record to the set // add_aaaa adds an AAAA record to the set
pub fn (mut rs DNSRecordSet) add_aaaa(args AAAARecord) { pub fn (mut rs DNSRecordSet) add_aaaa(args AddAAAARecordArgs) {
rs.aaaa << AAAARecord{ mut rec := rs.records[args.sub_domain] or { Record{} }
name: args.name if mut v := rec.aaaa {
ip: args.ip v << args.AAAA_Record
ttl: args.ttl } else {
} rec.aaaa = [args.AAAA_Record]
}
rs.records[args.sub_domain] = rec
}
pub struct AddNSRecordArgs {
NS_Record
pub:
sub_domain string = '@'
} }
// add_ns adds an NS record to the set // add_ns adds an NS record to the set
pub fn (mut rs DNSRecordSet) add_ns(args NSRecord) { pub fn (mut rs DNSRecordSet) add_ns(args AddNSRecordArgs) {
rs.ns << NSRecord{ mut rec := rs.records[args.sub_domain] or { Record{} }
host: args.host if mut v := rec.ns {
ttl: args.ttl v << args.NS_Record
} } else {
rec.ns = [args.NS_Record]
}
rs.records[args.sub_domain] = rec
} }
// set_soa sets the SOA record for the set // set_soa sets the SOA record for the set
pub fn (mut rs DNSRecordSet) set_soa(args SOARecord) { pub fn (mut rs DNSRecordSet) set_soa(args SOA_Record) {
rs.soa = SOARecord{ mut rec := rs.records['@'] or { Record{} }
mbox: args.mbox rec.soa = args
ns: args.ns rs.records['@'] = rec
refresh: args.refresh }
retry: args.retry
expire: args.expire pub struct SetArgs {
minttl: args.minttl pub:
ttl: args.ttl domain string
} key_prefix string
} }
// populate_redis populates Redis with the DNS records // populate_redis populates Redis with the DNS records
//domain e.g. example.com. (not sure the . is at end) // domain e.g. example.com. (not sure the . is at end)
pub fn (rs DNSRecordSet) set(domain string) ! { pub fn (mut rs DNSRecordSet) set(args SetArgs) ! {
mut redis := rs.redis or {redisclient.core_get()!} mut redis := rs.redis or {
r := redisclient.core_get()!
rs.redis = r
r
}
// Store SRV records key := '${args.key_prefix}${args.domain}.'
for srv in rs.srv { for field, val in rs.records {
key := '_ssh._tcp.host1' redis.hset(key, field, json2.encode(val))!
value := json.encode({ }
'srv': {
'ttl': srv.ttl
'target': srv.target
'port': srv.port
'priority': srv.priority
'weight': srv.weight
}
})
redis.hset(domain, key, value)!
}
// Store TXT and MX records for wildcard
if rs.txt.len > 0 || rs.mx.len > 0 {
mut records := map[string]map[string]json.Any{}
if rs.txt.len > 0 {
records['txt'] = {
'text': rs.txt[0].text
'ttl': "${rs.txt[0].ttl}"
}
}
if rs.mx.len > 0 {
records['mx'] = {
'host': rs.mx[0].host
'priority': rs.mx[0].preference
'ttl': rs.mx[0].ttl
}
}
redis.hset(domain, '*', json.encode(records))!
}
// Store A records
for a in rs.a {
value := json.encode({
'a': {
'ip4': a.ip
'ttl': "${a.ttl}"
}
})
redis.hset(domain, a.name, value)!
}
// Store AAAA records
for aaaa in rs.aaaa {
value := json.encode({
'aaaa': {
'ip6': aaaa.ip
'ttl': aaaa.ttl
}
})
redis.hset(domain, aaaa.name, value)!
}
// Store NS records
if rs.ns.len > 0 {
mut ns_records := []map[string]json.Any{}
for ns in rs.ns {
ns_records << {
'host': ns.host
'ttl': ns.ttl
}
}
value := json.encode({
'ns': ns_records
})
redis.hset(domain, 'subdel', value)!
}
// Store SOA and root NS records at @
if soa := rs.soa {
mut root_records := map[string]json.Any{}
root_records['soa'] = {
'ttl': soa.ttl
'minttl': soa.minttl
'mbox': soa.mbox
'ns': soa.ns
'refresh': soa.refresh
'retry': soa.retry
'expire': soa.expire
}
if rs.ns.len > 0 {
mut ns_records := []map[string]json.Any{}
for ns in rs.ns {
ns_records << {
'host': ns.host
'ttl': ns.ttl
}
}
root_records['ns'] = ns_records
}
redis.hset(domain, '@', json.encode(root_records))!
}
} }
pub fn (mut rs DNSRecordSet) example() ! { pub fn (mut rs DNSRecordSet) example() ! {
// Create and populate DNS records // Create and populate DNS records
rs.set_soa(mbox: 'hostmaster.example.net.', ns: 'ns1.example.net.') rs.set_soa(mbox: 'hostmaster.example.net.', ns: 'ns1.example.net.')
rs.add_srv(target: 'tcp.example.com.', port: 123) rs.add_srv(service: 'ssh', protocol: 'tcp', host: 'host1', target: 'tcp.example.com.', port: 123)
rs.add_txt(text: 'this is a wildcard') rs.add_txt(sub_domain: '*', text: 'this is a wildcard')
rs.add_mx(host: 'host1.example.net.') rs.add_mx(sub_domain: '*', host: 'host1.example.net.')
rs.add_a(name: 'host1', ip: '5.5.5.5') rs.add_a(sub_domain: 'host1', ip: '5.5.5.5')
rs.add_aaaa(name: 'host1', ip: '2001:db8::1') rs.add_aaaa(sub_domain: 'host1', ip: '2001:db8::1')
rs.add_txt(text: 'this is not a wildcard') rs.add_txt(sub_domain: 'sub.*', text: 'this is not a wildcard')
rs.add_ns(host: 'ns1.subdel.example.net.') rs.add_ns(sub_domain: 'subdel', host: 'ns1.subdel.example.net.')
rs.add_ns(host: 'ns2.subdel.example.net.') rs.add_ns(sub_domain: 'subdel', host: 'ns2.subdel.example.net.')
rs.add_ns(host: 'ns1.example.net.') rs.add_ns(host: 'ns1.example.net.')
rs.add_ns(host: 'ns2.example.net.') rs.add_ns(host: 'ns2.example.net.')
// Store records in Redis // Store records in Redis
rs.set("example.com")! rs.set(domain: 'example.com')!
} }

View File

@@ -113,7 +113,7 @@ pub fn ipaddr_pub_get() !string {
//also check the address is on local interface //also check the address is on local interface
pub fn ipaddr_pub_get_check() !string { pub fn ipaddr_pub_get_check() !string {
// Check if the public IP matches any local interface // Check if the public IP matches any local interface
public_ip := ipaddr_pub_get_check()! public_ip := ipaddr_pub_get()!
if !is_ip_on_local_interface(public_ip)! { if !is_ip_on_local_interface(public_ip)! {
return error('Public IP ${public_ip} is NOT bound to any local interface (possibly behind a NAT firewall).') return error('Public IP ${public_ip} is NOT bound to any local interface (possibly behind a NAT firewall).')
} }
@@ -123,7 +123,7 @@ pub fn ipaddr_pub_get_check() !string {
// Check if the public IP matches any of the local network interfaces // Check if the public IP matches any of the local network interfaces
pub fn is_ip_on_local_interface(public_ip string) !bool { pub fn is_ip_on_local_interface(public_ip string) !bool {
interfaces := exec(cmd: 'ip addr show', stdout: false) or { interfaces := exec(cmd: 'ip addr show', stdout: false) or {
return error('Failed to enumerate network interfaces.') return error('Failed to enumerate network interfaces: ${err}')
} }
lines := interfaces.output.split('\n') lines := interfaces.output.split('\n')