Skip to content

Add a Workshop stack with an InvenTree parts inventory - #512

Open
ChrisonSimtian wants to merge 3 commits into
mainfrom
feat/workshop-inventree
Open

Add a Workshop stack with an InvenTree parts inventory#512
ChrisonSimtian wants to merge 3 commits into
mainfrom
feat/workshop-inventree

Conversation

@ChrisonSimtian

Copy link
Copy Markdown
Collaborator

Closes #508.

Problem

There was no record of what parts are on hand, so every build started by guessing — re-ordering an ESP32 that is already in a drawer, or discovering mid-build that the one reed switch went into something else. It surfaced writing SmartHome#48, where the honest instruction was "list everything, we will work out stock later", because there was nothing to check against.

Outcome

Workshop — a new in-tree stack — running InvenTree on CT 8000 (hpe-01, Ubuntu 24.04, 2 cores / 2 GB / 16 GB), at http://inventory.homelab.chrison.internal. Deployed, converged idempotently, and answering BOM-vs-stock over its API.

InvenTree over Part-DB and Homebox on one argument: it is the only one of the three where a BOM and the stock are the same system, which is exactly the question being asked.

Its own stack, in-tree. Every other stack runs something — Core is the lifeline, Monitoring watches the lab, Media serves it, SmartHome automates the house, DevOps builds it. This is record-keeping about the lab's physical parts and serves every other stack's builds; filing it under SmartHome because the first BOM happened to be a garage door would be an accident of chronology. ADR-0008 keeps cross-cutting stacks in-tree, and there are no blobs or separate audience here to pay the 2-PR tax for.

What is in here

  • stacks/Workshop/ — the stack (CTID block 8000–8099, skipping 7xxx because CT 7000 shopdemo is unmanaged) and the inventree member, with a pinned reservation carrying inventory.homelab.chrison.internal.
  • InvenTreeProvisionerct/inventree.sh leaves an instance nobody can use: site_url points at the DHCP address, and there is no superuser (invoke superuser is Django's interactive createsuperuser, so converge cannot call it). Both are closed through config keys InvenTree already reads, letting its own startup hook create the account — which skips an existing username, so a restart is not a reset.
  • A validator fix, in its own commit: quoted YAML scalars were re-typed by their text, so osVersion: "24.04" was rejected as a number. Widening the schema would have been the wrong fix — 24.10 round-trips through a double as 24.1, silently requesting a different Ubuntu.
  • INVENTREE_ADMIN_PASSWORD in secrets.env.template + Bitwarden SM. 13 new tests; 386/386 pass; 30/30 shapes valid.

Verified live

  • Converge: CREATEAPPLIED0 applied, 1 no-change on re-run.
  • Superuser admin created; the Bitwarden password authenticates against the API and mints a token — end-to-end proof the no-trailing-newline handling is right.
  • API read and write over the DNS name (InvenTree 1.5.1, apiVersion 530).
  • Backups: nightly vzdump 03:30 → ds1813-nfs-volume-1, keep 7d/4w/3m. A proof run produced vzdump-lxc-8000-…tar.zst (695 MiB).
  • Seeded with SmartHome#48's twelve parts and the assembly's BOM, so the shopping-list query works today. Stock counts are all 0 on purpose — only Chris knows what is in the drawers, and inventing numbers would make the system lie on its first question.

Two things found on the way, filed separately

  • The first invoke update failed and an identical re-run succeeded (TransactionManagementError behind a PRAGMA foreign_key_check). SQLite plus the services the postinstall had already started is the likely cause; documented in the stack README so a CREATE FAILED here does not trigger a needless rebuild.
  • The password file landed 0644 on the first apply because the recipe leaned on umask before the redirect — a umask only applies when the redirect creates the file. Now an explicit chmod 600, with a test. Caught because the marker hashes the recipe, so the fix re-converged instead of no-opping.

🤖 Generated with Claude Code

The validator deserialised a shape with YamlDotNet's object graph, which hands
every scalar back as a string, and then re-typed each one by its text so the
schema's integer/boolean constraints would evaluate. That re-typing could not
see quoting, so `osVersion: "24.04"` was reported as a number and rejected
against `string, integer` — a value the author had explicitly quoted and that
ShapeLoader already reads as a string.

Nothing hit it until now because every OS version in the lab was an integer
(Debian 13). Ubuntu 24.04 is the first that is not, and widening the schema to
accept a number would have been the wrong fix: 24.10 round-trips through a
double as 24.1, silently requesting a different Ubuntu.

Parse through the representation model instead, which preserves scalar style,
and re-type only PLAIN scalars.
There is no record of what parts are on hand, so every build starts by guessing
— re-ordering an ESP32 that is already in a drawer, or discovering mid-build
that the one reed switch went into something else. It surfaced writing the
garage-door BOM (SmartHome#48), where the honest instruction was "list
everything, we will work out stock later", because there was nothing to check
against.

InvenTree over Part-DB and Homebox on one argument: it is the only one where a
BOM and the stock are the same system, which is exactly the question being
asked. CT 8000 on hpe-01 — always-on, and the node with disk to spare.

Its own stack, in-tree. Every other stack runs something; this is record-keeping
about the lab's physical parts and it serves every other stack's builds. Filing
it under SmartHome because the first BOM happened to be a garage door would be
an accident of chronology. ADR-0008 keeps cross-cutting stacks in-tree, and
there are no blobs or separate audience here to pay the 2-PR tax for.

The provisioner exists because ct/inventree.sh leaves an instance nobody can
use: site_url points at the CT's DHCP address, and there is no superuser at all
— `invoke superuser` is Django's interactive createsuperuser, so converge
cannot call it. Both are closed through config keys InvenTree already reads,
letting its own startup hook create the account (which skips an existing
username, so a restart is not a reset).
@ChrisonSimtian ChrisonSimtian added enhancement New feature or request iac labels Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request iac

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Homelab needs a parts-inventory LXC with an API

1 participant