Finished BUD 1

This commit is contained in:
Your Name
2025-08-18 21:51:54 -04:00
parent e641c813eb
commit 95ccb3a9c4
24 changed files with 1728 additions and 31 deletions

View File

@@ -0,0 +1,39 @@
# Workspace Rules for Ginxsom Development
## Important Reminders for Future Assistants
### SQLite Configuration
- **SQLite is compiled locally** in this project, not using system package
- Location: `sqlite3-build/` directory contains local SQLite 3.37.2 build
- The system SQLite package had version conflicts, so we built from source
- Database file: `db/ginxsom.db` (local to project)
- Always use local SQLite binary: `./sqlite3-build/sqlite3`
### Development Setup
- **All development is LOCAL** - work within the project directory
- Local nginx runs on port 9001 (not system nginx on port 80)
- Configuration: `config/local-nginx.conf` (NOT system nginx configs)
- FastCGI socket: `/tmp/ginxsom-fcgi.sock`
- Never modify system nginx files in `/etc/nginx/` during development
### File Structure
- Blob files stored in: `blobs/` directory
- Database: `db/ginxsom.db`
- Built binary: `build/ginxsom-fcgi`
- Logs: `logs/` directory (nginx access/error logs)
### Testing
- Test files are already in `blobs/`:
- `708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp`
- `f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e.jpg`
- Test URLs: `http://localhost:9001/<sha256>` or `http://localhost:9001/<sha256>.<ext>`
### Build Process
- Compile with: `make`
- Start FastCGI: `./scripts/start-fcgi.sh`
- Start nginx: `nginx -p . -c config/local-nginx.conf`
### Common Issues
- If nginx fails to start, check for port conflicts or kill existing processes
- If FastCGI fails, check socket permissions and that binary exists
- Always use local paths, never assume system installations

368
FASTCGI.md Normal file
View File

@@ -0,0 +1,368 @@
# FastCGI Protocol Flow Documentation
This document provides ASCII flow charts to understand how FastCGI works with nginx for the ginxsom blossom server.
## FastCGI Overview
FastCGI is a binary protocol that allows web servers (like nginx) to communicate with application servers efficiently. Unlike CGI which spawns a new process per request, FastCGI applications are persistent and can handle multiple concurrent requests.
## 1. FastCGI Connection Setup Flow
```
┌─────────────────┐ Socket ┌─────────────────┐
│ nginx │◄──────────────►│ FastCGI App │
│ Web Server │ Connection │ (ginxsom) │
└─────────────────┘ └─────────────────┘
│ │
│ 1. Create Unix socket │
│ /tmp/ginxsom.sock │
│ │
│ 2. FastCGI app binds & listens │
│◄───────────────────────────────────│
│ │
│ 3. nginx connects when needed │
│───────────────────────────────────►│
│ │
│ 4. Connection established │
│◄──────────────────────────────────►│
```
## 2. HTTP Request Processing Flow
```
┌─────────┐ HTTP ┌─────────┐ FastCGI ┌─────────┐
│ Client │────────────►│ nginx │─────────────►│ ginxsom │
│Browser │ │ │ │ FastCGI │
└─────────┘ └─────────┘ └─────────┘
│ │ │
│ HTTP Request │ │
│ PUT /upload │ │
│────────────────────────► │
│ │ │
│ │ FCGI_BEGIN_REQUEST │
│ │───────────────────────►│
│ │ │
│ │ FCGI_PARAMS │
│ │ (HTTP headers, etc) │
│ │───────────────────────►│
│ │ │
│ │ FCGI_STDIN │
│ │ (Request body) │
│ │───────────────────────►│
│ │ │
│ │ Process │
│ │◄───────Request─────────│
│ │ │
│ │ FCGI_STDOUT │
│ │ (Response headers) │
│ │◄───────────────────────│
│ │ │
│ │ FCGI_STDOUT │
│ │ (Response body) │
│ │◄───────────────────────│
│ │ │
│ │ FCGI_END_REQUEST │
│ │◄───────────────────────│
│ │ │
│ HTTP Response │ │
│◄──────────────────────│ │
```
## 3. FastCGI Record Structure
```
FastCGI Record Format:
┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ Version │ Type │RequestID│RequestID│ Length │ Length │ Padding │Reserved │
│ (1) │ (1) │ Hi │ Lo │ Hi │ Lo │ Length │ (1) │
│ │ │ (1) │ (1) │ (1) │ (1) │ (1) │ │
└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
│◄────────────────── 8 bytes header ──────────────────────►│
│ │
│◄─────────────────── Content (Length bytes) ─────────────►│
│ │
│◄───── Padding (Padding Length bytes) ────►│
Record Types:
- FCGI_BEGIN_REQUEST (1) - Start new request
- FCGI_PARAMS (4) - Environment variables
- FCGI_STDIN (5) - Request body data
- FCGI_STDOUT (6) - Response data
- FCGI_END_REQUEST (3) - End of request
```
## 4. Ginxsom Endpoint Handling Flow
### 4a. Static File Request (Direct nginx)
```
┌─────────┐ ┌─────────┐
│ Client │ │ nginx │
└─────────┘ └─────────┘
│ │
│ GET /{sha256hash} │
│───────────────────────►│
│ │
│ │ Check file exists:
│ │ /var/lib/ginxsom/files/
│ │ {first2}/{remaining}
│ │
│ │ ┌─ File exists? ─┐
│ │ │ YES │
│ │ └────────────────┘
│ │ │
│ HTTP 200 + File │ │ Serve directly
│◄──────────────────────│◄──────┘
│ │
Alternative flow:
│ │ ┌─ File exists? ─┐
│ │ │ NO │
│ │ └────────────────┘
│ │ │
│ HTTP 404 Not Found │ │
│◄──────────────────────│◄──────┘
```
### 4b. Upload Request (FastCGI)
```
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Client │ │ nginx │ │ ginxsom │ │ File │
│ │ │ │ │ FastCGI │ │ System │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │
│ PUT /upload │ │ │
│ Auth: xyz │ │ │
│ Body: file │ │ │
│─────────────►│ │ │
│ │ │ │
│ │ FCGI_PARAMS │ │
│ │ METHOD=PUT │ │
│ │ URI=/upload │ │
│ │ AUTH=xyz │ │
│ │─────────────►│ │
│ │ │ │
│ │ FCGI_STDIN │ │
│ │ (file data) │ │
│ │─────────────►│ │
│ │ │ │
│ │ │ Verify Auth │
│ │ │ (nostr sig) │
│ │ │ │
│ │ │ Calculate │
│ │ │ SHA-256 │
│ │ │ │
│ │ │ Write file │
│ │ │─────────────►│
│ │ │ │
│ │ │ Store │
│ │ │ metadata │
│ │ │────────────────────►
│ │ │ │ DB
│ │ │ │
│ │ FCGI_STDOUT │ │
│ │ 200 OK │ │
│ │ {sha256} │ │
│ │◄─────────────│ │
│ │ │ │
│ HTTP 200 OK │ │ │
│ {sha256} │ │ │
│◄─────────────│ │ │
```
### 4c. HEAD Request for Metadata (FastCGI)
```
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Client │ │ nginx │ │ ginxsom │
│ │ │ │ │ FastCGI │
└─────────┘ └─────────┘ └─────────┘
│ │ │
│ HEAD /{sha256} │ │
│──────────────────►│ │
│ │ │
│ │ FCGI_PARAMS │
│ │ METHOD=HEAD │
│ │ BLOSSOM_HASH=... │
│ │──────────────────►│
│ │ │
│ │ │ Query DB for
│ │ │ file metadata
│ │ │
│ │ FCGI_STDOUT │
│ │ Content-Length: X │
│ │ Content-Type: Y │
│ │ x-sha256: hash │
│ │◄──────────────────│
│ │ │
│ HTTP Headers Only │ │
│◄──────────────────│ │
```
## 5. Development vs Production Deployment
### Development Mode
```
┌─────────────┐ ┌─────────────┐
│ Terminal │ │ nginx │
│ │ │ │
│ ./ginxsom │◄──────────────►│ Config: │
│ --fastcgi │ Unix Socket │ fastcgi_pass│
│ --socket │ /tmp/ginxsom │ unix:/tmp/ │
│ /tmp/... │ .sock │ ginxsom.sock│
└─────────────┘ └─────────────┘
│ │
│ Direct execution │ sudo systemctl
│ Easy debugging │ reload nginx
│ Ctrl+C to stop │ (config changes)
│ Real-time logs │
```
### Production Mode
```
┌─────────────┐ ┌─────────────┐
│ systemd │ │ nginx │
│ │ │ │
│ ginxsom │◄──────────────►│ Config: │
│ .service │ Unix Socket │ fastcgi_pass│
│ │ /run/ginxsom/ │ unix:/run/ │
│ Auto-start │ ginxsom.sock │ ginxsom/... │
│ Auto-restart│ │ │
│ Log to file │ │ │
└─────────────┘ └─────────────┘
```
## 6. Complete nginx + FastCGI Request Flow
```
┌───────┐ ┌─────────────────────────────────────────────────┐ ┌─────────┐
│Client │ │ nginx │ │ginxsom │
└───────┘ └─────────────────────────────────────────────────┘ │FastCGI │
│ │ └─────────┘
│ GET /abc123...def │ │
│───────────────────────►│ │
│ │ │
│ │ location ~ ^/([a-f0-9]{64})$ { │
│ │ # Check if static file exists │
│ │ try_files /$prefix/$suffix =404 │
│ │ } │
│ │ │
│ │ ┌─ File exists? ─┐ │
│ │ │ YES │ │
│ │ └────────────────┘ │
│ │ │ │
│ │ │ Serve directly (fast!) │
│ HTTP 200 + File │ │ │
│◄──────────────────────│◄──────┘ │
│ │ │
│ │ │
│ PUT /upload │ │
│ Authorization: xyz │ │
│───────────────────────►│ │
│ │ │
│ │ location /upload { │
│ │ fastcgi_pass unix:/tmp/ │
│ │ ginxsom.sock; │
│ │ } │
│ │ │
│ │ ┌─ FastCGI Protocol ─┐ │
│ │ │ FCGI_BEGIN_REQUEST │─────────────►│
│ │ │ FCGI_PARAMS │─────────────►│
│ │ │ FCGI_STDIN │─────────────►│
│ │ └─────────────────────┘ │
│ │ │
│ │ │ Verify
│ │ │ nostr
│ │ │ signature
│ │ │
│ │ │ Calculate
│ │ │ SHA-256
│ │ │
│ │ │ Store file
│ │ │ & metadata
│ │ │
│ │ ┌─ FastCGI Response ─┐ │
│ │ │ FCGI_STDOUT │◄─────────────│
│ │ │ FCGI_END_REQUEST │◄─────────────│
│ │ └─────────────────────┘ │
│ │ │
│ HTTP 200 OK │ │
│ {"sha256": "..."} │ │
│◄──────────────────────│ │
```
## 7. libfcgi Library Usage
### Basic FastCGI Application Structure
```c
#include <fcgiapp.h>
int main() {
FCGX_Request request;
// Initialize FastCGI library
FCGX_Init();
FCGX_InitRequest(&request, 0, 0);
// Main request processing loop
while (FCGX_Accept_r(&request) == 0) {
// Read environment variables (HTTP headers, etc)
char* method = FCGX_GetParam("REQUEST_METHOD", request.envp);
char* uri = FCGX_GetParam("REQUEST_URI", request.envp);
char* auth = FCGX_GetParam("HTTP_AUTHORIZATION", request.envp);
// Route requests
if (strcmp(uri, "/health") == 0) {
handle_health(&request);
} else if (strcmp(uri, "/upload") == 0) {
handle_upload(&request);
} else if (strncmp(uri, "/head/", 6) == 0) {
handle_head(&request);
}
// Finish this request
FCGX_Finish_r(&request);
}
return 0;
}
```
### Reading Request Data
```
┌─────────────────┐ ┌─────────────────┐
│ nginx sends: │ │ ginxsom reads: │
├─────────────────┤ ├─────────────────┤
│ FCGI_PARAMS │────────►│ FCGX_GetParam() │
│ REQUEST_METHOD │ │ "PUT" │
│ REQUEST_URI │ │ "/upload" │
│ CONTENT_LENGTH │ │ "1024" │
│ HTTP_* │ │ headers │
├─────────────────┤ ├─────────────────┤
│ FCGI_STDIN │────────►│ FCGX_GetStr() │
│ (request body) │ │ file data │
└─────────────────┘ └─────────────────┘
```
### Writing Response Data
```
┌─────────────────┐ ┌─────────────────┐
│ ginxsom writes: │ │ nginx sends: │
├─────────────────┤ ├─────────────────┤
│ FCGX_PutS() │────────►│ HTTP Response │
│ "Content-Type: │ │ Headers │
│ application/ │ │ │
│ json\r\n\r\n" │ │ │
├─────────────────┤ ├─────────────────┤
│ FCGX_PutS() │────────►│ HTTP Response │
│ '{"status": │ │ Body │
│ "ok"}' │ │ │
└─────────────────┘ └─────────────────┘
```
This documentation shows how FastCGI enables nginx to efficiently serve static blossom files directly while delegating authenticated operations to the ginxsom FastCGI application.

View File

