repos
/ orchard main

orchard

mirror

Every 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

15.2 KB · 425 lines · Go Raw History
  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}