Skip to content

Commit

Permalink
Merge pull request #1 from xvzf/feat/move-to-mvp
Browse files Browse the repository at this point in the history
feat: move from PoC to MVP
  • Loading branch information
xvzf authored Jun 5, 2024
2 parents d96d706 + d0744fc commit 729b5da
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 250 deletions.
85 changes: 85 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: release

on:
push:
branches: [ main ]

permissions: write-all

jobs:
# Release-please for auto-updated PRs
release-please:
runs-on: ubuntu-latest
steps:
- uses: google-github-actions/release-please-action@v3
id: release-please
with:
release-type: simple # actual releasing is handled by goreleaser
package-name: gobootme
outputs:
release_created: ${{ steps.release-please.outputs.release_created }}

# Goreleaser for binary releases / GH release
goreleaser:
runs-on: ubuntu-latest
needs:
- release-please
if: needs.release-please.outputs.release_created
steps:
# Checkout code (full history)
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

# Setup golang with caching
- name: Setup Golang
uses: actions/setup-go@v4
with:
go-version: stable
- id: go-cache-paths
run: |
echo "go-build=$(go env GOCACHE)" >> "$GITHUB_OUTPUT"
echo "go-mod=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT"
- name: Go Build Cache
uses: actions/cache@v3
with:
path: ${{ steps.go-cache-paths.outputs.go-build }}
key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}
- name: Go Mod Cache
uses: actions/cache@v3
with:
path: ${{ steps.go-cache-paths.outputs.go-mod }}
key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}

# Setup docker buildx
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 'Login to GitHub Container Registry'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{github.actor}}
password: ${{secrets.GITHUB_TOKEN}}

# Install cosign
- name: Install Cosign
uses: sigstore/cosign-installer@v3

# Build fanunit firmware
- name: Build FanUnit Firmware
run: make build-fanunit

