feat(runtime-control): 注册流程改为通过AuthZ服务

注册现在通过AuthZ进行处理,而登录/运行时查找仍然使用deploy-control。
更新了API调用逻辑,将注册请求从直接调用deploy-control和instance-api
改为统一调用AuthZ服务。

- 修改了注册API路由(/api/runtime/register)以使用callAuthzService
- 更新README.md文档说明新的架构流程
- 添加AUTHZ_API_BASE_URL环境变量配置
- 更新注册页面描述信息
- 移除了不再使用的callDeployControl和callInstanceApi相关代码
This commit is contained in:
2026-03-16 11:07:08 +08:00
parent be30aa9465
commit df5e3d693c
16 changed files with 247 additions and 16 deletions

4
auth-portal/src/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
.next/
*.tsbuildinfo
npm-debug.log*

View File

@ -8,9 +8,10 @@ Dedicated login/register frontend for nanobot containers.
## Env ## Env
The portal now talks to the deployment control API on the server side: Registration now goes through AuthZ, while login/runtime lookup still uses deploy-control:
```bash ```bash
AUTHZ_API_BASE_URL=http://127.0.0.1:19090
DEPLOY_API_BASE_URL=http://127.0.0.1:8090 DEPLOY_API_BASE_URL=http://127.0.0.1:8090
DEPLOY_API_TOKEN=change-me DEPLOY_API_TOKEN=change-me
``` ```

View File

@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import type { TokenResponse } from '@/types/auth'; import type { TokenResponse } from '@/types/auth';
import { HttpError, callDeployControl, callInstanceApi, normalizeTokenResponse } from '@/lib/runtime-control'; import { HttpError, callAuthzService } from '@/lib/runtime-control';
function errorStatus(error: unknown): number { function errorStatus(error: unknown): number {
if (error instanceof HttpError) { if (error instanceof HttpError) {
@ -32,23 +32,13 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ detail: 'username and password are required' }, { status: 400 }); return NextResponse.json({ detail: 'username and password are required' }, { status: 400 });
} }
const routing = await callDeployControl<{ const response = await callAuthzService<TokenResponse>('/portal/register', {
api_base_url?: string;
frontend_base_url?: string;
public_url?: string;
}>('/api/instances/register', {
username, username,
email, email,
password, password,
}); });
const response = await callInstanceApi<TokenResponse>(routing.api_base_url || '', '/api/auth/register', { return NextResponse.json(response);
username,
email,
password,
});
return NextResponse.json(normalizeTokenResponse(response, routing));
} catch (error) { } catch (error) {
return NextResponse.json({ detail: errorDetail(error) }, { status: errorStatus(error) }); return NextResponse.json({ detail: errorDetail(error) }, { status: errorStatus(error) });
} }

View File

