feat: clarify deployment update strategies

This commit is contained in:
fawney19
2026-05-23 20:14:26 +08:00
parent 4b66cadf15
commit 18eac2dd7a
15 changed files with 399 additions and 61 deletions

View File

@@ -57,10 +57,18 @@ ADMIN_USERNAME=admin123456
# docker compose 下 app 启动前自动执行 pending migration/backfill默认 true # docker compose 下 app 启动前自动执行 pending migration/backfill默认 true
# AETHER_GATEWAY_AUTO_PREPARE_DATABASE=true # AETHER_GATEWAY_AUTO_PREPARE_DATABASE=true
# 管理后台一键更新(正式发布版默认可用) # 管理后台更新策略:
# 正式发布版会下载 GitHub Release 包,校验 SHA256 后切换 /opt/aether/current 并重启。 # - systemd/二进制部署使用 self下载 GitHub Release 包,校验 SHA256 后切换 current 并重启。
# 源码/本地构建不支持后台在线更新 # - Docker Compose 使用 docker后台只提示版本实际更新请在 compose 目录执行 ./update.sh
# - 源码/本地构建使用 manual手动拉取源码或下载 release。
# Compose 默认把持久化文件放在 ./datas/{postgres,mysql,sqlite,redis},日志放在 ./logs。
# 分布式/多节点部署不要使用 ./datas 作为共享数据目录;应使用外部共享 Postgres/MySQL 和 Redis。
# 多节点不要从管理后台一键更新单个节点应使用镜像滚动更新、systemd 分批发布或外部编排。
# AETHER_BASE_DIR=/opt/aether # AETHER_BASE_DIR=/opt/aether
# AETHER_UPDATE_STRATEGY=docker
# AETHER_DOCKER_UPDATE_COMMAND=./update.sh
# AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY=single-node
# AETHER_GATEWAY_NODE_ROLE=all
# Docker Compose 默认把应用日志输出到 stdout/stderr。 # Docker Compose 默认把应用日志输出到 stdout/stderr。
# 如需文件日志,可改成 file 或 both并把可写目录挂载到 /opt/aether/logs。 # 如需文件日志,可改成 file 或 both并把可写目录挂载到 /opt/aether/logs。
# AETHER_LOG_DESTINATION=stdout # AETHER_LOG_DESTINATION=stdout

View File

@@ -31,6 +31,7 @@ WORKDIR /opt/aether
ENV RUST_LOG=aether_gateway=info \ ENV RUST_LOG=aether_gateway=info \
APP_PORT=8084 \ APP_PORT=8084 \
AETHER_UPDATE_STRATEGY=docker \
AETHER_GATEWAY_STATIC_DIR=/opt/aether/current/frontend AETHER_GATEWAY_STATIC_DIR=/opt/aether/current/frontend
EXPOSE 8084 EXPOSE 8084

View File

@@ -136,6 +136,7 @@ ENV LANG=C.UTF-8 \
LC_ALL=C.UTF-8 \ LC_ALL=C.UTF-8 \
RUST_LOG=aether_gateway=info \ RUST_LOG=aether_gateway=info \
APP_PORT=8084 \ APP_PORT=8084 \
AETHER_UPDATE_STRATEGY=manual \
AETHER_GATEWAY_STATIC_DIR=/srv/frontend AETHER_GATEWAY_STATIC_DIR=/srv/frontend
EXPOSE 8084 EXPOSE 8084

View File

@@ -139,6 +139,7 @@ ENV LANG=C.UTF-8 \
RUST_LOG=aether_gateway=info \ RUST_LOG=aether_gateway=info \
APP_PORT=8084 \ APP_PORT=8084 \
AETHER_BASE_DIR=/opt/aether \ AETHER_BASE_DIR=/opt/aether \
AETHER_UPDATE_STRATEGY=self \
AETHER_GATEWAY_STATIC_DIR=/opt/aether/current/frontend AETHER_GATEWAY_STATIC_DIR=/opt/aether/current/frontend
EXPOSE 8084 EXPOSE 8084

View File

