mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
sideload-by-url
This commit is contained in:
@@ -21,6 +21,7 @@ use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::registry::asset::BufferedHttpSource;
|
||||
use crate::db::model::package::{ManifestPreference, PackageStateMatchModelRef};
|
||||
use crate::prelude::*;
|
||||
use crate::progress::{FullProgress, FullProgressTracker, PhasedProgressBar};
|
||||
@@ -285,6 +286,57 @@ pub async fn sideload(
|
||||
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)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
@@ -441,6 +441,12 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
||||
.with_metadata("get_session", Value::Bool(true))
|
||||
.no_cli(),
|
||||
)
|
||||
.subcommand(
|
||||
"sideload-url",
|
||||
from_fn_async(install::sideload_url)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_cli(),
|
||||
)
|
||||
.subcommand(
|
||||
"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
|
||||
├── protocol.rs — JSON-RPC 2.0 types, MCP request/response structs, error codes
|
||||
├── 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
|
||||
@@ -89,7 +89,7 @@ Resource URIs are validated to only allow `/public/**` subtrees and the special
|
||||
|
||||
## 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
|
||||
|
||||
@@ -139,7 +139,7 @@ These configure the Start9 tunnel service, which has its own management interfac
|
||||
|
||||
| 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
|
||||
|
||||
|
||||
@@ -299,6 +299,26 @@ pub fn tool_registry() -> HashMap<String, ToolEntry> {
|
||||
sync_db: true,
|
||||
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 {
|
||||
definition: ToolDefinition {
|
||||
name: "package.uninstall".into(),
|
||||
|
||||
Reference in New Issue
Block a user