release: hmac-file-server 3.2

This commit is contained in:
2025-06-13 04:24:11 +02:00
parent cc3a4f4dd7
commit 16f50940d0
34 changed files with 10354 additions and 2255 deletions

4
.gitignore vendored
View File

@ -1,4 +0,0 @@
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json

154
CHANGELOG.MD Normal file
View File

@ -0,0 +1,154 @@
# Changelog
> **Note:** This file is a technical changelog for developers and maintainers. For user-focused highlights, migration notes, and upgrade instructions, see [README.MD](./README.MD).
All notable changes to this project will be documented in this file.
## [3.2] - Stable Release
### Added (3.2)
- **Enhanced Documentation**: Comprehensive update of all documentation files to match current codebase
- **Protocol Specification Updates**: Detailed protocol documentation with implementation examples
- **Configuration Validation**: Improved configuration structure validation and error reporting
- **Developer Resources**: Updated build instructions and development setup guides
### Changed (3.2)
- **Documentation Structure**: Reorganized documentation for better clarity and maintenance
- **Configuration Examples**: Updated all configuration examples to reflect current options
- **API Documentation**: Enhanced API endpoint documentation with comprehensive examples
### Completed (3.2)
- **Feature Development**: Active development of new features and improvements
- **Testing Enhancements**: Expanded test coverage and validation
- **Performance Optimizations**: Ongoing performance improvements and monitoring
---
## [3.1-Stable] - 2025-06-08
### Added (3.1)
- **v3 (mod_http_upload_external) Support**: Implemented secure file uploads using HMAC-SHA256 validation and expiration checks, specifically designed for Prosody's mod_http_upload_external compatibility.
- **JWT Authentication**: Complete JWT token authentication system with configurable algorithms and expiration times.
- **Multiple Authentication Protocols**: Support for legacy v1, enhanced v2, token-based, and v3 HMAC protocols alongside JWT authentication.
- **File Naming Strategy**: Configurable file naming options including HMAC-based, original filename preservation, or no specific naming convention.
- **Advanced Configuration Structure**: Comprehensive configuration sections including server, security, uploads, downloads, logging, deduplication, ISO, timeouts, versioning, ClamAV, Redis, and workers.
### Changed (3.1)
- **Enhanced HMAC Validation**: Improved validation logic to support multiple protocol versions (v1, v2, token, v3) with proper fallback mechanisms.
- **Authentication Priority**: Implemented authentication priority system with JWT taking precedence when enabled, falling back to HMAC protocols.
- **Network Protocol Support**: Enhanced IPv4/IPv6 dual-stack support with protocol forcing options (ipv4, ipv6, auto).
- **Configuration Hot-Reloading**: Added support for reloading logging configuration via SIGHUP signal without full server restart.
### Fixed (3.1)
- **Protocol Compatibility**: Addressed compatibility issues with different HMAC protocol versions and mod_http_upload_external clients.
- **Error Handling**: Improved error handling for invalid or expired signatures during file uploads.
- **Configuration Validation**: Enhanced configuration validation to prevent common misconfigurations.
---
## [3.0-Stable] - 2025-06-07
### Added (3.0)
- Official Docker Compose support and example (`dockerenv/docker-compose.yml`).
- Multi-stage Dockerfile for minimal images (`dockerenv/dockerbuild/Dockerfile`).
- Extended documentation for Docker, Compose, and deployment paths.
- Quickstart and configuration examples for containerized environments.
- Monitoring and Prometheus metrics documentation improvements.
- **Seamless IPv4 and IPv6 support:** The server now automatically supports both IPv4 and IPv6 connections out of the box, with improved dual-stack handling and configuration via `forceprotocol`.
### Changed (3.0)
- Minimum Go version is now **1.24** (was 1.20).
- Updated all documentation and config examples to reflect new version and Docker usage.
- Improved configuration normalization and environment variable overrides for containers.
- Enhanced worker pool and resource auto-scaling logic.
### Fixed (3.0)
- Minor bugfixes for config parsing and Docker path handling.
- Improved error messages for missing or invalid configuration in container environments.
---
## [2.8-Stable] - 2026-05-01
### Added (2.8)
- Version check history for improved tracking.
- Enhanced ClamAV scanning with concurrent workers.
### Changed (2.8)
- Improved ISO-based storage for specialized use cases.
- Auto-scaling workers for optimized performance.
### Fixed (2.8)
- Minor issues in worker thread adjustments under high load.
---
## [2.7] - 2026-02-10
### Added (2.7)
- Concurrency improvements and auto-scaling worker enhancements
- Cleanup and removal of unused parameters in sorting functions
### Changed (2.7)
- Additional logging for file scanning operations
### Fixed (2.7)
- Minor stability issues related to ISO container mounting
- Fixed dual stack for upload (IPv4/IPv6)
---
## [2.6-Stable] - 2025-12-01
### Added (2.6)
- Deduplication support (removes duplicate files).
- ISO Container management.
- Dynamic worker scaling based on CPU & memory.
- PreCaching feature for faster file access.
### Changed (2.6)
- Worker pool scaling strategies for better performance.
- Enhanced logging with rotating logs using lumberjack.
### Fixed (2.6)
- Temporary file handling issues causing "Unsupported file type" warnings.
- MIME type checks for file extension mismatches.
---
## [2.5] - 2025-09-15
### Added (2.5)
- Redis caching integration for file metadata.
- ClamAV scanning for virus detection before finalizing uploads.
### Changed (2.5)
- Extended the default chunk size for chunked uploads.
- Updated official documentation links.
### Fixed (2.5)
- Edge case with versioning causing file rename conflicts.
---
## [2.0] - 2025-06-01
### Added (2.0)
- Chunked file uploads and downloads.
- Resumable upload support with partial file retention.
### Changed (2.0)
- Moved configuration management to Viper.
- Default Prometheus metrics for tracking memory & CPU usage.
### Fixed (2.0)
- Race conditions in file locking under heavy concurrency.
---
## [1.0] - 2025-01-01
### Added (1.0)
- Initial release with HMAC-based authentication.
- Basic file upload/download endpoints.
- Logging and fundamental configuration using .toml files.

302
INSTALL.MD Normal file
View File

@ -0,0 +1,302 @@
# HMAC File Server 3.2 Installation Guide
## Quick Installation for XMPP Operators
The HMAC File Server includes an automated installer script designed specifically for XMPP operators who want to quickly deploy a file sharing service for their chat servers.
### Prerequisites
- Linux system with systemd (Ubuntu 18.04+, CentOS 7+, Debian 9+, etc.)
- Root or sudo access
- At least 1GB free disk space
- Internet connection for downloading dependencies
### Installation
1. **Download or clone the repository:**
```bash
git clone https://github.com/PlusOne/hmac-file-server.git
cd hmac-file-server
```
2. **Run the installer:**
```bash
sudo ./installer.sh
```
**Alternative: Pre-set secrets via environment variables:**
```bash
# For automation or if interactive input doesn't work
HMAC_SECRET='your-super-secret-hmac-key-here-minimum-32-characters' sudo -E ./installer.sh
# With both HMAC and JWT secrets
HMAC_SECRET='your-hmac-secret-32-chars-minimum' \
JWT_SECRET='your-jwt-secret-also-32-chars-minimum' \
sudo -E ./installer.sh
```
3. **Follow the interactive prompts:**
- System user (default: `hmac-server`)
- Installation directories
- Server ports
- **HMAC secret**: Choose automatic generation (recommended) or enter manually
- **Optional features** (JWT, Redis, ClamAV, SSL/TLS)
- **JWT secret**: Also supports automatic generation if enabled
### Configuration Options
#### Core Settings
- **Server Port**: Default 8080 (HTTP file server)
- **Metrics Port**: Default 9090 (Prometheus metrics)
- **HMAC Secret**: Strong secret for authentication
- **Automatic generation** (recommended): Creates 48-character secure random key
- **Manual entry**: Minimum 32 characters required
- **Environment variable**: `HMAC_SECRET='your-secret'`
#### Optional Features
- **JWT Authentication**: Token-based auth for enhanced security
- **Automatic generation** available for JWT secrets
- Configurable expiration and algorithms
- **Redis Integration**: For session management and caching
- **ClamAV Scanning**: Real-time virus scanning of uploaded files
- **SSL/TLS**: Direct HTTPS support (or use reverse proxy)
### XMPP Server Integration
#### Prosody Configuration
Add to your Prosody configuration:
```lua
Component "upload.yourdomain.com" "http_file_share"
http_file_share_url = "http://localhost:8080"
```
#### Ejabberd Configuration
Add to your Ejabberd configuration:
```yaml
mod_http_file_share:
external_secret: "your-hmac-secret"
service_url: "http://localhost:8080"
```
### Post-Installation
1. **Start the service:**
```bash
sudo systemctl start hmac-file-server
```
2. **Check status:**
```bash
sudo systemctl status hmac-file-server
```
3. **View logs:**
```bash
sudo journalctl -u hmac-file-server -f
```
4. **Configure firewall (required):**
```bash
# Example for ufw (Ubuntu/Debian)
sudo ufw allow 8080/tcp comment "HMAC File Server"
sudo ufw allow 9090/tcp comment "HMAC File Server Metrics"
# Example for firewalld (CentOS/RHEL/Fedora)
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --permanent --add-port=9090/tcp
sudo firewall-cmd --reload
# Example for iptables (manual)
sudo iptables -A INPUT -p tcp --dport 8080 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 9090 -j ACCEPT
```
5. **Configure reverse proxy (recommended):**
```nginx
server {
listen 443 ssl http2;
server_name upload.yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# File upload settings
client_max_body_size 100M;
proxy_request_buffering off;
}
}
```
### File Locations
After installation:
- **Binary**: `/opt/hmac-file-server/hmac-file-server`
- **Configuration**: `/etc/hmac-file-server/config.toml`
- **Uploads**: `/var/lib/hmac-file-server/uploads/`
- **Logs**: `/var/log/hmac-file-server/hmac-file-server.log`
### Management Commands
```bash
# Service management
sudo systemctl start hmac-file-server
sudo systemctl stop hmac-file-server
sudo systemctl restart hmac-file-server
sudo systemctl reload hmac-file-server
# View logs
sudo journalctl -u hmac-file-server -f
sudo tail -f /var/log/hmac-file-server/hmac-file-server.log
# Edit configuration
sudo nano /etc/hmac-file-server/config.toml
sudo systemctl reload hmac-file-server # Apply changes
```
### Uninstallation
The HMAC File Server installer includes a comprehensive uninstallation system with advanced data preservation options:
```bash
sudo ./installer.sh --uninstall
```
#### Safe Uninstallation Features
🔒 **Interactive Confirmation System**
- Multiple confirmation steps prevent accidental data loss
- Automatic detection of data directories from configuration
- Smart backup system with timestamped backups in `/var/backups/hmac-file-server-*`
- Detailed reporting showing file counts and directory sizes
#### Five Data Handling Options
**1. 🗑️ Complete Removal**
- Deletes all data including uploads, deduplication files, and logs
- Requires typing "DELETE" for final confirmation
- Provides comprehensive warning about permanent data loss
**2. 💾 Preserve Uploads and Deduplication**
- Preserves critical user files and deduplication data
- Removes logs (typically not needed for data recovery)
- Ideal for system migration or reinstallation
**3. 📋 Preserve All Data**
- Keeps uploads, deduplication data, and logs
- Comprehensive data preservation option
- Best for troubleshooting or temporary removal
**4. 🎯 Custom Selection**
- Interactive selection of which directories to preserve
- Shows detailed information for each directory before decision
- Allows granular control over data preservation
**5. ❌ Cancel Operation**
- Safely exits without making any changes
- No system modifications performed
#### What Gets Removed (Service Components)
- ✓ Systemd service (stopped and disabled)
- ✓ Installation directory (`/opt/hmac-file-server/`)
- ✓ Configuration files (`/etc/hmac-file-server/`)
- ✓ System user (`hmac-server`)
- ✓ Any remaining binaries
#### Data Backup Location
When data preservation is selected, files are moved to:
- `/var/backups/hmac-file-server-TIMESTAMP/`
- Timestamped directories for multiple backup versions
- Preserves original directory structure
**⚠️ Important**: The uninstaller provides multiple safety checks and data preservation options. Choose wisely based on your needs!
### Security Considerations
1. **Configure firewall properly** - Only allow necessary ports (8080, 9090) to authorized networks
2. **Use strong HMAC secrets** (minimum 32 characters, use random generators)
3. **Enable JWT authentication** for enhanced security
4. **Set up SSL/TLS** either directly or via reverse proxy
5. **Enable ClamAV** for virus scanning if handling untrusted files
6. **Regular backups** of configuration and uploaded files
7. **Monitor logs** for suspicious activity
8. **Restrict network access** - Consider limiting access to internal networks only
### Monitoring
The server provides Prometheus metrics at `/metrics` endpoint:
```bash
curl http://localhost:9090/metrics
```
Key metrics to monitor:
- `hmac_requests_total` - Total requests
- `hmac_upload_size_bytes` - Upload sizes
- `hmac_errors_total` - Error counts
- `hmac_active_connections` - Active connections
### Troubleshooting
#### Service won't start
1. Check logs: `sudo journalctl -u hmac-file-server -f`
2. Verify configuration: `sudo nano /etc/hmac-file-server/config.toml`
3. Check permissions on data directories
4. Ensure ports are not in use: `sudo netstat -tlnp | grep :8080`
#### High memory usage
1. Adjust worker settings in configuration
2. Enable Redis for session management
3. Check for large file uploads in progress
#### Files not uploading
1. Verify HMAC secret matches between XMPP server and file server
2. Check file size limits in configuration
3. Ensure sufficient disk space
4. Review ClamAV logs if virus scanning enabled
### Support
- **Documentation**: See `README.MD` and `WIKI.MD`
- **Protocol Details**: See `PROTOCOL_SPECIFICATIONS.MD`
- **Issues**: GitHub issue tracker
- **Configuration**: All options documented in `WIKI.MD`
### Example Production Setup
For a production XMPP server with 1000+ users:
```toml
[server]
listenport = "8080"
metricsenabled = true
deduplicationenabled = true
[security]
enablejwt = true
# Strong secrets here
[uploads]
maxfilesize = "50MB"
ttlenabled = true
ttl = "720h" # 30 days
[workers]
max = 200
autoscaling = true
[redis]
enabled = true
host = "localhost"
port = 6379
[clamav]
enabled = true
```
This setup provides robust file sharing with deduplication, automatic cleanup, virus scanning, and scalable worker management.

View File

295
PROTOCOL_SPECIFICATIONS.MD Normal file
View File