@@ -14,39 +14,42 @@ This document outlines the implementation plan for ginxsom, a FastCGI-based Blos
## Phase 1: Basic File Serving & Retrieval (BUD-01) ## Phase 1: Basic File Serving & Retrieval (BUD-01)
### 1.1 Infrastructure Setup ### 1.1 Infrastructure Setup
- [ ] Create basic directory structure - [x] Create basic directory structure
- [ ] Create `blobs/` directory for file storage - [x] Create `blobs/` directory for file storage
- [ ] Create `db/` directory for SQLite database - [x] Create `db/` directory for SQLite database
- [ ] Create `logs/` directory for application logs - [x] Create `logs/` directory for application logs
- [ ] Set up proper permissions (nginx readable, app writable) - [x] Set up proper permissions (nginx readable, app writable)
### 1.2 Database Schema ### 1.2 Database Schema
- [ ] Design SQLite schema for blob metadata - [x] Design SQLite schema for blob metadata
- [ ] `blobs` table: sha256, size, type, uploaded_at, uploader_pubkey - [x] `blobs` table: sha256, size, type, uploaded_at, uploader_pubkey, filename
- [ ] `server_config` table: key-value pairs for server settings - [x] `server_config` table: key-value pairs for server settings
- [ ] Create database initialization script - [x] Create database initialization script
- [ ] Add proper indexes on sha256 hash - [x] Add proper indexes on sha256 hash
### 1.3 nginx Configuration ### 1.3 nginx Configuration
- [ ] Configure nginx for static file serving - [x] Configure nginx for static file serving
- [ ] Set up location block for `GET /<sha256>` pattern - [x] Set up location block for `GET /<sha256>` pattern with extension support
- [ ] Configure proper MIME type detection - [x] Configure try_files directive for multiple extension fallbacks
- [ ] Add proper headers (Cache-Control, ETag, etc.) - [x] Configure proper MIME type detection
- [ ] Handle 404s gracefully when blob doesn't exist - [x] Add proper headers (Cache-Control, ETag, etc.)
- [ ] Configure FastCGI pass-through for non-GET requests - [x] Handle 404s gracefully when blob doesn't exist
- [x] Configure FastCGI pass-through for HEAD and non-GET requests
### 1.4 Basic HEAD Endpoint ### 1.4 Basic HEAD Endpoint
- [ ] Implement FastCGI handler for `HEAD /<sha256>` - [x] Implement FastCGI handler for `HEAD /<sha256>`
- [ ] Query database for blob metadata - [x] Query database for blob metadata (single source of truth)
- [ ] Return proper headers (Content-Type, Content-Length, etc.) - [x] Extract SHA-256 from URL (strip extensions)
- [ ] Return 404 if blob doesn't exist - [x] Return proper headers (Content-Type, Content-Length, etc.)
- [ ] Add server timing headers for debugging - [x] Return 404 if blob doesn't exist in database
- [x] Add server timing headers for debugging
### 1.5 Testing & Validation ### 1.5 Testing & Validation
- [ ] Create test blobs with known SHA-256 hashes - [x] Create test blobs with known SHA-256 hashes
- [ ] Verify nginx serves files correctly - [x] Verify nginx serves files correctly with extension support
- [ ] Verify HEAD requests return proper metadata - [x] Verify HEAD requests return proper metadata
- [ ] Test with missing files (404 responses) - [x] Test with missing files (404 responses)
- [x] Test HEAD requests with and without extensions
- [ ] Performance test with large files - [ ] Performance test with large files
--- ---
@@ -177,10 +180,10 @@ This document outlines the implementation plan for ginxsom, a FastCGI-based Blos
## Development Milestones ## Development Milestones
### Milestone 1: Basic Functionality (Phase 1 Complete) ### Milestone 1: Basic Functionality (Phase 1 Complete)
- nginx serves files by hash - [x] nginx serves files by hash with extension support
- HEAD requests return metadata - [x] HEAD requests return metadata from database
- Database stores blob information - [x] Database stores blob information with proper schema
### Milestone 2: Full Upload Support (Phase 2 Complete) ### Milestone 2: Full Upload Support (Phase 2 Complete)
- Authenticated uploads working - Authenticated uploads working
@@ -262,4 +265,3 @@ This document outlines the implementation plan for ginxsom, a FastCGI-based Blos
- [ ] Backup procedures - [ ] Backup procedures
- [ ] Security hardening guide - [ ] Security hardening guide
- [ ] Documentation and examples - [ ] Documentation and examples

50
Makefile Normal file
View File

@@ -0,0 +1,50 @@
# Ginxsom Blossom Server Makefile
CC = gcc
CFLAGS = -Wall -Wextra -std=c99 -O2
LIBS = -lfcgi -lsqlite3
SRCDIR = src
BUILDDIR = build
TARGET = $(BUILDDIR)/ginxsom-fcgi
# Source files
SOURCES = $(SRCDIR)/main.c
OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(BUILDDIR)/%.o)
# Default target
all: $(TARGET)
# Create build directory
$(BUILDDIR):
mkdir -p $(BUILDDIR)
# Compile object files
$(BUILDDIR)/%.o: $(SRCDIR)/%.c | $(BUILDDIR)
$(CC) $(CFLAGS) -c $< -o $@
# Link final executable
$(TARGET): $(OBJECTS)
$(CC) $(OBJECTS) $(LIBS) -o $@
# Clean build files
clean:
rm -rf $(BUILDDIR)
# Install (copy to system location)
install: $(TARGET)
sudo cp $(TARGET) /usr/local/bin/
sudo chmod 755 /usr/local/bin/ginxsom-fcgi
# Uninstall
uninstall:
sudo rm -f /usr/local/bin/ginxsom-fcgi
# Run the FastCGI application
run: $(TARGET)
./$(TARGET)
# Debug build
debug: CFLAGS += -g -DDEBUG
debug: $(TARGET)
.PHONY: all clean install uninstall run debug

115
README.md
View File

@@ -231,9 +231,122 @@ Successful uploads return blob descriptors:
│ └── b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf │ └── b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf
├── a8/ ├── a8/
│ └── 47/ │ └── 47/
│ └── a8472f6d93e42c1e5b4e9f3a7b2c8d4e6f9a1b3c5d7e8f0a1b2c3d4e5f6a7b8.png │ └── a8472f6d93e42c1e5b4e6f9a1b3c5d7e8f0a1b2c3d4e6f9a1b3c5d7e8f0a1b2c3d4e5f6a7b8.png
``` ```
## File and Extension Handling
ginxsom implements a sophisticated file and extension handling strategy that optimizes both performance and flexibility:
### Database-Driven Architecture
The system uses the SQLite database as the **single source of truth** for blob existence and metadata:
```sql
-- Database schema (clean SHA-256 hashes, no extensions)
CREATE TABLE blobs (
sha256 TEXT PRIMARY KEY, -- Clean hash: b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553
size INTEGER NOT NULL,
type TEXT NOT NULL,
uploaded_at INTEGER NOT NULL,
uploader_pubkey TEXT,
filename TEXT -- Original filename: document.pdf
);
```
**Key Benefits:**
- **Database Lookup vs Filesystem**: FastCGI queries the database instead of checking filesystem
- **Single Source of Truth**: Database definitively knows if a blob exists
- **Extension Irrelevant**: Database uses clean SHA-256 hashes without extensions
- **Performance**: Database queries are faster than filesystem stat() calls
### URL and Extension Support
ginxsom supports flexible URL patterns for maximum client compatibility:
```
# Both forms work identically:
GET /b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553
GET /b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf
# HEAD requests work with or without extensions:
HEAD /b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553
HEAD /b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf
```
### nginx File Storage Strategy
Files are stored on disk **with extensions** for nginx MIME type detection and optimal performance:
```nginx
# Blossom-compliant nginx configuration
location ~ ^/([a-f0-9]{64}).*$ {
root /var/lib/ginxsom/blobs;
try_files /$1* =404;
}
```
**How it works:**
1. **Hash-only lookup**: nginx extracts the 64-character SHA-256 hash from the URL, ignoring any extension
2. **Wildcard matching**: `try_files /$1*` finds any file starting with that hash
3. **Blossom compliance**: Serves correct file regardless of URL extension
**Examples:**
Client requests: `b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf`
- nginx extracts hash: `b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553`
- nginx looks for: `b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553*`
- nginx finds: `b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf`
- nginx serves the PDF with correct `Content-Type: application/pdf`
Client requests: `b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.mp3`
- nginx extracts same hash: `b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553`
- nginx looks for: `b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553*`
- nginx finds: `b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf`
- nginx serves the PDF (not 404) with correct `Content-Type: application/pdf`
**Key Benefits:**
- **Blossom Protocol Compliance**: Accepts any extension, returns correct MIME type
- **Performance**: nginx serves files directly without FastCGI involvement
- **Flexibility**: URLs work with correct extension, wrong extension, or no extension
- **MIME Detection**: nginx determines Content-Type from actual file extension on disk
This approach ensures that files are always found by their SHA-256 hash regardless of what extension (if any) is used in the request URL, while maintaining nginx's excellent static file serving performance.
### HEAD Request Handling
HEAD requests are processed differently to ensure accuracy:
1. **nginx receives HEAD request**: `/b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf`
2. **nginx forwards to FastCGI**: All HEAD requests go to ginxsom for metadata
3. **ginxsom extracts clean hash**: Strips `.pdf``b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553`
4. **ginxsom queries database**: `SELECT size, type FROM blobs WHERE sha256 = ?`
5. **ginxsom returns headers**: Content-Length, Content-Type, etc.
### Why This Approach?
**Performance:**
- GET requests: nginx serves directly from disk (no database hit)
- HEAD requests: Single database query (no filesystem checking)
- Extension mismatches: Eliminated by database-driven approach
**Reliability:**
- Database is authoritative source of blob existence
- No race conditions between filesystem and metadata
- Consistent behavior regardless of URL format
**Flexibility:**
- Clients can use URLs with or without extensions
- Browser-friendly URLs with proper file extensions
- API-friendly clean hash URLs
**Scalability:**
- nginx handles the heavy lifting (file serving)
- FastCGI only processes metadata operations
- Database queries scale better than filesystem operations
## Development ## Development
### Project Structure ### Project Structure

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
build/ginxsom-fcgi Executable file

