From 34c4e87d14b3561e22c49d6531b74b3d577411af Mon Sep 17 00:00:00 2001 From: archipelago Date: Wed, 20 May 2026 23:02:57 -0400 Subject: [PATCH] feat(apps): add saleor storefront --- CHANGELOG.md | 7 + app-catalog/catalog.json | 6 +- .../archipelago/src/api/rpc/package/stacks.rs | 151 +++++++++++++++++- .../src/container/docker_packages.rs | 4 +- core/container/src/podman_client.rs | 2 +- neode-ui/public/catalog.json | 6 +- neode-ui/src/stores/appLauncher.ts | 1 + .../__tests__/appSessionConfig.test.ts | 1 + .../src/views/appSession/appSessionConfig.ts | 2 +- neode-ui/src/views/discover/curatedApps.ts | 2 +- .../src/views/marketplace/marketplaceData.ts | 2 +- .../src/views/settings/AccountInfoSection.vue | 13 ++ scripts/sync-npm-public-hosts.sh | 14 +- 13 files changed, 194 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5082e4b..7b9d040b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.7.79-alpha (2026-05-20) + +- Saleor now installs the official Saleor Storefront as part of the stack, built from the pinned `saleor/storefront` source and served as the customer-facing shop on port `9011`. +- Saleor app launches now open the storefront while the admin dashboard remains available on port `9010` with the generated `admin@example.com` credentials shown in Archipelago. +- Public Nginx Proxy Manager hosts forwarding to the Saleor storefront also expose same-origin `/graphql/`, so public storefront domains can talk to the local Saleor API without mixed-content or private-LAN reachability failures. +- Saleor stack metadata, marketplace descriptions, catalog ports, scanner exclusions, and app-session routing now describe the storefront/dashboard/API split explicitly. + ## v1.7.78-alpha (2026-05-20) - Public Nginx Proxy Manager hosts for Saleor now keep browser GraphQL calls same-origin at `/graphql/` and proxy them to the local API on `8000`, fixing `Failed to fetch` when a public domain such as `noderunner.shop` was loaded from devices that cannot reach the node's private LAN/tailnet API address. diff --git a/app-catalog/catalog.json b/app-catalog/catalog.json index 6430b0f2..68e55d82 100644 --- a/app-catalog/catalog.json +++ b/app-catalog/catalog.json @@ -68,7 +68,7 @@ "id": "saleor", "title": "Saleor", "version": "3.23", - "description": "Composable commerce platform with GraphQL API, dashboard, worker, mail testing, and tracing.", + "description": "Composable commerce platform with customer storefront, GraphQL API, dashboard, worker, mail testing, and tracing.", "icon": "/assets/img/app-icons/saleor.svg", "author": "Saleor", "category": "commerce", @@ -76,9 +76,9 @@ "dockerImage": "ghcr.io/saleor/saleor:3.23", "repoUrl": "https://github.com/saleor/saleor", "containerConfig": { - "ports": ["9010:80", "8000:8000", "8025:8025", "16686:16686"], + "ports": ["9011:80", "9010: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 9010, API on 8000, Mailpit on 8025, and Jaeger on 16686. Supporting containers include PostgreSQL, Valkey, Celery worker, and services required by Saleor." + "notes": "Installed as a Saleor stack: customer storefront on 9011, admin dashboard on 9010, API on 8000, Mailpit on 8025, and Jaeger on 16686. Supporting containers include PostgreSQL, Valkey, Celery worker, and services required by Saleor." } }, { diff --git a/core/archipelago/src/api/rpc/package/stacks.rs b/core/archipelago/src/api/rpc/package/stacks.rs index f786aa46..c709b35c 100644 --- a/core/archipelago/src/api/rpc/package/stacks.rs +++ b/core/archipelago/src/api/rpc/package/stacks.rs @@ -244,6 +244,8 @@ async fn repair_saleor_network_aliases() { ("saleor-api", "api"), ("saleor-worker", "worker"), ("saleor", "saleor"), + ("saleor-storefront", "storefront"), + ("saleor-storefront-app", "storefront-app"), ] { let exists = tokio::process::Command::new("podman") .args(["container", "exists", container]) @@ -446,6 +448,9 @@ 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_STOREFRONT_IMAGE: &str = "localhost/archipelago/saleor-storefront:6eb0b97"; +const SALEOR_STOREFRONT_CONTEXT: &str = + "https://github.com/saleor/storefront.git#6eb0b97b25bd4344d8139515a1cabf763d703b39"; 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"; @@ -1656,6 +1661,8 @@ impl RpcHandler { "saleor-api", "saleor-worker", "saleor", + "saleor-storefront", + "saleor-storefront-app", ], ) .await? @@ -1663,7 +1670,7 @@ impl RpcHandler { return Ok(adopted); } - install_log("INSTALL START: saleor stack (postgres + valkey + api + worker + dashboard)") + install_log("INSTALL START: saleor stack (postgres + valkey + api + worker + dashboard + storefront)") .await; info!("Installing Saleor stack"); @@ -1692,6 +1699,8 @@ impl RpcHandler { "saleor", "saleor-api", "saleor-worker", + "saleor-storefront", + "saleor-storefront-app", "saleor-db", "saleor-cache", "saleor-jaeger", @@ -1717,6 +1726,7 @@ impl RpcHandler { "/var/lib/archipelago/saleor", "/var/lib/archipelago/saleor-db", "/var/lib/archipelago/saleor-cache", + "/var/lib/archipelago/saleor-storefront", ]) .output() .await; @@ -1725,6 +1735,7 @@ impl RpcHandler { "/var/lib/archipelago/saleor", "/var/lib/archipelago/saleor-db", "/var/lib/archipelago/saleor-cache", + "/var/lib/archipelago/saleor-storefront", ] { let _ = tokio::process::Command::new("sudo") .args(["chown", "-R", &format!("{}:{}", user, user), dir]) @@ -1744,10 +1755,12 @@ impl RpcHandler { let dashboard_origin = format!("http://{}:9010", host_ip); let dashboard_url = format!("{}/", dashboard_origin); let api_url = format!("http://{}:8000/graphql/", host_ip); + let internal_api_url = "http://api:8000/graphql/"; + let storefront_origin = format!("http://{}:9011", host_ip); let allowed_hosts = format!("localhost,127.0.0.1,api,saleor-api,{}", host_ip); let allowed_client_hosts = format!( - "{},http://localhost:9010,http://127.0.0.1:9010", - dashboard_origin + "{},{},http://localhost:9010,http://127.0.0.1:9010,http://localhost:9011,http://127.0.0.1:9011", + dashboard_origin, storefront_origin ); let database_url = format!("postgres://saleor:{}@db/saleor", db_pass); @@ -2067,6 +2080,90 @@ user.save() ]); run_required_stack_command("saleor", "create dashboard", &mut dashboard_cmd).await?; + let mut storefront_build_cmd = tokio::process::Command::new("podman"); + storefront_build_cmd.args([ + "build", + "--network", + "saleor-net", + "--pull=always", + "-t", + SALEOR_STOREFRONT_IMAGE, + "--build-arg", + &format!("NEXT_PUBLIC_SALEOR_API_URL={}", internal_api_url), + "--build-arg", + &format!("NEXT_PUBLIC_STOREFRONT_URL={}", storefront_origin), + "--build-arg", + "NEXT_PUBLIC_DEFAULT_CHANNEL=default-channel", + SALEOR_STOREFRONT_CONTEXT, + ]); + run_required_stack_command("saleor", "build storefront", &mut storefront_build_cmd) + .await?; + + let mut storefront_cmd = tokio::process::Command::new("podman"); + storefront_cmd.args([ + "run", + "-d", + "--name", + "saleor-storefront-app", + "--network", + "saleor-net", + "--network-alias", + "storefront-app", + "--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=2048", + "-e", + &format!("NEXT_PUBLIC_SALEOR_API_URL={}", internal_api_url), + "-e", + &format!("NEXT_PUBLIC_STOREFRONT_URL={}", storefront_origin), + "-e", + "NEXT_PUBLIC_DEFAULT_CHANNEL=default-channel", + SALEOR_STOREFRONT_IMAGE, + ]); + run_required_stack_command("saleor", "create storefront app", &mut storefront_cmd).await?; + + write_saleor_storefront_proxy_config().await?; + let mut storefront_proxy_cmd = tokio::process::Command::new("podman"); + storefront_proxy_cmd.args([ + "run", + "-d", + "--name", + "saleor-storefront", + "--network", + "saleor-net", + "--network-alias", + "storefront", + "--restart=unless-stopped", + "--cap-drop=ALL", + "--cap-add=CHOWN", + "--cap-add=DAC_OVERRIDE", + "--cap-add=FOWNER", + "--cap-add=NET_BIND_SERVICE", + "--cap-add=SETGID", + "--cap-add=SETUID", + "--security-opt=no-new-privileges:true", + "--memory=128m", + "--pids-limit=1024", + "-p", + "9011:80", + "-v", + "/var/lib/archipelago/saleor-storefront/nginx.conf:/etc/nginx/conf.d/default.conf:ro", + NETBIRD_PROXY_IMAGE, + ]); + run_required_stack_command( + "saleor", + "create storefront proxy", + &mut storefront_proxy_cmd, + ) + .await?; + wait_for_stack_containers( "saleor", &[ @@ -2077,6 +2174,8 @@ user.save() "saleor-api", "saleor-worker", "saleor", + "saleor-storefront", + "saleor-storefront-app", ], 120, ) @@ -2092,6 +2191,8 @@ user.save() "saleor-api", "saleor-worker", "saleor", + "saleor-storefront", + "saleor-storefront-app", ], 30, ) @@ -2109,7 +2210,7 @@ user.save() Ok(serde_json::json!({ "success": true, "package_id": "saleor", - "message": "Saleor stack installed (7 containers)", + "message": "Saleor stack installed (9 containers)", })) } } @@ -2130,6 +2231,48 @@ async fn read_or_generate_b64_secret(name: &str) -> String { secret } +async fn write_saleor_storefront_proxy_config() -> Result<()> { + tokio::fs::create_dir_all("/var/lib/archipelago/saleor-storefront") + .await + .context("Failed to create Saleor storefront config directory")?; + + let nginx_conf = r#"server { + listen 80; + server_name _; + + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + location ^~ /graphql/ { + proxy_pass http://api:8000/graphql/; + proxy_set_header Host api; + proxy_set_header Origin ""; + } + + location / { + proxy_pass http://storefront-app:3000; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Accept-Encoding ""; + sub_filter_once off; + sub_filter_types text/html application/javascript text/javascript; + sub_filter 'http://api:8000/graphql/' '$http_x_forwarded_proto://$host/graphql/'; + } +} +"#; + + tokio::fs::write( + "/var/lib/archipelago/saleor-storefront/nginx.conf", + nginx_conf, + ) + .await + .context("Failed to write Saleor storefront nginx.conf")?; + Ok(()) +} + async fn write_netbird_config_files(host_ip: &str) -> Result<()> { let public_origin = format!("http://{}:8087", host_ip); let server_origin = format!("http://{}:8086", host_ip); diff --git a/core/archipelago/src/container/docker_packages.rs b/core/archipelago/src/container/docker_packages.rs index a5a8accf..1c161c98 100644 --- a/core/archipelago/src/container/docker_packages.rs +++ b/core/archipelago/src/container/docker_packages.rs @@ -69,6 +69,8 @@ impl DockerPackageScanner { "saleor-cache", "saleor-jaeger", "saleor-mailpit", + "saleor-storefront", + "saleor-storefront-app", "buildx_buildkit_default", ]; @@ -519,7 +521,7 @@ fn get_app_metadata(app_id: &str) -> AppMetadata { }, "saleor" => AppMetadata { title: "Saleor".to_string(), - description: "Composable commerce platform with GraphQL API and dashboard. Log in with admin@example.com; the password is stored on the node at /var/lib/archipelago/secrets/saleor-admin-password".to_string(), + description: "Composable commerce platform with storefront, dashboard, and GraphQL API. The customer storefront opens on port 9011; admin dashboard is on 9010 with admin@example.com credentials stored on the node.".to_string(), icon: "/assets/img/app-icons/saleor.svg".to_string(), repo: "https://github.com/saleor/saleor".to_string(), tier: "", diff --git a/core/container/src/podman_client.rs b/core/container/src/podman_client.rs index b0012482..3111a4db 100644 --- a/core/container/src/podman_client.rs +++ b/core/container/src/podman_client.rs @@ -129,7 +129,7 @@ impl PodmanClient { "filebrowser" => "http://localhost:8083", "nginx-proxy-manager" => "http://localhost:8081", "portainer" => "http://localhost:9000", - "saleor" => "http://localhost:9010", + "saleor" => "http://localhost:9011", "uptime-kuma" => "http://localhost:3002", "fedimint" | "fedimintd" => "http://localhost:8175", "fedimint-gateway" => "http://localhost:8176", diff --git a/neode-ui/public/catalog.json b/neode-ui/public/catalog.json index 6430b0f2..68e55d82 100644 --- a/neode-ui/public/catalog.json +++ b/neode-ui/public/catalog.json @@ -68,7 +68,7 @@ "id": "saleor", "title": "Saleor", "version": "3.23", - "description": "Composable commerce platform with GraphQL API, dashboard, worker, mail testing, and tracing.", + "description": "Composable commerce platform with customer storefront, GraphQL API, dashboard, worker, mail testing, and tracing.", "icon": "/assets/img/app-icons/saleor.svg", "author": "Saleor", "category": "commerce", @@ -76,9 +76,9 @@ "dockerImage": "ghcr.io/saleor/saleor:3.23", "repoUrl": "https://github.com/saleor/saleor", "containerConfig": { - "ports": ["9010:80", "8000:8000", "8025:8025", "16686:16686"], + "ports": ["9011:80", "9010: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 9010, API on 8000, Mailpit on 8025, and Jaeger on 16686. Supporting containers include PostgreSQL, Valkey, Celery worker, and services required by Saleor." + "notes": "Installed as a Saleor stack: customer storefront on 9011, admin dashboard on 9010, API on 8000, Mailpit on 8025, and Jaeger on 16686. Supporting containers include PostgreSQL, Valkey, Celery worker, and services required by Saleor." } }, { diff --git a/neode-ui/src/stores/appLauncher.ts b/neode-ui/src/stores/appLauncher.ts index 66992031..f4540db5 100644 --- a/neode-ui/src/stores/appLauncher.ts +++ b/neode-ui/src/stores/appLauncher.ts @@ -103,6 +103,7 @@ const PORT_TO_APP_ID: Record = { '8888': 'searxng', '9000': 'portainer', '9010': 'saleor', + '9011': 'saleor', '8087': 'netbird', '8086': 'netbird', '9980': 'onlyoffice', diff --git a/neode-ui/src/views/appSession/__tests__/appSessionConfig.test.ts b/neode-ui/src/views/appSession/__tests__/appSessionConfig.test.ts index 47965416..411bfc26 100644 --- a/neode-ui/src/views/appSession/__tests__/appSessionConfig.test.ts +++ b/neode-ui/src/views/appSession/__tests__/appSessionConfig.test.ts @@ -17,6 +17,7 @@ describe('appSessionConfig', () => { expect(resolveAppUrl('mempool')).toBe('http://192.168.1.228:4080') expect(resolveAppUrl('indeedhub')).toBe('http://192.168.1.228:7778') + expect(resolveAppUrl('saleor')).toBe('http://192.168.1.228:9011') }) it('keeps NetBird on the unified dashboard proxy port', () => { diff --git a/neode-ui/src/views/appSession/appSessionConfig.ts b/neode-ui/src/views/appSession/appSessionConfig.ts index 3eb2b6df..dcf26861 100644 --- a/neode-ui/src/views/appSession/appSessionConfig.ts +++ b/neode-ui/src/views/appSession/appSessionConfig.ts @@ -14,7 +14,7 @@ export const APP_PORTS: Record = { 'archy-electrs-ui': 50002, 'mempool-electrs': 50002, 'btcpay-server': 23000, - 'saleor': 9010, + 'saleor': 9011, 'lnd': 18083, 'archy-lnd-ui': 18083, 'mempool': 4080, diff --git a/neode-ui/src/views/discover/curatedApps.ts b/neode-ui/src/views/discover/curatedApps.ts index e042099c..39e9f528 100644 --- a/neode-ui/src/views/discover/curatedApps.ts +++ b/neode-ui/src/views/discover/curatedApps.ts @@ -79,7 +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. Log in with admin@example.com; the password is stored on the node.', icon: '/assets/img/app-icons/saleor.svg', author: 'Saleor', dockerImage: 'ghcr.io/saleor/saleor:3.23', repoUrl: 'https://github.com/saleor/saleor' }, + { id: 'saleor', title: 'Saleor', version: '3.23', category: 'commerce', description: 'Composable commerce platform with customer storefront, GraphQL API, dashboard, worker, mail testing, and tracing. Storefront opens on port 9011; admin dashboard remains on 9010.', 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' }, diff --git a/neode-ui/src/views/marketplace/marketplaceData.ts b/neode-ui/src/views/marketplace/marketplaceData.ts index 554c6d3f..1204d8b5 100644 --- a/neode-ui/src/views/marketplace/marketplaceData.ts +++ b/neode-ui/src/views/marketplace/marketplaceData.ts @@ -163,7 +163,7 @@ export function getCuratedAppList(): MarketplaceApp[] { title: 'Saleor', version: '3.23', category: 'commerce', - description: 'Composable commerce platform with GraphQL API, dashboard, worker, mail testing, and tracing.', + description: 'Composable commerce platform with customer storefront, GraphQL API, dashboard, worker, mail testing, and tracing.', icon: '/assets/img/app-icons/saleor.svg', author: 'Saleor', dockerImage: 'ghcr.io/saleor/saleor:3.23', diff --git a/neode-ui/src/views/settings/AccountInfoSection.vue b/neode-ui/src/views/settings/AccountInfoSection.vue index da020473..ce6fed7e 100644 --- a/neode-ui/src/views/settings/AccountInfoSection.vue +++ b/neode-ui/src/views/settings/AccountInfoSection.vue @@ -180,6 +180,19 @@ init()
+ +
+
+ v1.7.79-alpha + May 20, 2026 +
+
+

