Mastodon bot that posts lines from Snoopy's novel and other writings from the Peanuts comic strip. Runs as an Azure Container Apps Job on a daily schedule.
- Go 1.22+
- Azure Developer CLI (azd)
- Docker (for building the container image)
- An existing Azure Container Registry
- A Mastodon account and access token
Fill in your credentials in .env:
| Variable | Description |
|---|---|
MASTODON_SERVER |
Your Mastodon instance URL (e.g. https://mastodon.social) |
MASTODON_ACCESS_TOKEN |
Access token with write:statuses scope |
AZURE_STORAGE_ACCOUNT |
Azure Storage account name |
AZURE_STORAGE_ACCESS_KEY |
Azure Storage account key |
DRY_RUN |
Set to true to print the post without sending it |
To get a Mastodon access token: go to your instance → Preferences → Development → New Application → enable write:statuses → copy the access token.
Run locally (dry run by default):
go run .To fire a real post, set DRY_RUN=false in .env before running.
main.go # Entry point: validates env vars, calls bot.DoWork()
internal/
bot/
data.go # Novel lines and miscellaneous quotes
bot.go # DoWork(): coin flip, post novel or misc quote
mastodon/
client.go # Mastodon API client (POST /api/v1/statuses)
storage/
state.go # Azure Tables: tracks current novel line index
Dockerfile # Multi-stage build → scratch-based image (~8 MB)
azure.yaml # Azure Developer CLI template definition
infra/
main.bicep # Subscription-scope: resource group + module
resources.bicep # All Azure resources (storage, container apps env, job)
main.parameters.bicepparam # Reads values from azd environment
scripts/
postprovision.sh # Hook: updates .env and prints GitHub secret commands
postprovision.ps1 # Same, for Windows/PowerShell
.github/
workflows/
deploy.yml # CI/CD: build image → push to ACR → update job
Infrastructure is managed with the Azure Developer CLI. A single azd provision creates the resource group, storage account, Container Apps Environment, and Container Apps Job.
azd handles AZURE_ENV_NAME and AZURE_LOCATION automatically — it will prompt for the location on first run and remember it. Set the remaining values before provisioning:
# Existing Azure Container Registry
azd env set AZURE_CONTAINER_REGISTRY_LOGIN_SERVER <registry>.azurecr.io
azd env set AZURE_CONTAINER_REGISTRY_USERNAME <acr-admin-username>
azd env set AZURE_CONTAINER_REGISTRY_PASSWORD <acr-admin-password>
# Mastodon credentials
azd env set MASTODON_SERVER https://mastodon.social
azd env set MASTODON_ACCESS_TOKEN <value>ACR admin credentials are found in the Azure Portal under your registry → Access keys. Enable the admin user if it is not already on.
azd provisionThis creates the following resources inside a new rg-<env> resource group:
| Resource | Purpose |
|---|---|
| Storage Account | Hosts the state table tracking the novel line index |
| Log Analytics Workspace | Container Apps structured logging |
| Container Apps Environment | Shared environment for the job |
| Container Apps Job | Runs daily at 5:00 PM UTC; 0.25 vCPU / 0.5 GiB |
After provisioning, the postprovision hook automatically updates AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_ACCESS_KEY in your local .env file. It also prints the gh secret set commands needed for CI/CD.
The deployment workflow (.github/workflows/deploy.yml) requires these repository secrets:
| Secret | Where to get it |
|---|---|
ACR_LOGIN_SERVER |
Your ACR login server (e.g. myregistry.azurecr.io) |
ACR_USERNAME |
ACR admin username |
ACR_PASSWORD |
ACR admin password |
AZURE_CREDENTIALS |
Service principal JSON — see below |
AZURE_RG |
Printed by postprovision hook |
CONTAINERAPPS_JOB_NAME |
Printed by postprovision hook |
Create the AZURE_CREDENTIALS service principal:
az ad sp create-for-rbac \
--name snoopybot-deploy \
--role Contributor \
--scopes /subscriptions/<subscription-id>/resourceGroups/rg-<env> \
--sdk-authCopy the JSON output as the AZURE_CREDENTIALS secret.
Push to master to trigger the GitHub Actions workflow, which builds the Docker image, pushes it to ACR, and updates the Container Apps Job. To manually trigger a job execution after deploying:
az containerapp job start --name <job-name> --resource-group rg-<env>GitHub push → Actions workflow → docker build → ACR → Container Apps Job
│
(daily 5 PM UTC cron)
│
Go binary runs
┌──────┴──────┐
50% novel 50% misc
│
Azure Table Storage
(tracks novel index)
│
Mastodon API v1
(posts status)