mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
sideload-by-url
This commit is contained in:
@@ -21,6 +21,7 @@ use tracing::instrument;
|
|||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
|
use crate::registry::asset::BufferedHttpSource;
|
||||||
use crate::db::model::package::{ManifestPreference, PackageStateMatchModelRef};
|
use crate::db::model::package::{ManifestPreference, PackageStateMatchModelRef};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::{FullProgress, FullProgressTracker, PhasedProgressBar};
|
use crate::progress::{FullProgress, FullProgressTracker, PhasedProgressBar};
|
||||||
@@ -285,6 +286,57 @@ pub async fn sideload(
|
|||||||
Ok(SideloadResponse { upload, progress })
|
Ok(SideloadResponse { upload, progress })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct SideloadUrlParams {
|
||||||
|
#[ts(type = "string")]
|
||||||
|
url: Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn sideload_url(
|
||||||
|
ctx: RpcContext,
|
||||||
|
SideloadUrlParams { url }: SideloadUrlParams,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if !matches!(url.scheme(), "http" | "https") {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("URL scheme must be http or https, got: {}", url.scheme()),
|
||||||
|
ErrorKind::InvalidRequest,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let progress_tracker = FullProgressTracker::new();
|
||||||
|
let download_progress = progress_tracker.add_phase("Downloading".into(), Some(100));
|
||||||
|
let client = ctx.client.clone();
|
||||||
|
let db = ctx.db.clone();
|
||||||
|
let pt_ref = progress_tracker.clone();
|
||||||
|
|
||||||
|
let download = ctx
|
||||||
|
.services
|
||||||
|
.install(
|
||||||
|
ctx.clone(),
|
||||||
|
|| async move {
|
||||||
|
let source = BufferedHttpSource::new(client, url, download_progress).await?;
|
||||||
|
let key = db.peek().await.into_private().into_developer_key();
|
||||||
|
crate::s9pk::load(source, || Ok(key.de()?.0), Some(&pt_ref)).await
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
None::<Never>,
|
||||||
|
Some(progress_tracker),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = async { download.await?.await }.await {
|
||||||
|
tracing::error!("Error sideloading package from URL: {e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|||||||
@@ -441,6 +441,12 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
|||||||
.with_metadata("get_session", Value::Bool(true))
|
.with_metadata("get_session", Value::Bool(true))
|
||||||
.no_cli(),
|
.no_cli(),
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
"sideload-url",
|
||||||
|
from_fn_async(install::sideload_url)
|
||||||
|
.with_metadata("sync_db", Value::Bool(true))
|
||||||
|
.no_cli(),
|
||||||
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"install",
|
"install",
|
||||||
from_fn_async_local(install::cli_install)
|
from_fn_async_local(install::cli_install)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ core/src/mcp/
|
|||||||
├── mod.rs — HTTP handlers, routing, MCP method dispatch, shell execution, CORS
|
├── mod.rs — HTTP handlers, routing, MCP method dispatch, shell execution, CORS
|
||||||
├── protocol.rs — JSON-RPC 2.0 types, MCP request/response structs, error codes
|
├── protocol.rs — JSON-RPC 2.0 types, MCP request/response structs, error codes
|
||||||
├── session.rs — Session map, create/remove/sweep, resource subscriptions with debounce
|
├── session.rs — Session map, create/remove/sweep, resource subscriptions with debounce
|
||||||
└── tools.rs — Tool registry (88 tools), HashMap<String, ToolEntry> mapping names → RPC methods + schemas
|
└── tools.rs — Tool registry (89 tools), HashMap<String, ToolEntry> mapping names → RPC methods + schemas
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tool Dispatch
|
## Tool Dispatch
|
||||||
@@ -89,7 +89,7 @@ Resource URIs are validated to only allow `/public/**` subtrees and the special
|
|||||||
|
|
||||||
## Excluded RPC Methods
|
## Excluded RPC Methods
|
||||||
|
|
||||||
Of the ~194 RPC methods registered in the StartOS backend, 87 are exposed as MCP tools (plus 1 MCP-only tool: `package.shell`). The remaining 105 are excluded for the following reasons.
|
Of the ~195 RPC methods registered in the StartOS backend, 88 are exposed as MCP tools (plus 1 MCP-only tool: `package.shell`). The remaining 105 are excluded for the following reasons.
|
||||||
|
|
||||||
### Wrong context — Setup / Init / Diagnostic modes
|
### Wrong context — Setup / Init / Diagnostic modes
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ These configure the Start9 tunnel service, which has its own management interfac
|
|||||||
|
|
||||||
| Method | Reason |
|
| Method | Reason |
|
||||||
|--------|--------|
|
|--------|--------|
|
||||||
| `package.sideload` | Requires multipart file upload via middleware, not JSON-RPC params |
|
| `package.sideload` | Requires multipart file upload via REST continuation, not JSON-RPC params. Use `package.sideload-by-url` MCP tool (backed by `package.sideload-url` RPC) which accepts a URL instead |
|
||||||
|
|
||||||
### Security — host-level shell access excluded
|
### Security — host-level shell access excluded
|
||||||
|
|
||||||
|
|||||||
@@ -299,6 +299,26 @@ pub fn tool_registry() -> HashMap<String, ToolEntry> {
|
|||||||
sync_db: true,
|
sync_db: true,
|
||||||
needs_session: false,
|
needs_session: false,
|
||||||
},
|
},
|
||||||
|
ToolEntry {
|
||||||
|
definition: ToolDefinition {
|
||||||
|
name: "package.sideload-by-url".into(),
|
||||||
|
description: "Install a package (service) from a direct URL to an .s9pk file. \
|
||||||
|
Use this to install packages from download links rather than from a registry. \
|
||||||
|
The download and installation is asynchronous — subscribe to \
|
||||||
|
startos:///public/packageData to monitor installation progress in real time.".into(),
|
||||||
|
input_schema: json!({
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"url": { "type": "string", "description": "Direct URL to an .s9pk package file (http or https)" }
|
||||||
|
},
|
||||||
|
"required": ["url"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
rpc_method: "package.sideload-url",
|
||||||
|
sync_db: true,
|
||||||
|
needs_session: false,
|
||||||
|
},
|
||||||
ToolEntry {
|
ToolEntry {
|
||||||
definition: ToolDefinition {
|
definition: ToolDefinition {
|
||||||
name: "package.uninstall".into(),
|
name: "package.uninstall".into(),
|
||||||
|
|||||||
Reference in New Issue
Block a user