Skip to content

Commit dcafc8d

Browse files
committed
feat: embed default pages into binary as fallback when files.root lacks them
Add internal/defaults package that embeds public/index.html, public/404.html, and public/style.css into the binary via //go:embed. Update FileHandler to fall back to these embedded assets when the configured files.root does not contain the requested file, ensuring sensible default pages are always available regardless of deployment layout.
1 parent c032030 commit dcafc8d

File tree

5 files changed

+382
-1
lines changed

5 files changed

+382
-1
lines changed

internal/defaults/defaults.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Package defaults embeds the built-in static assets (index.html, 404.html,
2+
// style.css) that ship with the binary. These are used as a fallback when the
3+
// configured files.root directory does not contain those files, ensuring that
4+
// the server always has sensible default pages regardless of deployment layout.
5+
//
6+
// Embedded asset paths use the "public/" prefix, e.g.:
7+
//
8+
// data, err := fs.ReadFile(defaults.FS, "public/index.html")
9+
package defaults
10+
11+
import "embed"
12+
13+
// FS holds the embedded public/ directory tree.
14+
// Consumers should read files via fs.ReadFile(FS, "public/<name>") where
15+
// <name> is one of: index.html, 404.html, style.css.
16+
//
17+
//go:embed public/index.html public/404.html public/style.css
18+
var FS embed.FS

internal/defaults/public/404.html

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>404 — Not Found</title>
7+
<link rel="stylesheet" href="/style.css" />
8+
</head>
9+
<body>
10+
11+
<div class="line">
12+
<span class="prompt">$</span>
13+
<span>GET <span class="path-val"></span></span>
14+
</div>
15+
16+
<div class="line spacer">
17+
<span class="err"></span>
18+
<span><strong>404</strong> — resource not found</span>
19+
</div>
20+
21+
<div class="block block-error">
22+
<div class="block-title">// error details</div>
23+
<div class="kv">
24+
<span class="key">status</span>
25+
<span class="val err">404 Not Found</span>
26+
</div>
27+
<div class="kv">
28+
<span class="key">hint</span>
29+
<span class="val dim">the file does not exist or was moved</span>
30+
</div>
31+
<div class="kv">
32+
<span class="key">tip</span>
33+
<span class="val dim">double-check the URL for typos</span>
34+
</div>
35+
</div>
36+
37+
<div class="actions">
38+
<a href="/" class="btn">
39+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
40+
<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>
41+
</svg>
42+
~/
43+
</a>
44+
</div>
45+
46+
<hr class="section-divider" />
47+
48+
<div class="footer-line">
49+
Made with ❤️ by <a href="https://21no.de">21no.de</a>
50+
&mdash; <a href="https://github.com/BackendStack21/static-web">static-web</a>
51+
</div>
52+
53+
</body>
54+
</html>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>static-web</title>
7+
<link rel="stylesheet" href="/style.css" />
8+
</head>
9+
<body>
10+
11+
<div class="line">
12+
<span class="prompt">$</span>
13+
<span>static-web start<span class="cursor"></span></span>
14+
</div>
15+
16+
<div class="line spacer">
17+
<span class="ok"></span>
18+
<span>server is <strong>running</strong><span class="tag">live</span></span>
19+
</div>
20+
21+
<div class="block">
22+
<div class="block-title">// server info</div>
23+
<div class="kv">
24+
<span class="key">project</span>
25+
<span class="val"><a href="https://github.com/BackendStack21/static-web">21no.de/static-web</a></span>
26+
</div>
27+
<div class="kv">
28+
<span class="key">language</span>
29+
<span class="val info">Go</span>
30+
</div>
31+
<div class="kv">
32+
<span class="key">listen</span>
33+
<span class="val">:8080</span>
34+
</div>
35+
<div class="kv">
36+
<span class="key">serving</span>
37+
<span class="val dim">./public/</span>
38+
</div>
39+
</div>
40+
41+
<div class="block">
42+
<div class="block-title">// replace this page</div>
43+
<div class="kv">
44+
<span class="key">step 1</span>
45+
<span class="val dim">drop your files into <span class="val">./public/</span></span>
46+
</div>
47+
<div class="kv">
48+
<span class="key">step 2</span>
49+
<span class="val dim">your <span class="val">index.html</span> replaces this page</span>
50+
</div>
51+
<div class="kv">
52+
<span class="key">config</span>
53+
<span class="val dim">edit <span class="val">config.toml</span> to customize</span>
54+
</div>
55+
</div>
56+
57+
<div class="block">
58+
<div class="block-title">// capabilities</div>
59+
<div class="kv"><span class="key ok">✓ cache</span> <span class="val dim">in-memory LRU, configurable TTL</span></div>
60+
<div class="kv"><span class="key ok">✓ compress</span> <span class="val dim">gzip on-the-fly + pre-compressed sidecar files</span></div>
61+
<div class="kv"><span class="key ok">✓ tls</span> <span class="val dim">TLS 1.2 / 1.3, HTTP/2 via ALPN</span></div>
62+
<div class="kv"><span class="key ok">✓ headers</span> <span class="val dim">ETag, Cache-Control, CSP, CORS, HSTS</span></div>
63+
<div class="kv"><span class="key ok">✓ security</span> <span class="val dim">dotfile blocking, security headers</span></div>
64+
<div class="kv"><span class="key ok">✓ graceful</span> <span class="val dim">SIGHUP reload · SIGTERM drain &amp; shutdown</span></div>
65+
</div>
66+
67+
<hr class="section-divider" />
68+
69+
<div class="line">
70+
<span class="dim">#</span>
71+
<span class="dim">docs &amp; source →</span>
72+
<a href="https://github.com/BackendStack21/static-web">github.com/BackendStack21/static-web</a>
73+
</div>
74+
75+
<div class="footer-line">
76+
Made with ❤️ by <a href="https://21no.de">21no.de</a>
77+
</div>
78+
79+
</body>
80+
</html>

internal/defaults/public/style.css

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
2+
3+
:root {
4+
--bg: #0d1117;
5+
--surface: #161b22;
6+
--border: #30363d;
7+
--text: #e6edf3;
8+
--muted: #7d8590;
9+
--green: #3fb950;
10+
--cyan: #79c0ff;
11+
--yellow: #e3b341;
12+
--red: #f85149;
13+
--mono: ui-monospace, "SF Mono", Menlo, "Cascadia Code", Consolas, monospace;
14+
}
15+
16+
html, body { height: 100%; }
17+
18+
body {
19+
font-family: var(--mono);
20+
background: var(--bg);
21+
color: var(--text);
22+
font-size: 14px;
23+
line-height: 1.6;
24+
padding: 40px 24px;
25+
display: flex;
26+
flex-direction: column;
27+
align-items: flex-start;
28+
justify-content: center;
29+
min-height: 100vh;
30+
max-width: 720px;
31+
margin: 0 auto;
32+
}
33+
34+
/* layout primitives */
35+
36+
.line {
37+
display: flex;
38+
align-items: baseline;
39+
gap: 10px;
40+
min-height: 1.6em;
41+
}
42+
43+
.line + .line { margin-top: 2px; }
44+
.spacer { margin-top: 20px; }
45+
46+
/* colour tokens */
47+
48+
.prompt { color: var(--muted); user-select: none; }
49+
.ok { color: var(--green); }
50+
.info { color: var(--cyan); }
51+
.warn { color: var(--yellow); }
52+
.err { color: var(--red); }
53+
.dim { color: var(--muted); }
54+
55+
/* info block */
56+
57+
.block {
58+
background: var(--surface);
59+
border: 1px solid var(--border);
60+
border-left: 3px solid var(--border);
61+
border-radius: 6px;
62+
padding: 16px 20px;
63+
margin-top: 24px;
64+
width: 100%;
65+
}
66+
67+
.block.block-error {
68+
border-left-color: var(--red);
69+
}
70+
71+
.block-title {
72+
color: var(--muted);
73+
font-size: 11px;
74+
letter-spacing: .08em;
75+
text-transform: uppercase;
76+
margin-bottom: 12px;
77+
}
78+
79+
.kv {
80+
display: flex;
81+
gap: 12px;
82+
line-height: 1.9;
83+
}
84+
85+
.key {
86+
color: var(--muted);
87+
flex-shrink: 0;
88+
min-width: 120px;
89+
}
90+
91+
.val { color: var(--text); }
92+
93+
.path-val {
94+
color: var(--red);
95+
word-break: break-all;
96+
}
97+
98+
/* tag / badge */
99+
100+
.tag {
101+
display: inline-block;
102+
background: rgba(63,185,80,.12);
103+
color: var(--green);
104+
border: 1px solid rgba(63,185,80,.25);
105+
border-radius: 4px;
106+
padding: 1px 7px;
107+
font-size: 12px;
108+
vertical-align: middle;
109+
margin-left: 6px;
110+
}
111+
112+
/* cursor */
113+
114+
.cursor {
115+
display: inline-block;
116+
width: 9px;
117+
height: 1.1em;
118+
background: var(--green);
119+
vertical-align: text-bottom;
120+
animation: blink 1.1s step-start infinite;
121+
margin-left: 2px;
122+
}
123+
124+
@keyframes blink {
125+
0%, 100% { opacity: 1; }
126+
50% { opacity: 0; }
127+
}
128+
129+
/* buttons */
130+
131+
.actions {
132+
display: flex;
133+
gap: 12px;
134+
flex-wrap: wrap;
135+
margin-top: 24px;
136+
}
137+
138+
.btn {
139+
display: inline-flex;
140+
align-items: center;
141+
gap: 6px;
142+
border: 1px solid var(--border);
143+
border-radius: 6px;
144+
padding: 7px 14px;
145+
font-family: var(--mono);
146+
font-size: 13px;
147+
color: var(--text);
148+
text-decoration: none;
149+
background: var(--surface);
150+
cursor: pointer;
151+
transition: border-color .12s, color .12s;
152+
}
153+
154+
.btn:hover {
155+
border-color: var(--cyan);
156+
color: var(--cyan);
157+
}
158+
159+
/* divider / footer */
160+
161+
.section-divider {
162+
border: none;
163+
border-top: 1px solid var(--border);
164+
margin: 28px 0;
165+
width: 100%;
166+
}
167+
168+
.footer-line {
169+
color: var(--muted);
170+
font-size: 12px;
171+
}
172+
173+
a {
174+
color: var(--cyan);
175+
text-decoration: none;
176+
}
177+
178+
a:hover { text-decoration: underline; }
179+
180+
/* responsive */
181+
182+
@media (max-width: 540px) {
183+
body { font-size: 13px; padding: 24px 16px; }
184+
.key { min-width: 90px; }
185+
}

0 commit comments

Comments
 (0)