Files
origin_client/go/hostinfo.go
T
warkanumandClaude Sonnet 5 2590e404f5
CI / rust (push) Successful in 3m6s
CI / go (push) Successful in 33s
CI / js (push) Successful in 34s
feat: initial Go/JS/Rust Origin check-in client SDKs
Standalone clients that resolve a machine unique ID (OS machine ID first,
falling back to a generated UUID persisted to disk), detect hostname/IP/
container/orchestrator, and register+re-ping origin.warky.dev's public
service-checkin endpoint on a daily interval. Ported from icy2's internal
origincheckin package but extended to the full check-in contract and made
dependency-light for external reuse.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 15:25:30 +02:00

101 lines
2.4 KiB
Go

package originclient
import (
"net"
"os"
"strings"
)
// Hostname returns the OS hostname, or "" if it cannot be determined.
func Hostname() string {
h, err := os.Hostname()
if err != nil {
return ""
}
return h
}
// OutboundIP returns the first non-loopback IPv4 address bound to this
// host. It is a LAN-facing address, not the host's public internet
// address, and is not part of Origin's wire contract — it exists so
// callers can populate BaseURL/InternalURL/Description themselves.
func OutboundIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return ""
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok || ipNet.IP.IsLoopback() {
continue
}
if ip4 := ipNet.IP.To4(); ip4 != nil {
return ip4.String()
}
}
return ""
}
// ContainerID best-effort detects the Docker/Kubernetes container ID this
// process runs in. It returns "" outside a container or when detection
// fails; an empty container ID is treated as "not containerized" rather
// than an error.
func ContainerID() string {
if data, err := os.ReadFile("/proc/self/cgroup"); err == nil {
if id := parseCgroupContainerID(data); id != "" {
return id
}
}
if _, err := os.Stat("/.dockerenv"); err == nil {
if h := Hostname(); isContainerIDLike(h) {
return h
}
}
return ""
}
// Orchestrator best-effort detects the orchestration environment:
// "kubernetes" under Kubernetes, "docker" in a plain Docker container, ""
// otherwise.
func Orchestrator() string {
if os.Getenv("KUBERNETES_SERVICE_HOST") != "" {
return "kubernetes"
}
if _, err := os.Stat("/.dockerenv"); err == nil {
return "docker"
}
return ""
}
// parseCgroupContainerID extracts a container ID from /proc/self/cgroup
// content, handling both cgroup v1 (".../docker/<id>") and cgroup v2
// systemd-scope (".../docker-<id>.scope") layouts.
func parseCgroupContainerID(data []byte) string {
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(line)
idx := strings.LastIndex(line, "/")
if idx == -1 {
continue
}
segment := line[idx+1:]
segment = strings.TrimSuffix(segment, ".scope")
segment = strings.TrimPrefix(segment, "docker-")
if isContainerIDLike(segment) {
return segment
}
}
return ""
}
func isContainerIDLike(s string) bool {
if len(s) < 12 || len(s) > 64 {
return false
}
for _, r := range s {
if (r < '0' || r > '9') && (r < 'a' || r > 'f') {
return false
}
}
return true
}