diff --git a/CHANGELOG.md b/CHANGELOG.md index a4aca4d5..9486cbbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v1.7.77-alpha (2026-05-20) + +- Saleor first-use now exposes generated credentials through Archipelago instead of leaving users at an unexplained dashboard login: App Details shows copyable `admin@example.com` credentials, and My Apps/mobile icon launches show a pre-launch credentials modal. +- Saleor installs now create or repair the `admin@example.com` staff account idempotently after sample data loads, use the correct dashboard mount path, and re-check stack containers after startup so stopped containers are caught. +- NetBird embedded login now uses the upstream-compatible IdP signing-key behavior and sends ID tokens from the dashboard to the management API, fixing the post-signup `Unauthenticated` state while preserving the unified local proxy/logout routes. +- Transient unnamed Podman helper containers created during app install tasks are hidden from My Apps, so generated names like `eager_keldysh` no longer appear as user applications. +- Validation passed with catalog/release JSON checks, `npm run type-check`, and `cargo fmt --all --check --manifest-path core/Cargo.toml`; live checks on `100.114.134.21` confirmed Saleor dashboard/API availability, generated Saleor admin login, NetBird OAuth availability, and NetBird logout redirects. + ## v1.7.76-alpha (2026-05-20) - Saleor installs now use dashboard port `9010`, avoiding the existing Portainer `9000` binding on the test node while keeping API `8000`, Mailpit `8025`, and Jaeger `16686` unchanged. diff --git a/core/archipelago/src/api/rpc/dispatcher.rs b/core/archipelago/src/api/rpc/dispatcher.rs index fc69ab8e..367b584a 100644 --- a/core/archipelago/src/api/rpc/dispatcher.rs +++ b/core/archipelago/src/api/rpc/dispatcher.rs @@ -55,6 +55,7 @@ impl RpcHandler { "package.restart" => self.handle_package_restart(params).await, "package.uninstall" => self.clone().spawn_package_uninstall(params).await, "package.update" => self.clone().spawn_package_update(params).await, + "package.credentials" => self.handle_package_credentials(params).await, "app.filebrowser-token" => self.handle_filebrowser_token().await, // Bundled app management (for pre-loaded container images) diff --git a/core/archipelago/src/api/rpc/package/install.rs b/core/archipelago/src/api/rpc/package/install.rs index c54a9b98..18658a6f 100644 --- a/core/archipelago/src/api/rpc/package/install.rs +++ b/core/archipelago/src/api/rpc/package/install.rs @@ -1859,6 +1859,42 @@ autopilot.active=false\n", Ok(serde_json::json!({ "token": token })) } + + pub(in crate::api::rpc) async fn handle_package_credentials( + &self, + params: Option, + ) -> Result { + let app_id = params + .as_ref() + .and_then(|p| p.get("app_id")) + .and_then(|v| v.as_str()) + .unwrap_or_default(); + super::validation::validate_app_id(app_id)?; + + match app_id { + "saleor" => { + let password = + tokio::fs::read_to_string("/var/lib/archipelago/secrets/saleor-admin-password") + .await + .unwrap_or_default() + .trim() + .to_string(); + if password.is_empty() { + return Ok(serde_json::json!({ "credentials": [] })); + } + + Ok(serde_json::json!({ + "title": "Saleor admin login", + "description": "Saleor opens to its own dashboard login. Use this generated admin account to sign in.", + "credentials": [ + { "label": "Email", "value": "admin@example.com", "sensitive": false }, + { "label": "Password", "value": password, "sensitive": true } + ] + })) + } + _ => Ok(serde_json::json!({ "credentials": [] })), + } + } } async fn cleanup_stale_package_ports(package_id: &str) { diff --git a/core/archipelago/src/api/rpc/package/stacks.rs b/core/archipelago/src/api/rpc/package/stacks.rs index 3738341d..36d774de 100644 --- a/core/archipelago/src/api/rpc/package/stacks.rs +++ b/core/archipelago/src/api/rpc/package/stacks.rs @@ -1929,6 +1929,17 @@ impl RpcHandler { } } + let admin_script = format!( + r#"from django.contrib.auth import get_user_model +User = get_user_model() +user, _ = User.objects.get_or_create(email="admin@example.com", defaults={{"is_staff": True, "is_superuser": True}}) +user.is_staff = True +user.is_superuser = True +user.set_password({:?}) +user.save() +"#, + admin_pass + ); let mut admin_cmd = tokio::process::Command::new("podman"); admin_cmd.args([ "run", @@ -1940,17 +1951,14 @@ impl RpcHandler { ]); admin_cmd.args(&saleor_env); admin_cmd.args([ - "-e", - "DJANGO_SUPERUSER_EMAIL=admin@example.com", - "-e", - &format!("DJANGO_SUPERUSER_PASSWORD={}", admin_pass), SALEOR_API_IMAGE, "python3", "manage.py", - "createsuperuser", - "--noinput", + "shell", + "-c", + &admin_script, ]); - run_required_stack_command("saleor", "create admin user", &mut admin_cmd).await?; + run_required_stack_command("saleor", "create or update admin user", &mut admin_cmd).await?; install_log("INSTALL INFO: saleor admin email admin@example.com; password stored in /var/lib/archipelago/secrets/saleor-admin-password").await; let mut api_cmd = tokio::process::Command::new("podman"); @@ -2044,7 +2052,7 @@ impl RpcHandler { "-e", &format!("API_URL={}", api_url), "-e", - &format!("APP_MOUNT_URI={}", dashboard_url), + "APP_MOUNT_URI=/", SALEOR_DASHBOARD_IMAGE, ]); run_required_stack_command("saleor", "create dashboard", &mut dashboard_cmd).await?; @@ -2063,6 +2071,21 @@ impl RpcHandler { 120, ) .await?; + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + wait_for_stack_containers( + "saleor", + &[ + "saleor-db", + "saleor-cache", + "saleor-jaeger", + "saleor-mailpit", + "saleor-api", + "saleor-worker", + "saleor", + ], + 30, + ) + .await?; self.set_install_phase("saleor", InstallPhase::WaitingHealthy) .await; @@ -2117,7 +2140,7 @@ async fn write_netbird_config_files(host_ip: &str) -> Result<()> { auth: issuer: "{public_origin}/oauth2" localAuthDisabled: false - signKeyRefreshEnabled: true + signKeyRefreshEnabled: false dashboardRedirectURIs: - "{public_origin}/nb-auth" - "{public_origin}/nb-silent-auth" @@ -2145,7 +2168,7 @@ USE_AUTH0=false AUTH_SUPPORTED_SCOPES=openid profile email groups AUTH_REDIRECT_URI=/nb-auth AUTH_SILENT_REDIRECT_URI=/nb-silent-auth -NETBIRD_TOKEN_SOURCE=accessToken +NETBIRD_TOKEN_SOURCE=idToken NGINX_SSL_PORT=443 LETSENCRYPT_DOMAIN=none "# diff --git a/core/archipelago/src/container/docker_packages.rs b/core/archipelago/src/container/docker_packages.rs index c5a6f1dc..a5a8accf 100644 --- a/core/archipelago/src/container/docker_packages.rs +++ b/core/archipelago/src/container/docker_packages.rs @@ -125,6 +125,11 @@ impl DockerPackageScanner { continue; } + if is_transient_podman_helper(&app_id, &container.ports) { + debug!("Skipping transient Podman helper container: {}", app_id); + continue; + } + // Skip podman-compose infrastructure containers (e.g. indeedhub-build_api_1) // These have the project prefix pattern: {project}_{service}_{instance} if app_id.starts_with("indeedhub-build_") { @@ -299,6 +304,21 @@ fn get_app_tier(app_id: &str) -> &'static str { } } +fn is_transient_podman_helper(app_id: &str, ports: &[String]) -> bool { + if !ports.is_empty() { + return false; + } + + let Some((left, right)) = app_id.split_once('_') else { + return false; + }; + + !left.is_empty() + && !right.is_empty() + && left.chars().all(|c| c.is_ascii_lowercase()) + && right.chars().all(|c| c.is_ascii_lowercase()) +} + fn get_app_metadata(app_id: &str) -> AppMetadata { let mut meta = match app_id { "bitcoin-core" => AppMetadata { @@ -499,7 +519,7 @@ fn get_app_metadata(app_id: &str) -> AppMetadata { }, "saleor" => AppMetadata { title: "Saleor".to_string(), - description: "Composable commerce platform with GraphQL API and dashboard. Admin email: admin@example.com; password is stored on the node at /var/lib/archipelago/secrets/saleor-admin-password".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(), icon: "/assets/img/app-icons/saleor.svg".to_string(), repo: "https://github.com/saleor/saleor".to_string(), tier: "", diff --git a/neode-ui/src/types/api.ts b/neode-ui/src/types/api.ts index 3f0773ff..b6054f99 100644 --- a/neode-ui/src/types/api.ts +++ b/neode-ui/src/types/api.ts @@ -140,6 +140,18 @@ export interface InterfaceAddress { 'lan-address': string | null } +export interface AppCredential { + label: string + value: string + sensitive?: boolean +} + +export interface AppCredentialsResponse { + title?: string + description?: string + credentials: AppCredential[] +} + export const ServiceStatus = { Stopped: 'stopped', Starting: 'starting', @@ -275,4 +287,3 @@ export interface Update { sequence: number patch: PatchOperation[] } - diff --git a/neode-ui/src/views/AppDetails.vue b/neode-ui/src/views/AppDetails.vue index 05d43e8a..f1920428 100644 --- a/neode-ui/src/views/AppDetails.vue +++ b/neode-ui/src/views/AppDetails.vue @@ -56,6 +56,7 @@ :lan-url="lanUrl" :tor-url="torUrl" :show-tor-address="showTorAddress" + :credentials="credentials" /> @@ -137,6 +138,7 @@ import { useAppLauncherStore } from '../stores/appLauncher' import { useModalKeyboard } from '@/composables/useModalKeyboard' import { dummyApps } from '../utils/dummyApps' import { rpcClient } from '@/api/rpc-client' +import type { AppCredentialsResponse } from '@/types/api' import AppHeroSection from './appDetails/AppHeroSection.vue' import AppContentSection from './appDetails/AppContentSection.vue' import AppSidebar from './appDetails/AppSidebar.vue' @@ -214,6 +216,7 @@ const needsBitcoinSync = computed(() => BITCOIN_DEPENDENT_APPS.includes(packageK const bitcoinSyncPercent = ref(0) const bitcoinBlockHeight = ref(0) const bitcoinSynced = computed(() => bitcoinSyncPercent.value >= 99.9) +const credentials = ref(null) async function loadBitcoinSync() { if (!needsBitcoinSync.value) return @@ -230,8 +233,23 @@ async function loadBitcoinSync() { } } +async function loadCredentials() { + if (!appId.value) return + try { + const result = await rpcClient.call({ + method: 'package.credentials', + params: { app_id: packageKey.value }, + timeout: 5000, + }) + credentials.value = result.credentials?.length ? result : null + } catch { + credentials.value = null + } +} + onMounted(() => { loadBitcoinSync() + loadCredentials() }) const actionError = ref('') diff --git a/neode-ui/src/views/Apps.vue b/neode-ui/src/views/Apps.vue index 86d8e731..c2aae308 100644 --- a/neode-ui/src/views/Apps.vue +++ b/neode-ui/src/views/Apps.vue @@ -173,6 +173,37 @@ @confirm="onConfirmUninstall" /> + +
+
+
+
+

