Let’s Encrypt 提供各种免费 SSL/TLS 证书
我们主要使用的是通配符证书
直接使用acme.sh申请就行,不需要使用官方的Certbot
获取腾讯云 API 密钥
登录 腾讯云控制台 → 访问管理 → API 密钥管理。
下载acme.sh
1
| curl https://get.acme.sh | sh -s email=your@email.com
|
设置腾讯云 API 环境变量
export Tencent_SecretId=”xxxxxx”
export Tencent_SecretKey=”xxxxxxxx”
首次申请证书
1 2 3 4 5 6
| # 申请证书(包含所有多个域名) acme.sh --issue --dns dns_tencent \ -d "whoisjory.com" \ -d "*.whoisjory.com" \ -d "storage.whoisjory.com" \ -d "*.storage.whoisjory.com"
|
- 这将只获得一个证书,但一个证书全都能用(每个证书最多包含 100个域名)
1 2
| # 可以查看一下证书包含的域名(记得换路径) openssl x509 -in /root/nginx/cert/fullchain.pem -text -noout | grep -A1 "X509v3 Subject Alternative Name"
|
现在有证书了, 先不要着急做下面的事件,去运行起来,run!
配置证书自动部署
1 2 3 4 5
| # 设置自动部署命令 acme.sh --install-cert -d whoisjory.com \ --key-file /root/nginx/cert/acme/privkey.pem \ --fullchain-file /root/nginx/cert/acme/fullchain.pem \ --reloadcmd "docker exec nginx nginx -s reload"
|
配置自动续签时间
Let’s Encrypt 证书有效期默认为 90 天
1 2 3 4 5 6
| # 编辑 acme.sh 的账户配置 vim ~/.acme.sh/account.conf
# 添加以下内容: AUTO_UPGRADE="1" #自动更新 acme.sh,避免因脚本版本过旧导致与Let's Encrypt API的兼容性问题 RENEW_DAYS_BEFORE_EXPIRE="60" #证书续签触发时间在证书到期前 60 天 开始
|
acme.sh 通过 cron 任务实现自动化,这表示每天午夜(00:00)检查一次证书状态
验证续签系统
1 2 3 4 5
| # 手动测试续签(不实际续签) acme.sh --renew -d whoisjory.com --force --test
# 强制立即续签(实际测试) acme.sh --renew -d whoisjory.com --force
|
两个命令的核心区别
| 命令类型 |
--issue (签发证书) |
--install-cert (安装证书) |
| 作用 |
向 Let’s Encrypt 申请新证书 |
将已申请的证书部署到指定路径并设置续签行为 |
| 使用频率 |
首次申请或需要新增域名时使用 |
每次证书签发/续签后自动执行 |
| 是否修改证书内容 |
是(生成新证书) |
否(仅配置部署规则) |
| 典型使用场景 |
证书首次申请、新增域名、证书撤销后重新申请 |
设置证书存储路径、配置服务重启命令 |
| 与 account.conf 关系 |
不受其直接影响 |
依赖 account.conf 中的 AUTO_UPGRADE 和 RENEW_DAYS_BEFORE_EXPIRE 参数控制自动续签行为 |
let artalkItem = null
const option = null
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const destroyArtalk = () => {
if (artalkItem) {
artalkItem.destroy()
artalkItem = null
}
}
const artalkChangeMode = theme => artalkItem && artalkItem.setDarkMode(theme === 'dark')
const initArtalk = (el = document, pageKey = location.pathname) => {
artalkItem = Artalk.init({
el: el.querySelector('#artalk-wrap'),
server: 'https://interact.whoisjory.com',
site: 'Jorysinteract',
darkMode: document.documentElement.getAttribute('data-theme') === 'dark',
...option,
pageKey: isShuoshuo ? pageKey : (option && option.pageKey) || pageKey,
imgUpload: true
})
if (GLOBAL_CONFIG.lightbox === 'null') return
artalkItem.on('list-loaded', () => {
artalkItem.ctx.get('list').getCommentNodes().forEach(comment => {
const $content = comment.getRender().$content
btf.loadLightbox($content.querySelectorAll('img:not([atk-emoticon])'))
})
})
if (isShuoshuo) {
window.shuoshuoComment.destroyArtalk = () => {
destroyArtalk()
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
btf.addGlobalFn('pjaxSendOnce', destroyArtalk, 'destroyArtalk')
btf.addGlobalFn('themeChange', artalkChangeMode, 'artalk')
}
const loadArtalk = async (el, pageKey) => {
if (typeof Artalk === 'object') initArtalk(el, pageKey)
else {
await btf.getCSS('https://lib.baomitu.com/artalk/2.9.1/Artalk.min.css')
await btf.getScript('https://lib.baomitu.com/artalk/2.9.1/Artalk.min.js')
initArtalk(el, pageKey)
}
}
if (isShuoshuo) {
'Artalk' === 'Artalk'
? window.shuoshuoComment = { loadComment: loadArtalk }
: window.loadOtherComment = loadArtalk
return
}
if ('Artalk' === 'Artalk' || !false) {
if (false) btf.loadComment(document.getElementById('artalk-wrap'), loadArtalk)
else setTimeout(loadArtalk, 100)
} else {
window.loadOtherComment = loadArtalk
}
})()