# 配置（oakapp.toml）

每个应用目录下都必须有一个 oakapp.toml 文件，其中包含应用的静态元数据和构建步骤。 元数据包括应用的标识符和版本。构建步骤是在容器中执行以构建或运行应用的命令。 示例 oakapp.toml 文件：

```toml
# (必需) 应用标识符
identifier = "com.luxonis.python_demo"
# (必需) 应用入口点
entrypoint = ["bash", "-c", "python3 /app/main.py"]

# (可选) 准备容器命令
# 这里是您可以在运行时安装所有依赖的地方
prepare_container = [
    { type = "COPY", source = "requirements.txt", target = "requirements.txt" },
    { type = "RUN", command = "apt-get update" },
    { type = "RUN", command = "apt-get install -y python3-pip" },
    { type = "RUN", command = "pip3 install -r /app/requirements.txt --break-system-packages" },
]

# (可选) 准备构建依赖
# 这里是您可以在构建时安装所有依赖的地方
prepare_build_container = [
    # 示例: npm, gcc, ...
]

# (可选) 所有应用程序文件复制到容器后的附加命令
build_steps = []
```

## 配置字段

### 应用元数据

 * identifier - 应用名称，采用 Java 风格的格式（以点号分隔；例如 com.my_company.my_app）
   * com.luxonis 命名空间保留用于官方应用
   * com.example 命名空间不推荐用于生产应用
 * app_version - 应用版本，格式为 major.minor.patch（例如 1.0.0）

### 应用构建与运行时配置

 * entrypoint - 字符串数组（argv 格式的命令）
 * cwd -（可选，默认为 /）容器的当前工作目录
 * shell -（可选，默认为 ["/bin/bash", "-c"]）用于执行 build_steps、entrypoint 以及 prepare_container 和 prepare_build_container 部分中的 RUN 命令的
   shell
 * app_dir_name（可选/高级，默认为 app）- 容器内部用于存放应用文件的目录；仅当你已经将此目录名用于其他用途时才使用

> **Shell 格式与可执行格式**
> 与 Docker 类似，所有命令既可以指定为可执行格式（字符串数组），也可以指定为 shell 格式（单个字符串）。对于
> `entrypoint`
> ，我们强烈建议使用可执行格式。

### 应用层

与 Docker 类似，应用由多个层构成：

 1. 基础镜像层（在 base_image 部分指定）
 2. 运行时层（在 prepare_container 部分构建）
 3. 构建层（在 prepare_build_container 部分构建；构建后该层与运行时层分离）
 4. 应用层（应用目录中的所有文件以及 build_steps 在此执行）

> **注意**
> 用 Docker 术语来说，每一层对应一个 Dockerfile 中的
> `RUN`
> 指令。 这既能实现高效缓存，又能避免常见问题。

除了应用层外，还有两个并行的附加层：

 * 静态前端层（如果在 static_frontend 部分指定）
 * DepthAI 模型缓存层（如果在 depthai_models 部分指定）

如果其中任何一层发生变化，后续所有层都会被重建。并行层之间没有相互依赖关系，因此彼此独立重建。

#### 基础镜像

默认基础镜像是 debian/bookworm-slim，从 registry-1.docker.io 拉取。 完整的 base_image 配置如下：

```toml
[base_image]
api_url = "https://registry-1.docker.io" # Optional: Service https address
service = "registry.docker.io" # Optional: Name of Image Registry Service
image_name = "library/debian"
image_tag = "bookworm-slim"

oauth_url = "https://auth.docker.io/token" # Optional: address to oauth 2.0 token for this service
auth_type = "repository" # Optional: scope type
auth_name = "library/debian" # Optional: name of resource
```

