Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.2.0] - 2026-02-10

### Added
- **Human-readable size formats**: Configuration options `max_item_size` and `disk_cache_max_size` now support human-readable formats like "10M", "1G", "10MiB", "1GiB", etc.
- Supports both decimal (MB, GB) and binary (MiB, GiB) units
- Backward compatible with numeric byte values
- **Command-line options for cache settings**:
- `--forward-headers` / `--no-forward-headers`: Enable/disable X-Forwarded-* header forwarding
- `--disk-cache` / `--no-disk-cache`: Enable/disable disk-based cache
- `--disk-cache-path <PATH>`: Specify disk cache directory
- `--disk-cache-max-size <SIZE>`: Set maximum disk cache size with human-readable format support
- **Environment variables for cache settings**:
- `FORWARD_HEADERS_ENABLED`: Enable/disable X-Forwarded-* header forwarding (true/false)
- `DISK_CACHE_ENABLED`: Enable/disable disk-based cache (true/false)
- `DISK_CACHE_PATH`: Specify disk cache directory path
- `DISK_CACHE_MAX_SIZE`: Set maximum disk cache size with human-readable format support

### Changed
- Bumped version from 0.1.2 to 0.2.0

## [0.1.2] - 2026-02-10

### Added
Expand Down Expand Up @@ -45,7 +65,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Initial implementation

[Unreleased]: https://github.com/BlockG-ws/akkoproxy/compare/v0.1.2...HEAD
[Unreleased]: https://github.com/BlockG-ws/akkoproxy/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/BlockG-ws/akkoproxy/compare/v0.1.2...v0.2.0
[0.1.2]: https://github.com/BlockG-ws/akkoproxy/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/BlockG-ws/akkoproxy/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/BlockG-ws/akkoproxy/releases/tag/v0.1.0
9 changes: 8 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "akkoproxy"
version = "0.1.2"
version = "0.2.0"
edition = "2021"
authors = ["Akkoproxy Contributors"]
description = "A fast caching and optimization media proxy for Akkoma/Pleroma"
Expand All @@ -27,6 +27,7 @@ libavif = { version = "0.12", optional = true }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.8"
bytesize = "2.3"

# Caching
moka = { version = "0.12", features = ["future"] }
Expand Down
10 changes: 7 additions & 3 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@ max_capacity = 10000
# Time to live for cached items in seconds (default: 3600)
ttl = 3600

# Maximum size of a cached item in bytes (default: 10485760, 10MB)
max_item_size = 10485760
# Maximum size of a cached item in bytes (default: 10485760, 10MiB)
# Supports human-readable formats: "10M", "10MB", "10MiB", "1G", "1GB", "1GiB", etc.
# Note: M/MB uses base 10 (1000^2), MiB uses base 2 (1024^2)
max_item_size = "10MiB"

# Enable disk-based cache (default: false)
# When enabled, cached media will be stored on disk for persistence across restarts
Expand All @@ -60,7 +62,9 @@ disk_cache_enabled = false
disk_cache_path = "./cache"

# Maximum disk cache size in bytes (default: 1073741824, 1GB)
disk_cache_max_size = 1073741824
# Supports human-readable formats: "10M", "10MB", "10MiB", "1G", "1GB", "1GiB", etc.
# Note: G/GB uses base 10 (1000^3), GiB uses base 2 (1024^3)
disk_cache_max_size = "1GiB"

[image]
# Enable AVIF conversion (default: true)
Expand Down
93 changes: 90 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize};
use std::fs;
use std::net::SocketAddr;
use std::path::Path;

/// Deserialize a size that can be either a number (bytes) or a human-readable string like "10M", "1G"
fn deserialize_size<'de, D>(deserializer: D) -> std::result::Result<u64, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum SizeValue {
Numeric(u64),
String(String),
}

match SizeValue::deserialize(deserializer)? {
SizeValue::Numeric(n) => Ok(n),
SizeValue::String(s) => {
// Try to parse as a human-readable size using bytesize
s.parse::<bytesize::ByteSize>()
.map(|bs| bs.as_u64())
.map_err(serde::de::Error::custom)
}
}
}