{{ credentialModal.title }}

+

{{ credentialModal.description }}

+
+ +
+
+
+
+ {{ cred.label }} + +
+

{{ cred.value }}

+
+
+
+ + +
+
+
+
+
{}) } -function launchApp(id: string) { +async function launchApp(id: string) { + const shown = await maybeShowCredentialsBeforeLaunch(id) + if (shown) return + launchAppNow(id) +} + +function launchAppNow(id: string) { const pkg = packages.value[id] const isMobile = typeof window !== 'undefined' && window.innerWidth < 768 const webOnlyUrl = WEB_ONLY_APP_URLS[id] @@ -456,6 +501,52 @@ function launchApp(id: string) { useAppLauncherStore().openSession(id) } +async function maybeShowCredentialsBeforeLaunch(id: string): Promise { + try { + const result = await rpcClient.call({ + method: 'package.credentials', + params: { app_id: id }, + timeout: 5000, + }) + if (!result.credentials?.length) return false + credentialModal.value = { + show: true, + appId: id, + title: result.title || `${packages.value[id]?.manifest.title || id} credentials`, + description: result.description || 'Use these credentials when the app asks you to sign in.', + credentials: result.credentials, + copied: '', + } + return true + } catch { + return false + } +} + +function closeCredentialModal() { + credentialModal.value.show = false +} + +function continueCredentialLaunch() { + const id = credentialModal.value.appId + closeCredentialModal() + if (id) launchAppNow(id) +} + +async function copyModalCredential(label: string, value: string) { + try { + await navigator.clipboard.writeText(value) + } catch { + const textarea = document.createElement('textarea') + textarea.value = value + document.body.appendChild(textarea) + textarea.select() + document.execCommand('copy') + document.body.removeChild(textarea) + } + credentialModal.value.copied = label +} + async function updateApp(id: string) { try { await serverStore.updatePackage(id) diff --git a/neode-ui/src/views/appDetails/AppSidebar.vue b/neode-ui/src/views/appDetails/AppSidebar.vue index 0904a2c1..ff9644bd 100644 --- a/neode-ui/src/views/appDetails/AppSidebar.vue +++ b/neode-ui/src/views/appDetails/AppSidebar.vue @@ -86,6 +86,22 @@
+
+

Credentials

+

{{ credentials.description }}

+
+
+
+ {{ cred.label }} + +
+

{{ cred.value }}

+
+
+
+

{{ t('appDetails.requirements') }}

@@ -151,9 +167,29 @@ diff --git a/neode-ui/src/views/apps/AppIconGrid.vue b/neode-ui/src/views/apps/AppIconGrid.vue index cc7e16a8..a3772f6d 100644 --- a/neode-ui/src/views/apps/AppIconGrid.vue +++ b/neode-ui/src/views/apps/AppIconGrid.vue @@ -70,6 +70,33 @@ @click="scrollToPage(i)" >
+ + +
+
+
+
+

{{ credentialModal.title }}

+

{{ credentialModal.description }}

+
+ +
+
+
+
+ {{ cred.label }} + +
+

{{ cred.value }}

+
+
+
+ + +
+
+
+
@@ -77,7 +104,8 @@ import { computed, ref } from 'vue' import { useServerStore } from '@/stores/server' import { useAppLauncherStore } from '@/stores/appLauncher' -import type { PackageDataEntry } from '@/types/api' +import type { AppCredential, AppCredentialsResponse, PackageDataEntry } from '@/types/api' +import { rpcClient } from '@/api/rpc-client' import { resolveAppUrl } from '@/views/appSession/appSessionConfig' import { canLaunch, handleImageError, isWebsitePackage, opensInTab, resolveAppIcon, resolveRuntimeLaunchUrl, WEB_ONLY_APP_URLS } from './appsConfig' import { getCuratedAppList } from '../discover/curatedApps' @@ -88,6 +116,14 @@ const serverStore = useServerStore() const appLauncher = useAppLauncherStore() const curatedMap = new Map(getCuratedAppList().map(a => [a.id, a])) +const credentialModal = ref({ + show: false, + appId: '', + title: '', + description: '', + credentials: [] as AppCredential[], + copied: '', +}) const props = defineProps<{ apps: [string, PackageDataEntry][] @@ -118,34 +154,79 @@ function getIcon(id: string, pkg: PackageDataEntry): string { return resolveAppIcon(id, pkg, curatedMap.get(id)?.icon) } -function handleTap(id: string, pkg: PackageDataEntry) { +async function handleTap(id: string, pkg: PackageDataEntry) { if (canLaunch(pkg)) { - const isMobile = typeof window !== 'undefined' && window.innerWidth < 768 - const webOnlyUrl = WEB_ONLY_APP_URLS[id] - if (webOnlyUrl) { - appLauncher.open({ url: webOnlyUrl, title: getTitle(id, pkg), openInNewTab: !isMobile }) - return - } - if (isWebsitePackage(id, pkg)) { - const url = resolveRuntimeLaunchUrl(pkg) - if (url) { - appLauncher.open({ url, title: getTitle(id, pkg), openInNewTab: !isMobile }) - return - } - } - if (!isMobile && opensInTab(id)) { - const appUrl = resolveRuntimeLaunchUrl(pkg) || resolveAppUrl(id) - if (appUrl) { - window.open(appUrl, '_blank', 'noopener,noreferrer') - return - } - } - appLauncher.openSession(id) + const shown = await maybeShowCredentialsBeforeLaunch(id, pkg) + if (shown) return + launchNow(id, pkg) } else { emit('goToApp', id) } } +function launchNow(id: string, pkg: PackageDataEntry) { + const isMobile = typeof window !== 'undefined' && window.innerWidth < 768 + const webOnlyUrl = WEB_ONLY_APP_URLS[id] + if (webOnlyUrl) { + appLauncher.open({ url: webOnlyUrl, title: getTitle(id, pkg), openInNewTab: !isMobile }) + return + } + if (isWebsitePackage(id, pkg)) { + const url = resolveRuntimeLaunchUrl(pkg) + if (url) { + appLauncher.open({ url, title: getTitle(id, pkg), openInNewTab: !isMobile }) + return + } + } + if (!isMobile && opensInTab(id)) { + const appUrl = resolveRuntimeLaunchUrl(pkg) || resolveAppUrl(id) + if (appUrl) { + window.open(appUrl, '_blank', 'noopener,noreferrer') + return + } + } + appLauncher.openSession(id) +} + +async function maybeShowCredentialsBeforeLaunch(id: string, pkg: PackageDataEntry): Promise { + try { + const result = await rpcClient.call({ method: 'package.credentials', params: { app_id: id }, timeout: 5000 }) + if (!result.credentials?.length) return false + credentialModal.value = { + show: true, + appId: id, + title: result.title || `${getTitle(id, pkg)} credentials`, + description: result.description || 'Use these credentials when the app asks you to sign in.', + credentials: result.credentials, + copied: '', + } + return true + } catch { + return false + } +} + +function closeCredentialModal() { credentialModal.value.show = false } + +function continueCredentialLaunch() { + const id = credentialModal.value.appId + const entry = props.apps.find(([appId]) => appId === id) + closeCredentialModal() + if (entry) launchNow(entry[0], entry[1]) +} + +async function copyModalCredential(label: string, value: string) { + try { await navigator.clipboard.writeText(value) } catch { + const textarea = document.createElement('textarea') + textarea.value = value + document.body.appendChild(textarea) + textarea.select() + document.execCommand('copy') + document.body.removeChild(textarea) + } + credentialModal.value.copied = label +} + function onScroll() { const el = scrollContainer.value if (!el) return diff --git a/neode-ui/src/views/discover/curatedApps.ts b/neode-ui/src/views/discover/curatedApps.ts index 2a5eb83d..e042099c 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.', 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 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: '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/settings/AccountInfoSection.vue b/neode-ui/src/views/settings/AccountInfoSection.vue index cd7c3353..913521c3 100644 --- a/neode-ui/src/views/settings/AccountInfoSection.vue +++ b/neode-ui/src/views/settings/AccountInfoSection.vue @@ -187,8 +187,10 @@ init() May 20, 2026
-

Saleor now installs on dashboard port 9010 instead of conflicting with Portainer on 9000, keeps its API on 8000, and starts cleanly in rootless Podman with the nginx capabilities it needs.

-

NetBird API and OAuth routes now proxy through the stable host-published server port, so login/logout routes survive a netbird-server restart without 502s from stale Podman DNS.

+

Saleor now installs on dashboard port 9010 instead of conflicting with Portainer on 9000, keeps its API on 8000, creates or repairs the admin@example.com staff account after sample data loads, and starts cleanly with the correct dashboard mount path.

+

Apps with generated first-use credentials now show them in Archipelago before launch and in App Details. Saleor exposes copyable admin email/password fields so the dashboard login screen is no longer a dead end.

+

NetBird API and OAuth routes now proxy through the stable host-published server port, and the embedded IdP keeps upstream-compatible signing-key refresh settings while the dashboard sends ID tokens to the API so signup no longer lands in an Unauthenticated dashboard state.

+

Transient unnamed Podman helper containers created during app installs are hidden from My Apps, so random generated names like eager_keldysh no longer appear as applications.

Mobile App Store categories are now visible as horizontal chips above the tab bar, Discover is reachable on mobile, category choices update the actual view, and apps that require a real tab open directly from the icon tap.

diff --git a/release-manifest.json b/release-manifest.json index 8a397f72..0e1034b4 100644 --- a/release-manifest.json +++ b/release-manifest.json @@ -4,7 +4,11 @@ "changelog": [ "Saleor installs now use dashboard port `9010`, avoiding the existing Portainer `9000` binding on the test node while keeping API `8000`, Mailpit `8025`, and Jaeger `16686` unchanged.", "Saleor's Valkey cache no longer bind-mounts `/var/lib/archipelago/saleor-cache`, and the dashboard container has the minimal rootless nginx capabilities it needs to chown cache files, bind port 80 inside the container, and drop workers to the nginx user.", + "Saleor installs now create or repair the `admin@example.com` staff/superuser account idempotently after sample data loads, publish the correct dashboard mount path, and document where the generated admin password is stored so first-use login no longer blocks users.", + "Apps with generated first-use credentials now expose those credentials through Archipelago: Saleor shows copyable admin email/password fields in the app details Access area and in a pre-launch modal before opening the Saleor dashboard.", "NetBird's browser proxy now sends API, OAuth, relay, WebSocket, and management traffic through the stable host-published server port at `169.254.1.2:8086`, avoiding stale rootless Podman DNS/IPs after `netbird-server` restarts.", + "NetBird's embedded IdP keeps signing-key refresh disabled like the upstream quickstart config and the dashboard now uses the ID token for API calls, preventing newly-created local users from landing in an Unauthenticated dashboard state after signup.", + "Transient unnamed Podman helper containers created during app install tasks are hidden from My Apps, so random generated names like `eager_keldysh` no longer appear as user applications.", "Mobile App Store category chips now stay visible above the tab bar, Discover is available on mobile, and category selection updates the page route/query so the selected category is actually shown.", "Apps that require a real browser tab now open directly from the app icon tap instead of first entering an in-shell app-session route, including BTCPay, Grafana, Home Assistant, Vaultwarden, Nextcloud, Portainer, OnlyOffice, Tailscale, Uptime Kuma, Gitea, and Nginx Proxy Manager.", "Validation passed with catalog JSON checks, `npm run type-check`, `cargo fmt --all --check --manifest-path core/Cargo.toml`, and `cargo check -p archipelago --manifest-path core/Cargo.toml`; live checks on `100.70.96.88` confirmed Saleor dashboard `9010`/API `8000` and NetBird API/OAuth routes survive `netbird-server` restart." diff --git a/releases/manifest.json b/releases/manifest.json index 8a397f72..0e1034b4 100644 --- a/releases/manifest.json +++ b/releases/manifest.json @@ -4,7 +4,11 @@ "changelog": [ "Saleor installs now use dashboard port `9010`, avoiding the existing Portainer `9000` binding on the test node while keeping API `8000`, Mailpit `8025`, and Jaeger `16686` unchanged.", "Saleor's Valkey cache no longer bind-mounts `/var/lib/archipelago/saleor-cache`, and the dashboard container has the minimal rootless nginx capabilities it needs to chown cache files, bind port 80 inside the container, and drop workers to the nginx user.", + "Saleor installs now create or repair the `admin@example.com` staff/superuser account idempotently after sample data loads, publish the correct dashboard mount path, and document where the generated admin password is stored so first-use login no longer blocks users.", + "Apps with generated first-use credentials now expose those credentials through Archipelago: Saleor shows copyable admin email/password fields in the app details Access area and in a pre-launch modal before opening the Saleor dashboard.", "NetBird's browser proxy now sends API, OAuth, relay, WebSocket, and management traffic through the stable host-published server port at `169.254.1.2:8086`, avoiding stale rootless Podman DNS/IPs after `netbird-server` restarts.", + "NetBird's embedded IdP keeps signing-key refresh disabled like the upstream quickstart config and the dashboard now uses the ID token for API calls, preventing newly-created local users from landing in an Unauthenticated dashboard state after signup.", + "Transient unnamed Podman helper containers created during app install tasks are hidden from My Apps, so random generated names like `eager_keldysh` no longer appear as user applications.", "Mobile App Store category chips now stay visible above the tab bar, Discover is available on mobile, and category selection updates the page route/query so the selected category is actually shown.", "Apps that require a real browser tab now open directly from the app icon tap instead of first entering an in-shell app-session route, including BTCPay, Grafana, Home Assistant, Vaultwarden, Nextcloud, Portainer, OnlyOffice, Tailscale, Uptime Kuma, Gitea, and Nginx Proxy Manager.", "Validation passed with catalog JSON checks, `npm run type-check`, `cargo fmt --all --check --manifest-path core/Cargo.toml`, and `cargo check -p archipelago --manifest-path core/Cargo.toml`; live checks on `100.70.96.88` confirmed Saleor dashboard `9010`/API `8000` and NetBird API/OAuth routes survive `netbird-server` restart."