# Deploy Hook

Deploy Hook 是 Certbot 中用于在证书成功签发或续期后自动执行脚本的一种机制。它常用于**自动化部署证书**，比如重启服务、复制证书、更新配置等。

---

## 一、Certbot Hook 简介

在 Certbot 的生命周期中，主要有三种 Hook：

| Hook 类型        | 触发时机 |
|------------------|--------|
| `--pre-hook`     | 申请/续期前执行 |
| `--post-hook`    | 申请/续期后执行（无论成功与否） |
| `--deploy-hook`  | **仅在证书成功更新后执行** |

Deploy Hook 是最常用的一种自动化方式，因为它只在证书真正更新时触发，避免不必要操作。

---

## 二、Deploy Hook 适用场景

Deploy Hook 常用于：

- 重启 Web 服务（Nginx / Apache）
- 重新加载 TLS 配置
- 复制证书到其他路径
- 通知其他系统（如 API、负载均衡器）
- 更新容器或服务中的证书

---

## 三、基本使用方法

### 1. 在命令行中使用

```bash
certbot renew --deploy-hook "/usr/local/bin/reload-nginx.sh"
```

或申请证书时：

```bash
certbot certonly \
  --deploy-hook "/usr/local/bin/deploy-cert.sh" \
  -d example.com
```

### 2. 使用目录自动执行（推荐）

Certbot 支持自动执行 Hook 目录中的脚本：

目录路径 : `/etc/letsencrypt/renewal-hooks/deploy/`

只需将脚本放入该目录：

```bash
/etc/letsencrypt/renewal-hooks/deploy/
```

并赋予执行权限：

```bash
chmod +x /etc/letsencrypt/renewal-hooks/deploy/your-script.sh
```
**Certbot 在成功续期后会自动执行这些脚本。**

## 四、Deploy Hook 脚本示例

**示例 1：重启 Nginx**

```sh
#!/bin/bash
systemctl reload nginx
```

**示例 2：复制证书到自定义路径**

```sh
#!/bin/bash

DOMAIN="example.com"
LE_PATH="/etc/letsencrypt/live/$DOMAIN"
TARGET_PATH="/opt/certs"

cp "$LE_PATH/fullchain.pem" "$TARGET_PATH/fullchain.pem"
cp "$LE_PATH/privkey.pem" "$TARGET_PATH/privkey.pem"

echo "Certificates updated for $DOMAIN"
```

**示例 3：通知系统更新（HTTP API）**

```sh
#!/bin/bash

curl -X POST https://api.example.com/cert-updated \
  -d "domain=$RENEWED_LINEAGE"

echo "Notification sent"
```

## 五、工业级 Deploy Hook 架构设计（可维护 + 可扩展）

在实际生产环境中，直接把逻辑写在 `/etc/letsencrypt/renewal-hooks/deploy/` 里的单个脚本，很快会变得难以维护。

更推荐采用一种**入口调度 + 模块化脚本架构**：

> Deploy Hook 只负责“调度”，具体逻辑拆分到多个独立脚本

---

### 一、整体架构设计

推荐目录结构如下：

脚本入口:

```
/etc/letsencrypt/renewal-hooks/deploy/
└── dispatcher.sh

/usr/local/lib/certbot/
├── deploy/
│   ├── 10-axigen.sh
│   ├── 20-hysteria.sh
│   └── 90-notify.sh
│
├── lib/
│   ├── common.sh
│   └── logger.sh
```

### 二、核心设计思想

#### 1. 入口统一

Certbot 只调用 :

```bash
/etc/letsencrypt/renewal-hooks/deploy/dispatcher.sh
```

#### 2. 业务解耦

每个服务一个脚本：

- Nginx → 10-nginx.sh
- Docker → 20-docker.sh
- Hysteria → 30-hysteria.sh
- 通知系统 → 90-notify.sh

用数字排序控制执行顺序

#### 3. 可维护性
单个脚本职责单一
可单独测试
可独立启用/禁用

### 三、入口调度脚本（Dispatcher）

`/etc/letsencrypt/renewal-hooks/deploy/dispatcher.sh`