Binary file not shown.

BIN
build/main.o Normal file

Binary file not shown.

233
config/deploy.sh Executable file
View File

@@ -0,0 +1,233 @@
#!/bin/bash
set -euo pipefail
# Ginxsom Deployment Script
# This script sets up the ginxsom blossom server configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
check_root() {
if [[ $EUID -eq 0 ]]; then
log_error "This script should not be run as root. Please run as a regular user with sudo access."
exit 1
fi
}
check_dependencies() {
log_info "Checking dependencies..."
if ! command -v nginx &> /dev/null; then
log_error "nginx is not installed. Please install nginx first."
exit 1
fi
if ! command -v systemctl &> /dev/null; then
log_error "systemctl is not available. This script requires systemd."
exit 1
fi
log_info "Dependencies check passed."
}
create_directories() {
log_info "Creating required directories..."
# Create data directories
sudo mkdir -p /var/lib/ginxsom/files
sudo mkdir -p /var/lib/ginxsom/db
sudo mkdir -p /run/ginxsom
sudo mkdir -p /var/log/ginxsom
sudo mkdir -p /etc/ginxsom
log_info "Directories created."
}
create_user() {
log_info "Creating ginxsom user..."
if ! id "ginxsom" &>/dev/null; then
sudo useradd --system --no-create-home --shell /bin/false ginxsom
log_info "User 'ginxsom' created."
else
log_info "User 'ginxsom' already exists."
fi
}
set_permissions() {
log_info "Setting file permissions..."
# Set ownership of data directories
sudo chown -R ginxsom:ginxsom /var/lib/ginxsom
sudo chown -R ginxsom:ginxsom /run/ginxsom
sudo chown -R ginxsom:ginxsom /var/log/ginxsom
sudo chown -R ginxsom:ginxsom /etc/ginxsom
# Set proper permissions
sudo chmod 755 /var/lib/ginxsom
sudo chmod 755 /var/lib/ginxsom/files
sudo chmod 755 /var/lib/ginxsom/db
sudo chmod 755 /run/ginxsom
sudo chmod 755 /var/log/ginxsom
sudo chmod 755 /etc/ginxsom
log_info "Permissions set."
}
deploy_nginx_config() {
log_info "Deploying nginx configuration..."
# Copy nginx configuration
sudo cp "$SCRIPT_DIR/nginx/ginxsom.conf" /etc/nginx/sites-available/ginxsom
# Enable the site
if [[ ! -L /etc/nginx/sites-enabled/ginxsom ]]; then
sudo ln -sf /etc/nginx/sites-available/ginxsom /etc/nginx/sites-enabled/ginxsom
log_info "Nginx site enabled."
else
log_info "Nginx site already enabled."
fi
# Test nginx configuration
if sudo nginx -t; then
log_info "Nginx configuration test passed."
else
log_error "Nginx configuration test failed. Please check the configuration."
exit 1
fi
}
deploy_systemd_service() {
log_info "Deploying systemd service..."
# Copy systemd service file
sudo cp "$SCRIPT_DIR/systemd/ginxsom.service" /etc/systemd/system/ginxsom.service
# Reload systemd
sudo systemctl daemon-reload
log_info "Systemd service installed."
}
create_sample_config() {
log_info "Creating sample configuration..."
if [[ ! -f /etc/ginxsom/config.toml ]]; then
sudo tee /etc/ginxsom/config.toml > /dev/null << 'EOF'
# Ginxsom Blossom Server Configuration
[server]
# FastCGI socket path
socket = "/run/ginxsom/ginxsom.sock"
# Data directory for files
data_dir = "/var/lib/ginxsom"
# Maximum file size in bytes (100MB)
max_file_size = 104857600
[logging]
level = "info"
file = "/var/log/ginxsom/ginxsom.log"
[storage]
# File storage directory
files_dir = "/var/lib/ginxsom/files"
# Database file for metadata
database = "/var/lib/ginxsom/db/ginxsom.db"
[auth]
# Enable authentication for uploads
require_auth = true
# Enable list endpoint
enable_list = false
# Enable mirror endpoint
enable_mirror = false
EOF
sudo chown ginxsom:ginxsom /etc/ginxsom/config.toml
sudo chmod 640 /etc/ginxsom/config.toml
log_info "Sample configuration created at /etc/ginxsom/config.toml"
else
log_info "Configuration file already exists at /etc/ginxsom/config.toml"
fi
}
show_status() {
log_info "Deployment complete! Next steps:"
echo ""
echo "1. Build and install the ginxsom binary:"
echo " cd $PROJECT_ROOT"
echo " make build"
echo " sudo make install"
echo ""
echo "2. Start the services:"
echo " sudo systemctl enable ginxsom"
echo " sudo systemctl start ginxsom"
echo " sudo systemctl reload nginx"
echo ""
echo "3. Check status:"
echo " sudo systemctl status ginxsom"
echo " sudo systemctl status nginx"
echo ""
echo "4. Test the server:"
echo " curl http://localhost/health"
echo ""
echo "Configuration files:"
echo " - Nginx: /etc/nginx/sites-available/ginxsom"
echo " - SystemD: /etc/systemd/system/ginxsom.service"
echo " - Config: /etc/ginxsom/config.toml"
echo ""
}
main() {
log_info "Starting ginxsom deployment..."
check_root
check_dependencies
create_user
create_directories
set_permissions
deploy_nginx_config
deploy_systemd_service
create_sample_config
show_status
log_info "Deployment script completed successfully!"
}
# Parse command line arguments
case "${1:-}" in
--help|-h)
echo "Usage: $0 [options]"
echo ""
echo "Options:"
echo " --help, -h Show this help message"
echo ""
echo "This script deploys the ginxsom blossom server configuration."
exit 0
;;
*)
main "$@"
;;
esac

109
config/local-nginx.conf Normal file
View File

