-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
22 changed files
with
6,101 additions
and
130 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
name: Format | ||
|
||
on: | ||
pull_request: | ||
branches-ignore: | ||
- 'spike/**' | ||
|
||
# cancels in-progress jobs on this pull request | ||
# avoids wasted work when a new commit is pushed | ||
concurrency: | ||
group: format-${{ github.head_ref || github.run_id }} | ||
cancel-in-progress: true | ||
|
||
permissions: read-all | ||
|
||
jobs: | ||
formatting: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
with: | ||
fetch-depth: 0 | ||
|
||
- name: Run formatter | ||
run: npm run lint |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,130 +1,2 @@ | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
lerna-debug.log* | ||
.pnpm-debug.log* | ||
|
||
# Diagnostic reports (https://nodejs.org/api/report.html) | ||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json | ||
|
||
# Runtime data | ||
pids | ||
*.pid | ||
*.seed | ||
*.pid.lock | ||
|
||
# Directory for instrumented libs generated by jscoverage/JSCover | ||
lib-cov | ||
|
||
# Coverage directory used by tools like istanbul | ||
coverage | ||
*.lcov | ||
|
||
# nyc test coverage | ||
.nyc_output | ||
|
||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) | ||
.grunt | ||
|
||
# Bower dependency directory (https://bower.io/) | ||
bower_components | ||
|
||
# node-waf configuration | ||
.lock-wscript | ||
|
||
# Compiled binary addons (https://nodejs.org/api/addons.html) | ||
build/Release | ||
|
||
# Dependency directories | ||
node_modules/ | ||
jspm_packages/ | ||
|
||
# Snowpack dependency directory (https://snowpack.dev/) | ||
web_modules/ | ||
|
||
# TypeScript cache | ||
*.tsbuildinfo | ||
|
||
# Optional npm cache directory | ||
.npm | ||
|
||
# Optional eslint cache | ||
.eslintcache | ||
|
||
# Optional stylelint cache | ||
.stylelintcache | ||
|
||
# Microbundle cache | ||
.rpt2_cache/ | ||
.rts2_cache_cjs/ | ||
.rts2_cache_es/ | ||
.rts2_cache_umd/ | ||
|
||
# Optional REPL history | ||
.node_repl_history | ||
|
||
# Output of 'npm pack' | ||
*.tgz | ||
|
||
# Yarn Integrity file | ||
.yarn-integrity | ||
|
||
# dotenv environment variable files | ||
.env | ||
.env.development.local | ||
.env.test.local | ||
.env.production.local | ||
.env.local | ||
|
||
# parcel-bundler cache (https://parceljs.org/) | ||
.cache | ||
.parcel-cache | ||
|
||
# Next.js build output | ||
.next | ||
out | ||
|
||
# Nuxt.js build / generate output | ||
.nuxt | ||
dist | ||
|
||
# Gatsby files | ||
.cache/ | ||
# Comment in the public line in if your project uses Gatsby and not Next.js | ||
# https://nextjs.org/blog/next-9-1#public-directory-support | ||
# public | ||
|
||
# vuepress build output | ||
.vuepress/dist | ||
|
||
# vuepress v2.x temp and cache directory | ||
.temp | ||
.cache | ||
|
||
# Docusaurus cache and generated files | ||
.docusaurus | ||
|
||
# Serverless directories | ||
.serverless/ | ||
|
||
# FuseBox cache | ||
.fusebox/ | ||
|
||
# DynamoDB Local files | ||
.dynamodb/ | ||
|
||
# TernJS port file | ||
.tern-port | ||
|
||
# Stores VSCode versions used for testing VSCode extensions | ||
.vscode-test | ||
|
||
# yarn v2 | ||
.yarn/cache | ||
.yarn/unplugged | ||
.yarn/build-state.yml | ||
.yarn/install-state.gz | ||
.pnp.* | ||
brain.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
module.exports = { | ||
singleQuote: true, | ||
trailingComma: 'all', | ||
arrowParens: 'always', | ||
semi: false, | ||
bracketSpacing: false, | ||
tabWidth: 2, | ||
useTabs: false, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
SHELL:=/bin/bash | ||
PATH:=node_modules/.bin:$(PATH) | ||
|
||
SYSCONFDIR=$(PREFIX)/etc | ||
VARDIR=$(PREFIX)/var | ||
INITDIR=$(SYSCONFDIR)/systemd/system | ||
CIVIBOTDIR=/home/civibot/civibot | ||
LOGDIR=/home/civibot/logs | ||
BACKUPDIR=/home/civibot/brain_backups | ||
INSTALL=/bin/install -p | ||
|
||
fmt: | ||
npm run lint:fix | ||
|
||
node_modules: | ||
test -d node_modules || npm install | ||
|
||
install: | ||
npm install | ||
# Remove any cruft not stored in git | ||
# git clean -d -f | ||
sudo mkdir -p $(LOGDIR) $(BACKUPDIR) $(SYSCONFDIR)/civibot $(INITDIR) $(SYSCONFDIR)/logrotate.d $(SYSCONFDIR)/cron.hourly | ||
sudo chown civibot:civibot $(LOGDIR) | ||
sudo chown civibot:civibot $(BACKUPDIR) | ||
sudo /bin/cp -p $(CIVIBOTDIR)/files/civibot.service $(INITDIR) | ||
sudo /bin/systemctl daemon-reload | ||
sudo /bin/cp -sf $(CIVIBOTDIR)/files/civibot.logrotate $(SYSCONFDIR)/logrotate.d | ||
sudo /bin/cp -sf $(CIVIBOTDIR)/files/backup-brain.sh $(SYSCONFDIR)/cron.hourly | ||
sudo /bin/systemctl enable civibot.service | ||
sudo /bin/systemctl restart civibot.service | ||
|
||
uninstall: | ||
# This only removes the service, logrotate, and cronjob, not the app files | ||
sudo /bin/systemctl stop civibot.service | ||
sudo /bin/systemctl disable civibot.service | ||
sudo rm -f $(INITDIR)/civibot.service | ||
sudo rm -f $(SYSCONFDIR)/logrotate.d/civibot.logrotate | ||
sudo rm -f $(SYSCONFDIR)/cron.hourly/backup-brain.sh | ||
sudo /bin/systemctl daemon-reload | ||
|
||
restart: | ||
sudo /bin/systemctl restart civibot.service | ||
|
||
.PHONY: list | ||
list: | ||
# This horrible thing lists user-defined targets of the Makefile | ||
@$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,38 @@ | ||
# civibot | ||
Slack app for the CiviForm workspace | ||
Slack app for the CiviForm workspace. This is designed to be run in a specific EC2 instance. | ||
|
||
## Setup | ||
### Prerequisites | ||
1. EC2 instance with Ubuntu 24.04 (probably works fine on others, but this is what it's using now). | ||
2. Create a user called `civibot` with sudo access, using /bin/bash for the shell. | ||
3. Install make, nodejs, npm, git, unzip. | ||
4. Install the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) | ||
5. In IAM (not IAM Identity Center), create a `civibot` user. Create an access key for the user. | ||
6. On the host, run `aws configure`, providing the access key and secret. | ||
7. Requires `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`, and `SLACK_APP_TOKEN` secrets in AWS Secrets Manager. | ||
a. Each secret should have the given name, and the actual key in the secret should also be the given name, with the value being the Slack secret value. It should also have a tag called `name` with the value being the secret name. | ||
b. Create a policy in AWS that allows the `secretsmanager:GetSecretValue` permission on the ARNs for the three secrets (note that the ARN looks like `arn:aws:secretsmanager:us-east-1:<account id>:secret:SLACK_BOT_TOKEN-<random string>` so you will probably want to use `SLACK_BOT_TOKEN-*` in the resource definition), as well as `secretsmanager:ListSecrets` for all resources (`"*"`). | ||
c. Apply the policy to the `civibot` user. | ||
8. The civibot_github key should be in `/home/civibot/.ssh` with mode `0600`. You can get this key from Nick. | ||
9. `/home/civibot/.ssh/config` should have the following contents: | ||
``` | ||
Host github.com | ||
HostName github.com | ||
User git | ||
IdentityFile ~/.ssh/civibot_github | ||
IdentitiesOnly yes | ||
``` | ||
|
||
### Running | ||
1. Clone the repo. | ||
2. Run `make install`. This will install a service that runs the bot. | ||
3. Tail the log in `/home/civibot/civibot/logs/civibot.log` to ensure there are no errors. | ||
|
||
## Development | ||
You can deploy a different branch of the civibot repo by using the `!deploy` command. You must be in either #civibot-admin or #civibot-test to do this, and must be on the list of CiviBot admins. If you get things stuck with the app unable to start on the new branch, contact Nick to SSH into the node and fix it (or do so yourself if you have the SSH key). There is also a `!restart` command in case things get weird, but it's still responding to commands. | ||
|
||
Run `make fmt` to format your code before submitting a PR. | ||
|
||
### Tips | ||
* If you want to be able to have CiviBot respond in whatever context it was triggered in (channel, DM, thread), use `context.say` instead of just `say`. If you need to respond with custom blocks (see the xkcd script), use the regular `say`. | ||
* Create a `help` hash that maps command names to help text. Then, export both this and a `setup` function that does the actual meat of the script. These two things are automatically loaded by the app. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
const {App} = require('@slack/bolt') | ||
const fs = require('fs') | ||
const path = require('path') | ||
const {loadBrain} = require('./utils/brain.js') | ||
const {ADMIN_ROOMS} = require('./utils/constants.js') | ||
const process = require('process') | ||
const {exec} = require('child_process') | ||
|
||
let secrets = { | ||
SLACK_BOT_TOKEN: '', | ||
SLACK_SIGNING_SECRET: '', | ||
SLACK_APP_TOKEN: '', | ||
} | ||
|
||
// This is used to prevent querying for all users on startup, | ||
// since doing this frequently during development will get | ||
// you rate limited. | ||
let SKIP_USER_LOAD = true | ||
|
||
async function loadAllSecrets() { | ||
const {SecretsManagerClient, ListSecretsCommand, GetSecretValueCommand} = | ||
await import('@aws-sdk/client-secrets-manager') | ||
const secretsManager = new SecretsManagerClient({region: 'us-east-1'}) | ||
const listResponse = await secretsManager.send(new ListSecretsCommand({})) | ||
const secretArns = listResponse.SecretList | ||
const secretPromises = Object.keys(secrets).map(async (secret) => { | ||
const secretArn = secretArns.find((s) => | ||
s.Tags.some((tag) => tag.Key === 'name' && tag.Value === secret), | ||
) | ||
if (!secretArn) { | ||
throw new Error(`Secret ${secret} not found`) | ||
} | ||
|
||
const value = await secretsManager.send( | ||
new GetSecretValueCommand({SecretId: secretArn.ARN}), | ||
) | ||
secrets[secret] = JSON.parse(value.SecretString)[secret] | ||
console.log(`Loaded secret ${secret}`) | ||
}) | ||
await Promise.all(secretPromises) | ||
} | ||
|
||
async function startApp() { | ||
await loadAllSecrets() | ||
|
||
// Load the brain from disk on startup | ||
loadBrain() | ||
|
||
// Create the app | ||
const app = new App({ | ||
token: secrets.SLACK_BOT_TOKEN, | ||
signingSecret: secrets.SLACK_SIGNING_SECRET, | ||
socketMode: true, | ||
appToken: secrets.SLACK_APP_TOKEN, | ||
port: process.env.PORT || 30000, | ||
}) | ||
|
||
// Middleware to ensure we always provide thread_ts when it exists | ||
// so we respond in the appropriate way. | ||
app.use(async ({message, context, say, next}) => { | ||
context.say = async (text) => { | ||
if (text) { | ||
await say({text: text, thread_ts: message.thread_ts}) | ||
} | ||
} | ||
await next() | ||
}) | ||
|
||
// Load all users on startup, then listen for new user events | ||
if (!SKIP_USER_LOAD) { | ||
require('./utils/users.js')(app, secrets.SLACK_BOT_TOKEN) | ||
} | ||
|
||
// Load scripts | ||
const scriptsPath = path.join(__dirname, 'scripts') | ||
fs.readdirSync(scriptsPath).forEach((file) => { | ||
if (file.endsWith('.js')) { | ||
const scriptModule = require(path.join(scriptsPath, file)) | ||
if (scriptModule.setup) { | ||
scriptModule.setup(app) | ||
} | ||
} | ||
}) | ||
|
||
let rev | ||
exec('git rev-parse HEAD', (err, stdout) => { | ||
if (err) { | ||
console.error(`Error executing git rev-parse: ${err}`) | ||
rev = 'unknown' | ||
} else { | ||
rev = stdout.trim() | ||
} | ||
}) | ||
|
||
await app.start() | ||
|
||
for (const room of ADMIN_ROOMS) { | ||
await app.client.chat.postMessage({ | ||
token: secrets.SLACK_BOT_TOKEN, | ||
channel: room, | ||
text: '⚡️ CiviBot is running at revision ' + rev, | ||
}) | ||
} | ||
console.log('⚡️ CiviBot is running at revision ' + rev) | ||
} | ||
|
||
startApp().catch((error) => { | ||
console.error('Error:', error) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
const prettierConfig = require('eslint-config-prettier') | ||
const prettierPlugin = require('eslint-plugin-prettier') | ||
const {node} = require('globals') | ||
|
||
module.exports = [ | ||
{ | ||
ignores: ['node_modules'], | ||
}, | ||
{ | ||
files: ['**/*.js'], | ||
plugins: { | ||
prettier: prettierPlugin, | ||
}, | ||
rules: { | ||
'prettier/prettier': 'error', | ||
'no-unused-vars': 'warn', | ||
}, | ||
languageOptions: { | ||
ecmaVersion: 'latest', | ||
sourceType: 'script', | ||
globals: { | ||
...node, | ||
}, | ||
}, | ||
}, | ||
{ | ||
files: ['**/*.js'], | ||
...prettierConfig, | ||
}, | ||
] |
Oops, something went wrong.