From 6d7db84e9bcc1d9cdb52063a3ef0d898ffb2becf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 03:37:40 +0000 Subject: [PATCH 1/8] Initial plan From 4cdc5de6cb46a40f20a4549c4d98a1d7c6b8010e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 03:41:44 +0000 Subject: [PATCH 2/8] Add human-readable size parsing for max_item_size and disk_cache_max_size Co-authored-by: GrassBlock1 <46253950+GrassBlock1@users.noreply.github.com> --- Cargo.lock | 7 ++++ Cargo.toml | 1 + src/config.rs | 93 +++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 98 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 705e001..c9cf9b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,7 @@ dependencies = [ "anyhow", "axum", "bytes", + "bytesize", "clap", "futures", "hex", @@ -434,6 +435,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +[[package]] +name = "bytesize" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3" + [[package]] name = "cargo-lock" version = "8.0.3" diff --git a/Cargo.toml b/Cargo.toml index 30a7073..f07e0f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/src/config.rs b/src/config.rs index 92eb300..353dc84 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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) -> Result +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::() + .map(|bs| bs.as_u64()) + .map_err(serde::de::Error::custom) + } + } +} + /// Application configuration #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Config { @@ -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) @@ -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, } @@ -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); + } } From d51421eadb2a42b4a215f56314bc9dc9c3443315 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 03:42:42 +0000 Subject: [PATCH 3/8] Add CLI options and env vars for forward_headers_enabled and disk_cache settings Co-authored-by: GrassBlock1 <46253950+GrassBlock1@users.noreply.github.com> --- src/main.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/main.rs b/src/main.rs index 342e9d3..f1b6df6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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, + + /// Maximum disk cache size (e.g., "1G", "500M", "1GiB") + #[arg(long, value_name = "SIZE")] + disk_cache_max_size: Option, } #[tokio::main] @@ -156,6 +180,29 @@ fn load_config(cli: &Cli) -> Result { 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 { + match size_str.parse::() { + Ok(size) => config.cache.disk_cache_max_size = size.as_u64(), + Err(e) => anyhow::bail!("Invalid disk cache max size '{}': {}", size_str, e), + } + } + // Priority 1 (highest): Apply environment variables if let Ok(upstream_url) = std::env::var("UPSTREAM_URL") { info!("Overriding upstream URL from environment: {}", upstream_url); @@ -176,6 +223,35 @@ fn load_config(cli: &Cli) -> Result { } } + if let Ok(forward_headers) = std::env::var("FORWARD_HEADERS_ENABLED") { + if let Ok(value) = forward_headers.parse::() { + 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::() { + 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") { + match size_str.parse::() { + Ok(size) => { + info!("Overriding disk_cache_max_size from environment: {}", size); + config.cache.disk_cache_max_size = size.as_u64(); + } + Err(e) => anyhow::bail!("Invalid DISK_CACHE_MAX_SIZE '{}': {}", size_str, e), + } + } + // Validate that we have an upstream URL if config.upstream.url.is_empty() { anyhow::bail!( From 95f856531454cd8113e43f5f863e66f0ab6b6592 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 03:44:06 +0000 Subject: [PATCH 4/8] Bump version to v0.2.0 and update documentation Co-authored-by: GrassBlock1 <46253950+GrassBlock1@users.noreply.github.com> --- CHANGELOG.md | 23 ++++++++++++++++++++++- Cargo.lock | 2 +- Cargo.toml | 2 +- config.example.toml | 8 ++++++-- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 601e46f..dcbd97d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 `: Specify disk cache directory + - `--disk-cache-max-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 @@ -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 diff --git a/Cargo.lock b/Cargo.lock index c9cf9b3..6c0a67a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ dependencies = [ [[package]] name = "akkoproxy" -version = "0.1.2" +version = "0.2.0" dependencies = [ "anyhow", "axum", diff --git a/Cargo.toml b/Cargo.toml index f07e0f1..dc086e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/config.example.toml b/config.example.toml index 6416d8c..f698a7e 100644 --- a/config.example.toml +++ b/config.example.toml @@ -50,7 +50,9 @@ max_capacity = 10000 ttl = 3600 # Maximum size of a cached item in bytes (default: 10485760, 10MB) -max_item_size = 10485760 +# 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 @@ -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: M/MB uses base 10 (1000^3), MiB uses base 2 (1024^3) +disk_cache_max_size = "1GiB" [image] # Enable AVIF conversion (default: true) From 9046626ab4c8a51b1f9d7ab638633a19a8d5263d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 03:45:23 +0000 Subject: [PATCH 5/8] Fix comment in config.example.toml for disk_cache_max_size units --- config.example.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.example.toml b/config.example.toml index f698a7e..ddc6eac 100644 --- a/config.example.toml +++ b/config.example.toml @@ -63,7 +63,7 @@ disk_cache_path = "./cache" # Maximum disk cache size in bytes (default: 1073741824, 1GB) # Supports human-readable formats: "10M", "10MB", "10MiB", "1G", "1GB", "1GiB", etc. -# Note: M/MB uses base 10 (1000^3), MiB uses base 2 (1024^3) +# Note: G/GB uses base 10 (1000^3), GiB uses base 2 (1024^3) disk_cache_max_size = "1GiB" [image] From 7d3789af4500cd91908363c237205e1d0ebadca0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 03:47:11 +0000 Subject: [PATCH 6/8] Refactor size parsing into helper function to reduce duplication Co-authored-by: GrassBlock1 <46253950+GrassBlock1@users.noreply.github.com> --- src/main.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index f1b6df6..ca68587 100644 --- a/src/main.rs +++ b/src/main.rs @@ -132,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 { + let size = size_str + .parse::() + .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 { // Priority 3 (lowest): Load from config file if it exists @@ -197,10 +205,7 @@ fn load_config(cli: &Cli) -> Result { } if let Some(ref size_str) = cli.disk_cache_max_size { - match size_str.parse::() { - Ok(size) => config.cache.disk_cache_max_size = size.as_u64(), - Err(e) => anyhow::bail!("Invalid disk cache max size '{}': {}", size_str, e), - } + config.cache.disk_cache_max_size = parse_size(size_str, "disk cache max size")?; } // Priority 1 (highest): Apply environment variables @@ -243,13 +248,9 @@ fn load_config(cli: &Cli) -> Result { } if let Ok(size_str) = std::env::var("DISK_CACHE_MAX_SIZE") { - match size_str.parse::() { - Ok(size) => { - info!("Overriding disk_cache_max_size from environment: {}", size); - config.cache.disk_cache_max_size = size.as_u64(); - } - Err(e) => anyhow::bail!("Invalid DISK_CACHE_MAX_SIZE '{}': {}", size_str, e), - } + 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 From 500fc0534099ae0a33d1139b9987fcf3f7c2cb74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E6=96=B9=E5=9D=97?= <46253950+GrassBlock1@users.noreply.github.com> Date: Tue, 10 Feb 2026 12:38:43 +0800 Subject: [PATCH 7/8] Update config.example.toml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- config.example.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.example.toml b/config.example.toml index ddc6eac..7349833 100644 --- a/config.example.toml +++ b/config.example.toml @@ -49,7 +49,7 @@ 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) +# 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" From f26f2d7706c2d1b0c96b5dbe2263a9cf7263a25c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E6=96=B9=E5=9D=97?= <46253950+GrassBlock1@users.noreply.github.com> Date: Tue, 10 Feb 2026 12:39:05 +0800 Subject: [PATCH 8/8] Update src/config.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 353dc84..d08bc00 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,7 +5,7 @@ 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) -> Result +fn deserialize_size<'de, D>(deserializer: D) -> std::result::Result where D: Deserializer<'de>, {