Files
mfer/internal/cli/export.go
T
clawbot 5336a2b4ff
check / check (push) Waiting to run
Sign and verify manifests in Go with OpenPGP instead of running gpg (closes #181)
mfer ran the gpg binary to sign, export keys and verify, so signing and
loading signed manifests failed wherever gpg is missing. It now uses
github.com/ProtonMail/go-crypto/openpgp. --sign-key and MFER_SIGN_KEY
name a file holding one OpenPGP secret key; a protected key's passphrase
comes from MFER_SIGN_KEY_PASSPHRASE or a prompt on the terminal.
Verification keeps the rules of the --require-signature fix: one primary
key in the embedded block, counted from its packets so that keys the
library skips count too, exactly one signature, made by that key or one
of its subkeys, and signer equal to its fingerprint. Tests make their
keys in process.

Model: opus-5-5
2026-10-08 01:02:03 +00:00

83 lines
1.7 KiB
Go

package cli
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"time"
"github.com/urfave/cli/v3"
"sneak.berlin/go/mfer/mfer"
)
// ExportEntry represents a single file entry in the exported JSON output.
type ExportEntry struct {
Path string `json:"path"`
Size int64 `json:"size"`
Hashes []string `json:"hashes"`
Mtime *string `json:"mtime,omitempty"`
Ctime *string `json:"ctime,omitempty"`
Mode string `json:"mode"` // octal, "0000" when none was recorded
}
func (mfa *CLIApp) exportManifestOperation(
ctx context.Context, cmd *cli.Command,
) error {
pathOrURL, err := mfa.resolveManifestArg(cmd)
if err != nil {
return err
}
rc, err := mfa.openManifestReader(ctx, pathOrURL)
if err != nil {
return err
}
defer func() { _ = rc.Close() }()
manifest, err := mfer.NewManifestFromReader(rc)
if err != nil {
return fmt.Errorf("parse manifest: %w", err)
}
files := manifest.Files()
entries := make([]ExportEntry, 0, len(files))
for _, f := range files {
entry := ExportEntry{
Path: f.GetPath(),
Size: f.GetSize(),
Hashes: make([]string, 0, len(f.GetHashes())),
Mode: fmt.Sprintf("%04o", f.GetMode()),
}
for _, h := range f.GetHashes() {
entry.Hashes = append(entry.Hashes, hex.EncodeToString(h.GetMultiHash()))
}
if mtime, ok := entryMtime(f); ok {
t := mtime.UTC().Format(time.RFC3339Nano)
entry.Mtime = &t
}
if f.GetCtime() != nil {
t := time.Unix(f.GetCtime().GetSeconds(), int64(f.GetCtime().GetNanos())).
UTC().Format(time.RFC3339Nano)
entry.Ctime = &t
}
entries = append(entries, entry)
}
enc := json.NewEncoder(mfa.Stdout)
enc.SetIndent("", " ")
err = enc.Encode(entries)
if err != nil {
return fmt.Errorf("encode JSON: %w", err)
}
return nil
}