Skip to main content
Version: 1.4.1

Kiến Trúc sPhoton ERP CLI (erp)

1. Tổng quan​

sPhoton ERP CLI (erp) là công cụ dòng lệnh Go (Cobra + Bubble Tea) dùng để khởi tạo, cấu hình và vận hành các môi trường ERPNext/Frappe thông qua Docker Compose (MariaDB, Redis, Nginx, Frappe bench, custom apps).

Cấu trúc phân tầng gồm 4 lớp:

┌─────────────────────────────────────────────────────┐
│ cmd/ │ Lớp CLI — định nghĩa lệnh, cờ (flags)
│ chỉ khai báo Cobra command + delegate │
├─────────────────────────────────────────────────────┤
│ service/ │ Lớp nghiệp vụ — logic cốt lõi
│ provisioning, deploy, app, github, upgrade, ... │
├─────────────────────────────────────────────────────┤
│ config/ + engine/ │ Lớp dữ liệu — types, serialization, compose
├─────────────────────────────────────────────────────┤
│ tui/ │ Lớp giao diện — Bubble Tea (wizard, dashboard)
└─────────────────────────────────────────────────────┘

Nguyên tắc phụ thuộc (dependency rule):

  • cmd/ → service/, config/, tui/
  • service/ → config/
  • tui/ → config/, service/, tui/components/
  • config/ không phụ thuộc bất kỳ package nội bộ nào

2. Cấu trúc thư mục​

cli/
├── main.go # Entry point → cmd.Execute()
├── cmd/ # Cobra commands (1 command mỗi file)
│ ├── root.go # Root command + version + Execute()
│ ├── new.go, new_helper.go
│ ├── run.go # erp run
│ ├── app.go # erp app install/uninstall/update
│ ├── github.go # erp github login
│ ├── upgrade.go # erp upgrade (tự nâng cấp binary)
│ ├── backup.go, restore # erp backup / restore
│ ├── migrate.go # erp migrate-from
│ ├── lang.go # erp export:lang / import:lang
│ ├── skills.go # erp skills
│ ├── devmode.go, dev.go # erp devmode / erp dev ...
│ ├── exec.go, verify.go, stop.go
│ ├── hostname.go, reset_password.go, sshkey.go
│ └── dashboard.go # erp dashboard (TUI HUD)
├── service/ # Lớp nghiệp vụ
│ ├── provision.go # RunProvisioning — entry point
│ ├── deploy.go # RunSiteSetup, InstallAppService, language setup
│ ├── app.go # AppInstall / AppUninstall / AppUpdate / DevCreateApp
│ ├── patching.go # Patch setup_wizard.js, hooks.py, desk.js
│ ├── docker.go # GetBenchCommand, RestartContainers, DetectFrappeBranch, ...
│ ├── github.go # Token mã hóa AES-GCM, URL resolution
│ ├── database.go # MariaDB ping/wait, recreate volume
│ ├── upgrade.go # Fetch/download/replace binary
│ ├── util.go # ExpandTilde, IsGitURL, ParseAppNameFromGitURL, ...
│ ├── registry.go # Wrap config.LoadRegistry
│ ├── sshkey.go # SSH key path (wrap config)
│ ├── upgrade_windows.go # Windows background updater
│ └── upgrade_other.go # Stub cho non-Windows
├── config/ # Lớp dữ liệu
│ ├── types.go # Config, App, AppsList, Compose types
│ ├── serialization.go # ParseTXT, LoadConfig, LoadConfigFile, SaveConfig
│ ├── compose.go # WriteDockerCompose (sinh compose.yml)
│ ├── defaults.go # DefaultImage/Version, StandardAppRepos, GenerateRandomPassword
│ ├── validation.go # ValidateCompany/Port/Environment/...
│ ├── embed.go # Dockerfile + Nginx resources (embedded)
│ ├── injector.go # MockBenchInjector (cho test/dry-run)
│ ├── sshkey.go # SSH key path config
│ └── projects_registry.go # Project registry (danh sách dự án)
├── tui/ # Lớp giao diện Bubble Tea
│ ├── model.go # Wizard model + constructor
│ ├── navigation.go # Wizard navigation logic
│ ├── view.go # Wizard view
│ ├── update.go # Wizard message handling
│ ├── tui.go # RunWizard entry point + EofTrackingReader
│ ├── styles.go # Shared lipgloss styles
│ ├── dashboard_model.go # Dashboard model
│ ├── dashboard_update.go # Dashboard actions (start/stop/restart)
│ ├── dashboard_view.go # Dashboard view
│ └── components/ # Component tái sử dụng
│ ├── progress.go # ProgressBar
│ ├── confirm.go # Confirm dialog
│ └── selector.go # List selector
├── engine/ # (stub — dự phòng cho logic engine tương lai)
└── test/
└── e2e/ # Hermetic E2E harness (mock docker/bench)

