Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe2495f897 |
612
STATIC_MUSL_GUIDE.md
Normal file
612
STATIC_MUSL_GUIDE.md
Normal file
@@ -0,0 +1,612 @@
|
|||||||
|
# Static MUSL Build Guide for C Programs
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide explains how to build truly portable static binaries using Alpine Linux and MUSL libc. These binaries have **zero runtime dependencies** and work on any Linux distribution without modification.
|
||||||
|
|
||||||
|
This guide is specifically tailored for C programs that use:
|
||||||
|
- **nostr_core_lib** - Nostr protocol implementation
|
||||||
|
- **nostr_login_lite** - Nostr authentication library
|
||||||
|
- Common dependencies: libwebsockets, OpenSSL, SQLite, curl, secp256k1
|
||||||
|
|
||||||
|
## Why MUSL Static Binaries?
|
||||||
|
|
||||||
|
### Advantages Over glibc
|
||||||
|
|
||||||
|
| Feature | MUSL Static | glibc Static | glibc Dynamic |
|
||||||
|
|---------|-------------|--------------|---------------|
|
||||||
|
| **Portability** | ✓ Any Linux | ⚠ glibc only | ✗ Requires matching libs |
|
||||||
|
| **Binary Size** | ~7-10 MB | ~12-15 MB | ~2-3 MB |
|
||||||
|
| **Dependencies** | None | NSS libs | Many system libs |
|
||||||
|
| **Deployment** | Single file | Single file + NSS | Binary + libraries |
|
||||||
|
| **Compatibility** | Universal | glibc version issues | Library version hell |
|
||||||
|
|
||||||
|
### Key Benefits
|
||||||
|
|
||||||
|
1. **True Portability**: Works on Alpine, Ubuntu, Debian, CentOS, Arch, etc.
|
||||||
|
2. **No Library Hell**: No `GLIBC_2.XX not found` errors
|
||||||
|
3. **Simple Deployment**: Just copy one file
|
||||||
|
4. **Reproducible Builds**: Same Docker image = same binary
|
||||||
|
5. **Security**: No dependency on system libraries with vulnerabilities
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Docker installed and running
|
||||||
|
- Your C project with source code
|
||||||
|
- Internet connection for downloading dependencies
|
||||||
|
|
||||||
|
### Basic Build Process
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Copy the Dockerfile template (see below)
|
||||||
|
cp /path/to/c-relay/Dockerfile.alpine-musl ./Dockerfile.static
|
||||||
|
|
||||||
|
# 2. Customize for your project (see Customization section)
|
||||||
|
vim Dockerfile.static
|
||||||
|
|
||||||
|
# 3. Build the static binary
|
||||||
|
docker build --platform linux/amd64 -f Dockerfile.static -t my-app-builder .
|
||||||
|
|
||||||
|
# 4. Extract the binary
|
||||||
|
docker create --name temp-container my-app-builder
|
||||||
|
docker cp temp-container:/build/my_app_static ./my_app_static
|
||||||
|
docker rm temp-container
|
||||||
|
|
||||||
|
# 5. Verify it's static
|
||||||
|
ldd ./my_app_static # Should show "not a dynamic executable"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dockerfile Template
|
||||||
|
|
||||||
|
Here's a complete Dockerfile template you can customize for your project:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# Alpine-based MUSL static binary builder
|
||||||
|
# Produces truly portable binaries with zero runtime dependencies
|
||||||
|
|
||||||
|
FROM alpine:3.19 AS builder
|
||||||
|
|
||||||
|
# Install build dependencies
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
build-base \
|
||||||
|
musl-dev \
|
||||||
|
git \
|
||||||
|
cmake \
|
||||||
|
pkgconfig \
|
||||||
|
autoconf \
|
||||||
|
automake \
|
||||||
|
libtool \
|
||||||
|
openssl-dev \
|
||||||
|
openssl-libs-static \
|
||||||
|
zlib-dev \
|
||||||
|
zlib-static \
|
||||||
|
curl-dev \
|
||||||
|
curl-static \
|
||||||
|
sqlite-dev \
|
||||||
|
sqlite-static \
|
||||||
|
linux-headers \
|
||||||
|
wget \
|
||||||
|
bash
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Build libsecp256k1 static (required for Nostr)
|
||||||
|
RUN cd /tmp && \
|
||||||
|
git clone https://github.com/bitcoin-core/secp256k1.git && \
|
||||||
|
cd secp256k1 && \
|
||||||
|
./autogen.sh && \
|
||||||
|
./configure --enable-static --disable-shared --prefix=/usr \
|
||||||
|
CFLAGS="-fPIC" && \
|
||||||
|
make -j$(nproc) && \
|
||||||
|
make install && \
|
||||||
|
rm -rf /tmp/secp256k1
|
||||||
|
|
||||||
|
# Build libwebsockets static (if needed for WebSocket support)
|
||||||
|
RUN cd /tmp && \
|
||||||
|
git clone --depth 1 --branch v4.3.3 https://github.com/warmcat/libwebsockets.git && \
|
||||||
|
cd libwebsockets && \
|
||||||
|
mkdir build && cd build && \
|
||||||
|
cmake .. \
|
||||||
|
-DLWS_WITH_STATIC=ON \
|
||||||
|
-DLWS_WITH_SHARED=OFF \
|
||||||
|
-DLWS_WITH_SSL=ON \
|
||||||
|
-DLWS_WITHOUT_TESTAPPS=ON \
|
||||||
|
-DLWS_WITHOUT_TEST_SERVER=ON \
|
||||||
|
-DLWS_WITHOUT_TEST_CLIENT=ON \
|
||||||
|
-DLWS_WITHOUT_TEST_PING=ON \
|
||||||
|
-DLWS_WITH_HTTP2=OFF \
|
||||||
|
-DLWS_WITH_LIBUV=OFF \
|
||||||
|
-DLWS_WITH_LIBEVENT=OFF \
|
||||||
|
-DLWS_IPV6=ON \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||||
|
-DCMAKE_C_FLAGS="-fPIC" && \
|
||||||
|
make -j$(nproc) && \
|
||||||
|
make install && \
|
||||||
|
rm -rf /tmp/libwebsockets
|
||||||
|
|
||||||
|
# Copy git configuration for submodules
|
||||||
|
COPY .gitmodules /build/.gitmodules
|
||||||
|
COPY .git /build/.git
|
||||||
|
|
||||||
|
# Initialize submodules
|
||||||
|
RUN git submodule update --init --recursive
|
||||||
|
|
||||||
|
# Copy and build nostr_core_lib
|
||||||
|
COPY nostr_core_lib /build/nostr_core_lib/
|
||||||
|
RUN cd nostr_core_lib && \
|
||||||
|
chmod +x build.sh && \
|
||||||
|
sed -i 's/CFLAGS="-Wall -Wextra -std=c99 -fPIC -O2"/CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -fPIC -O2"/' build.sh && \
|
||||||
|
rm -f *.o *.a 2>/dev/null || true && \
|
||||||
|
./build.sh --nips=1,6,13,17,19,44,59
|
||||||
|
|
||||||
|
# Copy and build nostr_login_lite (if used)
|
||||||
|
# COPY nostr_login_lite /build/nostr_login_lite/
|
||||||
|
# RUN cd nostr_login_lite && make static
|
||||||
|
|
||||||
|
# Copy your application source
|
||||||
|
COPY src/ /build/src/
|
||||||
|
COPY Makefile /build/Makefile
|
||||||
|
|
||||||
|
# Build your application with full static linking
|
||||||
|
RUN gcc -static -O2 -Wall -Wextra -std=c99 \
|
||||||
|
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
|
||||||
|
-I. -Inostr_core_lib -Inostr_core_lib/nostr_core \
|
||||||
|
-Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \
|
||||||
|
src/*.c \
|
||||||
|
-o /build/my_app_static \
|
||||||
|
nostr_core_lib/libnostr_core_x64.a \
|
||||||
|
-lwebsockets -lssl -lcrypto -lsqlite3 -lsecp256k1 \
|
||||||
|
-lcurl -lz -lpthread -lm -ldl && \
|
||||||
|
strip /build/my_app_static
|
||||||
|
|
||||||
|
# Verify it's truly static
|
||||||
|
RUN echo "=== Binary Information ===" && \
|
||||||
|
file /build/my_app_static && \
|
||||||
|
ls -lh /build/my_app_static && \
|
||||||
|
echo "=== Checking for dynamic dependencies ===" && \
|
||||||
|
(ldd /build/my_app_static 2>&1 || echo "Binary is static")
|
||||||
|
|
||||||
|
# Output stage - just the binary
|
||||||
|
FROM scratch AS output
|
||||||
|
COPY --from=builder /build/my_app_static /my_app_static
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customization Guide
|
||||||
|
|
||||||
|
### 1. Adjust Dependencies
|
||||||
|
|
||||||
|
**Add dependencies** by modifying the `apk add` section:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
build-base \
|
||||||
|
musl-dev \
|
||||||
|
# Add your dependencies here:
|
||||||
|
libpng-dev \
|
||||||
|
libpng-static \
|
||||||
|
libjpeg-turbo-dev \
|
||||||
|
libjpeg-turbo-static
|
||||||
|
```
|
||||||
|
|
||||||
|
**Remove unused dependencies** to speed up builds:
|
||||||
|
- Remove `libwebsockets` section if you don't need WebSocket support
|
||||||
|
- Remove `sqlite` if you don't use databases
|
||||||
|
- Remove `curl` if you don't make HTTP requests
|
||||||
|
|
||||||
|
### 2. Configure nostr_core_lib NIPs
|
||||||
|
|
||||||
|
Specify which NIPs your application needs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build.sh --nips=1,6,19 # Minimal: Basic protocol, keys, bech32
|
||||||
|
./build.sh --nips=1,6,13,17,19,44,59 # Full: All common NIPs
|
||||||
|
./build.sh --nips=all # Everything available
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common NIP combinations:**
|
||||||
|
- **Basic client**: `1,6,19` (events, keys, bech32)
|
||||||
|
- **With encryption**: `1,6,19,44` (add modern encryption)
|
||||||
|
- **With DMs**: `1,6,17,19,44,59` (add private messages)
|
||||||
|
- **Relay/server**: `1,6,13,17,19,42,44,59` (add PoW, auth)
|
||||||
|
|
||||||
|
### 3. Modify Compilation Flags
|
||||||
|
|
||||||
|
**For your application:**
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
RUN gcc -static -O2 -Wall -Wextra -std=c99 \
|
||||||
|
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \ # REQUIRED for MUSL
|
||||||
|
-I. -Inostr_core_lib \ # Include paths
|
||||||
|
src/*.c \ # Your source files
|
||||||
|
-o /build/my_app_static \ # Output binary
|
||||||
|
nostr_core_lib/libnostr_core_x64.a \ # Nostr library
|
||||||
|
-lwebsockets -lssl -lcrypto \ # Link libraries
|
||||||
|
-lsqlite3 -lsecp256k1 -lcurl \
|
||||||
|
-lz -lpthread -lm -ldl
|
||||||
|
```
|
||||||
|
|
||||||
|
**Debug build** (with symbols, no optimization):
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
RUN gcc -static -g -O0 -DDEBUG \
|
||||||
|
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
|
||||||
|
# ... rest of flags
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Multi-Architecture Support
|
||||||
|
|
||||||
|
Build for different architectures:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# x86_64 (Intel/AMD)
|
||||||
|
docker build --platform linux/amd64 -f Dockerfile.static -t my-app-x86 .
|
||||||
|
|
||||||
|
# ARM64 (Apple Silicon, Raspberry Pi 4+)
|
||||||
|
docker build --platform linux/arm64 -f Dockerfile.static -t my-app-arm64 .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build Script Template
|
||||||
|
|
||||||
|
Create a `build_static.sh` script for convenience:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
BUILD_DIR="$SCRIPT_DIR/build"
|
||||||
|
DOCKERFILE="$SCRIPT_DIR/Dockerfile.static"
|
||||||
|
|
||||||
|
# Detect architecture
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
case "$ARCH" in
|
||||||
|
x86_64)
|
||||||
|
PLATFORM="linux/amd64"
|
||||||
|
OUTPUT_NAME="my_app_static_x86_64"
|
||||||
|
;;
|
||||||
|
aarch64|arm64)
|
||||||
|
PLATFORM="linux/arm64"
|
||||||
|
OUTPUT_NAME="my_app_static_arm64"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown architecture: $ARCH"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "Building for platform: $PLATFORM"
|
||||||
|
mkdir -p "$BUILD_DIR"
|
||||||
|
|
||||||
|
# Build Docker image
|
||||||
|
docker build \
|
||||||
|
--platform "$PLATFORM" \
|
||||||
|
-f "$DOCKERFILE" \
|
||||||
|
-t my-app-builder:latest \
|
||||||
|
--progress=plain \
|
||||||
|
.
|
||||||
|
|
||||||
|
# Extract binary
|
||||||
|
CONTAINER_ID=$(docker create my-app-builder:latest)
|
||||||
|
docker cp "$CONTAINER_ID:/build/my_app_static" "$BUILD_DIR/$OUTPUT_NAME"
|
||||||
|
docker rm "$CONTAINER_ID"
|
||||||
|
|
||||||
|
chmod +x "$BUILD_DIR/$OUTPUT_NAME"
|
||||||
|
|
||||||
|
echo "✓ Build complete: $BUILD_DIR/$OUTPUT_NAME"
|
||||||
|
echo "✓ Size: $(du -h "$BUILD_DIR/$OUTPUT_NAME" | cut -f1)"
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
if ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1 | grep -q "not a dynamic executable"; then
|
||||||
|
echo "✓ Binary is fully static"
|
||||||
|
else
|
||||||
|
echo "⚠ Warning: Binary may have dynamic dependencies"
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
Make it executable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x build_static.sh
|
||||||
|
./build_static.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Issues and Solutions
|
||||||
|
|
||||||
|
### Issue 1: Fortification Errors
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
undefined reference to '__snprintf_chk'
|
||||||
|
undefined reference to '__fprintf_chk'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause**: GCC's `-O2` enables fortification by default, which uses glibc-specific functions.
|
||||||
|
|
||||||
|
**Solution**: Add these flags to **all** compilation commands:
|
||||||
|
```bash
|
||||||
|
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0
|
||||||
|
```
|
||||||
|
|
||||||
|
This must be applied to:
|
||||||
|
1. nostr_core_lib build.sh
|
||||||
|
2. Your application compilation
|
||||||
|
3. Any other libraries you build
|
||||||
|
|
||||||
|
### Issue 2: Missing Symbols from nostr_core_lib
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
undefined reference to 'nostr_create_event'
|
||||||
|
undefined reference to 'nostr_sign_event'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause**: Required NIPs not included in nostr_core_lib build.
|
||||||
|
|
||||||
|
**Solution**: Add missing NIPs:
|
||||||
|
```bash
|
||||||
|
./build.sh --nips=1,6,19 # Add the NIPs you need
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue 3: Docker Permission Denied
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
permission denied while trying to connect to the Docker daemon socket
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
```bash
|
||||||
|
sudo usermod -aG docker $USER
|
||||||
|
newgrp docker # Or logout and login
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue 4: Binary Won't Run on Target System
|
||||||
|
|
||||||
|
**Checks**:
|
||||||
|
```bash
|
||||||
|
# 1. Verify it's static
|
||||||
|
ldd my_app_static # Should show "not a dynamic executable"
|
||||||
|
|
||||||
|
# 2. Check architecture
|
||||||
|
file my_app_static # Should match target system
|
||||||
|
|
||||||
|
# 3. Test on different distributions
|
||||||
|
docker run --rm -v $(pwd):/app alpine:latest /app/my_app_static --version
|
||||||
|
docker run --rm -v $(pwd):/app ubuntu:latest /app/my_app_static --version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure Example
|
||||||
|
|
||||||
|
Organize your project for easy static builds:
|
||||||
|
|
||||||
|
```
|
||||||
|
my-nostr-app/
|
||||||
|
├── src/
|
||||||
|
│ ├── main.c
|
||||||
|
│ ├── handlers.c
|
||||||
|
│ └── utils.c
|
||||||
|
├── nostr_core_lib/ # Git submodule
|
||||||
|
├── nostr_login_lite/ # Git submodule (if used)
|
||||||
|
├── Dockerfile.static # Static build Dockerfile
|
||||||
|
├── build_static.sh # Build script
|
||||||
|
├── Makefile # Regular build
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Makefile Integration
|
||||||
|
|
||||||
|
Add static build targets to your Makefile:
|
||||||
|
|
||||||
|
```makefile
|
||||||
|
# Regular dynamic build
|
||||||
|
all: my_app
|
||||||
|
|
||||||
|
my_app: src/*.c
|
||||||
|
gcc -O2 src/*.c -o my_app \
|
||||||
|
nostr_core_lib/libnostr_core_x64.a \
|
||||||
|
-lssl -lcrypto -lsecp256k1 -lz -lpthread -lm
|
||||||
|
|
||||||
|
# Static MUSL build via Docker
|
||||||
|
static:
|
||||||
|
./build_static.sh
|
||||||
|
|
||||||
|
# Clean
|
||||||
|
clean:
|
||||||
|
rm -f my_app build/my_app_static_*
|
||||||
|
|
||||||
|
.PHONY: all static clean
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Single Binary Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy to server
|
||||||
|
scp build/my_app_static_x86_64 user@server:/opt/my-app/
|
||||||
|
|
||||||
|
# Run (no dependencies needed!)
|
||||||
|
ssh user@server
|
||||||
|
/opt/my-app/my_app_static_x86_64
|
||||||
|
```
|
||||||
|
|
||||||
|
### SystemD Service
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=My Nostr Application
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=myapp
|
||||||
|
WorkingDirectory=/opt/my-app
|
||||||
|
ExecStart=/opt/my-app/my_app_static_x86_64
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Container (Minimal)
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM scratch
|
||||||
|
COPY my_app_static_x86_64 /app
|
||||||
|
ENTRYPOINT ["/app"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Build and run:
|
||||||
|
```bash
|
||||||
|
docker build -t my-app:latest .
|
||||||
|
docker run --rm my-app:latest --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reusing c-relay Files
|
||||||
|
|
||||||
|
You can directly copy these files from c-relay:
|
||||||
|
|
||||||
|
### 1. Dockerfile.alpine-musl
|
||||||
|
```bash
|
||||||
|
cp /path/to/c-relay/Dockerfile.alpine-musl ./Dockerfile.static
|
||||||
|
```
|
||||||
|
|
||||||
|
Then customize:
|
||||||
|
- Change binary name (line 125)
|
||||||
|
- Adjust source files (line 122-124)
|
||||||
|
- Modify include paths (line 120-121)
|
||||||
|
|
||||||
|
### 2. build_static.sh
|
||||||
|
```bash
|
||||||
|
cp /path/to/c-relay/build_static.sh ./
|
||||||
|
```
|
||||||
|
|
||||||
|
Then customize:
|
||||||
|
- Change `OUTPUT_NAME` variable (lines 66, 70)
|
||||||
|
- Update Docker image name (line 98)
|
||||||
|
- Modify verification commands (lines 180-184)
|
||||||
|
|
||||||
|
### 3. .dockerignore (Optional)
|
||||||
|
```bash
|
||||||
|
cp /path/to/c-relay/.dockerignore ./
|
||||||
|
```
|
||||||
|
|
||||||
|
Helps speed up Docker builds by excluding unnecessary files.
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Version Control**: Commit your Dockerfile and build script
|
||||||
|
2. **Tag Builds**: Include git commit hash in binary version
|
||||||
|
3. **Test Thoroughly**: Verify on multiple distributions
|
||||||
|
4. **Document Dependencies**: List required NIPs and libraries
|
||||||
|
5. **Automate**: Use CI/CD to build on every commit
|
||||||
|
6. **Archive Binaries**: Keep old versions for rollback
|
||||||
|
|
||||||
|
## Performance Comparison
|
||||||
|
|
||||||
|
| Metric | MUSL Static | glibc Dynamic |
|
||||||
|
|--------|-------------|---------------|
|
||||||
|
| Binary Size | 7-10 MB | 2-3 MB + libs |
|
||||||
|
| Startup Time | ~50ms | ~40ms |
|
||||||
|
| Memory Usage | Similar | Similar |
|
||||||
|
| Portability | ✓ Universal | ✗ System-dependent |
|
||||||
|
| Deployment | Single file | Binary + libraries |
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [MUSL libc](https://musl.libc.org/)
|
||||||
|
- [Alpine Linux](https://alpinelinux.org/)
|
||||||
|
- [nostr_core_lib](https://github.com/chebizarro/nostr_core_lib)
|
||||||
|
- [Static Linking Best Practices](https://www.musl-libc.org/faq.html)
|
||||||
|
- [c-relay Implementation](./docs/musl_static_build.md)
|
||||||
|
|
||||||
|
## Example: Minimal Nostr Client
|
||||||
|
|
||||||
|
Here's a complete example of building a minimal Nostr client:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// minimal_client.c
|
||||||
|
#include "nostr_core/nostr_core.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Generate keypair
|
||||||
|
char nsec[64], npub[64];
|
||||||
|
nostr_generate_keypair(nsec, npub);
|
||||||
|
|
||||||
|
printf("Generated keypair:\n");
|
||||||
|
printf("Private key (nsec): %s\n", nsec);
|
||||||
|
printf("Public key (npub): %s\n", npub);
|
||||||
|
|
||||||
|
// Create event
|
||||||
|
cJSON *event = nostr_create_event(1, "Hello, Nostr!", NULL);
|
||||||
|
nostr_sign_event(event, nsec);
|
||||||
|
|
||||||
|
char *json = cJSON_Print(event);
|
||||||
|
printf("\nSigned event:\n%s\n", json);
|
||||||
|
|
||||||
|
free(json);
|
||||||
|
cJSON_Delete(event);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dockerfile.static:**
|
||||||
|
```dockerfile
|
||||||
|
FROM alpine:3.19 AS builder
|
||||||
|
RUN apk add --no-cache build-base musl-dev git autoconf automake libtool \
|
||||||
|
openssl-dev openssl-libs-static zlib-dev zlib-static
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Build secp256k1
|
||||||
|
RUN cd /tmp && git clone https://github.com/bitcoin-core/secp256k1.git && \
|
||||||
|
cd secp256k1 && ./autogen.sh && \
|
||||||
|
./configure --enable-static --disable-shared --prefix=/usr CFLAGS="-fPIC" && \
|
||||||
|
make -j$(nproc) && make install
|
||||||
|
|
||||||
|
# Copy and build nostr_core_lib
|
||||||
|
COPY nostr_core_lib /build/nostr_core_lib/
|
||||||
|
RUN cd nostr_core_lib && \
|
||||||
|
sed -i 's/CFLAGS="-Wall/CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall/' build.sh && \
|
||||||
|
./build.sh --nips=1,6,19
|
||||||
|
|
||||||
|
# Build application
|
||||||
|
COPY minimal_client.c /build/
|
||||||
|
RUN gcc -static -O2 -Wall -std=c99 \
|
||||||
|
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
|
||||||
|
-Inostr_core_lib -Inostr_core_lib/nostr_core -Inostr_core_lib/cjson \
|
||||||
|
minimal_client.c -o /build/minimal_client_static \
|
||||||
|
nostr_core_lib/libnostr_core_x64.a \
|
||||||
|
-lssl -lcrypto -lsecp256k1 -lz -lpthread -lm -ldl && \
|
||||||
|
strip /build/minimal_client_static
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=builder /build/minimal_client_static /minimal_client_static
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build and run:**
|
||||||
|
```bash
|
||||||
|
docker build -f Dockerfile.static -t minimal-client .
|
||||||
|
docker create --name temp minimal-client
|
||||||
|
docker cp temp:/minimal_client_static ./
|
||||||
|
docker rm temp
|
||||||
|
|
||||||
|
./minimal_client_static
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Static MUSL binaries provide the best portability for C applications. While they're slightly larger than dynamic binaries, the benefits of zero dependencies and universal compatibility make them ideal for:
|
||||||
|
|
||||||
|
- Server deployments across different Linux distributions
|
||||||
|
- Embedded systems and IoT devices
|
||||||
|
- Docker containers (FROM scratch)
|
||||||
|
- Distribution to users without dependency management
|
||||||
|
- Long-term archival and reproducibility
|
||||||
|
|
||||||
|
Follow this guide to create portable, self-contained binaries for your Nostr applications!
|
||||||
BIN
build/bud04.o
BIN
build/bud04.o
Binary file not shown.
BIN
build/bud08.o
BIN
build/bud08.o
Binary file not shown.
Binary file not shown.
BIN
build/main.o
BIN
build/main.o
Binary file not shown.
@@ -351,10 +351,10 @@ http {
|
|||||||
autoindex_format json;
|
autoindex_format json;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Root redirect
|
# Root redirect - Server info endpoint
|
||||||
location = / {
|
location = / {
|
||||||
return 200 "Ginxsom Blossom Server\nEndpoints: GET /<sha256>, PUT /upload, GET /list/<pubkey>\nHealth: GET /health\n";
|
return 200 '{\n "server": "ginxsom",\n "version": "1.0.0",\n "description": "Ginxsom Blossom Server",\n "endpoints": {\n "blob_get": "GET /<sha256>",\n "blob_head": "HEAD /<sha256>",\n "upload": "PUT /upload",\n "upload_requirements": "HEAD /upload",\n "list": "GET /list/<pubkey>",\n "delete": "DELETE /<sha256>",\n "mirror": "PUT /mirror",\n "report": "PUT /report",\n "health": "GET /health"\n },\n "supported_buds": [\n "BUD-01",\n "BUD-02",\n "BUD-04",\n "BUD-06",\n "BUD-08",\n "BUD-09"\n ],\n "limits": {\n "max_upload_size": 104857600,\n "supported_mime_types": [\n "image/jpeg",\n "image/png",\n "image/webp",\n "image/gif",\n "video/mp4",\n "video/webm",\n "audio/mpeg",\n "audio/ogg",\n "text/plain",\n "application/pdf"\n ]\n },\n "authentication": {\n "required_for_upload": false,\n "required_for_delete": true,\n "required_for_list": false,\n "nip42_enabled": true\n }\n}';
|
||||||
add_header Content-Type text/plain;
|
add_header Content-Type application/json;
|
||||||
add_header Access-Control-Allow-Origin * always;
|
add_header Access-Control-Allow-Origin * always;
|
||||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
|
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
|
||||||
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
|
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
|
||||||
@@ -683,10 +683,10 @@ http {
|
|||||||
autoindex_format json;
|
autoindex_format json;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Root redirect
|
# Root redirect - Server info endpoint
|
||||||
location = / {
|
location = / {
|
||||||
return 200 "Ginxsom Blossom Server (HTTPS)\nEndpoints: GET /<sha256>, PUT /upload, GET /list/<pubkey>\nHealth: GET /health\n";
|
return 200 '{\n "server": "ginxsom",\n "version": "1.0.0",\n "description": "Ginxsom Blossom Server (HTTPS)",\n "endpoints": {\n "blob_get": "GET /<sha256>",\n "blob_head": "HEAD /<sha256>",\n "upload": "PUT /upload",\n "upload_requirements": "HEAD /upload",\n "list": "GET /list/<pubkey>",\n "delete": "DELETE /<sha256>",\n "mirror": "PUT /mirror",\n "report": "PUT /report",\n "health": "GET /health"\n },\n "supported_buds": [\n "BUD-01",\n "BUD-02",\n "BUD-04",\n "BUD-06",\n "BUD-08",\n "BUD-09"\n ],\n "limits": {\n "max_upload_size": 104857600,\n "supported_mime_types": [\n "image/jpeg",\n "image/png",\n "image/webp",\n "image/gif",\n "video/mp4",\n "video/webm",\n "audio/mpeg",\n "audio/ogg",\n "text/plain",\n "application/pdf"\n ]\n },\n "authentication": {\n "required_for_upload": false,\n "required_for_delete": true,\n "required_for_list": false,\n "nip42_enabled": true\n }\n}';
|
||||||
add_header Content-Type text/plain;
|
add_header Content-Type application/json;
|
||||||
add_header Access-Control-Allow-Origin * always;
|
add_header Access-Control-Allow-Origin * always;
|
||||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
|
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
|
||||||
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
|
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
|
||||||
|
|||||||
BIN
db/ginxsom.db
BIN
db/ginxsom.db
Binary file not shown.
285
deploy_lt.sh
Executable file
285
deploy_lt.sh
Executable file
@@ -0,0 +1,285 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
print_status() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||||
|
print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||||
|
print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||||
|
print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
REMOTE_HOST="laantungir.net"
|
||||||
|
REMOTE_USER="ubuntu"
|
||||||
|
REMOTE_DIR="/home/ubuntu/ginxsom"
|
||||||
|
REMOTE_DB_PATH="/home/ubuntu/ginxsom/db/ginxsom.db"
|
||||||
|
REMOTE_NGINX_CONFIG="/etc/nginx/conf.d/default.conf"
|
||||||
|
REMOTE_BINARY_PATH="/home/ubuntu/ginxsom/ginxsom.fcgi"
|
||||||
|
REMOTE_SOCKET="/tmp/ginxsom-fcgi.sock"
|
||||||
|
REMOTE_DATA_DIR="/var/www/html/blossom"
|
||||||
|
|
||||||
|
print_status "Starting deployment to $REMOTE_HOST..."
|
||||||
|
|
||||||
|
# Step 1: Build and prepare local binary
|
||||||
|
print_status "Building ginxsom binary..."
|
||||||
|
make clean && make
|
||||||
|
if [[ ! -f "build/ginxsom-fcgi" ]]; then
|
||||||
|
print_error "Build failed - binary not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
print_success "Binary built successfully"
|
||||||
|
|
||||||
|
# Step 2: Setup remote environment first (before copying files)
|
||||||
|
print_status "Setting up remote environment..."
|
||||||
|
ssh $REMOTE_USER@$REMOTE_HOST << 'EOF'
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Create data directory if it doesn't exist (using existing /var/www/html/blossom)
|
||||||
|
sudo mkdir -p /var/www/html/blossom
|
||||||
|
sudo chown www-data:www-data /var/www/html/blossom
|
||||||
|
sudo chmod 755 /var/www/html/blossom
|
||||||
|
|
||||||
|
# Ensure socket directory exists
|
||||||
|
sudo mkdir -p /tmp
|
||||||
|
sudo chmod 755 /tmp
|
||||||
|
|
||||||
|
# Install required dependencies
|
||||||
|
echo "Installing required dependencies..."
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y spawn-fcgi libfcgi-dev
|
||||||
|
|
||||||
|
# Stop any existing ginxsom processes
|
||||||
|
echo "Stopping existing ginxsom processes..."
|
||||||
|
sudo pkill -f ginxsom-fcgi || true
|
||||||
|
sudo rm -f /tmp/ginxsom-fcgi.sock || true
|
||||||
|
|
||||||
|
echo "Remote environment setup complete"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
print_success "Remote environment configured"
|
||||||
|
|
||||||
|
# Step 3: Copy files to remote server
|
||||||
|
print_status "Copying files to remote server..."
|
||||||
|
|
||||||
|
# Copy entire project directory (excluding unnecessary files)
|
||||||
|
print_status "Copying entire ginxsom project..."
|
||||||
|
rsync -avz --exclude='.git' --exclude='build' --exclude='logs' --exclude='Trash' --exclude='blobs' --exclude='db/ginxsom.db' --no-g --no-o --no-perms --omit-dir-times . $REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/
|
||||||
|
|
||||||
|
# Build on remote server to ensure compatibility
|
||||||
|
print_status "Building ginxsom on remote server..."
|
||||||
|
ssh $REMOTE_USER@$REMOTE_HOST "cd $REMOTE_DIR && make clean && make" || {
|
||||||
|
print_error "Build failed on remote server"
|
||||||
|
print_status "Checking what packages are actually installed..."
|
||||||
|
ssh $REMOTE_USER@$REMOTE_HOST "dpkg -l | grep -E '(sqlite|fcgi)'"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy binary to application directory
|
||||||
|
print_status "Copying ginxsom binary to application directory..."
|
||||||
|
ssh $REMOTE_USER@$REMOTE_HOST << EOF
|
||||||
|
# Stop any running process first
|
||||||
|
sudo pkill -f ginxsom-fcgi || true
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Remove old binary if it exists
|
||||||
|
rm -f $REMOTE_BINARY_PATH
|
||||||
|
|
||||||
|
# Copy new binary
|
||||||
|
cp $REMOTE_DIR/build/ginxsom-fcgi $REMOTE_BINARY_PATH
|
||||||
|
chmod +x $REMOTE_BINARY_PATH
|
||||||
|
chown ubuntu:ubuntu $REMOTE_BINARY_PATH
|
||||||
|
|
||||||
|
echo "Binary copied successfully"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Skip nginx config update - user prefers manual control
|
||||||
|
print_status "Skipping nginx configuration update (manual control preferred)"
|
||||||
|
|
||||||
|
print_success "Files copied to remote server"
|
||||||
|
|
||||||
|
# Step 3: Setup remote environment
|
||||||
|
print_status "Setting up remote environment..."
|
||||||
|
|
||||||
|
ssh $REMOTE_USER@$REMOTE_HOST << 'EOF'
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Create data directory if it doesn't exist (using existing /var/www/html/blossom)
|
||||||
|
sudo mkdir -p /var/www/html/blossom
|
||||||
|
sudo chown www-data:www-data /var/www/html/blossom
|
||||||
|
sudo chmod 755 /var/www/html/blossom
|
||||||
|
|
||||||
|
# Ensure socket directory exists
|
||||||
|
sudo mkdir -p /tmp
|
||||||
|
sudo chmod 755 /tmp
|
||||||
|
|
||||||
|
# Install required dependencies
|
||||||
|
echo "Installing required dependencies..."
|
||||||
|
sudo apt-get update 2>/dev/null || true # Continue even if apt update has issues
|
||||||
|
sudo apt-get install -y spawn-fcgi libfcgi-dev libsqlite3-dev sqlite3 libcurl4-openssl-dev
|
||||||
|
|
||||||
|
# Verify installations
|
||||||
|
echo "Verifying installations..."
|
||||||
|
if ! dpkg -l libsqlite3-dev >/dev/null 2>&1; then
|
||||||
|
echo "libsqlite3-dev not found, trying alternative..."
|
||||||
|
sudo apt-get install -y libsqlite3-dev || {
|
||||||
|
echo "Failed to install libsqlite3-dev"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
if ! dpkg -l libfcgi-dev >/dev/null 2>&1; then
|
||||||
|
echo "libfcgi-dev not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if sqlite3.h exists
|
||||||
|
if [ ! -f /usr/include/sqlite3.h ]; then
|
||||||
|
echo "sqlite3.h not found in /usr/include/"
|
||||||
|
find /usr -name "sqlite3.h" 2>/dev/null || echo "sqlite3.h not found anywhere"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stop any existing ginxsom processes
|
||||||
|
echo "Stopping existing ginxsom processes..."
|
||||||
|
sudo pkill -f ginxsom-fcgi || true
|
||||||
|
sudo rm -f /tmp/ginxsom-fcgi.sock || true
|
||||||
|
|
||||||
|
echo "Remote environment setup complete"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
print_success "Remote environment configured"
|
||||||
|
|
||||||
|
# Step 4: Setup database directory and migrate database
|
||||||
|
print_status "Setting up database directory..."
|
||||||
|
|
||||||
|
ssh $REMOTE_USER@$REMOTE_HOST << EOF
|
||||||
|
# Create db directory if it doesn't exist
|
||||||
|
mkdir -p $REMOTE_DIR/db
|
||||||
|
|
||||||
|
# Backup current database if it exists in old location
|
||||||
|
if [ -f /var/www/html/blossom/ginxsom.db ]; then
|
||||||
|
echo "Backing up existing database..."
|
||||||
|
cp /var/www/html/blossom/ginxsom.db /var/www/html/blossom/ginxsom.db.backup.\$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
|
# Migrate database to new location if not already there
|
||||||
|
if [ ! -f $REMOTE_DB_PATH ]; then
|
||||||
|
echo "Migrating database to new location..."
|
||||||
|
cp /var/www/html/blossom/ginxsom.db $REMOTE_DB_PATH
|
||||||
|
else
|
||||||
|
echo "Database already exists at new location"
|
||||||
|
fi
|
||||||
|
elif [ ! -f $REMOTE_DB_PATH ]; then
|
||||||
|
echo "No existing database found - will be created on first run"
|
||||||
|
else
|
||||||
|
echo "Database already exists at $REMOTE_DB_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set proper permissions - www-data needs write access to db directory for SQLite journal files
|
||||||
|
sudo chown -R www-data:www-data $REMOTE_DIR/db
|
||||||
|
sudo chmod 755 $REMOTE_DIR/db
|
||||||
|
sudo chmod 644 $REMOTE_DB_PATH 2>/dev/null || true
|
||||||
|
|
||||||
|
# Allow www-data to access the application directory for spawn-fcgi chdir
|
||||||
|
chmod 755 $REMOTE_DIR
|
||||||
|
|
||||||
|
echo "Database directory setup complete"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
print_success "Database directory configured"
|
||||||
|
|
||||||
|
# Step 5: Start ginxsom FastCGI process
|
||||||
|
print_status "Starting ginxsom FastCGI process..."
|
||||||
|
|
||||||
|
ssh $REMOTE_USER@$REMOTE_HOST << EOF
|
||||||
|
# Clean up any existing socket
|
||||||
|
sudo rm -f $REMOTE_SOCKET
|
||||||
|
|
||||||
|
# Start FastCGI process with explicit paths
|
||||||
|
echo "Starting ginxsom FastCGI with configuration:"
|
||||||
|
echo " Working directory: $REMOTE_DIR"
|
||||||
|
echo " Binary: $REMOTE_BINARY_PATH"
|
||||||
|
echo " Database: $REMOTE_DB_PATH"
|
||||||
|
echo " Storage: $REMOTE_DATA_DIR"
|
||||||
|
|
||||||
|
sudo spawn-fcgi -M 666 -u www-data -g www-data -s $REMOTE_SOCKET -U www-data -G www-data -d $REMOTE_DIR -- $REMOTE_BINARY_PATH --db-path "$REMOTE_DB_PATH" --storage-dir "$REMOTE_DATA_DIR"
|
||||||
|
|
||||||
|
# Give it a moment to start
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Verify process is running
|
||||||
|
if pgrep -f "ginxsom-fcgi" > /dev/null; then
|
||||||
|
echo "FastCGI process started successfully"
|
||||||
|
echo "PID: \$(pgrep -f ginxsom-fcgi)"
|
||||||
|
else
|
||||||
|
echo "Process not found by pgrep, but socket exists - this may be normal for FastCGI"
|
||||||
|
echo "Checking socket..."
|
||||||
|
ls -la $REMOTE_SOCKET
|
||||||
|
echo "Checking if binary exists and is executable..."
|
||||||
|
ls -la $REMOTE_BINARY_PATH
|
||||||
|
echo "Testing if we can connect to the socket..."
|
||||||
|
# Try to test the FastCGI connection
|
||||||
|
if command -v cgi-fcgi >/dev/null 2>&1; then
|
||||||
|
echo "Testing FastCGI connection..."
|
||||||
|
SCRIPT_NAME=/health SCRIPT_FILENAME=$REMOTE_BINARY_PATH REQUEST_METHOD=GET cgi-fcgi -bind -connect $REMOTE_SOCKET 2>/dev/null | head -5 || echo "Connection test failed"
|
||||||
|
else
|
||||||
|
echo "cgi-fcgi not available for testing"
|
||||||
|
fi
|
||||||
|
# Don't exit - the socket existing means spawn-fcgi worked
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
print_success "FastCGI process started"
|
||||||
|
else
|
||||||
|
print_error "Failed to start FastCGI process"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 6: Test nginx configuration and reload
|
||||||
|
print_status "Testing and reloading nginx..."
|
||||||
|
|
||||||
|
ssh $REMOTE_USER@$REMOTE_HOST << 'EOF'
|
||||||
|
# Test nginx configuration
|
||||||
|
if sudo nginx -t; then
|
||||||
|
echo "Nginx configuration test passed"
|
||||||
|
sudo nginx -s reload
|
||||||
|
echo "Nginx reloaded successfully"
|
||||||
|
else
|
||||||
|
echo "Nginx configuration test failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
print_success "Nginx reloaded"
|
||||||
|
|
||||||
|
# Step 7: Test deployment
|
||||||
|
print_status "Testing deployment..."
|
||||||
|
|
||||||
|
# Test health endpoint
|
||||||
|
echo "Testing health endpoint..."
|
||||||
|
if curl -k -s --max-time 10 "https://blossom.laantungir.net/health" | grep -q "OK"; then
|
||||||
|
print_success "Health check passed"
|
||||||
|
else
|
||||||
|
print_warning "Health check failed - checking response..."
|
||||||
|
curl -k -v --max-time 10 "https://blossom.laantungir.net/health" 2>&1 | head -10
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test basic endpoints
|
||||||
|
echo "Testing root endpoint..."
|
||||||
|
if curl -k -s --max-time 10 "https://blossom.laantungir.net/" | grep -q "Ginxsom"; then
|
||||||
|
print_success "Root endpoint responding"
|
||||||
|
else
|
||||||
|
print_warning "Root endpoint not responding as expected - checking response..."
|
||||||
|
curl -k -v --max-time 10 "https://blossom.laantungir.net/" 2>&1 | head -10
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_success "Deployment to $REMOTE_HOST completed!"
|
||||||
|
print_status "Ginxsom should now be available at: https://blossom.laantungir.net"
|
||||||
|
print_status "Test endpoints:"
|
||||||
|
echo " Health: curl -k https://blossom.laantungir.net/health"
|
||||||
|
echo " Root: curl -k https://blossom.laantungir.net/"
|
||||||
|
echo " List: curl -k https://blossom.laantungir.net/list"
|
||||||
356
docs/PRODUCTION_MIGRATION_PLAN.md
Normal file
356
docs/PRODUCTION_MIGRATION_PLAN.md
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
# Production Directory Structure Migration Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document outlines the plan to migrate the ginxsom production deployment from the current configuration to a new, more organized directory structure.
|
||||||
|
|
||||||
|
## Current Configuration (As-Is)
|
||||||
|
|
||||||
|
```
|
||||||
|
Binary Location: /var/www/html/blossom/ginxsom.fcgi
|
||||||
|
Database Location: /var/www/html/blossom/ginxsom.db
|
||||||
|
Data Directory: /var/www/html/blossom/
|
||||||
|
Working Directory: /var/www/html/blossom/ (set via spawn-fcgi -d)
|
||||||
|
Socket: /tmp/ginxsom-fcgi.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
**Issues with Current Setup:**
|
||||||
|
1. Binary and database mixed with data files in web-accessible directory
|
||||||
|
2. Database path hardcoded as relative path `db/ginxsom.db` but database is at root of working directory
|
||||||
|
3. No separation between application files and user data
|
||||||
|
4. Security concern: application files in web root
|
||||||
|
|
||||||
|
## Target Configuration (To-Be)
|
||||||
|
|
||||||
|
```
|
||||||
|
Binary Location: /home/ubuntu/ginxsom/ginxsom.fcgi
|
||||||
|
Database Location: /home/ubuntu/ginxsom/db/ginxsom.db
|
||||||
|
Data Directory: /var/www/html/blossom/
|
||||||
|
Working Directory: /home/ubuntu/ginxsom/ (set via spawn-fcgi -d)
|
||||||
|
Socket: /tmp/ginxsom-fcgi.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits of New Setup:**
|
||||||
|
1. Application files separated from user data
|
||||||
|
2. Database in proper subdirectory structure
|
||||||
|
3. Application files outside web root (better security)
|
||||||
|
4. Clear separation of concerns
|
||||||
|
5. Easier backup and maintenance
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
### Application Directory: `/home/ubuntu/ginxsom/`
|
||||||
|
```
|
||||||
|
/home/ubuntu/ginxsom/
|
||||||
|
├── ginxsom.fcgi # FastCGI binary
|
||||||
|
├── db/
|
||||||
|
│ └── ginxsom.db # SQLite database
|
||||||
|
├── build/ # Build artifacts (from rsync)
|
||||||
|
├── src/ # Source code (from rsync)
|
||||||
|
├── include/ # Headers (from rsync)
|
||||||
|
├── config/ # Config files (from rsync)
|
||||||
|
└── scripts/ # Utility scripts (from rsync)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Directory: `/var/www/html/blossom/`
|
||||||
|
```
|
||||||
|
/var/www/html/blossom/
|
||||||
|
├── <sha256>.jpg # User uploaded files
|
||||||
|
├── <sha256>.png
|
||||||
|
├── <sha256>.mp4
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Command-Line Arguments
|
||||||
|
|
||||||
|
The ginxsom binary supports these arguments (from [`src/main.c`](src/main.c:1488-1509)):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
--db-path PATH # Database file path (default: db/ginxsom.db)
|
||||||
|
--storage-dir DIR # Storage directory for files (default: .)
|
||||||
|
--help, -h # Show help message
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration Steps
|
||||||
|
|
||||||
|
### 1. Update deploy_lt.sh Configuration
|
||||||
|
|
||||||
|
Update the configuration variables in [`deploy_lt.sh`](deploy_lt.sh:16-23):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Configuration
|
||||||
|
REMOTE_HOST="laantungir.net"
|
||||||
|
REMOTE_USER="ubuntu"
|
||||||
|
REMOTE_DIR="/home/ubuntu/ginxsom"
|
||||||
|
REMOTE_DB_PATH="/home/ubuntu/ginxsom/db/ginxsom.db"
|
||||||
|
REMOTE_NGINX_CONFIG="/etc/nginx/conf.d/default.conf"
|
||||||
|
REMOTE_BINARY_PATH="/home/ubuntu/ginxsom/ginxsom.fcgi"
|
||||||
|
REMOTE_SOCKET="/tmp/ginxsom-fcgi.sock"
|
||||||
|
REMOTE_DATA_DIR="/var/www/html/blossom"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Update Binary Deployment
|
||||||
|
|
||||||
|
Modify the binary copy section (lines 82-97) to use new path:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy binary to application directory (not web directory)
|
||||||
|
print_status "Copying ginxsom binary to application directory..."
|
||||||
|
ssh $REMOTE_USER@$REMOTE_HOST << EOF
|
||||||
|
# Stop any running process first
|
||||||
|
sudo pkill -f ginxsom-fcgi || true
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Remove old binary if it exists
|
||||||
|
rm -f $REMOTE_BINARY_PATH
|
||||||
|
|
||||||
|
# Copy new binary
|
||||||
|
cp $REMOTE_DIR/build/ginxsom-fcgi $REMOTE_BINARY_PATH
|
||||||
|
chmod +x $REMOTE_BINARY_PATH
|
||||||
|
chown ubuntu:ubuntu $REMOTE_BINARY_PATH
|
||||||
|
|
||||||
|
echo "Binary copied successfully"
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Create Database Directory Structure
|
||||||
|
|
||||||
|
Add database setup before starting FastCGI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Setup database directory
|
||||||
|
print_status "Setting up database directory..."
|
||||||
|
ssh $REMOTE_USER@$REMOTE_HOST << EOF
|
||||||
|
# Create db directory if it doesn't exist
|
||||||
|
mkdir -p $REMOTE_DIR/db
|
||||||
|
|
||||||
|
# Copy database if it exists in old location
|
||||||
|
if [ -f /var/www/html/blossom/ginxsom.db ]; then
|
||||||
|
echo "Migrating database from old location..."
|
||||||
|
cp /var/www/html/blossom/ginxsom.db $REMOTE_DB_PATH
|
||||||
|
elif [ ! -f $REMOTE_DB_PATH ]; then
|
||||||
|
echo "Initializing new database..."
|
||||||
|
# Database will be created by application on first run
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set proper permissions
|
||||||
|
chown -R ubuntu:ubuntu $REMOTE_DIR/db
|
||||||
|
chmod 755 $REMOTE_DIR/db
|
||||||
|
chmod 644 $REMOTE_DB_PATH 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "Database directory setup complete"
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Update spawn-fcgi Command
|
||||||
|
|
||||||
|
Modify the FastCGI startup (line 164) to include command-line arguments:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start FastCGI process with explicit paths
|
||||||
|
echo "Starting ginxsom FastCGI..."
|
||||||
|
sudo spawn-fcgi \
|
||||||
|
-M 666 \
|
||||||
|
-u www-data \
|
||||||
|
-g www-data \
|
||||||
|
-s $REMOTE_SOCKET \
|
||||||
|
-U www-data \
|
||||||
|
-G www-data \
|
||||||
|
-d $REMOTE_DIR \
|
||||||
|
-- $REMOTE_BINARY_PATH \
|
||||||
|
--db-path "$REMOTE_DB_PATH" \
|
||||||
|
--storage-dir "$REMOTE_DATA_DIR"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Changes:**
|
||||||
|
- `-d $REMOTE_DIR`: Sets working directory to `/home/ubuntu/ginxsom/`
|
||||||
|
- `--db-path "$REMOTE_DB_PATH"`: Explicit database path
|
||||||
|
- `--storage-dir "$REMOTE_DATA_DIR"`: Explicit data directory
|
||||||
|
|
||||||
|
### 5. Verify Permissions
|
||||||
|
|
||||||
|
Ensure proper permissions for all directories:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Application directory - owned by ubuntu
|
||||||
|
sudo chown -R ubuntu:ubuntu /home/ubuntu/ginxsom
|
||||||
|
sudo chmod 755 /home/ubuntu/ginxsom
|
||||||
|
sudo chmod +x /home/ubuntu/ginxsom/ginxsom.fcgi
|
||||||
|
|
||||||
|
# Database directory - readable by www-data
|
||||||
|
sudo chmod 755 /home/ubuntu/ginxsom/db
|
||||||
|
sudo chmod 644 /home/ubuntu/ginxsom/db/ginxsom.db
|
||||||
|
|
||||||
|
# Data directory - writable by www-data
|
||||||
|
sudo chown -R www-data:www-data /var/www/html/blossom
|
||||||
|
sudo chmod 755 /var/www/html/blossom
|
||||||
|
```
|
||||||
|
|
||||||
|
## Path Resolution Logic
|
||||||
|
|
||||||
|
### How Paths Work with spawn-fcgi -d Option
|
||||||
|
|
||||||
|
When spawn-fcgi starts the FastCGI process:
|
||||||
|
|
||||||
|
1. **Working Directory**: Set to `/home/ubuntu/ginxsom/` via `-d` option
|
||||||
|
2. **Relative Paths**: Resolved from working directory
|
||||||
|
3. **Absolute Paths**: Used as-is
|
||||||
|
|
||||||
|
### Default Behavior (Without Arguments)
|
||||||
|
|
||||||
|
From [`src/main.c`](src/main.c:30-31):
|
||||||
|
```c
|
||||||
|
char g_db_path[MAX_PATH_LEN] = "db/ginxsom.db"; // Relative to working dir
|
||||||
|
char g_storage_dir[MAX_PATH_LEN] = "."; // Current working dir
|
||||||
|
```
|
||||||
|
|
||||||
|
With working directory `/home/ubuntu/ginxsom/`:
|
||||||
|
- Database: `/home/ubuntu/ginxsom/db/ginxsom.db` ✓
|
||||||
|
- Storage: `/home/ubuntu/ginxsom/` ✗ (wrong - we want `/var/www/html/blossom/`)
|
||||||
|
|
||||||
|
### With Command-Line Arguments
|
||||||
|
|
||||||
|
```bash
|
||||||
|
--db-path "/home/ubuntu/ginxsom/db/ginxsom.db"
|
||||||
|
--storage-dir "/var/www/html/blossom"
|
||||||
|
```
|
||||||
|
|
||||||
|
Result:
|
||||||
|
- Database: `/home/ubuntu/ginxsom/db/ginxsom.db` ✓
|
||||||
|
- Storage: `/var/www/html/blossom/` ✓
|
||||||
|
|
||||||
|
## Testing Plan
|
||||||
|
|
||||||
|
### 1. Pre-Migration Verification
|
||||||
|
```bash
|
||||||
|
# Check current setup
|
||||||
|
ssh ubuntu@laantungir.net "
|
||||||
|
echo 'Current binary location:'
|
||||||
|
ls -la /var/www/html/blossom/ginxsom.fcgi
|
||||||
|
|
||||||
|
echo 'Current database location:'
|
||||||
|
ls -la /var/www/html/blossom/ginxsom.db
|
||||||
|
|
||||||
|
echo 'Current process:'
|
||||||
|
ps aux | grep ginxsom-fcgi | grep -v grep
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Post-Migration Verification
|
||||||
|
```bash
|
||||||
|
# Check new setup
|
||||||
|
ssh ubuntu@laantungir.net "
|
||||||
|
echo 'New binary location:'
|
||||||
|
ls -la /home/ubuntu/ginxsom/ginxsom.fcgi
|
||||||
|
|
||||||
|
echo 'New database location:'
|
||||||
|
ls -la /home/ubuntu/ginxsom/db/ginxsom.db
|
||||||
|
|
||||||
|
echo 'Data directory:'
|
||||||
|
ls -la /var/www/html/blossom/ | head -10
|
||||||
|
|
||||||
|
echo 'Process working directory:'
|
||||||
|
sudo ls -la /proc/\$(pgrep -f ginxsom.fcgi)/cwd
|
||||||
|
|
||||||
|
echo 'Process command line:'
|
||||||
|
ps aux | grep ginxsom-fcgi | grep -v grep
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Functional Testing
|
||||||
|
```bash
|
||||||
|
# Test health endpoint
|
||||||
|
curl -k https://blossom.laantungir.net/health
|
||||||
|
|
||||||
|
# Test file upload
|
||||||
|
./tests/file_put_production.sh
|
||||||
|
|
||||||
|
# Test file retrieval
|
||||||
|
curl -k -I https://blossom.laantungir.net/<sha256>
|
||||||
|
|
||||||
|
# Test list endpoint
|
||||||
|
curl -k https://blossom.laantungir.net/list/<pubkey>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rollback Plan
|
||||||
|
|
||||||
|
If migration fails:
|
||||||
|
|
||||||
|
1. **Stop new process:**
|
||||||
|
```bash
|
||||||
|
sudo pkill -f ginxsom-fcgi
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Restore old binary location:**
|
||||||
|
```bash
|
||||||
|
sudo cp /home/ubuntu/ginxsom/build/ginxsom-fcgi /var/www/html/blossom/ginxsom.fcgi
|
||||||
|
sudo chown www-data:www-data /var/www/html/blossom/ginxsom.fcgi
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Restart with old configuration:**
|
||||||
|
```bash
|
||||||
|
sudo spawn-fcgi -M 666 -u www-data -g www-data \
|
||||||
|
-s /tmp/ginxsom-fcgi.sock \
|
||||||
|
-U www-data -G www-data \
|
||||||
|
-d /var/www/html/blossom \
|
||||||
|
/var/www/html/blossom/ginxsom.fcgi
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional Considerations
|
||||||
|
|
||||||
|
### 1. Database Backup
|
||||||
|
Before migration, backup the current database:
|
||||||
|
```bash
|
||||||
|
ssh ubuntu@laantungir.net "
|
||||||
|
cp /var/www/html/blossom/ginxsom.db /var/www/html/blossom/ginxsom.db.backup
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. NIP-94 Origin Configuration
|
||||||
|
After migration, update [`src/bud08.c`](src/bud08.c) to return production domain:
|
||||||
|
```c
|
||||||
|
void nip94_get_origin(char *origin, size_t origin_size) {
|
||||||
|
snprintf(origin, origin_size, "https://blossom.laantungir.net");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Monitoring
|
||||||
|
Monitor logs after migration:
|
||||||
|
```bash
|
||||||
|
# Application logs
|
||||||
|
ssh ubuntu@laantungir.net "sudo journalctl -u nginx -f"
|
||||||
|
|
||||||
|
# FastCGI process
|
||||||
|
ssh ubuntu@laantungir.net "ps aux | grep ginxsom-fcgi"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
Migration is successful when:
|
||||||
|
|
||||||
|
1. ✓ Binary running from `/home/ubuntu/ginxsom/ginxsom.fcgi`
|
||||||
|
2. ✓ Database accessible at `/home/ubuntu/ginxsom/db/ginxsom.db`
|
||||||
|
3. ✓ Files stored in `/var/www/html/blossom/`
|
||||||
|
4. ✓ Health endpoint returns 200 OK
|
||||||
|
5. ✓ File upload works correctly
|
||||||
|
6. ✓ File retrieval works correctly
|
||||||
|
7. ✓ Database queries succeed
|
||||||
|
8. ✓ No permission errors in logs
|
||||||
|
|
||||||
|
## Timeline
|
||||||
|
|
||||||
|
1. **Preparation**: Update deploy_lt.sh script (15 minutes)
|
||||||
|
2. **Backup**: Backup current database (5 minutes)
|
||||||
|
3. **Migration**: Run updated deployment script (10 minutes)
|
||||||
|
4. **Testing**: Verify all endpoints (15 minutes)
|
||||||
|
5. **Monitoring**: Watch for issues (30 minutes)
|
||||||
|
|
||||||
|
**Total Estimated Time**: ~75 minutes
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- Current deployment script: [`deploy_lt.sh`](deploy_lt.sh)
|
||||||
|
- Main application: [`src/main.c`](src/main.c)
|
||||||
|
- Command-line parsing: [`src/main.c:1488-1509`](src/main.c:1488-1509)
|
||||||
|
- Global configuration: [`src/main.c:30-31`](src/main.c:30-31)
|
||||||
|
- Database operations: [`src/main.c:333-385`](src/main.c:333-385)
|
||||||
@@ -33,6 +33,10 @@
|
|||||||
#define DEFAULT_MAX_BLOBS_PER_USER 1000
|
#define DEFAULT_MAX_BLOBS_PER_USER 1000
|
||||||
#define DEFAULT_RATE_LIMIT 10
|
#define DEFAULT_RATE_LIMIT 10
|
||||||
|
|
||||||
|
/* Global configuration variables */
|
||||||
|
extern char g_db_path[MAX_PATH_LEN];
|
||||||
|
extern char g_storage_dir[MAX_PATH_LEN];
|
||||||
|
|
||||||
/* Error codes */
|
/* Error codes */
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GINXSOM_OK = 0,
|
GINXSOM_OK = 0,
|
||||||
|
|||||||
376
remote.nginx.config
Normal file
376
remote.nginx.config
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
# FastCGI upstream configuration
|
||||||
|
upstream ginxsom_backend {
|
||||||
|
server unix:/tmp/ginxsom-fcgi.sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main domains
|
||||||
|
server {
|
||||||
|
if ($host = laantungir.net) {
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
} # managed by Certbot
|
||||||
|
|
||||||
|
|
||||||
|
listen 80;
|
||||||
|
server_name laantungir.com www.laantungir.com laantungir.net www.laantungir.net laantungir.org www.laantungir.org;
|
||||||
|
|
||||||
|
root /var/www/html;
|
||||||
|
index index.html index.htm;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 404 /404.html;
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /var/www/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main domains HTTPS - using the main certificate
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name laantungir.com www.laantungir.com laantungir.net www.laantungir.net laantungir.org www.laantungir.org;
|
||||||
|
ssl_certificate /etc/letsencrypt/live/laantungir.net/fullchain.pem; # managed by Certbot
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/laantungir.net/privkey.pem; # managed by Certbot
|
||||||
|
|
||||||
|
root /var/www/html;
|
||||||
|
index index.html index.htm;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 404 /404.html;
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /var/www/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Blossom subdomains HTTP - redirect to HTTPS (keep for ACME)
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name blossom.laantungir.com blossom.laantungir.net blossom.laantungir.org;
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Blossom subdomains HTTPS - ginxsom FastCGI
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name blossom.laantungir.com blossom.laantungir.net blossom.laantungir.org;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/git.laantungir.net/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/git.laantungir.net/privkey.pem;
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Content-Type-Options nosniff always;
|
||||||
|
add_header X-Frame-Options DENY always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
|
||||||
|
# CORS for Blossom protocol
|
||||||
|
add_header Access-Control-Allow-Origin * always;
|
||||||
|
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
|
||||||
|
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
|
||||||
|
add_header Access-Control-Max-Age 86400 always;
|
||||||
|
|
||||||
|
# Root directory for blob storage
|
||||||
|
root /var/www/html/blossom;
|
||||||
|
|
||||||
|
# Maximum upload size
|
||||||
|
client_max_body_size 100M;
|
||||||
|
|
||||||
|
# OPTIONS preflight handler
|
||||||
|
if ($request_method = OPTIONS) {
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
|
||||||
|
# PUT /upload - File uploads
|
||||||
|
location = /upload {
|
||||||
|
if ($request_method !~ ^(PUT|HEAD)$) {
|
||||||
|
return 405;
|
||||||
|
}
|
||||||
|
fastcgi_pass ginxsom_backend;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
|
||||||
|
}
|
||||||
|
|
||||||
|
# GET /list/<pubkey> - List user blobs
|
||||||
|
location ~ "^/list/([a-f0-9]{64})$" {
|
||||||
|
if ($request_method !~ ^(GET)$) {
|
||||||
|
return 405;
|
||||||
|
}
|
||||||
|
fastcgi_pass ginxsom_backend;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
|
||||||
|
}
|
||||||
|
|
||||||
|
# PUT /mirror - Mirror content
|
||||||
|
location = /mirror {
|
||||||
|
if ($request_method !~ ^(PUT)$) {
|
||||||
|
return 405;
|
||||||
|
}
|
||||||
|
fastcgi_pass ginxsom_backend;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
|
||||||
|
}
|
||||||
|
|
||||||
|
# PUT /report - Report content
|
||||||
|
location = /report {
|
||||||
|
if ($request_method !~ ^(PUT)$) {
|
||||||
|
return 405;
|
||||||
|
}
|
||||||
|
fastcgi_pass ginxsom_backend;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
|
||||||
|
}
|
||||||
|
|
||||||
|
# GET /auth - NIP-42 challenges
|
||||||
|
location = /auth {
|
||||||
|
if ($request_method !~ ^(GET)$) {
|
||||||
|
return 405;
|
||||||
|
}
|
||||||
|
fastcgi_pass ginxsom_backend;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Admin API
|
||||||
|
location /api/ {
|
||||||
|
if ($request_method !~ ^(GET|PUT)$) {
|
||||||
|
return 405;
|
||||||
|
}
|
||||||
|
fastcgi_pass ginxsom_backend;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Blob serving - SHA256 patterns
|
||||||
|
location ~ "^/([a-f0-9]{64})(\.[a-zA-Z0-9]+)?$" {
|
||||||
|
# Handle DELETE via rewrite
|
||||||
|
if ($request_method = DELETE) {
|
||||||
|
rewrite ^/(.*)$ /fcgi-delete/$1 last;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Route HEAD to FastCGI
|
||||||
|
if ($request_method = HEAD) {
|
||||||
|
rewrite ^/(.*)$ /fcgi-head/$1 last;
|
||||||
|
}
|
||||||
|
|
||||||
|
# GET requests - serve files directly
|
||||||
|
if ($request_method != GET) {
|
||||||
|
return 405;
|
||||||
|
}
|
||||||
|
|
||||||
|
try_files /$1.txt /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.md =404;
|
||||||
|
|
||||||
|
# Cache headers
|
||||||
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Internal FastCGI handlers
|
||||||
|
location ~ "^/fcgi-delete/([a-f0-9]{64}).*$" {
|
||||||
|
internal;
|
||||||
|
fastcgi_pass ginxsom_backend;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
|
||||||
|
fastcgi_param REQUEST_URI /$1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ "^/fcgi-head/([a-f0-9]{64}).*$" {
|
||||||
|
internal;
|
||||||
|
fastcgi_pass ginxsom_backend;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
|
||||||
|
fastcgi_param REQUEST_URI /$1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
location /health {
|
||||||
|
access_log off;
|
||||||
|
return 200 "OK\n";
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
add_header Access-Control-Allow-Origin * always;
|
||||||
|
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
|
||||||
|
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
|
||||||
|
add_header Access-Control-Max-Age 86400 always;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Default location - Server info endpoint
|
||||||
|
location / {
|
||||||
|
return 200 '{\n "server": "ginxsom",\n "version": "1.0.0",\n "description": "Ginxsom Blossom Server",\n "endpoints": {\n "blob_get": "GET /<sha256>",\n "blob_head": "HEAD /<sha256>",\n "upload": "PUT /upload",\n "upload_requirements": "HEAD /upload",\n "list": "GET /list/<pubkey>",\n "delete": "DELETE /<sha256>",\n "mirror": "PUT /mirror",\n "report": "PUT /report",\n "health": "GET /health"\n },\n "supported_buds": [\n "BUD-01",\n "BUD-02",\n "BUD-04",\n "BUD-06",\n "BUD-08",\n "BUD-09"\n ],\n "limits": {\n "max_upload_size": 104857600,\n "supported_mime_types": [\n "image/jpeg",\n "image/png",\n "image/webp",\n "image/gif",\n "video/mp4",\n "video/webm",\n "audio/mpeg",\n "audio/ogg",\n "text/plain",\n "application/pdf"\n ]\n },\n "authentication": {\n "required_for_upload": false,\n "required_for_delete": true,\n "required_for_list": false,\n "nip42_enabled": true\n }\n}';
|
||||||
|
add_header Content-Type application/json;
|
||||||
|
add_header Access-Control-Allow-Origin * always;
|
||||||
|
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always;
|
||||||
|
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always;
|
||||||
|
add_header Access-Control-Max-Age 86400 always;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name relay.laantungir.com relay.laantungir.net relay.laantungir.org;
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:8888;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header Sec-WebSocket-Key $http_sec_websocket_key;
|
||||||
|
proxy_set_header Sec-WebSocket-Version $http_sec_websocket_version;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
proxy_read_timeout 86400s;
|
||||||
|
proxy_send_timeout 86400s;
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
gzip off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# # Relay HTTPS - proxy to c-relay
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name relay.laantungir.com relay.laantungir.net relay.laantungir.org;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/blossom.laantungir.net/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/blossom.laantungir.net/privkey.pem;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:8888;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header Sec-WebSocket-Key $http_sec_websocket_key;
|
||||||
|
proxy_set_header Sec-WebSocket-Version $http_sec_websocket_version;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
proxy_read_timeout 86400s;
|
||||||
|
proxy_send_timeout 86400s;
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
gzip off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Git subdomains HTTP - redirect to HTTPS
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name git.laantungir.com git.laantungir.net git.laantungir.org;
|
||||||
|
|
||||||
|
# Allow larger file uploads for Git releases
|
||||||
|
client_max_body_size 50M;
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Auth subdomains HTTP - redirect to HTTPS
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name auth.laantungir.com auth.laantungir.net auth.laantungir.org;
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Git subdomains HTTPS - proxy to gitea
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name git.laantungir.com git.laantungir.net git.laantungir.org;
|
||||||
|
|
||||||
|
# Allow larger file uploads for Git releases
|
||||||
|
client_max_body_size 50M;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/git.laantungir.net/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/git.laantungir.net/privkey.pem;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:3000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
proxy_read_timeout 86400s;
|
||||||
|
proxy_send_timeout 86400s;
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
gzip off;
|
||||||
|
# proxy_set_header Sec-WebSocket-Extensions ;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header Sec-WebSocket-Key $http_sec_websocket_key;
|
||||||
|
proxy_set_header Sec-WebSocket-Version $http_sec_websocket_version;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Auth subdomains HTTPS - proxy to nostr-auth
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name auth.laantungir.com auth.laantungir.net auth.laantungir.org;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/git.laantungir.net/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/git.laantungir.net/privkey.pem;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:3001;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
proxy_read_timeout 86400s;
|
||||||
|
proxy_send_timeout 86400s;
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
gzip off;
|
||||||
|
# proxy_set_header Sec-WebSocket-Extensions ;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header Sec-WebSocket-Key $http_sec_websocket_key;
|
||||||
|
proxy_set_header Sec-WebSocket-Version $http_sec_websocket_version;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -168,7 +168,7 @@ export GINX_DEBUG=1
|
|||||||
|
|
||||||
# Start FastCGI application with proper logging (daemonized but with redirected streams)
|
# Start FastCGI application with proper logging (daemonized but with redirected streams)
|
||||||
echo "FastCGI starting at $(date)" >> logs/app/stderr.log
|
echo "FastCGI starting at $(date)" >> logs/app/stderr.log
|
||||||
spawn-fcgi -s "$SOCKET_PATH" -M 666 -u "$USER" -g "$USER" -f "$FCGI_BINARY" -P "$PID_FILE" 1>>logs/app/stdout.log 2>>logs/app/stderr.log
|
spawn-fcgi -s "$SOCKET_PATH" -M 666 -u "$USER" -g "$USER" -P "$PID_FILE" -- "$FCGI_BINARY" --storage-dir blobs 1>>logs/app/stdout.log 2>>logs/app/stderr.log
|
||||||
|
|
||||||
if [ $? -eq 0 ] && [ -f "$PID_FILE" ]; then
|
if [ $? -eq 0 ] && [ -f "$PID_FILE" ]; then
|
||||||
PID=$(cat "$PID_FILE")
|
PID=$(cat "$PID_FILE")
|
||||||
@@ -250,6 +250,8 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\n${GREEN}=== Restart sequence complete ===${NC}"
|
echo -e "\n${GREEN}=== Restart sequence complete ===${NC}"
|
||||||
echo -e "${YELLOW}Server should be available at: http://localhost:9001${NC}"
|
|
||||||
echo -e "${YELLOW}To stop all processes, run: nginx -p . -c $NGINX_CONFIG -s stop && kill \$(cat $PID_FILE 2>/dev/null)${NC}"
|
echo -e "${YELLOW}To stop all processes, run: nginx -p . -c $NGINX_CONFIG -s stop && kill \$(cat $PID_FILE 2>/dev/null)${NC}"
|
||||||
echo -e "${YELLOW}To monitor logs, check: logs/error.log, logs/access.log, and logs/fcgi-stderr.log${NC}"
|
echo -e "${YELLOW}To monitor logs, check: logs/nginx/error.log, logs/nginx/access.log, logs/app/stderr.log, logs/app/stdout.log${NC}"
|
||||||
|
echo -e "\n${YELLOW}Server is available at:${NC}"
|
||||||
|
echo -e " ${GREEN}HTTP:${NC} http://localhost:9001"
|
||||||
|
echo -e " ${GREEN}HTTPS:${NC} https://localhost:9443"
|
||||||
@@ -426,9 +426,9 @@ void handle_mirror_request(void) {
|
|||||||
// Determine file extension from Content-Type using centralized mapping
|
// Determine file extension from Content-Type using centralized mapping
|
||||||
const char* extension = mime_to_extension(content_type_final);
|
const char* extension = mime_to_extension(content_type_final);
|
||||||
|
|
||||||
// Save file to blobs directory
|
// Save file to storage directory using global g_storage_dir variable
|
||||||
char filepath[512];
|
char filepath[512];
|
||||||
snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256_hex, extension);
|
snprintf(filepath, sizeof(filepath), "%s/%s%s", g_storage_dir, sha256_hex, extension);
|
||||||
|
|
||||||
FILE* outfile = fopen(filepath, "wb");
|
FILE* outfile = fopen(filepath, "wb");
|
||||||
if (!outfile) {
|
if (!outfile) {
|
||||||
|
|||||||
75
src/bud08.c
75
src/bud08.c
@@ -24,7 +24,7 @@ int nip94_is_enabled(void) {
|
|||||||
return 1; // Default enabled on DB error
|
return 1; // Default enabled on DB error
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* sql = "SELECT value FROM server_config WHERE key = 'nip94_enabled'";
|
const char* sql = "SELECT value FROM config WHERE key = 'nip94_enabled'";
|
||||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||||
if (rc == SQLITE_OK) {
|
if (rc == SQLITE_OK) {
|
||||||
rc = sqlite3_step(stmt);
|
rc = sqlite3_step(stmt);
|
||||||
@@ -45,49 +45,52 @@ int nip94_get_origin(char* out, size_t out_size) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if request came over HTTPS (nginx sets HTTPS=on for SSL requests)
|
// Check database config first for custom origin
|
||||||
const char* https_env = getenv("HTTPS");
|
|
||||||
if (https_env && strcmp(https_env, "on") == 0) {
|
|
||||||
// HTTPS request - use HTTPS origin
|
|
||||||
strncpy(out, "https://localhost:9443", out_size - 1);
|
|
||||||
out[out_size - 1] = '\0';
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request - check database config first, then use HTTP origin
|
|
||||||
sqlite3* db;
|
sqlite3* db;
|
||||||
sqlite3_stmt* stmt;
|
sqlite3_stmt* stmt;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
|
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
|
||||||
if (rc) {
|
if (rc == SQLITE_OK) {
|
||||||
// Default on DB error - use HTTP
|
const char* sql = "SELECT value FROM config WHERE key = 'cdn_origin'";
|
||||||
strncpy(out, "http://localhost:9001", out_size - 1);
|
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||||
out[out_size - 1] = '\0';
|
if (rc == SQLITE_OK) {
|
||||||
|
rc = sqlite3_step(stmt);
|
||||||
|
if (rc == SQLITE_ROW) {
|
||||||
|
const char* value = (const char*)sqlite3_column_text(stmt, 0);
|
||||||
|
if (value) {
|
||||||
|
strncpy(out, value, out_size - 1);
|
||||||
|
out[out_size - 1] = '\0';
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
sqlite3_close(db);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
}
|
||||||
|
sqlite3_close(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if request came over HTTPS (nginx sets HTTPS=on for SSL requests)
|
||||||
|
const char* https_env = getenv("HTTPS");
|
||||||
|
const char* server_name = getenv("SERVER_NAME");
|
||||||
|
|
||||||
|
// Use production domain if SERVER_NAME is set and not localhost
|
||||||
|
if (server_name && strcmp(server_name, "localhost") != 0) {
|
||||||
|
if (https_env && strcmp(https_env, "on") == 0) {
|
||||||
|
snprintf(out, out_size, "https://%s", server_name);
|
||||||
|
} else {
|
||||||
|
snprintf(out, out_size, "http://%s", server_name);
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* sql = "SELECT value FROM server_config WHERE key = 'cdn_origin'";
|
// Fallback to localhost for development
|
||||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
if (https_env && strcmp(https_env, "on") == 0) {
|
||||||
if (rc == SQLITE_OK) {
|
strncpy(out, "https://localhost:9443", out_size - 1);
|
||||||
rc = sqlite3_step(stmt);
|
} else {
|
||||||
if (rc == SQLITE_ROW) {
|
strncpy(out, "http://localhost:9001", out_size - 1);
|
||||||
const char* value = (const char*)sqlite3_column_text(stmt, 0);
|
|
||||||
if (value) {
|
|
||||||
strncpy(out, value, out_size - 1);
|
|
||||||
out[out_size - 1] = '\0';
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
sqlite3_close(db);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_close(db);
|
|
||||||
|
|
||||||
// Default fallback - HTTP
|
|
||||||
strncpy(out, "http://localhost:9001", out_size - 1);
|
|
||||||
out[out_size - 1] = '\0';
|
out[out_size - 1] = '\0';
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ extern sqlite3* db;
|
|||||||
int init_database(void);
|
int init_database(void);
|
||||||
void close_database(void);
|
void close_database(void);
|
||||||
|
|
||||||
|
// Global configuration variables (defined in main.c)
|
||||||
|
extern char g_db_path[4096];
|
||||||
|
extern char g_storage_dir[4096];
|
||||||
|
|
||||||
// SHA-256 extraction and validation
|
// SHA-256 extraction and validation
|
||||||
const char* extract_sha256_from_uri(const char* uri);
|
const char* extract_sha256_from_uri(const char* uri);
|
||||||
|
|
||||||
|
|||||||
383
src/main.c
383
src/main.c
@@ -7,6 +7,7 @@
|
|||||||
#include "ginxsom.h"
|
#include "ginxsom.h"
|
||||||
#include "../nostr_core_lib/nostr_core/nostr_common.h"
|
#include "../nostr_core_lib/nostr_core/nostr_common.h"
|
||||||
#include "../nostr_core_lib/nostr_core/utils.h"
|
#include "../nostr_core_lib/nostr_core/utils.h"
|
||||||
|
#include <getopt.h>
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <fcgi_stdio.h>
|
#include <fcgi_stdio.h>
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
@@ -22,11 +23,15 @@
|
|||||||
// Debug macros removed
|
// Debug macros removed
|
||||||
|
|
||||||
#define MAX_SHA256_LEN 65
|
#define MAX_SHA256_LEN 65
|
||||||
#define MAX_PATH_LEN 512
|
#define MAX_PATH_LEN 4096
|
||||||
#define MAX_MIME_LEN 128
|
#define MAX_MIME_LEN 128
|
||||||
|
|
||||||
// Database path
|
// Configuration variables - can be overridden via command line
|
||||||
#define DB_PATH "db/ginxsom.db"
|
char g_db_path[MAX_PATH_LEN] = "db/ginxsom.db";
|
||||||
|
char g_storage_dir[MAX_PATH_LEN] = ".";
|
||||||
|
|
||||||
|
// Use global configuration variables
|
||||||
|
#define DB_PATH g_db_path
|
||||||
|
|
||||||
|
|
||||||
// Configuration system implementation
|
// Configuration system implementation
|
||||||
@@ -35,22 +40,6 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
// ===== UNUSED CODE - SAFE TO REMOVE AFTER TESTING =====
|
|
||||||
// Server configuration structure
|
|
||||||
/*
|
|
||||||
typedef struct {
|
|
||||||
char admin_pubkey[256];
|
|
||||||
char admin_enabled[8];
|
|
||||||
int config_loaded;
|
|
||||||
} server_config_t;
|
|
||||||
|
|
||||||
// Global configuration instance
|
|
||||||
static server_config_t g_server_config = {0};
|
|
||||||
|
|
||||||
// Global server private key (stored in memory only for security)
|
|
||||||
static char server_private_key[128] = {0};
|
|
||||||
*/
|
|
||||||
// ===== END UNUSED CODE =====
|
|
||||||
|
|
||||||
// Function to get XDG config directory
|
// Function to get XDG config directory
|
||||||
const char *get_config_dir(char *buffer, size_t buffer_size) {
|
const char *get_config_dir(char *buffer, size_t buffer_size) {
|
||||||
@@ -70,240 +59,6 @@ const char *get_config_dir(char *buffer, size_t buffer_size) {
|
|||||||
return ".config/ginxsom";
|
return ".config/ginxsom";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// ===== UNUSED CODE - SAFE TO REMOVE AFTER TESTING =====
|
|
||||||
// Load server configuration from database or create defaults
|
|
||||||
int initialize_server_config(void) {
|
|
||||||
sqlite3 *db = NULL;
|
|
||||||
sqlite3_stmt *stmt = NULL;
|
|
||||||
int rc;
|
|
||||||
|
|
||||||
memset(&g_server_config, 0, sizeof(g_server_config));
|
|
||||||
|
|
||||||
// Open database
|
|
||||||
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
fprintf(stderr, "CONFIG: Could not open database for config: %s\n",
|
|
||||||
sqlite3_errmsg(db));
|
|
||||||
// Config database doesn't exist - leave config uninitialized
|
|
||||||
g_server_config.config_loaded = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load admin_pubkey
|
|
||||||
const char *sql = "SELECT value FROM config WHERE key = ?";
|
|
||||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
||||||
if (rc == SQLITE_OK) {
|
|
||||||
sqlite3_bind_text(stmt, 1, "admin_pubkey", -1, SQLITE_STATIC);
|
|
||||||
rc = sqlite3_step(stmt);
|
|
||||||
if (rc == SQLITE_ROW) {
|
|
||||||
const char *value = (const char *)sqlite3_column_text(stmt, 0);
|
|
||||||
if (value) {
|
|
||||||
strncpy(g_server_config.admin_pubkey, value,
|
|
||||||
sizeof(g_server_config.admin_pubkey) - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load admin_enabled
|
|
||||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
||||||
if (rc == SQLITE_OK) {
|
|
||||||
sqlite3_bind_text(stmt, 1, "admin_enabled", -1, SQLITE_STATIC);
|
|
||||||
rc = sqlite3_step(stmt);
|
|
||||||
if (rc == SQLITE_ROW) {
|
|
||||||
const char *value = (const char *)sqlite3_column_text(stmt, 0);
|
|
||||||
if (value && strcmp(value, "true") == 0) {
|
|
||||||
strcpy(g_server_config.admin_enabled, "true");
|
|
||||||
} else {
|
|
||||||
strcpy(g_server_config.admin_enabled, "false");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_close(db);
|
|
||||||
|
|
||||||
g_server_config.config_loaded = 1;
|
|
||||||
fprintf(stderr, "CONFIG: Server configuration loaded\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
// ===== END UNUSED CODE =====
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
// File-based configuration system
|
|
||||||
// Config file path resolution
|
|
||||||
int get_config_file_path(char *path, size_t path_size) {
|
|
||||||
const char *home = getenv("HOME");
|
|
||||||
const char *xdg_config = getenv("XDG_CONFIG_HOME");
|
|
||||||
|
|
||||||
if (xdg_config) {
|
|
||||||
snprintf(path, path_size, "%s/ginxsom/ginxsom_config_event.json",
|
|
||||||
xdg_config);
|
|
||||||
} else if (home) {
|
|
||||||
snprintf(path, path_size, "%s/.config/ginxsom/ginxsom_config_event.json",
|
|
||||||
home);
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Load and validate config event
|
|
||||||
int load_server_config(const char *config_path) {
|
|
||||||
FILE *file = fopen(config_path, "r");
|
|
||||||
if (!file) {
|
|
||||||
return 0; // Config file doesn't exist
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read entire file
|
|
||||||
fseek(file, 0, SEEK_END);
|
|
||||||
long file_size = ftell(file);
|
|
||||||
fseek(file, 0, SEEK_SET);
|
|
||||||
|
|
||||||
char *json_data = malloc(file_size + 1);
|
|
||||||
if (!json_data) {
|
|
||||||
fclose(file);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fread(json_data, 1, file_size, file);
|
|
||||||
json_data[file_size] = '\0';
|
|
||||||
fclose(file);
|
|
||||||
|
|
||||||
// Parse and validate JSON event
|
|
||||||
cJSON *event = cJSON_Parse(json_data);
|
|
||||||
free(json_data);
|
|
||||||
|
|
||||||
if (!event) {
|
|
||||||
fprintf(stderr, "Invalid JSON in config file\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate event structure and signature
|
|
||||||
if (nostr_validate_event(event) != NOSTR_SUCCESS) {
|
|
||||||
fprintf(stderr, "Invalid or corrupted config event\n");
|
|
||||||
cJSON_Delete(event);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract configuration and apply to server
|
|
||||||
int result = apply_config_from_event(event);
|
|
||||||
cJSON_Delete(event);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Extract config from validated event and apply to server
|
|
||||||
int apply_config_from_event(cJSON *event) {
|
|
||||||
sqlite3 *db;
|
|
||||||
sqlite3_stmt *stmt;
|
|
||||||
int rc;
|
|
||||||
|
|
||||||
// Open database for config storage
|
|
||||||
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL);
|
|
||||||
if (rc) {
|
|
||||||
fprintf(stderr, "Failed to open database for config\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract admin pubkey from event
|
|
||||||
cJSON *pubkey_json = cJSON_GetObjectItem(event, "pubkey");
|
|
||||||
if (!pubkey_json || !cJSON_IsString(pubkey_json)) {
|
|
||||||
sqlite3_close(db);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const char *admin_pubkey = cJSON_GetStringValue(pubkey_json);
|
|
||||||
|
|
||||||
// Store admin pubkey in database
|
|
||||||
const char *insert_sql = "INSERT OR REPLACE INTO config (key, value, "
|
|
||||||
"description) VALUES (?, ?, ?)";
|
|
||||||
rc = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL);
|
|
||||||
if (rc == SQLITE_OK) {
|
|
||||||
sqlite3_bind_text(stmt, 1, "admin_pubkey", -1, SQLITE_STATIC);
|
|
||||||
sqlite3_bind_text(stmt, 2, admin_pubkey, -1, SQLITE_STATIC);
|
|
||||||
sqlite3_bind_text(stmt, 3, "Admin public key from config event", -1,
|
|
||||||
SQLITE_STATIC);
|
|
||||||
sqlite3_step(stmt);
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract server private key and store securely (in memory only)
|
|
||||||
cJSON *tags = cJSON_GetObjectItem(event, "tags");
|
|
||||||
if (tags && cJSON_IsArray(tags)) {
|
|
||||||
cJSON *tag = NULL;
|
|
||||||
cJSON_ArrayForEach(tag, tags) {
|
|
||||||
if (!cJSON_IsArray(tag))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
cJSON *tag_name = cJSON_GetArrayItem(tag, 0);
|
|
||||||
cJSON *tag_value = cJSON_GetArrayItem(tag, 1);
|
|
||||||
|
|
||||||
if (!tag_name || !cJSON_IsString(tag_name) || !tag_value ||
|
|
||||||
!cJSON_IsString(tag_value))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const char *key = cJSON_GetStringValue(tag_name);
|
|
||||||
const char *value = cJSON_GetStringValue(tag_value);
|
|
||||||
|
|
||||||
if (strcmp(key, "server_privkey") == 0) {
|
|
||||||
// Store server private key in global variable (memory only)
|
|
||||||
// strncpy(server_private_key, value, sizeof(server_private_key) - 1);
|
|
||||||
// server_private_key[sizeof(server_private_key) - 1] = '\0';
|
|
||||||
} else {
|
|
||||||
// Store other config values in database
|
|
||||||
rc = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL);
|
|
||||||
if (rc == SQLITE_OK) {
|
|
||||||
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
|
|
||||||
sqlite3_bind_text(stmt, 2, value, -1, SQLITE_STATIC);
|
|
||||||
sqlite3_bind_text(stmt, 3, "From config event", -1, SQLITE_STATIC);
|
|
||||||
sqlite3_step(stmt);
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_close(db);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Interactive setup runner
|
|
||||||
int run_interactive_setup(const char *config_path) {
|
|
||||||
printf("\n=== Ginxsom First-Time Setup Required ===\n");
|
|
||||||
printf("No configuration found at: %s\n\n", config_path);
|
|
||||||
printf("Options:\n");
|
|
||||||
printf("1. Run interactive setup wizard\n");
|
|
||||||
printf("2. Exit and create config manually\n");
|
|
||||||
printf("Choice (1/2): ");
|
|
||||||
|
|
||||||
char choice[10];
|
|
||||||
if (!fgets(choice, sizeof(choice), stdin)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (choice[0] == '1') {
|
|
||||||
// Run setup script
|
|
||||||
char script_path[512];
|
|
||||||
snprintf(script_path, sizeof(script_path), "./scripts/setup.sh \"%s\"",
|
|
||||||
config_path);
|
|
||||||
return system(script_path);
|
|
||||||
} else {
|
|
||||||
printf("\nManual setup instructions:\n");
|
|
||||||
printf("1. Run: ./scripts/generate_config.sh\n");
|
|
||||||
printf("2. Place signed config at: %s\n", config_path);
|
|
||||||
printf("3. Restart ginxsom\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Function declarations
|
// Function declarations
|
||||||
void handle_options_request(void);
|
void handle_options_request(void);
|
||||||
@@ -434,7 +189,23 @@ int file_exists_with_type(const char *sha256, const char *mime_type) {
|
|||||||
char filepath[MAX_PATH_LEN];
|
char filepath[MAX_PATH_LEN];
|
||||||
const char *extension = mime_to_extension(mime_type);
|
const char *extension = mime_to_extension(mime_type);
|
||||||
|
|
||||||
snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256, extension);
|
// Construct path safely
|
||||||
|
size_t dir_len = strlen(g_storage_dir);
|
||||||
|
size_t sha_len = strlen(sha256);
|
||||||
|
size_t ext_len = strlen(extension);
|
||||||
|
size_t total_len = dir_len + 1 + sha_len + ext_len + 1; // +1 for /, +1 for null
|
||||||
|
|
||||||
|
if (total_len > sizeof(filepath)) {
|
||||||
|
fprintf(stderr, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256, extension);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build path manually to avoid compiler warnings
|
||||||
|
memcpy(filepath, g_storage_dir, dir_len);
|
||||||
|
filepath[dir_len] = '/';
|
||||||
|
memcpy(filepath + dir_len + 1, sha256, sha_len);
|
||||||
|
memcpy(filepath + dir_len + 1 + sha_len, extension, ext_len);
|
||||||
|
filepath[total_len - 1] = '\0';
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
int result = stat(filepath, &st);
|
int result = stat(filepath, &st);
|
||||||
@@ -524,18 +295,6 @@ const char *extract_sha256_from_uri(const char *uri) {
|
|||||||
return sha256_buffer;
|
return sha256_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// BUD 02 - Upload & Authentication
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// AUTHENTICATION RULES SYSTEM (4.1.2)
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -911,7 +670,23 @@ void handle_delete_request_with_validation(const char *sha256, nostr_request_res
|
|||||||
}
|
}
|
||||||
|
|
||||||
char filepath[MAX_PATH_LEN];
|
char filepath[MAX_PATH_LEN];
|
||||||
snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256, extension);
|
// Construct path safely
|
||||||
|
size_t dir_len = strlen(g_storage_dir);
|
||||||
|
size_t sha_len = strlen(sha256);
|
||||||
|
size_t ext_len = strlen(extension);
|
||||||
|
size_t total_len = dir_len + 1 + sha_len + ext_len + 1; // +1 for /, +1 for null
|
||||||
|
|
||||||
|
if (total_len > sizeof(filepath)) {
|
||||||
|
fprintf(stderr, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256, extension);
|
||||||
|
// Continue anyway - unlink will fail gracefully
|
||||||
|
} else {
|
||||||
|
// Build path manually to avoid compiler warnings
|
||||||
|
memcpy(filepath, g_storage_dir, dir_len);
|
||||||
|
filepath[dir_len] = '/';
|
||||||
|
memcpy(filepath + dir_len + 1, sha256, sha_len);
|
||||||
|
memcpy(filepath + dir_len + 1 + sha_len, extension, ext_len);
|
||||||
|
filepath[total_len - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
// Delete the physical file
|
// Delete the physical file
|
||||||
if (unlink(filepath) != 0) {
|
if (unlink(filepath) != 0) {
|
||||||
@@ -1029,9 +804,29 @@ void handle_upload_request(void) {
|
|||||||
// Determine file extension from Content-Type using centralized mapping
|
// Determine file extension from Content-Type using centralized mapping
|
||||||
const char *extension = mime_to_extension(content_type);
|
const char *extension = mime_to_extension(content_type);
|
||||||
|
|
||||||
// Save file to blobs directory with SHA-256 + extension
|
// Save file to storage directory with SHA-256 + extension
|
||||||
char filepath[MAX_PATH_LEN];
|
char filepath[MAX_PATH_LEN];
|
||||||
snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256_hex, extension);
|
// Construct path safely
|
||||||
|
size_t dir_len = strlen(g_storage_dir);
|
||||||
|
size_t sha_len = strlen(sha256_hex);
|
||||||
|
size_t ext_len = strlen(extension);
|
||||||
|
size_t total_len = dir_len + 1 + sha_len + ext_len + 1; // +1 for /, +1 for null
|
||||||
|
|
||||||
|
if (total_len > sizeof(filepath)) {
|
||||||
|
fprintf(stderr, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256_hex, extension);
|
||||||
|
printf("Status: 500 Internal Server Error\r\n");
|
||||||
|
|
||||||
|
printf("Content-Type: text/plain\r\n\r\n");
|
||||||
|
printf("File path too long\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build path manually to avoid compiler warnings
|
||||||
|
memcpy(filepath, g_storage_dir, dir_len);
|
||||||
|
filepath[dir_len] = '/';
|
||||||
|
memcpy(filepath + dir_len + 1, sha256_hex, sha_len);
|
||||||
|
memcpy(filepath + dir_len + 1 + sha_len, extension, ext_len);
|
||||||
|
filepath[total_len - 1] = '\0';
|
||||||
|
|
||||||
FILE *outfile = fopen(filepath, "wb");
|
FILE *outfile = fopen(filepath, "wb");
|
||||||
if (!outfile) {
|
if (!outfile) {
|
||||||
@@ -1280,9 +1075,29 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re
|
|||||||
// Determine file extension from Content-Type using centralized mapping
|
// Determine file extension from Content-Type using centralized mapping
|
||||||
const char *extension = mime_to_extension(content_type);
|
const char *extension = mime_to_extension(content_type);
|
||||||
|
|
||||||
// Save file to blobs directory with SHA-256 + extension
|
// Save file to storage directory with SHA-256 + extension
|
||||||
char filepath[MAX_PATH_LEN];
|
char filepath[MAX_PATH_LEN];
|
||||||
snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256_hex, extension);
|
// Construct path safely
|
||||||
|
size_t dir_len = strlen(g_storage_dir);
|
||||||
|
size_t sha_len = strlen(sha256_hex);
|
||||||
|
size_t ext_len = strlen(extension);
|
||||||
|
size_t total_len = dir_len + 1 + sha_len + ext_len + 1; // +1 for /, +1 for null
|
||||||
|
|
||||||
|
if (total_len > sizeof(filepath)) {
|
||||||
|
fprintf(stderr, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256_hex, extension);
|
||||||
|
printf("Status: 500 Internal Server Error\r\n");
|
||||||
|
|
||||||
|
printf("Content-Type: text/plain\r\n\r\n");
|
||||||
|
printf("File path too long\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build path manually to avoid compiler warnings
|
||||||
|
memcpy(filepath, g_storage_dir, dir_len);
|
||||||
|
filepath[dir_len] = '/';
|
||||||
|
memcpy(filepath + dir_len + 1, sha256_hex, sha_len);
|
||||||
|
memcpy(filepath + dir_len + 1 + sha_len, extension, ext_len);
|
||||||
|
filepath[total_len - 1] = '\0';
|
||||||
|
|
||||||
FILE *outfile = fopen(filepath, "wb");
|
FILE *outfile = fopen(filepath, "wb");
|
||||||
if (!outfile) {
|
if (!outfile) {
|
||||||
@@ -1480,7 +1295,31 @@ void handle_auth_challenge_request(void) {
|
|||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
int main(void) {
|
int main(int argc, char *argv[]) {
|
||||||
|
// Parse command line arguments
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
if (strcmp(argv[i], "--db-path") == 0 && i + 1 < argc) {
|
||||||
|
strncpy(g_db_path, argv[i + 1], sizeof(g_db_path) - 1);
|
||||||
|
i++; // Skip next argument
|
||||||
|
} else if (strcmp(argv[i], "--storage-dir") == 0 && i + 1 < argc) {
|
||||||
|
strncpy(g_storage_dir, argv[i + 1], sizeof(g_storage_dir) - 1);
|
||||||
|
i++; // Skip next argument
|
||||||
|
} else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
|
||||||
|
printf("Usage: %s [options]\n", argv[0]);
|
||||||
|
printf("Options:\n");
|
||||||
|
printf(" --db-path PATH Database file path (default: db/ginxsom.db)\n");
|
||||||
|
printf(" --storage-dir DIR Storage directory for files (default: blobs)\n");
|
||||||
|
printf(" --help, -h Show this help message\n");
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Unknown option: %s\n", argv[i]);
|
||||||
|
fprintf(stderr, "Use --help for usage information\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "STARTUP: Using database path: %s\n", g_db_path);
|
||||||
|
fprintf(stderr, "STARTUP: Using storage directory: %s\n", g_storage_dir);
|
||||||
|
|
||||||
// Initialize server configuration and identity
|
// Initialize server configuration and identity
|
||||||
// Try file-based config first, then fall back to database config
|
// Try file-based config first, then fall back to database config
|
||||||
|
|||||||
7
test_blob_1762770636.txt
Normal file
7
test_blob_1762770636.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Test blob content for Ginxsom Blossom server (PRODUCTION)
|
||||||
|
Timestamp: 2025-11-10T06:30:36-04:00
|
||||||
|
Random data: bb7d8d5206aadf4ecd48829f9674454c160bae68e98c6ce5f7f678f997dbe86a
|
||||||
|
Test message: Hello from production test!
|
||||||
|
|
||||||
|
This file is used to test the upload functionality
|
||||||
|
of the Ginxsom Blossom server on blossom.laantungir.net
|
||||||
@@ -6,7 +6,8 @@
|
|||||||
set -e # Exit on any error
|
set -e # Exit on any error
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
SERVER_URL="http://localhost:9001"
|
# SERVER_URL="http://localhost:9001"
|
||||||
|
SERVER_URL="https://localhost:9443"
|
||||||
UPLOAD_ENDPOINT="${SERVER_URL}/upload"
|
UPLOAD_ENDPOINT="${SERVER_URL}/upload"
|
||||||
TEST_FILE="test_blob_$(date +%s).txt"
|
TEST_FILE="test_blob_$(date +%s).txt"
|
||||||
CLEANUP_FILES=()
|
CLEANUP_FILES=()
|
||||||
@@ -87,7 +88,7 @@ check_prerequisites() {
|
|||||||
check_server() {
|
check_server() {
|
||||||
log_info "Checking if server is running..."
|
log_info "Checking if server is running..."
|
||||||
|
|
||||||
if curl -s -f "${SERVER_URL}/health" > /dev/null 2>&1; then
|
if curl -k -s -f "${SERVER_URL}/health" > /dev/null 2>&1; then
|
||||||
log_success "Server is running at ${SERVER_URL}"
|
log_success "Server is running at ${SERVER_URL}"
|
||||||
else
|
else
|
||||||
log_error "Server is not responding at ${SERVER_URL}"
|
log_error "Server is not responding at ${SERVER_URL}"
|
||||||
@@ -168,7 +169,7 @@ perform_upload() {
|
|||||||
CLEANUP_FILES+=("${RESPONSE_FILE}")
|
CLEANUP_FILES+=("${RESPONSE_FILE}")
|
||||||
|
|
||||||
# Perform the upload with verbose output
|
# Perform the upload with verbose output
|
||||||
HTTP_STATUS=$(curl -s -w "%{http_code}" \
|
HTTP_STATUS=$(curl -k -s -w "%{http_code}" \
|
||||||
-X PUT \
|
-X PUT \
|
||||||
-H "Authorization: ${AUTH_HEADER}" \
|
-H "Authorization: ${AUTH_HEADER}" \
|
||||||
-H "Content-Type: text/plain" \
|
-H "Content-Type: text/plain" \
|
||||||
@@ -217,7 +218,7 @@ test_retrieval() {
|
|||||||
|
|
||||||
RETRIEVAL_URL="${SERVER_URL}/${HASH}"
|
RETRIEVAL_URL="${SERVER_URL}/${HASH}"
|
||||||
|
|
||||||
if curl -s -f "${RETRIEVAL_URL}" > /dev/null 2>&1; then
|
if curl -k -s -f "${RETRIEVAL_URL}" > /dev/null 2>&1; then
|
||||||
log_success "File can be retrieved at: ${RETRIEVAL_URL}"
|
log_success "File can be retrieved at: ${RETRIEVAL_URL}"
|
||||||
else
|
else
|
||||||
log_warning "File not yet available for retrieval (expected if upload processing not implemented)"
|
log_warning "File not yet available for retrieval (expected if upload processing not implemented)"
|
||||||
|
|||||||
265
tests/file_put_production.sh
Executable file
265
tests/file_put_production.sh
Executable file
@@ -0,0 +1,265 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# file_put_production.sh - Test script for production Ginxsom Blossom server
|
||||||
|
# Tests upload functionality on blossom.laantungir.net
|
||||||
|
|
||||||
|
set -e # Exit on any error
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SERVER_URL="https://blossom.laantungir.net"
|
||||||
|
UPLOAD_ENDPOINT="${SERVER_URL}/upload"
|
||||||
|
TEST_FILE="test_blob_$(date +%s).txt"
|
||||||
|
CLEANUP_FILES=()
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Cleanup function
|
||||||
|
cleanup() {
|
||||||
|
echo -e "${YELLOW}Cleaning up temporary files...${NC}"
|
||||||
|
for file in "${CLEANUP_FILES[@]}"; do
|
||||||
|
if [[ -f "$file" ]]; then
|
||||||
|
rm -f "$file"
|
||||||
|
echo "Removed: $file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set up cleanup on exit
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check prerequisites
|
||||||
|
check_prerequisites() {
|
||||||
|
log_info "Checking prerequisites..."
|
||||||
|
|
||||||
|
# Check if nak is installed
|
||||||
|
if ! command -v nak &> /dev/null; then
|
||||||
|
log_error "nak command not found. Please install nak first."
|
||||||
|
log_info "Install with: go install github.com/fiatjaf/nak@latest"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log_success "nak is installed"
|
||||||
|
|
||||||
|
# Check if curl is available
|
||||||
|
if ! command -v curl &> /dev/null; then
|
||||||
|
log_error "curl command not found. Please install curl."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log_success "curl is available"
|
||||||
|
|
||||||
|
# Check if sha256sum is available
|
||||||
|
if ! command -v sha256sum &> /dev/null; then
|
||||||
|
log_error "sha256sum command not found."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log_success "sha256sum is available"
|
||||||
|
|
||||||
|
# Check if base64 is available
|
||||||
|
if ! command -v base64 &> /dev/null; then
|
||||||
|
log_error "base64 command not found."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log_success "base64 is available"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if server is running
|
||||||
|
check_server() {
|
||||||
|
log_info "Checking if server is running..."
|
||||||
|
|
||||||
|
if curl -s -f "${SERVER_URL}/health" > /dev/null 2>&1; then
|
||||||
|
log_success "Server is running at ${SERVER_URL}"
|
||||||
|
else
|
||||||
|
log_error "Server is not responding at ${SERVER_URL}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create test file
|
||||||
|
create_test_file() {
|
||||||
|
log_info "Creating test file: ${TEST_FILE}"
|
||||||
|
|
||||||
|
# Create test content with timestamp and random data
|
||||||
|
cat > "${TEST_FILE}" << EOF
|
||||||
|
Test blob content for Ginxsom Blossom server (PRODUCTION)
|
||||||
|
Timestamp: $(date -Iseconds)
|
||||||
|
Random data: $(openssl rand -hex 32)
|
||||||
|
Test message: Hello from production test!
|
||||||
|
|
||||||
|
This file is used to test the upload functionality
|
||||||
|
of the Ginxsom Blossom server on blossom.laantungir.net
|
||||||
|
EOF
|
||||||
|
|
||||||
|
CLEANUP_FILES+=("${TEST_FILE}")
|
||||||
|
log_success "Created test file with $(wc -c < "${TEST_FILE}") bytes"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate file hash
|
||||||
|
calculate_hash() {
|
||||||
|
log_info "Calculating SHA-256 hash..."
|
||||||
|
|
||||||
|
HASH=$(sha256sum "${TEST_FILE}" | cut -d' ' -f1)
|
||||||
|
|
||||||
|
log_success "Data to hash: ${TEST_FILE}"
|
||||||
|
log_success "File hash: ${HASH}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate nostr event
|
||||||
|
generate_nostr_event() {
|
||||||
|
log_info "Generating kind 24242 nostr event with nak..."
|
||||||
|
|
||||||
|
# Calculate expiration time (1 hour from now)
|
||||||
|
EXPIRATION=$(date -d '+1 hour' +%s)
|
||||||
|
|
||||||
|
# Generate the event using nak
|
||||||
|
EVENT_JSON=$(nak event -k 24242 -c "" \
|
||||||
|
-t "t=upload" \
|
||||||
|
-t "x=${HASH}" \
|
||||||
|
-t "expiration=${EXPIRATION}")
|
||||||
|
|
||||||
|
if [[ -z "$EVENT_JSON" ]]; then
|
||||||
|
log_error "Failed to generate nostr event"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Generated nostr event"
|
||||||
|
echo "Event JSON: $EVENT_JSON"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create authorization header
|
||||||
|
create_auth_header() {
|
||||||
|
log_info "Creating authorization header..."
|
||||||
|
|
||||||
|
# Base64 encode the event (without newlines)
|
||||||
|
AUTH_B64=$(echo -n "$EVENT_JSON" | base64 -w 0)
|
||||||
|
AUTH_HEADER="Nostr ${AUTH_B64}"
|
||||||
|
|
||||||
|
log_success "Created authorization header"
|
||||||
|
echo "Auth header length: ${#AUTH_HEADER} characters"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Perform upload
|
||||||
|
perform_upload() {
|
||||||
|
log_info "Performing upload to ${UPLOAD_ENDPOINT}..."
|
||||||
|
|
||||||
|
# Create temporary file for response
|
||||||
|
RESPONSE_FILE=$(mktemp)
|
||||||
|
CLEANUP_FILES+=("${RESPONSE_FILE}")
|
||||||
|
|
||||||
|
# Perform the upload with verbose output
|
||||||
|
HTTP_STATUS=$(curl -s -w "%{http_code}" \
|
||||||
|
-X PUT \
|
||||||
|
-H "Authorization: ${AUTH_HEADER}" \
|
||||||
|
-H "Content-Type: text/plain" \
|
||||||
|
-H "Content-Disposition: attachment; filename=\"${TEST_FILE}\"" \
|
||||||
|
--data-binary "@${TEST_FILE}" \
|
||||||
|
"${UPLOAD_ENDPOINT}" \
|
||||||
|
-o "${RESPONSE_FILE}")
|
||||||
|
|
||||||
|
echo "HTTP Status: ${HTTP_STATUS}"
|
||||||
|
echo "Response body:"
|
||||||
|
cat "${RESPONSE_FILE}"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Check response
|
||||||
|
case "${HTTP_STATUS}" in
|
||||||
|
200)
|
||||||
|
log_success "Upload successful!"
|
||||||
|
;;
|
||||||
|
201)
|
||||||
|
log_success "Upload successful (created)!"
|
||||||
|
;;
|
||||||
|
400)
|
||||||
|
log_error "Bad request - check the event format"
|
||||||
|
;;
|
||||||
|
401)
|
||||||
|
log_error "Unauthorized - authentication failed"
|
||||||
|
;;
|
||||||
|
405)
|
||||||
|
log_error "Method not allowed - check nginx configuration"
|
||||||
|
;;
|
||||||
|
413)
|
||||||
|
log_error "Payload too large"
|
||||||
|
;;
|
||||||
|
501)
|
||||||
|
log_warning "Upload endpoint not yet implemented (expected for now)"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Upload failed with HTTP status: ${HTTP_STATUS}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test file retrieval
|
||||||
|
test_retrieval() {
|
||||||
|
log_info "Testing file retrieval..."
|
||||||
|
|
||||||
|
RETRIEVAL_URL="${SERVER_URL}/${HASH}"
|
||||||
|
|
||||||
|
if curl -s -f "${RETRIEVAL_URL}" > /dev/null 2>&1; then
|
||||||
|
log_success "File can be retrieved at: ${RETRIEVAL_URL}"
|
||||||
|
|
||||||
|
# Download and verify
|
||||||
|
DOWNLOADED_FILE=$(mktemp)
|
||||||
|
CLEANUP_FILES+=("${DOWNLOADED_FILE}")
|
||||||
|
curl -s "${RETRIEVAL_URL}" -o "${DOWNLOADED_FILE}"
|
||||||
|
|
||||||
|
DOWNLOADED_HASH=$(sha256sum "${DOWNLOADED_FILE}" | cut -d' ' -f1)
|
||||||
|
if [[ "${DOWNLOADED_HASH}" == "${HASH}" ]]; then
|
||||||
|
log_success "Downloaded file hash matches! Verification successful."
|
||||||
|
else
|
||||||
|
log_error "Hash mismatch! Expected: ${HASH}, Got: ${DOWNLOADED_HASH}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_warning "File not yet available for retrieval"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
main() {
|
||||||
|
echo "=== Ginxsom Blossom Production Upload Test ==="
|
||||||
|
echo "Server: ${SERVER_URL}"
|
||||||
|
echo "Timestamp: $(date -Iseconds)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
check_prerequisites
|
||||||
|
check_server
|
||||||
|
create_test_file
|
||||||
|
calculate_hash
|
||||||
|
generate_nostr_event
|
||||||
|
create_auth_header
|
||||||
|
perform_upload
|
||||||
|
test_retrieval
|
||||||
|
|
||||||
|
echo
|
||||||
|
log_info "Test completed!"
|
||||||
|
echo "Summary:"
|
||||||
|
echo " Test file: ${TEST_FILE}"
|
||||||
|
echo " File hash: ${HASH}"
|
||||||
|
echo " Server: ${SERVER_URL}"
|
||||||
|
echo " Upload endpoint: ${UPLOAD_ENDPOINT}"
|
||||||
|
echo " Retrieval URL: ${SERVER_URL}/${HASH}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function
|
||||||
|
main "$@"
|
||||||
@@ -3,18 +3,31 @@
|
|||||||
# Mirror Test Script for BUD-04
|
# Mirror Test Script for BUD-04
|
||||||
# Tests the PUT /mirror endpoint with a sample PNG file and NIP-42 authentication
|
# Tests the PUT /mirror endpoint with a sample PNG file and NIP-42 authentication
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# CONFIGURATION - Choose your target Blossom server
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Local server (uncomment to use)
|
||||||
|
BLOSSOM_SERVER="http://localhost:9001"
|
||||||
|
|
||||||
|
# Remote server (uncomment to use)
|
||||||
|
#BLOSSOM_SERVER="https://blossom.laantungir.net"
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
# Test URL - PNG file with known SHA-256 hash
|
# Test URL - PNG file with known SHA-256 hash
|
||||||
TEST_URL="https://laantungir.github.io/img_repo/24308d48eb498b593e55a87b6300ccffdea8432babc0bb898b1eff21ebbb72de.png"
|
TEST_URL="https://laantungir.github.io/img_repo/24308d48eb498b593e55a87b6300ccffdea8432babc0bb898b1eff21ebbb72de.png"
|
||||||
EXPECTED_HASH="24308d48eb498b593e55a87b6300ccffdea8432babc0bb898b1eff21ebbb72de"
|
EXPECTED_HASH="24308d48eb498b593e55a87b6300ccffdea8432babc0bb898b1eff21ebbb72de"
|
||||||
|
|
||||||
echo "=== BUD-04 Mirror Endpoint Test with Authentication ==="
|
echo "=== BUD-04 Mirror Endpoint Test with Authentication ==="
|
||||||
|
echo "Blossom Server: $BLOSSOM_SERVER"
|
||||||
echo "Target URL: $TEST_URL"
|
echo "Target URL: $TEST_URL"
|
||||||
echo "Expected Hash: $EXPECTED_HASH"
|
echo "Expected Hash: $EXPECTED_HASH"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Get a fresh challenge from the server
|
# Get a fresh challenge from the server
|
||||||
echo "=== Getting Authentication Challenge ==="
|
echo "=== Getting Authentication Challenge ==="
|
||||||
challenge=$(curl -s "http://localhost:9001/auth" | jq -r '.challenge')
|
challenge=$(curl -s "$BLOSSOM_SERVER/auth" | jq -r '.challenge')
|
||||||
if [ "$challenge" = "null" ] || [ -z "$challenge" ]; then
|
if [ "$challenge" = "null" ] || [ -z "$challenge" ]; then
|
||||||
echo "❌ Failed to get challenge from server"
|
echo "❌ Failed to get challenge from server"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -48,7 +61,7 @@ RESPONSE=$(curl -s -w "\nHTTP_CODE:%{http_code}\n" \
|
|||||||
-H "Authorization: $auth_header" \
|
-H "Authorization: $auth_header" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$JSON_BODY" \
|
-d "$JSON_BODY" \
|
||||||
http://localhost:9001/mirror)
|
"$BLOSSOM_SERVER/mirror")
|
||||||
|
|
||||||
echo "Response:"
|
echo "Response:"
|
||||||
echo "$RESPONSE"
|
echo "$RESPONSE"
|
||||||
@@ -65,9 +78,9 @@ if [ "$HTTP_CODE" = "200" ]; then
|
|||||||
# Try to access the mirrored blob
|
# Try to access the mirrored blob
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Verifying Mirrored Blob ==="
|
echo "=== Verifying Mirrored Blob ==="
|
||||||
echo "Attempting to fetch: http://localhost:9001/$EXPECTED_HASH.png"
|
echo "Attempting to fetch: $BLOSSOM_SERVER/$EXPECTED_HASH.png"
|
||||||
|
|
||||||
BLOB_RESPONSE=$(curl -s -w "HTTP_CODE:%{http_code}" -I "http://localhost:9001/$EXPECTED_HASH.png")
|
BLOB_RESPONSE=$(curl -s -w "HTTP_CODE:%{http_code}" -I "$BLOSSOM_SERVER/$EXPECTED_HASH.png")
|
||||||
BLOB_HTTP_CODE=$(echo "$BLOB_RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2)
|
BLOB_HTTP_CODE=$(echo "$BLOB_RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2)
|
||||||
|
|
||||||
if [ "$BLOB_HTTP_CODE" = "200" ]; then
|
if [ "$BLOB_HTTP_CODE" = "200" ]; then
|
||||||
@@ -82,7 +95,7 @@ if [ "$HTTP_CODE" = "200" ]; then
|
|||||||
# Test HEAD request for metadata
|
# Test HEAD request for metadata
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Testing HEAD Request ==="
|
echo "=== Testing HEAD Request ==="
|
||||||
HEAD_RESPONSE=$(curl -s -w "HTTP_CODE:%{http_code}" -I -X HEAD "http://localhost:9001/$EXPECTED_HASH")
|
HEAD_RESPONSE=$(curl -s -w "HTTP_CODE:%{http_code}" -I -X HEAD "$BLOSSOM_SERVER/$EXPECTED_HASH")
|
||||||
HEAD_HTTP_CODE=$(echo "$HEAD_RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2)
|
HEAD_HTTP_CODE=$(echo "$HEAD_RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2)
|
||||||
|
|
||||||
if [ "$HEAD_HTTP_CODE" = "200" ]; then
|
if [ "$HEAD_HTTP_CODE" = "200" ]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user