lwvwv/automation/README.md

284 lines
9.7 KiB
Markdown

# LWVWV Member Subscription Automation
Part of the [LWVWV project](../). This directory contains Docker-based automation for subscribing LWVWV members to Mailman 3 mailing lists via the [mailman-connector](https://git.bikeshopi.dev/bike/mailman-connector) API service.
For standalone script usage (without Docker), see the [main project README](../README.md).
## Overview
This automation coordinates:
1. **Downloading** the latest member roster from the LWVWV portal
2. **Converting** the roster to CSV format (email,first_name,last_name)
3. **Subscribing** members to the Mailman list via the connector API
## Architecture
```
┌─────────┐ ┌─────────────────────────────┐ ┌──────────────────┐
│ Ofelia │────>│ lwvwv-subscriber-cron │────>│ LWVWV Portal │
│Scheduler│cron │ (Docker Container) │ │ (download CSV) │
└─────────┘ └─────────────────────────────┘ └──────────────────┘
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│download- │ │google-civic- │ │subscribe- │
│roster.js │──>│api.pl │──>│members.sh │
│(portal) │ │(email-csv) │ │(connector) │
└──────────┘ └──────────────┘ └──────────────┘
┌──────────────────────────────────┐
│ [mailman-connector](https://git. │
│ bikeshopi.dev/bike/mailman- │
│ connector) - REST API for batch │
│ Mailman 3 subscriptions │
└──────────────────────────────────┘
```
## Files
| File | Description |
|------|-------------|
| `Dockerfile` | Container image definition |
| `docker-compose.yml` | Service orchestration with Ofelia labels |
| `entrypoint.sh` | Parses ../env and exports variables |
| `run-subscription.sh` | Master orchestration script |
| `start-automation.sh` | Quick-start helper script |
| `README.md` | This file |
## Environmental Variables
### Single Source of Truth: `../env`
All configuration is stored in **one file**: `../env` (in the parent directory)
This file uses **Perl-style syntax** (which existing scripts expect):
```perl
$VARIABLE_NAME = "value";
```
### How It Works
1. **`../env`** is the **single source of truth** for all configuration
2. The **entrypoint.sh** script parses `../env` and exports variables as environment variables
3. All scripts can then access them via standard environment variable syntax (`$VARIABLE_NAME`)
### Configuration Variables
#### Required for Subscription
| Variable | Description | Example |
|----------|-------------|---------|
| `$CONNECTOR_URL` | URL of mailman-connector | `https://mailman-connector.example.org` |
| `$CONNECTOR_PASSWORD` | Secret password for connector API | `your_secret_password` |
| `$MAILMAN_LIST_ID` | Target mailing list ID(s). Format: `list_id:league_id` or just `list_id`. For multiple lists, use comma-separated values. | `members.lists.lwvwv.org:WV000` or `list1:WV000, list2:WV103` |
#### Required for Portal Download
| Variable | Description | Example |
|----------|-------------|---------|
| `$PORTAL_URL` | LWVWV portal URL | `https://portal.lwv.org` |
| `$MEMBERSHIP_URL` | Membership page URL | `https://portal.lwv.org/groups/...` |
#### Email Notifications (Optional)
| Variable | Description | Example |
|----------|-------------|---------|
| `$SMTP_HOST` | SMTP server | `mail.example.org` |
| `$SMTP_PORT` | SMTP port | `587` |
| `$SMTP_USER` | SMTP username | `example@example.org` |
| `$SMTP_PASSWORD` | SMTP password | `your_smtp_password` |
| `$EMAIL_FROM` | Sender address | `example@example.org` |
| `$EMAIL_TO` | Recipient for alerts | `hello@example.org` |
#### Google API (for district lookup)
| Variable | Description | Example |
|----------|-------------|---------|
| `$key` | Google Civic API key | `AIzaSy...` |
| `$STATE` | State code | `WV` |
| `$stateLeagueID` | State league ID | `WV000` |
| `$localLeagueIDs` | Local league IDs | `WV102|WV103|WV112` |
### Example `../env` File
```perl
# Portal Configuration
$PORTAL_URL = "https://portal.lwv.org";
$MEMBERSHIP_URL = "https://portal.lwv.org/groups/53a93df1/league_membership_state_view";
# Connector Settings
$CONNECTOR_URL = "https://mailman-connector.example.org";
$CONNECTOR_PASSWORD = "your_secret_password";
$MAILMAN_LIST_ID = "members.lists.example.org";
# Email Notification Settings
$SMTP_HOST = "mail.example.org";
$SMTP_PORT = "587";
$SMTP_USER = "example@example.org";
$SMTP_PASSWORD = "your_smtp_password";
$EMAIL_FROM = "example@example.org";
$EMAIL_TO = "hello@example.org";
# Google API Configuration
$key = "LALKDdkdk_12LSL_RKFDL";
$STATE = "WV";
$stateLeagueID = "WV000";
$localLeagueIDs = "WV102|WV103|WV112";
```
### Multi-List Configuration with League Filtering
When managing multiple local leagues, you can subscribe different member groups to different lists using the colon-separated format:
```perl
# Format: list_id:league_id
# State list gets WV000 members, Morgantown list gets WV103 members
$MAILMAN_LIST_ID = "members.lists.lwvwv.org:WV000, morgantown.lists.lwvwv.org:WV103";
```
**How it works:**
- **list_id**: The Mailman list identifier (e.g., `members.lists.lwvwv.org`)
- **league_id**: The LWV League ID to filter members (e.g., `WV000`, `WV103`, or `ALL`)
- If `:league_id` is omitted, defaults to `ALL` (all members)
**Examples:**
```perl
# Single list, all members
$MAILMAN_LIST_ID = "members.lists.lwvwv.org";
# Single list, only state (WV000) members
$MAILMAN_LIST_ID = "members.lists.lwvwv.org:WV000";
# Multiple lists with different league filters
$MAILMAN_LIST_ID = "members.lists.lwvwv.org:WV000, morgantown.lists.lwvwv.org:WV103, huntington.lists.lwvwv.org:WV102";
```
The script filters the roster by League ID before subscribing, so each list only receives its members.
## Quick Start
### 1. Configure Environment
Ensure `../env` exists with your settings:
```bash
cd automation
# The env file should already exist in the parent directory
ls ../env
```
### 2. Build and Start
```bash
docker-compose up -d
```
### 3. Verify Container is Running
```bash
docker ps | grep lwvwv-subscriber
```
## Usage
### Manual Trigger (for testing)
```bash
# Run the full workflow (downloads roster, converts, subscribes)
docker exec lwvwv-subscriber /app/run-subscription.sh
# With a specific CSV file (skips download)
docker exec lwvwv-subscriber /app/run-subscription.sh /path/to/members.csv
# Run subscribe-members.sh directly with env vars
docker exec lwvwv-subscriber /app/subscribe-members.sh /tmp/rosters/members.csv
```
### Scheduled Execution (via Ofelia)
The container includes Ofelia labels for automatic scheduling:
- **Default**: 4x daily at 00:00, 06:00, 12:00, 18:00
- **Format**: Cron expression `0 0,6,12,18 * * *`
To change schedule, edit `docker-compose.yml`:
```yaml
labels:
ofelia.enabled: "true"
ofelia.job-exec.lwvwv-subscribe.schedule: "0 0,6,12,18 * * *"
```
### Standalone Usage (without Docker)
The scripts also work outside Docker using positional arguments:
```bash
# subscribe-members.sh with all positional args
./subscribe-members.sh members.csv mypassword members.lists.example.org https://connector.example.com
# With some env vars, some positional
CONNECTOR_PASSWORD=mypassword ./subscribe-members.sh members.csv
```
## Logs
```bash
# View container logs
docker logs lwvwv-subscriber
# Follow logs in real-time
docker logs -f lwvwv-subscriber
# View Ofelia scheduler logs
docker logs ofelia
```
## Troubleshooting
### Session Expired
If portal download fails with "Session expired":
```bash
# Run save-session.js on host to refresh session
cd ..
node save-session.js
# Then restart container
docker-compose restart
```
### Missing Environment Variables
If you see errors like `CONNECTOR_URL not set`:
1. Check that `../env` exists and contains the variable
2. Verify the entrypoint is parsing it correctly:
```bash
docker exec lwvwv-subscriber env | grep CONNECTOR
```
### Manual CSV Subscription
To subscribe members without downloading:
```bash
# Place CSV file in shared volume
cp members.csv automation/rosters/
# Run with specific file
docker exec lwvwv-subscriber /app/run-subscription.sh /tmp/rosters/members.csv
```
## Network Requirements
The container needs access to:
- LWV Portal (`https://portal.lwv.org`)
- SMTP server (if using email alerts)
## Security Notes
- `../env` is mounted **read-only** (`:ro`) in the container
- Sensitive data is never committed to git
- Session file (`.session.json`) is also mounted read-only
- All scripts use the single `../env` file - no duplicated configuration
## See Also
- **[Main Project README](../README.md)** - Standalone script usage, Google Civic API documentation, and general project information
- **[mailman-connector](https://git.bikeshopi.dev/bike/mailman-connector)** - The REST API service used for batch Mailman 3 subscriptions