iop/apps/edge/internal/bootstrap/artifact_server.go
toki 5c3fef65e9 feat: edge local dev infrastructure and artifact server implementation
- Add artifact_server.go for edge node artifact serving
- Refactor bootstrap/runtime.go to use new artifact server
- Update Makefile for new build workflow
- Remove deprecated bin/build/field-binaries.sh
- Update edge README with new development workflow
- Update edge-local-dev-guide.md with artifact server info
- Add config and hostsetup updates for artifact serving
- Update agent-ops rules and testing domain rules
2026-05-28 18:53:12 +09:00

72 lines
1.9 KiB
Go

package bootstrap
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"os"
"time"
"go.uber.org/zap"
)
// ArtifactServer serves node bootstrap scripts and node binaries from the
// configured artifact directory. It is intentionally static: iop-edge prepares
// the directory, then serve exposes it without mutating the artifacts.
type ArtifactServer struct {
listen string
dir string
logger *zap.Logger
server *http.Server
}
func NewArtifactServer(listen, dir string, logger *zap.Logger) *ArtifactServer {
return &ArtifactServer{listen: listen, dir: dir, logger: logger}
}
func (s *ArtifactServer) Start(ctx context.Context) error {
if s == nil || s.listen == "" || s.dir == "" {
return nil
}
if _, err := os.Stat(s.dir); err != nil {
if errors.Is(err, os.ErrNotExist) {
s.logger.Warn("bootstrap artifact directory does not exist", zap.String("dir", s.dir))
} else {
return fmt.Errorf("stat bootstrap artifact dir: %w", err)
}
}
mux := http.NewServeMux()
mux.Handle("/", http.FileServer(http.Dir(s.dir)))
s.server = &http.Server{
Addr: s.listen,
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}
ln, err := net.Listen("tcp", s.listen)
if err != nil {
return fmt.Errorf("bootstrap artifact server listen %s: %w", s.listen, err)
}
go func() {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = s.server.Shutdown(shutdownCtx)
}()
go func() {
s.logger.Info("bootstrap artifact server listening", zap.String("addr", s.listen), zap.String("dir", s.dir))
if err := s.server.Serve(ln); err != nil && !errors.Is(err, http.ErrServerClosed) {
s.logger.Warn("bootstrap artifact server exited", zap.Error(err))
}
}()
return nil
}
func (s *ArtifactServer) Stop(ctx context.Context) error {
if s == nil || s.server == nil {
return nil
}
return s.server.Shutdown(ctx)
}