@@ -0,0 +1,109 @@
# Local Ginxsom Development Server Configuration
# This configuration serves files directly from the local repo directory
# Main context - specify error log here to override system default
error_log logs/error.log;
pid logs/nginx.pid;
events {
worker_connections 1024;
}
# HTTP context
http {
# Basic settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# MIME types
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging (relative to prefix directory)
access_log logs/access.log;
# FastCGI upstream configuration
upstream fastcgi_backend {
server unix:/tmp/ginxsom-fcgi.sock;
}
# Local development server
server {
listen 9001;
server_name localhost;
# Root directory for blossom files (local blobs directory)
root blobs;
# Maximum upload size (adjust as needed)
client_max_body_size 100M;
# Security headers
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
# Handle GET and HEAD requests for blob files - Blossom compliant
location ~ "^/([a-f0-9]{64}).*$" {
limit_except HEAD GET {
deny all;
}
# Route HEAD requests to FastCGI via rewrite
if ($request_method = HEAD) {
rewrite ^/(.*)$ /fcgi-head/$1 last;
}
# GET requests served directly with hash-only lookup
try_files /$1* =404;
# Set appropriate headers for blobs
add_header Cache-Control "public, max-age=31536000, immutable";
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
}
# FastCGI handler for HEAD requests
location ~ "^/fcgi-head/([a-f0-9]{64}).*$" {
internal;
fastcgi_pass fastcgi_backend;
fastcgi_param REQUEST_METHOD HEAD;
fastcgi_param REQUEST_URI /$1;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
}
# Health check endpoint
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
# List files endpoint for debugging
location /debug/list {
autoindex on;
autoindex_format json;
}
# Root redirect
location = / {
return 200 "Ginxsom Local Development Server\nTry: GET /<sha256>\nHealth: GET /health\n";
add_header Content-Type text/plain;
}
}
}

131
config/nginx/ginxsom.conf Normal file
View File

@@ -0,0 +1,131 @@
# Ginxsom Blossom Server Configuration
# This configuration serves files directly via nginx for maximum performance
# while handling authenticated operations through FastCGI
server {
listen 80;
server_name localhost; # Change this to your domain
# Root directory for blossom files (organized by SHA-256 hash)
root /var/lib/ginxsom/files;
# Maximum upload size (adjust as needed)
client_max_body_size 100M;
# Security headers
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
# Logging
access_log /var/log/nginx/ginxsom_access.log;
error_log /var/log/nginx/ginxsom_error.log;
# Static file serving - nginx handles this directly for maximum performance
# Files are stored as: /var/lib/ginxsom/files/{first2chars}/{remaining_hash}
location ~ ^/([a-f0-9]{64})$ {
set $hash $1;
set $prefix $1; # First 2 characters
set $suffix $1; # Remaining characters
# Extract first 2 chars and remaining
if ($hash ~ ^([a-f0-9]{2})([a-f0-9]{62})$) {
set $prefix $1;
set $suffix $2;
}
try_files /$prefix/$suffix =404;
# Set proper content type based on file extension in metadata
# This will be enhanced when we add metadata lookup
add_header Content-Type application/octet-stream;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# HEAD requests for file existence checking
# This endpoint checks if a file exists and returns metadata
location ~ ^/head/([a-f0-9]{64})$ {
# Pass to FastCGI application for metadata lookup
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_param REQUEST_METHOD HEAD;
fastcgi_param BLOSSOM_HASH $1;
fastcgi_pass unix:/run/ginxsom/ginxsom.sock;
}
# Upload endpoint - requires authentication
location /upload {
# Pass to FastCGI application for processing
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_pass unix:/run/ginxsom/ginxsom.sock;
# Only allow PUT method for uploads
if ($request_method !~ ^(PUT)$ ) {
return 405;
}
}
# List endpoint - returns list of files (if enabled)
location /list {
# Pass to FastCGI application
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_pass unix:/run/ginxsom/ginxsom.sock;
# Only allow GET method
if ($request_method !~ ^(GET)$ ) {
return 405;
}
}
# Mirror endpoint - for mirroring files from other servers
location /mirror {
# Pass to FastCGI application
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_pass unix:/run/ginxsom/ginxsom.sock;
# Only allow PUT method
if ($request_method !~ ^(PUT)$ ) {
return 405;
}
}
# Delete endpoint - requires authentication
location ~ ^/([a-f0-9]{64})$ {
# Handle DELETE requests through FastCGI
if ($request_method = DELETE) {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_param BLOSSOM_HASH $1;
fastcgi_pass unix:/run/ginxsom/ginxsom.sock;
}
# For GET/HEAD, fall through to static file serving above
}
# Health check endpoint
location /health {
# Pass to FastCGI application
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_pass unix:/run/ginxsom/ginxsom.sock;
access_log off;
}
# Deny access to hidden files and directories
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Deny access to backup and temporary files
location ~ ~$ {
deny all;
access_log off;
log_not_found off;
}
}

View File

@@ -0,0 +1,47 @@
[Unit]
Description=Ginxsom Blossom Server FastCGI Application
After=network.target
Wants=network-online.target
After=network-online.target
[Service]
Type=notify
User=ginxsom
Group=ginxsom
WorkingDirectory=/var/lib/ginxsom
ExecStart=/usr/local/bin/ginxsom --fastcgi --socket /run/ginxsom/ginxsom.sock
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=5s
# Security settings
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/ginxsom /run/ginxsom /var/log/ginxsom
PrivateTmp=true
PrivateDevices=true
ProtectHostname=true
ProtectClock=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictRealtime=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
# Resource limits
LimitNOFILE=65536
LimitNPROC=4096
# Environment
Environment=GINXSOM_CONFIG=/etc/ginxsom/config.toml
Environment=GINXSOM_DATA_DIR=/var/lib/ginxsom
Environment=GINXSOM_LOG_LEVEL=info
[Install]
WantedBy=multi-user.target

BIN
db/ginxsom.db Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

52
db/init.sh Executable file
View File

@@ -0,0 +1,52 @@
#!/bin/bash
# Ginxsom Database Initialization Script
# This script creates the SQLite database with the proper schema
set -e # Exit on any error
# Configuration
DB_DIR="$(dirname "$0")"
DB_FILE="$DB_DIR/ginxsom.db"
SCHEMA_FILE="$DB_DIR/schema.sql"
echo "Initializing Ginxsom database..."
# Check if schema file exists
if [ ! -f "$SCHEMA_FILE" ]; then
echo "Error: Schema file not found at $SCHEMA_FILE"
exit 1
fi
# Create database directory if it doesn't exist
mkdir -p "$DB_DIR"
# Remove existing database if it exists (for clean initialization)
if [ -f "$DB_FILE" ]; then
echo "Warning: Existing database found. Creating backup..."
cp "$DB_FILE" "$DB_FILE.backup.$(date +%s)"
rm "$DB_FILE"
fi
# Create new database with schema
echo "Creating database at $DB_FILE..."
sqlite3 "$DB_FILE" < "$SCHEMA_FILE"
# Set proper permissions
chmod 644 "$DB_FILE"
chmod 755 "$DB_DIR"
echo "Database initialized successfully!"
echo "Database location: $DB_FILE"
# Show database info
echo ""
echo "Database tables:"
sqlite3 "$DB_FILE" ".tables"
echo ""
echo "Server configuration:"
sqlite3 "$DB_FILE" "SELECT key, value, description FROM server_config;"
echo ""
echo "Database ready for use."

67
db/schema.sql Normal file
View File

@@ -0,0 +1,67 @@
-- Ginxsom Blossom Server Database Schema
-- SQLite database for blob metadata and server configuration
-- Enable foreign key constraints
PRAGMA foreign_keys = ON;
-- Main blobs table for storing blob metadata
CREATE TABLE IF NOT EXISTS blobs (
sha256 TEXT PRIMARY KEY NOT NULL, -- SHA-256 hash (64 hex chars)
size INTEGER NOT NULL, -- File size in bytes
type TEXT NOT NULL, -- MIME type
uploaded_at INTEGER NOT NULL, -- Unix timestamp
uploader_pubkey TEXT, -- Nostr public key (optional)
filename TEXT, -- Original filename (optional)
CHECK (length(sha256) = 64), -- Ensure valid SHA-256 hash length
CHECK (size >= 0), -- Ensure non-negative size
CHECK (uploaded_at > 0) -- Ensure valid timestamp
);
-- Server configuration table for key-value settings
CREATE TABLE IF NOT EXISTS server_config (
key TEXT PRIMARY KEY NOT NULL, -- Configuration key
value TEXT NOT NULL, -- Configuration value
description TEXT, -- Human-readable description
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) -- Last update timestamp
);
-- Indexes for performance optimization
CREATE INDEX IF NOT EXISTS idx_blobs_uploaded_at ON blobs(uploaded_at);
CREATE INDEX IF NOT EXISTS idx_blobs_uploader_pubkey ON blobs(uploader_pubkey);
CREATE INDEX IF NOT EXISTS idx_blobs_type ON blobs(type);
CREATE INDEX IF NOT EXISTS idx_blobs_size ON blobs(size);
-- Insert default server configuration
INSERT OR IGNORE INTO server_config (key, value, description) VALUES
('max_file_size', '104857600', 'Maximum file size in bytes (100MB)'),
('require_auth', 'false', 'Whether authentication is required for uploads'),
('allowed_types', '*', 'Allowed MIME types (* for all)'),
('server_name', 'ginxsom', 'Server name for responses'),
('storage_quota', '10737418240', 'Total storage quota in bytes (10GB)'),
('cleanup_interval', '86400', 'Cleanup interval in seconds (daily)'),
('max_upload_rate', '1048576', 'Max upload rate per client in bytes/sec (1MB/s)');
-- View for storage statistics
CREATE VIEW IF NOT EXISTS storage_stats AS
SELECT
COUNT(*) as total_blobs,
SUM(size) as total_bytes,
AVG(size) as avg_blob_size,
MIN(uploaded_at) as first_upload,
MAX(uploaded_at) as last_upload,
COUNT(DISTINCT uploader_pubkey) as unique_uploaders
FROM blobs;
-- View for recent uploads (last 24 hours)
CREATE VIEW IF NOT EXISTS recent_uploads AS
SELECT
sha256,
size,
type,
uploaded_at,
uploader_pubkey,
filename,
datetime(uploaded_at, 'unixepoch') as uploaded_datetime
FROM blobs
WHERE uploaded_at > (strftime('%s', 'now') - 86400)
ORDER BY uploaded_at DESC;

