feat: rootless podman, session hardening, boot stability, sidebar fix
Rootless podman migration (TASK-11): - Remove sudo from all podman calls in PodmanClient + 8 backend files - Remove sudo from all podman/docker calls in deploy script - Restore full systemd security hardening: NoNewPrivileges, RestrictAddressFamilies, MemoryDenyWriteExecute, RestrictRealtime, RestrictNamespaces, RestrictSUIDSGID, SystemCallFilter, ProtectSystem=strict - Enable loginctl linger for rootless container persistence - Remove Ollama from auto-deploy (marketplace-only) Session & auth hardening: - Increase MAX_CONCURRENT_SESSIONS 20→50 (prevents eviction storms) - Debounced 401 redirect in rpc-client.ts (prevents redirect storms) Boot stability: - optimize-debian.sh: adds chrony, swap, removes policy-rc.d - deploy script: pre-restart chrony + swap setup - ISO build: chrony package, swap file creation - BootScreen: no longer clears localStorage (prevents splash replay) - RootRedirect: sole owner of localStorage clearing on server ready UI fixes: - Sidebar opacity default changed from 0→visible (fixes missing sidebar after page-persistence login without entrance animation) - Console.log/error wrapped in import.meta.env.DEV guards - Remove unused route import from RootRedirect Beta tracking: - CLAUDE.md: beta freeze protocol added - MASTER_PLAN.md: TASK-11, TASK-17, phase structure - BETA-PROGRESS.md: initial tracking doc - Tagged v1.2.0-alpha.1 as pre-rootless baseline Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
934d120243
commit
870ff095d8
@@ -593,7 +593,7 @@ async function handleNostrRequest(event: MessageEvent) {
|
||||
if (!source) return
|
||||
const storedIdentity = getStoredIdentity()
|
||||
const identityId = storedIdentity?.id || null
|
||||
console.log(`[NIP-07] ${method} identityId=${identityId} storedPubkey=${storedIdentity?.nostr_pubkey?.slice(0, 12) || 'none'}`)
|
||||
if (import.meta.env.DEV) console.log(`[NIP-07] ${method} identityId=${identityId} storedPubkey=${storedIdentity?.nostr_pubkey?.slice(0, 12) || 'none'}`)
|
||||
|
||||
try {
|
||||
let result: unknown
|
||||
@@ -601,7 +601,7 @@ async function handleNostrRequest(event: MessageEvent) {
|
||||
// Use stored nostr_pubkey directly if available (avoids RPC call that may 401)
|
||||
if (storedIdentity?.nostr_pubkey) {
|
||||
result = storedIdentity.nostr_pubkey
|
||||
console.log('[NIP-07] getPublicKey from stored identity:', (result as string).slice(0, 12))
|
||||
if (import.meta.env.DEV) console.log('[NIP-07] getPublicKey from stored identity:', (result as string).slice(0, 12))
|
||||
} else if (identityId) {
|
||||
const res = await rpcClient.call<{ nostr_pubkey: string }>({ method: 'identity.get', params: { id: identityId } })
|
||||
result = res.nostr_pubkey
|
||||
@@ -610,13 +610,13 @@ async function handleNostrRequest(event: MessageEvent) {
|
||||
result = res.nostr_pubkey
|
||||
}
|
||||
} else if (method === 'signEvent') {
|
||||
console.log(`[NIP-07] signEvent kind=${params.event?.kind} using identity=${identityId || 'node-default'}`)
|
||||
if (import.meta.env.DEV) console.log(`[NIP-07] signEvent kind=${params.event?.kind} using identity=${identityId || 'node-default'}`)
|
||||
if (identityId) {
|
||||
result = await rpcClient.call<unknown>({ method: 'identity.nostr-sign', params: { id: identityId, event: params.event } })
|
||||
} else {
|
||||
result = await rpcClient.call<unknown>({ method: 'node.nostr-sign', params: { event: params.event } })
|
||||
}
|
||||
console.log('[NIP-07] signEvent OK')
|
||||
if (import.meta.env.DEV) console.log('[NIP-07] signEvent OK')
|
||||
} else if (method === 'getRelays') { result = {} }
|
||||
else if (method === 'nip04.encrypt') { result = (await rpcClient.call<{ ciphertext: string }>({ method: 'identity.nostr-encrypt-nip04', params: { id: identityId || undefined, pubkey: params.pubkey, plaintext: params.plaintext } })).ciphertext }
|
||||
else if (method === 'nip04.decrypt') { result = (await rpcClient.call<{ plaintext: string }>({ method: 'identity.nostr-decrypt-nip04', params: { id: identityId || undefined, pubkey: params.pubkey, ciphertext: params.ciphertext } })).plaintext }
|
||||
@@ -625,7 +625,7 @@ async function handleNostrRequest(event: MessageEvent) {
|
||||
else { throw new Error(`Unsupported NIP-07 method: ${method}`) }
|
||||
source.postMessage({ type: 'nostr-response', id, result }, '*')
|
||||
} catch (err) {
|
||||
console.error(`[NIP-07] ${method} FAILED:`, err instanceof Error ? err.message : err)
|
||||
if (import.meta.env.DEV) console.error(`[NIP-07] ${method} FAILED:`, err instanceof Error ? err.message : err)
|
||||
source.postMessage({ type: 'nostr-response', id, error: err instanceof Error ? err.message : 'Unknown error' }, '*')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -982,10 +982,11 @@ function dismissNotification(id: string) {
|
||||
|
||||
.sidebar-inner {
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Only hide sidebar content when doing the login entrance animation */
|
||||
.sidebar-animate .sidebar-inner {
|
||||
opacity: 0;
|
||||
animation: sidebar-inner-draw 0.7s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
|
||||
animation-delay: 6.1s;
|
||||
}
|
||||
|
||||
@@ -14,12 +14,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { isOnboardingComplete } from '@/composables/useOnboarding'
|
||||
import BootScreen from '@/components/BootScreen.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const showBootScreen = ref(false)
|
||||
|
||||
async function quickHealthCheck(): Promise<boolean> {
|
||||
@@ -66,20 +65,24 @@ async function proceedToApp() {
|
||||
}
|
||||
|
||||
function onServerReady() {
|
||||
// Clear flags so splash intro plays on reload
|
||||
if (import.meta.env.DEV) console.log('[RootRedirect] onServerReady — setting flag and reloading')
|
||||
localStorage.removeItem('neode_intro_seen')
|
||||
localStorage.removeItem('neode_onboarding_complete')
|
||||
// Reload with ?intro=1 so we know to skip boot and let App.vue handle splash
|
||||
window.location.href = '/?intro=1'
|
||||
sessionStorage.setItem('archipelago_from_boot', '1')
|
||||
window.location.href = '/'
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const devMode = import.meta.env.VITE_DEV_MODE
|
||||
|
||||
// Coming back from boot screen — do nothing, let App.vue's SplashScreen take over
|
||||
if (route.query.intro === '1') {
|
||||
// Clean the URL without navigating
|
||||
window.history.replaceState({}, '', '/')
|
||||
// Coming back from boot screen — let App.vue's SplashScreen take over
|
||||
if (sessionStorage.getItem('archipelago_from_boot') === '1') {
|
||||
return
|
||||
}
|
||||
|
||||
// Splash already completed this session — go to app
|
||||
if (sessionStorage.getItem('archipelago_from_splash') === '1') {
|
||||
proceedToApp()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -89,7 +92,7 @@ onMounted(async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// Boot dev mode — always show boot screen
|
||||
// Boot dev mode — always show boot screen (first load only)
|
||||
if (devMode === 'boot') {
|
||||
showBootScreen.value = true
|
||||
return
|
||||
|
||||
@@ -1009,7 +1009,7 @@ async function saveServerName() {
|
||||
try {
|
||||
await rpcClient.call({ method: 'server.set-name', params: { name } })
|
||||
} catch (e) {
|
||||
console.error('Failed to rename server:', e)
|
||||
if (import.meta.env.DEV) console.error('Failed to rename server:', e)
|
||||
}
|
||||
editingServerName.value = false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user