/// Application configuration
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Config {
Expand Down Expand Up @@ -77,7 +100,7 @@ pub struct CacheConfig {
pub ttl: u64,

/// Maximum size of a cached item in bytes
#[serde(default = "default_max_item_size")]
#[serde(default = "default_max_item_size", deserialize_with = "deserialize_size")]
pub max_item_size: u64,

/// Enable disk-based cache (default: false)
Expand All @@ -89,7 +112,7 @@ pub struct CacheConfig {
pub disk_cache_path: String,

/// Maximum disk cache size in bytes (default: 1GB)
#[serde(default = "default_disk_cache_max_size")]
#[serde(default = "default_disk_cache_max_size", deserialize_with = "deserialize_size")]
pub disk_cache_max_size: u64,
}

Expand Down Expand Up @@ -260,4 +283,68 @@ mod tests {
assert!(config.image.enable_avif);
assert!(config.image.enable_webp);
}

#[test]
fn test_parse_size_numeric() {
let toml = r#"
[upstream]
url = "https://example.com"

[cache]
max_item_size = 10485760
disk_cache_max_size = 1073741824
"#;

let config: Config = toml::from_str(toml).unwrap();
assert_eq!(config.cache.max_item_size, 10485760);
assert_eq!(config.cache.disk_cache_max_size, 1073741824);
}

#[test]
fn test_parse_size_human_readable() {
let toml = r#"
[upstream]
url = "https://example.com"

[cache]
max_item_size = "10MiB"
disk_cache_max_size = "1GiB"
"#;

let config: Config = toml::from_str(toml).unwrap();
assert_eq!(config.cache.max_item_size, 10 * 1024 * 1024);
assert_eq!(config.cache.disk_cache_max_size, 1024 * 1024 * 1024);
}

#[test]
fn test_parse_size_various_formats() {
let toml = r#"
[upstream]
url = "https://example.com"

[cache]
max_item_size = "5MB"
disk_cache_max_size = "2GB"
"#;

let config: Config = toml::from_str(toml).unwrap();
assert_eq!(config.cache.max_item_size, 5 * 1000 * 1000);
assert_eq!(config.cache.disk_cache_max_size, 2 * 1000 * 1000 * 1000);
}

#[test]
fn test_parse_size_kilobytes() {
let toml = r#"
[upstream]
url = "https://example.com"

[cache]
max_item_size = "512KiB"
disk_cache_max_size = "100KB"
"#;

let config: Config = toml::from_str(toml).unwrap();
assert_eq!(config.cache.max_item_size, 512 * 1024);
assert_eq!(config.cache.disk_cache_max_size, 100 * 1000);
}
}
77 changes: 77 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,30 @@ struct Cli {
/// Preserve all headers from upstream when responding
#[arg(long)]
preserve_headers: bool,

/// Enable forwarding of X-Forwarded-* headers to upstream
#[arg(long)]
forward_headers: bool,

/// Disable forwarding of X-Forwarded-* headers to upstream
#[arg(long, conflicts_with = "forward_headers")]
no_forward_headers: bool,

/// Enable disk-based cache
#[arg(long)]
disk_cache: bool,

/// Disable disk-based cache
#[arg(long, conflicts_with = "disk_cache")]
no_disk_cache: bool,

/// Path to disk cache directory
#[arg(long, value_name = "PATH")]
disk_cache_path: Option<PathBuf>,

/// Maximum disk cache size (e.g., "1G", "500M", "1GiB")
#[arg(long, value_name = "SIZE")]
disk_cache_max_size: Option<String>,
}

#[tokio::main]
Expand Down Expand Up @@ -108,6 +132,14 @@ async fn main() -> Result<()> {
Ok(())
}

/// Parse a human-readable size string into bytes
fn parse_size(size_str: &str, context: &str) -> Result<u64> {
let size = size_str
.parse::<bytesize::ByteSize>()
.map_err(|e| anyhow::anyhow!("Invalid {} '{}': {}", context, size_str, e))?;
Ok(size.as_u64())
}

/// Load configuration with priority: env > cmdline options > config file
fn load_config(cli: &Cli) -> Result<Config> {
// Priority 3 (lowest): Load from config file if it exists
Expand Down Expand Up @@ -156,6 +188,26 @@ fn load_config(cli: &Cli) -> Result<Config> {
config.server.preserve_upstream_headers = true;
}

if cli.forward_headers {
config.server.forward_headers_enabled = true;
} else if cli.no_forward_headers {
config.server.forward_headers_enabled = false;
}

if cli.disk_cache {
config.cache.disk_cache_enabled = true;
} else if cli.no_disk_cache {
config.cache.disk_cache_enabled = false;
}

if let Some(ref path) = cli.disk_cache_path {
config.cache.disk_cache_path = path.to_string_lossy().to_string();
}

if let Some(ref size_str) = cli.disk_cache_max_size {
config.cache.disk_cache_max_size = parse_size(size_str, "disk cache max size")?;
}

// Priority 1 (highest): Apply environment variables
if let Ok(upstream_url) = std::env::var("UPSTREAM_URL") {
info!("Overriding upstream URL from environment: {}", upstream_url);
Expand All @@ -176,6 +228,31 @@ fn load_config(cli: &Cli) -> Result<Config> {
}
}

if let Ok(forward_headers) = std::env::var("FORWARD_HEADERS_ENABLED") {
if let Ok(value) = forward_headers.parse::<bool>() {
info!("Overriding forward_headers_enabled from environment: {}", value);
config.server.forward_headers_enabled = value;
}
}

if let Ok(disk_cache) = std::env::var("DISK_CACHE_ENABLED") {
if let Ok(value) = disk_cache.parse::<bool>() {
info!("Overriding disk_cache_enabled from environment: {}", value);
config.cache.disk_cache_enabled = value;
}
}

if let Ok(path) = std::env::var("DISK_CACHE_PATH") {
info!("Overriding disk_cache_path from environment: {}", path);
config.cache.disk_cache_path = path;
}

if let Ok(size_str) = std::env::var("DISK_CACHE_MAX_SIZE") {
let size = parse_size(&size_str, "DISK_CACHE_MAX_SIZE")?;
info!("Overriding disk_cache_max_size from environment: {}", bytesize::ByteSize(size));
config.cache.disk_cache_max_size = size;
}

// Validate that we have an upstream URL
if config.upstream.url.is_empty() {
anyhow::bail!(
Expand Down
Loading