37 changed files with 1941 additions and 429 deletions
@ -0,0 +1,82 @@ |
|||||||
|
# Multi-stage Dockerfile for ORLY relay with embedded web UI |
||||||
|
|
||||||
|
# Stage 1: Web UI build |
||||||
|
FROM node:20-bookworm AS web-builder |
||||||
|
|
||||||
|
WORKDIR /web |
||||||
|
|
||||||
|
# Copy web UI files |
||||||
|
COPY app/web/package.json app/web/package-lock.json* app/web/ ./ |
||||||
|
|
||||||
|
# Install dependencies and build |
||||||
|
RUN npm ci && npm run build |
||||||
|
|
||||||
|
# Stage 2: Go build stage |
||||||
|
FROM golang:1.25-bookworm AS builder |
||||||
|
|
||||||
|
# Install build dependencies |
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends git make && rm -rf /var/lib/apt/lists/* |
||||||
|
|
||||||
|
# Set working directory |
||||||
|
WORKDIR /build |
||||||
|
|
||||||
|
# Copy go mod files and build context first (needed for replace directives in go.mod) |
||||||
|
COPY go.mod go.sum ./ |
||||||
|
COPY .docker-build-context/ ./docker-build-context/ |
||||||
|
|
||||||
|
# Copy vendored dependencies (created by build script) |
||||||
|
COPY vendor/ ./vendor/ |
||||||
|
|
||||||
|
# Download dependencies (will use vendor if available) |
||||||
|
RUN go mod download |
||||||
|
|
||||||
|
# Copy source code |
||||||
|
COPY . . |
||||||
|
|
||||||
|
# Copy built web UI from web-builder stage |
||||||
|
COPY --from=web-builder /web/dist ./app/web/dist |
||||||
|
|
||||||
|
# Build the binary with CGO disabled |
||||||
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o orly -ldflags="-w -s" . |
||||||
|
|
||||||
|
# Stage 3: Runtime stage |
||||||
|
FROM debian:bookworm-slim |
||||||
|
|
||||||
|
# Install runtime dependencies |
||||||
|
RUN apt-get update && \ |
||||||
|
apt-get install -y --no-install-recommends ca-certificates curl libsecp256k1-1 && \ |
||||||
|
rm -rf /var/lib/apt/lists/* |
||||||
|
|
||||||
|
# Create app user |
||||||
|
RUN groupadd -g 1000 orly && \ |
||||||
|
useradd -m -u 1000 -g orly orly |
||||||
|
|
||||||
|
# Set working directory |
||||||
|
WORKDIR /app |
||||||
|
|
||||||
|
# Copy binary (libsecp256k1.so.1 is already installed via apt) |
||||||
|
COPY --from=builder /build/orly /app/orly |
||||||
|
|
||||||
|
# Create data directory |
||||||
|
RUN mkdir -p /data && chown -R orly:orly /data /app |
||||||
|
|
||||||
|
# Switch to app user |
||||||
|
USER orly |
||||||
|
|
||||||
|
# Expose ports |
||||||
|
EXPOSE 3334 |
||||||
|
|
||||||
|
# Health check |
||||||
|
HEALTHCHECK --interval=10s --timeout=5s --start-period=20s --retries=3 \ |
||||||
|
CMD curl -f http://localhost:3334/ || exit 1 |
||||||
|
|
||||||
|
# Set default environment variables |
||||||
|
ENV ORLY_LISTEN=0.0.0.0 \ |
||||||
|
ORLY_PORT=3334 \ |
||||||
|
ORLY_DATA_DIR=/data \ |
||||||
|
ORLY_LOG_LEVEL=info \ |
||||||
|
ORLY_WEB_DISABLE=false \ |
||||||
|
ORLY_WEB_DEV_PROXY_URL="" |
||||||
|
|
||||||
|
# Run the binary |
||||||
|
ENTRYPOINT ["/app/orly"] |
||||||
@ -0,0 +1,129 @@ |
|||||||
|
# Running ORLY Relay Remotely |
||||||
|
|
||||||
|
## Quick Start |
||||||
|
|
||||||
|
### 1. Pull the Image |
||||||
|
```bash |
||||||
|
docker pull silberengel/next-orly:v0.58.5 |
||||||
|
``` |
||||||
|
|
||||||
|
### 2. Run with Docker Run |
||||||
|
```bash |
||||||
|
docker run -d \ |
||||||
|
--name orly-relay \ |
||||||
|
--restart always \ |
||||||
|
-p 3334:3334 \ |
||||||
|
-v /var/lib/orly:/data \ |
||||||
|
-e ORLY_DATA_DIR=/data \ |
||||||
|
-e ORLY_LISTEN=0.0.0.0 \ |
||||||
|
-e ORLY_PORT=3334 \ |
||||||
|
-e ORLY_LOG_LEVEL=info \ |
||||||
|
silberengel/next-orly:v0.58.5 |
||||||
|
``` |
||||||
|
|
||||||
|
### 3. Or Use Docker Compose |
||||||
|
|
||||||
|
Create a `docker-compose.yml`: |
||||||
|
```yaml |
||||||
|
version: '3.8' |
||||||
|
|
||||||
|
services: |
||||||
|
orly-relay: |
||||||
|
image: silberengel/next-orly:v0.58.5 |
||||||
|
container_name: orly-relay |
||||||
|
restart: always |
||||||
|
ports: |
||||||
|
- "3334:3334" |
||||||
|
volumes: |
||||||
|
- /var/lib/orly:/data |
||||||
|
environment: |
||||||
|
- ORLY_DATA_DIR=/data |
||||||
|
- ORLY_LISTEN=0.0.0.0 |
||||||
|
- ORLY_PORT=3334 |
||||||
|
- ORLY_LOG_LEVEL=info |
||||||
|
``` |
||||||
|
|
||||||
|
Then run: |
||||||
|
```bash |
||||||
|
docker-compose up -d |
||||||
|
``` |
||||||
|
|
||||||
|
## Configuration Options |
||||||
|
|
||||||
|
### Basic Configuration |
||||||
|
- `ORLY_DATA_DIR` - Data directory (default: `/data`) |
||||||
|
- `ORLY_LISTEN` - Listen address (default: `0.0.0.0`) |
||||||
|
- `ORLY_PORT` - Port number (default: `3334`) |
||||||
|
- `ORLY_LOG_LEVEL` - Log level: `debug`, `info`, `warn`, `error` |
||||||
|
|
||||||
|
### Advanced Configuration |
||||||
|
- `ORLY_ADMINS` - Comma-separated list of admin npub keys |
||||||
|
- `ORLY_OWNERS` - Comma-separated list of owner npub keys |
||||||
|
- `ORLY_ACL_MODE` - ACL mode: `open`, `follows`, `managed` |
||||||
|
- `ORLY_SPIDER_MODE` - Spider mode: `off`, `follows`, `all` |
||||||
|
- `ORLY_RELAY_URL` - Public relay URL (for metadata) |
||||||
|
- `ORLY_MAX_CONNECTIONS` - Max concurrent connections (default: 1000) |
||||||
|
- `ORLY_MAX_EVENT_SIZE` - Max event size in bytes (default: 65536) |
||||||
|
|
||||||
|
## Useful Commands |
||||||
|
|
||||||
|
### View Logs |
||||||
|
```bash |
||||||
|
docker logs -f orly-relay |
||||||
|
``` |
||||||
|
|
||||||
|
### Stop Container |
||||||
|
```bash |
||||||
|
docker stop orly-relay |
||||||
|
``` |
||||||
|
|
||||||
|
### Start Container |
||||||
|
```bash |
||||||
|
docker start orly-relay |
||||||
|
``` |
||||||
|
|
||||||
|
### Restart Container |
||||||
|
```bash |
||||||
|
docker restart orly-relay |
||||||
|
``` |
||||||
|
|
||||||
|
### Remove Container |
||||||
|
```bash |
||||||
|
docker rm -f orly-relay |
||||||
|
``` |
||||||
|
|
||||||
|
### Update to Latest Version |
||||||
|
```bash |
||||||
|
docker pull silberengel/next-orly:v0.58.5 |
||||||
|
docker stop orly-relay |
||||||
|
docker rm orly-relay |
||||||
|
# Then run your docker run or docker-compose command again |
||||||
|
``` |
||||||
|
|
||||||
|
## Behind a Reverse Proxy |
||||||
|
|
||||||
|
If running behind nginx/Caddy/etc, bind to localhost only: |
||||||
|
```bash |
||||||
|
docker run -d \ |
||||||
|
--name orly-relay \ |
||||||
|
--restart always \ |
||||||
|
-p 127.0.0.1:3334:3334 \ |
||||||
|
-v /var/lib/orly:/data \ |
||||||
|
-e ORLY_DATA_DIR=/data \ |
||||||
|
-e ORLY_LISTEN=0.0.0.0 \ |
||||||
|
-e ORLY_PORT=3334 \ |
||||||
|
silberengel/next-orly:v0.58.5 |
||||||
|
``` |
||||||
|
|
||||||
|
## Data Persistence |
||||||
|
|
||||||
|
Data is stored in `/var/lib/orly` on the host (or whatever path you mount). |
||||||
|
To backup: |
||||||
|
```bash |
||||||
|
tar -czf orly-backup-$(date +%Y%m%d).tar.gz /var/lib/orly |
||||||
|
``` |
||||||
|
|
||||||
|
To restore: |
||||||
|
```bash |
||||||
|
tar -xzf orly-backup-YYYYMMDD.tar.gz -C / |
||||||
|
``` |
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 379 KiB |
@ -1,69 +0,0 @@ |
|||||||
html, |
|
||||||
body { |
|
||||||
position: relative; |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
} |
|
||||||
|
|
||||||
body { |
|
||||||
color: #333; |
|
||||||
margin: 0; |
|
||||||
padding: 8px; |
|
||||||
box-sizing: border-box; |
|
||||||
font-family: |
|
||||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, |
|
||||||
Cantarell, "Helvetica Neue", sans-serif; |
|
||||||
} |
|
||||||
|
|
||||||
a { |
|
||||||
color: rgb(0, 100, 200); |
|
||||||
text-decoration: none; |
|
||||||
} |
|
||||||
|
|
||||||
a:hover { |
|
||||||
text-decoration: underline; |
|
||||||
} |
|
||||||
|
|
||||||
a:visited { |
|
||||||
color: rgb(0, 80, 160); |
|
||||||
} |
|
||||||
|
|
||||||
label { |
|
||||||
display: block; |
|
||||||
} |
|
||||||
|
|
||||||
input, |
|
||||||
button, |
|
||||||
select, |
|
||||||
textarea { |
|
||||||
font-family: inherit; |
|
||||||
font-size: inherit; |
|
||||||
-webkit-padding: 0.4em 0; |
|
||||||
padding: 0.4em; |
|
||||||
margin: 0 0 0.5em 0; |
|
||||||
box-sizing: border-box; |
|
||||||
border: 1px solid #ccc; |
|
||||||
border-radius: 2px; |
|
||||||
} |
|
||||||
|
|
||||||
input:disabled { |
|
||||||
color: #ccc; |
|
||||||
} |
|
||||||
|
|
||||||
button { |
|
||||||
color: #333; |
|
||||||
background-color: #f4f4f4; |
|
||||||
outline: none; |
|
||||||
} |
|
||||||
|
|
||||||
button:disabled { |
|
||||||
color: #999; |
|
||||||
} |
|
||||||
|
|
||||||
button:not(:disabled):active { |
|
||||||
background-color: #ddd; |
|
||||||
} |
|
||||||
|
|
||||||
button:focus { |
|
||||||
border-color: #666; |
|
||||||
} |
|
||||||
@ -1,44 +1 @@ |
|||||||
<!doctype html> |
<!-- Placeholder - will be replaced by Docker build --> |
||||||
<html lang="en"> |
|
||||||
<head> |
|
||||||
<meta charset="utf-8" /> |
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" /> |
|
||||||
<meta name="color-scheme" content="light dark" /> |
|
||||||
|
|
||||||
<title>ORLY?</title> |
|
||||||
|
|
||||||
<style> |
|
||||||
:root { |
|
||||||
color-scheme: light dark; |
|
||||||
} |
|
||||||
html, body { |
|
||||||
background-color: #fff; |
|
||||||
color: #000; |
|
||||||
} |
|
||||||
@media (prefers-color-scheme: dark) { |
|
||||||
html, body { |
|
||||||
background-color: #000; |
|
||||||
color: #fff; |
|
||||||
} |
|
||||||
} |
|
||||||
</style> |
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="/favicon.png" /> |
|
||||||
<link rel="manifest" href="/manifest.json" /> |
|
||||||
<link rel="apple-touch-icon" href="/icon-192.png" /> |
|
||||||
<meta name="theme-color" content="#000000" /> |
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" /> |
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" /> |
|
||||||
<link rel="stylesheet" href="/global.css" /> |
|
||||||
<link rel="stylesheet" href="/bundle.css" /> |
|
||||||
|
|
||||||
<script defer src="/bundle.js"></script> |
|
||||||
</head> |
|
||||||
|
|
||||||
<body></body> |
|
||||||
<script> |
|
||||||
if ('serviceWorker' in navigator) { |
|
||||||
navigator.serviceWorker.register('/sw.js'); |
|
||||||
} |
|
||||||
</script> |
|
||||||
</html> |
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 514 KiB |
@ -0,0 +1,61 @@ |
|||||||
|
#!/bin/bash |
||||||
|
# Build script for next-orly v0.48.10 Docker image |
||||||
|
|
||||||
|
set -e |
||||||
|
|
||||||
|
# Check for Docker |
||||||
|
DOCKER_CMD="" |
||||||
|
if command -v docker >/dev/null 2>&1; then |
||||||
|
DOCKER_CMD="docker" |
||||||
|
elif command -v podman >/dev/null 2>&1; then |
||||||
|
DOCKER_CMD="podman" |
||||||
|
elif command -v docker.io >/dev/null 2>&1; then |
||||||
|
DOCKER_CMD="docker.io" |
||||||
|
else |
||||||
|
echo "Error: Docker is not installed or not in PATH." |
||||||
|
echo "" |
||||||
|
echo "To install Docker on Ubuntu/Debian:" |
||||||
|
echo " sudo apt install docker.io" |
||||||
|
echo " sudo systemctl enable --now docker" |
||||||
|
echo " sudo usermod -aG docker $USER" |
||||||
|
echo " # Then log out and back in" |
||||||
|
echo "" |
||||||
|
echo "Or install Podman (Docker alternative):" |
||||||
|
echo " sudo apt install podman" |
||||||
|
echo "" |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
|
||||||
|
# Check if we're on the correct tag |
||||||
|
CURRENT_TAG=$(git describe --tags --exact-match HEAD 2>/dev/null || echo "") |
||||||
|
if [ "$CURRENT_TAG" != "v0.48.10" ]; then |
||||||
|
echo "Checking out v0.48.10..." |
||||||
|
git checkout v0.48.10 |
||||||
|
fi |
||||||
|
|
||||||
|
# Check if app/web/dist exists (web UI already built) |
||||||
|
if [ -d "app/web/dist" ]; then |
||||||
|
echo "Web UI already built, using existing Dockerfile..." |
||||||
|
DOCKERFILE="Dockerfile" |
||||||
|
else |
||||||
|
echo "Web UI not found, using Dockerfile.with-web (will build web UI in Docker)..." |
||||||
|
DOCKERFILE="Dockerfile.with-web" |
||||||
|
fi |
||||||
|
|
||||||
|
# Build the Docker image with both version and latest tags |
||||||
|
echo "Building Docker image silberengel/next-orly:v0.48.10 using $DOCKER_CMD..." |
||||||
|
$DOCKER_CMD build -t silberengel/next-orly:v0.48.10 -t silberengel/next-orly:latest -f "$DOCKERFILE" . |
||||||
|
|
||||||
|
echo "" |
||||||
|
echo "Build complete! Image tags:" |
||||||
|
echo " - silberengel/next-orly:v0.48.10" |
||||||
|
echo " - silberengel/next-orly:latest" |
||||||
|
echo "" |
||||||
|
echo "To push to Docker Hub:" |
||||||
|
echo " $DOCKER_CMD push silberengel/next-orly:v0.48.10" |
||||||
|
echo " $DOCKER_CMD push silberengel/next-orly:latest" |
||||||
|
echo "" |
||||||
|
echo "To run with Docker Compose:" |
||||||
|
echo " docker compose -f docker-compose-orly.yml up -d" |
||||||
|
echo " docker compose -f docker-compose-orly.yml logs -f" |
||||||
|
echo " docker compose -f docker-compose-orly.yml down" |
||||||
@ -0,0 +1,187 @@ |
|||||||
|
#!/bin/bash |
||||||
|
# Build script for next-orly v0.58.5 Docker image |
||||||
|
|
||||||
|
set -e |
||||||
|
|
||||||
|
# Check for Docker |
||||||
|
DOCKER_CMD="" |
||||||
|
if command -v docker >/dev/null 2>&1; then |
||||||
|
DOCKER_CMD="docker" |
||||||
|
elif command -v podman >/dev/null 2>&1; then |
||||||
|
DOCKER_CMD="podman" |
||||||
|
elif command -v docker.io >/dev/null 2>&1; then |
||||||
|
DOCKER_CMD="docker.io" |
||||||
|
else |
||||||
|
echo "Error: Docker is not installed or not in PATH." |
||||||
|
echo "" |
||||||
|
echo "To install Docker on Ubuntu/Debian:" |
||||||
|
echo " sudo apt install docker.io" |
||||||
|
echo " sudo systemctl enable --now docker" |
||||||
|
echo " sudo usermod -aG docker $USER" |
||||||
|
echo " # Then log out and back in" |
||||||
|
echo "" |
||||||
|
echo "Or install Podman (Docker alternative):" |
||||||
|
echo " sudo apt install podman" |
||||||
|
echo "" |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
|
||||||
|
# Check if we're on the correct tag |
||||||
|
CURRENT_TAG=$(git describe --tags --exact-match HEAD 2>/dev/null || echo "") |
||||||
|
if [ "$CURRENT_TAG" != "v0.58.5" ]; then |
||||||
|
echo "Warning: Not on v0.58.5 tag (current: $CURRENT_TAG)" |
||||||
|
echo "Continuing anyway..." |
||||||
|
fi |
||||||
|
|
||||||
|
# Always rebuild web UI to ensure latest changes are included |
||||||
|
# Remove old dist folder if it exists to force fresh build |
||||||
|
if [ -d "app/web/dist" ]; then |
||||||
|
echo "Removing old web UI build to force fresh rebuild..." |
||||||
|
rm -rf app/web/dist |
||||||
|
fi |
||||||
|
|
||||||
|
# Create minimal placeholder for Go embed directive (needed for local development) |
||||||
|
# Docker will replace this with the actual build |
||||||
|
mkdir -p app/web/dist |
||||||
|
echo "<!-- Placeholder - will be replaced by Docker build -->" > app/web/dist/index.html |
||||||
|
|
||||||
|
echo "Using Dockerfile.with-web (will build web UI in Docker with latest changes)..." |
||||||
|
DOCKERFILE="Dockerfile.with-web" |
||||||
|
|
||||||
|
# Ensure dev proxy is disabled in Dockerfile to prevent 502 errors |
||||||
|
echo "Ensuring dev proxy is disabled in Dockerfile..." |
||||||
|
# Remove any existing ORLY_WEB lines to avoid duplicates |
||||||
|
sed -i '/ORLY_WEB_DISABLE/d' "$DOCKERFILE" |
||||||
|
sed -i '/ORLY_WEB_DEV_PROXY_URL/d' "$DOCKERFILE" |
||||||
|
# Remove trailing backslash from ORLY_LOG_LEVEL if it exists (we'll add it back) |
||||||
|
sed -i 's/^\( ORLY_LOG_LEVEL=info\) \\$/\1/' "$DOCKERFILE" |
||||||
|
# Now add the new lines properly formatted |
||||||
|
awk '/^ ORLY_LOG_LEVEL=info$/ { |
||||||
|
print $0 " \\" |
||||||
|
print " ORLY_WEB_DISABLE=false \\" |
||||||
|
print " ORLY_WEB_DEV_PROXY_URL=\"\"" |
||||||
|
next |
||||||
|
} 1' "$DOCKERFILE" > "$DOCKERFILE.tmp" && mv "$DOCKERFILE.tmp" "$DOCKERFILE" |
||||||
|
echo "Added ORLY_WEB_DISABLE=false and ORLY_WEB_DEV_PROXY_URL=\"\" to Dockerfile" |
||||||
|
|
||||||
|
# Check if local nostr clone exists and prepare it for Docker build |
||||||
|
NOSTR_PATH="${NOSTR_PATH:-/home/firefly/Dokumente/repos/nostr}" |
||||||
|
mkdir -p .docker-build-context |
||||||
|
|
||||||
|
# Backup go.mod and go.sum before making changes |
||||||
|
cp go.mod go.mod.backup |
||||||
|
cp go.sum go.sum.backup 2>/dev/null || true |
||||||
|
|
||||||
|
if [ -d "$NOSTR_PATH" ] && [ -f "$NOSTR_PATH/go.mod" ]; then |
||||||
|
echo "Found local nostr clone at: $NOSTR_PATH" |
||||||
|
|
||||||
|
# Remove any existing replace directive |
||||||
|
sed -i '/^replace git.mleku.dev\/mleku\/nostr/d' go.mod |
||||||
|
|
||||||
|
# First, use the actual local path for vendoring (absolute path) |
||||||
|
echo "replace git.mleku.dev/mleku/nostr => $NOSTR_PATH" >> go.mod |
||||||
|
echo "Using local nostr module for vendoring" |
||||||
|
|
||||||
|
# Vendor all dependencies locally (using local network, not Docker's network) |
||||||
|
echo "Vendoring all dependencies locally..." |
||||||
|
go mod vendor || { |
||||||
|
echo "Error: Failed to vendor dependencies. Make sure you have network access." |
||||||
|
exit 1 |
||||||
|
} |
||||||
|
|
||||||
|
# Copy nostr into build context for Docker |
||||||
|
rm -rf .docker-build-context/nostr |
||||||
|
cp -r "$NOSTR_PATH" .docker-build-context/nostr |
||||||
|
|
||||||
|
# Now update go.mod to use Docker build context path |
||||||
|
echo "Preparing for Docker build..." |
||||||
|
sed -i '/^replace git.mleku.dev\/mleku\/nostr/d' go.mod |
||||||
|
echo "replace git.mleku.dev/mleku/nostr => ./docker-build-context/nostr" >> go.mod |
||||||
|
|
||||||
|
# Update vendor/modules.txt to match the new replace path |
||||||
|
# The nostr code is already in vendor/, we just need to update the metadata |
||||||
|
if [ -f vendor/modules.txt ]; then |
||||||
|
# Replace the absolute path with the relative Docker build context path |
||||||
|
sed -i "s|=> $NOSTR_PATH|=> ./docker-build-context/nostr|g" vendor/modules.txt |
||||||
|
fi |
||||||
|
echo "Using local nostr module from build context" |
||||||
|
else |
||||||
|
echo "Local nostr clone not found at $NOSTR_PATH" |
||||||
|
echo "Will try to fetch from remote (may have DNS issues)..." |
||||||
|
# Create empty directory so COPY doesn't fail |
||||||
|
mkdir -p .docker-build-context/nostr |
||||||
|
|
||||||
|
# Remove any existing replace directive |
||||||
|
sed -i '/^replace git.mleku.dev\/mleku\/nostr/d' go.mod |
||||||
|
|
||||||
|
# Vendor all dependencies locally |
||||||
|
echo "Vendoring all dependencies locally..." |
||||||
|
go mod vendor || { |
||||||
|
echo "Error: Failed to vendor dependencies. Make sure you have network access." |
||||||
|
exit 1 |
||||||
|
} |
||||||
|
fi |
||||||
|
|
||||||
|
# Function to restore go.mod and go.sum on exit |
||||||
|
restore_gomod() { |
||||||
|
if [ -f go.mod.backup ]; then |
||||||
|
mv go.mod.backup go.mod |
||||||
|
echo "Restored go.mod" |
||||||
|
fi |
||||||
|
if [ -f go.sum.backup ]; then |
||||||
|
mv go.sum.backup go.sum |
||||||
|
echo "Restored go.sum" |
||||||
|
fi |
||||||
|
# Clean up build context and vendor |
||||||
|
rm -rf .docker-build-context |
||||||
|
rm -rf vendor |
||||||
|
} |
||||||
|
trap restore_gomod EXIT |
||||||
|
|
||||||
|
# Build the Docker image with both version and latest tags |
||||||
|
# Local nostr clone (if found) is already copied into build context |
||||||
|
echo "Building Docker image silberengel/next-orly:v0.58.5 using $DOCKER_CMD..." |
||||||
|
if [ "$DOCKER_CMD" = "docker" ]; then |
||||||
|
# Try using host network mode first (uses host DNS) - best for DNS issues |
||||||
|
if docker build --help 2>/dev/null | grep -q "\-\-network"; then |
||||||
|
echo "Using host network mode for better DNS resolution..." |
||||||
|
$DOCKER_CMD build --network=host -t silberengel/next-orly:v0.58.5 -t silberengel/next-orly:latest -f "$DOCKERFILE" . |
||||||
|
elif docker build --help 2>/dev/null | grep -q "\-\-dns"; then |
||||||
|
# Fallback to DNS configuration |
||||||
|
echo "Using DNS configuration (8.8.8.8, 8.8.4.4)..." |
||||||
|
$DOCKER_CMD build --dns 8.8.8.8 --dns 8.8.4.4 -t silberengel/next-orly:v0.58.5 -t silberengel/next-orly:latest -f "$DOCKERFILE" . |
||||||
|
else |
||||||
|
# Last resort - no DNS config |
||||||
|
echo "Warning: No DNS configuration available, build may fail..." |
||||||
|
$DOCKER_CMD build -t silberengel/next-orly:v0.58.5 -t silberengel/next-orly:latest -f "$DOCKERFILE" . |
||||||
|
fi |
||||||
|
else |
||||||
|
# buildx or other - try DNS if available |
||||||
|
if docker buildx build --help 2>/dev/null | grep -q "\-\-dns"; then |
||||||
|
$DOCKER_CMD build --dns 8.8.8.8 --dns 8.8.4.4 -t silberengel/next-orly:v0.58.5 -t silberengel/next-orly:latest -f "$DOCKERFILE" . |
||||||
|
else |
||||||
|
$DOCKER_CMD build -t silberengel/next-orly:v0.58.5 -t silberengel/next-orly:latest -f "$DOCKERFILE" . |
||||||
|
fi |
||||||
|
fi |
||||||
|
|
||||||
|
echo "" |
||||||
|
echo "Build complete! Image tags:" |
||||||
|
echo " - silberengel/next-orly:v0.58.5" |
||||||
|
echo " - silberengel/next-orly:latest" |
||||||
|
echo "" |
||||||
|
echo "To push to Docker Hub:" |
||||||
|
echo " $DOCKER_CMD push silberengel/next-orly:v0.58.5" |
||||||
|
echo " $DOCKER_CMD push silberengel/next-orly:latest" |
||||||
|
echo "" |
||||||
|
echo "Or run this script with --push to build and push automatically:" |
||||||
|
echo " $0 --push" |
||||||
|
|
||||||
|
# Push if requested |
||||||
|
if [ "$1" == "--push" ]; then |
||||||
|
echo "" |
||||||
|
echo "Pushing images to Docker Hub..." |
||||||
|
$DOCKER_CMD push silberengel/next-orly:v0.58.5 |
||||||
|
$DOCKER_CMD push silberengel/next-orly:latest |
||||||
|
echo "" |
||||||
|
echo "✅ Images pushed successfully!" |
||||||
|
fi |
||||||
@ -0,0 +1,131 @@ |
|||||||
|
//go:build !(js && wasm)
|
||||||
|
|
||||||
|
package db |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"lol.mleku.dev" |
||||||
|
"lol.mleku.dev/chk" |
||||||
|
"lol.mleku.dev/log" |
||||||
|
|
||||||
|
"next.orly.dev/pkg/database" |
||||||
|
) |
||||||
|
|
||||||
|
func runImport(args []string) { |
||||||
|
var inputFile string |
||||||
|
var showHelp bool |
||||||
|
|
||||||
|
for i, arg := range args { |
||||||
|
if arg == "--file" || arg == "-f" { |
||||||
|
if i+1 < len(args) { |
||||||
|
inputFile = args[i+1] |
||||||
|
} |
||||||
|
} else if arg == "--help" || arg == "-h" { |
||||||
|
showHelp = true |
||||||
|
} else if arg != "" && !strings.HasPrefix(arg, "-") && inputFile == "" { |
||||||
|
inputFile = arg |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if showHelp { |
||||||
|
printImportHelp() |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if inputFile == "" { |
||||||
|
fmt.Fprintln(os.Stderr, "error: input file required") |
||||||
|
fmt.Fprintln(os.Stderr, "usage: orly db import <file.jsonl>") |
||||||
|
fmt.Fprintln(os.Stderr, " or: orly db import --file <file.jsonl>") |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
cfg := loadConfig() |
||||||
|
lol.SetLogLevel(cfg.LogLevel) |
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background()) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
// Create database configuration
|
||||||
|
dbCfg := &database.DatabaseConfig{ |
||||||
|
DataDir: cfg.DataDir, |
||||||
|
LogLevel: cfg.LogLevel, |
||||||
|
BlockCacheMB: cfg.BlockCacheMB, |
||||||
|
IndexCacheMB: cfg.IndexCacheMB, |
||||||
|
QueryCacheSizeMB: cfg.QueryCacheSizeMB, |
||||||
|
QueryCacheMaxAge: cfg.QueryCacheMaxAge, |
||||||
|
QueryCacheDisabled: cfg.QueryCacheDisabled, |
||||||
|
SerialCachePubkeys: cfg.SerialCachePubkeys, |
||||||
|
SerialCacheEventIds: cfg.SerialCacheEventIds, |
||||||
|
ZSTDLevel: cfg.ZSTDLevel, |
||||||
|
} |
||||||
|
|
||||||
|
// Initialize database directly
|
||||||
|
log.I.F("initializing database at %s for import", cfg.DataDir) |
||||||
|
db, err := database.NewWithConfig(ctx, cancel, dbCfg) |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to initialize database: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
defer db.Close() |
||||||
|
|
||||||
|
// Wait for database to be ready
|
||||||
|
log.I.F("waiting for database to be ready...") |
||||||
|
<-db.Ready() |
||||||
|
log.I.F("database ready") |
||||||
|
|
||||||
|
// Open input file
|
||||||
|
log.I.F("opening input file: %s", inputFile) |
||||||
|
file, err := os.Open(inputFile) |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to open input file: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
defer file.Close() |
||||||
|
|
||||||
|
// Get file size for progress
|
||||||
|
fileInfo, err := file.Stat() |
||||||
|
if chk.E(err) { |
||||||
|
log.E.F("failed to stat file: %v", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
fileSizeMB := float64(fileInfo.Size()) / 1024 / 1024 |
||||||
|
log.I.F("importing %.2f MB from %s", fileSizeMB, inputFile) |
||||||
|
|
||||||
|
// Import events
|
||||||
|
log.I.F("starting import...") |
||||||
|
db.Import(file) |
||||||
|
|
||||||
|
log.I.F("import completed successfully") |
||||||
|
fmt.Fprintf(os.Stdout, "✓ Imported events from %s (%.2f MB)\n", inputFile, fileSizeMB) |
||||||
|
} |
||||||
|
|
||||||
|
func printImportHelp() { |
||||||
|
fmt.Println(` |
||||||
|
Import events from a JSONL file directly into the database. |
||||||
|
|
||||||
|
Usage: |
||||||
|
orly db import <file.jsonl> |
||||||
|
orly db import --file <file.jsonl> |
||||||
|
|
||||||
|
This command imports events from a JSONL (JSON Lines) file directly into |
||||||
|
the Badger database, bypassing the HTTP API. This is much faster for large |
||||||
|
imports when you have direct access to the database. |
||||||
|
|
||||||
|
The input file should contain one JSON event per line (JSONL format). |
||||||
|
|
||||||
|
Environment variables: |
||||||
|
ORLY_DATA_DIR - Database data directory (required) |
||||||
|
ORLY_DB_LOG_LEVEL - Log level (default: info) |
||||||
|
ORLY_DB_BLOCK_CACHE_MB - Block cache size in MB (default: 1024) |
||||||
|
ORLY_DB_INDEX_CACHE_MB - Index cache size in MB (default: 512) |
||||||
|
ORLY_DB_ZSTD_LEVEL - ZSTD compression level (default: 3) |
||||||
|
|
||||||
|
Examples: |
||||||
|
orly db import events.jsonl |
||||||
|
orly db import --file /path/to/large-export.jsonl |
||||||
|
`) |
||||||
|
} |
||||||
@ -0,0 +1,94 @@ |
|||||||
|
version: '3.8' |
||||||
|
|
||||||
|
services: |
||||||
|
orly-relay: |
||||||
|
image: silberengel/next-orly:v0.48.10 |
||||||
|
container_name: orly-relay |
||||||
|
restart: always |
||||||
|
ports: |
||||||
|
- "127.0.0.1:3334:3334" |
||||||
|
- "127.0.0.1:7777:7777" |
||||||
|
volumes: |
||||||
|
# Use bind mount to host filesystem for large datasets (20GB+) |
||||||
|
# Change /var/lib/orly to your desired data directory on the host |
||||||
|
- /var/lib/orly:/data |
||||||
|
environment: |
||||||
|
# Relay Configuration |
||||||
|
- ORLY_DATA_DIR=/data |
||||||
|
- ORLY_LISTEN=0.0.0.0 |
||||||
|
- ORLY_PORT=7777 |
||||||
|
- ORLY_LOG_LEVEL=Info |
||||||
|
|
||||||
|
# Admin and Owner Configuration |
||||||
|
- ORLY_ADMINS=npub1m4ny6hjqzepn4rxknuq94c2gpqzr29ufkkw7ttcxyak7v43n6vvsajc2jl,npub1v30tsz9vw6ylpz63g0a702nj3xa26t3m7p5us8f2y2sd8v6cnsvq465zjx,npub12umrfdjgvdxt45g0y3ghwcyfagssjrv5qlm3t6pu2aa5vydwdmwq8q0z04,npub18cddpua960qjy3wmw7y9gmzr4h3ajlrwq3k9jnmqzlxke4qkg6gqeyaztw |
||||||
|
- ORLY_OWNERS=npub1m4ny6hjqzepn4rxknuq94c2gpqzr29ufkkw7ttcxyak7v43n6vvsajc2jl,npub1v30tsz9vw6ylpz63g0a702nj3xa26t3m7p5us8f2y2sd8v6cnsvq465zjx,npub12umrfdjgvdxt45g0y3ghwcyfagssjrv5qlm3t6pu2aa5vydwdmwq8q0z04,npub18cddpua960qjy3wmw7y9gmzr4h3ajlrwq3k9jnmqzlxke4qkg6gqeyaztw |
||||||
|
|
||||||
|
# ACL Configuration (follows mode for access control based on admin follow lists) |
||||||
|
- ORLY_ACL_MODE=follows |
||||||
|
|
||||||
|
# Spider Configuration (syncs events for followed pubkeys) |
||||||
|
- ORLY_SPIDER_MODE=follows |
||||||
|
|
||||||
|
# Relay URL (for dashboard and metadata) |
||||||
|
- ORLY_RELAY_URL=wss://orly-relay.imwald.eu |
||||||
|
|
||||||
|
# Sprocket Configuration (event processing plugin system) |
||||||
|
- ORLY_SPROCKET_ENABLED=false |
||||||
|
|
||||||
|
# Database Logging |
||||||
|
- ORLY_DB_LOG_LEVEL=error |
||||||
|
|
||||||
|
# Database Cache Tuning for Large Datasets (20GB+) |
||||||
|
# Increased caches for better performance with large working sets |
||||||
|
- ORLY_DB_BLOCK_CACHE_MB=2048 # 2GB block cache (default: 1024MB) |
||||||
|
- ORLY_DB_INDEX_CACHE_MB=1024 # 1GB index cache (default: 512MB) |
||||||
|
- ORLY_SERIAL_CACHE_PUBKEYS=500000 # 500k pubkeys cache (default: 100k) |
||||||
|
- ORLY_SERIAL_CACHE_EVENT_IDS=2000000 # 2M event IDs cache (default: 500k) |
||||||
|
- ORLY_DB_ZSTD_LEVEL=9 # ZSTD compression level 9 (best compression, reduces disk IO) |
||||||
|
|
||||||
|
# Storage GC Configuration for Large Archives |
||||||
|
# Enable GC with aggressive eviction to manage storage growth |
||||||
|
- ORLY_GC_ENABLED=true # Enable storage garbage collection |
||||||
|
- ORLY_GC_BATCH_SIZE=5000 # GC batch size for efficient processing |
||||||
|
- ORLY_MAX_STORAGE_BYTES=107374182400 # 100GB storage cap (adjust as needed) |
||||||
|
|
||||||
|
# Bootstrap relay for initial sync |
||||||
|
- ORLY_BOOTSTRAP_RELAYS=wss://profiles.nostr1.com,wss://purplepag.es,wss://relay.damus.io |
||||||
|
|
||||||
|
# Disable subscription/payment requirements |
||||||
|
- ORLY_SUBSCRIPTION_ENABLED=false |
||||||
|
- ORLY_MONTHLY_PRICE_SAT=0 |
||||||
|
|
||||||
|
# Performance Settings |
||||||
|
- ORLY_MAX_CONNECTIONS=1000 |
||||||
|
- ORLY_MAX_EVENT_SIZE=65536 |
||||||
|
- ORLY_MAX_SUBSCRIPTIONS=20 |
||||||
|
|
||||||
|
healthcheck: |
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:7777"] |
||||||
|
interval: 30s |
||||||
|
timeout: 10s |
||||||
|
retries: 3 |
||||||
|
start_period: 20s |
||||||
|
|
||||||
|
# Resource limits - Increased for large imports (20GB+) |
||||||
|
# Memory increased to handle Badger caches and import operations |
||||||
|
deploy: |
||||||
|
resources: |
||||||
|
limits: |
||||||
|
memory: 4096M # 4GB - increased from 1GB for large dataset imports |
||||||
|
cpus: "2.0" # Increased CPU for faster compaction during imports |
||||||
|
reservations: |
||||||
|
memory: 2048M # 2GB reservation |
||||||
|
cpus: "1.0" |
||||||
|
|
||||||
|
# Logging configuration |
||||||
|
logging: |
||||||
|
driver: "json-file" |
||||||
|
options: |
||||||
|
max-size: "10m" |
||||||
|
max-file: "3" |
||||||
|
|
||||||
|
# Note: Using bind mount instead of named volume for large datasets |
||||||
|
# Data is stored directly on host filesystem at /var/lib/orly |
||||||
|
# To use a different path, change the volume mount above |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
{ |
||||||
|
"name": "next.orly.dev", |
||||||
|
"lockfileVersion": 3, |
||||||
|
"requires": true, |
||||||
|
"packages": {} |
||||||
|
} |
||||||
@ -0,0 +1,89 @@ |
|||||||
|
#!/bin/bash |
||||||
|
# Run Orly relay using docker run (alternative to docker-compose) |
||||||
|
# Optimized for large dataset imports (20GB+) |
||||||
|
|
||||||
|
set -e |
||||||
|
|
||||||
|
CONTAINER_NAME="orly-relay" |
||||||
|
IMAGE="silberengel/next-orly:v0.58.5" |
||||||
|
|
||||||
|
# Data directory on host filesystem (change this to your desired path) |
||||||
|
# Using bind mount instead of volume for better performance with large datasets |
||||||
|
DATA_DIR="${ORLY_DATA_DIR:-/var/lib/orly}" |
||||||
|
|
||||||
|
# Create data directory if it doesn't exist |
||||||
|
mkdir -p "${DATA_DIR}" |
||||||
|
# Set ownership to UID 1000 (orly user in container) and permissions |
||||||
|
chown -R 1000:1000 "${DATA_DIR}" 2>/dev/null || { |
||||||
|
echo "Warning: Could not set ownership of ${DATA_DIR} to UID 1000" |
||||||
|
echo "You may need to run: sudo chown -R 1000:1000 ${DATA_DIR}" |
||||||
|
} |
||||||
|
chmod 755 "${DATA_DIR}" |
||||||
|
|
||||||
|
# Pull the latest image |
||||||
|
echo "Pulling ${IMAGE}..." |
||||||
|
docker pull ${IMAGE} |
||||||
|
|
||||||
|
# Check if container already exists |
||||||
|
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then |
||||||
|
echo "Container ${CONTAINER_NAME} already exists." |
||||||
|
echo "Removing existing container..." |
||||||
|
docker rm -f ${CONTAINER_NAME} 2>/dev/null || true |
||||||
|
fi |
||||||
|
|
||||||
|
# Run the container |
||||||
|
echo "Starting ${CONTAINER_NAME}..." |
||||||
|
docker run -d \ |
||||||
|
--name ${CONTAINER_NAME} \ |
||||||
|
--restart always \ |
||||||
|
-p 0.0.0.0:3334:3334 \ |
||||||
|
-p 0.0.0.0:7777:7777 \ |
||||||
|
-v "${DATA_DIR}:/data" \ |
||||||
|
--memory=6144m \ |
||||||
|
--cpus="2.0" \ |
||||||
|
--health-cmd="curl -f http://localhost:7777/ || exit 1" \ |
||||||
|
--health-interval=10s \ |
||||||
|
--health-timeout=5s \ |
||||||
|
--health-start-period=20s \ |
||||||
|
--health-retries=3 \ |
||||||
|
-e ORLY_DATA_DIR=/data \ |
||||||
|
-e ORLY_LISTEN=0.0.0.0 \ |
||||||
|
-e ORLY_PORT=7777 \ |
||||||
|
-e ORLY_LOG_LEVEL=Info \ |
||||||
|
-e ORLY_ADMINS=npub1m4ny6hjqzepn4rxknuq94c2gpqzr29ufkkw7ttcxyak7v43n6vvsajc2jl,npub1v30tsz9vw6ylpz63g0a702nj3xa26t3m7p5us8f2y2sd8v6cnsvq465zjx,npub12umrfdjgvdxt45g0y3ghwcyfagssjrv5qlm3t6pu2aa5vydwdmwq8q0z04,npub18cddpua960qjy3wmw7y9gmzr4h3ajlrwq3k9jnmqzlxke4qkg6gqeyaztw \ |
||||||
|
-e ORLY_OWNERS=npub1m4ny6hjqzepn4rxknuq94c2gpqzr29ufkkw7ttcxyak7v43n6vvsajc2jl,npub1v30tsz9vw6ylpz63g0a702nj3xa26t3m7p5us8f2y2sd8v6cnsvq465zjx,npub12umrfdjgvdxt45g0y3ghwcyfagssjrv5qlm3t6pu2aa5vydwdmwq8q0z04,npub18cddpua960qjy3wmw7y9gmzr4h3ajlrwq3k9jnmqzlxke4qkg6gqeyaztw \ |
||||||
|
-e ORLY_ACL_MODE=follows \ |
||||||
|
-e ORLY_SPIDER_MODE=follows \ |
||||||
|
-e ORLY_RELAY_URL=wss://orly-relay.imwald.eu \ |
||||||
|
-e ORLY_SPROCKET_ENABLED=false \ |
||||||
|
-e ORLY_DB_LOG_LEVEL=error \ |
||||||
|
-e ORLY_DB_BLOCK_CACHE_MB=512 \ |
||||||
|
-e ORLY_DB_INDEX_CACHE_MB=256 \ |
||||||
|
-e ORLY_SERIAL_CACHE_PUBKEYS=100000 \ |
||||||
|
-e ORLY_SERIAL_CACHE_EVENT_IDS=500000 \ |
||||||
|
-e ORLY_DB_ZSTD_LEVEL=9 \ |
||||||
|
-e ORLY_GC_ENABLED=true \ |
||||||
|
-e ORLY_GC_BATCH_SIZE=5000 \ |
||||||
|
-e ORLY_MAX_STORAGE_BYTES=107374182400 \ |
||||||
|
-e ORLY_BOOTSTRAP_RELAYS=wss://profiles.nostr1.com,wss://purplepag.es,wss://relay.nostr.band,wss://relay.damus.io \ |
||||||
|
-e ORLY_SUBSCRIPTION_ENABLED=false \ |
||||||
|
-e ORLY_MONTHLY_PRICE_SAT=0 \ |
||||||
|
-e ORLY_MAX_CONNECTIONS=1000 \ |
||||||
|
-e ORLY_MAX_EVENT_SIZE=65536 \ |
||||||
|
-e ORLY_MAX_SUBSCRIPTIONS=20 \ |
||||||
|
-e ORLY_WEB_DISABLE=false \ |
||||||
|
-e ORLY_WEB_DEV_PROXY_URL="" \ |
||||||
|
-e ORLY_RATE_LIMIT_EMERGENCY_THRESHOLD=4.0 \ |
||||||
|
-e ORLY_RATE_LIMIT_RECOVERY_THRESHOLD=3.0 \ |
||||||
|
${IMAGE} |
||||||
|
|
||||||
|
echo "" |
||||||
|
echo "Container started!" |
||||||
|
echo "Data directory: ${DATA_DIR}" |
||||||
|
echo "View logs: docker logs -f ${CONTAINER_NAME}" |
||||||
|
echo "Stop: docker stop ${CONTAINER_NAME}" |
||||||
|
echo "Start: docker start ${CONTAINER_NAME}" |
||||||
|
echo "Remove: docker rm -f ${CONTAINER_NAME}" |
||||||
|
echo "" |
||||||
|
echo "For large imports (20GB+), use the web UI or API:" |
||||||
|
echo " curl -X POST -F 'file=@your-events.jsonl' http://localhost:7777/api/import" |
||||||
Loading…
Reference in new issue