Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a68dc23393 | |||
| c87dcb2253 | |||
| 7eadf71a0b |
@@ -9,5 +9,7 @@
|
||||
# CA / internal cert, use `tls /path/cert.pem /path/key.pem`.
|
||||
:80 {
|
||||
encode gzip
|
||||
reverse_proxy server:3000
|
||||
# Host network (prod): the server runs on the host's net namespace (to reach the booth LAN /
|
||||
# device VLAN), so reach it over loopback, not the compose service name `server`.
|
||||
reverse_proxy 127.0.0.1:3000
|
||||
}
|
||||
|
||||
@@ -599,7 +599,7 @@ export const en: Catalog = {
|
||||
save: "Save",
|
||||
saved: "Saved.",
|
||||
fieldParkName: "Park name",
|
||||
fieldParkNamePh: "e.g. Acme Parking",
|
||||
fieldParkNamePh: "e.g. Airport Parking",
|
||||
fieldOperator: "Operator (legal name)",
|
||||
fieldOperatorPh: "operating company",
|
||||
fieldNius: "NIUS",
|
||||
|
||||
+10
-10
@@ -308,13 +308,13 @@ export const sq = {
|
||||
setup: {
|
||||
title: "Konfigurimi",
|
||||
intro:
|
||||
"Shto fillimisht kontrolluesit e barrierave — cakto cili rele është hyrje/dalje dhe në cilin terminal është lidhur butoni i hyrjes. Pastaj shto lexues, kamera dhe printera dhe drejto secilin te barriera që shërben.",
|
||||
"Shto fillimisht kontrollerat e barrierave — cakto cili rele është hyrje/dalje dhe në cilin terminal është lidhur butoni i hyrjes. Pastaj shto lexues, kamera dhe printera dhe drejto secilin te barriera që shërben.",
|
||||
// Category titles + the singular noun used in buttons/modal titles.
|
||||
catControllers: "Kontrolluesit (barrierat + butoni i hyrjes)",
|
||||
catControllers: "Kontrollerat (barrierat + butoni i hyrjes)",
|
||||
catReaders: "Lexuesit (QR / RFID)",
|
||||
catCameras: "Kamerat (foto + targë)",
|
||||
catPrinters: "Printerat (bileta / vouchera)",
|
||||
nounController: "kontrollues",
|
||||
nounController: "kontroller",
|
||||
nounReader: "lexues",
|
||||
nounCamera: "kamerë",
|
||||
nounPrinter: "printer",
|
||||
@@ -322,7 +322,7 @@ export const sq = {
|
||||
addAnother: "+ Shto edhe një {{noun}}",
|
||||
addTitle: "Shto {{noun}}",
|
||||
editTitle: "Ndrysho {{noun}}",
|
||||
needControllerFirst: "Shto fillimisht një kontrollues — {{noun}} drejtohet te një prej releve të tij.",
|
||||
needControllerFirst: "Shto fillimisht një kontroller — {{noun}} drejtohet te një prej releve të tij.",
|
||||
failedToLoad: "Ngarkimi i konfigurimit dështoi: {{error}}",
|
||||
loadingCatalog: "Duke ngarkuar katalogun e pajisjeve…",
|
||||
// Direction labels (relay direction + inherited binding).
|
||||
@@ -344,9 +344,9 @@ export const sq = {
|
||||
// Device form.
|
||||
noDrivers: "Asnjë drejtues i regjistruar.",
|
||||
chooseDevice: "Zgjidh një pajisje…",
|
||||
scan: "Skano për kontrollues",
|
||||
scan: "Skano për kontroller",
|
||||
scanning: "Duke skanuar…",
|
||||
noControllersFound: "Asnjë kontrollues në LAN.",
|
||||
noControllersFound: "Asnjë kontroller në LAN.",
|
||||
use: "Përdor",
|
||||
test: "Testo lidhjen",
|
||||
testing: "Duke testuar…",
|
||||
@@ -365,7 +365,7 @@ export const sq = {
|
||||
noNicOnSubnet: "⚠ asnjë NIC në subnetin e pajisjes — pajisja mund të mos arrijë backend-in",
|
||||
backendIpHint: "Adresa te e cila kjo pajisje do të dërgojë eventet e hyrjes.",
|
||||
// Relay editor.
|
||||
relaysTitle: "Relet në këtë kontrollues",
|
||||
relaysTitle: "Relet në këtë kontroller",
|
||||
relaysHint:
|
||||
"Çdo rele hap një barrierë. Cakto drejtimin e saj; për hyrje kalimtare, cakto në cilin terminal hyrës është lidhur butoni i hyrjes.",
|
||||
outputsTitle: "Daljet — relet (barrierat + drita)",
|
||||
@@ -432,10 +432,10 @@ export const sq = {
|
||||
alarmFieldPort: "Porta",
|
||||
// Binding picker.
|
||||
whichBarrier: "Cilën barrierë shërben kjo pajisje?",
|
||||
controller: "Kontrolluesi",
|
||||
controller: "Kontrolleri",
|
||||
choose: "Zgjidh…",
|
||||
relayLabel: "Rele {{relay}} ({{direction}})",
|
||||
noRelaysConfigured: "Ky kontrollues nuk ka rele të konfiguruar.",
|
||||
noRelaysConfigured: "Ky kontroller nuk ka rele të konfiguruar.",
|
||||
},
|
||||
lab: {
|
||||
title: "Lab Tarife",
|
||||
@@ -611,7 +611,7 @@ export const sq = {
|
||||
save: "Ruaj",
|
||||
saved: "U ruajt.",
|
||||
fieldParkName: "Emri i parkimit",
|
||||
fieldParkNamePh: "p.sh. Acme Parking",
|
||||
fieldParkNamePh: "p.sh. Parking Aeroport",
|
||||
fieldOperator: "Operatori (emri ligjor)",
|
||||
fieldOperatorPh: "kompania operuese",
|
||||
fieldNius: "NIUS",
|
||||
|
||||
+31
-18
@@ -7,24 +7,25 @@
|
||||
# See wiki/decisions/container-deployment.md.
|
||||
|
||||
services:
|
||||
# Reverse proxy: :80 → server:3000 (WebSocket /api/ws upgrades pass through natively).
|
||||
# Caddy is a single static binary with a one-line proxy config; swapping http:// for the
|
||||
# site's real hostname later enables automatic HTTPS. The booth is reached at
|
||||
# http://<name-or-ip>/ (the name set via hosts/DNS on-site — NOT baked into any image).
|
||||
# Reverse proxy: :80 → server (127.0.0.1:3000). On the HOST network (see the server note),
|
||||
# so it reaches the host-net server over loopback and publishes :80 directly on the host.
|
||||
# WebSocket /api/ws upgrades pass through natively. Swapping http:// for the site's real
|
||||
# hostname later enables automatic HTTPS. Reached at http://<name-or-ip>/ (name via hosts/DNS
|
||||
# on-site — NOT baked into any image).
|
||||
proxy:
|
||||
image: caddy:2-alpine
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80"
|
||||
# - "443:443" # uncomment when moving to TLS (and set a real hostname in Caddyfile)
|
||||
# Host network: Caddy listens on the host's :80 and proxies the host-net server on
|
||||
# 127.0.0.1:3000. (No `ports:` mapping — host mode publishes directly.)
|
||||
network_mode: host
|
||||
# host mode is mutually exclusive with a named network; the base file doesn't attach proxy,
|
||||
# so nothing to null here (server does — see below).
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- caddy-data:/data
|
||||
- caddy-config:/config
|
||||
depends_on:
|
||||
- server
|
||||
networks:
|
||||
- parking
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
@@ -33,14 +34,22 @@ services:
|
||||
|
||||
server:
|
||||
restart: always
|
||||
# No published port — only the proxy reaches the server, over the private network.
|
||||
expose:
|
||||
- "3000"
|
||||
# Let the server ICMP-ping push-only readers (Dingtian/GEE QR) for an honest
|
||||
# online/offline status WITHOUT CAP_NET_RAW: opening ping_group_range to all gids
|
||||
# enables `/bin/ping` in unprivileged SOCK_DGRAM mode for the non-root runtime user.
|
||||
# (The reader exposes no TCP port, so a connect-probe can't work — see reader.ts /
|
||||
# wiki/entities/dingtian-qr-reader.md.)
|
||||
# HOST NETWORK — the crux of the appliance. The server is the ONLY container doing device
|
||||
# I/O (camera ISAPI snapshots, relay control, receiving reader/alarm pushes), all on the
|
||||
# booth's LAN / isolated device VLAN (10.0.10.x). On a bridge network it sees only the Docker
|
||||
# subnet (172.18.0.x) — it can't reach the relay, can't be reached by push devices, and the
|
||||
# backend-IP picker (net.ts networkInterfaces) only sees eth0. Host mode puts it on the real
|
||||
# NICs. Vision stays bridged (it never touches a device — the server hands it JPEG bytes).
|
||||
network_mode: host
|
||||
# host mode is mutually exclusive with a named network — detach the base file's `parking`
|
||||
# attachment (compose errors otherwise: "network_mode and networks cannot both be set").
|
||||
networks: !reset []
|
||||
# Listens on :3000 directly on the host (Caddy proxies it). Loopback to vision:
|
||||
environment:
|
||||
VISION_URL: http://127.0.0.1:8089
|
||||
# Reader liveness ping (Dingtian/GEE QR push-only readers): on host net the server uses the
|
||||
# HOST's net.ipv4.ping_group_range — ensure the host sets it (see appliance-provisioning).
|
||||
# The container-level sysctl below is a no-op under host net but kept as documentation.
|
||||
sysctls:
|
||||
- net.ipv4.ping_group_range=0 2147483647
|
||||
logging:
|
||||
@@ -52,9 +61,13 @@ services:
|
||||
vision:
|
||||
restart: always
|
||||
# The real ANPR engine. The image baked the model weights at build (offline-first).
|
||||
# Stays on the bridge network (isolated — it makes NO outbound device calls), but PUBLISHES
|
||||
# 8089 on the host LOOPBACK ONLY so the host-net server can reach it. 127.0.0.1 binding keeps
|
||||
# it off the booth LAN — nothing on the network can hit the ANPR service.
|
||||
environment:
|
||||
VISION_RECOGNIZER: fast_alpr
|
||||
# No published ports — vision is reached only by the server over the private network.
|
||||
ports:
|
||||
- "127.0.0.1:8089:8089"
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
|
||||
+5
-2
@@ -13,8 +13,11 @@ services:
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DATABASE_URL: /data/parking.sqlite
|
||||
# Reach the vision service over the private compose network by service name.
|
||||
VISION_URL: http://vision:8089
|
||||
# Reach the vision service. DEV: the private compose-network service name (`vision`).
|
||||
# PROD: the server runs on the HOST network (to see the booth LAN / device VLAN — it's the
|
||||
# only container doing device I/O), where compose DNS doesn't resolve, so the prod override
|
||||
# sets VISION_URL=http://127.0.0.1:8089 and vision publishes 8089 on the host loopback.
|
||||
VISION_URL: ${VISION_URL:-http://vision:8089}
|
||||
VISION_ENABLED: ${VISION_ENABLED:-1}
|
||||
# JWT signing secret MUST be provided at deploy (no insecure default — see auth.ts).
|
||||
JWT_SECRET: ${JWT_SECRET:?set JWT_SECRET in the env/.env}
|
||||
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
# i18n long-sentence review — EN vs SQ
|
||||
|
||||
Generated 2026-06-24. Source: `apps/web/src/lib/i18n/en.ts` + `sq.ts`. Threshold: strings ≥ 80 chars in either language (35 keys).
|
||||
|
||||
Review each pair for MEANING (does SQ say the same as EN?). Mark your decision in the **Verdict** line: `OK` / `FIX: <new text>` / `?`.
|
||||
|
||||
Keys with an automated note are tagged **⚑ NOTE**.
|
||||
|
||||
---
|
||||
|
||||
## 1. `badgeOverstayTitle` ⚑
|
||||
|
||||
- **EN** (en.ts:138, 86 ch): Paid session. The customer failed to exit during the grace period. A new period began.
|
||||
- **SQ** (sq.ts:140, 93 ch): Sesion i paguar. Klienti nuk doli brenda afatit kohor. Ka filluar një periudhë e re tarifimi.
|
||||
- **⚑ NOTE:** Spelling: "brënda" → standard is "brenda" (no ë).
|
||||
- **Verdict:**
|
||||
|
||||
## 2. `activeSince`
|
||||
|
||||
- **EN** (en.ts:254, 133 ch): Active since {{date}} · {{count}} version(s) in history. Publishing creates a new version; past sessions keep their original pricing.
|
||||
- **SQ** (sq.ts:257, 137 ch): Aktive që nga {{date}} · {{count}} version(e) në histori. Publikimi krijon një version të ri; sesionet e kaluara ruajnë çmimin origjinal.
|
||||
- **Verdict:**
|
||||
|
||||
## 3. `rateBlocksHint`
|
||||
|
||||
- **EN** (en.ts:263, 256 ch): Each band lasts a number of hours and bills at its own price; bands are consumed in order (the first hours, then the next hours). The last band is \"thereafter\" (open-ended) — its price applies once the ladder is exhausted. Price is per billing increment.
|
||||
- **SQ** (sq.ts:266, 251 ch): Çdo brez zgjat një numër orësh dhe faturohet me çmimin e tij; brezat konsumohen me radhë (orët e para, pastaj orët në vijim). Brezi i fundit është \"më pas\" (i hapur) — çmimi i tij zbatohet pas mbarimit të shkallës. Çmimi është për interval faturimi.
|
||||
- **Verdict:**
|
||||
|
||||
## 4. `defaultCardHint`
|
||||
|
||||
- **EN** (en.ts:275, 98 ch): The base rate applied when no time/seasonal tier matches. This alone is enough for most car parks.
|
||||
- **SQ** (sq.ts:278, 116 ch): Çmimi bazë i zbatuar kur asnjë nivel kohor/sezonal nuk vlen. Kjo e vetme është mjaftueshëm për shumicën e parkimeve.
|
||||
- **Verdict:**
|
||||
|
||||
## 5. `steppedHint`
|
||||
|
||||
- **EN** (en.ts:280, 210 ch): Set the TOTAL price for a stay up to a given time (e.g. up to 3h = 500). The first row whose limit ≥ the duration wins (the limit is inclusive). The last row's total repeats as a per-day price for longer stays.
|
||||
- **SQ** (sq.ts:283, 233 ch): Vendos çmimin TOTAL për një qëndrim deri në një kohë të caktuar (p.sh. deri 3 orë = 500). Fiton rreshti i parë me kufi ≥ kohëzgjatjes (kufiri përfshihet). Totali i rreshtit të fundit përsëritet si çmim ditor për qëndrime më të gjata.
|
||||
- **Verdict:**
|
||||
|
||||
## 6. `steppedTiersConflict`
|
||||
|
||||
- **EN** (en.ts:285, 235 ch): ⚠ Time/seasonal tiers do NOT apply when the base rate is 'By duration (up-to)' — the engine ignores them entirely. Remove the tiers, or switch the base rate to 'Hourly ladder' or 'Flat price'. Publishing is blocked until this is fixed.
|
||||
- **SQ** (sq.ts:288, 244 ch): ⚠ Nivelet kohore/sezonale NUK zbatohen kur tarifa bazë është 'Sipas kohëzgjatjes (deri-në)' — motori i shpërfill plotësisht. Hiqi nivelet, ose ndrysho tarifën bazë në 'Shkallë orësh' a 'Çmim fiks'. Publikimi bllokohet derisa kjo të rregullohet.
|
||||
- **Verdict:**
|
||||
|
||||
## 7. `tiersHint`
|
||||
|
||||
- **EN** (en.ts:287, 174 ch): Optional. Add tiers that apply only at certain hours/days/dates or for a category (e.g. happy hour, night rate, weekend, bus). With no tiers, just the base rate is published.
|
||||
- **SQ** (sq.ts:290, 182 ch): Opsionale. Shto nivele tarifore që vlejnë vetëm në orë/ditë/data ose kategori të caktuara (p.sh. orë e lirë, tarifë nate, fundjavë, autobus). Pa nivele, publikohet vetëm tarifa bazë.
|
||||
- **Verdict:**
|
||||
|
||||
## 8. `intro`
|
||||
|
||||
- **EN** (en.ts:526, 160 ch): Admin-defined plans the operator sells from — the price is looked up, never typed. Editing a plan publishes a new version; past sales keep their recorded price.
|
||||
- **SQ** (sq.ts:537, 193 ch): Planet i përcakton admini; operatori vetëm shet prej tyre — çmimi merret automatikisht, nuk shkruhet. Ndryshimi i një plani publikon një version të ri; shitjet e mëparshme ruajnë çmimin e tyre.
|
||||
- **Verdict:**
|
||||
|
||||
## 9. `relaysHint`
|
||||
|
||||
- **EN** (en.ts:361, 124 ch): Each relay opens one barrier. Set its direction; for transient entry, set which input terminal the entry button is wired to.
|
||||
- **SQ** (sq.ts:370, 130 ch): Çdo rele hap një barrierë. Cakto drejtimin e saj; për hyrje kalimtare, cakto në cilin terminal hyrës është lidhur butoni i hyrjes.
|
||||
- **Verdict:**
|
||||
|
||||
## 10. `outputsHint`
|
||||
|
||||
- **EN** (en.ts:364, 175 ch): Relays are OUTPUTS: each opens a barrier (or drives the button lamp). Set the relay number and direction. The input terminals (button, sensor) are in the Inputs section below.
|
||||
- **SQ** (sq.ts:373, 178 ch): Relet janë DALJE: secila hap një barrierë (ose ndez dritën e butonit). Cakto numrin e relesë dhe drejtimin. Terminalet hyrëse (butoni, sensori) janë te seksioni Hyrjet më poshtë.
|
||||
- **Verdict:**
|
||||
|
||||
## 11. `pulseOpenHint`
|
||||
|
||||
- **EN** (en.ts:366, 75 ch): How long a barrier relay is held open (jog). Applies to all barrier relays.
|
||||
- **SQ** (sq.ts:375, 85 ch): Sa kohë mbahet rele e barrierës e hapur (jog). Vlen për të gjitha relet e barrierave.
|
||||
- **Verdict:**
|
||||
|
||||
## 12. `inputsHint` ⚑
|
||||
|
||||
- **EN** (en.ts:369, 152 ch): Inputs are TERMINALS the host READS: the entry button and the presence/radar sensor. Each belongs to an entry barrier — it triggers or gates that relay.
|
||||
- **SQ** (sq.ts:378, 153 ch): Hyrjet janë TERMINALE që hosti i LEXON: butoni i hyrjes dhe sensori i pranisë/radari. Secila i përket një barriere hyrëse — e gateron ose e nis atë rele.
|
||||
- **⚑ NOTE:** "e gateron" is an anglicism ("gates it"). Native: "e kushtëzon" / "e lejon".
|
||||
- **Verdict:**
|
||||
|
||||
## 13. `inputsIdleHighHint`
|
||||
|
||||
- **EN** (en.ts:371, 62 ch): This board idles inputs HIGH (status 1111); a press pulls LOW.
|
||||
- **SQ** (sq.ts:380, 80 ch): Kjo pllakë i mban hyrjet HIGH në pushim (statusi 1111); një shtypje e ul në LOW.
|
||||
- **Verdict:**
|
||||
|
||||
## 14. `inputsNoEntryRelay`
|
||||
|
||||
- **EN** (en.ts:373, 87 ch): No entry relay — add an 'Entry' or 'Entry + exit' relay in Outputs to assign terminals.
|
||||
- **SQ** (sq.ts:382, 97 ch): Asnjë rele hyrëse — shto një rele 'Hyrje' ose 'Hyrje + dalje' te Daljet që të caktosh terminalet.
|
||||
- **Verdict:**
|
||||
|
||||
## 15. `presenceInputHint` ⚑
|
||||
|
||||
- **EN** (en.ts:378, 290 ch): Input terminal the vehicle-presence sensor (induction loop or radar) is wired to. When set, exactly ONE ticket issues per car: the button prints only while a car is present, and no second ticket issues until the sensor clears (the car drove in) and a new car re-occupies it. Preferred mode.
|
||||
- **SQ** (sq.ts:387, 275 ch): Terminali hyrës ku është lidhur sensori/laku i pranisë së automjetit. Kur vendoset, lëshohet vetëm NJË biletë për automjet: butoni printon vetëm kur ka makinë, dhe nuk lëshon biletë të dytë derisa laku të lirohet (makina hyri) dhe një makinë e re ta zërë. Mënyra e preferuar.
|
||||
- **⚑ NOTE:** EN names "induction loop OR radar"; SQ "sensori/laku i pranisë" omits radar explicitly.
|
||||
- **Verdict:**
|
||||
|
||||
## 16. `entryCooldownHint`
|
||||
|
||||
- **EN** (en.ts:381, 175 ch): When there's no presence sensor: repeat button presses are suppressed for this many seconds after a ticket. A fallback (not a guarantee) — a determined abuser can wait it out.
|
||||
- **SQ** (sq.ts:390, 165 ch): Kur nuk ka sensor pranie: shtypjet e përsëritura të butonit shtypen për kaq sekonda pas një bilete. Zgjidhje rezervë (jo garanci) — një abuzues mund ta presë afatin.
|
||||
- **Verdict:**
|
||||
|
||||
## 17. `presenceActiveLowHint`
|
||||
|
||||
- **EN** (en.ts:387, 178 ch): Tick if the presence sensor (e.g. a radar) idles HIGH and goes LOW on detection — the opposite of the button. This inverts that terminal's reading so 'present' is read correctly.
|
||||
- **SQ** (sq.ts:396, 184 ch): Shëno nëse sensori i pranisë (p.sh. radari) qëndron HIGH në pushim dhe shkon LOW kur detekton — e kundërta e butonit. Kjo përmbys leximin e atij terminali që 'prania' të lexohet saktë.
|
||||
- **Verdict:**
|
||||
|
||||
## 18. `buttonLightHint`
|
||||
|
||||
- **EN** (en.ts:391, 152 ch): The button's 12 V light on a spare relay. Blinks when the radar detects but the camera doesn't confirm a car; solid on when both confirm; off otherwise.
|
||||
- **SQ** (sq.ts:400, 160 ch): Drita 12V e butonit e lidhur në një rele rezervë. Pulson kur radari detekton por kamera s'konfirmon makinë; ndizet fiks kur të dy konfirmojnë; përndryshe fiket.
|
||||
- **Verdict:**
|
||||
|
||||
## 19. `anprHint` ⚑
|
||||
|
||||
- **EN** (en.ts:398, 203 ch): Enable to scan plates on this camera: the vision service reads the plate from a snapshot and feeds it as a read (advisory only — it never opens a barrier on its own). Requires the vision service running.
|
||||
- **SQ** (sq.ts:408, 185 ch): Aktivizo që ky aparat të skanojë targat: shërbimi i vizionit lexon targën nga pamja dhe e dërgon si lexim (vetëm këshillues — nuk hap vetë barrierën). Kërkon shërbimin e vizionit aktiv.
|
||||
- **⚑ NOTE:** SQ uses "aparat" for camera; elsewhere camera = "kamerë" (see testAnprHint). Inconsistent.
|
||||
- **Verdict:**
|
||||
|
||||
## 20. `testAnprHint`
|
||||
|
||||
- **EN** (en.ts:402, 143 ch): Takes a live snapshot from this camera and tries to read a plate, reporting the result and the time it took. Point a plate at the camera first.
|
||||
- **SQ** (sq.ts:412, 169 ch): Merr një pamje të drejtpërdrejtë nga kjo kamerë dhe përpiqet të lexojë një targë, duke raportuar rezultatin dhe kohën e nevojshme. Vendos një targë para kamerës më parë.
|
||||
- **Verdict:**
|
||||
|
||||
## 21. `curveHint`
|
||||
|
||||
- **EN** (en.ts:443, 88 ch): Fee from entry at several durations — see where the daily cap flattens or windows shift.
|
||||
- **SQ** (sq.ts:454, 96 ch): Tarifa nga hyrja për disa kohëzgjatje — shih ku rrafshohet kufiri ditor ose ndryshojnë dritaret.
|
||||
- **Verdict:**
|
||||
|
||||
## 22. `versionHint`
|
||||
|
||||
- **EN** (en.ts:465, 128 ch): Move this subscriber to another version of the same plan. The price stays as billed; only the access hours change going forward.
|
||||
- **SQ** (sq.ts:476, 139 ch): Zhvendos këtë abonent në një version tjetër të të njëjtit plan. Çmimi mbetet siç u faturua; ndryshon vetëm orari i lejuar nga këtu e tutje.
|
||||
- **Verdict:**
|
||||
|
||||
## 23. `deleteInUse`
|
||||
|
||||
- **EN** (en.ts:550, 68 ch): Can't delete — subscriptions still use this plan. Retire it instead.
|
||||
- **SQ** (sq.ts:561, 80 ch): S'mund të fshihet — abonime ende e përdorin këtë plan. Tërhiqe në vend të kësaj.
|
||||
- **Verdict:**
|
||||
|
||||
## 24. `newVersionHint`
|
||||
|
||||
- **EN** (en.ts:553, 84 ch): This publishes a NEW version of the plan — existing sales keep their original price.
|
||||
- **SQ** (sq.ts:564, 86 ch): Kjo publikon një version TË RI të planit — shitjet ekzistuese ruajnë çmimin origjinal.
|
||||
- **Verdict:**
|
||||
|
||||
## 25. `timeframesHint`
|
||||
|
||||
- **EN** (en.ts:570, 168 ch): A scan outside the allowed window is charged the normal transient tariff for the out-of-window minutes (early entry is deferred to exit; late exit is gated until paid).
|
||||
- **SQ** (sq.ts:581, 184 ch): Një skanim jashtë intervalit të lejuar tarifohet me tarifën normale kalimtare për minutat jashtë intervalit (hyrja e hershme shtyhet në dalje; dalja e vonuar bllokohet derisa paguhet).
|
||||
- **Verdict:**
|
||||
|
||||
## 26. `reserveSubsHint` ⚑
|
||||
|
||||
- **EN** (en.ts:582, 164 ch): Hold a spot for each active subscriber's car(s) even when they're not parked — transients see 'full' sooner. Off: only cars inside count (handle overflow by valet).
|
||||
- **SQ** (sq.ts:593, 198 ch): Mban një vend për makinat e çdo abonenti aktiv edhe kur nuk janë të parkuar — kalimtarët e shohin 'plot' më shpejt. Joaktiv: numërohen vetëm makinat brenda (mbingarkesa menaxhohet me parkim manual).
|
||||
- **⚑ NOTE:** EN "handle overflow by valet" → SQ "parkim manual" (manual parking) — meaning shift; valet ≠ manual parking.
|
||||
- **Verdict:**
|
||||
|
||||
## 27. `anprEntryHint`
|
||||
|
||||
- **EN** (en.ts:584, 195 ch): When on, a subscriber's plate read by a lane camera opens the barrier through the normal subscription gate. Off: subscribers must use their card/QR. Plate snapshots are still recorded either way.
|
||||
- **SQ** (sq.ts:595, 207 ch): Kur është aktiv, targa e një abonenti e lexuar nga kamera e korsisë hap barrierën përmes portës normale të abonimit. Joaktiv: abonentët duhet të përdorin kartën/QR-në. Fotot e targave regjistrohen gjithsesi.
|
||||
- **Verdict:**
|
||||
|
||||
## 28. `voucherHint`
|
||||
|
||||
- **EN** (en.ts:659, 128 ch): A receipt (Mandat Arkëtimi) adds cash; a disbursement (Mandat Pagese) removes it. The float only moves with an admin's sign-off.
|
||||
- **SQ** (sq.ts:671, 97 ch): Mandat Arkëtimi shton para; Mandat Pagese heq para. Arka lëviz vetëm me autorizimin e një admini.
|
||||
- **Verdict:**
|
||||
|
||||
## 29. `xReportHint`
|
||||
|
||||
- **EN** (en.ts:667, 83 ch): View only — nothing is recorded. These figures are signed when the shift is closed.
|
||||
- **SQ** (sq.ts:679, 85 ch): Vetëm për shikim — asgjë nuk regjistrohet. Këto shifra nënshkruhen kur mbyllet turni.
|
||||
- **Verdict:**
|
||||
|
||||
## 30. `gateBody`
|
||||
|
||||
- **EN** (en.ts:691, 80 ch): No shift is open. Open your shift so payments and exits are recorded against it.
|
||||
- **SQ** (sq.ts:703, 95 ch): Asnjë turn nuk është i hapur. Hap turnin tënd që pagesat dhe daljet të regjistrohen te ky turn.
|
||||
- **Verdict:**
|
||||
|
||||
## 31. `gateOtherBody`
|
||||
|
||||
- **EN** (en.ts:694, 120 ch): {{operator}} has an open shift. Only one shift may be open at a time — they must close theirs before you can open yours.
|
||||
- **SQ** (sq.ts:706, 140 ch): {{operator}} ka një turn të hapur. Vetëm një turn mund të jetë i hapur njëkohësisht — ai duhet të mbyllë turnin para se ti të hapësh tëndin.
|
||||
- **Verdict:**
|
||||
|
||||
## 32. `overstayHint` ⚑
|
||||
|
||||
- **EN** (en.ts:813, 155 ch): Earlier session paid. The customer failed to exit during the grace period. Payment for the new period is required. The total below is the new period's fee.
|
||||
- **SQ** (sq.ts:827, 151 ch): Sesion i mëparshëm i paguar. Klienti nuk doli brënda afatit kohor. Kërkohet pagesë për periudhën e re. Totali më poshtë është tarifa e periudhës së re.
|
||||
- **⚑ NOTE:** Spelling: "brënda" → standard is "brenda" (no ë).
|
||||
- **Verdict:**
|
||||
|
||||
## 33. `subAssistHint`
|
||||
|
||||
- **EN** (en.ts:837, 101 ch): Prepaid subscription. Open the barrier to assist the exit (faulty reader / missing card). No payment.
|
||||
- **SQ** (sq.ts:851, 108 ch): Abonim i parapaguar. Hap barrierën për të ndihmuar daljen (lexues me defekt / kartë e munguar). S'ka pagesë.
|
||||
- **Verdict:**
|
||||
|
||||
## 34. `windowChargeHint`
|
||||
|
||||
- **EN** (en.ts:842, 154 ch): This subscriber parked outside their plan's allowed hours. They owe the transient tariff for the out-of-window time — take payment, then open the barrier.
|
||||
- **SQ** (sq.ts:856, 153 ch): Ky abonent parkoi jashtë orarit të lejuar të planit. Detyrohet të paguajë tarifën kalimtare për kohën jashtë orarit — merr pagesën, pastaj hap barrierën.
|
||||
- **Verdict:**
|
||||
|
||||
## 35. `cancelTicketHint`
|
||||
|
||||
- **EN** (en.ts:852, 115 ch): Cancels a wrongly-printed ticket. A signed record is kept (operator + reason); the original entry is never deleted.
|
||||
- **SQ** (sq.ts:866, 129 ch): Anulon një biletë të printuar gabimisht. Ruhet një gjurmë e nënshkruar (operatori + arsyeja); hyrja origjinale nuk fshihet kurrë.
|
||||
- **Verdict:**
|
||||
|
||||
---
|
||||
|
||||
## Summary of flagged items
|
||||
|
||||
- `badgeOverstayTitle` — Spelling: "brënda" → standard is "brenda" (no ë).
|
||||
- `inputsHint` — "e gateron" is an anglicism ("gates it"). Native: "e kushtëzon" / "e lejon".
|
||||
- `presenceInputHint` — EN names "induction loop OR radar"; SQ "sensori/laku i pranisë" omits radar explicitly.
|
||||
- `anprHint` — SQ uses "aparat" for camera; elsewhere camera = "kamerë" (see testAnprHint). Inconsistent.
|
||||
- `reserveSubsHint` — EN "handle overflow by valet" → SQ "parkim manual" (manual parking) — meaning shift; valet ≠ manual parking.
|
||||
- `overstayHint` — Spelling: "brënda" → standard is "brenda" (no ë).
|
||||
|
||||
_All other pairs: meaning judged faithful in automated review; confirm during your read._
|
||||
@@ -2,17 +2,18 @@
|
||||
type: reference
|
||||
tags: [parking, deployment, appliance, hardening, runbook, offline-first]
|
||||
sources: []
|
||||
updated: 2026-06-23
|
||||
updated: 2026-06-27
|
||||
status: settled
|
||||
---
|
||||
|
||||
# Appliance provisioning runbook (booth PC)
|
||||
|
||||
Step-by-step to take a booth PC from factory Windows to a hardened, encrypted, container-running
|
||||
parking appliance. Written from the **first real provisioning, 2026-06-23**, on the actual hardware
|
||||
below — every command here was run and verified on that machine, including the firmware-specific
|
||||
workaround. Companion to [[disk-os-hardening]] (the *why*), [[tpm]] (TPM analysis), and
|
||||
[[container-deployment]] (the images this runs).
|
||||
parking appliance. Written from the **first real provisioning, 2026-06-23** (hardening) +
|
||||
**first Komodo deploy, 2026-06-27** (the runtime) — every command here was run and verified on the
|
||||
actual hardware, including the firmware-specific workaround. Companion to [[disk-os-hardening]] (the
|
||||
*why*), [[tpm]] (TPM analysis), [[container-deployment]] (the images), and
|
||||
[[fleet-deployment-komodo]] (the deploy control plane this runbook's §7 uses).
|
||||
|
||||
> ⚠ This box is the [[threat-model|outsider-with-the-box]] defence. The load-bearing anti-fraud
|
||||
> control is still [[reconciliation]] over the [[append-only-event-chain|signed chain]] — disk
|
||||
@@ -171,50 +172,140 @@ default) — `admin`+sudo IS the root path; enabling root adds risk, no gain.
|
||||
|
||||
## 5b. Further hardening (TODO — not yet done)
|
||||
|
||||
- **Key-based SSH only** (disable password auth) if SSH is enabled at all.
|
||||
- **Key-based SSH only** (disable password auth) if SSH is enabled at all. Routine ops no longer
|
||||
need SSH — Komodo Periphery (§7) drives deploys + gives a container terminal over the mesh — so
|
||||
SSH can be locked down hard or disabled, leaving the mesh + Komodo as the management path.
|
||||
- **No/locked-down desktop + kiosk autostart** — single-purpose; the operator never reaches a shell
|
||||
([[desktop-shell-tauri]]).
|
||||
- Consider moving the host **event-signing key into the TPM** (non-extractable) — [[tpm]], [[open-questions]] #12.
|
||||
- `sudo apt autoremove` the leftover old kernel once the new one is proven.
|
||||
|
||||
## 6. Runtime — Docker stack (VERIFIED 2026-06-23)
|
||||
## 6. Runtime — Docker engine (VERIFIED 2026-06-23)
|
||||
|
||||
Install Docker Engine + compose (as `admin`). NB Ubuntu 26.04 codename is **`resolute`**, which
|
||||
download.docker.com may not yet publish — pin the repo line to `noble`, OR use Ubuntu's `docker.io`.
|
||||
Add only `admin` to the `docker` group (root-equivalent — NEVER the operator).
|
||||
|
||||
Deploy from a standalone dir (hand-copied; no repo on the appliance), e.g. `/opt/parking_solution`:
|
||||
`docker-compose.yml` + `docker-compose.prod.yml` (the Caddy/prod override) + `Caddyfile` + a `.env`
|
||||
(chmod 600). The `.env` (driven into the containers by the base compose):
|
||||
This gives the appliance the engine. **How the stack gets ONTO it is step 7** — and as of
|
||||
2026-06-27 the primary path is **Komodo (remote, no-SSH)**, not a hand-copied dir. The manual
|
||||
`docker compose` flow survives as a **break-glass fallback** (§7c).
|
||||
|
||||
```
|
||||
JWT_SECRET=<openssl rand -hex 32> # server REFUSES to boot without (>=32, no insecure default)
|
||||
EVENT_SIGNING_KEY=<a DIFFERENT openssl rand -hex 32>
|
||||
COOKIE_SECURE=0 # CRITICAL on plain-http or the auth cookie never sends → no login
|
||||
WS_ALLOWED_ORIGINS=http://<name-or-ip> # any REMOTE origin admins use (same-origin always passes)
|
||||
VISION_ENABLED=1
|
||||
# REGISTRY/TAG default to git.infra.msai.al/mca/parking_solution + dev; set TAG=main to pin.
|
||||
```
|
||||
## 7. Deploy the stack — Komodo Periphery (PRIMARY, 2026-06-27)
|
||||
|
||||
The booth is driven by a central **Komodo Core** over the **NetBird** mesh. The appliance runs a
|
||||
small **Periphery** agent that *dials out* to Core; Core then deploys the same compose files. No
|
||||
inbound port on the booth, no SSH for routine ops. Full rationale + threat model:
|
||||
[[fleet-deployment-komodo]]. Verified end-to-end on the first booth (`park-buzi`) 2026-06-27.
|
||||
|
||||
### 7a. Install Periphery (on the booth, as `admin`)
|
||||
|
||||
Prereq: the booth is on the **NetBird** mesh and can reach Core's reverse-proxy URL
|
||||
(`https://komodo.infra.msai.al`).
|
||||
|
||||
1. In Core: **Settings → Onboarding → + New Onboarding Key** (Name = the booth, e.g. `park-buzi`;
|
||||
Expiry ~1 day; Pre-Existing Key empty). Copy the one-time `O-…` key. **Single-use** — delete it
|
||||
after the agent connects.
|
||||
2. On the booth, install Periphery in **user mode** (runs as `admin`, who is in `docker`; NO root
|
||||
daemon; **outbound** → opens no inbound port):
|
||||
|
||||
```bash
|
||||
docker login git.infra.msai.al # a read-only package token, not the account password
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml config # dry-run: verify the merged env
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
# Seed the FIRST admin (DB starts empty → nobody can log in until this runs; idempotent):
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml exec \
|
||||
-e ADMIN_USER=admin -e ADMIN_PASS='<strong-pw>' server node scripts/seed-admin.mjs
|
||||
curl -sSL https://raw.githubusercontent.com/moghtech/komodo/main/scripts/setup-periphery.py | python3 - --user \
|
||||
--core-address="https://komodo.infra.msai.al" \
|
||||
--connect-as="park-buzi" \
|
||||
--onboarding-key="O-…"
|
||||
sudo loginctl enable-linger admin # so the user service starts at boot without a login
|
||||
```
|
||||
|
||||
Healthy startup logs: vision `Initialized LicensePlateDetector …` with NO "Downloading" (baked
|
||||
weights), server `[migrate] done` → `SPA static serving enabled` → `Server listening`. The transient
|
||||
- `--connect-as` is the **Server name in Core** — unique, stable, site-meaningful (the fleet's
|
||||
primary key). Booth #2 = a different name (e.g. `park-durres`); never reuse one.
|
||||
- `--core-address` is Core's **reverse-proxy URL** (the URL you load the Core UI at over the mesh),
|
||||
NOT `:9120` — Core's container port `9120` is exposed-not-published; the agent reaches it through
|
||||
the proxy. (Gotcha #7 below.)
|
||||
- Config lands at `~/.config/komodo/periphery.config.toml`. The key field is **`core_address`**
|
||||
(singular); `root_directory` must be a path `admin` can write (user-mode default is fine — a
|
||||
`/etc/komodo` default from a system install would `Permission denied` for the user service).
|
||||
|
||||
Verify: `systemctl --user status periphery` → active; the server **`park-buzi`** appears and goes
|
||||
**OK/green** in Core → Servers. Then **delete the onboarding key**.
|
||||
|
||||
### 7b. Deploy the Stack (in Core — by hand once, then code)
|
||||
|
||||
Add **Registry Account** + **Git Account** for `git.infra.msai.al` (user `komodo`, tokens) in Core
|
||||
so Periphery can clone the repo AND pull the private images. Two distinct credential types — the
|
||||
git clone working does NOT imply the image pull is authed (gotcha #8). Per-booth secrets
|
||||
(`park_<booth>_jwt_secret`, `park_<booth>_event_signing_key` — distinct values, `openssl rand -hex
|
||||
32`) live in Core's **Variables/Secrets** store, referenced from the Stack as `[[…]]`.
|
||||
|
||||
Create a **Stack** (UI → Stacks → New), name = the booth (`park-buzi`):
|
||||
|
||||
- **Server:** `park-buzi` · **Source:** repo `mca/parking_solution`, branch `dev`, files
|
||||
`docker-compose.yml` + `docker-compose.prod.yml` · **Registry account:** `komodo` (else the pull
|
||||
is anonymous → `no basic auth credentials`).
|
||||
- **Environment** (Komodo writes this to a `.env` on the booth at deploy, substituting `[[…]]`):
|
||||
|
||||
```
|
||||
REGISTRY=git.infra.msai.al/mca/parking_solution
|
||||
TAG=dev # moving tag (staging). PIN to dev-<sha> for a live booth.
|
||||
COOKIE_SECURE=0 # CRITICAL on plain-http or the auth cookie never sends → no login
|
||||
VISION_ENABLED=1
|
||||
WS_ALLOWED_ORIGINS= # browser at the booth URL is same-origin; leave empty (the
|
||||
# Tauri desktop app needs its origin here — separate task)
|
||||
JWT_SECRET=[[park_buzi_jwt_secret]]
|
||||
EVENT_SIGNING_KEY=[[park_buzi_event_signing_key]]
|
||||
```
|
||||
|
||||
Deploy → Periphery pulls + `compose up`s. All containers (`proxy`/Caddy, `server`, `vision`) green.
|
||||
Seed the FIRST admin (DB starts empty → nobody can log in until this runs; idempotent) **via
|
||||
Komodo's terminal on the `server` container** (no SSH):
|
||||
|
||||
```bash
|
||||
docker exec -it -e ADMIN_USER=admin -e ADMIN_PASS='<strong-pw>' \
|
||||
park-buzi-server-1 node scripts/seed-admin.mjs
|
||||
```
|
||||
|
||||
> **Secrets-on-disk note.** The generated `.env` lands on the booth with **cleartext** secrets
|
||||
> (compose needs real values). That's why the disk is LUKS-encrypted (§3–4) and keys are per-booth
|
||||
> — the encryption is the control, and a single-booth compromise leaks only that booth's key. See
|
||||
> [[fleet-deployment-komodo]] (the `EVENT_SIGNING_KEY`-in-Core blast-radius caveat; ATECC608 is the
|
||||
> intended long-term signer).
|
||||
|
||||
### 7b-bis. Fleet-as-code (`resources.toml`) — optional but recommended
|
||||
|
||||
The repo's `komodo/resources.toml` mirrors the working Stack. Pointing a Core **ResourceSync** at
|
||||
it makes the fleet **git-managed**: booth #N is a copy-pasted `[[stack]]` block; an image bump is a
|
||||
one-line `TAG=` edit + push + Execute; every change is an auditable commit; a rebuilt Core
|
||||
re-creates everything from the file. Keep the sync **Unmanaged** + **Delete-Unmatched OFF** until
|
||||
trusted. An **empty diff / disabled Execute = the file already matches the live Stack** (success,
|
||||
not an error). See `komodo/README.md` and [[fleet-deployment-komodo]].
|
||||
|
||||
### 7c. Break-glass — manual compose (mesh/Core down)
|
||||
|
||||
When the mesh or Core is unreachable, the same compose files run locally via `scripts/booth.sh`
|
||||
(or raw `docker compose`). Needs a local `.env` and a `docker login git.infra.msai.al` (a
|
||||
read-only package token). This is the FALLBACK, not the routine path:
|
||||
|
||||
```bash
|
||||
docker login git.infra.msai.al
|
||||
ENV=prod ./booth.sh config # dry-run the merged env
|
||||
ENV=prod ./booth.sh up
|
||||
```
|
||||
|
||||
`booth.sh` runs from wherever it sits next to the compose files (the booth deploys them flat, e.g.
|
||||
`/opt/parking_systems/`). See [[container-deployment]].
|
||||
|
||||
### Healthy startup + web-access
|
||||
|
||||
Healthy logs: vision `Initialized LicensePlateDetector …` with NO "Downloading" (baked weights),
|
||||
server `[migrate] done` → `SPA static serving enabled` → `Server listening`. The transient
|
||||
`vision-service -> offline` at boot then `-> ready (fast_alpr)` ~8s later is normal (monitor polls
|
||||
before vision finishes loading). Reach the UI at **`http://<name-or-ip>/`** (Caddy on :80).
|
||||
|
||||
**Web-access gotchas (all fixed in the images/compose — see [[container-deployment]] "Web access"):**
|
||||
the SPA uses a RELATIVE `/api` base (works from any host; do NOT bake a domain) + a Caddy proxy gives
|
||||
the clean port-80 URL; the domain (`parksystems.msai.al`) is pointed at the booth's LAN IP via
|
||||
`hosts`/DNS ON-SITE, never an image rebuild.
|
||||
`hosts`/DNS ON-SITE, never an image rebuild. The **Tauri desktop app** is hardcoded to
|
||||
`localhost:3000` (CSP + endpoints) and can't reach a remote booth without code changes — a browser
|
||||
works; the desktop app is a separate workstream.
|
||||
|
||||
## Quick-reference: the gotchas, in order they bit us
|
||||
|
||||
@@ -225,3 +316,19 @@ the clean port-80 URL; the domain (`parksystems.msai.al`) is pointed at the boot
|
||||
5. Always keep the **password slot** + an off-machine copy of the passphrase (TPM is never the only key).
|
||||
6. GRUB password MUST be **edit-only** (`--unrestricted` on entries) or it prompts on EVERY boot →
|
||||
breaks unattended reboot. Verify `grep -c unrestricted /boot/grub/grub.cfg` ≥1 before rebooting.
|
||||
|
||||
### Komodo deploy gotchas (2026-06-27)
|
||||
|
||||
7. Periphery `core_address` is **Core's reverse-proxy URL** (`https://komodo.infra.msai.al`), NOT
|
||||
`100.x:9120`. Core's `9120` is exposed-not-published (`docker ps` shows `9120/tcp` with no `->`)
|
||||
→ a direct dial gets `Connection refused`. Ping/SSH working over the mesh does NOT mean `:9120`
|
||||
is reachable.
|
||||
8. **Git auth ≠ registry auth.** The repo cloning fine does not mean image pull is authed — they're
|
||||
separate Komodo credentials. A blank registry account on the Stack → anonymous pull →
|
||||
`no basic auth credentials`. Set the Stack's **Registry Account** (`komodo`).
|
||||
9. **User-mode Periphery + `/etc/komodo` `root_directory` = `Permission denied`** writing the agent
|
||||
key. User-mode (runs as `admin`, no root daemon) must keep `root_directory` under `$HOME`.
|
||||
10. The config key is **`core_address`** (singular). And `--core-address` derives `wss://` from
|
||||
`https://` — if Core were plain-HTTP you'd need `http://` (→ `ws://`).
|
||||
11. ResourceSync **Execute disabled + file shown clean in Info = empty diff = already in sync**
|
||||
(success). Execute only enables when the file and Core diverge (e.g. you edit `TAG`).
|
||||
|
||||
+18
-1
@@ -1695,8 +1695,25 @@ break-glass. Three settled choices: many/growing fleet · deploys **manual + pin
|
||||
`dev-<sha>` (no webhook — preserves the determinism we chose by pinning) · secrets
|
||||
**Komodo-managed, per-booth + unique**. Threat-model caveats recorded: Periphery is a root agent
|
||||
(bind to NetBird interface only, passkey+TLS, part of the TCB); `EVENT_SIGNING_KEY` in Core is a
|
||||
fraud-root blast radius → per-booth keys + [[atecc608-secure-element|ATECC608]] as the real
|
||||
fraud-root blast radius → per-booth keys + [[atecc608|ATECC608]] as the real
|
||||
long-term signer; Core becomes Tier-0. GPL-3.0 OK (external ops tooling, not a shipped dep — same
|
||||
boundary logic as the AGPL vision exception). New page [[fleet-deployment-komodo]]; infra-as-code
|
||||
sketch in `komodo/` (`resources.toml` + README + `.env.komodo.example`). Catalogued in `index.md`;
|
||||
`container-deployment` cross-linked + reframed (booth.sh = fallback).
|
||||
|
||||
## [2026-06-27] deploy | First Komodo booth deploy VERIFIED end-to-end (park-buzi)
|
||||
|
||||
Took the first booth through the whole Komodo flow on real hardware (Core v2.1.2 → agent reported
|
||||
v2.2): onboarding key → Periphery installed **user-mode** (runs as `admin`, no root daemon,
|
||||
**outbound** so the booth opens no inbound port) → server `park-buzi` **OK** in Core → Stack
|
||||
(repo `mca/parking_solution`@`dev`, base+prod compose, registry account `komodo`, per-booth
|
||||
`[[…]]` secrets) → all containers green → admin seeded via Komodo's container terminal (no SSH).
|
||||
Then `komodo/resources.toml` rewritten to mirror the **working** Stack (exported from Core, v2.2
|
||||
field shape, **Stack-only — no `[[server]]`** since onboarding owns the server), committed + pushed
|
||||
(`dev` 9918f27); a ResourceSync reads it clean — **empty diff / Execute disabled = already in
|
||||
sync** (success, not error). `booth.sh` fixed for the flat `/opt` layout earlier (83298bc).
|
||||
Gotchas that bit us (now in [[appliance-provisioning]] §7 + gotchas 7–11): `core_address` is Core's
|
||||
**proxy URL** not `:9120` (exposed-not-published → Connection refused); **git-auth ≠ registry-auth**
|
||||
(blank registry account → `no basic auth credentials`); user-mode + `/etc/komodo` root_directory →
|
||||
`Permission denied`; config key is **`core_address`** singular. [[appliance-provisioning]] §6 split:
|
||||
§6 = engine, §7 = Komodo deploy (PRIMARY) with §7c manual `booth.sh` break-glass.
|
||||
|
||||
Reference in New Issue
Block a user