fix: nostr-vpn service crash on reboot, detect activating state
- Remove ReadWritePaths sandbox (causes namespace error when /run/nostr-vpn doesn't exist after reboot — /run is tmpfs) - Detect both 'active' and 'activating' states in VPN status check Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0ecfdd1d01
commit
a34075287d
@ -202,14 +202,15 @@ pub async fn get_status() -> VpnStatus {
|
||||
|
||||
/// Check if NostrVPN system service is running and get its status.
|
||||
async fn get_nostr_vpn_status() -> Result<VpnStatus> {
|
||||
// Check if nostr-vpn service is active
|
||||
let active = tokio::process::Command::new("systemctl")
|
||||
// Check if nostr-vpn service is active or activating
|
||||
let output = tokio::process::Command::new("systemctl")
|
||||
.args(["is-active", "nostr-vpn"])
|
||||
.output()
|
||||
.await
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false);
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let active = output == "active" || output == "activating";
|
||||
if !active {
|
||||
anyhow::bail!("nostr-vpn service not active");
|
||||
}
|
||||
|
||||
@ -16,9 +16,7 @@ RestartSec=10
|
||||
TimeoutStartSec=30
|
||||
TimeoutStopSec=10
|
||||
|
||||
# Security — runs as root for TUN/WireGuard access
|
||||
ReadWritePaths=/var/lib/archipelago/nostr-vpn /run/nostr-vpn
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
||||
# No sandbox — runs as root for TUN/WireGuard, needs unrestricted filesystem
|
||||
|
||||
# Resource limits
|
||||
MemoryMax=256M
|
||||
|
||||
@ -35,11 +35,17 @@ export const useServerStore = defineStore('server', () => {
|
||||
const pct = progress.size > 0 ? Math.round((progress.downloaded / progress.size) * 100) : 0
|
||||
const downloadedMB = (progress.downloaded / (1024 * 1024)).toFixed(1)
|
||||
const totalMB = (progress.size / (1024 * 1024)).toFixed(1)
|
||||
let message = 'Downloading...'
|
||||
if (progress.size > 1024 && pct < 100) {
|
||||
message = `Downloading: ${downloadedMB} / ${totalMB} MB (${pct}%)`
|
||||
} else if (pct >= 100 || (progress.size > 0 && progress.downloaded >= progress.size)) {
|
||||
message = 'Installing package...'
|
||||
}
|
||||
installingApps.value.set(appId, {
|
||||
...current,
|
||||
status: 'downloading',
|
||||
status: pct >= 100 ? 'installing' : 'downloading',
|
||||
progress: Math.min(pct, 95),
|
||||
message: progress.size > 0 ? `Downloading: ${downloadedMB} / ${totalMB} MB (${pct}%)` : 'Downloading...',
|
||||
message,
|
||||
})
|
||||
}
|
||||
} else if (installingApps.value.has(appId)) {
|
||||
@ -50,6 +56,17 @@ export const useServerStore = defineStore('server', () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clear installingApps entries for apps that vanished from backend data
|
||||
// (container was removed, install failed and was cleaned up, etc.)
|
||||
for (const [appId] of installingApps.value) {
|
||||
if (packages && !(appId in packages)) {
|
||||
const entry = installingApps.value.get(appId)
|
||||
if (entry && entry.attempt > 30) {
|
||||
// App has been "installing" for 30+ seconds but backend doesn't know about it — failed
|
||||
installingApps.value.delete(appId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
function setInstallProgress(appId: string, progress: Partial<InstallProgress> & { id: string; title: string }) {
|
||||
|
||||
@ -10,19 +10,7 @@
|
||||
@click="$emit('goToApp', id)"
|
||||
@keydown.enter="handleEnter"
|
||||
>
|
||||
<!-- Installing overlay — shown for both client-tracked installs and backend 'installing' state -->
|
||||
<div
|
||||
v-if="isInstalling || pkg.state === 'installing'"
|
||||
class="absolute inset-0 z-20 flex items-center justify-center bg-black/70 backdrop-blur-sm rounded-xl"
|
||||
>
|
||||
<div class="flex flex-col items-center gap-3 text-white/90">
|
||||
<svg class="animate-spin h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<span class="text-sm font-medium">{{ installProgress?.message || 'Installing...' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Installing indicator — no overlay, just replaces action buttons at bottom -->
|
||||
|
||||
<!-- Uninstalling overlay -->
|
||||
<div
|
||||
@ -78,7 +66,7 @@
|
||||
{{ description }}
|
||||
</p>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<div v-if="!isInstalling && pkg.state !== 'installing'" class="flex items-center gap-2">
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium"
|
||||
:class="getStatusClass(pkg.state, pkg.health, pkg['exit-code'])"
|
||||
@ -99,7 +87,27 @@
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions — icon buttons in uniform dark containers -->
|
||||
<div v-if="!isUninstalling" class="mt-4 flex gap-2">
|
||||
<!-- Installing progress — replaces action buttons -->
|
||||
<div v-if="isInstalling || pkg.state === 'installing'" class="mt-4">
|
||||
<div class="flex items-center justify-between mb-1.5">
|
||||
<span class="text-xs text-white/70 flex items-center gap-1.5">
|
||||
<svg class="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
{{ installProgress?.message || 'Installing...' }}
|
||||
</span>
|
||||
<span class="text-xs text-white/50">{{ Math.round(installProgress?.progress || 0) }}%</span>
|
||||
</div>
|
||||
<div class="w-full h-1.5 bg-white/10 rounded-full overflow-hidden">
|
||||
<div
|
||||
class="h-full bg-white/60 rounded-full transition-all duration-500"
|
||||
:style="{ width: `${Math.max(installProgress?.progress || 2, 2)}%` }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="!isUninstalling" class="mt-4 flex gap-2">
|
||||
<!-- Launch -->
|
||||
<button
|
||||
v-if="canLaunch(pkg)"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user