> **建议**
> 对于 DepthAI OAK 应用，我们推荐使用我们的
> [基础镜像](https://docs.luxonis.com/software-v3/oak-apps/base-image.md)
> ，其中包含了你可能需要的多数依赖。

#### 使用自定义基础镜像

如果你已经在使用 Docker 进行开发，可以通过将 base_image 指向你自己的镜像，在 OAK 应用中复用之前的工作。当你希望先以熟悉的 Docker 工作流程对操作系统包、运行时或共享依赖进行迭代，然后再通过 oakapp.toml
打包最终应用时，这种方法非常有用。

如果你正在开发一个可复用的基础环境，并希望在多个应用之间共享它，这也是一种很好的选择。使用自定义基础镜像来搭建稳定、共享的环境，同时使用 prepare_container 和 prepare_build_container 来处理应用特定的运行时和构建步骤。

要使用不同的镜像作为基础层，在 oakapp.toml 中按如下方式定义：

```toml
[base_image]
image_name = "myorg/my-base"
image_tag = "latest"
```

默认情况下，镜像从 Docker Hub 拉取，但你也可以通过 api_url 字段将 base_image 指向其他仓库。

> **Docker Hub 未验证的拉取**
> `oak-agent`
> 当前从 Docker Hub 进行未验证的拉取。在多次拉取或从共享网络进行构建后，Docker Hub 可能会因为匿名拉取速率限制而拒绝请求。
> 为避免这种情况，请参阅
> [自建容器仓库](#Self-Hosting%20a%20Container%20Registry)
> 。
> 如果发生该情况，构建可能会失败，并显示类似如下的错误：
> ```sh
> oakctl command failed:
> 0: Received an error RPC response: Failed to build application container:
> Image download failed: OCI registry client received an error:
> APIError(ErrorList { errors: [Error { code: "TOOMANYREQUESTS", message:
> "You have reached your unauthenticated pull rate limit.
> https://www.docker.com/increase-rate-limit", detail: Null }] })
> ```

#### 自托管容器注册表

为了避免 Docker Hub 的速率限制，你可以在开发期间自托管一个容器注册表，并将 base_image.api_url 指向它。

运行一个可从设备访问的注册表：

```bash
docker run --rm -d -p 5000:5000 --name registry registry:2
```

构建并将镜像推送到本地注册表：

```sh
echo "FROM ubuntu:26.04" > ./Dockerfile
docker buildx build --platform linux/arm64 -t myapp-base:latest --load .
docker tag myapp-base:latest localhost:5000/myapp-base:latest
docker push localhost:5000/myapp-base:latest
```

在 oakapp 中使用基础镜像。在 oakapp.toml 中添加 [base_image] 部分，如下所示：

```toml
[base_image]
api_url = "http://<self-hosted-registry-ip>:5000" # Service http address visible from the device
image_name = "myapp-base"
image_tag = "latest"
```

请注意，应用程序是在设备上构建的，因此注册表必须可从设备访问。base_image.api_url 必须指向设备网络可访问的地址。 要使上传的镜像在 OAK4 设备上正常工作，它必须以 linux/arm64 镜像的形式上传。

#### 运行时和构建层

prepare_container 和 prepare_build_container 都是在容器中执行的命令数组，用于准备相应的层。唯一的区别是构建层在运行时不存在。

可用的命令类型有：

 * RUN - 在容器中运行命令

```toml
prepare_container = [
    { type = "RUN", command = "apt-get update" },
    { type = "RUN", command = "apt-get install -y python3-pip" },
]
```

 * COPY - 从应用的目录复制文件到容器中，早于应用层的复制。这对于复制依赖文件（如 requirements.txt）很有用。此处无需复制实际的应用程序文件，因为稍后在应用层中会复制它们。
   * source - 源文件的路径，相对于主机上的应用目录
   * target - 容器内应用目录的路径（如果指定了 app_dir_name，则相对于它）

```toml
prepare_container = [
    { type = "COPY", source = "requirements.txt", target = "requirements.txt" },
]
```

#### 应用层

首先，来自应用目录的所有文件都被复制到容器中（如果指定了 app_dir_name，则复制到该目录）。然后，按顺序执行 build_steps 中的命令。

 * build_steps - 构建应用程序或其他准备命令的步骤（字符串数组）

```toml
build_steps = [
    "g++ -o /app/my_app /app/main.cpp",
    "chmod +x /app/my_app",
]
```

你可以通过在应用目录中添加 .oakappignore 文件来忽略复制到容器的文件。语法类似于 .gitignore。示例：

```plaintext
# Ignore all .log files
*.log
# Ingore node_modules/ directory
node_modules/
```

### 挂载

挂载允许你将运行应用的设备上的目录或设备链接到应用的容器中。你不能通过这种方式挂载主机上的目录。 必需的挂载必须在构建时和运行时都存在。

 * required_mounts, required_devices - 必需的挂载 - 如果在容器启动时不存在，应用将因错误而停止

语法为 source[:target[:options]]；默认值：

 * source=target
 * options="rbind,rw"
 * 所有挂载路径必须是绝对路径。对于 required_mounts、required_devices、optional_mounts、optional_devices 和 additional_mounts（source/target），相对路径会被拒绝。

示例：

```toml
required_mounts = [
    "/data/my-storage:/app/storage:rw,rbind",
]
```

 * optional_mounts, optional_devices - 如果在容器启动时不存在，这些挂载将被忽略
 * additional_mounts ( source , target , type , options , required ) 对象数组，示例如下：

```toml
additional_mounts = [
    { source = "/run/user/1000/pulse", target = "/run/user/1000/pulse", type = "none", options = [
        "rbind",
        "rw",
    ], required = true },
    { source = "/home/root/.config/pulse/cookie", target = "/root/.config/pulse/cookie", type = "none", options = [
        "bind",
        "ro",
    ], required = true },
]
```

 * additional_build_mounts - 与 additional_mounts 相同，但仅用于构建时；这些挂载在运行时不存在

 * allowed_devices - 应用被允许在容器内访问的设备列表

```toml
allowed_devices = [{ allow = true, access = "rwm" }]
```

### 静态前端

静态前端层允许你构建并提供与你的应用一起使用的静态 Web 前端。请参见 [GitHub
上的示例](https://github.com/luxonis/oak-examples/blob/main/custom-frontend/open-vocabulary-object-detection/oakapp.toml)。

 * static_frontend
   * dist_path - 构建后的前端文件路径（相对于主机上的应用目录）
     * build（可选）- 前端的构建配置
       * source_path - 前端源文件的路径
       * steps - 构建前端的命令数组

```toml
[static_frontend]
dist_path = "./frontend/dist" # 构建的前端文件的路径

[static_frontend.build]
source_path = "./frontend" # 前端源文件的路径
steps = ["cd /app/frontend/src && npm install && npm run build"]
```

 * assign_frontend_port - 如果设置为 true，则会为容器内的 OAKAPP_STATIC_FRONTEND_PORT 环境变量分配一个可用端口；即使未指定 static_frontend，也可以使用此选项。

```toml
assign_frontend_port = true
```

 * static_frontend_dir_name（可选/高级；默认值为 static_frontend）- 容器内挂载静态前端文件的目录（将存储在 OAKAPP_STATIC_FRONTEND_PATH
   环境变量中）；仅当您已经将此目录名称用于其他用途时才使用。

### DepthAI 模型缓存

此部分允许您指定一个包含 *.yaml 格式的 DepthAI 模型的文件夹，该文件夹将在构建期间缓存在容器内。这样，您可以创建离线工作的应用程序，而无需在运行时下载模型。

 * depthai_models
   * yaml_path - 指向 *.yaml 格式的 DepthAI 模型文件夹的路径
   * use_only_cache - 如果设置为 true，则仅使用缓存的模型，不会在运行时下载新模型（设置 DEPTHAI_ZOO_INTERNET_CHECK 环境变量）

```toml
depthai_models = { yaml_path = "./backend/src/depthai_models" }
```

 * depthai_models_dir_name（可选/高级；默认值为 depthai_models）- 容器内挂载 DepthAI 模型的目录；仅当您已经将此目录名称用于其他用途时才使用。

### 环境变量和构建参数

环境变量仅在运行时设置。

```toml
[env]
MY_ENV_VAR = "some_value"
ANOTHER_ENV_VAR = "another_value"
```

构建参数仅在构建时设置，在运行时不可用。

```toml
[arg]
MY_BUILD_ARG = "some_build_value"
ANOTHER_BUILD_ARG = "another_build_value"
```

环境变量和构建参数的优先级均低于用户在命令行中设置的变量（oakctl app run --env MY_ENV_VAR=value）。
