4 Commits

8 changed files with 29 additions and 25 deletions

View File

@ -1,8 +1,10 @@
# Needs python3 >= 3.9, sed, git for build # Needs python3 >= 3.9, sed, git for build, docker for tests
build: clean build: clean
python3 -m pip install -r requirements.txt --no-compile --target build python3 -m pip install -r requirements.txt --no-compile --target build
cp -r mail4one/ build/ cp -r mail4one/ build/
sed -i "s/DEVELOMENT/$(shell scripts/get_version.sh)/" build/mail4one/version.py sed -i "s/DEVELOMENT/$(shell scripts/get_version.sh)/" build/mail4one/version.py
find build -name "*.pyi" -o -name "py.typed" | xargs -I typefile rm typefile
rm -rf build/bin
rm -rf build/mail4one/__pycache__ rm -rf build/mail4one/__pycache__
rm -rf build/*.dist-info rm -rf build/*.dist-info
python3 -m zipapp \ python3 -m zipapp \
@ -18,11 +20,13 @@ clean:
docker-tests: docker-tests:
docker run --pull=always -v `pwd`:/app -w /app --rm python:3.11-alpine sh scripts/runtests.sh docker run --pull=always -v `pwd`:/app -w /app --rm python:3.11-alpine sh scripts/runtests.sh
docker run --pull=always -v `pwd`:/app -w /app --rm python:3.10-alpine sh scripts/runtests.sh docker run --pull=always -v `pwd`:/app -w /app --rm python:3.10-alpine sh scripts/runtests.sh
docker run --pull=always -v `pwd`:/app -w /app --rm python:3.12 sh scripts/runtests.sh
docker run --pull=always -v `pwd`:/app -w /app --rm python:3.11 sh scripts/runtests.sh docker run --pull=always -v `pwd`:/app -w /app --rm python:3.11 sh scripts/runtests.sh
docker run --pull=always -v `pwd`:/app -w /app --rm python:3.10 sh scripts/runtests.sh docker run --pull=always -v `pwd`:/app -w /app --rm python:3.10 sh scripts/runtests.sh
docker run --pull=always -v `pwd`:/app -w /app --rm python:3.9 sh scripts/runtests.sh docker run --pull=always -v `pwd`:/app -w /app --rm python:3.9 sh scripts/runtests.sh
# ============================================================================ # ============================================================================
# Below targets for devs. Need pipenv, black installed
requirements.txt: Pipfile.lock requirements.txt: Pipfile.lock
pipenv requirements > requirements.txt pipenv requirements > requirements.txt
@ -38,6 +42,11 @@ setup:
cleanup: cleanup:
pipenv --rm pipenv --rm
update:
rm requirements.txt Pipfile.lock
pipenv update
pipenv requirements > requirements.txt
shell: shell:
MYPYPATH=`pipenv --venv`/lib/python3.11/site-packages pipenv shell MYPYPATH=`pipenv --venv`/lib/python3.11/site-packages pipenv shell

8
Pipfile.lock generated
View File

@ -22,6 +22,7 @@
"sha256:f9243b7dfe00aaf567da8728d891752426b51392174a34d2cf5c18053b63dcbc" "sha256:f9243b7dfe00aaf567da8728d891752426b51392174a34d2cf5c18053b63dcbc"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version ~= '3.7'",
"version": "==1.4.4.post2" "version": "==1.4.4.post2"
}, },
"atpublic": { "atpublic": {
@ -34,11 +35,11 @@
}, },
"attrs": { "attrs": {
"hashes": [ "hashes": [
"sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30",
"sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==23.1.0" "version": "==23.2.0"
}, },
"python-jata": { "python-jata": {
"hashes": [ "hashes": [
@ -46,6 +47,7 @@
"sha256:ff4cd7ca75c9a8306b69ef6e878c296a5602f3279c6f9a82b6105b8eba764760" "sha256:ff4cd7ca75c9a8306b69ef6e878c296a5602f3279c6f9a82b6105b8eba764760"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==1.2" "version": "==1.2"
} }
}, },

View File

@ -1,6 +1,6 @@
# Mail4one # Mail4one
Personal mail server for a single user or a small family. Written in pure python with minimal dependencies. Personal mail server for a single user or a small family. Written in pure python with [minimal dependencies](Pipfile).
Designed for dynamic alias based workflow where a different alias is used for each purpose. Designed for dynamic alias based workflow where a different alias is used for each purpose.
# Getting started # Getting started
@ -23,7 +23,7 @@ Mail4one only takes care of receiving and serving email. For sending email, use
Most of them have generous free tier which is more than enough for personal use. Most of them have generous free tier which is more than enough for personal use.
Sending email is tricky. Even if everything is correctly setup (DMARC, DKIM, SPF), popular email vendors like google, microsoft may mark emails sent from your IP as spam for no reason. Sending email is tricky. Even if everything is correctly setup (DMARC, DKIM, SPF), popular email vendors like google, microsoft may mark emails sent from your IP as spam for no reason. Hence using a dedicated service is the only reliable way to send emails.
# Community # Community
@ -62,7 +62,7 @@ This should generate `mail4one.pyz` in current folder. This is a [executable pyt
* Write dedicated documentation * Write dedicated documentation
* Test with more email clients ([Thunderbird](https://www.thunderbird.net/) and [k9mail](https://k9mail.app/) are tested now) * Test with more email clients ([Thunderbird](https://www.thunderbird.net/) and [k9mail](https://k9mail.app/) are tested now)
* IMAP support * IMAP support
* Web UI for editing config * Web UI for editing config ([WIP](https://github.com/mail4one/mail4one/tree/webform))
* Support email submission from client to forward to other senders or direct delivery * Support email submission from client to forward to other senders or direct delivery
* Optional SPAM filtering * Optional SPAM filtering
* Optional DMARC,SPF,DKIM verification * Optional DMARC,SPF,DKIM verification

View File

@ -2,13 +2,11 @@
# certbot deploy hook to copy certificates to mail4one when renewed. # certbot deploy hook to copy certificates to mail4one when renewed.
# Initial setup, Install certbot(https://certbot.eff.org/) and run `certbot certonly` as root # Initial setup, Install certbot(https://certbot.eff.org/) and run `certbot certonly` as root
# Doc: https://eff-certbot.readthedocs.io/en/latest/using.html#renewing-certificates
# #
# This file is supposed to be copied to /etc/letsencrypt/renewal-hooks/deploy/ # This file is supposed to be copied to /etc/letsencrypt/renewal-hooks/deploy/
# Change the mail domain to the one on MX record # Change the mail domain to the one on MX record
set -x
if [ "$RENEWED_DOMAINS" = "mail.mydomain.com" ] if [ "$RENEWED_DOMAINS" = "mail.mydomain.com" ]
then then
mkdir -p /var/lib/mail4one/certs mkdir -p /var/lib/mail4one/certs
@ -17,4 +15,5 @@ then
cp "$RENEWED_LINEAGE/fullchain.pem" /var/lib/mail4one/certs/ cp "$RENEWED_LINEAGE/fullchain.pem" /var/lib/mail4one/certs/
cp "$RENEWED_LINEAGE/privkey.pem" /var/lib/mail4one/certs/ cp "$RENEWED_LINEAGE/privkey.pem" /var/lib/mail4one/certs/
systemctl restart mail4one.service systemctl restart mail4one.service
echo "$(date) Renewed and deployed certificates for mail4one" >> /var/log/mail4one-cert-renew.log
fi fi

View File

@ -25,8 +25,6 @@ logger = logging.getLogger("smtp")
class MyHandler(AsyncMessage): class MyHandler(AsyncMessage):
peer_addr: Optional[str]
def __init__(self, mails_path: Path, mbox_finder: Callable[[str], list[str]]): def __init__(self, mails_path: Path, mbox_finder: Callable[[str], list[str]]):
super().__init__() super().__init__()
self.mails_path = mails_path self.mails_path = mails_path
@ -36,11 +34,7 @@ class MyHandler(AsyncMessage):
self, server: SMTPServer, session: SMTPSession, envelope: SMTPEnvelope self, server: SMTPServer, session: SMTPSession, envelope: SMTPEnvelope
) -> str: ) -> str:
self.rcpt_tos = envelope.rcpt_tos self.rcpt_tos = envelope.rcpt_tos
match session.peer: self.peer = session.peer
case addr, port:
self.peer_addr = addr
case _:
self.peer_addr = session.peer
return await super().handle_DATA(server, session, envelope) return await super().handle_DATA(server, session, envelope)
async def handle_message(self, m: Message): # type: ignore[override] async def handle_message(self, m: Message): # type: ignore[override]
@ -49,7 +43,7 @@ class MyHandler(AsyncMessage):
for mbox in self.mbox_finder(addr.lower()): for mbox in self.mbox_finder(addr.lower()):
all_mboxes.add(mbox) all_mboxes.add(mbox)
if not all_mboxes: if not all_mboxes:
logger.warning(f"dropping message from: {self.peer_addr}") logger.warning(f"dropping message from: {self.peer}")
return return
for mbox in all_mboxes: for mbox in all_mboxes:
for sub in ("new", "tmp", "cur"): for sub in ("new", "tmp", "cur"):
@ -64,7 +58,7 @@ class MyHandler(AsyncMessage):
for mbox in all_mboxes: for mbox in all_mboxes:
shutil.copy(temp_email_path, self.mails_path / mbox / "new") shutil.copy(temp_email_path, self.mails_path / mbox / "new")
logger.info( logger.info(
f"Saved mail at {filename} addrs: {','.join(self.rcpt_tos)}, mboxes: {','.join(all_mboxes)} peer: {self.peer_addr}" f"Saved mail at {filename} addrs: {','.join(self.rcpt_tos)}, mboxes: {','.join(all_mboxes)} peer: {self.peer}"
) )

View File

@ -1,5 +1,5 @@
-i https://pypi.org/simple -i https://pypi.org/simple
aiosmtpd==1.4.4.post2 aiosmtpd==1.4.4.post2; python_version ~= '3.7'
atpublic==4.0 ; python_version >= '3.8' atpublic==4.0; python_version >= '3.8'
attrs==23.1.0 ; python_version >= '3.7' attrs==23.2.0; python_version >= '3.7'
python-jata==1.2 python-jata==1.2; python_version >= '3.8'

View File

@ -8,7 +8,7 @@ then
tag_val=$(git describe --dirty=DIRTY --exact-match) tag_val=$(git describe --dirty=DIRTY --exact-match)
case "$tag_val" in case "$tag_val" in
*DIRTY) *DIRTY)
echo "git=$commit-changes" echo "git-$commit-changes"
;; ;;
v*) # Only consider tags starting with v v*) # Only consider tags starting with v
echo "$tag_val" echo "$tag_val"