# Run goreleaser
- name: Run Goreleaser
uses: goreleaser/goreleaser-action@v5
with:
version: latest
args: release --clean
env:
COSIGN_YES: "true"
KO_DOCKER_REPO: ghcr.io/${{ github.repository }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
internal/tftp/bootfiles/
dist/
14 changes: 6 additions & 8 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,24 @@ before:
hooks:
- go generate ./...
builds:
- env:
- id: gobootme
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- arm64
- amd64
binary: gobootme
main: ./cmd/gobootme/main.go
id: gobootme
main: ./cmd/gobootme

# .goreleaser.yaml
kos:
- id: ko-gobootme
build: gobootme
repository: ghcr.io/xvzf/gobootme
- repository: ghcr.io/xvzf/gobootme
main: ./cmd/gobootme
platforms:
- linux/amd64
- linux/arm64
tags:
- latest
- '{{.Tag}}'
- '{{if not .Prerelease}}stable{{end}}'

58 changes: 53 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,58 @@
# gobootme
> API driven (i)PXE booting for Raspberry Pis and more. Inspired by [dananderson/netboot](https://github.com/danderson/netboot).
> While this software heavily focuses on Raspberry Pis (especially for the compute-blade), it also boots AMD64 machines. They are just easier and have less quirks.
> **Disclaimer**: works, but test coverage is lacking!

Bringing Raspberry Pis from zero to iPXE pointing to e.g. [matchbox](http://matchbox.psdn.io) in seconds.

It acts as ProxyDHCP server to provide boot information to the Pis, and then it serves the iPXE boot script to the Pis.

## Requirements/Assumptions
- DHCP server without PXE booting configured
- Privileged execution

## Recommendations
While this software tries to boot the Pis from zero, it's recommended to use the UEFI firmware for the most reliable booting experience.

## Configuration
- `IPXE_BOOT_ENDPOINT`: HTTP endpoint called when a device tries to (i)PXE boot
- `LOG_LEVEL`: Log level (**default: `info`**)
- `LOG_MODE`: Log mode (**default: `console`**)
- `IPXE_BOOT_ENDPOINT_AUTO`: Expect matchbox running on the same host (**default: `true`**)
- `IPXE_BOOT_ENDPOINT`: custom iPXE boot script endpoint (only used if `IPXE_BOOT_ENDPOINT_AUTO` is `false`)
- `ENABLE_PROXY_DHCP`: Enable proxy DHCP server (**default: `true`**)
- `PROXY_DHCP_INTERFACE`: Interface to listen for bootp requests (**default: `eth0`**)
- `HTTP_PORT`: internal http port for serving iPXE scripts (**default: `8082`**)

## Running the service
Run the provided container image or binary on a linux host
## Example runtime

e.g. bringing up matchbox and gobootme in docker-compose
```yaml
version: '3.8'

services:
gobootme:
image: ghcr.io/xvzf/gobootme:latest
container_name: gobootme
restart: unless-stopped
network_mode: host
environment:
- LOG_LEVEL=info
- LOG_MODE=json
- IPXE_BOOT_ENDPOINT_AUTO=true
- ENABLE_PROXY_DHCP=true
- PROXY_DHCP_INTERFACE=eth0

matchbox:
image: quay.io/poseidon/matchbox:latest
container_name: matchbox
restart: unless-stopped
args:
- -address=0.0.0.0:8080
- -log-level=debug
volumes:
- ./matchbox/assets:/var/lib/matchbox/assets
- ./matchbox/data:/var/lib/matchbox/data
- ./matchbox/rules:/var/lib/matchbox/rules
ports:
- "8080:8080" # Example port mapping, adjust as needed
```
99 changes: 50 additions & 49 deletions cmd/gobootme/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,20 @@ import (

"github.com/insomniacslk/dhcp/dhcpv4/server4"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/sethvargo/go-envconfig"
"github.com/xvzf/gobootme/internal/ipxe"
"github.com/xvzf/gobootme/internal/proxydhcp"
"github.com/xvzf/gobootme/internal/tftp"
"golang.org/x/sync/errgroup"
)

type Config struct {
IpxeBootEndpoint string `env:"IPXE_BOOT_ENDPOINT"`
ProxyDhcpInterface string `env:"PROXY_DHCP_INTERFACE, default=eth0"`
HttpPort int `env:"HTTP_PORT, default=8082"`
LogLevel string `env:"LOG_LEVEL, default=info"`
LogMode string `env:"LOG_MODE, default=console"`
EnableProxyDhcp bool `env:"ENABLE_PROXY_DHCP, default=true"`
ProxyDhcpInterface string `env:"PROXY_DHCP_INTERFACE, default=eth0"`
IpxeBootEndpointAuto bool `env:"IPXE_BOOT_ENDPOINT_AUTO, default=true"`
IpxeBootEndpoint string `env:"IPXE_BOOT_ENDPOINT"`
}

func requestLogger(next http.Handler) http.Handler {
Expand All @@ -41,15 +44,33 @@ func requestLogger(next http.Handler) http.Handler {
}

func main() {
var logger zerolog.Logger
ctx, cancel := context.WithCancel(context.Background())
logger := zerolog.New(zerolog.NewConsoleWriter()).With().Timestamp().Logger()
ctx = logger.WithContext(ctx)

defer cancel()
var config Config
if err := envconfig.Process(ctx, &config); err != nil {
zerolog.Ctx(ctx).Fatal().Err(err).Msg("Could not parse config")
log.Fatal().Err(err).Msg("Could not parse environment variables")
}

// Setup logger
if config.LogMode == "json" {
logger = zerolog.New(os.Stdout).With().Timestamp().Logger()
} else {
logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Logger()
}
switch config.LogLevel {
case "debug":
logger = logger.Level(zerolog.DebugLevel)
case "info":
logger = logger.Level(zerolog.InfoLevel)
case "warn":
logger = logger.Level(zerolog.WarnLevel)
case "error":
logger = logger.Level(zerolog.ErrorLevel)
}

ctx = logger.WithContext(ctx)

// Resolve interface IP
var interfaceIP net.IP
Expand Down Expand Up @@ -88,28 +109,33 @@ func main() {
grp, ctx := errgroup.WithContext(ctx)

// Launch ProxyDHCP server

if config.IpxeBootEndpointAuto {
config.IpxeBootEndpoint = fmt.Sprintf("http://%s:8080/boot.ipxe", interfaceIP.String())
}

proxyDhcpHandleFunc := proxydhcp.HandlePkt(
ctx,
interfaceIP,
fmt.Sprintf(
"http://%s:%d/ipxe?mac=${mac}&buildarch=${buildarch}&serial=${serial}",
interfaceIP.String(),
config.HttpPort,
),
config.IpxeBootEndpoint,
)
proxyDhcpServer, err := server4.NewServer(config.ProxyDhcpInterface, nil, proxyDhcpHandleFunc)
if err != nil {
zerolog.Ctx(ctx).Fatal().Err(err).Msg("Could not create ProxyDHCP server")
if config.EnableProxyDhcp {
proxyDhcpServer, err := server4.NewServer(config.ProxyDhcpInterface, nil, proxyDhcpHandleFunc)
if err != nil {
zerolog.Ctx(ctx).Fatal().Err(err).Msg("Could not create ProxyDHCP server")
}
grp.Go(func() error {
zerolog.Ctx(ctx).Info().Msgf("Starting ProxyDHCP server on %s", config.ProxyDhcpInterface)
return proxyDhcpServer.Serve()
})
grp.Go(func() error {
<-ctx.Done()
zerolog.Ctx(ctx).Info().Msg("Shutting down ProxyDHCP server")
return proxyDhcpServer.Close()
})
} else {
zerolog.Ctx(ctx).Info().Msg("ProxyDHCP server is disabled")
}
grp.Go(func() error {
zerolog.Ctx(ctx).Info().Msgf("Starting ProxyDHCP server on %s", config.ProxyDhcpInterface)
return proxyDhcpServer.Serve()
})
grp.Go(func() error {
<-ctx.Done()
zerolog.Ctx(ctx).Info().Msg("Shutting down ProxyDHCP server")
return proxyDhcpServer.Close()
})

// Start tftp server
tftpServer := tftp.NewIpxeServer()
Expand All @@ -124,31 +150,6 @@ func main() {
return nil
})

// Launch iPXE boot endpoint
zerolog.Ctx(ctx).Info().Msgf("iPXE boot config endpoint: %s", config.IpxeBootEndpoint)
bootRetriever := ipxe.NewIpxeBootRetriever(config.IpxeBootEndpoint)
mux := http.NewServeMux()
mux.HandleFunc("/ipxe", bootRetriever.HttpHandle)

ipxeServer := &http.Server{
Addr: fmt.Sprintf("%s:%d", interfaceIP.String(), config.HttpPort),
Handler: requestLogger(mux),
BaseContext: func(_ net.Listener) context.Context {
return ctx
},
}
grp.Go(func() error {
zerolog.Ctx(ctx).Info().Msgf("Starting iPXE boot config server on %s:%d", interfaceIP.String(), config.HttpPort)
return ipxeServer.ListenAndServe()
})
grp.Go(func() error {
<-ctx.Done()
zerolog.Ctx(ctx).Info().Msg("Shutting down iPXE boot config server")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
return ipxeServer.Shutdown(shutdownCtx)
})

// Wait for all goroutines to finish
if err := grp.Wait(); err != nil {
zerolog.Ctx(ctx).Fatal().Err(err).Msg("Error while running")
Expand Down
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
module github.com/xvzf/gobootme

go 1.21
go 1.22

toolchain go1.21.6
toolchain go1.22.1

require (
github.com/google/uuid v1.6.0
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
github.com/jellydator/ttlcache/v3 v3.1.1
github.com/pin/tftp/v3 v3.1.0
github.com/rs/zerolog v1.31.0
github.com/sethvargo/go-envconfig v1.0.0
Expand All @@ -18,6 +18,7 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/stretchr/testify v1.8.2 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
Expand Down
14 changes: 10 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
github.com/jellydator/ttlcache/v3 v3.1.1 h1:RCgYJqo3jgvhl+fEWvjNW8thxGWsgxi+TPhRir1Y9y8=
github.com/jellydator/ttlcache/v3 v3.1.1/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
Expand All @@ -32,12 +33,15 @@ github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/sethvargo/go-envconfig v1.0.0 h1:1C66wzy4QrROf5ew4KdVw942CQDa55qmlYmw9FZxZdU=
github.com/sethvargo/go-envconfig v1.0.0/go.mod h1:Lzc75ghUn5ucmcRGIdGQ33DKJrcjk4kihFYgSTBmjIc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
Expand All @@ -52,5 +56,7 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading

0 comments on commit 729b5da

Please sign in to comment.