Compare commits
3 commits
jedi/autod
...
live
Author | SHA1 | Date | |
---|---|---|---|
d73bebd5de | |||
2c609427ec | |||
120507512d |
8 changed files with 84 additions and 82 deletions
35
.forgejo/issue_template/bug.yml
Normal file
35
.forgejo/issue_template/bug.yml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
name: Bug Report
|
||||||
|
about: File a bug report
|
||||||
|
labels:
|
||||||
|
- Kind/Bug
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report!
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: Also tell us, what did you expect to happen?
|
||||||
|
placeholder: Tell us what you see!
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: browsers
|
||||||
|
attributes:
|
||||||
|
label: What browsers are you seeing the problem on?
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Firefox (Windows)
|
||||||
|
- Firefox (MacOS)
|
||||||
|
- Firefox (Linux)
|
||||||
|
- Firefox (Android)
|
||||||
|
- Firefox (iOS)
|
||||||
|
- Chrome (Windows)
|
||||||
|
- Chrome (MacOS)
|
||||||
|
- Chrome (Linux)
|
||||||
|
- Chrome (Android)
|
||||||
|
- Chrome (iOS)
|
||||||
|
- Safari
|
||||||
|
- Microsoft Edge
|
27
.forgejo/issue_template/feature.yml
Normal file
27
.forgejo/issue_template/feature.yml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
name: 'New Feature'
|
||||||
|
about: 'This template is for new features'
|
||||||
|
labels:
|
||||||
|
- Kind/Feature
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Before creating a Feature Ticket, please check for duplicates.
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
### Implementation Checklist
|
||||||
|
- [ ] concept
|
||||||
|
- [ ] frontend
|
||||||
|
- [ ] backend
|
||||||
|
- [ ] unittests
|
||||||
|
- [ ] tested on staging
|
||||||
|
visible: [ content ]
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: 'Feature Description'
|
||||||
|
description: 'Explain the the feature.'
|
||||||
|
placeholder: Description
|
||||||
|
validations:
|
||||||
|
required: true
|
|
@ -1,60 +0,0 @@
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- live
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: docker
|
|
||||||
container:
|
|
||||||
image: ghcr.io/catthehacker/ubuntu:act-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.11'
|
|
||||||
cache-dependency-path: '**/requirements.dev.txt'
|
|
||||||
- name: Install dependencies
|
|
||||||
working-directory: core
|
|
||||||
run: pip3 install -r requirements.dev.txt
|
|
||||||
- name: Run django tests
|
|
||||||
working-directory: core
|
|
||||||
run: python3 manage.py test
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
needs: [test]
|
|
||||||
runs-on: docker
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Install ansible
|
|
||||||
run: |
|
|
||||||
apt update -y
|
|
||||||
apt install python3-pip -y
|
|
||||||
python3 -m pip install ansible
|
|
||||||
python3 -m pip install ansible-lint
|
|
||||||
|
|
||||||
- name: Populate relevant files
|
|
||||||
run: |
|
|
||||||
mkdir ~/.ssh
|
|
||||||
echo "${{ secrets.C3LF_SSH_LIVE }}" > ~/.ssh/id_ed25519
|
|
||||||
chmod 0600 ~/.ssh/id_ed25519
|
|
||||||
ls -lah ~/.ssh
|
|
||||||
command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )
|
|
||||||
eval $(ssh-agent -s)
|
|
||||||
ssh-add ~/.ssh/id_ed25519
|
|
||||||
echo "polaris.lab.or.it ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO//BP4rx4Aav0voFicFSoYEG4VPfrbcWwd2JFfItRZT" >> ~/.ssh/known_hosts
|
|
||||||
mkdir /etc/ansible
|
|
||||||
echo "${{ secrets.C3LF_INVENTORY_LIVE }}" > /etc/ansible/hosts
|
|
||||||
|
|
||||||
- name: Check ansible version
|
|
||||||
run: |
|
|
||||||
ansible --version
|
|
||||||
|
|
||||||
- name: List ansible hosts
|
|
||||||
run: |
|
|
||||||
ansible -m ping Polaris
|
|
||||||
|
|
||||||
- name: Deploy testing
|
|
||||||
run: |
|
|
||||||
cd deploy/ansible
|
|
||||||
ansible-playbook playbooks/deploy-c3lf-sys3.yml
|
|
|
@ -1,16 +0,0 @@
|
||||||
"""
|
|
||||||
WSGI config for core project.
|
|
||||||
|
|
||||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')
|
|
||||||
|
|
||||||
application = get_wsgi_application()
|
|
|
@ -29,13 +29,13 @@ class Email(SoftDeleteModel):
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def train_spam(self):
|
def train_spam(self):
|
||||||
if ACTIVE_SPAM_TRAINING:
|
if ACTIVE_SPAM_TRAINING and self.raw_file.path:
|
||||||
import subprocess
|
import subprocess
|
||||||
path = self.raw_file.path
|
path = self.raw_file.path
|
||||||
subprocess.run(["rspamc", "learn_spam", path])
|
subprocess.run(["rspamc", "learn_spam", path])
|
||||||
|
|
||||||
def train_ham(self):
|
def train_ham(self):
|
||||||
if ACTIVE_SPAM_TRAINING:
|
if ACTIVE_SPAM_TRAINING and self.raw_file.path:
|
||||||
import subprocess
|
import subprocess
|
||||||
path = self.raw_file.path
|
path = self.raw_file.path
|
||||||
subprocess.run(["rspamc", "learn_ham", path])
|
subprocess.run(["rspamc", "learn_ham", path])
|
||||||
|
|
|
@ -91,7 +91,7 @@ async def send_smtp(message):
|
||||||
await aiosmtplib.send(message, hostname="127.0.0.1", port=25, use_tls=False, start_tls=False)
|
await aiosmtplib.send(message, hostname="127.0.0.1", port=25, use_tls=False, start_tls=False)
|
||||||
|
|
||||||
|
|
||||||
def find_active_issue_thread(in_reply_to, address, subject):
|
def find_active_issue_thread(in_reply_to, address, subject, event):
|
||||||
from re import match
|
from re import match
|
||||||
uuid_match = match(r'^ticket\+([a-f0-9-]{36})@', address)
|
uuid_match = match(r'^ticket\+([a-f0-9-]{36})@', address)
|
||||||
if uuid_match:
|
if uuid_match:
|
||||||
|
@ -102,7 +102,7 @@ def find_active_issue_thread(in_reply_to, address, subject):
|
||||||
if reply_to.exists():
|
if reply_to.exists():
|
||||||
return reply_to.first().issue_thread, False
|
return reply_to.first().issue_thread, False
|
||||||
else:
|
else:
|
||||||
issue = IssueThread.objects.create(name=subject)
|
issue = IssueThread.objects.create(name=subject, event=event)
|
||||||
return issue, True
|
return issue, True
|
||||||
|
|
||||||
|
|
||||||
|
@ -202,11 +202,14 @@ def receive_email(envelope, log=None):
|
||||||
subject = unescape_and_decode_base64(subject)
|
subject = unescape_and_decode_base64(subject)
|
||||||
target_event = find_target_event(recipient)
|
target_event = find_target_event(recipient)
|
||||||
|
|
||||||
active_issue_thread, new = find_active_issue_thread(header_in_reply_to, recipient, subject)
|
active_issue_thread, new = find_active_issue_thread(header_in_reply_to, recipient, subject, target_event)
|
||||||
|
|
||||||
|
from hashlib import sha256
|
||||||
|
random_filename = 'mail-' + sha256(envelope.content).hexdigest()
|
||||||
|
|
||||||
email = Email.objects.create(
|
email = Email.objects.create(
|
||||||
sender=sender, recipient=recipient, body=body, subject=subject, reference=header_message_id,
|
sender=sender, recipient=recipient, body=body, subject=subject, reference=header_message_id,
|
||||||
in_reply_to=header_in_reply_to, raw_file=ContentFile(envelope.content), event=target_event,
|
in_reply_to=header_in_reply_to, raw_file=ContentFile(envelope.content, name=random_filename), event=target_event,
|
||||||
issue_thread=active_issue_thread)
|
issue_thread=active_issue_thread)
|
||||||
for attachment in attachments:
|
for attachment in attachments:
|
||||||
email.attachments.add(attachment)
|
email.attachments.add(attachment)
|
||||||
|
|
|
@ -142,6 +142,7 @@ class LMTPHandlerTestCase(TestCase): # TODO replace with less hacky test
|
||||||
aiosmtplib.send.assert_called_once()
|
aiosmtplib.send.assert_called_once()
|
||||||
self.assertEqual('test ä', Email.objects.all()[0].subject)
|
self.assertEqual('test ä', Email.objects.all()[0].subject)
|
||||||
self.assertEqual('Text mit Quoted-Printable-Kodierung: äöüß', Email.objects.all()[0].body)
|
self.assertEqual('Text mit Quoted-Printable-Kodierung: äöüß', Email.objects.all()[0].body)
|
||||||
|
self.assertTrue( Email.objects.all()[0].raw_file.path)
|
||||||
|
|
||||||
def test_handle_quoted_printable_2(self):
|
def test_handle_quoted_printable_2(self):
|
||||||
from aiosmtpd.smtp import Envelope
|
from aiosmtpd.smtp import Envelope
|
||||||
|
@ -162,6 +163,7 @@ class LMTPHandlerTestCase(TestCase): # TODO replace with less hacky test
|
||||||
aiosmtplib.send.assert_called_once()
|
aiosmtplib.send.assert_called_once()
|
||||||
self.assertEqual('suche_Mütze', Email.objects.all()[0].subject)
|
self.assertEqual('suche_Mütze', Email.objects.all()[0].subject)
|
||||||
self.assertEqual('Text mit Quoted-Printable-Kodierung: äöüß', Email.objects.all()[0].body)
|
self.assertEqual('Text mit Quoted-Printable-Kodierung: äöüß', Email.objects.all()[0].body)
|
||||||
|
self.assertTrue( Email.objects.all()[0].raw_file.path)
|
||||||
|
|
||||||
def test_handle_base64(self):
|
def test_handle_base64(self):
|
||||||
from aiosmtpd.smtp import Envelope
|
from aiosmtpd.smtp import Envelope
|
||||||
|
@ -182,6 +184,7 @@ class LMTPHandlerTestCase(TestCase): # TODO replace with less hacky test
|
||||||
aiosmtplib.send.assert_called_once()
|
aiosmtplib.send.assert_called_once()
|
||||||
self.assertEqual('test', Email.objects.all()[0].subject)
|
self.assertEqual('test', Email.objects.all()[0].subject)
|
||||||
self.assertEqual('Text mit Base64-Kodierung: äöüß', Email.objects.all()[0].body)
|
self.assertEqual('Text mit Base64-Kodierung: äöüß', Email.objects.all()[0].body)
|
||||||
|
self.assertTrue( Email.objects.all()[0].raw_file.path)
|
||||||
|
|
||||||
def test_handle_client_reply(self):
|
def test_handle_client_reply(self):
|
||||||
issue_thread = IssueThread.objects.create(
|
issue_thread = IssueThread.objects.create(
|
||||||
|
@ -229,6 +232,7 @@ class LMTPHandlerTestCase(TestCase): # TODO replace with less hacky test
|
||||||
self.assertEqual(IssueThread.objects.all()[0].name, 'test')
|
self.assertEqual(IssueThread.objects.all()[0].name, 'test')
|
||||||
self.assertEqual(IssueThread.objects.all()[0].state, 'pending_new')
|
self.assertEqual(IssueThread.objects.all()[0].state, 'pending_new')
|
||||||
self.assertEqual(IssueThread.objects.all()[0].assigned_to, None)
|
self.assertEqual(IssueThread.objects.all()[0].assigned_to, None)
|
||||||
|
self.assertTrue( Email.objects.all()[2].raw_file.path)
|
||||||
|
|
||||||
def test_handle_client_reply_2(self):
|
def test_handle_client_reply_2(self):
|
||||||
issue_thread = IssueThread.objects.create(
|
issue_thread = IssueThread.objects.create(
|
||||||
|
@ -281,6 +285,7 @@ class LMTPHandlerTestCase(TestCase): # TODO replace with less hacky test
|
||||||
self.assertEqual(IssueThread.objects.all()[0].name, 'test')
|
self.assertEqual(IssueThread.objects.all()[0].name, 'test')
|
||||||
self.assertEqual(IssueThread.objects.all()[0].state, 'pending_open')
|
self.assertEqual(IssueThread.objects.all()[0].state, 'pending_open')
|
||||||
self.assertEqual(IssueThread.objects.all()[0].assigned_to, None)
|
self.assertEqual(IssueThread.objects.all()[0].assigned_to, None)
|
||||||
|
self.assertTrue( Email.objects.all()[2].raw_file.path)
|
||||||
|
|
||||||
def test_mail_reply(self):
|
def test_mail_reply(self):
|
||||||
issue_thread = IssueThread.objects.create(
|
issue_thread = IssueThread.objects.create(
|
||||||
|
@ -384,6 +389,7 @@ class LMTPHandlerTestCase(TestCase): # TODO replace with less hacky test
|
||||||
states = StateChange.objects.filter(issue_thread=IssueThread.objects.all()[0])
|
states = StateChange.objects.filter(issue_thread=IssueThread.objects.all()[0])
|
||||||
self.assertEqual(1, len(states))
|
self.assertEqual(1, len(states))
|
||||||
self.assertEqual('pending_new', states[0].state)
|
self.assertEqual('pending_new', states[0].state)
|
||||||
|
self.assertEqual(event, IssueThread.objects.all()[0].event)
|
||||||
|
|
||||||
def test_mail_html_body(self):
|
def test_mail_html_body(self):
|
||||||
from aiosmtpd.smtp import Envelope
|
from aiosmtpd.smtp import Envelope
|
||||||
|
|
|
@ -70,6 +70,13 @@ server {
|
||||||
alias /var/www/c3lf-sys3/staticfiles/;
|
alias /var/www/c3lf-sys3/staticfiles/;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /metrics {
|
||||||
|
allow 95.156.226.90;
|
||||||
|
allow 127.0.0.1;
|
||||||
|
allow ::1;
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
listen 443 ssl http2; # managed by Certbot
|
listen 443 ssl http2; # managed by Certbot
|
||||||
ssl_certificate /etc/letsencrypt/live/{{ web_domain }}/fullchain.pem; # managed by Certbot
|
ssl_certificate /etc/letsencrypt/live/{{ web_domain }}/fullchain.pem; # managed by Certbot
|
||||||
ssl_certificate_key /etc/letsencrypt/live/{{ web_domain }}/privkey.pem; # managed by Certbot
|
ssl_certificate_key /etc/letsencrypt/live/{{ web_domain }}/privkey.pem; # managed by Certbot
|
||||||
|
|
Loading…
Reference in a new issue