Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcf5f7555b |
@@ -18,10 +18,10 @@ jobs:
|
|||||||
# `before:` hook and for every one of the four cross-compiles.
|
# `before:` hook and for every one of the four cross-compiles.
|
||||||
# Without this step the release either fails at the before-hook or,
|
# Without this step the release either fails at the before-hook or,
|
||||||
# worse, ships binaries built by whatever Go the runner happens to
|
# worse, ships binaries built by whatever Go the runner happens to
|
||||||
# carry. check.yml's runner gets the same Go through
|
# carry. check.yml's script/cibuild also puts Go on its own runner,
|
||||||
# script/bootstrap, which calls script/install-go, and uses it only
|
# through script/bootstrap from the package manager at whatever
|
||||||
# for `go mod download` and gofmt; it compiles inside the
|
# version that ships, but uses it only for `go mod download` and
|
||||||
# digest-pinned Dockerfile images.
|
# gofmt; it compiles inside the digest-pinned Dockerfile images.
|
||||||
#
|
#
|
||||||
# actions/setup-go would pin the action by commit sha, but the Go
|
# actions/setup-go would pin the action by commit sha, but the Go
|
||||||
# tarball it downloads at runtime is verified against no value in
|
# tarball it downloads at runtime is verified against no value in
|
||||||
|
|||||||
@@ -763,11 +763,9 @@ standard: normalized scripts in `script/` are the entrypoints for the
|
|||||||
development workflow, and the Makefile targets are thin shims that call
|
development workflow, and the Makefile targets are thin shims that call
|
||||||
them. We provide:
|
them. We provide:
|
||||||
|
|
||||||
* `script/bootstrap` — install all development dependencies (Go, Go
|
* `script/bootstrap` — install all development dependencies (go, Go
|
||||||
module download). A host without Go gets the `go.mod` version through
|
module download). It deliberately does not install `golangci-lint`;
|
||||||
`script/install-go`, in `.tool/go`, which `script/fmt` and
|
see `script/lint` below.
|
||||||
`script/fmt-check` also look in. It deliberately does not install
|
|
||||||
`golangci-lint`; see `script/lint` below.
|
|
||||||
* `script/setup` — make a fresh clone ready for development: runs
|
* `script/setup` — make a fresh clone ready for development: runs
|
||||||
`script/bootstrap`, then `script/install-precommit`
|
`script/bootstrap`, then `script/install-precommit`
|
||||||
* `script/projectname` — print the project name (used for the Docker
|
* `script/projectname` — print the project name (used for the Docker
|
||||||
@@ -782,9 +780,9 @@ them. We provide:
|
|||||||
`script/bootstrap` insists on.
|
`script/bootstrap` insists on.
|
||||||
* `script/install-go` — install the Go toolchain named by `go.mod`'s
|
* `script/install-go` — install the Go toolchain named by `go.mod`'s
|
||||||
`go` directive into `.tool/go` from a sha256-verified `go.dev`
|
`go` directive into `.tool/go` from a sha256-verified `go.dev`
|
||||||
archive, and put it on `PATH`. Idempotent. Called by the release
|
archive, and put it on `PATH`. Idempotent. Called only by the release
|
||||||
workflow, which needs a host Go for `goreleaser` to shell out to, and
|
workflow, which needs a host Go for `goreleaser` to shell out to;
|
||||||
by `script/bootstrap` on a host without Go. `actions/setup-go` is not
|
nothing else on the release runner does. `actions/setup-go` is not
|
||||||
used because it verifies the downloaded toolchain against no value in
|
used because it verifies the downloaded toolchain against no value in
|
||||||
this repo. Bumping Go edits `go.mod`, the checksum in this script, and
|
this repo. Bumping Go edits `go.mod`, the checksum in this script, and
|
||||||
the two `golang` digests in the `Dockerfile` together.
|
the two `golang` digests in the `Dockerfile` together.
|
||||||
@@ -815,7 +813,7 @@ them. We provide:
|
|||||||
find out whether the tree is clean.
|
find out whether the tree is clean.
|
||||||
* `script/fmt` — format all code (writes)
|
* `script/fmt` — format all code (writes)
|
||||||
* `script/fmt-check` — check formatting (read-only). It runs `gofmt` on
|
* `script/fmt-check` — check formatting (read-only). It runs `gofmt` on
|
||||||
the host, over the Go files git lists.
|
the host.
|
||||||
* `script/check` — run `script/test`, `script/lint`, and
|
* `script/check` — run `script/test`, `script/lint`, and
|
||||||
`script/fmt-check`.
|
`script/fmt-check`.
|
||||||
* `script/docker` — build the Docker image tagged via
|
* `script/docker` — build the Docker image tagged via
|
||||||
|
|||||||
@@ -31,15 +31,6 @@ the tag exists and is exercised; what is left is merging `next` to
|
|||||||
is v2.14.0, with its new findings fixed in the code, and the rules in
|
is v2.14.0, with its new findings fixed in the code, and the rules in
|
||||||
`CLAUDE.md` now live in `AGENTS.md`.
|
`CLAUDE.md` now live in `AGENTS.md`.
|
||||||
|
|
||||||
- 2026-10-06: Made `--older-than` and `--keep-newer-than` reject a
|
|
||||||
duration with characters outside its number-and-unit parts
|
|
||||||
([issue #215](https://git.eeqj.de/sneak/vaultik/issues/215)). The
|
|
||||||
parser picked out the parts it recognised and skipped the rest, so
|
|
||||||
`1.0y` became zero and `--prune --keep-newer-than 1.0y` deleted every
|
|
||||||
snapshot of the backed-up names, the new one included. `1.0y`,
|
|
||||||
`1,5y`, `30 days`, `x7d` and a bare `0` are now errors; decimals still
|
|
||||||
work in Go units such as `1.5h`.
|
|
||||||
|
|
||||||
- 2026-10-06: Made a backup re-chunk a known file that lists a chunk no
|
- 2026-10-06: Made a backup re-chunk a known file that lists a chunk no
|
||||||
uploaded blob holds
|
uploaded blob holds
|
||||||
([issue #214](https://git.eeqj.de/sneak/vaultik/issues/214)). File
|
([issue #214](https://git.eeqj.de/sneak/vaultik/issues/214)). File
|
||||||
|
|||||||
@@ -18,19 +18,14 @@ var Appname = "vaultik" //nolint:gochecknoglobals // set via -ldflags at build t
|
|||||||
// deliberately not a number.
|
// deliberately not a number.
|
||||||
const DevVersion = "dev"
|
const DevVersion = "dev"
|
||||||
|
|
||||||
// Unknown is what Commit and CommitDate hold when the build did not
|
|
||||||
// stamp them, and the version script/docker and script/cibuild stamp
|
|
||||||
// when the host has no git checkout.
|
|
||||||
const Unknown = "unknown"
|
|
||||||
|
|
||||||
// Version is the application version, populated from main().
|
// Version is the application version, populated from main().
|
||||||
var Version = DevVersion //nolint:gochecknoglobals // set via -ldflags at build time
|
var Version = DevVersion //nolint:gochecknoglobals // set via -ldflags at build time
|
||||||
|
|
||||||
// Commit is the git commit hash, populated from main().
|
// Commit is the git commit hash, populated from main().
|
||||||
var Commit = Unknown //nolint:gochecknoglobals // set via -ldflags at build time
|
var Commit = "unknown" //nolint:gochecknoglobals // set via -ldflags at build time
|
||||||
|
|
||||||
// CommitDate is the ISO-8601 date of the commit, populated from main().
|
// CommitDate is the ISO-8601 date of the commit, populated from main().
|
||||||
var CommitDate = Unknown //nolint:gochecknoglobals // set via -ldflags at build time
|
var CommitDate = "unknown" //nolint:gochecknoglobals // set via -ldflags at build time
|
||||||
|
|
||||||
// Author identifies the upstream author of vaultik.
|
// Author identifies the upstream author of vaultik.
|
||||||
const Author = "Jeffrey Paul <sneak@sneak.berlin>"
|
const Author = "Jeffrey Paul <sneak@sneak.berlin>"
|
||||||
@@ -77,10 +72,11 @@ func New() (*Globals, error) {
|
|||||||
// safe reading of "we could not establish that this is a release" is
|
// safe reading of "we could not establish that this is a release" is
|
||||||
// that it is not one. The Makefile refuses to build at all in that
|
// that it is not one. The Makefile refuses to build at all in that
|
||||||
// case; this is the second line of defence, for a binary linked by
|
// case; this is the second line of defence, for a binary linked by
|
||||||
// something other than the Makefile. Unknown counts for the same
|
// something other than the Makefile. "unknown" counts for the same
|
||||||
// reason.
|
// reason: script/docker and script/cibuild stamp it when the host has
|
||||||
|
// no git checkout.
|
||||||
func IsDevVersion(v string) bool {
|
func IsDevVersion(v string) bool {
|
||||||
if v == "" || v == Unknown || v == DevVersion ||
|
if v == "" || v == "unknown" || v == DevVersion ||
|
||||||
strings.HasPrefix(v, DevVersion+"-") ||
|
strings.HasPrefix(v, DevVersion+"-") ||
|
||||||
strings.HasSuffix(v, "-dirty") {
|
strings.HasSuffix(v, "-dirty") {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -140,34 +140,24 @@ func parseSnapshotName(snapshotID string) string {
|
|||||||
// parseDuration parses a duration string with support for human-friendly units:
|
// parseDuration parses a duration string with support for human-friendly units:
|
||||||
// d/day/days, w/week/weeks, mo/month/months, y/year/years, plus standard Go
|
// d/day/days, w/week/weeks, mo/month/months, y/year/years, plus standard Go
|
||||||
// duration units. Following Go, m is minutes and mo is months. A bare number,
|
// duration units. Following Go, m is minutes and mo is months. A bare number,
|
||||||
// an unknown unit, and a negative value are all rejected. Outside the Go
|
// an unknown unit, and a negative value are all rejected.
|
||||||
// units the input must be whole numbers each followed directly by a unit
|
|
||||||
// (2w3d), so 1.0y and 30 days are errors; decimals work only in Go units
|
|
||||||
// (1.5h).
|
|
||||||
func parseDuration(s string) (time.Duration, error) {
|
func parseDuration(s string) (time.Duration, error) {
|
||||||
if strings.HasPrefix(strings.TrimSpace(s), "-") {
|
if strings.HasPrefix(strings.TrimSpace(s), "-") {
|
||||||
return 0, errNegativeDuration
|
return 0, errNegativeDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
// A bare number has no unit, but time.ParseDuration accepts 0, which
|
|
||||||
// would put the cutoff at now and select every snapshot.
|
|
||||||
_, err := strconv.ParseFloat(s, 64)
|
|
||||||
if err == nil {
|
|
||||||
return 0, fmt.Errorf("%w: %q", errInvalidDuration, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := time.ParseDuration(s)
|
d, err := time.ParseDuration(s)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !regexp.MustCompile(`^(\d+[a-zA-Z]+)+$`).MatchString(s) {
|
re := regexp.MustCompile(`(\d+)\s*([a-zA-Z]+)`)
|
||||||
|
|
||||||
|
matches := re.FindAllStringSubmatch(s, -1)
|
||||||
|
if len(matches) == 0 {
|
||||||
return 0, fmt.Errorf("%w: %q", errInvalidDuration, s)
|
return 0, fmt.Errorf("%w: %q", errInvalidDuration, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
re := regexp.MustCompile(`(\d+)([a-zA-Z]+)`)
|
|
||||||
matches := re.FindAllStringSubmatch(s, -1)
|
|
||||||
|
|
||||||
var total time.Duration
|
var total time.Duration
|
||||||
|
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ func TestParseDuration(t *testing.T) {
|
|||||||
{"30s", 30 * time.Second, false},
|
{"30s", 30 * time.Second, false},
|
||||||
{"6m", 6 * time.Minute, false},
|
{"6m", 6 * time.Minute, false},
|
||||||
{"1h", time.Hour, false},
|
{"1h", time.Hour, false},
|
||||||
{"1.5h", 90 * time.Minute, false},
|
|
||||||
// Extended calendar units.
|
// Extended calendar units.
|
||||||
{"30d", 30 * 24 * time.Hour, false},
|
{"30d", 30 * 24 * time.Hour, false},
|
||||||
{"3days", 3 * 24 * time.Hour, false},
|
{"3days", 3 * 24 * time.Hour, false},
|
||||||
@@ -74,21 +73,11 @@ func TestParseDuration(t *testing.T) {
|
|||||||
{"1y6mo", 365*24*time.Hour + 180*24*time.Hour, false},
|
{"1y6mo", 365*24*time.Hour + 180*24*time.Hour, false},
|
||||||
// Rejected inputs.
|
// Rejected inputs.
|
||||||
{"6", 0, true}, // bare number, no unit
|
{"6", 0, true}, // bare number, no unit
|
||||||
{"0", 0, true}, // bare number; time.ParseDuration accepts it
|
|
||||||
{"+0", 0, true}, // bare number; time.ParseDuration accepts it
|
|
||||||
{"5x", 0, true}, // unknown unit
|
{"5x", 0, true}, // unknown unit
|
||||||
{"-5d", 0, true}, // negative, extended unit
|
{"-5d", 0, true}, // negative, extended unit
|
||||||
{"-5h", 0, true}, // negative, Go unit
|
{"-5h", 0, true}, // negative, Go unit
|
||||||
{"", 0, true}, // empty
|
{"", 0, true}, // empty
|
||||||
{"garbage", 0, true},
|
{"garbage", 0, true},
|
||||||
// Characters outside the number-and-unit parts must not be
|
|
||||||
// skipped: 1.0y read as 0y would select every snapshot.
|
|
||||||
{"1.0y", 0, true},
|
|
||||||
{"2.1w", 0, true},
|
|
||||||
{"1,5y", 0, true},
|
|
||||||
{"30 days", 0, true},
|
|
||||||
{"30 days ago", 0, true},
|
|
||||||
{"x7d", 0, true},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
+8
-8
@@ -38,7 +38,12 @@ pkg_install() {
|
|||||||
detect_pkgmgr
|
detect_pkgmgr
|
||||||
case "$PKGMGR" in
|
case "$PKGMGR" in
|
||||||
nix) nix-env -iA "nixpkgs.$1" ;;
|
nix) nix-env -iA "nixpkgs.$1" ;;
|
||||||
apt) $SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y "$2" ;;
|
# A fresh image, such as the CI runner's, has no package lists,
|
||||||
|
# so apt-get install finds nothing until they are fetched.
|
||||||
|
apt)
|
||||||
|
$SUDO apt-get update
|
||||||
|
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y "$2"
|
||||||
|
;;
|
||||||
brew) brew install "$3" ;;
|
brew) brew install "$3" ;;
|
||||||
apk) apk add --no-cache "$4" ;;
|
apk) apk add --no-cache "$4" ;;
|
||||||
esac
|
esac
|
||||||
@@ -104,13 +109,8 @@ main() {
|
|||||||
if missing git; then pkg_install git git git git; fi
|
if missing git; then pkg_install git git git git; fi
|
||||||
if missing make; then pkg_install gnumake make make make; fi
|
if missing make; then pkg_install gnumake make make make; fi
|
||||||
|
|
||||||
# Go toolchain: the host's own, or else the version go.mod names,
|
# Go toolchain
|
||||||
# hash-verified, in .tool/go. That directory is not on the caller's
|
if missing go; then pkg_install go golang go go; fi
|
||||||
# PATH, so script/fmt and script/fmt-check look there too.
|
|
||||||
if missing go; then
|
|
||||||
"$ROOT/script/install-go"
|
|
||||||
PATH="$PATH:$ROOT/.tool/go/bin"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# golangci-lint is deliberately NOT installed: script/lint lints by
|
# golangci-lint is deliberately NOT installed: script/lint lints by
|
||||||
# building the lint phase of the Dockerfile, whose digest-pinned
|
# building the lint phase of the Dockerfile, whose digest-pinned
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
# Where script/bootstrap installs Go when the host has none.
|
|
||||||
PATH="$PATH:$ROOT/.tool/go/bin"
|
|
||||||
go fmt ./...
|
go fmt ./...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-7
@@ -7,13 +7,7 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
# Where script/bootstrap installs Go when the host has none.
|
unformatted="$(gofmt -l .)"
|
||||||
PATH="$PATH:$ROOT/.tool/go/bin"
|
|
||||||
# The Go files git lists, which leaves out the sources of that
|
|
||||||
# toolchain in the ignored .tool/go.
|
|
||||||
files="$(git ls-files --cached --others --exclude-standard '*.go')"
|
|
||||||
# shellcheck disable=SC2086 # one argument per file name
|
|
||||||
unformatted="$(gofmt -l $files)"
|
|
||||||
if [ -n "$unformatted" ]; then
|
if [ -n "$unformatted" ]; then
|
||||||
echo "Files not formatted:" >&2
|
echo "Files not formatted:" >&2
|
||||||
echo "$unformatted" >&2
|
echo "$unformatted" >&2
|
||||||
|
|||||||
+9
-7
@@ -4,13 +4,14 @@
|
|||||||
# own extension to scripts-to-rule-them-all. Idempotent: exits at once
|
# own extension to scripts-to-rule-them-all. Idempotent: exits at once
|
||||||
# when the pinned toolchain is already installed.
|
# when the pinned toolchain is already installed.
|
||||||
#
|
#
|
||||||
# .gitea/workflows/release.yml calls this, and so does script/bootstrap
|
# Only .gitea/workflows/release.yml calls this. goreleaser is not a
|
||||||
# when the host has no Go, as on the check runner. goreleaser is not a
|
|
||||||
# compiler: it shells out to `go` for the `before:` hook and for every
|
# compiler: it shells out to `go` for the `before:` hook and for every
|
||||||
# one of the four cross-compiles, so the release runner needs a Go
|
# one of the four cross-compiles, so the release runner needs a Go
|
||||||
# toolchain on PATH. The check runner compiles nothing on the host; it
|
# toolchain on PATH. check.yml compiles nothing on the host: it uses
|
||||||
# uses this Go only for bootstrap's `go mod download` and for gofmt in
|
# whatever Go script/bootstrap finds or installs only for that script's
|
||||||
# script/fmt-check. Per REPO_POLICIES.md a host Go is pinned by hash.
|
# `go mod download` and for gofmt in script/fmt-check. So this is the
|
||||||
|
# only host Go that builds anything, and per REPO_POLICIES.md it must be
|
||||||
|
# pinned by hash.
|
||||||
# actions/setup-go exposes no checksum input, so Go is installed the way
|
# actions/setup-go exposes no checksum input, so Go is installed the way
|
||||||
# script/install-goreleaser installs goreleaser: download the exact
|
# script/install-goreleaser installs goreleaser: download the exact
|
||||||
# archive from go.dev and refuse it unless its sha256 matches the value
|
# archive from go.dev and refuse it unless its sha256 matches the value
|
||||||
@@ -22,8 +23,9 @@
|
|||||||
# go.mod, the checksum here, and the Dockerfile's two golang digests
|
# go.mod, the checksum here, and the Dockerfile's two golang digests
|
||||||
# together.
|
# together.
|
||||||
#
|
#
|
||||||
# Linux only, because that is what both runners are. A darwin dev uses
|
# Linux only, because that is what the release runner is. A darwin dev
|
||||||
# their own Go; supporting an OS means adding its checksums.
|
# building a snapshot uses their own Go; supporting an OS means adding
|
||||||
|
# its checksums.
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
|
|||||||
Reference in New Issue
Block a user