@ -0,0 +1,295 @@
# HMAC File Server Authentication Protocol Specifications
This document outlines the different authentication protocols supported by the HMAC File Server for secure file uploads and downloads. The server supports multiple authentication methods to ensure backward compatibility while providing enhanced security features.
## Overview
The HMAC File Server supports two primary authentication mechanisms:
1. **HMAC-based Authentication** (Multiple versions: v1, v2, token, v3)
2. **JWT Authentication** (Bearer tokens)
All protocols use SHA256 hashing and require a shared secret key configured on the server.
---
## HMAC Authentication Protocols
### Common Elements
- **Algorithm**: HMAC-SHA256
- **Secret**: Shared secret key configured in `[security]` section
- **Transport**: URL query parameters for HMAC, headers for signatures
- **Encoding**: Hexadecimal encoding for HMAC values
---
### Legacy v1 Protocol (`v` parameter)
**Overview**: The original HMAC authentication protocol.
**URL Format**:
```
PUT /filename.ext?v=HMAC_SIGNATURE
```
**Message Construction**:
```
fileStorePath + "\x20" + contentLength
```
**Example**:
```bash
# For file "test.txt" with 1024 bytes
# Message: "test.txt\x201024"
curl -X PUT "http://localhost:8080/test.txt?v=a1b2c3d4..." --data-binary @test.txt
```
**Implementation Notes**:
- Uses space character (`\x20`) as separator
- Content-Length header must be accurate
- Simplest protocol, minimal metadata validation
---
### Enhanced v2 Protocol (`v2` parameter)
**Overview**: Enhanced version including content type validation.
**URL Format**:
```
PUT /filename.ext?v2=HMAC_SIGNATURE
```
**Message Construction**:
```
fileStorePath + "\x00" + contentLength + "\x00" + contentType
```
**Example**:
```bash
# For file "document.pdf" with 2048 bytes
# Message: "document.pdf\x002048\x00application/pdf"
curl -X PUT "http://localhost:8080/document.pdf?v2=e5f6g7h8..." --data-binary @document.pdf
```
**Implementation Notes**:
- Uses null characters (`\x00`) as separators
- Content-Type automatically detected from file extension
- Fallback to "application/octet-stream" for unknown extensions
---
### Token Protocol (`token` parameter)
**Overview**: Alternative parameter name for v2-style authentication.
**URL Format**:
```
PUT /filename.ext?token=HMAC_SIGNATURE
```
**Message Construction**: Same as v2 protocol
```
fileStorePath + "\x00" + contentLength + "\x00" + contentType
```
**Example**:
```bash
curl -X PUT "http://localhost:8080/image.jpg?token=i9j0k1l2..." --data-binary @image.jpg
```
**Implementation Notes**:
- Identical to v2 protocol but uses `token` parameter
- Useful for clients that prefer different parameter naming
---
### v3 Protocol - mod_http_upload_external Compatible (`v3` parameter)
**Overview**: Specifically designed for Prosody's `mod_http_upload_external` compatibility with expiration support.
**URL Format**:
```
PUT /path/to/file.ext?v3=HMAC_SIGNATURE&expires=UNIX_TIMESTAMP
```
**Message Construction**:
```
METHOD + "\n" + expires + "\n" + requestPath
```
**Example**:
```bash
# Current timestamp: 1717804800
# Message: "PUT\n1717804800\n/upload/myfile.txt"
curl -X PUT "http://localhost:8080/upload/myfile.txt?v3=m3n4o5p6...&expires=1717804800" --data-binary @myfile.txt
```
**Verification Process**:
1. Extract `v3` signature and `expires` timestamp
2. Validate `expires` is in the future
3. Construct message: `"{METHOD}\n{expires}\n{path}"`
4. Calculate HMAC-SHA256 of message
5. Compare with provided signature
**Implementation Notes**:
- Includes expiration timestamp validation
- Prevents replay attacks through time-based validation
- Path-only signing (no query parameters in signed message)
- HTTP method is part of the signed message
---
## JWT Authentication
**Overview**: Token-based authentication using JSON Web Tokens.
**Configuration**:
```toml
[security]
enablejwt = true
jwtsecret = "your-256-bit-secret"
jwtalgorithm = "HS256"
jwtexpiration = "24h"
```
**Header Format**:
```
Authorization: Bearer JWT_TOKEN
```
**Fallback Query Parameter**:
```
GET /file.txt?token=JWT_TOKEN
```
**Example Usage**:
```bash
# Header-based JWT
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." http://localhost:8080/file.txt
# Query parameter fallback
curl "http://localhost:8080/file.txt?token=eyJhbGciOiJIUzI1NiIs..."
```
**JWT Claims**: Standard JWT claims (exp, iat, iss, etc.) as configured.
---
## POST Upload Authentication
### X-Signature Header Method
**Overview**: For multipart form uploads via POST requests.
**Header Format**:
```
X-Signature: HMAC_OF_REQUEST_PATH
```
**Message Construction**:
```
requestPath (e.g., "/upload")
```
**Example**:
```bash
# HMAC of "/upload"
curl -X POST \
-H "X-Signature: CALCULATED_HMAC" \
-F 'file=@myfile.txt' \
http://localhost:8080/upload
```
---
## Authentication Priority and Fallbacks
The server checks authentication in the following order:
1. **JWT Authentication** (if `enablejwt = true`)
- Authorization header (Bearer token)
- Query parameter `token`
2. **HMAC Authentication** (if JWT disabled or not found)
- X-Signature header (for POST uploads)
- v3 protocol (with expires validation)
- v2 protocol
- token protocol
- v1 protocol (legacy)
---
## Security Considerations
### HMAC Protocols
- **Secret Management**: Use strong, randomly generated secrets
- **Time Validation**: v3 protocol includes expiration to prevent replay attacks
- **Content Validation**: v2/token protocols include content-type validation
- **Path Sanitization**: All protocols validate and sanitize file paths
### JWT Authentication
- **Token Expiration**: Configure appropriate expiration times
- **Secret Rotation**: Regularly rotate JWT signing keys
- **Algorithm Security**: Default HS256 is secure for most use cases
- **Transport Security**: Always use HTTPS in production
### General Security
- **HTTPS Only**: Use TLS encryption for all production deployments
- **Rate Limiting**: Implement reverse proxy rate limiting
- **File Validation**: Configure allowed file extensions
- **Virus Scanning**: Enable ClamAV integration for malware detection
- **Access Logs**: Monitor authentication failures and suspicious activity
---
## Migration Guide
### From v1 to v2
- Update HMAC calculation to include content type
- Change separator from `\x20` to `\x00`
- No breaking changes in URL structure
### From HMAC to JWT
- Set `enablejwt = true` in configuration
- Generate JWT tokens server-side or use external auth provider
- HMAC authentication remains available as fallback
### Adding v3 Support
- Implement expiration timestamp generation
- Update HMAC calculation to include HTTP method and expiration
- Useful for mod_http_upload_external compatibility
---
## Example Implementations
### Client-Side HMAC Generation (Python)
```python
import hmac
import hashlib
import time
def generate_v3_signature(secret, method, expires, path):
message = f"{method}\n{expires}\n{path}"
signature = hmac.new(
secret.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
return signature
# Example usage
secret = "your-hmac-secret"
expires = int(time.time()) + 3600 # 1 hour from now
signature = generate_v3_signature(secret, "PUT", expires, "/upload/file.txt")
```
### Server-Side Validation (Reference)
See the main.go file for complete implementation details of all validation functions:
- `validateHMAC()`: Legacy v1, v2, and token protocols
- `validateV3HMAC()`: v3 protocol with expiration
- `validateJWTFromRequest()`: JWT validation
---
This specification ensures consistent implementation across clients and provides multiple authentication options for different use cases and security requirements.

590
README.MD
View File

