feat(updater): add 'checkout' and 'upload_artifact' steps

Adds a new `checkout` step intended to replace `git checkout` by
actually checking out the correct revision when running commands.

Adds a new `upload_artifact` step that uploads an artifact to a package
specific prefix. Primarily intended for supporting Go dependency
archives, but could also be used for anything.

Added `net-vpn/tailscale` using this new functionality.
This commit is contained in:
Jared Allard 2024-05-10 12:42:43 -07:00
parent f49156839f
commit 09d39ad36d
Signed by: jaredallard
SSH key fingerprint: SHA256:wyRyyv28jBYw8Yp/oABNPUYvbGd6hyZj23XVXEm5G/U
21 changed files with 659 additions and 186 deletions

View file

@ -4,6 +4,8 @@ An automated ebuild updating system.
## Usage
**Requirements**: `docker`.
Create an `updater.yml` file in the root of your repository. Create a
key for each package that should be managed by the updater.

View file

@ -28,6 +28,7 @@ import (
logger "github.com/charmbracelet/log"
"github.com/jaredallard/overlay/.tools/internal/config"
"github.com/jaredallard/overlay/.tools/internal/config/packages"
"github.com/jaredallard/overlay/.tools/internal/ebuild"
updater "github.com/jaredallard/overlay/.tools/internal/resolver"
"github.com/jaredallard/overlay/.tools/internal/steps"
@ -42,8 +43,9 @@ var log = logger.NewWithOptions(os.Stderr, logger.Options{
// rootCmd is the root command used by cobra
var rootCmd = &cobra.Command{
Use: "updater",
Use: "updater <package>",
Short: "updater automatically updates ebuilds",
Args: cobra.MaximumNArgs(1),
RunE: entrypoint,
SilenceErrors: true,
}
@ -88,12 +90,27 @@ func getDefaultSteps() []steps.Step {
func entrypoint(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
cfg, err := config.LoadConfig("updater.yml")
cfg, err := config.LoadConfig(".updater.yml")
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
cfg = &config.Config{}
}
for _, ce := range cfg {
pkgs, err := packages.LoadPackages("packages.yml")
if err != nil {
return fmt.Errorf("failed to load packages: %w", err)
}
// If we have exactly one argument, we only want to update that
// package.
if len(args) == 1 {
pkgName := args[0]
if _, ok := pkgs[pkgName]; !ok {
return fmt.Errorf("package not found in packages.yml: %s", pkgName)
}
pkgs = packages.List{pkgName: pkgs[pkgName]}
}
for _, ce := range pkgs {
log.With("name", ce.Name).With("resolver", ce.Resolver).Info("checking for updates")
ebuildDir := ce.Name
@ -132,6 +149,7 @@ func entrypoint(cmd *cobra.Command, args []string) error {
}
executor := steps.NewExecutor(log, ceSteps, &steps.ExecutorInput{
Config: cfg,
OriginalEbuild: e,
ExistingEbuilds: ebuilds,
LatestVersion: latestVersion,

View file

@ -8,6 +8,7 @@ require (
github.com/docker/docker v25.0.3+incompatible
github.com/egym-playground/go-prefix-writer v0.0.0-20180609083313-7326ea162eca
github.com/fatih/color v1.15.0
github.com/minio/minio-go/v7 v7.0.70
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.8.0
gopkg.in/yaml.v3 v3.0.1
@ -23,17 +24,23 @@ require (
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.17.6 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/muesli/reflow v0.3.0 // indirect
@ -41,6 +48,7 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 // indirect
go.opentelemetry.io/otel v1.23.1 // indirect
@ -48,9 +56,12 @@ require (
go.opentelemetry.io/otel/metric v1.23.1 // indirect
go.opentelemetry.io/otel/sdk v1.23.1 // indirect
go.opentelemetry.io/otel/trace v1.23.1 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
pault.ag/go/topsort v0.1.1 // indirect
)

View file

@ -25,6 +25,8 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/egym-playground/go-prefix-writer v0.0.0-20180609083313-7326ea162eca h1:sWNMfkKG8GW1pGUyNlbsWq6f04pFgcsomY+Fly8XdB4=
github.com/egym-playground/go-prefix-writer v0.0.0-20180609083313-7326ea162eca/go.mod h1:Ar+qogA+fkjeUR18xJfFzrMSjfs/sCPO+yjVvhXpyEg=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
@ -38,18 +40,27 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/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/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
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/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
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=
@ -61,6 +72,10 @@ github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.70 h1:1u9NtMgfK1U42kUxcsl5v0yj6TEOPR497OAQxpJnn2g=
github.com/minio/minio-go/v7 v7.0.70/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
@ -81,6 +96,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
@ -114,8 +131,8 @@ go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7e
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@ -124,8 +141,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -135,9 +152,10 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
@ -162,6 +180,8 @@ google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=

View file

@ -21,27 +21,32 @@ import (
"fmt"
"os"
"github.com/jaredallard/overlay/.tools/internal/steps"
"gopkg.in/yaml.v3"
)
// Resolver is the resolver to use to determine if an update is available.
type Resolver string
// Config represents the configuration for the updater itself.
type Config struct {
// StepConfig contains configuration for steps that support
// updater-wide configuration.
StepConfig struct {
// UploadArtifact contains the configuration for where the
// 'upload_artifact' step should upload the artifact to.
UploadArtifact struct {
// Bucket is the S3 bucket to upload the artifact to.
Bucket string `yaml:"bucket"`
// Contains the supported resolvers.
const (
// GitResolver is the git resolver.
GitResolver Resolver = "git"
// Host is the host of the S3 bucket.
Host string `yaml:"host"`
// APTResolver is a version resolver powered by an APT repository.
APTResolver Resolver = "apt"
)
// Config is the configuration for the updater.
type Config map[string]Ebuild
// Prefix is the prefix to use for the artifact when storing it in
// S3.
Prefix string `yaml:"prefix"`
} `yaml:"upload_artifact"`
} `yaml:"step_config"`
}
// LoadConfig loads the updater configuration from the provided path.
func LoadConfig(path string) (Config, error) {
func LoadConfig(path string) (*Config, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open config file: %w", err)
@ -52,102 +57,5 @@ func LoadConfig(path string) (Config, error) {
if err := yaml.NewDecoder(f).Decode(&cfg); err != nil {
return nil, fmt.Errorf("failed to decode config: %w", err)
}
return cfg, nil
}
// Ebuild is an ebuild that should be updated by the updater.
type Ebuild struct {
// Name of the ebuild. This is only set when loaded from the config.
// It is a readonly field.
Name string `yaml:"name,omitempty"`
// Resolver to use to determine if an update is available.
// Currently only "git" is supported.
Resolver Resolver `yaml:"resolver"`
// GitOptions is the options for the git resolver.
GitOptions GitOptions `yaml:"options"`
// APTOptions is the options for the APT resolver.
APTOptions APTOptions `yaml:"options"`
// Steps are the steps to use to update the ebuild, if not set it
// defaults to a copy the existing ebuild and regenerate the manifest.
Steps steps.Steps `yaml:"steps"`
}
// UnmarshalYAML unmarshals the ebuild configuration from YAML while
// converting options into the appropriate type for the provided
// resolver.
func (e *Ebuild) UnmarshalYAML(unmarshal func(interface{}) error) error {
var raw struct {
Resolver Resolver `yaml:"resolver"`
Options yaml.Node `yaml:"options"`
Steps steps.Steps
}
if err := unmarshal(&raw); err != nil {
return err
}
e.Resolver = raw.Resolver
e.Steps = raw.Steps
switch e.Resolver {
case GitResolver:
if err := raw.Options.Decode(&e.GitOptions); err != nil {
return fmt.Errorf("failed to decode git options: %w", err)
}
case APTResolver:
if err := raw.Options.Decode(&e.APTOptions); err != nil {
return fmt.Errorf("failed to decode APT options: %w", err)
}
default:
return fmt.Errorf("unsupported resolver: %s", e.Resolver)
}
return nil
}
// UnmarshalYAML unmarshals the configuration from YAML while carrying
// over the ebuild name into the ebuild struct.
func (c Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
var ebuilds map[string]Ebuild
if err := unmarshal(&ebuilds); err != nil {
return err
}
for name, ebuild := range ebuilds {
ebuild.Name = name
c[name] = ebuild
}
return nil
}
// GitOptions is the options for the git resolver.
type GitOptions struct {
// URL is the URL to the git repository. Must be a valid option to
// 'git clone'.
URL string `yaml:"url"`
// Tags denote if tags should be used as the version source.
Tags bool `yaml:"tags"`
}
// APTOptions contains the options for the APT resolver.
type APTOptions struct {
// Repository is the URL of the APT repository. Should match the
// following format:
// deb http://archive.ubuntu.com/ubuntu/ focal main
Repository string `yaml:"repository"`
// Package is the name of the package to watch versions for.
Package string `yaml:"package"`
// StripRelease is a boolean that denotes if extra release information
// (in the context of a semver) should be stripped. Defaults to true.
StripRelease *bool `yaml:"strip_release"`
return &cfg, nil
}

View file

@ -0,0 +1,155 @@
// Copyright (C) 2024 Jared Allard
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package packages
import (
"fmt"
"os"
"github.com/jaredallard/overlay/.tools/internal/steps"
"gopkg.in/yaml.v3"
)
// Resolver is the resolver to use to determine if an update is available.
type Resolver string
// Contains the supported resolvers.
const (
// GitResolver is the git resolver.
GitResolver Resolver = "git"
// APTResolver is a version resolver powered by an APT repository.
APTResolver Resolver = "apt"
)
// List contains all of the packages that should be updated.
type List map[string]Package
// Package is an package that should be updated by the updater.
type Package struct {
// Name of the ebuild. This is only set when loaded from the config.
// It is a readonly field.
Name string `yaml:"name,omitempty"`
// Resolver to use to determine if an update is available.
// Currently only "git" is supported.
Resolver Resolver `yaml:"resolver"`
// GitOptions is the options for the git resolver.
GitOptions GitOptions `yaml:"options"`
// APTOptions is the options for the APT resolver.
APTOptions APTOptions `yaml:"options"`
// Steps are the steps to use to update the ebuild, if not set it
// defaults to a copy the existing ebuild and regenerate the manifest.
Steps steps.Steps `yaml:"steps"`
}
// LoadPackages returns a map of packages from the provided path.
func LoadPackages(path string) (List, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open ebuilds file: %w", err)
}
defer f.Close()
pkgs := make(List)
if err := yaml.NewDecoder(f).Decode(&pkgs); err != nil {
return nil, fmt.Errorf("failed to decode ebuilds: %w", err)
}
return pkgs, nil
}
// UnmarshalYAML unmarshals the ebuild configuration from YAML while
// converting options into the appropriate type for the provided
// resolver.
func (p *Package) UnmarshalYAML(unmarshal func(interface{}) error) error {
var raw struct {
Resolver Resolver `yaml:"resolver"`
Options yaml.Node `yaml:"options"`
Steps steps.Steps
}
if err := unmarshal(&raw); err != nil {
return err
}
p.Resolver = raw.Resolver
p.Steps = raw.Steps
switch p.Resolver {
case GitResolver:
if err := raw.Options.Decode(&p.GitOptions); err != nil {
return fmt.Errorf("failed to decode git options: %w", err)
}
case APTResolver:
if err := raw.Options.Decode(&p.APTOptions); err != nil {
return fmt.Errorf("failed to decode APT options: %w", err)
}
default:
return fmt.Errorf("unsupported resolver: %s", p.Resolver)
}
return nil
}
// UnmarshalYAML unmarshals the ebuilds from YAML while carrying the
// name from the key into the ebuild name.
func (l List) UnmarshalYAML(unmarshal func(interface{}) error) error {
pkgs := make(map[string]Package)
if err := unmarshal(&pkgs); err != nil {
return err
}
for name, ebuild := range pkgs {
ebuild.Name = name
l[name] = ebuild
}
return nil
}
// GitOptions is the options for the git resolver.
type GitOptions struct {
// URL is the URL to the git repository. Must be a valid option to
// 'git clone'.
URL string `yaml:"url"`
// Tags denote if tags should be used as the version source.
Tags bool `yaml:"tags"`
// ConsiderPreReleases denotes if pre-releases should be considered,
// when a tag is used and the version is able to be parsed as a
// semver. Defaults to false.
ConsiderPreReleases bool `yaml:"consider_pre_releases"`
}
// APTOptions contains the options for the APT resolver.
type APTOptions struct {
// Repository is the URL of the APT repository. Should match the
// following format:
// deb http://archive.ubuntu.com/ubuntu/ focal main
Repository string `yaml:"repository"`
// Package is the name of the package to watch versions for.
Package string `yaml:"package"`
// StripRelease is a boolean that denotes if extra release information
// (in the context of a semver) should be stripped. Defaults to true.
StripRelease *bool `yaml:"strip_release"`
}

View file

@ -17,13 +17,13 @@ package updater
import (
"github.com/blang/semver/v4"
"github.com/jaredallard/overlay/.tools/internal/config"
"github.com/jaredallard/overlay/.tools/internal/config/packages"
"github.com/jaredallard/overlay/.tools/internal/resolver/apt"
)
// getAPTVersion returns the latest version of an APT package based on
// the config provided.
func getAPTVersion(ce *config.Ebuild) (string, error) {
func getAPTVersion(ce *packages.Package) (string, error) {
v, err := apt.GetPackageVersion(apt.Lookup{
SourcesEntry: ce.APTOptions.Repository,
Package: ce.APTOptions.Package,

View file

@ -23,12 +23,13 @@ import (
"os/exec"
"strings"
"github.com/jaredallard/overlay/.tools/internal/config"
"github.com/blang/semver/v4"
"github.com/jaredallard/overlay/.tools/internal/config/packages"
)
// getGitVersion returns the latest version available from a git
// repository.
func getGitVersion(ce *config.Ebuild) (string, error) {
func getGitVersion(ce *packages.Package) (string, error) {
dir, err := os.MkdirTemp("", "updater")
if err != nil {
return "", fmt.Errorf("failed to create temporary directory: %w", err)
@ -66,6 +67,16 @@ func getGitVersion(ce *config.Ebuild) (string, error) {
// Strip the "refs/tags/" prefix.
tag := strings.TrimPrefix(fqTag, "refs/tags/")
// Attempt to parse as a semver, for other options.
if sv, err := semver.ParseTolerant(tag); err == nil {
isPreRelease := len(sv.Pre) > 0
if isPreRelease && !ce.GitOptions.ConsiderPreReleases {
// Skip the version if we're not considering pre-releases.
continue
}
}
newVersion = tag
break
}

View file

@ -20,15 +20,16 @@ package updater
import (
"fmt"
"github.com/jaredallard/overlay/.tools/internal/config"
"github.com/jaredallard/overlay/.tools/internal/config/packages"
)
// GetLatestVersion returns the latest version available for the given ebuild.
func GetLatestVersion(ce *config.Ebuild) (string, error) {
// GetLatestVersion returns the latest version available for the given
// package.
func GetLatestVersion(ce *packages.Package) (string, error) {
switch ce.Resolver {
case config.GitResolver:
case packages.GitResolver:
return getGitVersion(ce)
case config.APTResolver:
case packages.APTResolver:
return getAPTVersion(ce)
case "":
return "", fmt.Errorf("no resolver specified")

View file

@ -0,0 +1,61 @@
// Copyright (C) 2024 Jared Allard
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package steps
import (
"context"
_ "embed"
"fmt"
"github.com/jaredallard/overlay/.tools/internal/steps/stepshelpers"
)
// CheckoutStep checks out a repository at the given path. If the Git
// resolver was used, it defaults to the URL provided in the GitOptions.
// Otherwise, it will use the URL provided.
//
// The latest version is used as the revision to checkout.
type CheckoutStep struct {
url string
}
// NewCheckoutStep creates a new CheckoutStep from the provided input.
func NewCheckoutStep(input any) (StepRunner, error) {
url, ok := input.(string)
if !ok {
return nil, fmt.Errorf("expected string, got %T", input)
}
return &CheckoutStep{url}, nil
}
// Run runs the provided command inside of the step runner.
func (c CheckoutStep) Run(ctx context.Context, env Environment) (*StepOutput, error) {
cmds := [][]string{
{"git", "-c", "init.defaultBranch=main", "init", env.workDir},
{"git", "remote", "add", "origin", c.url},
{"git", "-c", "protocol.version=2", "fetch", "origin", "v" + env.in.LatestVersion},
{"git", "reset", "--hard", "FETCH_HEAD"},
}
for _, cmd := range cmds {
if err := stepshelpers.RunCommandInContainer(ctx, env.containerID, cmd...); err != nil {
return nil, fmt.Errorf("failed to run command %v: %w", cmd, err)
}
}
return nil, nil
}

View file

@ -23,6 +23,7 @@ import (
"strings"
logger "github.com/charmbracelet/log"
"github.com/jaredallard/overlay/.tools/internal/config"
"github.com/jaredallard/overlay/.tools/internal/ebuild"
"github.com/docker/docker/api/types/container"
@ -60,6 +61,10 @@ type Environment struct {
// ExecutorInput is input to the executor. This should contain state
// that existed before the executor was ran.
type ExecutorInput struct {
// Config is the configuration for the updater, which contains
// configuration for some steps.
Config *config.Config
// OriginalEbuild is the original ebuild that can be used for
// generating a new one.
OriginalEbuild *ebuild.Ebuild
@ -91,7 +96,10 @@ func (e *Executor) Run(ctx context.Context) (*Results, error) {
// TODO(jaredallard): Use the Docker API for this, but for now the CLI
// is much better.
bid, err := exec.Command("docker", "run", "-d", "--rm", "--entrypoint", "sleep", "ghcr.io/jaredallard/overlay:updater", "infinity").Output()
bid, err := exec.Command(
"docker", "run", "-d", "--rm", "--entrypoint", "sleep",
"ghcr.io/jaredallard/overlay:updater", "infinity",
).Output()
if err != nil {
var execErr *exec.ExitError
if errors.As(err, &execErr) {

View file

@ -45,7 +45,7 @@ type Steps []Step
// Step encapsulates a step that should be ran.
type Step struct {
// Args are the arguments that should be passed to the step.
// Args are the arguments that were provided to the step.
Args any
// Runner is the runner that runs this step.
@ -55,7 +55,7 @@ type Step struct {
// UnmarshalYAML unmarshals the steps from the YAML configuration file
// turning them into their type safe representations.
func (s *Steps) UnmarshalYAML(node *yaml.Node) error {
var raw []map[string]any
var raw []any
if err := node.Decode(&raw); err != nil {
return err
}
@ -63,32 +63,47 @@ func (s *Steps) UnmarshalYAML(node *yaml.Node) error {
// knownSteps map of key values to their respective steps.
knownSteps := map[string]func(any) (StepRunner, error){
"command": NewCommandStep,
"checkout": NewCheckoutStep,
"ebuild": NewEbuildStep,
"original_ebuild": NewOriginalEbuildStep,
"upload_artifact": NewUploadArtifactStep,
}
for _, rawStep := range raw {
// Find the first key that maps to a known step.
var found bool
for key := range knownSteps {
if _, ok := rawStep[key]; ok {
found = true
var stepName string
var stepData any
step, err := knownSteps[key](rawStep[key])
if err != nil {
return fmt.Errorf("failed to create step: %w", err)
}
switch rawStep := rawStep.(type) {
case map[string]any:
// If there's more than one key, fail.
if len(rawStep) != 1 {
return fmt.Errorf("expected one key on step, got %d", len(rawStep))
}
*s = append(*s, Step{
Args: rawStep[key],
Runner: step,
})
// If it's a map, use the first key.
for key, value := range rawStep {
stepName = key
stepData = value
break
}
case string:
// If it's just a string, then we use it as-is.
stepName = rawStep
}
if !found {
return fmt.Errorf("invalid step")
if _, ok := knownSteps[stepName]; !ok {
return fmt.Errorf("unknown step: %s", stepName)
}
step, err := knownSteps[stepName](stepData)
if err != nil {
return fmt.Errorf("failed to create step: %w", err)
}
*s = append(*s, Step{
Args: stepData,
Runner: step,
})
}
return nil

View file

@ -81,6 +81,33 @@ func ReadFileInContainer(ctx context.Context, containerID, path string) ([]byte,
return b, nil
}
// StreamFileFromContainer streams a file from a container to the
// provided writer. Because this function streams the file using the
// exec package, the caller must call the returned wait function to wait
// for the command to finish and clean up resources.
//
// The returned int64 is the size of the file being streamed.
func StreamFileFromContainer(ctx context.Context, containerID, path string) (io.Reader, int64, func() error, error) {
cmd := exec.CommandContext(ctx, "docker", "cp", fmt.Sprintf("%s:%s", containerID, path), "-")
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, 0, nil, fmt.Errorf("failed to create stdout pipe: %w", err)
}
if err := cmd.Start(); err != nil {
return nil, 0, nil, fmt.Errorf("failed to start command: %w", err)
}
// Process the output as a tar stream.
t := tar.NewReader(stdout)
th, err := t.Next()
if err != nil {
return nil, 0, nil, fmt.Errorf("failed to read tar: %w", err)
}
return t, th.Size, cmd.Wait, nil
}
// RunCommandInContainer runs a command inside of a container.
func RunCommandInContainer(ctx context.Context, containerID string, origArgs ...string) error {
args := []string{"exec", containerID, "bash", "-eo", "pipefail"}

View file

@ -0,0 +1,108 @@
// Copyright (C) 2024 Jared Allard
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package steps
import (
"context"
_ "embed"
"fmt"
"path/filepath"
"strings"
"github.com/jaredallard/overlay/.tools/internal/steps/stepshelpers"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
// UploadArtifactStep takes the provided path and uploads it from the
// container to a stable S3 bucket. This is used to store artifacts that
// are required by ebuilds (e.g., Go dependency archives) and retrieve
// them later.
//
// S3 configuration is provided by the environment. The file will be
// stored using the following structure:
//
// <host>/<package_name>/<package_version>/<basename of path>
type UploadArtifactStep struct {
// path is the path to the artifact.
path string
}
// NewUploadArtifactStep creates a new UploadArtifactStep from the provided input.
func NewUploadArtifactStep(input any) (StepRunner, error) {
path, ok := input.(string)
if !ok {
return nil, fmt.Errorf("expected string, got %T", input)
}
return &UploadArtifactStep{path}, nil
}
// Run runs the provided command inside of the step runner.
func (e UploadArtifactStep) Run(ctx context.Context, env Environment) (*StepOutput, error) {
if !filepath.IsAbs(e.path) {
e.path = filepath.Join(env.workDir, e.path)
}
// TODO(jaredallard): We should create the client once.
s3Conf := env.in.Config.StepConfig.UploadArtifact
if s3Conf.Host == "" {
return nil, fmt.Errorf("s3 host was not set in the config")
}
if s3Conf.Bucket == "" {
return nil, fmt.Errorf("s3 bucket was not set in the config")
}
// Without a doubt, the worse code I've ever written.
hostWithSchema := strings.TrimPrefix(strings.TrimPrefix(s3Conf.Host, "http://"), "https://")
mc, err := minio.New(hostWithSchema, &minio.Options{
Creds: credentials.NewEnvAWS(),
Secure: strings.HasPrefix(s3Conf.Host, "https"),
})
if err != nil {
return nil, fmt.Errorf("failed to create minio client: %w", err)
}
uploadFileName := filepath.Base(e.path)
// Example: <?prefix>/net-vpn/tailscale/1.66.1/deps.xz
uploadPath := filepath.Join(
s3Conf.Prefix, env.in.OriginalEbuild.Category, env.in.OriginalEbuild.Name, env.in.LatestVersion, uploadFileName,
)
out, size, wait, err := stepshelpers.StreamFileFromContainer(ctx, env.containerID, e.path)
if err != nil {
return nil, fmt.Errorf("failed to stream file from container: %w", err)
}
env.log.With("path", uploadPath, "size", size).Info("uploading artifact")
inf, err := mc.PutObject(ctx, s3Conf.Bucket, uploadPath, out, size, minio.PutObjectOptions{
SendContentMd5: true,
})
if err != nil {
return nil, fmt.Errorf("failed to upload artifact: %w", err)
}
if err := wait(); err != nil {
return nil, fmt.Errorf("failed to wait for command to finish: %w", err)
}
env.log.With("size", inf.Size).Info("uploaded artifact")
return nil, nil
}

5
.updater.yml Normal file
View file

@ -0,0 +1,5 @@
step_config:
upload_artifact:
host: https://c41358b3e2e8f5345933f0d433e3abef.r2.cloudflarestorage.com
bucket: gentoo-rgst-io
prefix: updater_artifacts

View file

@ -7,3 +7,8 @@ RUN emerge-webrsync && \
ACCEPT_KEYWORDS="~amd64 ~arm64" emerge -v app-portage/pycargoebuild && \
emerge -v app-portage/gentoolkit && \
eclean --deep packages && eclean --deep distfiles
# Install mise for things that might need it.
RUN curl https://mise.run | sh
ENV PATH="/root/.local/bin:/root/.local/share/mise/shims:${PATH}"
RUN set -e; whoami; echo $HOME; mise --version

View file

@ -0,0 +1,3 @@
DIST deps.tar.xz 395317936 BLAKE2B 50b0e9bb784dd655ac5f2ce7aed0eddbac772511dc9e768c0005ef84b653c58719cabbb1e52c82cc8ecc257eed26cbc976d9df24e30bf6fc5cb871a21767499d SHA512 a9ae271d8272b54a0bfe91bc9d59fa3c2af31fa1565552f5fbd8517e5a7acbcad7fb45bb080a7876e45810394b8ac28d0815b7db3b58ac9e78a0b65d0693d62e
DIST tailscale-1.66.0.tar.gz 2633400 BLAKE2B b089731d5412187d3ef9e3d0f7064143adc6baa8fd70622d2ce0c97d28420da1170d8f388fd4b1617dacd338360669a148f1a0f0f9f90914c77b0b0ff20b2e0e SHA512 5eab341a0d8208bd6915f0dd87d94a22e962527bc0c940b10f84fbcc82cfb9be979441b7367dced40a3adec4a72c0fdb0841db5790e0c24ae67f27f48fe93854
DIST tailscale-1.66.1.tar.gz 2634211 BLAKE2B cbf90ee36545fa8ba302f872948dc92e735bfe690451428540bee10399d1db1cdbf050949a5f3dbc69a77596b6a6d94724eb43bf9a4ee78a5cee9ada03889a3e SHA512 46e226c651abd5dee248e49fcf40a0cea9de72fe9e330015299acd9ec1fc83e1f192948c26b803f0fe3404558471b374391f3cf9155ecb2257a2563f79f3278b

View file

@ -0,0 +1,60 @@
# Copyright 2020-2024 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
EAPI=8
inherit go-module systemd tmpfiles
# These settings are obtained by running ./build_dist.sh shellvars in
# the upstream repo.
VERSION_MINOR="60"
VERSION_SHORT="1.60.0"
VERSION_LONG="1.60.0-tf4e3ee53e"
VERSION_GIT_HASH="f4e3ee53ea4605d400df2ef6b6005b026661f96b"
DESCRIPTION="Tailscale vpn client"
HOMEPAGE="https://tailscale.com"
SRC_URI="https://github.com/tailscale/tailscale/archive/v${PV}.tar.gz -> ${P}.tar.gz"
SRC_URI+=" https://gentoo.rgst.io/updater_artifacts/${CATEGORY}/${PN}/${PV}/deps.tar.xz"
LICENSE="MIT"
SLOT="0"
KEYWORDS="~amd64 ~arm arm64 ~riscv ~x86"
RDEPEND="net-firewall/iptables"
BDEPEND=">=dev-lang/go-1.21"
RESTRICT="test"
# This translates the build command from upstream's build_dist.sh to an
# ebuild equivalent.
build_dist() {
ego build -tags xversion -ldflags "
-X tailscale.com/version.Long=${VERSION_LONG}
-X tailscale.com/version.Short=${VERSION_SHORT}
-X tailscale.com/version.GitCommit=${VERSION_GIT_HASH}" "$@"
}
src_compile() {
build_dist ./cmd/tailscale
build_dist ./cmd/tailscaled
}
src_install() {
dosbin tailscaled
dobin tailscale
systemd_dounit cmd/tailscaled/tailscaled.service
insinto /etc/default
newins cmd/tailscaled/tailscaled.defaults tailscaled
keepdir /var/lib/${PN}
fperms 0750 /var/lib/${PN}
newtmpfiles "${FILESDIR}/${PN}.tmpfiles" ${PN}.conf
newinitd "${FILESDIR}/${PN}d.initd" ${PN}
newconfd "${FILESDIR}/${PN}d.confd" ${PN}
}
pkg_postinst() {
tmpfiles_process ${PN}.conf
}

67
packages.yml Normal file
View file

@ -0,0 +1,67 @@
# Configure automatic updates for ebuilds.
app-admin/chezmoi-bin:
resolver: git
options:
url: https://github.com/twpayne/chezmoi
app-admin/1password:
resolver: apt
options:
repository: "deb https://downloads.1password.com/linux/debian/amd64 stable main"
package: 1password
app-admin/op-cli-bin:
resolver: apt
options:
repository: "deb https://downloads.1password.com/linux/debian/amd64 stable main"
package: 1password-cli
app-arch/7-zip:
resolver: git
options:
url: https://github.com/ip7z/7zip
dev-util/mise:
resolver: git
options:
url: https://github.com/jdx/mise
# We have to regenerate the ebuild to get new crates and licenses to
# be reflected, so we have to have custom steps.
steps:
- checkout: https://github.com/jdx/mise
- original_ebuild: mise.ebuild
- command: pycargoebuild -i mise.ebuild
- ebuild: mise.ebuild
net-im/armcord:
resolver: git
options:
url: https://github.com/ArmCord/ArmCord
net-vpn/tailscale:
resolver: git
options:
url: https://github.com/tailscale/tailscale
# We have to generate a Go dependency archive and upload it to a
# stable location, so we do that during this process.
steps:
- checkout: https://github.com/tailscale/tailscale
- original_ebuild: new.ebuild
- command: |-
set -euxo pipefail
GO_VERSION=$(grep "^go" go.mod | awk '{ print $2 }' | awk -F '.' '{ print $1"."$2}')
mise use -g golang@"${GO_VERSION}"
# Create the dependency tar.
GOMODCACHE="${PWD}"/go-mod go mod download -modcacherw
tar --create --file deps.tar go-mod
xz --threads 0 deps.tar
# Get the shell variables and rewrite the ebuild to contain
# them.
eval "$(./build_dist.sh shellvars)"
sed -i 's/VERSION_MINOR=".*"/VERSION_MINOR="'"${VERSION_MINOR}"'"/' new.ebuild
sed -i 's/VERSION_SHORT=".*"/VERSION_SHORT="'"${VERSION_SHORT}"'"/' new.ebuild
sed -i 's/VERSION_LONG=".*"/VERSION_LONG="'"${VERSION_LONG}"'"/' new.ebuild
sed -i 's/VERSION_GIT_HASH=".*"/VERSION_GIT_HASH="'"${VERSION_GIT_HASH}"'"/' new.ebuild
sed -i 's|dev-lang/golang-.*|dev-lang/golang-${GO_VERSION}|' new.ebuild
- upload_artifact: deps.tar.xz
- ebuild: new.ebuild

23
update-base-image.sh Executable file
View file

@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Rebuilds the base image used by the tools in this repository and
# pushes it upstream.
set -euo pipefail
# PUSH determines if we should push the image to the remote or not.
PUSH=false
if [[ "${1:-}" == "--push" ]]; then
PUSH=true
fi
args=(
"--tag" "ghcr.io/jaredallard/overlay:updater"
"$(pwd)"
)
if [[ "$PUSH" == "true" ]]; then
args+=("--platform" "linux/amd64,linux/arm64" "--push")
else
args+=("--load")
fi
exec docker buildx build "${args[@]}"

View file

@ -1,35 +0,0 @@
# Configure automatic updates for ebuilds.
app-admin/chezmoi-bin:
resolver: git
options:
url: https://github.com/twpayne/chezmoi
app-admin/1password:
resolver: apt
options:
repository: "deb https://downloads.1password.com/linux/debian/amd64 stable main"
package: 1password
app-admin/op-cli-bin:
resolver: apt
options:
repository: "deb https://downloads.1password.com/linux/debian/amd64 stable main"
package: 1password-cli
app-arch/7-zip:
resolver: git
options:
url: https://github.com/ip7z/7zip
dev-util/mise:
resolver: git
options:
url: https://github.com/jdx/mise
# We have to regenerate the ebuild to get new crates and licenses to
# be reflected, so we have to have custom steps.
steps:
- command: git clone https://github.com/jdx/mise .
- original_ebuild: mise.ebuild
- command: pycargoebuild -i mise.ebuild
- ebuild: mise.ebuild
net-im/armcord:
resolver: git
options:
url: https://github.com/ArmCord/ArmCord