itsrda

Python ile Günlük Özet E-postası: smtplib, Gmail Uygulama Şifresi ve systemd Timer

Bir sistemin günlük durumunu her sabah e-postayla almak için Python'da güvenli SMTP gönderimi, uygulama şifresinin saklanması ve zamanlanmış çalıştırma adım adım anlatılıyor.

Bir sistemin başında durmadan, ne durumda olduğunu bilmek istersiniz: dün kaç kayıt işlendi, yedek alındı mı, disk ne kadar dolu, bir servis düştü mü? Her sabah gelen kısa bir özet e-posta, bir paneli açıp bakmaktan çok daha güvenilir bir alışkanlık yaratır; çünkü bilgi size gelir. Bu yazıda Python standart kütüphanesiyle, ek bağımlılık olmadan bunu nasıl kuracağınızı gösteriyoruz.

1. Gmail'de uygulama şifresi

Gmail artık hesabın normal parolasıyla betiklerin SMTP üzerinden giriş yapmasına izin vermez. Doğru yol bir uygulama şifresi oluşturmaktır:

  1. Google Hesabınızda iki adımlı doğrulamayı etkinleştirin (uygulama şifresi bunsuz çalışmaz).
  2. Hesap ayarlarında "Uygulama şifreleri" bölümünden yeni bir şifre oluşturun ve adına örneğin sunucu-ozet yazın.
  3. Google 16 karakterlik bir şifre gösterir; yalnızca bir kez görürsünüz. Boşlukları çıkararak kaydedin.

Bu şifre normal parolanızdan ayrı ve iptal edilebilirdir. Sızarsa yalnızca onu silersiniz, hesabınızın parolası etkilenmez. Yine de bir şifre gibi koruyun.

Kurumsal bir Google Workspace hesabı kullanıyorsanız yöneticinizin uygulama şifrelerine izin verip vermediğini kontrol edin; bazı kurumlar bunu kapatır. Alternatif olarak bir transactional e-posta servisi kullanabilirsiniz.

2. Şifreyi koddan ayrı saklamak

Şifreyi betiğin içine yazmayın. Yalnızca çalıştıracak kullanıcının okuyabileceği bir ortam dosyasına koyun:

nano ~/ozet.env
[email protected]
MAIL_SIFRE=abcdefghijklmnop
[email protected]
chmod 600 ~/ozet.env

Dosya izninin 600 olması, diğer kullanıcıların okuyamaması demektir. Bu dosyayı hiçbir zaman bir kod deposuna (git) eklemeyin; .gitignore içine yazın.

3. Mesajı oluşturmak ve göndermek

Python'un email.message.EmailMessage sınıfı, Türkçe karakterleri ve başlıkları doğru kodlar. Gönderim için smtplib.SMTP_SSL ile 465 numaralı porta bağlanın:

#!/usr/bin/env python3
import os, smtplib, shutil, socket
from datetime import date
from email.message import EmailMessage

def env(ad):
    try:
        return os.environ[ad]
    except KeyError:
        raise SystemExit(f"Eksik ayar: {ad}")

def ozet_metni():
    toplam, kullanilan, bos = shutil.disk_usage("/")
    satirlar = [
        f"Sunucu: {socket.gethostname()}",
        f"Disk: %{kullanilan * 100 // toplam} dolu ({bos // 2**30} GB boş)",
    ]
    return "\n".join(satirlar)

def gonder(konu, metin):
    msg = EmailMessage()
    msg["Subject"] = konu
    msg["From"] = env("MAIL_KULLANICI")
    msg["To"] = env("MAIL_ALICI")
    msg.set_content(metin)
    with smtplib.SMTP_SSL("smtp.gmail.com", 465, timeout=30) as s:
        s.login(env("MAIL_KULLANICI"), env("MAIL_SIFRE"))
        s.send_message(msg)

if __name__ == "__main__":
    gonder(f"Günlük özet {date.today():%d.%m.%Y}", ozet_metni())
    print("gönderildi")

