Private
Public Access
feat: initial Go/JS/Rust Origin check-in client SDKs
CI / rust (push) Successful in 3m6s
CI / go (push) Successful in 33s
CI / js (push) Successful in 34s
CI / rust (push) Successful in 3m6s
CI / go (push) Successful in 33s
CI / js (push) Successful in 34s
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>
This commit is contained in:
+100
@@ -0,0 +1,100 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user