Skip to content

Commit

Permalink
feature: add support for localDir (#2)
Browse files Browse the repository at this point in the history
localDir allows one to serve files that are stored on the host machine
  • Loading branch information
OGKevin committed Jun 5, 2021
1 parent 7f8084a commit 6d6ff5c
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 15 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,17 @@ mounts:
# When proxy is defined, these requests are proxied to a HTTP/HTTPS address.
proxy: http://archive.ubuntu.com/ubuntu/dists/bionic-updates/main/installer-amd64/current/images/hwe-netboot/ubuntu-installer/amd64/
# When true, the proxy path defined above gets a suffix to the Path prefix appended to it.
proxyAppendSuffix: true
appendSuffix: true

- path: /subdir
# When true, all paths starting with this prefix use this mount.
pathIsPrefix: true
# Provides a path on the host to find the files.
# So that localDir: /tftpboot path: /subdir and client request: /subdir/file.x so that the host
# path becomes /tfptboot/file.x
localDir: /tftpboot
# When true, the localDir path defined above gets a suffix to the Path prefix appended to it.
appendSuffix: true

- path: /install.ipxe
# The templating context provides access to: .LocalIP, .RemoteIP, .HttpBaseUrl and .Manifest.
Expand Down
44 changes: 40 additions & 4 deletions httpd/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ package httpd
import (
"bytes"
_ "embed"
mfest "github.com/DSpeichert/netbootd/manifest"
"github.com/DSpeichert/netbootd/static"
"github.com/Masterminds/sprig"
"io"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"path/filepath"
"strings"
"text/template"
"time"

mfest "github.com/DSpeichert/netbootd/manifest"
"github.com/DSpeichert/netbootd/static"
"github.com/Masterminds/sprig"
)

type Handler struct {
Expand Down Expand Up @@ -129,8 +132,41 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
rp.ServeHTTP(w, r)
return
} else if mount.LocalDir != "" {
path := filepath.Join(mount.LocalDir, mount.Path)

if mount.AppendSuffix {
path = filepath.Join(mount.LocalDir, strings.TrimPrefix(r.URL.Path, mount.Path))
}

if !strings.HasPrefix(path, mount.LocalDir) {
h.server.logger.Error().
Err(err).
Msgf("Requested path is invalid: %q", path)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

f, err := os.Open(path)
if err != nil {
h.server.logger.Error().
Err(err).
Msgf("Could not get file from local dir: %q", path)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
stat, err := f.Stat()
if err != nil {
h.server.logger.Error().
Err(err).
Msgf("could not stat file: %q", path)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.ServeContent(w, r, r.URL.Path, stat.ModTime(), f)
return
} else {
// mount has neither .Path nor .Proxy defined
// mount has neither .Path, .Proxy nor .LocalDir defined
h.server.logger.Error().
Str("path", r.RequestURI).
Str("client", raddr.String()).
Expand Down
26 changes: 24 additions & 2 deletions manifest/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ package manifest

import (
"encoding/json"
"fmt"
"gopkg.in/yaml.v2"
"path/filepath"
)

func ManifestFromJson(content []byte) (manifest Manifest, err error) {
err = json.Unmarshal(content, &manifest)
return
if err != nil {
return manifest, err
}

return manifest, manifest.Validate()
}

func (m *Manifest) ToJson() ([]byte, error) {
Expand All @@ -16,7 +22,23 @@ func (m *Manifest) ToJson() ([]byte, error) {

func ManifestFromYaml(content []byte) (manifest Manifest, err error) {
err = yaml.Unmarshal(content, &manifest)
return
if err != nil {
return manifest, err
}

return manifest, manifest.Validate()
}

func (m Manifest) Validate() error {
for _, mount := range m.Mounts {
if mount.LocalDir != "" {
if !filepath.IsAbs(mount.LocalDir) {
return fmt.Errorf("localDir needs to be absolute path")
}
}
}

return nil
}

func (m *Manifest) ToYaml() ([]byte, error) {
Expand Down
11 changes: 8 additions & 3 deletions manifest/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,18 @@ type Mount struct {
// The proxy destination used when handling requests.
// Mutually exclusive with Content option.
Proxy string
// If PathIsPrefix is true and ProxyAppendSuffix is true, the suffix to Path Prefix will also be appended to Proxy.
// If PathIsPrefix is true and AppendSuffix is true, the suffix to Path Prefix will also be appended to Proxy Or LocalDir.
// Otherwise, it will be many to one proxy.
ProxyAppendSuffix bool `yaml:"proxyAppendSuffix"`
AppendSuffix bool `yaml:"appendSuffix"`

// Provides content template (passed through template/text) to serve.
// Mutually exclusive with Proxy option.
Content string

// Provides a path on the host to find the files.
// So that LocalDir: /tftpboot path: /subdir and client requests: /subdir/file.x the path on the host
// becomes /tfptboot/file.x
LocalDir string `yaml:"localDir"`
}

func (m Mount) ProxyDirector() (func(req *http.Request), error) {
Expand All @@ -72,7 +77,7 @@ func (m Mount) ProxyDirector() (func(req *http.Request), error) {
req.Header.Set("User-Agent", "")
}

if m.ProxyAppendSuffix {
if m.AppendSuffix {
req.URL.Path = target.Path + strings.TrimPrefix(req.URL.Path, m.Path)
req.URL.RawPath = target.RawPath + strings.TrimPrefix(req.URL.RawPath, m.Path)
} else {
Expand Down
60 changes: 55 additions & 5 deletions tftpd/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@ import (
_ "embed"
"errors"
"fmt"
mfest "github.com/DSpeichert/netbootd/manifest"
"github.com/DSpeichert/netbootd/static"
"github.com/Masterminds/sprig"
"github.com/pin/tftp"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"text/template"

mfest "github.com/DSpeichert/netbootd/manifest"
"github.com/DSpeichert/netbootd/static"
"github.com/Masterminds/sprig"
"github.com/pin/tftp"
)

func (server *Server) tftpReadHandler(filename string, rf io.ReaderFrom) error {
Expand Down Expand Up @@ -64,7 +67,7 @@ func (server *Server) tftpReadHandler(filename string, rf io.ReaderFrom) error {

if mount.Proxy != "" {
url := mount.Proxy
if mount.ProxyAppendSuffix {
if mount.AppendSuffix {
url = url + strings.TrimPrefix(filename, mount.Path)
}

Expand Down Expand Up @@ -156,6 +159,53 @@ func (server *Server) tftpReadHandler(filename string, rf io.ReaderFrom) error {
return err
}

server.logger.Info().
Err(err).
Str("path", filename).
Str("client", raddr.IP.String()).
Int64("sent", n).
Msg("transfer finished")
} else if mount.LocalDir != "" {
path := filepath.Join(mount.LocalDir, mount.Path)

if mount.AppendSuffix {
path = filepath.Join(mount.LocalDir, strings.TrimPrefix(filename, mount.Path))
}

if !strings.HasPrefix(path, mount.LocalDir) {
err := fmt.Errorf("requested path is invalid")
server.logger.Error().
Err(err).
Msgf("Requested path is invalid: %q", path)
return err
}

f, err := os.Open(path)
if err != nil {
server.logger.Error().
Err(err).
Msgf("Could not get file from local dir: %q", filename)

return err
}

stat, err := f.Stat()
if err != nil {
server.logger.Error().
Err(err).
Msgf("Could not stat file: %q", path)
return err
}

rf.(tftp.OutgoingTransfer).SetSize(int64(stat.Size()))

n, err := rf.ReadFrom(f)
if err != nil {
server.logger.Error().
Msgf("ReadFrom failed: %v", err)
return err
}

server.logger.Info().
Err(err).
Str("path", filename).
Expand Down

0 comments on commit 6d6ff5c

Please sign in to comment.