Skip to content
quarry Artifact [8aa827e390]
Login

Artifact [8aa827e390]

Artifact 8aa827e3902d0a353777fb912fbe5527ccd67c29ce466245da97e033a76b8f41:


// Package runnerdist serves the quarry-runner program for download, so
// a repository's owners can run its CI on their own machine without
// building anything: the forge carries a build of the runner for each
// platform, made alongside the forge itself, and the setup page links
// to it.
package runnerdist

import (
	"net/http"
	"os"
	"path/filepath"
	"regexp"
)

// Platform is one downloadable build.
type Platform struct {
	OS, Arch, Label string
}

// File is the name the platform's build is served and saved as.
func (p Platform) File() string {
	name := "quarry-runner-" + p.OS + "-" + p.Arch
	if p.OS == "windows" {
		name += ".exe"
	}
	return name
}

// Platforms is every build the forge carries, in the order a setup page
// lists them. scripts/build-linux.sh builds exactly these.
var Platforms = []Platform{
	{"linux", "amd64", "Linux x86-64"},
	{"linux", "arm64", "Linux ARM64"},
	{"darwin", "arm64", "macOS Apple silicon"},
	{"darwin", "amd64", "macOS Intel"},
	{"windows", "amd64", "Windows x86-64"},
}

var fileName = regexp.MustCompile(`^quarry-runner-(linux|darwin|windows)-(amd64|arm64)(\.exe)?$`)

// Handler serves GET /runner/{file} from dir. Only names shaped like a
// build are looked up, so the route can never reach another file in
// dir or outside it.
func Handler(dir string) http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /runner/{file}", func(w http.ResponseWriter, req *http.Request) {
		name := req.PathValue("file")
		if !fileName.MatchString(name) {
			http.NotFound(w, req)
			return
		}
		f, err := os.Open(filepath.Join(dir, name))
		if err != nil {
			http.Error(w, "this forge carries no "+name+" build", http.StatusNotFound)
			return
		}
		defer f.Close()
		st, err := f.Stat()
		if err != nil || st.IsDir() {
			http.NotFound(w, req)
			return
		}
		w.Header().Set("Content-Type", "application/octet-stream")
		w.Header().Set("Content-Disposition", `attachment; filename="`+name+`"`)
		w.Header().Set("X-Content-Type-Options", "nosniff")
		// Each deploy replaces the build; revalidate rather than keep a
		// runner that no longer matches the forge it talks to.
		w.Header().Set("Cache-Control", "no-cache")
		http.ServeContent(w, req, name, st.ModTime(), f)
	})
	return mux
}

// DefaultDir is where the builds live when no flag says: runner/ beside
// the quarry binary, which is where deploy installs them.
func DefaultDir() string {
	exe, err := os.Executable()
	if err != nil {
		return "runner"
	}
	return filepath.Join(filepath.Dir(exe), "runner")
}