#!/bin/bash
# Cloud Raiser - EC2 开机自举（AL2023 健壮版）
# 关键改动：去掉 set -e、所有安装加 || true、直接用 dnf、自动建库建表、正确删除 nginx 默认 server 块
DB_HOST="database-1.cluster-cfud9bwy8iiy.rds.cn-northwest-1.amazonaws.com.cn"
DB_USER="admin"
DB_PASS="123456789QWERTY"
DB_NAME="crdb"
AWS_REGION="cn-northwest-1"
DEPLOY_BUCKET="cr-autodeploy-wsc57980405"
exec > >(tee /var/log/user-data.log) 2>&1
echo "==== start $(date) ===="

# ---------- 1. 装依赖（全程不中断）----------
dnf update -y || true
dnf install -y python3 python3-pip nginx gcc python3-devel || true
dnf install -y cronie || true
dnf install -y mariadb105 || true
pip3 install --quiet --upgrade pip || true
pip3 install --quiet flask gunicorn boto3 pymysql || true
echo "[1] deps done"

# ---------- 2. API 代码 ----------
mkdir -p /opt/crapp /opt/webapp /etc/crapp.d
cat > /opt/crapp/api.py <<'PYEOF'
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Cloud Raiser · 浙江省选拔赛 - 云计算项目 - 架构完善模块 - 阶段1 Restful API
=========================================================================
端点：
    POST /post_data1         把 {id,value} 写入 DynamoDB（表 cr-data1）
    POST /post_data2         把 {id,value} 写入 Aurora MySQL（表 data2）
    GET  /get_value?id=01    从两库按 id 查 value，求和后返回 {"message":"579"}
    GET  /healthz            健康检查

