refactor(metal): more robust installer callback with colored logs

This commit is contained in:
Khue Doan
2025-10-04 19:57:18 +07:00
parent 9e2f7b22f7
commit 59524980c3
6 changed files with 147 additions and 63 deletions

View File

@@ -12,13 +12,8 @@ configure:
git status
metal:
make -C metal
# TODO new install flow
# sudo -v
# sudo nix run .#nixosPxeServer
# echo 'waiting until installer is booted' && sleep 30
# ssh root@192.168.1.2 cat /etc/os-release | grep VARIANT_ID=installer && nixos-anywhere --flake .#metal1 --target-host root@192.168.1.2
# nixos-rebuild --flake . --target-host root@metal1.local switch
@echo 'Running PXE server as root for privileged ports'
sudo nix run .#homelabInstall
system:
make -C system

View File

@@ -29,7 +29,8 @@
pname = "homelab-install";
version = "0.1.0";
src = ./tools;
vendorHash = "sha256-cYS7Jel5ARf+FioPxP5/Wu+LHQLX1U94kfMQ+K26Rnk=";
# TODO better way to build this shit
vendorHash = "sha256-rTJt3UWRUyhRDx1Sdno0fFBYMb4RPtzB7Z7sg45ZJ8o=";
postInstall = ''
wrapProgram $out/bin/homelab-install \

View File

@@ -1,4 +1,10 @@
{ pkgs, lib, config, modulesPath, ... }:
{
pkgs,
lib,
config,
modulesPath,
...
}:
{
imports = [
@@ -23,16 +29,26 @@
];
};
systemd.services."phone-home" =
systemd.services."installer-callback" =
let
reportScript = pkgs.writeShellScript "phone-home.sh" ''
reportScript = pkgs.writeShellScript "installer-callback.sh" ''
set -euo pipefail
sleep 30
# Wait for default route and IP address to be ready
echo "Waiting for network to be ready..."
while true; do
if ${pkgs.iproute2}/bin/ip route show default > /dev/null 2>&1; then
iface=$(${pkgs.iproute2}/bin/ip route show default | ${pkgs.coreutils}/bin/cut -d ' ' -f 5)
ip=$(${pkgs.iproute2}/bin/ip route show default | ${pkgs.coreutils}/bin/cut -d ' ' -f 9)
if [ -n "$iface" ] && [ -n "$ip" ] && [ "$ip" != "0.0.0.0" ]; then
echo "Network is ready: interface=$iface ip=$ip"
break
fi
fi
sleep 1
done
iface=$(${pkgs.iproute2}/bin/ip route show default | ${pkgs.coreutils}/bin/cut -d ' ' -f 5)
mac=$(cat /sys/class/net/$iface/address)
ip=$(${pkgs.iproute2}/bin/ip route show default | ${pkgs.coreutils}/bin/cut -d ' ' -f 9)
${pkgs.curl}/bin/curl -sf -X POST "http://192.168.1.15:5000/report" \
--data-urlencode "mac=$mac" \

View File