```bash
#!/bin/bash
set -euo pipefail

HOOK_DIR="/usr/local/lib/certbot/deploy"

echo "[certbot] deploy hook started"

# 按顺序执行所有脚本
for script in "$HOOK_DIR"/*.sh; do
    if [ -x "$script" ]; then
        echo "[certbot] running: $script"
        "$script"
    else
        echo "[certbot] skip (not executable): $script"
    fi
done

echo "[certbot] deploy hook finished"
```

核心点：
- 自动执行所有模块
- 自动排序（通过文件名）
- 可扩展性极强

### 四、模块脚本示例
示例 1：Nginx Reload
`/usr/local/lib/certbot/deploy/10-nginx.sh`

```bash
#!/bin/bash
set -euo pipefail

echo "[nginx] reloading..."

systemctl reload nginx

echo "[nginx] done"
```

示例 2：Docker 证书更新
`/usr/local/lib/certbot/deploy/20-docker.sh`

```bash
#!/bin/bash
set -euo pipefail

DOMAIN="example.com"
SRC="/etc/letsencrypt/live/$DOMAIN"

TARGET="/opt/docker/certs"

echo "[docker] updating certs..."

cp "$SRC/fullchain.pem" "$TARGET/fullchain.pem"
cp "$SRC/privkey.pem" "$TARGET/privkey.pem"

docker restart my-container

echo "[docker] done"
```

示例 3：Hysteria / 其他服务
`/usr/local/lib/certbot/deploy/30-hysteria.sh`

```
#!/bin/bash
set -euo pipefail

LE_PATH="/etc/letsencrypt/live/www.example.com"

cp "$LE_PATH/fullchain.pem" /etc/hysteria/cert.pem
cp "$LE_PATH/privkey.pem" /etc/hysteria/key.pem

systemctl restart hysteria

echo "[hysteria] restarted"
```

示例 4：通知系统
`/usr/local/lib/certbot/deploy/90-notify.sh`

```bash
#!/bin/bash
set -euo pipefail

echo "[notify] sending webhook..."

curl -fsS -X POST https://example.com/webhook \
  -d "event=cert_updated&domain=${RENEWED_LINEAGE}"

echo "[notify] done"
```

### 五、公共库（可选但推荐）

#### 日志工具层

作用：统一日志输出格式,每个脚本各写各的日志格式

> Q:它解决什么问题？

> A:没有 logger：


示例 ：临时日志(带时间戳的 echo)
`/usr/local/lib/certbot/lib/logger.sh`

```bash
log() {
    echo "[$(date +'%F %T')] $*"
}
```

**本质理解**

`logger.sh` = 日志规范层
- 统一输出格式
- 方便后续接入：
    - syslog
    - journald
    - ELK / Loki
- 未来可以轻松升级日志系统，而不用改业务脚本

---

#### 通用逻辑层

这个脚本通常放：
- 公共函数
- 公共变量
- 常用判断逻辑

示例 : 函数
`/usr/local/lib/certbot/lib/common.sh`

```bash
#!/bin/bash

get_cert_path() {
    echo "${RENEWED_LINEAGE:-}"
}
```

> Q: 它解决什么问题？

> A: 没有 `common.sh` 每个脚本都要写：

```
CERT_PATH="/etc/letsencrypt/live/example.com"
```

或:
```
if [ -z "$RENEWED_LINEAGE" ]; then
  echo "error"
  exit 1
fi
```
重复 + 易出错

有 `common.sh` ：

```
source /usr/local/lib/certbot/lib/common.sh

CERT_PATH=$(get_cert_path)
```

统一入口

**本质理解**

`common.sh` = 业务基础能力层

它提供：
- 环境变量封装
- 路径获取
- 通用判断
- 公共工具函数

---

**两者关系（重点）**
可以用一个简单分层理解：

```
[ deploy 脚本（业务逻辑） ]
          ↓
[ common.sh（通用逻辑） ]
          ↓
[ logger.sh（基础输出能力） ]
```

**实际使用方式**

在你的 deploy 脚本中：

```bash
#!/bin/bash
set -euo pipefail

# 引入公共库
source /usr/local/lib/certbot/lib/logger.sh
source /usr/local/lib/certbot/lib/common.sh

log "[nginx] start"

CERT_PATH=$(get_cert_path)

log "[nginx] cert path: $CERT_PATH"

systemctl reload nginx

log "[nginx] done"
```