Implement configurable STATE_DIR for state files

Move processed_message_ids.txt and last_run.txt to STATE_DIR,
configurable via .env. Install script creates ./state by default.

Changes:
- Add STATE_DIR to .env.example and config loading
- Update EmailForwarder to use os.path.join for file paths
- Add os.makedirs for state dir creation
- Update README with STATE_DIR option
This commit is contained in:
Jeena 2026-01-04 19:45:48 +09:00
parent 4adce1079f
commit a32f79980e
3 changed files with 13 additions and 10 deletions

View file

@ -7,6 +7,7 @@ DEST_PORT=993
DEST_USER=your@mxroute.com
DEST_PASS=password
FOLDERS=INBOX,Sent
STATE_DIR=./state
DRY_RUN=false
UPTIME_SUCCESS_URL=https://your-uptime-instance.com/api/push/your-token?status=up&msg=OK&ping=
UPTIME_FAIL_URL=https://your-uptime-instance.com/api/push/your-token?status=down&msg=Failed

View file

@ -19,6 +19,7 @@ This will set up the virtual environment, systemd services, and provide post-ins
- `DEST_USER`: Destination email/username
- `DEST_PASS`: Destination password
- `FOLDERS`: Folders to sync (default "INBOX,Sent"; use "all" for all folders or comma-separated list)
- `STATE_DIR`: Directory for state files (default "./state")
- `DRY_RUN`: Set to `true` for testing without forwarding (default false)
- `UPTIME_SUCCESS_URL`: URL for success ping
- `UPTIME_FAIL_URL`: URL for failure ping

View file

@ -126,6 +126,10 @@ class EmailForwarder:
def __init__(self, config: dict):
self.config = config
self.state_dir = config["state_dir"]
os.makedirs(self.state_dir, exist_ok=True)
self.processed_file = os.path.join(self.state_dir, "processed_message_ids.txt")
self.last_run_file = os.path.join(self.state_dir, "last_run.txt")
self.source = SourceImap(
config["source_host"],
config["source_port"],
@ -144,23 +148,21 @@ class EmailForwarder:
def load_state(self):
"""Load last run and processed IDs."""
last_run_file = self.config["last_run_file"]
if os.path.exists(last_run_file):
with open(last_run_file) as f:
if os.path.exists(self.last_run_file):
with open(self.last_run_file) as f:
last_run_str = f.read().strip()
self.last_run = datetime.fromisoformat(last_run_str)
else:
self.last_run = datetime.now(timezone.utc)
processed_file = self.config["processed_file"]
if os.path.exists(processed_file):
with open(processed_file, "r") as f:
if os.path.exists(self.processed_file):
with open(self.processed_file, "r") as f:
self.processed_ids = set(filter(None, (line.strip() for line in f)))
def save_state(self):
"""Save last run and processed IDs."""
if not self.dry_run:
with open(self.config["last_run_file"], "w") as f:
with open(self.last_run_file, "w") as f:
f.write(datetime.now(timezone.utc).isoformat())
def sync_all_folders(self):
@ -209,7 +211,7 @@ class EmailForwarder:
if not self.dry_run:
self.dest.append_email(folder, raw_msg)
self.processed_ids.add(msg_id)
with open(self.config["processed_file"], "a") as f:
with open(self.processed_file, "a") as f:
f.write(msg_id + "\n")
forwarded += 1
logging.info(f"Forwarded {msg_id} from {folder}")
@ -239,8 +241,7 @@ def load_config():
"dest_user": os.getenv("DEST_USER"),
"dest_pass": os.getenv("DEST_PASS"),
"folders": os.getenv("FOLDERS", "INBOX"),
"processed_file": "processed_message_ids.txt",
"last_run_file": "last_run.txt",
"state_dir": os.getenv("STATE_DIR", "./state"),
"dry_run": os.getenv("DRY_RUN", "false").lower() == "true",
}
# Validate required