@@ -4,7 +4,6 @@ import (
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
@@ -12,11 +11,13 @@ import (
"sync"
"syscall"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/log"
"go.universe.tf/netboot/out/ipxe"
"go.universe.tf/netboot/pixiecore"
)
// Hardcoded map of MAC → flake target
// TODO do not harcode map of MAC → flake target
var hostMap = map[string]string{
"bc:24:11:d0:28:34": ".#metal2",
"bc:24:11:0d:2f:20": ".#metal1",
@@ -32,19 +33,38 @@ var (
shutdownCh = make(chan struct{})
)
func getSubsystemStyle(subsystem string) lipgloss.Style {
switch subsystem {
case "DHCP":
return lipgloss.NewStyle().Foreground(lipgloss.Color("1"))
case "TFTP":
return lipgloss.NewStyle().Foreground(lipgloss.Color("2"))
case "HTTP":
return lipgloss.NewStyle().Foreground(lipgloss.Color("3"))
case "CALLBACK":
return lipgloss.NewStyle().Foreground(lipgloss.Color("4"))
case "INSTALL":
return lipgloss.NewStyle().Foreground(lipgloss.Color("5"))
case "PXE":
return lipgloss.NewStyle().Foreground(lipgloss.Color("6"))
default:
return lipgloss.NewStyle().Foreground(lipgloss.Color("7"))
}
}
var (
kernelPath = flag.String("kernel", "", "Path to kernel bzImage")
initrdPath = flag.String("initrd", "", "Path to initrd")
initPath = flag.String("init", "", "Path to init in the system")
address = flag.String("address", "0.0.0.0", "Address to listen on (default: 0.0.0.0 for all interfaces)")
debug = flag.Bool("debug", false, "Enable debug mode")
)
type phoneHome struct {
type callback struct {
MAC string
IP string
}
// Pixiecore boot handler
type bootHandler struct {
kernelPath string
initrdPath string
@@ -54,7 +74,6 @@ type bootHandler struct {
func (b bootHandler) BootSpec(m pixiecore.Machine) (*pixiecore.Spec, error) {
mac := m.MAC.String()
if _, ok := hostMap[mac]; ok {
// Serve the NixOS installer kernel/initrd
return &pixiecore.Spec{
Kernel: pixiecore.ID("kernel"),
Initrd: []pixiecore.ID{"initrd"},
@@ -100,34 +119,19 @@ func main() {
log.Fatal("Usage: homelab-install -kernel <path> -initrd <path> -init <path>")
}
fmt.Println("Servers to install:", hostMap)
if *debug {
log.SetLevel(log.DebugLevel)
}
// Load pixiecore's embedded iPXE binaries
ipxeMap := make(map[pixiecore.Firmware][]byte)
// Load BIOS/PXE binary
biosData, err := ipxe.Asset("third_party/ipxe/src/bin/undionly.kpxe")
if err != nil {
log.Fatalf("Failed to load embedded BIOS iPXE binary: %v", err)
}
ipxeMap[pixiecore.FirmwareX86PC] = biosData
// Load EFI64 binary
efi64Data, err := ipxe.Asset("third_party/ipxe/src/bin-x86_64-efi/ipxe.efi")
if err != nil {
log.Fatalf("Failed to load embedded EFI64 iPXE binary: %v", err)
log.Fatal("Failed to load embedded EFI64 iPXE binary", "error", err)
}
ipxeMap[pixiecore.FirmwareEFI64] = efi64Data
ipxeMap[pixiecore.FirmwareEFIBC] = efi64Data
// Load EFI32 binary
efi32Data, err := ipxe.Asset("third_party/ipxe/src/bin-i386-efi/ipxe.efi")
if err != nil {
log.Fatalf("Failed to load embedded EFI32 iPXE binary: %v", err)
}
ipxeMap[pixiecore.FirmwareEFI32] = efi32Data
// Start Pixiecore PXE server
pxeServer = &pixiecore.Server{
Booter: bootHandler{
kernelPath: *kernelPath,
@@ -139,32 +143,44 @@ func main() {
HTTPPort: 8080,
Ipxe: ipxeMap,
Debug: func(subsystem, msg string) {
log.Printf("[DEBUG] %s: %s", subsystem, msg)
coloredSubsystem := getSubsystemStyle(subsystem).Render(fmt.Sprintf("[%s]", subsystem))
log.Debugf("%s %s", coloredSubsystem, msg)
},
Log: func(subsystem, msg string) {
log.Printf("[INFO] %s: %s", subsystem, msg)
coloredSubsystem := getSubsystemStyle(subsystem).Render(fmt.Sprintf("[%s]", subsystem))
log.Infof("%s %s", coloredSubsystem, msg)
},
}
go func() {
if err := pxeServer.Serve(); err != nil {
log.Fatalf("PXE server error: %v", err)
log.Fatal("PXE server error", "error", err)
}
}()
// Start HTTP phone-home server
mux := http.NewServeMux()
mux.HandleFunc("/report", reportHandler)
server = &http.Server{Addr: ":5000", Handler: mux}
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("HTTP server error: %v", err)
log.Fatal("HTTP server error", "error", err)
}
}()
fmt.Println("PXE server listening on port 8080")
fmt.Println("Phone-home server listening on port 5000")
// These ports can technically be set for testing, but the
// protocols burned in firmware on the client side hardcode these,
// so if you change them in production, nothing will work.
log.Info(
"Running on ports",
"DHCP", 67,
"TFTP", 69,
"PXE", 4001,
"HTTP", 8080,
"CALLBACK", 5000,
)
log.Info("Servers to install", "hosts", hostMap)
// Handle SIGINT for cleanup
sigCh := make(chan os.Signal, 1)
@@ -196,44 +212,50 @@ func reportHandler(w http.ResponseWriter, r *http.Request) {
return
}
fmt.Printf("Got phone-home from %s at %s\n", mac, ip)
coloredSubsystem := getSubsystemStyle("CALLBACK").Render("[CALLBACK]")
log.Infof("%s Got callback mac=%s ip=%s", coloredSubsystem, mac, ip)
mu.Lock()
defer mu.Unlock()
if flake, ok := hostMap[mac]; ok {
if !installed[mac] && !inFlight[mac] {
fmt.Printf("→ Starting nixos-anywhere for %s on %s\n", flake, ip)
coloredSubsystem := getSubsystemStyle("INSTALL").Render("[INSTALL]")
log.Infof("%s Starting installation flake=%s ip=%s", coloredSubsystem, flake, ip)
inFlight[mac] = true
go monitorInstall(mac, ip, flake)
} else {
fmt.Printf("%s already installed or in progress, ignoring\n", mac)
log.Warn("Host already installed or in progress", "mac", mac)
}
} else {
fmt.Println("Unknown MAC, ignoring")
log.Warn("Unknown MAC, ignoring", "mac", mac)
}
w.WriteHeader(http.StatusOK)
}
func monitorInstall(mac, ip, flake string) {
fmt.Printf("Running nixos-anywhere install of %s on %s\n", flake, ip)
coloredSubsystem := getSubsystemStyle("INSTALL").Render("[INSTALL]")
log.Infof("%s Running installation flake=%s ip=%s", coloredSubsystem, flake, ip)
// Run nixos-anywhere with password authentication
cmd := exec.Command("nixos-anywhere",
"--env-password",
"--no-substitute-on-destination",
"--flake", flake,
fmt.Sprintf("root@%s", ip),
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// Set SSHPASS environment variable with the hardcoded installer password
cmd.Env = append(os.Environ(), "SSHPASS=nixos-installer")
if *debug {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
if err := cmd.Run(); err != nil {
fmt.Printf("Error installing %s: %v\n", mac, err)
coloredSubsystem := getSubsystemStyle("INSTALL").Render("[INSTALL]")
log.Errorf("%s Error installing host mac=%s error=%v", coloredSubsystem, mac, err)
mu.Lock()
delete(inFlight, mac)
mu.Unlock()
@@ -245,8 +267,9 @@ func monitorInstall(mac, ip, flake string) {
delete(inFlight, mac)
installed[mac] = true
fmt.Printf("Successfully installed %s\n", mac)
fmt.Printf("Installed servers: %+v\n", installed)
coloredSubsystem = getSubsystemStyle("INSTALL").Render("[INSTALL]")
log.Infof("%s Successfully installed host mac=%s", coloredSubsystem, mac)
log.Infof("%s Installation status: installed=%v", coloredSubsystem, installed)
pending := make(map[string]struct{})
for k := range hostMap {
@@ -254,23 +277,23 @@ func monitorInstall(mac, ip, flake string) {
pending[k] = struct{}{}
}
}
fmt.Printf("Pending servers: %+v\n", pending)
log.Infof("%s Pending servers: %v", coloredSubsystem, pending)
if len(pending) == 0 {
fmt.Println("All servers installed, shutting down.")
log.Infof("%s All servers installed, shutting down", coloredSubsystem)
close(shutdownCh)
}
}
func stopServer() {
fmt.Println("Stopping servers...")
log.Info("Stopping servers...")
if server != nil {
server.Close()
fmt.Println("HTTP server shutdown complete.")
log.Info("HTTP server shutdown complete")
}
if pxeServer != nil {
pxeServer.Shutdown()
fmt.Println("PXE server stopped.")
log.Info("PXE server stopped")
}
os.Exit(0)
}

View File

@@ -5,7 +5,22 @@ go 1.24.6
require go.universe.tf/netboot v0.0.0-20240531232330-2ed7bd30206a
require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/lipgloss v1.1.0 // indirect
github.com/charmbracelet/log v0.4.2 // indirect
github.com/charmbracelet/x/ansi v0.8.0 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/sys v0.30.0 // indirect
)

View File

@@ -397,6 +397,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -406,6 +408,18 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -453,6 +467,8 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -604,6 +620,8 @@ github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NB
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -616,6 +634,10 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
@@ -631,6 +653,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@@ -661,6 +685,9 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
@@ -696,6 +723,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -747,6 +776,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -957,6 +988,9 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=