orchard
mirrorEvery site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.
blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics
1package main
2
3import "strings"
4
5// Identity is hardcoded, never configured. Staging derives from baseURL, so
6// that one line also drives the noindex and the robots.txt Disallow.
7const (
8 baseURL = "https://isaacbythewood.com"
9 siteTitle = "Isaac Bythewood"
10 siteDesc = "Isaac Bythewood is a Senior Solutions Architect at Craftmaster Furniture located in Elkin, NC."
11 themeColor = "#20232e"
12 githubUser = "overshard"
13 monorepo = "orchard"
14 contactMail = "[email protected]"
15)
16
17// Staging drives both robots.txt and the noindex meta tag, since a meta tag
18// alone is useless on a URL robots.txt told the crawler not to fetch.
19var Staging = !strings.HasSuffix(baseURL, "//isaacbythewood.com")
20
21// The container name first, since the blog is a sibling on the same bridge and
22// the public hostname would route out through Cloudflare and back down the
23// tunnel. The public URL is the fallback for `make run`.
24var blogLatestSources = []string{
25 "http://orchard-blog:8000/latest.json",
26 "https://blog.bythewood.me/latest.json",
27}
28
29// NavPage is one entry in the menu and one value for the sidebar's counter.
30type NavPage struct {
31 Num string
32 Href string
33 Title string
34}
35
36var navPages = []NavPage{
37 {Num: "000", Href: "/", Title: "Home"},
38 {Num: "001", Href: "/about", Title: "About"},
39 {Num: "002", Href: "/code", Title: "Code"},
40 {Num: "003", Href: "/art", Title: "Art"},
41 {Num: "004", Href: "/contact", Title: "Contact"},
42}
43
44type TopLink struct {
45 Label string
46 Href string
47}
48
49var topLinks = []TopLink{
50 {Label: "Blog", Href: "https://blog.bythewood.me/"},
51 {Label: "Status", Href: "https://status.bythewood.me/a40ff31d-18b0-49c3-9a36-deb02c909204"},
52 {Label: "Analytics", Href: "https://analytics.bythewood.me/30e69c06-9beb-4283-8919-8c7a686ab013"},
53 {Label: "GitHub", Href: "https://github.com/overshard"},
54}
55
56type Word struct {
57 Text string
58 Slot int
59}
60
61var heroWords = []string{"AI Agents", "Automation", "DevOps", "Architecture"}
62
63var aboutWords = []string{
64 "AI Agents", "DevOps", "Full-Stack", "Testing", "Cloud",
65 "Security", "Developer", "Automation", "Architecture",
66}
67
68// Site is one card in the live section: something in orchard that is running
69// right now, as opposed to a repository you can only read.
70type Site struct {
71 Name string
72 Dir string
73 Description string
74 Tech []string
75 // The site's own front door, which every one of these has. Most of the
76 // dashboards want a login, the landing pages do not.
77 Visit string
78}
79
80// Newest first, same as the archived list, which puts the seven built from
81// scratch above the four that were ports.
82var sites = []Site{
83 {
84 Name: "chat",
85 Dir: "chat.bythewood.me",
86 Description: "A conversation with a small model running on the card in my desktop, with tools for whatever it cannot know and an incognito mode that writes nothing down.",
87 Tech: []string{"Go", "SQLite", "llama.cpp"},
88 Visit: "https://chat.bythewood.me",
89 },
90 {
91 Name: "llm",
92 Dir: "llm.bythewood.me",
93 Description: "One set of weights on one card in front of every service here that wants one, with a key per caller and every prompt and completion written down.",
94 Tech: []string{"Go", "SQLite", "llama-swap"},
95 Visit: "https://llm.bythewood.me",
96 },
97 {
98 Name: "search",
99 Dir: "search.bythewood.me",
100 Description: "Ask it a question and it searches the web, reads the pages it finds, and writes an answer whose every sentence is checked against the passage it cites.",
101 Tech: []string{"Go", "SQLite", "FTS5"},
102 Visit: "https://search.bythewood.me",
103 },
104 {
105 Name: "auth",
106 Dir: "auth.bythewood.me",
107 Description: "The front door to the rest of these. One account, a six digit code pushed to my phone over ntfy, and a session the other sites check against this one.",
108 Tech: []string{"Go", "SQLite", "Argon2"},
109 Visit: "https://auth.bythewood.me",
110 },
111 {
112 Name: "dash",
113 Dir: "dash.bythewood.me",
114 Description: "One page of markets, news, the weather, and the health of everything else here, all of it pushing itself over server sent events.",
115 Tech: []string{"Go", "SSE", "Vite"},
116 Visit: "https://dash.bythewood.me",
117 },
118 {
119 Name: "repos",
120 Dir: "repos.bythewood.me",
121 Description: "A git remote I push to over HTTPS, with a browser on top: commits, diffs, blobs and atom feeds. It mirrors my GitHub account too.",
122 Tech: []string{"Go", "Git", "SQLite"},
123 Visit: "https://repos.bythewood.me",
124 },
125 {
126 Name: "logging",
127 Dir: "logging.bythewood.me",
128 Description: "Every site here ships its structured logs to this one, so a request I want to find is in one place instead of eleven docker logs.",
129 Tech: []string{"Go", "SQLite", "Typst"},
130 Visit: "https://logging.bythewood.me",
131 },
132 {
133 Name: "status",
134 Dir: "status.bythewood.me",
135 Description: "Uptime monitoring and public status pages, with Lighthouse audits and an SEO crawler on a timer and PDF reports out the other end.",
136 Tech: []string{"Go", "SQLite", "Lighthouse"},
137 Visit: "https://status.bythewood.me",
138 },
139 {
140 Name: "analytics",
141 Dir: "analytics.bythewood.me",
142 Description: "Self-hosted website analytics: a collector, dashboards you can share without a login, a world map, and Typst PDF reports.",
143 Tech: []string{"Go", "SQLite", "GeoIP"},
144 Visit: "https://analytics.bythewood.me",
145 },
146 {
147 Name: "blog",
148 Dir: "blog.bythewood.me",
149 Description: "Markdown files on disk and no database behind them, with live search, and a PDF and a social card built for every post.",
150 Tech: []string{"Go", "Typst", "Vite"},
151 Visit: "https://blog.bythewood.me",
152 },
153 {
154 Name: "isaacbythewood.com",
155 Dir: "isaacbythewood.com",
156 Description: "The site you are reading. Go and Vite with no third party Go dependencies at all, canvas animations included.",
157 Tech: []string{"Go", "Vite", "Canvas"},
158 Visit: "https://isaacbythewood.com",
159 },
160}
161
162// SourcePath is the site's directory inside the monorepo, which is both the
163// GitHub path filter for its commits and the tail of its source link.
164func (s Site) SourcePath() string {
165 return "sites/" + s.Dir
166}
167
168func (s Site) CommitTarget() CommitTarget {
169 return CommitTarget{Key: s.SourcePath(), Repo: monorepo, Path: s.SourcePath()}
170}
171
172// Project is one card on the code page.
173type Project struct {
174 Name string
175 Slug string
176 Description string
177 Tech []string
178 // Archived splits the page, so a read-only snapshot does not sit in a wall
179 // of cards that all look equally alive.
180 Archived bool
181}
182
183var projects = []Project{
184 {
185 Name: "orchard",
186 Slug: "orchard",
187 Description: "Every site I run, in one repo, along with the shared Cloudflare Tunnel and Caddy that front them. It all runs from a desktop at home, this site included.",
188 Tech: []string{"Go", "Vite", "SQLite"},
189 },
190 {
191 Name: "taproot",
192 Slug: "taproot",
193 Description: "The dotfiles and containers I use to set up a machine for development, one to write code in and one that runs a local coding model.",
194 Tech: []string{"Docker", "Shell"},
195 },
196
197 // Archived, newest first, and named exactly as the repository is on GitHub,
198 // so a card title is something you can search for.
199 {
200 Name: "isaacbythewood.com-nextjs",
201 Slug: "isaacbythewood.com-nextjs",
202 Description: "The Next.js build of this site: a custom cursor, a page transition loader, and full canvas background animations. Replaced by the Go rebuild you are reading now.",
203 Tech: []string{"Next.js", "React"},
204 Archived: true,
205 },
206 {
207 Name: "blog.bythewood.me-rust",
208 Slug: "blog.bythewood.me-rust",
209 Description: "Single binary markdown blog: no database, live search, Typst PDF export, and great SEO.",
210 Tech: []string{"Rust", "Axum", "Typst"},
211 Archived: true,
212 },
213 {
214 Name: "blog.bythewood.me-flask",
215 Slug: "blog.bythewood.me-flask",
216 Description: "The Flask rewrite of my blog: markdown files on disk, no database, and no CMS behind it. Replaced by the Rust build above.",
217 Tech: []string{"Python", "Flask"},
218 Archived: true,
219 },
220 {
221 Name: "blog.bythewood.me-wagtail",
222 Slug: "blog.bythewood.me-wagtail",
223 Description: "The Django and Wagtail build of my blog, with a real CMS behind it. The last handcoded version, and where the blog spent most of its life.",
224 Tech: []string{"Python", "Django", "Wagtail"},
225 Archived: true,
226 },
227 {
228 Name: "darkfurrow.com-rust",
229 Slug: "darkfurrow.com-rust",
230 Description: "A living almanac of seasons, soil, and the quiet knowledge that used to be common. Taken offline, and I no longer own the domain.",
231 Tech: []string{"Rust", "Axum"},
232 Archived: true,
233 },
234 {
235 Name: "status-django",
236 Slug: "status-django",
237 Description: "The Django build of my uptime monitor and status page builder, before it was rewritten in Rust.",
238 Tech: []string{"Python", "Django", "SQLite"},
239 Archived: true,
240 },
241 {
242 Name: "analytics-django",
243 Slug: "analytics-django",
244 Description: "The Django build of my self-hosted website analytics, before it was rewritten in Rust.",
245 Tech: []string{"Python", "Django", "SQLite"},
246 Archived: true,
247 },
248 {
249 Name: "status-rust",
250 Slug: "status-rust",
251 Description: "Single binary uptime monitoring and status pages: HTTP probes, Lighthouse audits, an SEO crawler, and PDF reports.",
252 Tech: []string{"Rust", "Axum", "SQLite"},
253 Archived: true,
254 },
255 {
256 Name: "analytics-rust",
257 Slug: "analytics-rust",
258 Description: "Single binary website analytics: a collector API, dashboards, a world map, and PDF reports.",
259 Tech: []string{"Rust", "Axum", "SQLite"},
260 Archived: true,
261 },
262 {
263 Name: "timelite-nextjs",
264 Slug: "timelite-nextjs",
265 Description: "Time tracking that never leaves your browser. No accounts, no sync, no server side state.",
266 Tech: []string{"Next.js", "React"},
267 Archived: true,
268 },
269 {
270 Name: "repos-rust",
271 Slug: "repos-rust",
272 Description: "A minimal self-hosted git browser. Bare repos as a website: commits, diffs, syntax highlighted blobs, atom feeds, and clone over HTTPS.",
273 Tech: []string{"Rust", "Axum", "gix"},
274 Archived: true,
275 },
276 {
277 Name: "finance-rust",
278 Slug: "finance-rust",
279 Description: "A self-hosted market watcher for stocks, ETFs, indexes and futures, with live charts, key stats, fundamentals and SEC filings.",
280 Tech: []string{"Rust", "Axum", "SQLite"},
281 Archived: true,
282 },
283 {
284 Name: "dockerfiles",
285 Slug: "dockerfiles",
286 Description: "All the Dockerfiles I used for various purposes, each with its usage notes at the top.",
287 Tech: []string{"Docker"},
288 Archived: true,
289 },
290 {
291 Name: "dotfiles",
292 Slug: "dotfiles",
293 Description: "Config files for setting up a new system the way I like it.",
294 Tech: []string{"Shell", "Neovim"},
295 Archived: true,
296 },
297 {
298 Name: "timestrap-django",
299 Slug: "timestrap-django",
300 Description: "Time tracking you can host anywhere, with full export in several formats and an extensible core.",
301 Tech: []string{"Python", "Django"},
302 Archived: true,
303 },
304 {
305 Name: "alpinefiles",
306 Slug: "alpinefiles",
307 Description: "The files I ran on my Alpine Linux servers: Caddy, Docker, borg backups, and ufw.",
308 Tech: []string{"Shell", "Alpine"},
309 Archived: true,
310 },
311 {
312 Name: "newtab",
313 Slug: "newtab",
314 Description: "A clean new tab page extension for Chrome, built to be taken and customized.",
315 Tech: []string{"JavaScript", "Chrome"},
316 Archived: true,
317 },
318 {
319 Name: "ai-art",
320 Slug: "ai-art",
321 Description: "Art generation with VQGAN and CLIP in docker containers. A simplified, updated and expanded take on the original notebooks.",
322 Tech: []string{"Python", "PyTorch"},
323 Archived: true,
324 },
325 {
326 Name: "docker-teamspeak",
327 Slug: "docker-teamspeak",
328 Description: "A nice and easy way to get a TeamSpeak server up and running with Docker.",
329 Tech: []string{"Docker", "Shell"},
330 Archived: true,
331 },
332 {
333 Name: "docker-minecraft",
334 Slug: "docker-minecraft",
335 Description: "An easy way to get a Minecraft server up and running with Docker.",
336 Tech: []string{"Docker", "Shell"},
337 Archived: true,
338 },
339 {
340 Name: "pinry",
341 Slug: "pinry",
342 Description: "A tiling image board system. Development moved on to pinry/pinry.",
343 Tech: []string{"Python"},
344 Archived: true,
345 },
346}
347
348// Pour is one acrylic pour on the art page.
349type Pour struct {
350 Number string
351 Title string
352 Priority bool
353}
354
355var pours = []Pour{
356 {Number: "006", Title: "Molten Copper", Priority: true},
357 {Number: "005", Title: "Nebulas in Triangulum", Priority: true},
358 {Number: "004", Title: "Metal on Mars", Priority: true},
359 {Number: "003", Title: "Water on Jupiter", Priority: true},
360 {Number: "002", Title: "Cracks in Clay", Priority: false},
361 {Number: "001", Title: "Reef Drop-off", Priority: false},
362 {Number: "000", Title: "Blood in Waves", Priority: false},
363}
364
365// Generative is one canvas piece on the art page.
366type Generative struct {
367 Number string
368 Title string
369 Slug string
370 Description string
371 Source string
372 Autoplay bool
373}
374
375var generative = []Generative{
376 {
377 Number: "000",
378 Title: "Constellations",
379 Slug: "constellations",
380 Description: "Drifting points on a dark canvas that form connections with their nearest " +
381 "neighbors. As stars wander closer together lines appear between them, brighter the " +
382 "nearer they get, building and dissolving constellations that never repeat.",
383 Source: sourceURL("constellations"),
384 Autoplay: true,
385 },
386 {
387 Number: "001",
388 Title: "Retro Stars",
389 Slug: "retrostars",
390 Description: "Layered star fields at different depths that drift in response to your cursor, " +
391 "creating a parallax effect. Inspired by the pixel art aesthetic of Celeste and the " +
392 "feeling of staring into a sky that moves with you.",
393 Source: sourceURL("retrostars"),
394 },
395 {
396 Number: "002",
397 Title: "Slime Mold",
398 Slug: "slimemold",
399 Description: "Thousands of agents wander a dark field, each leaving a faint chemical trail " +
400 "and steering toward the strongest scent ahead. From three simple rules (sense, turn, " +
401 "deposit) emerge living networks reminiscent of Physarum slime molds, neurons, and the " +
402 "cosmic web. The pattern never settles; trails decay as fast as they form.",
403 Source: sourceURL("slimemold"),
404 },
405}
406
407func sourceURL(slug string) string {
408 return "https://github.com/" + githubUser +
409 "/orchard/blob/main/sites/isaacbythewood.com/frontend/static_src/js/canvas/" + slug + ".js"
410}
411
412type ContactMethod struct {
413 Key string
414 Value string
415 Href string
416 External bool
417}
418
419var contactMethods = []ContactMethod{
420 {Key: "Email", Value: contactMail, Href: "mailto:" + contactMail},
421 {Key: "LinkedIn", Value: "Isaac Bythewood", Href: "https://www.linkedin.com/in/ibythewood/", External: true},
422 {Key: "GitHub", Value: "/" + githubUser, Href: "https://github.com/" + githubUser, External: true},
423 {Key: "Discord", Value: "Overshard#4907", Href: "https://discordapp.com/", External: true},
424}