diff --git a/goconserver/gomod/README.md b/goconserver/gomod/README.md new file mode 100644 index 0000000..7ee69f2 --- /dev/null +++ b/goconserver/gomod/README.md @@ -0,0 +1,14 @@ +# Pinned Go module manifest for goconserver + +`go.mod` + `go.sum` pin goconserver's Go dependencies so the rpm build is **reproducible without +vendoring the whole dependency tree**. The build runs **inside a mock chroot** (network enabled) and +downloads the modules from the Go proxy at build time; `go.sum` integrity-checks every module, so the +result is deterministic even though the deps are not committed. + +- Generated from **xcat2/goconserver @ 6166fe5ec1c5b3c20475e322a9f0e8e93c87e45f** (the pin in + mockbuild-all.pl / goconserver/mockbuild.pl), with the archived `github.com/kr/pty` replaced by + `github.com/creack/pty@v1.1.21` and the etcd storage backend removed (xCAT uses file storage only). +- To regenerate after bumping the goconserver pin: clone at the new SHA, remove `storage/etcd*`, + `go mod init github.com/xcat2/goconserver`, + `go mod edit -replace github.com/kr/pty=github.com/creack/pty@v1.1.21`, `go mod tidy`, then copy + go.mod/go.sum here. (No `go mod vendor` needed.) diff --git a/goconserver/gomod/go.mod b/goconserver/gomod/go.mod new file mode 100644 index 0000000..092743d --- /dev/null +++ b/goconserver/gomod/go.mod @@ -0,0 +1,27 @@ +module github.com/xcat2/goconserver + +go 1.26.4 + +replace github.com/kr/pty => github.com/creack/pty v1.1.21 + +require ( + github.com/golang/protobuf v1.5.4 + github.com/gorilla/mux v1.8.1 + github.com/kr/pty v0.0.0-00010101000000-000000000000 + github.com/sirupsen/logrus v1.9.4 + github.com/spf13/cobra v1.10.2 + github.com/spf13/pflag v1.0.10 + golang.org/x/crypto v0.54.0 + golang.org/x/net v0.57.0 + google.golang.org/grpc v1.83.0 + gopkg.in/yaml.v2 v2.4.0 +) + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/term v0.45.0 // indirect + golang.org/x/text v0.40.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/protobuf v1.36.11 // indirect +) diff --git a/goconserver/gomod/go.sum b/goconserver/gomod/go.sum new file mode 100644 index 0000000..a466044 --- /dev/null +++ b/goconserver/gomod/go.sum @@ -0,0 +1,70 @@ +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ= +google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/goconserver/mockbuild.pl b/goconserver/mockbuild.pl index e152b09..ed31dcb 100755 --- a/goconserver/mockbuild.pl +++ b/goconserver/mockbuild.pl @@ -22,7 +22,7 @@ my $version = '0.3.3'; my $go_repo = 'https://github.com/xcat2/goconserver.git'; # Immutable pin: goconserver 0.3.3 is unreleased (newest tag v0.3.2) so it lives only on master. # mockbuild-all.pl passes --go-ref with the canonical pin; this default keeps standalone runs -# reproducible too. `git clone --branch` cannot take a raw SHA, so the clone below fetches by ref. +# reproducible too. The committed vendored/ tree (go.mod/go.sum/vendor) corresponds to THIS SHA. my $go_ref = '6166fe5ec1c5b3c20475e322a9f0e8e93c87e45f'; my $release_suffix = ''; # CD Release bump (".snap."); passed by mockbuild-all.pl my $build_timestamp; @@ -43,7 +43,10 @@ GetOptions( die "Run as root (current uid=$>)\n" if $> != 0; -for my $bin (qw(go git rpmbuild rpm)) { +# The Go compile happens INSIDE the mock chroot (BuildRequires: golang); the host only needs to +# fetch the pinned source and drive mock. (No host `go` build any more -- that was the non-hermetic +# path this rewrite removes.) +for my $bin (qw(git rpm mock)) { run("command -v " . sh_quote($bin) . " >/dev/null 2>&1"); } @@ -74,17 +77,24 @@ unless ($SOURCE_DATE_EPOCH && $SOURCE_DATE_EPOCH =~ /^\d+$/) { $SOURCE_DATE_EPOCH = time() unless $SOURCE_DATE_EPOCH =~ /^\d+$/; $ENV{SOURCE_DATE_EPOCH} = $SOURCE_DATE_EPOCH; +# goconserver is a CGO-free static Go binary. el8/el9 chroots ship a Go too old to build 0.3.3, so +# always COMPILE in the el10 chroot for this arch (regardless of the target EL), then ship the static +# binary to every EL repo. The Release still carries the target's dist tag (4.el$rel) so each EL repo +# gets a correctly-named, byte-identical rpm. +(my $build_cfg = $mock_cfg) =~ s/-\d+-/-10-/; + print_step("Configuration"); -print "repo_root: $repo_root\n"; -print "pkg_dir: $pkg_dir\n"; -print "work_dir: $work_dir\n"; -print "result_dir: $result_dir\n"; -print "log_dir: $log_dir\n"; -print "mock_cfg: $mock_cfg\n"; -print "arch: $arch\n"; -print "version: $version\n"; -print "go_repo: $go_repo\n"; -print "go_ref: $go_ref\n"; +print "repo_root: $repo_root\n"; +print "pkg_dir: $pkg_dir\n"; +print "work_dir: $work_dir\n"; +print "result_dir: $result_dir\n"; +print "log_dir: $log_dir\n"; +print "mock_cfg: $mock_cfg (target dist tag: el$rel)\n"; +print "build_cfg: $build_cfg (el10 -- portable static build for arch $arch)\n"; +print "arch: $arch\n"; +print "version: $version\n"; +print "go_ref: $go_ref\n"; +print "release_suffix: " . ($release_suffix ne '' ? $release_suffix : '(none)') . "\n"; print "skip_install: $skip_install\n"; make_path($result_dir); @@ -94,79 +104,45 @@ print_step("Stage build environment"); remove_tree($work_dir) if -d $work_dir; make_path($work_dir); -# Unique per run (nested under the run/target-scoped --work-dir) so concurrent builds -- e.g. -# parallel EL targets on one host -- don't wipe each other. (Was a shared -# /var/tmp/xcat-rpmbuild-goconserver, which collided under parallelism.) -my $rpmbuild_top = "$work_dir/rpmbuild"; -remove_tree($rpmbuild_top) if -d $rpmbuild_top; -for my $d (qw(BUILD BUILDROOT RPMS SOURCES SPECS SRPMS)) { - make_path("$rpmbuild_top/$d"); -} - +# --- Fetch the pinned goconserver source (immutable SHA -> reproducible) --- print_step("Clone goconserver source"); my $src_dir = "$work_dir/goconserver-src"; -# Fetch the exact pinned ref (a SHA, or a branch/tag). `git clone --branch` rejects a raw SHA, so -# init + shallow fetch the one object + checkout it -- reproducible and immutable, never "latest -# master". (xcat2/goconserver has allowReachableSHA1InWant, so fetching a master-reachable SHA works.) my $clone_log = sh_quote("$log_dir/git-clone.log"); run("git init -q " . sh_quote($src_dir) . " >$clone_log 2>&1"); run("git -C " . sh_quote($src_dir) . " remote add origin " . sh_quote($go_repo) . " >>$clone_log 2>&1"); run("git -C " . sh_quote($src_dir) . " fetch --depth 1 origin " . sh_quote($go_ref) . " >>$clone_log 2>&1"); run("git -C " . sh_quote($src_dir) . " checkout -q FETCH_HEAD >>$clone_log 2>&1"); -# etcd storage backend has broken deps with modern Go modules; -# xCAT only uses file storage, so remove etcd before building. +# etcd storage backend has broken deps with modern Go modules; xCAT only uses file storage. unlink "$src_dir/storage/etcd.go"; remove_tree("$src_dir/storage/etcd") if -d "$src_dir/storage/etcd"; +remove_tree("$src_dir/.git") if -d "$src_dir/.git"; # keep the SRPM tarball clean + reproducible -print_step("Initialize Go modules"); -$ENV{GOPATH} = "$work_dir/gopath"; -$ENV{GOCACHE} = "$work_dir/gocache"; -$ENV{GOMODCACHE} = "$work_dir/gomodcache"; -$ENV{CGO_ENABLED} = '0'; +# --- Overlay the committed, PINNED go.mod/go.sum (no vendored tree) --- +# go.mod already replaces the archived github.com/kr/pty with creack/pty (see gomod/README.md), and +# go.sum integrity-checks every module. The in-chroot build downloads the modules from the Go proxy +# (mock networking is enabled) but is reproducible because go.sum pins them -- no `go mod tidy`, and +# no 400k-line vendor tree committed. +print_step("Overlay pinned go.mod/go.sum"); +my $gomod_dir = "$pkg_dir/gomod"; +die "pinned go.mod/go.sum missing under $gomod_dir (regenerate per gomod/README.md)\n" + unless -f "$gomod_dir/go.mod" && -f "$gomod_dir/go.sum"; +copy("$gomod_dir/go.mod", "$src_dir/go.mod") or die "copy go.mod: $!\n"; +copy("$gomod_dir/go.sum", "$src_dir/go.sum") or die "copy go.sum: $!\n"; -# The archived github.com/kr/pty sets SysProcAttr.Ctty to the parent-side fd, -# which modern Go's os/exec rejects with "Setctty set but Ctty not valid in -# child". Replace it with the API-identical maintained fork creack/pty. -run("cd " . sh_quote($src_dir) . " && " . - "go mod init github.com/xcat2/goconserver && " . - "go mod edit -replace github.com/kr/pty=github.com/creack/pty\@v1.1.21 && " . - "go mod tidy" . - " >" . sh_quote("$log_dir/go-mod.log") . " 2>&1"); +# --- Assemble SRPM sources: the source tree (incl. vendor) + the xcat-authored unit + config --- +print_step("Assemble SRPM sources"); +my $srctop = "goconserver-$version"; +my $staged = "$work_dir/$srctop"; +remove_tree($staged) if -d $staged; +run("cp -a " . sh_quote($src_dir) . " " . sh_quote($staged)); +my $sources_dir = "$work_dir/sources"; +make_path($sources_dir); +my $tarball = "$sources_dir/goconserver-$version.tar.gz"; +run("tar --sort=name --owner=0 --group=0 --mtime=\@$SOURCE_DATE_EPOCH" . + " -C " . sh_quote($work_dir) . " -czf " . sh_quote($tarball) . " " . sh_quote($srctop)); -print_step("Build goconserver binaries"); -my $go_build_dir = "$work_dir/bin"; -make_path($go_build_dir); - -my $ldflags = "-X main.Version=$version"; - -run("cd " . sh_quote($src_dir) . " && " . - "go build -trimpath -buildvcs=false -ldflags " . sh_quote($ldflags) . - " -o " . sh_quote("$go_build_dir/goconserver") . " goconserver.go" . - " >" . sh_quote("$log_dir/go-build-server.log") . " 2>&1"); - -run("cd " . sh_quote($src_dir) . " && " . - "go build -trimpath -buildvcs=false -ldflags " . sh_quote($ldflags) . - " -o " . sh_quote("$go_build_dir/congo") . " cmd/congo.go" . - " >" . sh_quote("$log_dir/go-build-client.log") . " 2>&1"); - -die "goconserver binary not built\n" if !-x "$go_build_dir/goconserver"; -die "congo binary not built\n" if !-x "$go_build_dir/congo"; - -print_step("Create source tarball"); -my $payload_dir = "$work_dir/goconserver-$version"; -make_path("$payload_dir/usr/bin"); -make_path("$payload_dir/usr/lib/systemd/system"); -make_path("$payload_dir/etc/goconserver"); - -copy("$go_build_dir/goconserver", "$payload_dir/usr/bin/goconserver") - or die "copy goconserver: $!\n"; -copy("$go_build_dir/congo", "$payload_dir/usr/bin/congo") - or die "copy congo: $!\n"; -chmod 0755, "$payload_dir/usr/bin/goconserver"; -chmod 0755, "$payload_dir/usr/bin/congo"; - -write_file("$payload_dir/usr/lib/systemd/system/goconserver.service", <<'SERVICE'); +write_file("$sources_dir/goconserver.service", <<'SERVICE'); [Unit] Description=goconserver console server After=network.target @@ -181,13 +157,11 @@ StateDirectory=goconserver WantedBy=multi-user.target SERVICE -# The goconserver binary parses server.conf as YAML. Ship a VALID YAML default: the old INI-style -# ([server]\nhost = ...) is read by the YAML parser as a sequence -> `panic: cannot unmarshal !!seq into -# common.ServerConfig` at startup -> systemd rate-limits the service to `failed`. On an xCAT MN, -# xCAT::Goconserver.pm overwrites this with a cert-enabled config, so this default only has to PARSE and -# start (no SSL here -- the xcat certs don't exist until xCAT is configured). Keys/ports mirror the schema -# xCAT itself writes (api 12429, console 12430). -write_file("$payload_dir/etc/goconserver/server.conf", <<'CONF'); +# The goconserver binary parses server.conf as YAML. Ship a VALID YAML default (the old INI-style +# [server] block is read by the YAML parser as a sequence -> `panic: cannot unmarshal !!seq` at +# startup -> systemd rate-limits the service to `failed`). On an xCAT MN, xCAT::Goconserver.pm +# overwrites this with a cert-enabled config; this default only has to PARSE and start. +write_file("$sources_dir/server.conf", <<'CONF'); global: host: 0.0.0.0 logfile: /var/log/goconserver/server.log @@ -199,13 +173,13 @@ console: log_timestamp: true CONF -my $tarball = "$rpmbuild_top/SOURCES/goconserver-$version.tar.gz"; -run("tar --sort=name --owner=0 --group=0 --mtime=\@$SOURCE_DATE_EPOCH" . - " -C " . sh_quote($work_dir) . " -czf " . sh_quote($tarball) . - " goconserver-$version"); - -print_step("Create spec and build RPM"); -my $spec_content = <<"SPEC"; +# --- Spec: the Go compile runs in %build INSIDE the chroot, offline, from the vendored tree --- +print_step("Write spec"); +my $spec_file = "$work_dir/goconserver.spec"; +write_file($spec_file, <<"SPEC"); +# Go binaries carry no useful DWARF debugsource; the empty debuginfo subpackage otherwise fails +# packaging ("Empty %files debugsourcefiles.list"). Disable it. +%global debug_package %{nil} Name: goconserver Version: $version Release: 4.el$rel$release_suffix @@ -215,25 +189,33 @@ URL: https://github.com/xcat2/goconserver BuildArch: $arch Source0: goconserver-%{version}.tar.gz +Source1: goconserver.service +Source2: server.conf + +BuildRequires: golang %description goconserver is a scalable console server written in Go. It provides console logging and management for xCAT cluster nodes. %prep -%setup -n goconserver-%{version} +%setup -q -n goconserver-%{version} + +%build +# Compile in-chroot. Modules are downloaded from the Go proxy at build time (mock networking is on) +# but PINNED + integrity-checked by the committed go.sum, so the build is reproducible without a +# vendored tree. GOTOOLCHAIN=local pins the chroot's Go (never auto-downloads a toolchain). +export GOFLAGS=-mod=mod GOTOOLCHAIN=local CGO_ENABLED=0 +export GOCACHE=%{_builddir}/.gocache GOPATH=%{_builddir}/.gopath GOMODCACHE=%{_builddir}/.gomodcache +go build -trimpath -buildvcs=false -ldflags "-X main.Version=%{version}" -o goconserver goconserver.go +go build -trimpath -buildvcs=false -ldflags "-X main.Version=%{version}" -o congo cmd/congo.go %install -mkdir -p %{buildroot}/usr/bin -mkdir -p %{buildroot}/usr/lib/systemd/system -mkdir -p %{buildroot}/etc/goconserver -mkdir -p %{buildroot}/var/log/goconserver -mkdir -p %{buildroot}/var/lib/goconserver - -install -m 755 usr/bin/goconserver %{buildroot}/usr/bin/goconserver -install -m 755 usr/bin/congo %{buildroot}/usr/bin/congo -install -m 644 usr/lib/systemd/system/goconserver.service %{buildroot}/usr/lib/systemd/system/goconserver.service -install -m 644 etc/goconserver/server.conf %{buildroot}/etc/goconserver/server.conf +install -Dm0755 goconserver %{buildroot}/usr/bin/goconserver +install -Dm0755 congo %{buildroot}/usr/bin/congo +install -Dm0644 %{SOURCE1} %{buildroot}/usr/lib/systemd/system/goconserver.service +install -Dm0644 %{SOURCE2} %{buildroot}/etc/goconserver/server.conf +mkdir -p %{buildroot}/var/log/goconserver %{buildroot}/var/lib/goconserver %files /usr/bin/goconserver @@ -244,53 +226,75 @@ install -m 644 etc/goconserver/server.conf %{buildroot}/etc/goconserver/server.c %dir /var/lib/goconserver %changelog -* Thu Jul 23 2026 xCAT build - 0.3.3-4.el10 -- Ship /etc/goconserver/server.conf in YAML (the format the goconserver binary parses) instead of the - old INI [server] style, which the YAML parser reads as a sequence -> panic (cannot unmarshal !!seq) at - startup -> systemd rate-limits the service to failed before xCAT can convert the config. Fixes console - provisioning (makegocons) on the management node. -* Mon Jun 08 2026 xCAT EL10 build - 0.3.3-2.el10 -- Replace archived github.com/kr/pty with github.com/creack/pty to fix - "Setctty set but Ctty not valid in child" console fork failure on modern Go. +* Mon Aug 10 2026 xCAT build - $version-4.el$rel +- Build inside a mock chroot (no host build). Modules are downloaded at build time but pinned + + integrity-checked by a committed go.sum (no `go mod tidy`, no vendored tree). Compiled in the + el10 chroot for the arch and shipped to every EL repo (CGO-free static binary). +- Ship /etc/goconserver/server.conf as YAML (the format the goconserver binary parses). +- Replace archived github.com/kr/pty with github.com/creack/pty (console fork on modern Go). SPEC -my $spec_file = "$rpmbuild_top/SPECS/goconserver.spec"; -write_file($spec_file, $spec_content); +# --- Build in the el10 chroot for this arch --- +my $mock_uniqueext_opt = $mock_uniqueext ne '' ? ' --uniqueext ' . sh_quote($mock_uniqueext) : ''; +print_step("Mock config check"); +run("mock -r " . sh_quote($build_cfg) . $mock_uniqueext_opt . " --print-root-path >/dev/null"); +my $det_cfg = create_deterministic_mock_cfg($build_cfg, $SOURCE_DATE_EPOCH, $work_dir); -run( - "rpmbuild --define " . sh_quote("_topdir $rpmbuild_top") . +print_step("Build SRPM with mock"); +my $srpm_out = "$work_dir/srpm"; +make_path($srpm_out); +run("mock -r " . sh_quote($det_cfg) . $mock_uniqueext_opt . + " --buildsrpm --spec " . sh_quote($spec_file) . + " --sources " . sh_quote($sources_dir) . " --define " . sh_quote("use_source_date_epoch_as_buildtime 1") . " --define " . sh_quote("clamp_mtime_to_source_date_epoch 1") . " --define " . sh_quote("_buildhost xcat-build") . - " -ba " . sh_quote($spec_file) . - " >" . sh_quote("$log_dir/rpmbuild.log") . " 2>&1" -); + " --resultdir " . sh_quote($srpm_out) . + " >" . sh_quote("$log_dir/mock-buildsrpm.log") . " 2>&1"); +my @srpms = sort glob("$srpm_out/goconserver-*.src.rpm"); +die "SRPM not generated in $srpm_out\n" if !@srpms; +my $srpm = $srpms[-1]; +print "SRPM: $srpm\n"; + +print_step("Rebuild RPM with mock (offline, in-chroot go build)"); +my $rpm_out = "$work_dir/rpm"; +make_path($rpm_out); +run("mock -r " . sh_quote($det_cfg) . $mock_uniqueext_opt . + " --rebuild " . sh_quote($srpm) . + " --define " . sh_quote("use_source_date_epoch_as_buildtime 1") . + " --define " . sh_quote("clamp_mtime_to_source_date_epoch 1") . + " --define " . sh_quote("_buildhost xcat-build") . + " --resultdir " . sh_quote($rpm_out) . + " >" . sh_quote("$log_dir/mock-rebuild.log") . " 2>&1"); print_step("Collect results"); -for my $rpm (glob("$rpmbuild_top/RPMS/*/*.rpm"), glob("$rpmbuild_top/SRPMS/*.rpm")) { +my @arch_rpms = sort grep { !/\.src\.rpm$/ } glob("$rpm_out/goconserver-*.$arch.rpm"); +die "No goconserver $arch rpm generated in $rpm_out\n" if !@arch_rpms; +for my $rpm (@arch_rpms, glob("$rpm_out/*.src.rpm")) { my $dest = "$result_dir/" . basename($rpm); copy($rpm, $dest) or die "Failed to copy $rpm to $dest: $!\n"; print "Copied: $dest\n"; } +for my $log (qw(build.log root.log state.log)) { + my $s = "$rpm_out/$log"; + copy($s, "$log_dir/mock-$log") if -f $s; +} + +# Reclaim goconserver's own build chroot. mockbuild-all's scrub keys on the TARGET cfg (el$rel), not +# the el10 build cfg used here, so scrub it ourselves to avoid leaking /var/lib/mock. Best-effort. +system("mock -r " . sh_quote($build_cfg) . $mock_uniqueext_opt . + " --scrub=chroot >" . sh_quote("$log_dir/mock-scrub.log") . " 2>&1"); if (!$skip_install) { print_step("Install and smoke test"); - my @built = glob("$rpmbuild_top/RPMS/$arch/goconserver-*.rpm"); - die "No arch RPM found\n" if !@built; - my $main_rpm = $built[0]; - - run("dnf -y install " . sh_quote($main_rpm) . - " >" . sh_quote("$log_dir/dnf-install.log") . " 2>&1"); - + my $main_rpm = $arch_rpms[0]; + run("dnf -y install " . sh_quote($main_rpm) . " >" . sh_quote("$log_dir/dnf-install.log") . " 2>&1"); die "Missing /usr/bin/goconserver\n" if !-x '/usr/bin/goconserver'; die "Missing /usr/bin/congo\n" if !-x '/usr/bin/congo'; - my $rc_help = run_rc("goconserver -h >" . sh_quote("$log_dir/smoke-help.log") . " 2>&1"); die "goconserver -h failed (rc=$rc_help)\n" if $rc_help > 1; - my $rc_congo = run_rc("congo -h >" . sh_quote("$log_dir/smoke-congo.log") . " 2>&1"); die "congo -h failed (rc=$rc_congo)\n" if $rc_congo > 1; - print "Smoke tests passed.\n"; } @@ -302,18 +306,23 @@ sub usage { return <<"USAGE"; Usage: $0 [options] -Build goconserver RPM from source. +Build the goconserver RPM inside a mock chroot: fetch the pinned source, overlay the committed +go.mod/go.sum, and compile IN-CHROOT (modules downloaded at build time but pinned by go.sum -- no +vendored tree, no `go mod tidy`). The compile runs in the el10 chroot for the host arch (goconserver +is a CGO-free static binary; el8/el9 ship too old a Go), and the rpm is tagged with the target EL +(4.el) so every EL repo gets an identical static binary. Options: --work-dir PATH Working directory (default: /tmp/goconserver-mockbuild) - --mock-cfg NAME Mock config name (auto-detected if omitted) - --mock-uniqueext STR Mock uniqueext value (for compatibility with mockbuild-all.pl) + --mock-cfg NAME Target mock config (sets the EL dist tag; the build runs in its el10 peer) + --mock-uniqueext STR Mock uniqueext (for concurrency isolation under mockbuild-all.pl) --result-dir PATH Output directory for RPMs --log-dir PATH Output directory for logs --skip-install Skip dnf install + smoke tests --version VER Version string (default: 0.3.3) --go-repo URL Git repo URL (default: github.com/xcat2/goconserver) - --go-ref REF Git ref to build (default: master) + --go-ref REF Git ref/SHA to build (default: the pinned commit) + --release-suffix STR Appended to Release for CD (e.g. .snap.) --build-timestamp EPOCH SOURCE_DATE_EPOCH for deterministic builds USAGE } @@ -374,3 +383,18 @@ sub resolve_mock_cfg { } return "${os_id}+epel-${rel}-${arch}"; } + +sub create_deterministic_mock_cfg { + my ($base_cfg, $epoch, $dir) = @_; + my $cfg_path = "$dir/mock-deterministic.cfg"; + open my $fh, '>', $cfg_path or die "Cannot write $cfg_path: $!\n"; + print $fh "include('/etc/mock/${base_cfg}.cfg')\n"; + print $fh "config_opts['environment']['SOURCE_DATE_EPOCH'] = '$epoch'\n"; + print $fh "config_opts['environment']['ZERO_AR_DATE'] = '1'\n"; + # Allow network during %build so `go build` can download the (go.sum-pinned) modules -- we build + # against the proxy rather than committing a vendored tree. + print $fh "config_opts['rpmbuild_networking'] = True\n"; + print $fh "config_opts['use_host_resolv'] = True\n"; + close $fh; + return $cfg_path; +}