feat(apps): add saleor storefront

This commit is contained in:
archipelago
2026-05-20 23:02:57 -04:00
parent e61c757633
commit 34c4e87d14
13 changed files with 194 additions and 17 deletions

View File

@@ -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.

View File

@@ -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."
}
},
{

View File

@@ -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);

View File

@@ -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: "",

View File

@@ -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",

View File

@@ -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."
}
},
{

View File

@@ -103,6 +103,7 @@ const PORT_TO_APP_ID: Record<string, string> = {
'8888': 'searxng',
'9000': 'portainer',
'9010': 'saleor',
'9011': 'saleor',
'8087': 'netbird',
'8086': 'netbird',
'9980': 'onlyoffice',

View File

@@ -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', () => {

View File

@@ -14,7 +14,7 @@ export const APP_PORTS: Record<string, number> = {
'archy-electrs-ui': 50002,
'mempool-electrs': 50002,
'btcpay-server': 23000,
'saleor': 9010,
'saleor': 9011,
'lnd': 18083,
'archy-lnd-ui': 18083,
'mempool': 4080,

View File

@@ -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' },

View File

@@ -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',

View File

@@ -180,6 +180,19 @@ init()
</button>
</div>
<div class="overflow-y-auto flex-1 min-h-0 space-y-6 pr-1">
<!-- v1.7.79-alpha -->
<div>
<div class="flex items-center gap-2 mb-3">
<span class="text-xs font-mono px-2 py-0.5 rounded bg-orange-500/20 text-orange-300">v1.7.79-alpha</span>
<span class="text-xs text-white/40">May 20, 2026</span>
</div>
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
<p>Saleor now includes the official Saleor Storefront as the customer-facing shop on port 9011.</p>
<p>Launching Saleor opens the storefront, while the admin dashboard remains on port 9010 with generated admin@example.com credentials shown in Archipelago.</p>
<p>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.</p>
<p>Saleor catalog, marketplace, scanner, and app-session metadata now describe the storefront/dashboard/API split explicitly.</p>
</div>
</div>
<!-- v1.7.78-alpha -->
<div>
<div class="flex items-center gap-2 mb-3">

View File

@@ -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,6 +80,14 @@ for row in rows:
proxy_set_header Origin "";
}
"""
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;