Skip to content

Commit 6d6ff5c

Browse files
authored
feature: add support for localDir (#2)
localDir allows one to serve files that are stored on the host machine
1 parent 7f8084a commit 6d6ff5c

File tree

5 files changed

+138
-15
lines changed

5 files changed

+138
-15
lines changed

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,17 @@ mounts:
109109
# When proxy is defined, these requests are proxied to a HTTP/HTTPS address.
110110
proxy: http://archive.ubuntu.com/ubuntu/dists/bionic-updates/main/installer-amd64/current/images/hwe-netboot/ubuntu-installer/amd64/
111111
# When true, the proxy path defined above gets a suffix to the Path prefix appended to it.
112-
proxyAppendSuffix: true
112+
appendSuffix: true
113+
114+
- path: /subdir
115+
# When true, all paths starting with this prefix use this mount.
116+
pathIsPrefix: true
117+
# Provides a path on the host to find the files.
118+
# So that localDir: /tftpboot path: /subdir and client request: /subdir/file.x so that the host
119+
# path becomes /tfptboot/file.x
120+
localDir: /tftpboot
121+
# When true, the localDir path defined above gets a suffix to the Path prefix appended to it.
122+
appendSuffix: true
113123

114124
- path: /install.ipxe
115125
# The templating context provides access to: .LocalIP, .RemoteIP, .HttpBaseUrl and .Manifest.

httpd/handler.go

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@ package httpd
33
import (
44
"bytes"
55
_ "embed"
6-
mfest "github.com/DSpeichert/netbootd/manifest"
7-
"github.com/DSpeichert/netbootd/static"
8-
"github.com/Masterminds/sprig"
96
"io"
107
"net"
118
"net/http"
129
"net/http/httputil"
1310
"net/url"
11+
"os"
12+
"path/filepath"
1413
"strings"
1514
"text/template"
1615
"time"
16+
17+
mfest "github.com/DSpeichert/netbootd/manifest"
18+
"github.com/DSpeichert/netbootd/static"
19+
"github.com/Masterminds/sprig"
1720
)
1821

1922
type Handler struct {
@@ -129,8 +132,41 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
129132
}
130133
rp.ServeHTTP(w, r)
131134
return
135+
} else if mount.LocalDir != "" {
136+
path := filepath.Join(mount.LocalDir, mount.Path)
137+
138+
if mount.AppendSuffix {
139+
path = filepath.Join(mount.LocalDir, strings.TrimPrefix(r.URL.Path, mount.Path))
140+
}
141+
142+
if !strings.HasPrefix(path, mount.LocalDir) {
143+
h.server.logger.Error().
144+
Err(err).
145+
Msgf("Requested path is invalid: %q", path)
146+
http.Error(w, err.Error(), http.StatusBadRequest)
147+
return
148+
}
149+
150+
f, err := os.Open(path)
151+
if err != nil {
152+
h.server.logger.Error().
153+
Err(err).
154+
Msgf("Could not get file from local dir: %q", path)
155+
http.Error(w, err.Error(), http.StatusInternalServerError)
156+
return
157+
}
158+
stat, err := f.Stat()
159+
if err != nil {
160+
h.server.logger.Error().
161+
Err(err).
162+
Msgf("could not stat file: %q", path)
163+
http.Error(w, err.Error(), http.StatusInternalServerError)
164+
return
165+
}
166+
http.ServeContent(w, r, r.URL.Path, stat.ModTime(), f)
167+
return
132168
} else {
133-
// mount has neither .Path nor .Proxy defined
169+
// mount has neither .Path, .Proxy nor .LocalDir defined
134170
h.server.logger.Error().
135171
Str("path", r.RequestURI).
136172
Str("client", raddr.String()).

manifest/io.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@ package manifest
22

33
import (
44
"encoding/json"
5+
"fmt"
56
"gopkg.in/yaml.v2"
7+
"path/filepath"
68
)
79

810
func ManifestFromJson(content []byte) (manifest Manifest, err error) {
911
err = json.Unmarshal(content, &manifest)
10-
return
12+
if err != nil {
13+
return manifest, err
14+
}
15+
16+
return manifest, manifest.Validate()
1117
}
1218

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

1723
func ManifestFromYaml(content []byte) (manifest Manifest, err error) {
1824
err = yaml.Unmarshal(content, &manifest)
19-
return
25+
if err != nil {
26+
return manifest, err
27+
}
28+
29+
return manifest, manifest.Validate()
30+
}
31+
32+
func (m Manifest) Validate() error {
33+
for _, mount := range m.Mounts {
34+
if mount.LocalDir != "" {
35+
if !filepath.IsAbs(mount.LocalDir) {
36+
return fmt.Errorf("localDir needs to be absolute path")
37+
}
38+
}
39+
}
40+
41+
return nil
2042
}
2143

2244
func (m *Manifest) ToYaml() ([]byte, error) {

manifest/schema.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,18 @@ type Mount struct {
4040
// The proxy destination used when handling requests.
4141
// Mutually exclusive with Content option.
4242
Proxy string
43-
// If PathIsPrefix is true and ProxyAppendSuffix is true, the suffix to Path Prefix will also be appended to Proxy.
43+
// If PathIsPrefix is true and AppendSuffix is true, the suffix to Path Prefix will also be appended to Proxy Or LocalDir.
4444
// Otherwise, it will be many to one proxy.
45-
ProxyAppendSuffix bool `yaml:"proxyAppendSuffix"`
45+
AppendSuffix bool `yaml:"appendSuffix"`
4646

4747
// Provides content template (passed through template/text) to serve.
4848
// Mutually exclusive with Proxy option.
4949
Content string
50+
51+
// Provides a path on the host to find the files.
52+
// So that LocalDir: /tftpboot path: /subdir and client requests: /subdir/file.x the path on the host
53+
// becomes /tfptboot/file.x
54+
LocalDir string `yaml:"localDir"`
5055
}
5156

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

75-
if m.ProxyAppendSuffix {
80+
if m.AppendSuffix {
7681
req.URL.Path = target.Path + strings.TrimPrefix(req.URL.Path, m.Path)
7782
req.URL.RawPath = target.RawPath + strings.TrimPrefix(req.URL.RawPath, m.Path)
7883
} else {

tftpd/handler.go

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@ import (
55
_ "embed"
66
"errors"
77
"fmt"
8-
mfest "github.com/DSpeichert/netbootd/manifest"
9-
"github.com/DSpeichert/netbootd/static"
10-
"github.com/Masterminds/sprig"
11-
"github.com/pin/tftp"
128
"io"
139
"net/http"
1410
"net/url"
11+
"os"
12+
"path/filepath"
1513
"strings"
1614
"text/template"
15+
16+
mfest "github.com/DSpeichert/netbootd/manifest"
17+
"github.com/DSpeichert/netbootd/static"
18+
"github.com/Masterminds/sprig"
19+
"github.com/pin/tftp"
1720
)
1821

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

6568
if mount.Proxy != "" {
6669
url := mount.Proxy
67-
if mount.ProxyAppendSuffix {
70+
if mount.AppendSuffix {
6871
url = url + strings.TrimPrefix(filename, mount.Path)
6972
}
7073

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

162+
server.logger.Info().
163+
Err(err).
164+
Str("path", filename).
165+
Str("client", raddr.IP.String()).
166+
Int64("sent", n).
167+
Msg("transfer finished")
168+
} else if mount.LocalDir != "" {
169+
path := filepath.Join(mount.LocalDir, mount.Path)
170+
171+
if mount.AppendSuffix {
172+
path = filepath.Join(mount.LocalDir, strings.TrimPrefix(filename, mount.Path))
173+
}
174+
175+
if !strings.HasPrefix(path, mount.LocalDir) {
176+
err := fmt.Errorf("requested path is invalid")
177+
server.logger.Error().
178+
Err(err).
179+
Msgf("Requested path is invalid: %q", path)
180+
return err
181+
}
182+
183+
f, err := os.Open(path)
184+
if err != nil {
185+
server.logger.Error().
186+
Err(err).
187+
Msgf("Could not get file from local dir: %q", filename)
188+
189+
return err
190+
}
191+
192+
stat, err := f.Stat()
193+
if err != nil {
194+
server.logger.Error().
195+
Err(err).
196+
Msgf("Could not stat file: %q", path)
197+
return err
198+
}
199+
200+
rf.(tftp.OutgoingTransfer).SetSize(int64(stat.Size()))
201+
202+
n, err := rf.ReadFrom(f)
203+
if err != nil {
204+
server.logger.Error().
205+
Msgf("ReadFrom failed: %v", err)
206+
return err
207+
}
208+
159209
server.logger.Info().
160210
Err(err).
161211
Str("path", filename).

0 commit comments

Comments
 (0)