diff --git a/.clinerules/workspace_rules.md b/.clinerules/workspace_rules.md new file mode 100644 index 0000000..17dccfc --- /dev/null +++ b/.clinerules/workspace_rules.md @@ -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/` or `http://localhost:9001/.` + +### 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 diff --git a/FASTCGI.md b/FASTCGI.md new file mode 100644 index 0000000..4d0d954 --- /dev/null +++ b/FASTCGI.md @@ -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 + +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. diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index c7d2a9b..652b55a 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -14,39 +14,42 @@ This document outlines the implementation plan for ginxsom, a FastCGI-based Blos ## Phase 1: Basic File Serving & Retrieval (BUD-01) ### 1.1 Infrastructure Setup -- [ ] Create basic directory structure - - [ ] Create `blobs/` directory for file storage - - [ ] Create `db/` directory for SQLite database - - [ ] Create `logs/` directory for application logs - - [ ] Set up proper permissions (nginx readable, app writable) +- [x] Create basic directory structure + - [x] Create `blobs/` directory for file storage + - [x] Create `db/` directory for SQLite database + - [x] Create `logs/` directory for application logs + - [x] Set up proper permissions (nginx readable, app writable) ### 1.2 Database Schema -- [ ] Design SQLite schema for blob metadata - - [ ] `blobs` table: sha256, size, type, uploaded_at, uploader_pubkey - - [ ] `server_config` table: key-value pairs for server settings - - [ ] Create database initialization script - - [ ] Add proper indexes on sha256 hash +- [x] Design SQLite schema for blob metadata + - [x] `blobs` table: sha256, size, type, uploaded_at, uploader_pubkey, filename + - [x] `server_config` table: key-value pairs for server settings + - [x] Create database initialization script + - [x] Add proper indexes on sha256 hash ### 1.3 nginx Configuration -- [ ] Configure nginx for static file serving - - [ ] Set up location block for `GET /` pattern - - [ ] Configure proper MIME type detection - - [ ] Add proper headers (Cache-Control, ETag, etc.) - - [ ] Handle 404s gracefully when blob doesn't exist - - [ ] Configure FastCGI pass-through for non-GET requests +- [x] Configure nginx for static file serving + - [x] Set up location block for `GET /` pattern with extension support + - [x] Configure try_files directive for multiple extension fallbacks + - [x] Configure proper MIME type detection + - [x] Add proper headers (Cache-Control, ETag, etc.) + - [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 -- [ ] Implement FastCGI handler for `HEAD /` - - [ ] Query database for blob metadata - - [ ] Return proper headers (Content-Type, Content-Length, etc.) - - [ ] Return 404 if blob doesn't exist - - [ ] Add server timing headers for debugging +- [x] Implement FastCGI handler for `HEAD /` + - [x] Query database for blob metadata (single source of truth) + - [x] Extract SHA-256 from URL (strip extensions) + - [x] Return proper headers (Content-Type, Content-Length, etc.) + - [x] Return 404 if blob doesn't exist in database + - [x] Add server timing headers for debugging ### 1.5 Testing & Validation -- [ ] Create test blobs with known SHA-256 hashes -- [ ] Verify nginx serves files correctly -- [ ] Verify HEAD requests return proper metadata -- [ ] Test with missing files (404 responses) +- [x] Create test blobs with known SHA-256 hashes +- [x] Verify nginx serves files correctly with extension support +- [x] Verify HEAD requests return proper metadata +- [x] Test with missing files (404 responses) +- [x] Test HEAD requests with and without extensions - [ ] Performance test with large files --- @@ -177,10 +180,10 @@ This document outlines the implementation plan for ginxsom, a FastCGI-based Blos ## Development Milestones -### Milestone 1: Basic Functionality ✓ (Phase 1 Complete) -- nginx serves files by hash -- HEAD requests return metadata -- Database stores blob information +### Milestone 1: Basic Functionality (Phase 1 Complete) +- [x] nginx serves files by hash with extension support +- [x] HEAD requests return metadata from database +- [x] Database stores blob information with proper schema ### Milestone 2: Full Upload Support (Phase 2 Complete) - Authenticated uploads working @@ -262,4 +265,3 @@ This document outlines the implementation plan for ginxsom, a FastCGI-based Blos - [ ] Backup procedures - [ ] Security hardening guide - [ ] Documentation and examples - diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..be51725 --- /dev/null +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index 9a4a1b6..11e9675 100644 --- a/README.md +++ b/README.md @@ -231,9 +231,122 @@ Successful uploads return blob descriptors: │ └── b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf ├── a8/ │ └── 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 ### Project Structure diff --git a/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp b/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp new file mode 100644 index 0000000..d3d1481 Binary files /dev/null and b/blobs/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp differ diff --git a/blobs/f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e.jpg b/blobs/f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e.jpg new file mode 100644 index 0000000..f08dda3 Binary files /dev/null and b/blobs/f8b5b4904c79bb53b2b417bc9c939268ac2871f194e95523b7b66113862da15e.jpg differ diff --git a/build/ginxsom-fcgi b/build/ginxsom-fcgi new file mode 100755 index 0000000..05df38b Binary files /dev/null and b/build/ginxsom-fcgi differ diff --git a/build/main.o b/build/main.o new file mode 100644 index 0000000..8b4d429 Binary files /dev/null and b/build/main.o differ diff --git a/config/deploy.sh b/config/deploy.sh new file mode 100755 index 0000000..2eefb66 --- /dev/null +++ b/config/deploy.sh @@ -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 diff --git a/config/local-nginx.conf b/config/local-nginx.conf new file mode 100644 index 0000000..145e48e --- /dev/null +++ b/config/local-nginx.conf @@ -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 /\nHealth: GET /health\n"; + add_header Content-Type text/plain; + } + } +} diff --git a/config/nginx/ginxsom.conf b/config/nginx/ginxsom.conf new file mode 100644 index 0000000..437b898 --- /dev/null +++ b/config/nginx/ginxsom.conf @@ -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; + } +} diff --git a/config/systemd/ginxsom.service b/config/systemd/ginxsom.service new file mode 100644 index 0000000..8b5b991 --- /dev/null +++ b/config/systemd/ginxsom.service @@ -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 diff --git a/db/ginxsom.db b/db/ginxsom.db new file mode 100644 index 0000000..401db6e Binary files /dev/null and b/db/ginxsom.db differ diff --git a/db/ginxsom.db.backup.1755562070 b/db/ginxsom.db.backup.1755562070 new file mode 100644 index 0000000..ce6cca2 Binary files /dev/null and b/db/ginxsom.db.backup.1755562070 differ diff --git a/db/ginxsom.db.backup.1755563905 b/db/ginxsom.db.backup.1755563905 new file mode 100644 index 0000000..284fade Binary files /dev/null and b/db/ginxsom.db.backup.1755563905 differ diff --git a/db/ginxsom.db.backup.1755565735 b/db/ginxsom.db.backup.1755565735 new file mode 100644 index 0000000..ee947cb Binary files /dev/null and b/db/ginxsom.db.backup.1755565735 differ diff --git a/db/init.sh b/db/init.sh new file mode 100755 index 0000000..7c323e7 --- /dev/null +++ b/db/init.sh @@ -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." diff --git a/db/schema.sql b/db/schema.sql new file mode 100644 index 0000000..51acb33 --- /dev/null +++ b/db/schema.sql @@ -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; diff --git a/logs/access.log b/logs/access.log new file mode 100644 index 0000000..6f2ed10 --- /dev/null +++ b/logs/access.log @@ -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" diff --git a/logs/error.log b/logs/error.log new file mode 100644 index 0000000..14e0681 --- /dev/null +++ b/logs/error.log @@ -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() diff --git a/logs/nginx.pid b/logs/nginx.pid new file mode 100644 index 0000000..8666562 --- /dev/null +++ b/logs/nginx.pid @@ -0,0 +1 @@ +265507 diff --git a/scripts/start-fcgi.sh b/scripts/start-fcgi.sh new file mode 100755 index 0000000..4376a5d --- /dev/null +++ b/scripts/start-fcgi.sh @@ -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 diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..676e422 --- /dev/null +++ b/src/main.c @@ -0,0 +1,261 @@ +/* + * Ginxsom Blossom Server - FastCGI Application + * Handles HEAD requests and other dynamic operations + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +}