mirror of
https://git.meli.delivery/meli/issue-bot
synced 2024-05-21 12:46:38 +02:00
Read config/db paths from env, plus add docs
This commit is contained in:
parent
908493327f
commit
bb718d51e0
86
README.md
86
README.md
|
@ -2,6 +2,26 @@
|
||||||
|
|
||||||
A bot to handle bug reports through mail, for gitea's issue tracker.
|
A bot to handle bug reports through mail, for gitea's issue tracker.
|
||||||
|
|
||||||
|
Expects configuration file path in environment variable `ISSUE_BOT_CONFIG`. If
|
||||||
|
it's not defined, default is `./config.toml` (current working directory).
|
||||||
|
|
||||||
|
Expects database file path in environment variable `ISSUE_BOT_DB`. If it's not
|
||||||
|
defined, default is `./sqlite3.db` (current working directory).
|
||||||
|
|
||||||
|
```
|
||||||
|
issue-bot
|
||||||
|
```
|
||||||
|
|
||||||
|
by default expects to read a valid [RFC5322](https://www.rfc-editor.org/rfc/rfc5322) e-mail in valid utf-8 or valid ascii.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
issue-bot cron
|
||||||
|
```
|
||||||
|
|
||||||
|
Checks if there are new comments or other updates in the issues, and sends
|
||||||
|
emails to anyone subscribed. An example systemd service and timer file is provided in `docs/`.
|
||||||
|
|
||||||
## Problem
|
## Problem
|
||||||
|
|
||||||
Users have to register to your gitea instance to file bugs. This is a deterrent. A mailing list requires less effort, but lacks bridging with an issue tracker.
|
Users have to register to your gitea instance to file bugs. This is a deterrent. A mailing list requires less effort, but lacks bridging with an issue tracker.
|
||||||
|
@ -18,9 +38,9 @@ Spam?
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The bot looks for a `config.toml` file in the same directory as the binary. The config needs the following values:
|
The config file must be valid TOML and needs the following values:
|
||||||
|
|
||||||
```text
|
```toml
|
||||||
# the tag that prefixes email subjects eg "[issue-bot-issues] blah blah"
|
# the tag that prefixes email subjects eg "[issue-bot-issues] blah blah"
|
||||||
tag = "issue-bot-issues"
|
tag = "issue-bot-issues"
|
||||||
# the auth_token for the gitea's instance API
|
# the auth_token for the gitea's instance API
|
||||||
|
@ -38,68 +58,12 @@ bot_username = "issue_bot"
|
||||||
mailer = "/usr/sbin/sendmail -t"
|
mailer = "/usr/sbin/sendmail -t"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Optionally, you can set `dry_run = true` to avoid any email/db update being performed in order to debug what would happen if you ran the `cron` command.
|
||||||
|
|
||||||
Setup your mail server to deliver mail with destination `{local_part}+tags@{domain}` to this binary. Simply call the binary and write the email in UTF-8 in the binary's standard input.
|
Setup your mail server to deliver mail with destination `{local_part}+tags@{domain}` to this binary. Simply call the binary and write the email in UTF-8 in the binary's standard input.
|
||||||
|
|
||||||
On postfix this can be done by creating a transport map and a pipe. A transport map is a file that tells postfix to send mails send to `{local_part}` to a specific program. The pipe will be this program.
|
For postfix setup see `docs/POSTFIX.md`.
|
||||||
|
|
||||||
Open `master.cf` and paste this line at the bottom:
|
|
||||||
|
|
||||||
```text
|
|
||||||
issue_bot unix - n n - - pipe
|
|
||||||
user=issuebot directory=/path/to/binarydir/ argv=/path/to/binary
|
|
||||||
```
|
|
||||||
|
|
||||||
an example:
|
|
||||||
|
|
||||||
|
|
||||||
```text
|
|
||||||
issue_bot unix - n n - - pipe
|
|
||||||
user=issuebot directory=/home/issuebot/ argv=/home/issuebot/issue-bot
|
|
||||||
```
|
|
||||||
|
|
||||||
Then create your transport map:
|
|
||||||
|
|
||||||
```text
|
|
||||||
{local_part}@{domain} issue_bot:
|
|
||||||
```
|
|
||||||
|
|
||||||
Notice the colon at the end. This means that it refers to a transfer, not an address. Save the file somewhere (eg `/etc/postfix/issue_transport`) and make it readable by postfix. Issue `postmap /etcpostfix/issue_transport`. Finally add the entry `hash:/etc/postfix/issue_transport` in your `transport_maps` and `local_recipient_maps` keys in `main.cf`. `postfix reload` to load the configuration changes.
|
|
||||||
|
|
||||||
You will also need the following setting to allow tags in your recipient addresses:
|
|
||||||
|
|
||||||
```text
|
|
||||||
recipient_delimiter = +
|
|
||||||
```
|
|
||||||
|
|
||||||
Setup a periodic check in your preferred task scheduler to run `issue_bot_bin cron` in order to fetch replies to issues. On systemd this can be done with timers.
|
|
||||||
|
|
||||||
|
|
||||||
### Troubleshooting
|
|
||||||
If you your email stops working or postfix doesn't pass mail to the bot, make sure you're not using a non-default setup like virtual mailboxes. In that case you have to add the transport along with the transports of your setup, whatever that be.
|
|
||||||
|
|
||||||
If the e-mail gets to the binary and nothing happens, make sure:
|
|
||||||
|
|
||||||
- the binary is executable and readable by the pipe's user
|
|
||||||
- the configuration file is in the same directory as the binary
|
|
||||||
- that in `master.cf` there are no `flags=` in the transport entry. The mail must be piped unaltered.
|
|
||||||
- your auth token works. You can check yourself by issuing requests to your API via cURL. There are examples here: https://docs.gitea.io/en-us/api-usage/
|
|
||||||
|
|
||||||
If commands (using +reply, +close etc) don't work, make sure you have added `recipient_delimiter = +` in your `main.cf` file.
|
|
||||||
|
|
||||||
The bot's state is saved in a sqlite3 database in the same directory as the binary. You can view its data by using the `sqlite3` cli tool:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
root# sqlite3 /home/issuebot/sqlite3.db
|
|
||||||
SQLite version ****** ********** ********
|
|
||||||
Enter ".help" for usage hints.
|
|
||||||
sqlite> .tables
|
|
||||||
issue
|
|
||||||
sqlite> select * from issue;
|
|
||||||
1|Name <add@res.tld>|1F:|2019-09-29T12:20:21.658495173Z|0|1|issue title|"2019-09-29T15:20:21+03:00"
|
|
||||||
2|Name <add@res.tld>|{^D0u|2019-09-29T12:23:48.291970808Z|0|1|issue title#2|"2019-09-29T15:23:48+03:00"
|
|
||||||
3|Name <add@res.tld>|Gd)i]|2019-09-29T12:24:31.414792595Z|0|1|issue title again|"2019-09-29T15:26:53+03:00"
|
|
||||||
4|Name <add@res.tld>|$3fBוv|2019-09-29T12:28:21.187425505Z|1|1|many issues|"2019-09-29T15:28:21+03:00"
|
|
||||||
```
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
My email:
|
My email:
|
||||||
|
|
73
docs/POSTFIX.md
Normal file
73
docs/POSTFIX.md
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
# issue-bot with postfix
|
||||||
|
|
||||||
|
Setup your mail server to deliver mail with destination `{local_part}+tags@{domain}` to this binary. Simply call the binary and write the email in UTF-8 in the binary's standard input.
|
||||||
|
|
||||||
|
On postfix this can be done by creating a transport map and a pipe. A transport map is a file that tells postfix to send mails send to `{local_part}` to a specific program. The pipe will be this program.
|
||||||
|
|
||||||
|
**BEWARE**: If `issue-bot` needs to read its configuration file and database file paths from environment variables, create a wrapper script and call that from postfix instead of going through the complicated trouble of setting up the exported environment (see postfix manual pages `master(t)` and `pipe(8)`)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
/bin/sh
|
||||||
|
|
||||||
|
export ISSUE_BOT_CONFIG=_
|
||||||
|
export ISSUE_BOT_DB=_
|
||||||
|
/path/to/issue-bot
|
||||||
|
```
|
||||||
|
|
||||||
|
Open `master.cf` and paste this line at the bottom:
|
||||||
|
|
||||||
|
```text
|
||||||
|
issue_bot unix - n n - - pipe
|
||||||
|
user=issuebot directory=/path/to/binarydir/ argv=/path/to/binary
|
||||||
|
```
|
||||||
|
|
||||||
|
an example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
issue_bot unix - n n - - pipe
|
||||||
|
user=issuebot directory=/home/issuebot/ argv=/home/issuebot/issue-bot
|
||||||
|
```
|
||||||
|
|
||||||
|
Then create your transport map:
|
||||||
|
|
||||||
|
```text
|
||||||
|
{local_part}@{domain} issue_bot:
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice the colon at the end. This means that it refers to a transfer, not an address. Save the file somewhere (eg `/etc/postfix/issue_transport`) and make it readable by postfix. Issue `postmap /etcpostfix/issue_transport`. Finally add the entry `hash:/etc/postfix/issue_transport` in your `transport_maps` and `local_recipient_maps` keys in `main.cf`. `postfix reload` to load the configuration changes.
|
||||||
|
|
||||||
|
You will also need the following setting to allow tags in your recipient addresses:
|
||||||
|
|
||||||
|
```text
|
||||||
|
recipient_delimiter = +
|
||||||
|
```
|
||||||
|
|
||||||
|
Setup a periodic check in your preferred task scheduler to run `issue_bot_bin cron` in order to fetch replies to issues. On systemd this can be done with timers.
|
||||||
|
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
If you your email stops working or postfix doesn't pass mail to the bot, make sure you're not using a non-default setup like virtual mailboxes. In that case you have to add the transport along with the transports of your setup, whatever that be.
|
||||||
|
|
||||||
|
If the e-mail gets to the binary and nothing happens, make sure:
|
||||||
|
|
||||||
|
- the binary is executable and readable by the pipe's user
|
||||||
|
- the configuration file is in the same directory as the binary
|
||||||
|
- that in `master.cf` there are no `flags=` in the transport entry. The mail must be piped unaltered.
|
||||||
|
- your auth token works. You can check yourself by issuing requests to your API via cURL. There are examples here: https://docs.gitea.io/en-us/api-usage/
|
||||||
|
|
||||||
|
If commands (using +reply, +close etc) don't work, make sure you have added `recipient_delimiter = +` in your `main.cf` file.
|
||||||
|
|
||||||
|
The bot's state is saved in a sqlite3 database in the same directory as the binary. You can view its data by using the `sqlite3` cli tool:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
root# sqlite3 /home/issuebot/sqlite3.db
|
||||||
|
SQLite version ****** ********** ********
|
||||||
|
Enter ".help" for usage hints.
|
||||||
|
sqlite> .tables
|
||||||
|
issue
|
||||||
|
sqlite> select * from issue;
|
||||||
|
1|Name <add@res.tld>|1F:|2019-09-29T12:20:21.658495173Z|0|1|issue title|"2019-09-29T15:20:21+03:00"
|
||||||
|
2|Name <add@res.tld>|{^D0u|2019-09-29T12:23:48.291970808Z|0|1|issue title#2|"2019-09-29T15:23:48+03:00"
|
||||||
|
3|Name <add@res.tld>|Gd)i]|2019-09-29T12:24:31.414792595Z|0|1|issue title again|"2019-09-29T15:26:53+03:00"
|
||||||
|
4|Name <add@res.tld>|$3fBוv|2019-09-29T12:28:21.187425505Z|1|1|many issues|"2019-09-29T15:28:21+03:00"
|
||||||
|
```
|
34
docs/SCHEDULING.md
Normal file
34
docs/SCHEDULING.md
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# issue-bot scheduled jobs
|
||||||
|
|
||||||
|
You can set up scheduled jobs by configuring `crontab` to run `issue-bot cron` whenever you want. For the more complicated but more reliable systemd setup, two example files are included in this directory: a service unit file and a timer unit file. The service unit executes once, and the timer unit is responsible for calling the service at the intervals you set.
|
||||||
|
|
||||||
|
Copy the example files somewhere else and edit them with your own values.
|
||||||
|
|
||||||
|
You can put `dry_run = true` in the config file to check it works without making changes or sending any mail. Also, backup your database if needed.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
systemctl --user enable issue-bot.service
|
||||||
|
```
|
||||||
|
|
||||||
|
You can do a test run with
|
||||||
|
|
||||||
|
```shell
|
||||||
|
systemctl --user start issue-bot.service
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you enable/activate the timer.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
systemctl --user enable issue-bot.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
systemctl --user start issue-bot.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Monitor the service status:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
systemctl --user status issue-bot
|
||||||
|
```
|
14
docs/issue-bot.service
Normal file
14
docs/issue-bot.service
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[Unit]
|
||||||
|
Description=issue-bot cron
|
||||||
|
RefuseManualStart=no # Allow manual starts
|
||||||
|
RefuseManualStop=no # Allow manual stops
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/path/to/issue-bot cron
|
||||||
|
Environment=ISSUE_BOT_CONFIG=/a/b/c/d.toml
|
||||||
|
Environment=ISSUE_BOT_DB=/a/b/c/sqlite3.db
|
||||||
|
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
29
docs/issue-bot.timer
Normal file
29
docs/issue-bot.timer
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
[Unit]
|
||||||
|
Description=issue-bot crons
|
||||||
|
RefuseManualStart=no # Allow manual starts
|
||||||
|
RefuseManualStop=no # Allow manual stops
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
#Execute job if it missed a run due to machine being off
|
||||||
|
Persistent=true
|
||||||
|
#Run 120 seconds after boot for the first time
|
||||||
|
OnBootSec=120
|
||||||
|
#Run every 5 minutes thereafter
|
||||||
|
OnUnitActiveSec=300
|
||||||
|
#File describing job to execute
|
||||||
|
Unit=issue-bot.service
|
||||||
|
|
||||||
|
|
||||||
|
## more complicated examples:
|
||||||
|
# # run on the minute of every minute every hour of every day
|
||||||
|
# OnCalendar=*-*-* *:*:00
|
||||||
|
# # run on the hour of every hour of every day
|
||||||
|
# OnCalendar=*-*-* *:00:00
|
||||||
|
# # run every day
|
||||||
|
# OnCalendar=*-*-* 00:00:00
|
||||||
|
# # run 11:12:13 of the first or fifth day of any month of the year
|
||||||
|
# # 2012, but only if that day is a Thursday or Friday
|
||||||
|
# OnCalendar=Thu,Fri 2012-*-1,5 11:12:13
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
|
@ -122,7 +122,6 @@ pub fn check(conn: Connection, conf: Configuration) -> Result<()> {
|
||||||
for issue in results {
|
for issue in results {
|
||||||
errors.push(check_issue(&conn, &conf, issue));
|
errors.push(check_issue(&conn, &conf, issue));
|
||||||
}
|
}
|
||||||
if errors.iter().any(|r| matches!(r, Ok(true))) {
|
|
||||||
let successes_count = errors.iter().filter(|r| matches!(r, Ok(true))).count();
|
let successes_count = errors.iter().filter(|r| matches!(r, Ok(true))).count();
|
||||||
let error_count = errors.iter().filter(|r| r.is_err()).count();
|
let error_count = errors.iter().filter(|r| r.is_err()).count();
|
||||||
log::info!(
|
log::info!(
|
||||||
|
@ -130,7 +129,6 @@ pub fn check(conn: Connection, conf: Configuration) -> Result<()> {
|
||||||
successes_count,
|
successes_count,
|
||||||
error_count
|
error_count
|
||||||
);
|
);
|
||||||
}
|
|
||||||
_ = errors.into_iter().collect::<Result<Vec<bool>>>()?;
|
_ = errors.into_iter().collect::<Result<Vec<bool>>>()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
18
src/main.rs
18
src/main.rs
|
@ -300,7 +300,9 @@ fn run_request(conn: Connection, conf: Configuration) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_app() -> Result<()> {
|
fn run_app() -> Result<()> {
|
||||||
let mut file = std::fs::File::open("./config.toml")?;
|
let conf_path =
|
||||||
|
std::env::var("ISSUE_BOT_CONFIG").unwrap_or_else(|_| "./config.toml".to_string());
|
||||||
|
let mut file = std::fs::File::open(&conf_path)?;
|
||||||
let args = std::env::args().skip(1).collect::<Vec<String>>();
|
let args = std::env::args().skip(1).collect::<Vec<String>>();
|
||||||
let perform_cron: bool;
|
let perform_cron: bool;
|
||||||
if args.len() > 1 {
|
if args.len() > 1 {
|
||||||
|
@ -339,11 +341,11 @@ fn run_app() -> Result<()> {
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
let db_path = "./sqlite3.db";
|
let db_path = std::env::var("ISSUE_BOT_DB").unwrap_or_else(|_| "./sqlite3.db".to_string());
|
||||||
let conn = Connection::open(db_path)?;
|
let conn = Connection::open(&db_path)?;
|
||||||
|
|
||||||
conn.execute(
|
conn.execute_batch(
|
||||||
"CREATE TABLE IF NOT EXISTS issue (
|
r##"CREATE TABLE IF NOT EXISTS issue (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
submitter TEXT NOT NULL,
|
submitter TEXT NOT NULL,
|
||||||
password BLOB,
|
password BLOB,
|
||||||
|
@ -352,8 +354,10 @@ fn run_app() -> Result<()> {
|
||||||
subscribed BOOLEAN,
|
subscribed BOOLEAN,
|
||||||
title TEXT NOT NULL,
|
title TEXT NOT NULL,
|
||||||
last_update TEXT
|
last_update TEXT
|
||||||
)",
|
);
|
||||||
[],
|
|
||||||
|
UPDATE issue SET last_update = replace(last_update, '"', '');
|
||||||
|
"##,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if perform_cron {
|
if perform_cron {
|
||||||
|
|
Loading…
Reference in a new issue