@ -1,216 +1,454 @@
# HMAC File Server Release Notes
# HMAC File Server 3.2
**HMAC File Server** is a secure, scalable, and feature-rich file server with advanced capabilities like HMAC authentication, resumable uploads, chunked uploads, file versioning, and optional ClamAV scanning for file integrity and security. This server is built with extensibility and operational monitoring in mind, including Prometheus metrics support and Redis integration.
## Overview
The **HMAC File Server** ensures secure file uploads and downloads using HMAC authentication and JWT tokens. It incorporates comprehensive security features, file versioning, deduplication, ISO container support, virus scanning, and Unix socket support for enhanced flexibility. Redis integration provides efficient caching and session management. Prometheus metrics and graceful shutdown mechanisms ensure reliable and efficient file handling.
Special thanks to **Thomas Leister** for inspiration drawn from [prosody-filer](https://github.com/ThomasLeister/prosody-filer).
## Features
- **Multiple Authentication Methods**: HMAC-based authentication and JWT token support
- **Multiple Protocol Support**: v1 (legacy), v2, v3 (mod_http_upload_external), and token-based uploads
- **File Management**: Deduplication, configurable TTL for automatic file cleanup
- **Upload Methods**: POST multipart uploads, PUT uploads for legacy protocols, v3 protocol support
- **Security**: Virus scanning via ClamAV, configurable file extensions validation
- **Performance**: Chunked uploads and downloads, worker pool management with auto-scaling
- **Storage Options**: Local storage, ISO container mounting for specialized needs
- **Monitoring**: Prometheus metrics integration with detailed system and operation metrics
- **Network Support**: IPv4/IPv6 dual-stack support with protocol forcing options
- **Configuration**: Hot-reloading of logging settings via SIGHUP signal
- **HMAC Authentication:** Secure file uploads and downloads with HMAC tokens.
- **File Versioning:** Enable versioning for uploaded files with configurable retention.
- **Chunked and Resumable Uploads:** Handle large files efficiently with support for resumable and chunked uploads.
- **ClamAV Scanning:** Optional virus scanning for uploaded files.
- **Prometheus Metrics:** Monitor system and application-level metrics.
- **Redis Integration:** Use Redis for caching or storing application states.
- **File Expiration:** Automatically delete files after a specified TTL.
- **Graceful Shutdown:** Handles signals and ensures proper cleanup.
- **Deduplication:** Remove duplicate files based on hashing for storage efficiency.
## Table of Contents
1. [Installation](#installation)
2. [Configuration](#configuration)
3. [Authentication](#authentication)
4. [API Endpoints](#api-endpoints)
5. [Usage Examples](#usage-examples)
6. [Setup](#setup)
- [Reverse Proxy](#reverse-proxy)
- [Systemd Service](#systemd-service)
7. [Building](#building)
8. [Docker Support](#docker-support)
9. [Changelog](#changelog)
10. [License](#license)
---
## Installation
### Prerequisites
### Quick Installation for XMPP Operators
- Go 1.20+
- Redis (optional, if Redis integration is enabled)
- ClamAV (optional, if file scanning is enabled)
### Clone and Build
The easiest way to install HMAC File Server is using the automated installer:
```bash
git clone https://github.com/your-repo/hmac-file-server.git
git clone https://github.com/PlusOne/hmac-file-server.git
cd hmac-file-server
go build -o hmac-file-server main.go
sudo ./installer.sh
```
The installer will:
- Install Go 1.24 (if needed)
- Create system user and directories
- Build and configure the server
- Set up systemd service
- Optionally install Redis and ClamAV
For detailed installation instructions, see [INSTALL.MD](INSTALL.MD).
### Manual Installation
> **Tip:** You can also run HMAC File Server using Docker Compose for easy deployment. See the Wiki for Docker setup instructions. The official image is available at `ghcr.io/plusone/hmac-file-server:latest`.
#### Prerequisites
- Go **1.24** or higher
- Redis server (optional, for caching)
- ClamAV (optional, for virus scanning)
#### Steps
1. Clone the repository:
```bash
git clone https://github.com/PlusOne/hmac-file-server.git
cd hmac-file-server
```
2. Build the server:
```bash
go build -o hmac-file-server ./cmd/server/main.go
```
3. Generate example configuration:
```bash
./hmac-file-server -genconfig
# or write to file:
./hmac-file-server -genconfig-path config.toml
```
4. Create necessary directories:
```bash
mkdir -p /path/to/hmac-file-server/data/
mkdir -p /path/to/hmac-file-server/deduplication/
```
5. Edit your `config.toml` file with appropriate settings.
6. Start the server:
```bash
./hmac-file-server -config config.toml
```
---
## Uninstallation
The installer script provides comprehensive uninstallation options with data preservation:
```bash
sudo ./installer.sh --uninstall
```
### Uninstall Options
The uninstaller offers five data handling options:
1. **🗑️ Delete all data** - Complete removal (requires typing "DELETE" to confirm)
2. **💾 Preserve uploads and deduplication data** - Keeps important files, removes logs
3. **📋 Preserve all data** - Keeps uploads, deduplication data, and logs
4. **🎯 Custom selection** - Choose exactly what to preserve
5. **❌ Cancel** - Exit without making changes
### Data Preservation
When preserving data, the uninstaller:
- Creates timestamped backups in `/var/backups/hmac-file-server-YYYYMMDD-HHMMSS/`
- Shows file counts and sizes before deletion decisions
- Safely moves data to backup locations
- Provides clear feedback on what was preserved or removed
### What Gets Removed
The uninstaller always removes:
- ✓ Systemd service and service file
- ✓ Installation directory (`/opt/hmac-file-server`)
- ✓ Configuration directory (`/etc/hmac-file-server`)
- ✓ System user (`hmac-server`)
- ✓ Binary files in common locations
Data directories are handled according to your selection.
---
## Configuration
The server configuration is managed through a `config.toml` file. Below are the supported configuration options:
The server uses a comprehensive `config.toml` file with the following main sections:
### **Server Configuration**
### Key Configuration Sections
| Key | Description | Example |
|------------------------|-----------------------------------------------------|---------------------------------|
| `ListenPort` | Port or Unix socket to listen on | `":8080"` |
| `UnixSocket` | Use a Unix socket (`true`/`false`) | `false` |
| `Secret` | Secret key for HMAC authentication | `"your-secret-key"` |
| `StoragePath` | Directory to store uploaded files | `"/mnt/storage/hmac-file-server"` |
| `LogLevel` | Logging level (`info`, `debug`, etc.) | `"info"` |
| `LogFile` | Log file path (optional) | `"/var/log/hmac-file-server.log"` |
| `MetricsEnabled` | Enable Prometheus metrics (`true`/`false`) | `true` |
| `MetricsPort` | Prometheus metrics server port | `"9090"` |
| `FileTTL` | File Time-to-Live duration | `"168h0m0s"` |
| `DeduplicationEnabled` | Enable file deduplication based on hashing | `true` |
| `MinFreeBytes` | Minimum free space required on storage path (in bytes) | `104857600` |
- **[server]**: Basic server settings (port, storage, metrics)
- **[security]**: HMAC secrets, JWT configuration
- **[uploads/downloads]**: File handling settings, allowed extensions
- **[logging]**: Log levels, file rotation settings
- **[clamav]**: Virus scanning configuration
- **[redis]**: Cache and session management
- **[workers]**: Thread pool and performance tuning
- **[iso]**: ISO container mounting (specialized storage)
- **[timeouts]**: HTTP timeout configurations
### **Uploads**
| Key | Description | Example |
|----------------------------|-----------------------------------------------|-------------|
| `ResumableUploadsEnabled` | Enable resumable uploads | `true` |
| `ChunkedUploadsEnabled` | Enable chunked uploads | `true` |
| `ChunkSize` | Chunk size for chunked uploads (bytes) | `1048576` |
| `AllowedExtensions` | Allowed file extensions for uploads | `[".png", ".jpg"]` |
### **Time Settings**
| Key | Description | Example |
|------------------|--------------------------------|----------|
| `ReadTimeout` | HTTP server read timeout | `"2h"` |
| `WriteTimeout` | HTTP server write timeout | `"2h"` |
| `IdleTimeout` | HTTP server idle timeout | `"2h"` |
### **ClamAV Configuration**
| Key | Description | Example |
|--------------------|-------------------------------------------|----------------------------------|
| `ClamAVEnabled` | Enable ClamAV virus scanning (`true`) | `true` |
| `ClamAVSocket` | Path to ClamAV Unix socket | `"/var/run/clamav/clamd.ctl"` |
| `NumScanWorkers` | Number of workers for file scanning | `2` |
### **Redis Configuration**
| Key | Description | Example |
|----------------------------|----------------------------------|-------------------|
| `RedisEnabled` | Enable Redis integration | `true` |
| `RedisDBIndex` | Redis database index | `0` |
| `RedisAddr` | Redis server address | `"localhost:6379"`|
| `RedisPassword` | Password for Redis authentication| `""` |
| `RedisHealthCheckInterval` | Health check interval for Redis | `"30s"` |
### **Workers and Connections**
| Key | Description | Example |
|------------------------|------------------------------------|-------------------|
| `NumWorkers` | Number of upload workers | `2` |
| `UploadQueueSize` | Size of the upload queue | `50` |
---
## Running the Server
### Basic Usage
Run the server with a configuration file:
```bash
./hmac-file-server -config ./config.toml
```
### Metrics Server
If `MetricsEnabled` is `true`, the Prometheus metrics server will run on the port specified in `MetricsPort` (default: `9090`).
---
## Development Notes
- **Versioning:** Enabled via `EnableVersioning`. Ensure `MaxVersions` is set appropriately to prevent storage issues.
- **File Cleaner:** The file cleaner runs hourly and deletes files older than the configured `FileTTL`.
- **Redis Health Check:** Automatically monitors Redis connectivity and logs warnings on failure.
---
## Testing
To run the server locally for development:
```bash
go run main.go -config ./config.toml
```
Use tools like **cURL** or **Postman** to test file uploads and downloads.
### Example File Upload with HMAC Token
```bash
curl -X PUT -H "Authorization: Bearer <HMAC-TOKEN>" -F "file=@example.txt" http://localhost:8080/uploads/example.txt
```
Replace `<HMAC-TOKEN>` with a valid HMAC signature generated using the configured `Secret`.
---
## Monitoring
Prometheus metrics include:
- File upload/download durations
- Memory usage
- CPU usage
- Active connections
- HTTP requests metrics (total, method, path)
---
## Example `config.toml`
### Example Configuration
```toml
[server]
bind_ip = "0.0.0.0"
listenport = "8080"
unixsocket = false
storagepath = "/mnt/storage/"
loglevel = "info"
logfile = "/var/log/file-server.log"
storagepath = "./uploads"
metricsenabled = true
metricsport = "9090"
DeduplicationEnabled = true
filettl = "336h" # 14 days
minfreebytes = 104857600 # 100 MB in bytes
[timeouts]
readtimeout = "4800s"
writetimeout = "4800s"
idletimeout = "24h"
deduplicationenabled = true
filenaming = "HMAC" # Options: "HMAC", "original", "None"
forceprotocol = "auto" # Options: "ipv4", "ipv6", "auto"
[security]
secret = "example-secret-key"
[versioning]
enableversioning = false
maxversions = 1
secret = "your-secure-hmac-secret"
enablejwt = false
jwtsecret = "your-jwt-secret"
jwtalgorithm = "HS256"
jwtexpiration = "24h"
[uploads]
resumableuploadsenabled = true
allowedextensions = [".txt", ".pdf", ".jpg", ".png", ".zip"]
chunkeduploadsenabled = true
chunksize = 8192
allowedextensions = [".txt", ".pdf", ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".svg", ".webp", ".wav", ".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".mpeg", ".mpg", ".m4v", ".3gp", ".3g2", ".mp3", ".ogg"]
[clamav]
clamavenabled = true
clamavsocket = "/var/run/clamav/clamd.ctl"
numscanworkers = 2
[redis]
redisenabled = true
redisdbindex = 0
redisaddr = "localhost:6379"
redispassword = ""
redishealthcheckinterval = "120s"
[workers]
numworkers = 2
uploadqueuesize = 50
chunksize = "10MB"
```
This configuration file is set up with essential features like Prometheus integration, ClamAV scanning, and file handling with deduplication and versioning options. Adjust the settings according to your infrastructure needs.
For complete configuration details, see the [Wiki](./WIKI.MD).
### Additional Features
---
- **Deduplication**: Automatically remove duplicate files based on hashing.
- **Versioning**: Store multiple versions of files and keep a maximum of `MaxVersions` versions.
- **ClamAV Integration**: Scan uploaded files for viruses using ClamAV.
- **Redis Caching**: Utilize Redis for caching file metadata for faster access.
## Authentication
This release ensures an efficient and secure file management system, suited for environments requiring high levels of data security and availability.
The server supports multiple authentication methods:
### 1. HMAC Authentication (Default)
- **Legacy v1**: Basic HMAC with path + content length
- **v2**: Enhanced HMAC with content type validation
- **Token**: Alternative HMAC parameter name
- **v3**: mod_http_upload_external compatible with expiration
### 2. JWT Authentication
When `enablejwt = true`:
- Bearer tokens in Authorization header
- Query parameter `token` as fallback
- Configurable expiration and algorithm
### Authentication Examples
```bash
# HMAC v2 upload
curl -X PUT "http://localhost:8080/myfile.txt?v2=HMAC_SIGNATURE" -d @file.txt
# JWT upload
curl -X POST -H "Authorization: Bearer JWT_TOKEN" -F 'file=@myfile.txt' http://localhost:8080/upload
# v3 protocol (mod_http_upload_external)
curl -X PUT "http://localhost:8080/upload/file.txt?v3=SIGNATURE&expires=TIMESTAMP" -d @file.txt
```
---
## API Endpoints
### Upload Endpoints
- **POST /upload**: Multipart form uploads (modern clients)
- **PUT /{filename}**: Direct uploads with HMAC or JWT authentication
- **PUT with v3 protocol**: mod_http_upload_external compatible uploads
### Download Endpoints
- **GET /{filename}**: Direct file downloads
- **HEAD /{filename}**: File metadata (size, type)
- **GET /download/{filename}**: Alternative download endpoint
### Management Endpoints
- **GET /health**: Health check endpoint for monitoring
- **GET /metrics**: Prometheus metrics (if enabled)
- **Various helper endpoints**: Defined in router setup
---
## Usage Examples
### Upload Examples
#### Multipart POST Upload
```bash
curl -X POST -F 'file=@example.jpg' \
-H "X-Signature: HMAC_OF_PATH" \
http://localhost:8080/upload
```
#### Legacy PUT Upload (v2)
```bash
# Calculate HMAC of: filename + "\x00" + content_length + "\x00" + content_type
curl -X PUT "http://localhost:8080/example.jpg?v2=CALCULATED_HMAC" \
--data-binary @example.jpg
```
#### v3 Protocol Upload (mod_http_upload_external)
```bash
# HMAC of: "PUT\n{timestamp}\n/path/to/file"
curl -X PUT "http://localhost:8080/upload/file.txt?v3=SIGNATURE&expires=1234567890" \
--data-binary @file.txt
```
#### JWT Upload
```bash
curl -X POST \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-F 'file=@example.jpg' \
http://localhost:8080/upload
```
### Download Examples
#### Direct Download
```bash
curl http://localhost:8080/example.jpg -o downloaded_file.jpg
```
#### Get File Info
```bash
curl -I http://localhost:8080/example.jpg
```
#### Health Check
```bash
curl http://localhost:8080/health
```
---
## Setup
### Reverse Proxy
#### Nginx Configuration
```nginx
server {
listen 80;
server_name your-domain.com;
client_max_body_size 10G; # Important for large uploads
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeout settings for large uploads
proxy_read_timeout 300;
proxy_connect_timeout 60;
proxy_send_timeout 300;
}
}
```
#### Apache2 Configuration
```apache
<VirtualHost *:80>
ServerName your-domain.com
ProxyPreserveHost On
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
# Large upload support
LimitRequestBody 10737418240 # 10GB
ProxyTimeout 300
</VirtualHost>
```
### Systemd Service
```ini
[Unit]
Description=HMAC File Server
After=network.target redis.service
[Service]
Type=simple
ExecStart=/path/to/hmac-file-server -config /path/to/config.toml
ExecReload=/bin/kill -SIGHUP $MAINPID
WorkingDirectory=/path/to/hmac-file-server
Restart=always
RestartSec=10
User=hmac-server
Group=hmac-server
# Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/path/to/uploads /path/to/logs
[Install]
WantedBy=multi-user.target
```
Enable and start:
```bash
sudo systemctl daemon-reload
sudo systemctl enable hmac-file-server
sudo systemctl start hmac-file-server
# Reload configuration (logging settings)
sudo systemctl reload hmac-file-server
```
---
## Building
### Local Build
```bash
go build -o hmac-file-server ./cmd/server/main.go
```
### Cross-Platform Builds
```bash
# Linux amd64
GOOS=linux GOARCH=amd64 go build -o hmac-file-server-linux-amd64 ./cmd/server/main.go
# Linux arm64
GOOS=linux GOARCH=arm64 go build -o hmac-file-server-linux-arm64 ./cmd/server/main.go
# Windows
GOOS=windows GOARCH=amd64 go build -o hmac-file-server-windows-amd64.exe ./cmd/server/main.go
```
---
## Docker Support
### Quick Start with Docker Compose
```yaml
version: '3.8'
services:
hmac-file-server:
image: ghcr.io/plusone/hmac-file-server:latest
ports:
- "8080:8080"
- "9090:9090" # Metrics
volumes:
- ./config:/etc/hmac-file-server
- ./uploads:/opt/hmac-file-server/data/uploads
environment:
- CONFIG_PATH=/etc/hmac-file-server/config.toml
restart: unless-stopped
```
### Docker Build
```bash
docker build -t hmac-file-server .
docker run -p 8080:8080 -v $(pwd)/config.toml:/etc/hmac-file-server/config.toml hmac-file-server
```
See the Wiki for detailed Docker setup instructions.
---
## Changelog
### Version 3.2 (Stable)
- **Development Version**: Active development branch with latest features
- **Enhanced Documentation**: Updated comprehensive documentation and protocol specifications
- **Configuration Improvements**: Better configuration validation and structure
- **Authentication System**: Full JWT and multi-protocol HMAC support
### Version 3.1-Stable (2025-06-08)
- **v3 Protocol Support**: Added mod_http_upload_external compatibility
- **Enhanced Authentication**: Improved HMAC validation with multiple protocol support
- **JWT Integration**: Complete JWT authentication system
- **File Naming Options**: HMAC-based or original filename preservation
- **Network Improvements**: IPv4/IPv6 dual-stack with protocol forcing
### Version 3.0-Stable (2025-06-07)
- **Docker Support**: Official Docker images and compose files
- **Go 1.24 Requirement**: Updated minimum Go version
- **Configuration Improvements**: Better validation and hot-reloading
- **Performance Enhancements**: Worker auto-scaling and memory optimization
### Previous Versions
See [CHANGELOG.MD](./CHANGELOG.MD) for complete version history.
---
## License
MIT License
Copyright (c) 2025 Alexander Renz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

1208
WIKI.MD Normal file

File diff suppressed because it is too large Load Diff

View File

196
build-multi-arch.sh Executable file
View File

@ -0,0 +1,196 @@
#!/bin/bash
# HMAC File Server v3.2 - Multi-Architecture Build Script
# Compiles binaries for AMD64, ARM64, and ARM32 architectures
# Remove set -e to prevent early exit on errors
# Colors for output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m'
print_status() {
echo -e "${GREEN}[BUILD]${NC} $1"
}
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
print_arch() {
echo -e "${CYAN}[ARCH]${NC} $1"
}
# Check if Go is installed
if ! command -v go &> /dev/null; then
print_error "Go is not installed or not in PATH"
exit 1
fi
# Create temp directory if it doesn't exist
TEMP_DIR="./temp"
if [[ ! -d "$TEMP_DIR" ]]; then
mkdir -p "$TEMP_DIR"
print_info "Created temp directory: $TEMP_DIR"
fi
# Source files to compile
SOURCE_FILES="cmd/server/main.go cmd/server/helpers.go cmd/server/config_validator.go cmd/server/config_test_scenarios.go"
print_status "Starting multi-architecture build for HMAC File Server v3.2"
print_info "Source files: $SOURCE_FILES"
print_info "Output directory: $TEMP_DIR"
echo ""
# Build function
build_for_arch() {
local goos=$1
local goarch=$2
local output_name=$3
local arch_description=$4
print_arch "Building for $arch_description ($goos/$goarch)..."
# Set environment variables for cross-compilation
export GOOS=$goos
export GOARCH=$goarch
export CGO_ENABLED=0
# Build the binary
if go build -ldflags="-w -s" -o "$TEMP_DIR/$output_name" $SOURCE_FILES 2>/dev/null; then
# Get file size
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
SIZE=$(stat -f%z "$TEMP_DIR/$output_name" | awk '{printf "%.1fMB", $1/1024/1024}')
else
# Linux
SIZE=$(stat -c%s "$TEMP_DIR/$output_name" | awk '{printf "%.1fMB", $1/1024/1024}')
fi
print_status "Build successful: $arch_description"
print_info " Binary: $TEMP_DIR/$output_name"
print_info " Size: $SIZE"
# Test binary (version check)
if timeout 10s "$TEMP_DIR/$output_name" --version >/dev/null 2>&1; then
print_info " Version check: PASSED"
else
print_warning " Version check: SKIPPED (cross-compiled binary)"
fi
return 0
else
print_error "Build failed: $arch_description"
return 1
fi
}
# Track build results
BUILDS_ATTEMPTED=0
BUILDS_SUCCESSFUL=0
FAILED_BUILDS=()
echo "Starting builds..."
echo "===================="
echo ""
# Build for AMD64 (x86_64)
print_arch "AMD64 (Intel/AMD 64-bit)"
((BUILDS_ATTEMPTED++))
if build_for_arch "linux" "amd64" "hmac-file-server-linux-amd64" "AMD64 Linux"; then
((BUILDS_SUCCESSFUL++))
else
FAILED_BUILDS+=("AMD64")
fi
echo ""
# Build for ARM64 (AArch64)
print_arch "ARM64 (AArch64)"
((BUILDS_ATTEMPTED++))
if build_for_arch "linux" "arm64" "hmac-file-server-linux-arm64" "ARM64 Linux"; then
((BUILDS_SUCCESSFUL++))
else
FAILED_BUILDS+=("ARM64")
fi
echo ""
# Build for ARM32 (ARMv7)
print_arch "ARM32 (ARMv7)"
export GOARM=7 # ARMv7 with hardware floating point
((BUILDS_ATTEMPTED++))
if build_for_arch "linux" "arm" "hmac-file-server-linux-arm32" "ARM32 Linux"; then
((BUILDS_SUCCESSFUL++))
else
FAILED_BUILDS+=("ARM32")
fi
echo ""
# Reset environment variables
unset GOOS GOARCH CGO_ENABLED GOARM
# Build summary
echo "Build Summary"
echo "================"
print_info "Builds attempted: $BUILDS_ATTEMPTED"
print_info "Builds successful: $BUILDS_SUCCESSFUL"
if [[ $BUILDS_SUCCESSFUL -eq $BUILDS_ATTEMPTED ]]; then
print_status "ALL BUILDS SUCCESSFUL!"
echo ""
print_info "Generated binaries in $TEMP_DIR:"
ls -lh "$TEMP_DIR"/hmac-file-server-* | while read -r line; do
echo " $line"
done
echo ""
print_info "Usage examples:"
echo " - Copy to target system and run: ./hmac-file-server-linux-amd64 --version"
echo " - Deploy with installer: cp temp/hmac-file-server-linux-amd64 /opt/hmac-file-server/"
echo " - Docker deployment: COPY temp/hmac-file-server-linux-amd64 /usr/local/bin/"
elif [[ $BUILDS_SUCCESSFUL -gt 0 ]]; then
print_warning "PARTIAL SUCCESS: $BUILDS_SUCCESSFUL/$BUILDS_ATTEMPTED builds completed"
if [[ ${#FAILED_BUILDS[@]} -gt 0 ]]; then
print_error "Failed architectures: ${FAILED_BUILDS[*]}"
fi
else
print_error "ALL BUILDS FAILED!"
exit 1
fi
echo ""
print_info "Architecture compatibility:"
echo " - AMD64: Intel/AMD 64-bit servers, desktops, cloud instances"
echo " - ARM64: Apple Silicon, AWS Graviton, modern ARM servers"
echo " - ARM32: Raspberry Pi, embedded systems, older ARM devices"
echo ""
print_status "Multi-architecture build completed!"
# Final verification
echo ""
print_info "Final verification:"
for binary in "$TEMP_DIR"/hmac-file-server-*; do
if [[ -f "$binary" ]]; then
filename=$(basename "$binary")
if file "$binary" >/dev/null 2>&1; then
file_info=$(file "$binary" | cut -d: -f2- | sed 's/^ *//')
print_info " OK $filename: $file_info"
else
print_info " OK $filename: Binary file"
fi
fi
done
exit 0

407
builddebian.sh Executable file
View File

@ -0,0 +1,407 @@
#!/bin/bash
# HMAC File Server v3.2 - Debian Package Builder
# Creates .deb packages for AMD64 and ARM64 architectures
set -e
# Colors for output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
print_status() {
echo -e "${GREEN}[BUILD]${NC} $1"
}
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Project configuration
PROJECT_DIR=$(pwd)
BUILD_DIR=$PROJECT_DIR/build
DEB_DIR=$PROJECT_DIR/debian
PACKAGE_NAME="hmac-file-server"
VERSION="3.2.0"
MAINTAINER="Alex Renz <renz@uuxo.net>"
# Source files for compilation
SOURCE_FILES="cmd/server/main.go cmd/server/helpers.go cmd/server/config_validator.go cmd/server/config_test_scenarios.go"
print_status "Starting Debian package build for HMAC File Server v$VERSION"
print_info "Building packages for: AMD64, ARM64"
# Check if Go is installed
if ! command -v go &> /dev/null; then
print_error "Go is not installed or not in PATH"
exit 1
fi
# Check if dpkg-deb is available
if ! command -v dpkg-deb &> /dev/null; then
print_error "dpkg-deb is not installed. Please install dpkg-dev package"
exit 1
fi
# Clean and create required directories
print_info "Setting up build directories..."
rm -rf $BUILD_DIR $DEB_DIR
mkdir -p $BUILD_DIR/{amd64,arm64}
mkdir -p $DEB_DIR/DEBIAN
mkdir -p $DEB_DIR/usr/local/bin
mkdir -p $DEB_DIR/etc/hmac-file-server
mkdir -p $DEB_DIR/var/lib/hmac-file-server/{uploads,deduplication,runtime}
mkdir -p $DEB_DIR/var/log/hmac-file-server
mkdir -p $DEB_DIR/usr/share/doc/hmac-file-server
mkdir -p $DEB_DIR/lib/systemd/system
# Compile Go binaries for both architectures
print_status "Compiling binaries..."
for ARCH in amd64 arm64; do
print_info "Building for $ARCH..."
# Set cross-compilation environment
export GOOS=linux
export GOARCH=$ARCH
export CGO_ENABLED=0
# Build hmac-file-server
if go build -ldflags="-w -s" -o $BUILD_DIR/$ARCH/hmac-file-server $SOURCE_FILES; then
SIZE=$(stat -c%s "$BUILD_DIR/$ARCH/hmac-file-server" | awk '{printf "%.1fMB", $1/1024/1024}')
print_info " $ARCH binary built successfully ($SIZE)"
else
print_error "Failed to build $ARCH binary"
exit 1
fi
done
# Reset environment variables
unset GOOS GOARCH CGO_ENABLED
# Prepare Debian control file template
print_info "Creating package metadata..."
CONTROL_TEMPLATE=$DEB_DIR/DEBIAN/control.template
cat <<EOF > $CONTROL_TEMPLATE
Package: $PACKAGE_NAME
Version: $VERSION
Architecture: ARCH_PLACEHOLDER
Maintainer: $MAINTAINER
Depends: redis-server, clamav, clamav-daemon
Recommends: nginx
Section: net
Priority: optional
Homepage: https://github.com/PlusOne/hmac-file-server
Description: HMAC File Server v3.2 - Enterprise XMPP File Sharing
A lightweight, secure file server designed for XMPP environments with
enterprise-grade features including:
.
* HMAC-based authentication and JWT support
* Redis integration for session management
* ClamAV virus scanning for uploaded files
* Prometheus metrics for monitoring
* Chunked upload/download support
* File deduplication capabilities
* Comprehensive configuration validation
.
Perfect for Prosody, Ejabberd, and other XMPP servers requiring
secure file sharing capabilities with professional deployment features.
EOF
# Prepare systemd service file
print_info "Creating systemd service configuration..."
cat <<EOF > $DEB_DIR/lib/systemd/system/hmac-file-server.service
[Unit]
Description=HMAC File Server 3.2
Documentation=https://github.com/PlusOne/hmac-file-server
After=network.target
Wants=network-online.target
After=redis.service
After=clamav-daemon.service
[Service]
Type=simple
User=hmac-file-server
Group=hmac-file-server
ExecStart=/usr/local/bin/hmac-file-server -config /etc/hmac-file-server/config.toml
ExecReload=/bin/kill -SIGHUP \$MAINPID
WorkingDirectory=/var/lib/hmac-file-server
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=hmac-file-server
# Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/hmac-file-server /var/log/hmac-file-server
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
# Resource limits
LimitNOFILE=65536
LimitNPROC=4096
[Install]
WantedBy=multi-user.target
EOF
# Prepare example configuration file
print_info "Creating example configuration..."
cat <<EOF > $DEB_DIR/etc/hmac-file-server/config.toml
# HMAC File Server v3.2 Configuration
# Complete configuration reference: https://github.com/PlusOne/hmac-file-server/blob/main/WIKI.MD
[server]
bind_ip = "127.0.0.1"
listenport = "8080"
unixsocket = false
storagepath = "/var/lib/hmac-file-server/uploads"
metricsenabled = true
metricsport = "9090"
deduplicationenabled = true
deduplicationpath = "/var/lib/hmac-file-server/deduplication"
filenaming = "HMAC"
force_protocol = "auto"
sslenabled = false
pidfilepath = "/var/lib/hmac-file-server/runtime/hmac-file-server.pid"
[security]
secret = "CHANGE_THIS_SECRET_IN_PRODUCTION_USE_48_CHARS_MIN"
enablejwt = false
jwtsecret = ""
jwtalgorithm = "HS256"
jwtexpiration = "24h"
[uploads]
allowedextensions = [".txt", ".pdf", ".jpg", ".jpeg", ".png", ".gif", ".webp", ".zip", ".tar", ".gz", ".7z", ".mp4", ".webm", ".ogg", ".mp3", ".wav", ".flac", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".odt", ".ods", ".odp"]
maxfilesize = "100MB"
chunkeduploadsenabled = true
chunksize = "10MB"
resumableuploadsenabled = true
ttlenabled = false
ttl = "168h"
[downloads]
chunkeddownloadsenabled = true
chunksize = "10MB"
[logging]
level = "INFO"
file = "/var/log/hmac-file-server/hmac-file-server.log"
max_size = 100
max_backups = 3
max_age = 30
compress = true
[workers]
numworkers = 10
uploadqueuesize = 1000
autoscaling = true
[timeouts]
readtimeout = "30s"
writetimeout = "30s"
idletimeout = "120s"
shutdown = "30s"
[clamav]
enabled = false
socket = "/var/run/clamav/clamd.ctl"
timeout = "30s"
[redis]
enabled = false
address = "localhost:6379"
database = 0
password = ""
EOF
# Prepare post-installation script
print_info "Creating installation scripts..."
cat <<EOF > $DEB_DIR/DEBIAN/postinst
#!/bin/bash
set -e
# Create hmac-file-server user and group if they do not exist
if ! id -u hmac-file-server >/dev/null 2>&1; then
useradd --system --no-create-home --shell /usr/sbin/nologin --home-dir /var/lib/hmac-file-server hmac-file-server
echo "Created system user: hmac-file-server"
fi
# Set proper ownership and permissions
chown -R hmac-file-server:hmac-file-server /var/lib/hmac-file-server
chown -R hmac-file-server:hmac-file-server /var/log/hmac-file-server
chown hmac-file-server:hmac-file-server /etc/hmac-file-server/config.toml
# Set directory permissions
chmod 755 /var/lib/hmac-file-server
chmod 755 /var/lib/hmac-file-server/uploads
chmod 755 /var/lib/hmac-file-server/deduplication
chmod 755 /var/lib/hmac-file-server/runtime
chmod 755 /var/log/hmac-file-server
chmod 640 /etc/hmac-file-server/config.toml
# Reload systemd and enable service
systemctl daemon-reload
systemctl enable hmac-file-server.service
echo ""
echo "HMAC File Server v3.2 installed successfully!"
echo ""
echo "Next steps:"
echo "1. Edit /etc/hmac-file-server/config.toml (CHANGE THE SECRET!)"
echo "2. Enable Redis/ClamAV if needed: systemctl enable redis-server clamav-daemon"
echo "3. Start the service: systemctl start hmac-file-server"
echo "4. Check status: systemctl status hmac-file-server"
echo ""
echo "Documentation: https://github.com/PlusOne/hmac-file-server"
echo ""
EOF
chmod 0755 $DEB_DIR/DEBIAN/postinst
# Prepare pre-removal script
cat <<EOF > $DEB_DIR/DEBIAN/prerm
#!/bin/bash
set -e
# Stop the service before removal
if systemctl is-active --quiet hmac-file-server.service; then
echo "Stopping HMAC File Server service..."
systemctl stop hmac-file-server.service || true
fi
EOF
chmod 0755 $DEB_DIR/DEBIAN/prerm
# Prepare post-removal script
cat <<EOF > $DEB_DIR/DEBIAN/postrm
#!/bin/bash
set -e
case "\$1" in
purge)
# Remove systemd service
systemctl disable hmac-file-server.service >/dev/null 2>&1 || true
rm -f /lib/systemd/system/hmac-file-server.service
systemctl daemon-reload >/dev/null 2>&1 || true
# Remove user and group
if id -u hmac-file-server >/dev/null 2>&1; then
userdel hmac-file-server || true
fi
if getent group hmac-file-server >/dev/null 2>&1; then
groupdel hmac-file-server || true
fi
# Remove data directories (ask user)
echo ""
echo "HMAC File Server has been removed."
echo "Data directories remain at:"
echo " - /var/lib/hmac-file-server/"
echo " - /var/log/hmac-file-server/"
echo " - /etc/hmac-file-server/"
echo ""
echo "Remove them manually if no longer needed:"
echo " sudo rm -rf /var/lib/hmac-file-server"
echo " sudo rm -rf /var/log/hmac-file-server"
echo " sudo rm -rf /etc/hmac-file-server"
echo ""
;;
remove)
# Just disable service
systemctl disable hmac-file-server.service >/dev/null 2>&1 || true
systemctl daemon-reload >/dev/null 2>&1 || true
;;
esac
EOF
chmod 0755 $DEB_DIR/DEBIAN/postrm
# Prepare documentation
print_info "Including documentation..."
cp README.MD $DEB_DIR/usr/share/doc/hmac-file-server/
cp INSTALL.MD $DEB_DIR/usr/share/doc/hmac-file-server/
cp WIKI.MD $DEB_DIR/usr/share/doc/hmac-file-server/
cp CHANGELOG.MD $DEB_DIR/usr/share/doc/hmac-file-server/
cp config-example-xmpp.toml $DEB_DIR/usr/share/doc/hmac-file-server/
# Create .deb packages
print_status "Building Debian packages..."
for ARCH in amd64 arm64; do
print_info "Creating package for $ARCH..."
# Update control file for the current architecture
sed "s/ARCH_PLACEHOLDER/$ARCH/" $CONTROL_TEMPLATE > $DEB_DIR/DEBIAN/control
# Copy binary for current architecture
cp $BUILD_DIR/$ARCH/hmac-file-server $DEB_DIR/usr/local/bin/
# Calculate installed size
INSTALLED_SIZE=$(du -sk $DEB_DIR | cut -f1)
echo "Installed-Size: $INSTALLED_SIZE" >> $DEB_DIR/DEBIAN/control
# Ensure proper permissions
find $DEB_DIR -type d -exec chmod 755 {} \;
find $DEB_DIR -type f -exec chmod 644 {} \;
chmod 0755 $DEB_DIR/usr/local/bin/hmac-file-server
chmod 0755 $DEB_DIR/DEBIAN/postinst
chmod 0755 $DEB_DIR/DEBIAN/prerm
chmod 0755 $DEB_DIR/DEBIAN/postrm
# Build the .deb package
PACKAGE_FILE="${PACKAGE_NAME}_${VERSION}_${ARCH}.deb"
if dpkg-deb --build $DEB_DIR $PACKAGE_FILE; then
SIZE=$(stat -c%s "$PACKAGE_FILE" | awk '{printf "%.1fMB", $1/1024/1024}')
print_info " Package created: $PACKAGE_FILE ($SIZE)"
else
print_error "Failed to create package for $ARCH"
exit 1
fi
# Clean up binary for next build
rm -f $DEB_DIR/usr/local/bin/hmac-file-server
rm -f $DEB_DIR/DEBIAN/control
done
# Cleanup temporary directories
print_info "Cleaning up build directories..."
rm -rf $BUILD_DIR $DEB_DIR
# Show results
print_status "Debian package build completed!"
echo ""
print_info "Generated packages:"
for PACKAGE in ${PACKAGE_NAME}_${VERSION}_*.deb; do
if [[ -f "$PACKAGE" ]]; then
SIZE=$(stat -c%s "$PACKAGE" | awk '{printf "%.1fMB", $1/1024/1024}')
print_info " $PACKAGE ($SIZE)"
fi
done
echo ""
print_info "Installation commands:"
echo " sudo dpkg -i ${PACKAGE_NAME}_${VERSION}_amd64.deb"
echo " sudo dpkg -i ${PACKAGE_NAME}_${VERSION}_arm64.deb"
echo ""
print_info "Package information:"
echo " dpkg -I ${PACKAGE_NAME}_${VERSION}_amd64.deb"
echo " dpkg -c ${PACKAGE_NAME}_${VERSION}_amd64.deb"
echo ""
print_warning "Remember to:"
echo " 1. Edit /etc/hmac-file-server/config.toml"
echo " 2. Change the default secret"
echo " 3. Configure Redis/ClamAV if needed"
echo " 4. Start the service: systemctl start hmac-file-server"
exit 0

15
builddocker.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
set -e
IMAGE_NAME="hmac-file-server"
DOCKERFILE_PATH="dockerenv/dockerbuild/Dockerfile"
COMPOSE_FILE="dockerenv/docker-compose.yml"
echo "Building Docker image: $IMAGE_NAME"
docker build -t "$IMAGE_NAME" -f "$DOCKERFILE_PATH" .
#echo "Starting services using $COMPOSE_FILE"
#docker-compose -f "$COMPOSE_FILE" up -d
echo "Build and deployment complete."

80
buildgo.sh Executable file
View File

@ -0,0 +1,80 @@
#!/bin/bash
# HMAC File Server - Build Script
set -e
# Colors
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
print_status() {
echo -e "${GREEN}[BUILD]${NC} $1"
}
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if Go is installed
if ! command -v go &> /dev/null; then
print_error "Go is not installed or not in PATH"
exit 1
fi
# Build the application
print_status "Building HMAC File Server v3.2..."
go build -o hmac-file-server cmd/server/main.go cmd/server/helpers.go cmd/server/config_validator.go cmd/server/config_test_scenarios.go
if [ $? -eq 0 ]; then
print_status "Build successful! Binary created: ./hmac-file-server"
# Check binary size
SIZE=$(du -h hmac-file-server | cut -f1)
print_info "Binary size: $SIZE"
# Show help to verify it works
print_info "Testing binary functionality..."
./hmac-file-server --help > /dev/null 2>&1
if [ $? -eq 0 ]; then
print_status "Binary is functional!"
else
print_error "Binary test failed"
exit 1
fi
else
print_error "Build failed!"
exit 1
fi
# Create test file for manual testing
print_info "Creating test file..."
echo "Hello, HMAC File Server! $(date)" > test_upload.txt
# Generate HMAC signature for manual testing
print_info "HMAC signature generation for testing:"
SECRET="hmac-file-server-is-the-win"
MESSAGE="/upload"
# Check if openssl is available
if command -v openssl &> /dev/null; then
SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)
echo "Secret: $SECRET"
echo "Message: $MESSAGE"
echo "Signature: $SIGNATURE"
echo ""
echo "Test with curl (requires server running on localhost:8080):"
echo "curl -v -X POST -H \"X-Signature: $SIGNATURE\" -F \"file=@test_upload.txt\" http://localhost:8080/upload"
else
print_info "OpenSSL not found. You can generate HMAC manually or use the Go tests."
echo "To start server: ./hmac-file-server"
echo "For testing, check the test/ directory for Go test files."
fi
print_status "Build complete! Ready to run: ./hmac-file-server"

1050
cmd/monitor/monitor.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,67 +0,0 @@
# Server Settings
[server]
ListenPort = "8080"
UnixSocket = false
StoreDir = "./testupload"
LogLevel = "info"
LogFile = "./hmac-file-server.log"
MetricsEnabled = true
MetricsPort = "9090"
FileTTL = "8760h"
# Workers and Connections
[workers]
NumWorkers = 2
UploadQueueSize = 500
# Timeout Settings
[timeouts]
ReadTimeout = "600s"
WriteTimeout = "600s"
IdleTimeout = "600s"
# Security Settings
[security]
Secret = "a-orc-and-a-humans-is-drinking-ale"
# Versioning Settings
[versioning]
EnableVersioning = false
MaxVersions = 1
# Upload/Download Settings
[uploads]
ResumableUploadsEnabled = true
ChunkedUploadsEnabled = true
ChunkSize = 16777216
AllowedExtensions = [
# Document formats
".txt", ".pdf",
# Image formats
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".svg", ".webp",
# Video formats
".wav", ".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".mpeg", ".mpg", ".m4v", ".3gp", ".3g2",
# Audio formats
".mp3", ".ogg"
]
# ClamAV Settings
[clamav]
ClamAVEnabled = false
ClamAVSocket = "/var/run/clamav/clamd.ctl"
NumScanWorkers = 4
# Redis Settings
[redis]
RedisEnabled = false
RedisAddr = "localhost:6379"
RedisPassword = ""
RedisDBIndex = 0
RedisHealthCheckInterval = "120s"
# Deduplication
[deduplication]
enabled = false

View File

@ -0,0 +1,294 @@
// config_test_scenarios.go
package main
import (
"fmt"
"os"
"path/filepath"
)
// ConfigTestScenario represents a test scenario for configuration validation
type ConfigTestScenario struct {
Name string
Config Config
ShouldPass bool
ExpectedErrors []string
ExpectedWarnings []string
}
// GetConfigTestScenarios returns a set of test scenarios for configuration validation
func GetConfigTestScenarios() []ConfigTestScenario {
baseValidConfig := Config{
Server: ServerConfig{
ListenAddress: "8080",
BindIP: "0.0.0.0",
StoragePath: "/tmp/test-storage",
MetricsEnabled: true,
MetricsPort: "9090",
FileTTLEnabled: true,
FileTTL: "24h",
MinFreeBytes: "1GB",
FileNaming: "HMAC",
ForceProtocol: "auto",
PIDFilePath: "/tmp/test.pid",
},
Security: SecurityConfig{
Secret: "test-secret-key-32-characters",
EnableJWT: false,
},
Logging: LoggingConfig{
Level: "info",
File: "/tmp/test.log",
MaxSize: 100,
MaxBackups: 3,
MaxAge: 30,
},
Timeouts: TimeoutConfig{
Read: "30s",
Write: "30s",
Idle: "60s",
},
Workers: WorkersConfig{
NumWorkers: 4,
UploadQueueSize: 50,
},
Uploads: UploadsConfig{
AllowedExtensions: []string{".txt", ".pdf", ".jpg"},
ChunkSize: "10MB",
},
Downloads: DownloadsConfig{
AllowedExtensions: []string{".txt", ".pdf", ".jpg"},
ChunkSize: "10MB",
},
}
return []ConfigTestScenario{
{
Name: "Valid Basic Configuration",
Config: baseValidConfig,
ShouldPass: true,
},
{
Name: "Missing Listen Address",
Config: func() Config {
c := baseValidConfig
c.Server.ListenAddress = ""
return c
}(),
ShouldPass: false,
ExpectedErrors: []string{"server.listen_address is required"},
},
{
Name: "Invalid Port Number",
Config: func() Config {
c := baseValidConfig
c.Server.ListenAddress = "99999"
return c
}(),
ShouldPass: false,
ExpectedErrors: []string{"invalid port number"},
},
{
Name: "Invalid IP Address",
Config: func() Config {
c := baseValidConfig
c.Server.BindIP = "999.999.999.999"
return c
}(),
ShouldPass: false,
ExpectedErrors: []string{"invalid IP address format"},
},
{
Name: "Same Port for Server and Metrics",
Config: func() Config {
c := baseValidConfig
c.Server.ListenAddress = "8080"
c.Server.MetricsPort = "8080"
return c
}(),
ShouldPass: false,
ExpectedErrors: []string{"metrics port cannot be the same as main listen port"},
},
{
Name: "JWT Enabled Without Secret",
Config: func() Config {
c := baseValidConfig
c.Security.EnableJWT = true
c.Security.JWTSecret = ""
return c
}(),
ShouldPass: false,
ExpectedErrors: []string{"JWT secret is required when JWT is enabled"},
},
{
Name: "Short JWT Secret",
Config: func() Config {
c := baseValidConfig
c.Security.EnableJWT = true
c.Security.JWTSecret = "short"
c.Security.JWTAlgorithm = "HS256"
return c
}(),
ShouldPass: true,
ExpectedWarnings: []string{"JWT secret should be at least 32 characters"},
},
{
Name: "Invalid Log Level",
Config: func() Config {
c := baseValidConfig
c.Logging.Level = "invalid"
return c
}(),
ShouldPass: false,
ExpectedErrors: []string{"invalid log level"},
},
{
Name: "Invalid Timeout Format",
Config: func() Config {
c := baseValidConfig
c.Timeouts.Read = "invalid"
return c
}(),
ShouldPass: false,
ExpectedErrors: []string{"invalid read timeout format"},
},
{
Name: "Negative Worker Count",
Config: func() Config {
c := baseValidConfig
c.Workers.NumWorkers = -1
return c
}(),
ShouldPass: false,
ExpectedErrors: []string{"number of workers must be positive"},
},
{
Name: "Extensions Without Dots",
Config: func() Config {
c := baseValidConfig
c.Uploads.AllowedExtensions = []string{"txt", "pdf"}
return c
}(),
ShouldPass: false,
ExpectedErrors: []string{"file extensions must start with a dot"},
},
{
Name: "High Worker Count Warning",
Config: func() Config {
c := baseValidConfig
c.Workers.NumWorkers = 100
return c
}(),
ShouldPass: true,
ExpectedWarnings: []string{"very high worker count may impact performance"},
},
{
Name: "Deduplication Without Directory",
Config: func() Config {
c := baseValidConfig
c.Deduplication.Enabled = true
c.Deduplication.Directory = ""
return c
}(),
ShouldPass: false,
ExpectedErrors: []string{"deduplication directory is required"},
},
}
}
// RunConfigTests runs all configuration test scenarios
func RunConfigTests() {
scenarios := GetConfigTestScenarios()
passed := 0
failed := 0
fmt.Println("🧪 Running Configuration Test Scenarios")
fmt.Println("=======================================")
fmt.Println()
for i, scenario := range scenarios {
fmt.Printf("Test %d: %s\n", i+1, scenario.Name)
// Create temporary directories for testing
tempDir := filepath.Join(os.TempDir(), fmt.Sprintf("hmac-test-%d", i))
os.MkdirAll(tempDir, 0755)
defer os.RemoveAll(tempDir)
// Update paths in config to use temp directory
scenario.Config.Server.StoragePath = filepath.Join(tempDir, "storage")
scenario.Config.Logging.File = filepath.Join(tempDir, "test.log")
scenario.Config.Server.PIDFilePath = filepath.Join(tempDir, "test.pid")
if scenario.Config.Deduplication.Enabled {
scenario.Config.Deduplication.Directory = filepath.Join(tempDir, "dedup")
}
result := ValidateConfigComprehensive(&scenario.Config)
// Check if test passed as expected
testPassed := true
if scenario.ShouldPass && result.HasErrors() {
fmt.Printf(" ❌ Expected to pass but failed with errors:\n")
for _, err := range result.Errors {
fmt.Printf(" • %s\n", err.Message)
}
testPassed = false
} else if !scenario.ShouldPass && !result.HasErrors() {
fmt.Printf(" ❌ Expected to fail but passed\n")
testPassed = false
} else if !scenario.ShouldPass && result.HasErrors() {
// Check if expected errors are present
expectedFound := true
for _, expectedError := range scenario.ExpectedErrors {
found := false
for _, actualError := range result.Errors {
if contains([]string{actualError.Message}, expectedError) ||
contains([]string{actualError.Error()}, expectedError) {
found = true
break
}
}
if !found {
fmt.Printf(" ❌ Expected error not found: %s\n", expectedError)
expectedFound = false
}
}
if !expectedFound {
testPassed = false
}
}
// Check expected warnings
if len(scenario.ExpectedWarnings) > 0 {
for _, expectedWarning := range scenario.ExpectedWarnings {
found := false
for _, actualWarning := range result.Warnings {
if contains([]string{actualWarning.Message}, expectedWarning) ||
contains([]string{actualWarning.Error()}, expectedWarning) {
found = true
break
}
}
if !found {
fmt.Printf(" ⚠️ Expected warning not found: %s\n", expectedWarning)
}
}
}
if testPassed {
fmt.Printf(" ✅ Passed\n")
passed++
} else {
failed++
}
fmt.Println()
}
// Summary
fmt.Printf("📊 Test Results: %d passed, %d failed\n", passed, failed)
if failed > 0 {
fmt.Printf("❌ Some tests failed. Please review the implementation.\n")
os.Exit(1)
} else {
fmt.Printf("✅ All tests passed!\n")
}
}

File diff suppressed because it is too large Load Diff

713
cmd/server/helpers.go Normal file
View File

@ -0,0 +1,713 @@
package main
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net"
"net/http"
"os"
"os/signal"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
"github.com/dutchcoders/go-clamd"
"github.com/go-redis/redis/v8"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/mem"
"gopkg.in/natefinch/lumberjack.v2"
)
// WorkerPool represents a pool of workers
type WorkerPool struct {
workers int
taskQueue chan UploadTask
scanQueue chan ScanTask
ctx context.Context
cancel context.CancelFunc
}
// NewWorkerPool creates a new worker pool
func NewWorkerPool(workers int, queueSize int) *WorkerPool {
ctx, cancel := context.WithCancel(context.Background())
return &WorkerPool{
workers: workers,
taskQueue: make(chan UploadTask, queueSize),
scanQueue: make(chan ScanTask, queueSize),
ctx: ctx,
cancel: cancel,
}
}
// Start starts the worker pool
func (wp *WorkerPool) Start() {
for i := 0; i < wp.workers; i++ {
go wp.worker()
}
}
// Stop stops the worker pool
func (wp *WorkerPool) Stop() {
wp.cancel()
close(wp.taskQueue)
close(wp.scanQueue)
}
// worker is the worker function
func (wp *WorkerPool) worker() {
for {
select {
case <-wp.ctx.Done():
return
case task := <-wp.taskQueue:
if task.Result != nil {
task.Result <- nil // Simple implementation
}
case scanTask := <-wp.scanQueue:
err := processScan(scanTask)
if scanTask.Result != nil {
scanTask.Result <- err
}
}
}
}
// Stub for precacheStoragePath
func precacheStoragePath(storagePath string) error {
// TODO: Implement actual pre-caching logic
// This would typically involve walking the storagePath
// and loading file information into a cache.
log.Infof("Pre-caching for storage path '%s' is a stub and not yet implemented.", storagePath)
return nil
}
func checkFreeSpaceWithRetry(path string, retries int, delay time.Duration) error {
for i := 0; i < retries; i++ {
minFreeBytes, err := parseSize(conf.Server.MinFreeBytes)
if err != nil {
log.Fatalf("Invalid MinFreeBytes: %v", err)
}
if err := checkStorageSpace(path, minFreeBytes); err != nil {
log.Warnf("Free space check failed (attempt %d/%d): %v", i+1, retries, err)
time.Sleep(delay)
continue
}
return nil
}
return fmt.Errorf("insufficient free space after %d attempts", retries)
}
func handleFileCleanup(conf *Config) {
if !conf.Server.FileTTLEnabled {
log.Println("File TTL is disabled.")
return
}
ttlDuration, err := parseTTL(conf.Server.FileTTL)
if err != nil {
log.Fatalf("Invalid TTL configuration: %v", err)
}
log.Printf("TTL cleanup enabled. Files older than %v will be deleted.", ttlDuration)
ticker := time.NewTicker(24 * time.Hour)
defer ticker.Stop()
for range ticker.C {
deleteOldFiles(conf, ttlDuration)
}
}
func computeSHA256(ctx context.Context, filePath string) (string, error) {
if filePath == "" {
return "", fmt.Errorf("file path is empty")
}
file, err := os.Open(filePath)
if err != nil {
return "", fmt.Errorf("failed to open file %s: %w", filePath, err)
}
defer file.Close()
hasher := sha256.New()
if _, err := io.Copy(hasher, file); err != nil {
return "", fmt.Errorf("failed to hash file: %w", err)
}
return hex.EncodeToString(hasher.Sum(nil)), nil
}
func handleDeduplication(ctx context.Context, absFilename string) error {
checksum, err := computeSHA256(ctx, absFilename)
if err != nil {
return err
}
dedupDir := conf.Deduplication.Directory
if dedupDir == "" {
return fmt.Errorf("deduplication directory not configured")
}
dedupPath := filepath.Join(dedupDir, checksum)
if err := os.MkdirAll(dedupPath, os.ModePerm); err != nil {
return err
}
existingPath := filepath.Join(dedupPath, filepath.Base(absFilename))
if _, err := os.Stat(existingPath); err == nil {
return os.Link(existingPath, absFilename)
}
if err := os.Rename(absFilename, existingPath); err != nil {
return err
}
return os.Link(existingPath, absFilename)
}
func handleISOContainer(absFilename string) error {
isoPath := filepath.Join(conf.ISO.MountPoint, "container.iso")
if err := CreateISOContainer([]string{absFilename}, isoPath, conf.ISO.Size, conf.ISO.Charset); err != nil {
return err
}
if err := MountISOContainer(isoPath, conf.ISO.MountPoint); err != nil {
return err
}
return UnmountISOContainer(conf.ISO.MountPoint)
}
func sanitizeFilePath(baseDir, filePath string) (string, error) {
absBaseDir, err := filepath.Abs(baseDir)
if err != nil {
return "", err
}
absFilePath, err := filepath.Abs(filepath.Join(absBaseDir, filePath))
if err != nil {
return "", err
}
if !strings.HasPrefix(absFilePath, absBaseDir) {
return "", fmt.Errorf("invalid file path: %s", filePath)
}
return absFilePath, nil
}
// Stub for formatBytes
func formatBytes(bytes int64) string {
const unit = 1024
if bytes < unit {
return fmt.Sprintf("%d B", bytes)
}
div, exp := int64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %ciB", float64(bytes)/float64(div), "KMGTPE"[exp])
}
// Stub for deleteOldFiles
func deleteOldFiles(conf *Config, ttlDuration time.Duration) {
// TODO: Implement actual file deletion logic based on TTL
log.Infof("deleteOldFiles is a stub and not yet implemented. It would check for files older than %v.", ttlDuration)
}
// Stub for CreateISOContainer
func CreateISOContainer(files []string, isoPath, size, charset string) error {
// TODO: Implement actual ISO container creation logic
log.Infof("CreateISOContainer is a stub and not yet implemented. It would create an ISO at %s.", isoPath)
return nil
}
// Stub for MountISOContainer
func MountISOContainer(isoPath, mountPoint string) error {
// TODO: Implement actual ISO container mounting logic
log.Infof("MountISOContainer is a stub and not yet implemented. It would mount %s to %s.", isoPath, mountPoint)
return nil
}
// Stub for UnmountISOContainer
func UnmountISOContainer(mountPoint string) error {
// TODO: Implement actual ISO container unmounting logic
log.Infof("UnmountISOContainer is a stub and not yet implemented. It would unmount %s.", mountPoint)
return nil
}
func checkStorageSpace(storagePath string, minFreeBytes int64) error {
var stat syscall.Statfs_t
if err := syscall.Statfs(storagePath, &stat); err != nil {
return err
}
availableBytes := stat.Bavail * uint64(stat.Bsize)
if int64(availableBytes) < minFreeBytes {
return fmt.Errorf("not enough space: available %d < required %d", availableBytes, minFreeBytes)
}
return nil
}
// setupLogging initializes logging configuration
func setupLogging() {
log.Infof("DEBUG: Starting setupLogging function")
if conf.Logging.File != "" {
log.Infof("DEBUG: Setting up file logging to: %s", conf.Logging.File)
log.SetOutput(&lumberjack.Logger{
Filename: conf.Logging.File,
MaxSize: conf.Logging.MaxSize,
MaxBackups: conf.Logging.MaxBackups,
MaxAge: conf.Logging.MaxAge,
Compress: conf.Logging.Compress,
})
log.Infof("Logging configured to file: %s", conf.Logging.File)
}
log.Infof("DEBUG: setupLogging function completed")
}
// logSystemInfo logs system information
func logSystemInfo() {
memStats, err := mem.VirtualMemory()
if err != nil {
log.Warnf("Failed to get memory stats: %v", err)
} else {
log.Infof("System Memory: Total=%s, Available=%s, Used=%.1f%%",
formatBytes(int64(memStats.Total)),
formatBytes(int64(memStats.Available)),
memStats.UsedPercent)
}
cpuStats, err := cpu.Info()
if err != nil {
log.Warnf("Failed to get CPU stats: %v", err)
} else if len(cpuStats) > 0 {
log.Infof("CPU: %s, Cores=%d", cpuStats[0].ModelName, len(cpuStats))
}
log.Infof("Go Runtime: Version=%s, NumCPU=%d, NumGoroutine=%d",
runtime.Version(), runtime.NumCPU(), runtime.NumGoroutine())
}
// initMetrics initializes Prometheus metrics
func initMetrics() {
uploadDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "upload_duration_seconds",
Help: "Duration of upload operations in seconds",
})
uploadErrorsTotal = prometheus.NewCounter(prometheus.CounterOpts{
Name: "upload_errors_total",
Help: "Total number of upload errors",
})
uploadsTotal = prometheus.NewCounter(prometheus.CounterOpts{
Name: "uploads_total",
Help: "Total number of uploads",
})
downloadDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "download_duration_seconds",
Help: "Duration of download operations in seconds",
})
downloadsTotal = prometheus.NewCounter(prometheus.CounterOpts{
Name: "downloads_total",
Help: "Total number of downloads",
})
downloadErrorsTotal = prometheus.NewCounter(prometheus.CounterOpts{
Name: "download_errors_total",
Help: "Total number of download errors",
})
memoryUsage = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "memory_usage_percent",
Help: "Current memory usage percentage",
})
cpuUsage = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "cpu_usage_percent",
Help: "Current CPU usage percentage",
})
activeConnections = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "active_connections_total",
Help: "Number of active connections",
})
requestsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "requests_total",
Help: "Total number of requests",
}, []string{"method", "status"})
goroutines = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "goroutines_total",
Help: "Number of goroutines",
})
uploadSizeBytes = prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "upload_size_bytes",
Help: "Size of uploaded files in bytes",
})
downloadSizeBytes = prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "download_size_bytes",
Help: "Size of downloaded files in bytes",
})
filesDeduplicatedTotal = prometheus.NewCounter(prometheus.CounterOpts{
Name: "files_deduplicated_total",
Help: "Total number of deduplicated files",
})
deduplicationErrorsTotal = prometheus.NewCounter(prometheus.CounterOpts{
Name: "deduplication_errors_total",
Help: "Total number of deduplication errors",
})
isoContainersCreatedTotal = prometheus.NewCounter(prometheus.CounterOpts{
Name: "iso_containers_created_total",
Help: "Total number of ISO containers created",
})
isoCreationErrorsTotal = prometheus.NewCounter(prometheus.CounterOpts{
Name: "iso_creation_errors_total",
Help: "Total number of ISO creation errors",
})
isoContainersMountedTotal = prometheus.NewCounter(prometheus.CounterOpts{
Name: "iso_containers_mounted_total",
Help: "Total number of ISO containers mounted",
})
isoMountErrorsTotal = prometheus.NewCounter(prometheus.CounterOpts{
Name: "iso_mount_errors_total",
Help: "Total number of ISO mount errors",
})
workerAdjustmentsTotal = prometheus.NewCounter(prometheus.CounterOpts{
Name: "worker_adjustments_total",
Help: "Total number of worker adjustments",
})
workerReAdjustmentsTotal = prometheus.NewCounter(prometheus.CounterOpts{
Name: "worker_readjustments_total",
Help: "Total number of worker readjustments",
})
// Register all metrics
prometheus.MustRegister(
uploadDuration, uploadErrorsTotal, uploadsTotal,
downloadDuration, downloadsTotal, downloadErrorsTotal,
memoryUsage, cpuUsage, activeConnections, requestsTotal,
goroutines, uploadSizeBytes, downloadSizeBytes,
filesDeduplicatedTotal, deduplicationErrorsTotal,
isoContainersCreatedTotal, isoCreationErrorsTotal,
isoContainersMountedTotal, isoMountErrorsTotal,
workerAdjustmentsTotal, workerReAdjustmentsTotal,
)
log.Info("Prometheus metrics initialized successfully")
}
// scanFileWithClamAV scans a file using ClamAV
func scanFileWithClamAV(filename string) error {
if clamClient == nil {
return fmt.Errorf("ClamAV client not initialized")
}
result, err := clamClient.ScanFile(filename)
if err != nil {
return fmt.Errorf("ClamAV scan failed: %w", err)
}
// Handle the result channel
if result != nil {
select {
case scanResult := <-result:
if scanResult != nil && scanResult.Status != "OK" {
return fmt.Errorf("virus detected in %s: %s", filename, scanResult.Status)
}
case <-time.After(30 * time.Second):
return fmt.Errorf("ClamAV scan timeout for file: %s", filename)
}
}
log.Debugf("File %s passed ClamAV scan", filename)
return nil
}
// initClamAV initializes ClamAV client
func initClamAV(socketPath string) (*clamd.Clamd, error) {
if socketPath == "" {
socketPath = "/var/run/clamav/clamd.ctl"
}
client := clamd.NewClamd(socketPath)
// Test connection
err := client.Ping()
if err != nil {
return nil, fmt.Errorf("failed to ping ClamAV daemon: %w", err)
}
log.Infof("ClamAV client initialized with socket: %s", socketPath)
return client, nil
}
// initRedis initializes Redis client
func initRedis() {
redisClient = redis.NewClient(&redis.Options{
Addr: conf.Redis.RedisAddr,
Password: conf.Redis.RedisPassword,
DB: conf.Redis.RedisDBIndex,
})
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := redisClient.Ping(ctx).Result()
if err != nil {
log.Warnf("Failed to connect to Redis: %v", err)
redisConnected = false
} else {
log.Info("Redis client initialized successfully")
redisConnected = true
}
}
// monitorNetwork monitors network events
func monitorNetwork(ctx context.Context) {
log.Info("Starting network monitoring")
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
log.Info("Network monitoring stopped")
return
case <-ticker.C:
// Simple network monitoring - check interface status
interfaces, err := net.Interfaces()
if err != nil {
log.Warnf("Failed to get network interfaces: %v", err)
continue
}
for _, iface := range interfaces {
if iface.Flags&net.FlagUp != 0 && iface.Flags&net.FlagLoopback == 0 {
select {
case networkEvents <- NetworkEvent{
Type: "interface_up",
Details: fmt.Sprintf("Interface %s is up", iface.Name),
}:
default:
// Channel full, skip
}
}
}
}
}
}
// handleNetworkEvents handles network events
func handleNetworkEvents(ctx context.Context) {
log.Info("Starting network event handler")
for {
select {
case <-ctx.Done():
log.Info("Network event handler stopped")
return
case event := <-networkEvents:
log.Debugf("Network event: %s - %s", event.Type, event.Details)
}
}
}
// updateSystemMetrics updates system metrics
func updateSystemMetrics(ctx context.Context) {
ticker := time.NewTicker(15 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
// Update memory metrics
if memStats, err := mem.VirtualMemory(); err == nil {
memoryUsage.Set(memStats.UsedPercent)
}
// Update CPU metrics
if cpuPercents, err := cpu.Percent(time.Second, false); err == nil && len(cpuPercents) > 0 {
cpuUsage.Set(cpuPercents[0])
}
// Update goroutine count
goroutines.Set(float64(runtime.NumGoroutine()))
}
}
}
// setupRouter sets up HTTP routes
func setupRouter() *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("/upload", handleUpload)
mux.HandleFunc("/download/", handleDownload)
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
if conf.Server.MetricsEnabled {
mux.Handle("/metrics", promhttp.Handler())
}
// Catch-all handler for all upload protocols (v, v2, token, v3)
// This must be added last as it matches all paths
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Handle PUT requests for all upload protocols
if r.Method == http.MethodPut {
query := r.URL.Query()
// Check if this is a v3 request (mod_http_upload_external)
if query.Get("v3") != "" && query.Get("expires") != "" {
handleV3Upload(w, r)
return
}
// Check if this is a legacy protocol request (v, v2, token)
if query.Get("v") != "" || query.Get("v2") != "" || query.Get("token") != "" {
handleLegacyUpload(w, r)
return
}
}
// Handle GET/HEAD requests for downloads
if r.Method == http.MethodGet || r.Method == http.MethodHead {
// Only handle download requests if the path looks like a file
path := strings.TrimPrefix(r.URL.Path, "/")
if path != "" && !strings.HasSuffix(path, "/") {
handleLegacyDownload(w, r)
return
}
}
// For all other requests, return 404
http.NotFound(w, r)
})
log.Info("HTTP router configured successfully with full protocol support (v, v2, token, v3)")
return mux
}
// setupGracefulShutdown sets up graceful shutdown
func setupGracefulShutdown(server *http.Server, cancel context.CancelFunc) {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigChan
log.Info("Received shutdown signal, initiating graceful shutdown...")
// Cancel context
cancel()
// Shutdown server with timeout
ctx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer shutdownCancel()
if err := server.Shutdown(ctx); err != nil {
log.Errorf("Server shutdown error: %v", err)
} else {
log.Info("Server shutdown completed")
}
// Clean up PID file
if conf.Server.CleanUponExit {
removePIDFile(conf.Server.PIDFilePath)
}
// Stop worker pool if it exists
if workerPool != nil {
workerPool.Stop()
log.Info("Worker pool stopped")
}
os.Exit(0)
}()
}
// ProgressWriter wraps an io.Writer to provide upload progress reporting
type ProgressWriter struct {
dst io.Writer
total int64
written int64
filename string
onProgress func(written, total int64, filename string)
lastReport time.Time
}
// NewProgressWriter creates a new ProgressWriter
func NewProgressWriter(dst io.Writer, total int64, filename string) *ProgressWriter {
return &ProgressWriter{
dst: dst,
total: total,
filename: filename,
onProgress: func(written, total int64, filename string) {
if total > 0 {
percentage := float64(written) / float64(total) * 100
sizeMiB := float64(written) / (1024 * 1024)
totalMiB := float64(total) / (1024 * 1024)
log.Infof("Upload progress for %s: %.1f%% (%.1f/%.1f MiB)",
filepath.Base(filename), percentage, sizeMiB, totalMiB)
}
},
lastReport: time.Now(),
}
}
// Write implements io.Writer interface with progress reporting
func (pw *ProgressWriter) Write(p []byte) (int, error) {
n, err := pw.dst.Write(p)
if err != nil {
return n, err
}
pw.written += int64(n)
// Report progress every 30 seconds or every 50MB for large files
now := time.Now()
shouldReport := false
if pw.total > 100*1024*1024 { // Files larger than 100MB
shouldReport = now.Sub(pw.lastReport) > 30*time.Second ||
(pw.written%(50*1024*1024) == 0 && pw.written > 0)
} else if pw.total > 10*1024*1024 { // Files larger than 10MB
shouldReport = now.Sub(pw.lastReport) > 10*time.Second ||
(pw.written%(10*1024*1024) == 0 && pw.written > 0)
}
if shouldReport && pw.onProgress != nil {
pw.onProgress(pw.written, pw.total, pw.filename)
pw.lastReport = now
}
return n, err
}
// copyWithProgress copies data from src to dst with progress reporting
func copyWithProgress(dst io.Writer, src io.Reader, total int64, filename string) (int64, error) {
progressWriter := NewProgressWriter(dst, total, filename)
// Use a pooled buffer for efficient copying
bufPtr := bufferPool.Get().(*[]byte)
defer bufferPool.Put(bufPtr)
buf := *bufPtr
return io.CopyBuffer(progressWriter, src, buf)
}

