Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/release-installer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: release-kubesoloctl
run-name: "kubesoloctl ${{ github.event.inputs.version }} release 🚀"

on:
workflow_dispatch:
inputs:
version:
description: "Release version tag, e.g. v1.1.5"
required: true

permissions:
contents: write

jobs:
test:
name: test installer
runs-on: ubuntu-latest
steps:
- name: "[preparation] checkout the current branch"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
tags: true
- name: "[preparation] set up golang"
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: go.mod
cache-dependency-path: go.sum
- name: "[execution] run kubesoloctl tests"
run: make test-kubesoloctl

build:
name: build kubesoloctl-linux-${{ matrix.goarch }}
runs-on: ubuntu-latest
needs: [test]
strategy:
fail-fast: false
matrix:
include:
- goarch: amd64
- goarch: arm64
steps:
Comment on lines +33 to +42
- name: "[preparation] checkout the current branch"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
tags: true
- name: "[preparation] set up golang"
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: go.mod
cache-dependency-path: go.sum
- name: "[execution] build kubesoloctl for linux/${{ matrix.goarch }}"
env:
GOARM: ${{ matrix.goarm }}
run: |
make build-kubesoloctl \
GOOS=linux \
GOARCH=${{ matrix.goarch }} \
VERSION="${{ github.event.inputs.version }}" \
KUBESOLOCTL_OUTPUT=./dist/kubesoloctl-linux-${{ matrix.goarch }}
- name: "[post] upload kubesoloctl to github release"
run: |
gh release upload --clobber \
--repo portainer/kubesolo \
"${{ github.event.inputs.version }}" \
"./dist/kubesoloctl-linux-${{ matrix.goarch }}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40 changes: 40 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -215,5 +215,45 @@ archive:
archive-musl:
tar -czf dist/kubesolo-musl.tar.gz dist/kubesolo install.sh

# ── kubesoloctl binary ──────────────────────────────────────────────────────────
#
# kubesoloctl is built with CGO_ENABLED=0 (pure Go). A single binary per
# architecture runs on both glibc and musl systems, so there is no libc split.
#
# Supported targets: linux/amd64, linux/arm64, linux/arm (armhf), linux/riscv64

KUBESOLOCTL_LDFLAGS = -s -w \
-X main.Version=$(VERSION) \
-X main.Commit=$(COMMIT) \
-X main.BuildDate=$(BUILD_DATE)

KUBESOLOCTL_OUTPUT ?= ./dist/kubesoloctl-$(GOOS)-$(GOARCH)

# Build kubesoloctl for the current GOOS/GOARCH.
# Pass GOARM=7 when targeting arm (armhf/ARMv7), e.g.: make build-kubesoloctl GOARCH=arm GOARM=7
.PHONY: build-kubesoloctl
build-kubesoloctl:
@mkdir -p $(dir $(KUBESOLOCTL_OUTPUT))
CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go build \
-ldflags="$(KUBESOLOCTL_LDFLAGS)" \
-o $(KUBESOLOCTL_OUTPUT) \
./cmd/kubesoloctl

# Build kubesoloctl for all supported architectures
.PHONY: build-kubesoloctl-all
build-kubesoloctl-all:
GOARCH=amd64 KUBESOLOCTL_OUTPUT=./dist/kubesoloctl-linux-amd64 make build-kubesoloctl
GOARCH=arm64 KUBESOLOCTL_OUTPUT=./dist/kubesoloctl-linux-arm64 make build-kubesoloctl

Comment on lines +223 to +247
# Run kubesoloctl tests (no CGO required, no cross-compiler needed)
.PHONY: test-kubesoloctl
test-kubesoloctl:
CGO_ENABLED=0 go test ./internal/cli/... -v -count=1

# Clean kubesoloctl build artefacts
.PHONY: clean-kubesoloctl
clean-kubesoloctl:
rm -f ./dist/kubesoloctl-*

