[{"content":"","date":"17 June 2026","externalUrl":null,"permalink":"/tags/devops/","section":"Tags","summary":"","title":"Devops","type":"tags"},{"content":"","date":"17 June 2026","externalUrl":null,"permalink":"/tags/dns/","section":"Tags","summary":"","title":"Dns","type":"tags"},{"content":"","date":"17 June 2026","externalUrl":null,"permalink":"/tags/github/","section":"Tags","summary":"","title":"Github","type":"tags"},{"content":"","date":"17 June 2026","externalUrl":null,"permalink":"/tags/hosting/","section":"Tags","summary":"","title":"Hosting","type":"tags"},{"content":"GitHub Pages is a free static hosting service built into every GitHub repository. Combined with a custom domain and a handful of DNS records, you can serve a production site with HTTPS for nothing beyond the cost of the domain name.\nThis note covers the full path: repo setup, Pages configuration, DNS records, and TLS enforcement.\nPrerequisites # A GitHub account and a repository containing your static site output (HTML/CSS/JS) A registered domain name managed at any DNS registrar (examples use Porkbun) gh CLI installed and authenticated (gh auth login) 1. Prepare the repository # Your repo needs to end up with built HTML at a known location. Two options:\nOption A — build in CI (recommended) Use a GitHub Actions workflow to build your site and deploy the output. The workflow publishes to Pages directly; nothing gets committed. Set Pages source to GitHub Actions in repo Settings → Pages.\nOption B — publish from a branch Commit the built output to a branch (commonly gh-pages). Set Pages source to that branch + folder in repo Settings → Pages. Simpler but mixes source and output.\nOption A keeps the repo clean and rebuilds on every push. Option B is fine for sites with no build step.\n2. Enable GitHub Pages # Go to repo Settings → Pages Under Source, select GitHub Actions (or your branch if using Option B) Leave Custom domain blank for now — set it after DNS is ready At this point https://\u0026lt;username\u0026gt;.github.io/\u0026lt;repo\u0026gt; will serve your site once the first build completes.\nNote: For an organisation or user root site (repo named \u0026lt;username\u0026gt;.github.io), the site serves at https://\u0026lt;username\u0026gt;.github.io with no path suffix.\n3. Add a CNAME file to your repo # GitHub Pages reads a file named CNAME at the root of the published output to know which custom domain to expect. Create it in your source tree so it gets included in every build.\nFor a plain HTML site, add it at the repo root:\nyour-domain.com For a static site generator, place it where the generator copies static files verbatim into the output (e.g. static/CNAME for Hugo, public/CNAME for Gatsby). The file must contain exactly one line: the bare domain, no protocol, no trailing slash.\nCommit and push. Confirm the file appears in the deployed output.\n4. Configure DNS at your registrar # GitHub Pages requires specific DNS records depending on whether you want the apex domain (example.com), a subdomain (www.example.com), or both.\nApex domain — four A records # GitHub Pages serves from four IP addresses. Add an A record for each:\nType Host Value A @ 185.199.108.153 A @ 185.199.109.153 A @ 185.199.110.153 A @ 185.199.111.153 @ means the root of the domain. Some registrars use the domain name itself or leave the field blank for the apex — check your registrar\u0026rsquo;s UI.\nwww subdomain — one CNAME record # Type Host Value CNAME www \u0026lt;username\u0026gt;.github.io. Replace \u0026lt;username\u0026gt; with your GitHub username or organisation name. The trailing dot is part of the DNS record; most registrars add it automatically.\nSubdomain only (e.g. app.example.com) # Skip the A records and add a single CNAME pointing the subdomain at \u0026lt;username\u0026gt;.github.io.. No apex records needed.\n5. Verify DNS propagation # DNS changes can take minutes to hours to propagate. Check with:\ndig your-domain.com A +short # should return the four 185.199.x.153 addresses dig www.your-domain.com CNAME +short # should return \u0026lt;username\u0026gt;.github.io. Or use Google\u0026rsquo;s public resolver to bypass local caching:\ndig @8.8.8.8 your-domain.com A +short Wait until you see the expected records before proceeding.\n6. Set the custom domain in GitHub Pages settings # Go to repo Settings → Pages → Custom domain Enter your domain (your-domain.com) and click Save GitHub runs a DNS check immediately — a green tick means it can see your A records This step also writes a CNAME entry to the Pages configuration on GitHub\u0026rsquo;s side, which is separate from the file in your repo. Both must match.\n7. Enforce HTTPS # After the DNS check passes, GitHub provisions a TLS certificate via Let\u0026rsquo;s Encrypt. This typically takes 5–30 minutes.\nOnce the cert is issued:\nReload repo Settings → Pages Check Enforce HTTPS From this point all HTTP requests redirect to HTTPS automatically. GitHub renews the cert before expiry — no manual renewal needed.\nVerification checklist # # DNS resolves correctly dig @8.8.8.8 your-domain.com A +short # → four 185.199.x.153 lines # Site responds over HTTPS curl -sI https://your-domain.com | head -3 # → HTTP/2 200 # No HTTP leakage (should redirect) curl -sI http://your-domain.com | head -3 # → HTTP/1.1 301 or 308 to https:// Common problems # DNS check fails in GitHub UI The A records exist but the check fails. Usually means the CNAME file is missing from the deployed output, or the domain in Settings doesn\u0026rsquo;t exactly match the CNAME file. Check both.\nSite serves at github.io URL but not the custom domain Custom domain not set in Settings → Pages, or DNS hasn\u0026rsquo;t propagated yet. Run dig to confirm records are live before blaming GitHub.\nHTTPS not available / cert pending indefinitely GitHub can\u0026rsquo;t verify domain ownership if the A records don\u0026rsquo;t point to its IPs, or if CAA records at your registrar block Let\u0026rsquo;s Encrypt. Check for a CAA record and either remove it or add letsencrypt.org as an allowed issuer.\nwww redirects but apex doesn\u0026rsquo;t (or vice versa) You need both the A records (apex) and the CNAME (www) configured. GitHub Pages will canonicalise to whichever you set as the custom domain and redirect the other.\nCost # Item Cost GitHub Pages Free TLS certificate Free (Let\u0026rsquo;s Encrypt via GitHub) Domain name Varies by registrar and TLD; typically $10–20/year for .com/.org ","date":"17 June 2026","externalUrl":null,"permalink":"/lab-notes/github-pages-dns-routing/","section":"Lab Notes","summary":"GitHub Pages is a free static hosting service built into every GitHub repository. Combined with a custom domain and a handful of DNS records, you can serve a production site with HTTPS for nothing beyond the cost of the domain name.\nThis note covers the full path: repo setup, Pages configuration, DNS records, and TLS enforcement.\nPrerequisites # A GitHub account and a repository containing your static site output (HTML/CSS/JS) A registered domain name managed at any DNS registrar (examples use Porkbun) gh CLI installed and authenticated (gh auth login) 1. Prepare the repository # Your repo needs to end up with built HTML at a known location. Two options:\n","title":"Hosting a Static Site on GitHub Pages with a Custom Domain","type":"lab-notes"},{"content":"","date":"17 June 2026","externalUrl":null,"permalink":"/lab-notes/","section":"Lab Notes","summary":"","title":"Lab Notes","type":"lab-notes"},{"content":"","date":"17 June 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"Welcome to the lab. This is where we publish the tooling, writeups, and half-finished experiments behind the tsx_cmd astrophotography workflow, The Moment print records, the print-farm rigs we run for Prints, and the workings of BadWolf Stellar Cafe.\nM51 — captured at the home observatory. If you\u0026rsquo;re here for hands-on setups and gotchas, head straight to the Lab Notes.\n","date":"17 June 2026","externalUrl":null,"permalink":"/","section":"ThetaSigma Labs","summary":"Welcome to the lab. This is where we publish the tooling, writeups, and half-finished experiments behind the tsx_cmd astrophotography workflow, The Moment print records, the print-farm rigs we run for Prints, and the workings of BadWolf Stellar Cafe.\nM51 — captured at the home observatory. If you’re here for hands-on setups and gotchas, head straight to the Lab Notes.\n","title":"ThetaSigma Labs","type":"page"},{"content":"","date":"10 June 2026","externalUrl":null,"permalink":"/tags/codesign/","section":"Tags","summary":"","title":"Codesign","type":"tags"},{"content":"","date":"10 June 2026","externalUrl":null,"permalink":"/tags/firewall/","section":"Tags","summary":"","title":"Firewall","type":"tags"},{"content":"","date":"10 June 2026","externalUrl":null,"permalink":"/tags/hugo/","section":"Tags","summary":"","title":"Hugo","type":"tags"},{"content":" Overview # The Hugo dev server worked on localhost but was unreachable on the Mac\u0026rsquo;s LAN IP (\u0026lt;mac-ip\u0026gt;), even with the correct --bind flag. Root cause: the macOS application firewall identifies apps by code signature, and Homebrew\u0026rsquo;s hugo binary is shipped completely unsigned — so even an explicit firewall allow rule could not attach to it. Fix: ad-hoc sign the binary.\nSymptom # hugo server -D --bind \u0026lt;mac-ip\u0026gt; --baseURL http://\u0026lt;mac-ip\u0026gt; http://localhost:1313 → works http://\u0026lt;mac-ip\u0026gt;:1313 → curl: (52) Empty reply from server TCP connects, then the connection dies with no HTTP response. Loopback traffic never crosses the application firewall, which is why localhost always worked.\nDiagnosis chain # Confirm Hugo is actually listening on the LAN IP:\nlsof -nP -iTCP:1313 -sTCP:LISTEN # hugo ... TCP \u0026lt;mac-ip\u0026gt;:1313 (LISTEN) Confirm the firewall is on:\n/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate # Firewall is enabled. (State = 1) Check the binary\u0026rsquo;s signature — this is the key step:\ncodesign -dv /usr/local/bin/hugo # /usr/local/bin/hugo: code object is not signed at all Adding a firewall allow rule (socketfilterfw --add / --unblockapp) reported Incoming connection to /usr/local/bin/hugo is permitted — but connections were still dropped. The rule exists; it just can\u0026rsquo;t bind to an unsigned executable.\nFix # Ad-hoc sign the real binary (the /usr/local/bin path is a symlink into the Cellar):\ncodesign -s - --force /usr/local/Cellar/hugo/0.161.1/bin/hugo Verify the signature took:\ncodesign -dv /usr/local/bin/hugo # Identifier=hugo-\u0026lt;hash\u0026gt; Restart the dev server (the firewall verdict is made when the process starts listening):\nhugo server -D --bind \u0026lt;mac-ip\u0026gt; --baseURL http://\u0026lt;mac-ip\u0026gt; Verify from the LAN:\ncurl -s -o /dev/null -w \u0026#34;%{http_code}\\n\u0026#34; http://\u0026lt;mac-ip\u0026gt;:1313/ # 200 Caveats # brew upgrade hugo undoes this. A new version means a new unsigned binary at a new Cellar path — re-run the codesign command (and re-check the firewall rule) after every upgrade.\nA firewall daemon in a stale state can drop traffic for all local services after rule changes. If unrelated ports stop responding on the LAN IP, restart it:\nsudo pkill socketfilterfw # launchd respawns it --bind 0.0.0.0 exposes the server on every interface; prefer binding the specific LAN IP for dev work.\n","date":"10 June 2026","externalUrl":null,"permalink":"/lab-notes/lab-note-hugo-macos-firewall-codesign/","section":"Lab Notes","summary":"Overview # The Hugo dev server worked on localhost but was unreachable on the Mac’s LAN IP (\u003cmac-ip\u003e), even with the correct --bind flag. Root cause: the macOS application firewall identifies apps by code signature, and Homebrew’s hugo binary is shipped completely unsigned — so even an explicit firewall allow rule could not attach to it. Fix: ad-hoc sign the binary.\n","title":"Lab Note: Signing Hugo so macOS Firewall Allows LAN Access to the Dev Server","type":"lab-notes"},{"content":"","date":"10 June 2026","externalUrl":null,"permalink":"/tags/macos/","section":"Tags","summary":"","title":"MacOS","type":"tags"},{"content":"","date":"10 June 2026","externalUrl":null,"permalink":"/tags/networking/","section":"Tags","summary":"","title":"Networking","type":"tags"},{"content":"","date":"4 June 2026","externalUrl":null,"permalink":"/tags/ci/cd/","section":"Tags","summary":"","title":"CI/CD","type":"tags"},{"content":"","date":"4 June 2026","externalUrl":null,"permalink":"/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":"","date":"4 June 2026","externalUrl":null,"permalink":"/tags/jenkins/","section":"Tags","summary":"","title":"Jenkins","type":"tags"},{"content":" Overview # A private Docker registry running on the Synology NAS at \u0026lt;nas-ip\u0026gt;, used by the Jenkins CI/CD pipeline to store built images and available for pulling to any LAN machine. Includes the joxit/docker-registry-ui browse interface.\nCompose file lives at /volume1/docker/registry/docker-compose.yml on the NAS.\nWhy This Is Useful # For CI/CD: Jenkins builds The Moment, pushes the image here, then pulls it on the ARM64 production node (Odroid N2+) to deploy. No GitHub or internet dependency in the build/deploy loop — the pipeline runs entirely on the LAN.\nFor personal use: Any machine on the home network can pull a known-good image directly. If The Moment needs to move to a different machine (e.g. a new Odroid, a spare PC, or a second NAS), a single docker pull \u0026lt;nas-ip\u0026gt;:5050/the-moment:latest gets the exact same build that Jenkins produced and tested — no rebuilding from source, no internet required.\nVersioned history: Every Jenkins build pushes a tagged image. Rolling back to a previous version is docker pull \u0026lt;nas-ip\u0026gt;:5050/the-moment:\u0026lt;build_number\u0026gt;. The browse UI at port 5051 shows all available tags at a glance so you can pick the right one.\nCompose File # services: registry: image: registry:2 container_name: registry restart: always ports: - \u0026#34;5050:5000\u0026#34; environment: - REGISTRY_HTTP_HEADERS_Access-Control-Allow-Origin=[\u0026#39;*\u0026#39;] - REGISTRY_HTTP_HEADERS_Access-Control-Allow-Methods=[\u0026#39;HEAD\u0026#39;,\u0026#39;GET\u0026#39;,\u0026#39;OPTIONS\u0026#39;,\u0026#39;DELETE\u0026#39;] - REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers=[\u0026#39;Authorization\u0026#39;,\u0026#39;Accept\u0026#39;,\u0026#39;Cache-Control\u0026#39;] - REGISTRY_HTTP_HEADERS_Access-Control-Expose-Headers=[\u0026#39;Docker-Content-Digest\u0026#39;] - REGISTRY_HTTP_HEADERS_Access-Control-Allow-Credentials=[true] - REGISTRY_STORAGE_DELETE_ENABLED=true volumes: - ./data:/var/lib/registry ui: image: joxit/docker-registry-ui:latest ports: - \u0026#34;5051:80\u0026#34; environment: - SINGLE_REGISTRY=true - NGINX_PROXY_PASS_URL=http://registry:5000 - REGISTRY_TITLE=NAS Registry - DELETE_IMAGES=true depends_on: - registry Registry API: http://\u0026lt;nas-ip\u0026gt;:5050 Browse UI: http://\u0026lt;nas-ip\u0026gt;:5051 Key Fix: NGINX_PROXY_PASS_URL vs REGISTRY_URL # The UI has two modes for reaching the registry:\nVariable How it works Result REGISTRY_URL Browser JS calls registry directly CORS error — browser blocks cross-origin requests NGINX_PROXY_PASS_URL nginx in the UI container proxies to registry No CORS — browser only talks to the UI on port 5051 Always use NGINX_PROXY_PASS_URL for a same-host setup. REGISTRY_URL requires configuring CORS headers on the registry itself and is unnecessary complexity.\nThe value must use the Docker service name (registry), not the NAS IP, so the container-to-container call stays on the internal Docker network:\nNGINX_PROXY_PASS_URL=http://registry:5000 ✓ NGINX_PROXY_PASS_URL=http://\u0026lt;nas-ip\u0026gt;:5050 ✗ (may not route correctly inside Docker) Quick API Browse (no UI needed) # Standard registry API endpoints work directly in a browser:\nhttp://\u0026lt;nas-ip\u0026gt;:5050/v2/_catalog # list all repos http://\u0026lt;nas-ip\u0026gt;:5050/v2/the-moment/tags/list # list tags for an image Pulling Images on Another LAN Machine # The registry is HTTP (not HTTPS). Docker treats it as insecure by default and will refuse to pull without explicit permission. One-time setup per machine:\nLinux # Edit or create /etc/docker/daemon.json:\n{ \u0026#34;insecure-registries\u0026#34;: [\u0026#34;\u0026lt;nas-ip\u0026gt;:5050\u0026#34;] } Restart Docker:\nsudo systemctl restart docker macOS (Docker Desktop) # Open Docker Desktop → Settings → Docker Engine tab. Add the key to the JSON config:\n{ \u0026#34;insecure-registries\u0026#34;: [\u0026#34;\u0026lt;nas-ip\u0026gt;:5050\u0026#34;] } Click Apply \u0026amp; Restart.\nWindows (Docker Desktop) # Same as macOS: Docker Desktop → Settings → Docker Engine, add the same JSON key, then Apply \u0026amp; Restart.\nWindows (Docker Engine without Desktop) # Edit C:\\ProgramData\\docker\\config\\daemon.json (create it if missing):\n{ \u0026#34;insecure-registries\u0026#34;: [\u0026#34;\u0026lt;nas-ip\u0026gt;:5050\u0026#34;] } Restart the Docker service:\nnet stop docker \u0026amp;\u0026amp; net start docker Pulling and using the image # Once the insecure registry is configured, pull and run normally:\ndocker pull \u0026lt;nas-ip\u0026gt;:5050/the-moment:latest In a docker-compose.yml on the target machine, replace the image line:\nservices: the-moment: image: \u0026lt;nas-ip\u0026gt;:5050/the-moment:latest # ... rest of compose config unchanged ... Then docker compose up -d — Docker pulls from the NAS registry instead of GHCR.\nDeleting Images via the UI # The UI delete button (trashcan) calls the registry DELETE /v2/\u0026lt;name\u0026gt;/manifests/\u0026lt;digest\u0026gt; API. Two things must both be true for it to work:\n1. Delete must be enabled on the registry — it is off by default.\n- REGISTRY_STORAGE_DELETE_ENABLED=true Gotcha: the value must be the bare word true, not [true]. The bracket notation is only for list-type headers (Allow-Origin, Allow-Methods, etc.). Using [true] passes the literal string [true] to the registry, which it does not recognise as truthy — delete silently stays disabled and the UI returns \u0026ldquo;Operation not supported\u0026rdquo;.\n2. CORS headers must expose Docker-Content-Digest — the UI reads the digest from the response header to build the delete URL. Without Access-Control-Expose-Headers including Docker-Content-Digest, the browser blocks the header and the UI has nothing to delete.\nBoth are included in the compose file above.\nGarbage collection after deletion # Deleting a manifest removes the reference but the blobs (layers) remain on disk until garbage collection runs:\ndocker exec registry registry garbage-collect /etc/docker/registry/config.yml Run this after bulk deletes to reclaim space. The registry must be running when you run it (garbage collection is online-safe in registry:2).\nJenkins Integration # Jenkins pushes to this registry after each successful build. The image tag follows the build number: \u0026lt;nas-ip\u0026gt;:5050/the-moment:\u0026lt;BUILD_NUMBER\u0026gt; and latest.\nThe Jenkins controller and all agents must also have \u0026lt;nas-ip\u0026gt;:5050 in their insecure-registries list, or Docker push/pull steps will fail with a TLS error.\nMulti-arch images: --platform is required on both build agents # The pipeline builds linux/arm64 on the ARM64 agent and linux/amd64 on the Windows agent, then combines them into a multi-arch manifest with docker buildx imagetools create.\nThe ARM64 build must include --platform linux/arm64 explicitly:\ndocker build --platform linux/arm64 --target production \\ -t \u0026lt;nas-ip\u0026gt;:5050/the-moment:\u0026lt;TAG\u0026gt;-arm64 . Without --platform, Docker builds for the native host but does not record the platform metadata reliably enough for buildx imagetools create to identify it as linux/arm64. The manifest ends up showing only linux/amd64. Adding the flag fixes it.\n","date":"4 June 2026","externalUrl":null,"permalink":"/lab-notes/lab-note-docker-registry-nas/","section":"Lab Notes","summary":"Overview # A private Docker registry running on the Synology NAS at \u003cnas-ip\u003e, used by the Jenkins CI/CD pipeline to store built images and available for pulling to any LAN machine. Includes the joxit/docker-registry-ui browse interface.\nCompose file lives at /volume1/docker/registry/docker-compose.yml on the NAS.\nWhy This Is Useful # For CI/CD: Jenkins builds The Moment, pushes the image here, then pulls it on the ARM64 production node (Odroid N2+) to deploy. No GitHub or internet dependency in the build/deploy loop — the pipeline runs entirely on the LAN.\n","title":"Lab Note: Docker Registry on Synology NAS with Browse UI","type":"lab-notes"},{"content":"","date":"4 June 2026","externalUrl":null,"permalink":"/tags/registry/","section":"Tags","summary":"","title":"Registry","type":"tags"},{"content":"","date":"4 June 2026","externalUrl":null,"permalink":"/tags/synology/","section":"Tags","summary":"","title":"Synology","type":"tags"},{"content":"","date":"4 June 2026","externalUrl":null,"permalink":"/tags/the-moment/","section":"Tags","summary":"","title":"The Moment","type":"tags"},{"content":"","date":"3 June 2026","externalUrl":null,"permalink":"/tags/buddy3d-camera/","section":"Tags","summary":"","title":"Buddy3D Camera","type":"tags"},{"content":"","date":"3 June 2026","externalUrl":null,"permalink":"/tags/camera-api/","section":"Tags","summary":"","title":"Camera API","type":"tags"},{"content":"Overview # Investigation into whether the Prusa Buddy3D Camera can be used to capture print-end snapshots locally via PrusaLink for The Moment print history. Triggered by implementing a camera snapshot feature that correctly handled the API response but produced no images on a real print.\nSetup # Component Detail Printer Prusa Core One L Printer firmware 6.5.3+12780 PrusaLink API version 2.0.0 Camera Buddy3D Camera (Guardian PR1), firmware 3.1.3 Camera connectivity Wi-Fi 2.4 GHz, own IP address on LAN What Was Implemented # The Moment\u0026rsquo;s camera snapshot feature queries GET /api/v1/cameras on the printer, then calls GET /api/v1/cameras/{id}/snap to capture a JPEG. This endpoint exists in the PrusaLink OpenAPI spec and was confirmed in the spec for the Raspberry Pi-based PrusaLink (used on MK3/MK4/MK3.9 printers).\nFindings # /api/v1/cameras returns 404 on Core One L # The endpoint is not implemented in the Core One L\u0026rsquo;s Buddy board firmware. The Buddy board exposes a PrusaLink-compatible API, but camera management is not part of it because the Buddy3D Camera is a separate network device, not a USB camera attached to the printer\u0026rsquo;s board.\nGET http://\u0026lt;printer-ip\u0026gt;/api/v1/cameras → 404 Not Found This is not a bug in The Moment. The code correctly treats 404 as \u0026ldquo;no camera\u0026rdquo; and silently no-ops. No crash, no error, no snapshot.\nBuddy3D Camera architecture # The Buddy3D Camera (Guardian PR1) is a standalone Wi-Fi camera with its own IP address. It is not proxied through the printer\u0026rsquo;s PrusaLink API.\nAccess method Available Notes GET /api/v1/cameras on printer ❌ No Printer firmware does not implement it HTTP snapshot endpoint on camera ❌ No No HTTP API on the camera itself RTSP stream ✅ Yes rtsp://\u0026lt;camera-ip\u0026gt;/live, port 554, LAN only PrusaConnect cloud snapshots ✅ Yes Automatic, every 10–90 s, requires cloud auth ONVIF / REST API on camera ❌ None native go2rtc or similar proxy required RTSP stream # The camera exposes an unencrypted RTSP stream on the local network when enabled via the Prusa app:\nrtsp://\u0026lt;camera-ip\u0026gt;/live rtsp://admin:admin@\u0026lt;camera-ip\u0026gt;:554/live (alternate format seen in the wild) A still frame can be captured from this stream using ffmpeg or a Go RTSP library, but requires knowing the camera\u0026rsquo;s separate IP address.\nOptions Going Forward # Option A — Add configurable snapshot URL per printer (recommended) # Add an optional CameraSnapshotURL field to the printer config. If set, The Moment fetches from that URL instead of (or in addition to) trying /api/v1/cameras. Supports:\nAny HTTP/HTTPS endpoint returning a JPEG RTSP via ffmpeg exec or Go RTSP library Users with the Buddy3D Camera configure the RTSP URL. The existing PrusaLink camera API fallback remains active for Pi-based printers.\nOption B — Wait for firmware update # Prusa may add /api/v1/cameras support to Buddy board firmware in a future version. If/when they do, The Moment\u0026rsquo;s existing code activates automatically. No action required today.\nReferences # PrusaLink OpenAPI spec — cameras Buddy3D Camera Knowledge Base Buddy3D Camera firmware 3.1.0 forum thread Buddy3D Camera REST API discussion Prusa Buddy3D Cameras and UniFi Protect ","date":"3 June 2026","externalUrl":null,"permalink":"/lab-notes/lab-note-prusa-buddy3d-camera-snapshot/","section":"Lab Notes","summary":"Overview # Investigation into whether the Prusa Buddy3D Camera can be used to capture print-end snapshots locally via PrusaLink for The Moment print history. Triggered by implementing a camera snapshot feature that correctly handled the API response but produced no images on a real print.\n","title":"Lab Note: Prusa Buddy3D Camera — Local Snapshot Feasibility","type":"lab-notes"},{"content":"","date":"3 June 2026","externalUrl":null,"permalink":"/tags/prusa-core-one/","section":"Tags","summary":"","title":"Prusa Core One","type":"tags"},{"content":"","date":"3 June 2026","externalUrl":null,"permalink":"/tags/prusalink/","section":"Tags","summary":"","title":"PrusaLink","type":"tags"},{"content":"","date":"2 June 2026","externalUrl":null,"permalink":"/tags/go/","section":"Tags","summary":"","title":"Go","type":"tags"},{"content":" Overview # Notes for setting up Jenkins build agents across all three lab nodes (Oroid N2+/arm64, Beelink/Windows, MiniMac/macOS). Covers Go installation per platform, MinGW gcc on Windows for CGO builds, and running the Jenkins agent as a persistent Windows service via NSSM.\nInstalling Go # Windows — beelink # winget install -e --id GoLang.Go Open a new cmd prompt after install (PATH won\u0026rsquo;t update in the existing window).\ngo version Expect: go version go1.24.x windows/amd64\nCheck the module cache paths are writable. This matters if the Jenkins agent runs as a service account rather than the interactive user:\ngo env GOPATH go env GOCACHE Both should point inside the service account\u0026rsquo;s profile. If not, set GOPATH and GOCACHE explicitly in the Jenkins node environment variables.\nmacOS — miniMac # brew install go Or download the .pkg installer from https://go.dev/dl/ and run it.\ngo version Linux ARM64 — ODroid N2+ # Download the ARM64 tarball from https://go.dev/dl/ (filename: go1.24.x.linux-arm64.tar.gz).\nsudo rm -rf /usr/local/go sudo tar -C /usr/local -xzf go1.24.x.linux-arm64.tar.gz Add to PATH permanently:\necho \u0026#39;export PATH=/usr/local/go/bin:$PATH\u0026#39; | sudo tee /etc/profile.d/go.sh sudo chmod +x /etc/profile.d/go.sh source /etc/profile.d/go.sh go version Jenkins caveat: The Jenkins agent process does not source /etc/profile.d/. The Go binary path must be hardcoded in the Jenkinsfile environment block:\nenvironment { PATH = \u0026#39;/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\u0026#39; } Installing MinGW-w64 gcc on Windows — beelink # Required for mattn/go-sqlite3 which uses CGO_ENABLED=1 and needs a C compiler. Pure-Go SQLite alternatives (e.g. modernc.org/sqlite) would avoid this, but The Moment uses mattn/go-sqlite3.\nOption A — Chocolatey (simplest):\nchoco install mingw Option B — MSYS2:\nwinget install -e --id MSYS2.MSYS2 Then in the MSYS2 MinGW64 shell:\npacman -S mingw-w64-x86_64-gcc Add C:\\msys64\\mingw64\\bin to the system PATH (not user PATH — the Jenkins service account needs it).\nVerify:\ngcc --version Expect: gcc (x86_64-posix-seh-rev..., Built by MinGW-W64...) 13.x\nRunning the Jenkins Agent as a Windows Service — NSSM # NSSM (Non-Sucking Service Manager) wraps any executable as a proper Windows service with restart-on-failure and log capture. Much cleaner than Task Scheduler for a Jenkins agent.\n1. Install Java 21 (Temurin) # Download the Windows x64 MSI from https://adoptium.net/temurin/releases/?version=21\nRun the installer. On the optional features screen, tick:\nAdd to PATH Set JAVA_HOME variable Verify:\njava -version Expect: openjdk version \u0026quot;21.x.x\u0026quot; ... Temurin-21\n2. Install NSSM # Download from https://nssm.cc/download — get the latest release zip.\nExtract to C:\\tools\\nssm. Add C:\\tools\\nssm\\win64 to the system PATH.\nVerify:\nnssm version 3. Download agent.jar # In Jenkins UI: Manage Jenkins → Nodes → beelink → Agent (or the node\u0026rsquo;s status page). The page shows a launch command containing the secret and a download link for agent.jar.\nSave to: C:\\jenkins-agent\\agent.jar\nAlso create the log directory:\nmkdir C:\\jenkins-agent\\logs 4. Create the service # nssm install JenkinsAgent This opens a GUI. Fill in the Application tab:\nField Value Path C:\\Program Files\\Eclipse Adoptium\\jdk-21.x.x.x-hotspot\\bin\\java.exe Startup directory C:\\jenkins-agent Arguments -jar agent.jar -url http://\u0026lt;jenkins-ip\u0026gt;:8085 -name \u0026lt;agent-name\u0026gt; -secret \u0026lt;SECRET\u0026gt; -workDir C:\\jenkins-agent Switch to the I/O tab:\nField Value Stdout C:\\jenkins-agent\\logs\\stdout.log Stderr C:\\jenkins-agent\\logs\\stderr.log Click Install service.\n5. Start and verify # nssm start JenkinsAgent Check Jenkins UI: Manage Jenkins → Nodes → beelink — the node should show as online within a few seconds.\nCheck the logs if it doesn\u0026rsquo;t connect:\ntype C:\\jenkins-agent\\logs\\stderr.log NSSM quick-reference # nssm status JenkinsAgent # Running / Stopped / paused nssm restart JenkinsAgent # Restart the service nssm stop JenkinsAgent # Stop without removing nssm edit JenkinsAgent # Re-open the GUI to change settings nssm remove JenkinsAgent confirm # Permanently remove the service To update the agent secret (e.g. after re-registering the node in Jenkins):\nnssm edit JenkinsAgent Change the Arguments field, save, then nssm restart JenkinsAgent.\n","date":"2 June 2026","externalUrl":null,"permalink":"/lab-notes/lab-note-jenkins-agent-go-install/","section":"Lab Notes","summary":"Overview # Notes for setting up Jenkins build agents across all three lab nodes (Oroid N2+/arm64, Beelink/Windows, MiniMac/macOS). Covers Go installation per platform, MinGW gcc on Windows for CGO builds, and running the Jenkins agent as a persistent Windows service via NSSM.\nInstalling Go # Windows — beelink # winget install -e --id GoLang.Go Open a new cmd prompt after install (PATH won’t update in the existing window).\n","title":"Lab Note: Jenkins Agent Setup — Go, MinGW, and NSSM on Windows","type":"lab-notes"},{"content":"","date":"2 June 2026","externalUrl":null,"permalink":"/tags/nssm/","section":"Tags","summary":"","title":"NSSM","type":"tags"},{"content":"","date":"2 June 2026","externalUrl":null,"permalink":"/tags/windows/","section":"Tags","summary":"","title":"Windows","type":"tags"},{"content":"","date":"27 May 2026","externalUrl":null,"permalink":"/tags/filament-tracking/","section":"Tags","summary":"","title":"Filament Tracking","type":"tags"},{"content":" Overview # This note documents the process for initializing and writing NFC tags for use with The Moment and Prusa Core One filament tracking. It covers the correct tag types, apps required, and the initialization workaround needed for blank NFC-V stickers on iOS.\nTag Types # Two tag types have been confirmed working:\nNFC-V (ISO 15693 / ICODE SLIX2) — Required for Prusa Core One native NFC reader. Longer read range. Use for filament spools on the Core One. NTAG215 — Standard NFC tag. Confirmed working for app-based reading and writing. Suitable for location tags and non-Prusa filament use cases. Use Cases # Use Case Tag Type Prusa Core One filament spool NFC-V (ICODE SLIX2) Location tags (printer stations, storage) NTAG215 or NFC-V App-only filament tracking (no printer reader) NTAG215 Apps Required # NFC.cool # Platform: iOS and Android Cost: Free for OpenPrintTag feature (version 6.3.0+) Function: Read and write OpenPrintTag records to NFC tags Path in app: NFC Apps → OpenPrintTag Notes: Cannot write to blank unformatted NFC-V tags. Tags must be initialized first (see below). Serialio NFC Read Write # Platform: iOS only Cost: Free with in-app purchases Function: Can write NDEF records to blank, unformatted ISO 15693 (NFC-V) tags — something most iOS NFC apps cannot do Use: One-time initialization of blank NFC-V tags only NFC Tools # Platform: iOS and Android Cost: Free (paid Pro version available) Function: Read and write NDEF tags after initialization. Confirmed working once a tag has been initialized. The Problem: Blank NFC-V Tags on iOS # Factory-blank NFC-V tags ship without an NDEF structure. iOS NFC apps (including NFC.cool and NFC Tools) cannot detect or write to them in this state. The tag appears either invisible or \u0026ldquo;read only.\u0026rdquo;\nThis is an iOS limitation with unformatted ISO 15693 tags.\nFix: Initialize with Serialio First # This is a one-time step per tag.\nOpen Serialio NFC Read Write Tap Write NDEF Select record type Text Enter any value — hello or hello world is sufficient Hold the blank NFC-V sticker flat against the top edge of your iPhone Hold still for 2–3 seconds until the write confirms The tag now has a valid NDEF structure.\nAfter Initialization # Once initialized, the tag works normally with both NFC.cool and NFC Tools.\nTo write OpenPrintTag data:\nOpen NFC.cool Go to NFC Apps → OpenPrintTag Fill in filament fields (material, brand, color, weight, etc.) Tap NFC button — this overwrites the hello text record with OpenPrintTag data To read or update later:\nNFC.cool, NFC Tools, or the Prusa app can all read the tag NFC.cool can update individual fields (e.g. remaining weight after a print) OpenPrintTag Fields (Reference) # Field Notes Material type PLA, PETG, ASA, TPU, etc. Brand e.g. Polymaker, Prusament Color Supports up to 6 colors per spool Weight remaining Update after each print Batch / lot Optional Custom notes Free text field Integration Notes for The Moment # NFC tag UID can serve as the spool identifier linking the physical tag to a Spoolman spool record Location tags (NTAG215 or NFC-V) can encode a plain text or URI record identifying the printer station or storage slot Tag scan events can be logged as print start / end markers in The Moment\u0026rsquo;s print history Both NFC-V and NTAG215 are readable by iPhone without any additional hardware Known Issues and Tips # If NFC.cool still says \u0026ldquo;read only\u0026rdquo; after Serialio initialization, try using NFC Tools → Other → Erase Tag, then retry with NFC.cool Hold the tag flat and still against the very top edge of the iPhone during any write operation NFC-V tags have a longer read range than NTAG tags — better for printer-mounted spool detection NTAG215 tags are cheaper and more widely available for location and general-purpose use References # OpenPrintTag standard NFC.cool OpenPrintTag support Prusa Forum: How to write an OpenPrintTag Serialio NFC Read Write iOS app ","date":"27 May 2026","externalUrl":null,"permalink":"/lab-notes/lab-note-nfc-tag-setup/","section":"Lab Notes","summary":"Overview # This note documents the process for initializing and writing NFC tags for use with The Moment and Prusa Core One filament tracking. It covers the correct tag types, apps required, and the initialization workaround needed for blank NFC-V stickers on iOS.\nTag Types # Two tag types have been confirmed working:\n","title":"Lab Note: NFC Tag Setup for Filament and Location Tracking","type":"lab-notes"},{"content":"","date":"27 May 2026","externalUrl":null,"permalink":"/tags/nfc/","section":"Tags","summary":"","title":"NFC","type":"tags"},{"content":"","date":"27 May 2026","externalUrl":null,"permalink":"/tags/openprinttag/","section":"Tags","summary":"","title":"OpenPrintTag","type":"tags"},{"content":"docker-compose.dev.yml — added build: context: . so Compose can rebuild the image itself with --build, no separate docker build needed.\n.vscode/tasks.json — 5 new tasks added:\nTask What it does Docker: Build Dev Image Standalone build + prune dangling Docker: Start Dev Stack Start stack using existing local image Docker: Build and Restart Dev Stack Primary task — rebuild, restart, prune Docker: Stop Dev Stack Bring the whole dev stack down Docker: Logs Stream the-moment logs in a dedicated panel Daily driver: Terminal → Run Task → Docker: Build and Restart Dev Stack. This is the replacement for your manual docker build command — it rebuilds the image, prunes the dangling old one, and restarts the container in one step.\nTip: The Docker VSCode extension (ms-azuretools.vscode-docker) adds a visual Docker Explorer panel with compose group management and inline log viewing — worth installing as a complement to these tasks.\n","date":"16 May 2026","externalUrl":null,"permalink":"/lab-notes/docker_dev_vscode_improvements/","section":"Lab Notes","summary":"docker-compose.dev.yml — added build: context: . so Compose can rebuild the image itself with --build, no separate docker build needed.\n.vscode/tasks.json — 5 new tasks added:\nTask What it does Docker: Build Dev Image Standalone build + prune dangling Docker: Start Dev Stack Start stack using existing local image Docker: Build and Restart Dev Stack Primary task — rebuild, restart, prune Docker: Stop Dev Stack Bring the whole dev stack down Docker: Logs Stream the-moment logs in a dedicated panel Daily driver: Terminal → Run Task → Docker: Build and Restart Dev Stack. This is the replacement for your manual docker build command — it rebuilds the image, prunes the dangling old one, and restarts the container in one step.\n","title":"Docker_dev_vscode_improvements","type":"lab-notes"},{"content":"","date":"16 May 2026","externalUrl":null,"permalink":"/tags/vscode/","section":"Tags","summary":"","title":"Vscode","type":"tags"},{"content":"","date":"16 May 2026","externalUrl":null,"permalink":"/tags/git/","section":"Tags","summary":"","title":"Git","type":"tags"},{"content":" Option A — Squash all commits into one before pushing # git checkout --orphan release git add -A git commit -m \u0026#34;Beta_1.0.1\u0026#34; git branch -D main git branch -m main git push origin main --force This replaces the entire history with a single clean commit.\nOption B — GitHub Releases (tag-based, history stays) # If the goal is just to publish a versioned release ZIP for users to download (not hide commits), use GitHub Releases:\ngit tag v1.0.1 git push origin v1.0.1 Then on GitHub: Releases → Draft a new release → pick the tag → publish. GitHub auto-generates a source zip. This is the conventional open-source approach.\nReference # git checkout --orphan release Creates a new branch called release with no history — no parent commits. Existing files are kept in the working tree but the branch starts completely empty.\ngit add -A Stages all current files (adds everything to the index ready to commit).\ngit commit -m \u0026quot;Beta_1.0.1\u0026quot; Creates a single fresh commit with all your files and the message Beta_1.0.1. This is now the entire history — one commit, no WIP noise.\ngit branch -D main Force-deletes the old main branch (which still has all the messy history). The -D is needed because release and main have diverged histories.\ngit branch -m main Renames the current branch (release) to main. Now main points to your single clean commit.\ngit push origin main --force Force-pushes the new clean main to GitHub, overwriting the remote\u0026rsquo;s history. \u0026ndash;force is required because the histories are incompatible — you\u0026rsquo;re replacing, not extending.\n","date":"16 May 2026","externalUrl":null,"permalink":"/lab-notes/github_push_and_tag/","section":"Lab Notes","summary":"Option A — Squash all commits into one before pushing # git checkout --orphan release git add -A git commit -m \"Beta_1.0.1\" git branch -D main git branch -m main git push origin main --force This replaces the entire history with a single clean commit.\nOption B — GitHub Releases (tag-based, history stays) # If the goal is just to publish a versioned release ZIP for users to download (not hide commits), use GitHub Releases:\n","title":"Github_push_and_tag","type":"lab-notes"},{"content":" User scope # ~/.claude/settings.json (user scope — applies to all projects)\n\u0026lt;project_folder\u0026gt;/.claude/settings.local.json (user scope — applies only to the project_folder)\nWhy it\u0026rsquo;s there # It\u0026rsquo;s an explicit override you (or /model) set at some point — it is not a default. Without that line, Claude Code picks a model based on your plan/account. With it, every session in every project starts on Opus.\n\u0026quot;opus\u0026quot; is a family alias, not a pinned version. It always resolves to the newest Opus available to you — today that\u0026rsquo;s Opus 4.7 (claude-opus-4-7). So you automatically move up when a new Opus ships, without editing this file. (You pin a version instead — see below.)\nWhat the values can be # Value form Example Behavior Family alias \u0026quot;opus\u0026quot;, \u0026quot;sonnet\u0026quot;, \u0026quot;haiku\u0026quot; Always the latest in that family Version prefix \u0026quot;opus-4-7\u0026quot; Pins to that version line Full model ID \u0026quot;claude-sonnet-4-6\u0026quot; Exact model, no auto-upgrade The three families (current generation, Claude 4.X):\nOpus — claude-opus-4-7 — most capable, what you\u0026rsquo;re on now Sonnet — claude-sonnet-4-6 — balanced speed/capability Haiku — claude-haiku-4-5-20251001 — fastest, cheapest, lightest Separately: Fast mode (/fast) keeps you on Opus but with faster output — it does not downgrade to a smaller model. It\u0026rsquo;s a different axis from model selection. How to manage it # /model — interactive picker, the normal way to switch. Recommended. /config — general settings UI; model is one of the simple settings it exposes. claude --model sonnet — per-session override from the CLI, ignores the settings.json value for that run only. Edit ~/.claude/settings.json — what\u0026rsquo;s there now; this is the user (global, all-projects) scope. Precedence (later wins): user (~/.claude/settings.json) → project (.claude/settings.json) → local (.claude/settings.local.json) → \u0026ndash;model flag → managed/enterprise policy. A .claude/settings.json in a specific repo can override your global Opus default for just that project. To remove the global pin and fall back to the account default, just delete the \u0026quot;model\u0026quot;: \u0026quot;opus\u0026quot; line (mind the trailing comma on the line above it).\n","date":"15 May 2026","externalUrl":null,"permalink":"/lab-notes/working_with_claude_and_vscode/","section":"Lab Notes","summary":"User scope # ~/.claude/settings.json (user scope — applies to all projects)\n\u003cproject_folder\u003e/.claude/settings.local.json (user scope — applies only to the project_folder)\nWhy it’s there # It’s an explicit override you (or /model) set at some point — it is not a default. Without that line, Claude Code picks a model based on your plan/account. With it, every session in every project starts on Opus.\n","title":"Working_with_claude_and_vscode","type":"lab-notes"},{"content":"ThetaSigma Labs is a home lab building open-source tools for astrophotography, 3D printing, and creative making.\nThe name, ThetaSigma (ΘΣ), is a reference to Doctor Who\u0026rsquo;s real name d³Σx². Canonically, the Doctor has been referred to as Theta Sigma, the Doctor\u0026rsquo;s nickname at the Academy on Gallifrey.\nThe personal dome observatory where images are captured is BadWolf Stellar Cafe — also a nod to The Doctor, and a tribute to the coffee that fuels long imaging sessions.\nWhat\u0026rsquo;s here # Projects — tsx-cmd (soon), astrophotography session planner and full-observatory automation; and The Moment, filament tracking, print history, and cost accounting Models (soon) — airbrushed scale models from the workshop, published on Printables Prints (soon) — original 3D print designs from the workshop, published on Printables Astro (soon) — the gear, dome, and workings of BadWolf Stellar Cafe Gallery (soon) — astrophotography from the observatory, catalogued on Astrobin Lab Notes — setup notes, docker commands, code snippets, and lessons from the bench All software is open source. Source on GitHub.\n","date":"14 May 2026","externalUrl":null,"permalink":"/about/","section":"ThetaSigma Labs","summary":"ThetaSigma Labs is a home lab building open-source tools for astrophotography, 3D printing, and creative making.\nThe name, ThetaSigma (ΘΣ), is a reference to Doctor Who’s real name d³Σx². Canonically, the Doctor has been referred to as Theta Sigma, the Doctor’s nickname at the Academy on Gallifrey.\nThe personal dome observatory where images are captured is BadWolf Stellar Cafe — also a nod to The Doctor, and a tribute to the coffee that fuels long imaging sessions.\n","title":"About","type":"page"},{"content":" Use a Shell function (recommended — zero dependencies) # Add this to ~/.zshrc:\nlabnote() { local slug=\u0026#34;${1:-}\u0026#34; if [[ -z \u0026#34;$slug\u0026#34; ]]; then read \u0026#34;slug?Lab note slug: \u0026#34; fi slug=\u0026#34;${slug// /_}\u0026#34; # spaces → underscores (cd ~/code/thetasigma-site \u0026amp;\u0026amp; hugo new \u0026#34;lab-notes/${slug}.md\u0026#34; \u0026amp;\u0026amp; code \u0026#34;content/lab-notes/${slug}.md\u0026#34;) } Then labnote thetasigma_labs_setup or just labnote and it prompts you. Opens it in VSCode immediately. This is almost certainly what you want — it\u0026rsquo;s a 30-second setup and you never leave the terminal.\nSetup to create lab notes # The code command isn\u0026rsquo;t installed by default on macOS — VSCode ships it but you have to enable it. Two ways:\nOption 1: From inside VSCode (easiest) # Open VSCode ⌘⇧P to open the command palette Type: Shell Command: Install \u0026lsquo;code\u0026rsquo; command in PATH Hit enter — it adds a symlink to /usr/local/bin/code Then source ~/.zshrc (or open a new terminal) and code will work. Option 2: Add it to PATH manually # If option 1 doesn\u0026rsquo;t appear or fails, add this to ~/.zshrc:\nexport PATH=\u0026#34;/Applications/Visual Studio Code.app/Contents/Resources/app/bin:$PATH\u0026#34; Reload with source ~/.zshrc. `` ","date":"14 May 2026","externalUrl":null,"permalink":"/lab-notes/automate_entry_with_code/","section":"Lab Notes","summary":"Use a Shell function (recommended — zero dependencies) # Add this to ~/.zshrc:\nlabnote() { local slug=\"${1:-}\" if [[ -z \"$slug\" ]]; then read \"slug?Lab note slug: \" fi slug=\"${slug// /_}\" # spaces → underscores (cd ~/code/thetasigma-site \u0026\u0026 hugo new \"lab-notes/${slug}.md\" \u0026\u0026 code \"content/lab-notes/${slug}.md\") } Then labnote thetasigma_labs_setup or just labnote and it prompts you. Opens it in VSCode immediately. This is almost certainly what you want — it’s a 30-second setup and you never leave the terminal.\n","title":"Automate HUGO labnotes","type":"lab-notes"},{"content":"","date":"14 May 2026","externalUrl":null,"permalink":"/tags/how-to/","section":"Tags","summary":"","title":"How-To","type":"tags"},{"content":" Setup # register domain name\ncreate github org\nbrew install hugo\nCreate the site\nmkdir thetasigma-site cd thetasigma-site hugo new site . git init Instal VSCode extensions:\nFor making markdown look more like a document than a text file in VSCode, here are the best options # Markdown All in One (yzhang.markdown-all-in-one) — The most popular all-rounder. Better syntax highlighting, auto-formatting, table of contents, keyboard shortcuts for bold/italic, and improved list handling.\nMarkdown Preview Enhanced (shd101wyy.markdown-preview-enhanced) — Side-by-side preview that renders like a real document with proper typography, math (KaTeX), diagrams (Mermaid), and export to PDF/HTML. Best for \u0026ldquo;make it look like a document.\u0026rdquo;\nMarp for VS Code (marp-team.marp-vscode) — If you want slide-deck style rendering.\nFor editor appearance (not preview):\nMarkdown Editor (zaaack.markdown-editor) — Replaces the text editor with a WYSIWYG view, so headings actually look like headings, not # text. Front Matter CMS (eliostruyf.vscode-front-matter) — Particularly useful for your Hugo site, since it understands the frontmatter in files like content/lab-notes/_index.md and gives a CMS-like editing UI. My recommendation: install Markdown All in One + Markdown Preview Enhanced together. If you want the editor itself (not a preview pane) to look document-like, add Markdown Editor . Given you\u0026rsquo;re working in a Hugo site, Front Matter CMS is worth a look too.\n","date":"14 May 2026","externalUrl":null,"permalink":"/lab-notes/thetasigma_labs_setup/","section":"Lab Notes","summary":"Setup # register domain name\ncreate github org\nbrew install hugo\nCreate the site\nmkdir thetasigma-site cd thetasigma-site hugo new site . git init Instal VSCode extensions:\nFor making markdown look more like a document than a text file in VSCode, here are the best options # Markdown All in One (yzhang.markdown-all-in-one) — The most popular all-rounder. Better syntax highlighting, auto-formatting, table of contents, keyboard shortcuts for bold/italic, and improved list handling.\n","title":"Thetasigma_labs_setup","type":"lab-notes"},{"content":"","date":"14 May 2026","externalUrl":null,"permalink":"/lab-notes/navismo-ender3/","section":"Lab Notes","summary":"","title":"Navismo Ender3","type":"lab-notes"},{"content":"","date":"1 June 2024","externalUrl":null,"permalink":"/gallery/m51/","section":"Gallery","summary":"","title":"M51 — Whirlpool Galaxy","type":"gallery"},{"content":" Setting Up a Single Extruder as a Multi-Extruder in PrusaSlicer # Printer: Prusa Core One L Slicer: PrusaSlicer Use case: Flush inlay lettering (black body with white letters) using a manual filament swap Status: Verified working\nPurpose # PrusaSlicer has two workflows for two-color prints on a single-nozzle printer:\nColor Change — uses the layer slider and the + icon. Inserts M600 directly. Best when every layer is one color only. Single Extruder Multi Material (multi-extruder mode) — assigns parts of the model to \u0026ldquo;extruder 1\u0026rdquo; and \u0026ldquo;extruder 2\u0026rdquo;. This is needed when a feature must be split out by object or modifier, not just by Z height. This note covers Option 2, because the lettering inlay was set up as a separate object/modifier and assigned to a second extruder.\nOut of the box, Option 2 does not work on a single-nozzle printer:\nThe slicer outputs T0 / T1 tool change commands, which the Core One firmware ignores A purge tower is generated and printed, wasting time and filament The printer never pauses to ask for a filament swap The steps below fix all three problems.\nModel Design Requirements # First layer height: 0.2 mm Insert depth: 2 layers (0.4 mm total at 0.2 mm layer height) The inlay region must be modeled flush with the surrounding surface so the white filament fills the recess PrusaSlicer Setup Steps # Do these in order. Each one matters.\n1. Configure the printer for two extruders # Open Printer Settings tab Go to General in the left sidebar Set Extruders = 2 Check Single Extruder Multi Material 2. Add M600 to the Tool change G-code # Still in Printer Settings, go to Custom G-code in the left sidebar In the Tool change G-code field, type: M600 ; Filament change Important: this must go in Tool change G-code, not in Color Change G-code. The Color Change field only fires when an \u0026ldquo;Add color change\u0026rdquo; marker is placed via the layer slider. The Tool change field is what fires when the slicer emits a T0 or T1.\n3. Fix the Single Extruder MM setup numbers # In Printer Settings, go to Single extruder MM setup Set: Cooling tube position = 10 Filament parking position = 0 Extra loading distance = 0 The default values in this section are tuned for a real MMU and cause a large blob during manual swaps. These numbers stop that.\n4. Disable the wipe tower # Open Print Settings tab Go to Multiple Extruders in the left sidebar Under Wipe tower, uncheck Enable Also uncheck Prime all printing extruders if it is ticked 5. Save the profile # Click the small disk icon next to the printer profile name at the top\nSave as a custom name (for example Core One L — manual two-color)\nPrusa system profiles cannot be overwritten, so this must be a custom profile\nRepeat for the print profile if any Print Settings were changed\n6. Force a re-slice # Click (Re)Slice Now in the bottom-right of the Plater Do not rely on the cached slice — settings changes are not always reflected until the slice is forced 7. Assign filaments # In the Plater, the right-hand filament dropdowns now show two slots Set both to the filament type you are printing (for example PLA + PLA) Set the color of each slot to match the real filament you will load Verify your object or modifier is assigned to extruder 2 for the inlay region Verifying the G-code (Debug Step) # The first time the project is set up, switch to text G-code so the file can be opened in a text editor.\nSwitch to text G-code # Two options:\nPer profile: Printer Settings → General → uncheck Supports binary G-code, then save the profile Globally: Configuration → Preferences → Other → uncheck Use binary G-code when the printer supports it Re-slice. The export will now be .gcode instead of .bgcode.\nWhat to search for in the file # Search term What it means Expected count for a single inlay M600 Filament change pause 1 or 2 (one swap to white, optional swap back) T0, T1 Tool change commands Same count as M600 — each should sit right before an M600 WIPE_TOWER or CP TOOLCHANGE Wipe tower comments Should be zero if the wipe tower is properly disabled Sanity check in the preview # In PrusaSlicer after slicing, the layer slider on the right shows pause icons at each M600 Confirm the icons sit at the Z heights where the filament should swap Switch back to binary G-code # Once the file is verified, re-enable binary G-code:\nPer profile: re-check Supports binary G-code, save profile Globally: re-check Use binary G-code when the printer supports it Binary G-code is smaller and uploads faster to the printer.\nKnown Quirks on the Core One # Door interlock during M600: The print head will not move while the door is open. The standard \u0026ldquo;hold the purge string and let the nozzle pull it off\u0026rdquo; trick from MK3/MK4 days does not work. Close the door before resuming. There will be a small ooze blob on the next move. Possible \u0026ldquo;bonus\u0026rdquo; M600 pauses: Some users on GitHub report extra filament-change prompts at unexpected points. Verify with the G-code search before a long print. Open feature request (March 2026): Prusa is considering a proper single-extruder purge tower for M600 prints. May simplify this workflow in a future PrusaSlicer release. When to Use Color Change Instead (Option 1) # If the inlay design has every layer as one color only (no layer has both black and white), the simpler workflow is:\nSlice the model with one filament Drag the layer slider to the layer where white should start Click the orange + icon → Add color change Repeat at the layer where black should resume No multi-extruder setup, no wipe tower, no Tool change G-code edit. Use this when the geometry allows it.\nSources # Prusa Knowledge Base — Color change Prusa Knowledge Base — Binary G-code Prusa forum — Having trouble with single extruder multicolor printing (Core One) Prusa forum — Filament Towers (explains T0/T1 vs M600) GitHub Issue #14694 — Single Extruder Multi Material M600 ignored on Core One GitHub Issue #15278 — Feature request: purge tower for single-extruder M600 Printables — Prusa Tool Box (Core One L) with multi-color manual swap instructions ","externalUrl":null,"permalink":"/lab-notes/single_extruder_as_multi_extruder/","section":"Lab Notes","summary":"Setting Up a Single Extruder as a Multi-Extruder in PrusaSlicer # Printer: Prusa Core One L Slicer: PrusaSlicer Use case: Flush inlay lettering (black body with white letters) using a manual filament swap Status: Verified working\nPurpose # PrusaSlicer has two workflows for two-color prints on a single-nozzle printer:\n","title":"","type":"lab-notes"},{"content":"hello2\n","externalUrl":null,"permalink":"/lab-notes/test/","section":"Lab Notes","summary":"hello2\n","title":"","type":"lab-notes"},{"content":"","externalUrl":null,"permalink":"/quotes/marcus-aurelius-impediment/","section":"Quotes","summary":"","title":"","type":"quotes"},{"content":"","externalUrl":null,"permalink":"/quotes/marcus-aurelius-true-delight/","section":"Quotes","summary":"","title":"","type":"quotes"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","externalUrl":null,"permalink":"/projects/","section":"Projects","summary":"","title":"Projects","type":"projects"},{"content":"","externalUrl":null,"permalink":"/quotes/","section":"Quotes","summary":"","title":"Quotes","type":"quotes"},{"content":" Sit between your printers and Spoolman. When a print finishes, The Moment automatically deducts filament, logs the run with duration and cost, records which spool was on which toolhead, and stores a thumbnail if your slicer embedded one. You never touch Spoolman manually again.\nEvery gram used. Every minute spent. Every dollar it cost. Logged automatically, the moment a print finishes.\nDeploy # No git clone. No build step.\ncurl -O https://raw.githubusercontent.com/ThetaSigmaLabs/the-moment/main/docker-compose.yml curl -O https://raw.githubusercontent.com/ThetaSigmaLabs/the-moment/main/.env.example \u0026amp;\u0026amp; cp .env.example .env docker compose up -d Open http://\u0026lt;your-server-ip\u0026gt;:5000 → Settings → Add Printer.\nSpoolman is bundled in the compose file — both services start with those three commands. Add your filament types and spools in Spoolman before your first print; The Moment reads from Spoolman, it doesn\u0026rsquo;t create records there.\nFeatures # Filament \u0026amp; Spool Tracking # Automatic weight deduction in Spoolman the moment a print finishes Multi-toolhead support — each toolhead tracks its own spool independently Filament-change tracking — spool swaps mid-print recorded as separate entries Location tracking — spools carry their location (printer toolhead or storage shelf); bidirectional sync with Spoolman Print History # Every print logged: source printer, spool used, grams consumed, duration, cost breakdown Thumbnail extraction from PrusaSlicer and OrcaSlicer G-code Freeform notes on any history entry; searchable and deletable Cost Accounting # Filament cost prorated from Spoolman\u0026rsquo;s per-spool price Electricity: configurable kWh rate × print wattage × duration Preheat charge, high-temp surcharge (auto-applied for ABS, ASA, PA, PC) Maintenance, depreciation, and profit margin rates Per-printer overrides for wattage, preheat spec, and depreciation Configurable currency (USD, CAD, EUR, GBP, etc.) Quick calculator: test cost settings with arbitrary weight and time, no hardware needed NFC Tags # Tap a spool with your phone. Tap the printer slot. Done — spool assigned in The Moment and Spoolman simultaneously.\nICODE SLIX2 tags programmed via NFC Tools Pro Dual-record NDEF: OpenPrintTag CBOR + URL fallback One location tag per printer toolhead; tap spool then location within 5 minutes QR codes generated alongside every tag for NFC-free environments Supported Printers # Printer Interface Multi-toolhead Status PrusaLink (CORE One, XL, MK4, Mini+) PrusaLink API Yes Supported OctoPrint (Ender, CR-10, Voron, etc.) OctoPrint plugin Single-head Supported Bambu X1C, P1S, A1, A1 Mini MQTT over LAN AMS slots → toolheads Beta INDX 8-head TBD 8 toolheads Planned Links # GitHub →\nForked from FilaBridge by needo37 — the archived original that pioneered the Spoolman bridge pattern. GPL-3.0.\n","externalUrl":null,"permalink":"/projects/the-moment/","section":"Projects","summary":" Sit between your printers and Spoolman. When a print finishes, The Moment automatically deducts filament, logs the run with duration and cost, records which spool was on which toolhead, and stores a thumbnail if your slicer embedded one. You never touch Spoolman manually again.\nEvery gram used. Every minute spent. Every dollar it cost. Logged automatically, the moment a print finishes.\nDeploy # No git clone. No build step.\n","title":"The Moment","type":"projects"}]