File diff suppressed because it is too large Load Diff

0
config-example-xmpp.toml Normal file
View File

View File

@ -27,8 +27,8 @@
"overrides": []
},
"gridPos": {
"h": 6,
"w": 24,
"h": 7,
"w": 3,
"x": 0,
"y": 0
},
@ -39,12 +39,11 @@
"showLineNumbers": false,
"showMiniMap": false
},
"content": "<div style=\"text-align: center; background-color: #111217; padding: 20px;\">\n <h3 style=\"color: white; font-family: 'Arial', sans-serif; font-weight: bold;\">HMAC Dashboard</h3>\n <img src=\"https://block.uuxo.net/hmac_icon.png\" alt=\"HMAC Icon\" style=\"width: 50px; height: 50px; display: block; margin: 10px auto;\">\n <p style=\"font-family: 'Verdana', sans-serif; color: white;\">\n This dashboard monitors <strong style=\"color: #FF5733;\">key metrics</strong> for the \n <span style=\"font-style: italic; color: #007BFF;\">HMAC File Server</span>.\n </p>\n</div>\n",
"content": "<div style=\"text-align: center; background-color: transparent; padding: 20px;\">\n <h3 style=\"color: white; font-family: 'Arial', sans-serif; font-weight: bold;\">HMAC Dashboard</h3>\n <img src=\"https://git.uuxo.net/uuxo/hmac-file-server/raw/branch/main/dashboard/hmac_icon.png\" alt=\"HMAC Icon\" style=\"width: 50px; height: 50px; display: block; margin: 10px auto;\">\n <p style=\"font-family: 'Verdana', sans-serif; color: white;\">\n This dashboard monitors <strong style=\"color: #FF5733;\">key metrics</strong> for the \n <span style=\"font-style: italic; color: #007BFF;\">HMAC File Server</span>.\n </p>\n</div>\n",
"mode": "html"
},
"pluginVersion": "11.3.1",
"pluginVersion": "11.4.0",
"title": "HMAC Dashboard",
"transparent": true,
"type": "text"
},
{
@ -76,10 +75,10 @@
"overrides": []
},
"gridPos": {
"h": 5,
"h": 7,
"w": 6,
"x": 0,
"y": 6
"x": 3,
"y": 0
},
"id": 14,
"options": {
@ -106,7 +105,7 @@
"sizing": "auto",
"valueMode": "color"
},
"pluginVersion": "11.3.1",
"pluginVersion": "11.4.0",
"targets": [
{
"editorMode": "code",
@ -142,10 +141,10 @@
"overrides": []
},
"gridPos": {
"h": 5,
"h": 7,
"w": 6,
"x": 6,
"y": 6
"x": 9,
"y": 0
},
"id": 18,
"options": {
@ -165,7 +164,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"pluginVersion": "11.4.0",
"targets": [
{
"editorMode": "code",
@ -192,6 +191,10 @@
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
@ -199,10 +202,68 @@
"overrides": []
},
"gridPos": {
"h": 5,
"w": 6,
"x": 12,
"y": 6
"h": 7,
"w": 5,
"x": 15,
"y": 0
},
"id": 10,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "value",
"wideLayout": true
},
"pluginVersion": "11.4.0",
"targets": [
{
"editorMode": "code",
"expr": "go_threads",
"format": "table",
"legendFormat": "{{hmac-file-server}}",
"range": true,
"refId": "A"
}
],
"title": "HMAC GO Threads",
"type": "stat"
},
{
"datasource": {
"default": true,
"type": "prometheus"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 4,
"x": 20,
"y": 0
},
"id": 17,
"options": {
@ -222,7 +283,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"pluginVersion": "11.4.0",
"targets": [
{
"editorMode": "code",
@ -262,10 +323,10 @@
"overrides": []
},
"gridPos": {
"h": 5,
"w": 3,
"x": 18,
"y": 6
"h": 7,
"w": 5,
"x": 0,
"y": 7
},
"id": 11,
"options": {
@ -285,11 +346,11 @@
"textMode": "value",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"pluginVersion": "11.4.0",
"targets": [
{
"editorMode": "code",
"expr": "hmac_file_server_uploads_total",
"expr": "increase(hmac_file_server_uploads_total[1h])",
"format": "table",
"legendFormat": "Uploads",
"range": true,
@ -325,10 +386,10 @@
"overrides": []
},
"gridPos": {
"h": 5,
"w": 3,
"x": 21,
"y": 6
"h": 7,
"w": 5,
"x": 5,
"y": 7
},
"id": 12,
"options": {
@ -348,7 +409,7 @@
"textMode": "value",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"pluginVersion": "11.4.0",
"targets": [
{
"editorMode": "code",
@ -390,10 +451,10 @@
"overrides": []
},
"gridPos": {
"h": 5,
"h": 7,
"w": 3,
"x": 0,
"y": 11
"x": 10,
"y": 7
},
"id": 15,
"options": {
@ -413,7 +474,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"pluginVersion": "11.4.0",
"targets": [
{
"datasource": {
@ -457,201 +518,12 @@
"overrides": []
},
"gridPos": {
"h": 5,
"h": 7,
"w": 3,
"x": 3,
"y": 11
"x": 13,
"y": 7
},
"id": 10,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "value",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"editorMode": "code",
"expr": "go_threads",
"format": "table",
"legendFormat": "{{hmac-file-server}}",
"range": true,
"refId": "A"
}
],
"title": "HMAC GO Threads",
"type": "stat"
},
{
"datasource": {
"default": true,
"type": "prometheus"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 3,
"x": 6,
"y": 11
},
"id": 21,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "value",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"editorMode": "code",
"expr": "hmac_file_deletions_total",
"format": "table",
"legendFormat": "{{hmac-file-server}}",
"range": true,
"refId": "A"
}
],
"title": "HMAC FileTTL Deletion(s)",
"type": "stat"
},
{
"datasource": {
"default": true,
"type": "prometheus"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 3,
"x": 9,
"y": 11
},
"id": 20,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "value",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"editorMode": "code",
"expr": "hmac_cache_misses_total",
"format": "table",
"legendFormat": "{{hmac-file-server}}",
"range": true,
"refId": "A"
}
],
"title": "HMAC Cache Misses",
"type": "stat"
},
{
"datasource": {
"default": true,
"type": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 3,
"x": 12,
"y": 11
},
"id": 16,
"id": 13,
"options": {
"colorMode": "value",
"graphMode": "area",
@ -669,81 +541,17 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"pluginVersion": "11.4.0",
"targets": [
{
"editorMode": "code",
"exemplar": false,
"expr": "hmac_active_connections_total",
"format": "table",
"instant": false,
"legendFormat": "__auto",
"expr": "hmac_file_server_download_errors_total",
"legendFormat": "Download Errors",
"range": true,
"refId": "A"
}
],
"title": "HMAC Active Connections",
"type": "stat"
},
{
"datasource": {
"default": true,
"type": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 3,
"x": 15,
"y": 11
},
"id": 19,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"editorMode": "code",
"expr": "hmac_infected_files_total",
"format": "table",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "HMAC infected file(s)",
"title": "HMAC Download Errors",
"type": "stat"
},
{
@ -771,10 +579,10 @@
"overrides": []
},
"gridPos": {
"h": 5,
"h": 7,
"w": 3,
"x": 18,
"y": 11
"x": 16,
"y": 7
},
"id": 2,
"options": {
@ -794,7 +602,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"pluginVersion": "11.4.0",
"targets": [
{
"editorMode": "code",
@ -832,12 +640,73 @@
"overrides": []
},
"gridPos": {
"h": 5,
"w": 3,
"x": 21,
"y": 11
"h": 7,
"w": 5,
"x": 19,
"y": 7
},
"id": 13,
"id": 21,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "value",
"wideLayout": true
},
"pluginVersion": "11.4.0",
"targets": [
{
"editorMode": "code",
"expr": "hm",
"format": "table",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "HMAC FileTTL Deletion(s)",
"type": "stat"
},
{
"datasource": {
"default": true,
"type": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 3,
"x": 0,
"y": 14
},
"id": 19,
"options": {
"colorMode": "value",
"graphMode": "area",
@ -855,18 +724,254 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"pluginVersion": "11.4.0",
"targets": [
{
"editorMode": "code",
"expr": "hmac_file_server_download_errors_total",
"legendFormat": "Download Errors",
"expr": "hmac_infected_files_total",
"format": "table",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "HMAC Download Errors",
"title": "HMAC infected file(s)",
"type": "stat"
},
{
"datasource": {
"default": true,
"type": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"stacking": {
"group": "A",
"mode": "none"
}
},
"fieldMinMax": false,
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "files"
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 7,
"x": 3,
"y": 14
},
"id": 22,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "11.4.0",
"targets": [
{
"editorMode": "code",
"exemplar": false,
"expr": "increase(hmac_file_server_clamav_scans_total[24h])",
"format": "time_series",
"instant": true,
"interval": "",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "HMAC ClamAV San (24h)",
"type": "histogram"
},
{
"datasource": {
"default": true,
"type": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"stacking": {
"group": "A",
"mode": "none"
}
},
"fieldMinMax": false,
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "files"
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 7,
"x": 10,
"y": 14
},
"id": 23,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "11.4.0",
"targets": [
{
"editorMode": "code",
"exemplar": false,
"expr": "increase(hmac_file_server_clamav_errors_total[24h])",
"format": "time_series",
"instant": true,
"interval": "",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "HMAC ClamAV SanError(s) (24h)",
"type": "histogram"
},
{
"datasource": {
"default": true,
"type": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"stacking": {
"group": "A",
"mode": "none"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 7,
"x": 17,
"y": 14
},
"id": 16,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "11.4.0",
"targets": [
{
"editorMode": "code",
"exemplar": false,
"expr": "histogram_quantile(0.95, sum(rate(hmac_file_server_request_duration_seconds_bucket[5m])) by (le))",
"format": "time_series",
"instant": true,
"interval": "",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "HMAC Request Duration",
"type": "histogram"
}
],
"preload": false,
@ -876,13 +981,13 @@
"list": []
},
"time": {
"from": "now-5m",
"from": "now-24h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "HMAC File Server Metrics",
"uid": "de0ye5t0hzq4ge",
"version": 129,
"version": 153,
"weekStart": ""
}

BIN
dashboard/hmac_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -0,0 +1,83 @@
[server]
listenport = "8080"
unixsocket = false
storagepath = "/opt/hmac-file-server/data/uploads"
metricsenabled = true
metricsport = "9090"
deduplicationenabled = true
minfreebytes = "5GB"
filettl = "2y"
filettlenabled = false
autoadjustworkers = true
networkevents = false
pidfilepath = "./hmac-file-server.pid"
precaching = false
[deduplication]
enabled = true
directory = "/opt/hmac-file-server/data/duplicates"
[logging]
level = "debug"
file = "./hmac-file-server.log"
max_size = 100
max_backups = 7
max_age = 30
compress = true
[iso]
enabled = false
size = "1TB"
mountpoint = "/mnt/nfs_vol01/hmac-file-server/iso/"
charset = "utf-8"
[timeouts]
readtimeout = "3600s"
writetimeout = "3600s"
idletimeout = "3600s"
[security]
secret = "hmac-file-server-is-the-win"
[versioning]
enableversioning = false
maxversions = 1
[uploads]
resumableuploadsenabled = false
chunkeduploadsenabled = true
chunksize = "32MB"
allowedextensions = [
".txt", ".pdf", ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".svg", ".webp",
".wav", ".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".mpeg", ".mpg",
".m4v", ".3gp", ".3g2", ".mp3", ".ogg"
]
[downloads]
chunkeddownloadsenabled = false
chunksize = "32MB"
allowedextensions = [
".txt", ".pdf", ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".svg", ".webp",
".wav", ".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".mpeg", ".mpg",
".m4v", ".3gp", ".3g2", ".mp3", ".ogg"
]
[clamav]
clamavenabled = false
clamavsocket = "/var/run/clamav/clamd.ctl"
numscanworkers = 4
scanfileextensions = [".exe", ".dll", ".bin", ".com", ".bat", ".sh", ".php", ".js"]
[redis]
redisenabled = false
redisdbindex = 0
redisaddr = "localhost:6379"
redispassword = ""
redishealthcheckinterval = "120s"
[workers]
numworkers = 4
uploadqueuesize = 5000
[file]
filerevision = 1

View File

View File

@ -0,0 +1,17 @@
#version: '3.8'
services:
hmac-file-server:
container_name: hmac-file-server
image: hmac-file-server:latest
ports:
- "8080:8080"
volumes:
- ./config:/etc/hmac-file-server
- ./data/uploads:/opt/hmac-file-server/data/uploads
- ./data/duplicates:/opt/hmac-file-server/data/duplicates
- ./data/temp:/opt/hmac-file-server/data/temp
- ./data/logs:/opt/hmac-file-server/data/logs
environment:
- CONFIG_PATH=/etc/hmac-file-server/config.toml
restart: unless-stopped

View File

@ -0,0 +1,27 @@
# Stage 1: Build
FROM golang:1.24-alpine AS builder
WORKDIR /build
RUN apk add --no-cache git
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o hmac-file-server cmd/server/main.go cmd/server/helpers.go cmd/server/config_validator.go cmd/server/config_test_scenarios.go
# Stage 2: Runtime
FROM alpine:latest
RUN apk --no-cache add ca-certificates
RUN mkdir -p /opt/hmac-file-server/data/uploads \
&& mkdir -p /opt/hmac-file-server/data/duplicates \
&& mkdir -p /opt/hmac-file-server/data/temp \
&& mkdir -p /opt/hmac-file-server/data/logs
WORKDIR /opt/hmac-file-server
COPY --from=builder /build/hmac-file-server .
EXPOSE 8080
CMD ["./hmac-file-server", "--config", "/etc/hmac-file-server/config.toml"]

35
go.mod
View File

@ -1,39 +1,46 @@
module github.com/PlusOne/hmac-file-server
go 1.21
go 1.24.0
require (
github.com/gdamore/tcell/v2 v2.7.4
github.com/go-redis/redis/v8 v8.11.5
github.com/pelletier/go-toml v1.9.5
github.com/prometheus/client_golang v1.20.5
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/shirou/gopsutil/v3 v3.24.5
github.com/sirupsen/logrus v1.9.3
github.com/spf13/viper v1.11.0
)
require (
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.19.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/term v0.17.0 // indirect
golang.org/x/text v0.21.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
require (
github.com/BurntSushi/toml v1.4.0
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
@ -42,11 +49,13 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.60.1 // indirect
github.com/prometheus/common v0.61.0
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.9.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/sys v0.26.0 // indirect
google.golang.org/protobuf v1.35.1 // indirect
golang.org/x/sys v0.28.0 // indirect
google.golang.org/protobuf v1.35.2 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1
)

126
go.sum
View File

@ -1,5 +1,3 @@
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@ -7,30 +5,46 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e h1:rcHHSQqzCgvlwP0I/fQ8rQMn/MpHE5gWSLdtpxtP6KQ=
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e/go.mod h1:Byz7q8MSzSPkouskHJhX0er2mZY/m0Vj5bMeMCkkyY4=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
@ -43,76 +57,124 @@ github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc=
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ=
github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592 h1:YIJ+B1hePP6AgynC5TcqpO0H9k3SSoZa2BGyL6vDUzM=
github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/spf13/viper v1.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44=
github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

1273
installer.sh Executable file

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@ import (
const (
serverURL = "http://[::1]:8080" // Replace with your actual server URL
secret = "a-orc-and-a-humans-is-drinking-ale" // Replace with your HMAC secret key
secret = "hmac-file-server-is-the-win" // Replace with your HMAC secret key
uploadPath = "hmac_icon.png" // Test file to upload
protocolType = "v2" // Use v2, v, or token as needed
)

39
test/server_flags_test.go Normal file
View File

@ -0,0 +1,39 @@
package main
import (
"os"
"os/exec"
"strings"
"testing"
)
// TestGenConfigFlag runs the server with --genconfig and checks output for expected config keys
func TestGenConfigFlag(t *testing.T) {
cmd := exec.Command("go", "run", "../cmd/server/main.go", "--genconfig")
output, err := cmd.CombinedOutput()
if err != nil && !strings.Contains(string(output), "[server]") {
t.Fatalf("Failed to run with --genconfig: %v\nOutput: %s", err, output)
}
if !strings.Contains(string(output), "[server]") || !strings.Contains(string(output), "bind_ip") {
t.Errorf("Example config missing expected keys. Output: %s", output)
}
}
// TestIPv4IPv6Flag runs the server with forceprotocol=ipv4 and ipv6 and checks for startup errors
func TestIPv4IPv6Flag(t *testing.T) {
for _, proto := range []string{"ipv4", "ipv6", "auto"} {
cmd := exec.Command("go", "run", "../cmd/server/main.go", "--config", "../cmd/server/config.toml")
cmd.Env = append(os.Environ(), "FORCEPROTOCOL="+proto)
// Set Go module cache environment variables if not already set
if os.Getenv("GOMODCACHE") == "" {
cmd.Env = append(cmd.Env, "GOMODCACHE="+os.Getenv("HOME")+"/go/pkg/mod")
}
if os.Getenv("GOPATH") == "" {
cmd.Env = append(cmd.Env, "GOPATH="+os.Getenv("HOME")+"/go")
}
output, err := cmd.CombinedOutput()
if err != nil && !strings.Contains(string(output), "Configuration loaded successfully") {
t.Errorf("Server failed to start with forceprotocol=%s: %v\nOutput: %s", proto, err, output)
}
}
}

173
test/test_installer_config.sh Executable file
View File

@ -0,0 +1,173 @@
#!/bin/bash
# Test script to validate installer configuration generation
# Tests that the installer generates config compatible with fixed struct definitions
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}🔍 Testing Installer Configuration Generation${NC}"
echo "============================================="
echo ""
# Test configuration values that simulate installer input
export HMAC_SECRET="test-hmac-secret-32-characters-long-minimum"
export JWT_SECRET="test-jwt-secret-also-32-characters-long-minimum"
# Create a test directory
TEST_DIR="/tmp/hmac-installer-test-$$"
mkdir -p "$TEST_DIR"
cd "$TEST_DIR"
echo -e "${YELLOW}📁 Test directory: $TEST_DIR${NC}"
echo ""
# Copy necessary files for testing
cp /home/renz/source/hmac-file-server-uuxo/go.mod .
cp /home/renz/source/hmac-file-server-uuxo/go.sum .
cp -r /home/renz/source/hmac-file-server-uuxo/cmd .
# Extract the generate_config function and create a test version
cat > test_config_generation.sh << 'EOF'
#!/bin/bash
# Simulated installer variables
DEFAULT_CONFIG_DIR="./test-config"
DATA_DIR="./test-data"
DEFAULT_LOG_DIR="./test-logs"
SERVER_PORT="8080"
METRICS_PORT="9090"
ENABLE_TLS="false"
HMAC_SECRET="test-hmac-secret-32-characters-long-minimum"
ENABLE_JWT="false"
ENABLE_CLAMAV="false"
ENABLE_REDIS="false"
# Create directories
mkdir -p "$DEFAULT_CONFIG_DIR"
mkdir -p "$DATA_DIR/runtime"
mkdir -p "$DEFAULT_LOG_DIR"
# Generate configuration (extracted from installer)
generate_config() {
echo "Generating test configuration..."
cat > "$DEFAULT_CONFIG_DIR/config.toml" << EOFCONFIG
# HMAC File Server Configuration
# Generated by installer test on $(date)
[server]
bind_ip = "0.0.0.0"
listenport = "$SERVER_PORT"
unixsocket = false
storagepath = "$DATA_DIR/uploads"
metricsenabled = true
metricsport = "$METRICS_PORT"
deduplicationenabled = true
deduplicationpath = "$DATA_DIR/deduplication"
filenaming = "HMAC"
force_protocol = "auto"
pidfilepath = "$DATA_DIR/runtime/hmac-file-server.pid"
sslenabled = false
[security]
secret = "$HMAC_SECRET"
enablejwt = false
[uploads]
allowedextensions = [".txt", ".pdf", ".jpg", ".jpeg", ".png", ".gif", ".webp", ".zip", ".tar", ".gz", ".7z", ".mp4", ".webm", ".ogg", ".mp3", ".wav", ".flac", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".odt", ".ods", ".odp"]
maxfilesize = "100MB"
chunkeduploadsenabled = true
chunksize = "10MB"
ttlenabled = false
ttl = "168h"
[downloads]
chunkeddownloadsenabled = true
chunksize = "10MB"
[logging]
level = "INFO"
file = "$DEFAULT_LOG_DIR/hmac-file-server.log"
max_size = 100
max_backups = 3
max_age = 30
compress = true
[workers]
numworkers = 10
uploadqueuesize = 1000
autoscaling = true
[timeouts]
readtimeout = "30s"
writetimeout = "30s"
idletimeout = "120s"
shutdown = "30s"
[clamav]
enabled = false
[redis]
enabled = false
EOFCONFIG
echo "Configuration file created: $DEFAULT_CONFIG_DIR/config.toml"
}
# Call the function
generate_config
EOF
chmod +x test_config_generation.sh
./test_config_generation.sh
echo -e "${YELLOW}📋 Generated test configuration:${NC}"
echo ""
cat ./test-config/config.toml
echo ""
# Build a test binary to validate the configuration
echo -e "${YELLOW}🔨 Building test binary...${NC}"
if go build -o hmac-test-server ./cmd/server/*.go; then
echo -e "${GREEN}✅ Build successful${NC}"
else
echo -e "${RED}❌ Build failed${NC}"
exit 1
fi
echo ""
echo -e "${YELLOW}🔍 Testing configuration validation...${NC}"
# Test configuration validation
if ./hmac-test-server -config ./test-config/config.toml -validate-config -validate-quiet; then
echo -e "${GREEN}✅ Configuration validation PASSED!${NC}"
echo ""
echo -e "${GREEN}🎉 All critical fixes verified:${NC}"
echo -e "${GREEN} ✓ Workers: numworkers/uploadqueuesize (not initial/max)${NC}"
echo -e "${GREEN} ✓ Protocol: force_protocol (not forceprotocol)${NC}"
echo -e "${GREEN} ✓ PID file: pidfilepath configured${NC}"
echo -e "${GREEN} ✓ Timeouts: read/write/idle/shutdown${NC}"
echo -e "${GREEN} ✓ Logging: level/file/max_size/max_backups/max_age${NC}"
VALIDATION_RESULT=0
else
echo -e "${RED}❌ Configuration validation FAILED!${NC}"
echo ""
echo -e "${YELLOW}Running detailed validation for diagnosis...${NC}"
./hmac-test-server -config ./test-config/config.toml -validate-config -validate-verbose || true
VALIDATION_RESULT=1
fi
echo ""
echo -e "${YELLOW}🧹 Cleaning up test directory...${NC}"
cd /
rm -rf "$TEST_DIR"
echo -e "${BLUE}Test completed.${NC}"
exit $VALIDATION_RESULT

230
verify_installation.sh Executable file
View File

@ -0,0 +1,230 @@
#!/bin/bash
# HMAC File Server v3.2 - Installation Verification Script
# Run this script on your production server to verify the installation
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
echo -e "${BLUE}🔍 HMAC File Server v3.2 - Installation Verification${NC}"
echo "======================================================"
echo ""
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}❌ This script must be run as root (use sudo)${NC}"
exit 1
fi
ERRORS=0
WARNINGS=0
# Function to report status
report_status() {
local status=$1
local message=$2
local details=$3
case $status in
"OK")
echo -e "${GREEN}$message${NC}"
[[ -n "$details" ]] && echo -e " ${CYAN}$details${NC}"
;;
"WARNING")
echo -e "${YELLOW}⚠️ $message${NC}"
[[ -n "$details" ]] && echo -e " ${YELLOW}$details${NC}"
((WARNINGS++))
;;
"ERROR")
echo -e "${RED}$message${NC}"
[[ -n "$details" ]] && echo -e " ${RED}$details${NC}"
((ERRORS++))
;;
"INFO")
echo -e "${CYAN} $message${NC}"
[[ -n "$details" ]] && echo -e " $details"
;;
esac
}
# 1. Check SystemD Service Status
echo -e "${YELLOW}🔧 Checking SystemD Service...${NC}"
if systemctl is-active --quiet hmac-file-server; then
service_status=$(systemctl status hmac-file-server --no-pager -l | head -10)
uptime=$(systemctl show hmac-file-server --property=ActiveEnterTimestamp --value)
report_status "OK" "HMAC File Server service is running" "Active since: $uptime"
else
service_status=$(systemctl status hmac-file-server --no-pager -l | head -10)
report_status "ERROR" "HMAC File Server service is not running" "$service_status"
fi
if systemctl is-enabled --quiet hmac-file-server; then
report_status "OK" "Service is enabled (will start on boot)"
else
report_status "WARNING" "Service is not enabled for auto-start"
fi
echo ""
# 2. Check Installation Files
echo -e "${YELLOW}📁 Checking Installation Files...${NC}"
# Binary
if [[ -f "/opt/hmac-file-server/hmac-file-server" ]]; then
binary_info=$(ls -lh /opt/hmac-file-server/hmac-file-server)
report_status "OK" "Binary installed" "$binary_info"
# Check if binary has version flag (indicates correct build)
if /opt/hmac-file-server/hmac-file-server --version >/dev/null 2>&1; then
version=$(/opt/hmac-file-server/hmac-file-server --version 2>/dev/null || echo "Unknown")
report_status "OK" "Binary supports --version flag" "Version: $version"
else
report_status "WARNING" "Binary doesn't support --version flag (may be old build)"
fi
else
report_status "ERROR" "Binary not found at /opt/hmac-file-server/hmac-file-server"
fi
# Configuration
if [[ -f "/etc/hmac-file-server/config.toml" ]]; then
config_info=$(ls -lh /etc/hmac-file-server/config.toml)
report_status "OK" "Configuration file exists" "$config_info"
else
report_status "ERROR" "Configuration file not found at /etc/hmac-file-server/config.toml"
fi
# Data directories
data_dirs=("/var/lib/hmac-file-server" "/var/log/hmac-file-server")
for dir in "${data_dirs[@]}"; do
if [[ -d "$dir" ]]; then
dir_info=$(ls -lhd "$dir")
report_status "OK" "Directory exists: $dir" "$dir_info"
else
report_status "WARNING" "Directory missing: $dir"
fi
done
echo ""
# 3. Check Configuration Validation
echo -e "${YELLOW}⚙️ Checking Configuration Validation...${NC}"
if [[ -f "/opt/hmac-file-server/hmac-file-server" ]]; then
echo -e "${CYAN}Running configuration validation...${NC}"
# Run validation with timeout
if timeout 30s /opt/hmac-file-server/hmac-file-server -config /etc/hmac-file-server/config.toml --validate-config >/tmp/hmac_validation.log 2>&1; then
report_status "OK" "Configuration validation passed"
# Check for warnings in validation output
if grep -q "WARNING\|WARN" /tmp/hmac_validation.log; then
warning_count=$(grep -c "WARNING\|WARN" /tmp/hmac_validation.log)
report_status "WARNING" "Configuration validation has $warning_count warnings" "Check logs for details"
fi
else
validation_error=$(tail -5 /tmp/hmac_validation.log)
report_status "ERROR" "Configuration validation failed" "$validation_error"
fi
rm -f /tmp/hmac_validation.log
fi
echo ""
# 4. Check Network Connectivity
echo -e "${YELLOW}🌐 Checking Network Connectivity...${NC}"
# Extract ports from config
if [[ -f "/etc/hmac-file-server/config.toml" ]]; then
server_port=$(grep -E "^listenport\s*=" /etc/hmac-file-server/config.toml | cut -d'"' -f2 | tr -d '"' || echo "8080")
metrics_port=$(grep -E "^metricsport\s*=" /etc/hmac-file-server/config.toml | cut -d'"' -f2 | tr -d '"' || echo "9090")
# Check if ports are listening
if netstat -tln 2>/dev/null | grep -q ":$server_port "; then
report_status "OK" "Server port $server_port is listening"
else
report_status "ERROR" "Server port $server_port is not listening"
fi
if netstat -tln 2>/dev/null | grep -q ":$metrics_port "; then
report_status "OK" "Metrics port $metrics_port is listening"
else
report_status "WARNING" "Metrics port $metrics_port is not listening"
fi
# Test HTTP connectivity
if curl -s --connect-timeout 5 "http://localhost:$server_port" >/dev/null 2>&1; then
report_status "OK" "HTTP server responding on port $server_port"
elif curl -s --connect-timeout 5 "http://localhost:$server_port" 2>&1 | grep -q "404\|401\|403"; then
report_status "OK" "HTTP server responding (expected auth required)"
else
report_status "WARNING" "HTTP server not responding on port $server_port"
fi
fi
echo ""
# 5. Check System Resources
echo -e "${YELLOW}💾 Checking System Resources...${NC}"
# Memory usage
memory_usage=$(ps -o pid,ppid,cmd,%mem,%cpu --sort=-%mem -C hmac-file-server | tail -n +2)
if [[ -n "$memory_usage" ]]; then
report_status "OK" "Process running and using resources" "$memory_usage"
else
report_status "WARNING" "No process information available"
fi
# Disk space
storage_path=$(grep -E "^storagepath\s*=" /etc/hmac-file-server/config.toml 2>/dev/null | cut -d'"' -f2 | tr -d '"' || echo "/var/lib/hmac-file-server")
if [[ -d "$storage_path" ]]; then
disk_usage=$(df -h "$storage_path" | tail -1)
report_status "INFO" "Storage directory disk usage" "$disk_usage"
fi
echo ""
# 6. Check Logs
echo -e "${YELLOW}📋 Checking Recent Logs...${NC}"
# SystemD logs
recent_logs=$(journalctl -u hmac-file-server --since "5 minutes ago" --no-pager -q)
if [[ -n "$recent_logs" ]]; then
report_status "INFO" "Recent SystemD logs available"
echo -e "${CYAN}Last 5 log entries:${NC}"
echo "$recent_logs" | tail -5
else
report_status "INFO" "No recent SystemD logs (service may be stable)"
fi
echo ""
# 7. Final Summary
echo -e "${BLUE}📊 Verification Summary${NC}"
echo "========================"
if [[ $ERRORS -eq 0 && $WARNINGS -eq 0 ]]; then
echo -e "${GREEN}🎉 PERFECT! HMAC File Server installation is working correctly!${NC}"
echo -e "${GREEN} No errors or warnings found.${NC}"
elif [[ $ERRORS -eq 0 ]]; then
echo -e "${YELLOW}✅ GOOD! HMAC File Server is working with $WARNINGS warning(s).${NC}"
echo -e "${YELLOW} Review warnings above for optimization opportunities.${NC}"
else
echo -e "${RED}❌ ISSUES FOUND! $ERRORS error(s) and $WARNINGS warning(s) detected.${NC}"
echo -e "${RED} Please address the errors above before using in production.${NC}"
fi
echo ""
echo -e "${CYAN}💡 Additional Checks You Can Perform:${NC}"
echo " • Test file upload: curl -X POST -F \"file=@testfile.txt\" http://localhost:$server_port/"
echo " • Check metrics: curl http://localhost:$metrics_port/metrics"
echo " • Review full logs: journalctl -u hmac-file-server -f"
echo " • Test configuration: /opt/hmac-file-server/hmac-file-server --validate-config"
exit $ERRORS