20 Commits

Author SHA1 Message Date
aaa838a520 feat: Optimize metrics fetching and process list management; enhance UI update efficiency 2025-06-12 18:47:43 +02:00
d005543234 clean: cleanup 2025-06-12 18:38:08 +02:00
519091b8d4 Remove obsolete files and scripts; implement multi-architecture build and Debian package creation for HMAC File Server v3.2 2025-06-12 17:52:00 +02:00
c944dd14b1 feat: Add upload test script and enhance Dockerfile for configuration validation 2025-06-12 16:22:46 +02:00
a71d12a681 feat: Add comprehensive installation verification scripts for HMAC File Server 2025-06-12 15:50:17 +02:00
20e9da0413 feat: Enhance configuration validation system and cleanup repository
- Added comprehensive configuration validation framework in HMAC File Server v3.2.
- Implemented core validation checks including error/warning categorization, resource validation, and network connectivity testing.
- Introduced standalone configuration checker script for interactive validation.
- Enhanced installer and uninstaller with data preservation options and smart directory detection.
- Created production fix and quick fix scripts to address systemd startup issues.
- Consolidated documentation and removed temporary files to streamline repository structure.
- Added test scripts for configuration generation, validation, and custom data selection functionality.
- Updated README and INSTALL documentation to reflect new features and usage instructions.
2025-06-12 15:46:07 +02:00
1fda7ef9be Add test script for validating installer configuration generation
- Introduced `test_installer_config.sh` to validate the configuration generated by the installer.
- The script sets up a test environment, simulates installer input, and generates a configuration file.
- It includes a function to create a sample configuration and validates it against the expected structure.
- Added detailed output for build success and configuration validation results.
- Cleans up the test directory after execution.
2025-06-12 15:34:26 +02:00
c566fbe00a feat: add test script for verifying color formatting in installer completion message 2025-06-12 14:33:49 +02:00
38820c401f feat: fix installation message color formatting and add production fix scripts for systemd startup issues 2025-06-12 14:31:43 +02:00
41a0635b43 feat: enhance installer with ClamAV auto-detection and validation file inclusion 2025-06-12 14:13:47 +02:00
ce42a107dc feat: enhance uninstaller with comprehensive data preservation options and interactive interface 2025-06-12 13:58:35 +02:00
130f877d00 Add test configurations with intentional errors for validation checks
- Created `test_config_errors.toml` with various invalid settings including:
  - Invalid port number and IP address
  - Nonexistent storage path
  - Incorrect formats for max upload size and TTL
  - Negative worker count and zero queue size
  - Invalid Redis address format

- Created `test_errors.toml` with additional intentional errors such as:
  - High invalid port number and empty storage path
  - Weak secret and invalid log level
  - Invalid Redis address and zero scan workers for ClamAV
2025-06-12 13:43:07 +02:00
e34c7dde7e feat: add comprehensive configuration validation and testing framework 2025-06-12 13:19:46 +02:00
6d9b062e58 feat: add health check endpoint for monitoring 2025-06-11 19:06:47 +02:00
65e5907b03 fix: cleanup 2025-06-11 13:09:03 +02:00
3f8bb3bf69 release: hmac-file-server 3.2 2025-06-11 10:17:33 +02:00
969cb0cfea release: 3.1 with v3 sha256 2025-06-08 11:55:24 +02:00
5584b954ef release: 3.1 with v3 sha256 2025-06-08 08:54:36 +02:00
2308546e69 release: 3.1 with v3 sha256 2025-06-08 07:15:17 +02:00
56a15323a9 release: 3.1 with v3 sha256 2025-06-08 07:09:21 +02:00
28 changed files with 7167 additions and 2677 deletions

7
.gitignore vendored
View File

@ -1,7 +0,0 @@
.vscode/
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
cmd/monitor/hmac-monitor-1.0_amd64
cmd/monitor/hmac-monitor-1.0_arm64

View File

@ -1,9 +1,51 @@
# Changelog
> **Note:** This file is a technical changelog for developers and maintainers. For user-focused highlights, migration notes, and upgrade instructions, see [RELEASE-NOTES.MD](./RELEASE-NOTES.MD).
> **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)

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.

21
LICENSE
View File

@ -1,21 +0,0 @@
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.

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.

448
README.MD
View File