Saleor now includes the official Saleor Storefront as the customer-facing shop on port 9011.

+

Launching Saleor opens the storefront, while the admin dashboard remains on port 9010 with generated admin@example.com credentials shown in Archipelago.

+

Public storefront domains also get same-origin /graphql/ proxying so phones and laptops can reach the local Saleor API through the domain instead of a private node address.

+

Saleor catalog, marketplace, scanner, and app-session metadata now describe the storefront/dashboard/API split explicitly.

+
+
diff --git a/scripts/sync-npm-public-hosts.sh b/scripts/sync-npm-public-hosts.sh index 25b4e632..c752f09e 100644 --- a/scripts/sync-npm-public-hosts.sh +++ b/scripts/sync-npm-public-hosts.sh @@ -62,7 +62,9 @@ for row in rows: forward_port = int(port) except (TypeError, ValueError): forward_port = None - is_saleor = forward_port == 9010 + is_saleor_dashboard = forward_port == 9010 + is_saleor_storefront = forward_port == 9011 + is_saleor = is_saleor_dashboard or is_saleor_storefront graphql_location = "" saleor_proxy_headers = "" if is_saleor: @@ -78,7 +80,15 @@ for row in rows: proxy_set_header Origin ""; } """ - saleor_proxy_headers = """ + if is_saleor_storefront: + saleor_proxy_headers = """ + proxy_set_header Accept-Encoding ""; + sub_filter_once off; + sub_filter_types text/html application/javascript text/javascript; + sub_filter 'http://api:8000/graphql/' 'https://$host/graphql/'; +""" + if is_saleor_dashboard: + saleor_proxy_headers = """ proxy_set_header Accept-Encoding ""; sub_filter_once off; sub_filter_types text/html;