docs: edge local dev guide 및 관련 문서 업데이트
This commit is contained in:
parent
e02ae80ce6
commit
95693f1cec
6 changed files with 160 additions and 252 deletions
23
Makefile
23
Makefile
|
|
@ -1,4 +1,4 @@
|
|||
.PHONY: all build build-edge build-node build-node-target pack-node-target pack-edge tidy test test-e2e test-openai-ollama proto proto-dart portal-test portal-build-web clean
|
||||
.PHONY: all build build-local build-edge build-node build-node-target build-node-targets pack-node-target pack-edge archive-edge tidy test test-e2e test-openai-ollama proto proto-dart portal-test portal-build-web clean
|
||||
|
||||
GOFLAGS ?= -trimpath
|
||||
BUILD_DIR ?= build
|
||||
|
|
@ -10,16 +10,17 @@ EDGE_PACKAGE_NAME ?= iop-edge-$(EDGE_TARGET)
|
|||
EDGE_PACKAGE ?= $(PACK_DIR)/$(EDGE_PACKAGE_NAME).tar.gz
|
||||
EDGE_PACKAGE_TMP ?= $(BUILD_DIR)/.edge-package
|
||||
NODE_TARGET ?= $(shell go env GOOS)-$(shell go env GOARCH)
|
||||
NODE_TARGETS ?= linux-arm64 linux-amd64 darwin-arm64 darwin-amd64 windows-arm64 windows-amd64
|
||||
NODE_TARGET_PARTS = $(subst -, ,$(NODE_TARGET))
|
||||
NODE_GOOS = $(word 1,$(NODE_TARGET_PARTS))
|
||||
NODE_GOARCH = $(word 2,$(NODE_TARGET_PARTS))
|
||||
|
||||
all: build
|
||||
|
||||
build:
|
||||
mkdir -p $(BUILD_BIN_DIR)
|
||||
go build $(GOFLAGS) -o $(BUILD_BIN_DIR)/iop-edge ./apps/edge/cmd/edge
|
||||
go build $(GOFLAGS) -o $(BUILD_BIN_DIR)/iop-node ./apps/node/cmd/node
|
||||
build: build-node-targets
|
||||
$(MAKE) archive-edge
|
||||
|
||||
build-local: build-edge build-node
|
||||
|
||||
build-edge:
|
||||
mkdir -p $(BUILD_BIN_DIR)
|
||||
|
|
@ -34,10 +35,20 @@ build-node-target:
|
|||
mkdir -p $(BUILD_BIN_DIR)
|
||||
GOOS=$(NODE_GOOS) GOARCH=$(NODE_GOARCH) go build $(GOFLAGS) -o $(BUILD_BIN_DIR)/iop-node-$(NODE_TARGET) ./apps/node/cmd/node
|
||||
|
||||
build-node-targets: build-edge
|
||||
rm -rf "$(ARTIFACT_DIR)"
|
||||
@for target in $(NODE_TARGETS); do \
|
||||
echo "==> node target $$target"; \
|
||||
$(MAKE) build-node-target NODE_TARGET=$$target; \
|
||||
$(BUILD_BIN_DIR)/iop-edge bootstrap pack --target $$target --node-binary $(BUILD_BIN_DIR)/iop-node-$$target --output $(ARTIFACT_DIR); \
|
||||
done
|
||||
|
||||
pack-node-target: build-edge build-node-target
|
||||
$(BUILD_BIN_DIR)/iop-edge bootstrap pack --target $(NODE_TARGET) --node-binary $(BUILD_BIN_DIR)/iop-node-$(NODE_TARGET) --output $(ARTIFACT_DIR)
|
||||
|
||||
pack-edge: build-edge
|
||||
pack-edge: build-edge archive-edge
|
||||
|
||||
archive-edge:
|
||||
@test -d "$(ARTIFACT_DIR)/bootstrap" || (echo "missing node bootstrap artifacts; run make pack-node-target NODE_TARGET=<goos>-<goarch> first" >&2; exit 2)
|
||||
rm -rf "$(EDGE_PACKAGE_TMP)"
|
||||
mkdir -p "$(EDGE_PACKAGE_TMP)/$(EDGE_PACKAGE_NAME)/artifacts" "$(EDGE_PACKAGE_TMP)/$(EDGE_PACKAGE_NAME)/logs" "$(PACK_DIR)"
|
||||
|
|
|
|||
|
|
@ -104,8 +104,10 @@ sudo iop-edge setup --dry-run --binary /usr/local/bin/iop-edge
|
|||
sudo iop-edge setup --enable --start --binary /usr/local/bin/iop-edge
|
||||
```
|
||||
|
||||
Edge 배포 archive는 repo root에서 `make pack-node-target NODE_TARGET=<goos>-<goarch>` 후 `make pack-edge`로 만든다.
|
||||
archive에는 `iop-edge`와 Node bootstrap용 `artifacts/`가 들어가며, Edge host에서 `config init`과 `node register`를 수행해 현재 host 기준 bootstrap URL을 확정한다.
|
||||
Edge 배포 archive는 repo root에서 `make build`로 만든다.
|
||||
archive에는 `iop-edge`와 Linux/macOS/Windows arm64/amd64 Node bootstrap용 `artifacts/`가 들어가며, Edge host에서 `config init`과 `node register`를 수행해 현재 host 기준 bootstrap URL을 확정한다.
|
||||
Node host는 `<artifact-base-url>/bootstrap/node.sh` 하나를 실행하고, bootstrap script가 OS/architecture를 감지해 맞는 Node artifact를 받는다.
|
||||
특정 Node target만 담긴 개발용 package는 `make build NODE_TARGETS="<goos>-<goarch>"`로 만들 수 있다. Edge만 갱신할 때는 `make build-edge && make pack-edge`, Node artifact만 갱신할 때는 `make pack-node-target NODE_TARGET=<goos>-<goarch> && make pack-edge`를 사용한다.
|
||||
|
||||
`setup`의 `--config` 기본값은 `/etc/iop/edge.yaml`이다. dev 단계 명령(`serve`, `console`, `config init/print/check`, `env`, `node register`)의 root persistent `--config` 기본값은 bundle-local `edge.yaml` (없을 시 executable-adjacent `edge.yaml`, 그 다음 repo dev fallback `configs/edge.yaml`)로 확인된다. 기존 설정 파일은 그대로 두며 `--overwrite-config`를 지정해야 덮어쓴다.
|
||||
|
||||
|
|
|
|||
|
|
@ -400,6 +400,10 @@ func bootstrapScriptURL(baseURL, target string) string {
|
|||
return strings.TrimSuffix(baseURL, "/") + "/bootstrap/node-" + target + ".sh"
|
||||
}
|
||||
|
||||
func universalBootstrapScriptURL(baseURL string) string {
|
||||
return strings.TrimSuffix(baseURL, "/") + "/bootstrap/node.sh"
|
||||
}
|
||||
|
||||
type envReport struct {
|
||||
ConfigPath string
|
||||
BinaryDir string
|
||||
|
|
@ -428,7 +432,7 @@ func renderEnv(w io.Writer, r envReport) {
|
|||
fmt.Fprintf(w, "artifact_dir: %s\n", r.Bootstrap.ArtifactDir)
|
||||
fmt.Fprintf(w, "artifact_listen: %s\n", r.Bootstrap.Listen)
|
||||
fmt.Fprintf(w, "artifact_base_url: %s (%s)\n", artifactURL, artifactSource)
|
||||
fmt.Fprintf(w, "node_bootstrap: %s\n", bootstrapScriptURL(artifactURL, runtime.GOOS+"-"+runtime.GOARCH))
|
||||
fmt.Fprintf(w, "node_bootstrap: %s\n", universalBootstrapScriptURL(artifactURL))
|
||||
if r.OpenAI.Enabled {
|
||||
fmt.Fprintf(w, "openai_url: http://%s/v1\n", addressFor(r.Advertise.Host, r.OpenAI.Listen))
|
||||
} else {
|
||||
|
|
@ -551,7 +555,7 @@ func bootstrapPackCmd() *cobra.Command {
|
|||
return err
|
||||
}
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "wrote bootstrap artifacts to %s\n", outputDir)
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "bootstrap: %s\n", bootstrapScriptURL(artifactBaseURL, target))
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "bootstrap: %s\n", universalBootstrapScriptURL(artifactBaseURL))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
@ -642,6 +646,22 @@ func packNodeBootstrap(opts nodeBootstrapPackOptions) error {
|
|||
if err := os.WriteFile(scriptPath, []byte(script), 0o755); err != nil {
|
||||
return fmt.Errorf("write bootstrap script: %w", err)
|
||||
}
|
||||
if err := writeUniversalBootstrapScript(bootstrapDir, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeUniversalBootstrapScript(bootstrapDir string, opts nodeBootstrapPackOptions) error {
|
||||
universalOpts := opts
|
||||
universalOpts.Target = ""
|
||||
scriptPath := filepath.Join(bootstrapDir, "node.sh")
|
||||
if err := os.MkdirAll(bootstrapDir, 0o755); err != nil {
|
||||
return fmt.Errorf("mkdir bootstrap dir: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(scriptPath, []byte(nodeBootstrapScript(universalOpts)), 0o755); err != nil {
|
||||
return fmt.Errorf("write universal bootstrap script: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -702,18 +722,42 @@ func nodeBootstrapScript(opts nodeBootstrapPackOptions) string {
|
|||
set -euo pipefail
|
||||
|
||||
VERSION=` + shellQuote(opts.Version) + `
|
||||
TARGET=` + shellQuote(opts.Target) + `
|
||||
DEFAULT_TARGET=` + shellQuote(opts.Target) + `
|
||||
DEFAULT_ARTIFACT_BASE_URL=` + shellQuote(strings.TrimSuffix(opts.ArtifactBaseURL, "/")) + `
|
||||
DEFAULT_EDGE_ADDR=` + shellQuote(opts.EdgeAddr) + `
|
||||
POSITIONAL_NODE_TOKEN="${1:-}"
|
||||
TARGET="$DEFAULT_TARGET"
|
||||
ARTIFACT_BASE_URL="$DEFAULT_ARTIFACT_BASE_URL"
|
||||
EDGE_ADDR="$DEFAULT_EDGE_ADDR"
|
||||
NODE_TOKEN="$POSITIONAL_NODE_TOKEN"
|
||||
INSTALL_DIR="${IOP_HOME:-$HOME/iop-field}"
|
||||
CONFIG_FILE="${IOP_NODE_CONFIG:-$INSTALL_DIR/node.yaml}"
|
||||
BIN_FILE="$INSTALL_DIR/iop-node"
|
||||
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/iop-node-bootstrap.XXXXXX")"
|
||||
|
||||
detect_target() {
|
||||
local os arch
|
||||
case "$(uname -s)" in
|
||||
Linux*) os="linux" ;;
|
||||
Darwin*) os="darwin" ;;
|
||||
MINGW*|MSYS*|CYGWIN*) os="windows" ;;
|
||||
*)
|
||||
echo "[iop-bootstrap] unsupported OS: $(uname -s)" >&2
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) arch="amd64" ;;
|
||||
arm64|aarch64) arch="arm64" ;;
|
||||
*)
|
||||
echo "[iop-bootstrap] unsupported architecture: $(uname -m)" >&2
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
|
||||
printf '%s-%s\n' "$os" "$arch"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
|
|
@ -723,10 +767,19 @@ ARTIFACT_BASE_URL="${ARTIFACT_BASE_URL%/}"
|
|||
|
||||
if [[ -z "$NODE_TOKEN" ]]; then
|
||||
echo "[iop-bootstrap] missing node token" >&2
|
||||
echo "[iop-bootstrap] usage: curl -fsSL <artifact-base-url>/bootstrap/node-$TARGET.sh | bash -s TOKEN" >&2
|
||||
echo "[iop-bootstrap] usage: curl -fsSL <artifact-base-url>/bootstrap/node.sh | bash -s TOKEN" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ -z "$TARGET" ]]; then
|
||||
TARGET="$(detect_target)"
|
||||
fi
|
||||
|
||||
BIN_FILE="$INSTALL_DIR/iop-node"
|
||||
if [[ "$TARGET" == windows-* ]]; then
|
||||
BIN_FILE="$INSTALL_DIR/iop-node.exe"
|
||||
fi
|
||||
|
||||
echo "[iop-bootstrap] version=$VERSION target=$TARGET"
|
||||
echo "[iop-bootstrap] install_dir=$INSTALL_DIR"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
|
|
@ -785,6 +838,18 @@ var tokenGenerator = func() string {
|
|||
|
||||
func refreshBootstrapForExistingArtifact(cfg *config.EdgeConfig, target, artifactURL string) error {
|
||||
outputDir := resolveBootstrapArtifactDir(cfg.Bootstrap.ArtifactDir)
|
||||
adv := resolveAdvertiseHost(cfg.Server.AdvertiseHost)
|
||||
bootstrapDir := filepath.Join(outputDir, "bootstrap")
|
||||
baseOpts := nodeBootstrapPackOptions{
|
||||
OutputDir: outputDir,
|
||||
Version: version.Version,
|
||||
ArtifactBaseURL: artifactURL,
|
||||
EdgeAddr: addressFor(adv.Host, cfg.Server.Listen),
|
||||
}
|
||||
if err := writeUniversalBootstrapScript(bootstrapDir, baseOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nodeBinary := filepath.Join(outputDir, target, "iop-node")
|
||||
if _, err := os.Stat(nodeBinary); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
|
|
@ -792,15 +857,10 @@ func refreshBootstrapForExistingArtifact(cfg *config.EdgeConfig, target, artifac
|
|||
}
|
||||
return fmt.Errorf("stat node artifact: %w", err)
|
||||
}
|
||||
adv := resolveAdvertiseHost(cfg.Server.AdvertiseHost)
|
||||
return packNodeBootstrap(nodeBootstrapPackOptions{
|
||||
NodeBinary: nodeBinary,
|
||||
OutputDir: outputDir,
|
||||
Target: target,
|
||||
Version: version.Version,
|
||||
ArtifactBaseURL: artifactURL,
|
||||
EdgeAddr: addressFor(adv.Host, cfg.Server.Listen),
|
||||
})
|
||||
targetOpts := baseOpts
|
||||
targetOpts.NodeBinary = nodeBinary
|
||||
targetOpts.Target = target
|
||||
return packNodeBootstrap(targetOpts)
|
||||
}
|
||||
|
||||
func validateEdgeConfig(cfg *config.EdgeConfig) error {
|
||||
|
|
@ -861,7 +921,7 @@ edge.yaml. The printed bootstrap command is the user-facing command to run on
|
|||
the Node host; the user should not create node.yaml or run iop-node serve
|
||||
directly in the default flow.`,
|
||||
Example: ` iop-edge node register node-silicon-ollama --adapter ollama --ollama-base-url http://127.0.0.1:11434
|
||||
iop-edge node register node-linux-cli --adapter cli --target linux-amd64
|
||||
iop-edge node register node-linux-cli --adapter cli
|
||||
iop-edge config check`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -1038,7 +1098,7 @@ directly in the default flow.`,
|
|||
}
|
||||
|
||||
// Render final user-facing bootstrap command
|
||||
bootstrapURL := bootstrapScriptURL(artifactURL, target)
|
||||
bootstrapURL := universalBootstrapScriptURL(artifactURL)
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "curl -fsSL %s | bash -s %s\n", bootstrapURL, nodeDef.Token)
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -362,7 +361,7 @@ func TestEnvCmdMarksAutoAdvertiseHostAndOfflineStateForEmptyConfig(t *testing.T)
|
|||
if !strings.Contains(got, expectedArtifact) {
|
||||
t.Errorf("expected derived artifact_base_url %q, got:\n%s", expectedArtifact, got)
|
||||
}
|
||||
expectedBootstrap := "node_bootstrap: " + bootstrapScriptURL(defaultArtifactBaseURL(resolveAdvertiseHost("").Host), runtime.GOOS+"-"+runtime.GOARCH)
|
||||
expectedBootstrap := "node_bootstrap: " + universalBootstrapScriptURL(defaultArtifactBaseURL(resolveAdvertiseHost("").Host))
|
||||
if !strings.Contains(got, expectedBootstrap) {
|
||||
t.Errorf("expected node_bootstrap %q, got:\n%s", expectedBootstrap, got)
|
||||
}
|
||||
|
|
@ -436,6 +435,7 @@ bootstrap:
|
|||
for _, path := range []string{
|
||||
filepath.Join(outDir, "linux-arm64", "iop-node"),
|
||||
filepath.Join(outDir, "linux-arm64", "SHA256SUMS"),
|
||||
filepath.Join(outDir, "bootstrap", "node.sh"),
|
||||
filepath.Join(outDir, "bootstrap", "node-linux-arm64.sh"),
|
||||
} {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
|
|
@ -452,6 +452,16 @@ bootstrap:
|
|||
if !strings.Contains(string(script), `DEFAULT_EDGE_ADDR="edge.example.test:39090"`) {
|
||||
t.Fatalf("script missing derived edge addr:\n%s", script)
|
||||
}
|
||||
universalScript, err := os.ReadFile(filepath.Join(outDir, "bootstrap", "node.sh"))
|
||||
if err != nil {
|
||||
t.Fatalf("read universal script: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(universalScript), `DEFAULT_TARGET=""`) {
|
||||
t.Fatalf("universal script should detect target dynamically:\n%s", universalScript)
|
||||
}
|
||||
if !strings.Contains(string(universalScript), `detect_target()`) {
|
||||
t.Fatalf("universal script missing target detection:\n%s", universalScript)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeRegisterRefreshesExistingBootstrapArtifact(t *testing.T) {
|
||||
|
|
@ -497,13 +507,23 @@ nodes: []
|
|||
if err != nil {
|
||||
t.Fatalf("read refreshed script: %v", err)
|
||||
}
|
||||
universalScript, err := os.ReadFile(filepath.Join(artifactDir, "bootstrap", "node.sh"))
|
||||
if err != nil {
|
||||
t.Fatalf("read refreshed universal script: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(script), `DEFAULT_ARTIFACT_BASE_URL="http://edge.deploy.test:38080"`) {
|
||||
t.Fatalf("script missing refreshed artifact URL:\n%s", script)
|
||||
}
|
||||
if !strings.Contains(string(universalScript), `DEFAULT_ARTIFACT_BASE_URL="http://edge.deploy.test:38080"`) {
|
||||
t.Fatalf("universal script missing refreshed artifact URL:\n%s", universalScript)
|
||||
}
|
||||
if !strings.Contains(string(script), `DEFAULT_EDGE_ADDR="edge.deploy.test:39090"`) {
|
||||
t.Fatalf("script missing refreshed edge addr:\n%s", script)
|
||||
}
|
||||
if !strings.Contains(out.String(), `curl -fsSL http://edge.deploy.test:38080/bootstrap/node-darwin-arm64.sh | bash -s test-token`) {
|
||||
if !strings.Contains(string(universalScript), `DEFAULT_EDGE_ADDR="edge.deploy.test:39090"`) {
|
||||
t.Fatalf("universal script missing refreshed edge addr:\n%s", universalScript)
|
||||
}
|
||||
if !strings.Contains(out.String(), `curl -fsSL http://edge.deploy.test:38080/bootstrap/node.sh | bash -s test-token`) {
|
||||
t.Fatalf("unexpected register output:\n%s", out.String())
|
||||
}
|
||||
}
|
||||
|
|
@ -645,7 +665,7 @@ func TestNodeRegisterUpsertsYAMLAndOutputsBootstrap(t *testing.T) {
|
|||
|
||||
output := out.String()
|
||||
// Check one-line bootstrap output
|
||||
expectedCmd := "curl -fsSL " + bootstrapScriptURL(defaultArtifactBaseURL(resolveAdvertiseHost("").Host), runtime.GOOS+"-"+runtime.GOARCH) + " | bash -s mocked-token-1234"
|
||||
expectedCmd := "curl -fsSL " + universalBootstrapScriptURL(defaultArtifactBaseURL(resolveAdvertiseHost("").Host)) + " | bash -s mocked-token-1234"
|
||||
if strings.TrimSpace(output) != expectedCmd {
|
||||
t.Errorf("expected only bootstrap command %q, got %q", expectedCmd, output)
|
||||
}
|
||||
|
|
@ -679,7 +699,7 @@ func TestNodeRegisterUpsertsYAMLAndOutputsBootstrap(t *testing.T) {
|
|||
}
|
||||
|
||||
output = out.String()
|
||||
expectedCmd2 := "curl -fsSL " + bootstrapScriptURL(defaultArtifactBaseURL(resolveAdvertiseHost("").Host), runtime.GOOS+"-"+runtime.GOARCH) + " | bash -s explicit-token-abc"
|
||||
expectedCmd2 := "curl -fsSL " + universalBootstrapScriptURL(defaultArtifactBaseURL(resolveAdvertiseHost("").Host)) + " | bash -s explicit-token-abc"
|
||||
if strings.TrimSpace(output) != expectedCmd2 {
|
||||
t.Errorf("expected only bootstrap command %q, got %q", expectedCmd2, output)
|
||||
}
|
||||
|
|
@ -850,7 +870,7 @@ bootstrap:
|
|||
}
|
||||
|
||||
output := out.String()
|
||||
expectedCmd := "curl -fsSL http://configured-artifact-server:8080/bootstrap/node-darwin-arm64.sh | bash -s test-token"
|
||||
expectedCmd := "curl -fsSL http://configured-artifact-server:8080/bootstrap/node.sh | bash -s test-token"
|
||||
if !strings.Contains(output, expectedCmd) {
|
||||
t.Errorf("expected bootstrap command %q, got %q", expectedCmd, output)
|
||||
}
|
||||
|
|
@ -866,7 +886,7 @@ bootstrap:
|
|||
}
|
||||
|
||||
output = out.String()
|
||||
expectedCmdOverride := "curl -fsSL http://flag-artifact-server:9000/bootstrap/node-linux-amd64.sh | bash -s test-token-2"
|
||||
expectedCmdOverride := "curl -fsSL http://flag-artifact-server:9000/bootstrap/node.sh | bash -s test-token-2"
|
||||
if !strings.Contains(output, expectedCmdOverride) {
|
||||
t.Errorf("expected bootstrap command %q, got %q", expectedCmdOverride, output)
|
||||
}
|
||||
|
|
@ -895,7 +915,7 @@ bootstrap:
|
|||
t.Fatalf("execute register: %v\n%s", err, out.String())
|
||||
}
|
||||
|
||||
expectedCmd := "curl -fsSL http://edge.example.test:18080/bootstrap/node-linux-amd64.sh | bash -s test-token"
|
||||
expectedCmd := "curl -fsSL http://edge.example.test:18080/bootstrap/node.sh | bash -s test-token"
|
||||
if !strings.Contains(out.String(), expectedCmd) {
|
||||
t.Errorf("expected bootstrap command %q, got %q", expectedCmd, out.String())
|
||||
}
|
||||
|
|
@ -927,7 +947,7 @@ func TestTempFileCLIWorkflowConfigInitNodeRegisterConfigCheck(t *testing.T) {
|
|||
}
|
||||
|
||||
output := out.String()
|
||||
expectedCmd := "curl -fsSL http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh | bash -s test-token"
|
||||
expectedCmd := "curl -fsSL http://toki-labs.com:18080/bootstrap/node.sh | bash -s test-token"
|
||||
if !strings.Contains(output, expectedCmd) {
|
||||
t.Errorf("expected bootstrap command %q, got %q", expectedCmd, output)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ Edge가 제시한 bootstrap 명령 한 줄을 실행하면 Node binary 다운로
|
|||
|
||||
```bash
|
||||
# 빌드
|
||||
make build-node
|
||||
make build-local
|
||||
|
||||
# 버전 확인
|
||||
./build/bin/iop-node version
|
||||
|
|
|
|||
|
|
@ -1,264 +1,79 @@
|
|||
# Edge-local Dev Guide
|
||||
# Edge Local Quickstart
|
||||
|
||||
이 문서는 Control Plane 없이 로컬 또는 개발 서버에서 Edge/Node bundle을 빠르게 세팅하고 확인하는 사용자용 흐름이다.
|
||||
사용자는 `iop-edge`를 중심으로 진행한다.
|
||||
Edge는 package 하나, Node는 bootstrap 한 줄로 설치한다.
|
||||
|
||||
하지 않는다:
|
||||
|
||||
- `node.yaml` 생성, 편집, 확인
|
||||
- `edge.yaml`의 `nodes[]` 수동 작성
|
||||
- `iop-node serve --config ...` 직접 실행
|
||||
- `scripts/dev/*.sh` helper나 별도 dev deploy 바이너리를 공식 경로로 사용
|
||||
|
||||
## 1. 빌드와 Edge 배포 패키지
|
||||
|
||||
공식 빌드 진입점은 `make build` 하나로 둔다.
|
||||
이 명령은 Edge와 Node를 함께 빌드하고, 현재 host에서 바로 쓸 수 있는 바이너리만 `build/bin/`에 맞춰 둔다.
|
||||
## 1. Package
|
||||
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
Edge 또는 Node만 갱신할 때도 같은 빌드 통로를 사용한다.
|
||||
결과: `build/packages/iop-edge-<edge-os>-<edge-arch>.tar.gz`
|
||||
|
||||
## 2. Edge
|
||||
|
||||
```bash
|
||||
make build-edge
|
||||
make build-node
|
||||
```
|
||||
tar -xzf iop-edge-<edge-os>-<edge-arch>.tar.gz -C /opt
|
||||
cd /opt/iop-edge-<edge-os>-<edge-arch>
|
||||
|
||||
산출물:
|
||||
|
||||
```text
|
||||
build/bin/iop-edge
|
||||
build/bin/iop-node
|
||||
```
|
||||
|
||||
Edge host에 배포할 때는 Node target artifact를 먼저 준비한 뒤 Edge archive를 만든다.
|
||||
|
||||
```bash
|
||||
make pack-node-target NODE_TARGET=darwin-arm64
|
||||
make pack-edge
|
||||
```
|
||||
|
||||
Intel Mac Node면 `NODE_TARGET=darwin-amd64`를 사용한다.
|
||||
Edge만 새로 빌드한 경우에는 기존 `build/artifacts/`를 유지한 상태에서 `make pack-edge`만 다시 실행한다.
|
||||
Node binary를 새로 빌드한 경우에는 `make pack-node-target NODE_TARGET=<goos>-<goarch>` 후 `make pack-edge`를 다시 실행한다.
|
||||
|
||||
배포 산출물:
|
||||
|
||||
```text
|
||||
build/packages/iop-edge-<edge-os>-<edge-arch>.tar.gz
|
||||
```
|
||||
|
||||
## 2. Edge 패키지 압축 해제
|
||||
|
||||
Edge host에서는 archive 하나만 옮긴 뒤 압축을 푼다.
|
||||
|
||||
```bash
|
||||
tar -xzf build/packages/iop-edge-$(go env GOOS)-$(go env GOARCH).tar.gz -C /opt
|
||||
cd /opt/iop-edge-$(go env GOOS)-$(go env GOARCH)
|
||||
```
|
||||
|
||||
압축 해제 후 기본 구조:
|
||||
|
||||
```text
|
||||
iop-edge
|
||||
artifacts/
|
||||
logs/
|
||||
```
|
||||
|
||||
repo-local에서 archive 없이 확인할 때는 새 bundle 디렉터리에 Edge binary와 artifacts를 함께 둔다.
|
||||
|
||||
```bash
|
||||
mkdir -p /tmp/iop-edge-dev
|
||||
cp build/bin/iop-edge /tmp/iop-edge-dev/
|
||||
cp -R build/artifacts /tmp/iop-edge-dev/
|
||||
cd /tmp/iop-edge-dev
|
||||
```
|
||||
|
||||
## 3. Edge config 생성
|
||||
|
||||
```bash
|
||||
./iop-edge config init
|
||||
```
|
||||
|
||||
기대 결과:
|
||||
|
||||
```text
|
||||
<bundle-dir>/edge.yaml
|
||||
```
|
||||
|
||||
`edge.yaml`은 Control Plane이 없는 동안 테스트용 source of truth다.
|
||||
처음부터 직접 작성하지 않고 `iop-edge`가 생성한 템플릿에서 시작한다.
|
||||
|
||||
## 4. 현재 환경 확인
|
||||
|
||||
```bash
|
||||
./iop-edge env
|
||||
```
|
||||
|
||||
출력에서 최소한 아래 항목을 확인한다.
|
||||
필요하면 `edge.yaml`의 host/port/model만 수정한다.
|
||||
|
||||
```text
|
||||
config path
|
||||
binary dir
|
||||
log dir
|
||||
artifact dir
|
||||
advertise host
|
||||
Node transport address
|
||||
artifact/bootstrap URL
|
||||
OpenAI-compatible URL
|
||||
configured nodes
|
||||
connected nodes
|
||||
```
|
||||
|
||||
`advertise host`가 비어 있으면 `iop-edge`가 host 기준으로 자동 판별한다.
|
||||
명시 설정이 있으면 자동 판별보다 설정값을 따른다.
|
||||
|
||||
로그 기본 위치는 바이너리 디렉터리 아래다.
|
||||
|
||||
```text
|
||||
<bundle-dir>/logs/
|
||||
```
|
||||
|
||||
## 5. Bootstrap artifact 확인
|
||||
|
||||
Edge archive에는 `make pack-node-target`으로 준비한 Node artifact가 들어 있어야 한다.
|
||||
|
||||
```text
|
||||
artifacts/
|
||||
bootstrap/node-<os>-<arch>.sh
|
||||
<os>-<arch>/iop-node
|
||||
<os>-<arch>/SHA256SUMS
|
||||
```
|
||||
|
||||
`node register`는 해당 target의 `artifacts/<target>/iop-node`가 있으면 현재 `edge.yaml` 기준으로 bootstrap script와 checksum을 다시 생성한다.
|
||||
즉 build machine에서 만든 archive를 다른 Edge host에 풀어도, Edge host에서 config를 확정한 뒤 등록하면 Node가 받을 URL과 Edge 주소가 현재 host 기준으로 맞춰진다.
|
||||
|
||||
새 Node binary를 Edge host에서 직접 교체한 경우에만 `./iop-edge bootstrap pack --target <goos>-<goarch> --node-binary <path>`로 다시 pack한다.
|
||||
|
||||
## 6. Node 등록과 bootstrap 출력
|
||||
|
||||
Node 정의는 `edge.yaml`을 직접 편집하지 않고 `iop-edge node register`로 만든다.
|
||||
|
||||
기본 등록:
|
||||
## 3. Node 등록
|
||||
|
||||
```bash
|
||||
./iop-edge node register node-silicon-ollama
|
||||
./iop-edge node register node-1
|
||||
```
|
||||
|
||||
Ollama Node로 등록할 때:
|
||||
Ollama Node:
|
||||
|
||||
```bash
|
||||
./iop-edge node register node-silicon-ollama \
|
||||
--target darwin-arm64 \
|
||||
./iop-edge node register node-1 \
|
||||
--adapter ollama \
|
||||
--ollama-base-url http://127.0.0.1:11434
|
||||
```
|
||||
|
||||
이 명령은 `edge.yaml`의 `nodes[]`를 생성 또는 갱신하고, 마지막에 Node host에서 실행할 bootstrap 명령을 출력한다.
|
||||
`--target` 값은 `artifacts/<target>/`에 들어 있는 Node artifact와 같아야 한다.
|
||||
출력된 `curl ... | bash -s <token>` 한 줄을 복사한다.
|
||||
|
||||
출력 예:
|
||||
|
||||
```bash
|
||||
curl -fsSL <bootstrap-url> | bash -s <generated-token>
|
||||
```
|
||||
|
||||
사용자는 이 한 줄만 Node host에서 실행한다.
|
||||
전달받은 명령에 `IOP_ARTIFACT_BASE_URL=`, `IOP_EDGE_ADDR=`, `IOP_NODE_TOKEN=` 같은 named parameter가 있으면 기본 경로가 아니다.
|
||||
|
||||
## 7. Config 검증
|
||||
## 4. Edge 실행
|
||||
|
||||
```bash
|
||||
./iop-edge config check
|
||||
```
|
||||
|
||||
이 단계에서 `nodes[]` token/id/alias 충돌, adapter/runtime 설정 오류가 잡혀야 한다.
|
||||
|
||||
## 8. Edge 실행
|
||||
|
||||
개발 중 foreground 실행:
|
||||
|
||||
```bash
|
||||
./iop-edge serve
|
||||
```
|
||||
|
||||
`serve`는 Edge transport와 함께 bootstrap artifact 서버도 시작한다.
|
||||
기본 artifact 경로는 `<bundle-dir>/artifacts/`이고 기본 bootstrap URL 포트는 `18080`이다.
|
||||
|
||||
서비스로 올릴 때:
|
||||
서비스 실행:
|
||||
|
||||
```bash
|
||||
sudo ./iop-edge setup --enable --start
|
||||
```
|
||||
|
||||
로그 확인:
|
||||
## 5. Node 설치
|
||||
|
||||
Node host에서 복사한 명령을 실행한다.
|
||||
|
||||
```bash
|
||||
curl -fsSL http://<edge-host>:18080/bootstrap/node.sh | bash -s <token>
|
||||
```
|
||||
|
||||
`node.sh`가 OS/architecture를 자동 감지한다.
|
||||
|
||||
## 6. 확인
|
||||
|
||||
```bash
|
||||
tail -f logs/edge.log
|
||||
```
|
||||
|
||||
## 9. Node host에서 bootstrap 실행
|
||||
|
||||
`iop-edge node register ...`가 출력한 한 줄을 Node host에서 그대로 실행한다.
|
||||
|
||||
```bash
|
||||
curl -fsSL <bootstrap-url> | bash -s <generated-token>
|
||||
```
|
||||
|
||||
Node host 사용자는 `node.yaml`을 만들거나 편집하지 않는다.
|
||||
Node 프로세스 실행 방식은 bootstrap 내부 구현 세부다.
|
||||
|
||||
## 10. 연결 확인
|
||||
|
||||
Edge host 로그에서 Node 등록 이벤트를 확인한다.
|
||||
|
||||
```bash
|
||||
tail -f logs/edge.log
|
||||
```
|
||||
|
||||
기대 로그:
|
||||
|
||||
```text
|
||||
node registered
|
||||
```
|
||||
|
||||
설정에 등록된 Node 목록은 아래 명령으로 확인한다.
|
||||
|
||||
```bash
|
||||
./iop-edge nodes list
|
||||
```
|
||||
|
||||
OpenAI-compatible smoke는 OpenAI 입력 표면과 Ollama model 설정이 완료된 뒤 실행한다.
|
||||
정상: `node registered`
|
||||
|
||||
## Build Options
|
||||
|
||||
```bash
|
||||
./iop-edge smoke openai --model gemma4:26b
|
||||
make build NODE_TARGETS="darwin-arm64"
|
||||
make build-edge && make pack-edge
|
||||
make pack-node-target NODE_TARGET=darwin-arm64 && make pack-edge
|
||||
```
|
||||
|
||||
## 11. Cline 접속 정보 확인
|
||||
|
||||
```bash
|
||||
./iop-edge env
|
||||
```
|
||||
|
||||
출력의 OpenAI-compatible URL을 Cline에 넣는다.
|
||||
|
||||
```text
|
||||
Base URL: <OpenAI-compatible URL>
|
||||
Model: gemma4:26b
|
||||
API Key: ASCII 값
|
||||
```
|
||||
|
||||
API Key에는 한글이나 non-ASCII 문자를 넣지 않는다.
|
||||
|
||||
## 12. 문제 발생 시 공유할 것
|
||||
|
||||
```bash
|
||||
./iop-edge env
|
||||
./iop-edge nodes list
|
||||
./iop-edge config check
|
||||
tail -n 120 logs/edge.log
|
||||
```
|
||||
|
||||
공유할 때 token 값은 지운다.
|
||||
|
|
|
|||
Loading…
Reference in a new issue