Add direct Slack bot path for learners
- learner-add.sh: add users to learners group, source Slack env - New design doc comparing direct Slack vs maubot/Matrix approach 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
29ce3a9fa5
commit
0ad7ca7b98
219
docs/learner-slack-direct.md
Normal file
219
docs/learner-slack-direct.md
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
# Direct Slack Bot Development for Learners
|
||||
|
||||
Design doc for the "direct to Slack" learner path, bypassing Matrix/maubot.
|
||||
|
||||
## Overview
|
||||
|
||||
Learners 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 stored in `/etc/slack-learner.env`:
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `SLACK_BOT_TOKEN` | Bot identity (xoxb-...) |
|
||||
| `SLACK_APP_TOKEN` | Socket Mode connection (xapp-...) |
|
||||
|
||||
These come from the existing mautrix-slack bridge login (Chochacho workspace, vlad's account).
|
||||
|
||||
### Access Control
|
||||
|
||||
- File owned by `root:learners`, mode `640`
|
||||
- Learner users added to `learners` group on creation
|
||||
- `.bashrc` sources the env file on login
|
||||
|
||||
### Scripts
|
||||
|
||||
```
|
||||
/usr/local/bin/learner-add.sh <username> '<ssh-pubkey>'
|
||||
/usr/local/bin/learner-remove.sh <username> [--archive]
|
||||
```
|
||||
|
||||
## Learner Workflow
|
||||
|
||||
### 1. Get Access
|
||||
|
||||
Send SSH pubkey to admin. Admin runs:
|
||||
```bash
|
||||
ssh root@ops-jrz1 'learner-add.sh alice "ssh-ed25519 AAAA..."'
|
||||
```
|
||||
|
||||
### 2. Connect
|
||||
|
||||
```bash
|
||||
ssh alice@<server-ip>
|
||||
```
|
||||
|
||||
Tokens are available immediately:
|
||||
```bash
|
||||
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
|
||||
|
||||
app = App(token=os.environ["SLACK_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, os.environ["SLACK_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 learners use same bot identity
|
||||
2. **Token exposure** - If leaked, affects everyone
|
||||
3. **Rate limits** - Shared across all bots
|
||||
4. **Process sprawl** - N learners = 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 env vars, not code
|
||||
- Can rotate tokens if leaked (re-login bridge)
|
||||
|
||||
## Future Improvements
|
||||
|
||||
### Process Management
|
||||
|
||||
Options:
|
||||
1. **systemd user services** - Each learner manages their own
|
||||
2. **supervisor** - Central process manager
|
||||
3. **tmux/screen** - Manual but simple
|
||||
4. **Container per learner** - 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-Learner Bot Identity
|
||||
|
||||
Create separate Slack App per learner:
|
||||
- More setup friction
|
||||
- Full isolation
|
||||
- Each bot has own name/avatar
|
||||
|
||||
### Test Channel
|
||||
|
||||
Create `#learner-sandbox` in Slack:
|
||||
- All learner 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)
|
||||
|
|
@ -68,6 +68,9 @@ create_user() {
|
|||
# NixOS: don't specify shell (uses default), group is 'users'
|
||||
useradd -m -g users "$username"
|
||||
|
||||
# Add to learners group for Slack token access
|
||||
usermod -aG learners "$username"
|
||||
|
||||
# Set up SSH key
|
||||
local ssh_dir="/home/$username/.ssh"
|
||||
mkdir -p "$ssh_dir"
|
||||
|
|
@ -76,6 +79,11 @@ create_user() {
|
|||
chmod 600 "$ssh_dir/authorized_keys"
|
||||
chown -R "$username:users" "$ssh_dir"
|
||||
|
||||
# Add Slack env vars to bashrc
|
||||
echo '' >> "/home/$username/.bashrc"
|
||||
echo '# Slack bot development tokens' >> "/home/$username/.bashrc"
|
||||
echo 'source /etc/slack-learner.env' >> "/home/$username/.bashrc"
|
||||
|
||||
log_info "User created with SSH access"
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue