Compare commits
2 Commits
fix/issue-
...
bd4b135e17
| Author | SHA1 | Date | |
|---|---|---|---|
| bd4b135e17 | |||
| bc5b2b039a |
@@ -1,3 +1,5 @@
|
|||||||
*.tmp
|
*.tzst
|
||||||
*.dockerimage
|
*.tar
|
||||||
.git
|
/buildimage
|
||||||
|
/dockerdeps
|
||||||
|
/tmp
|
||||||
|
|||||||
11
.drone.yml
11
.drone.yml
@@ -7,17 +7,8 @@ steps:
|
|||||||
network_mode: bridge
|
network_mode: bridge
|
||||||
settings:
|
settings:
|
||||||
repo: sneak/mfer
|
repo: sneak/mfer
|
||||||
build_args_from_env: [ DRONE_COMMIT_SHA ]
|
|
||||||
dry_run: true
|
dry_run: true
|
||||||
custom_dns: [ 116.202.204.30 ]
|
custom_dns: [ 116.202.204.30 ]
|
||||||
tags:
|
tags:
|
||||||
- ${DRONE_COMMIT_SHA:0:7}
|
- ${DRONE_COMMIT_SHA}
|
||||||
- ${DRONE_BRANCH}
|
- ${DRONE_BRANCH}
|
||||||
- latest
|
|
||||||
- name: notify
|
|
||||||
image: plugins/slack
|
|
||||||
settings:
|
|
||||||
webhook:
|
|
||||||
from_secret: SLACK_WEBHOOK_URL
|
|
||||||
when:
|
|
||||||
event: pull_request
|
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,6 +1,6 @@
|
|||||||
mfer/*.pb.go
|
mfer/*.pb.go
|
||||||
/mfer.cmd
|
/mfer.cmd
|
||||||
|
vendor
|
||||||
/tmp
|
/tmp
|
||||||
*.tmp
|
/buildimage/go.mod
|
||||||
*.dockerimage
|
/buildimage/go.sum
|
||||||
/vendor
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
run:
|
|
||||||
tests: false
|
|
||||||
58
Dockerfile
58
Dockerfile
@@ -1,37 +1,29 @@
|
|||||||
################################################################################
|
## lint image
|
||||||
#2345678911234567892123456789312345678941234567895123456789612345678971234567898
|
FROM golangci/golangci-lint:v1.50.1
|
||||||
################################################################################
|
|
||||||
FROM sneak/builder:2022-12-08 AS builder
|
RUN mkdir -p /build
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
WORKDIR /build
|
||||||
WORKDIR /build
|
COPY ./ ./
|
||||||
COPY ./Makefile ./.golangci.yml ./go.mod ./go.sum /build/
|
RUN golangci-lint run
|
||||||
COPY ./vendor.tzst /build/vendor.tzst
|
|
||||||
COPY ./modcache.tzst /build/modcache.tzst
|
## build image:
|
||||||
COPY ./internal ./internal
|
FROM sneak/mfer-build AS builder
|
||||||
COPY ./bin/gitrev.sh ./bin/gitrev.sh
|
WORKDIR /build
|
||||||
COPY ./mfer ./mfer
|
|
||||||
COPY ./cmd ./cmd
|
COPY go.mod .
|
||||||
ARG GITREV unknown
|
COPY go.sum .
|
||||||
ARG DRONE_COMMIT_SHA unknown
|
|
||||||
|
COPY ./ ./
|
||||||
|
# don't lint again during build because there's no golangci-lint in this
|
||||||
|
# image and we already did it in a previous stage
|
||||||
|
#RUN make lint
|
||||||
|
RUN make mfer.cmd
|
||||||
|
#RUN go mod vendor
|
||||||
|
RUN tar -c . | bzip2 > /src.tbz2
|
||||||
|
|
||||||
|
|
||||||
RUN mkdir -p "$(go env GOMODCACHE)" && cd "$(go env GOMODCACHE)" && \
|
|
||||||
zstdmt -d --stdout /build/modcache.tzst | tar xf - && \
|
|
||||||
rm /build/modcache.tzst && cd /build
|
|
||||||
RUN \
|
|
||||||
cd mfer && go generate . && cd .. && \
|
|
||||||
GOPACKAGESDEBUG=true golangci-lint run ./... && \
|
|
||||||
mkdir vendor && cd vendor && \
|
|
||||||
zstdmt -d --stdout /build/vendor.tzst | tar xf - && rm /build/vendor.tzst && \
|
|
||||||
cd .. && \
|
|
||||||
make mfer.cmd
|
|
||||||
RUN rm -rf /build/vendor && go mod vendor && tar -c . | zstdmt -19 > /src.tzst
|
|
||||||
################################################################################
|
|
||||||
#2345678911234567892123456789312345678941234567895123456789612345678971234567898
|
|
||||||
################################################################################
|
|
||||||
## final image
|
|
||||||
################################################################################
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
# we put all the source into the final image for posterity, it's small
|
COPY --from=builder /src.tbz2 /src.tbz2
|
||||||
COPY --from=builder /src.tzst /src.tzst
|
|
||||||
COPY --from=builder /build/mfer.cmd /mfer
|
COPY --from=builder /build/mfer.cmd /mfer
|
||||||
ENTRYPOINT ["/mfer"]
|
ENTRYPOINT ["/mfer"]
|
||||||
|
|
||||||
|
|||||||
57
Makefile
57
Makefile
@@ -1,52 +1,46 @@
|
|||||||
export DOCKER_BUILDKIT := 1
|
export DOCKER_BUILDKIT := 1
|
||||||
export PROGRESS_NO_TRUNC := 1
|
|
||||||
GOPATH := $(shell go env GOPATH)
|
GOPATH := $(shell go env GOPATH)
|
||||||
export PATH := $(PATH):$(GOPATH)/bin
|
export PATH := $(PATH):$(GOPATH)/bin
|
||||||
PROTOC_GEN_GO := $(GOPATH)/bin/protoc-gen-go
|
PROTOC_GEN_GO := $(GOPATH)/bin/protoc-gen-go
|
||||||
SOURCEFILES := mfer/*.go mfer/*.proto internal/*/*.go cmd/*/*.go go.mod go.sum
|
|
||||||
ARCH := $(shell uname -m)
|
ARCH := $(shell uname -m)
|
||||||
GITREV_BUILD := $(shell bash $(PWD)/bin/gitrev.sh)
|
GITREV := $(shell git describe --always --dirty=-dirty)
|
||||||
APPNAME := mfer
|
APPNAME := mfer
|
||||||
VERSION := 0.1.0
|
|
||||||
export DOCKER_IMAGE_CACHE_DIR := $(HOME)/Library/Caches/Docker/$(APPNAME)-$(ARCH)
|
export DOCKER_IMAGE_CACHE_DIR := $(HOME)/Library/Caches/Docker/$(APPNAME)-$(ARCH)
|
||||||
GOLDFLAGS += -X main.Version=$(VERSION)
|
|
||||||
GOLDFLAGS += -X main.Gitrev=$(GITREV_BUILD)
|
GOLDFLAGS += -X main.Version=0.1.0
|
||||||
|
GOLDFLAGS += -X main.Gitrev=$(GITREV)
|
||||||
GOFLAGS := -ldflags "$(GOLDFLAGS)"
|
GOFLAGS := -ldflags "$(GOLDFLAGS)"
|
||||||
|
|
||||||
.PHONY: docker default run ci test fixme
|
default: test
|
||||||
|
|
||||||
default: fmt test
|
|
||||||
|
|
||||||
run: ./mfer.cmd
|
run: ./mfer.cmd
|
||||||
./$<
|
./$<
|
||||||
./$< gen --ignore-dotfiles
|
./$< gen --ignore-dotfiles
|
||||||
|
|
||||||
ci: test
|
test: fmt
|
||||||
|
|
||||||
test: $(SOURCEFILES) mfer/mf.pb.go
|
|
||||||
go test -v --timeout 3s ./...
|
go test -v --timeout 3s ./...
|
||||||
|
|
||||||
$(PROTOC_GEN_GO):
|
$(PROTOC_GEN_GO):
|
||||||
test -e $(PROTOC_GEN_GO) || go install -v google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1
|
test -e $(PROTOC_GEN_GO) || go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||||
|
|
||||||
fixme:
|
fixme:
|
||||||
@grep -nir fixme . | grep -v Makefile
|
@grep -nir fixme . | grep -v Makefile
|
||||||
|
|
||||||
devprereqs:
|
devprereqs:
|
||||||
which gofumpt || go install -v mvdan.cc/gofumpt@latest
|
which gofumpt || go install -v mvdan.cc/gofumpt@latest
|
||||||
which golangci-lint || go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@v1.50.1
|
which golangci-lint || go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||||
|
|
||||||
mfer/mf.pb.go: mfer/mf.proto
|
mfer.cmd: mfer/*.go internal/*/*.go cmd/*/*.go
|
||||||
cd mfer && go generate .
|
|
||||||
|
|
||||||
mfer.cmd: $(SOURCEFILES) mfer/mf.pb.go
|
|
||||||
protoc --version
|
protoc --version
|
||||||
|
cd mfer && go generate .
|
||||||
|
make test
|
||||||
cd cmd/mfer && go build -tags urfave_cli_no_docs -o ../../mfer.cmd $(GOFLAGS) .
|
cd cmd/mfer && go build -tags urfave_cli_no_docs -o ../../mfer.cmd $(GOFLAGS) .
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rfv mfer/*.pb.go mfer.cmd cmd/mfer/mfer *.dockerimage
|
rm -rfv mfer/*.pb.go mfer.cmd cmd/mfer/mfer
|
||||||
|
|
||||||
fmt: mfer/mf.pb.go
|
fmt: devprereqs
|
||||||
gofumpt -l -w mfer internal cmd
|
gofumpt -l -w mfer internal cmd
|
||||||
golangci-lint run --fix
|
golangci-lint run --fix
|
||||||
-prettier -w *.json
|
-prettier -w *.json
|
||||||
@@ -56,27 +50,10 @@ lint:
|
|||||||
golangci-lint run
|
golangci-lint run
|
||||||
sh -c 'test -z "$$(gofmt -l .)"'
|
sh -c 'test -z "$$(gofmt -l .)"'
|
||||||
|
|
||||||
docker: sneak-mfer.$(ARCH).tzst.dockerimage
|
docker:
|
||||||
|
bash -x bin/docker-prereqs.sh
|
||||||
sneak-mfer.$(ARCH).tzst.dockerimage: $(SOURCEFILES) vendor.tzst modcache.tzst
|
docker build .
|
||||||
docker build --progress plain --build-arg GITREV=$(GITREV_BUILD) -t sneak/mfer .
|
|
||||||
docker save sneak/mfer | pv | zstdmt -19 > $@
|
|
||||||
du -sh $@
|
|
||||||
|
|
||||||
godoc:
|
godoc:
|
||||||
open http://127.0.0.1:6060
|
open http://127.0.0.1:6060
|
||||||
godoc -http=:6060
|
godoc -http=:6060
|
||||||
|
|
||||||
vendor.tzst: go.mod go.sum
|
|
||||||
go mod tidy
|
|
||||||
go mod vendor
|
|
||||||
cd vendor && tar -c . | pv | zstdmt -19 > $(PWD)/$@.tmp
|
|
||||||
rm -rf vendor
|
|
||||||
mv $@.tmp $@
|
|
||||||
|
|
||||||
modcache.tzst: go.mod go.sum
|
|
||||||
go mod tidy
|
|
||||||
cd $(HOME)/go/pkg && chmod -R u+rw . && rm -rf mod sumdb
|
|
||||||
go mod download -x
|
|
||||||
cd $(shell go env GOMODCACHE) && tar -c . | pv | zstdmt -19 > $(PWD)/$@.tmp
|
|
||||||
mv $@.tmp $@
|
|
||||||
|
|||||||
63
README.md
63
README.md
@@ -1,48 +1,11 @@
|
|||||||
# mfer
|
# mfer
|
||||||
|
|
||||||
[mfer](https://git.eeqj.de/sneak/mfer) is a reference implementation library
|
Manifest file generator and checker.
|
||||||
and thin wrapper command-line utility written in [Go](https://golang.org)
|
|
||||||
and first published in 2022 under the [WTFPL](https://wtfpl.net) (public
|
|
||||||
domain) license. It specifies and generates `.mf` manifest files over a
|
|
||||||
directory tree of files to encapsulate metadata about them (such as
|
|
||||||
cryptographic checksums or signatures over same) to aid in archiving,
|
|
||||||
downloading, and streaming, or mirroring. The manifest files' data is
|
|
||||||
serialized with Google's [protobuf serialization
|
|
||||||
format](https://developers.google.com/protocol-buffers). The structure of
|
|
||||||
these files can be found [in the format
|
|
||||||
specification](https://git.eeqj.de/sneak/mfer/src/branch/main/mfer/mf.proto)
|
|
||||||
which is included in the [project
|
|
||||||
repository](https://git.eeqj.de/sneak/mfer).
|
|
||||||
|
|
||||||
The current version is pre-1.0 and while the repo was published in 2022,
|
|
||||||
there has not yet been any versioned release. [SemVer](https://semver.org)
|
|
||||||
will be used for releases.
|
|
||||||
|
|
||||||
This project was started by [@sneak](https://sneak.berlin) to scratch an
|
|
||||||
itch in 2022 and is currently a one-person effort, though the goal is for
|
|
||||||
this to emerge as a de-facto standard and be incorporated into other
|
|
||||||
software. A compatible javascript library is planned.
|
|
||||||
|
|
||||||
# Build Status
|
# Build Status
|
||||||
|
|
||||||
[](https://drone.datavi.be/sneak/mfer)
|
[](https://drone.datavi.be/sneak/mfer)
|
||||||
|
|
||||||
# Participation
|
|
||||||
|
|
||||||
The community is as yet nonexistent so there are no defined policies or
|
|
||||||
norms yet. Primary development happens on a privately-run Gitea instance at
|
|
||||||
[https://git.eeqj.de/sneak/mfer](https://git.eeqj.de/sneak/mfer) and issues
|
|
||||||
are [tracked there](https://git.eeqj.de/sneak/mfer/issues).
|
|
||||||
|
|
||||||
Changes must always be formatted with a standard `go fmt`, syntactically
|
|
||||||
valid, and must pass the linting defined in the repository (presently only
|
|
||||||
the `golangci-lint` defaults), which can be run with a `make lint`. The
|
|
||||||
`main` branch is protected and all changes must be made via [pull
|
|
||||||
requests](https://git.eeqj.de/sneak/mfer/pulls) and pass CI to be merged.
|
|
||||||
Any changes submitted to this project must also be
|
|
||||||
[WTFPL-licensed](https://wtfpl.net) to be considered.
|
|
||||||
|
|
||||||
|
|
||||||
# Problem Statement
|
# Problem Statement
|
||||||
|
|
||||||
Given a plain URL, there is no standard way to safely and programmatically
|
Given a plain URL, there is no standard way to safely and programmatically
|
||||||
@@ -207,24 +170,6 @@ regardless of filesystem format.
|
|||||||
Please email [`sneak@sneak.berlin`](mailto:sneak@sneak.berlin) with your
|
Please email [`sneak@sneak.berlin`](mailto:sneak@sneak.berlin) with your
|
||||||
desired username for an account on this Gitea instance.
|
desired username for an account on this Gitea instance.
|
||||||
|
|
||||||
# See Also
|
I am currently interested in hiring a contractor skilled with the Go
|
||||||
|
standard library interfaces to specify this tool in full and develop a
|
||||||
## Prior Art: Metalink
|
prototype implementation.
|
||||||
|
|
||||||
* [Metalink - Mozilla Wiki](https://wiki.mozilla.org/Metalink)
|
|
||||||
* [Metalink - Wikipedia](https://en.wikipedia.org/wiki/Metalink)
|
|
||||||
* [RFC 5854 - The Metalink Download Description Format](https://datatracker.ietf.org/doc/html/rfc5854)
|
|
||||||
* [RFC 6249 - Metalink/HTTP: Mirrors and Hashes](https://www.rfc-editor.org/rfc/rfc6249.html)
|
|
||||||
|
|
||||||
## Links
|
|
||||||
|
|
||||||
* Repo: [https://git.eeqj.de/sneak/mfer](https://git.eeqj.de/sneak/mfer)
|
|
||||||
* Issues: [https://git.eeqj.de/sneak/mfer/issues](https://git.eeqj.de/sneak/mfer/issues)
|
|
||||||
|
|
||||||
# Authors
|
|
||||||
|
|
||||||
* [@sneak <sneak@sneak.berlin>](mailto:sneak@sneak.berlin)
|
|
||||||
|
|
||||||
# License
|
|
||||||
|
|
||||||
* [WTFPL](https://wtfpl.net)
|
|
||||||
|
|||||||
122
TODO.md
122
TODO.md
@@ -1,122 +0,0 @@
|
|||||||
# TODO: mfer 1.0
|
|
||||||
|
|
||||||
## Design Questions
|
|
||||||
|
|
||||||
*sneak: please answer inline below each question. These are preserved for posterity.*
|
|
||||||
|
|
||||||
### Format Design
|
|
||||||
|
|
||||||
**1. Should `MFFileChecksum` be simplified?**
|
|
||||||
Currently it's a separate message wrapping a single `bytes multiHash` field. Since multihash already self-describes the algorithm, `repeated bytes hashes` directly on `MFFilePath` would be simpler and reduce per-file protobuf overhead. Is the extra message layer intentional (e.g. planning to add per-hash metadata like `verified_at`)?
|
|
||||||
|
|
||||||
> *answer:*
|
|
||||||
|
|
||||||
**2. Should file permissions/mode be stored?**
|
|
||||||
The format stores mtime/ctime but not Unix file permissions. For archival use (ExFAT, filesystem-independent checksums) this may not matter, but for software distribution or filesystem restoration it's a gap. Should we reserve a field now (e.g. `optional uint32 mode = 305`) even if we don't populate it yet?
|
|
||||||
|
|
||||||
> *answer:*
|
|
||||||
|
|
||||||
**3. Should `atime` be removed from the schema?**
|
|
||||||
Access time is volatile, non-deterministic, and often disabled (`noatime`). Including it means two manifests of the same directory at different times will differ, which conflicts with the determinism goal. Remove it, or document it as "never set by default"?
|
|
||||||
|
|
||||||
> *answer:*
|
|
||||||
|
|
||||||
**4. What are the path normalization rules?**
|
|
||||||
The proto has `string path` with no specification about: always forward-slash? Must be relative? No `..` components allowed? UTF-8 NFC vs NFD normalization (macOS vs Linux)? Max path length? This is a security issue (path traversal) and a cross-platform compatibility issue. What rules should the spec mandate?
|
|
||||||
|
|
||||||
> *answer:*
|
|
||||||
|
|
||||||
**5. Should we add a version byte after the magic?**
|
|
||||||
Currently `ZNAVSRFG` is followed immediately by protobuf. Adding a version byte (`ZNAVSRFG\x01`) would allow future framing changes without requiring protobuf parsing to detect the version. `MFFileOuter.Version` serves this purpose but requires successful deserialization to read. Worth the extra byte?
|
|
||||||
|
|
||||||
> *answer:*
|
|
||||||
|
|
||||||
**6. Should we add a length-prefix after the magic?**
|
|
||||||
Protobuf is not self-delimiting. If we ever want to concatenate manifests or append data after the protobuf, the current framing is insufficient. Add a varint or fixed-width length-prefix?
|
|
||||||
|
|
||||||
> *answer:*
|
|
||||||
|
|
||||||
### Signature Design
|
|
||||||
|
|
||||||
**7. What does the outer SHA-256 hash cover — compressed or uncompressed data?**
|
|
||||||
The review notes it currently hashes compressed data (good for verifying before decompression), but this should be explicitly documented. Which is the intended behavior?
|
|
||||||
|
|
||||||
> *answer:*
|
|
||||||
|
|
||||||
**8. Should `signatureString()` sign raw bytes instead of a hex-encoded string?**
|
|
||||||
Currently the canonical string is `MAGIC-UUID-MULTIHASH` with hex encoding, which adds a transformation layer. Signing the raw `sha256` bytes (or compressed `innerMessage` directly) would be simpler. Keep the string format or switch to raw bytes?
|
|
||||||
|
|
||||||
> *answer:*
|
|
||||||
|
|
||||||
**9. Should we support detached signature files (`.mf.sig`)?**
|
|
||||||
Embedded signatures are better for single-file distribution. Detached `.mf.sig` files follow the familiar `SHASUMS`/`SHASUMS.asc` pattern and are simpler for HTTP serving. Support both modes?
|
|
||||||
|
|
||||||
> *answer:*
|
|
||||||
|
|
||||||
**10. GPG vs pure-Go crypto for signatures?**
|
|
||||||
Shelling out to `gpg` is fragile (may not be installed, version-dependent output). `github.com/ProtonMail/go-crypto` provides pure-Go OpenPGP, or we could go Ed25519/signify (simpler, no key management). Which direction?
|
|
||||||
|
|
||||||
> *answer:*
|
|
||||||
|
|
||||||
### Implementation Design
|
|
||||||
|
|
||||||
**11. Should manifests be deterministic by default?**
|
|
||||||
This means: sort file entries by path, omit `createdAt` timestamp (or make it opt-in), no `atime`. Should determinism be the default, with a `--include-timestamps` flag to opt in?
|
|
||||||
|
|
||||||
> *answer:*
|
|
||||||
|
|
||||||
**12. Should we consolidate or keep both scanner/checker implementations?**
|
|
||||||
There are two parallel implementations: `mfer/scanner.go` + `mfer/checker.go` (typed with `FileSize`, `RelFilePath`) and `internal/scanner/` + `internal/checker/` (raw `int64`, `string`). The `mfer/` versions are superior. Delete the `internal/` versions?
|
|
||||||
|
|
||||||
> *answer:*
|
|
||||||
|
|
||||||
**13. Should the `manifest` type be exported?**
|
|
||||||
Currently unexported with exported constructors (`New`, `NewFromPaths`, etc.). Consumers can't declare `var m *mfer.manifest`. Export the type, or define an interface?
|
|
||||||
|
|
||||||
> *answer:*
|
|
||||||
|
|
||||||
**14. What should the Go module path be for 1.0?**
|
|
||||||
Currently mixed between `sneak.berlin/go/mfer` and `git.eeqj.de/sneak/mfer`. Which is canonical?
|
|
||||||
|
|
||||||
> *answer:*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation Plan
|
|
||||||
|
|
||||||
### Phase 1: Foundation (format correctness)
|
|
||||||
|
|
||||||
- [ ] Delete `internal/scanner/` and `internal/checker/` — consolidate on `mfer/` package versions; update CLI code
|
|
||||||
- [ ] Add deterministic file ordering — sort entries by path (lexicographic, byte-order) in `Builder.Build()`; add test asserting byte-identical output from two runs
|
|
||||||
- [ ] Add decompression size limit — `io.LimitReader` in `deserializeInner()` with `m.pbOuter.Size` as bound
|
|
||||||
- [ ] Fix `errors.Is` dead code in checker — replace with `os.IsNotExist(err)` or `errors.Is(err, fs.ErrNotExist)`
|
|
||||||
- [ ] Fix `AddFile` to verify size — check `totalRead == size` after reading, return error on mismatch
|
|
||||||
- [ ] Specify path invariants — add proto comments (UTF-8, forward-slash, relative, no `..`, no leading `/`); validate in `Builder.AddFile` and `Builder.AddFileWithHash`
|
|
||||||
|
|
||||||
### Phase 2: CLI polish
|
|
||||||
|
|
||||||
- [ ] Fix flag naming — all CLI flags use kebab-case as primary (`--include-dotfiles`, `--follow-symlinks`)
|
|
||||||
- [ ] Fix URL construction in fetch — use `BaseURL.JoinPath()` or `url.JoinPath()` instead of string concatenation
|
|
||||||
- [ ] Add progress rate-limiting to Checker — throttle to once per second, matching Scanner
|
|
||||||
- [ ] Add `--deterministic` flag (or make it default) — omit `createdAt`, sort files
|
|
||||||
|
|
||||||
### Phase 3: Robustness
|
|
||||||
|
|
||||||
- [ ] Replace GPG subprocess with pure-Go crypto — `github.com/ProtonMail/go-crypto` or Ed25519/signify
|
|
||||||
- [ ] Add timeout to any remaining subprocess calls
|
|
||||||
- [ ] Add fuzzing tests for `NewManifestFromReader`
|
|
||||||
- [ ] Add retry logic to fetch — exponential backoff for transient HTTP errors
|
|
||||||
|
|
||||||
### Phase 4: Format finalization
|
|
||||||
|
|
||||||
- [ ] Remove or deprecate `atime` from proto (pending design question answer)
|
|
||||||
- [ ] Reserve `optional uint32 mode = 305` in `MFFilePath` for future file permissions
|
|
||||||
- [ ] Add version byte after magic — `ZNAVSRFG\x01` for format version 1
|
|
||||||
- [ ] Write format specification document — separate from README: magic, outer structure, compression, inner structure, path invariants, signature scheme, canonical serialization
|
|
||||||
|
|
||||||
### Phase 5: Release prep
|
|
||||||
|
|
||||||
- [ ] Finalize Go module path
|
|
||||||
- [ ] Audit all error messages for consistency and helpfulness
|
|
||||||
- [ ] Add `--version` output matching SemVer
|
|
||||||
- [ ] Tag v1.0.0
|
|
||||||
50
bin/docker-prereqs.sh
Normal file
50
bin/docker-prereqs.sh
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euxo pipefail
|
||||||
|
GOI="golang:1.19.3-bullseye"
|
||||||
|
CII="golangci/golangci-lint:v1.50.1"
|
||||||
|
BII="sneak/mfer-build"
|
||||||
|
|
||||||
|
if [[ ! -d "$DOCKER_IMAGE_CACHE_DIR" ]]; then
|
||||||
|
mkdir -p "$DOCKER_IMAGE_CACHE_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DICD="$DOCKER_IMAGE_CACHE_DIR"
|
||||||
|
|
||||||
|
function buildImageCache() {
|
||||||
|
if [[ ! -e "$DICD/go.tzst" ]]; then
|
||||||
|
docker pull $GOI
|
||||||
|
docker save $GOI | pv | zstdmt -19 > $DICD/go.tzst.tmp && \
|
||||||
|
mv $DICD/go.tzst.tmp $DICD/go.tzst
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -e "$DICD/ci.tzst" ]]; then
|
||||||
|
docker pull $CII
|
||||||
|
docker save $CII | pv | zstdmt -19 > $DICD/ci.tzst.tmp && \
|
||||||
|
mv $DICD/ci.tzst.tmp $DICD/ci.tzst
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -e "$DICD/build.tzst" ]]; then
|
||||||
|
go mod download -x
|
||||||
|
cd buildimage
|
||||||
|
cp ../go.mod ../go.sum .
|
||||||
|
docker build -t $BII . && rm -rf go.mod go.sum && \
|
||||||
|
cd .. && \
|
||||||
|
docker save $BII | pv | zstdmt -19 > $DICD/build.tzst.tmp && \
|
||||||
|
mv $DICD/build.tzst.tmp $DICD/build.tzst
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadImageCache() {
|
||||||
|
docker image ls $CII || \
|
||||||
|
zstdmt -d --stdout $DICD/ci.tzst | pv | docker load
|
||||||
|
|
||||||
|
docker image ls $GOI || \
|
||||||
|
zstdmt -d --stdout $DICD/go.tzst | pv | docker load
|
||||||
|
|
||||||
|
docker image ls $BII || \
|
||||||
|
zstdmt -d --stdout $DICD/build.tzst | pv | docker load
|
||||||
|
}
|
||||||
|
|
||||||
|
buildImageCache
|
||||||
|
|
||||||
|
loadImageCache
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
if [[ ! -z "$DRONE_COMMIT_SHA" ]]; then
|
|
||||||
echo "${DRONE_COMMIT_SHA:0:7}"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -z "$GITREV" ]]; then
|
|
||||||
echo $GITREV
|
|
||||||
else
|
|
||||||
git describe --always --dirty=-dirty
|
|
||||||
fi
|
|
||||||
21
buildimage/Dockerfile
Normal file
21
buildimage/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
## build image:
|
||||||
|
FROM golang:1.19.3-bullseye AS builder
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
RUN apt update && apt install -y make bzip2 curl unzip
|
||||||
|
RUN mkdir -p /build
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# install newer protoc
|
||||||
|
RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v21.10/protoc-21.10-linux-aarch_64.zip && \
|
||||||
|
unzip *.zip -d /usr/local && rm -v *.zip && protoc --version
|
||||||
|
|
||||||
|
RUN go install -v google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1
|
||||||
|
|
||||||
|
RUN go env
|
||||||
|
|
||||||
|
COPY ./go.mod .
|
||||||
|
COPY ./go.sum .
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/go/pkg go mod download -x
|
||||||
|
RUN rm -rfv /var/cache/* /var/tmp/*
|
||||||
24
go.mod
24
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module git.eeqj.de/sneak/mfer
|
module git.eeqj.de/sneak/mfer
|
||||||
|
|
||||||
go 1.22
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/apex/log v1.9.0
|
github.com/apex/log v1.9.0
|
||||||
@@ -8,26 +8,6 @@ require (
|
|||||||
github.com/pterm/pterm v0.12.35
|
github.com/pterm/pterm v0.12.35
|
||||||
github.com/spf13/afero v1.8.0
|
github.com/spf13/afero v1.8.0
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
github.com/urfave/cli/v2 v2.23.6
|
github.com/urfave/cli/v2 v2.3.0
|
||||||
google.golang.org/protobuf v1.28.1
|
google.golang.org/protobuf v1.28.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/atomicgo/cursor v0.0.1 // indirect
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
|
||||||
github.com/fatih/color v1.7.0 // indirect
|
|
||||||
github.com/gookit/color v1.4.2 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.2 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.8 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
|
||||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c // indirect
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
|
||||||
golang.org/x/text v0.3.4 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
|
||||||
|
|||||||
17
go.sum
17
go.sum
@@ -61,8 +61,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
|||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -188,9 +188,11 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
|
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
|
||||||
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
|
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
|
||||||
@@ -215,12 +217,10 @@ github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj
|
|||||||
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
|
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
|
||||||
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
|
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
|
||||||
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
|
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
|
||||||
github.com/urfave/cli/v2 v2.23.6 h1:iWmtKD+prGo1nKUtLO0Wg4z9esfBM4rAV4QRLQiEmJ4=
|
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||||
github.com/urfave/cli/v2 v2.23.6/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
||||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
@@ -529,6 +529,7 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
|
|||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package bork
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrMissingMagic = errors.New("missing magic bytes in file")
|
|
||||||
ErrFileTruncated = errors.New("file/stream is truncated abnormally")
|
|
||||||
)
|
|
||||||
|
|
||||||
func Newf(format string, args ...interface{}) error {
|
|
||||||
return fmt.Errorf(format, args...)
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package bork
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBuild(t *testing.T) {
|
|
||||||
assert.NotNil(t, ErrMissingMagic)
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,5 @@ import (
|
|||||||
|
|
||||||
func (mfa *CLIApp) fetchManifestOperation(c *cli.Context) error {
|
func (mfa *CLIApp) fetchManifestOperation(c *cli.Context) error {
|
||||||
log.Debugf("fetchManifestOperation()")
|
log.Debugf("fetchManifestOperation()")
|
||||||
panic("not implemented")
|
return nil
|
||||||
return nil //nolint
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,36 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"fmt"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"git.eeqj.de/sneak/mfer/internal/log"
|
|
||||||
"git.eeqj.de/sneak/mfer/mfer"
|
"git.eeqj.de/sneak/mfer/mfer"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (mfa *CLIApp) generateManifestOperation(ctx *cli.Context) error {
|
func (mfa *CLIApp) generateManifestOperation(c *cli.Context) error {
|
||||||
log.Debug("generateManifestOperation()")
|
fmt.Println("generateManifestOperation()")
|
||||||
myArgs := ctx.Args()
|
myArgs := c.Args()
|
||||||
log.Dump(myArgs)
|
spew.Dump(myArgs)
|
||||||
|
|
||||||
|
fmt.Printf("%#v\n", c.Args().First())
|
||||||
|
if c.Args().Len() > 0 {
|
||||||
|
fmt.Printf("%#v\n", c.Args().Get(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Printf("called with arg: %s\n", c.String("input"))
|
||||||
|
|
||||||
opts := &mfer.ManifestScanOptions{
|
opts := &mfer.ManifestScanOptions{
|
||||||
IgnoreDotfiles: ctx.Bool("IgnoreDotfiles"),
|
IgnoreDotfiles: c.Bool("IgnoreDotfiles"),
|
||||||
FollowSymLinks: ctx.Bool("FollowSymLinks"),
|
FollowSymLinks: c.Bool("FollowSymLinks"),
|
||||||
}
|
}
|
||||||
paths := make([]string, ctx.Args().Len()-1)
|
// FIXME add command flags for ignoring dotfiles and following symlinks
|
||||||
for i := 0; i < ctx.Args().Len(); i++ {
|
mf, err := mfer.NewFromPath(c.String("input"), opts)
|
||||||
ap, err := filepath.Abs(ctx.Args().Get(i))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Dump(ap)
|
|
||||||
paths = append(paths, ap)
|
|
||||||
}
|
|
||||||
mf, err := mfer.NewFromPaths(opts, paths...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
mf.WithContext(ctx.Context)
|
|
||||||
|
|
||||||
log.Dump(mf)
|
spew.Dump(mf)
|
||||||
|
|
||||||
err = mf.Scan()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
|
|
||||||
err = mf.WriteTo(buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dat := buf.Bytes()
|
|
||||||
|
|
||||||
log.Dump(dat)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.eeqj.de/sneak/mfer/internal/log"
|
"github.com/pterm/pterm"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
"github.com/apex/log"
|
||||||
|
acli "github.com/apex/log/handlers/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CLIApp struct {
|
type CLIApp struct {
|
||||||
@@ -18,46 +21,36 @@ type CLIApp struct {
|
|||||||
app *cli.App
|
app *cli.App
|
||||||
}
|
}
|
||||||
|
|
||||||
const banner = ` ___ ___ ___ ___
|
|
||||||
/__/\ / /\ / /\ / /\
|
|
||||||
| |::\ / /:/_ / /:/_ / /::\
|
|
||||||
| |:|:\ / /:/ /\ / /:/ /\ / /:/\:\
|
|
||||||
__|__|:|\:\ / /:/ /:/ / /:/ /:/_ / /:/~/:/
|
|
||||||
/__/::::| \:\ /__/:/ /:/ /__/:/ /:/ /\ /__/:/ /:/___
|
|
||||||
\ \:\~~\__\/ \ \:\/:/ \ \:\/:/ /:/ \ \:\/:::::/
|
|
||||||
\ \:\ \ \::/ \ \::/ /:/ \ \::/~~~~
|
|
||||||
\ \:\ \ \:\ \ \:\/:/ \ \:\
|
|
||||||
\ \:\ \ \:\ \ \::/ \ \:\
|
|
||||||
\__\/ \__\/ \__\/ \__\/`
|
|
||||||
|
|
||||||
func (mfa *CLIApp) printBanner() {
|
func (mfa *CLIApp) printBanner() {
|
||||||
fmt.Println(banner)
|
s, _ := pterm.DefaultBigText.WithLetters(pterm.NewLettersFromString(mfa.appname)).Srender()
|
||||||
|
pterm.DefaultCenter.Println(s) // Print BigLetters with the default CenterPrinter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mfa *CLIApp) disableStyling() {
|
||||||
|
pterm.DisableColor()
|
||||||
|
pterm.DisableStyling()
|
||||||
|
pterm.Debug.Prefix.Text = ""
|
||||||
|
pterm.Info.Prefix.Text = ""
|
||||||
|
pterm.Success.Prefix.Text = ""
|
||||||
|
pterm.Warning.Prefix.Text = ""
|
||||||
|
pterm.Error.Prefix.Text = ""
|
||||||
|
pterm.Fatal.Prefix.Text = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mfa *CLIApp) VersionString() string {
|
func (mfa *CLIApp) VersionString() string {
|
||||||
return fmt.Sprintf("%s (%s)", mfa.version, mfa.gitrev)
|
return fmt.Sprintf("%s (%s)", mfa.version, mfa.gitrev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mfa *CLIApp) setVerbosity(v int) {
|
|
||||||
_, present := os.LookupEnv("MFER_DEBUG")
|
|
||||||
if present {
|
|
||||||
log.EnableDebugLogging()
|
|
||||||
} else {
|
|
||||||
log.SetLevelFromVerbosity(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mfa *CLIApp) run() {
|
func (mfa *CLIApp) run() {
|
||||||
mfa.startupTime = time.Now()
|
mfa.startupTime = time.Now()
|
||||||
|
|
||||||
if NO_COLOR {
|
if NO_COLOR {
|
||||||
// shoutout to rob pike who thinks it's juvenile
|
// shoutout to rob pike who thinks it's juvenile
|
||||||
log.DisableStyling()
|
mfa.disableStyling()
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Init()
|
log.SetHandler(acli.Default)
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
var verbosity int
|
|
||||||
|
|
||||||
mfa.app = &cli.App{
|
mfa.app = &cli.App{
|
||||||
Name: mfa.appname,
|
Name: mfa.appname,
|
||||||
@@ -69,7 +62,6 @@ func (mfa *CLIApp) run() {
|
|||||||
Name: "verbose",
|
Name: "verbose",
|
||||||
Usage: "Verbosity level",
|
Usage: "Verbosity level",
|
||||||
Aliases: []string{"v"},
|
Aliases: []string{"v"},
|
||||||
Count: &verbosity,
|
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "quiet",
|
Name: "quiet",
|
||||||
@@ -77,6 +69,12 @@ func (mfa *CLIApp) run() {
|
|||||||
Aliases: []string{"q"},
|
Aliases: []string{"q"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
if c.Bool("verbose") {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
Commands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
{
|
{
|
||||||
Name: "generate",
|
Name: "generate",
|
||||||
@@ -86,7 +84,6 @@ func (mfa *CLIApp) run() {
|
|||||||
if !c.Bool("quiet") {
|
if !c.Bool("quiet") {
|
||||||
mfa.printBanner()
|
mfa.printBanner()
|
||||||
}
|
}
|
||||||
mfa.setVerbosity(verbosity)
|
|
||||||
return mfa.generateManifestOperation(c)
|
return mfa.generateManifestOperation(c)
|
||||||
},
|
},
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
@@ -100,6 +97,13 @@ func (mfa *CLIApp) run() {
|
|||||||
Aliases: []string{"ignore-dotfiles"},
|
Aliases: []string{"ignore-dotfiles"},
|
||||||
Usage: "Ignore any dot (hidden) files encountered",
|
Usage: "Ignore any dot (hidden) files encountered",
|
||||||
},
|
},
|
||||||
|
// FIXME this should be a positional arg
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "input",
|
||||||
|
Value: ".",
|
||||||
|
Aliases: []string{"i"},
|
||||||
|
Usage: "Specify input directory.",
|
||||||
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "output",
|
Name: "output",
|
||||||
Value: "./index.mf",
|
Value: "./index.mf",
|
||||||
@@ -115,7 +119,6 @@ func (mfa *CLIApp) run() {
|
|||||||
if !c.Bool("quiet") {
|
if !c.Bool("quiet") {
|
||||||
mfa.printBanner()
|
mfa.printBanner()
|
||||||
}
|
}
|
||||||
mfa.setVerbosity(verbosity)
|
|
||||||
return mfa.checkManifestOperation(c)
|
return mfa.checkManifestOperation(c)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -134,7 +137,6 @@ func (mfa *CLIApp) run() {
|
|||||||
if !c.Bool("quiet") {
|
if !c.Bool("quiet") {
|
||||||
mfa.printBanner()
|
mfa.printBanner()
|
||||||
}
|
}
|
||||||
mfa.setVerbosity(verbosity)
|
|
||||||
return mfa.fetchManifestOperation(c)
|
return mfa.fetchManifestOperation(c)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/apex/log"
|
|
||||||
acli "github.com/apex/log/handlers/cli"
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/pterm/pterm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Level = log.Level
|
|
||||||
|
|
||||||
func DisableStyling() {
|
|
||||||
pterm.DisableColor()
|
|
||||||
pterm.DisableStyling()
|
|
||||||
pterm.Debug.Prefix.Text = ""
|
|
||||||
pterm.Info.Prefix.Text = ""
|
|
||||||
pterm.Success.Prefix.Text = ""
|
|
||||||
pterm.Warning.Prefix.Text = ""
|
|
||||||
pterm.Error.Prefix.Text = ""
|
|
||||||
pterm.Fatal.Prefix.Text = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func Init() {
|
|
||||||
log.SetHandler(acli.Default)
|
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Debugf(format string, args ...interface{}) {
|
|
||||||
DebugReal(fmt.Sprintf(format, args...), 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Debug(arg string) {
|
|
||||||
DebugReal(arg, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DebugReal(arg string, cs int) {
|
|
||||||
_, callerFile, callerLine, ok := runtime.Caller(cs)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tag := fmt.Sprintf("%s:%d: ", callerFile, callerLine)
|
|
||||||
log.Debug(tag + arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Dump(args ...interface{}) {
|
|
||||||
DebugReal(spew.Sdump(args...), 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func EnableDebugLogging() {
|
|
||||||
SetLevel(log.DebugLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func VerbosityStepsToLogLevel(l int) log.Level {
|
|
||||||
switch l {
|
|
||||||
case 1:
|
|
||||||
return log.WarnLevel
|
|
||||||
case 2:
|
|
||||||
return log.InfoLevel
|
|
||||||
case 3:
|
|
||||||
return log.DebugLevel
|
|
||||||
}
|
|
||||||
return log.ErrorLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetLevelFromVerbosity(l int) {
|
|
||||||
SetLevel(VerbosityStepsToLogLevel(l))
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetLevel(arg log.Level) {
|
|
||||||
log.SetLevel(arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetLogger() *log.Logger {
|
|
||||||
if logger, ok := log.Log.(*log.Logger); ok {
|
|
||||||
return logger
|
|
||||||
}
|
|
||||||
panic("unable to get logger")
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetLevel() log.Level {
|
|
||||||
return GetLogger().Level
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithError(e error) *log.Entry {
|
|
||||||
return GetLogger().WithError(e)
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBuild(t *testing.T) {
|
|
||||||
Init()
|
|
||||||
assert.True(t, true)
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
package mfer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"git.eeqj.de/sneak/mfer/internal/bork"
|
|
||||||
"git.eeqj.de/sneak/mfer/internal/log"
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m *manifest) validateProtoOuter() error {
|
|
||||||
if m.pbOuter.Version != MFFileOuter_VERSION_ONE {
|
|
||||||
return errors.New("unknown version")
|
|
||||||
}
|
|
||||||
if m.pbOuter.CompressionType != MFFileOuter_COMPRESSION_GZIP {
|
|
||||||
return errors.New("unknown compression type")
|
|
||||||
}
|
|
||||||
|
|
||||||
bb := bytes.NewBuffer(m.pbOuter.InnerMessage)
|
|
||||||
|
|
||||||
gzr, err := gzip.NewReader(bb)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dat, err := io.ReadAll(gzr)
|
|
||||||
defer gzr.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
isize := len(dat)
|
|
||||||
if int64(isize) != m.pbOuter.Size {
|
|
||||||
log.Debugf("truncated data, got %d expected %d", isize, m.pbOuter.Size)
|
|
||||||
return bork.ErrFileTruncated
|
|
||||||
}
|
|
||||||
log.Debugf("inner data size is %d", isize)
|
|
||||||
log.Dump(dat)
|
|
||||||
log.Dump(m.pbOuter.Sha256)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateMagic(dat []byte) bool {
|
|
||||||
ml := len([]byte(MAGIC))
|
|
||||||
if len(dat) < ml {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
got := dat[0:ml]
|
|
||||||
expected := []byte(MAGIC)
|
|
||||||
return bytes.Equal(got, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFromProto(input io.Reader) (*manifest, error) {
|
|
||||||
m := New()
|
|
||||||
dat, err := io.ReadAll(input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !validateMagic(dat) {
|
|
||||||
return nil, errors.New("invalid file format")
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove magic bytes prefix:
|
|
||||||
ml := len([]byte(MAGIC))
|
|
||||||
bb := bytes.NewBuffer(dat[ml:])
|
|
||||||
dat = bb.Bytes()
|
|
||||||
|
|
||||||
log.Dump(dat)
|
|
||||||
|
|
||||||
// deserialize:
|
|
||||||
m.pbOuter = new(MFFileOuter)
|
|
||||||
err = proto.Unmarshal(dat, m.pbOuter)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ve := m.validateProtoOuter()
|
|
||||||
if ve != nil {
|
|
||||||
return nil, ve
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME TODO deserialize inner
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package mfer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.eeqj.de/sneak/mfer/internal/log"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAPIExample(t *testing.T) {
|
|
||||||
// read from filesystem
|
|
||||||
m, err := NewFromFS(&ManifestScanOptions{
|
|
||||||
IgnoreDotfiles: true,
|
|
||||||
}, big)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, m)
|
|
||||||
|
|
||||||
// scan for files
|
|
||||||
m.Scan()
|
|
||||||
|
|
||||||
// serialize
|
|
||||||
var buf bytes.Buffer
|
|
||||||
m.WriteTo(&buf)
|
|
||||||
|
|
||||||
// show serialized
|
|
||||||
log.Dump(buf.Bytes())
|
|
||||||
|
|
||||||
// do it again
|
|
||||||
var buf2 bytes.Buffer
|
|
||||||
m.WriteTo(&buf2)
|
|
||||||
|
|
||||||
// should be same!
|
|
||||||
assert.True(t, bytes.Equal(buf.Bytes(), buf2.Bytes()))
|
|
||||||
|
|
||||||
// deserialize
|
|
||||||
m2, err := NewFromProto(&buf)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, m2)
|
|
||||||
|
|
||||||
log.Dump(m2)
|
|
||||||
}
|
|
||||||
156
mfer/manifest.go
156
mfer/manifest.go
@@ -1,42 +1,36 @@
|
|||||||
package mfer
|
package mfer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.eeqj.de/sneak/mfer/internal/log"
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
type manifestFile struct {
|
type ManifestFile struct {
|
||||||
path string
|
Path string
|
||||||
info fs.FileInfo
|
FileInfo fs.FileInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *manifestFile) String() string {
|
func (m *ManifestFile) String() string {
|
||||||
return fmt.Sprintf("<File \"%s\">", m.path)
|
return fmt.Sprintf("<File \"%s\">", m.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
type manifest struct {
|
type Manifest struct {
|
||||||
sourceFS []afero.Fs
|
SourceFS afero.Fs
|
||||||
files []*manifestFile
|
SourceFSRoot string
|
||||||
scanOptions *ManifestScanOptions
|
Files []*ManifestFile
|
||||||
totalFileSize int64
|
ScanOptions *ManifestScanOptions
|
||||||
pbInner *MFFile
|
TotalFileSize int64
|
||||||
pbOuter *MFFileOuter
|
PBInner *MFFile
|
||||||
output *bytes.Buffer
|
PBOuter *MFFileOuter
|
||||||
ctx context.Context
|
|
||||||
errors []*error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *manifest) String() string {
|
func (m *Manifest) String() string {
|
||||||
return fmt.Sprintf("<Manifest count=%d totalSize=%d>", len(m.files), m.totalFileSize)
|
return fmt.Sprintf("<Manifest count=%d totalSize=%d>", len(m.Files), m.TotalFileSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ManifestScanOptions struct {
|
type ManifestScanOptions struct {
|
||||||
@@ -44,73 +38,38 @@ type ManifestScanOptions struct {
|
|||||||
FollowSymLinks bool
|
FollowSymLinks bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *manifest) HasError() bool {
|
func NewFromPath(inputPath string, options *ManifestScanOptions) (*Manifest, error) {
|
||||||
return len(m.errors) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *manifest) AddError(e error) *manifest {
|
|
||||||
m.errors = append(m.errors, &e)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *manifest) WithContext(c context.Context) *manifest {
|
|
||||||
m.ctx = c
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *manifest) addInputPath(inputPath string) error {
|
|
||||||
abs, err := filepath.Abs(inputPath)
|
abs, err := filepath.Abs(inputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
// FIXME check to make sure inputPath/abs exists maybe
|
afs := afero.NewBasePathFs(afero.NewOsFs(), abs)
|
||||||
afs := afero.NewReadOnlyFs(afero.NewBasePathFs(afero.NewOsFs(), abs))
|
m, err := NewFromFS(afs, options)
|
||||||
return m.addInputFS(afs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *manifest) addInputFS(f afero.Fs) error {
|
|
||||||
if m.sourceFS == nil {
|
|
||||||
m.sourceFS = make([]afero.Fs, 0)
|
|
||||||
}
|
|
||||||
m.sourceFS = append(m.sourceFS, f)
|
|
||||||
// FIXME do some sort of check on f here?
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() *manifest {
|
|
||||||
m := &manifest{}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFromPaths(options *ManifestScanOptions, inputPaths ...string) (*manifest, error) {
|
|
||||||
log.Dump(inputPaths)
|
|
||||||
m := New()
|
|
||||||
m.scanOptions = options
|
|
||||||
for _, p := range inputPaths {
|
|
||||||
err := m.addInputPath(p)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
m.SourceFSRoot = abs
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFromFS(options *ManifestScanOptions, fs afero.Fs) (*manifest, error) {
|
func NewFromFS(fs afero.Fs, options *ManifestScanOptions) (*Manifest, error) {
|
||||||
m := New()
|
m := &Manifest{
|
||||||
m.scanOptions = options
|
SourceFS: fs,
|
||||||
err := m.addInputFS(fs)
|
ScanOptions: options,
|
||||||
|
}
|
||||||
|
err := m.Scan()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *manifest) GetFileCount() int64 {
|
func (m *Manifest) GetFileCount() int64 {
|
||||||
return int64(len(m.files))
|
return int64(len(m.Files))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *manifest) GetTotalFileSize() int64 {
|
func (m *Manifest) GetTotalFileSize() int64 {
|
||||||
return m.totalFileSize
|
return m.TotalFileSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func pathIsHidden(p string) bool {
|
func pathIsHidden(p string) bool {
|
||||||
@@ -130,44 +89,39 @@ func pathIsHidden(p string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *manifest) addFile(p string, fi fs.FileInfo, sfsIndex int) error {
|
/*
|
||||||
if m.scanOptions.IgnoreDotfiles && pathIsHidden(p) {
|
func timeToTimestamp(t time.Time) *Timestamp {
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (m *Manifest) Scan() error {
|
||||||
|
// FIXME scan and whatever function does the hashing should take ctx
|
||||||
|
oe := afero.Walk(m.SourceFS, "/", func(p string, info fs.FileInfo, err error) error {
|
||||||
|
if m.ScanOptions.IgnoreDotfiles && pathIsHidden(p) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if fi != nil && fi.IsDir() {
|
|
||||||
|
if info != nil && info.IsDir() {
|
||||||
// manifests contain only files, directories are implied.
|
// manifests contain only files, directories are implied.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// FIXME test if 'fi' is already result of stat
|
|
||||||
fileinfo, staterr := m.sourceFS[sfsIndex].Stat(p)
|
|
||||||
if staterr != nil {
|
|
||||||
return staterr
|
|
||||||
}
|
|
||||||
cleanPath := p
|
|
||||||
if cleanPath[0:1] == "/" {
|
|
||||||
cleanPath = cleanPath[1:]
|
|
||||||
}
|
|
||||||
nf := &manifestFile{
|
|
||||||
path: cleanPath,
|
|
||||||
info: fileinfo,
|
|
||||||
}
|
|
||||||
m.files = append(m.files, nf)
|
|
||||||
m.totalFileSize = m.totalFileSize + fi.Size()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *manifest) Scan() error {
|
fileinfo, staterr := m.SourceFS.Stat(p)
|
||||||
// FIXME scan and whatever function does the hashing should take ctx
|
if staterr != nil {
|
||||||
for idx, sfs := range m.sourceFS {
|
panic(staterr)
|
||||||
if sfs == nil {
|
|
||||||
return errors.New("invalid source fs")
|
|
||||||
}
|
}
|
||||||
e := afero.Walk(sfs, "/", func(p string, info fs.FileInfo, err error) error {
|
|
||||||
return m.addFile(p, info, idx)
|
nf := &ManifestFile{
|
||||||
|
Path: p,
|
||||||
|
FileInfo: fileinfo,
|
||||||
|
}
|
||||||
|
m.Files = append(m.Files, nf)
|
||||||
|
m.TotalFileSize = m.TotalFileSize + info.Size()
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
if e != nil {
|
if oe != nil {
|
||||||
return e
|
return oe
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
option go_package = "git.eeqj.de/sneak/mfer/mfer";
|
option go_package = "git.eeqj.de/sneak/mfer";
|
||||||
|
|
||||||
message Timestamp {
|
message Timestamp {
|
||||||
int64 seconds = 1;
|
int64 seconds = 1;
|
||||||
@@ -9,26 +9,18 @@ message Timestamp {
|
|||||||
|
|
||||||
message MFFileOuter {
|
message MFFileOuter {
|
||||||
enum Version {
|
enum Version {
|
||||||
VERSION_NONE = 0;
|
NONE = 0;
|
||||||
VERSION_ONE = 1; // only one for now
|
ONE = 1; // only one for now
|
||||||
}
|
}
|
||||||
|
|
||||||
// required mffile root attributes 1xx
|
// required mffile root attributes 1xx
|
||||||
Version version = 101;
|
Version version = 101;
|
||||||
|
bytes innerMessage = 102;
|
||||||
enum CompressionType {
|
|
||||||
COMPRESSION_NONE = 0;
|
|
||||||
COMPRESSION_GZIP = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
CompressionType compressionType = 102;
|
|
||||||
|
|
||||||
// these are used solely to detect corruption/truncation
|
// these are used solely to detect corruption/truncation
|
||||||
// and not for cryptographic integrity.
|
// and not for cryptographic integrity.
|
||||||
int64 size = 103;
|
int64 size = 103;
|
||||||
bytes sha256 = 104;
|
bytes sha256 = 104;
|
||||||
|
|
||||||
bytes innerMessage = 199;
|
|
||||||
// 2xx for optional manifest root attributes
|
// 2xx for optional manifest root attributes
|
||||||
// think we might use gosignify instead of gpg:
|
// think we might use gosignify instead of gpg:
|
||||||
// github.com/frankbraun/gosignify
|
// github.com/frankbraun/gosignify
|
||||||
@@ -64,8 +56,8 @@ message MFFileChecksum {
|
|||||||
|
|
||||||
message MFFile {
|
message MFFile {
|
||||||
enum Version {
|
enum Version {
|
||||||
VERSION_NONE = 0;
|
NONE = 0;
|
||||||
VERSION_ONE = 1; // only one for now
|
ONE = 1; // only one for now
|
||||||
}
|
}
|
||||||
Version version = 100;
|
Version version = 100;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package mfer
|
package mfer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.eeqj.de/sneak/mfer/internal/log"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@@ -16,25 +14,18 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
af *afero.Afero = &afero.Afero{Fs: afero.NewMemMapFs()}
|
mf afero.Fs = afero.NewMemMapFs()
|
||||||
big *afero.Afero = &afero.Afero{Fs: afero.NewMemMapFs()}
|
af *afero.Afero = &afero.Afero{Fs: mf}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
log.EnableDebugLogging()
|
|
||||||
|
|
||||||
// create test files and directories
|
// create test files and directories
|
||||||
af.MkdirAll("/a/b/c", 0o755)
|
af.MkdirAll("/a/b/c", 0o755)
|
||||||
af.MkdirAll("/.hidden", 0o755)
|
af.MkdirAll("/.hidden", 0o755)
|
||||||
af.WriteFile("/a/b/c/hello.txt", []byte("hello world\n\n\n\n"), 0o755)
|
afero.WriteFile(af, "/a/b/c/hello.txt", []byte("hello world\n\n\n\n"), 0o755)
|
||||||
af.WriteFile("/a/b/c/hello2.txt", []byte("hello world\n\n\n\n"), 0o755)
|
afero.WriteFile(af, "/a/b/c/hello2.txt", []byte("hello world\n\n\n\n"), 0o755)
|
||||||
af.WriteFile("/.hidden/hello.txt", []byte("hello world\n"), 0o755)
|
afero.WriteFile(af, "/.hidden/hello.txt", []byte("hello world\n"), 0o755)
|
||||||
af.WriteFile("/.hidden/hello2.txt", []byte("hello world\n"), 0o755)
|
afero.WriteFile(af, "/.hidden/hello2.txt", []byte("hello world\n"), 0o755)
|
||||||
|
|
||||||
big.MkdirAll("/home/user/Library", 0o755)
|
|
||||||
for i := range [25]int{} {
|
|
||||||
big.WriteFile(fmt.Sprintf("/home/user/Library/hello%d.txt", i), []byte("hello world\n"), 0o755)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathHiddenFunc(t *testing.T) {
|
func TestPathHiddenFunc(t *testing.T) {
|
||||||
@@ -46,29 +37,22 @@ func TestPathHiddenFunc(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestManifestGenerationOne(t *testing.T) {
|
func TestManifestGenerationOne(t *testing.T) {
|
||||||
m, err := NewFromFS(&ManifestScanOptions{
|
m, err := NewFromFS(mf, &ManifestScanOptions{
|
||||||
IgnoreDotfiles: true,
|
IgnoreDotfiles: true,
|
||||||
}, af)
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.NotNil(t, m)
|
assert.NotNil(t, m)
|
||||||
m.Scan()
|
|
||||||
assert.Equal(t, int64(2), m.GetFileCount())
|
assert.Equal(t, int64(2), m.GetFileCount())
|
||||||
assert.Equal(t, int64(30), m.GetTotalFileSize())
|
assert.Equal(t, int64(30), m.GetTotalFileSize())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifestGenerationTwo(t *testing.T) {
|
func TestManifestGenerationTwo(t *testing.T) {
|
||||||
m, err := NewFromFS(&ManifestScanOptions{
|
m, err := NewFromFS(mf, &ManifestScanOptions{
|
||||||
IgnoreDotfiles: false,
|
IgnoreDotfiles: false,
|
||||||
}, af)
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.NotNil(t, m)
|
assert.NotNil(t, m)
|
||||||
m.Scan()
|
spew.Dump(m)
|
||||||
assert.Equal(t, int64(4), m.GetFileCount())
|
assert.Equal(t, int64(4), m.GetFileCount())
|
||||||
assert.Equal(t, int64(54), m.GetTotalFileSize())
|
assert.Equal(t, int64(54), m.GetTotalFileSize())
|
||||||
err = m.generate()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
var buf bytes.Buffer
|
|
||||||
err = m.WriteTo(&buf)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
log.Dump(buf.Bytes())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *manifest) WriteToFile(path string) error {
|
func (m *Manifest) WriteToFile(path string) error {
|
||||||
// FIXME refuse to overwrite without -f if file exists
|
// FIXME refuse to overwrite without -f if file exists
|
||||||
|
|
||||||
f, err := os.Create(path)
|
f, err := os.Create(path)
|
||||||
@@ -14,20 +14,11 @@ func (m *manifest) WriteToFile(path string) error {
|
|||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
return m.WriteTo(f)
|
return m.Write(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *manifest) WriteTo(output io.Writer) error {
|
func (m *Manifest) Write(output io.Writer) error {
|
||||||
if m.pbOuter == nil {
|
// FIXME implement
|
||||||
err := m.generate()
|
panic("nope")
|
||||||
if err != nil {
|
return nil // nolint:all
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := output.Write(m.output.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +1,12 @@
|
|||||||
package mfer
|
package mfer
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"crypto/sha256"
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate protoc --go_out=. --go_opt=paths=source_relative mf.proto
|
//go:generate protoc --go_out=. --go_opt=paths=source_relative mf.proto
|
||||||
|
|
||||||
// rot13("MANIFEST")
|
func (m *Manifest) Generate() error {
|
||||||
const MAGIC string = "ZNAVSRFG"
|
m.PBInner = &MFFile{
|
||||||
|
Version: MFFile_ONE,
|
||||||
func newTimestampFromTime(t time.Time) *Timestamp {
|
// CreatedAt: time.Now(),
|
||||||
out := &Timestamp{
|
|
||||||
Seconds: t.Unix(),
|
|
||||||
Nanos: int32(t.UnixNano() - (t.Unix() * 1000000000)),
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *manifest) generate() error {
|
|
||||||
if m.pbInner == nil {
|
|
||||||
e := m.generateInner()
|
|
||||||
if e != nil {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if m.pbOuter == nil {
|
|
||||||
e := m.generateOuter()
|
|
||||||
if e != nil {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dat, err := proto.MarshalOptions{Deterministic: true}.Marshal(m.pbOuter)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.output = bytes.NewBuffer([]byte(MAGIC))
|
|
||||||
_, err = m.output.Write(dat)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *manifest) generateOuter() error {
|
|
||||||
if m.pbInner == nil {
|
|
||||||
return errors.New("internal error")
|
|
||||||
}
|
|
||||||
innerData, err := proto.MarshalOptions{Deterministic: true}.Marshal(m.pbInner)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
h := sha256.New()
|
|
||||||
h.Write(innerData)
|
|
||||||
|
|
||||||
idc := new(bytes.Buffer)
|
|
||||||
gzw, err := gzip.NewWriterLevel(idc, gzip.BestCompression)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = gzw.Write(innerData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
gzw.Close()
|
|
||||||
|
|
||||||
o := &MFFileOuter{
|
|
||||||
InnerMessage: idc.Bytes(),
|
|
||||||
Size: int64(len(innerData)),
|
|
||||||
Sha256: h.Sum(nil),
|
|
||||||
Version: MFFileOuter_VERSION_ONE,
|
|
||||||
CompressionType: MFFileOuter_COMPRESSION_GZIP,
|
|
||||||
}
|
|
||||||
m.pbOuter = o
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *manifest) generateInner() error {
|
|
||||||
m.pbInner = &MFFile{
|
|
||||||
Version: MFFile_VERSION_ONE,
|
|
||||||
CreatedAt: newTimestampFromTime(time.Now()),
|
|
||||||
Files: []*MFFilePath{},
|
Files: []*MFFilePath{},
|
||||||
}
|
}
|
||||||
for _, f := range m.files {
|
|
||||||
nf := &MFFilePath{
|
|
||||||
Path: f.path,
|
|
||||||
// FIXME add more stuff
|
|
||||||
}
|
|
||||||
m.pbInner.Files = append(m.pbInner.Files, nf)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
modcache.tzst
BIN
modcache.tzst
Binary file not shown.
BIN
vendor.tzst
BIN
vendor.tzst
Binary file not shown.
Reference in New Issue
Block a user