- Remove beads from VPS deployment (kept locally for dev workflow) - Add slack-bot-token and slack-app-token secrets with devs group access - Remove dead acme-email secret reference - Increase egress limits from 30/min to 150/min (burst 60→300) - Change egress blocking from REJECT to DROP for better app behavior - Add egress-status script for user self-diagnosis - Update dev-slack-direct.md with new /run/secrets access patterns 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
235 lines
6.2 KiB
Markdown
235 lines
6.2 KiB
Markdown
# Direct Slack Bot Development for Devs
|
|
|
|
Design doc for the "direct to Slack" dev path, bypassing Matrix/maubot.
|
|
|
|
## Overview
|
|
|
|
Devs write Python bots using `slack-bolt` that connect directly to Slack via Socket Mode. No Matrix, no bridge, no maubot.
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ VPS (ops-jrz1) │
|
|
│ │
|
|
│ alice's bot.py ──┐ │
|
|
│ bob's bot.py ────┼── Socket Mode ─────────► Slack API
|
|
│ carol's bot.py ──┘ (WebSocket) │
|
|
│ │
|
|
└─────────────────────────────────────────┘
|
|
```
|
|
|
|
## Current Setup
|
|
|
|
### Credentials
|
|
|
|
Shared Slack App tokens managed via sops-nix, deployed to `/run/secrets/`:
|
|
|
|
| Secret File | Purpose |
|
|
|-------------|---------|
|
|
| `/run/secrets/slack-bot-token` | Bot identity (xoxb-...) |
|
|
| `/run/secrets/slack-app-token` | Socket Mode connection (xapp-...) |
|
|
|
|
These come from the existing mautrix-slack bridge login (Chochacho workspace, vlad's account).
|
|
|
|
### Access Control
|
|
|
|
- Files owned by `root:devs`, mode `0440`
|
|
- Dev users added to `devs` group on creation
|
|
- Secrets encrypted in git, decrypted at boot via sops-nix
|
|
|
|
### Scripts
|
|
|
|
```
|
|
/usr/local/bin/dev-add.sh <username> '<ssh-pubkey>'
|
|
/usr/local/bin/dev-remove.sh <username> [--archive]
|
|
```
|
|
|
|
## Dev Workflow
|
|
|
|
### 1. Get Access
|
|
|
|
Send SSH pubkey to admin. Admin runs:
|
|
```bash
|
|
ssh root@ops-jrz1 'dev-add.sh alice "ssh-ed25519 AAAA..."'
|
|
```
|
|
|
|
### 2. Connect
|
|
|
|
```bash
|
|
ssh alice@<server-ip>
|
|
```
|
|
|
|
Load tokens from secrets:
|
|
```bash
|
|
export SLACK_BOT_TOKEN=$(cat /run/secrets/slack-bot-token)
|
|
export SLACK_APP_TOKEN=$(cat /run/secrets/slack-app-token)
|
|
|
|
# Verify they're set
|
|
echo $SLACK_BOT_TOKEN # xoxb-...
|
|
echo $SLACK_APP_TOKEN # xapp-...
|
|
```
|
|
|
|
### 3. Write a Bot
|
|
|
|
```python
|
|
# ~/bot.py
|
|
from slack_bolt import App
|
|
from slack_bolt.adapter.socket_mode import SocketModeHandler
|
|
import os
|
|
|
|
# Read tokens from sops-nix secrets (or fall back to env vars)
|
|
def read_secret(path, env_fallback):
|
|
try:
|
|
with open(path) as f:
|
|
return f.read().strip()
|
|
except FileNotFoundError:
|
|
return os.environ.get(env_fallback, "")
|
|
|
|
BOT_TOKEN = read_secret("/run/secrets/slack-bot-token", "SLACK_BOT_TOKEN")
|
|
APP_TOKEN = read_secret("/run/secrets/slack-app-token", "SLACK_APP_TOKEN")
|
|
|
|
app = App(token=BOT_TOKEN)
|
|
|
|
@app.message("hello")
|
|
def hello(message, say):
|
|
say(f"Hey <@{message['user']}>!")
|
|
|
|
@app.message("dice")
|
|
def dice(message, say):
|
|
import random
|
|
say(f"You rolled a {random.randint(1, 6)}!")
|
|
|
|
if __name__ == "__main__":
|
|
print("Bot starting...")
|
|
SocketModeHandler(app, APP_TOKEN).start()
|
|
```
|
|
|
|
### 4. Run It
|
|
|
|
```bash
|
|
pip install slack-bolt
|
|
python bot.py
|
|
```
|
|
|
|
Bot responds to "hello" and "dice" in Slack.
|
|
|
|
## slack-bolt Basics
|
|
|
|
### Event Handlers
|
|
|
|
```python
|
|
# Match exact text
|
|
@app.message("hello")
|
|
def handle_hello(message, say):
|
|
say("Hello!")
|
|
|
|
# Match regex
|
|
@app.message(re.compile(r"^roll (\d+)d(\d+)"))
|
|
def handle_roll(message, say, context):
|
|
# context["matches"] contains regex groups
|
|
pass
|
|
|
|
# Match any message
|
|
@app.event("message")
|
|
def handle_all(event, say):
|
|
pass
|
|
|
|
# React to app mentions (@bot)
|
|
@app.event("app_mention")
|
|
def handle_mention(event, say):
|
|
say("You mentioned me!")
|
|
```
|
|
|
|
### Slash Commands
|
|
|
|
Requires additional Slack App config (not set up yet):
|
|
```python
|
|
@app.command("/mycommand")
|
|
def handle_command(ack, respond, command):
|
|
ack() # Must acknowledge within 3 seconds
|
|
respond(f"You said: {command['text']}")
|
|
```
|
|
|
|
### Sending Messages
|
|
|
|
```python
|
|
# Reply in thread
|
|
say("Reply!", thread_ts=message["ts"])
|
|
|
|
# Send to specific channel
|
|
app.client.chat_postMessage(channel="C1234567", text="Hello")
|
|
|
|
# Rich formatting (blocks)
|
|
say(blocks=[
|
|
{"type": "section", "text": {"type": "mrkdwn", "text": "*Bold* and _italic_"}}
|
|
])
|
|
```
|
|
|
|
## Tradeoffs
|
|
|
|
### vs Maubot/Matrix Path
|
|
|
|
| Aspect | Direct Slack | Maubot/Matrix |
|
|
|--------|--------------|---------------|
|
|
| **Setup complexity** | Lower - just Python | Higher - Matrix + bridge + maubot |
|
|
| **Feedback loop** | Fast - direct to Slack | Slower - goes through bridge |
|
|
| **Mental model** | Simple - just Slack | Complex - Matrix concepts |
|
|
| **Documentation** | Excellent (Bolt is popular) | Sparse (maubot is niche) |
|
|
| **Process management** | Manual (you run it) | Managed (maubot lifecycle) |
|
|
| **Bot identity** | Shared (all bots = same user) | Per-instance configurable |
|
|
| **Crash recovery** | Manual restart | Auto-managed |
|
|
| **Production path** | Direct - same code works | Need to rewrite for Slack-only |
|
|
|
|
### Risks
|
|
|
|
1. **Shared tokens** - All devs use same bot identity
|
|
2. **Token exposure** - If leaked, affects everyone
|
|
3. **Rate limits** - Shared across all bots
|
|
4. **Process sprawl** - N devs = N processes to manage
|
|
5. **No guardrails** - Bad code can spam/crash easily
|
|
|
|
### Mitigations
|
|
|
|
- Dedicated test channel in Slack
|
|
- Supervisor/systemd for process management (not yet set up)
|
|
- Tokens in sops-nix secrets, encrypted in git, decrypted only at runtime
|
|
- Can rotate tokens if leaked (re-login bridge, update secrets.yaml)
|
|
|
|
## Future Improvements
|
|
|
|
### Process Management
|
|
|
|
Options:
|
|
1. **systemd user services** - Each dev manages their own
|
|
2. **supervisor** - Central process manager
|
|
3. **tmux/screen** - Manual but simple
|
|
4. **Container per dev** - Isolation but complex
|
|
|
|
### Starter Template
|
|
|
|
Create `~/slack-bot-template/` with:
|
|
- `bot.py` - Hello world
|
|
- `requirements.txt` - slack-bolt pinned
|
|
- `Makefile` - run, install targets
|
|
- `README.md` - Quick start
|
|
|
|
### Per-Dev Bot Identity
|
|
|
|
Create separate Slack App per dev:
|
|
- More setup friction
|
|
- Full isolation
|
|
- Each bot has own name/avatar
|
|
|
|
### Test Channel
|
|
|
|
Create `#dev-sandbox` in Slack:
|
|
- All dev bots invited
|
|
- Safe place to spam
|
|
- Doesn't pollute real channels
|
|
|
|
## References
|
|
|
|
- [Bolt for Python - Getting Started](https://slack.dev/bolt-python/tutorial/getting-started)
|
|
- [Slack API - Socket Mode](https://api.slack.com/apis/connections/socket)
|
|
- [Slack API - Events](https://api.slack.com/events)
|
|
- [slack-bolt GitHub](https://github.com/slackapi/bolt-python)
|