64
logs/access.log Normal file
View File

@@ -0,0 +1,64 @@
127.0.0.1 - - [18/Aug/2025:19:08:28 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:10:04 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:10:15 -0400] "GET / HTTP/1.1" 200 72 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:10:39 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:11:05 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:11:11 -0400] "HEAD /70/8d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 200 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:11:37 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 200 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:11:44 -0400] "HEAD /f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e HTTP/1.1" 200 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:11:59 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 200 203886 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:11:59 -0400] "GET /f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e HTTP/1.1" 200 30236 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:12:11 -0400] "GET /f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e HTTP/1.1" 200 30236 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:19:12:44 -0400] "GET /f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e.webp HTTP/1.1" 404 564 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:19:12:44 -0400] "GET /favicon.ico HTTP/1.1" 404 564 "http://localhost:9001/f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e.webp" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:19:17:05 -0400] "GET /f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e.jpg HTTP/1.1" 200 30236 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:19:17:06 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:17:13 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 200 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:17:38 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 200 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:17:45 -0400] "HEAD /f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e HTTP/1.1" 200 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:17:50 -0400] "HEAD /f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e.jpg HTTP/1.1" 200 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:18:20 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 200 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:20:27 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 200 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:19:23:40 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 200 203886 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:20:15:17 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:16:34 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:16:50 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:17:01 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 400 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:18:01 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:18:06 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 400 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:18:26 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 404 564 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:20:29:58 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 404 564 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:20:31:09 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 404 162 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:31:41 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 404 564 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:20:31:42 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 404 564 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:20:31:49 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 404 162 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:32:15 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 200 203886 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:20:32:41 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:20:33:48 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:34:15 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 400 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:34:20 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:20:34:22 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 200 203886 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:34:25 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 200 203886 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:20:34:25 -0400] "GET /favicon.ico HTTP/1.1" 404 564 "http://localhost:9001/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:20:34:53 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 400 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:36:59 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:37:06 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:37:32 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:20:40:39 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:41:52 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:42:04 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:20:42:51 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:42:56 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:20:42:59 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 200 203886 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:43:08 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:48:43 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:20:52:51 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:20:53:44 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:20:54:04 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:21:10:59 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 200 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:21:11:07 -0400] "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 200 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:21:11:13 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:21:11:13 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1" 200 203886 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:21:11:21 -0400] "HEAD /f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e HTTP/1.1" 200 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:21:11:28 -0400] "HEAD /0000000000000000000000000000000000000000000000000000000000000000 HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [18/Aug/2025:21:49:44 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"

92
logs/error.log Normal file
View File