Dikkat edilecekler:

  • timeout=30 ağ sorununda betiğin sonsuza dek asılmasını önler.
  • Hata durumunda betik sıfırdan farklı bir kodla çıkmalı ki systemd bunu başarısızlık olarak görsün (aşağıda).
  • Alternatif olarak 587 portunda SMTP + starttls() de kullanılır; ikisi de güvenlidir, ama yapılandırmayı karıştırmayın.

4. Test: elle çalıştırmak

Ortam dosyasını yükleyip betiği deneyin:

set -a; . ~/ozet.env; set +a
python3 ozet.py

set -a sonrası okunan değişkenler dışa aktarılır. Mail gelmiyorsa önce spam klasörüne bakın; ardından SMTPAuthenticationError alıyorsanız şifrede boşluk ya da yanlış karakter vardır ya da iki adımlı doğrulama etkin değildir.

5. Özetin içeriği: "okunan" değil "işe yarayan"

Her gün aynı şeyi yazan bir e-posta bir süre sonra okunmaz. İyi bir özet, normalden sapmayı öne çıkarır:

  • Dünkü kayıt sayısı ve bir önceki güne göre fark.
  • Son yedeğin zamanı ve başarılı olup olmadığı; yedek 24 saatten eskiyse konu satırında uyarı verin.
  • Disk doluluğu eşik değeri (örneğin %85) aşarsa uyarı.
  • Çalışması gereken servislerin durumu (systemctl is-active).
  • Eksik veya şüpheli kayıtlar (örneğin kapanmamış vardiyalar).

Konu satırına durumu yazın: "Günlük özet — sorun yok" ve "Günlük özet — 2 UYARI" gibi. Böylece e-postayı açmadan bile önemli olanı ayırt edersiniz. Bir servisin durumunu betikten sorgulamak için:

import subprocess
def aktif_mi(servis):
    r = subprocess.run(["systemctl", "is-active", servis], capture_output=True, text=True)
    return r.stdout.strip() == "active"

6. systemd ile her sabah çalıştırmak

Betiği bir servis ve bir timer ile zamanlayın. /etc/systemd/system/ozet.service:

[Unit]
Description=Gunluk ozet e-postasi

[Service]
Type=oneshot
User=kullanici
EnvironmentFile=/home/kullanici/ozet.env
ExecStart=/usr/bin/python3 /home/kullanici/ozet.py

/etc/systemd/system/ozet.timer:

[Unit]
Description=Her sabah 08:00 ozet

[Timer]
OnCalendar=*-*-* 08:00:00
Persistent=true

[Install]
WantedBy=timers.target

Etkinleştirin ve doğrulayın:

sudo systemctl daemon-reload
sudo systemctl enable --now ozet.timer
systemctl list-timers ozet.timer
sudo systemctl start ozet.service     # hemen bir deneme
journalctl -u ozet.service -n 20

Persistent=true, cihaz o saatte kapalıysa açıldığında kaçırılan çalıştırmanın yapılmasını sağlar. Cron ile karşılaştırması için systemd timer yazısına bakın.

7. Güvenlik notları

  • Betik yalnızca giden bağlantı kurar; sunucunuzda yeni bir port açmanız gerekmez.
  • E-postada kişisel veri gönderiyorsanız (örneğin isim listesi), bunun KVKK açısından sonuçlarını düşünün. Özetlerde mümkünse sayı ve durum bilgisi verin, kişi listesini panelde tutun (ayrıntılar).
  • Alıcı adresini değiştirmek betiği değiştirmeyi gerektirmesin; ortam dosyasından okuyun.
  • E-posta yoluyla gelen bir alarmın sessiz kalması da bir arızadır. Özetin gelmediğini fark etmek için, haftada bir bakmayı ya da dışarıdan bir "heartbeat" servisi kullanmayı düşünün.

Bu küçük betik, bir sistemi gözlemlemenin en ucuz yollarından biridir. Üzerine yeni kontroller eklemek kolaydır ve her ek, bir sorunu sizi aramadan önce yakalama şansı verir.

Not: Komutları üretim ortamında çalıştırmadan önce yedek alın ve önce test ortamında deneyin. Bir adım sizde farklı davranıyorsa bize yazın, rehberi güncelleyelim.