# Include custom make targets
-include $(wildcard .dev/*.make)
12 changes: 6 additions & 6 deletions cmd/kubesolo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,12 +397,12 @@ func (s *kubesolo) bootstrap() {
},

// Containerd paths
ContainerdDir: filepath.Join(basePath, types.DefaultContainerdDir),
ContainerdSocketFile: filepath.Join(basePath, types.DefaultContainerdDir, types.DefaultContainerdSocket),
ContainerdBinaryFile: filepath.Join(basePath, types.DefaultContainerdDir, "containerd"),
ContainerdImagesDir: filepath.Join(basePath, types.DefaultContainerdDir, "images"),
ContainerdShimBinaryFile: filepath.Join(basePath, types.DefaultContainerdDir, "containerd-shim-runc-v2"),
ContainerdConfigFile: filepath.Join(basePath, types.DefaultContainerdDir, "config.toml"),
ContainerdDir: filepath.Join(basePath, types.DefaultContainerdDir),
ContainerdSocketFile: filepath.Join(basePath, types.DefaultContainerdDir, types.DefaultContainerdSocket),
ContainerdBinaryFile: filepath.Join(basePath, types.DefaultContainerdDir, "containerd"),
ContainerdImagesDir: filepath.Join(basePath, types.DefaultContainerdDir, "images"),
ContainerdShimBinaryFile: filepath.Join(basePath, types.DefaultContainerdDir, "containerd-shim-runc-v2"),
ContainerdConfigFile: filepath.Join(basePath, types.DefaultContainerdDir, "config.toml"),
ContainerdRootDir: filepath.Join(basePath, types.DefaultContainerdDir, "root"),
ContainerdStateDir: filepath.Join(basePath, types.DefaultContainerdDir, "state"),
ContainerdRegistryConfigDir: filepath.Join(basePath, types.DefaultContainerdDir, "registry"),
Expand Down
25 changes: 25 additions & 0 deletions cmd/kubesoloctl/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// cmd/kubesoloctl is the management CLI for KubeSolo — a single-node
// Kubernetes distribution for constrained edge environments.
//
// It is built with CGO_ENABLED=0 so a single binary per architecture covers
// both glibc and musl systems.
package main

import (
"os"

"github.com/portainer/kubesolo/internal/cli"
)

var (
Version = "dev"
BuildDate = "unknown"
Commit = "unknown"
)

func main() {
cli.SetVersionInfo(Version, Commit, BuildDate)
if err := cli.Execute(); err != nil {
os.Exit(1)
}
}
30 changes: 30 additions & 0 deletions internal/cli/cmd_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cli

import (
"github.com/portainer/kubesolo/internal/cli/config"
"github.com/portainer/kubesolo/internal/cli/detect"
"github.com/portainer/kubesolo/internal/cli/preflight"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)

func checkCmd(cfg *config.Config) *cobra.Command {
return &cobra.Command{
Use: "check",
Short: "Run pre-flight checks without installing",
Long: `Validates that this host meets all requirements for KubeSolo. Exits 0 on success.`,
RunE: func(cmd *cobra.Command, args []string) error {
info, err := detect.Detect()
if err != nil {
return err
}
log.Info().
Str("arch", info.Arch).
Str("libc", string(info.LibC)).
Str("init", string(info.InitSystem)).
Str("env", string(info.Environment)).
Msg("host detected")
return preflight.RunSuite(preflight.Suite(cfg.InstallPrereqs, cfg.PprofServer))
},
Comment on lines +11 to +28
}
}
50 changes: 50 additions & 0 deletions internal/cli/cmd_completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package cli

import (
"fmt"
"os"

"github.com/spf13/cobra"
)

func completionCmd() *cobra.Command {
return &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate shell completion scripts",
Long: `Generate shell completion scripts for kubesoloctl.

To load completions:

Bash:
$ source <(kubesoloctl completion bash)
# To load completions for each session, execute once:
$ kubesoloctl completion bash > /etc/bash_completion.d/kubesoloctl

Zsh:
$ source <(kubesoloctl completion zsh)
# To load completions for each session, execute once:
$ kubesoloctl completion zsh > "${fpath[1]}/_kubesoloctl"

Fish:
$ kubesoloctl completion fish | source
# To load completions for each session, execute once:
$ kubesoloctl completion fish > ~/.config/fish/completions/kubesoloctl.fish`,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
RunE: func(cmd *cobra.Command, args []string) error {
switch args[0] {
case "bash":
return cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
return cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
return cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
default:
return fmt.Errorf("unsupported shell: %s", args[0])
}
},
}
}
55 changes: 55 additions & 0 deletions internal/cli/cmd_download.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package cli

import (
"github.com/portainer/kubesolo/internal/cli/config"
"github.com/portainer/kubesolo/internal/cli/detect"
"github.com/portainer/kubesolo/internal/cli/download"
"github.com/spf13/cobra"
)

func downloadCmd(cfg *config.Config) *cobra.Command {
var dir string
var targetArch string

cmd := &cobra.Command{
Use: "download",
Short: "Download the KubeSolo binary bundle for offline installation",
Long: `Download the KubeSolo release tarball and kubesoloctl binary to a local
directory for use on air-gapped machines.

By default the bundle targets the current host's architecture. Use --arch to
prepare a bundle for a different target, e.g. when downloading on an amd64
laptop for deployment to an arm64 device.

Examples:
kubesoloctl download --version=v1.1.5 --path=./offline-bundle
kubesoloctl download --version=v1.1.5 --path=./offline-bundle --arch=arm64
kubesoloctl download --version=v1.1.5 --path=./offline-bundle --arch=amd64-musl`,
RunE: func(cmd *cobra.Command, args []string) error {
if dir == "" {
dir = "."
}
var info *detect.SystemInfo
var err error
if targetArch != "" {
info, err = detect.ForTarget(targetArch)
} else {
info, err = detect.Detect()
}
if err != nil {
return err
}
return download.DownloadBundle(dir, info.ArchiveName(cfg.Version), info.InstallerName(), cfg.Version)
},
}

cmd.Flags().StringVar(&cfg.Version, "version",
envOr("KUBESOLO_VERSION", config.DefaultVersion),
"Version to download")
cmd.Flags().StringVar(&dir, "path", ".", "Directory to download files into")
cmd.Flags().StringVar(&targetArch, "arch", "",
"Target architecture for the bundle (default: current host).\n"+
"Valid values: amd64, arm64, arm, riscv64, amd64-musl, arm64-musl.\n"+
"The -musl suffix selects the musl KubeSolo archive; the kubesoloctl binary has no libc split.")
return cmd
}
Loading