@@ -0,0 +1,92 @@
2025/08/18 19:05:40 [emerg] 212857#212857: bind() to 0.0.0.0:8080 failed (98: Unknown error)
2025/08/18 19:05:40 [emerg] 212857#212857: bind() to 0.0.0.0:8080 failed (98: Unknown error)
2025/08/18 19:05:40 [emerg] 212857#212857: bind() to 0.0.0.0:8080 failed (98: Unknown error)
2025/08/18 19:05:40 [emerg] 212857#212857: bind() to 0.0.0.0:8080 failed (98: Unknown error)
2025/08/18 19:05:40 [emerg] 212857#212857: bind() to 0.0.0.0:8080 failed (98: Unknown error)
2025/08/18 19:05:40 [emerg] 212857#212857: still could not bind()
2025/08/18 19:08:10 [emerg] 213180#213180: bind() to 0.0.0.0:9000 failed (98: Unknown error)
2025/08/18 19:08:10 [emerg] 213180#213180: bind() to 0.0.0.0:9000 failed (98: Unknown error)
2025/08/18 19:08:10 [emerg] 213180#213180: bind() to 0.0.0.0:9000 failed (98: Unknown error)
2025/08/18 19:08:10 [emerg] 213180#213180: bind() to 0.0.0.0:9000 failed (98: Unknown error)
2025/08/18 19:08:10 [emerg] 213180#213180: bind() to 0.0.0.0:9000 failed (98: Unknown error)
2025/08/18 19:08:10 [emerg] 213180#213180: still could not bind()
2025/08/18 19:08:28 [error] 213123#213123: *1 open() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe" failed (2: No such file or directory), client: 127.0.0.1, server: localhost, request: "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1", host: "localhost:9000"
2025/08/18 19:08:33 [alert] 212996#212996: unlink() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/logs/nginx.pid" failed (2: No such file or directory)
2025/08/18 19:10:04 [error] 213452#213452: *1 open() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe" failed (2: No such file or directory), client: 127.0.0.1, server: localhost, request: "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1", host: "localhost:9001"
2025/08/18 19:10:39 [error] 213452#213452: *4 open() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe" failed (2: No such file or directory), client: 127.0.0.1, server: localhost, request: "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1", host: "localhost:9001"
2025/08/18 19:11:05 [error] 215075#215075: *5 open() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe" failed (2: No such file or directory), client: 127.0.0.1, server: localhost, request: "HEAD /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe HTTP/1.1", host: "localhost:9001"
2025/08/18 19:12:44 [error] 215220#215220: *11 open() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e.webp" failed (2: No such file or directory), client: 127.0.0.1, server: localhost, request: "GET /f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 19:12:44 [error] 215220#215220: *11 open() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/favicon.ico" failed (2: No such file or directory), client: 127.0.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "localhost:9001", referrer: "http://localhost:9001/f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e.webp"
2025/08/18 20:12:11 [emerg] 244527#244527: bind() to 0.0.0.0:9001 failed (98: Unknown error)
2025/08/18 20:12:11 [emerg] 244527#244527: bind() to 0.0.0.0:9001 failed (98: Unknown error)
2025/08/18 20:12:11 [emerg] 244527#244527: bind() to 0.0.0.0:9001 failed (98: Unknown error)
2025/08/18 20:12:11 [emerg] 244527#244527: bind() to 0.0.0.0:9001 failed (98: Unknown error)
2025/08/18 20:12:11 [emerg] 244527#244527: bind() to 0.0.0.0:9001 failed (98: Unknown error)
2025/08/18 20:12:11 [emerg] 244527#244527: still could not bind()
2025/08/18 20:18:26 [crit] 246736#246736: *11 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:18:26 [crit] 246736#246736: *11 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:18:26 [crit] 246736#246736: *11 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.jpg" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:18:26 [crit] 246736#246736: *11 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.jpeg" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:18:26 [crit] 246736#246736: *11 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.png" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:18:26 [crit] 246736#246736: *11 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.gif" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:18:26 [crit] 246736#246736: *11 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.pdf" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:18:26 [crit] 246736#246736: *11 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.txt" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:18:26 [crit] 246736#246736: *11 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.mp4" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:18:26 [crit] 246736#246736: *11 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.mp3" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:29:58 [crit] 246736#246736: *12 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:29:58 [crit] 246736#246736: *12 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:29:58 [crit] 246736#246736: *12 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.jpg" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:29:58 [crit] 246736#246736: *12 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.jpeg" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:29:58 [crit] 246736#246736: *12 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.png" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:29:58 [crit] 246736#246736: *12 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.gif" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:29:58 [crit] 246736#246736: *12 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.pdf" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:29:58 [crit] 246736#246736: *12 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.txt" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:29:58 [crit] 246736#246736: *12 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.mp4" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:29:58 [crit] 246736#246736: *12 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.mp3" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:09 [crit] 246736#246736: *13 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:09 [crit] 246736#246736: *13 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:09 [crit] 246736#246736: *13 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.jpg" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:09 [crit] 246736#246736: *13 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.jpeg" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:09 [crit] 246736#246736: *13 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.png" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:09 [crit] 246736#246736: *13 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.gif" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:09 [crit] 246736#246736: *13 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.pdf" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:09 [crit] 246736#246736: *13 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.txt" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:09 [crit] 246736#246736: *13 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.mp4" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:09 [crit] 246736#246736: *13 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.mp3" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:41 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:41 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:41 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.jpg" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:41 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.jpeg" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:41 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.png" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:41 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.gif" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:41 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.pdf" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:41 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.txt" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:41 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.mp4" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:41 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.mp3" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:42 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:42 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:42 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.jpg" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:42 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.jpeg" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:42 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.png" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:42 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.gif" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:42 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.pdf" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:42 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.txt" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:42 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.mp4" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:42 [crit] 246736#246736: *14 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.mp3" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:49 [crit] 246736#246736: *15 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:49 [crit] 246736#246736: *15 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:49 [crit] 246736#246736: *15 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.jpg" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:49 [crit] 246736#246736: *15 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.jpeg" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:49 [crit] 246736#246736: *15 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.png" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:49 [crit] 246736#246736: *15 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.gif" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:49 [crit] 246736#246736: *15 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.pdf" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:49 [crit] 246736#246736: *15 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.txt" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:49 [crit] 246736#246736: *15 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.mp4" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:31:49 [crit] 246736#246736: *15 stat() "/home/teknari/Sync/Programming/VibeCoding/ginxsom/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.mp3" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1", host: "localhost:9001"
2025/08/18 20:34:25 [error] 252785#252785: *6 open() "./blobs/favicon.ico" failed (2: No such file or directory), client: 127.0.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "localhost:9001", referrer: "http://localhost:9001/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp"
2025/08/18 21:07:53 [emerg] 265258#265258: bind() to 0.0.0.0:9001 failed (98: Unknown error)
2025/08/18 21:07:53 [emerg] 265258#265258: bind() to 0.0.0.0:9001 failed (98: Unknown error)
2025/08/18 21:07:53 [emerg] 265258#265258: bind() to 0.0.0.0:9001 failed (98: Unknown error)
2025/08/18 21:07:53 [emerg] 265258#265258: bind() to 0.0.0.0:9001 failed (98: Unknown error)
2025/08/18 21:07:53 [emerg] 265258#265258: bind() to 0.0.0.0:9001 failed (98: Unknown error)
2025/08/18 21:07:53 [emerg] 265258#265258: still could not bind()

1
logs/nginx.pid Normal file
View File

@@ -0,0 +1 @@
265507

68
scripts/start-fcgi.sh Executable file
View File

@@ -0,0 +1,68 @@
#!/bin/bash
# Start Ginxsom FastCGI Application
# Configuration
FCGI_BINARY="./build/ginxsom-fcgi"
SOCKET_PATH="/tmp/ginxsom-fcgi.sock"
PID_FILE="/tmp/ginxsom-fcgi.pid"
# Function to cleanup on exit
cleanup() {
echo "Cleaning up..."
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if kill -0 "$PID" 2>/dev/null; then
echo "Stopping FastCGI process $PID"
kill "$PID"
sleep 1
# Force kill if still running
if kill -0 "$PID" 2>/dev/null; then
kill -9 "$PID"
fi
fi
rm -f "$PID_FILE"
fi
rm -f "$SOCKET_PATH"
exit 0
}
# Setup signal handlers
trap cleanup SIGINT SIGTERM
# Check if binary exists
if [ ! -f "$FCGI_BINARY" ]; then
echo "Error: FastCGI binary not found at $FCGI_BINARY"
echo "Please run 'make' to build the application first"
exit 1
fi
# Clean up old socket and pid file
rm -f "$SOCKET_PATH" "$PID_FILE"
# Start the FastCGI application
echo "Starting Ginxsom FastCGI application..."
echo "Socket: $SOCKET_PATH"
echo "Binary: $FCGI_BINARY"
# Use spawn-fcgi to start the application
spawn-fcgi -s "$SOCKET_PATH" -M 666 -u "$USER" -g "$USER" -f "$FCGI_BINARY" -P "$PID_FILE"
if [ $? -eq 0 ]; then
echo "FastCGI application started successfully"
echo "PID: $(cat $PID_FILE 2>/dev/null || echo 'unknown')"
# Wait for the process to exit or be killed
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
echo "Monitoring process $PID (Press Ctrl+C to stop)"
while kill -0 "$PID" 2>/dev/null; do
sleep 1
done
echo "FastCGI process exited"
fi
else
echo "Failed to start FastCGI application"
exit 1
fi
cleanup

261
src/main.c Normal file
View File