@ -47,7 +47,7 @@ export default function RegisterPage() {
<div className="portal-notes"> <div className="portal-notes">
<div className="portal-note"> <div className="portal-note">
<strong></strong> <strong></strong>
deploy-control AuthZ backend auth portal AuthZ deploy-control backend auth portal
</div> </div>
<div className="portal-note"> <div className="portal-note">
<strong></strong> <strong></strong>

View File

@ -1,2 +1,3 @@
AUTHZ_API_BASE_URL=http://127.0.0.1:19090
DEPLOY_API_BASE_URL=http://127.0.0.1:8090 DEPLOY_API_BASE_URL=http://127.0.0.1:8090
DEPLOY_API_TOKEN=change-me DEPLOY_API_TOKEN=change-me

View File

@ -1,5 +1,6 @@
import type { TokenResponse } from '@/types/auth'; import type { TokenResponse } from '@/types/auth';
const AUTHZ_API_BASE_URL = (process.env.AUTHZ_API_BASE_URL || 'http://127.0.0.1:19090').trim().replace(/\/+$/, '');
const DEPLOY_API_BASE_URL = (process.env.DEPLOY_API_BASE_URL || 'http://127.0.0.1:8090').trim().replace(/\/+$/, ''); const DEPLOY_API_BASE_URL = (process.env.DEPLOY_API_BASE_URL || 'http://127.0.0.1:8090').trim().replace(/\/+$/, '');
const DEPLOY_API_TOKEN = (process.env.DEPLOY_API_TOKEN || '').trim(); const DEPLOY_API_TOKEN = (process.env.DEPLOY_API_TOKEN || '').trim();
const REQUEST_TIMEOUT_MS = 15000; const REQUEST_TIMEOUT_MS = 15000;
@ -79,6 +80,13 @@ export async function callDeployControl<T>(path: string, payload: JsonObject): P
}); });
} }
export async function callAuthzService<T>(path: string, payload: JsonObject): Promise<T> {
return fetchJson<T>(`${AUTHZ_API_BASE_URL}${path}`, {
method: 'POST',
body: JSON.stringify(payload),
});
}
export async function callInstanceApi<T>(apiBaseUrl: string, path: string, payload: JsonObject): Promise<T> { export async function callInstanceApi<T>(apiBaseUrl: string, path: string, payload: JsonObject): Promise<T> {
const baseUrl = apiBaseUrl.trim().replace(/\/+$/, ''); const baseUrl = apiBaseUrl.trim().replace(/\/+$/, '');
if (!baseUrl) { if (!baseUrl) {

File diff suppressed because one or more lines are too long

View File

@ -45,6 +45,8 @@ curl http://127.0.0.1:19090/.well-known/jwks.json
- 显式设置 `AUTHZ_INTERNAL_TOKEN` - 显式设置 `AUTHZ_INTERNAL_TOKEN`
- 显式设置外部可访问的 `AUTHZ_ISSUER` - 显式设置外部可访问的 `AUTHZ_ISSUER`
- 例如 `https://authz.example.com` - 例如 `https://authz.example.com`
- 如果要让 AuthZ 负责编排实例注册,还要设置 `DEPLOY_API_BASE_URL`
- 如果 deploy-control 开了鉴权,还要设置 `DEPLOY_API_TOKEN`
- 不要把 `src/data/` 里的本地示例或真实数据直接拿去打镜像 - 不要把 `src/data/` 里的本地示例或真实数据直接拿去打镜像
## API 说明 ## API 说明

View File

@ -1,3 +1,5 @@
AUTHZ_ISSUER=http://127.0.0.1:19090 AUTHZ_ISSUER=http://127.0.0.1:19090
AUTHZ_INTERNAL_TOKEN=change-me AUTHZ_INTERNAL_TOKEN=change-me
AUTHZ_ACCESS_TOKEN_TTL_SECONDS=3600 AUTHZ_ACCESS_TOKEN_TTL_SECONDS=3600
DEPLOY_API_BASE_URL=http://127.0.0.1:8090
DEPLOY_API_TOKEN=change-me

3
authz-service/src/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.venv/
__pycache__/
*.pyc

View File

@ -80,6 +80,14 @@ Authorization: Bearer <AUTHZ_INTERNAL_TOKEN>
3. 配置 `POST /backends/{backend_id}/permissions` 3. 配置 `POST /backends/{backend_id}/permissions`
4.`POST /oauth/token` 获取 token 4.`POST /oauth/token` 获取 token
### 流程 C由 Auth Portal 发起的一站式注册
1. Auth Portal 调用 `POST /portal/register`
2. AuthZ 先调用 deploy-control 创建或解析实例
3. AuthZ 再调用实例自己的 `POST /api/auth/register`
4. 实例在注册过程中回调 AuthZ 的 `/oauth/register` / `/backends/register`
5. AuthZ 将最终 token 和 backend 连接信息回传给 Auth Portal
## 注册时需要提供什么信息 ## 注册时需要提供什么信息
### 用户注册接口:`POST /oauth/register` ### 用户注册接口:`POST /oauth/register`

View File

@ -5,6 +5,7 @@ import re
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
import httpx
from fastapi import Depends, FastAPI, Header, HTTPException, Request from fastapi import Depends, FastAPI, Header, HTTPException, Request
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
@ -18,6 +19,7 @@ from app.models import (
OAuthTokenRequest, OAuthTokenRequest,
OAuthTokenResponse, OAuthTokenResponse,
OutlookSettings, OutlookSettings,
PortalRegisterRequest,
RegisterBackendRequest, RegisterBackendRequest,
RegisterBackendResponse, RegisterBackendResponse,
RegisterUserRequest, RegisterUserRequest,
@ -35,6 +37,9 @@ ISSUER = os.getenv("AUTHZ_ISSUER", "http://127.0.0.1:19090").rstrip("/")
INTERNAL_TOKEN = os.getenv("AUTHZ_INTERNAL_TOKEN", "dev-internal-token") INTERNAL_TOKEN = os.getenv("AUTHZ_INTERNAL_TOKEN", "dev-internal-token")
ACCESS_TOKEN_TTL_SECONDS = int(os.getenv("AUTHZ_ACCESS_TOKEN_TTL_SECONDS", "3600")) ACCESS_TOKEN_TTL_SECONDS = int(os.getenv("AUTHZ_ACCESS_TOKEN_TTL_SECONDS", "3600"))
PRIVATE_KEY_PATH = Path(os.getenv("AUTHZ_PRIVATE_KEY_PATH", DATA_DIR / "signing_key.pem")) PRIVATE_KEY_PATH = Path(os.getenv("AUTHZ_PRIVATE_KEY_PATH", DATA_DIR / "signing_key.pem"))
DEPLOY_API_BASE_URL = os.getenv("DEPLOY_API_BASE_URL", "http://127.0.0.1:8090").rstrip("/")
DEPLOY_API_TOKEN = os.getenv("DEPLOY_API_TOKEN", "").strip()
UPSTREAM_TIMEOUT_SECONDS = float(os.getenv("AUTHZ_UPSTREAM_TIMEOUT_SECONDS", "15"))
store = JsonStore(DATA_DIR) store = JsonStore(DATA_DIR)
signer = JwtSigner(PRIVATE_KEY_PATH, ISSUER, ACCESS_TOKEN_TTL_SECONDS) signer = JwtSigner(PRIVATE_KEY_PATH, ISSUER, ACCESS_TOKEN_TTL_SECONDS)
@ -69,6 +74,106 @@ def _clean_optional(value: str | None) -> str | None:
return cleaned or None return cleaned or None
def _as_object(value: Any) -> dict[str, Any]:
return value if isinstance(value, dict) else {}
def _as_string(value: Any) -> str:
return value.strip() if isinstance(value, str) else ""
def _http_error_detail(response: httpx.Response) -> str:
try:
payload = response.json()
except ValueError:
payload = {}
detail = _as_string(_as_object(payload).get("detail"))
return detail or response.text.strip() or f"upstream request failed with status {response.status_code}"
async def _request_json(
method: str,
url: str,
*,
json_body: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
) -> dict[str, Any]:
try:
async with httpx.AsyncClient(
timeout=UPSTREAM_TIMEOUT_SECONDS,
follow_redirects=True,
trust_env=False,
) as client:
response = await client.request(
method,
url,
json=json_body,
headers=headers,
)
except httpx.TimeoutException as exc:
raise HTTPException(status_code=504, detail="upstream request timed out") from exc
except httpx.HTTPError as exc:
raise HTTPException(status_code=502, detail=str(exc)) from exc
if response.is_error:
raise HTTPException(status_code=response.status_code, detail=_http_error_detail(response))
if not response.content:
return {}
try:
payload = response.json()
except ValueError as exc:
raise HTTPException(status_code=502, detail="upstream response was not valid JSON") from exc
if not isinstance(payload, dict):
raise HTTPException(status_code=502, detail="upstream response must be a JSON object")
return payload
async def _call_deploy_control(path: str, payload: dict[str, Any]) -> dict[str, Any]:
headers: dict[str, str] = {}
if DEPLOY_API_TOKEN:
headers["Authorization"] = f"Bearer {DEPLOY_API_TOKEN}"
return await _request_json(
"POST",
f"{DEPLOY_API_BASE_URL}{path}",
json_body=payload,
headers=headers,
)
async def _call_instance_api(base_url: str, path: str, payload: dict[str, Any]) -> dict[str, Any]:
normalized_base_url = base_url.rstrip("/")
if not normalized_base_url:
raise HTTPException(status_code=502, detail="instance api base url is missing")
return await _request_json(
"POST",
f"{normalized_base_url}{path}",
json_body=payload,
)
def _normalize_portal_token_response(
response: dict[str, Any],
routing: dict[str, Any],
) -> dict[str, Any]:
frontend_base_url = _as_string(routing.get("frontend_base_url"))
api_base_url = _as_string(routing.get("api_base_url")) or _as_string(routing.get("public_url"))
public_url = _as_string(routing.get("public_url")) or api_base_url
backend_connection = _as_object(response.get("backend_connection"))
merged_backend_connection = {
**backend_connection,
"frontend_base_url": _as_string(backend_connection.get("frontend_base_url")) or frontend_base_url or public_url or None,
"api_base_url": _as_string(backend_connection.get("api_base_url")) or api_base_url or public_url or None,
"public_base_url": _as_string(backend_connection.get("public_base_url")) or public_url or api_base_url or None,
}
return {
**response,
"backend_connection": merged_backend_connection,
}
def _require_internal(authorization: str | None = Header(default=None)) -> None: def _require_internal(authorization: str | None = Header(default=None)) -> None:
token = "" token = ""
if authorization and authorization.lower().startswith("bearer "): if authorization and authorization.lower().startswith("bearer "):
@ -282,6 +387,55 @@ async def jwks() -> dict[str, Any]:
return signer.build_jwks() return signer.build_jwks()
@app.post("/portal/register")
async def portal_register(req: PortalRegisterRequest) -> dict[str, Any]:
username = req.username.strip()
if not username:
raise HTTPException(status_code=400, detail="username is required")
if not req.password:
raise HTTPException(status_code=400, detail="password is required")
deploy_payload: dict[str, Any] = {
"username": username,
"password": req.password,
"authz_base_url": ISSUER,
}
email = _clean_optional(req.email)
if email is not None:
deploy_payload["email"] = email
optional_fields = {
"instance_id": _clean_optional(req.instance_id),
"backend_name": _clean_optional(req.backend_name),
"provider": _clean_optional(req.provider),
"model": _clean_optional(req.model),
"api_key": _clean_optional(req.api_key),
"api_base": _clean_optional(req.api_base),
"image_name": _clean_optional(req.image_name),
}
for key, value in optional_fields.items():
if value is not None:
deploy_payload[key] = value
if req.replace:
deploy_payload["replace"] = True
routing = await _call_deploy_control("/api/instances/register", deploy_payload)
api_base_url = _as_string(routing.get("api_base_url")) or _as_string(routing.get("public_url"))
instance_payload: dict[str, Any] = {
"username": username,
"password": req.password,
"authz_base_url": ISSUER,
}
if email is not None:
instance_payload["email"] = email
backend_name = _clean_optional(req.backend_name)
if backend_name is not None:
instance_payload["backend_name"] = backend_name
response = await _call_instance_api(api_base_url, "/api/auth/register", instance_payload)
return _normalize_portal_token_response(response, routing)
@app.post("/backends/register", response_model=RegisterBackendResponse) @app.post("/backends/register", response_model=RegisterBackendResponse)
async def register_backend(req: RegisterBackendRequest) -> RegisterBackendResponse: async def register_backend(req: RegisterBackendRequest) -> RegisterBackendResponse:
backend_name, backend_id, base_url, frontend_base_url = _resolve_register_backend_payload(req) backend_name, backend_id, base_url, frontend_base_url = _resolve_register_backend_payload(req)

View File

@ -134,6 +134,20 @@ class RegisterUserResponse(BaseModel):
backend: RegisterUserBackendResult backend: RegisterUserBackendResult
class PortalRegisterRequest(BaseModel):
username: str
password: str
email: str | None = None
instance_id: str | None = None
backend_name: str | None = None
provider: str | None = None
model: str | None = None
api_key: str | None = None
api_base: str | None = None
image_name: str | None = None
replace: bool = False
class RotateSecretResponse(BaseModel): class RotateSecretResponse(BaseModel):
backend_id: str backend_id: str
client_id: str client_id: str

View File

@ -6,6 +6,7 @@ requires-python = ">=3.10"
dependencies = [ dependencies = [
"fastapi>=0.115.0,<1.0.0", "fastapi>=0.115.0,<1.0.0",
"uvicorn[standard]>=0.34.0,<1.0.0", "uvicorn[standard]>=0.34.0,<1.0.0",
"httpx>=0.28.0,<1.0.0",
"pydantic>=2.12.0,<3.0.0", "pydantic>=2.12.0,<3.0.0",
"cryptography>=45.0.0,<46.0.0", "cryptography>=45.0.0,<46.0.0",
"PyJWT>=2.10.0,<3.0.0", "PyJWT>=2.10.0,<3.0.0",

View File

@ -41,6 +41,7 @@ source = { editable = "." }
dependencies = [ dependencies = [
{ name = "cryptography" }, { name = "cryptography" },
{ name = "fastapi" }, { name = "fastapi" },
{ name = "httpx" },
{ name = "pydantic" }, { name = "pydantic" },
{ name = "pyjwt" }, { name = "pyjwt" },
{ name = "python-multipart" }, { name = "python-multipart" },
@ -56,6 +57,7 @@ dev = [
requires-dist = [ requires-dist = [
{ name = "cryptography", specifier = ">=45.0.0,<46.0.0" }, { name = "cryptography", specifier = ">=45.0.0,<46.0.0" },
{ name = "fastapi", specifier = ">=0.115.0,<1.0.0" }, { name = "fastapi", specifier = ">=0.115.0,<1.0.0" },
{ name = "httpx", specifier = ">=0.28.0,<1.0.0" },
{ name = "pydantic", specifier = ">=2.12.0,<3.0.0" }, { name = "pydantic", specifier = ">=2.12.0,<3.0.0" },
{ name = "pyjwt", specifier = ">=2.10.0,<3.0.0" }, { name = "pyjwt", specifier = ">=2.10.0,<3.0.0" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.0,<9.0.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.0,<9.0.0" },
@ -64,6 +66,15 @@ requires-dist = [
] ]
provides-extras = ["dev"] provides-extras = ["dev"]
[[package]]
name = "certifi"
version = "2026.2.25"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
]
[[package]] [[package]]
name = "cffi" name = "cffi"
version = "2.0.0" version = "2.0.0"
@ -251,6 +262,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
] ]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]] [[package]]
name = "httptools" name = "httptools"
version = "0.7.1" version = "0.7.1"
@ -294,6 +318,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" }, { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" },
] ]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.11" version = "3.11"

View File

@ -11,6 +11,8 @@ HOST_BIND_IP="${HOST_BIND_IP:-0.0.0.0}"
AUTHZ_ISSUER="${AUTHZ_ISSUER:-http://127.0.0.1:${HOST_PORT}}" AUTHZ_ISSUER="${AUTHZ_ISSUER:-http://127.0.0.1:${HOST_PORT}}"
AUTHZ_INTERNAL_TOKEN="${AUTHZ_INTERNAL_TOKEN:-dev-internal-token}" AUTHZ_INTERNAL_TOKEN="${AUTHZ_INTERNAL_TOKEN:-dev-internal-token}"
AUTHZ_ACCESS_TOKEN_TTL_SECONDS="${AUTHZ_ACCESS_TOKEN_TTL_SECONDS:-3600}" AUTHZ_ACCESS_TOKEN_TTL_SECONDS="${AUTHZ_ACCESS_TOKEN_TTL_SECONDS:-3600}"
DEPLOY_API_BASE_URL="${DEPLOY_API_BASE_URL:-http://127.0.0.1:8090}"
DEPLOY_API_TOKEN="${DEPLOY_API_TOKEN:-}"
FORCE_BUILD=0 FORCE_BUILD=0
REPLACE=0 REPLACE=0
@ -65,6 +67,8 @@ docker run -d \
-e "AUTHZ_ISSUER=${AUTHZ_ISSUER}" \ -e "AUTHZ_ISSUER=${AUTHZ_ISSUER}" \
-e "AUTHZ_INTERNAL_TOKEN=${AUTHZ_INTERNAL_TOKEN}" \ -e "AUTHZ_INTERNAL_TOKEN=${AUTHZ_INTERNAL_TOKEN}" \
-e "AUTHZ_ACCESS_TOKEN_TTL_SECONDS=${AUTHZ_ACCESS_TOKEN_TTL_SECONDS}" \ -e "AUTHZ_ACCESS_TOKEN_TTL_SECONDS=${AUTHZ_ACCESS_TOKEN_TTL_SECONDS}" \
-e "DEPLOY_API_BASE_URL=${DEPLOY_API_BASE_URL}" \
-e "DEPLOY_API_TOKEN=${DEPLOY_API_TOKEN}" \
"${IMAGE_NAME}" >/dev/null "${IMAGE_NAME}" >/dev/null
printf 'container_name=%s\n' "${CONTAINER_NAME}" printf 'container_name=%s\n' "${CONTAINER_NAME}"