Arrmate turns plain English into media server commands. Type what you want — it figures out what to do.
"can you add Yellowstone for me?"
"the sound is off on Breaking Bad, can you fix it?"
"what movies do I have?"
"add the Dune audiobook"
"what's downloading right now?"
Built for families. The admin sets it up once. Everyone else — parents, grandparents, partners, kids — just types what they need in plain language, the same way they'd send a text message. No need to know what Sonarr or Radarr is. No menus to navigate, no API keys to deal with. Just ask.
Works with Sonarr, Radarr, Lidarr, Bazarr, Plex, AudioBookshelf, ReadMeABook, SABnzbd, qBittorrent, and more. Powered by your choice of local (Ollama, LM Studio, LocalAI) or cloud AI (OpenAI, Anthropic, Groq, OpenRouter, Mistral, and any OpenAI-compatible API).
- A family member types a plain English message — exactly how they'd text you
- Arrmate's AI reads it and figures out what they mean
- It calls the right service (Sonarr, Radarr, Plex, etc.) automatically
- They see a plain English result — no technical jargon
The admin never needs to get involved for day-to-day requests. Family members self-serve.
You run the media server — Sonarr, Radarr, Plex, etc. You set up Arrmate once, connect it to your services, and invite your family. After that, most requests take care of themselves.
They just type. A few real-world examples of what they might send:
| What they type | What Arrmate does |
|---|---|
| "can you add Yellowstone?" | Searches Sonarr, adds the show |
| "the sound is weird on Interstellar" | Triggers a quality upgrade in Radarr |
| "I can't hear what they're saying in Succession S2" | Searches for a better copy |
| "can you get me the Harry Potter audiobook?" | Submits a request to AudioBookshelf |
| "what shows do I have?" | Lists the library |
| "I finished Ozark, you can delete it" | Removes the series (if they have permission) |
| "put Silo on my list" | Adds it to the library |
| "what's downloading?" | Shows the current download queue |
| "the picture on The Bear is all green" | Upgrades the file |
| "fix Mandalorian season 2, it keeps stopping" | Triggers a search for a better version |
They don't need to know what Sonarr is. They don't need an account on each service. They just need access to Arrmate.
| Service | Type | What you can do |
|---|---|---|
| Sonarr | TV | Search, add, remove, upgrade, monitor/unmonitor shows and episodes |
| Radarr | Movies | Search, add, remove, upgrade, monitor/unmonitor movies |
| Lidarr | Music | Search, add, remove artists and albums |
| Plex | Media server | History, continue watching, on deck, recently added, rate items, Butler maintenance, terminate sessions |
| Bazarr | Subtitles | Download and sync subtitles by language |
| AudioBookshelf | Audiobooks | Browse and search libraries |
| ReadMeABook | Audiobooks | Search, request, and manage audiobook acquisition |
| LazyLibrarian | Books | Search and manage book libraries |
| Prowlarr | Indexer aggregator | Search all indexers, send results to download managers |
| SABnzbd | Downloads | Queue, speed control, per-item priority/pause/resume, add by URL |
| NZBget | Downloads | Queue, speed control, per-item priority/pause/resume, add by URL |
| qBittorrent | Torrents | Queue, speed limits, priority reorder, add by URL/magnet |
| Transmission | Torrents | Queue, speed limits, bandwidth priority, add by URL/magnet |
Services are optional — anything not configured simply doesn't appear in the UI.
Arrmate needs an AI model to understand natural language. You have two main options:
Option A: Ollama (local, free, no data leaves your server)
Recommended if you already run Ollama, or want full privacy. Pull a model that supports tool calling:
ollama pull qwen2.5:7b # recommended, fast, good quality
# or
ollama pull llama3.1:8b # larger, slightly betterOption B: Cloud AI (easiest to start)
Sign up for a free account at one of these — no credit card required for the free tiers:
- Groq — console.groq.com → API Keys → Create key. Use model
llama-3.3-70b-versatile. Very fast, generous free tier. - Google Gemini — aistudio.google.com → Get API key. Use model
gemini-2.0-flash. Free tier available. - OpenRouter — openrouter.ai → Create key. Many free models available (look for
:freesuffix).
See the full LLM Providers section for all options.
# Download the compose file and example config
curl -O https://raw.githubusercontent.com/TechButton/Arrmate/main/docker-compose.prod.yml
curl -O https://raw.githubusercontent.com/TechButton/Arrmate/main/.env.example
cp .env.example .envEdit .env with your details (see Step 3), then start:
docker compose -f docker-compose.prod.yml up -dOpen http://your-server-ip:8000 in a browser.
Open .env in a text editor. The key settings:
LLM (choose one):
# Option A: Ollama
LLM_PROVIDER=ollama
OLLAMA_BASE_URL=http://ollama:11434 # or http://localhost:11434 if Ollama is on the host
OLLAMA_MODEL=qwen2.5:7b
# Option B: Groq (cloud, free tier)
LLM_PROVIDER=openai
OPENAI_API_KEY=gsk_your-key-from-console.groq.com
OPENAI_BASE_URL=https://api.groq.com/openai/v1
OPENAI_MODEL=llama-3.3-70b-versatile
# Option C: OpenAI
LLM_PROVIDER=openai
OPENAI_API_KEY=sk-your-key-from-platform.openai.com
# Option D: Anthropic
LLM_PROVIDER=anthropic
ANTHROPIC_API_KEY=sk-ant-your-key-from-console.anthropic.comMedia services (add whichever you run):
SONARR_URL=http://sonarr:8989 # or http://your-server-ip:8989
SONARR_API_KEY=your-sonarr-api-key
RADARR_URL=http://radarr:7878
RADARR_API_KEY=your-radarr-api-key
PLEX_URL=http://plex:32400
PLEX_TOKEN=your-plex-token
LIDARR_URL=http://lidarr:8686
LIDARR_API_KEY=your-lidarr-api-key
BAZARR_URL=http://bazarr:6767
BAZARR_API_KEY=your-bazarr-api-key
AUDIOBOOKSHELF_URL=http://audiobookshelf:13378
AUDIOBOOKSHELF_API_KEY=your-abs-token
READMEABOOK_URL=http://readmeabook:3030
READMEABOOK_API_KEY=your-readmeabook-tokenTip: If Arrmate and your services are on the same Docker network, use the container name as the hostname (e.g.
http://sonarr:8989). If they're on different machines or not in Docker, use the IP address or hostname instead.
How to find your API keys:
| Service | Where to find the key |
|---|---|
| Sonarr | Settings → General → API Key |
| Radarr | Settings → General → API Key |
| Lidarr | Settings → General → API Key |
| Bazarr | Settings → General → API Key |
| AudioBookshelf | Settings → Users → your user → API Token |
| ReadMeABook | Profile → API Tokens |
| Plex | Visit https://plex.tv/claim or open any item in Plex → ··· → Get Info → View XML → look for X-Plex-Token in the URL |
Open http://your-server-ip:8000. The default credentials are:
Username: admin
Password: changeme123
You'll be prompted to set a new password immediately. Do this before sharing the URL with anyone.
Go to Admin Panel (/web/admin) → Invite Links → create a link.
Pick a role for the family member:
| Role | What they can do |
|---|---|
| user | Type commands, browse library, submit requests for new content — can't delete anything |
| power_user | Everything a user can do, plus approve requests and execute commands directly |
| admin | Full access — settings, user management, everything |
Send them the invite link. They register their own account and can start typing immediately.
If you want to be notified when a family member submits a media request, add a Slack or Discord webhook to your .env:
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...
# or
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...You can also use the Settings → Notifications tab in the web UI.
Arrmate supports three built-in providers, plus any OpenAI-compatible API via the openai provider with a custom base URL.
| Provider | Set in .env | Notes |
|---|---|---|
| Ollama | LLM_PROVIDER=ollama |
Runs locally, free, private. qwen2.5:7b recommended. |
| OpenAI | LLM_PROVIDER=openai |
Requires OPENAI_API_KEY. Defaults to gpt-4o. |
| Anthropic | LLM_PROVIDER=anthropic |
Requires ANTHROPIC_API_KEY. Defaults to claude-sonnet-4-6. |
Any service with an OpenAI-compatible API works by setting LLM_PROVIDER=openai, a custom OPENAI_BASE_URL, and the matching OPENAI_MODEL. The model must support tool/function calling.
| Provider | OPENAI_BASE_URL |
Good models | Free tier |
|---|---|---|---|
| Groq | https://api.groq.com/openai/v1 |
llama-3.3-70b-versatile |
✅ Yes |
| OpenRouter | https://openrouter.ai/api/v1 |
meta-llama/llama-3.3-70b-instruct:free |
✅ Yes |
| Google Gemini | https://generativelanguage.googleapis.com/v1beta/openai/ |
gemini-2.0-flash |
✅ Yes |
| Mistral AI | https://api.mistral.ai/v1 |
mistral-small-latest |
❌ Paid |
| Together AI | https://api.together.xyz/v1 |
meta-llama/Llama-3.3-70B-Instruct-Turbo |
❌ Paid |
| xAI / Grok | https://api.x.ai/v1 |
grok-2-latest |
❌ Paid |
| LM Studio | http://host.docker.internal:1234/v1 |
(any tool-calling model) | ✅ Local |
| LocalAI | http://host.docker.internal:8080/v1 |
(any tool-calling model) | ✅ Local |
| Jan | http://host.docker.internal:1337/v1 |
(any tool-calling model) | ✅ Local |
You can also configure all of this through the Settings → AI / LLM tab in the web UI — it has a provider preset picker that auto-fills the base URL and suggests a good model for each service.
Important: The model must support tool/function calling. Not all models do. If commands aren't working, this is usually why — switch to one of the recommended models above.
- Plex SSO configurable from the UI — Enable/disable "Sign in with Plex" and set all SSO options directly from the Setup Wizard (Extras step) or Settings → Auth, without needing to edit environment variables or restart.
- Require admin approval for new Plex accounts — New
Require admin approvaloption creates new Plex users as disabled. They see a "pending approval" message and cannot access Arrmate until an admin enables their account in the Admin Panel. Admins receive an in-app notification when a new user registers. - Auto-approve Plex friends — New
Auto-approve Plex friendsoption: when approval is required, users who are already friends/shared users on your Plex Media Server are automatically approved on first sign-in (matched by Plex UUID via the plex.tv API). Requires Plex URL + Token to be configured.
- White page after session expiry on Plex page — When a session expired while the Plex "now playing" strip was polling (every 30s), HTMX would redirect to
/web/login?next=/web/plex/nowplaying— a partial HTML endpoint. After login, the user landed on the raw fragment, causing a blank page. Fixed by using theHX-Current-URLheader (the real page the user was viewing) instead of the partial path when building the post-login redirect. - Post-login redirect now uses the actual page URL — All auth dependencies (
require_auth,require_admin,require_power_user) now useHX-Current-URLfor HTMX-triggered redirects, preventing partial endpoints from polluting thenextURL.
- Fixed "No URL configured" on Test Connection — The test connection button was always returning "No URL configured" regardless of what you typed. HTMX sends form fields using their actual
nameattribute (e.g.sonarr_url), but the backend was only looking for a genericurlfield. The route now accepts both forms, so Test Connection works correctly. - URL format hints on every step — Each wizard step that has URL fields (LLM, Media Services, Download Clients, Extras) now shows an inline hint explaining the three URL formats:
http://servicename:port— same Docker network (use the container name)http://host.docker.internal:port— same machine, not in Dockerhttp://192.168.1.x:port— different machine on your network
- Password minimum length enforced server-side — The 8-character minimum is now checked in the database layer (
create_user,change_password), not just the frontend form. Previously a direct API call could bypass it. - Configurable session cookie
secureflag — The session cookie was previously always set withSecure=true, which caused silent authentication failures on HTTP-only deployments (no TLS reverse proxy). A newCOOKIE_SECUREenv var (defaulttrue) lets operators setCOOKIE_SECURE=falsefor HTTP-only setups. See.env.example. - Transcoding job store now pruned — The in-memory job store grew without bound on long-running instances. Completed/cancelled jobs are now evicted after 24 hours, with a hard cap of 100 entries.
- Startup warning for unconfigured
TRANSCODE_ALLOWED_ROOTS— When Sonarr/Radarr are configured butTRANSCODE_ALLOWED_ROOTSis not set, Arrmate now logs a warning at startup. Without this setting, the transcoder accepts any file path reported by those services.
Bug fix for users upgrading from versions prior to v2.0.0.
v2.0.0 introduced a non-root container user (arrmate) for security. However, data files created by older images (run as root) — users.db, services.json, plex_cache.db — were left with root ownership. When the new image started it could not write to those files, causing the login page to show a "first time setup" prompt and any registration attempt to fail with "An internal error occurred."
What changed:
docker/entrypoint.shnow useschown -R(recursive) instead ofchown, so it fixes ownership on all files inside/dataat every container start, not just the directory itself.- Both
docker-compose.ymlanddocker-compose.prod.ymlnow include anarrmate-initservice that runs before arrmate and ensures/datais fully owned by thearrmateuser. This covers users on older images where the entrypoint did not do a recursive chown.
If you were affected: Pull the new image and recreate the container — the init service will fix ownership automatically. No manual intervention needed.
This release is a major security overhaul informed by a full SDL (Security Development Lifecycle) review, plus three new features and a bug fix.
Authentication & Sessions
- Session cookie
secureflag — the session cookie was missingsecure=True, allowing it to be sent over plain HTTP. Now enforced. - Default password removed from logs — the first-boot admin account no longer logs
changeme123in plaintext at INFO level. - Minimum password length 4 → 8 — both the registration and change-password forms now require at least 8 characters.
- 500 errors no longer leak internals — unhandled exceptions in the command executor previously returned raw Python exception messages. The response is now a generic "An internal error occurred" and full detail is written to server logs only.
- Command input length capped at 2000 characters — oversized requests are rejected at the API layer (HTTP 422) before reaching the LLM.
Docker & Infrastructure
- Non-root container user — the Docker image now creates a dedicated
arrmatesystem user and drops privileges viasu-execbefore starting the app. The container no longer runs as root. - Ollama port not exposed to host —
11434was previously bound to0.0.0.0in all compose files, making the unauthenticated Ollama API accessible on the host network. Port binding removed; Ollama is now reachable only within the internal Docker network.
File System
- Transcoder path validation — the H.265 transcoder now validates that the target file is within a configured allowlist (
TRANSCODE_ALLOWED_ROOTS). Path traversal sequences (../../etc/passwd) are blocked viaPath.resolve()before comparison. WhenTRANSCODE_ALLOWED_ROOTSis empty the old behaviour is preserved for backward compatibility.
Static Analysis Results (Bandit + Safety)
- 0 high-severity findings
- 1 medium finding resolved:
update_user()SQL dynamic column names are now explicitly documented as safe (keys filtered to an allowlist before interpolation; values always parameterized) fastapipinned to>=0.115.0to close CVE-2024-24762 (path-traversal) and a ReDoS fixed in earlier minor versions- 43 low-severity
try/except/passnotices — all intentional (service discovery fallbacks, DB migration idempotency); no code changes required
Sign in with Plex (SSO)
- Users can now authenticate with their Plex account via the standard Plex PIN-based OAuth flow.
- Off by default — enable with
PLEX_SSO_ENABLED=true. - Security design: the Plex
authTokenis never stored; it is used once to fetch the user's Plex UUID then discarded. State is carried via a short-lived (5-min) signed cookie to prevent CSRF. New Plex users are created withrole=userand cannot log in with a local password. - Optional email allowlist:
[email protected],[email protected] - New settings:
PLEX_SSO_ENABLED,PLEX_SSO_DEFAULT_ROLE,PLEX_SSO_ALLOWED_EMAILS,ARRMATE_BASE_URL
Login Rate Limiting
- Both
POST /web/loginandPOST /api/v1/auth/tokenare now rate-limited to 10 attempts per IP per 60-second window. Exceeding the limit returns HTTP 429 with aRetry-Afterheader. No external dependencies — uses an in-memory fixed-window counter.
Setup Wizard
- First-time setup is now guided. After the admin changes the default password, they are taken to a step-by-step wizard: LLM provider → Media services → Download clients → Extras → Done.
- Each service has a live "Test connection" button (no page reload).
- Skip at any time; re-run any time from the Admin panel ("🧙 Run Setup Wizard").
- Setup completion is tracked in the database so the wizard only triggers automatically once.
Download Status Notifications
- Arrmate now polls Sonarr and Radarr every 5 minutes in the background.
- When a requested title enters the download queue, the requesting user receives an in-app "⬇️ Download started" notification.
- When the file is imported into the library, they receive a "🎉 Ready in your library!" notification and the request is automatically closed.
- Both events also fire Slack/Discord webhooks if configured.
- Notifications page blank after session expiry — if a session expired while on the notifications panel, re-logging-in redirected back to
/web/notificationswhich returned a bare HTML fragment, appearing completely blank. Fixed: direct navigation to HTMX-only routes now redirects to/web/.
A SECURITY.md has been added with our vulnerability reporting process (GitHub Security Advisories), response timelines, and production hardening recommendations.
- Search — "In Library" status: Results from the Search tab now cross-reference your Sonarr/Radarr library. Items you already own show a green ✓ In Library badge on the poster and title, and the add button is replaced with a non-clickable indicator so you can't accidentally re-add something.
- Visible 500 errors on commands: Server errors no longer silently leave the result area blank. The actual error message and traceback are shown inline so you can see what went wrong.
- Settings UI cleanup: LLM provider dropdown now groups options into Local, Free Cloud, and Paid Cloud sections. Media and Downloads tabs group related services under labelled sections (TV & Movies, Music & Subtitles, Books & Audiobooks, Usenet/NZB, Torrents).
- Fixed HTTP 400 when adding shows via commands: The command executor now uses the full Sonarr lookup result as the POST body, matching the fix applied to Discover in v0.9.2.
- Discover tab — "In Library" badge for TV shows: Shows already in Sonarr now correctly display the green ✓ In Library badge and hide the add button, matching the existing movie behaviour.
- Discover tab — fixed HTTP 400 on add: Adding TV shows from Discover now uses the full Sonarr lookup result as the POST body, so all required fields (
titleSlug,seriesType,languageProfileId, seasons, etc.) are always present. - Better error messages on add: If a show or movie is already in your library and the add is rejected, the UI now correctly reads the response body and shows "already in your library" instead of a raw HTTP error.
- EST air times on Upcoming calendar: Each scheduled episode now shows its air time in Eastern time, with a note that new episodes usually take 10–60 minutes to appear after airing.
- Improved error handling on commands: Errors are now categorised (not found, already exists, connection issue, etc.) with friendly messages, a pre-filled retry input, and contextual quick-action buttons.
- OpenAI-compatible provider preset picker: The Settings → AI/LLM tab now has a dropdown to auto-fill the base URL and model for Groq, OpenRouter, LM Studio, Google Gemini, and more.
- Expanded LLM provider support — any OpenAI-compatible API works.
Arrmate understands plain English — including the way real people talk:
- Add content: "put Yellowstone on my list", "can you get me Dune?", "add the Breaking Bad audiobook"
- Fix problems: "the sound is off on Interstellar", "Succession keeps freezing", "the picture on The Bear is all green"
- Browse: "what shows do I have?", "what can I watch tonight?", "what movies are downloading?"
- Subtitles: "get English subtitles for Parasite", "the subtitles are out of sync on The Wire"
- Quality: "get a better copy of Inception", "upgrade The Matrix to 4K"
- Maintenance: "clean up Plex", "backup the Plex database", "rescan Breaking Bad"
Three roles keep everything safe:
- admin — full access to everything including settings and user management
- power_user — can execute commands and approve media requests; no settings access
- user — browse library and submit requests; nothing gets deleted without admin approval
Family members get user role by default. They can request content, and you'll get notified. No one can accidentally wipe your library.
- Family member submits a request: "can you add Outlander?"
- Admin/power_user gets notified (Slack/Discord/in-app)
- They approve it — Arrmate adds it automatically
- The family member sees it appear in their library
"what's downloading right now?" → shows active downloads with progress
"what was downloaded recently?" → shows recent import history
"what episodes am I missing?" → shows monitored but undownloaded content
"what's playing on Plex?" → active streams
"rate The Matrix 5 stars" → rate items
"mark Succession as watched" → update watch status
"refresh Plex metadata for The Wire" → refresh artwork/info
"clean up Plex" → run Butler maintenance
"empty Plex trash" → clear deleted items
Arrmate can scan your library for files not already in H.265 and convert them in the background to save disk space:
"convert all my movies to H265"
"transcode Breaking Bad to save space"
"convert all TV shows to HEVC"
Track progress at /web/transcode. Requires media files to be accessible inside the container — see volume mount comments in the compose file.
"download English subtitles for Parasite"
"sync subtitles for Breaking Bad season 3"
"what shows are missing subtitles?"
"stop monitoring The Office" → won't download new episodes
"monitor Breaking Bad season 3" → re-enable a specific season
"unmonitor Inception" → pause movie monitoring
"rename Breaking Bad files" → rename to match your naming convention
"rescan disk for The Sopranos" → pick up manually added files
Fresh installs create a default admin account:
Username: admin
Password: changeme123
You'll be prompted to change this on first login.
Invite additional users from the Admin Panel (/web/admin) → Invite Links. Send them the link — they register themselves with the role you chose.
Sessions persist across restarts when SECRET_KEY is set in .env. To generate one:
python3 -c "import secrets; print(secrets.token_hex(32))"Locked out? Delete /data/users.db and restart — the default admin account will be recreated.
Set up Slack or Discord webhooks to get notified when family members submit media requests:
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T.../B.../...
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/.../...Or configure them in Settings → Notifications in the web UI.
The REST API lives at /api/v1/. Interactive docs: http://localhost:8000/docs.
# Execute a command
curl -X POST http://localhost:8000/api/v1/execute \
-H "Content-Type: application/json" \
-d '{"command": "list my TV shows"}'
# With auth
curl -u username:password http://localhost:8000/api/v1/servicesgit clone https://github.com/TechButton/Arrmate.git
cd Arrmate
pip install -e ".[dev]"
cp .env.example .env
# edit .env
python -m arrmate.interfaces.api.appFull local stack with Sonarr + Radarr + Ollama:
docker compose -f docker-compose.full.yml up -d- Docker Hub
- GitHub
- Issues
- Buy Me a Coffee — if Arrmate saves you time, consider supporting development
MIT