3. Luồng hoạt động chính​

3.1. erp new <project_dir>​

erp new myproject [flags]
│
├─ [1] config.MockBenchInjector() # tự sửa mock bench nếu cần (dry-run/test)
├─ [2] Kiểm tra tên thư mục hợp lệ, thư mục trống
├─ [3] Xác định môi trường (local/staging/production) từ flags
│
├─ CÓ đủ (--name + --port + --env)?
│ ├─ YES → buildConfigFromFlags() # không tương tác
│ └─ NO → tui.RunWizard() # TUI tương tác
│
├─ [4] config.SaveConfig(dir, conf) # ghi erp-config.yaml + compose.yml
├─ [5] service.LoadRegistry().RegisterProject() # đăng ký vào registry
│
└─ Có --run?
├─ Lưu GitHub token (nếu có, mã hóa AES-GCM)
├─ Lưu SSH key path (nếu có)
└─ service.RunProvisioning() # dựng môi trường

3.2. erp run — Provisioning​

service.RunProvisioning(projectDir, dryRun, force)
│
├─ config.LoadConfig() # đọc erp-config.yaml
├─ EnsureDockerAvailable() # kiểm tra docker/compose/daemon
│
└─ service.RunSiteSetup(dir, cfg, dryRun, force)
│
├─ [1] config.WriteDockerCompose() # sinh compose.yml mới
├─ [2] config.WriteDockerResources() # sinh Dockerfile + resources
├─ [3] startComposeServices() # docker compose up -d
│ └─ lỗi image? → hỏi build local → docker compose build
├─ [4] PatchSetupWizardJS() # sửa default language = vi
├─ [5] PatchDisable/EnableUpdateCheck() # bật/tắt popup update theo config
├─ [6] Check .site-initialized # đã init rồi thì bỏ qua
├─ [7] WaitForDatabase() # chờ MariaDB sẵn sàng
│ └─ Access denied? → hỏi recreate volume → down -v + up
├─ [8] bench new-site frontend --force
├─ [9] bench install-app erpnext
├─ [10] Với mỗi app: InstallAppService()
│ ├─ thư mục tồn tại & !force → skip get-app
│ ├─ force → xóa thư mục cũ
│ └─ bench get-app --branch <X> <app> <url>
│ X = ResolveAppBranch() (#url > explicit > auto-detect)
├─ [11] bench migrate
├─ [12] configureLanguageAndSettings() # vi, VND, Asia/Ho_Chi_Minh, ...
├─ [13] CreateSiteNameSymlink() # alias domain → frontend
├─ [14] RestartContainers() # nạp app mới
└─ [15] Ghi .site-initialized

3.3. erp app install​

service.AppInstall(dir, appName, repoURL, branch, force)
│
├─ DetectFrappeBranch() # frappe.__version__ → version-16
├─ ResolveRepoURL() # chèn token GitHub nếu có (AES-GCM)
│
├─ ResolveAppBranch(appName, repoURL, branch, detected)
│ ├─ URL chứa '#'? → bỏ qua --branch (git tự xử lý #fragment)
│ ├─ branch flag? → dùng branch đó
│ └─ default → GetAppBranch() (bảng app chuẩn) hoặc detectedBranch
│
├─ findExistingAppDir() # tái sử dụng nếu tồn tại & !force
│
├─ bench get-app [--overwrite] [--branch <X>] <app> <url>
├─ bench --site frontend install-app <app> [--force]
├─ config.SaveConfig() # cập nhật apps trong erp-config.yaml
└─ RestartContainers()

3.4. erp upgrade — Tự nâng cấp binary​

service.FetchVersions() # GET /versions.json
service.NormalizeVersion() # "1.1" → "1.1.0"
service.CompareVersions() # so sánh semver số học
service.DownloadBinary(url) # GET dist/latest/<file>
service.PrintUpgradeInfo() # hiển thị box thông tin (lipgloss)
service.ReplaceExecutable(dl) # rename .old → ghi mới → xóa .old
└─ Windows? → ReplaceExecutableWindowsDeferred() # script nền erp-update.cmd

4. Cơ chế quan trọng​

4.1. Mã hóa GitHub Token (AES-GCM)​

  • Token được mã hóa bằng AES-256-GCM với nonce ngẫu nhiên mỗi lần.
  • Khóa 32-byte sinh ngẫu nhiên lưu tại ~/.erp_github_token_key (0600).
  • Ciphertext lưu tại ~/.erp_github_token (0600).
  • Trên CLI: github login --token <PAT> → xác thực git ls-remote → mã hóa → lưu.

4.2. Project Registry​

  • Danh sách dự án lưu tại <UserConfigDir>/erp-manager/projects.json.
  • Được dùng bởi erp dashboard (HUD) và tự động cập nhật khi new, set:hostname, migrate-from.

4.3. Chế độ Mock/Test (Hermetic E2E)​

  • Biến môi trường MOCK_LOG_PATH kích hoạt chế độ mock: mọi docker/bench/git được ghi log thay vì thực thi thật.
  • ERP_DRY_RUN=1 bật chế độ dry-run cho provisioning.
  • config.MockBenchInjector() tự tạo bản sao mock bench từ docker khi thiếu.
  • E2E harness (cli/test/e2e/) biên dịch binary erp + mock docker/bench, tiêm vào PATH, không cần Docker thật.

4.4. Chọn branch/tag cho app​

Xem chi tiết tại docs/cli-guide.md — mục app.

Ưu tiênCơ chế
1# trong URL (repo.git#develop) → truyền thẳng cho git
2--branch <branch> flag
3Tự động: bảng app chuẩn (hrms→version-16, crm→main...) hoặc theo Frappe version

5. Kiến trúc Docker Compose​

Khi config.WriteDockerCompose() chạy, nó sinh compose.yml với các service:

ServiceVai tròMôi trường
configurator1 lần: set db_host, redis, socketio configsall
backendGunicorn WSGIall
frontendNginx reverse proxy + staticall
websocketNode.js Socket.IOall
queue-shortbench worker (short,default)all
queue-longbench worker (long,default,short)all
schedulerbench scheduleall
watchbench watch (asset)dev
mailpitMail testing UIdev
mariadbMariaDB 11.8all
redisRedis cache/queueall

Volumes: sites, logs, db-data, redis-data (production thêm apps). Dev mount ./apps từ host.

6. Chiến lược Test​

CấpPackageMô tả
Unitcli/configvalidation, serialization
Unitcli/cmdhelpers (branch, git URL, upgrade version, ...)
E2Ecli/test/e2echạy binary thật với mock docker/bench — hermetic, không cần Docker thật

Lệnh chạy:

make test # go test ./cli/test/e2e/ -v
go test ./cli/... # toàn bộ

7. Mở rộng CLI​

Để thêm một lệnh mới erp <ten-lenh>:

  1. Tạo cli/cmd/<ten>.go — định nghĩa cobra.Command + init() đăng ký vào rootCmd.
  2. Logic nghiệp vụ đặt trong cli/service/ (file mới hoặc thêm hàm).
  3. Nếu cần field config mới → thêm vào config/types.go và đăng ký validate trong config/validation.go.
  4. Nếu command có TUI → thêm model/view/update trong tui/ hoặc dùng tui/components/.
  5. Thêm unit test cho helper + E2E test trong cli/test/e2e/.
  6. Cập nhật docs/cli-guide.md (tiếng Việt).