replace shell update scripts with Go tooling
This commit is contained in:
parent
df368945bc
commit
2f252345d9
2
.github/workflows/sync-skills.yml
vendored
2
.github/workflows/sync-skills.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Sync skills from clawdbot
|
||||
run: scripts/sync-skills.sh
|
||||
run: go run ./cmd/sync-skills
|
||||
|
||||
- name: Commit & push if changed
|
||||
run: |
|
||||
|
||||
2
.github/workflows/update-tools.yml
vendored
2
.github/workflows/update-tools.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
||||
- name: Update tool versions and hashes
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: scripts/update-tools.sh
|
||||
run: go run ./cmd/update-tools
|
||||
|
||||
- name: Commit & push if changed
|
||||
run: |
|
||||
|
||||
@ -65,7 +65,7 @@ inputs.nix-steipete-tools.packages.aarch64-darwin.peekaboo
|
||||
Skills are vendored from [clawdbot/clawdbot](https://github.com/clawdbot/clawdbot) main branch. No pinning - we track latest.
|
||||
|
||||
```bash
|
||||
scripts/sync-skills.sh
|
||||
go run ./cmd/sync-skills
|
||||
```
|
||||
|
||||
Pulls latest main via sparse checkout, only updates files when contents actually change.
|
||||
@ -75,7 +75,7 @@ Pulls latest main via sparse checkout, only updates files when contents actually
|
||||
Tools track upstream GitHub releases directly (not Homebrew).
|
||||
|
||||
```bash
|
||||
scripts/update-tools.sh
|
||||
go run ./cmd/update-tools
|
||||
```
|
||||
|
||||
Fetches latest release versions/URLs/hashes and updates the Nix expressions. Oracle uses pnpm and auto-derives its hash via build mismatch.
|
||||
|
||||
112
cmd/sync-skills/main.go
Normal file
112
cmd/sync-skills/main.go
Normal file
@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Mapping struct {
|
||||
Tool string
|
||||
Up string
|
||||
}
|
||||
|
||||
func run(dir string, name string, args ...string) error {
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Dir = dir
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("%s %v: %v: %s", name, args, err, out.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
if _, err := io.Copy(out, in); err != nil {
|
||||
return err
|
||||
}
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
func main() {
|
||||
repoRoot, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
workdir, err := os.MkdirTemp("", "clawdbot-skills-")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(workdir)
|
||||
|
||||
mappings := []Mapping{
|
||||
{"summarize", "skills/summarize"},
|
||||
{"gogcli", "skills/gog"},
|
||||
{"camsnap", "skills/camsnap"},
|
||||
{"sonoscli", "skills/sonoscli"},
|
||||
{"bird", "skills/bird"},
|
||||
{"peekaboo", "skills/peekaboo"},
|
||||
{"sag", "skills/sag"},
|
||||
{"imsg", "skills/imsg"},
|
||||
{"oracle", "skills/oracle"},
|
||||
}
|
||||
|
||||
log.Printf("[sync-skills] cloning clawdbot main")
|
||||
if err := run("", "git", "clone", "--depth", "1", "--filter=blob:none", "--sparse", "https://github.com/clawdbot/clawdbot.git", workdir); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
paths := []string{}
|
||||
for _, m := range mappings {
|
||||
paths = append(paths, m.Up)
|
||||
}
|
||||
args := append([]string{"sparse-checkout", "set"}, paths...)
|
||||
if err := run(workdir, "git", args...); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
updated := false
|
||||
for _, m := range mappings {
|
||||
src := filepath.Join(workdir, m.Up, "SKILL.md")
|
||||
dest := filepath.Join(repoRoot, "tools", m.Tool, "skills", filepath.Base(m.Up), "SKILL.md")
|
||||
if _, err := os.Stat(src); err != nil {
|
||||
log.Printf("[sync-skills] missing %s", src)
|
||||
continue
|
||||
}
|
||||
same := false
|
||||
if b1, err1 := os.ReadFile(src); err1 == nil {
|
||||
if b2, err2 := os.ReadFile(dest); err2 == nil && bytes.Equal(b1, b2) {
|
||||
same = true
|
||||
}
|
||||
}
|
||||
if !same {
|
||||
if err := copyFile(src, dest); err != nil {
|
||||
log.Fatalf("copy %s -> %s: %v", src, dest, err)
|
||||
}
|
||||
updated = true
|
||||
log.Printf("[sync-skills] updated %s", m.Tool)
|
||||
}
|
||||
}
|
||||
|
||||
if !updated {
|
||||
log.Printf("[sync-skills] no changes")
|
||||
}
|
||||
}
|
||||
147
cmd/update-tools/main.go
Normal file
147
cmd/update-tools/main.go
Normal file
@ -0,0 +1,147 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/clawdbot/nix-stepiete-tools/internal"
|
||||
)
|
||||
|
||||
type Tool struct {
|
||||
Name string
|
||||
Repo string
|
||||
AssetRegex *regexp.Regexp
|
||||
NixFile string
|
||||
}
|
||||
|
||||
func updateTool(tool Tool) error {
|
||||
log.Printf("[update-tools] %s", tool.Name)
|
||||
rel, err := internal.LatestRelease(tool.Repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version := strings.TrimPrefix(rel.TagName, "v")
|
||||
var assetURL string
|
||||
for _, a := range rel.Assets {
|
||||
if tool.AssetRegex.MatchString(a.Name) {
|
||||
assetURL = a.BrowserDownloadURL
|
||||
break
|
||||
}
|
||||
}
|
||||
if assetURL == "" {
|
||||
return fmt.Errorf("no asset matched for %s", tool.Name)
|
||||
}
|
||||
hash, err := internal.PrefetchHash(assetURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := internal.ReplaceOnce(tool.NixFile, regexp.MustCompile(`version = "[^"]+";`), fmt.Sprintf(`version = "%s";`, version)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := internal.ReplaceOnce(tool.NixFile, regexp.MustCompile(`url = "[^"]+";`), fmt.Sprintf(`url = "%s";`, assetURL)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := internal.ReplaceOnce(tool.NixFile, regexp.MustCompile(`hash = "sha256-[^"]+";`), fmt.Sprintf(`hash = "%s";`, hash)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateOracle(repoRoot string) error {
|
||||
log.Printf("[update-tools] oracle")
|
||||
rel, err := internal.LatestRelease("steipete/oracle")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version := strings.TrimPrefix(rel.TagName, "v")
|
||||
var assetURL string
|
||||
for _, a := range rel.Assets {
|
||||
if matched, _ := regexp.MatchString(`oracle-[0-9.]+\.tgz`, a.Name); matched {
|
||||
assetURL = a.BrowserDownloadURL
|
||||
break
|
||||
}
|
||||
}
|
||||
if assetURL == "" {
|
||||
return fmt.Errorf("no asset matched for oracle")
|
||||
}
|
||||
assetHash, err := internal.PrefetchHash(assetURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lockURL := fmt.Sprintf("https://github.com/steipete/oracle/archive/refs/tags/%s.tar.gz", rel.TagName)
|
||||
lockHash, err := internal.PrefetchHash(lockURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oracleFile := filepath.Join(repoRoot, "nix", "pkgs", "oracle.nix")
|
||||
if err := internal.ReplaceOnce(oracleFile, regexp.MustCompile(`version = "[^"]+";`), fmt.Sprintf(`version = "%s";`, version)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := internal.ReplaceOnce(oracleFile, regexp.MustCompile(`url = "[^"]+";`), fmt.Sprintf(`url = "%s";`, assetURL)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := internal.ReplaceOnce(oracleFile, regexp.MustCompile(`hash = "sha256-[^"]+";`), fmt.Sprintf(`hash = "%s";`, assetHash)); err != nil {
|
||||
return err
|
||||
}
|
||||
lockRe := regexp.MustCompile(`lockSrc = fetchFromGitHub \{[^}]*?hash = "sha256-[^"]+";`)
|
||||
if err := internal.ReplaceOnceFunc(oracleFile, lockRe, func(s string) string {
|
||||
return regexp.MustCompile(`hash = "sha256-[^"]+";`).ReplaceAllString(s, fmt.Sprintf(`hash = "%s";`, lockHash))
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
pnpmRe := regexp.MustCompile(`pnpmDeps.*?hash = "sha256-[^"]+";`)
|
||||
if err := internal.ReplaceOnceFunc(oracleFile, pnpmRe, func(s string) string {
|
||||
return regexp.MustCompile(`hash = "sha256-[^"]+";`).ReplaceAllString(s, `hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";`)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[update-tools] oracle: deriving pnpm hash")
|
||||
logText, _ := internal.NixBuildOracle()
|
||||
pnpmHash := internal.ExtractGotHash(logText)
|
||||
if pnpmHash == "" {
|
||||
return fmt.Errorf("oracle pnpm hash not found in build output")
|
||||
}
|
||||
if err := internal.ReplaceOnceFunc(oracleFile, pnpmRe, func(s string) string {
|
||||
return regexp.MustCompile(`hash = "sha256-[^"]+";`).ReplaceAllString(s, fmt.Sprintf(`hash = "%s";`, pnpmHash))
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
repoRoot, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
tools := []Tool{
|
||||
{"summarize", "steipete/summarize", regexp.MustCompile(`summarize-macos-arm64-v[0-9.]+\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "summarize.nix")},
|
||||
{"gogcli", "steipete/gogcli", regexp.MustCompile(`gogcli_[0-9.]+_darwin_arm64\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "gogcli.nix")},
|
||||
{"camsnap", "steipete/camsnap", regexp.MustCompile(`camsnap-macos-arm64\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "camsnap.nix")},
|
||||
{"sonoscli", "steipete/sonoscli", regexp.MustCompile(`sonoscli-macos-arm64\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "sonoscli.nix")},
|
||||
{"bird", "steipete/bird", regexp.MustCompile(`bird-macos-universal-v[0-9.]+\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "bird.nix")},
|
||||
{"peekaboo", "steipete/peekaboo", regexp.MustCompile(`peekaboo-macos-universal\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "peekaboo.nix")},
|
||||
{"poltergeist", "steipete/poltergeist", regexp.MustCompile(`poltergeist-macos-universal-v[0-9.]+\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "poltergeist.nix")},
|
||||
{"sag", "steipete/sag", regexp.MustCompile(`sag_[0-9.]+_darwin_universal\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "sag.nix")},
|
||||
{"imsg", "steipete/imsg", regexp.MustCompile(`imsg-macos\.zip`), filepath.Join(repoRoot, "nix", "pkgs", "imsg.nix")},
|
||||
}
|
||||
|
||||
for _, tool := range tools {
|
||||
if err := updateTool(tool); err != nil {
|
||||
log.Fatalf("update %s failed: %v", tool.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := updateOracle(repoRoot); err != nil {
|
||||
log.Fatalf("update oracle failed: %v", err)
|
||||
}
|
||||
}
|
||||
33
internal/fs.go
Normal file
33
internal/fs.go
Normal file
@ -0,0 +1,33 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func ReplaceOnce(path string, re *regexp.Regexp, replace string) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orig := string(data)
|
||||
out := re.ReplaceAllString(orig, replace)
|
||||
if out == orig {
|
||||
return fmt.Errorf("pattern not found in %s", path)
|
||||
}
|
||||
return os.WriteFile(path, []byte(out), 0644)
|
||||
}
|
||||
|
||||
func ReplaceOnceFunc(path string, re *regexp.Regexp, fn func(string) string) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orig := string(data)
|
||||
out := re.ReplaceAllStringFunc(orig, fn)
|
||||
if out == orig {
|
||||
return fmt.Errorf("pattern not found in %s", path)
|
||||
}
|
||||
return os.WriteFile(path, []byte(out), 0644)
|
||||
}
|
||||
49
internal/github.go
Normal file
49
internal/github.go
Normal file
@ -0,0 +1,49 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Release struct {
|
||||
TagName string `json:"tag_name"`
|
||||
Assets []Asset `json:"assets"`
|
||||
}
|
||||
|
||||
type Asset struct {
|
||||
Name string `json:"name"`
|
||||
BrowserDownloadURL string `json:"browser_download_url"`
|
||||
}
|
||||
|
||||
func LatestRelease(repo string) (*Release, error) {
|
||||
url := fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", repo)
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Accept", "application/vnd.github+json")
|
||||
if token := os.Getenv("GH_TOKEN"); token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("github api %s: %s", resp.Status, string(body))
|
||||
}
|
||||
var rel Release
|
||||
if err := json.NewDecoder(resp.Body).Decode(&rel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rel.TagName == "" {
|
||||
return nil, errors.New("missing tag_name in release")
|
||||
}
|
||||
return &rel, nil
|
||||
}
|
||||
49
internal/nix.go
Normal file
49
internal/nix.go
Normal file
@ -0,0 +1,49 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PrefetchResult struct {
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
|
||||
func PrefetchHash(url string) (string, error) {
|
||||
cmd := exec.Command("nix", "store", "prefetch-file", "--json", url)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", fmt.Errorf("prefetch failed: %v: %s", err, out.String())
|
||||
}
|
||||
var res PrefetchResult
|
||||
if err := json.Unmarshal(out.Bytes(), &res); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if res.Hash == "" {
|
||||
return "", fmt.Errorf("empty hash for %s", url)
|
||||
}
|
||||
return res.Hash, nil
|
||||
}
|
||||
|
||||
func NixBuildOracle() (string, error) {
|
||||
cmd := exec.Command("nix", "build", ".#oracle")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
err := cmd.Run()
|
||||
return out.String(), err
|
||||
}
|
||||
|
||||
func ExtractGotHash(log string) string {
|
||||
for _, line := range strings.Split(log, "\n") {
|
||||
if idx := strings.Index(line, "got: sha256-"); idx != -1 {
|
||||
return strings.TrimSpace(line[idx+5:])
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
workdir="$(mktemp -d)"
|
||||
trap 'rm -rf "${workdir}"' EXIT
|
||||
|
||||
# Mapping: tool -> upstream skill dir (relative to clawdbot repo)
|
||||
declare -A SKILLS
|
||||
SKILLS[summarize]="skills/summarize"
|
||||
SKILLS[gogcli]="skills/gog"
|
||||
SKILLS[camsnap]="skills/camsnap"
|
||||
SKILLS[sonoscli]="skills/sonoscli"
|
||||
SKILLS[bird]="skills/bird"
|
||||
SKILLS[peekaboo]="skills/peekaboo"
|
||||
SKILLS[sag]="skills/sag"
|
||||
SKILLS[imsg]="skills/imsg"
|
||||
SKILLS[oracle]="skills/oracle"
|
||||
|
||||
# Clone only the needed paths from latest main.
|
||||
# Note: no pinning; this is explicitly "latest main".
|
||||
git -c advice.detachedHead=false clone --depth 1 --filter=blob:none --sparse \
|
||||
https://github.com/clawdbot/clawdbot.git "${workdir}/clawdbot" >/dev/null
|
||||
|
||||
pushd "${workdir}/clawdbot" >/dev/null
|
||||
git sparse-checkout set "${SKILLS[@]}" >/dev/null
|
||||
popd >/dev/null
|
||||
|
||||
updated=0
|
||||
for tool in "${!SKILLS[@]}"; do
|
||||
src_dir="${workdir}/clawdbot/${SKILLS[$tool]}"
|
||||
dest_dir="${repo_root}/tools/${tool}/skills/$(basename "${SKILLS[$tool]}")"
|
||||
|
||||
if [[ ! -d "${src_dir}" ]]; then
|
||||
echo "[sync-skills] missing upstream skill: ${SKILLS[$tool]} (skip)" >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
mkdir -p "${dest_dir}"
|
||||
|
||||
# Copy only if content changed (avoid noisy commits).
|
||||
if ! cmp -s "${src_dir}/SKILL.md" "${dest_dir}/SKILL.md" 2>/dev/null; then
|
||||
cp "${src_dir}/SKILL.md" "${dest_dir}/SKILL.md"
|
||||
updated=1
|
||||
echo "[sync-skills] updated ${tool}" >&2
|
||||
fi
|
||||
|
||||
# If upstream has extra files in the skill dir, sync them too.
|
||||
rsync -a --delete --exclude SKILL.md "${src_dir}/" "${dest_dir}/" >/dev/null
|
||||
if [[ -n "$(git -C "${repo_root}" status --porcelain "${dest_dir}" 2>/dev/null)" ]]; then
|
||||
updated=1
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
if [[ $updated -eq 0 ]]; then
|
||||
echo "[sync-skills] no changes" >&2
|
||||
fi
|
||||
@ -1,182 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
echo "[update-tools] jq is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v nix >/dev/null 2>&1; then
|
||||
echo "[update-tools] nix is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
prefetch_hash() {
|
||||
local url="$1"
|
||||
nix store prefetch-file --json "$url" | jq -r .hash
|
||||
}
|
||||
|
||||
latest_release() {
|
||||
local repo="$1"
|
||||
gh api "repos/${repo}/releases/latest"
|
||||
}
|
||||
|
||||
update_nix_file() {
|
||||
local file="$1"
|
||||
local version="$2"
|
||||
local url="$3"
|
||||
local hash="$4"
|
||||
|
||||
VERSION="$version" URL="$url" HASH="$hash" FILE="$file" python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
import os
|
||||
import re
|
||||
|
||||
path = Path(os.environ["FILE"])
|
||||
version = os.environ["VERSION"]
|
||||
url = os.environ["URL"]
|
||||
hash_ = os.environ["HASH"]
|
||||
|
||||
text = path.read_text()
|
||||
text, n1 = re.subn(r'version = "[^"]+";', f'version = "{version}";', text, count=1)
|
||||
text, n2 = re.subn(r'url = "[^"]+";', f'url = "{url}";', text, count=1)
|
||||
text, n3 = re.subn(r'hash = "sha256-[^"]+";', f'hash = "{hash_}";', text, count=1)
|
||||
|
||||
if n1 == 0 or n2 == 0 or n3 == 0:
|
||||
raise SystemExit(f"update failed for {path}: version/url/hash not found")
|
||||
|
||||
path.write_text(text)
|
||||
PY
|
||||
}
|
||||
|
||||
update_tool() {
|
||||
local tool="$1"
|
||||
local repo="$2"
|
||||
local asset_regex="$3"
|
||||
local nix_file="$4"
|
||||
|
||||
echo "[update-tools] ${tool}" >&2
|
||||
local json
|
||||
json=$(latest_release "$repo")
|
||||
|
||||
local tag
|
||||
tag=$(echo "$json" | jq -r .tag_name)
|
||||
local version
|
||||
version="${tag#v}"
|
||||
|
||||
local asset
|
||||
asset=$(echo "$json" | jq -r --arg re "$asset_regex" '.assets[] | select(.name|test($re)) | .browser_download_url' | head -1)
|
||||
|
||||
if [[ -z "$asset" ]]; then
|
||||
echo "[update-tools] no asset matched for ${tool} (${asset_regex})" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local hash
|
||||
hash=$(prefetch_hash "$asset")
|
||||
|
||||
update_nix_file "$nix_file" "$version" "$asset" "$hash"
|
||||
}
|
||||
|
||||
update_oracle() {
|
||||
local tool="oracle"
|
||||
local repo="steipete/oracle"
|
||||
local nix_file="$repo_root/nix/pkgs/oracle.nix"
|
||||
|
||||
echo "[update-tools] ${tool}" >&2
|
||||
local json
|
||||
json=$(latest_release "$repo")
|
||||
|
||||
local tag
|
||||
tag=$(echo "$json" | jq -r .tag_name)
|
||||
local version
|
||||
version="${tag#v}"
|
||||
|
||||
local asset
|
||||
asset=$(echo "$json" | jq -r '.assets[] | select(.name|test("oracle-[0-9.]+\\.tgz")) | .browser_download_url' | head -1)
|
||||
|
||||
if [[ -z "$asset" ]]; then
|
||||
echo "[update-tools] no asset matched for oracle" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local asset_hash
|
||||
asset_hash=$(prefetch_hash "$asset")
|
||||
|
||||
local lock_url
|
||||
lock_url="https://github.com/steipete/oracle/archive/refs/tags/${tag}.tar.gz"
|
||||
local lock_hash
|
||||
lock_hash=$(prefetch_hash "$lock_url")
|
||||
|
||||
VERSION="$version" URL="$asset" HASH="$asset_hash" LOCK_HASH="$lock_hash" FILE="$nix_file" python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
import os
|
||||
import re
|
||||
|
||||
path = Path(os.environ["FILE"])
|
||||
version = os.environ["VERSION"]
|
||||
url = os.environ["URL"]
|
||||
hash_ = os.environ["HASH"]
|
||||
lock_hash = os.environ["LOCK_HASH"]
|
||||
|
||||
text = path.read_text()
|
||||
text, n1 = re.subn(r'version = "[^"]+";', f'version = "{version}";', text, count=1)
|
||||
text, n2 = re.subn(r'url = "[^"]+";', f'url = "{url}";', text, count=1)
|
||||
text, n3 = re.subn(r'hash = "sha256-[^"]+";', f'hash = "{hash_}";', text, count=1)
|
||||
text, n4 = re.subn(r'lockSrc = fetchFromGitHub \{[^}]*?hash = "sha256-[^"]+";',
|
||||
lambda m: re.sub(r'hash = "sha256-[^"]+";', f'hash = "{lock_hash}";', m.group(0)),
|
||||
text, count=1, flags=re.S)
|
||||
text, n5 = re.subn(r'pnpmDeps.*?hash = "sha256-[^"]+";',
|
||||
lambda m: re.sub(r'hash = "sha256-[^"]+";', 'hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";', m.group(0)),
|
||||
text, count=1, flags=re.S)
|
||||
|
||||
if n1 == 0 or n2 == 0 or n3 == 0 or n4 == 0 or n5 == 0:
|
||||
raise SystemExit(f"update failed for {path}: fields not found")
|
||||
|
||||
path.write_text(text)
|
||||
PY
|
||||
|
||||
# Derive pnpmDeps hash from a build mismatch
|
||||
set +e
|
||||
build_log=$(nix build .#oracle 2>&1)
|
||||
set -e
|
||||
if echo "$build_log" | grep -q "got: sha256-"; then
|
||||
pnpm_hash=$(echo "$build_log" | sed -n 's/.*got: \(sha256-[A-Za-z0-9+/=]*\).*/\1/p' | head -1)
|
||||
if [[ -n "$pnpm_hash" ]]; then
|
||||
PNPM_HASH="$pnpm_hash" FILE="$nix_file" python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
import os
|
||||
import re
|
||||
|
||||
path = Path(os.environ["FILE"])
|
||||
pnpm_hash = os.environ["PNPM_HASH"]
|
||||
text = path.read_text()
|
||||
text, n = re.subn(r'pnpmDeps.*?hash = "sha256-[^"]+";',
|
||||
lambda m: re.sub(r'hash = "sha256-[^"]+";', f'hash = "{pnpm_hash}";', m.group(0)),
|
||||
text, count=1, flags=re.S)
|
||||
if n == 0:
|
||||
raise SystemExit(f"update failed for {path}: pnpmDeps hash not found")
|
||||
path.write_text(text)
|
||||
PY
|
||||
else
|
||||
echo "[update-tools] failed to extract pnpmDeps hash" >&2
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo "[update-tools] no pnpmDeps hash mismatch found" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
update_tool summarize "steipete/summarize" "summarize-macos-arm64-v[0-9.]+\\.tar\\.gz" "$repo_root/nix/pkgs/summarize.nix"
|
||||
update_tool gogcli "steipete/gogcli" "gogcli_[0-9.]+_darwin_arm64\\.tar\\.gz" "$repo_root/nix/pkgs/gogcli.nix"
|
||||
update_tool camsnap "steipete/camsnap" "camsnap-macos-arm64\\.tar\\.gz" "$repo_root/nix/pkgs/camsnap.nix"
|
||||
update_tool sonoscli "steipete/sonoscli" "sonoscli-macos-arm64\\.tar\\.gz" "$repo_root/nix/pkgs/sonoscli.nix"
|
||||
update_tool bird "steipete/bird" "bird-macos-universal-v[0-9.]+\\.tar\\.gz" "$repo_root/nix/pkgs/bird.nix"
|
||||
update_tool peekaboo "steipete/peekaboo" "peekaboo-macos-universal\\.tar\\.gz" "$repo_root/nix/pkgs/peekaboo.nix"
|
||||
update_tool poltergeist "steipete/poltergeist" "poltergeist-macos-universal-v[0-9.]+\\.tar\\.gz" "$repo_root/nix/pkgs/poltergeist.nix"
|
||||
update_tool sag "steipete/sag" "sag_[0-9.]+_darwin_universal\\.tar\\.gz" "$repo_root/nix/pkgs/sag.nix"
|
||||
update_tool imsg "steipete/imsg" "imsg-macos\\.zip" "$repo_root/nix/pkgs/imsg.nix"
|
||||
update_oracle
|
||||
Loading…
Reference in New Issue
Block a user