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.
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:
- LAN1 NIC MAC Auto-Detection — Generates
EDGE_<HEX6>using the last 6 characters of the MAC (e.g.,EDGE_3F8A21). - Hostname Sync — Changes
hostnametoedge-<hex6>(RFC 1123, lowercase+hyphen). - 5-Field Prompt — SITE_ID / SERVER_HOST / SERVER_API_KEY / LAN1 NIC / LAN2 NIC.
install.shDelegation — Docker / cosign / random secret / app.properties / container startup.- factory-label.txt Output — For attachment to the box. Sealed after printing.
- 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):
| Identifier | Regular Expression | Example |
|---|---|---|
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 + Hyphen | edge-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_macfails → 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):
| Variable | Length | Location |
|---|---|---|
ADMIN_PASS (UI Login) | Random 14 chars + ! | /etc/kopens/credentials.txt |
API_KEY (REST) | Random 16 chars | Above file + env.sh.generated |
MQTT_PASS (HiveMQ) | Random 14 chars + ! | Above file |
OPCUA_PASS | Random 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.
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
| Symptom | Cause | Action |
|---|---|---|
EDGE_ID '...' fails ^EDGE_[A-Z0-9_]{1,60}$ | LAN1 NIC MAC is a virtual NIC, not ASCII | Specify as in NET1_IFACE=enp1s0 bash factory.sh |
already_in_inventory: EDGE_3F8A21 already provisioned | Rerunning same box or MAC collision | Remove 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.pub | Check for missing build server sign-image.sh call, update cosign.pub |
hostnamectl set-hostname failed | Executed inside container / Insufficient permissions | factory.sh requires root — sudo bash factory.sh |
| Prompt not visible (Label not printed) | install.sh failed mid-way | Check journalctl -u plantpulse-edge.service -n 50 then refer to Installation Troubleshooting |
8. Related Documents
plantpulse-edge-docker/README.md— Operational Scenarios (Mass Production / SI / CI) section (included in the distribution)plantpulse-edge-docker/README.md— Image Signature (Cosign) section (keypair generation · rollout · rotation)- Quick Install
- Post-Installation Inspection
- Air-gapped Installation