Skip to main content

Mass Production Line Installation

Used when processing 100 to 1,000 boxes on a production line. Automation from EDGE_ID / hostname / credentials / Docker / cosign verify to container startup occurs once the operator fills in a single screen prompt. 30 seconds per box.

This page is the Mass Production Line SOP

For single box / field installation (SI staff), please refer to the Quick Install page first. The only difference between mass production line and field installation is the entry point; the result is the same.


0. Overview​

sudo -i
mkdir -p /home/kopens
curl -fsSL https://product.kopens.io/plantpulse-edge/plantpulse-edge-docker.tar.gz \
| tar xz -C /home/kopens
cd /home/kopens/plantpulse-edge-docker/bin
sudo bash factory.sh

factory.sh automatically performs:

  1. LAN1 NIC MAC Auto-Detection — Generates EDGE_<HEX6> using the last 6 characters of the MAC (e.g., EDGE_3F8A21).
  2. Hostname Sync — Changes hostname to edge-<hex6> (RFC 1123, lowercase+hyphen).
  3. 5-Field Prompt — SITE_ID / SERVER_HOST / SERVER_API_KEY / LAN1 NIC / LAN2 NIC.
  4. install.sh Delegation — Docker / cosign / random secret / app.properties / container startup.
  5. factory-label.txt Output — For attachment to the box. Sealed after printing.
  6. factory-inventory.csv Append — Collected at the end of the line → Company inventory DB.

1. ID / Hostname Auto-Generation​

EDGE_ID — EDGE_<HEX6>​

LAN1 NIC eth1 MAC = 00:1a:2b:3f:8a:21
─────
마지막 6 자 (HEX6) = 3F8A21
EDGE_ID = EDGE_3F8A21
hostname = edge-3f8a21

Why MAC?

  • Hostnames can change with every OS install (cloud-init / hostnamectl) and are prone to collisions.
  • MAC addresses are embedded in the hardware, guaranteeing uniqueness per box.
  • Last 6 characters (24-bit) → approx. 16.7 million combinations. Collision probability in 1,000-unit mass production is ~0.003% (Safe based on Birthday paradox).

Enforcement of domain rules (CLAUDE.md in the product repository):

IdentifierRegular ExpressionExample
EDGE_ID^EDGE_[A-Z0-9_]{1,60}$EDGE_3F8A21
SITE_ID^SITE_[A-Z0-9_]{1,60}$SITE_FAB1_042
hostname (RFC 1123)Lowercase + Hyphenedge-3f8a21

factory.sh proceeds after verifying these regexes — immediately aborts if validation fails.

MAC Collision / Boxes without NIC​

  • Collision Probability — Immediately aborts if the same EDGE_ID already exists in /data1/factory-inventory.csv (prevents silent duplication). Automatically detects cases where the same box is provisioned twice or MAC collisions occur.
  • Boxes without MAC (e.g., Virtual NIC) — derive_edge_id_from_mac fails → install.sh uses hostname-based fallback. This rarely occurs on the production line.

2. Prompt 5 Fields​

Operator input on the line:

══════════════════════════════════════════════════════════════════════
PlantPulse Edge — Factory Provisioning
══════════════════════════════════════════════════════════════════════
Detected NIC eth1 MAC : 00:1a:2b:3f:8a:21
Auto EDGE_ID : EDGE_3F8A21
Auto hostname : edge-3f8a21
══════════════════════════════════════════════════════════════════════
Site ID (^SITE_[A-Z0-9_]+) [SITE_00001]: SITE_FAB1_042
Platform server host [192.168.0.41]: 10.10.0.5
Platform API key []: kFa7bGz8pQrN3xMv9wT2sLcEjY1uV6oH
LAN1 NIC (PLC) [eth1]: ← Enter = default
LAN2 NIC (WAN) [eth2]: ← Enter = default
══════════════════════════════════════════════════════════════════════
Summary
EDGE_ID : EDGE_3F8A21
hostname : edge-3f8a21
SITE_ID : SITE_FAB1_042
SERVER_HOST : 10.10.0.5
SERVER_API_KEY : kFa7…(32 chars)
LAN1 NIC (PLC) : eth1
LAN2 NIC (WAN) : eth2
══════════════════════════════════════════════════════════════════════
Proceed with install.sh? [y/N]

After clicking y, install.sh runs for 5 to 10 minutes (including the first 3 GB image pull) and then outputs the label.


3. Auto-Generated Credentials (G2)​

install.sh generates a different random password for each box (For details, see the Password / API Key (Secrets) section of the downloaded plantpulse-edge-docker/README.md):

VariableLengthLocation
ADMIN_PASS (UI Login)Random 14 chars + !/etc/kopens/credentials.txt
API_KEY (REST)Random 16 charsAbove file + env.sh.generated
MQTT_PASS (HiveMQ)Random 14 chars + !Above file
OPCUA_PASSRandom 14 chars + !Above file
CERT_PASS (SSL keystore)Random 14 chars + !Above file

