chore: deploy to Cloudflare Pages and update branding

- Add Cloudflare Pages deployment for landing page (snapotter.com) and
  docs (docs.snapotter.com)
- Create deploy-landing.yml and update deploy-docs.yml workflows
- Update CI to ignore apps/landing/** paths
- Fix logo transparency (remove white background) across all apps
- Recreate social-preview.png with SnapOtter branding
- Update all docs URLs from GitHub Pages to docs.snapotter.com
- Update VitePress config: light theme default, fix llms.txt paths
- Add .vitepress/cache/ and .env.* to gitignore
This commit is contained in:
SnapOtter
2026-04-24 18:06:29 +08:00
parent fbaa603d03
commit 0309e0f680
174 changed files with 970 additions and 926 deletions
+8 -8
View File
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
# ============================================
# ashim - Unified Production Dockerfile
# SnapOtter - Unified Production Dockerfile
# Single image: GPU auto-detected on amd64, CPU on arm64
# ============================================
@@ -38,7 +38,7 @@ COPY apps/web/public ./apps/web/public
# Build only the web frontend (API runs from TS source via tsx)
RUN --mount=type=cache,id=turbo-cache,target=/app/.turbo \
pnpm --filter @ashim/web build
pnpm --filter @snapotter/web build
# ============================================
# Stage 2: Build caire (content-aware resize)
@@ -223,7 +223,7 @@ RUN mkdir -p /data /data/files /data/ai/models /data/ai/pip-cache /tmp/workspace
ENV PORT=1349 \
NODE_ENV=production \
STORAGE_MODE=local \
DB_PATH=/data/ashim.db \
DB_PATH=/data/snapotter.db \
WORKSPACE_PATH=/tmp/workspace \
FILES_STORAGE_PATH=/data/files \
PYTHON_VENV_PATH=/data/ai/venv \
@@ -232,7 +232,7 @@ ENV PORT=1349 \
U2NET_HOME=/data/ai/models/rembg \
DEFAULT_THEME=light \
DEFAULT_LOCALE=en \
APP_NAME="ashim" \
APP_NAME="snapotter" \
FILE_MAX_AGE_HOURS=72 \
CLEANUP_INTERVAL_MINUTES=60 \
MAX_UPLOAD_SIZE_MB=0 \
@@ -264,10 +264,10 @@ ENV PYTHONWARNINGS=default \
PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK=True
# Create non-root user for runtime
RUN groupadd -r ashim && useradd -r -g ashim -d /app -s /sbin/nologin ashim
RUN chown -R ashim:ashim /app /data /tmp/workspace /opt/venv
RUN groupadd -r snapotter && useradd -r -g snapotter -d /app -s /sbin/nologin snapotter
RUN chown -R snapotter:snapotter /app /data /tmp/workspace /opt/venv
# Entrypoint fixes volume permissions then drops to ashim via gosu
# Entrypoint fixes volume permissions then drops to snapotter via gosu
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
@@ -278,4 +278,4 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
# tini as PID 1 for zombie reaping + signal forwarding
ENTRYPOINT ["tini", "--", "entrypoint.sh"]
CMD ["pnpm", "--filter", "@ashim/api", "run", "start"]
CMD ["pnpm", "--filter", "@snapotter/api", "run", "start"]
+2 -2
View File
@@ -1,5 +1,5 @@
# ============================================
# ashim - Test Dockerfile
# SnapOtter - Test Dockerfile
# Runs the full test suite (unit + integration)
# ============================================
@@ -40,7 +40,7 @@ ENV NODE_ENV=test \
AUTH_ENABLED=true \
DEFAULT_USERNAME=admin \
DEFAULT_PASSWORD=admin \
DB_PATH=/tmp/test-ashim.db \
DB_PATH=/tmp/test-snapotter.db \
WORKSPACE_PATH=/tmp/test-workspace \
MAX_MEGAPIXELS=100 \
MAX_UPLOAD_SIZE_MB=100 \
+9 -9
View File
@@ -1,22 +1,22 @@
name: ashim
name: SnapOtter
# NVIDIA GPU deployment — requires nvidia-container-toolkit.
# Install: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
# Usage: docker compose -f docker-compose-gpu.yml up -d
# Verify: docker logs ashim 2>&1 | grep GPU
# Verify: docker logs SnapOtter 2>&1 | grep GPU
services:
ashim:
SnapOtter:
build:
context: ..
dockerfile: docker/Dockerfile
image: ashim:latest
container_name: ashim
image: snapotter:latest
container_name: SnapOtter
ports:
- "1349:1349"
volumes:
- ashim-data:/data # Database, AI models, user files
- ashim-workspace:/tmp/workspace # Temp processing (auto-cleaned)
- SnapOtter-data:/data # Database, AI models, user files
- SnapOtter-workspace:/tmp/workspace # Temp processing (auto-cleaned)
environment:
- AUTH_ENABLED=true
- DEFAULT_USERNAME=admin
@@ -55,5 +55,5 @@ services:
max-file: "5"
volumes:
ashim-data:
ashim-workspace:
SnapOtter-data:
SnapOtter-workspace:
+4 -4
View File
@@ -8,14 +8,14 @@ services:
build:
context: ..
dockerfile: docker/Dockerfile.test
container_name: ashim-test-unit
container_name: SnapOtter-test-unit
command: ["pnpm", "test:ci"]
environment:
- NODE_ENV=test
- AUTH_ENABLED=true
- DEFAULT_USERNAME=admin
- DEFAULT_PASSWORD=admin
- DB_PATH=/tmp/test-ashim.db
- DB_PATH=/tmp/test-snapotter.db
- WORKSPACE_PATH=/tmp/test-workspace
- MAX_MEGAPIXELS=100
- RATE_LIMIT_PER_MIN=1000
@@ -28,14 +28,14 @@ services:
build:
context: ..
dockerfile: docker/Dockerfile.test
container_name: ashim-test-e2e
container_name: SnapOtter-test-e2e
command: ["sh", "-c", "npx playwright install --with-deps chromium && pnpm test:e2e"]
environment:
- NODE_ENV=test
- AUTH_ENABLED=true
- DEFAULT_USERNAME=admin
- DEFAULT_PASSWORD=admin
- DB_PATH=/tmp/test-ashim.db
- DB_PATH=/tmp/test-snapotter.db
- WORKSPACE_PATH=/tmp/test-workspace
- MAX_MEGAPIXELS=100
- RATE_LIMIT_PER_MIN=1000
+8 -8
View File
@@ -1,21 +1,21 @@
name: ashim
name: SnapOtter
# CPU deployment — no GPU required.
# Usage: docker compose up -d
# GPU: docker compose -f docker-compose-gpu.yml up -d
services:
ashim:
SnapOtter:
build:
context: ..
dockerfile: docker/Dockerfile
image: ashim:latest
container_name: ashim
image: snapotter:latest
container_name: SnapOtter
ports:
- "1349:1349"
volumes:
- ashim-data:/data # Database, AI models, user files
- ashim-workspace:/tmp/workspace # Temp processing (auto-cleaned)
- SnapOtter-data:/data # Database, AI models, user files
- SnapOtter-workspace:/tmp/workspace # Temp processing (auto-cleaned)
environment:
- AUTH_ENABLED=true
- DEFAULT_USERNAME=admin
@@ -47,5 +47,5 @@ services:
max-file: "5"
volumes:
ashim-data:
ashim-workspace:
SnapOtter-data:
SnapOtter-workspace:
+1 -1
View File
@@ -11,7 +11,7 @@ import urllib.request
# Some servers (e.g. Berkeley) block the default Python-urllib User-Agent.
_opener = urllib.request.build_opener()
_opener.addheaders = [("User-Agent", "ashim/1.0")]
_opener.addheaders = [("User-Agent", "snapotter/1.0")]
urllib.request.install_opener(_opener)
+17 -17
View File
@@ -25,51 +25,51 @@ if [ ! -d "$AI_VENV" ] && [ -d "/opt/venv" ]; then
echo "AI venv ready at $AI_VENV"
fi
# Fix ownership of mounted volumes so the non-root ashim user can write.
# This runs as root, fixes permissions, then drops to ashim via gosu.
# Fix ownership of mounted volumes so the non-root snapotter user can write.
# This runs as root, fixes permissions, then drops to snapotter via gosu.
if [ "$(id -u)" = "0" ]; then
# PUID/PGID support: remap the ashim user/group to match host UID/GID.
# PUID/PGID support: remap the snapotter user/group to match host UID/GID.
# This prevents permission conflicts when using bind mounts.
PUID="${PUID:-$(id -u ashim)}"
PGID="${PGID:-$(id -g ashim)}"
PUID="${PUID:-$(id -u snapotter)}"
PGID="${PGID:-$(id -g snapotter)}"
if [ "$PUID" = "0" ] || [ "$PGID" = "0" ]; then
echo "WARNING: PUID=0 or PGID=0 would run the app as root. Ignoring — using default ashim UID/GID." >&2
PUID=$(id -u ashim)
PGID=$(id -g ashim)
echo "WARNING: PUID=0 or PGID=0 would run the app as root. Ignoring — using default snapotter UID/GID." >&2
PUID=$(id -u snapotter)
PGID=$(id -g snapotter)
fi
CUR_UID=$(id -u ashim)
CUR_GID=$(id -g ashim)
CUR_UID=$(id -u snapotter)
CUR_GID=$(id -g snapotter)
if [ "$CUR_UID" != "$PUID" ] || [ "$CUR_GID" != "$PGID" ]; then
# Evict any conflicting user/group that holds the target UID/GID.
# Delete user first (may cascade-delete its primary group).
if [ "$CUR_UID" != "$PUID" ]; then
EXISTING_USER=$(getent passwd "$PUID" 2>/dev/null | cut -d: -f1 || true)
if [ -n "$EXISTING_USER" ] && [ "$EXISTING_USER" != "ashim" ]; then
if [ -n "$EXISTING_USER" ] && [ "$EXISTING_USER" != "snapotter" ]; then
deluser "$EXISTING_USER" 2>/dev/null || userdel "$EXISTING_USER" 2>/dev/null || true
fi
fi
if [ "$CUR_GID" != "$PGID" ]; then
EXISTING_GROUP=$(getent group "$PGID" 2>/dev/null | cut -d: -f1 || true)
if [ -n "$EXISTING_GROUP" ] && [ "$EXISTING_GROUP" != "ashim" ]; then
if [ -n "$EXISTING_GROUP" ] && [ "$EXISTING_GROUP" != "snapotter" ]; then
delgroup "$EXISTING_GROUP" 2>/dev/null || groupdel "$EXISTING_GROUP" 2>/dev/null || true
fi
groupmod -g "$PGID" ashim 2>/dev/null || true
groupmod -g "$PGID" snapotter 2>/dev/null || true
fi
if [ "$CUR_UID" != "$PUID" ]; then
usermod -u "$PUID" ashim 2>/dev/null || true
usermod -u "$PUID" snapotter 2>/dev/null || true
fi
fi
# Chown writable directories (/data is the persistent volume, /tmp/workspace is ephemeral).
# /app and /opt/venv are read-only at runtime — no chown needed.
chown -R ashim:ashim /data /tmp/workspace 2>&1 || \
chown -R snapotter:snapotter /data /tmp/workspace 2>&1 || \
echo "WARNING: Could not fix volume permissions. Use named volumes (not Windows bind mounts) to avoid this. See docs for details." >&2
exec gosu ashim "$@"
exec gosu snapotter "$@"
fi
# Already running as ashim (e.g. Kubernetes runAsUser)
# Already running as snapotter (e.g. Kubernetes runAsUser)
exec "$@"