diff --git a/devolutions-agent/src/updater/mod.rs b/devolutions-agent/src/updater/mod.rs index 447e05b43..52e62a740 100644 --- a/devolutions-agent/src/updater/mod.rs +++ b/devolutions-agent/src/updater/mod.rs @@ -678,7 +678,7 @@ async fn check_for_updates( } // Target MSI found, proceed with update. - if product == Product::Agent && version <= AGENT_MIN_SELF_UPDATE_VERSION { + if product == Product::Agent && version < AGENT_MIN_SELF_UPDATE_VERSION { warn!( %product, %version, diff --git a/devolutions-agent/src/updater/package.rs b/devolutions-agent/src/updater/package.rs index 20aae7a3b..319eeb212 100644 --- a/devolutions-agent/src/updater/package.rs +++ b/devolutions-agent/src/updater/package.rs @@ -152,7 +152,8 @@ async fn launch_updater_shim_detached( return Err(UpdaterError::AgentUpdateAlreadyInProgress); } - let shim_log_path = shim_path.with_extension("shim.log"); + // The shim derives its log path from the MSI path (see `devolutions-agent-updater/src/main.rs`). + let shim_log_path = format!("{}.shim.log", msi_path); let mut cmd = tokio::process::Command::new(shim_path.as_str()); if let Some(code) = downgrade_product_code { diff --git a/devolutions-gateway/openapi/gateway-api.yaml b/devolutions-gateway/openapi/gateway-api.yaml index 0027ee100..5bdc52a30 100644 --- a/devolutions-gateway/openapi/gateway-api.yaml +++ b/devolutions-gateway/openapi/gateway-api.yaml @@ -682,7 +682,7 @@ paths: - Update summary: Retrieve the current Devolutions Agent auto-update schedule. description: |- - Reads the `Schedule` field from `update.json`. When the field is absent the response + Reads the `Schedule` field from `agent_status.json`. When the field is absent the response contains zeroed defaults (`Enabled: false`, interval `0`, window start `0`, no products). operationId: GetUpdateSchedule responses: @@ -1661,28 +1661,11 @@ components: - tcp - udp UpdateProduct: - oneOf: - - type: string - enum: - - Gateway - - type: string - enum: - - HubService - - type: string - enum: - - Agent - - type: object - required: - - Other - properties: - Other: - type: string - description: A product name not recognised by this gateway version. + type: string description: |- - Known product names accepted by the update endpoint. + Product names accepted by the update endpoint. - `Other` captures any product name not yet known to this gateway version; - it is forwarded to the agent unchanged so future agents can act on it. + Known values are `Gateway`, `HubService`, and `Agent`. Any other product name is also accepted and forwarded to the agent unchanged so future product types are supported transparently. UpdateProductRequest: type: object description: Per-product version request. @@ -1738,3 +1721,4 @@ components: scheme: bearer bearerFormat: JWT description: Token allowing usage of the standalone web application + diff --git a/devolutions-gateway/src/api/update.rs b/devolutions-gateway/src/api/update.rs index 0f90e12a0..9440c9702 100644 --- a/devolutions-gateway/src/api/update.rs +++ b/devolutions-gateway/src/api/update.rs @@ -95,7 +95,6 @@ pub(crate) struct UpdateProductRequest { /// /// `Other` captures any product name not yet known to this gateway version; /// it is forwarded to the agent unchanged so future agents can act on it. -#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub(crate) enum UpdateProduct { Gateway, @@ -105,6 +104,29 @@ pub(crate) enum UpdateProduct { Other(String), } +#[cfg(feature = "openapi")] +impl<'__s> utoipa::ToSchema<'__s> for UpdateProduct { + fn schema() -> (&'__s str, utoipa::openapi::RefOr) { + use utoipa::openapi::schema::{ObjectBuilder, SchemaType}; + use utoipa::openapi::RefOr; + ( + "UpdateProduct", + RefOr::T( + ObjectBuilder::new() + .schema_type(SchemaType::String) + .description(Some( + "Product names accepted by the update endpoint.\n\n\ + Known values are `Gateway`, `HubService`, and `Agent`. \ + Any other product name is also accepted and forwarded to the agent \ + unchanged so future product types are supported transparently.", + )) + .build() + .into(), + ), + ) + } +} + impl<'de> serde::Deserialize<'de> for UpdateProduct { fn deserialize>(d: D) -> Result { struct V; @@ -297,6 +319,13 @@ pub(super) async fn trigger_update_check( .insert(UpdateProduct::Gateway, UpdateProductRequest { version }); } + // Reject requests that specify no products at all; an empty write would clear any + // pending update requests already written into update.json. + if request.products.is_empty() { + return Err(HttpErrorBuilder::new(StatusCode::BAD_REQUEST) + .msg("request must specify at least one product to update")); + } + // Read the existing manifest (503 when agent is not installed). // `was_v2` tells us if the file on disk was V2; determines which products are accepted. let (mut manifest, was_v2) = read_manifest().await?; @@ -440,7 +469,7 @@ pub(crate) struct SetUpdateScheduleResponse {} /// Retrieve the current Devolutions Agent auto-update schedule. /// -/// Reads the `Schedule` field from `update.json`. When the field is absent the response +/// Reads the `Schedule` field from `agent_status.json`. When the field is absent the response /// contains zeroed defaults (`Enabled: false`, interval `0`, window start `0`, no products). #[cfg_attr(feature = "openapi", utoipa::path( get,