File size: 7,710 Bytes
c293b5b
 
 
 
 
 
 
 
 
3d64dad
 
c293b5b
 
3d64dad
 
 
 
c293b5b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3d64dad
 
 
 
 
c293b5b
3d64dad
 
 
 
c293b5b
 
 
 
 
 
3d64dad
c293b5b
 
 
 
 
 
 
 
 
 
 
 
 
 
3d64dad
c293b5b
3d64dad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c293b5b
 
 
3a88149
c293b5b
3d64dad
 
 
 
3a88149
c293b5b
 
 
 
 
3d64dad
c293b5b
 
3d64dad
 
 
c293b5b
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
FROM oven/bun:1-slim

ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
  git ca-certificates curl sqlite3 rsync python3 \
  && rm -rf /var/lib/apt/lists/*

WORKDIR /app

ARG LUMIVERSE_REPO=https://github.com/CloudCompile/Lumiverse.git
ARG LUMIVERSE_REF=Leaderboard
RUN git clone --depth 1 --branch "${LUMIVERSE_REF}" "${LUMIVERSE_REPO}" .

# This patch only applies to forks/branches that still have the old
# unpatched host-only rewrite block. If the fork has already diverged
# (different code, or already fixed upstream), the patch is skipped
# instead of failing the whole build.
RUN cat > /tmp/patch-auth-rewrite.mjs <<'PATCH'
import { readFileSync, writeFileSync } from "fs";

const path = "src/app.ts";
const src = readFileSync(path, "utf8");

const before = `app.on(["POST", "GET"], "/api/auth/*", (c) => {
  const host = c.req.header("host");
  if (host) {
    const url = new URL(c.req.url);
    const rewritten = new URL(url.pathname + url.search, \`http://\${host}\`);
    return auth.handler(new Request(rewritten.toString(), c.req.raw));
  }
  return auth.handler(c.req.raw);
});`;

const after = `app.on(["POST", "GET"], "/api/auth/*", (c) => {
  const host = c.req.header("x-forwarded-host") || c.req.header("host");
  const proto = c.req.header("x-forwarded-proto") || "http";

  if (host) {
    const url = new URL(c.req.url);
    const rewritten = new URL(url.pathname + url.search, \`\${proto}://\${host}\`);
    return auth.handler(new Request(rewritten.toString(), c.req.raw));
  }
  return auth.handler(c.req.raw);
});`;

if (src.includes("x-forwarded-host") || src.includes("x-forwarded-proto")) {
  console.log("[patch] src/app.ts already forwards x-forwarded-host/proto — skipping patch");
  process.exit(0);
}

if (!src.includes(before)) {
  console.warn("[patch] Expected auth rewrite block not found in src/app.ts — skipping patch");
  console.warn("[patch] This fork/branch may have a different auth setup. If HTTPS auth redirects");
  console.warn("[patch] fail at runtime, this file will need a manual patch.");
  process.exit(0);
}

writeFileSync(path, src.replace(before, after), "utf8");
console.log("[patch] Patched src/app.ts for x-forwarded-proto/host");
PATCH

RUN bun /tmp/patch-auth-rewrite.mjs

RUN rm -f package-lock.json && bun install --production

WORKDIR /app/frontend
RUN rm -f package-lock.json && bun install && bun run build
RUN printf "self.addEventListener('install',e=>self.skipWaiting());self.addEventListener('activate',e=>e.waitUntil(self.clients.claim()));\n" > /app/frontend/dist/sw.js
RUN test -f /app/frontend/dist/index.html

WORKDIR /app
ENV NODE_ENV=production
ENV PORT=7860
ENV DATA_DIR=/app/data
ENV FRONTEND_DIR=/app/frontend/dist
ENV TRUST_ANY_ORIGIN=true
ENV OWNER_PASSWORD=admin123

# === DATA INTEGRITY & CORRUPTION RECOVERY ===
# Create a recovery script that runs before the app starts
RUN cat > /app/check-and-repair-data.sh <<'REPAIR'
#!/usr/bin/env sh
set -e

DATA_DIR="${DATA_DIR:-/app/data}"

echo "[integrity] Starting pre-startup data checks..."

# Ensure data directory structure exists
mkdir -p "$DATA_DIR"

# Critical check: top-level entries (users, uploads, extensions) must be
# directories. HF Spaces storage remounts have been observed to leave these
# as stray files instead. If any of them is a file, quarantine it before
# trying to mkdir, or every startup will fail with "File exists".
for entry in users uploads extensions; do
  entry_path="$DATA_DIR/$entry"
  if [ -e "$entry_path" ] && [ ! -d "$entry_path" ]; then
    echo "[integrity] $entry_path exists but is not a directory (corrupted remount)"
    echo "[integrity] Quarantining to ${entry_path}.corrupted-$(date +%s)"
    mv "$entry_path" "${entry_path}.corrupted-$(date +%s)"
  fi
done

USERS_DIR="$DATA_DIR/users"

# If users directory exists, scan for corrupted entries
if [ -d "$USERS_DIR" ]; then
  echo "[integrity] Checking user directories for corruption..."
  
  find "$USERS_DIR" -maxdepth 1 -type f 2>/dev/null | while read -r corrupt_file; do
    # There should be NO files directly in users/ — only directories
    echo "[integrity] Found stray file in users/: $corrupt_file"
    echo "[integrity] Moving to quarantine: ${corrupt_file}.corrupted"
    mv "$corrupt_file" "${corrupt_file}.corrupted"
  done
  
  # Check each user's subdirectory for storage file/directory collision
  find "$USERS_DIR" -maxdepth 2 -name "storage" -type f 2>/dev/null | while read -r storage_file; do
    user_dir=$(dirname "$storage_file")
    echo "[integrity] Found file where directory expected: $storage_file"
    echo "[integrity] This was caused by HF Spaces storage remount. Removing corrupt file..."
    rm -f "$storage_file"
    echo "[integrity] Directory will be auto-created on first use"
  done
fi

# Pre-create critical directories to prevent race conditions.
# These run with '|| true' as a last-resort safety net: the loop above should
# have already cleared any file/directory collisions, but if something
# unexpected still blocks mkdir, log it instead of crashing the container —
# the app's own provisioning code can then surface a clearer error.
mkdir -p "$DATA_DIR/users" || echo "[integrity] WARNING: could not create $DATA_DIR/users, check manually"
mkdir -p "$DATA_DIR/uploads" || echo "[integrity] WARNING: could not create $DATA_DIR/uploads, check manually"
mkdir -p "$DATA_DIR/extensions" || echo "[integrity] WARNING: could not create $DATA_DIR/extensions, check manually"

# Verify SQLite database integrity if it exists
DB_PATH="$DATA_DIR/lumiverse.db"
if [ -e "$DB_PATH" ]; then
  echo "[integrity] Inspecting $DB_PATH..."
  ls -la "$DB_PATH" 2>&1 || true
  file "$DB_PATH" 2>&1 || true

  if [ ! -f "$DB_PATH" ]; then
    echo "[integrity] $DB_PATH exists but is not a regular file (corrupted remount)"
    echo "[integrity] Quarantining to ${DB_PATH}.corrupted-$(date +%s)"
    mv "$DB_PATH" "${DB_PATH}.corrupted-$(date +%s)"
  else
    echo "[integrity] Running SQLite integrity check..."
    if ! sqlite3 "$DB_PATH" "PRAGMA integrity_check;" 2>&1 | grep -q "ok"; then
      echo "[integrity] WARNING: Database integrity check failed (disk I/O error or corruption)."
      echo "[integrity] NOT deleting or overwriting the database automatically — that risks data loss."
      echo "[integrity] Backing up as-is to ${DB_PATH}.bak-$(date +%s) for manual inspection."
      cp "$DB_PATH" "${DB_PATH}.bak-$(date +%s)" 2>&1 || echo "[integrity] Backup copy also failed — underlying mount may be unhealthy."
      echo "[integrity] The app will attempt to start anyway; if it cannot open the database,"
      echo "[integrity] this usually means the persistent storage mount itself needs to be"
      echo "[integrity] detached and reattached from the Space's Settings page."
    else
      echo "[integrity] Database integrity check passed"
    fi
  fi
else
  echo "[integrity] No existing database found at $DB_PATH (fresh install or not yet created)"
fi

echo "[integrity] Pre-startup checks complete"
REPAIR

RUN chmod +x /app/check-and-repair-data.sh

# Startup script that runs integrity check BEFORE the app
RUN cat > /app/start.sh <<'SH'
#!/usr/bin/env sh
set -eu
export DATA_DIR="${DATA_DIR:-/app/data}"

# Run data integrity checks first
/app/check-and-repair-data.sh

# Then start the app
exec bun run scripts/runner.ts
SH

RUN chmod +x /app/start.sh

USER root
# Create data directory with proper permissions
RUN mkdir -p /app/data && chown -R bun:bun /app/data

# Pre-create user directory to prevent first-run permission issues
RUN mkdir -p /app/data/users && chown -R bun:bun /app/data/users

EXPOSE 7860
VOLUME /app/data

USER bun
CMD ["/app/start.sh"]