部署方式：Gunicorn + Nginx（端口 80 反向代理到 5000；Web 应用 8082）
"""
import os
import json
import logging
from flask import Flask, request, jsonify
import boto3
import pymysql
from botocore.config import Config

# ---------------- 日志 ----------------
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
    handlers=[
        logging.StreamHandler(),
        logging.FileHandler("/var/log/cr-api.log", encoding="utf-8"),
    ],
)
log = logging.getLogger("cr-api")

app = Flask(__name__)

# ---------------- DynamoDB ----------------
DDB_TABLE = os.environ.get("DDB_TABLE", "cr-data1")
AWS_REGION = os.environ.get("AWS_REGION", "cn-northwest-1")
ddb = boto3.resource(
    "dynamodb",
    region_name=AWS_REGION,
    config=Config(retries={"max_attempts": 3, "mode": "standard"}),
)
table_ddb = ddb.Table(DDB_TABLE)

# ---------------- Aurora MySQL ----------------
DB_HOST = os.environ["DB_HOST"]
DB_USER = os.environ.get("DB_USER", "admin")
DB_PASS = os.environ["DB_PASS"]
DB_NAME = os.environ.get("DB_NAME", "crdb")


def db_conn():
    return pymysql.connect(
        host=DB_HOST,
        user=DB_USER,
        password=DB_PASS,
        database=DB_NAME,
        charset="utf8mb4",
        connect_timeout=5,
        read_timeout=10,
        write_timeout=10,
    )


def _ensure_db_and_table():
    """首次启动时自动创建 database 和 data2 表（建库阶段没建也能自愈）"""
    # 1) 先连 MySQL server（不带 database）建库
    cn = pymysql.connect(
        host=DB_HOST,
        user=DB_USER,
        password=DB_PASS,
        charset="utf8mb4",
        connect_timeout=5,
        read_timeout=10,
        write_timeout=10,
    )
    try:
        with cn.cursor() as c:
            c.execute(f"CREATE DATABASE IF NOT EXISTS {DB_NAME} CHARACTER SET utf8mb4")
        cn.commit()
    finally:
        cn.close()

    # 2) 再连目标库建表
    cn = db_conn()
    try:
        with cn.cursor() as c:
            c.execute(
                """CREATE TABLE IF NOT EXISTS data2 (
                    id   VARCHAR(64)  PRIMARY KEY,
                    value VARCHAR(255) NOT NULL
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"""
            )
        cn.commit()
    finally:
        cn.close()


# 启动时建库建表
try:
    _ensure_db_and_table()
    log.info("data2 table ensured")
except Exception as e:  # pragma: no cover
    log.warning("ensure_table failed: %s", e)


# ---------------- 路由 ----------------
@app.route("/healthz", methods=["GET"])
def healthz():
    return jsonify(ok=True)


@app.route("/post_data1", methods=["POST"])
def post_data1():
    body = request.get_json(force=True, silent=True) or {}
    _id = str(body.get("id", "")).strip()
    val = body.get("value", "")
    if not _id:
        return jsonify(message="id required"), 400
    table_ddb.put_item(Item={"id": _id, "value": str(val)})
    log.info("DDB PUT id=%s val=%s", _id, val)
    return jsonify(message="ok")


@app.route("/post_data2", methods=["POST"])
def post_data2():
    body = request.get_json(force=True, silent=True) or {}
    _id = str(body.get("id", "")).strip()
    val = body.get("value", "")
    if not _id:
        return jsonify(message="id required"), 400
    cn = db_conn()
    try:
        with cn.cursor() as c:
            c.execute(
                """INSERT INTO data2 (id, value) VALUES (%s, %s)
                   ON DUPLICATE KEY UPDATE value=VALUES(value)""",
                (_id, val),
            )
        cn.commit()
    finally:
        cn.close()
    log.info("RDS PUT id=%s val=%s", _id, val)
    return jsonify(message="ok")


@app.route("/get_value", methods=["GET"])
def get_value():
    _id = str(request.args.get("id", "")).strip()
    if not _id:
        return jsonify(message="id required"), 400

    ddb_val = (
        table_ddb.get_item(Key={"id": _id})
        .get("Item", {})
        .get("value", "0")
    )

    rds_val = "0"
    cn = db_conn()
    try:
        with cn.cursor() as c:
            c.execute("SELECT value FROM data2 WHERE id=%s", (_id,))
            row = c.fetchone()
            if row:
                rds_val = row[0]
    finally:
        cn.close()

    try:
        total = int(ddb_val) + int(rds_val)
    except (TypeError, ValueError):
        total = f"{ddb_val}{rds_val}"
    log.info("GET id=%s ddb=%s rds=%s total=%s", _id, ddb_val, rds_val, total)
    return jsonify(message=str(total))


# ---------------- 入口 ----------------
if __name__ == "__main__":
    # 直接 python3 跑也支持；生产由 gunicorn 启动
    app.run(host="127.0.0.1", port=5000)
PYEOF
echo "[2] api.py written"

# ---------- 3. systemd ----------
cat > /etc/crapp.env <<EOF
AWS_REGION=${AWS_REGION}
DDB_TABLE=cr-data1
DB_HOST=${DB_HOST}
DB_USER=${DB_USER}
DB_PASS=${DB_PASS}
DB_NAME=${DB_NAME}
EOF
chmod 600 /etc/crapp.env

cat > /etc/systemd/system/cr-api.service <<'SVCEOF'
[Unit]
Description=CR API (Flask via Gunicorn)
After=network.target
[Service]
WorkingDirectory=/opt/crapp
EnvironmentFile=/etc/crapp.env
ExecStart=/usr/bin/python3 -m gunicorn --workers 2 --threads 4 --bind 127.0.0.1:5000 api:app
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target

SVCEOF
echo "[3] systemd unit written"

# ---------- 4. Nginx ----------
echo "${DEPLOY_BUCKET}" > /etc/crapp.d/bucket
mkdir -p /etc/nginx/conf.d
cat > /etc/nginx/conf.d/crapp.conf <<'NGXEOF'
server {
    listen 80 default_server;
    server_name _;
    client_max_body_size 16m;
    location = /post_data1 { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; }
    location = /post_data2 { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; }
    location = /get_value  { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; }
    location = /healthz    { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; }
    location / { proxy_pass http://127.0.0.1:8082; proxy_set_header Host $host; }
}
NGXEOF
if [ -f /etc/nginx/nginx.conf ]; then
  python3 - <<'PYFIX' || true
import re
p='/etc/nginx/nginx.conf'
s=open(p).read()
# 删除默认 server { ... } 块（含嵌套大括号），避免与 conf.d/crapp.conf 冲突
start=-1
for i in range(len(s)):
    if s.startswith('server {', i) and (i==0 or not s[i-1].isalnum()):
        start=i
        break
if start>=0:
    depth=1
    i=start+len('server {')
    while i<len(s) and depth>0:
        if s[i]=='{': depth+=1
        elif s[i]=='}': depth-=1
        i+=1
    s=s[:start]+'    # default server block removed by user-data\n'+s[i:]
open(p,'w').write(s)

PYFIX
fi
echo "[4] nginx config written"

# ---------- 5. 自动发布（cron 轮询 S3 ETag）----------
cat > /opt/crapp/autodeploy.sh <<'WDEOF'
#!/bin/bash
LOCK=/var/lock/cr-autodeploy.lock
WORK=/opt/webapp
BUCKET=$(cat /etc/crapp.d/bucket 2>/dev/null)
[ -z "$BUCKET" ] && exit 0
[ -e "$LOCK" ] && exit 0
trap "rm -f $LOCK" EXIT
touch "$LOCK"
aws s3api get-object --bucket "$BUCKET" --key server.zip /tmp/server.zip.new      --query ETag --output text > /tmp/etag.new 2>/dev/null || exit 0
ETAG=$(cat /tmp/etag.new | tr -d '"')
[ -e "$WORK/.last_etag" ] && [ "$(cat "$WORK/.last_etag")" = "$ETAG" ] && exit 0
echo "[autodeploy] new server.zip detected ($ETAG)"
rm -rf "$WORK/app" && mkdir -p "$WORK/app"
unzip -qo /tmp/server.zip.new -d "$WORK/app" || true
# 关键：unzip 不会还原 zip 里存的执行权限位，必须手动补，否则 run.sh 起不来（502）
chmod +x "$WORK/app/run.sh" 2>/dev/null || true
chmod -R a+rX "$WORK/app" 2>/dev/null || true
echo "$ETAG" > "$WORK/.last_etag"
printf '{"server_port": 8082}\n' > "$WORK/app/config.json"
pkill -f /opt/webapp/app || true
sleep 1
( cd "$WORK/app" && nohup ./run.sh > /var/log/webapp.log 2>&1 & ) || true
echo "[autodeploy] web app launched"

WDEOF
chmod +x /opt/crapp/autodeploy.sh
echo "* * * * * root /opt/crapp/autodeploy.sh >> /var/log/autodeploy.log 2>&1" > /etc/cron.d/cr-autodeploy || true
systemctl enable crond 2>/dev/null || true
systemctl restart crond 2>/dev/null || true
echo "[5] autodeploy installed"

# ---------- 6. 启动服务 ----------
systemctl daemon-reload
systemctl enable cr-api 2>/dev/null || true
systemctl enable nginx 2>/dev/null || true
systemctl restart cr-api 2>/dev/null || true
systemctl restart nginx 2>/dev/null || true
echo "[6] services started"

# ---------- 7. 自检 ----------
sleep 5
echo "--- nginx: $(systemctl is-active nginx) ---"
echo "--- cr-api: $(systemctl is-active cr-api) ---"
echo "--- healthz: $(curl -s -m 5 http://127.0.0.1/healthz) ---"
echo "==== done $(date) ===="