@ -1,43 +1,69 @@
# HMAC File Server 3.1-Stable
## Overview
The **HMAC File Server** ensures secure file uploads and downloads using HMAC authentication. It incorporates rate limiting, CORS support, retries, file versioning, and Unix socket support for enhanced flexibility. Redis integration provides efficient caching and session management. Prometheus metrics and a graceful shutdown mechanism ensure reliable and efficient file handling.
# HMAC File Server 3.2
Special thanks to **Thomas Leister** for inspiration drawn from [[prosody-filer](https://github.com/ThomasLeister/prosody-filer)](https://github.com/ThomasLeister/prosody-filer).
## 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
- File deduplication
- Configurable TTL for automatic file cleanup
- Secure HMAC-based authentication
- Chunked uploads and downloads
- Virus scanning via ClamAV
- Prometheus metrics integration
- Customizable worker management
- Support ISO-based storage
- Support for mod_http_upload_external (v3) for secure file uploads with HMAC-SHA256 validation and expiration checks
- **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
## Table of Contents
1. [Installation](#installation)
2. [Configuration](#configuration)
3. [Usage](#usage)
4. [Setup](#setup)
3. [Authentication](#authentication)
4. [API Endpoints](#api-endpoints)
5. [Usage Examples](#usage-examples)
6. [Setup](#setup)
- [Reverse Proxy](#reverse-proxy)
- [Systemd Service](#systemd-service)
5. [Building](#building)
6. [Changelog](#changelog)
7. [License](#license)
7. [Building](#building)
8. [Docker Support](#docker-support)
9. [Changelog](#changelog)
10. [License](#license)
---
## Installation
> **Tip:** You can also run HMAC File Server using Docker Compose for easy deployment. See `dockerenv/docker-compose.yml` for a ready-to-use example. The official image is available at `ghcr.io/plusone/hmac-file-server:latest`.
### Quick Installation for XMPP Operators
### Prerequisites
The easiest way to install HMAC File Server is using the automated installer:
```bash
git clone https://github.com/PlusOne/hmac-file-server.git
cd hmac-file-server
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
#### Steps
1. Clone the repository:
```bash
git clone https://github.com/PlusOne/hmac-file-server.git
@ -46,48 +72,215 @@ Special thanks to **Thomas Leister** for inspiration drawn from [[prosody-filer]
2. Build the server:
```bash
go build -o hmac-file-server
go build -o hmac-file-server ./cmd/server/main.go
```
3. Create necessary directories:
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/
mkdir -p /path/to/hmac-file-server/iso/
```
4. Copy and edit the configuration file:
```bash
cp config.example.toml config.toml
```
5. Edit your `config.toml` file with appropriate settings.
5. Start the server:
6. Start the server:
```bash
./hmac-file-server -config config.toml
```
---
## Configuration
The server is configured via a `config.toml` file. Key settings include:
## Uninstallation
- **Server Settings**: Port, logging, metrics
- **Security**: HMAC secret, TLS options
- **File Management**: TTL, deduplication, uploads, and downloads
- **ISO**: Generation and mounting settings
- **Workers**: Adjust thread management
The installer script provides comprehensive uninstallation options with data preservation:
For detailed configuration options, refer to the [Wiki](./wiki.md).
```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.
---
## Usage
Start the server and access it on the configured port. Use curl or a client library to interact with the API.
## Configuration
The server uses a comprehensive `config.toml` file with the following main sections:
### Key Configuration Sections
- **[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
### Example Configuration
```toml
[server]
bind_ip = "0.0.0.0"
listenport = "8080"
unixsocket = false
storagepath = "./uploads"
metricsenabled = true
metricsport = "9090"
deduplicationenabled = true
filenaming = "HMAC" # Options: "HMAC", "original", "None"
forceprotocol = "auto" # Options: "ipv4", "ipv6", "auto"
[security]
secret = "your-secure-hmac-secret"
enablejwt = false
jwtsecret = "your-jwt-secret"
jwtalgorithm = "HS256"
jwtexpiration = "24h"
[uploads]
allowedextensions = [".txt", ".pdf", ".jpg", ".png", ".zip"]
chunkeduploadsenabled = true
chunksize = "10MB"
```
For complete configuration details, see the [Wiki](./WIKI.MD).
---
## Authentication
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
### Example
Upload a file:
```bash
curl -X POST -F 'file=@example.jpg' http://localhost:8080/upload
# 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
```
---
@ -95,115 +288,156 @@ curl -X POST -F 'file=@example.jpg' http://localhost:8080/upload
## Setup
### Reverse Proxy
Set up a reverse proxy using Apache2 or Nginx to handle requests.
#### Apache2 Example
```apache
<VirtualHost *:80>
ServerName your-domain.com
ProxyPreserveHost On
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
</VirtualHost>
```
#### Nginx Example
#### 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
Create a systemd service file for the HMAC File Server:
```ini
[Unit]
Description=HMAC File Server
After=network.target
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
User=www-data
Group=www-data
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 the service:
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
To build for different architectures:
- **Linux (amd64)**:
```bash
GOOS=linux GOARCH=amd64 go build -o hmac-file-server main.go
```
### Local Build
```bash
go build -o hmac-file-server ./cmd/server/main.go
```
- **Linux (arm64)**:
```bash
GOOS=linux GOARCH=arm64 go build -o hmac-file-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
### Added
- **Deduplication Support:** Automatically remove duplicate files based on SHA256 hashing to save storage space.
- **ISO Container Management:** Create and mount ISO containers for specialized storage needs, enhancing flexibility in file management.
- **Prometheus Metrics Enhancements:** Added detailed metrics for deduplication and ISO container operations to improve monitoring and observability.
- **Redis Integration Improvements:** Enhanced caching mechanisms using Redis for faster access to file metadata and application states.
- **Precaching Feature:** Implemented precaching of file structures on startup to reduce access times for frequently used files.
- **Configuration Options:** Updated `config.toml` to include new settings for deduplication, ISO management, and worker scaling.
### 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
### Changed
- **Worker Pool Scaling:** Implemented dynamic adjustment of worker threads based on system resources to optimize performance.
- **Logging Enhancements:** Improved logging for file operations, including detailed information on file extensions and MIME types during uploads.
- **Temporary Path Configuration:** Replaced hardcoded temporary upload directories with a configurable `TempPath` parameter in `config.toml` for greater flexibility.
### 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
### Fixed
- **Temporary File Handling:** Resolved issues where temporary `.tmp` files caused "Unsupported file type" warnings by enhancing MIME type detection logic.
- **MIME Type Detection:** Improved MIME type detection to ensure better compatibility and accuracy during file uploads.
### 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
### Deprecated
- **Thumbnail Support (Previous Implementation):** Dropped the previous thumbnail support mechanism. This feature will not return in future releases.
---
## Version 3.1 - 2025-06-08
### Features
- **v3 (mod_http_upload_external) Support**: Added support for secure file uploads using HMAC-SHA256 validation and expiration checks. This feature ensures that Prosody clients can upload files securely with signed URLs.
- **GET URL Construction**: Automatically generates GET URLs for uploaded files, making them accessible for download.
### Improvements
- Enhanced HMAC validation logic to support v3 alongside existing fallback mechanisms (v2 and v).
- Improved error handling for invalid or expired signatures during file uploads.
### Notes
- This release introduces significant improvements for secure file uploads, ensuring compatibility with mod_http_upload_external.
---
**Important Update:**
- The minimum Go version required is now **1.24**. Please ensure your environment meets this requirement for successful compilation.
### Previous Versions
See [CHANGELOG.MD](./CHANGELOG.MD) for complete version history.
---
@ -213,9 +447,7 @@ 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:
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.

View File

@ -1,99 +0,0 @@
# Release Notes for HMAC File Server 3.1
> **Note:** This file summarizes user-facing changes, migration notes, and upgrade instructions. For a detailed technical changelog, see [CHANGELOG.md](./CHANGELOG.md).
## [3.1] - 2025-06-08
### Added
- **v3 (mod_http_upload_external) Support**: Implemented secure file uploads using HMAC-SHA256 validation and expiration checks. This feature enables Prosody clients to upload files securely with signed URLs.
- **GET URL Construction**: Automatically generates GET URLs for uploaded files, making them accessible for download.
### Changed
- Enhanced HMAC validation logic to support v3 alongside existing fallback mechanisms (v2 and v).
- Improved error handling for invalid or expired signatures during file uploads.
- Updated version strings from `2.9-Stable` to `3.1-Stable` in the codebase.
### Fixed
- Addressed minor issues in file upload handling to ensure compatibility with v3 clients.
---
# Release Notes for HMAC File Server 3.0
> **Note:** This file summarizes user-facing changes, migration notes, and upgrade instructions. For a detailed technical changelog, see [CHANGELOG.md](./CHANGELOG.md).
## [3.0] - 2025-06-05
### Added
- `--genconfig` and `--genconfig-path` flags to generate example configurations on demand.
- Robust dual-stack HTTP client with idle connection timeouts and keep-alive settings for seamless IPv4/IPv6 support.
- Automated end-to-end tests for `--genconfig` generation and IPv4/IPv6 startup behavior.
- Proxy best practices and recommendations (timeouts, buffer sizes, security headers) in documentation.
- Enhanced startup logging of `BindIP`, `FileNaming`, and `ForceProtocol` settings.
### Changed
- Consolidated all configuration structs into a single `Config` type; removed duplicated and unused fields.
- Improved `parseSize` and `parseTTL` functions with strict unit parsing and error handling.
- Fixed Markdown lint issues in `CHANGELOG.md` and `wiki.md` for consistent headings and formatting.
- Refined PID file write/remove logic and added graceful shutdown logging enhancements.
### Fixed
- Ensured seamless IPv4/IPv6 dual-stack handling with appropriate timeouts and error control.
- Validated `--config` flag usage with clear error messages and fallback behavior.
- Resolved edge cases in network protocol initialization preventing unexpected errors.
---
# Release Notes for HMAC File Server 2.7-Stable
## Summary
Version 2.6-Stable focuses on improving the overall stability and performance of the HMAC File Server. Significant changes have been made to prioritize reliability and scalability for production environments.
## Key Changes
### Breaking Changes
- **Thumbnail Generation Dropped**: Support for automatic thumbnail generation has been removed in this release. This decision was made to enhance system stability and reduce resource consumption. Users requiring thumbnails are encouraged to use external tools.
### New Features
- **ISO-Based Storage Support**: Introduced support for ISO-based storage to accommodate specialized use cases.
- **Enhanced ClamAV Integration**: Improved ClamAV scanning with concurrent workers, providing better performance for large-scale deployments.
- **Timeout Configuration**: Added granular timeout settings for read, write, and idle connections, improving connection management.
- **FileNaming Configuration**: Added support for a "None" option in the `FileNaming` configuration. When set to "None", the filename remains unchanged.
- **Example Configuration Generation**: If no configuration file is found, the server will output an example configuration for the user to copy and paste.
- **Prometheus Metrics**: Enhanced Prometheus metrics for better monitoring and performance tracking. New metrics include upload and download durations, error counts, memory and CPU usage, and more.
### Improvements
- **Worker Management**: Auto-scaling worker threads based on system load for optimal performance.
- **Logging Enhancements**: Improved log verbosity control, making debugging and monitoring easier.
### Bug Fixes
- Resolved minor issues affecting deduplication and file upload performance.
- Fixed a rare crash scenario during high-concurrency file uploads.
## Migration Notes
1. **Thumbnail Settings**: Remove `[thumbnails]` configuration blocks from your `config.toml` file to avoid errors.
2. **Updated Configuration**: Review new timeout settings in `[timeouts]` and adjust as needed.
3. **ISO Integration**: Configure the new `[iso]` block for environments utilizing ISO-based storage.
4. **FileNaming Configuration**: Update the `FileNaming` setting in `[server]` to use the new "None" option if you want filenames to remain unchanged.
[server]
# FileNaming options: "HMAC", "None"
FileNaming = "HMAC"
## Recommendations
- **Security**: Ensure that the HMAC secret key in `config.toml` is updated to a strong, unique value.
- **Backups**: Regularly back up your `config.toml` and important data directories.
- **Monitoring**: Leverage Prometheus metrics for real-time monitoring of server performance.
For a detailed guide on setting up and configuring the HMAC File Server, refer to the [README.md](./README.md).
---
Thank you for using HMAC File Server! If you encounter any issues, feel free to report them on our GitHub repository.
## Version 2.7
- Refinements in worker scaling logic
- Removed obsolete parameters for sorting
- Further improvements to ISO-based storage handling
- Fixed dual stack for upload (IPv4/IPv6)

View File

@ -249,12 +249,16 @@ idletimeout = "3600s" # Maximum keep-alive time for idle connections
---
### Security Settings
### Security Configuration
```toml
# Security settings
[security]
secret = "your-secure-secret-key" # HMAC shared secret key (change to a secure value)
enablejwt = false # Enable JWT authentication
jwtsecret = "your-jwt-secret" # JWT signing secret
jwtalgorithm = "HS256" # JWT algorithm
jwtexpiration = "24h" # JWT token expiration
```
#### Configuration Options
@ -265,6 +269,28 @@ secret = "your-secure-secret-key" # HMAC shared secret key (change to a secure
- *Default*: `"your-secure-secret-key"`
- *Warning*: **Change this immediately** to a unique, strong string in production environments to ensure the security of HMAC operations.
- **enablejwt**:
- *Type*: `Boolean`
- *Description*: Enables or disables JWT token authentication. When enabled, the server will accept JWT tokens for authentication.
- *Default*: `false`
- **jwtsecret**:
- *Type*: `String`
- *Description*: The secret key used for signing and validating JWT tokens. Must be strong and secure.
- *Default*: `"your-jwt-secret"`
- **jwtalgorithm**:
- *Type*: `String`
- *Description*: The algorithm used for JWT token signing.
- *Options*: `"HS256"`, `"HS384"`, `"HS512"`
- *Default*: `"HS256"`
- **jwtexpiration**:
- *Type*: `String`
- *Description*: The expiration time for JWT tokens.
- *Format*: Duration (e.g., `"24h"` for 24 hours, `"30m"` for 30 minutes)
- *Default*: `"24h"`
---
### Versioning Settings
@ -290,7 +316,55 @@ maxversions = 1 # Number of file versions to retain
---
### Uploads Settings
### Logging Configuration
```toml
# Logging settings
[logging]
level = "debug"
file = "/path/to/hmac-file-server.log"
max_size = 100 # Maximum log file size in MB
max_backups = 7 # Number of backup log files to keep
max_age = 30 # Maximum age of log files in days
compress = true # Compress old log files
```
#### Configuration Options
- **level**:
- *Type*: `String`
- *Description*: Sets the verbosity level of logs.
- *Options*: `"debug"`, `"info"`, `"warn"`, `"error"`
- *Default*: `"debug"`
- **file**:
- *Type*: `String`
- *Description*: Specifies the file path for logging. If left empty, logs are output to `stdout`.
- *Default*: `"/path/to/hmac-file-server.log"`
- **max_size**:
- *Type*: `Integer`
- *Description*: Maximum size of log files before rotation (in MB).
- *Default*: `100`
- **max_backups**:
- *Type*: `Integer`
- *Description*: Number of backup log files to retain after rotation.
- *Default*: `7`
- **max_age**:
- *Type*: `Integer`
- *Description*: Maximum age of log files in days before deletion.
- *Default*: `30`
- **compress**:
- *Type*: `Boolean`
- *Description*: Whether to compress old log files with gzip.
- *Default*: `true`
---
### Uploads Configuration
```toml
# Upload settings
@ -300,8 +374,6 @@ chunkeduploadsenabled = true
chunksize = "32MB" # Chunk size for uploads
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"
]
```
@ -331,15 +403,13 @@ allowedextensions = [
```toml
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 Settings
### Downloads Configuration
```toml
# Downloads settings
@ -367,7 +437,7 @@ chunksize = "32MB"
- *Format*: Size (e.g., `"32MB"`)
- *Default*: `"32MB"`
> **Note**: The `allowedextensions` key is **not** part of the `[downloads]` configuration based on the provided code. Ensure that it is omitted to prevent configuration errors.
> **Note**: Downloads inherit allowed extensions from the uploads configuration. There is no separate `allowedextensions` setting for downloads.
---
@ -457,12 +527,13 @@ redishealthcheckinterval = "120s" # Interval for Redis health checks
---
### Worker Settings
### Workers Configuration
```toml
# Worker settings
[worker]
numworkers = 10 # Number of worker threads
# Workers settings
[workers]
numworkers = 10 # Number of worker threads
uploadqueuesize = 5000 # Size of upload queue
```
#### Configuration Options
@ -472,6 +543,11 @@ numworkers = 10 # Number of worker threads
- *Description*: Specifies the number of worker threads to handle file operations.
- *Default*: `10`
- **uploadqueuesize**:
- *Type*: `Integer`
- *Description*: Sets the size of the upload queue buffer.
- *Default*: `5000`
---
#### Configuration Options
@ -484,6 +560,140 @@ numworkers = 10 # Number of worker threads
---
## Configuration Validation
The HMAC File Server v3.2 includes a comprehensive configuration validation system with specialized command-line flags for different validation scenarios.
### Available Validation Flags
#### Core Validation Commands
**`--validate-config`**
- **Purpose**: Full comprehensive validation of all configuration sections
- **Usage**: `./hmac-file-server --validate-config`
- **Output**: Complete validation report with all errors and warnings
```bash
# Example
./hmac-file-server -config config.toml --validate-config
```
**`--test-config`**
- **Purpose**: Run predefined configuration test scenarios
- **Usage**: `./hmac-file-server --test-config`
- **Output**: Test scenario results for configuration validation
#### Specialized Validation Modes
**`--check-security`**
- **Purpose**: Security-focused validation only
- **Checks**: Secret strength, default values, JWT algorithms, network exposure, file permissions
- **Example**: `./hmac-file-server -config config.toml --check-security`
**`--check-performance`**
- **Purpose**: Performance-focused validation only
- **Checks**: Worker optimization, memory usage, timeout balance, large file handling
- **Example**: `./hmac-file-server -config config.toml --check-performance`
**`--check-connectivity`**
- **Purpose**: Network connectivity validation only
- **Checks**: Redis connections, ClamAV sockets, address validation, DNS resolution
- **Example**: `./hmac-file-server -config config.toml --check-connectivity`
#### Output Control Flags
**`--validate-quiet`**
- **Purpose**: Minimal output, returns only exit codes
- **Usage**: Perfect for automation and scripts
**`--validate-verbose`**
- **Purpose**: Detailed output with comprehensive analysis
- **Usage**: Best for troubleshooting and development
**`--check-fixable`**
- **Purpose**: Show only issues that can be automatically fixed
- **Usage**: Helps prioritize configuration improvements
### Validation Categories
#### Security Checks (6 categories)
- Secret strength analysis
- Default value detection
- Algorithm recommendations
- Network exposure warnings
- File permission analysis
- Debug logging security
#### Performance Checks (5 categories)
- Resource optimization
- Memory usage analysis
- Timeout balancing
- Large file preparation
- Configuration efficiency
#### Connectivity Checks (4 categories)
- Service connectivity
- Socket accessibility
- Address validation
- DNS resolution
#### System Checks (5 categories)
- CPU availability
- Memory monitoring
- Disk space validation
- Permission testing
- Resource constraints
### Integration Examples
#### Shell Script Integration
```bash
#!/bin/bash
CONFIG_FILE="/etc/hmac-file-server/config.toml"
echo "🔍 Validating HMAC File Server configuration..."
# Run validation
if ./hmac-file-server -config "$CONFIG_FILE" --validate-config; then
echo "✅ Configuration validation passed"
# Additional specific checks
echo "🔐 Running security audit..."
./hmac-file-server -config "$CONFIG_FILE" --check-security
echo "⚡ Checking performance settings..."
./hmac-file-server -config "$CONFIG_FILE" --check-performance
else
echo "❌ Configuration validation failed"
echo "💡 Try: ./hmac-file-server -config $CONFIG_FILE --check-fixable"
exit 1
fi
```
#### Docker Integration
```dockerfile
# Add validation step to Dockerfile
RUN ./hmac-file-server -config /etc/config.toml --validate-config && \
./hmac-file-server -config /etc/config.toml --check-security
```
#### Kubernetes Health Check
```yaml
livenessProbe:
exec:
command:
- /usr/local/bin/hmac-file-server
- -config
- /etc/config/config.toml
- --validate-quiet
initialDelaySeconds: 30
periodSeconds: 60
```
The enhanced command-line validation system provides comprehensive coverage with 50+ validation checks across all configuration areas, making HMAC File Server v3.2 production-ready with enterprise-grade configuration management.
---
## Example Configuration
Below is an example `config.toml` file with default settings:
@ -493,10 +703,9 @@ Below is an example `config.toml` file with default settings:
# Server configuration
listenport = "8080"
bind_ip = "0.0.0.0"
unixsocket = false
storagepath = "/path/to/hmac-file-server/data/"
loglevel = "debug"
logfile = "/path/to/hmac-file-server.log"
metricsenabled = true
metricsport = "9090"
deduplicationenabled = true
@ -507,8 +716,18 @@ autoadjustworkers = true
networkevents = false
pidfilepath = "./hmac-file-server.pid"
precaching = true
filenaming = "HMAC"
forceprotocol = "auto"
# Logging settings
[logging]
level = "debug"
file = "/path/to/hmac-file-server.log"
max_size = 100
max_backups = 7
max_age = 30
compress = true
# Deduplication settings
[deduplication]
enabled = true
@ -530,6 +749,10 @@ idletimeout = "3600s"
# Security settings
[security]
secret = "your-secure-secret-key"
enablejwt = false
jwtsecret = "your-jwt-secret"
jwtalgorithm = "HS256"
jwtexpiration = "24h"
# Versioning settings
[versioning]
@ -572,9 +795,10 @@ redisaddr = "localhost:6379"
redispassword = ""
redishealthcheckinterval = "120s"
# Worker settings
[worker]
# Workers settings
[workers]
numworkers = 10
uploadqueuesize = 5000
```
---
@ -593,7 +817,7 @@ To install the HMAC File Server, follow these steps:
2. Build the server:
```sh
go build -o hmac-file-server
go build -o hmac-file-server ./cmd/server/main.go
```
3. Create the necessary directories:

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

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"

View File

@ -1,18 +1,18 @@
package main
import (
"bufio"
"context"
"fmt"
"io"
"log"
"net/http"
"os"
"sort"
"strconv"
"strings"
"time"
"context"
"io"
"sync"
"bufio"
"time"
"github.com/gdamore/tcell/v2"
"github.com/pelletier/go-toml"
@ -24,11 +24,11 @@ import (
)
var (
prometheusURL string
configFilePath string // Pfad der gefundenen Konfiguration
logFilePath string // Pfad der Logdatei aus der Konfiguration
metricsEnabled bool // Neue Variable für die Aktivierung von Metriken
bindIP string // Neue Variable für die gebundene IP-Adresse
prometheusURL string
configFilePath string // Pfad der gefundenen Konfiguration
logFilePath string // Pfad der Logdatei aus der Konfiguration
metricsEnabled bool // Neue Variable für die Aktivierung von Metriken
bindIP string // Neue Variable für die gebundene IP-Adresse
)
func init() {
@ -128,68 +128,94 @@ const (
// ProcessInfo holds information about a process
type ProcessInfo struct {
PID int32
Name string
CPUPercent float64
MemPercent float32
CommandLine string
Uptime string // Neues Feld für die Uptime
Status string // Neues Feld für den Status
ErrorCount int // Neues Feld für die Anzahl der Fehler
TotalRequests int64 // Neues Feld für die Gesamtanzahl der Anfragen
ActiveConnections int // Neues Feld für aktive Verbindungen
AverageResponseTime float64 // Neues Feld für die durchschnittliche Antwortzeit in Millisekunden
PID int32
Name string
CPUPercent float64
MemPercent float32
CommandLine string
Uptime string // Neues Feld für die Uptime
Status string // Neues Feld für den Status
ErrorCount int // Neues Feld für die Anzahl der Fehler
TotalRequests int64 // Neues Feld für die Gesamtanzahl der Anfragen
ActiveConnections int // Neues Feld für aktive Verbindungen
AverageResponseTime float64 // Neues Feld für die durchschnittliche Antwortzeit in Millisekunden
}
// Function to fetch and parse Prometheus metrics
// Optimized metrics fetching with timeout and connection reuse
func fetchMetrics() (map[string]float64, error) {
resp, err := http.Get(prometheusURL)
// Create HTTP client with timeout and connection reuse
client := &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
DisableCompression: true,
},
}
resp, err := client.Get(prometheusURL)
if err != nil {
return nil, fmt.Errorf("failed to fetch metrics: %w", err)
}
defer resp.Body.Close()
// Limit response body size to prevent memory issues
limitedReader := io.LimitReader(resp.Body, 1024*1024) // 1MB limit
parser := &expfmt.TextParser{}
metricFamilies, err := parser.TextToMetricFamilies(resp.Body)
metricFamilies, err := parser.TextToMetricFamilies(limitedReader)
if err != nil {
return nil, fmt.Errorf("failed to parse metrics: %w", err)
}
metrics := make(map[string]float64)
// More selective metric filtering to reduce processing
relevantPrefixes := []string{
"hmac_file_server_",
"memory_usage_bytes",
"cpu_usage_percent",
"active_connections_total",
"goroutines_count",
"total_requests",
"average_response_time_ms",
}
for name, mf := range metricFamilies {
// Filter the metrics you're interested in
if strings.HasPrefix(name, "hmac_file_server_") ||
name == "memory_usage_bytes" ||
name == "cpu_usage_percent" ||
name == "active_connections_total" ||
name == "goroutines_count" ||
name == "total_requests" ||
name == "average_response_time_ms" {
// Quick prefix check to skip irrelevant metrics
relevant := false
for _, prefix := range relevantPrefixes {
if strings.HasPrefix(name, prefix) || name == prefix {
relevant = true
break
}
}
if !relevant {
continue
}
for _, m := range mf.GetMetric() {
var value float64
if m.GetGauge() != nil {
value = m.GetGauge().GetValue()
} else if m.GetCounter() != nil {
value = m.GetCounter().GetValue()
} else if m.GetUntyped() != nil {
value = m.GetUntyped().GetValue()
} else {
// If the metric type is not handled, skip it
continue
}
for _, m := range mf.GetMetric() {
var value float64
if m.GetGauge() != nil {
value = m.GetGauge().GetValue()
} else if m.GetCounter() != nil {
value = m.GetCounter().GetValue()
} else if m.GetUntyped() != nil {
value = m.GetUntyped().GetValue()
} else {
continue
}
// Handle metrics with labels
if len(m.GetLabel()) > 0 {
labels := make([]string, 0)
for _, label := range m.GetLabel() {
labels = append(labels, fmt.Sprintf("%s=\"%s\"", label.GetName(), label.GetValue()))
}
metricKey := fmt.Sprintf("%s{%s}", name, strings.Join(labels, ","))
metrics[metricKey] = value
} else {
metrics[name] = value
// Simplified label handling
if len(m.GetLabel()) > 0 {
labels := make([]string, 0, len(m.GetLabel()))
for _, label := range m.GetLabel() {
labels = append(labels, fmt.Sprintf("%s=\"%s\"", label.GetName(), label.GetValue()))
}
metricKey := fmt.Sprintf("%s{%s}", name, strings.Join(labels, ","))
metrics[metricKey] = value
} else {
metrics[name] = value
}
}
}
@ -222,47 +248,82 @@ func fetchSystemData() (float64, float64, int, error) {
return v.UsedPercent, cpuUsage, cores, nil
}
// Funktion zum Abrufen der Prozessliste mit paralleler Verarbeitung
// Optimized process list fetching with better resource management
func fetchProcessList() ([]ProcessInfo, error) {
processes, err := process.Processes()
if err != nil {
return nil, fmt.Errorf("failed to fetch processes: %w", err)
}
var processList []ProcessInfo
// Pre-allocate slice with reasonable capacity
processList := make([]ProcessInfo, 0, len(processes))
var mu sync.Mutex
var wg sync.WaitGroup
// Begrenzung der gleichzeitigen Goroutinen auf 10
sem := make(chan struct{}, 10)
// Limit concurrent goroutines to prevent resource exhaustion
sem := make(chan struct{}, 5) // Reduced from 10 to 5
timeout := time.After(10 * time.Second) // Add timeout
// Process only a subset of processes to reduce load
maxProcesses := 200
if len(processes) > maxProcesses {
processes = processes[:maxProcesses]
}
for _, p := range processes {
select {
case <-timeout:
log.Printf("Process list fetch timeout, returning partial results")
return processList, nil
default:
}
wg.Add(1)
sem <- struct{}{} // Eintritt in semaphor
sem <- struct{}{} // Enter semaphore
go func(p *process.Process) {
defer wg.Done()
defer func() { <-sem }() // Austritt aus semaphor
defer func() {
<-sem // Exit semaphore
// Recover from any panics in process info fetching
if r := recover(); r != nil {
log.Printf("Process info fetch panic: %v", r)
}
}()
cpuPercent, err := p.CPUPercent()
// Set shorter timeout for individual process operations
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// Use context for process operations where possible
cpuPercent, err := p.CPUPercentWithContext(ctx)
if err != nil {
return
}
memPercent, err := p.MemoryPercent()
memPercent, err := p.MemoryPercentWithContext(ctx)
if err != nil {
return
}
name, err := p.Name()
name, err := p.NameWithContext(ctx)
if err != nil {
return
}
cmdline, err := p.Cmdline()
// Skip if CPU and memory usage are both very low to reduce noise
if cpuPercent < 0.1 && memPercent < 0.1 {
return
}
// Limit command line length to prevent memory bloat
cmdline, err := p.CmdlineWithContext(ctx)
if err != nil {
cmdline = ""
}
if len(cmdline) > 100 {
cmdline = cmdline[:100] + "..."
}
info := ProcessInfo{
PID: p.Pid,
@ -278,7 +339,20 @@ func fetchProcessList() ([]ProcessInfo, error) {
}(p)
}
wg.Wait()
// Wait with timeout
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
select {
case <-done:
// All goroutines completed
case <-time.After(15 * time.Second):
log.Printf("Process list fetch timeout after 15 seconds, returning partial results")
}
return processList, nil
}
@ -369,201 +443,245 @@ func fetchHmacFileServerInfo() (*ProcessInfo, error) {
return nil, fmt.Errorf("hmac-file-server process not found")
}
// Neue Funktion zur Zählung der Fehler in den Logs
// Optimized error counting with caching and limits
var (
errorCountCache int
errorCountCacheTime time.Time
errorCountMutex sync.RWMutex
)
func countHmacErrors() (int, error) {
logFilePath := "/var/log/hmac-file-server.log" // Pfad zur Logdatei
// Use cached value if recent (within 30 seconds)
errorCountMutex.RLock()
if time.Since(errorCountCacheTime) < 30*time.Second {
count := errorCountCache
errorCountMutex.RUnlock()
return count, nil
}
errorCountMutex.RUnlock()
// Use the configured log file path
file, err := os.Open(logFilePath)
if err != nil {
return 0, err
}
defer file.Close()
// Get file size to limit reading for very large files
stat, err := file.Stat()
if err != nil {
return 0, err
}
// Limit to last 1MB for large log files
var startPos int64 = 0
if stat.Size() > 1024*1024 {
startPos = stat.Size() - 1024*1024
file.Seek(startPos, io.SeekStart)
}
scanner := bufio.NewScanner(file)
errorCount := 0
for scanner.Scan() {
lineCount := 0
maxLines := 1000 // Limit lines scanned
for scanner.Scan() && lineCount < maxLines {
line := scanner.Text()
if strings.Contains(line, "level=error") {
errorCount++
}
lineCount++
}
if err := scanner.Err(); err != nil {
return 0, err
}
// Update cache
errorCountMutex.Lock()
errorCountCache = errorCount
errorCountCacheTime = time.Now()
errorCountMutex.Unlock()
return errorCount, nil
}
// Funktion zur Aktualisierung der UI mit paralleler Datenbeschaffung
// Optimized data structure for caching
type cachedData struct {
systemData systemData
metrics map[string]float64
processes []ProcessInfo
hmacInfo *ProcessInfo
lastUpdate time.Time
mu sync.RWMutex
}
type systemData struct {
memUsage float64
cpuUsage float64
cores int
}
var cache = &cachedData{}
// Optimized updateUI with reduced frequency and better resource management
func updateUI(ctx context.Context, app *tview.Application, pages *tview.Pages, sysPage, hmacPage tview.Primitive) {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
// Reduce update frequency significantly
fastTicker := time.NewTicker(5 * time.Second) // UI updates
slowTicker := time.NewTicker(15 * time.Second) // Process list updates
defer fastTicker.Stop()
defer slowTicker.Stop()
// Einführung von Channels für verschiedene Daten
systemDataCh := make(chan struct {
memUsage float64
cpuUsage float64
cores int
err error
})
var metricsCh chan struct {
metrics map[string]float64
err error
}
if metricsEnabled {
metricsCh = make(chan struct {
metrics map[string]float64
err error
})
}
processListCh := make(chan struct {
processes []ProcessInfo
err error
})
hmacInfoCh := make(chan struct {
info *ProcessInfo
metrics map[string]float64
err error
})
// Worker pool to limit concurrent operations
workerPool := make(chan struct{}, 3) // Max 3 concurrent operations
// Goroutine zur Datenbeschaffung
// Single goroutine for data collection
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("Data collection goroutine recovered from panic: %v", r)
}
}()
for {
select {
case <-ctx.Done():
close(systemDataCh)
if metricsEnabled {
close(metricsCh)
}
close(processListCh)
close(hmacInfoCh)
return
case <-ticker.C:
// Systemdaten abrufen asynchron
go func() {
memUsage, cpuUsage, cores, err := fetchSystemData()
systemDataCh <- struct {
memUsage float64
cpuUsage float64
cores int
err error
}{memUsage, cpuUsage, cores, err}
}()
if metricsEnabled {
// Metriken abrufen asynchron
case <-fastTicker.C:
// Only update system data and metrics (lightweight operations)
select {
case workerPool <- struct{}{}:
go func() {
metrics, err := fetchMetrics()
metricsCh <- struct {
metrics map[string]float64
err error
}{metrics, err}
defer func() { <-workerPool }()
updateSystemAndMetrics()
}()
default:
// Skip if worker pool is full
}
case <-slowTicker.C:
// Update process list less frequently (expensive operation)
select {
case workerPool <- struct{}{}:
go func() {
defer func() { <-workerPool }()
updateProcessData()
}()
default:
// Skip if worker pool is full
}
// Prozessliste abrufen asynchron
go func() {
processes, err := fetchProcessList()
processListCh <- struct {
processes []ProcessInfo
err error
}{processes, err}
}()
// hmac-file-server Informationen abrufen asynchron
go func() {
hmacInfo, err := fetchHmacFileServerInfo()
var metrics map[string]float64
if metricsEnabled {
metrics, err = fetchMetrics()
}
hmacInfoCh <- struct {
info *ProcessInfo
metrics map[string]float64
err error
}{hmacInfo, metrics, err}
}()
}
}
}()
// UI update loop
uiTicker := time.NewTicker(2 * time.Second)
defer uiTicker.Stop()
for {
select {
case <-ctx.Done():
return
case data, ok := <-systemDataCh:
if !ok {
systemDataCh = nil
continue
}
if data.err != nil {
log.Printf("Fehler beim Abrufen der Systemdaten: %v\n", data.err)
continue
}
// UI aktualisieren mit Systemdaten
case <-uiTicker.C:
app.QueueUpdateDraw(func() {
if currentPage, _ := pages.GetFrontPage(); currentPage == "system" {
sysFlex := sysPage.(*tview.Flex)
sysTable := sysFlex.GetItem(0).(*tview.Table)
updateSystemTable(sysTable, data.memUsage, data.cpuUsage, data.cores)
}
})
case data, ok := <-metricsCh:
if !ok {
metricsCh = nil
continue
}
if data.err != nil {
log.Printf("Fehler beim Abrufen der Metriken: %v\n", data.err)
continue
}
// UI aktualisieren mit Metriken
app.QueueUpdateDraw(func() {
if currentPage, _ := pages.GetFrontPage(); currentPage == "system" {
sysFlex := sysPage.(*tview.Flex)
metricsTable := sysFlex.GetItem(1).(*tview.Table)
updateMetricsTable(metricsTable, data.metrics)
}
})
case data, ok := <-processListCh:
if !ok {
processListCh = nil
continue
}
if data.err != nil {
log.Printf("Fehler beim Abrufen der Prozessliste: %v\n", data.err)
continue
}
// UI aktualisieren mit Prozessliste
app.QueueUpdateDraw(func() {
if currentPage, _ := pages.GetFrontPage(); currentPage == "system" {
sysFlex := sysPage.(*tview.Flex)
processTable := sysFlex.GetItem(2).(*tview.Table)
updateProcessTable(processTable, data.processes)
}
})
case data, ok := <-hmacInfoCh:
if !ok {
hmacInfoCh = nil
continue
}
if data.err != nil {
log.Printf("Fehler beim Abrufen der hmac-file-server Informationen: %v\n", data.err)
continue
}
// UI aktualisieren mit hmac-file-server Informationen
app.QueueUpdateDraw(func() {
if currentPage, _ := pages.GetFrontPage(); currentPage == "hmac" && data.info != nil {
hmacFlex := hmacPage.(*tview.Flex)
hmacTable := hmacFlex.GetItem(0).(*tview.Table)
updateHmacTable(hmacTable, data.info, data.metrics)
}
updateUIComponents(pages, sysPage, hmacPage)
})
}
}
}
// Abbruchbedingung, wenn alle Channels geschlossen sind
if systemDataCh == nil && (!metricsEnabled || metricsCh == nil) && processListCh == nil && hmacInfoCh == nil {
break
// Separate function to update system data and metrics
func updateSystemAndMetrics() {
defer func() {
if r := recover(); r != nil {
log.Printf("updateSystemAndMetrics recovered from panic: %v", r)
}
}()
// Get system data
memUsage, cpuUsage, cores, err := fetchSystemData()
if err != nil {
log.Printf("Error fetching system data: %v", err)
return
}
// Get metrics if enabled
var metrics map[string]float64
if metricsEnabled {
metrics, err = fetchMetrics()
if err != nil {
log.Printf("Error fetching metrics: %v", err)
metrics = make(map[string]float64) // Use empty map on error
}
}
// Update cache
cache.mu.Lock()
cache.systemData = systemData{memUsage, cpuUsage, cores}
cache.metrics = metrics
cache.lastUpdate = time.Now()
cache.mu.Unlock()
}
// Separate function to update process data (expensive operation)
func updateProcessData() {
defer func() {
if r := recover(); r != nil {
log.Printf("updateProcessData recovered from panic: %v", r)
}
}()
// Get process list
processes, err := fetchProcessList()
if err != nil {
log.Printf("Error fetching process list: %v", err)
return
}
// Get HMAC info
hmacInfo, err := fetchHmacFileServerInfo()
if err != nil {
log.Printf("Error fetching HMAC info: %v", err)
}
// Update cache
cache.mu.Lock()
cache.processes = processes
cache.hmacInfo = hmacInfo
cache.mu.Unlock()
}
// Update UI components with cached data
func updateUIComponents(pages *tview.Pages, sysPage, hmacPage tview.Primitive) {
currentPage, _ := pages.GetFrontPage()
cache.mu.RLock()
defer cache.mu.RUnlock()
switch currentPage {
case "system":
sysFlex := sysPage.(*tview.Flex)
// Update system table
sysTable := sysFlex.GetItem(0).(*tview.Table)
updateSystemTable(sysTable, cache.systemData.memUsage, cache.systemData.cpuUsage, cache.systemData.cores)
// Update metrics table
if metricsEnabled && len(cache.metrics) > 0 {
metricsTable := sysFlex.GetItem(1).(*tview.Table)
updateMetricsTable(metricsTable, cache.metrics)
}
// Update process table
if len(cache.processes) > 0 {
processTable := sysFlex.GetItem(2).(*tview.Table)
updateProcessTable(processTable, cache.processes)
}
case "hmac":
if cache.hmacInfo != nil {
hmacFlex := hmacPage.(*tview.Flex)
hmacTable := hmacFlex.GetItem(0).(*tview.Table)
updateHmacTable(hmacTable, cache.hmacInfo, cache.metrics)
}
}
}
@ -665,13 +783,13 @@ func updateHmacTable(hmacTable *tview.Table, hmacInfo *ProcessInfo, metrics map[
hmacTable.SetCell(4, 0, tview.NewTableCell("Command"))
hmacTable.SetCell(4, 1, tview.NewTableCell(hmacInfo.CommandLine))
hmacTable.SetCell(5, 0, tview.NewTableCell("Uptime"))
hmacTable.SetCell(5, 1, tview.NewTableCell(hmacInfo.Uptime)) // Neue Zeile für Uptime
hmacTable.SetCell(6, 0, tview.NewTableCell("Status"))
hmacTable.SetCell(6, 1, tview.NewTableCell(hmacInfo.Status)) // Neue Zeile für Status
hmacTable.SetCell(7, 0, tview.NewTableCell("Error Count"))
hmacTable.SetCell(7, 1, tview.NewTableCell(fmt.Sprintf("%d", hmacInfo.ErrorCount))) // Neue Zeile für Error Count
@ -740,41 +858,73 @@ func createLogsPage(ctx context.Context, app *tview.Application, logFilePath str
SetWordWrap(true)
logsTextView.SetTitle(" [::b]Logs ").SetBorder(true)
const numLines = 100 // Number of lines to read from the end of the log file
const numLines = 50 // Reduced from 100 to 50 lines
// Read logs periodically
// Cache for log content to avoid reading file too frequently
var lastLogUpdate time.Time
var logMutex sync.RWMutex
// Read logs less frequently and only when on logs page
go func() {
ticker := time.NewTicker(5 * time.Second) // Increased from 2 to 5 seconds
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
default:
case <-ticker.C:
// Only update if we haven't updated recently
logMutex.RLock()
timeSinceUpdate := time.Since(lastLogUpdate)
logMutex.RUnlock()
if timeSinceUpdate < 4*time.Second {
continue
}
content, err := readLastNLines(logFilePath, numLines)
if err != nil {
app.QueueUpdateDraw(func() {
logsTextView.SetText(fmt.Sprintf("[red]Error reading log file: %v[white]", err))
})
} else {
// Process the log content to add colors
lines := strings.Split(content, "\n")
var coloredLines []string
for _, line := range lines {
if strings.Contains(line, "level=info") {
coloredLines = append(coloredLines, "[green]"+line+"[white]")
} else if strings.Contains(line, "level=warn") {
coloredLines = append(coloredLines, "[yellow]"+line+"[white]")
} else if strings.Contains(line, "level=error") {
coloredLines = append(coloredLines, "[red]"+line+"[white]")
} else {
// Default color
coloredLines = append(coloredLines, line)
}
}
app.QueueUpdateDraw(func() {
logsTextView.SetText(strings.Join(coloredLines, "\n"))
})
continue
}
time.Sleep(2 * time.Second) // Refresh interval for logs
// Process the log content with color coding
lines := strings.Split(content, "\n")
var coloredLines []string
// Limit the number of lines processed
maxLines := min(len(lines), numLines)
coloredLines = make([]string, 0, maxLines)
for i := len(lines) - maxLines; i < len(lines); i++ {
if i < 0 {
continue
}
line := lines[i]
if strings.Contains(line, "level=info") {
coloredLines = append(coloredLines, "[green]"+line+"[white]")
} else if strings.Contains(line, "level=warn") {
coloredLines = append(coloredLines, "[yellow]"+line+"[white]")
} else if strings.Contains(line, "level=error") {
coloredLines = append(coloredLines, "[red]"+line+"[white]")
} else {
coloredLines = append(coloredLines, line)
}
}
logContent := strings.Join(coloredLines, "\n")
// Update cache
logMutex.Lock()
lastLogUpdate = time.Now()
logMutex.Unlock()
app.QueueUpdateDraw(func() {
logsTextView.SetText(logContent)
})
}
}
}()
@ -782,6 +932,14 @@ func createLogsPage(ctx context.Context, app *tview.Application, logFilePath str
return logsTextView
}
// Helper function for min
func min(a, b int) int {
if a < b {
return a
}
return b
}
// Optimized readLastNLines to handle large files efficiently
func readLastNLines(filePath string, n int) (string, error) {
file, err := os.Open(filePath)
@ -889,4 +1047,4 @@ func main() {
log.Fatalf("Error running application: %v", err)
log.Fatalf("Error running application: %v", err)
}
}
}

View File

@ -1,97 +0,0 @@
[server]
#bind_ip = "127.0.0.1"
listenport = "8080"
unixsocket = false
storagepath = "./uploads/"
metricsenabled = true
metricsport = "9090"
deduplicationenabled = true
minfreebytes = "5GB"
filettl = "2y"
filettlenabled = false
autoadjustworkers = true
networkevents = false
pidfilepath = "./hmac-file-server.pid"
precaching = false
#globalextensions = ["*"]
[deduplication]
enabled = true
directory = "./deduplication/"
[logging]
level = "debug"
file = "./hmac-file-server.log"
max_size = 100
max_backups = 7
max_age = 30
compress = true
[thumbnails]
enabled = false
directory = "./thumbnails/"
size = "200x200"
thumbnailintervalscan = "1h"
concurrency = 5
[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

@ -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

@ -3,7 +3,7 @@
services:
hmac-file-server:
container_name: hmac-file-server
image: ghcr.io/plusone/hmac-file-server:latest
image: hmac-file-server:latest
ports:
- "8080:8080"
volumes:

View File

@ -6,7 +6,7 @@ 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
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

View File

@ -1,32 +0,0 @@
**Important Update Regarding HMAC File Server Version 2.6-Stable**
We've identified and resolved critical issues with the deduplication processes in **Version 2.6-Stable** of the HMAC File Server. This update brings significant improvements to ensure reliable deduplication and overall server performance.
**Impact:**
- **Resolved Deduplication Issues:** The deduplication feature in **Version 2.6-Stable** has been thoroughly tested and stabilized, ensuring consistent file integrity and optimal server performance.
- **Enhanced Stability:** Addressed previous unexpected behaviors, including lost links to files and incorrect file handling operations.
**Recommended Actions:**
- **Upgrade to Version 2.6-Stable:** If you are using any earlier version, we strongly recommend upgrading to **Version 2.6-Stable** to benefit from the latest fixes and improvements.
- **Rollback Not Required:** Users on older releases can upgrade without the need to rollback, as **Version 2.6-Stable** resolves all critical issues present in prior versions.
**What's New in Version 2.6-Stable:**
- **Deduplication Enhancements:**
- **Reliable Deduplication:** Improved algorithms for accurate and efficient processing.
- **Performance Optimizations:** Reduced resource consumption for faster deduplication.
- **Enhanced Features:**
- **Robust Malware Scanning:** Integrated advanced scanning to ensure file safety.
- **Enhanced Redis Support:** Improved performance with Redis for better caching.
- **Better Configuration Options:** More flexible settings for customization.
- **Security Improvements:**
- **Strengthened Authentication Mechanisms:** Safeguards against unauthorized access.
- **Improved Data Encryption:** Advanced encryption standards for data at rest and in transit.
**Stay Updated:**
For detailed information on addressed issues, fixes, and enhancements introduced in **Version 2.6-Stable**, please visit our repositories:
- [Git uuxo.net](https://git.uuxo.net/uuxo/hmac-file-server)
- [GitHub](https://github.com/PlusOne/hmac-file-server)
We appreciate your patience and support as we continue improving HMAC File Server. **Version 2.6-Stable** reflects our commitment to delivering a reliable and efficient file server solution. Thank you for choosing HMAC File Server!

1
go.mod
View File

@ -17,6 +17,7 @@ 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

2
go.sum
View File

@ -22,6 +22,8 @@ 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=

1273
installer.sh Executable file

File diff suppressed because it is too large Load Diff

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