#!/usr/bin/env python3
"""
Portal Log Push Agent — HTTPS 推送方式（建議使用）
每次執行時讀取各 log 的新增行，透過 HTTPS POST 推送到 portal。
請用 cron 或 systemd timer 每 10 分鐘執行一次。

設定方式：
  1. 修改下方 PORTAL_URL 與 AGENT_TOKEN
  2. chmod +x /usr/local/bin/portal-log-push
  3. crontab -e  加入：*/10 * * * * /usr/local/bin/portal-log-push
"""

import json
import os
import sys
import urllib.request
import urllib.error

# ── 請修改這兩個設定 ─────────────────────────────────────────────
PORTAL_URL  = "https://portal.steps.tw"
AGENT_TOKEN = "YOUR_AGENT_TOKEN_HERE"
# ────────────────────────────────────────────────────────────────

STATE_FILE = "/var/lib/portal-log-push/state.json"
MAX_LINES  = 5000   # 每次最多推送行數（防止單次推太大）

LOG_SOURCES = [
    {"path": "/var/log/syslog",              "type": "syslog"},
    {"path": "/var/log/auth.log",            "type": "auth"},
    {"path": "/var/log/kern.log",            "type": "kernel"},
    {"path": "/var/log/apache2/access.log",  "type": "apache_access"},
    {"path": "/var/log/apache2/error.log",   "type": "apache_error"},
    {"path": "/var/log/nginx/access.log",    "type": "nginx_access"},
    {"path": "/var/log/nginx/error.log",     "type": "nginx_error"},
]

def load_state():
    try:
        with open(STATE_FILE) as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return {}

def save_state(state):
    os.makedirs(os.path.dirname(STATE_FILE), exist_ok=True)
    with open(STATE_FILE, "w") as f:
        json.dump(state, f)

def read_new_lines(path, last_inode, last_pos):
    try:
        st = os.stat(path)
    except FileNotFoundError:
        return [], last_inode, last_pos

    # 檔案被 rotate 了
    if st.st_ino != last_inode:
        last_pos = 0

    lines = []
    try:
        with open(path, errors="replace") as f:
            f.seek(last_pos)
            while len(lines) < MAX_LINES:
                line = f.readline()
                if not line:
                    break
                lines.append(line.rstrip("\n"))
            new_pos = f.tell()
    except PermissionError:
        return [], st.st_ino, last_pos

    return lines, st.st_ino, new_pos

def push_lines(lines, log_type):
    if not lines:
        return True
    body = json.dumps({"log_type": log_type, "lines": "\n".join(lines)}).encode()
    req  = urllib.request.Request(
        f"{PORTAL_URL}/api/logs/stream",
        data=body,
        headers={
            "Authorization": f"Bearer {AGENT_TOKEN}",
            "Content-Type":  "application/json",
            "Accept":        "application/json",
        },
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            return resp.status == 200
    except urllib.error.HTTPError as e:
        print(f"[portal-push] HTTP {e.code} for {log_type}", file=sys.stderr)
        return False
    except Exception as e:
        print(f"[portal-push] Error: {e}", file=sys.stderr)
        return False

def main():
    if AGENT_TOKEN == "YOUR_AGENT_TOKEN_HERE":
        print("[portal-push] 請先設定 AGENT_TOKEN", file=sys.stderr)
        sys.exit(1)

    state = load_state()
    changed = False

    for src in LOG_SOURCES:
        path = src["path"]
        if not os.path.exists(path):
            continue

        key   = path
        s     = state.get(key, {"inode": 0, "pos": 0})
        lines, inode, pos = read_new_lines(path, s["inode"], s["pos"])

        if lines:
            ok = push_lines(lines, src["type"])
            if ok:
                state[key] = {"inode": inode, "pos": pos}
                save_state(state)   # 每筆成功後立即存，防止 crash 後重傳
                changed = True
                print(f"[portal-push] {path}: pushed {len(lines)} lines")
        else:
            # 更新 inode（rotate 後 pos 歸零但還沒新行）
            if inode != s["inode"]:
                state[key] = {"inode": inode, "pos": 0}
                changed = True

    if changed:
        save_state(state)

if __name__ == "__main__":
    main()
