A demo build can claim a real shop instead of running on its own

The first demo sealed the office cameras into the package and ran the
PC standalone - a copy of the product with no head office. The bundle
can now carry an installation code instead: setup redeems it exactly as
the app's Setup screen does, the PC joins the shop, and its cameras
arrive from head office on the first sync. The demo then IS the product
- login, Loya, head office - not a local imitation of it. The code is
single-use, so one bundle is one install. release.sh ships the bundle
with DEMO_PACK=.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
2026-09-19 13:51:01 +05:30
parent 51b9cb9743
commit 4bd1718491
4 changed files with 130 additions and 23 deletions

View File

@@ -20,6 +20,7 @@ import (
"crypto/rand"
"crypto/sha256"
"encoding/base32"
"encoding/json"
"errors"
"fmt"
"strings"
@@ -29,6 +30,31 @@ import (
// told apart from corruption instead of failing as "authentication failed".
const magic = "BVDEMO1\n"
// Payload is what a sealed bundle carries. Two demo shapes exist:
//
// - cameras only: the PC runs on its own with these cameras (the first
// demo build);
// - an enrolment code: the PC claims a real shop at head office and gets
// its cameras from there, exactly as a customer install would, so the
// demo exercises the whole product rather than a local copy of it. The
// code is single-use, so one bundle is one install.
//
// A bundle from the first build is a bare JSON array; Decode accepts both.
type Payload struct {
Cameras []Camera `json:"cameras,omitempty"`
EnrolCode string `json:"enrol_code,omitempty"`
CloudBase string `json:"cloud_base,omitempty"`
}
// Decode reads either payload shape.
func Decode(plain []byte) (Payload, error) {
var p Payload
if len(plain) > 0 && plain[0] == '[' {
return p, json.Unmarshal(plain, &p.Cameras)
}
return p, json.Unmarshal(plain, &p)
}
// Camera is one entry as the engine's Add Camera endpoint accepts it.
type Camera struct {
ID string `json:"id"`