release: hmac-file-server 3.2
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
.vscode/*
|
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
154
CHANGELOG.MD
Normal file
154
CHANGELOG.MD
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
> **Note:** This file is a technical changelog for developers and maintainers. For user-focused highlights, migration notes, and upgrade instructions, see [README.MD](./README.MD).
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [3.2] - Stable Release
|
||||||
|
|
||||||
|
### Added (3.2)
|
||||||
|
- **Enhanced Documentation**: Comprehensive update of all documentation files to match current codebase
|
||||||
|
- **Protocol Specification Updates**: Detailed protocol documentation with implementation examples
|
||||||
|
- **Configuration Validation**: Improved configuration structure validation and error reporting
|
||||||
|
- **Developer Resources**: Updated build instructions and development setup guides
|
||||||
|
|
||||||
|
### Changed (3.2)
|
||||||
|
- **Documentation Structure**: Reorganized documentation for better clarity and maintenance
|
||||||
|
- **Configuration Examples**: Updated all configuration examples to reflect current options
|
||||||
|
- **API Documentation**: Enhanced API endpoint documentation with comprehensive examples
|
||||||
|
|
||||||
|
### Completed (3.2)
|
||||||
|
- **Feature Development**: Active development of new features and improvements
|
||||||
|
- **Testing Enhancements**: Expanded test coverage and validation
|
||||||
|
- **Performance Optimizations**: Ongoing performance improvements and monitoring
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [3.1-Stable] - 2025-06-08
|
||||||
|
|
||||||
|
### Added (3.1)
|
||||||
|
- **v3 (mod_http_upload_external) Support**: Implemented secure file uploads using HMAC-SHA256 validation and expiration checks, specifically designed for Prosody's mod_http_upload_external compatibility.
|
||||||
|
- **JWT Authentication**: Complete JWT token authentication system with configurable algorithms and expiration times.
|
||||||
|
- **Multiple Authentication Protocols**: Support for legacy v1, enhanced v2, token-based, and v3 HMAC protocols alongside JWT authentication.
|
||||||
|
- **File Naming Strategy**: Configurable file naming options including HMAC-based, original filename preservation, or no specific naming convention.
|
||||||
|
- **Advanced Configuration Structure**: Comprehensive configuration sections including server, security, uploads, downloads, logging, deduplication, ISO, timeouts, versioning, ClamAV, Redis, and workers.
|
||||||
|
|
||||||
|
### Changed (3.1)
|
||||||
|
- **Enhanced HMAC Validation**: Improved validation logic to support multiple protocol versions (v1, v2, token, v3) with proper fallback mechanisms.
|
||||||
|
- **Authentication Priority**: Implemented authentication priority system with JWT taking precedence when enabled, falling back to HMAC protocols.
|
||||||
|
- **Network Protocol Support**: Enhanced IPv4/IPv6 dual-stack support with protocol forcing options (ipv4, ipv6, auto).
|
||||||
|
- **Configuration Hot-Reloading**: Added support for reloading logging configuration via SIGHUP signal without full server restart.
|
||||||
|
|
||||||
|
### Fixed (3.1)
|
||||||
|
- **Protocol Compatibility**: Addressed compatibility issues with different HMAC protocol versions and mod_http_upload_external clients.
|
||||||
|
- **Error Handling**: Improved error handling for invalid or expired signatures during file uploads.
|
||||||
|
- **Configuration Validation**: Enhanced configuration validation to prevent common misconfigurations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [3.0-Stable] - 2025-06-07
|
||||||
|
|
||||||
|
### Added (3.0)
|
||||||
|
- Official Docker Compose support and example (`dockerenv/docker-compose.yml`).
|
||||||
|
- Multi-stage Dockerfile for minimal images (`dockerenv/dockerbuild/Dockerfile`).
|
||||||
|
- Extended documentation for Docker, Compose, and deployment paths.
|
||||||
|
- Quickstart and configuration examples for containerized environments.
|
||||||
|
- Monitoring and Prometheus metrics documentation improvements.
|
||||||
|
- **Seamless IPv4 and IPv6 support:** The server now automatically supports both IPv4 and IPv6 connections out of the box, with improved dual-stack handling and configuration via `forceprotocol`.
|
||||||
|
|
||||||
|
### Changed (3.0)
|
||||||
|
- Minimum Go version is now **1.24** (was 1.20).
|
||||||
|
- Updated all documentation and config examples to reflect new version and Docker usage.
|
||||||
|
- Improved configuration normalization and environment variable overrides for containers.
|
||||||
|
- Enhanced worker pool and resource auto-scaling logic.
|
||||||
|
|
||||||
|
### Fixed (3.0)
|
||||||
|
- Minor bugfixes for config parsing and Docker path handling.
|
||||||
|
- Improved error messages for missing or invalid configuration in container environments.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2.8-Stable] - 2026-05-01
|
||||||
|
|
||||||
|
### Added (2.8)
|
||||||
|
- Version check history for improved tracking.
|
||||||
|
- Enhanced ClamAV scanning with concurrent workers.
|
||||||
|
|
||||||
|
### Changed (2.8)
|
||||||
|
- Improved ISO-based storage for specialized use cases.
|
||||||
|
- Auto-scaling workers for optimized performance.
|
||||||
|
|
||||||
|
### Fixed (2.8)
|
||||||
|
- Minor issues in worker thread adjustments under high load.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2.7] - 2026-02-10
|
||||||
|
|
||||||
|
### Added (2.7)
|
||||||
|
- Concurrency improvements and auto-scaling worker enhancements
|
||||||
|
- Cleanup and removal of unused parameters in sorting functions
|
||||||
|
|
||||||
|
### Changed (2.7)
|
||||||
|
- Additional logging for file scanning operations
|
||||||
|
|
||||||
|
### Fixed (2.7)
|
||||||
|
- Minor stability issues related to ISO container mounting
|
||||||
|
- Fixed dual stack for upload (IPv4/IPv6)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2.6-Stable] - 2025-12-01
|
||||||
|
|
||||||
|
### Added (2.6)
|
||||||
|
- Deduplication support (removes duplicate files).
|
||||||
|
- ISO Container management.
|
||||||
|
- Dynamic worker scaling based on CPU & memory.
|
||||||
|
- PreCaching feature for faster file access.
|
||||||
|
|
||||||
|
### Changed (2.6)
|
||||||
|
- Worker pool scaling strategies for better performance.
|
||||||
|
- Enhanced logging with rotating logs using lumberjack.
|
||||||
|
|
||||||
|
### Fixed (2.6)
|
||||||
|
- Temporary file handling issues causing "Unsupported file type" warnings.
|
||||||
|
- MIME type checks for file extension mismatches.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2.5] - 2025-09-15
|
||||||
|
|
||||||
|
### Added (2.5)
|
||||||
|
- Redis caching integration for file metadata.
|
||||||
|
- ClamAV scanning for virus detection before finalizing uploads.
|
||||||
|
|
||||||
|
### Changed (2.5)
|
||||||
|
- Extended the default chunk size for chunked uploads.
|
||||||
|
- Updated official documentation links.
|
||||||
|
|
||||||
|
### Fixed (2.5)
|
||||||
|
- Edge case with versioning causing file rename conflicts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2.0] - 2025-06-01
|
||||||
|
|
||||||
|
### Added (2.0)
|
||||||
|
- Chunked file uploads and downloads.
|
||||||
|
- Resumable upload support with partial file retention.
|
||||||
|
|
||||||
|
### Changed (2.0)
|
||||||
|
- Moved configuration management to Viper.
|
||||||
|
- Default Prometheus metrics for tracking memory & CPU usage.
|
||||||
|
|
||||||
|
### Fixed (2.0)
|
||||||
|
- Race conditions in file locking under heavy concurrency.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.0] - 2025-01-01
|
||||||
|
|
||||||
|
### Added (1.0)
|
||||||
|
- Initial release with HMAC-based authentication.
|
||||||
|
- Basic file upload/download endpoints.
|
||||||
|
- Logging and fundamental configuration using .toml files.
|
302
INSTALL.MD
Normal file
302
INSTALL.MD
Normal 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.
|
0
MONITOR_OPTIMIZATION_SUMMARY.md
Normal file
0
MONITOR_OPTIMIZATION_SUMMARY.md
Normal file
295
PROTOCOL_SPECIFICATIONS.MD
Normal file
295
PROTOCOL_SPECIFICATIONS.MD
Normal 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.
|
592
README.MD
592
README.MD
@ -1,216 +1,454 @@
|
|||||||
# HMAC File Server Release Notes
|
# HMAC File Server 3.2
|
||||||
|
|
||||||
**HMAC File Server** is a secure, scalable, and feature-rich file server with advanced capabilities like HMAC authentication, resumable uploads, chunked uploads, file versioning, and optional ClamAV scanning for file integrity and security. This server is built with extensibility and operational monitoring in mind, including Prometheus metrics support and Redis integration.
|
## Overview
|
||||||
|
The **HMAC File Server** ensures secure file uploads and downloads using HMAC authentication and JWT tokens. It incorporates comprehensive security features, file versioning, deduplication, ISO container support, virus scanning, and Unix socket support for enhanced flexibility. Redis integration provides efficient caching and session management. Prometheus metrics and graceful shutdown mechanisms ensure reliable and efficient file handling.
|
||||||
|
|
||||||
|
Special thanks to **Thomas Leister** for inspiration drawn from [prosody-filer](https://github.com/ThomasLeister/prosody-filer).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
- **Multiple Authentication Methods**: HMAC-based authentication and JWT token support
|
||||||
|
- **Multiple Protocol Support**: v1 (legacy), v2, v3 (mod_http_upload_external), and token-based uploads
|
||||||
|
- **File Management**: Deduplication, configurable TTL for automatic file cleanup
|
||||||
|
- **Upload Methods**: POST multipart uploads, PUT uploads for legacy protocols, v3 protocol support
|
||||||
|
- **Security**: Virus scanning via ClamAV, configurable file extensions validation
|
||||||
|
- **Performance**: Chunked uploads and downloads, worker pool management with auto-scaling
|
||||||
|
- **Storage Options**: Local storage, ISO container mounting for specialized needs
|
||||||
|
- **Monitoring**: Prometheus metrics integration with detailed system and operation metrics
|
||||||
|
- **Network Support**: IPv4/IPv6 dual-stack support with protocol forcing options
|
||||||
|
- **Configuration**: Hot-reloading of logging settings via SIGHUP signal
|
||||||
|
|
||||||
- **HMAC Authentication:** Secure file uploads and downloads with HMAC tokens.
|
## Table of Contents
|
||||||
- **File Versioning:** Enable versioning for uploaded files with configurable retention.
|
1. [Installation](#installation)
|
||||||
- **Chunked and Resumable Uploads:** Handle large files efficiently with support for resumable and chunked uploads.
|
2. [Configuration](#configuration)
|
||||||
- **ClamAV Scanning:** Optional virus scanning for uploaded files.
|
3. [Authentication](#authentication)
|
||||||
- **Prometheus Metrics:** Monitor system and application-level metrics.
|
4. [API Endpoints](#api-endpoints)
|
||||||
- **Redis Integration:** Use Redis for caching or storing application states.
|
5. [Usage Examples](#usage-examples)
|
||||||
- **File Expiration:** Automatically delete files after a specified TTL.
|
6. [Setup](#setup)
|
||||||
- **Graceful Shutdown:** Handles signals and ensures proper cleanup.
|
- [Reverse Proxy](#reverse-proxy)
|
||||||
- **Deduplication:** Remove duplicate files based on hashing for storage efficiency.
|
- [Systemd Service](#systemd-service)
|
||||||
|
7. [Building](#building)
|
||||||
|
8. [Docker Support](#docker-support)
|
||||||
|
9. [Changelog](#changelog)
|
||||||
|
10. [License](#license)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Prerequisites
|
### Quick Installation for XMPP Operators
|
||||||
|
|
||||||
- Go 1.20+
|
The easiest way to install HMAC File Server is using the automated installer:
|
||||||
- Redis (optional, if Redis integration is enabled)
|
|
||||||
- ClamAV (optional, if file scanning is enabled)
|
|
||||||
|
|
||||||
### Clone and Build
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/your-repo/hmac-file-server.git
|
git clone https://github.com/PlusOne/hmac-file-server.git
|
||||||
cd hmac-file-server
|
cd hmac-file-server
|
||||||
go build -o hmac-file-server main.go
|
sudo ./installer.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The installer will:
|
||||||
|
- Install Go 1.24 (if needed)
|
||||||
|
- Create system user and directories
|
||||||
|
- Build and configure the server
|
||||||
|
- Set up systemd service
|
||||||
|
- Optionally install Redis and ClamAV
|
||||||
|
|
||||||
|
For detailed installation instructions, see [INSTALL.MD](INSTALL.MD).
|
||||||
|
|
||||||
|
### Manual Installation
|
||||||
|
|
||||||
|
> **Tip:** You can also run HMAC File Server using Docker Compose for easy deployment. See the Wiki for Docker setup instructions. The official image is available at `ghcr.io/plusone/hmac-file-server:latest`.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
- Go **1.24** or higher
|
||||||
|
- Redis server (optional, for caching)
|
||||||
|
- ClamAV (optional, for virus scanning)
|
||||||
|
|
||||||
|
#### Steps
|
||||||
|
1. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/PlusOne/hmac-file-server.git
|
||||||
|
cd hmac-file-server
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Build the server:
|
||||||
|
```bash
|
||||||
|
go build -o hmac-file-server ./cmd/server/main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Generate example configuration:
|
||||||
|
```bash
|
||||||
|
./hmac-file-server -genconfig
|
||||||
|
# or write to file:
|
||||||
|
./hmac-file-server -genconfig-path config.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Create necessary directories:
|
||||||
|
```bash
|
||||||
|
mkdir -p /path/to/hmac-file-server/data/
|
||||||
|
mkdir -p /path/to/hmac-file-server/deduplication/
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Edit your `config.toml` file with appropriate settings.
|
||||||
|
|
||||||
|
6. Start the server:
|
||||||
|
```bash
|
||||||
|
./hmac-file-server -config config.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Uninstallation
|
||||||
|
|
||||||
|
The installer script provides comprehensive uninstallation options with data preservation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./installer.sh --uninstall
|
||||||
|
```
|
||||||
|
|
||||||
|
### Uninstall Options
|
||||||
|
|
||||||
|
The uninstaller offers five data handling options:
|
||||||
|
|
||||||
|
1. **🗑️ Delete all data** - Complete removal (requires typing "DELETE" to confirm)
|
||||||
|
2. **💾 Preserve uploads and deduplication data** - Keeps important files, removes logs
|
||||||
|
3. **📋 Preserve all data** - Keeps uploads, deduplication data, and logs
|
||||||
|
4. **🎯 Custom selection** - Choose exactly what to preserve
|
||||||
|
5. **❌ Cancel** - Exit without making changes
|
||||||
|
|
||||||
|
### Data Preservation
|
||||||
|
|
||||||
|
When preserving data, the uninstaller:
|
||||||
|
- Creates timestamped backups in `/var/backups/hmac-file-server-YYYYMMDD-HHMMSS/`
|
||||||
|
- Shows file counts and sizes before deletion decisions
|
||||||
|
- Safely moves data to backup locations
|
||||||
|
- Provides clear feedback on what was preserved or removed
|
||||||
|
|
||||||
|
### What Gets Removed
|
||||||
|
|
||||||
|
The uninstaller always removes:
|
||||||
|
- ✓ Systemd service and service file
|
||||||
|
- ✓ Installation directory (`/opt/hmac-file-server`)
|
||||||
|
- ✓ Configuration directory (`/etc/hmac-file-server`)
|
||||||
|
- ✓ System user (`hmac-server`)
|
||||||
|
- ✓ Binary files in common locations
|
||||||
|
|
||||||
|
Data directories are handled according to your selection.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The server configuration is managed through a `config.toml` file. Below are the supported configuration options:
|
The server uses a comprehensive `config.toml` file with the following main sections:
|
||||||
|
|
||||||
### **Server Configuration**
|
### Key Configuration Sections
|
||||||
|
|
||||||
| Key | Description | Example |
|
- **[server]**: Basic server settings (port, storage, metrics)
|
||||||
|------------------------|-----------------------------------------------------|---------------------------------|
|
- **[security]**: HMAC secrets, JWT configuration
|
||||||
| `ListenPort` | Port or Unix socket to listen on | `":8080"` |
|
- **[uploads/downloads]**: File handling settings, allowed extensions
|
||||||
| `UnixSocket` | Use a Unix socket (`true`/`false`) | `false` |
|
- **[logging]**: Log levels, file rotation settings
|
||||||
| `Secret` | Secret key for HMAC authentication | `"your-secret-key"` |
|
- **[clamav]**: Virus scanning configuration
|
||||||
| `StoragePath` | Directory to store uploaded files | `"/mnt/storage/hmac-file-server"` |
|
- **[redis]**: Cache and session management
|
||||||
| `LogLevel` | Logging level (`info`, `debug`, etc.) | `"info"` |
|
- **[workers]**: Thread pool and performance tuning
|
||||||
| `LogFile` | Log file path (optional) | `"/var/log/hmac-file-server.log"` |
|
- **[iso]**: ISO container mounting (specialized storage)
|
||||||
| `MetricsEnabled` | Enable Prometheus metrics (`true`/`false`) | `true` |
|
- **[timeouts]**: HTTP timeout configurations
|
||||||
| `MetricsPort` | Prometheus metrics server port | `"9090"` |
|
|
||||||
| `FileTTL` | File Time-to-Live duration | `"168h0m0s"` |
|
|
||||||
| `DeduplicationEnabled` | Enable file deduplication based on hashing | `true` |
|
|
||||||
| `MinFreeBytes` | Minimum free space required on storage path (in bytes) | `104857600` |
|
|
||||||
|
|
||||||
### **Uploads**
|
### Example Configuration
|
||||||
|
|
||||||
| Key | Description | Example |
|
|
||||||
|----------------------------|-----------------------------------------------|-------------|
|
|
||||||
| `ResumableUploadsEnabled` | Enable resumable uploads | `true` |
|
|
||||||
| `ChunkedUploadsEnabled` | Enable chunked uploads | `true` |
|
|
||||||
| `ChunkSize` | Chunk size for chunked uploads (bytes) | `1048576` |
|
|
||||||
| `AllowedExtensions` | Allowed file extensions for uploads | `[".png", ".jpg"]` |
|
|
||||||
|
|
||||||
### **Time Settings**
|
|
||||||
|
|
||||||
| Key | Description | Example |
|
|
||||||
|------------------|--------------------------------|----------|
|
|
||||||
| `ReadTimeout` | HTTP server read timeout | `"2h"` |
|
|
||||||
| `WriteTimeout` | HTTP server write timeout | `"2h"` |
|
|
||||||
| `IdleTimeout` | HTTP server idle timeout | `"2h"` |
|
|
||||||
|
|
||||||
### **ClamAV Configuration**
|
|
||||||
|
|
||||||
| Key | Description | Example |
|
|
||||||
|--------------------|-------------------------------------------|----------------------------------|
|
|
||||||
| `ClamAVEnabled` | Enable ClamAV virus scanning (`true`) | `true` |
|
|
||||||
| `ClamAVSocket` | Path to ClamAV Unix socket | `"/var/run/clamav/clamd.ctl"` |
|
|
||||||
| `NumScanWorkers` | Number of workers for file scanning | `2` |
|
|
||||||
|
|
||||||
### **Redis Configuration**
|
|
||||||
|
|
||||||
| Key | Description | Example |
|
|
||||||
|----------------------------|----------------------------------|-------------------|
|
|
||||||
| `RedisEnabled` | Enable Redis integration | `true` |
|
|
||||||
| `RedisDBIndex` | Redis database index | `0` |
|
|
||||||
| `RedisAddr` | Redis server address | `"localhost:6379"`|
|
|
||||||
| `RedisPassword` | Password for Redis authentication| `""` |
|
|
||||||
| `RedisHealthCheckInterval` | Health check interval for Redis | `"30s"` |
|
|
||||||
|
|
||||||
### **Workers and Connections**
|
|
||||||
|
|
||||||
| Key | Description | Example |
|
|
||||||
|------------------------|------------------------------------|-------------------|
|
|
||||||
| `NumWorkers` | Number of upload workers | `2` |
|
|
||||||
| `UploadQueueSize` | Size of the upload queue | `50` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Running the Server
|
|
||||||
|
|
||||||
### Basic Usage
|
|
||||||
|
|
||||||
Run the server with a configuration file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./hmac-file-server -config ./config.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
### Metrics Server
|
|
||||||
|
|
||||||
If `MetricsEnabled` is `true`, the Prometheus metrics server will run on the port specified in `MetricsPort` (default: `9090`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Development Notes
|
|
||||||
|
|
||||||
- **Versioning:** Enabled via `EnableVersioning`. Ensure `MaxVersions` is set appropriately to prevent storage issues.
|
|
||||||
- **File Cleaner:** The file cleaner runs hourly and deletes files older than the configured `FileTTL`.
|
|
||||||
- **Redis Health Check:** Automatically monitors Redis connectivity and logs warnings on failure.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
To run the server locally for development:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go run main.go -config ./config.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
Use tools like **cURL** or **Postman** to test file uploads and downloads.
|
|
||||||
|
|
||||||
### Example File Upload with HMAC Token
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X PUT -H "Authorization: Bearer <HMAC-TOKEN>" -F "file=@example.txt" http://localhost:8080/uploads/example.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace `<HMAC-TOKEN>` with a valid HMAC signature generated using the configured `Secret`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Monitoring
|
|
||||||
|
|
||||||
Prometheus metrics include:
|
|
||||||
- File upload/download durations
|
|
||||||
- Memory usage
|
|
||||||
- CPU usage
|
|
||||||
- Active connections
|
|
||||||
- HTTP requests metrics (total, method, path)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Example `config.toml`
|
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[server]
|
[server]
|
||||||
|
bind_ip = "0.0.0.0"
|
||||||
listenport = "8080"
|
listenport = "8080"
|
||||||
unixsocket = false
|
unixsocket = false
|
||||||
storagepath = "/mnt/storage/"
|
storagepath = "./uploads"
|
||||||
loglevel = "info"
|
|
||||||
logfile = "/var/log/file-server.log"
|
|
||||||
metricsenabled = true
|
metricsenabled = true
|
||||||
metricsport = "9090"
|
metricsport = "9090"
|
||||||
DeduplicationEnabled = true
|
deduplicationenabled = true
|
||||||
filettl = "336h" # 14 days
|
filenaming = "HMAC" # Options: "HMAC", "original", "None"
|
||||||
minfreebytes = 104857600 # 100 MB in bytes
|
forceprotocol = "auto" # Options: "ipv4", "ipv6", "auto"
|
||||||
|
|
||||||
[timeouts]
|
|
||||||
readtimeout = "4800s"
|
|
||||||
writetimeout = "4800s"
|
|
||||||
idletimeout = "24h"
|
|
||||||
|
|
||||||
[security]
|
[security]
|
||||||
secret = "example-secret-key"
|
secret = "your-secure-hmac-secret"
|
||||||
|
enablejwt = false
|
||||||
[versioning]
|
jwtsecret = "your-jwt-secret"
|
||||||
enableversioning = false
|
jwtalgorithm = "HS256"
|
||||||
maxversions = 1
|
jwtexpiration = "24h"
|
||||||
|
|
||||||
[uploads]
|
[uploads]
|
||||||
resumableuploadsenabled = true
|
allowedextensions = [".txt", ".pdf", ".jpg", ".png", ".zip"]
|
||||||
chunkeduploadsenabled = true
|
chunkeduploadsenabled = true
|
||||||
chunksize = 8192
|
chunksize = "10MB"
|
||||||
allowedextensions = [".txt", ".pdf", ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".svg", ".webp", ".wav", ".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".mpeg", ".mpg", ".m4v", ".3gp", ".3g2", ".mp3", ".ogg"]
|
|
||||||
|
|
||||||
[clamav]
|
|
||||||
clamavenabled = true
|
|
||||||
clamavsocket = "/var/run/clamav/clamd.ctl"
|
|
||||||
numscanworkers = 2
|
|
||||||
|
|
||||||
[redis]
|
|
||||||
redisenabled = true
|
|
||||||
redisdbindex = 0
|
|
||||||
redisaddr = "localhost:6379"
|
|
||||||
redispassword = ""
|
|
||||||
redishealthcheckinterval = "120s"
|
|
||||||
|
|
||||||
[workers]
|
|
||||||
numworkers = 2
|
|
||||||
uploadqueuesize = 50
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This configuration file is set up with essential features like Prometheus integration, ClamAV scanning, and file handling with deduplication and versioning options. Adjust the settings according to your infrastructure needs.
|
For complete configuration details, see the [Wiki](./WIKI.MD).
|
||||||
|
|
||||||
### Additional Features
|
---
|
||||||
|
|
||||||
- **Deduplication**: Automatically remove duplicate files based on hashing.
|
## Authentication
|
||||||
- **Versioning**: Store multiple versions of files and keep a maximum of `MaxVersions` versions.
|
|
||||||
- **ClamAV Integration**: Scan uploaded files for viruses using ClamAV.
|
|
||||||
- **Redis Caching**: Utilize Redis for caching file metadata for faster access.
|
|
||||||
|
|
||||||
This release ensures an efficient and secure file management system, suited for environments requiring high levels of data security and availability.
|
The server supports multiple authentication methods:
|
||||||
```
|
|
||||||
|
### 1. HMAC Authentication (Default)
|
||||||
|
- **Legacy v1**: Basic HMAC with path + content length
|
||||||
|
- **v2**: Enhanced HMAC with content type validation
|
||||||
|
- **Token**: Alternative HMAC parameter name
|
||||||
|
- **v3**: mod_http_upload_external compatible with expiration
|
||||||
|
|
||||||
|
### 2. JWT Authentication
|
||||||
|
When `enablejwt = true`:
|
||||||
|
- Bearer tokens in Authorization header
|
||||||
|
- Query parameter `token` as fallback
|
||||||
|
- Configurable expiration and algorithm
|
||||||
|
|
||||||
|
### Authentication Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# HMAC v2 upload
|
||||||
|
curl -X PUT "http://localhost:8080/myfile.txt?v2=HMAC_SIGNATURE" -d @file.txt
|
||||||
|
|
||||||
|
# JWT upload
|
||||||
|
curl -X POST -H "Authorization: Bearer JWT_TOKEN" -F 'file=@myfile.txt' http://localhost:8080/upload
|
||||||
|
|
||||||
|
# v3 protocol (mod_http_upload_external)
|
||||||
|
curl -X PUT "http://localhost:8080/upload/file.txt?v3=SIGNATURE&expires=TIMESTAMP" -d @file.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### Upload Endpoints
|
||||||
|
- **POST /upload**: Multipart form uploads (modern clients)
|
||||||
|
- **PUT /{filename}**: Direct uploads with HMAC or JWT authentication
|
||||||
|
- **PUT with v3 protocol**: mod_http_upload_external compatible uploads
|
||||||
|
|
||||||
|
### Download Endpoints
|
||||||
|
- **GET /{filename}**: Direct file downloads
|
||||||
|
- **HEAD /{filename}**: File metadata (size, type)
|
||||||
|
- **GET /download/{filename}**: Alternative download endpoint
|
||||||
|
|
||||||
|
### Management Endpoints
|
||||||
|
- **GET /health**: Health check endpoint for monitoring
|
||||||
|
- **GET /metrics**: Prometheus metrics (if enabled)
|
||||||
|
- **Various helper endpoints**: Defined in router setup
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Upload Examples
|
||||||
|
|
||||||
|
#### Multipart POST Upload
|
||||||
|
```bash
|
||||||
|
curl -X POST -F 'file=@example.jpg' \
|
||||||
|
-H "X-Signature: HMAC_OF_PATH" \
|
||||||
|
http://localhost:8080/upload
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Legacy PUT Upload (v2)
|
||||||
|
```bash
|
||||||
|
# Calculate HMAC of: filename + "\x00" + content_length + "\x00" + content_type
|
||||||
|
curl -X PUT "http://localhost:8080/example.jpg?v2=CALCULATED_HMAC" \
|
||||||
|
--data-binary @example.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
#### v3 Protocol Upload (mod_http_upload_external)
|
||||||
|
```bash
|
||||||
|
# HMAC of: "PUT\n{timestamp}\n/path/to/file"
|
||||||
|
curl -X PUT "http://localhost:8080/upload/file.txt?v3=SIGNATURE&expires=1234567890" \
|
||||||
|
--data-binary @file.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
#### JWT Upload
|
||||||
|
```bash
|
||||||
|
curl -X POST \
|
||||||
|
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||||||
|
-F 'file=@example.jpg' \
|
||||||
|
http://localhost:8080/upload
|
||||||
|
```
|
||||||
|
|
||||||
|
### Download Examples
|
||||||
|
|
||||||
|
#### Direct Download
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8080/example.jpg -o downloaded_file.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Get File Info
|
||||||
|
```bash
|
||||||
|
curl -I http://localhost:8080/example.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Health Check
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8080/health
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### Reverse Proxy
|
||||||
|
|
||||||
|
#### Nginx Configuration
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
client_max_body_size 10G; # Important for large uploads
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:8080;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# Timeout settings for large uploads
|
||||||
|
proxy_read_timeout 300;
|
||||||
|
proxy_connect_timeout 60;
|
||||||
|
proxy_send_timeout 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Apache2 Configuration
|
||||||
|
```apache
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName your-domain.com
|
||||||
|
|
||||||
|
ProxyPreserveHost On
|
||||||
|
ProxyPass / http://localhost:8080/
|
||||||
|
ProxyPassReverse / http://localhost:8080/
|
||||||
|
|
||||||
|
# Large upload support
|
||||||
|
LimitRequestBody 10737418240 # 10GB
|
||||||
|
ProxyTimeout 300
|
||||||
|
</VirtualHost>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Systemd Service
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=HMAC File Server
|
||||||
|
After=network.target redis.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/path/to/hmac-file-server -config /path/to/config.toml
|
||||||
|
ExecReload=/bin/kill -SIGHUP $MAINPID
|
||||||
|
WorkingDirectory=/path/to/hmac-file-server
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
User=hmac-server
|
||||||
|
Group=hmac-server
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ReadWritePaths=/path/to/uploads /path/to/logs
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable and start:
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable hmac-file-server
|
||||||
|
sudo systemctl start hmac-file-server
|
||||||
|
|
||||||
|
# Reload configuration (logging settings)
|
||||||
|
sudo systemctl reload hmac-file-server
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
### Local Build
|
||||||
|
```bash
|
||||||
|
go build -o hmac-file-server ./cmd/server/main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cross-Platform Builds
|
||||||
|
```bash
|
||||||
|
# Linux amd64
|
||||||
|
GOOS=linux GOARCH=amd64 go build -o hmac-file-server-linux-amd64 ./cmd/server/main.go
|
||||||
|
|
||||||
|
# Linux arm64
|
||||||
|
GOOS=linux GOARCH=arm64 go build -o hmac-file-server-linux-arm64 ./cmd/server/main.go
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
GOOS=windows GOARCH=amd64 go build -o hmac-file-server-windows-amd64.exe ./cmd/server/main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Docker Support
|
||||||
|
|
||||||
|
### Quick Start with Docker Compose
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
hmac-file-server:
|
||||||
|
image: ghcr.io/plusone/hmac-file-server:latest
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
- "9090:9090" # Metrics
|
||||||
|
volumes:
|
||||||
|
- ./config:/etc/hmac-file-server
|
||||||
|
- ./uploads:/opt/hmac-file-server/data/uploads
|
||||||
|
environment:
|
||||||
|
- CONFIG_PATH=/etc/hmac-file-server/config.toml
|
||||||
|
restart: unless-stopped
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Build
|
||||||
|
```bash
|
||||||
|
docker build -t hmac-file-server .
|
||||||
|
docker run -p 8080:8080 -v $(pwd)/config.toml:/etc/hmac-file-server/config.toml hmac-file-server
|
||||||
|
```
|
||||||
|
|
||||||
|
See the Wiki for detailed Docker setup instructions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### Version 3.2 (Stable)
|
||||||
|
- **Development Version**: Active development branch with latest features
|
||||||
|
- **Enhanced Documentation**: Updated comprehensive documentation and protocol specifications
|
||||||
|
- **Configuration Improvements**: Better configuration validation and structure
|
||||||
|
- **Authentication System**: Full JWT and multi-protocol HMAC support
|
||||||
|
|
||||||
|
### Version 3.1-Stable (2025-06-08)
|
||||||
|
- **v3 Protocol Support**: Added mod_http_upload_external compatibility
|
||||||
|
- **Enhanced Authentication**: Improved HMAC validation with multiple protocol support
|
||||||
|
- **JWT Integration**: Complete JWT authentication system
|
||||||
|
- **File Naming Options**: HMAC-based or original filename preservation
|
||||||
|
- **Network Improvements**: IPv4/IPv6 dual-stack with protocol forcing
|
||||||
|
|
||||||
|
### Version 3.0-Stable (2025-06-07)
|
||||||
|
- **Docker Support**: Official Docker images and compose files
|
||||||
|
- **Go 1.24 Requirement**: Updated minimum Go version
|
||||||
|
- **Configuration Improvements**: Better validation and hot-reloading
|
||||||
|
- **Performance Enhancements**: Worker auto-scaling and memory optimization
|
||||||
|
|
||||||
|
### Previous Versions
|
||||||
|
See [CHANGELOG.MD](./CHANGELOG.MD) for complete version history.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Alexander Renz
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
0
build-multi-arch-fixed.sh
Normal file
0
build-multi-arch-fixed.sh
Normal file
196
build-multi-arch.sh
Executable file
196
build-multi-arch.sh
Executable 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
407
builddebian.sh
Executable file
@ -0,0 +1,407 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# HMAC File Server v3.2 - Debian Package Builder
|
||||||
|
# Creates .deb packages for AMD64 and ARM64 architectures
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
print_status() {
|
||||||
|
echo -e "${GREEN}[BUILD]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_warning() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Project configuration
|
||||||
|
PROJECT_DIR=$(pwd)
|
||||||
|
BUILD_DIR=$PROJECT_DIR/build
|
||||||
|
DEB_DIR=$PROJECT_DIR/debian
|
||||||
|
PACKAGE_NAME="hmac-file-server"
|
||||||
|
VERSION="3.2.0"
|
||||||
|
MAINTAINER="Alex Renz <renz@uuxo.net>"
|
||||||
|
|
||||||
|
# Source files for compilation
|
||||||
|
SOURCE_FILES="cmd/server/main.go cmd/server/helpers.go cmd/server/config_validator.go cmd/server/config_test_scenarios.go"
|
||||||
|
|
||||||
|
print_status "Starting Debian package build for HMAC File Server v$VERSION"
|
||||||
|
print_info "Building packages for: AMD64, ARM64"
|
||||||
|
|
||||||
|
# Check if Go is installed
|
||||||
|
if ! command -v go &> /dev/null; then
|
||||||
|
print_error "Go is not installed or not in PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if dpkg-deb is available
|
||||||
|
if ! command -v dpkg-deb &> /dev/null; then
|
||||||
|
print_error "dpkg-deb is not installed. Please install dpkg-dev package"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean and create required directories
|
||||||
|
print_info "Setting up build directories..."
|
||||||
|
rm -rf $BUILD_DIR $DEB_DIR
|
||||||
|
mkdir -p $BUILD_DIR/{amd64,arm64}
|
||||||
|
mkdir -p $DEB_DIR/DEBIAN
|
||||||
|
mkdir -p $DEB_DIR/usr/local/bin
|
||||||
|
mkdir -p $DEB_DIR/etc/hmac-file-server
|
||||||
|
mkdir -p $DEB_DIR/var/lib/hmac-file-server/{uploads,deduplication,runtime}
|
||||||
|
mkdir -p $DEB_DIR/var/log/hmac-file-server
|
||||||
|
mkdir -p $DEB_DIR/usr/share/doc/hmac-file-server
|
||||||
|
mkdir -p $DEB_DIR/lib/systemd/system
|
||||||
|
|
||||||
|
# Compile Go binaries for both architectures
|
||||||
|
print_status "Compiling binaries..."
|
||||||
|
for ARCH in amd64 arm64; do
|
||||||
|
print_info "Building for $ARCH..."
|
||||||
|
|
||||||
|
# Set cross-compilation environment
|
||||||
|
export GOOS=linux
|
||||||
|
export GOARCH=$ARCH
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
|
||||||
|
# Build hmac-file-server
|
||||||
|
if go build -ldflags="-w -s" -o $BUILD_DIR/$ARCH/hmac-file-server $SOURCE_FILES; then
|
||||||
|
SIZE=$(stat -c%s "$BUILD_DIR/$ARCH/hmac-file-server" | awk '{printf "%.1fMB", $1/1024/1024}')
|
||||||
|
print_info " $ARCH binary built successfully ($SIZE)"
|
||||||
|
else
|
||||||
|
print_error "Failed to build $ARCH binary"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Reset environment variables
|
||||||
|
unset GOOS GOARCH CGO_ENABLED
|
||||||
|
|
||||||
|
# Prepare Debian control file template
|
||||||
|
print_info "Creating package metadata..."
|
||||||
|
CONTROL_TEMPLATE=$DEB_DIR/DEBIAN/control.template
|
||||||
|
cat <<EOF > $CONTROL_TEMPLATE
|
||||||
|
Package: $PACKAGE_NAME
|
||||||
|
Version: $VERSION
|
||||||
|
Architecture: ARCH_PLACEHOLDER
|
||||||
|
Maintainer: $MAINTAINER
|
||||||
|
Depends: redis-server, clamav, clamav-daemon
|
||||||
|
Recommends: nginx
|
||||||
|
Section: net
|
||||||
|
Priority: optional
|
||||||
|
Homepage: https://github.com/PlusOne/hmac-file-server
|
||||||
|
Description: HMAC File Server v3.2 - Enterprise XMPP File Sharing
|
||||||
|
A lightweight, secure file server designed for XMPP environments with
|
||||||
|
enterprise-grade features including:
|
||||||
|
.
|
||||||
|
* HMAC-based authentication and JWT support
|
||||||
|
* Redis integration for session management
|
||||||
|
* ClamAV virus scanning for uploaded files
|
||||||
|
* Prometheus metrics for monitoring
|
||||||
|
* Chunked upload/download support
|
||||||
|
* File deduplication capabilities
|
||||||
|
* Comprehensive configuration validation
|
||||||
|
.
|
||||||
|
Perfect for Prosody, Ejabberd, and other XMPP servers requiring
|
||||||
|
secure file sharing capabilities with professional deployment features.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Prepare systemd service file
|
||||||
|
print_info "Creating systemd service configuration..."
|
||||||
|
cat <<EOF > $DEB_DIR/lib/systemd/system/hmac-file-server.service
|
||||||
|
[Unit]
|
||||||
|
Description=HMAC File Server 3.2
|
||||||
|
Documentation=https://github.com/PlusOne/hmac-file-server
|
||||||
|
After=network.target
|
||||||
|
Wants=network-online.target
|
||||||
|
After=redis.service
|
||||||
|
After=clamav-daemon.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=hmac-file-server
|
||||||
|
Group=hmac-file-server
|
||||||
|
ExecStart=/usr/local/bin/hmac-file-server -config /etc/hmac-file-server/config.toml
|
||||||
|
ExecReload=/bin/kill -SIGHUP \$MAINPID
|
||||||
|
WorkingDirectory=/var/lib/hmac-file-server
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
SyslogIdentifier=hmac-file-server
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths=/var/lib/hmac-file-server /var/log/hmac-file-server
|
||||||
|
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
|
|
||||||
|
# Resource limits
|
||||||
|
LimitNOFILE=65536
|
||||||
|
LimitNPROC=4096
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Prepare example configuration file
|
||||||
|
print_info "Creating example configuration..."
|
||||||
|
cat <<EOF > $DEB_DIR/etc/hmac-file-server/config.toml
|
||||||
|
# HMAC File Server v3.2 Configuration
|
||||||
|
# Complete configuration reference: https://github.com/PlusOne/hmac-file-server/blob/main/WIKI.MD
|
||||||
|
|
||||||
|
[server]
|
||||||
|
bind_ip = "127.0.0.1"
|
||||||
|
listenport = "8080"
|
||||||
|
unixsocket = false
|
||||||
|
storagepath = "/var/lib/hmac-file-server/uploads"
|
||||||
|
metricsenabled = true
|
||||||
|
metricsport = "9090"
|
||||||
|
deduplicationenabled = true
|
||||||
|
deduplicationpath = "/var/lib/hmac-file-server/deduplication"
|
||||||
|
filenaming = "HMAC"
|
||||||
|
force_protocol = "auto"
|
||||||
|
sslenabled = false
|
||||||
|
pidfilepath = "/var/lib/hmac-file-server/runtime/hmac-file-server.pid"
|
||||||
|
|
||||||
|
[security]
|
||||||
|
secret = "CHANGE_THIS_SECRET_IN_PRODUCTION_USE_48_CHARS_MIN"
|
||||||
|
enablejwt = false
|
||||||
|
jwtsecret = ""
|
||||||
|
jwtalgorithm = "HS256"
|
||||||
|
jwtexpiration = "24h"
|
||||||
|
|
||||||
|
[uploads]
|
||||||
|
allowedextensions = [".txt", ".pdf", ".jpg", ".jpeg", ".png", ".gif", ".webp", ".zip", ".tar", ".gz", ".7z", ".mp4", ".webm", ".ogg", ".mp3", ".wav", ".flac", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".odt", ".ods", ".odp"]
|
||||||
|
maxfilesize = "100MB"
|
||||||
|
chunkeduploadsenabled = true
|
||||||
|
chunksize = "10MB"
|
||||||
|
resumableuploadsenabled = true
|
||||||
|
ttlenabled = false
|
||||||
|
ttl = "168h"
|
||||||
|
|
||||||
|
[downloads]
|
||||||
|
chunkeddownloadsenabled = true
|
||||||
|
chunksize = "10MB"
|
||||||
|
|
||||||
|
[logging]
|
||||||
|
level = "INFO"
|
||||||
|
file = "/var/log/hmac-file-server/hmac-file-server.log"
|
||||||
|
max_size = 100
|
||||||
|
max_backups = 3
|
||||||
|
max_age = 30
|
||||||
|
compress = true
|
||||||
|
|
||||||
|
[workers]
|
||||||
|
numworkers = 10
|
||||||
|
uploadqueuesize = 1000
|
||||||
|
autoscaling = true
|
||||||
|
|
||||||
|
[timeouts]
|
||||||
|
readtimeout = "30s"
|
||||||
|
writetimeout = "30s"
|
||||||
|
idletimeout = "120s"
|
||||||
|
shutdown = "30s"
|
||||||
|
|
||||||
|
[clamav]
|
||||||
|
enabled = false
|
||||||
|
socket = "/var/run/clamav/clamd.ctl"
|
||||||
|
timeout = "30s"
|
||||||
|
|
||||||
|
[redis]
|
||||||
|
enabled = false
|
||||||
|
address = "localhost:6379"
|
||||||
|
database = 0
|
||||||
|
password = ""
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Prepare post-installation script
|
||||||
|
print_info "Creating installation scripts..."
|
||||||
|
cat <<EOF > $DEB_DIR/DEBIAN/postinst
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Create hmac-file-server user and group if they do not exist
|
||||||
|
if ! id -u hmac-file-server >/dev/null 2>&1; then
|
||||||
|
useradd --system --no-create-home --shell /usr/sbin/nologin --home-dir /var/lib/hmac-file-server hmac-file-server
|
||||||
|
echo "Created system user: hmac-file-server"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set proper ownership and permissions
|
||||||
|
chown -R hmac-file-server:hmac-file-server /var/lib/hmac-file-server
|
||||||
|
chown -R hmac-file-server:hmac-file-server /var/log/hmac-file-server
|
||||||
|
chown hmac-file-server:hmac-file-server /etc/hmac-file-server/config.toml
|
||||||
|
|
||||||
|
# Set directory permissions
|
||||||
|
chmod 755 /var/lib/hmac-file-server
|
||||||
|
chmod 755 /var/lib/hmac-file-server/uploads
|
||||||
|
chmod 755 /var/lib/hmac-file-server/deduplication
|
||||||
|
chmod 755 /var/lib/hmac-file-server/runtime
|
||||||
|
chmod 755 /var/log/hmac-file-server
|
||||||
|
chmod 640 /etc/hmac-file-server/config.toml
|
||||||
|
|
||||||
|
# Reload systemd and enable service
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable hmac-file-server.service
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "HMAC File Server v3.2 installed successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Edit /etc/hmac-file-server/config.toml (CHANGE THE SECRET!)"
|
||||||
|
echo "2. Enable Redis/ClamAV if needed: systemctl enable redis-server clamav-daemon"
|
||||||
|
echo "3. Start the service: systemctl start hmac-file-server"
|
||||||
|
echo "4. Check status: systemctl status hmac-file-server"
|
||||||
|
echo ""
|
||||||
|
echo "Documentation: https://github.com/PlusOne/hmac-file-server"
|
||||||
|
echo ""
|
||||||
|
EOF
|
||||||
|
chmod 0755 $DEB_DIR/DEBIAN/postinst
|
||||||
|
|
||||||
|
# Prepare pre-removal script
|
||||||
|
cat <<EOF > $DEB_DIR/DEBIAN/prerm
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Stop the service before removal
|
||||||
|
if systemctl is-active --quiet hmac-file-server.service; then
|
||||||
|
echo "Stopping HMAC File Server service..."
|
||||||
|
systemctl stop hmac-file-server.service || true
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
chmod 0755 $DEB_DIR/DEBIAN/prerm
|
||||||
|
|
||||||
|
# Prepare post-removal script
|
||||||
|
cat <<EOF > $DEB_DIR/DEBIAN/postrm
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
case "\$1" in
|
||||||
|
purge)
|
||||||
|
# Remove systemd service
|
||||||
|
systemctl disable hmac-file-server.service >/dev/null 2>&1 || true
|
||||||
|
rm -f /lib/systemd/system/hmac-file-server.service
|
||||||
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# Remove user and group
|
||||||
|
if id -u hmac-file-server >/dev/null 2>&1; then
|
||||||
|
userdel hmac-file-server || true
|
||||||
|
fi
|
||||||
|
if getent group hmac-file-server >/dev/null 2>&1; then
|
||||||
|
groupdel hmac-file-server || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove data directories (ask user)
|
||||||
|
echo ""
|
||||||
|
echo "HMAC File Server has been removed."
|
||||||
|
echo "Data directories remain at:"
|
||||||
|
echo " - /var/lib/hmac-file-server/"
|
||||||
|
echo " - /var/log/hmac-file-server/"
|
||||||
|
echo " - /etc/hmac-file-server/"
|
||||||
|
echo ""
|
||||||
|
echo "Remove them manually if no longer needed:"
|
||||||
|
echo " sudo rm -rf /var/lib/hmac-file-server"
|
||||||
|
echo " sudo rm -rf /var/log/hmac-file-server"
|
||||||
|
echo " sudo rm -rf /etc/hmac-file-server"
|
||||||
|
echo ""
|
||||||
|
;;
|
||||||
|
remove)
|
||||||
|
# Just disable service
|
||||||
|
systemctl disable hmac-file-server.service >/dev/null 2>&1 || true
|
||||||
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
EOF
|
||||||
|
chmod 0755 $DEB_DIR/DEBIAN/postrm
|
||||||
|
|
||||||
|
# Prepare documentation
|
||||||
|
print_info "Including documentation..."
|
||||||
|
cp README.MD $DEB_DIR/usr/share/doc/hmac-file-server/
|
||||||
|
cp INSTALL.MD $DEB_DIR/usr/share/doc/hmac-file-server/
|
||||||
|
cp WIKI.MD $DEB_DIR/usr/share/doc/hmac-file-server/
|
||||||
|
cp CHANGELOG.MD $DEB_DIR/usr/share/doc/hmac-file-server/
|
||||||
|
cp config-example-xmpp.toml $DEB_DIR/usr/share/doc/hmac-file-server/
|
||||||
|
|
||||||
|
# Create .deb packages
|
||||||
|
print_status "Building Debian packages..."
|
||||||
|
for ARCH in amd64 arm64; do
|
||||||
|
print_info "Creating package for $ARCH..."
|
||||||
|
|
||||||
|
# Update control file for the current architecture
|
||||||
|
sed "s/ARCH_PLACEHOLDER/$ARCH/" $CONTROL_TEMPLATE > $DEB_DIR/DEBIAN/control
|
||||||
|
|
||||||
|
# Copy binary for current architecture
|
||||||
|
cp $BUILD_DIR/$ARCH/hmac-file-server $DEB_DIR/usr/local/bin/
|
||||||
|
|
||||||
|
# Calculate installed size
|
||||||
|
INSTALLED_SIZE=$(du -sk $DEB_DIR | cut -f1)
|
||||||
|
echo "Installed-Size: $INSTALLED_SIZE" >> $DEB_DIR/DEBIAN/control
|
||||||
|
|
||||||
|
# Ensure proper permissions
|
||||||
|
find $DEB_DIR -type d -exec chmod 755 {} \;
|
||||||
|
find $DEB_DIR -type f -exec chmod 644 {} \;
|
||||||
|
chmod 0755 $DEB_DIR/usr/local/bin/hmac-file-server
|
||||||
|
chmod 0755 $DEB_DIR/DEBIAN/postinst
|
||||||
|
chmod 0755 $DEB_DIR/DEBIAN/prerm
|
||||||
|
chmod 0755 $DEB_DIR/DEBIAN/postrm
|
||||||
|
|
||||||
|
# Build the .deb package
|
||||||
|
PACKAGE_FILE="${PACKAGE_NAME}_${VERSION}_${ARCH}.deb"
|
||||||
|
if dpkg-deb --build $DEB_DIR $PACKAGE_FILE; then
|
||||||
|
SIZE=$(stat -c%s "$PACKAGE_FILE" | awk '{printf "%.1fMB", $1/1024/1024}')
|
||||||
|
print_info " Package created: $PACKAGE_FILE ($SIZE)"
|
||||||
|
else
|
||||||
|
print_error "Failed to create package for $ARCH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up binary for next build
|
||||||
|
rm -f $DEB_DIR/usr/local/bin/hmac-file-server
|
||||||
|
rm -f $DEB_DIR/DEBIAN/control
|
||||||
|
done
|
||||||
|
|
||||||
|
# Cleanup temporary directories
|
||||||
|
print_info "Cleaning up build directories..."
|
||||||
|
rm -rf $BUILD_DIR $DEB_DIR
|
||||||
|
|
||||||
|
# Show results
|
||||||
|
print_status "Debian package build completed!"
|
||||||
|
echo ""
|
||||||
|
print_info "Generated packages:"
|
||||||
|
for PACKAGE in ${PACKAGE_NAME}_${VERSION}_*.deb; do
|
||||||
|
if [[ -f "$PACKAGE" ]]; then
|
||||||
|
SIZE=$(stat -c%s "$PACKAGE" | awk '{printf "%.1fMB", $1/1024/1024}')
|
||||||
|
print_info " $PACKAGE ($SIZE)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
print_info "Installation commands:"
|
||||||
|
echo " sudo dpkg -i ${PACKAGE_NAME}_${VERSION}_amd64.deb"
|
||||||
|
echo " sudo dpkg -i ${PACKAGE_NAME}_${VERSION}_arm64.deb"
|
||||||
|
echo ""
|
||||||
|
print_info "Package information:"
|
||||||
|
echo " dpkg -I ${PACKAGE_NAME}_${VERSION}_amd64.deb"
|
||||||
|
echo " dpkg -c ${PACKAGE_NAME}_${VERSION}_amd64.deb"
|
||||||
|
echo ""
|
||||||
|
print_warning "Remember to:"
|
||||||
|
echo " 1. Edit /etc/hmac-file-server/config.toml"
|
||||||
|
echo " 2. Change the default secret"
|
||||||
|
echo " 3. Configure Redis/ClamAV if needed"
|
||||||
|
echo " 4. Start the service: systemctl start hmac-file-server"
|
||||||
|
|
||||||
|
exit 0
|
15
builddocker.sh
Executable file
15
builddocker.sh
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
IMAGE_NAME="hmac-file-server"
|
||||||
|
DOCKERFILE_PATH="dockerenv/dockerbuild/Dockerfile"
|
||||||
|
COMPOSE_FILE="dockerenv/docker-compose.yml"
|
||||||
|
|
||||||
|
echo "Building Docker image: $IMAGE_NAME"
|
||||||
|
docker build -t "$IMAGE_NAME" -f "$DOCKERFILE_PATH" .
|
||||||
|
|
||||||
|
#echo "Starting services using $COMPOSE_FILE"
|
||||||
|
#docker-compose -f "$COMPOSE_FILE" up -d
|
||||||
|
|
||||||
|
echo "Build and deployment complete."
|
80
buildgo.sh
Executable file
80
buildgo.sh
Executable file
@ -0,0 +1,80 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# HMAC File Server - Build Script
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
print_status() {
|
||||||
|
echo -e "${GREEN}[BUILD]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if Go is installed
|
||||||
|
if ! command -v go &> /dev/null; then
|
||||||
|
print_error "Go is not installed or not in PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
print_status "Building HMAC File Server v3.2..."
|
||||||
|
go build -o hmac-file-server cmd/server/main.go cmd/server/helpers.go cmd/server/config_validator.go cmd/server/config_test_scenarios.go
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
print_status "Build successful! Binary created: ./hmac-file-server"
|
||||||
|
|
||||||
|
# Check binary size
|
||||||
|
SIZE=$(du -h hmac-file-server | cut -f1)
|
||||||
|
print_info "Binary size: $SIZE"
|
||||||
|
|
||||||
|
# Show help to verify it works
|
||||||
|
print_info "Testing binary functionality..."
|
||||||
|
./hmac-file-server --help > /dev/null 2>&1
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
print_status "Binary is functional!"
|
||||||
|
else
|
||||||
|
print_error "Binary test failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_error "Build failed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create test file for manual testing
|
||||||
|
print_info "Creating test file..."
|
||||||
|
echo "Hello, HMAC File Server! $(date)" > test_upload.txt
|
||||||
|
|
||||||
|
# Generate HMAC signature for manual testing
|
||||||
|
print_info "HMAC signature generation for testing:"
|
||||||
|
SECRET="hmac-file-server-is-the-win"
|
||||||
|
MESSAGE="/upload"
|
||||||
|
|
||||||
|
# Check if openssl is available
|
||||||
|
if command -v openssl &> /dev/null; then
|
||||||
|
SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)
|
||||||
|
echo "Secret: $SECRET"
|
||||||
|
echo "Message: $MESSAGE"
|
||||||
|
echo "Signature: $SIGNATURE"
|
||||||
|
echo ""
|
||||||
|
echo "Test with curl (requires server running on localhost:8080):"
|
||||||
|
echo "curl -v -X POST -H \"X-Signature: $SIGNATURE\" -F \"file=@test_upload.txt\" http://localhost:8080/upload"
|
||||||
|
else
|
||||||
|
print_info "OpenSSL not found. You can generate HMAC manually or use the Go tests."
|
||||||
|
echo "To start server: ./hmac-file-server"
|
||||||
|
echo "For testing, check the test/ directory for Go test files."
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_status "Build complete! Ready to run: ./hmac-file-server"
|
1050
cmd/monitor/monitor.go
Normal file
1050
cmd/monitor/monitor.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,67 +0,0 @@
|
|||||||
# Server Settings
|
|
||||||
[server]
|
|
||||||
ListenPort = "8080"
|
|
||||||
UnixSocket = false
|
|
||||||
StoreDir = "./testupload"
|
|
||||||
LogLevel = "info"
|
|
||||||
LogFile = "./hmac-file-server.log"
|
|
||||||
MetricsEnabled = true
|
|
||||||
MetricsPort = "9090"
|
|
||||||
FileTTL = "8760h"
|
|
||||||
|
|
||||||
# Workers and Connections
|
|
||||||
[workers]
|
|
||||||
NumWorkers = 2
|
|
||||||
UploadQueueSize = 500
|
|
||||||
|
|
||||||
# Timeout Settings
|
|
||||||
[timeouts]
|
|
||||||
ReadTimeout = "600s"
|
|
||||||
WriteTimeout = "600s"
|
|
||||||
IdleTimeout = "600s"
|
|
||||||
|
|
||||||
# Security Settings
|
|
||||||
[security]
|
|
||||||
Secret = "a-orc-and-a-humans-is-drinking-ale"
|
|
||||||
|
|
||||||
# Versioning Settings
|
|
||||||
[versioning]
|
|
||||||
EnableVersioning = false
|
|
||||||
MaxVersions = 1
|
|
||||||
|
|
||||||
# Upload/Download Settings
|
|
||||||
[uploads]
|
|
||||||
ResumableUploadsEnabled = true
|
|
||||||
ChunkedUploadsEnabled = true
|
|
||||||
ChunkSize = 16777216
|
|
||||||
AllowedExtensions = [
|
|
||||||
# Document formats
|
|
||||||
".txt", ".pdf",
|
|
||||||
|
|
||||||
# Image formats
|
|
||||||
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".svg", ".webp",
|
|
||||||
|
|
||||||
# Video formats
|
|
||||||
".wav", ".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".mpeg", ".mpg", ".m4v", ".3gp", ".3g2",
|
|
||||||
|
|
||||||
# Audio formats
|
|
||||||
".mp3", ".ogg"
|
|
||||||
]
|
|
||||||
|
|
||||||
# ClamAV Settings
|
|
||||||
[clamav]
|
|
||||||
ClamAVEnabled = false
|
|
||||||
ClamAVSocket = "/var/run/clamav/clamd.ctl"
|
|
||||||
NumScanWorkers = 4
|
|
||||||
|
|
||||||
# Redis Settings
|
|
||||||
[redis]
|
|
||||||
RedisEnabled = false
|
|
||||||
RedisAddr = "localhost:6379"
|
|
||||||
RedisPassword = ""
|
|
||||||
RedisDBIndex = 0
|
|
||||||
RedisHealthCheckInterval = "120s"
|
|
||||||
|
|
||||||
# Deduplication
|
|
||||||
[deduplication]
|
|
||||||
enabled = false
|
|
294
cmd/server/config_test_scenarios.go
Normal file
294
cmd/server/config_test_scenarios.go
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
1131
cmd/server/config_validator.go
Normal file
1131
cmd/server/config_validator.go
Normal file
File diff suppressed because it is too large
Load Diff
713
cmd/server/helpers.go
Normal file
713
cmd/server/helpers.go
Normal 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)
|
||||||
|
}
|
3373
cmd/server/main.go
3373
cmd/server/main.go
File diff suppressed because it is too large
Load Diff
0
config-example-xmpp.toml
Normal file
0
config-example-xmpp.toml
Normal file
@ -27,8 +27,8 @@
|
|||||||
"overrides": []
|
"overrides": []
|
||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 6,
|
"h": 7,
|
||||||
"w": 24,
|
"w": 3,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0
|
"y": 0
|
||||||
},
|
},
|
||||||
@ -39,12 +39,11 @@
|
|||||||
"showLineNumbers": false,
|
"showLineNumbers": false,
|
||||||
"showMiniMap": false
|
"showMiniMap": false
|
||||||
},
|
},
|
||||||
"content": "<div style=\"text-align: center; background-color: #111217; padding: 20px;\">\n <h3 style=\"color: white; font-family: 'Arial', sans-serif; font-weight: bold;\">HMAC Dashboard</h3>\n <img src=\"https://block.uuxo.net/hmac_icon.png\" alt=\"HMAC Icon\" style=\"width: 50px; height: 50px; display: block; margin: 10px auto;\">\n <p style=\"font-family: 'Verdana', sans-serif; color: white;\">\n This dashboard monitors <strong style=\"color: #FF5733;\">key metrics</strong> for the \n <span style=\"font-style: italic; color: #007BFF;\">HMAC File Server</span>.\n </p>\n</div>\n",
|
"content": "<div style=\"text-align: center; background-color: transparent; padding: 20px;\">\n <h3 style=\"color: white; font-family: 'Arial', sans-serif; font-weight: bold;\">HMAC Dashboard</h3>\n <img src=\"https://git.uuxo.net/uuxo/hmac-file-server/raw/branch/main/dashboard/hmac_icon.png\" alt=\"HMAC Icon\" style=\"width: 50px; height: 50px; display: block; margin: 10px auto;\">\n <p style=\"font-family: 'Verdana', sans-serif; color: white;\">\n This dashboard monitors <strong style=\"color: #FF5733;\">key metrics</strong> for the \n <span style=\"font-style: italic; color: #007BFF;\">HMAC File Server</span>.\n </p>\n</div>\n",
|
||||||
"mode": "html"
|
"mode": "html"
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.4.0",
|
||||||
"title": "HMAC Dashboard",
|
"title": "HMAC Dashboard",
|
||||||
"transparent": true,
|
|
||||||
"type": "text"
|
"type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -76,10 +75,10 @@
|
|||||||
"overrides": []
|
"overrides": []
|
||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 5,
|
"h": 7,
|
||||||
"w": 6,
|
"w": 6,
|
||||||
"x": 0,
|
"x": 3,
|
||||||
"y": 6
|
"y": 0
|
||||||
},
|
},
|
||||||
"id": 14,
|
"id": 14,
|
||||||
"options": {
|
"options": {
|
||||||
@ -106,7 +105,7 @@
|
|||||||
"sizing": "auto",
|
"sizing": "auto",
|
||||||
"valueMode": "color"
|
"valueMode": "color"
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.4.0",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
@ -142,10 +141,10 @@
|
|||||||
"overrides": []
|
"overrides": []
|
||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 5,
|
"h": 7,
|
||||||
"w": 6,
|
"w": 6,
|
||||||
"x": 6,
|
"x": 9,
|
||||||
"y": 6
|
"y": 0
|
||||||
},
|
},
|
||||||
"id": 18,
|
"id": 18,
|
||||||
"options": {
|
"options": {
|
||||||
@ -165,7 +164,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.4.0",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
@ -192,6 +191,10 @@
|
|||||||
{
|
{
|
||||||
"color": "green",
|
"color": "green",
|
||||||
"value": null
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -199,10 +202,68 @@
|
|||||||
"overrides": []
|
"overrides": []
|
||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 5,
|
"h": 7,
|
||||||
"w": 6,
|
"w": 5,
|
||||||
"x": 12,
|
"x": 15,
|
||||||
"y": 6
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 10,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "none",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "value",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.4.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "go_threads",
|
||||||
|
"format": "table",
|
||||||
|
"legendFormat": "{{hmac-file-server}}",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "HMAC GO Threads",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"default": true,
|
||||||
|
"type": "prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 4,
|
||||||
|
"x": 20,
|
||||||
|
"y": 0
|
||||||
},
|
},
|
||||||
"id": 17,
|
"id": 17,
|
||||||
"options": {
|
"options": {
|
||||||
@ -222,7 +283,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.4.0",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
@ -262,10 +323,10 @@
|
|||||||
"overrides": []
|
"overrides": []
|
||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 5,
|
"h": 7,
|
||||||
"w": 3,
|
"w": 5,
|
||||||
"x": 18,
|
"x": 0,
|
||||||
"y": 6
|
"y": 7
|
||||||
},
|
},
|
||||||
"id": 11,
|
"id": 11,
|
||||||
"options": {
|
"options": {
|
||||||
@ -285,11 +346,11 @@
|
|||||||
"textMode": "value",
|
"textMode": "value",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.4.0",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
"expr": "hmac_file_server_uploads_total",
|
"expr": "increase(hmac_file_server_uploads_total[1h])",
|
||||||
"format": "table",
|
"format": "table",
|
||||||
"legendFormat": "Uploads",
|
"legendFormat": "Uploads",
|
||||||
"range": true,
|
"range": true,
|
||||||
@ -325,10 +386,10 @@
|
|||||||
"overrides": []
|
"overrides": []
|
||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 5,
|
"h": 7,
|
||||||
"w": 3,
|
"w": 5,
|
||||||
"x": 21,
|
"x": 5,
|
||||||
"y": 6
|
"y": 7
|
||||||
},
|
},
|
||||||
"id": 12,
|
"id": 12,
|
||||||
"options": {
|
"options": {
|
||||||
@ -348,7 +409,7 @@
|
|||||||
"textMode": "value",
|
"textMode": "value",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.4.0",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
@ -390,10 +451,10 @@
|
|||||||
"overrides": []
|
"overrides": []
|
||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 5,
|
"h": 7,
|
||||||
"w": 3,
|
"w": 3,
|
||||||
"x": 0,
|
"x": 10,
|
||||||
"y": 11
|
"y": 7
|
||||||
},
|
},
|
||||||
"id": 15,
|
"id": 15,
|
||||||
"options": {
|
"options": {
|
||||||
@ -413,7 +474,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.4.0",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"datasource": {
|
"datasource": {
|
||||||
@ -457,201 +518,12 @@
|
|||||||
"overrides": []
|
"overrides": []
|
||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 5,
|
"h": 7,
|
||||||
"w": 3,
|
"w": 3,
|
||||||
"x": 3,
|
"x": 13,
|
||||||
"y": 11
|
"y": 7
|
||||||
},
|
},
|
||||||
"id": 10,
|
"id": 13,
|
||||||
"options": {
|
|
||||||
"colorMode": "value",
|
|
||||||
"graphMode": "none",
|
|
||||||
"justifyMode": "auto",
|
|
||||||
"orientation": "auto",
|
|
||||||
"percentChangeColorMode": "standard",
|
|
||||||
"reduceOptions": {
|
|
||||||
"calcs": [
|
|
||||||
"lastNotNull"
|
|
||||||
],
|
|
||||||
"fields": "",
|
|
||||||
"values": false
|
|
||||||
},
|
|
||||||
"showPercentChange": false,
|
|
||||||
"textMode": "value",
|
|
||||||
"wideLayout": true
|
|
||||||
},
|
|
||||||
"pluginVersion": "11.3.1",
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"editorMode": "code",
|
|
||||||
"expr": "go_threads",
|
|
||||||
"format": "table",
|
|
||||||
"legendFormat": "{{hmac-file-server}}",
|
|
||||||
"range": true,
|
|
||||||
"refId": "A"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "HMAC GO Threads",
|
|
||||||
"type": "stat"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"default": true,
|
|
||||||
"type": "prometheus"
|
|
||||||
},
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "red",
|
|
||||||
"value": 80
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
},
|
|
||||||
"gridPos": {
|
|
||||||
"h": 5,
|
|
||||||
"w": 3,
|
|
||||||
"x": 6,
|
|
||||||
"y": 11
|
|
||||||
},
|
|
||||||
"id": 21,
|
|
||||||
"options": {
|
|
||||||
"colorMode": "value",
|
|
||||||
"graphMode": "none",
|
|
||||||
"justifyMode": "auto",
|
|
||||||
"orientation": "auto",
|
|
||||||
"percentChangeColorMode": "standard",
|
|
||||||
"reduceOptions": {
|
|
||||||
"calcs": [
|
|
||||||
"lastNotNull"
|
|
||||||
],
|
|
||||||
"fields": "",
|
|
||||||
"values": false
|
|
||||||
},
|
|
||||||
"showPercentChange": false,
|
|
||||||
"textMode": "value",
|
|
||||||
"wideLayout": true
|
|
||||||
},
|
|
||||||
"pluginVersion": "11.3.1",
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"editorMode": "code",
|
|
||||||
"expr": "hmac_file_deletions_total",
|
|
||||||
"format": "table",
|
|
||||||
"legendFormat": "{{hmac-file-server}}",
|
|
||||||
"range": true,
|
|
||||||
"refId": "A"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "HMAC FileTTL Deletion(s)",
|
|
||||||
"type": "stat"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"default": true,
|
|
||||||
"type": "prometheus"
|
|
||||||
},
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "red",
|
|
||||||
"value": 80
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
},
|
|
||||||
"gridPos": {
|
|
||||||
"h": 5,
|
|
||||||
"w": 3,
|
|
||||||
"x": 9,
|
|
||||||
"y": 11
|
|
||||||
},
|
|
||||||
"id": 20,
|
|
||||||
"options": {
|
|
||||||
"colorMode": "value",
|
|
||||||
"graphMode": "none",
|
|
||||||
"justifyMode": "auto",
|
|
||||||
"orientation": "auto",
|
|
||||||
"percentChangeColorMode": "standard",
|
|
||||||
"reduceOptions": {
|
|
||||||
"calcs": [
|
|
||||||
"lastNotNull"
|
|
||||||
],
|
|
||||||
"fields": "",
|
|
||||||
"values": false
|
|
||||||
},
|
|
||||||
"showPercentChange": false,
|
|
||||||
"textMode": "value",
|
|
||||||
"wideLayout": true
|
|
||||||
},
|
|
||||||
"pluginVersion": "11.3.1",
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"editorMode": "code",
|
|
||||||
"expr": "hmac_cache_misses_total",
|
|
||||||
"format": "table",
|
|
||||||
"legendFormat": "{{hmac-file-server}}",
|
|
||||||
"range": true,
|
|
||||||
"refId": "A"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "HMAC Cache Misses",
|
|
||||||
"type": "stat"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"default": true,
|
|
||||||
"type": "prometheus"
|
|
||||||
},
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "thresholds"
|
|
||||||
},
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "red",
|
|
||||||
"value": 80
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
},
|
|
||||||
"gridPos": {
|
|
||||||
"h": 5,
|
|
||||||
"w": 3,
|
|
||||||
"x": 12,
|
|
||||||
"y": 11
|
|
||||||
},
|
|
||||||
"id": 16,
|
|
||||||
"options": {
|
"options": {
|
||||||
"colorMode": "value",
|
"colorMode": "value",
|
||||||
"graphMode": "area",
|
"graphMode": "area",
|
||||||
@ -669,81 +541,17 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.4.0",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
"exemplar": false,
|
"expr": "hmac_file_server_download_errors_total",
|
||||||
"expr": "hmac_active_connections_total",
|
"legendFormat": "Download Errors",
|
||||||
"format": "table",
|
|
||||||
"instant": false,
|
|
||||||
"legendFormat": "__auto",
|
|
||||||
"range": true,
|
"range": true,
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "HMAC Active Connections",
|
"title": "HMAC Download Errors",
|
||||||
"type": "stat"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"default": true,
|
|
||||||
"type": "prometheus"
|
|
||||||
},
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "thresholds"
|
|
||||||
},
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
},
|
|
||||||
"gridPos": {
|
|
||||||
"h": 5,
|
|
||||||
"w": 3,
|
|
||||||
"x": 15,
|
|
||||||
"y": 11
|
|
||||||
},
|
|
||||||
"id": 19,
|
|
||||||
"options": {
|
|
||||||
"colorMode": "value",
|
|
||||||
"graphMode": "area",
|
|
||||||
"justifyMode": "auto",
|
|
||||||
"orientation": "auto",
|
|
||||||
"percentChangeColorMode": "standard",
|
|
||||||
"reduceOptions": {
|
|
||||||
"calcs": [
|
|
||||||
"lastNotNull"
|
|
||||||
],
|
|
||||||
"fields": "",
|
|
||||||
"values": false
|
|
||||||
},
|
|
||||||
"showPercentChange": false,
|
|
||||||
"textMode": "auto",
|
|
||||||
"wideLayout": true
|
|
||||||
},
|
|
||||||
"pluginVersion": "11.3.1",
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"editorMode": "code",
|
|
||||||
"expr": "hmac_infected_files_total",
|
|
||||||
"format": "table",
|
|
||||||
"legendFormat": "__auto",
|
|
||||||
"range": true,
|
|
||||||
"refId": "A"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "HMAC infected file(s)",
|
|
||||||
"type": "stat"
|
"type": "stat"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -771,10 +579,10 @@
|
|||||||
"overrides": []
|
"overrides": []
|
||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 5,
|
"h": 7,
|
||||||
"w": 3,
|
"w": 3,
|
||||||
"x": 18,
|
"x": 16,
|
||||||
"y": 11
|
"y": 7
|
||||||
},
|
},
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"options": {
|
"options": {
|
||||||
@ -794,7 +602,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.4.0",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
@ -832,12 +640,73 @@
|
|||||||
"overrides": []
|
"overrides": []
|
||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 5,
|
"h": 7,
|
||||||
"w": 3,
|
"w": 5,
|
||||||
"x": 21,
|
"x": 19,
|
||||||
"y": 11
|
"y": 7
|
||||||
},
|
},
|
||||||
"id": 13,
|
"id": 21,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "none",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "value",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.4.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "hm",
|
||||||
|
"format": "table",
|
||||||
|
"legendFormat": "__auto",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "HMAC FileTTL Deletion(s)",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"default": true,
|
||||||
|
"type": "prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 3,
|
||||||
|
"x": 0,
|
||||||
|
"y": 14
|
||||||
|
},
|
||||||
|
"id": 19,
|
||||||
"options": {
|
"options": {
|
||||||
"colorMode": "value",
|
"colorMode": "value",
|
||||||
"graphMode": "area",
|
"graphMode": "area",
|
||||||
@ -855,18 +724,254 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "11.3.1",
|
"pluginVersion": "11.4.0",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"editorMode": "code",
|
"editorMode": "code",
|
||||||
"expr": "hmac_file_server_download_errors_total",
|
"expr": "hmac_infected_files_total",
|
||||||
"legendFormat": "Download Errors",
|
"format": "table",
|
||||||
|
"legendFormat": "__auto",
|
||||||
"range": true,
|
"range": true,
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "HMAC Download Errors",
|
"title": "HMAC infected file(s)",
|
||||||
"type": "stat"
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"default": true,
|
||||||
|
"type": "prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"fillOpacity": 80,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"lineWidth": 1,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fieldMinMax": false,
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "files"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 7,
|
||||||
|
"x": 3,
|
||||||
|
"y": 14
|
||||||
|
},
|
||||||
|
"id": 22,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.4.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"editorMode": "code",
|
||||||
|
"exemplar": false,
|
||||||
|
"expr": "increase(hmac_file_server_clamav_scans_total[24h])",
|
||||||
|
"format": "time_series",
|
||||||
|
"instant": true,
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "__auto",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "HMAC ClamAV San (24h)",
|
||||||
|
"type": "histogram"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"default": true,
|
||||||
|
"type": "prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"fillOpacity": 80,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"lineWidth": 1,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fieldMinMax": false,
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "files"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 7,
|
||||||
|
"x": 10,
|
||||||
|
"y": 14
|
||||||
|
},
|
||||||
|
"id": 23,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.4.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"editorMode": "code",
|
||||||
|
"exemplar": false,
|
||||||
|
"expr": "increase(hmac_file_server_clamav_errors_total[24h])",
|
||||||
|
"format": "time_series",
|
||||||
|
"instant": true,
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "__auto",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "HMAC ClamAV SanError(s) (24h)",
|
||||||
|
"type": "histogram"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"default": true,
|
||||||
|
"type": "prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"fillOpacity": 80,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"lineWidth": 1,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 7,
|
||||||
|
"x": 17,
|
||||||
|
"y": 14
|
||||||
|
},
|
||||||
|
"id": 16,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.4.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"editorMode": "code",
|
||||||
|
"exemplar": false,
|
||||||
|
"expr": "histogram_quantile(0.95, sum(rate(hmac_file_server_request_duration_seconds_bucket[5m])) by (le))",
|
||||||
|
"format": "time_series",
|
||||||
|
"instant": true,
|
||||||
|
"interval": "",
|
||||||
|
"legendFormat": "__auto",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "HMAC Request Duration",
|
||||||
|
"type": "histogram"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"preload": false,
|
"preload": false,
|
||||||
@ -876,13 +981,13 @@
|
|||||||
"list": []
|
"list": []
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
"from": "now-5m",
|
"from": "now-24h",
|
||||||
"to": "now"
|
"to": "now"
|
||||||
},
|
},
|
||||||
"timepicker": {},
|
"timepicker": {},
|
||||||
"timezone": "",
|
"timezone": "",
|
||||||
"title": "HMAC File Server Metrics",
|
"title": "HMAC File Server Metrics",
|
||||||
"uid": "de0ye5t0hzq4ge",
|
"uid": "de0ye5t0hzq4ge",
|
||||||
"version": 129,
|
"version": 153,
|
||||||
"weekStart": ""
|
"weekStart": ""
|
||||||
}
|
}
|
||||||
|
BIN
dashboard/hmac_icon.png
Normal file
BIN
dashboard/hmac_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
83
dockerenv/config/config.toml
Normal file
83
dockerenv/config/config.toml
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
[server]
|
||||||
|
listenport = "8080"
|
||||||
|
unixsocket = false
|
||||||
|
storagepath = "/opt/hmac-file-server/data/uploads"
|
||||||
|
metricsenabled = true
|
||||||
|
metricsport = "9090"
|
||||||
|
deduplicationenabled = true
|
||||||
|
minfreebytes = "5GB"
|
||||||
|
filettl = "2y"
|
||||||
|
filettlenabled = false
|
||||||
|
autoadjustworkers = true
|
||||||
|
networkevents = false
|
||||||
|
pidfilepath = "./hmac-file-server.pid"
|
||||||
|
precaching = false
|
||||||
|
|
||||||
|
[deduplication]
|
||||||
|
enabled = true
|
||||||
|
directory = "/opt/hmac-file-server/data/duplicates"
|
||||||
|
|
||||||
|
[logging]
|
||||||
|
level = "debug"
|
||||||
|
file = "./hmac-file-server.log"
|
||||||
|
max_size = 100
|
||||||
|
max_backups = 7
|
||||||
|
max_age = 30
|
||||||
|
compress = true
|
||||||
|
|
||||||
|
[iso]
|
||||||
|
enabled = false
|
||||||
|
size = "1TB"
|
||||||
|
mountpoint = "/mnt/nfs_vol01/hmac-file-server/iso/"
|
||||||
|
charset = "utf-8"
|
||||||
|
|
||||||
|
[timeouts]
|
||||||
|
readtimeout = "3600s"
|
||||||
|
writetimeout = "3600s"
|
||||||
|
idletimeout = "3600s"
|
||||||
|
|
||||||
|
[security]
|
||||||
|
secret = "hmac-file-server-is-the-win"
|
||||||
|
|
||||||
|
[versioning]
|
||||||
|
enableversioning = false
|
||||||
|
maxversions = 1
|
||||||
|
|
||||||
|
[uploads]
|
||||||
|
resumableuploadsenabled = false
|
||||||
|
chunkeduploadsenabled = true
|
||||||
|
chunksize = "32MB"
|
||||||
|
allowedextensions = [
|
||||||
|
".txt", ".pdf", ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".svg", ".webp",
|
||||||
|
".wav", ".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".mpeg", ".mpg",
|
||||||
|
".m4v", ".3gp", ".3g2", ".mp3", ".ogg"
|
||||||
|
]
|
||||||
|
|
||||||
|
[downloads]
|
||||||
|
chunkeddownloadsenabled = false
|
||||||
|
chunksize = "32MB"
|
||||||
|
allowedextensions = [
|
||||||
|
".txt", ".pdf", ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".svg", ".webp",
|
||||||
|
".wav", ".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".mpeg", ".mpg",
|
||||||
|
".m4v", ".3gp", ".3g2", ".mp3", ".ogg"
|
||||||
|
]
|
||||||
|
|
||||||
|
[clamav]
|
||||||
|
clamavenabled = false
|
||||||
|
clamavsocket = "/var/run/clamav/clamd.ctl"
|
||||||
|
numscanworkers = 4
|
||||||
|
scanfileextensions = [".exe", ".dll", ".bin", ".com", ".bat", ".sh", ".php", ".js"]
|
||||||
|
|
||||||
|
[redis]
|
||||||
|
redisenabled = false
|
||||||
|
redisdbindex = 0
|
||||||
|
redisaddr = "localhost:6379"
|
||||||
|
redispassword = ""
|
||||||
|
redishealthcheckinterval = "120s"
|
||||||
|
|
||||||
|
[workers]
|
||||||
|
numworkers = 4
|
||||||
|
uploadqueuesize = 5000
|
||||||
|
|
||||||
|
[file]
|
||||||
|
filerevision = 1
|
0
dockerenv/data/logs/all-logs-here.txt
Normal file
0
dockerenv/data/logs/all-logs-here.txt
Normal file
0
dockerenv/data/uploads/all-uploads-here.txt
Normal file
0
dockerenv/data/uploads/all-uploads-here.txt
Normal file
17
dockerenv/docker-compose.yml
Normal file
17
dockerenv/docker-compose.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
hmac-file-server:
|
||||||
|
container_name: hmac-file-server
|
||||||
|
image: hmac-file-server:latest
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- ./config:/etc/hmac-file-server
|
||||||
|
- ./data/uploads:/opt/hmac-file-server/data/uploads
|
||||||
|
- ./data/duplicates:/opt/hmac-file-server/data/duplicates
|
||||||
|
- ./data/temp:/opt/hmac-file-server/data/temp
|
||||||
|
- ./data/logs:/opt/hmac-file-server/data/logs
|
||||||
|
environment:
|
||||||
|
- CONFIG_PATH=/etc/hmac-file-server/config.toml
|
||||||
|
restart: unless-stopped
|
27
dockerenv/dockerbuild/Dockerfile
Normal file
27
dockerenv/dockerbuild/Dockerfile
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Stage 1: Build
|
||||||
|
FROM golang:1.24-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY . .
|
||||||
|
RUN CGO_ENABLED=0 go build -o hmac-file-server cmd/server/main.go cmd/server/helpers.go cmd/server/config_validator.go cmd/server/config_test_scenarios.go
|
||||||
|
|
||||||
|
# Stage 2: Runtime
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
RUN apk --no-cache add ca-certificates
|
||||||
|
|
||||||
|
RUN mkdir -p /opt/hmac-file-server/data/uploads \
|
||||||
|
&& mkdir -p /opt/hmac-file-server/data/duplicates \
|
||||||
|
&& mkdir -p /opt/hmac-file-server/data/temp \
|
||||||
|
&& mkdir -p /opt/hmac-file-server/data/logs
|
||||||
|
|
||||||
|
WORKDIR /opt/hmac-file-server
|
||||||
|
|
||||||
|
COPY --from=builder /build/hmac-file-server .
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["./hmac-file-server", "--config", "/etc/hmac-file-server/config.toml"]
|
35
go.mod
35
go.mod
@ -1,39 +1,46 @@
|
|||||||
module github.com/PlusOne/hmac-file-server
|
module github.com/PlusOne/hmac-file-server
|
||||||
|
|
||||||
go 1.21
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/gdamore/tcell/v2 v2.7.4
|
||||||
github.com/go-redis/redis/v8 v8.11.5
|
github.com/go-redis/redis/v8 v8.11.5
|
||||||
|
github.com/pelletier/go-toml v1.9.5
|
||||||
github.com/prometheus/client_golang v1.20.5
|
github.com/prometheus/client_golang v1.20.5
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.5
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
|
github.com/spf13/viper v1.11.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // 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/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/spf13/afero v1.11.0 // indirect
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
github.com/spf13/cast v1.6.0 // indirect
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/spf13/viper v1.19.0 // indirect
|
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
golang.org/x/term v0.17.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
|
||||||
golang.org/x/text v0.18.0 // indirect
|
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.4.0
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
|
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
|
||||||
@ -42,11 +49,13 @@ require (
|
|||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/common v0.60.1 // indirect
|
github.com/prometheus/common v0.61.0
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
|
github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592
|
||||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
google.golang.org/protobuf v1.35.1 // indirect
|
google.golang.org/protobuf v1.35.2 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
)
|
)
|
||||||
|
126
go.sum
126
go.sum
@ -1,5 +1,3 @@
|
|||||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
|
||||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
@ -7,30 +5,46 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e h1:rcHHSQqzCgvlwP0I/fQ8rQMn/MpHE5gWSLdtpxtP6KQ=
|
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e h1:rcHHSQqzCgvlwP0I/fQ8rQMn/MpHE5gWSLdtpxtP6KQ=
|
||||||
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e/go.mod h1:Byz7q8MSzSPkouskHJhX0er2mZY/m0Vj5bMeMCkkyY4=
|
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e/go.mod h1:Byz7q8MSzSPkouskHJhX0er2mZY/m0Vj5bMeMCkkyY4=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
|
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
|
||||||
|
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
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 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
@ -43,76 +57,124 @@ github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
|||||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||||
github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc=
|
github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ=
|
||||||
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
|
github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592 h1:YIJ+B1hePP6AgynC5TcqpO0H9k3SSoZa2BGyL6vDUzM=
|
||||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
|
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
|
||||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
github.com/spf13/viper v1.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44=
|
||||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||||
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||||
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
|
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
|
||||||
|
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||||
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||||
|
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
1273
installer.sh
Executable file
1273
installer.sh
Executable file
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
serverURL = "http://[::1]:8080" // Replace with your actual server URL
|
serverURL = "http://[::1]:8080" // Replace with your actual server URL
|
||||||
secret = "a-orc-and-a-humans-is-drinking-ale" // Replace with your HMAC secret key
|
secret = "hmac-file-server-is-the-win" // Replace with your HMAC secret key
|
||||||
uploadPath = "hmac_icon.png" // Test file to upload
|
uploadPath = "hmac_icon.png" // Test file to upload
|
||||||
protocolType = "v2" // Use v2, v, or token as needed
|
protocolType = "v2" // Use v2, v, or token as needed
|
||||||
)
|
)
|
||||||
|
39
test/server_flags_test.go
Normal file
39
test/server_flags_test.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGenConfigFlag runs the server with --genconfig and checks output for expected config keys
|
||||||
|
func TestGenConfigFlag(t *testing.T) {
|
||||||
|
cmd := exec.Command("go", "run", "../cmd/server/main.go", "--genconfig")
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil && !strings.Contains(string(output), "[server]") {
|
||||||
|
t.Fatalf("Failed to run with --genconfig: %v\nOutput: %s", err, output)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(output), "[server]") || !strings.Contains(string(output), "bind_ip") {
|
||||||
|
t.Errorf("Example config missing expected keys. Output: %s", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIPv4IPv6Flag runs the server with forceprotocol=ipv4 and ipv6 and checks for startup errors
|
||||||
|
func TestIPv4IPv6Flag(t *testing.T) {
|
||||||
|
for _, proto := range []string{"ipv4", "ipv6", "auto"} {
|
||||||
|
cmd := exec.Command("go", "run", "../cmd/server/main.go", "--config", "../cmd/server/config.toml")
|
||||||
|
cmd.Env = append(os.Environ(), "FORCEPROTOCOL="+proto)
|
||||||
|
// Set Go module cache environment variables if not already set
|
||||||
|
if os.Getenv("GOMODCACHE") == "" {
|
||||||
|
cmd.Env = append(cmd.Env, "GOMODCACHE="+os.Getenv("HOME")+"/go/pkg/mod")
|
||||||
|
}
|
||||||
|
if os.Getenv("GOPATH") == "" {
|
||||||
|
cmd.Env = append(cmd.Env, "GOPATH="+os.Getenv("HOME")+"/go")
|
||||||
|
}
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil && !strings.Contains(string(output), "Configuration loaded successfully") {
|
||||||
|
t.Errorf("Server failed to start with forceprotocol=%s: %v\nOutput: %s", proto, err, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
173
test/test_installer_config.sh
Executable file
173
test/test_installer_config.sh
Executable 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
230
verify_installation.sh
Executable 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
|
Reference in New Issue
Block a user