/etc/kopens/env.sh.generated (chmod 0600 root) is a sidecar sourced by all subsequent scripts (upgrade.sh / restart.sh) — maintains the same credentials even after reboot.


4. Image Signature Verification (G3)​

install.sh automatically installs cosign + deploys cosign.pub + verifies immediately after docker pull.

[install] Installing cosign ...
[install] OK — cosign installed (cosign version 3.1.3)
[install] cosign.pub installed — image verification enabled
[install] docker pull — docker.kopens.io/pe/plantpulse-edge:2026.11-20261103-1006
[install] OK — Pull complete
[cosign] OK — signature verified for docker.kopens.io/pe/plantpulse-edge:2026.11-20261103-1006

Since image signatures use cosign 3 format, cosign 2.x cannot find the signatures. If cosign is missing or version is below 3, install.sh and upgrade.sh download the fixed version 3.1.3 from github.com, verify the checksum, and install (distribution packages are not used). If 3.x is already present, it is used as is. If it cannot be downloaded during the installation phase, only a warning is left; however, the upgrade will not start if the signature cannot be verified.

Air-gapped Network Box

Installation uses the cosigned bundle (tools/cosign-linux-amd64). Upgrades verify signatures without internet using pe upgrade --bundle <파일> [--cosign <cosign-linux-amd64>] — Air-gapped Upgrade.

If cosign.pub is still a placeholder (operations team has not generated the keypair), only a WARN is issued, and installation continues. Refer to the Image Signature (Cosign) section of the downloaded plantpulse-edge-docker/README.md.


5. Label + Inventory​

/etc/kopens/factory-label.txt — For Box Attachment​

Printed on screen and saved to the chmod 0600 file upon installation completion. Line staff print via label printer and seal the box:

══════════════════════════════════════════════════════════════════════
PlantPulse Edge — Factory Label
══════════════════════════════════════════════════════════════════════
EDGE_ID : EDGE_3F8A21
hostname : edge-3f8a21
SITE_ID : SITE_FAB1_042
build_date : 2026-05-19 14:25:33
──────────────────────────────────────────────────────────────────────
LAN1 (PLC) : eth1 MAC=00:1a:2b:3f:8a:21
LAN2 (WAN) : eth2 MAC=00:1a:2b:3f:8a:22
──────────────────────────────────────────────────────────────────────
UI admin : admin / aBcD1234EfGh!
REST API : kFa7bGz8pQrN3xMv9wT2sLcEjY1uV6oH
(full credentials → /etc/kopens/credentials.txt — root only)
══════════════════════════════════════════════════════════════════════

/data1/factory-inventory.csv — For Line Collection​

One line is appended per box. Collected by the operator at the end of the line → uploaded to the company inventory DB:

edge_id,hostname,site_id,server_host,lan1_iface,lan1_mac,lan2_iface,lan2_mac,build_date,iso8601
EDGE_3F8A21,edge-3f8a21,SITE_FAB1_042,10.10.0.5,eth1,00:1a:2b:3f:8a:21,eth2,00:1a:2b:3f:8a:22,2026-05-19 14:25:33,2026-05-19T05:25:33Z
EDGE_5B7C03,edge-5b7c03,SITE_FAB1_043,10.10.0.5,eth1,00:1a:2b:5b:7c:03,eth2,00:1a:2b:5b:7c:04,2026-05-19 14:28:12,2026-05-19T05:28:12Z

6. Batch Collection at Line End​

# 라인 마지막 박스 처리 후 회수 워크스테이션에서
scp root@edge-3f8a21:/data1/factory-inventory.csv ./inventory-line1-$(date +%F).csv

# 회수한 CSV 들을 사내 inventory DB 로 업로드 (예: scripts/import-inventory.py)
python3 import-inventory.py inventory-line1-2026-05-19.csv

Alternatively, a hook can be added to automatically push to the platform when the box powers on (Phase 1 — upon introduction of the internal provisioning REST API).


7. Common Pitfalls​

SymptomCauseAction
EDGE_ID '...' fails ^EDGE_[A-Z0-9_]{1,60}$LAN1 NIC MAC is a virtual NIC, not ASCIISpecify as in NET1_IFACE=enp1s0 bash factory.sh
already_in_inventory: EDGE_3F8A21 already provisionedRerunning same box or MAC collisionRemove the corresponding line using vi /data1/factory-inventory.csv and rerun. If MAC collision, stop line and report
cosign verify FAILED (COSIGN_REQUIRE=1)Image unsigned OR incorrect cosign.pubCheck for missing build server sign-image.sh call, update cosign.pub
hostnamectl set-hostname failedExecuted inside container / Insufficient permissionsfactory.sh requires root — sudo bash factory.sh
Prompt not visible (Label not printed)install.sh failed mid-wayCheck journalctl -u plantpulse-edge.service -n 50 then refer to Installation Troubleshooting