@@ -19,9 +19,9 @@ use crate::handlers::admin::system::shared::settings::{
}; };
use crate::handlers::admin::system::shared::smtp::build_admin_smtp_test_payload; use crate::handlers::admin::system::shared::smtp::build_admin_smtp_test_payload;
use crate::handlers::admin::system::shared::update::{ use crate::handlers::admin::system::shared::update::{
build_admin_system_update_capability_payload, prepare_admin_system_update_task, build_admin_system_update_capability_payload, current_self_update_blocker,
read_update_history, read_update_task_status, start_admin_system_rollback_task, prepare_admin_system_update_task, read_update_history, read_update_task_status,
start_admin_system_update_task, self_update_supported, start_admin_system_rollback_task, start_admin_system_update_task,
}; };
use crate::important_notification::build_important_notification_test_payload; use crate::important_notification::build_important_notification_test_payload;
use crate::maintenance::{ManualUsageCleanupMode, ManualUsageCleanupOptions}; use crate::maintenance::{ManualUsageCleanupMode, ManualUsageCleanupOptions};
@@ -99,6 +99,16 @@ pub(super) async fn maybe_build_local_admin_core_system_response(
&& request_method == http::Method::POST && request_method == http::Method::POST
&& request_path == "/api/admin/system/prepare-update" && request_path == "/api/admin/system/prepare-update"
{ {
if !self_update_supported() {
return Ok(Some(
(
http::StatusCode::PRECONDITION_REQUIRED,
Json(json!({ "detail": current_self_update_blocker() })),
)
.into_response(),
));
}
let target_version = request_body let target_version = request_body
.filter(|b| !b.is_empty()) .filter(|b| !b.is_empty())
.and_then(|body| serde_json::from_slice::<serde_json::Value>(body).ok()) .and_then(|body| serde_json::from_slice::<serde_json::Value>(body).ok())

View File

@@ -1,5 +1,8 @@
use crate::handlers::admin::request::AdminAppState; use crate::handlers::admin::request::AdminAppState;
use crate::handlers::admin::shared::build_admin_usage_counter_health_payload; use crate::handlers::admin::shared::build_admin_usage_counter_health_payload;
use crate::handlers::admin::system::shared::update::{
current_self_update_blocker, self_update_supported,
};
use crate::handlers::admin::system::shared::update_client::{ use crate::handlers::admin::system::shared::update_client::{
build_direct_update_http_client, build_update_http_client, has_explicit_update_proxy_env, build_direct_update_http_client, build_update_http_client, has_explicit_update_proxy_env,
update_github_token_from_env, update_github_token_from_env,
@@ -52,7 +55,7 @@ pub(crate) fn build_admin_system_check_update_payload_from_release(
latest_release, latest_release,
error, error,
); );
apply_source_build_check_update_override(&mut payload, current_build_is_release()); apply_self_update_check_update_override(&mut payload, self_update_supported());
payload payload
} }
@@ -62,7 +65,7 @@ pub(crate) fn build_admin_system_releases_list_payload(
) -> serde_json::Value { ) -> serde_json::Value {
let mut payload = let mut payload =
build_admin_system_releases_payload(current_aether_version(), releases, error); build_admin_system_releases_payload(current_aether_version(), releases, error);
apply_source_build_releases_override(&mut payload, current_build_is_release()); apply_self_update_releases_override(&mut payload, self_update_supported());
payload payload
} }
@@ -70,8 +73,20 @@ fn current_build_is_release() -> bool {
option_env!("AETHER_BUILD_TYPE").unwrap_or("source") == "release" option_env!("AETHER_BUILD_TYPE").unwrap_or("source") == "release"
} }
fn apply_source_build_check_update_override(payload: &mut Value, release_build: bool) { fn apply_self_update_check_update_override(payload: &mut Value, supported: bool) {
if release_build { apply_self_update_check_update_override_with_blocker(
payload,
supported,
current_self_update_blocker(),
);
}
fn apply_self_update_check_update_override_with_blocker(
payload: &mut Value,
supported: bool,
blocker: &str,
) {
if supported {
return; return;
} }
if payload.get("has_update").and_then(Value::as_bool) != Some(true) { if payload.get("has_update").and_then(Value::as_bool) != Some(true) {
@@ -79,11 +94,23 @@ fn apply_source_build_check_update_override(payload: &mut Value, release_build:
} }
payload["updatable"] = json!(false); payload["updatable"] = json!(false);
payload["update_blocker"] = json!(SOURCE_BUILD_UPDATE_BLOCKER); payload["update_blocker"] = json!(blocker);
} }
fn apply_source_build_releases_override(payload: &mut Value, release_build: bool) { fn apply_self_update_releases_override(payload: &mut Value, supported: bool) {
if release_build { apply_self_update_releases_override_with_blocker(
payload,
supported,
current_self_update_release_blocker(),
);
}
fn apply_self_update_releases_override_with_blocker(
payload: &mut Value,
supported: bool,
blocker: &str,
) {
if supported {
return; return;
} }
let Some(releases) = payload.get_mut("releases").and_then(Value::as_array_mut) else { let Some(releases) = payload.get_mut("releases").and_then(Value::as_array_mut) else {
@@ -96,11 +123,23 @@ fn apply_source_build_releases_override(payload: &mut Value, release_build: bool
} }
release["updatable"] = json!(false); release["updatable"] = json!(false);
if release.get("update_blocker").is_none() || release["update_blocker"].is_null() { if release.get("update_blocker").is_none() || release["update_blocker"].is_null() {
release["update_blocker"] = json!(SOURCE_BUILD_RELEASE_BLOCKER); release["update_blocker"] = json!(blocker);
} }
} }
} }
fn current_self_update_release_blocker() -> &'static str {
if !current_build_is_release() {
return SOURCE_BUILD_RELEASE_BLOCKER;
}
if self_update_supported() {
""
} else {
current_self_update_blocker()
}
}
#[cfg(not(test))] #[cfg(not(test))]
struct CachedReleases { struct CachedReleases {
all: Vec<AdminSystemUpdateRelease>, all: Vec<AdminSystemUpdateRelease>,
@@ -719,21 +758,25 @@ mod tests {
} }
#[test] #[test]
fn source_build_check_update_override_marks_latest_release_non_updatable() { fn self_update_check_update_override_marks_latest_release_non_updatable() {
let mut payload = json!({ let mut payload = json!({
"has_update": true, "has_update": true,
"updatable": true, "updatable": true,
"update_blocker": serde_json::Value::Null "update_blocker": serde_json::Value::Null
}); });
apply_source_build_check_update_override(&mut payload, false); apply_self_update_check_update_override_with_blocker(
&mut payload,
false,
SOURCE_BUILD_UPDATE_BLOCKER,
);
assert_eq!(payload["updatable"], false); assert_eq!(payload["updatable"], false);
assert_eq!(payload["update_blocker"], SOURCE_BUILD_UPDATE_BLOCKER); assert_eq!(payload["update_blocker"], SOURCE_BUILD_UPDATE_BLOCKER);
} }
#[test] #[test]
fn source_build_releases_override_marks_non_current_entries_non_updatable() { fn self_update_releases_override_marks_non_current_entries_non_updatable() {
let mut payload = json!({ let mut payload = json!({
"releases": [ "releases": [
{ {
@@ -751,7 +794,11 @@ mod tests {
] ]
}); });
apply_source_build_releases_override(&mut payload, false); apply_self_update_releases_override_with_blocker(
&mut payload,
false,
SOURCE_BUILD_RELEASE_BLOCKER,
);
assert_eq!(payload["releases"][0]["updatable"], false); assert_eq!(payload["releases"][0]["updatable"], false);
assert_eq!( assert_eq!(

View File

@@ -113,6 +113,74 @@ const MAX_SHA256SUMS_DOWNLOAD_BYTES: u64 = 1024 * 1024;
const MAX_EXTRACTED_RELEASE_BYTES: u64 = 1024 * 1024 * 1024; const MAX_EXTRACTED_RELEASE_BYTES: u64 = 1024 * 1024 * 1024;
const DEFAULT_UPDATE_DOWNLOAD_TIMEOUT_SECS: u64 = 600; const DEFAULT_UPDATE_DOWNLOAD_TIMEOUT_SECS: u64 = 600;
const DEFAULT_UPDATE_DOWNLOAD_IDLE_TIMEOUT_SECS: u64 = 30; const DEFAULT_UPDATE_DOWNLOAD_IDLE_TIMEOUT_SECS: u64 = 30;
const SOURCE_BUILD_UPDATE_BLOCKER: &str = "当前为源码构建,请使用 git pull 后重新编译。";
const DOCKER_UPDATE_BLOCKER: &str =
"Docker 部署请使用镜像更新:进入 docker-compose.yml 所在目录执行 ./update.sh。";
const MANUAL_UPDATE_BLOCKER: &str =
"当前部署策略不支持在线自更新,请手动下载 Release 或使用安装脚本更新。";
const MULTI_NODE_UPDATE_BLOCKER: &str =
"多节点部署不支持在管理后台更新单个节点,请使用镜像滚动更新或外部发布编排。";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum UpdateStrategy {
SelfManaged,
Docker,
Manual,
}
impl UpdateStrategy {
fn from_env_value(value: Option<&str>, release_build: bool) -> Self {
let Some(value) = value.map(str::trim).filter(|value| !value.is_empty()) else {
return if release_build {
Self::SelfManaged
} else {
Self::Manual
};
};
match value.to_ascii_lowercase().as_str() {
"self" | "self-managed" | "binary" | "systemd" | "launchd" => Self::SelfManaged,
"docker" | "compose" | "docker-compose" | "container" => Self::Docker,
"manual" | "source" | "none" | "off" | "disabled" => Self::Manual,
_ => Self::Manual,
}
}
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::SelfManaged => "self",
Self::Docker => "docker",
Self::Manual => "manual",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DeploymentTopology {
SingleNode,
MultiNode,
}
impl DeploymentTopology {
fn from_env_value(value: Option<&str>) -> Self {
match value
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_ascii_lowercase)
.as_deref()
{
Some("multi-node" | "multi" | "cluster") => Self::MultiNode,
_ => Self::SingleNode,
}
}
fn as_str(self) -> &'static str {
match self {
Self::SingleNode => "single-node",
Self::MultiNode => "multi-node",
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub(crate) struct UpdateHistoryEntry { pub(crate) struct UpdateHistoryEntry {
@@ -249,12 +317,84 @@ fn is_release_build() -> bool {
true true
} }
pub(crate) fn current_update_strategy() -> UpdateStrategy {
UpdateStrategy::from_env_value(
std::env::var("AETHER_UPDATE_STRATEGY").ok().as_deref(),
is_release_build(),
)
}
fn current_deployment_topology() -> DeploymentTopology {
DeploymentTopology::from_env_value(
std::env::var("AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY")
.ok()
.as_deref(),
)
}
fn self_update_supported_for(
release_build: bool,
update_strategy: UpdateStrategy,
deployment_topology: DeploymentTopology,
) -> bool {
release_build
&& update_strategy == UpdateStrategy::SelfManaged
&& deployment_topology == DeploymentTopology::SingleNode
}
pub(crate) fn self_update_supported() -> bool {
self_update_supported_for(
is_release_build(),
current_update_strategy(),
current_deployment_topology(),
)
}
pub(crate) fn current_self_update_blocker() -> &'static str {
if !is_release_build() {
return SOURCE_BUILD_UPDATE_BLOCKER;
}
if current_deployment_topology() == DeploymentTopology::MultiNode {
return MULTI_NODE_UPDATE_BLOCKER;
}
match current_update_strategy() {
UpdateStrategy::SelfManaged => "一键更新可用",
UpdateStrategy::Docker => DOCKER_UPDATE_BLOCKER,
UpdateStrategy::Manual => MANUAL_UPDATE_BLOCKER,
}
}
fn update_logs_dir() -> PathBuf {
std::env::var("AETHER_LOG_DIR")
.ok()
.filter(|value| !value.trim().is_empty())
.map(PathBuf::from)
.unwrap_or_else(|| aether_base_dir().join("logs"))
}
fn docker_update_command() -> String {
std::env::var("AETHER_DOCKER_UPDATE_COMMAND")
.ok()
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| "./update.sh".to_string())
}
pub(crate) fn build_admin_system_update_capability_payload() -> serde_json::Value { pub(crate) fn build_admin_system_update_capability_payload() -> serde_json::Value {
let supported = is_release_build();
let build_type = current_build_type(); let build_type = current_build_type();
let rollback_available = find_rollback_target().is_some(); let update_strategy = current_update_strategy();
let deployment_topology = current_deployment_topology();
let supported =
self_update_supported_for(is_release_build(), update_strategy, deployment_topology);
let rollback_available = supported && find_rollback_target().is_some();
let task_status = read_update_task_status(); let task_status = read_update_task_status();
let base_dir = aether_base_dir(); let base_dir = aether_base_dir();
let docker_command = if update_strategy == UpdateStrategy::Docker {
Some(docker_update_command())
} else {
None
};
let data_dir = base_dir.join("data");
json!({ json!({
"supported": supported, "supported": supported,
"enabled": supported, "enabled": supported,
@@ -262,11 +402,19 @@ pub(crate) fn build_admin_system_update_capability_payload() -> serde_json::Valu
"task_status": task_status.phase, "task_status": task_status.phase,
"task_error": task_status.error, "task_error": task_status.error,
"build_type": build_type, "build_type": build_type,
"install_root": base_dir, "update_strategy": update_strategy.as_str(),
"strategy": update_strategy.as_str(),
"deployment_topology": deployment_topology.as_str(),
"topology": deployment_topology.as_str(),
"install_root": base_dir.clone(),
"base_dir": base_dir,
"data_dir": data_dir,
"logs_dir": update_logs_dir(),
"docker_update_command": docker_command,
"message": if supported { "message": if supported {
"一键更新可用" "一键更新可用"
} else { } else {
"源码构建不支持在线更新" current_self_update_blocker()
}, },
}) })
} }
@@ -291,8 +439,8 @@ pub(crate) async fn prepare_admin_system_update_task(
tarball_url: String, tarball_url: String,
sha256sums_url: Option<String>, sha256sums_url: Option<String>,
) -> Result<Result<serde_json::Value, (http::StatusCode, serde_json::Value)>, GatewayError> { ) -> Result<Result<serde_json::Value, (http::StatusCode, serde_json::Value)>, GatewayError> {
if !is_release_build() { if !self_update_supported() {
return Ok(Err(source_build_rejection_response())); return Ok(Err(self_update_rejection_response()));
} }
let Some(sha256sums_url) = sha256sums_url.filter(|url| !url.trim().is_empty()) else { let Some(sha256sums_url) = sha256sums_url.filter(|url| !url.trim().is_empty()) else {
return Ok(Err(( return Ok(Err((
@@ -692,8 +840,8 @@ fn remove_path_if_exists(path: &Path) -> std::io::Result<()> {
pub(crate) async fn start_admin_system_update_task( pub(crate) async fn start_admin_system_update_task(
version: Option<String>, version: Option<String>,
) -> Result<Result<serde_json::Value, (http::StatusCode, serde_json::Value)>, GatewayError> { ) -> Result<Result<serde_json::Value, (http::StatusCode, serde_json::Value)>, GatewayError> {
if !is_release_build() { if !self_update_supported() {
return Ok(Err(source_build_rejection_response())); return Ok(Err(self_update_rejection_response()));
} }
let version = match version.or_else(get_prepared_version) { let version = match version.or_else(get_prepared_version) {
@@ -800,8 +948,8 @@ fn switch_current_symlink(version: &str) -> Result<(), String> {
pub(crate) async fn start_admin_system_rollback_task( pub(crate) async fn start_admin_system_rollback_task(
) -> Result<Result<serde_json::Value, (http::StatusCode, serde_json::Value)>, GatewayError> { ) -> Result<Result<serde_json::Value, (http::StatusCode, serde_json::Value)>, GatewayError> {
if !is_release_build() { if !self_update_supported() {
return Ok(Err(source_build_rejection_response())); return Ok(Err(self_update_rejection_response()));
} }
let Some(previous) = find_rollback_target() else { let Some(previous) = find_rollback_target() else {
@@ -861,10 +1009,10 @@ fn update_already_running_response() -> (http::StatusCode, serde_json::Value) {
) )
} }
fn source_build_rejection_response() -> (http::StatusCode, serde_json::Value) { fn self_update_rejection_response() -> (http::StatusCode, serde_json::Value) {
( (
http::StatusCode::PRECONDITION_REQUIRED, http::StatusCode::PRECONDITION_REQUIRED,
json!({ "detail": "\u{6e90}\u{7801}\u{6784}\u{5efa}\u{4e0d}\u{652f}\u{6301}\u{5728}\u{7ebf}\u{66f4}\u{65b0}\u{ff0c}\u{8bf7}\u{4f7f}\u{7528}\u{6b63}\u{5f0f}\u{53d1}\u{5e03}\u{7248}" }), json!({ "detail": current_self_update_blocker() }),
) )
} }
@@ -893,6 +1041,60 @@ mod tests {
.expect("frontend index should be written"); .expect("frontend index should be written");
} }
#[test]
fn update_strategy_defaults_to_self_only_for_release_builds() {
assert_eq!(
UpdateStrategy::from_env_value(None, true),
UpdateStrategy::SelfManaged
);
assert_eq!(
UpdateStrategy::from_env_value(None, false),
UpdateStrategy::Manual
);
}
#[test]
fn update_strategy_parses_docker_as_non_self_update() {
assert_eq!(
UpdateStrategy::from_env_value(Some("docker"), true),
UpdateStrategy::Docker
);
assert_eq!(
UpdateStrategy::from_env_value(Some("compose"), true),
UpdateStrategy::Docker
);
assert_eq!(
UpdateStrategy::from_env_value(Some("unknown"), true),
UpdateStrategy::Manual
);
}
#[test]
fn deployment_topology_defaults_to_single_node() {
assert_eq!(
DeploymentTopology::from_env_value(None),
DeploymentTopology::SingleNode
);
assert_eq!(
DeploymentTopology::from_env_value(Some("multi-node")),
DeploymentTopology::MultiNode
);
}
#[test]
fn multi_node_topology_disables_self_update() {
assert!(self_update_supported_for(
true,
UpdateStrategy::SelfManaged,
DeploymentTopology::SingleNode,
));
assert!(!self_update_supported_for(
true,
UpdateStrategy::SelfManaged,
DeploymentTopology::MultiNode,
));
}
#[test] #[test]
fn update_finds_nested_release_payload_dir() { fn update_finds_nested_release_payload_dir() {
let staging = temp_test_dir("nested"); let staging = temp_test_dir("nested");

View File

@@ -38,6 +38,7 @@ services:
AETHER_UPDATE_PROXY_URL: ${AETHER_UPDATE_PROXY_URL:-} AETHER_UPDATE_PROXY_URL: ${AETHER_UPDATE_PROXY_URL:-}
UPDATE_PROXY_URL: ${UPDATE_PROXY_URL:-} UPDATE_PROXY_URL: ${UPDATE_PROXY_URL:-}
AETHER_BASE_DIR: /opt/aether AETHER_BASE_DIR: /opt/aether
AETHER_UPDATE_STRATEGY: self
AETHER_GATEWAY_AUTO_PREPARE_DATABASE: ${AETHER_GATEWAY_AUTO_PREPARE_DATABASE:-true} AETHER_GATEWAY_AUTO_PREPARE_DATABASE: ${AETHER_GATEWAY_AUTO_PREPARE_DATABASE:-true}
ports: ports:
- "${AETHER_RELEASE_LOCAL_PORT:-18085}:${AETHER_RELEASE_LOCAL_PORT:-18085}" - "${AETHER_RELEASE_LOCAL_PORT:-18085}:${AETHER_RELEASE_LOCAL_PORT:-18085}"

View File

@@ -7,6 +7,8 @@ services:
environment: environment:
TZ: Asia/Shanghai TZ: Asia/Shanghai
AETHER_BASE_DIR: /opt/aether AETHER_BASE_DIR: /opt/aether
AETHER_UPDATE_STRATEGY: docker
AETHER_DOCKER_UPDATE_COMMAND: ${AETHER_DOCKER_UPDATE_COMMAND:-./update.sh}
AETHER_DATABASE_DRIVER: sqlite AETHER_DATABASE_DRIVER: sqlite
AETHER_DATABASE_URL: sqlite:///opt/aether/data/aether.db AETHER_DATABASE_URL: sqlite:///opt/aether/data/aether.db
AETHER_RUNTIME_BACKEND: memory AETHER_RUNTIME_BACKEND: memory
@@ -23,5 +25,6 @@ services:
ports: ports:
- "${APP_PORT:-8084}:${APP_PORT:-8084}" - "${APP_PORT:-8084}:${APP_PORT:-8084}"
volumes: volumes:
- ./data:/opt/aether/data - ./datas/sqlite:/opt/aether/data
- ./logs:/opt/aether/logs
restart: unless-stopped restart: unless-stopped

View File

@@ -12,7 +12,7 @@ services:
POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_PASSWORD: ${DB_PASSWORD}
TZ: Asia/Shanghai TZ: Asia/Shanghai
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - ./datas/postgres:/var/lib/postgresql/data
ports: ports:
- "127.0.0.1:${DB_PORT:-5432}:5432" - "127.0.0.1:${DB_PORT:-5432}:5432"
command: > command: >
@@ -39,7 +39,7 @@ services:
container_name: aether-redis container_name: aether-redis
command: redis-server --appendonly yes --appendfsync everysec --save 60 1000 --requirepass ${REDIS_PASSWORD} --maxclients ${REDIS_MAXCLIENTS:-10000} command: redis-server --appendonly yes --appendfsync everysec --save 60 1000 --requirepass ${REDIS_PASSWORD} --maxclients ${REDIS_MAXCLIENTS:-10000}
volumes: volumes:
- redis_data:/data - ./datas/redis:/data
ports: ports:
- "127.0.0.1:${REDIS_PORT:-6379}:6379" - "127.0.0.1:${REDIS_PORT:-6379}:6379"
healthcheck: healthcheck:
@@ -61,7 +61,7 @@ services:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-aether_root} MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-aether_root}
TZ: Asia/Shanghai TZ: Asia/Shanghai
volumes: volumes:
- mysql_data:/var/lib/mysql - ./datas/mysql:/var/lib/mysql
healthcheck: healthcheck:
test: test:
[ [
@@ -83,6 +83,8 @@ services:
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/0 REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/0
TZ: Asia/Shanghai TZ: Asia/Shanghai
AETHER_BASE_DIR: /opt/aether AETHER_BASE_DIR: /opt/aether
AETHER_UPDATE_STRATEGY: docker
AETHER_DOCKER_UPDATE_COMMAND: ${AETHER_DOCKER_UPDATE_COMMAND:-./update.sh}
AETHER_LOG_DESTINATION: ${AETHER_LOG_DESTINATION:-stdout} AETHER_LOG_DESTINATION: ${AETHER_LOG_DESTINATION:-stdout}
AETHER_LOG_FORMAT: ${AETHER_LOG_FORMAT:-pretty} AETHER_LOG_FORMAT: ${AETHER_LOG_FORMAT:-pretty}
AETHER_LOG_DIR: ${AETHER_LOG_DIR:-/opt/aether/logs} AETHER_LOG_DIR: ${AETHER_LOG_DIR:-/opt/aether/logs}
@@ -98,9 +100,6 @@ services:
condition: service_healthy condition: service_healthy
ports: ports:
- "${APP_PORT:-8084}:${APP_PORT:-8084}" - "${APP_PORT:-8084}:${APP_PORT:-8084}"
restart: unless-stopped
volumes: volumes:
postgres_data: - ./logs:/opt/aether/logs
mysql_data: restart: unless-stopped
redis_data:

View File

@@ -382,11 +382,19 @@ export interface CheckUpdateResponse {
export interface SystemUpdateCapabilityResponse { export interface SystemUpdateCapabilityResponse {
supported: boolean supported: boolean
build_type: string build_type: string
update_strategy?: 'self' | 'docker' | 'manual' | string
strategy?: 'self' | 'docker' | 'manual' | string
deployment_topology?: 'single-node' | 'multi-node' | string
topology?: 'single-node' | 'multi-node' | string
enabled: boolean enabled: boolean
rollback_available: boolean rollback_available: boolean
task_status: string task_status: string
task_error: string | null task_error: string | null
install_root?: string install_root?: string
base_dir?: string
data_dir?: string
logs_dir?: string
docker_update_command?: string | null
message: string message: string
} }

View File

@@ -127,6 +127,17 @@
> >
{{ updateBlockerText }} {{ updateBlockerText }}
</p> </p>
<div
v-if="isDockerUpdate && dockerUpdateCommand"
class="mt-3 w-full max-w-sm rounded-lg border border-border/60 bg-muted/30 px-3 py-2 text-left"
>
<p class="text-xs text-muted-foreground">
docker-compose.yml 所在目录执行
</p>
<code class="mt-1 block break-all rounded bg-background/70 px-2 py-1.5 font-mono text-xs text-foreground">
{{ dockerUpdateCommand }}
</code>
</div>
</template> </template>
</div> </div>
@@ -199,8 +210,10 @@ const props = defineProps<{
updatePhase?: 'download' | 'restart' | 'reconnecting' updatePhase?: 'download' | 'restart' | 'reconnecting'
updating?: boolean updating?: boolean
updateSupported?: boolean updateSupported?: boolean
updateStrategy?: string
updatable?: boolean updatable?: boolean
updateBlocker?: string | null updateBlocker?: string | null
dockerUpdateCommand?: string | null
reconnectMessage?: string reconnectMessage?: string
rollbackAvailable?: boolean rollbackAvailable?: boolean
rollingBack?: boolean rollingBack?: boolean
@@ -220,6 +233,9 @@ const updatePhase = computed(() => props.updatePhase ?? 'download')
const updateSupported = computed(() => props.updateSupported ?? true) const updateSupported = computed(() => props.updateSupported ?? true)
const updatable = computed(() => props.updatable ?? true) const updatable = computed(() => props.updatable ?? true)
const canApplyUpdate = computed(() => updateSupported.value && updatable.value) const canApplyUpdate = computed(() => updateSupported.value && updatable.value)
const updateStrategy = computed(() => props.updateStrategy ?? 'manual')
const isDockerUpdate = computed(() => updateStrategy.value === 'docker' && !canApplyUpdate.value)
const dockerUpdateCommand = computed(() => props.dockerUpdateCommand || '')
const updateBlockerText = computed(() => { const updateBlockerText = computed(() => {
if (!updateSupported.value) return props.updateBlocker || SOURCE_BUILD_UPDATE_HINT if (!updateSupported.value) return props.updateBlocker || SOURCE_BUILD_UPDATE_HINT
return props.updateBlocker || '当前版本暂不支持在线更新' return props.updateBlocker || '当前版本暂不支持在线更新'

View File

@@ -413,6 +413,8 @@
:update-supported="updateSupported" :update-supported="updateSupported"
:updatable="updateInfo.updatable" :updatable="updateInfo.updatable"
:update-blocker="updateInfo.update_blocker" :update-blocker="updateInfo.update_blocker"
:update-strategy="updateStrategy"
:docker-update-command="dockerUpdateCommand"
:reconnect-message="reconnectMessage" :reconnect-message="reconnectMessage"
:rollback-available="rollbackAvailable" :rollback-available="rollbackAvailable"
:rolling-back="rollingBack" :rolling-back="rollingBack"
@@ -434,7 +436,7 @@ import { useDarkMode } from '@/composables/useDarkMode'
import { useSiteInfo } from '@/composables/useSiteInfo' import { useSiteInfo } from '@/composables/useSiteInfo'
import { useToast } from '@/composables/useToast' import { useToast } from '@/composables/useToast'
import { isDemoMode } from '@/config/demo' import { isDemoMode } from '@/config/demo'
import { adminApi, type CheckUpdateResponse, type ReleaseEntry, type UpdateTaskStatusResponse } from '@/api/admin' import { adminApi, type CheckUpdateResponse, type ReleaseEntry, type SystemUpdateCapabilityResponse, type UpdateTaskStatusResponse } from '@/api/admin'
import { announcementApi, type Announcement } from '@/api/announcements' import { announcementApi, type Announcement } from '@/api/announcements'
import { parseApiError } from '@/utils/errorParser' import { parseApiError } from '@/utils/errorParser'
import Button from '@/components/ui/button.vue' import Button from '@/components/ui/button.vue'
@@ -518,6 +520,9 @@ const versionStatus = ref<CheckUpdateResponse | null>(null)
const loadingVersionStatus = ref(false) const loadingVersionStatus = ref(false)
const applyingSystemUpdate = ref(false) const applyingSystemUpdate = ref(false)
const updateSupported = ref(true) const updateSupported = ref(true)
const updateStrategy = ref('manual')
const updateCapabilityMessage = ref<string | null>(null)
const dockerUpdateCommand = ref<string | null>(null)
const reconnectMessage = ref('等待服务恢复...') const reconnectMessage = ref('等待服务恢复...')
const rollbackAvailable = ref(false) const rollbackAvailable = ref(false)
const rollingBack = ref(false) const rollingBack = ref(false)
@@ -529,6 +534,7 @@ const preparedUpdateVersion = ref<string | null>(
) )
const SOURCE_BUILD_UPDATE_HINT = '当前为源码构建,请使用 git pull 后重新编译。' const SOURCE_BUILD_UPDATE_HINT = '当前为源码构建,请使用 git pull 后重新编译。'
const SOURCE_BUILD_RELEASE_HINT = '当前为源码构建,请手动切换到对应标签后重新编译。' const SOURCE_BUILD_RELEASE_HINT = '当前为源码构建,请手动切换到对应标签后重新编译。'
const MANUAL_UPDATE_HINT = '当前部署策略不支持在线自更新,请手动下载 Release 或使用安装脚本更新。'
let versionStatusLoadPromise: Promise<CheckUpdateResponse | null> | null = null let versionStatusLoadPromise: Promise<CheckUpdateResponse | null> | null = null
let updateStatusPollTimer: number | null = null let updateStatusPollTimer: number | null = null
const updateProgressPercent = computed(() => updateTaskStatus.value?.progress_percent ?? null) const updateProgressPercent = computed(() => updateTaskStatus.value?.progress_percent ?? null)
@@ -700,14 +706,13 @@ async function loadVersionStatus(force = false) {
adminApi.getSystemUpdateCapability().catch(() => null), adminApi.getSystemUpdateCapability().catch(() => null),
]) ])
if (capability) { if (capability) {
rollbackAvailable.value = capability.rollback_available applyUpdateCapability(capability)
updateSupported.value = capability.supported
} }
versionStatus.value = capability?.supported === false && status.has_update versionStatus.value = updateSupported.value === false && status.has_update
? { ? {
...status, ...status,
updatable: false, updatable: false,
update_blocker: SOURCE_BUILD_UPDATE_HINT, update_blocker: updateUnsupportedMessage(SOURCE_BUILD_UPDATE_HINT),
} }
: status : status
syncSystemUpdatePhase(versionStatus.value) syncSystemUpdatePhase(versionStatus.value)
@@ -724,6 +729,18 @@ async function loadVersionStatus(force = false) {
return versionStatusLoadPromise return versionStatusLoadPromise
} }
function applyUpdateCapability(capability: SystemUpdateCapabilityResponse) {
updateSupported.value = capability.supported
rollbackAvailable.value = capability.supported && capability.rollback_available
updateStrategy.value = capability.update_strategy || capability.strategy || 'manual'
updateCapabilityMessage.value = capability.message || null
dockerUpdateCommand.value = capability.docker_update_command || null
}
function updateUnsupportedMessage(fallback = MANUAL_UPDATE_HINT): string {
return updateCapabilityMessage.value || fallback
}
function syncSystemUpdatePhase(status: CheckUpdateResponse | null) { function syncSystemUpdatePhase(status: CheckUpdateResponse | null) {
if (systemUpdatePhase.value === 'reconnecting') return if (systemUpdatePhase.value === 'reconnecting') return
if (systemUpdatePhase.value === 'restart') { if (systemUpdatePhase.value === 'restart') {
@@ -754,16 +771,16 @@ function buildUpdateInfoFromRelease(release: ReleaseEntry): CheckUpdateResponse
updateInfo.value?.current_version || updateInfo.value?.current_version ||
__APP_VERSION__ || __APP_VERSION__ ||
'' ''
const sourceBuild = !updateSupported.value const canSelfUpdate = updateSupported.value
return { return {
current_version: currentVersion, current_version: currentVersion,
latest_version: release.version, latest_version: release.version,
has_update: !release.is_current, has_update: !release.is_current,
updatable: !sourceBuild && !release.is_current && release.updatable, updatable: canSelfUpdate && !release.is_current && release.updatable,
update_blocker: release.is_current update_blocker: release.is_current
? '当前已是这个版本' ? '当前已是这个版本'
: sourceBuild : !canSelfUpdate
? SOURCE_BUILD_RELEASE_HINT ? updateUnsupportedMessage(SOURCE_BUILD_RELEASE_HINT)
: release.update_blocker, : release.update_blocker,
release_url: release.release_url, release_url: release.release_url,
release_notes: release.release_notes, release_notes: release.release_notes,
@@ -787,16 +804,14 @@ async function handleApplySystemUpdate() {
applyingSystemUpdate.value = true applyingSystemUpdate.value = true
try { try {
const capability = await adminApi.getSystemUpdateCapability() const capability = await adminApi.getSystemUpdateCapability()
rollbackAvailable.value = capability.rollback_available applyUpdateCapability(capability)
if (!capability.supported) { if (!capability.supported) {
updateSupported.value = false
showError( showError(
SOURCE_BUILD_UPDATE_HINT, updateUnsupportedMessage('不支持在线自更新'),
'不支持在线更新' '不支持在线更新'
) )
return return
} }
updateSupported.value = true
if (systemUpdatePhase.value === 'download') { if (systemUpdatePhase.value === 'download') {
const targetStatus = updateInfo.value || versionStatus.value const targetStatus = updateInfo.value || versionStatus.value

View File

@@ -1697,6 +1697,8 @@ AETHER_LOG_RETENTION_DAYS=7
AETHER_LOG_MAX_FILES=30 AETHER_LOG_MAX_FILES=30
APP_PORT=${APP_PORT:-8084} APP_PORT=${APP_PORT:-8084}
AETHER_BASE_DIR=${INSTALL_ROOT}
AETHER_UPDATE_STRATEGY=self
AETHER_GATEWAY_STATIC_DIR=${INSTALL_ROOT}/current/frontend AETHER_GATEWAY_STATIC_DIR=${INSTALL_ROOT}/current/frontend
AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE=rust-authoritative AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE=rust-authoritative
AETHER_GATEWAY_AUTO_PREPARE_DATABASE=true AETHER_GATEWAY_AUTO_PREPARE_DATABASE=true
@@ -1736,6 +1738,8 @@ AETHER_LOG_RETENTION_DAYS=7
AETHER_LOG_MAX_FILES=30 AETHER_LOG_MAX_FILES=30
APP_PORT=${APP_PORT:-8084} APP_PORT=${APP_PORT:-8084}
AETHER_BASE_DIR=${INSTALL_ROOT}
AETHER_UPDATE_STRATEGY=manual
AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY=multi-node AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY=multi-node
AETHER_GATEWAY_NODE_ROLE=${role} AETHER_GATEWAY_NODE_ROLE=${role}
AETHER_GATEWAY_STATIC_DIR=${INSTALL_ROOT}/current/frontend AETHER_GATEWAY_STATIC_DIR=${INSTALL_ROOT}/current/frontend
@@ -1824,6 +1828,8 @@ generate_compose_env() {
replace_or_append_env "${output}" "ADMIN_EMAIL" "${ADMIN_EMAIL:-admin@example.local}" replace_or_append_env "${output}" "ADMIN_EMAIL" "${ADMIN_EMAIL:-admin@example.local}"
replace_or_append_env "${output}" "ADMIN_USERNAME" "${ADMIN_USERNAME:-admin}" replace_or_append_env "${output}" "ADMIN_USERNAME" "${ADMIN_USERNAME:-admin}"
replace_or_append_env "${output}" "ADMIN_PASSWORD" "${ADMIN_PASSWORD}" replace_or_append_env "${output}" "ADMIN_PASSWORD" "${ADMIN_PASSWORD}"
replace_or_append_env "${output}" "AETHER_UPDATE_STRATEGY" "docker"
replace_or_append_env "${output}" "AETHER_DOCKER_UPDATE_COMMAND" "./update.sh"
append_compose_log_env_defaults "${output}" append_compose_log_env_defaults "${output}"
replace_or_append_env "${output}" "AETHER_GATEWAY_AUTO_PREPARE_DATABASE" "true" replace_or_append_env "${output}" "AETHER_GATEWAY_AUTO_PREPARE_DATABASE" "true"
} }
@@ -1843,6 +1849,8 @@ $(compose_log_env_block)
APP_IMAGE=$(compose_image) APP_IMAGE=$(compose_image)
APP_PORT=$(compose_app_port) APP_PORT=$(compose_app_port)
AETHER_UPDATE_STRATEGY=docker
AETHER_DOCKER_UPDATE_COMMAND=./update.sh
AETHER_GATEWAY_STATIC_DIR=${COMPOSE_RELEASE_FRONTEND_DIR} AETHER_GATEWAY_STATIC_DIR=${COMPOSE_RELEASE_FRONTEND_DIR}
AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE=rust-authoritative AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE=rust-authoritative
AETHER_GATEWAY_AUTO_PREPARE_DATABASE=true AETHER_GATEWAY_AUTO_PREPARE_DATABASE=true
@@ -2223,6 +2231,12 @@ install_compose_mode() {
resolve_compose_dir resolve_compose_dir
info "preparing Docker Compose deployment in ${COMPOSE_DIR}" info "preparing Docker Compose deployment in ${COMPOSE_DIR}"
ensure_directory "${COMPOSE_DIR}" ensure_directory "${COMPOSE_DIR}"
ensure_directory "${COMPOSE_DIR}/logs"
ensure_directory "${COMPOSE_DIR}/datas"
ensure_directory "${COMPOSE_DIR}/datas/postgres"
ensure_directory "${COMPOSE_DIR}/datas/redis"
ensure_directory "${COMPOSE_DIR}/datas/mysql"
ensure_directory "${COMPOSE_DIR}/datas/sqlite"
install_project_file "docker-compose.yml" "${COMPOSE_DIR}/docker-compose.yml" "0644" install_project_file "docker-compose.yml" "${COMPOSE_DIR}/docker-compose.yml" "0644"
install_project_file ".env.example" "${COMPOSE_DIR}/.env.example" "0644" install_project_file ".env.example" "${COMPOSE_DIR}/.env.example" "0644"
install_project_file "update.sh" "${COMPOSE_DIR}/update.sh" "0755" install_project_file "update.sh" "${COMPOSE_DIR}/update.sh" "0755"
@@ -2244,6 +2258,11 @@ Docker Compose files are ready:
${COMPOSE_DIR}/.env.example ${COMPOSE_DIR}/.env.example
${COMPOSE_DIR}/update.sh ${COMPOSE_DIR}/update.sh
${COMPOSE_DIR}/generate_keys.sh ${COMPOSE_DIR}/generate_keys.sh
${COMPOSE_DIR}/logs
${COMPOSE_DIR}/datas/postgres
${COMPOSE_DIR}/datas/redis
${COMPOSE_DIR}/datas/mysql
${COMPOSE_DIR}/datas/sqlite
EOF EOF
if [[ "${SKIP_START}" == "true" ]]; then if [[ "${SKIP_START}" == "true" ]]; then
@@ -2260,7 +2279,9 @@ install_compose_single_node_mode() {
resolve_compose_dir resolve_compose_dir
info "preparing Docker Compose single-node deployment in ${COMPOSE_DIR}" info "preparing Docker Compose single-node deployment in ${COMPOSE_DIR}"
ensure_directory "${COMPOSE_DIR}" ensure_directory "${COMPOSE_DIR}"
ensure_directory "${COMPOSE_DIR}/data" ensure_directory "${COMPOSE_DIR}/datas"
ensure_directory "${COMPOSE_DIR}/datas/sqlite"
ensure_directory "${COMPOSE_DIR}/logs"
install_project_file "docker-compose.single-node.yml" "${COMPOSE_DIR}/docker-compose.yml" "0644" install_project_file "docker-compose.single-node.yml" "${COMPOSE_DIR}/docker-compose.yml" "0644"
install_project_file ".env.example" "${COMPOSE_DIR}/.env.example" "0644" install_project_file ".env.example" "${COMPOSE_DIR}/.env.example" "0644"
@@ -2283,7 +2304,8 @@ Docker Compose single-node files are ready:
${COMPOSE_DIR}/.env.example ${COMPOSE_DIR}/.env.example
${COMPOSE_DIR}/update.sh ${COMPOSE_DIR}/update.sh
${COMPOSE_DIR}/generate_keys.sh ${COMPOSE_DIR}/generate_keys.sh
${COMPOSE_DIR}/data ${COMPOSE_DIR}/datas/sqlite
${COMPOSE_DIR}/logs
EOF EOF
if [[ "${SKIP_START}" == "true" ]]; then if [[ "${SKIP_START}" == "true" ]]; then

View File

@@ -267,7 +267,7 @@ validate_env_line_for_copy() {
should_skip_single_node_env_key() { should_skip_single_node_env_key() {
case "$1" in case "$1" in
APP_IMAGE|LOCAL_APP_IMAGE|APP_PORT|DB_HOST|DB_PORT|DB_USER|DB_NAME|DB_PASSWORD|POSTGRES_*|MYSQL_*|REDIS_HOST|REDIS_PORT|REDIS_PASSWORD|REDIS_URL|AETHER_GATEWAY_DATA_REDIS_URL|AETHER_GATEWAY_DATA_REDIS_KEY_PREFIX|DATABASE_URL|AETHER_DATABASE_URL|AETHER_DATABASE_DRIVER|AETHER_GATEWAY_DATA_POSTGRES_URL|AETHER_RUNTIME_BACKEND|AETHER_RUNTIME_REDIS_URL|AETHER_RUNTIME_REDIS_KEY_PREFIX|AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY|AETHER_GATEWAY_NODE_ROLE|AETHER_GATEWAY_STATIC_DIR|AETHER_LOG_DIR|AETHER_GATEWAY_AUTO_PREPARE_DATABASE) APP_IMAGE|LOCAL_APP_IMAGE|APP_PORT|DB_HOST|DB_PORT|DB_USER|DB_NAME|DB_PASSWORD|POSTGRES_*|MYSQL_*|REDIS_HOST|REDIS_PORT|REDIS_PASSWORD|REDIS_URL|AETHER_GATEWAY_DATA_REDIS_URL|AETHER_GATEWAY_DATA_REDIS_KEY_PREFIX|DATABASE_URL|AETHER_DATABASE_URL|AETHER_DATABASE_DRIVER|AETHER_GATEWAY_DATA_POSTGRES_URL|AETHER_RUNTIME_BACKEND|AETHER_RUNTIME_REDIS_URL|AETHER_RUNTIME_REDIS_KEY_PREFIX|AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY|AETHER_GATEWAY_NODE_ROLE|AETHER_GATEWAY_STATIC_DIR|AETHER_UPDATE_STRATEGY|AETHER_DOCKER_UPDATE_COMMAND|AETHER_LOG_DIR|AETHER_GATEWAY_AUTO_PREPARE_DATABASE)
return 0 return 0
;; ;;
*) *)
@@ -316,12 +316,14 @@ write_single_node_env() {
printf 'APP_IMAGE=%s\n' "$app_image" printf 'APP_IMAGE=%s\n' "$app_image"
printf 'APP_PORT=%s\n' "$app_port" printf 'APP_PORT=%s\n' "$app_port"
printf 'AETHER_GATEWAY_STATIC_DIR=/opt/aether/current/frontend\n' printf 'AETHER_GATEWAY_STATIC_DIR=/opt/aether/current/frontend\n'
printf 'AETHER_LOG_DESTINATION=both\n' printf 'AETHER_UPDATE_STRATEGY=docker\n'
printf 'AETHER_DOCKER_UPDATE_COMMAND=./update.sh\n'
printf 'AETHER_LOG_DESTINATION=stdout\n'
printf 'AETHER_LOG_FORMAT=pretty\n' printf 'AETHER_LOG_FORMAT=pretty\n'
printf 'AETHER_LOG_DIR=/app/logs\n' printf 'AETHER_LOG_DIR=/opt/aether/logs\n'
printf 'AETHER_DATABASE_DRIVER=sqlite\n' printf 'AETHER_DATABASE_DRIVER=sqlite\n'
printf 'AETHER_DATABASE_URL=sqlite://./data/aether.db\n' printf 'AETHER_DATABASE_URL=sqlite:///opt/aether/data/aether.db\n'
printf 'DATABASE_URL=sqlite://./data/aether.db\n' printf 'DATABASE_URL=sqlite:///opt/aether/data/aether.db\n'
printf 'AETHER_RUNTIME_BACKEND=memory\n' printf 'AETHER_RUNTIME_BACKEND=memory\n'
printf 'AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY=single-node\n' printf 'AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY=single-node\n'
printf 'AETHER_GATEWAY_NODE_ROLE=all\n' printf 'AETHER_GATEWAY_NODE_ROLE=all\n'
@@ -728,13 +730,15 @@ preflight() {
NOW="$(date +%Y%m%d%H%M%S)" NOW="$(date +%Y%m%d%H%M%S)"
if [[ -z "$WORK_DIR" ]]; then if [[ -z "$WORK_DIR" ]]; then
WORK_DIR="${SOURCE_COMPOSE_DIR}/data/pg-compose-to-single-node-${NOW}" WORK_DIR="${SOURCE_COMPOSE_DIR}/datas/sqlite/pg-compose-to-single-node-${NOW}"
fi fi
WORK_DIR="$(absolute_path_maybe_missing "$WORK_DIR")" WORK_DIR="$(absolute_path_maybe_missing "$WORK_DIR")"
mkdir -p "$WORK_DIR" mkdir -p "$WORK_DIR"
prepare_target_compose prepare_target_compose
TARGET_DB="${TARGET_DB:-${TARGET_COMPOSE_DIR}/data/aether.db}" mkdir -p "${TARGET_COMPOSE_DIR}/logs"
mkdir -p "${TARGET_COMPOSE_DIR}/datas/sqlite"
TARGET_DB="${TARGET_DB:-${TARGET_COMPOSE_DIR}/datas/sqlite/aether.db}"
TARGET_DB="$(absolute_path_maybe_missing "$TARGET_DB")" TARGET_DB="$(absolute_path_maybe_missing "$TARGET_DB")"
DB_USER="$(env_file_get "$SOURCE_ENV" "DB_USER")" DB_USER="$(env_file_get "$SOURCE_ENV" "DB_USER")"