Archipelago — open-source initial import
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
# Multi-stage Dockerfile for Indeehub Frontend (Next.js)
|
||||
# Build: podman build -t localhost/indeedhub:latest -f apps/indeedhub/Dockerfile /path/to/indeehub-frontend
|
||||
# Run: podman run -d --name indeedhub -p 8190:3000 localhost/indeedhub:latest
|
||||
|
||||
# ── Stage 1: Dependencies ──
|
||||
FROM node:20-alpine AS deps
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci --ignore-scripts
|
||||
|
||||
# ── Stage 2: Build ──
|
||||
FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Inject standalone output mode for containerized deployment
|
||||
RUN sed -i 's/reactStrictMode: true,/reactStrictMode: true, output: "standalone",/' next.config.js
|
||||
|
||||
# Build-time environment — connects to Indeehub production services
|
||||
ENV NEXT_PUBLIC_APP_ENVIRONMENT=production
|
||||
ENV NEXT_PUBLIC_APP_URL=http://localhost:8190
|
||||
ENV NEXT_PUBLIC_API_URL=https://staging-api.indeehub.studio
|
||||
ENV NEXT_PUBLIC_S3_PRIVATE_BUCKET=indeehub-private
|
||||
ENV NEXT_PUBLIC_S3_PUBLIC_BUCKET=indeehub-public
|
||||
ENV NEXT_PUBLIC_ENABLE_APPROVAL_FLOW=false
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# Remove shaka-player .d.ts files that break the build (per package.json build script)
|
||||
RUN rm -f ./node_modules/shaka-player/dist/*.d.ts
|
||||
|
||||
# Patch: replace home page with error-resilient version that doesn't crash
|
||||
# when the Webflow landing page URL is unreachable from the container.
|
||||
RUN printf '%s\n' \
|
||||
"import axios from 'axios';" \
|
||||
"import { HomeClient } from './page.client';" \
|
||||
"" \
|
||||
"export const dynamic = 'force-dynamic';" \
|
||||
"" \
|
||||
"export default async function Home() {" \
|
||||
" try {" \
|
||||
" const response = await axios('https://indeehub-30479a.webflow.io/', { timeout: 8000 });" \
|
||||
" if (response.status !== 200) throw new Error('Bad status');" \
|
||||
" const html = String(response.data)" \
|
||||
" .replace('https://cdn.prod.website-files.com/img/favicon.ico', '/favicon.ico')" \
|
||||
" .replace('https://cdn.prod.website-files.com/img/webclip.png', '/favicon.ico');" \
|
||||
" return <HomeClient html={html} />;" \
|
||||
" } catch {" \
|
||||
" return <HomeClient html=\"<html><head><title>IndeeHub</title></head><body style='background:#000;color:#fff;font-family:sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0'><div style='text-align:center'><h1>IndeeHub</h1><p>Loading content...</p><script>setTimeout(function(){location.reload()},5000)</script></div></body></html>\" />;" \
|
||||
" }" \
|
||||
"}" > src/app/page.tsx
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# ── Stage 3: Runner ──
|
||||
FROM node:20-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs && \
|
||||
adduser --system --uid 1001 nextjs
|
||||
|
||||
# Copy standalone build output
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
EXPOSE 3000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=40s \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
@@ -0,0 +1,53 @@
|
||||
# Indeehub — Bitcoin Documentary Streaming
|
||||
|
||||
Bitcoin documentary streaming platform featuring God Bless Bitcoin and other educational content about Bitcoin, sovereignty, and decentralized technology.
|
||||
|
||||
Self-hosted Next.js app with Nostr identity sign-in via Archipelago's NIP-07 provider.
|
||||
|
||||
## Building the Image
|
||||
|
||||
The app image is built from the **indeehub-frontend** project at `~/Projects/indeehub-frontend`.
|
||||
|
||||
### Option 1: Use the build script
|
||||
|
||||
```bash
|
||||
# From archy repo root
|
||||
./apps/indeedhub/build-from-prototype.sh
|
||||
```
|
||||
|
||||
### Option 2: Build from source directory
|
||||
|
||||
```bash
|
||||
cd ~/Projects/indeehub-frontend
|
||||
podman build -t localhost/indeedhub:latest -f ~/Projects/archy/apps/indeedhub/Dockerfile .
|
||||
```
|
||||
|
||||
## Installing from App Store
|
||||
|
||||
1. **Build the image** using one of the options above (must exist before install)
|
||||
2. Go to **Dashboard -> App Store** (Marketplace)
|
||||
3. Find **Indeehub** and click **Install**
|
||||
4. On first launch, pick a Nostr identity to sign in with
|
||||
5. The app appears in **My Apps** once the container is running
|
||||
|
||||
## Port
|
||||
|
||||
- Web UI: 8190 (maps to container port 3000)
|
||||
|
||||
## Container
|
||||
|
||||
- Image: `localhost/indeedhub:latest` (built locally, not pulled from a registry)
|
||||
- Runtime: Node.js 20 (Next.js standalone)
|
||||
- Port: 8190 -> 3000
|
||||
- Read-only root filesystem with tmpfs for /tmp and .next/cache
|
||||
|
||||
## Nostr Identity
|
||||
|
||||
On first launch, Archipelago shows a cypherpunk identity picker modal. Select which of your identities to use for NIP-07 signing. The NIP-07 provider is injected automatically via nginx proxy.
|
||||
|
||||
## Services
|
||||
|
||||
The app connects to the following external services (configured at build time):
|
||||
- Indeehub API (content, auth, streaming)
|
||||
- AWS S3 (media storage via CloudFront CDN)
|
||||
- Nostr relays (via NIP-07 provider from Archipelago)
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
# Build Indeehub container image from the indeehub-frontend project
|
||||
# Usage: ./build-from-prototype.sh [path-to-indeehub-frontend]
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DEFAULT_FRONTEND="$HOME/Projects/indeehub-frontend"
|
||||
FRONTEND_DIR="${1:-$DEFAULT_FRONTEND}"
|
||||
IMAGE_TAG="localhost/indeedhub:latest"
|
||||
|
||||
if [ ! -d "$FRONTEND_DIR" ]; then
|
||||
echo "Indeehub frontend not found at: $FRONTEND_DIR"
|
||||
echo " Set path: $0 /path/to/indeehub-frontend"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$FRONTEND_DIR/package.json" ]; then
|
||||
echo "No package.json found in $FRONTEND_DIR — is this the right directory?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine container runtime
|
||||
RUNTIME="podman"
|
||||
if ! command -v podman >/dev/null 2>&1; then
|
||||
RUNTIME="docker"
|
||||
fi
|
||||
|
||||
echo "Building Indeehub from $FRONTEND_DIR using $SCRIPT_DIR/Dockerfile"
|
||||
$RUNTIME build -t "$IMAGE_TAG" -f "$SCRIPT_DIR/Dockerfile" "$FRONTEND_DIR"
|
||||
|
||||
echo "Built $IMAGE_TAG"
|
||||
echo ""
|
||||
echo "You can now install Indeehub from the App Store in Archipelago."
|
||||
echo "Or run directly: $RUNTIME run -d --name indeedhub -p 8190:3000 $IMAGE_TAG"
|
||||
@@ -0,0 +1,104 @@
|
||||
app:
|
||||
id: indeedhub
|
||||
name: IndeeHub
|
||||
version: "1.0.0"
|
||||
description: Bitcoin documentary streaming platform featuring God Bless Bitcoin and other educational content about Bitcoin, sovereignty, and decentralized technology. Sign in with your Nostr identity.
|
||||
category: community
|
||||
|
||||
# The user-facing launcher (app_id "indeedhub"). Container is named "indeedhub"
|
||||
# (matches the runtime's per-app references + the live container, so the
|
||||
# orchestrator adopts it). Its nginx (listen 7777) proxies to the backends by
|
||||
# their short aliases on indeedhub-net: api:4000, minio:9000, relay:8080.
|
||||
container_name: indeedhub
|
||||
|
||||
container:
|
||||
image: 146.59.87.168:3000/lfg2025/indeedhub:1.0.0
|
||||
pull_policy: if-not-present
|
||||
network: indeedhub-net
|
||||
|
||||
dependencies:
|
||||
- app_id: indeedhub-api
|
||||
- storage: 1Gi
|
||||
|
||||
resources:
|
||||
memory_limit: 512Mi
|
||||
disk_limit: 1Gi
|
||||
|
||||
security:
|
||||
# nginx master runs as root and drops workers to the nginx user (uid/gid
|
||||
# 101) — needs SET{UID,GID}; CHOWN + DAC_OVERRIDE let it own + write the
|
||||
# proxy cache under the tmpfs /var/cache/nginx. The orchestrator does
|
||||
# --cap-drop=ALL, so (unlike the legacy `podman run` default caps) these
|
||||
# must be declared or nginx workers die with "setgid(101) failed".
|
||||
capabilities: [CHOWN, DAC_OVERRIDE, SETGID, SETUID]
|
||||
readonly_root: false
|
||||
network_policy: isolated
|
||||
|
||||
ports:
|
||||
- host: 7778
|
||||
container: 7777
|
||||
protocol: tcp # Web UI. Port 7777 on the host is reserved for the Nostr relay.
|
||||
|
||||
# Writable scratch the baked nginx needs; matches the legacy installer's
|
||||
# --tmpfs /run + /var/cache/nginx.
|
||||
volumes:
|
||||
- type: tmpfs
|
||||
target: /run
|
||||
options: [rw, nosuid, nodev, size=16m]
|
||||
- type: tmpfs
|
||||
target: /var/cache/nginx
|
||||
options: [rw, nosuid, nodev, size=32m]
|
||||
|
||||
environment: []
|
||||
|
||||
# Defensive + idempotent. The current indeedhub:1.0.0 image already bakes the
|
||||
# iframe-friendly nginx (X-Frame-Options omitted, nostr-provider.js present +
|
||||
# <script> injected), so these are mostly no-ops on that tag — but they keep
|
||||
# the app iframe-loadable + the provider script fresh for any image build that
|
||||
# predates the bake. copy_from_host pulls /opt/archipelago/web-ui/nostr-provider.js
|
||||
# (kept current by frontend OTA releases). Replaces the legacy hardcoded
|
||||
# patch_indeedhub_nostr_provider() Rust hook.
|
||||
hooks:
|
||||
post_install:
|
||||
- exec: ["sed", "-i", "/X-Frame-Options/d", "/etc/nginx/conf.d/default.conf"]
|
||||
- copy_from_host:
|
||||
src: "web-ui/nostr-provider.js"
|
||||
dest: "/usr/share/nginx/html/nostr-provider.js"
|
||||
- exec: ["sh", "-c", "grep -q nostr-provider /etc/nginx/conf.d/default.conf || sed -i 's#</head>#<script src=\"/nostr-provider.js\"></script></head>#' /etc/nginx/conf.d/default.conf"]
|
||||
- exec: ["nginx", "-s", "reload"]
|
||||
|
||||
# TCP liveness on the nginx port, NOT an http GET of /. nginx binds 7777 at
|
||||
# startup (before workers), so this passes immediately and stays green under
|
||||
# load. An http check of / runs the SPA + sub_filter and false-fails when the
|
||||
# node is busy → the reconciler then treats the frontend as wedged and
|
||||
# recreates it in a loop (observed churning the frontend on the loaded .198).
|
||||
health_check:
|
||||
type: tcp
|
||||
endpoint: localhost:7777
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
interfaces:
|
||||
main:
|
||||
name: Web UI
|
||||
description: Stream Bitcoin documentaries with Nostr identity
|
||||
type: ui
|
||||
port: 7778
|
||||
protocol: http
|
||||
path: /
|
||||
|
||||
metadata:
|
||||
author: Indeehub Team
|
||||
icon: /assets/img/app-icons/indeedhub.png
|
||||
website: https://indeedhub.com
|
||||
repo: https://github.com/indeedhub/indeedhub
|
||||
license: MIT
|
||||
tags:
|
||||
- bitcoin
|
||||
- documentary
|
||||
- streaming
|
||||
- media
|
||||
- education
|
||||
- nostr
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
# Build and push Indeehub container image to a registry
|
||||
# Usage: ./push-to-registry.sh [version]
|
||||
#
|
||||
# Environment variables:
|
||||
# REGISTRY - Registry host (default: ghcr.io)
|
||||
# NAMESPACE - Registry namespace (default: archipelago-os)
|
||||
# RUNTIME - Container runtime (default: podman)
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
FRONTEND_DIR="${INDEEHUB_FRONTEND:-$HOME/Projects/indeehub-frontend}"
|
||||
VERSION="${1:-latest}"
|
||||
REGISTRY="${REGISTRY:-146.59.87.168:3000}"
|
||||
NAMESPACE="${NAMESPACE:-lfg2025}"
|
||||
IMAGE_NAME="indeedhub"
|
||||
RUNTIME="${RUNTIME:-podman}"
|
||||
|
||||
FULL_TAG="${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${VERSION}"
|
||||
LATEST_TAG="${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:latest"
|
||||
|
||||
if [ ! -d "$FRONTEND_DIR" ]; then
|
||||
echo "Indeehub frontend not found at: $FRONTEND_DIR"
|
||||
echo "Set INDEEHUB_FRONTEND=/path/to/indeehub-frontend"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Indeehub Container Registry Push ==="
|
||||
echo "Source: $FRONTEND_DIR"
|
||||
echo "Image: $FULL_TAG"
|
||||
echo "Runtime: $RUNTIME"
|
||||
echo ""
|
||||
|
||||
# Step 1: Build for linux/amd64 (target architecture)
|
||||
echo "[1/3] Building image..."
|
||||
$RUNTIME build --platform linux/amd64 \
|
||||
-t "$FULL_TAG" \
|
||||
-t "$LATEST_TAG" \
|
||||
-t "localhost/${IMAGE_NAME}:latest" \
|
||||
-t "localhost/${IMAGE_NAME}:${VERSION}" \
|
||||
-f "$SCRIPT_DIR/Dockerfile" \
|
||||
"$FRONTEND_DIR"
|
||||
|
||||
echo "[2/3] Pushing to registry..."
|
||||
# Login check
|
||||
if ! $RUNTIME login --get-login "$REGISTRY" >/dev/null 2>&1; then
|
||||
echo ""
|
||||
echo "Not logged in to $REGISTRY."
|
||||
echo "Run: $RUNTIME login $REGISTRY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$RUNTIME push "$FULL_TAG"
|
||||
if [ "$VERSION" != "latest" ]; then
|
||||
$RUNTIME push "$LATEST_TAG"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[3/3] Done!"
|
||||
echo ""
|
||||
echo "Image pushed: $FULL_TAG"
|
||||
if [ "$VERSION" != "latest" ]; then
|
||||
echo "Also tagged: $LATEST_TAG"
|
||||
fi
|
||||
echo ""
|
||||
echo "Federated nodes can now install via:"
|
||||
echo " podman pull $FULL_TAG"
|
||||
echo ""
|
||||
echo "Update marketplace dockerImage to: $FULL_TAG"
|
||||
Reference in New Issue
Block a user