fix: beautiful media lightbox, filebrowser noauth, deploy script
MediaLightbox: full glassmorphic redesign with dark backdrop, smooth transitions, proper video/audio/image support. FileBrowser: noauth config on persistent volume. Deploy script: fixed sed quoting. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,105 +1,107 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="show"
|
||||
class="lightbox-backdrop"
|
||||
@click.self="close"
|
||||
@keydown="onKeydown"
|
||||
tabindex="0"
|
||||
ref="backdropEl"
|
||||
>
|
||||
<!-- Close button -->
|
||||
<button class="lightbox-close" @click="close">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Counter -->
|
||||
<div v-if="mediaItems.length > 1" class="lightbox-counter">
|
||||
{{ currentIndex + 1 }} / {{ mediaItems.length }}
|
||||
</div>
|
||||
|
||||
<!-- Previous button -->
|
||||
<button
|
||||
v-if="mediaItems.length > 1"
|
||||
class="lightbox-nav lightbox-nav-prev"
|
||||
@click.stop="prev"
|
||||
<Transition name="lightbox-fade">
|
||||
<div
|
||||
v-if="show"
|
||||
class="lightbox-backdrop"
|
||||
@click.self="close"
|
||||
@keydown="onKeydown"
|
||||
tabindex="0"
|
||||
ref="backdropEl"
|
||||
>
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Next button -->
|
||||
<button
|
||||
v-if="mediaItems.length > 1"
|
||||
class="lightbox-nav lightbox-nav-next"
|
||||
@click.stop="next"
|
||||
>
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Media content -->
|
||||
<div class="lightbox-content" @click.stop>
|
||||
<!-- Loading -->
|
||||
<div v-if="loading" class="lightbox-loading">
|
||||
<div class="w-10 h-10 border-3 border-white/20 border-t-white/80 rounded-full animate-spin"></div>
|
||||
<!-- Top bar -->
|
||||
<div class="lightbox-topbar">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<span v-if="mediaItems.length > 1" class="text-sm text-white/50">
|
||||
{{ currentIndex + 1 }} / {{ mediaItems.length }}
|
||||
</span>
|
||||
<p class="text-sm text-white/80 truncate">{{ currentItem?.name }}</p>
|
||||
</div>
|
||||
<button class="lightbox-btn" @click="close">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Image -->
|
||||
<img
|
||||
v-else-if="currentItem && currentUrl && isImageFile(currentItem)"
|
||||
:src="currentUrl"
|
||||
:alt="currentItem.name"
|
||||
class="lightbox-media"
|
||||
@error="onMediaError"
|
||||
/>
|
||||
|
||||
<!-- Video -->
|
||||
<video
|
||||
v-else-if="currentItem && currentUrl && isVideoFile(currentItem)"
|
||||
:src="currentUrl"
|
||||
:key="currentUrl"
|
||||
class="lightbox-media"
|
||||
controls
|
||||
autoplay
|
||||
@error="onMediaError"
|
||||
/>
|
||||
|
||||
<!-- Audio -->
|
||||
<div
|
||||
v-else-if="currentItem && currentUrl && isAudioFile(currentItem)"
|
||||
class="flex flex-col items-center justify-center gap-6 p-8"
|
||||
<!-- Navigation arrows -->
|
||||
<button
|
||||
v-if="mediaItems.length > 1"
|
||||
class="lightbox-nav lightbox-nav-prev"
|
||||
@click.stop="prev"
|
||||
>
|
||||
<div class="w-32 h-32 rounded-2xl bg-gradient-to-br from-orange-500/20 to-orange-600/10 flex items-center justify-center">
|
||||
<svg class="w-16 h-16 text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3" />
|
||||
</svg>
|
||||
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
v-if="mediaItems.length > 1"
|
||||
class="lightbox-nav lightbox-nav-next"
|
||||
@click.stop="next"
|
||||
>
|
||||
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Media content -->
|
||||
<div class="lightbox-content" @click.stop>
|
||||
<!-- Loading -->
|
||||
<div v-if="loading" class="flex items-center justify-center h-full">
|
||||
<div class="w-12 h-12 border-3 border-white/10 border-t-white/70 rounded-full animate-spin"></div>
|
||||
</div>
|
||||
<audio
|
||||
|
||||
<!-- Image -->
|
||||
<img
|
||||
v-else-if="currentItem && currentUrl && isImageFile(currentItem)"
|
||||
:src="currentUrl"
|
||||
:key="currentUrl"
|
||||
controls
|
||||
autoplay
|
||||
class="w-full max-w-md"
|
||||
:alt="currentItem.name"
|
||||
class="lightbox-media-img"
|
||||
@error="onMediaError"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<div v-else-if="mediaError" class="lightbox-error">
|
||||
<p class="text-white/60">Failed to load media</p>
|
||||
<!-- Video -->
|
||||
<video
|
||||
v-else-if="currentItem && currentUrl && isVideoFile(currentItem)"
|
||||
:src="currentUrl"
|
||||
:key="currentUrl"
|
||||
class="lightbox-media-video"
|
||||
controls
|
||||
autoplay
|
||||
@error="onMediaError"
|
||||
/>
|
||||
|
||||
<!-- Audio -->
|
||||
<div
|
||||
v-else-if="currentItem && currentUrl && isAudioFile(currentItem)"
|
||||
class="lightbox-audio-container"
|
||||
>
|
||||
<div class="lightbox-audio-artwork">
|
||||
<svg class="w-20 h-20 text-orange-400/80" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="text-white/70 text-sm mt-4 truncate max-w-xs">{{ currentItem.name }}</p>
|
||||
<audio
|
||||
:src="currentUrl"
|
||||
:key="currentUrl"
|
||||
controls
|
||||
autoplay
|
||||
class="lightbox-audio-player"
|
||||
@error="onMediaError"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<div v-else-if="mediaError" class="flex flex-col items-center justify-center gap-3">
|
||||
<svg class="w-12 h-12 text-white/20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4.5c-.77-.833-2.694-.833-3.464 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
||||
</svg>
|
||||
<p class="text-white/40 text-sm">Failed to load media</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filename -->
|
||||
<div class="lightbox-filename">
|
||||
<p class="text-sm text-white/70 truncate max-w-md">{{ currentItem?.name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
@@ -125,7 +127,6 @@ const mediaError = ref(false)
|
||||
const currentUrl = ref<string | null>(null)
|
||||
const backdropEl = ref<HTMLElement | null>(null)
|
||||
|
||||
// Cache blob URLs to avoid re-fetching
|
||||
const urlCache = new Map<string, string>()
|
||||
|
||||
const mediaItems = computed(() =>
|
||||
@@ -212,19 +213,11 @@ function onMediaError() {
|
||||
}
|
||||
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
close()
|
||||
} else if (e.key === 'ArrowLeft') {
|
||||
e.preventDefault()
|
||||
prev()
|
||||
} else if (e.key === 'ArrowRight') {
|
||||
e.preventDefault()
|
||||
next()
|
||||
}
|
||||
if (e.key === 'Escape') { e.preventDefault(); close() }
|
||||
else if (e.key === 'ArrowLeft') { e.preventDefault(); prev() }
|
||||
else if (e.key === 'ArrowRight') { e.preventDefault(); next() }
|
||||
}
|
||||
|
||||
// Load media when current item changes
|
||||
watch(currentItem, (item) => {
|
||||
if (item) {
|
||||
loadMedia(item)
|
||||
@@ -232,7 +225,6 @@ watch(currentItem, (item) => {
|
||||
}
|
||||
})
|
||||
|
||||
// Initialize when shown
|
||||
watch(() => props.show, async (visible) => {
|
||||
if (visible) {
|
||||
currentIndex.value = props.startIndex
|
||||
@@ -246,7 +238,6 @@ watch(() => props.show, async (visible) => {
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// Clean up blob URLs on unmount
|
||||
onUnmounted(() => {
|
||||
for (const url of urlCache.values()) {
|
||||
URL.revokeObjectURL(url)
|
||||
@@ -254,3 +245,151 @@ onUnmounted(() => {
|
||||
urlCache.clear()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.lightbox-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 60;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.92);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.lightbox-topbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem 1.5rem;
|
||||
background: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%, transparent 100%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.lightbox-btn {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.lightbox-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.lightbox-nav {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
}
|
||||
.lightbox-nav:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: white;
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
.lightbox-nav-prev { left: 1rem; }
|
||||
.lightbox-nav-next { right: 1rem; }
|
||||
|
||||
.lightbox-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 4rem 5rem;
|
||||
}
|
||||
|
||||
.lightbox-media-img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.lightbox-media-video {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.5);
|
||||
background: black;
|
||||
}
|
||||
|
||||
.lightbox-audio-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.lightbox-audio-artwork {
|
||||
width: 12rem;
|
||||
height: 12rem;
|
||||
border-radius: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, rgba(251, 146, 60, 0.12) 0%, rgba(251, 146, 60, 0.04) 100%);
|
||||
border: 1px solid rgba(251, 146, 60, 0.15);
|
||||
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.lightbox-audio-player {
|
||||
width: 100%;
|
||||
max-width: 24rem;
|
||||
margin-top: 1rem;
|
||||
border-radius: 2rem;
|
||||
filter: invert(1) hue-rotate(180deg) brightness(0.85) contrast(0.9);
|
||||
}
|
||||
|
||||
/* Transitions */
|
||||
.lightbox-fade-enter-active,
|
||||
.lightbox-fade-leave-active {
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
.lightbox-fade-enter-from,
|
||||
.lightbox-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 768px) {
|
||||
.lightbox-content { padding: 3.5rem 1rem; }
|
||||
.lightbox-nav { width: 2.5rem; height: 2.5rem; }
|
||||
.lightbox-nav-prev { left: 0.5rem; }
|
||||
.lightbox-nav-next { right: 0.5rem; }
|
||||
.lightbox-audio-artwork { width: 8rem; height: 8rem; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -955,7 +955,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint-gateway; th
|
||||
if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q '^lnd$' && [ -f "$LND_CERT" ] && [ -f "$LND_MACAROON" ]; then
|
||||
log " LND detected — using lnd mode"
|
||||
$DOCKER run -d --name fedimint-gateway --restart unless-stopped \
|
||||
--health-cmd="curl -sf http://localhost:8175/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
||||
--health-cmd="curl -sf http://localhost:8176/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
||||
--memory=$(mem_limit fedimint-gateway) --network archy-net --network-alias fedimint-gateway \
|
||||
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
|
||||
--security-opt no-new-privileges:true \
|
||||
@@ -972,7 +972,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint-gateway; th
|
||||
else
|
||||
log " No LND found — using ldk (built-in Lightning)"
|
||||
$DOCKER run -d --name fedimint-gateway --restart unless-stopped \
|
||||
--health-cmd="curl -sf http://localhost:8175/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
||||
--health-cmd="curl -sf http://localhost:8176/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
||||
--memory=$(mem_limit fedimint-gateway) --network archy-net --network-alias fedimint-gateway \
|
||||
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
|
||||
--security-opt no-new-privileges:true \
|
||||
@@ -1137,20 +1137,28 @@ track_container "searxng"
|
||||
# OnlyOffice removed — incompatible with rootless Podman (internal postgres/rabbitmq)
|
||||
# CryptPad is the replacement (single Node.js process, e2e encrypted)
|
||||
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q filebrowser; then
|
||||
log "Creating File Browser..."
|
||||
log "Creating File Browser (noauth — behind Archipelago login)..."
|
||||
mkdir -p /var/lib/archipelago/filebrowser /var/lib/archipelago/filebrowser-data
|
||||
# Pre-create default directories so FileBrowser doesn't 404 on first load
|
||||
mkdir -p /var/lib/archipelago/filebrowser/{Documents,Photos,Music,Downloads,Builds}
|
||||
# Config with noauth + database on persistent volume (survives container recreation)
|
||||
cat > /var/lib/archipelago/filebrowser-data/.filebrowser.json << 'FBEOF'
|
||||
{"port":80,"baseURL":"","address":"0.0.0.0","database":"/data/filebrowser.db","root":"/srv","log":"stdout"}
|
||||
FBEOF
|
||||
$DOCKER run -d --name filebrowser --restart unless-stopped \
|
||||
--health-cmd="curl -sf http://localhost:80/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
||||
--health-cmd="wget -q --spider http://localhost:80/health || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
||||
--memory=$(mem_limit filebrowser) \
|
||||
--cap-drop ALL --security-opt no-new-privileges:true \
|
||||
--read-only --tmpfs=/tmp:rw,noexec,nosuid,size=256m --tmpfs=/run:rw,noexec,nosuid,size=64m \
|
||||
--tmpfs=/tmp:rw,noexec,nosuid,size=256m --tmpfs=/run:rw,noexec,nosuid,size=64m \
|
||||
-p 8083:80 \
|
||||
-v /var/lib/archipelago/filebrowser:/srv \
|
||||
-v /var/lib/archipelago/filebrowser-data:/data \
|
||||
"$FILEBROWSER_IMAGE" \
|
||||
--database=/data/database.db --root=/srv --address=0.0.0.0 --port=80 2>>"$LOG" || true
|
||||
--config /data/.filebrowser.json 2>>"$LOG" || true
|
||||
# Set noauth after first start (initializes database on volume)
|
||||
sleep 3
|
||||
$DOCKER exec filebrowser /filebrowser config set --auth.method=noauth --database /data/filebrowser.db 2>>"$LOG" || true
|
||||
$DOCKER exec filebrowser /filebrowser users add admin admin --perm.admin --database /data/filebrowser.db 2>>"$LOG" || true
|
||||
$DOCKER restart filebrowser 2>>"$LOG" || true
|
||||
fi
|
||||
track_container "filebrowser"
|
||||
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q nginx-proxy-manager; then
|
||||
@@ -1236,33 +1244,38 @@ fi
|
||||
|
||||
# 8b. Indeehub (pull from registry, or use local build)
|
||||
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q indeedhub; then
|
||||
INDEEDHUB_IMAGE=""
|
||||
# Try local image first (pre-built or loaded from ISO)
|
||||
if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q 'localhost/indeedhub'; then
|
||||
INDEEDHUB_IMAGE="localhost/indeedhub:local"
|
||||
# Try registry image
|
||||
elif $DOCKER pull git.tx1138.com/lfg2025/indeedhub:local 2>>"$LOG"; then
|
||||
INDEEDHUB_IMAGE="git.tx1138.com/lfg2025/indeedhub:local"
|
||||
# Use image-versions.sh variable if sourced, otherwise detect
|
||||
if [ -z "${INDEEDHUB_IMAGE:-}" ]; then
|
||||
INDEEDHUB_IMAGE=""
|
||||
# Try local image first (pre-built or loaded from ISO)
|
||||
if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q 'localhost/indeedhub'; then
|
||||
INDEEDHUB_IMAGE="localhost/indeedhub:local"
|
||||
# Try pinned registry image
|
||||
elif $DOCKER pull "$ARCHY_REGISTRY/indeedhub:1.0.0" --tls-verify=false 2>>"$LOG"; then
|
||||
INDEEDHUB_IMAGE="$ARCHY_REGISTRY/indeedhub:1.0.0"
|
||||
fi
|
||||
fi
|
||||
if [ -n "$INDEEDHUB_IMAGE" ]; then
|
||||
log "Creating Indeehub from $INDEEDHUB_IMAGE..."
|
||||
$DOCKER run -d --name indeedhub --restart unless-stopped \
|
||||
--health-cmd="curl -sf http://localhost:80/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
||||
--network archy-net --network-alias indeedhub \
|
||||
--health-cmd="curl -sf http://localhost:7777/health || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
|
||||
--memory=$(mem_limit indeedhub) \
|
||||
--cap-drop ALL --security-opt no-new-privileges:true \
|
||||
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m --tmpfs /app/.next/cache:rw,noexec,nosuid,size=128m \
|
||||
-p 8190:3000 \
|
||||
-e NODE_ENV=production -e NEXT_TELEMETRY_DISABLED=1 \
|
||||
--tmpfs /tmp:rw,noexec,nosuid,size=64m \
|
||||
-p 7778:7777 \
|
||||
"$INDEEDHUB_IMAGE" 2>>"$LOG" || true
|
||||
# Fix IndeedHub for iframe: remove X-Frame-Options so it loads in Archipelago panel
|
||||
sleep 2
|
||||
if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q "^indeedhub$"; then
|
||||
$DOCKER exec indeedhub sed -i "/X-Frame-Options/d" /etc/nginx/conf.d/default.conf 2>/dev/null || true
|
||||
# Fix Host header for NIP-98 auth — $host strips port, $http_host preserves it
|
||||
$DOCKER exec indeedhub sed -i 's|proxy_set_header Host $host;|proxy_set_header Host $http_host;|g' /etc/nginx/conf.d/default.conf 2>/dev/null || true
|
||||
if [ -f /opt/archipelago/web-ui/nostr-provider.js ]; then
|
||||
$DOCKER cp /opt/archipelago/web-ui/nostr-provider.js indeedhub:/usr/share/nginx/html/nostr-provider.js 2>/dev/null || true
|
||||
fi
|
||||
$DOCKER exec indeedhub nginx -s reload 2>/dev/null || true
|
||||
log "Applied IndeedHub iframe fix (removed X-Frame-Options)"
|
||||
log "Applied IndeedHub iframe fix (X-Frame-Options, Host header, nostr-provider)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user