@@ -0,0 +1,261 @@
/*
* Ginxsom Blossom Server - FastCGI Application
* Handles HEAD requests and other dynamic operations
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcgi_stdio.h>
#include <sqlite3.h>
#include <sys/stat.h>
#include <time.h>
#define MAX_SHA256_LEN 65
#define MAX_PATH_LEN 512
#define MAX_MIME_LEN 128
// Database path
#define DB_PATH "db/ginxsom.db"
// Blob metadata structure
typedef struct {
char sha256[MAX_SHA256_LEN];
long size;
char type[MAX_MIME_LEN];
long uploaded_at;
char filename[256];
int found;
} blob_metadata_t;
// Get blob metadata from database
int get_blob_metadata(const char* sha256, blob_metadata_t* metadata) {
sqlite3* db;
sqlite3_stmt* stmt;
int rc;
printf("DEBUG: get_blob_metadata() called with sha256='%s'\r\n", sha256);
printf("DEBUG: Opening database at path: %s\r\n", DB_PATH);
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
if (rc) {
printf("DEBUG: Database open FAILED: %s\r\n", sqlite3_errmsg(db));
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
return 0;
}
printf("DEBUG: Database opened successfully\r\n");
const char* sql = "SELECT sha256, size, type, uploaded_at, filename FROM blobs WHERE sha256 = ?";
printf("DEBUG: Preparing SQL: %s\r\n", sql);
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
printf("DEBUG: SQL prepare FAILED: %s\r\n", sqlite3_errmsg(db));
fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 0;
}
printf("DEBUG: SQL prepared successfully\r\n");
printf("DEBUG: Binding parameter sha256='%s'\r\n", sha256);
sqlite3_bind_text(stmt, 1, sha256, -1, SQLITE_STATIC);
printf("DEBUG: Executing SQL query...\r\n");
rc = sqlite3_step(stmt);
printf("DEBUG: sqlite3_step() returned: %d (SQLITE_ROW=%d, SQLITE_DONE=%d)\r\n",
rc, SQLITE_ROW, SQLITE_DONE);
if (rc == SQLITE_ROW) {
printf("DEBUG: Row found! Extracting metadata...\r\n");
strncpy(metadata->sha256, (char*)sqlite3_column_text(stmt, 0), MAX_SHA256_LEN-1);
metadata->size = sqlite3_column_int64(stmt, 1);
strncpy(metadata->type, (char*)sqlite3_column_text(stmt, 2), MAX_MIME_LEN-1);
metadata->uploaded_at = sqlite3_column_int64(stmt, 3);
const char* filename = (char*)sqlite3_column_text(stmt, 4);
if (filename) {
strncpy(metadata->filename, filename, 255);
} else {
metadata->filename[0] = '\0';
}
metadata->found = 1;
printf("DEBUG: Metadata extracted - size=%ld, type='%s'\r\n",
metadata->size, metadata->type);
} else {
printf("DEBUG: No row found for sha256='%s'\r\n", sha256);
metadata->found = 0;
}
sqlite3_finalize(stmt);
sqlite3_close(db);
printf("DEBUG: Database closed, returning %d\r\n", metadata->found);
return metadata->found;
}
// Check if physical file exists (with extension based on MIME type)
int file_exists_with_type(const char* sha256, const char* mime_type) {
char filepath[MAX_PATH_LEN];
const char* extension = "";
// Determine file extension based on MIME type
if (strstr(mime_type, "image/jpeg")) {
extension = ".jpg";
} else if (strstr(mime_type, "image/webp")) {
extension = ".webp";
} else if (strstr(mime_type, "image/png")) {
extension = ".png";
} else if (strstr(mime_type, "image/gif")) {
extension = ".gif";
} else if (strstr(mime_type, "video/mp4")) {
extension = ".mp4";
} else if (strstr(mime_type, "video/webm")) {
extension = ".webm";
} else if (strstr(mime_type, "audio/mpeg")) {
extension = ".mp3";
} else if (strstr(mime_type, "audio/ogg")) {
extension = ".ogg";
} else if (strstr(mime_type, "text/plain")) {
extension = ".txt";
}
snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256, extension);
printf("DEBUG: file_exists_with_type() checking path: '%s' (MIME: %s)\r\n", filepath, mime_type);
struct stat st;
int result = stat(filepath, &st);
printf("DEBUG: stat() returned: %d (0=success, -1=fail)\r\n", result);
if (result == 0) {
printf("DEBUG: File exists! Size: %ld bytes\r\n", st.st_size);
return 1;
} else {
printf("DEBUG: File does not exist or stat failed\r\n");
return 0;
}
}
// Handle HEAD request for blob
void handle_head_request(const char* sha256) {
blob_metadata_t metadata = {0};
printf("DEBUG: handle_head_request called with sha256=%s\r\n", sha256);
// Validate SHA-256 format (64 hex characters)
if (strlen(sha256) != 64) {
printf("DEBUG: SHA-256 length validation failed: %zu\r\n", strlen(sha256));
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Invalid SHA-256 hash format\n");
return;
}
printf("DEBUG: SHA-256 length validation passed\r\n");
// Check if blob exists in database - this is the single source of truth
if (!get_blob_metadata(sha256, &metadata)) {
printf("DEBUG: Database lookup failed for sha256=%s\r\n", sha256);
printf("Status: 404 Not Found\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Blob not found\n");
return;
}
printf("DEBUG: Database lookup succeeded - blob exists\r\n");
// Return successful HEAD response with metadata from database
printf("Status: 200 OK\r\n");
printf("Content-Type: %s\r\n", metadata.type);
printf("Content-Length: %ld\r\n", metadata.size);
printf("Cache-Control: public, max-age=31536000, immutable\r\n");
printf("ETag: \"%s\"\r\n", metadata.sha256);
// Add timing header for debugging
printf("X-Ginxsom-Server: FastCGI\r\n");
printf("X-Ginxsom-Timestamp: %ld\r\n", time(NULL));
if (strlen(metadata.filename) > 0) {
printf("X-Original-Filename: %s\r\n", metadata.filename);
}
printf("\r\n");
// HEAD request - no body content
}
// Extract SHA-256 from request URI (Blossom compliant - ignores any extension)
const char* extract_sha256_from_uri(const char* uri) {
static char sha256_buffer[MAX_SHA256_LEN];
if (!uri || uri[0] != '/') {
return NULL;
}
const char* start = uri + 1; // Skip leading '/'
// Extract exactly 64 hex characters, ignoring anything after (extensions, etc.)
int len = 0;
for (int i = 0; i < 64 && start[i] != '\0'; i++) {
char c = start[i];
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
// If we hit a non-hex character before 64 chars, it's invalid
if (len < 64) {
return NULL;
}
break;
}
sha256_buffer[i] = c;
len = i + 1;
}
// Must be exactly 64 hex characters
if (len != 64) {
return NULL;
}
sha256_buffer[64] = '\0';
return sha256_buffer;
}
int main(void) {
while (FCGI_Accept() >= 0) {
// DEBUG: Log every request received
printf("DEBUG: FastCGI received request\r\n");
const char* request_method = getenv("REQUEST_METHOD");
const char* request_uri = getenv("REQUEST_URI");
// DEBUG: Log request details
printf("DEBUG: METHOD=%s, URI=%s\r\n",
request_method ? request_method : "NULL",
request_uri ? request_uri : "NULL");
if (!request_method || !request_uri) {
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Invalid request\n");
continue;
}
// Handle HEAD requests for blob metadata
if (strcmp(request_method, "HEAD") == 0) {
const char* sha256 = extract_sha256_from_uri(request_uri);
printf("DEBUG: Extracted SHA256=%s\r\n", sha256 ? sha256 : "NULL");
if (sha256) {
handle_head_request(sha256);
} else {
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Invalid SHA-256 hash in URI\n");
}
} else {
// Other methods not implemented yet
printf("Status: 501 Not Implemented\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Method %s not implemented\n", request_method);
}
}
return 0;
}