internal/rosa: mirror service via external cache
Test / Create distribution (push) Successful in 51s
Test / Sandbox (push) Successful in 2m43s
Test / ShareFS (push) Successful in 3m58s
Test / Hakurei (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 5m29s
Test / Hakurei (race detector) (push) Successful in 6m43s
Test / Flake checks (push) Successful in 1m18s
Test / Create distribution (push) Successful in 51s
Test / Sandbox (push) Successful in 2m43s
Test / ShareFS (push) Successful in 3m58s
Test / Hakurei (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 5m29s
Test / Hakurei (race detector) (push) Successful in 6m43s
Test / Flake checks (push) Successful in 1m18s
This provides an authenticated implementation of the external cache. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
+18
-1
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
|
"hakurei.app/internal/rosa"
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ type cache struct {
|
|||||||
// Loaded artifact of [rosa.QEMU].
|
// Loaded artifact of [rosa.QEMU].
|
||||||
qemu pkg.Artifact
|
qemu pkg.Artifact
|
||||||
|
|
||||||
base string
|
base, mirror string
|
||||||
}
|
}
|
||||||
|
|
||||||
// open opens the underlying [pkg.Cache].
|
// open opens the underlying [pkg.Cache].
|
||||||
@@ -86,6 +88,21 @@ func (cache *cache) open() (err error) {
|
|||||||
}
|
}
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
|
|
||||||
|
if cache.mirror != "" {
|
||||||
|
var pub []byte
|
||||||
|
pub, err = os.ReadFile(base.Append("ed25519.pub").String())
|
||||||
|
if err != nil {
|
||||||
|
cache.c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var r rosa.Remote
|
||||||
|
if r, err = rosa.NewRemote(cache.mirror, pub, http.DefaultClient); err != nil {
|
||||||
|
cache.c.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cache.c.SetExternal(r)
|
||||||
|
}
|
||||||
|
|
||||||
if cache.qemu != nil {
|
if cache.qemu != nil {
|
||||||
var pathname *check.Absolute
|
var pathname *check.Absolute
|
||||||
pathname, _, err = cache.c.Cure(cache.qemu)
|
pathname, _, err = cache.c.Cure(cache.qemu)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -47,6 +48,19 @@ import (
|
|||||||
"hakurei.app/cmd/mbf/internal/pkgserver/ui"
|
"hakurei.app/cmd/mbf/internal/pkgserver/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// writeFileExcl is like [os.WriteFile], but sets [os.O_EXCL] instead.
|
||||||
|
func writeFileExcl(name string, data []byte, perm os.FileMode) error {
|
||||||
|
f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = f.Write(data)
|
||||||
|
if err1 := f.Close(); err1 != nil && err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
container.TryArgv0(nil)
|
container.TryArgv0(nil)
|
||||||
|
|
||||||
@@ -186,6 +200,10 @@ func main() {
|
|||||||
&cm.base,
|
&cm.base,
|
||||||
"d", command.StringFlag("$MBF_CACHE_DIR"),
|
"d", command.StringFlag("$MBF_CACHE_DIR"),
|
||||||
"Directory to store cured artifacts",
|
"Directory to store cured artifacts",
|
||||||
|
).Flag(
|
||||||
|
&cm.mirror,
|
||||||
|
"r", command.StringFlag("$MBF_REMOTE"),
|
||||||
|
"URL of mirror service",
|
||||||
).Flag(
|
).Flag(
|
||||||
&cm.idle,
|
&cm.idle,
|
||||||
"sched-idle", command.BoolFlag(false),
|
"sched-idle", command.BoolFlag(false),
|
||||||
@@ -462,6 +480,70 @@ func main() {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
c.NewCommand(
|
||||||
|
"keygen",
|
||||||
|
"Create keypair for local cache",
|
||||||
|
func([]string) error {
|
||||||
|
pub, priv, err := ed25519.GenerateKey(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Join(writeFileExcl(filepath.Join(
|
||||||
|
cm.base,
|
||||||
|
"ed25519.pub",
|
||||||
|
), pub, 0444), writeFileExcl(filepath.Join(
|
||||||
|
cm.base,
|
||||||
|
"ed25519",
|
||||||
|
), priv, 0400))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
c.NewCommand(
|
||||||
|
"serve",
|
||||||
|
"Export local cache as mirror",
|
||||||
|
func(args []string) error {
|
||||||
|
const shutdownTimeout = 15 * time.Second
|
||||||
|
|
||||||
|
if len(args) != 1 {
|
||||||
|
return errors.New("serve requires 1 argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
var key ed25519.PrivateKey
|
||||||
|
if p, err := os.ReadFile(filepath.Join(cm.base, "ed25519")); err != nil {
|
||||||
|
return err
|
||||||
|
} else if len(p) != ed25519.PrivateKeySize {
|
||||||
|
return errors.New("invalid private key")
|
||||||
|
} else {
|
||||||
|
key = p
|
||||||
|
}
|
||||||
|
|
||||||
|
var h http.Handler
|
||||||
|
if base, err := os.OpenRoot(cm.base); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
h = rosa.NewMirror(msg, base, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := http.Server{Addr: args[0], Handler: h}
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
cc, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := server.Shutdown(cc); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
msg.Verbosef("listening on %q", args[0])
|
||||||
|
err := server.ListenAndServe()
|
||||||
|
if errors.Is(err, http.ErrServerClosed) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
flagGentoo string
|
flagGentoo string
|
||||||
|
|||||||
@@ -0,0 +1,361 @@
|
|||||||
|
package rosa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"context"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/sha512"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unique"
|
||||||
|
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
"hakurei.app/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Remote is an authenticated cache mirror.
|
||||||
|
type Remote struct {
|
||||||
|
// Mirror URL.
|
||||||
|
url *url.URL
|
||||||
|
// Trusted public key.
|
||||||
|
pub ed25519.PublicKey
|
||||||
|
// For requests to the mirror.
|
||||||
|
c *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRemote returns a populated [Remote]
|
||||||
|
func NewRemote(base string, pub ed25519.PublicKey, c *http.Client) (Remote, error) {
|
||||||
|
u, err := url.Parse(base)
|
||||||
|
return Remote{u, pub, c}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get makes a [http.MethodGet] request and returns the response, or nil if
|
||||||
|
// the response StatusCode is [http.StatusNotFound].
|
||||||
|
func (r Remote) get(ctx context.Context, elem ...string) (*http.Response, error) {
|
||||||
|
if r.url == nil || len(r.pub) != ed25519.PublicKeySize || r.c == nil {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(
|
||||||
|
ctx,
|
||||||
|
http.MethodGet,
|
||||||
|
r.url.JoinPath(elem...).String(),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("User-Agent", "Rosa/1.1")
|
||||||
|
|
||||||
|
var resp *http.Response
|
||||||
|
if resp, err = r.c.Do(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusOK:
|
||||||
|
return resp, nil
|
||||||
|
|
||||||
|
case http.StatusNotFound:
|
||||||
|
return nil, resp.Body.Close()
|
||||||
|
|
||||||
|
default:
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
return nil, pkg.ResponseStatusError(resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// dirArtifact holds signed artifact outcome checksums.
|
||||||
|
dirArtifact = "artifact"
|
||||||
|
// dirOutcome holds outcome archives by their checksum.
|
||||||
|
dirOutcome = "outcome"
|
||||||
|
// dirStatus holds signed status files.
|
||||||
|
dirStatus = "status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An OutcomeBadSizeError describes a mirror outcome with unexpected size.
|
||||||
|
type OutcomeBadSizeError struct {
|
||||||
|
Ident unique.Handle[pkg.ID]
|
||||||
|
Size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e OutcomeBadSizeError) Error() string {
|
||||||
|
if e.Size < 0 {
|
||||||
|
return "remote did not return outcome size for " +
|
||||||
|
pkg.Encode(e.Ident.Value())
|
||||||
|
}
|
||||||
|
return "outcome size " + strconv.FormatInt(e.Size, 10) +
|
||||||
|
" invalid for " + pkg.Encode(e.Ident.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
// An OutcomeAuthError describes a mirror outcome with invalid signature.
|
||||||
|
type OutcomeAuthError unique.Handle[pkg.ID]
|
||||||
|
|
||||||
|
func (e OutcomeAuthError) Error() string {
|
||||||
|
return "invalid outcome signature for " +
|
||||||
|
pkg.Encode(unique.Handle[pkg.ID](e).Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Artifact fetches and authenticates an outcome.
|
||||||
|
func (r Remote) Artifact(
|
||||||
|
ctx context.Context,
|
||||||
|
id unique.Handle[pkg.ID],
|
||||||
|
) (*pkg.Checksum, error) {
|
||||||
|
if len(r.pub) != ed25519.PublicKeySize || r.c == nil {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := r.get(ctx, dirArtifact, pkg.Encode(id.Value()))
|
||||||
|
if err != nil || resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf [ed25519.SignatureSize + 2*len(pkg.Checksum{})]byte
|
||||||
|
if resp.ContentLength != int64(len(buf)) {
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
return nil, OutcomeBadSizeError{id, resp.ContentLength}
|
||||||
|
}
|
||||||
|
if _, err = io.ReadFull(resp.Body, buf[:]); err != nil {
|
||||||
|
return nil, errors.Join(err, resp.Body.Close())
|
||||||
|
} else if err = resp.Body.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ed25519.Verify(
|
||||||
|
r.pub,
|
||||||
|
buf[ed25519.SignatureSize:],
|
||||||
|
buf[:ed25519.SignatureSize],
|
||||||
|
) {
|
||||||
|
return nil, OutcomeAuthError(id)
|
||||||
|
} else if unique.Make((pkg.ID)(buf[ed25519.SignatureSize:])) != id {
|
||||||
|
return nil, OutcomeAuthError(id)
|
||||||
|
}
|
||||||
|
return (*pkg.Checksum)(buf[ed25519.SignatureSize+len(pkg.Checksum{}):]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checksum returns an artifact satisfying checksum.
|
||||||
|
func (r Remote) Checksum(checksum unique.Handle[pkg.Checksum]) pkg.Artifact {
|
||||||
|
return pkg.NewArchive(pkg.NewHTTPGet(
|
||||||
|
r.c,
|
||||||
|
r.url.JoinPath(dirOutcome, pkg.Encode(checksum.Value())).String(),
|
||||||
|
checksum.Value(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A StatusBadSizeError describes a mirror status with unexpected size.
|
||||||
|
type StatusBadSizeError unique.Handle[pkg.ID]
|
||||||
|
|
||||||
|
func (e StatusBadSizeError) Error() string {
|
||||||
|
return "status payload too short for " +
|
||||||
|
pkg.Encode(unique.Handle[pkg.ID](e).Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
// A StatusAuthError describes a mirror status with invalid signature.
|
||||||
|
type StatusAuthError unique.Handle[pkg.ID]
|
||||||
|
|
||||||
|
func (e StatusAuthError) Error() string {
|
||||||
|
return "invalid status signature for " +
|
||||||
|
pkg.Encode(unique.Handle[pkg.ID](e).Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status authenticates the checksum of a status file and returns its
|
||||||
|
// corresponding measured reader.
|
||||||
|
func (r Remote) Status(
|
||||||
|
ctx *pkg.RContext,
|
||||||
|
id unique.Handle[pkg.ID],
|
||||||
|
) (io.ReadCloser, error) {
|
||||||
|
resp, err := r.get(ctx.Unwrap(), dirStatus, pkg.Encode(id.Value()))
|
||||||
|
if err != nil || resp == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf [ed25519.SignatureSize + 2*len(pkg.Checksum{})]byte
|
||||||
|
if _, err = io.ReadFull(resp.Body, buf[:]); err != nil {
|
||||||
|
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
err = StatusBadSizeError(id)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ed25519.Verify(
|
||||||
|
r.pub,
|
||||||
|
buf[ed25519.SignatureSize:],
|
||||||
|
buf[:ed25519.SignatureSize],
|
||||||
|
) {
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
return nil, StatusAuthError(id)
|
||||||
|
} else if unique.Make((pkg.ID)(buf[ed25519.SignatureSize:])) != id {
|
||||||
|
return nil, StatusAuthError(id)
|
||||||
|
}
|
||||||
|
return ctx.NewMeasuredReader(
|
||||||
|
resp.Body,
|
||||||
|
unique.Make((pkg.Checksum)(buf[ed25519.SignatureSize+len(pkg.Checksum{}):])),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMirror returns an [http.Handler] for servicing mirror requests.
|
||||||
|
func NewMirror(
|
||||||
|
msg message.Msg,
|
||||||
|
base *os.Root,
|
||||||
|
key ed25519.PrivateKey,
|
||||||
|
) http.Handler {
|
||||||
|
const identName = "ident"
|
||||||
|
var mux http.ServeMux
|
||||||
|
|
||||||
|
mux.HandleFunc("/"+dirArtifact+"/{"+identName+"}", func(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
req *http.Request,
|
||||||
|
) {
|
||||||
|
var buf [2 * len(pkg.Checksum{})]byte
|
||||||
|
if err := pkg.Decode(
|
||||||
|
(*pkg.Checksum)(buf[:len(pkg.Checksum{})]),
|
||||||
|
req.PathValue(identName),
|
||||||
|
); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ids := pkg.Encode((pkg.Checksum)(buf[:len(pkg.Checksum{})]))
|
||||||
|
if linkname, err := base.Readlink(filepath.Join(
|
||||||
|
"identifier",
|
||||||
|
ids,
|
||||||
|
)); err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg.GetLogger().Println(err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
} else if err = pkg.Decode(
|
||||||
|
(*pkg.Checksum)(buf[len(pkg.Checksum{}):]),
|
||||||
|
filepath.Base(linkname),
|
||||||
|
); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg.Verbosef("serving artifact %s", ids)
|
||||||
|
|
||||||
|
w.Header().Set(
|
||||||
|
"Content-Length",
|
||||||
|
strconv.Itoa(ed25519.SignatureSize+len(buf)),
|
||||||
|
)
|
||||||
|
if _, err := w.Write(append(
|
||||||
|
ed25519.Sign(key, buf[:]),
|
||||||
|
buf[:]...,
|
||||||
|
)); err != nil {
|
||||||
|
msg.Verbose(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/"+dirOutcome+"/{"+identName+"}", func(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
req *http.Request,
|
||||||
|
) {
|
||||||
|
if !strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
|
||||||
|
w.WriteHeader(http.StatusNotAcceptable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf pkg.Checksum
|
||||||
|
if err := pkg.Decode(
|
||||||
|
&buf,
|
||||||
|
req.PathValue(identName),
|
||||||
|
); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
checksums := pkg.Encode(buf)
|
||||||
|
rel := filepath.Join("checksum", checksums)
|
||||||
|
if _, err := base.Lstat(rel); err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg.GetLogger().Println(err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg.Verbosef("serving outcome %s", pkg.Encode(buf))
|
||||||
|
|
||||||
|
fsys, err := fs.Sub(base.FS(), rel)
|
||||||
|
if err != nil {
|
||||||
|
msg.GetLogger().Println(err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var gw *gzip.Writer
|
||||||
|
if gw, err = gzip.NewWriterLevel(w, gzip.BestCompression); err != nil {
|
||||||
|
msg.GetLogger().Println(err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Encoding", "gzip")
|
||||||
|
if err = pkg.Write(fsys, ".", gw); err != nil {
|
||||||
|
msg.Verbose(err)
|
||||||
|
}
|
||||||
|
if err = gw.Close(); err != nil {
|
||||||
|
msg.GetLogger().Println(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/"+dirStatus+"/{"+identName+"}", func(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
req *http.Request,
|
||||||
|
) {
|
||||||
|
var buf [2 * len(pkg.Checksum{})]byte
|
||||||
|
if err := pkg.Decode(
|
||||||
|
(*pkg.Checksum)(buf[:len(pkg.Checksum{})]),
|
||||||
|
req.PathValue(identName),
|
||||||
|
); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ids := pkg.Encode((pkg.Checksum)(buf[:len(pkg.Checksum{})]))
|
||||||
|
f, err := base.Open(filepath.Join(
|
||||||
|
"status",
|
||||||
|
ids,
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg.GetLogger().Println(err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg.Verbosef("serving status %s", ids)
|
||||||
|
|
||||||
|
h := sha512.New384()
|
||||||
|
if _, err = io.Copy(h, f); err != nil {
|
||||||
|
_ = f.Close()
|
||||||
|
msg.Verbose(err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
h.Sum(buf[len(pkg.Checksum{}):len(pkg.Checksum{})])
|
||||||
|
if _, err = w.Write(append(ed25519.Sign(key, buf[:]), buf[:]...)); err != nil {
|
||||||
|
msg.Verbose(err)
|
||||||
|
return
|
||||||
|
} else if _, err = f.Seek(0, io.SeekStart); err != nil {
|
||||||
|
msg.GetLogger().Println(err)
|
||||||
|
return
|
||||||
|
} else if _, err = io.Copy(w, f); err != nil {
|
||||||
|
msg.Verbose(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return &mux
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user