From 00ddd9e46000fab3bbca07e923ba81e25ca17c3c Mon Sep 17 00:00:00 2001 From: Thomas Cravey Date: Sat, 16 Aug 2025 12:29:58 -0500 Subject: [PATCH] defaults: raise max/defaults (OPENAI_MAX_TOKENS=128000, larger summarizer timeouts/limits, DIGEST_WINDOW=24h, RETENTION=365); docs: add inline env option, defaults table; compose: bind 127.0.0.1:8080 --- .dockerignore | 6 ++ README.md | 145 +++++++++++++++++++++++++++++++++++--- docker-compose.yml | 2 +- internal/config/config.go | 16 ++--- 4 files changed, 150 insertions(+), 19 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cc1d9f4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +# Exclude secrets and non-essential files from Docker build context +.env +.env.* +.git +.gitignore +**/*.md diff --git a/README.md b/README.md index 5fbeef3..8ed4575 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,11 @@ docker inspect --format='{{json .State.Health}}' sojuboy | jq Compose includes a healthcheck calling the binary’s `--health` flag, which returns 0 only when `/ready` is 200. -## Configuration (.env example) +## Configuration options + +You can configure via a `.env` file or inline `environment:` in your compose YAML. Both approaches are shown below. Defaults for all variables are listed in the table after the examples. + +### Option A: .env file (recommended for development) Below shows maximum or large/reasonable values. Defaults are noted where they are also the maximum or when relevant. @@ -178,20 +182,20 @@ LLM_PROVIDER=openai OPENAI_API_KEY=sk-... OPENAI_BASE_URL=https://api.openai.com/v1 OPENAI_MODEL=gpt-5 -# Max completion (output) tokens for GPT‑5 is ~128k (model limit). Default 700. +# Max completion (output) tokens for GPT‑5 is ~128k (model limit). Default 128000. OPENAI_MAX_TOKENS=128000 # Summarizer tuning SUMM_FOLLOW_LINKS=true # default true -SUMM_LINK_TIMEOUT=20s # no hard max; example large -SUMM_LINK_MAX_BYTES=1048576 # no hard max; example large (1 MiB/article) -SUMM_GROUP_WINDOW=120s # no hard max; example large grouping window -SUMM_MAX_LINKS=20 # no strict max; example large -SUMM_MAX_GROUPS=20000 # 0=no cap; example large -SUMM_TIMEOUT=10m # request timeout; default 5m +SUMM_LINK_TIMEOUT=20s # default 20s +SUMM_LINK_MAX_BYTES=1048576 # default 1048576 (1 MiB/article) +SUMM_GROUP_WINDOW=120s # default 120s +SUMM_MAX_LINKS=20 # default 20 +SUMM_MAX_GROUPS=20000 # default 0 (no cap); example large +SUMM_TIMEOUT=10m # request timeout; default 10m # Digests DIGEST_CRON=0 */6 * * * # every 6 hours -DIGEST_WINDOW=24h # no hard max; example large window +DIGEST_WINDOW=24h # default 24h QUIET_HOURS= # e.g., 22:00-07:00 # Mentions/alerts @@ -207,12 +211,133 @@ HTTP_TOKEN=put-a-long-random-token-here # Storage STORE_PATH=/data/app.db -STORE_RETENTION_DAYS=365 # example large retention +STORE_RETENTION_DAYS=365 # default 365 # Logging LOG_LEVEL=info ``` +Compose (with localhost bind suitable for Synology reverse proxy): + +```yaml +services: + sojuboy: + image: code.cravey.net/your-user/sojuboy:v0.1.0-beta1 + restart: unless-stopped + env_file: .env + ports: + - "127.0.0.1:8080:8080" # bind only to localhost; fronted by DSM Reverse Proxy + volumes: + - /volume1/docker/sojuboy/data:/data + healthcheck: + test: ["CMD", "/sojuboy", "--health"] + interval: 30s + timeout: 3s + retries: 3 +``` + +### Option B: Inline environment in compose (no .env) + +```yaml +services: + sojuboy: + image: code.cravey.net/your-user/sojuboy:v0.1.0-beta1 + restart: unless-stopped + ports: + - "127.0.0.1:8080:8080" # bind only to localhost; fronted by DSM Reverse Proxy + volumes: + - /volume1/docker/sojuboy/data:/data + environment: + SOJU_HOST: "bnc.example.org" # default 127.0.0.1 + SOJU_PORT: "6697" # default 6697 + SOJU_TLS: "true" # default true + SOJU_NETWORK: "your-network" # default "" + IRC_NICK: "yourNick" # default sojuboy + IRC_USERNAME: "yourUser/your-network@sojuboy" # default IRC_NICK + IRC_REALNAME: "Your Real Name" # default sojuboy + IRC_PASSWORD: "yourSojuClientPassword" # default "" + CHANNELS: "#animaniacs,#general" # default "" (none) + KEYWORDS: "yourNick,YourCompany" # default IRC_NICK + SOJU_AUTH: "raw" # default sasl (hint only) + NOTIFIER: "pushover" # default pushover + PUSHOVER_USER_KEY: "..." # default "" + PUSHOVER_API_TOKEN: "..." # default "" + LLM_PROVIDER: "openai" # default openai + OPENAI_API_KEY: "sk-..." # default "" + OPENAI_BASE_URL: "https://api.openai.com/v1" # default "" + OPENAI_MODEL: "gpt-5" # default gpt-5 + OPENAI_MAX_TOKENS: "128000" # default 128000 + SUMM_FOLLOW_LINKS: "true" # default true + SUMM_LINK_TIMEOUT: "20s" # default 20s + SUMM_LINK_MAX_BYTES: "1048576" # default 1048576 + SUMM_GROUP_WINDOW: "120s" # default 120s + SUMM_MAX_LINKS: "20" # default 20 + SUMM_MAX_GROUPS: "20000" # default 0 (no cap) + SUMM_TIMEOUT: "10m" # default 10m + DIGEST_CRON: "0 */6 * * *" # default 0 */6 * * * + DIGEST_WINDOW: "24h" # default 24h + QUIET_HOURS: "" # default "" + NOTIFY_BACKFILL: "false" # default false + MENTION_MIN_INTERVAL: "30s" # default 30s + MENTIONS_ONLY_CHANNELS: "" # default "" + MENTIONS_DENY_CHANNELS: "" # default "" + URGENT_KEYWORDS: "urgent,priority" # default "" + HTTP_LISTEN: ":8080" # default :8080 + HTTP_TOKEN: "" # default "" + STORE_PATH: "/data/app.db" # default /data/app.db + STORE_RETENTION_DAYS: "365" # default 365 + LOG_LEVEL: "info" # default info + healthcheck: + test: ["CMD", "/sojuboy", "--health"] + interval: 30s + timeout: 3s + retries: 3 +``` + +### Defaults reference + +| Variable | Default | +|---|---| +| SOJU_HOST | 127.0.0.1 | +| SOJU_PORT | 6697 | +| SOJU_TLS | true | +| IRC_NICK | sojuboy | +| IRC_USERNAME | IRC_NICK | +| IRC_REALNAME | sojuboy | +| IRC_PASSWORD | (empty) | +| SOJU_NETWORK | (empty) | +| CHANNELS | (empty) | +| KEYWORDS | IRC_NICK | +| SOJU_AUTH | sasl | +| NOTIFIER | pushover | +| PUSHOVER_USER_KEY | (empty) | +| PUSHOVER_API_TOKEN | (empty) | +| LLM_PROVIDER | openai | +| OPENAI_API_KEY | (empty) | +| OPENAI_BASE_URL | (empty) | +| OPENAI_MODEL | gpt-5 | +| OPENAI_MAX_TOKENS | 700 | +| SUMM_FOLLOW_LINKS | true | +| SUMM_LINK_TIMEOUT | 6s | +| SUMM_LINK_MAX_BYTES | 262144 | +| SUMM_GROUP_WINDOW | 90s | +| SUMM_MAX_LINKS | 5 | +| SUMM_MAX_GROUPS | 0 | +| SUMM_TIMEOUT | 5m | +| DIGEST_CRON | 0 */6 * * * | +| DIGEST_WINDOW | 6h | +| QUIET_HOURS | (empty) | +| NOTIFY_BACKFILL | false | +| MENTION_MIN_INTERVAL | 30s | +| MENTIONS_ONLY_CHANNELS | (empty) | +| MENTIONS_DENY_CHANNELS | (empty) | +| URGENT_KEYWORDS | (empty) | +| HTTP_LISTEN | :8080 | +| HTTP_TOKEN | (empty) | +| STORE_PATH | /data/app.db | +| STORE_RETENTION_DAYS | 7 | +| LOG_LEVEL | info | + ## Pushover setup 1) Install Pushover iOS app and log in diff --git a/docker-compose.yml b/docker-compose.yml index f41e8bd..3176c71 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: restart: unless-stopped env_file: .env ports: - - "8080:8080" + - "127.0.0.1:8080:8080" volumes: - sojuboy_data:/data healthcheck: diff --git a/internal/config/config.go b/internal/config/config.go index 0ff864a..d492f37 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -86,17 +86,17 @@ func FromEnv() Config { cfg.OpenAIAPIKey = getEnv("OPENAI_API_KEY", "") cfg.OpenAIBaseURL = getEnv("OPENAI_BASE_URL", "") cfg.OpenAIModel = getEnv("OPENAI_MODEL", "gpt-5") - cfg.OpenAIMaxTokens = getEnvInt("OPENAI_MAX_TOKENS", 700) + cfg.OpenAIMaxTokens = getEnvInt("OPENAI_MAX_TOKENS", 128000) cfg.SummFollowLinks = getEnvBool("SUMM_FOLLOW_LINKS", true) - cfg.SummLinkTimeout = getEnvDuration("SUMM_LINK_TIMEOUT", 6*time.Second) - cfg.SummLinkMaxBytes = getEnvInt("SUMM_LINK_MAX_BYTES", 262144) - cfg.SummGroupWindow = getEnvDuration("SUMM_GROUP_WINDOW", 90*time.Second) - cfg.SummMaxLinks = getEnvInt("SUMM_MAX_LINKS", 5) + cfg.SummLinkTimeout = getEnvDuration("SUMM_LINK_TIMEOUT", 20*time.Second) + cfg.SummLinkMaxBytes = getEnvInt("SUMM_LINK_MAX_BYTES", 1048576) + cfg.SummGroupWindow = getEnvDuration("SUMM_GROUP_WINDOW", 120*time.Second) + cfg.SummMaxLinks = getEnvInt("SUMM_MAX_LINKS", 20) cfg.SummMaxGroups = getEnvInt("SUMM_MAX_GROUPS", 0) - cfg.SummarizerTimeout = getEnvDuration("SUMM_TIMEOUT", 5*time.Minute) + cfg.SummarizerTimeout = getEnvDuration("SUMM_TIMEOUT", 10*time.Minute) cfg.DigestCron = getEnv("DIGEST_CRON", "0 */6 * * *") - cfg.DigestWindow = getEnvDuration("DIGEST_WINDOW", 6*time.Hour) + cfg.DigestWindow = getEnvDuration("DIGEST_WINDOW", 24*time.Hour) cfg.QuietHours = getEnv("QUIET_HOURS", "") cfg.NotifyBackfill = getEnvBool("NOTIFY_BACKFILL", false) cfg.MentionMinInterval = getEnvDuration("MENTION_MIN_INTERVAL", 30*time.Second) @@ -108,7 +108,7 @@ func FromEnv() Config { cfg.HTTPToken = getEnv("HTTP_TOKEN", "") cfg.StorePath = getEnv("STORE_PATH", "/data/app.db") - cfg.RetentionDays = getEnvInt("STORE_RETENTION_DAYS", 7) + cfg.RetentionDays = getEnvInt("STORE_RETENTION_DAYS", 365) cfg.LogLevel = getEnv("LOG_LEVEL", "info")