Skip to content

Commit 14d866c

Browse files
author
Paul C
committed
v9.6.0: Add App Store — browse, install & manage 16 built-in apps (Docker/LXC/bare-metal)
1 parent 7196f5b commit 14d866c

7 files changed

Lines changed: 1462 additions & 3 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "wolfstack"
3-
version = "9.5.2"
3+
version = "9.6.0"
44
edition = "2021"
55
authors = ["Wolf Software Systems Ltd"]
66
description = "Server management platform for the Wolf software suite"

src/api/mod.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::networking;
1818
use crate::backup;
1919
use crate::agent::{ClusterState, AgentMessage};
2020
use crate::auth::SessionManager;
21+
use crate::appstore;
2122

2223
mod console;
2324
mod pve_console;
@@ -4717,6 +4718,86 @@ pub async fn mysql_dump(
47174718
}
47184719
}
47194720

4721+
// ─── App Store ───
4722+
4723+
/// GET /api/appstore/apps?q=<query>&category=<cat> — list/search available apps
4724+
pub async fn appstore_list(
4725+
req: HttpRequest,
4726+
state: web::Data<AppState>,
4727+
query: web::Query<std::collections::HashMap<String, String>>,
4728+
) -> HttpResponse {
4729+
if let Err(resp) = require_auth(&req, &state) { return resp; }
4730+
let q = query.get("q").map(|s| s.as_str());
4731+
let cat = query.get("category").map(|s| s.as_str());
4732+
let apps = appstore::list_apps(q, cat);
4733+
HttpResponse::Ok().json(apps)
4734+
}
4735+
4736+
/// GET /api/appstore/apps/{id} — get app details
4737+
pub async fn appstore_get(
4738+
req: HttpRequest,
4739+
state: web::Data<AppState>,
4740+
path: web::Path<String>,
4741+
) -> HttpResponse {
4742+
if let Err(resp) = require_auth(&req, &state) { return resp; }
4743+
let id = path.into_inner();
4744+
match appstore::get_app(&id) {
4745+
Some(app) => HttpResponse::Ok().json(app),
4746+
None => HttpResponse::NotFound().json(serde_json::json!({ "error": format!("App '{}' not found", id) })),
4747+
}
4748+
}
4749+
4750+
#[derive(Deserialize)]
4751+
pub struct AppInstallRequest {
4752+
pub target: String, // "docker", "lxc", "bare"
4753+
pub container_name: String, // name for the container
4754+
#[serde(default)]
4755+
pub inputs: std::collections::HashMap<String, String>, // user input values
4756+
}
4757+
4758+
/// POST /api/appstore/apps/{id}/install — install an app
4759+
pub async fn appstore_install(
4760+
req: HttpRequest,
4761+
state: web::Data<AppState>,
4762+
path: web::Path<String>,
4763+
body: web::Json<AppInstallRequest>,
4764+
) -> HttpResponse {
4765+
if let Err(resp) = require_auth(&req, &state) { return resp; }
4766+
let id = path.into_inner();
4767+
let mut inputs = body.inputs.clone();
4768+
// Inject CONTAINER_NAME for ${CONTAINER_NAME} substitution in manifests
4769+
inputs.insert("CONTAINER_NAME".to_string(), body.container_name.clone());
4770+
4771+
match appstore::install_app(&id, &body.target, &body.container_name, &inputs) {
4772+
Ok(msg) => HttpResponse::Ok().json(serde_json::json!({ "message": msg })),
4773+
Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({ "error": e })),
4774+
}
4775+
}
4776+
4777+
/// GET /api/appstore/installed — list installed apps
4778+
pub async fn appstore_installed(
4779+
req: HttpRequest,
4780+
state: web::Data<AppState>,
4781+
) -> HttpResponse {
4782+
if let Err(resp) = require_auth(&req, &state) { return resp; }
4783+
let apps = appstore::list_installed_apps();
4784+
HttpResponse::Ok().json(apps)
4785+
}
4786+
4787+
/// DELETE /api/appstore/installed/{id} — uninstall an app
4788+
pub async fn appstore_uninstall(
4789+
req: HttpRequest,
4790+
state: web::Data<AppState>,
4791+
path: web::Path<String>,
4792+
) -> HttpResponse {
4793+
if let Err(resp) = require_auth(&req, &state) { return resp; }
4794+
let install_id = path.into_inner();
4795+
match appstore::uninstall_app(&install_id) {
4796+
Ok(msg) => HttpResponse::Ok().json(serde_json::json!({ "message": msg })),
4797+
Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({ "error": e })),
4798+
}
4799+
}
4800+
47204801
/// Configure all API routes
47214802
pub fn configure(cfg: &mut web::ServiceConfig) {
47224803
cfg
@@ -4892,6 +4973,12 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
48924973
.route("/api/wolfnet/used-ips", web::get().to(wolfnet_used_ips_endpoint))
48934974
// Geolocation proxy (ip-api.com is HTTP-only, browsers block mixed content on HTTPS pages)
48944975
.route("/api/geolocate", web::get().to(geolocate))
4976+
// App Store
4977+
.route("/api/appstore/apps", web::get().to(appstore_list))
4978+
.route("/api/appstore/apps/{id}", web::get().to(appstore_get))
4979+
.route("/api/appstore/apps/{id}/install", web::post().to(appstore_install))
4980+
.route("/api/appstore/installed", web::get().to(appstore_installed))
4981+
.route("/api/appstore/installed/{id}", web::delete().to(appstore_uninstall))
48954982
// System
48964983
.route("/api/config/export", web::get().to(config_export))
48974984
.route("/api/config/import", web::post().to(config_import))

0 commit comments

Comments
 (0)