v0.1.4 - Make response at root JSON
This commit is contained in:
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;
|
||||
}
|
||||
|
||||
# Root redirect
|
||||
# Root redirect - Server info endpoint
|
||||
location = / {
|
||||
return 200 "Ginxsom Blossom Server\nEndpoints: GET /<sha256>, PUT /upload, GET /list/<pubkey>\nHealth: GET /health\n";
|
||||
add_header Content-Type text/plain;
|
||||
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;
|
||||
@@ -683,10 +683,10 @@ http {
|
||||
autoindex_format json;
|
||||
}
|
||||
|
||||
# Root redirect
|
||||
# Root redirect - Server info endpoint
|
||||
location = / {
|
||||
return 200 "Ginxsom Blossom Server (HTTPS)\nEndpoints: GET /<sha256>, PUT /upload, GET /list/<pubkey>\nHealth: GET /health\n";
|
||||
add_header Content-Type text/plain;
|
||||
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 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;
|
||||
|
||||
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_RATE_LIMIT 10
|
||||
|
||||
/* Global configuration variables */
|
||||
extern char g_db_path[MAX_PATH_LEN];
|
||||
extern char g_storage_dir[MAX_PATH_LEN];
|
||||
|
||||
/* Error codes */
|
||||
typedef enum {
|
||||
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)
|
||||
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
|
||||
PID=$(cat "$PID_FILE")
|
||||
@@ -250,6 +250,8 @@ else
|
||||
fi
|
||||
|
||||
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 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
|
||||
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];
|
||||
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");
|
||||
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
|
||||
}
|
||||
|
||||
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);
|
||||
if (rc == SQLITE_OK) {
|
||||
rc = sqlite3_step(stmt);
|
||||
@@ -45,49 +45,52 @@ int nip94_get_origin(char* out, size_t out_size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if request came over HTTPS (nginx sets HTTPS=on for SSL requests)
|
||||
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
|
||||
// Check database config first for custom origin
|
||||
sqlite3* db;
|
||||
sqlite3_stmt* stmt;
|
||||
int rc;
|
||||
|
||||
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
|
||||
if (rc) {
|
||||
// Default on DB error - use HTTP
|
||||
strncpy(out, "http://localhost:9001", out_size - 1);
|
||||
out[out_size - 1] = '\0';
|
||||
if (rc == SQLITE_OK) {
|
||||
const char* sql = "SELECT value FROM config WHERE key = 'cdn_origin'";
|
||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
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;
|
||||
}
|
||||
|
||||
const char* sql = "SELECT value FROM server_config WHERE key = 'cdn_origin'";
|
||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
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);
|
||||
|
||||
// Fallback to localhost for development
|
||||
if (https_env && strcmp(https_env, "on") == 0) {
|
||||
strncpy(out, "https://localhost:9443", out_size - 1);
|
||||
} else {
|
||||
strncpy(out, "http://localhost:9001", out_size - 1);
|
||||
}
|
||||
|
||||
sqlite3_close(db);
|
||||
|
||||
// Default fallback - HTTP
|
||||
strncpy(out, "http://localhost:9001", out_size - 1);
|
||||
out[out_size - 1] = '\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ extern sqlite3* db;
|
||||
int init_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
|
||||
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 "../nostr_core_lib/nostr_core/nostr_common.h"
|
||||
#include "../nostr_core_lib/nostr_core/utils.h"
|
||||
#include <getopt.h>
|
||||
#include <curl/curl.h>
|
||||
#include <fcgi_stdio.h>
|
||||
#include <sqlite3.h>
|
||||
@@ -22,11 +23,15 @@
|
||||
// Debug macros removed
|
||||
|
||||
#define MAX_SHA256_LEN 65
|
||||
#define MAX_PATH_LEN 512
|
||||
#define MAX_PATH_LEN 4096
|
||||
#define MAX_MIME_LEN 128
|
||||
|
||||
// Database path
|
||||
#define DB_PATH "db/ginxsom.db"
|
||||
// Configuration variables - can be overridden via command line
|
||||
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
|
||||
@@ -35,22 +40,6 @@
|
||||
#include <sys/stat.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
|
||||
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";
|
||||
}
|
||||
|
||||
/*
|
||||
// ===== 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
|
||||
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];
|
||||
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;
|
||||
int result = stat(filepath, &st);
|
||||
@@ -524,18 +295,6 @@ const char *extract_sha256_from_uri(const char *uri) {
|
||||
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];
|
||||
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
|
||||
if (unlink(filepath) != 0) {
|
||||
@@ -1029,9 +804,29 @@ void handle_upload_request(void) {
|
||||
// Determine file extension from Content-Type using centralized mapping
|
||||
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];
|
||||
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");
|
||||
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
|
||||
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];
|
||||
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");
|
||||
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
|
||||
// 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
|
||||
|
||||
# Configuration
|
||||
SERVER_URL="http://localhost:9001"
|
||||
# SERVER_URL="http://localhost:9001"
|
||||
SERVER_URL="https://localhost:9443"
|
||||
UPLOAD_ENDPOINT="${SERVER_URL}/upload"
|
||||
TEST_FILE="test_blob_$(date +%s).txt"
|
||||
CLEANUP_FILES=()
|
||||
@@ -87,7 +88,7 @@ check_prerequisites() {
|
||||
check_server() {
|
||||
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}"
|
||||
else
|
||||
log_error "Server is not responding at ${SERVER_URL}"
|
||||
@@ -168,7 +169,7 @@ perform_upload() {
|
||||
CLEANUP_FILES+=("${RESPONSE_FILE}")
|
||||
|
||||
# Perform the upload with verbose output
|
||||
HTTP_STATUS=$(curl -s -w "%{http_code}" \
|
||||
HTTP_STATUS=$(curl -k -s -w "%{http_code}" \
|
||||
-X PUT \
|
||||
-H "Authorization: ${AUTH_HEADER}" \
|
||||
-H "Content-Type: text/plain" \
|
||||
@@ -217,7 +218,7 @@ test_retrieval() {
|
||||
|
||||
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}"
|
||||
else
|
||||
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
|
||||
# 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="https://laantungir.github.io/img_repo/24308d48eb498b593e55a87b6300ccffdea8432babc0bb898b1eff21ebbb72de.png"
|
||||
EXPECTED_HASH="24308d48eb498b593e55a87b6300ccffdea8432babc0bb898b1eff21ebbb72de"
|
||||
|
||||
echo "=== BUD-04 Mirror Endpoint Test with Authentication ==="
|
||||
echo "Blossom Server: $BLOSSOM_SERVER"
|
||||
echo "Target URL: $TEST_URL"
|
||||
echo "Expected Hash: $EXPECTED_HASH"
|
||||
echo ""
|
||||
|
||||
# Get a fresh challenge from the server
|
||||
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
|
||||
echo "❌ Failed to get challenge from server"
|
||||
exit 1
|
||||
@@ -48,7 +61,7 @@ RESPONSE=$(curl -s -w "\nHTTP_CODE:%{http_code}\n" \
|
||||
-H "Authorization: $auth_header" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$JSON_BODY" \
|
||||
http://localhost:9001/mirror)
|
||||
"$BLOSSOM_SERVER/mirror")
|
||||
|
||||
echo "Response:"
|
||||
echo "$RESPONSE"
|
||||
@@ -65,9 +78,9 @@ if [ "$HTTP_CODE" = "200" ]; then
|
||||
# Try to access the mirrored blob
|
||||
echo ""
|
||||
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)
|
||||
|
||||
if [ "$BLOB_HTTP_CODE" = "200" ]; then
|
||||
@@ -82,7 +95,7 @@ if [ "$HTTP_CODE" = "200" ]; then
|
||||
# Test HEAD request for metadata
|
||||
echo ""
|
||||
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)
|
||||
|
||||
if [ "$HEAD_HTTP_CODE" = "200" ]; then
|
||||
|
||||
Reference in New Issue
Block a user