feat(apps): add saleor and harden netbird repair
This commit is contained in:
parent
56f956973e
commit
522c046525
@ -64,6 +64,23 @@
|
||||
"bitcoin-knots"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "saleor",
|
||||
"title": "Saleor",
|
||||
"version": "3.23",
|
||||
"description": "Composable commerce platform with GraphQL API, dashboard, worker, mail testing, and tracing.",
|
||||
"icon": "/assets/img/app-icons/saleor.svg",
|
||||
"author": "Saleor",
|
||||
"category": "commerce",
|
||||
"tier": "recommended",
|
||||
"dockerImage": "ghcr.io/saleor/saleor:3.23",
|
||||
"repoUrl": "https://github.com/saleor/saleor",
|
||||
"containerConfig": {
|
||||
"ports": ["9000:80", "8000:8000", "8025:8025", "16686:16686"],
|
||||
"volumes": ["/var/lib/archipelago/saleor:/app/media", "/var/lib/archipelago/saleor-db:/var/lib/postgresql/data"],
|
||||
"notes": "Installed as a Saleor stack: dashboard on 9000, API on 8000, Mailpit on 8025, and Jaeger on 16686. Supporting containers include PostgreSQL, Valkey, Celery worker, and services required by Saleor."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mempool",
|
||||
"title": "Mempool Explorer",
|
||||
|
||||
@ -31,6 +31,7 @@ fn is_platform_managed_app(app_id: &str) -> bool {
|
||||
| "fedimint"
|
||||
| "fedimint-gateway"
|
||||
| "indeedhub"
|
||||
| "saleor"
|
||||
| "immich"
|
||||
)
|
||||
}
|
||||
@ -500,6 +501,15 @@ pub(super) fn all_container_names(package_id: &str) -> Vec<String> {
|
||||
"netbird-dashboard".into(),
|
||||
"netbird-server".into(),
|
||||
],
|
||||
"saleor" => vec![
|
||||
"saleor-db".into(),
|
||||
"saleor-cache".into(),
|
||||
"saleor-api".into(),
|
||||
"saleor-worker".into(),
|
||||
"saleor-jaeger".into(),
|
||||
"saleor-mailpit".into(),
|
||||
"saleor".into(),
|
||||
],
|
||||
"nostr-vpn" => vec![
|
||||
"nostr-vpn".into(),
|
||||
"archy-nostr-vpn".into(),
|
||||
@ -589,6 +599,7 @@ pub(super) fn get_data_dirs_for_app(package_id: &str) -> Vec<String> {
|
||||
format!("{}/penpot-postgres", base),
|
||||
],
|
||||
"netbird" => vec![format!("{}/netbird", base)],
|
||||
"saleor" => vec![format!("{}/saleor", base), format!("{}/saleor-db", base)],
|
||||
_ => vec![format!("{}/{}", base, package_id)],
|
||||
}
|
||||
}
|
||||
@ -1068,6 +1079,13 @@ pub(super) async fn get_app_config(
|
||||
None,
|
||||
None,
|
||||
),
|
||||
"saleor" => (
|
||||
vec!["9000:80".to_string(), "8000:8000".to_string()],
|
||||
vec!["/var/lib/archipelago/saleor:/app/media".to_string()],
|
||||
vec![],
|
||||
None,
|
||||
None,
|
||||
),
|
||||
"nostr-rs-relay" => (
|
||||
vec!["18081:8080".to_string()],
|
||||
vec!["/var/lib/archipelago/nostr-rs-relay:/usr/src/app/db".to_string()],
|
||||
|
||||
@ -289,6 +289,15 @@ pub(super) fn startup_order(package_id: &str) -> &'static [&'static str] {
|
||||
&["archy-btcpay-db", "archy-nbxplorer", "btcpay-server"]
|
||||
}
|
||||
"netbird" => &["netbird-server", "netbird-dashboard", "netbird"],
|
||||
"saleor" => &[
|
||||
"saleor-db",
|
||||
"saleor-cache",
|
||||
"saleor-jaeger",
|
||||
"saleor-mailpit",
|
||||
"saleor-api",
|
||||
"saleor-worker",
|
||||
"saleor",
|
||||
],
|
||||
"penpot" | "penpot-frontend" => &[
|
||||
"penpot-postgres",
|
||||
"penpot-valkey",
|
||||
|
||||
@ -244,6 +244,9 @@ impl RpcHandler {
|
||||
if package_id == "netbird" {
|
||||
return self.install_netbird_stack().await;
|
||||
}
|
||||
if package_id == "saleor" {
|
||||
return self.install_saleor_stack().await;
|
||||
}
|
||||
|
||||
// Dependency checks. Prefer the scanner's cached package state so a
|
||||
// congested Podman API does not turn an already-running dependency into
|
||||
|
||||
@ -99,6 +99,7 @@ async fn repair_stack_before_adopt(stack_name: &str) {
|
||||
}
|
||||
"indeedhub" => repair_indeedhub_network_aliases().await,
|
||||
"netbird" => repair_netbird_unified_origin().await,
|
||||
"saleor" => repair_saleor_network_aliases().await,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@ -151,23 +152,9 @@ async fn repair_netbird_unified_origin() {
|
||||
.unwrap_or_else(|| "127.0.0.1".to_string());
|
||||
let _ = write_netbird_config_files(&host_ip).await;
|
||||
|
||||
let names = tokio::process::Command::new("podman")
|
||||
.args(["ps", "-a", "--format", "{{.Names}}"])
|
||||
.output()
|
||||
.await
|
||||
.ok()
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let has_proxy = names.lines().any(|n| n.trim() == "netbird");
|
||||
let has_dashboard = names.lines().any(|n| n.trim() == "netbird-dashboard");
|
||||
if has_proxy && has_dashboard {
|
||||
return;
|
||||
}
|
||||
|
||||
if has_proxy && !has_dashboard {
|
||||
for container in ["netbird", "netbird-dashboard"] {
|
||||
let _ = tokio::process::Command::new("podman")
|
||||
.args(["rm", "-f", "netbird"])
|
||||
.args(["rm", "-f", container])
|
||||
.output()
|
||||
.await;
|
||||
}
|
||||
@ -177,6 +164,9 @@ async fn repair_netbird_unified_origin() {
|
||||
.output()
|
||||
.await;
|
||||
|
||||
let _ = pull_image_with_retry(NETBIRD_DASHBOARD_IMAGE).await;
|
||||
let _ = pull_image_with_retry(NETBIRD_PROXY_IMAGE).await;
|
||||
|
||||
let _ = tokio::process::Command::new("podman")
|
||||
.args([
|
||||
"run",
|
||||
@ -210,6 +200,75 @@ async fn repair_netbird_unified_origin() {
|
||||
])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
let _ = tokio::process::Command::new("podman")
|
||||
.args([
|
||||
"network",
|
||||
"disconnect",
|
||||
"-f",
|
||||
"netbird-net",
|
||||
"netbird-server",
|
||||
])
|
||||
.output()
|
||||
.await;
|
||||
let _ = tokio::process::Command::new("podman")
|
||||
.args([
|
||||
"network",
|
||||
"connect",
|
||||
"--alias",
|
||||
"netbird-server",
|
||||
"netbird-net",
|
||||
"netbird-server",
|
||||
])
|
||||
.output()
|
||||
.await;
|
||||
let _ = tokio::process::Command::new("podman")
|
||||
.args(["restart", "netbird-server"])
|
||||
.output()
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn repair_saleor_network_aliases() {
|
||||
let _ = tokio::process::Command::new("podman")
|
||||
.args(["network", "create", "saleor-net"])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
for (container, alias) in [
|
||||
("saleor-db", "db"),
|
||||
("saleor-cache", "cache"),
|
||||
("saleor-jaeger", "jaeger"),
|
||||
("saleor-mailpit", "mailpit"),
|
||||
("saleor-api", "api"),
|
||||
("saleor-worker", "worker"),
|
||||
("saleor", "saleor"),
|
||||
] {
|
||||
let exists = tokio::process::Command::new("podman")
|
||||
.args(["container", "exists", container])
|
||||
.status()
|
||||
.await
|
||||
.map(|s| s.success())
|
||||
.unwrap_or(false);
|
||||
if !exists {
|
||||
continue;
|
||||
}
|
||||
|
||||
let _ = tokio::process::Command::new("podman")
|
||||
.args(["network", "disconnect", "-f", "saleor-net", container])
|
||||
.output()
|
||||
.await;
|
||||
let _ = tokio::process::Command::new("podman")
|
||||
.args([
|
||||
"network",
|
||||
"connect",
|
||||
"--alias",
|
||||
alias,
|
||||
"saleor-net",
|
||||
container,
|
||||
])
|
||||
.output()
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_required_stack_command(
|
||||
@ -382,6 +441,12 @@ const REGISTRY: &str = "146.59.87.168:3000/lfg2025";
|
||||
const NETBIRD_DASHBOARD_IMAGE: &str = "docker.io/netbirdio/dashboard:v2.38.0";
|
||||
const NETBIRD_SERVER_IMAGE: &str = "docker.io/netbirdio/netbird-server:0.71.2";
|
||||
const NETBIRD_PROXY_IMAGE: &str = "docker.io/library/nginx:1.27-alpine";
|
||||
const SALEOR_API_IMAGE: &str = "ghcr.io/saleor/saleor:3.23";
|
||||
const SALEOR_DASHBOARD_IMAGE: &str = "ghcr.io/saleor/saleor-dashboard:3.23";
|
||||
const SALEOR_POSTGRES_IMAGE: &str = "docker.io/library/postgres:15-alpine";
|
||||
const SALEOR_VALKEY_IMAGE: &str = "docker.io/valkey/valkey:8.1-alpine";
|
||||
const SALEOR_JAEGER_IMAGE: &str = "docker.io/jaegertracing/jaeger:latest";
|
||||
const SALEOR_MAILPIT_IMAGE: &str = "docker.io/axllent/mailpit:latest";
|
||||
|
||||
/// Pull an image with retry and exponential backoff (3 attempts).
|
||||
async fn pull_image_with_retry(image: &str) -> Result<()> {
|
||||
@ -1574,6 +1639,415 @@ impl RpcHandler {
|
||||
"message": "NetBird self-hosted stack installed",
|
||||
}))
|
||||
}
|
||||
|
||||
/// Install Saleor stack (PostgreSQL + Valkey + API + worker + dashboard + Jaeger + Mailpit).
|
||||
pub(super) async fn install_saleor_stack(&self) -> Result<serde_json::Value> {
|
||||
if let Some(adopted) = adopt_stack_if_exists(
|
||||
"saleor",
|
||||
"saleor",
|
||||
&[
|
||||
"saleor-db",
|
||||
"saleor-cache",
|
||||
"saleor-jaeger",
|
||||
"saleor-mailpit",
|
||||
"saleor-api",
|
||||
"saleor-worker",
|
||||
"saleor",
|
||||
],
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(adopted);
|
||||
}
|
||||
|
||||
install_log("INSTALL START: saleor stack (postgres + valkey + api + worker + dashboard)")
|
||||
.await;
|
||||
info!("Installing Saleor stack");
|
||||
|
||||
let images = [
|
||||
SALEOR_POSTGRES_IMAGE,
|
||||
SALEOR_VALKEY_IMAGE,
|
||||
SALEOR_API_IMAGE,
|
||||
SALEOR_DASHBOARD_IMAGE,
|
||||
SALEOR_JAEGER_IMAGE,
|
||||
SALEOR_MAILPIT_IMAGE,
|
||||
];
|
||||
self.set_install_phase("saleor", InstallPhase::PullingImage)
|
||||
.await;
|
||||
let n_images = images.len() as u64;
|
||||
for (i, image) in images.iter().enumerate() {
|
||||
self.set_install_progress("saleor", i as u64, n_images)
|
||||
.await;
|
||||
pull_image_with_retry(image)
|
||||
.await
|
||||
.with_context(|| format!("Failed to pull Saleor image: {}", image))?;
|
||||
}
|
||||
self.set_install_progress("saleor", n_images, n_images)
|
||||
.await;
|
||||
|
||||
for name in [
|
||||
"saleor",
|
||||
"saleor-api",
|
||||
"saleor-worker",
|
||||
"saleor-db",
|
||||
"saleor-cache",
|
||||
"saleor-jaeger",
|
||||
"saleor-mailpit",
|
||||
] {
|
||||
let _ = tokio::process::Command::new("podman")
|
||||
.args(["rm", "-f", name])
|
||||
.status()
|
||||
.await;
|
||||
}
|
||||
let _ = tokio::process::Command::new("podman")
|
||||
.args(["network", "rm", "-f", "saleor-net"])
|
||||
.status()
|
||||
.await;
|
||||
|
||||
self.set_install_phase("saleor", InstallPhase::CreatingContainer)
|
||||
.await;
|
||||
|
||||
let _ = tokio::process::Command::new("sudo")
|
||||
.args([
|
||||
"mkdir",
|
||||
"-p",
|
||||
"/var/lib/archipelago/saleor",
|
||||
"/var/lib/archipelago/saleor-db",
|
||||
"/var/lib/archipelago/saleor-cache",
|
||||
])
|
||||
.output()
|
||||
.await;
|
||||
let user = std::env::var("USER").unwrap_or_else(|_| "archipelago".to_string());
|
||||
for dir in [
|
||||
"/var/lib/archipelago/saleor",
|
||||
"/var/lib/archipelago/saleor-db",
|
||||
"/var/lib/archipelago/saleor-cache",
|
||||
] {
|
||||
let _ = tokio::process::Command::new("sudo")
|
||||
.args(["chown", "-R", &format!("{}:{}", user, user), dir])
|
||||
.output()
|
||||
.await;
|
||||
}
|
||||
|
||||
let _ = tokio::process::Command::new("podman")
|
||||
.args(["network", "create", "saleor-net"])
|
||||
.status()
|
||||
.await;
|
||||
|
||||
let db_pass = super::config::read_or_generate_secret("saleor-db-password").await;
|
||||
let secret_key = super::config::read_or_generate_secret("saleor-secret-key").await;
|
||||
let host_ip = &self.config.host_ip;
|
||||
let dashboard_url = format!("http://{}:9000/", host_ip);
|
||||
let api_url = format!("http://{}:8000/graphql/", host_ip);
|
||||
let allowed_hosts = format!("localhost,127.0.0.1,api,saleor-api,{}", host_ip);
|
||||
let database_url = format!("postgres://saleor:{}@db/saleor", db_pass);
|
||||
|
||||
let mut db_cmd = tokio::process::Command::new("podman");
|
||||
db_cmd.args([
|
||||
"run",
|
||||
"-d",
|
||||
"--name",
|
||||
"saleor-db",
|
||||
"--network",
|
||||
"saleor-net",
|
||||
"--network-alias",
|
||||
"db",
|
||||
"--restart=unless-stopped",
|
||||
"--cap-drop=ALL",
|
||||
"--cap-add=CHOWN",
|
||||
"--cap-add=DAC_OVERRIDE",
|
||||
"--cap-add=FOWNER",
|
||||
"--cap-add=SETGID",
|
||||
"--cap-add=SETUID",
|
||||
"--security-opt=no-new-privileges:true",
|
||||
"--memory=512m",
|
||||
"--pids-limit=4096",
|
||||
"--health-cmd=pg_isready -U saleor || exit 1",
|
||||
"--health-interval=30s",
|
||||
"--health-retries=3",
|
||||
"-v",
|
||||
"/var/lib/archipelago/saleor-db:/var/lib/postgresql/data",
|
||||
"-e",
|
||||
"POSTGRES_USER=saleor",
|
||||
"-e",
|
||||
&format!("POSTGRES_PASSWORD={}", db_pass),
|
||||
"-e",
|
||||
"POSTGRES_DB=saleor",
|
||||
SALEOR_POSTGRES_IMAGE,
|
||||
]);
|
||||
run_required_stack_command("saleor", "create postgres", &mut db_cmd).await?;
|
||||
|
||||
let mut cache_cmd = tokio::process::Command::new("podman");
|
||||
cache_cmd.args([
|
||||
"run",
|
||||
"-d",
|
||||
"--name",
|
||||
"saleor-cache",
|
||||
"--network",
|
||||
"saleor-net",
|
||||
"--network-alias",
|
||||
"cache",
|
||||
"--restart=unless-stopped",
|
||||
"--cap-drop=ALL",
|
||||
"--cap-add=SETGID",
|
||||
"--cap-add=SETUID",
|
||||
"--security-opt=no-new-privileges:true",
|
||||
"--memory=128m",
|
||||
"--pids-limit=2048",
|
||||
"--health-cmd=valkey-cli ping || exit 1",
|
||||
"--health-interval=30s",
|
||||
"--health-retries=3",
|
||||
"-v",
|
||||
"/var/lib/archipelago/saleor-cache:/data",
|
||||
SALEOR_VALKEY_IMAGE,
|
||||
]);
|
||||
run_required_stack_command("saleor", "create cache", &mut cache_cmd).await?;
|
||||
|
||||
let mut jaeger_cmd = tokio::process::Command::new("podman");
|
||||
jaeger_cmd.args([
|
||||
"run",
|
||||
"-d",
|
||||
"--name",
|
||||
"saleor-jaeger",
|
||||
"--network",
|
||||
"saleor-net",
|
||||
"--network-alias",
|
||||
"jaeger",
|
||||
"--restart=unless-stopped",
|
||||
"--cap-drop=ALL",
|
||||
"--security-opt=no-new-privileges:true",
|
||||
"--memory=512m",
|
||||
"--pids-limit=4096",
|
||||
"-p",
|
||||
"16686:16686",
|
||||
"-p",
|
||||
"4317:4317",
|
||||
"-p",
|
||||
"4318:4318",
|
||||
"--tmpfs",
|
||||
"/tmp:rw,nosuid,nodev,size=128m",
|
||||
SALEOR_JAEGER_IMAGE,
|
||||
]);
|
||||
run_required_stack_command("saleor", "create jaeger", &mut jaeger_cmd).await?;
|
||||
|
||||
let mut mailpit_cmd = tokio::process::Command::new("podman");
|
||||
mailpit_cmd.args([
|
||||
"run",
|
||||
"-d",
|
||||
"--name",
|
||||
"saleor-mailpit",
|
||||
"--network",
|
||||
"saleor-net",
|
||||
"--network-alias",
|
||||
"mailpit",
|
||||
"--restart=unless-stopped",
|
||||
"--cap-drop=ALL",
|
||||
"--security-opt=no-new-privileges:true",
|
||||
"--memory=128m",
|
||||
"--pids-limit=2048",
|
||||
"-p",
|
||||
"1025:1025",
|
||||
"-p",
|
||||
"8025:8025",
|
||||
SALEOR_MAILPIT_IMAGE,
|
||||
]);
|
||||
run_required_stack_command("saleor", "create mailpit", &mut mailpit_cmd).await?;
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_secs(8)).await;
|
||||
|
||||
let saleor_env = vec![
|
||||
"-e".to_string(),
|
||||
"CACHE_URL=redis://cache:6379/0".to_string(),
|
||||
"-e".to_string(),
|
||||
"CELERY_BROKER_URL=redis://cache:6379/1".to_string(),
|
||||
"-e".to_string(),
|
||||
format!("DATABASE_URL={}", database_url),
|
||||
"-e".to_string(),
|
||||
"DEFAULT_CHANNEL_SLUG=default-channel".to_string(),
|
||||
"-e".to_string(),
|
||||
"DEFAULT_FROM_EMAIL=noreply@example.com".to_string(),
|
||||
"-e".to_string(),
|
||||
"EMAIL_URL=smtp://mailpit:1025".to_string(),
|
||||
"-e".to_string(),
|
||||
format!("SECRET_KEY={}", secret_key),
|
||||
"-e".to_string(),
|
||||
"OTEL_SERVICE_NAME=saleor".to_string(),
|
||||
"-e".to_string(),
|
||||
"OTEL_TRACES_EXPORTER=otlp".to_string(),
|
||||
"-e".to_string(),
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317".to_string(),
|
||||
"-e".to_string(),
|
||||
"HTTP_IP_FILTER_ALLOW_LOOPBACK_IPS=True".to_string(),
|
||||
"-e".to_string(),
|
||||
"HTTP_IP_FILTER_ENABLED=False".to_string(),
|
||||
"-e".to_string(),
|
||||
format!("DASHBOARD_URL={}", dashboard_url),
|
||||
"-e".to_string(),
|
||||
format!("ALLOWED_HOSTS={}", allowed_hosts),
|
||||
];
|
||||
|
||||
let mut migrate_cmd = tokio::process::Command::new("podman");
|
||||
migrate_cmd.args([
|
||||
"run",
|
||||
"--rm",
|
||||
"--network",
|
||||
"saleor-net",
|
||||
"-v",
|
||||
"/var/lib/archipelago/saleor:/app/media",
|
||||
]);
|
||||
migrate_cmd.args(&saleor_env);
|
||||
migrate_cmd.args([SALEOR_API_IMAGE, "python3", "manage.py", "migrate"]);
|
||||
run_required_stack_command("saleor", "run migrations", &mut migrate_cmd).await?;
|
||||
|
||||
let mut populate_cmd = tokio::process::Command::new("podman");
|
||||
populate_cmd.args([
|
||||
"run",
|
||||
"--rm",
|
||||
"--network",
|
||||
"saleor-net",
|
||||
"-v",
|
||||
"/var/lib/archipelago/saleor:/app/media",
|
||||
]);
|
||||
populate_cmd.args(&saleor_env);
|
||||
populate_cmd.args([
|
||||
SALEOR_API_IMAGE,
|
||||
"python3",
|
||||
"manage.py",
|
||||
"populatedb",
|
||||
"--createsuperuser",
|
||||
]);
|
||||
let populate = populate_cmd.output().await;
|
||||
if let Ok(output) = populate {
|
||||
if !output.status.success() {
|
||||
install_log(&format!(
|
||||
"INSTALL WARN: saleor - populate sample data skipped: {}{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
let mut api_cmd = tokio::process::Command::new("podman");
|
||||
api_cmd.args([
|
||||
"run",
|
||||
"-d",
|
||||
"--name",
|
||||
"saleor-api",
|
||||
"--network",
|
||||
"saleor-net",
|
||||
"--network-alias",
|
||||
"api",
|
||||
"--restart=unless-stopped",
|
||||
"--cap-drop=ALL",
|
||||
"--cap-add=CHOWN",
|
||||
"--cap-add=DAC_OVERRIDE",
|
||||
"--cap-add=FOWNER",
|
||||
"--cap-add=SETGID",
|
||||
"--cap-add=SETUID",
|
||||
"--security-opt=no-new-privileges:true",
|
||||
"--memory=1g",
|
||||
"--pids-limit=4096",
|
||||
"-p",
|
||||
"8000:8000",
|
||||
"-v",
|
||||
"/var/lib/archipelago/saleor:/app/media",
|
||||
]);
|
||||
api_cmd.args(&saleor_env);
|
||||
api_cmd.arg(SALEOR_API_IMAGE);
|
||||
run_required_stack_command("saleor", "create api", &mut api_cmd).await?;
|
||||
|
||||
let mut worker_cmd = tokio::process::Command::new("podman");
|
||||
worker_cmd.args([
|
||||
"run",
|
||||
"-d",
|
||||
"--name",
|
||||
"saleor-worker",
|
||||
"--network",
|
||||
"saleor-net",
|
||||
"--restart=unless-stopped",
|
||||
"--cap-drop=ALL",
|
||||
"--cap-add=CHOWN",
|
||||
"--cap-add=DAC_OVERRIDE",
|
||||
"--cap-add=FOWNER",
|
||||
"--cap-add=SETGID",
|
||||
"--cap-add=SETUID",
|
||||
"--security-opt=no-new-privileges:true",
|
||||
"--memory=1g",
|
||||
"--pids-limit=4096",
|
||||
"-v",
|
||||
"/var/lib/archipelago/saleor:/app/media",
|
||||
]);
|
||||
worker_cmd.args(&saleor_env);
|
||||
worker_cmd.args([
|
||||
SALEOR_API_IMAGE,
|
||||
"celery",
|
||||
"-A",
|
||||
"saleor",
|
||||
"--app=saleor.celeryconf:app",
|
||||
"worker",
|
||||
"--loglevel=info",
|
||||
"-B",
|
||||
]);
|
||||
run_required_stack_command("saleor", "create worker", &mut worker_cmd).await?;
|
||||
|
||||
self.set_install_phase("saleor", InstallPhase::StartingContainer)
|
||||
.await;
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
|
||||
let mut dashboard_cmd = tokio::process::Command::new("podman");
|
||||
dashboard_cmd.args([
|
||||
"run",
|
||||
"-d",
|
||||
"--name",
|
||||
"saleor",
|
||||
"--network",
|
||||
"saleor-net",
|
||||
"--restart=unless-stopped",
|
||||
"--cap-drop=ALL",
|
||||
"--security-opt=no-new-privileges:true",
|
||||
"--memory=256m",
|
||||
"--pids-limit=2048",
|
||||
"-p",
|
||||
"9000:80",
|
||||
"-e",
|
||||
&format!("API_URL={}", api_url),
|
||||
"-e",
|
||||
&format!("APP_MOUNT_URI={}", dashboard_url),
|
||||
SALEOR_DASHBOARD_IMAGE,
|
||||
]);
|
||||
run_required_stack_command("saleor", "create dashboard", &mut dashboard_cmd).await?;
|
||||
|
||||
wait_for_stack_containers(
|
||||
"saleor",
|
||||
&[
|
||||
"saleor-db",
|
||||
"saleor-cache",
|
||||
"saleor-jaeger",
|
||||
"saleor-mailpit",
|
||||
"saleor-api",
|
||||
"saleor-worker",
|
||||
"saleor",
|
||||
],
|
||||
120,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.set_install_phase("saleor", InstallPhase::WaitingHealthy)
|
||||
.await;
|
||||
self.set_install_phase("saleor", InstallPhase::PostInstall)
|
||||
.await;
|
||||
self.set_install_phase("saleor", InstallPhase::Done).await;
|
||||
self.clear_install_progress("saleor").await;
|
||||
|
||||
install_log("INSTALL OK: saleor stack").await;
|
||||
info!("Saleor stack installed");
|
||||
Ok(serde_json::json!({
|
||||
"success": true,
|
||||
"package_id": "saleor",
|
||||
"message": "Saleor stack installed (7 containers)",
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_or_generate_b64_secret(name: &str) -> String {
|
||||
@ -1616,6 +2090,8 @@ async fn write_netbird_config_files(host_ip: &str) -> Result<()> {
|
||||
dashboardRedirectURIs:
|
||||
- "{public_origin}/nb-auth"
|
||||
- "{public_origin}/nb-silent-auth"
|
||||
dashboardPostLogoutRedirectURIs:
|
||||
- "{public_origin}/"
|
||||
cliRedirectURIs:
|
||||
- "http://localhost:53000/"
|
||||
store:
|
||||
@ -1665,7 +2141,7 @@ LETSENCRYPT_DOMAIN=none
|
||||
proxy_read_timeout 1d;
|
||||
}}
|
||||
|
||||
location ~ ^/(api|oauth2)/ {{
|
||||
location ~ ^/(api|oauth2)(/|$) {{
|
||||
proxy_pass http://netbird-server:80;
|
||||
}}
|
||||
|
||||
|
||||
@ -63,6 +63,12 @@ impl DockerPackageScanner {
|
||||
"indeedhub-build_ffmpeg-worker_1",
|
||||
"netbird-server",
|
||||
"netbird-dashboard",
|
||||
"saleor-api",
|
||||
"saleor-worker",
|
||||
"saleor-db",
|
||||
"saleor-cache",
|
||||
"saleor-jaeger",
|
||||
"saleor-mailpit",
|
||||
"buildx_buildkit_default",
|
||||
];
|
||||
|
||||
@ -283,6 +289,7 @@ fn get_app_tier(app_id: &str) -> &'static str {
|
||||
"uptime-kuma" => "recommended",
|
||||
"grafana" => "recommended",
|
||||
"searxng" => "recommended",
|
||||
"saleor" => "recommended",
|
||||
"tailscale" | "netbird" => "recommended",
|
||||
"portainer" => "recommended",
|
||||
// Optional: everything else
|
||||
@ -488,6 +495,13 @@ fn get_app_metadata(app_id: &str) -> AppMetadata {
|
||||
repo: "https://github.com/netbirdio/netbird".to_string(),
|
||||
tier: "",
|
||||
},
|
||||
"saleor" => AppMetadata {
|
||||
title: "Saleor".to_string(),
|
||||
description: "Composable commerce platform with GraphQL API and dashboard".to_string(),
|
||||
icon: "/assets/img/app-icons/saleor.svg".to_string(),
|
||||
repo: "https://github.com/saleor/saleor".to_string(),
|
||||
tier: "",
|
||||
},
|
||||
"gitea" => AppMetadata {
|
||||
title: "Gitea".to_string(),
|
||||
description: "Self-hosted Git service with repository and package hosting".to_string(),
|
||||
@ -716,6 +730,7 @@ fn requires_reachable_launch(app_id: &str) -> bool {
|
||||
| "tailscale"
|
||||
| "immich"
|
||||
| "searxng"
|
||||
| "saleor"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -171,6 +171,12 @@ fn image_var_for_app(app_id: &str) -> Option<&'static str> {
|
||||
"netbird" => Some("NETBIRD_DASHBOARD_IMAGE"),
|
||||
"netbird-dashboard" => Some("NETBIRD_DASHBOARD_IMAGE"),
|
||||
"netbird-server" => Some("NETBIRD_SERVER_IMAGE"),
|
||||
"saleor" => Some("SALEOR_DASHBOARD_IMAGE"),
|
||||
"saleor-api" | "saleor-worker" => Some("SALEOR_API_IMAGE"),
|
||||
"saleor-db" => Some("SALEOR_POSTGRES_IMAGE"),
|
||||
"saleor-cache" => Some("SALEOR_VALKEY_IMAGE"),
|
||||
"saleor-jaeger" => Some("SALEOR_JAEGER_IMAGE"),
|
||||
"saleor-mailpit" => Some("SALEOR_MAILPIT_IMAGE"),
|
||||
|
||||
// Fedimint
|
||||
"fedimint" | "fedimintd" => Some("FEDIMINT_IMAGE"),
|
||||
@ -307,6 +313,15 @@ pub fn containers_for_stack(app_id: &str) -> Vec<(&'static str, &'static str)> {
|
||||
("netbird-dashboard", "NETBIRD_DASHBOARD_IMAGE"),
|
||||
("netbird-server", "NETBIRD_SERVER_IMAGE"),
|
||||
],
|
||||
"saleor" => vec![
|
||||
("saleor-db", "SALEOR_POSTGRES_IMAGE"),
|
||||
("saleor-cache", "SALEOR_VALKEY_IMAGE"),
|
||||
("saleor-api", "SALEOR_API_IMAGE"),
|
||||
("saleor-worker", "SALEOR_API_IMAGE"),
|
||||
("saleor-jaeger", "SALEOR_JAEGER_IMAGE"),
|
||||
("saleor-mailpit", "SALEOR_MAILPIT_IMAGE"),
|
||||
("saleor", "SALEOR_DASHBOARD_IMAGE"),
|
||||
],
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
5
neode-ui/public/assets/img/app-icons/saleor.svg
Normal file
5
neode-ui/public/assets/img/app-icons/saleor.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-label="Saleor">
|
||||
<rect width="128" height="128" rx="30" fill="#111827"/>
|
||||
<path d="M34 42c0-10 9-18 22-18h38v16H56c-5 0-8 2-8 5 0 4 4 5 13 7l15 3c15 3 24 11 24 24 0 15-12 25-31 25H31V88h39c8 0 13-3 13-8 0-4-4-6-12-8l-16-3C41 66 34 57 34 42Z" fill="#fff"/>
|
||||
<path d="M29 103h70v8H29z" fill="#7C3AED"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 389 B |
@ -64,6 +64,23 @@
|
||||
"bitcoin-knots"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "saleor",
|
||||
"title": "Saleor",
|
||||
"version": "3.23",
|
||||
"description": "Composable commerce platform with GraphQL API, dashboard, worker, mail testing, and tracing.",
|
||||
"icon": "/assets/img/app-icons/saleor.svg",
|
||||
"author": "Saleor",
|
||||
"category": "commerce",
|
||||
"tier": "recommended",
|
||||
"dockerImage": "ghcr.io/saleor/saleor:3.23",
|
||||
"repoUrl": "https://github.com/saleor/saleor",
|
||||
"containerConfig": {
|
||||
"ports": ["9000:80", "8000:8000", "8025:8025", "16686:16686"],
|
||||
"volumes": ["/var/lib/archipelago/saleor:/app/media", "/var/lib/archipelago/saleor-db:/var/lib/postgresql/data"],
|
||||
"notes": "Installed as a Saleor stack: dashboard on 9000, API on 8000, Mailpit on 8025, and Jaeger on 16686. Supporting containers include PostgreSQL, Valkey, Celery worker, and services required by Saleor."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mempool",
|
||||
"title": "Mempool Explorer",
|
||||
|
||||
@ -67,6 +67,9 @@
|
||||
data-controller-zone="main"
|
||||
class="flex-1 overflow-hidden relative pb-0 glass-piece z-10"
|
||||
:class="{ 'glass-throw-main': showZoomIn }"
|
||||
tabindex="-1"
|
||||
@pointerenter="activateMainScroll"
|
||||
@wheel.capture="activateMainScroll"
|
||||
>
|
||||
<div data-controller-main-entry class="absolute top-4 right-4 md:top-6 md:right-8 z-20">
|
||||
<!-- Controller zone entry point - no switcher -->
|
||||
@ -234,6 +237,14 @@ function restoreScroll(path: string) {
|
||||
})
|
||||
}
|
||||
|
||||
function activateMainScroll() {
|
||||
const active = document.activeElement as HTMLElement | null
|
||||
if (active?.closest?.('[data-controller-zone="sidebar"]')) {
|
||||
active.blur()
|
||||
document.getElementById('main-content')?.focus({ preventScroll: true })
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => route.path, (newPath) => {
|
||||
const isAppDetails = isDetailRoute(newPath)
|
||||
const wasAppDetails = showAltBackground.value
|
||||
|
||||
@ -14,6 +14,7 @@ export const APP_PORTS: Record<string, number> = {
|
||||
'archy-electrs-ui': 50002,
|
||||
'mempool-electrs': 50002,
|
||||
'btcpay-server': 23000,
|
||||
'saleor': 9000,
|
||||
'lnd': 18083,
|
||||
'archy-lnd-ui': 18083,
|
||||
'mempool': 4080,
|
||||
@ -71,7 +72,7 @@ export const EXTERNAL_URLS: Record<string, string> = {
|
||||
|
||||
export const APP_TITLES: Record<string, string> = {
|
||||
'bitcoin-knots': 'Bitcoin Knots', 'bitcoin-core': 'Bitcoin Core',
|
||||
'btcpay-server': 'BTCPay Server', 'indeedhub': 'Indeehub',
|
||||
'btcpay-server': 'BTCPay Server', 'saleor': 'Saleor', 'indeedhub': 'Indeehub',
|
||||
'botfights': 'BotFights', 'gitea': 'Gitea', '484-kitchen': '484 Kitchen', 'arch-presentation': 'Presentation',
|
||||
'homeassistant': 'Home Assistant', 'uptime-kuma': 'Uptime Kuma',
|
||||
'nginx-proxy-manager': 'Nginx Proxy Manager',
|
||||
|
||||
@ -79,6 +79,7 @@ export function getCuratedAppList(): MarketplaceApp[] {
|
||||
{ id: 'bitcoin-knots', title: 'Bitcoin Knots', version: '28.1.0', description: 'Run a full Bitcoin node. Validate and relay blocks and transactions on the Bitcoin network.', icon: '/assets/img/app-icons/bitcoin-knots.webp', author: 'Bitcoin Knots', dockerImage: `${R}/bitcoin-knots:latest`, repoUrl: 'https://github.com/bitcoinknots/bitcoin' },
|
||||
{ id: 'bitcoin-core', title: 'Bitcoin Core', version: '28.4', description: 'Reference implementation of the Bitcoin protocol. Run a full node validating and relaying blocks on the Bitcoin network.', icon: '/assets/img/app-icons/bitcoin-core.svg', author: 'Bitcoin Core contributors', dockerImage: 'docker.io/bitcoin/bitcoin:28.4', repoUrl: 'https://github.com/bitcoin/bitcoin' },
|
||||
{ id: 'btcpay-server', title: 'BTCPay Server', version: '2.3.9', description: 'Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries or fees.', icon: '/assets/img/app-icons/btcpay-server.png', author: 'BTCPay Server Foundation', dockerImage: 'docker.io/btcpayserver/btcpayserver:2.3.9', repoUrl: 'https://github.com/btcpayserver/btcpayserver' },
|
||||
{ id: 'saleor', title: 'Saleor', version: '3.23', category: 'commerce', description: 'Composable commerce platform with GraphQL API, dashboard, worker, mail testing, and tracing.', icon: '/assets/img/app-icons/saleor.svg', author: 'Saleor', dockerImage: 'ghcr.io/saleor/saleor:3.23', repoUrl: 'https://github.com/saleor/saleor' },
|
||||
{ id: 'lnd', title: 'LND', version: '0.18.4', description: 'Lightning Network Daemon. Fast and cheap Bitcoin payments through the Lightning Network.', icon: '/assets/img/app-icons/lnd.svg', author: 'Lightning Labs', dockerImage: `${R}/lnd:v0.18.4-beta`, repoUrl: 'https://github.com/lightningnetwork/lnd' },
|
||||
{ id: 'mempool', title: 'Mempool Explorer', version: '3.0.0', description: 'Self-hosted Bitcoin blockchain and mempool visualizer. Monitor transactions without revealing your addresses to third parties.', icon: '/assets/img/app-icons/mempool.webp', author: 'Mempool', dockerImage: `${R}/mempool-frontend:v3.0.0`, repoUrl: 'https://github.com/mempool/mempool' },
|
||||
{ id: 'homeassistant', title: 'Home Assistant', version: '2024.1', description: 'Open-source home automation. Control smart home devices privately, on your own hardware.', icon: '/assets/img/app-icons/homeassistant.png', author: 'Home Assistant', dockerImage: `${R}/home-assistant:2024.1`, repoUrl: 'https://github.com/home-assistant/core' },
|
||||
@ -120,6 +121,7 @@ export const INSTALLED_ALIASES: Record<string, string[]> = {
|
||||
mempool: ['mempool', 'mempool-web', 'archy-mempool-web'],
|
||||
bitcoin: ['bitcoin-knots'],
|
||||
btcpay: ['btcpay-server'],
|
||||
saleor: ['saleor'],
|
||||
immich: ['immich-server', 'immich-app', 'immich_server'],
|
||||
nextcloud: ['nextcloud-aio', 'nextcloud-server'],
|
||||
fedimint: ['fedimint-gateway'],
|
||||
@ -189,7 +191,7 @@ export function categorizeCommunityApp(app: MarketplaceApp): string {
|
||||
const combined = `${id} ${title} ${description}`
|
||||
|
||||
if (id.includes('bitcoin') || id.includes('btc') || id.includes('lightning') || id.includes('lnd') || id.includes('electr') || id.includes('fedimint') || id.includes('cashu') || combined.includes('wallet')) return 'money'
|
||||
if (id.includes('btcpay') || id.includes('commerce') || id.includes('shop') || id.includes('pos') || combined.includes('merchant')) return 'commerce'
|
||||
if (id.includes('btcpay') || id.includes('saleor') || id.includes('commerce') || id.includes('shop') || id.includes('pos') || combined.includes('merchant')) return 'commerce'
|
||||
if (id.includes('cloud') || id.includes('nextcloud') || id.includes('storage') || id.includes('file') || id.includes('photo') || id.includes('immich') || id.includes('jellyfin') || id.includes('media') || id.includes('vault') || combined.includes('password manager')) return 'data'
|
||||
if (id.includes('home-assistant') || id.includes('homeassistant') || combined.includes('home automation')) return 'home'
|
||||
if (id.includes('nostr') || combined.includes('nostr relay')) return 'nostr'
|
||||
|
||||
@ -47,6 +47,7 @@ export const INSTALLED_ALIASES: Record<string, string[]> = {
|
||||
mempool: ['mempool-web', 'mempool-api', 'archy-mempool-web', 'archy-mempool-db'],
|
||||
bitcoin: ['bitcoin-knots'],
|
||||
btcpay: ['btcpay-server', 'archy-btcpay-db', 'archy-nbxplorer'],
|
||||
saleor: ['saleor'],
|
||||
immich: ['immich-server', 'immich-app', 'immich_server', 'immich_postgres', 'immich_redis'],
|
||||
nextcloud: ['nextcloud-aio', 'nextcloud-server'],
|
||||
fedimint: ['fedimint-gateway'],
|
||||
@ -67,7 +68,7 @@ export const INSTALLED_ALIASES: Record<string, string[]> = {
|
||||
/** Get app tier classification (matches backend get_app_tier) */
|
||||
export function getAppTier(appId: string): string {
|
||||
const core = ['bitcoin-knots', 'bitcoin', 'lnd', 'mempool', 'btcpay-server', 'dwn', 'filebrowser']
|
||||
const recommended = ['fedimint', 'thunderhub', 'vaultwarden', 'uptime-kuma', 'grafana', 'searxng', 'tailscale', 'netbird', 'portainer']
|
||||
const recommended = ['fedimint', 'thunderhub', 'vaultwarden', 'uptime-kuma', 'grafana', 'searxng', 'tailscale', 'netbird', 'portainer', 'saleor']
|
||||
if (core.includes(appId)) return 'core'
|
||||
if (recommended.includes(appId)) return 'recommended'
|
||||
return 'optional'
|
||||
@ -89,7 +90,7 @@ export function categorizeCommunityApp(app: MarketplaceApp): string {
|
||||
return 'money'
|
||||
}
|
||||
|
||||
if (id.includes('btcpay') || id.includes('commerce') || id.includes('shop') ||
|
||||
if (id.includes('btcpay') || id.includes('saleor') || id.includes('commerce') || id.includes('shop') ||
|
||||
id.includes('store') || id.includes('pos') || id.includes('payment') ||
|
||||
combined.includes('merchant') || combined.includes('invoice')) {
|
||||
return 'commerce'
|
||||
@ -157,6 +158,18 @@ export function getCuratedAppList(): MarketplaceApp[] {
|
||||
manifestUrl: undefined,
|
||||
repoUrl: 'https://github.com/btcpayserver/btcpayserver'
|
||||
},
|
||||
{
|
||||
id: 'saleor',
|
||||
title: 'Saleor',
|
||||
version: '3.23',
|
||||
category: 'commerce',
|
||||
description: 'Composable commerce platform with GraphQL API, dashboard, worker, mail testing, and tracing.',
|
||||
icon: '/assets/img/app-icons/saleor.svg',
|
||||
author: 'Saleor',
|
||||
dockerImage: 'ghcr.io/saleor/saleor:3.23',
|
||||
manifestUrl: undefined,
|
||||
repoUrl: 'https://github.com/saleor/saleor'
|
||||
},
|
||||
{
|
||||
id: 'lnd',
|
||||
title: 'LND',
|
||||
|
||||
@ -54,6 +54,14 @@ NETBIRD_PROXY_IMAGE="docker.io/library/nginx:1.27-alpine"
|
||||
ALPINE_TOR_IMAGE="$ARCHY_REGISTRY/alpine-tor:0.4.8.13"
|
||||
ADGUARDHOME_IMAGE="$ARCHY_REGISTRY/adguardhome:v0.107.55"
|
||||
|
||||
# Saleor stack
|
||||
SALEOR_API_IMAGE="ghcr.io/saleor/saleor:3.23"
|
||||
SALEOR_DASHBOARD_IMAGE="ghcr.io/saleor/saleor-dashboard:3.23"
|
||||
SALEOR_POSTGRES_IMAGE="docker.io/library/postgres:15-alpine"
|
||||
SALEOR_VALKEY_IMAGE="docker.io/valkey/valkey:8.1-alpine"
|
||||
SALEOR_JAEGER_IMAGE="docker.io/jaegertracing/jaeger:latest"
|
||||
SALEOR_MAILPIT_IMAGE="docker.io/axllent/mailpit:latest"
|
||||
|
||||
# Fedimint
|
||||
FEDIMINT_IMAGE="$ARCHY_REGISTRY/fedimintd:v0.10.0"
|
||||
FEDIMINT_GATEWAY_IMAGE="$ARCHY_REGISTRY/gatewayd:v0.10.0"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user