-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfeed.json
203 lines (203 loc) · 105 KB
/
feed.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
{
"version": "https://jsonfeed.org/version/1",
"title": "NightFury",
"subtitle": "行动胜于空想",
"icon": "https://nightfury.top/assets/favicon.ico",
"description": "个人笔记 & 踩坑记录 & 各种收藏",
"home_page_url": "https://nightfury.top",
"items": [
{
"id": "https://nightfury.top/2024/07/05/%E5%88%A9%E7%94%A8Acme%E8%84%9A%E6%9C%AC%E8%87%AA%E5%8A%A8%E7%94%B3%E8%AF%B7SSL%E8%AF%81%E4%B9%A6/",
"url": "https://nightfury.top/2024/07/05/%E5%88%A9%E7%94%A8Acme%E8%84%9A%E6%9C%AC%E8%87%AA%E5%8A%A8%E7%94%B3%E8%AF%B7SSL%E8%AF%81%E4%B9%A6/",
"title": "利用Acme脚本自动申请SSL证书",
"date_published": "2024-07-05T08:09:11.000Z",
"content_html": "<p>在这篇博客中,我们将介绍如何在Linux服务器上使用 <code>acme.sh</code> 申请Let’s Encrypt证书,并配置自动续签任务。以下脚本将自动执行这些步骤,确保你的服务器能够使用免费的 SSL 证书并自动续签,完整代码开源在 <a href=\"https://github.com/szNightFury/Acme\">szNightFury‘s Github</a>。</p>\n<h3 id=\"前提条件\"><a href=\"#前提条件\" class=\"headerlink\" title=\"前提条件\"></a>前提条件</h3><ol>\n<li>确保你有一个域名,并且能够修改其 DNS 记录。</li>\n<li>确保你的服务器上已经安装了 Nginx(或其他Web服务器)。</li>\n</ol>\n<h3 id=\"脚本详解\"><a href=\"#脚本详解\" class=\"headerlink\" title=\"脚本详解\"></a>脚本详解</h3><h4 id=\"1-确保脚本以root身份运行\"><a href=\"#1-确保脚本以root身份运行\" class=\"headerlink\" title=\"1. 确保脚本以root身份运行\"></a>1. 确保脚本以<code>root</code>身份运行</h4><p>为了执行需要管理员权限的操作,脚本需要以<code>root</code>身份运行。如果当前用户不是<code>root</code>,脚本将退出并提示用户以<code>root</code>身份重新运行。</p>\n<pre><code class=\"language-bash\">if [ "$EUID" -ne 0 ]; then\n echo "请以 root 身份运行此脚本。"\n exit 1\nfi\n</code></pre>\n<h4 id=\"2-检查系统类型\"><a href=\"#2-检查系统类型\" class=\"headerlink\" title=\"2. 检查系统类型\"></a>2. 检查系统类型</h4><p>脚本会检测当前操作系统类型,以便使用适当的软件包管理器安装所需的依赖项。</p>\n<pre><code class=\"language-bash\">if [ -f /etc/os-release ]; then\n . /etc/os-release\n OS=$ID\nelse\n echo "无法确定操作系统类型,请手动检查。"\n exit 1\nfi\n</code></pre>\n<h4 id=\"3-安装socat\"><a href=\"#3-安装socat\" class=\"headerlink\" title=\"3. 安装socat\"></a>3. 安装<code>socat</code></h4><p><code>socat</code>是一个多功能的网络工具,用于建立独立的双向连接。脚本会检查是否已经安装了<code>socat</code>,如果没有,则根据操作系统类型进行安装。</p>\n<pre><code class=\"language-bash\">if ! command -v socat &> /dev/null; then\n echo "socat 未安装,正在安装 socat..."\n if [ "$OS" == "debian" ] || [ "$OS" == "ubuntu" ]; then\n apt update\n apt install -y socat\n elif [ "$OS" == "centos" ]; then\n yum install -y socat\n else\n echo "不支持的操作系统。"\n exit 1\n fi\n if [ $? -ne 0 ]; then\n echo "socat 安装失败,请检查错误信息。"\n exit 1\n fi\nelse\n echo "socat 已安装。"\nfi\n</code></pre>\n<h4 id=\"4-检查-Nginx-服务状态\"><a href=\"#4-检查-Nginx-服务状态\" class=\"headerlink\" title=\"4. 检查 Nginx 服务状态\"></a>4. 检查 Nginx 服务状态</h4><p>脚本会检查 Nginx 服务是否正在运行,如果是,则停止服务以释放 80 端口(如有其他程序占用 80 端口,请修改下面代码)</p>\n<pre><code class=\"language-bash\">nginx_status=$(systemctl is-active nginx)\nif [ "$nginx_status" == "active" ]; then\n echo "nginx 服务正在运行,准备停止..."\n systemctl stop nginx\n if [ $? -ne 0 ]; then\n echo "停止 nginx 失败,请检查错误信息。"\n exit 1\n fi\nelse\n echo "nginx 服务未运行,无需停止。"\nfi\n</code></pre>\n<h4 id=\"5-检查端口-80-是否被占用\"><a href=\"#5-检查端口-80-是否被占用\" class=\"headerlink\" title=\"5. 检查端口 80 是否被占用\"></a>5. 检查端口 80 是否被占用</h4><p>脚本会检查端口 80 是否被占用,如果被占用,将提示用户并退出。</p>\n<pre><code class=\"language-bash\">if lsof -i:80 &> /dev/null; then\n echo "端口 80 被占用,无法继续。"\n exit 1\nfi\n</code></pre>\n<h4 id=\"6-安装-acme-sh\"><a href=\"#6-安装-acme-sh\" class=\"headerlink\" title=\"6. 安装 acme.sh\"></a>6. 安装 <code>acme.sh</code></h4><p><code>acme.sh</code> 是一个纯 Unix Shell 脚本,用于从 Let’s Encrypt 申请 SSL 证书。脚本会检查是否已经安装了 <code>acme.sh</code>,如果没有,则进行安装。</p>\n<pre><code class=\"language-bash\">if [ ! -d "$HOME/.acme.sh" ]; then\n echo "正在安装 acme.sh..."\n curl https://get.acme.sh | sh\n if [ $? -ne 0 ]; then\n echo "acme.sh 安装失败,请检查错误信息。"\n exit 1\n fi\nelse\n echo "acme.sh 已安装。"\nfi\n</code></pre>\n<h4 id=\"7-设置默认-CA-为-Let’s-Encrypt\"><a href=\"#7-设置默认-CA-为-Let’s-Encrypt\" class=\"headerlink\" title=\"7. 设置默认 CA 为 Let’s Encrypt\"></a>7. 设置默认 CA 为 Let’s Encrypt</h4><pre><code class=\"language-bash\">echo "设置默认 CA 为 Let’s Encrypt..."\n~/.acme.sh/acme.sh --set-default-ca --server letsencrypt\n</code></pre>\n<h4 id=\"8-获取用户输入的域名\"><a href=\"#8-获取用户输入的域名\" class=\"headerlink\" title=\"8. 获取用户输入的域名\"></a>8. 获取用户输入的域名</h4><p>脚本会提示用户输入主域名和附加域名。</p>\n<pre><code class=\"language-bash\">read -p "请输入主域名: " main_domain\ndomains=($main_domain)\nwhile true; do\n read -p "请输入附加域名(或按 Enter 键结束输入): " additional_domain\n if [ -z "$additional_domain" ]; then\n break\n fi\n domains+=("$additional_domain")\ndone\n</code></pre>\n<h4 id=\"9-生成域名参数\"><a href=\"#9-生成域名参数\" class=\"headerlink\" title=\"9. 生成域名参数\"></a>9. 生成域名参数</h4><p>根据用户输入的域名生成域名参数,用于申请证书。</p>\n<pre><code class=\"language-bash\">domain_args=""\nfor domain in "${domains[@]}"; do\n domain_args="$domain_args -d $domain"\ndone\n</code></pre>\n<h4 id=\"10-申请测试证书\"><a href=\"#10-申请测试证书\" class=\"headerlink\" title=\"10. 申请测试证书\"></a>10. 申请测试证书</h4><pre><code class=\"language-bash\">echo "申请测试证书..."\n~/.acme.sh/acme.sh --issue $domain_args --standalone -k ec-256 --force --test\nif [ $? -ne 0 ]; then\n echo "测试证书申请失败,请检查错误信息。"\n exit 1\nfi\n\necho "删除测试证书..."\nrm -rf "$HOME/.acme.sh/${main_domain}_ecc"\n</code></pre>\n<p>如果机子只有 IPV6,则还需要加个参数<code>--listen-v6</code></p>\n<h4 id=\"11-申请正式证书\"><a href=\"#11-申请正式证书\" class=\"headerlink\" title=\"11. 申请正式证书\"></a>11. 申请正式证书</h4><pre><code class=\"language-bash\">echo "申请正式证书..."\n~/.acme.sh/acme.sh --issue $domain_args --standalone -k ec-256 --force\nif [ $? -ne 0 ]; then\n echo "正式证书申请失败,请检查错误信息。"\n exit 1\nfi\n</code></pre>\n<p>同理,如果机子只有 IPV6,同样需要加个参数<code>--listen-v6</code></p>\n<h4 id=\"12-安装证书\"><a href=\"#12-安装证书\" class=\"headerlink\" title=\"12. 安装证书\"></a>12. 安装证书</h4><pre><code class=\"language-bash\">echo "创建证书存储目录..."\nmkdir -p /etc/cert\n\necho "安装证书..."\n~/.acme.sh/acme.sh --installcert -d "$main_domain" --fullchainpath /etc/cert/fullchain.pem --keypath /etc/cert/privkey.pem --ecc --force\nif [ $? -ne 0 ]; then\n echo "证书安装失败,请检查错误信息。"\n exit 1\nfi\n</code></pre>\n<h4 id=\"13-重新启动-Nginx-服务\"><a href=\"#13-重新启动-Nginx-服务\" class=\"headerlink\" title=\"13. 重新启动 Nginx 服务\"></a>13. 重新启动 Nginx 服务</h4><p>如果 Nginx 之前正在运行,脚本会重新启动 Nginx 服务。</p>\n<pre><code class=\"language-bash\">if [ "$nginx_status" == "active" ]; then\n echo "重新启动 nginx 服务..."\n systemctl start nginx\n if [ $? -ne 0 ]; then\n echo "启动 nginx 失败,请检查错误信息。"\n exit 1\n fi\nfi\n</code></pre>\n<h4 id=\"14-设置自动续签任务\"><a href=\"#14-设置自动续签任务\" class=\"headerlink\" title=\"14. 设置自动续签任务\"></a>14. 设置自动续签任务</h4><p>为了确保证书在到期前自动续签,脚本会配置一个定时任务。</p>\n<pre><code class=\"language-bash\">echo "设置自动续签任务..."\ncrontab -l | grep -v "acme.sh --cron" | crontab -\n(crontab -l 2>/dev/null; echo "0 0 * * * ~/.acme.sh/acme.sh --cron --home ~/.acme.sh > /dev/null 2>&1") | crontab -\nif [ $? -ne 0 ]; then\n echo "自动续签任务设置失败,请检查错误信息。"\n exit 1\nfi\n\necho "证书申请、安装和自动续签任务设置完成。"\n</code></pre>\n<h3 id=\"结论\"><a href=\"#结论\" class=\"headerlink\" title=\"结论\"></a>结论</h3><p>通过这个脚本,你可以自动化从申请到安装以及配置自动续签 Let’s Encrypt 证书的整个过程。这将确保你的服务器始终使用有效的 SSL 证书,从而提高安全性。</p>\n",
"tags": [
"Configuration",
"Debian",
"Ubuntu",
"SSL",
"Linux"
]
},
{
"id": "https://nightfury.top/2024/07/05/Debian12%E9%83%A8%E7%BD%B2Cloudreve%E4%BA%91%E7%9B%98/",
"url": "https://nightfury.top/2024/07/05/Debian12%E9%83%A8%E7%BD%B2Cloudreve%E4%BA%91%E7%9B%98/",
"title": "Debian12部署Cloudreve云盘",
"date_published": "2024-07-05T06:38:20.000Z",
"content_html": "<p>Cloudreve 是一款开源的网盘系统,支持多种存储后端,可以方便地搭建私人网盘。</p>\n<p>VPS 环境:Debian 12</p>\n<p>CloudReve 版本:3.8.3</p>\n<h2 id=\"步骤一:更新系统\"><a href=\"#步骤一:更新系统\" class=\"headerlink\" title=\"步骤一:更新系统\"></a>步骤一:更新系统</h2><p>首先,确保你的系统是最新的。运行以下命令更新软件包列表并升级所有已安装的软件包:</p>\n<pre><code class=\"language-sh\">sudo apt update && sudo apt upgrade -y\n</code></pre>\n<h2 id=\"步骤二:下载-Cloudreve\"><a href=\"#步骤二:下载-Cloudreve\" class=\"headerlink\" title=\"步骤二:下载 Cloudreve\"></a>步骤二:下载 Cloudreve</h2><p>访问 <a href=\"https://github.com/cloudreve/Cloudreve/releases\">Cloudreve的GitHub页面</a> 以获取最新版本的 Cloudreve。或者,你可以使用 curl 命令直接下载 3.8.3 版本(截止博客发表前的最新版本):</p>\n<pre><code class=\"language-sh\">curl -L -o cloudreve.zip https://github.com/cloudreve/Cloudreve/releases/download/3.8.3/cloudreve_3.8.3_linux_amd64.tar.gz\n</code></pre>\n<p>下载完成后,解压文件:</p>\n<pre><code class=\"language-sh\">mkdir /opt/cloudreve\ntar -xvzf cloudreve_3.8.3_linux_amd64.tar.gz -C /opt/cloudreve\n</code></pre>\n<h2 id=\"步骤三:配置-Cloudreve\"><a href=\"#步骤三:配置-Cloudreve\" class=\"headerlink\" title=\"步骤三:配置 Cloudreve\"></a>步骤三:配置 Cloudreve</h2><p>进入 Cloudreve 的目录,并赋予可执行权限:</p>\n<pre><code class=\"language-sh\">cd /opt/cloudreve\nchmod +x cloudreve\n</code></pre>\n<p>运行 Cloudreve 以生成默认配置文件:</p>\n<pre><code class=\"language-sh\">./cloudreve # 初次启动会生成管理员账号和密码, 请妥善保存好, 后面可以进面板修改账号和密码\n</code></pre>\n<p>初次运行后,会生成一个名为 <code>conf.ini</code> 的配置文件,你可以根据需要修改此文件。以下是一个示例配置:</p>\n<pre><code class=\"language-ini\">[System]\n; 运行模式\nMode = master\n; 监听端口\nListen = :5212\n; 是否开启 Debug\nDebug = false\n; Session 密钥, 一般在首次启动时自动生成\nSessionSecret = 23333\n; Hash 加盐, 一般在首次启动时自动生成\nHashIDSalt = something really hard to guss\n\n; 实测只会生成上述上述五个配置, 下面的配置如有需要请自行选择\n\n; 呈递客户端 IP 时使用的 Header\nProxyHeader = X-Forwarded-For\n\n; SSL 相关\n[SSL]\n; SSL 监听端口\nListen = :443\n; 证书路径\nCertPath = C:\\Users\\i\\Documents\\fullchain.pem\n; 私钥路径\nKeyPath = C:\\Users\\i\\Documents\\privkey.pem\n\n; 启用 Unix Socket 监听\n[UnixSocket]\nListen = /run/cloudreve/cloudreve.sock\n; 设置产生的 socket 文件的权限\nPerm = 0666\n\n; 数据库相关,如果你只想使用内置的 SQLite 数据库,这一部分直接删去即可\n[Database]\n; 数据库类型,目前支持 sqlite/mysql/mssql/postgres\nType = mysql\n; MySQL 端口\nPort = 3306\n; 用户名\nUser = root\n; 密码\nPassword = root\n; 数据库地址\nHost = 127.0.0.1\n; 数据库名称\nName = v3\n; 数据表前缀\nTablePrefix = cd_\n; 字符集\nCharset = utf8mb4\n; SQLite 数据库文件路径\nDBFile = cloudreve.db\n; 进程退出前安全关闭数据库连接的缓冲时间\nGracePeriod = 30\n; 使用 Unix Socket 连接到数据库\nUnixSocket = false\n\n; 从机模式下的配置\n[Slave]\n; 通信密钥\nSecret = 1234567891234567123456789123456712345678912345671234567891234567\n; 回调请求超时时间 (s)\nCallbackTimeout = 20\n; 签名有效期\nSignatureTTL = 60\n\n; 跨域配置\n[CORS]\nAllowOrigins = *\nAllowMethods = OPTIONS,GET,POST\nAllowHeaders = *\nAllowCredentials = false\nSameSite = Default\nSecure = lse\n\n; Redis 相关\n[Redis]\nServer = 127.0.0.1:6379\nPassword =\nDB = 0\n\n; 从机配置覆盖\n[OptionOverwrite]\n; 可直接使用 `设置名称 = 值` 的格式覆盖\nmax_worker_num = 50\n</code></pre>\n<h2 id=\"步骤五:创建-systemd-服务\"><a href=\"#步骤五:创建-systemd-服务\" class=\"headerlink\" title=\"步骤五:创建 systemd 服务\"></a>步骤五:创建 systemd 服务</h2><p>为了方便管理 Cloudreve,可以创建一个 systemd 服务。</p>\n<p>创建一个名为 <code>cloudreve.service</code> 的文件:</p>\n<pre><code class=\"language-sh\">nano /usr/lib/systemd/system/cloudreve.service\n</code></pre>\n<p>在文件中添加以下内容(将下文 <code>PATH_TO_CLOUDREVE</code> 更换为程序所在目录,本文即 <code>/opt/cloudreve/</code>):</p>\n<pre><code class=\"language-ini\">[Unit]\nDescription=Cloudreve\nDocumentation=https://docs.cloudreve.org\nAfter=network.target\nAfter=mysqld.service\nWants=network.target\n\n[Service]\nWorkingDirectory=/PATH_TO_CLOUDREVE\nExecStart=/PATH_TO_CLOUDREVE/cloudreve\nRestart=on-abnormal\nRestartSec=5s\nKillMode=mixed\n\nStandardOutput=null\nStandardError=syslog\n\n[Install]\nWantedBy=multi-user.target\n</code></pre>\n<p>保存并退出后,重新加载 systemd 服务:</p>\n<pre><code class=\"language-sh\"># 更新配置\nsystemctl daemon-reload\n\n# 启动服务\nsystemctl start cloudreve\n\n# 设置开机启动\nsystemctl enable cloudreve\n</code></pre>\n<h2 id=\"步骤六:访问Cloudreve\"><a href=\"#步骤六:访问Cloudreve\" class=\"headerlink\" title=\"步骤六:访问Cloudreve\"></a>步骤六:访问Cloudreve</h2><p>Cloudreve 默认监听在 5212 端口。你可以通过访问 <code>http://你的服务器IP:5212</code> 来访问 Cloudreve。</p>\n<p>初次登录时,用管理员账户登录,然后点击头像里的管理面板做进一步的个性化设置。</p>\n<h2 id=\"步骤七:配置Nginx反向代理(可选)\"><a href=\"#步骤七:配置Nginx反向代理(可选)\" class=\"headerlink\" title=\"步骤七:配置Nginx反向代理(可选)\"></a>步骤七:配置Nginx反向代理(可选)</h2><p>为了更加安全和便捷地访问 Cloudreve,可以使用Nginx配置反向代理并启用HTTPS。</p>\n<p>在网站的 server 段添加以下内容(实际应用中推荐强制 https,然后在 443 端口反代 5212 端口):</p>\n<pre><code class=\"language-nginx\">location / {\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header Host $http_host;\n proxy_redirect off;\n proxy_pass http://127.0.0.1:5212;\n\n # 如果不使用本地存储策略,可以注释掉\n client_max_body_size 20G; # 设置理论最大文件尺寸(这里即20 GB)\n}\n</code></pre>\n<p>启用这个配置并重新加载Nginx:</p>\n<pre><code class=\"language-sh\">systemctl restart nginx\n</code></pre>\n<p>至此,你应该可以通过你的域名访问 Cloudreve 云盘了。现在你可以开始使用这个强大的网盘系统来管理和分享你的文件了。如果你在配置过程中遇到任何问题,可以参考<a href=\"https://docs.cloudreve.org/\">Cloudreve的文档</a>获取更多帮助。</p>\n",
"tags": [
"Configuration",
"Debian",
"Github",
"Cloudreve"
]
},
{
"id": "https://nightfury.top/2024/07/04/Debian12%E7%B3%BB%E7%BB%9FHexo%E9%83%A8%E7%BD%B2%E5%88%B0VPS/",
"url": "https://nightfury.top/2024/07/04/Debian12%E7%B3%BB%E7%BB%9FHexo%E9%83%A8%E7%BD%B2%E5%88%B0VPS/",
"title": "Debian12系统Hexo部署到VPS",
"date_published": "2024-07-04T04:34:08.000Z",
"content_html": "<p>网上大部分教程都是将 Hexo 部署到 GitHub Pages 上面,本文主要介绍如何部署到 VPS。</p>\n<p>VPS 环境:Debian 12</p>\n<h1 id=\"准备工作\"><a href=\"#准备工作\" class=\"headerlink\" title=\"准备工作\"></a>准备工作</h1><p>网上流传的武功秘籍分为两种:</p>\n<ul>\n<li>将 Hexo 项目上传到 VPS 上面后执行 <code>hexo server</code>,之后配置 Nginx 反向代理,让域名指向 <a href=\"http://localhost:4000。\">http://localhost:4000。</a></li>\n<li>将 Hexo 在本地通过 <code>hexo generate</code> 生成静态文件,在通过 <code>hexo deploy</code> 部署到 VPS 上面,使用 Nginx 直接做 Web 服务器。</li>\n<li>相比第二种方式,第一种每次写博客与更新博客时候的操作会很繁琐。所以我们使用第二种方式进行部署,这样既可以将静态文件 deploy 到 VPS 上,也可以上传到 Github 上用作备份,操作性和安全性上都要胜于前者。</li>\n<li>而对于第二种方式而言,常用的又有 <code>git hook</code> 和 <code>rsync </code>两种自动部署解决方案。</li>\n</ul>\n<p>本文主要介绍 <code>git hook</code> 部署过程。</p>\n<h1 id=\"Git-Hooks-自动部署\"><a href=\"#Git-Hooks-自动部署\" class=\"headerlink\" title=\"Git Hooks 自动部署\"></a>Git Hooks 自动部署</h1><h2 id=\"部署原理\"><a href=\"#部署原理\" class=\"headerlink\" title=\"部署原理\"></a>部署原理</h2><p>我们在本地编辑文本,然后使用 Git 远程部署到 VPS 的 Git 仓库。<code>hexo d</code> 命令实际上只 deploy 了本地的 public 文件夹,Git Hooks 实际上就是当 Git 仓库收到最新的 push 时,将 Git 仓库接受到的内容复制到 VPS 上的网站目录内。相当于完成了手动将 public 文件夹复制到 VPS 的网站根目录里。</p>\n<h2 id=\"安装配置-Git\"><a href=\"#安装配置-Git\" class=\"headerlink\" title=\"安装配置 Git\"></a>安装配置 Git</h2><h3 id=\"安装-Git\"><a href=\"#安装-Git\" class=\"headerlink\" title=\"安装 Git\"></a>安装 Git</h3><p>通过 SSH 连接 VPS,执行:<code>apt-get install git</code>,完成后通过 <code>git --version</code> 查看 Git 版本,若显示版本信息则说明安装成功。</p>\n<h3 id=\"创建-git-用户\"><a href=\"#创建-git-用户\" class=\"headerlink\" title=\"创建 git 用户\"></a>创建 git 用户</h3><p>执行:<code>adduser git</code>,根据提示设置密码,其他信息可以一路空格。</p>\n<h3 id=\"赋予-git-用户-sudo-权限\"><a href=\"#赋予-git-用户-sudo-权限\" class=\"headerlink\" title=\"赋予 git 用户 sudo 权限\"></a>赋予 git 用户 sudo 权限</h3><p>安装 sudo:</p>\n<pre><code class=\"language-shell\">apt update\napt install sudo\n</code></pre>\n<p>执行:</p>\n<pre><code class=\"language-shell\">chmod 740 /etc/sudoers\nnano /etc/sudoers\n</code></pre>\n<p>找到以下内容:</p>\n<pre><code class=\"language-shell\">## User privilege specification\nroot ALL=(ALL:ALL) ALL\n# 在 root ALL=(ALL:ALL) ALL 这一行下面添加\ngit ALL=(ALL:ALL) ALL\n</code></pre>\n<p>保存退出后,修改回文件权限:</p>\n<pre><code class=\"language-shell\">chmod 440 /etc/sudoers\n</code></pre>\n<h3 id=\"关闭-git-用户-shell-权限\"><a href=\"#关闭-git-用户-shell-权限\" class=\"headerlink\" title=\"关闭 git 用户 shell 权限\"></a>关闭 git 用户 shell 权限</h3><p>我们也可以通过:</p>\n<pre><code class=\"language-shell\">ssh git@VPS IP\n</code></pre>\n<p>ssh 连接服务器,登录到服务器上,对服务器进行各种操作,这通常很不安全,也不合适,我们只需要能对仓库操作就可以了,不需要更大的权限。</p>\n<p>因此我们关闭 git 用户 shell 权限,执行:</p>\n<pre><code class=\"language-shell\">nano /etc/passwd\n</code></pre>\n<p>将最后一行的 <code>git:x:1001:1001:,,,:/home/git:/bin/bash</code> 修改为 <code>git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell</code></p>\n<p>这样,git 用户可以正常通过 ssh 使用 git,但无法登录 shell,因为我们为 git 用户指定的 git-shell 每次一登录就自动退出。</p>\n<h3 id=\"初始化-git-仓库\"><a href=\"#初始化-git-仓库\" class=\"headerlink\" title=\"初始化 git 仓库\"></a>初始化 git 仓库</h3><pre><code class=\"language-shell\">cd /home/git # 切换到git用户目录\nmkdir blog.git # 创建git仓库文件夹,以blog.git为例\ncd blog.git # 进入仓库目录\ngit init --bare # 使用--bare参数初始化为裸仓库,这样创建的仓库不包含工作区\n</code></pre>\n<p>注意:裸仓库没有工作区,因为服务器上的 Git 仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的 Git 仓库通常都以 .git 结尾。</p>\n<h3 id=\"创建网站目录\"><a href=\"#创建网站目录\" class=\"headerlink\" title=\"创建网站目录\"></a>创建网站目录</h3><pre><code class=\"language-shell\">cd /home/wwwroot/ # 切换目录\n# 由于我想直接将博客放在/home/wwwroot/下,所以并没有创建blog目录\nmkdir blog # 创建网站目录,以blog为例\n</code></pre>\n<h3 id=\"配置-SSH\"><a href=\"#配置-SSH\" class=\"headerlink\" title=\"配置 SSH\"></a>配置 SSH</h3><pre><code class=\"language-shell\">cd /home/git # 切换到git用户目录\nmkdir .ssh # 创建.ssh目录\ncd .ssh\nnano authorized_keys\n</code></pre>\n<p>然后将本地的公钥复制到 <code>authorized_keys</code> 文件里 (公钥即本地执行 <code>cat ~/.ssh/id_rsa.pub</code> (如 <code>C:/Users/Your User Name/.ssh/id_rsa.pub</code>)查看的内容)。</p>\n<p>如果本地没有公钥,可以在本地用 <code>ssh-keygen</code> 命令生成一个新的 ssh 密钥对:</p>\n<pre><code class=\"language-shell\">ssh-keygen -t rsa -C "[email protected]"\n</code></pre>\n<p>当系统提示输入文件名时,可以按 <code>Enter</code> 键使用默认路径(通常是<code>~/.ssh/id_rsa</code>),也可以指定一个新的路径。</p>\n<pre><code class=\"language-shell\">Generating public/private rsa key pair.\nEnter file in which to save the key (/home/username/.ssh/id_rsa): [Press Enter]\n</code></pre>\n<p> 你可以选择为私钥设置一个密码短语,以增加额外的安全性,这里可以直接按 <code>Enter</code> 键跳过。</p>\n<pre><code class=\"language-shell\">Enter passphrase (empty for no passphrase): [Type a passphrase or press Enter]\nEnter same passphrase again: [Repeat the passphrase or press Enter]\n</code></pre>\n<p>生成完成后,系统会告诉你密钥对已经生成并存储在指定路径中。</p>\n<pre><code class=\"language-shell\">Your identification has been saved in /home/username/.ssh/id_rsa.\nYour public key has been saved in /home/username/.ssh/id_rsa.pub.\n# Windows -> C:/Users/username/.ssh/id_rsa, C:/Users/username/.ssh/id_rsa.pub\nThe key fingerprint is:\nSHA256:... [email protected]\nThe key's randomart image is:\n+---[RSA 2048]----+\n| .==+. |\n| =.o. |\n| o . |\n| . . o. |\n| . oSo.o. |\n| E .oBo= . |\n| . .++.+ o |\n| o+.o . o |\n| +=o . . |\n+----[SHA256]-----+\n</code></pre>\n<p>注意:收集所有需要登录的用户的公钥,就是他们自己的 <code>id_rsa.pub</code> 文件,把所有公钥导入到 <code>/home/git/.ssh/authorized_keys</code> 文件里,一行一个。</p>\n<p>通常这样配置后,<code>hexo -d</code> 部署就不需要输入密码了,而是通过证书校验身份。</p>\n<h3 id=\"用户组管理\"><a href=\"#用户组管理\" class=\"headerlink\" title=\"用户组管理\"></a>用户组管理</h3><p>查看 Nginx 运行用户:</p>\n<pre><code class=\"language-shell\">cat /etc/nginx/nginx.conf | grep user\n</code></pre>\n<p>输出示例:</p>\n<pre><code class=\"language-shell\">user www-data;\n</code></pre>\n<p>查看 Php-fpm 运行用户:</p>\n<pre><code class=\"language-shell\">cat /etc/php/8.2/fpm/pool.d/www.conf | grep -E '^(user|group) ='\n</code></pre>\n<p>输出示例:</p>\n<pre><code class=\"language-shell\">user = www-data\ngroup = www-data\n</code></pre>\n<p>创建用户组 <code>webgroup</code> 并添加 <code>git</code> 和 <code>www-data</code> 到用户组:</p>\n<pre><code class=\"language-shell\">groupadd webgroup\nusermod -aG webgroup git\nusermod -aG webgroup www-data\n</code></pre>\n<p>更改文件夹的组:</p>\n<pre><code class=\"language-shell\">chgrp -R webgroup /home/wwwroot\n</code></pre>\n<p>更改文件和目录的所有者:</p>\n<pre><code class=\"language-shell\">chown -R git:git /home/git\nchown -R www-data:webgroup /home/wwwroot\n</code></pre>\n<p>设置文件夹和文件的权限:</p>\n<pre><code class=\"language-shell\">chmod -R 770 /home/git\nchmod -R 770 /home/wwwroot\n</code></pre>\n<p>设置 SGID 位,确保新创建的文件或文件夹自动继承该组:</p>\n<pre><code class=\"language-shell\"># 只设置单层目录的方法\n# chmod g+s /home/git\n# chmod g+s /home/wwwroot\n\n# 递归设置所有子目录的 SGID 位\nfind /home/git -type d -exec chmod g+s {} \\;\nfind /home/wwwroot -type d -exec chmod g+s {} \\;\n</code></pre>\n<h2 id=\"安装配置-Nginx\"><a href=\"#安装配置-Nginx\" class=\"headerlink\" title=\"安装配置 Nginx\"></a>安装配置 Nginx</h2><p>由于我之前已经装过了 Nginx,可以参考前面的教程,以下nginx安装内容来源于网络,仅供参考。</p>\n<h3 id=\"安装-Nginx\"><a href=\"#安装-Nginx\" class=\"headerlink\" title=\"安装 Nginx\"></a>安装 Nginx</h3><p>执行:<code>apt-get install nginx</code>,若输入 <code>nginx -V</code> 可以看到 nginx 版本信息,则安装成功。</p>\n<h3 id=\"配置-nginx\"><a href=\"#配置-nginx\" class=\"headerlink\" title=\"配置 nginx\"></a>配置 nginx</h3><p>执行:</p>\n<pre><code class=\"language-shell\">cd /etc/nginx/sites-available # 切换目录\ncp default default.bak # 备份默认配置\nnano default # 修改配置\n</code></pre>\n<p>参考配置文件内容:</p>\n<pre><code class=\"language-shell\">server {\n listen 80 default; # 默认监听80端口\n root /home/wwwroot; # 网站根目录\n server_name nightfury.top, www.nightfury.top; # 网址\n access_log /var/log/nginx/blog_access.log;\n error_log /var/log/nginx/blog_error.log;\n error_page 404 = /404.html;\n \n location ~* ^.+\\.(ico|gif|jpg|jpeg|png)$ {\n root /home/wwwroot;\n access_log off;\n expires 1d;\n }\n\n location ~* ^.+\\.(css|js|txt|xml|swf|wav)$ {\n root /home/wwwroot;\n access_log off;\n expires 10m;\n }\n\n location / {\n root /home/wwwroot;\n if (-f $request_filename) {\n rewrite ^/(.*)$ /$1 break;\n }\n }\n\n location /nginx_status {\n stub_status on;\n access_log off;\n }\n}\n</code></pre>\n<p>保存退出后,启动 nginx:</p>\n<pre><code class=\"language-shell\">systemctl start nginx\n</code></pre>\n<p>设置开机自动启动:</p>\n<pre><code class=\"language-shell\">systemctl enable nginx\n</code></pre>\n<p>查看运行状态:</p>\n<pre><code class=\"language-shell\">systemctl status nginx\n</code></pre>\n<p>显示 running 表示成功运行。</p>\n<h2 id=\"配置-Git-Hooks\"><a href=\"#配置-Git-Hooks\" class=\"headerlink\" title=\"配置 Git Hooks\"></a>配置 Git Hooks</h2><h3 id=\"创建-post-receive-文件\"><a href=\"#创建-post-receive-文件\" class=\"headerlink\" title=\"创建 post-receive 文件\"></a>创建 post-receive 文件</h3><p>root 用户下执行(或者 git 用户下执行,就不需要 <code>sudo -u git</code>)</p>\n<pre><code class=\"language-shell\">cd /home/git/blog.git/hooks # 切换到hooks目录下\nsudo -u git nano post-receive\n</code></pre>\n<p>复制下面的内容到 <code>post-receive</code> 文件中:</p>\n<pre><code class=\"language-shell\">#!/bin/bash\necho "post-receive hook is running..."\n\nGIT_REPO=/home/git/blog.git\nTMP_GIT_CLONE=/tmp/blog\nPUBLIC_WWW=/home/wwwroot\n\nrm -rf ${TMP_GIT_CLONE}\ngit clone $GIT_REPO $TMP_GIT_CLONE\n# 如果想每次更新都删除掉原有所有内容则用下面这条命令\n# rm -rf ${PUBLIC_WWW}/*\n# 如果想每次更新都删除掉原有所有内容但排除掉一些文件(深度为1)则用下面这条命令(比如排除掉 .mp4 和 .png 文件)\nfind ${PUBLIC_WWW} -maxdepth 1 -mindepth 1 ! -name '.mp4' ! -name '*.png' -exec rm -rf {} +\ncp -rf ${TMP_GIT_CLONE}/* ${PUBLIC_WWW}\n</code></pre>\n<hr>\n<p>为什么不直接将裸仓库克隆到 Web 根目录下呢?我之前也一直被这个问题困扰,感觉先克隆到 tmp 目录再拷贝到 Web 根目录是多此一举。后来我觉得可能是出于项目安全的考虑,在执行 cp 命令的时候,.git 作为隐藏目录不会被拷贝到 Web 根目录下,也就避免了将整个仓库历史暴露在 Web 服务中。</p>\n<hr>\n<p>赋予可执行权限:</p>\n<pre><code class=\"language-shell\">chmod +x post-receive\n</code></pre>\n<h2 id=\"本地操作\"><a href=\"#本地操作\" class=\"headerlink\" title=\"本地操作\"></a>本地操作</h2><h3 id=\"尝试连接\"><a href=\"#尝试连接\" class=\"headerlink\" title=\"尝试连接\"></a>尝试连接</h3><p>在本地打开 Git Bash:</p>\n<pre><code class=\"language-shell\">ssh git@VPS的ip\n</code></pre>\n<p>若默认端口不是 22,则需要在后面加上 -p 端口号:</p>\n<pre><code class=\"language-shell\">ssh git@VPS的ip -p 2024\n</code></pre>\n<p>由于前面配置了证书,所以正常应该是不需要输入密码了,如果仍然需要输入密码,请检查 <code>/home/git/.ssh</code> 和 <code>/home/git/.ssh/authorized_keys</code> 的权限和所属用户/组是否正确,可以用如下命令查询 ssh 日志:</p>\n<pre><code class=\"language-shell\">journalctl -u sshd -xe\n</code></pre>\n<p>ssh 连接成功后返回结果应该如下:</p>\n<pre><code class=\"language-shell\">->ssh [email protected]\n->Linux XXX #1 SMP PREEMPT_DYNAMIC Debian XXX\n\nThe programs included with the Debian GNU/Linux system are free software;\nthe exact distribution terms for each program are described in the\nindividual files in /usr/share/doc/*/copyright.\n\nDebian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent\npermitted by applicable law.\nfatal: Interactive git shell is not enabled.\nhint: ~/git-shell-commands should exist and have read and execute access.\nConnection to nightfury.top closed.\n</code></pre>\n<p>提示无法登录 shell 是正常的,因为我们在之前就为 git 用户指定了 git-shell 每次一登录就自动退出。</p>\n<h2 id=\"配置-Hexo\"><a href=\"#配置-Hexo\" class=\"headerlink\" title=\"配置 Hexo\"></a>配置 Hexo</h2><p>打开本地博客根目录下的_config.yml 文件,找到最后的 deploy 配置,修改为:</p>\n<pre><code class=\"language-shell\">## Deployment\n\n## Docs: https://hexo.io/docs/deployment.html\n\ndeploy:\n - type: git\n repo: [email protected]:szNightFury/szNightFury.github.io.git\n branch: master\n - type: git\n repo: [email protected]:/home/git/blog.git\n branch: master\n</code></pre>\n<p>到此,Hexo 建站就全部配置部署完毕了。</p>\n",
"tags": [
"Configuration",
"Hexo",
"Debian",
"Github"
]
},
{
"id": "https://nightfury.top/2024/07/04/Debian12%E9%85%8D%E7%BD%AENginx1.22%E4%B8%8EPhp8.2/",
"url": "https://nightfury.top/2024/07/04/Debian12%E9%85%8D%E7%BD%AENginx1.22%E4%B8%8EPhp8.2/",
"title": "Debian12配置Nginx1.22与Php8.2",
"date_published": "2024-07-04T03:59:23.000Z",
"content_html": "<h1 id=\"实验环境\"><a href=\"#实验环境\" class=\"headerlink\" title=\"实验环境\"></a>实验环境</h1><ul>\n<li>操作系统:Debian 12</li>\n<li>Nginx:1.22.1</li>\n<li>Php:8.2.20</li>\n<li>Php-fpm:php8.2-fpm</li>\n</ul>\n<h1 id=\"实验步骤\"><a href=\"#实验步骤\" class=\"headerlink\" title=\"实验步骤\"></a>实验步骤</h1><h2 id=\"安装程序包与依赖\"><a href=\"#安装程序包与依赖\" class=\"headerlink\" title=\"安装程序包与依赖\"></a>安装程序包与依赖</h2><pre><code class=\"language-shell\"># 更新软件包列表\napt update\n\n# 安装程序包\napt install nginx\napt install php php-fpm php-xml php-json php-curl php-mbstring\n</code></pre>\n<h2 id=\"配置Nginx\"><a href=\"#配置Nginx\" class=\"headerlink\" title=\"配置Nginx\"></a>配置Nginx</h2><pre><code class=\"language-shell\"># 查看 php-fpm 监听配置\nnano /etc/php/8.2/fpm/pool.d/www.conf\n# 41行取消注释:\nlisten = /run/php/php8.2-fpm.sock\n\nnano /etc/nginx/conf.d/xxx.conf\n# 在 index.html 前面加入 index.php\n# 增加:\nlocation ~ \\.php$ {\n\tinclude snippets/fastcgi-php.conf;\n\tfastcgi_pass unix:/run/php/php8.2-fpm.sock;\t# 注意路径与前面的监听配置一致\n}\n</code></pre>\n<h2 id=\"修改时区\"><a href=\"#修改时区\" class=\"headerlink\" title=\"修改时区\"></a>修改时区</h2><pre><code class=\"language-shell\"># 查看当前时区\ntimedatectl\n# 输出示例:\nLocal time: Tue 2024-07-03 14:00:00 UTC\nUniversal time: Tue 2024-07-03 14:00:00 UTC\nRTC time: Tue 2024-07-03 14:00:00\nTime zone: UTC (UTC, +0000)\nSystem clock synchronized: yes\nNTP service: active\nRTC in local TZ: no\n\n# 设置 PRC 时区\ntimedatectl set-timezone Asia/Shanghai\n# 验证时区更改\ntimedatectl\n# 输出示例:\nLocal time: Tue 2024-07-03 22:00:00 CST\nUniversal time: Tue 2024-07-03 14:00:00 UTC\nRTC time: Tue 2024-07-03 14:00:00\nTime zone: Asia/Shanghai (CST, +0800)\nSystem clock synchronized: yes\nNTP service: active\nRTC in local TZ: no\n\n# 配置 Php 时区\nnano /etc/php/8.2/fpm/php.ini\n# 979行增加\ndate.timezone = Asia/Shanghai\n</code></pre>\n<h2 id=\"重启服务\"><a href=\"#重启服务\" class=\"headerlink\" title=\"重启服务\"></a>重启服务</h2><pre><code class=\"language-shell\">service php8.2-fpm restart # systemctl restart php8.2-fpm\nservice nginx restart # systemctl restart nginx\n</code></pre>\n<h2 id=\"调试步骤\"><a href=\"#调试步骤\" class=\"headerlink\" title=\"调试步骤\"></a>调试步骤</h2><pre><code class=\"language-shell\"># 检查 Nginx 访问日志\ntail -f /var/log/nginx/access.log\n# 查看 Php-fpm 错误日志\ntail -f /var/log/php8.2-fpm.log\n</code></pre>\n<h1 id=\"验收\"><a href=\"#验收\" class=\"headerlink\" title=\"验收\"></a>验收</h1><p>编写任意 php 文件,比如说简单的有 index.php:</p>\n<pre><code class=\"language-html\"><h1>\n <span> Hello, this is test page </span>\n</h1>\n</code></pre>\n<p>或者 php 探针:</p>\n<pre><code class=\"language-php\"><?php \n\tphpinfo(); \n?>\n</code></pre>\n<p>访问该 php 地址,得到正确的返回结果。完结,Move On!</p>\n",
"tags": [
"Configuration",
"Debian",
"Php",
"Nginx"
]
},
{
"id": "https://nightfury.top/2023/09/11/table/",
"url": "https://nightfury.top/2023/09/11/table/",
"title": "table",
"date_published": "2023-09-11T02:46:06.000Z",
"content_html": "<table>\n<thead>\n<tr>\n<th>refill</th>\n<th>0</th>\n<th>0</th>\n<th>0</th>\n<th>1</th>\n<th>1</th>\n<th>1</th>\n<th></th>\n<th></th>\n<th></th>\n<th></th>\n<th></th>\n<th></th>\n<th></th>\n<th></th>\n<th>0</th>\n<th>0</th>\n<th>0</th>\n<th>1</th>\n<th>1</th>\n<th>1</th>\n<th></th>\n<th></th>\n<th></th>\n<th></th>\n</tr>\n</thead>\n<tbody><tr>\n<td></td>\n<td>refillPush</td>\n<td>refillPush</td>\n<td>refillPush</td>\n<td>refillPush</td>\n<td>refillPush</td>\n<td>refillPush</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td>refillPush</td>\n<td>refillPush</td>\n<td>refillPush</td>\n<td>refillPush</td>\n<td>refillPush</td>\n<td>refillPush</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td>push</td>\n<td>push</td>\n<td>push</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td>push</td>\n<td>push</td>\n<td>push</td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td>refillOrPush</td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td>refill</td>\n<td></td>\n<td>0</td>\n<td>0</td>\n<td>0</td>\n<td>1</td>\n<td>1</td>\n<td>1</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td>reuse</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td>0</td>\n<td>0</td>\n<td>0</td>\n<td>1</td>\n<td>1</td>\n<td>1</td>\n<td>2</td>\n<td>2</td>\n<td>2</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td></td>\n<td>refillPop</td>\n<td>refillPop</td>\n<td>refillPop</td>\n<td>refillPop</td>\n<td>refillPop</td>\n<td>refillPop</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td></td>\n<td>orPush,orPop做判断</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td>reusePop</td>\n<td>reusePop</td>\n<td>reusePop</td>\n<td>reusePop</td>\n<td>reusePop</td>\n<td>reusePop</td>\n<td>reusePop</td>\n<td>reusePop</td>\n<td>reusePop</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td>pop</td>\n<td>pop</td>\n<td>pop</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td>reusePush</td>\n<td>reusePush</td>\n<td>reusePush</td>\n<td>reusePush</td>\n<td>reusePush</td>\n<td>reusePush</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td></td>\n<td>refillOrPop</td>\n<td>refillOrPop</td>\n<td>refillOrPop</td>\n<td>refillOrPop</td>\n<td>refillOrPop</td>\n<td>refillOrPop</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td>refillOrPop</td>\n<td>refillOrPop</td>\n<td>refillOrPop</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td>reuseOrPush</td>\n<td>reuseOrPush</td>\n<td>reuseOrPush</td>\n<td>reuseOrPush</td>\n<td>reuseOrPush</td>\n<td>reuseOrPush</td>\n<td>reuseOrPush</td>\n<td>reuseOrPush</td>\n<td>reuseOrPush</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td>reuseOrPop</td>\n<td>reuseOrPop</td>\n<td>reuseOrPop</td>\n<td>reuseOrPop</td>\n<td>reuseOrPop</td>\n<td>reuseOrPop</td>\n<td>reuseOrPop</td>\n<td>reuseOrPop</td>\n<td>reuseOrPop</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td>orPush</td>\n<td>orPush</td>\n<td>orPush</td>\n<td>orPush</td>\n<td>orPush</td>\n<td>orPush</td>\n<td>orPush</td>\n<td>orPush</td>\n<td>orPush</td>\n<td>orPush</td>\n<td>orPush</td>\n<td>orPush</td>\n<td>orPush</td>\n<td>orPush</td>\n<td>orPush</td>\n<td>orPush</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td></td>\n<td>orPop</td>\n<td>orPop</td>\n<td>orPop</td>\n<td>orPop</td>\n<td>orPop</td>\n<td>orPop</td>\n<td>orPop</td>\n<td>orPop</td>\n<td>orPop</td>\n<td>orPop</td>\n<td>orPop</td>\n<td>orPop</td>\n<td>orPop</td>\n<td>orPop</td>\n<td>orPop</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n<tr>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td>orPush,orPop做判断</td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n<td></td>\n</tr>\n</tbody></table>\n",
"tags": [
"Paper",
"Algorithm",
"SNN",
"FPGA",
"Hardware"
]
},
{
"id": "https://nightfury.top/2023/09/10/A-Low-Power-and-Low-Latency-FPGA-Based-Spiking-Neural-Network-Accelerator/",
"url": "https://nightfury.top/2023/09/10/A-Low-Power-and-Low-Latency-FPGA-Based-Spiking-Neural-Network-Accelerator/",
"title": "A Low Power and Low Latency FPGA-Based Spiking Neural Network Accelerator",
"date_published": "2023-09-10T07:36:45.000Z",
"content_html": "<h1 id=\"A-Low-Power-and-Low-Latency-FPGA-Based-Spiking-Neural-Network-Accelerator\"><a href=\"#A-Low-Power-and-Low-Latency-FPGA-Based-Spiking-Neural-Network-Accelerator\" class=\"headerlink\" title=\"A Low Power and Low Latency FPGA-Based Spiking Neural Network Accelerator\"></a>A Low Power and Low Latency FPGA-Based Spiking Neural Network Accelerator</h1><h2 id=\"摘要\"><a href=\"#摘要\" class=\"headerlink\" title=\"摘要\"></a>摘要</h2><p>翻译自 ChatGPT:</p>\n<blockquote>\n<p>脉冲神经网络(SNNs),被称为神经网络的第三代,因其生物合理性和类似大脑的特征而著名。最近的努力进一步展示了 SNN 在高速推断方面的潜力,通过设计具有时间或空间维度并行性的加速器。然而,由于硬件资源的限制,加速器设计必须利用片外内存来存储许多中间数据,这导致了高功耗和长延迟。本文侧重于层间数据流以提高算术效率。基于脉冲的离散特性,我们设计了一个卷积池化(CONVP)单元,<strong>将卷积层和池化层的处理合并</strong>,以减少延迟和资源利用率。此外,对于全连接层,我们应用了<strong>内部输出并行性和跨输出并行性</strong>来加速网络推理。我们通过在 Zynq XA7Z020 FPGA 上实现不同的 SNN 模型,并使用不同的数据集来展示我们提出的硬件架构的有效性。实验结果显示,我们的加速器在 MNIST 数据集上与 FPGA 实现相比,可以实现约 28 倍的推断速度提升,并在 DVSGesture 数据集上与 ASIC 设计相比,可以实现约 15 倍的推断速度提升,功耗较低。</p>\n</blockquote>\n<h2 id=\"相关工作\"><a href=\"#相关工作\" class=\"headerlink\" title=\"相关工作\"></a>相关工作</h2><ol>\n<li>使用 <strong>rate coding</strong>,<strong>IF</strong> 神经元模型,没给出具体的时间步是多少。</li>\n</ol>\n<h2 id=\"设计方法\"><a href=\"#设计方法\" class=\"headerlink\" title=\"设计方法\"></a>设计方法</h2><ol>\n<li><p>PC 端负责<strong>脉冲编码</strong>,通过 UART 协议输出给 PL 端;PL 端负责<strong>网络推理</strong>,<strong>对输入脉冲做 Line Buffer 做卷积(Adder Tree)</strong>,输出结果再传回 PC 端进行验证,系统框图如图:<img loading=\"lazy\" data-src=\"/assets/A-Low-Power-and-Low-Latency-FPGA-Based-Spiking-Neural-Network-Accelerator/%E7%B3%BB%E7%BB%9F%E6%A1%86%E5%9B%BE.png\" alt=\"系统框图\"></p>\n</li>\n<li><p>所谓的 CONVP 就是把 <strong>Conv 和 MaxPooling 融合</strong>(呃呃不就是很常见的多加个 buffer 吗,而且作者所做的似乎是 Valid Conv ),然后做了 <strong>Intra-Row Pooling</strong> 和 <strong>Inter-Row Pooling</strong>,说白了就是比如做 MP2 的时候,第一行先做 Intra-Row Pooling,每两个做一次 OR 操作再写入 Buffer,在第二行的时候则作 Inter-Row Pooling,本来是应该读出第一行的 pooling 结果然后接着做 OR 操作的,但作者似乎是偷懒多开了一行 Buffer 再一一做 OR 操作,如图:</p>\n<p><img loading=\"lazy\" data-src=\"/assets/A-Low-Power-and-Low-Latency-FPGA-Based-Spiking-Neural-Network-Accelerator/MaxPooling.png\" alt=\"MaxPooling\"></p>\n</li>\n<li><p>卷积过程也有 <strong>Inter-Output Parallelism</strong> 和 <strong>Intra-Output Parallelism</strong>,其中 Intra-Output Parallelism 是把对某个神经元有贡献的权重(n bits)分成 k 个作为一组,即并行度为 k,经过 Ci / k 个 Clock 后就可以算完一个输出神经元的膜电势;Intra-Output Parallelism 没怎么理解,我估计是对上个 FC 层计算完的结果直接拿来做运算,而不需要存起来再被这层 FC 取出来作为 Input,如图:<img loading=\"lazy\" data-src=\"/assets/A-Low-Power-and-Low-Latency-FPGA-Based-Spiking-Neural-Network-Accelerator/%E5%85%A8%E8%BF%9E%E6%8E%A5%E5%B9%B6%E8%A1%8C%E8%8C%83%E5%BC%8F.png\" alt=\"全连接并行范式\"><img loading=\"lazy\" data-src=\"/assets/A-Low-Power-and-Low-Latency-FPGA-Based-Spiking-Neural-Network-Accelerator/%E5%85%A8%E8%BF%9E%E6%8E%A5%E6%9D%83%E9%87%8D%E5%AD%98%E5%82%A8.png\" alt=\"全连接权重存储\"></p>\n</li>\n</ol>\n<h2 id=\"实验与评估\"><a href=\"#实验与评估\" class=\"headerlink\" title=\"实验与评估\"></a>实验与评估</h2><ol>\n<li><p>在 Xilinx Zynq XA7Z020 FPGA 上进行了部署,Verilog 设计,8 bits 定点量化,100 MHz 时钟频率,性能对比如图:</p>\n<p><img loading=\"lazy\" data-src=\"/assets/A-Low-Power-and-Low-Latency-FPGA-Based-Spiking-Neural-Network-Accelerator/%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94.png\" alt=\"性能对比\"></p>\n</li>\n<li><p>其他也没啥好说的了,作者最后还对比了下 <strong>CONVP</strong> 和 <strong>CONV-POOLING</strong> 以及有无 <strong>Inter-Output Parallelism</strong> 和 <strong>Intra-Output Parallelism</strong> 的推理速率、资源使用率的差别,只能说增加了下工作量吧。</p>\n</li>\n</ol>\n",
"tags": [
"Paper",
"Algorithm",
"SNN",
"FPGA",
"Hardware"
]
},
{
"id": "https://nightfury.top/2023/09/07/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/",
"url": "https://nightfury.top/2023/09/07/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/",
"title": "Efficient Hardware Acceleration of Sparsely Active Convolutional Spiking Neural Networks",
"date_published": "2023-09-07T12:46:18.000Z",
"content_html": "<h1 id=\"Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks\"><a href=\"#Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks\" class=\"headerlink\" title=\"Efficient Hardware Acceleration of Sparsely Active Convolutional Spiking Neural Networks\"></a>Efficient Hardware Acceleration of Sparsely Active Convolutional Spiking Neural Networks</h1><h2 id=\"摘要\"><a href=\"#摘要\" class=\"headerlink\" title=\"摘要\"></a>摘要</h2><p>翻译自 ChatGPT:</p>\n<blockquote>\n<p>脉冲神经网络(SNNs)以事件驱动方式进行计算,以实现比标准神经网络更高效的计算。在 SNNs 中,神经元的输出不是编码为实值激活,而是编码为二进制脉冲序列。使用 SNNs 而不是传统神经网络的动机根植于脉冲处理的特殊计算方面,尤其是高度稀疏的脉冲。已经建立良好的卷积神经网络(CNNs)实现具有大型的处理元素(PEs)空间阵列,但在面对激活稀疏性时,它们的利用率仍然很低。我们提出了一种针对高度稀疏的卷积脉冲神经网络(CSNNs)进行优化的新型架构。<strong>所提出的架构包括一个与卷积内核大小相同的PE阵列以及一个智能脉冲队列,提供了高PE利用率</strong>。通过将特征图压缩成可以逐个脉冲处理的队列,确保了脉冲的持续流动。这种压缩是在运行时执行的,导致了一种自时序调度。这使得处理时间可以随着脉冲数量的增加而扩展。此外,引入了一种新颖的内存组织方案,用于使用多个小型并行的片上 RAM 高效存储和检索各个神经元的膜电位。每个 RAM 都与其 PE 硬连线,减少了开关电路。我们在 FPGA 上实现了所提出的架构,并与以前提出的 SNN 实现相比实现了显著的加速(约 10 倍),同时需要更少的硬件资源并保持更高的能源效率(约 15 倍)。</p>\n</blockquote>\n<h2 id=\"相关工作\"><a href=\"#相关工作\" class=\"headerlink\" title=\"相关工作\"></a>相关工作</h2><ol>\n<li>使用 <strong>m-TTFS coding</strong>,比 rate coding 时间步更短,相较于 TTFS 的只发射一次脉冲变成持续发送脉冲,虽然脉冲稀疏性降低了,但是不需要再积累膜电势了,仍然比 rate coding 更加高效。但是后面在讲 Thresholding Unit 的时候,又提到说 m-TTFS coding 只发射一次脉冲,前后矛盾!!暂且认为是笔误了,Threshoding Unit 对于膜电势做两个判断,一是是否超过阈值,二是特定的 bit 有没有 set,set 说明发射过脉冲,而在最后一个时间步时会 reset.<img loading=\"lazy\" data-src=\"/assets/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/Coding%E7%9F%9B%E7%9B%BE1.png\" alt=\"Coding矛盾1\"><img loading=\"lazy\" data-src=\"/assets/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/Coding%E7%9F%9B%E7%9B%BE2.png\" alt=\"Coding矛盾2\"></li>\n</ol>\n<h2 id=\"硬件架构\"><a href=\"#硬件架构\" class=\"headerlink\" title=\"硬件架构\"></a>硬件架构</h2><ol>\n<li>卷积和全连接分成了两个部分硬件实现,不够优雅(你猜为什么测试用例在做 FC 之前卷积核个数锐减到 10 个?),系统框图如图:<img loading=\"lazy\" data-src=\"/assets/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/%E7%B3%BB%E7%BB%9F%E6%A1%86%E5%9B%BE.png\" alt=\"系统框图\"></li>\n<li><strong>输出通道作为循环最外层</strong>,先对某一通道做所有时间步的推理,然后将这些通道的 Spike 存入 AER 的表中。一层一层做,没有层间流水化:<img loading=\"lazy\" data-src=\"/assets/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/%E6%8E%A8%E7%90%86%E5%BE%AA%E7%8E%AF.png\" alt=\"推理循环\"></li>\n</ol>\n<h2 id=\"硬件实现\"><a href=\"#硬件实现\" class=\"headerlink\" title=\"硬件实现\"></a>硬件实现</h2><ol>\n<li><p>所谓的 <strong>Memory Interlacing</strong>,就是只做 3×3 卷积(这篇文章对于 3×3 卷积做了很多特定的设计,非常之不通用,详见后文),且这 9 个格的 Spike 可以并行写入到 9 个 RAM 里作为 AER 表格(深度未知,需要 fmapSize / 9 × ChannelOut?) 。他的分块方式是这样的:对特征图 FMap 直接做 3×3 的分块,然后将分块内的 9 个元素编号后分别存在各自的 RAM 中,膜电势和权重也是分成 9 个部分,AER 表格存储方式如图:<img loading=\"lazy\" data-src=\"/assets/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/%E5%86%85%E5%AD%98%E5%88%86%E5%9D%97.png\" alt=\"内存分块\"></p>\n</li>\n<li><p>卷积单元流程可以分为:<strong>计算地址 S1 → 读膜电势 S2 → 更新电势 S3 → 写膜电势 S4</strong>。</p>\n<ul>\n<li><p>计算 Spike 所产生的 Active 邻域地址的时候,作者对于 9 个输出地址与输入地址都做了相应的映射(对于 3×3 卷积设计非常的特质化!!或许有些情况可以合并,懒得深究了),如图为输入地址为(0, 0)[5]($s_{in}=5$)到邻域 $s_{in}=1$ 的映射公式与示例:</p>\n<p><img loading=\"lazy\" data-src=\"/assets/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/%E9%82%BB%E5%9F%9F%E6%98%A0%E5%B0%84%E7%A4%BA%E4%BE%8B.png\" alt=\"邻域映射示例\"><img loading=\"lazy\" data-src=\"/assets/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/%E9%82%BB%E5%9F%9F%E6%98%A0%E5%B0%84%E5%85%AC%E5%BC%8F.png\" alt=\"邻域映射公式\"></p>\n<p>除此之外,由于这 9 个卷积核到 9 个 PE 的映射取决于输入脉冲地址(比如从蓝框到紫框,最左上角从序号 1 变成了序号 6,但权重位置其实没有变),所以它还需要 9 个 9 选 1 的 Mux 来做膜电势更新。 </p>\n</li>\n<li><p>更新电势的过程中考虑了上溢下溢的情况做了饱和操作,饱和操作对于 m-TTFS 而言影响不大,通过 <strong>check 符号位的变化</strong>可以实现上下溢的判断,好评。</p>\n</li>\n<li><p>卷积会遇到 S2 - S4,S3 - S4 的 <strong>Data Hazard</strong>,为了避免这样的数据冒险,作者对 S2 - S4 的数据冒险(S4 写的膜电势地址与 S2 读的膜电势地址一致)做了 Bypass(意味着 9 个 2 选 1 的 Mux),因为 S4 计算完的膜电势刚好可以对 S2 用,而对于 S3 - S4 的数据冒险则是采用了对 S2,S1 做了阻塞,从而使得转换成 S2 - S4 的数据冒险类型,但这样其实是会空一拍。不过作者也说明了由于他是 0 - 9 的顺序去做的,所以其实也很难有同样地址的 S3 - S4 数据冒险。</p>\n</li>\n</ul>\n</li>\n<li><p>只支持 3×3 的 MaxPooling,垃圾。</p>\n<p><img loading=\"lazy\" data-src=\"/assets/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/MaxPooling.png\" alt=\"MaxPooling\"></p>\n</li>\n</ol>\n<h2 id=\"实验与总结\"><a href=\"#实验与总结\" class=\"headerlink\" title=\"实验与总结\"></a>实验与总结</h2><ol>\n<li><p>给出了不同并行度时的吞吐速率和功耗,进一步计算能效比,这样可以选择出合适的并行度,不错的思路。</p>\n<p><img loading=\"lazy\" data-src=\"/assets/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/%E8%83%BD%E6%95%88%E6%AF%94.png\" alt=\"能效比\"></p>\n</li>\n<li><p>8 bits 量化大概用了 60 个 36K BRAM,16 bits 量化大概用了 111 个 36K BRAM(网络本身很小,28×28-32C3-32C3-P3-10C3-F10,对于 8 bits 量化而言按道理权重需要 10 个 18K 而已,膜电势就算所有都全存储起来也只需要 50 个 18K,你在这用了 60 个 36 K 在干啥啊??),时钟频率跑到 333 MHz,但用的是 UltraScale+ ZU7EV,这个器件比 ZU5EV 大了一倍多,只能说还可以吧。<img loading=\"lazy\" data-src=\"/assets/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/%E8%B5%84%E6%BA%90%E5%88%A9%E7%94%A8%E7%8E%87.png\" alt=\"资源利用率\"></p>\n</li>\n<li><p>作者统计了下自己 PE 阵列的使用率,第二、三层都只有 50+ 的使用率,这依然不妨碍作者夸自己的 PE 使用率高。</p>\n<p><img loading=\"lazy\" data-src=\"/assets/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/PE%E4%BD%BF%E7%94%A8%E7%8E%87.png\" alt=\"PE使用率\"></p>\n</li>\n<li><p>性能对比:</p>\n<p><img loading=\"lazy\" data-src=\"/assets/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94.png\" alt=\"性能对比\"></p>\n</li>\n</ol>\n<h2 id=\"结论\"><a href=\"#结论\" class=\"headerlink\" title=\"结论\"></a>结论</h2><ol>\n<li><p>结论部分说自己的 neuron multiplexing 是为了降低膜电势的存储占用问题的???</p>\n<p><img loading=\"lazy\" data-src=\"/assets/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/%E8%BF%B7%E6%83%91.png\" alt=\"迷惑\"></p>\n</li>\n<li><p>通信作者是 IEEE Fellow,以及</p>\n<p><img loading=\"lazy\" data-src=\"/assets/Efficient-Hardware-Acceleration-of-Sparsely-Active-Convolutional-Spiking-Neural-Networks/%E6%8E%A5%E6%94%B6%E6%97%A5%E6%9C%9F.png\" alt=\"接收日期\"></p>\n<p>dddd,哈哈。</p>\n</li>\n</ol>\n",
"tags": [
"Paper",
"Algorithm",
"SNN",
"FPGA",
"Hardware"
]
},
{
"id": "https://nightfury.top/2023/08/18/StreamFifo/",
"url": "https://nightfury.top/2023/08/18/StreamFifo/",
"title": "StreamFifo",
"date_published": "2023-08-18T09:38:12.000Z",
"content_html": "<h1 id=\"StreamFifo\"><a href=\"#StreamFifo\" class=\"headerlink\" title=\"StreamFifo\"></a>StreamFifo</h1><pre><code class=\"language-scala\">import spinal.core._\nimport spinal.lib._\n\nimport scala.language.postfixOps\n\ntrait StreamReuseFifoInterface[T <: Data]{\n def push : Stream[T]\n def pop : Stream[T]\n def pushOccupancy : UInt\n def popOccupancy : UInt\n}\n\nobject StreamReuseFifo{\n def apply[T <: Data](dataType: HardType[T],\n depth: Int,\n latency : Int = 2,\n forFMax : Boolean = false) = {\n assert(latency >= 0 && latency <= 2)\n new StreamFifo(\n dataType,\n depth,\n withAsyncRead = latency < 2,\n withBypass = latency == 0,\n forFMax = forFMax\n )\n }\n}\n\n/**\n * Fully redesigned in release 1.8.2 allowing improved timing closure.\n * - latency of 0, 1, 2 cycles\n *\n * @param dataType\n * @param depth Number of element stored in the fifo, Note that if withAsyncRead==false, then one extra transaction can be stored\n * @param withAsyncRead Read the memory using asyncronous read port (ex distributed ram). If false, add 1 cycle latency\n * @param withBypass Bypass the push port to the pop port when the fifo is empty. If false, add 1 cycle latency\n * Only available if withAsyncRead == true\n * @param forFMax Tune the design to get the maximal clock frequency\n * @param useVec Use an Vec of register instead of a Mem to store the content\n * Only available if withAsyncRead == true\n */\nclass StreamReuseFifo[T <: Data](val dataType: HardType[T],\n val depth: Int,\n val withAsyncRead : Boolean = false,\n val withBypass : Boolean = false,\n val allowExtraMsb : Boolean = true,\n val forFMax : Boolean = false,\n val useVec : Boolean = false) extends Component {\n require(depth >= 0)\n\n if(withBypass) require(withAsyncRead)\n if(useVec) require (withAsyncRead)\n\n val io = new Bundle with StreamReuseFifoInterface[T]{\n val push = slave Stream (dataType)\n val pop = master Stream (dataType)\n val flush = in Bool() default(False)\n val occupancy = out UInt (log2Up(depth + 1) bits)\n val availability = out UInt (log2Up(depth + 1) bits)\n override def pushOccupancy = occupancy\n override def popOccupancy = occupancy\n }\n\n class CounterUpDownFmax(states : BigInt, init : BigInt) extends Area{\n val incr, decr = Bool()\n val value = Reg(UInt(log2Up(states) bits)) init(init)\n val plusOne = KeepAttribute(value + 1)\n val minusOne = KeepAttribute(value - 1)\n when(incr =/= decr){\n value := incr.mux(plusOne, minusOne)\n }\n when(io.flush) { value := init }\n }\n\n val withExtraMsb = allowExtraMsb && isPow2(depth)\n val bypass = (depth == 0) generate new Area {\n io.push >> io.pop\n io.occupancy := 0\n io.availability := 0\n }\n val oneStage = (depth == 1) generate new Area {\n val doFlush = CombInit(io.flush)\n val buffer = io.push.m2sPipe(flush = doFlush)\n io.pop << buffer\n io.occupancy := U(buffer.valid)\n io.availability := U(!buffer.valid)\n\n if(withBypass){\n when(!buffer.valid){\n io.pop.valid := io.push.valid\n io.pop.payload := io.push.payload\n doFlush setWhen(io.pop.ready)\n // 这里doFlush拉高是因为m2sPipe的rValid=RegNextWhen(self.valid, self.ready) init(False)\n // 而由于bypass的缘故, rValid不应该采样当前的self.valid, 因此doFlush拉高, rValid clearWhen(flush)\n }\n }\n }\n val logic = (depth > 1) generate new Area {\n val vec = useVec generate Vec(Reg(dataType), depth)\n val ram = !useVec generate Mem(dataType, depth)\n\n val ptr = new Area{\n val doPush, doPop = Bool() // 这两个信号都是对内部Ram而言的,其中doPush就是io.push.fire,仅在bypass情况有所不同\n val full, empty = Bool()\n val push = Reg(UInt(log2Up(depth) + withExtraMsb.toInt bits)) init(0)\n val pop = Reg(UInt(log2Up(depth) + withExtraMsb.toInt bits)) init(0)\n val occupancy = cloneOf(io.occupancy)\n val popOnIo = cloneOf(pop) // Used to track the global occupancy of the fifo (the extra buffer of !withAsyncRead)\n val wentUp = RegNextWhen(doPush, doPush =/= doPop) init(False) clearWhen (io.flush)\n // pop只是针对内部而言的, pop会在ram做pop操作后更改指针, 但pop操作到读结束还有latency, 因此用popOnIo记录读结束的指针\n\n val arb = new Area {\n // full是为了反压push的, pop相当于提前生成了读地址, 而popOnIo则是外部fire时的读地址\n // 因此full信号不用pop信号是因为他所指的指针可能并未真正的读(pop通常会比popOnIo多1)\n // empty是用于pop的valid信号的(准确来说是直接提供给addressGen), 但empty信号\n // 对外部并不暴露, 因此能否做pop操作只需要根据对内部的pop和push做判断即可\n val area = !forFMax generate {\n withExtraMsb match {\n case true => { //as we have extra MSB, we don't need the "wentUp"\n full := (push ^ popOnIo ^ depth) === 0 // full时push=popOnIo=depth\n empty := push === pop\n }\n case false => {\n full := push === popOnIo && wentUp\n empty := push === pop && !wentUp\n }\n }\n }\n\n val fmax = forFMax generate new Area {\n val counterWidth = log2Up(depth) + 1\n // empty对内部而言(为了pop)\n val emptyTracker = new CounterUpDownFmax(1 << counterWidth, 1 << (counterWidth - 1)) {\n incr := doPop\n decr := doPush\n empty := value.msb\n }\n // full对外部而言(为了push)\n val fullTracker = new CounterUpDownFmax(1 << counterWidth, (1 << (counterWidth - 1)) - depth) {\n incr := io.push.fire\n decr := io.pop.fire\n full := value.msb\n }\n }\n }\n\n\n when(doPush){\n push := push + 1\n if(!isPow2(depth)) when(push === depth - 1){ push := 0 }\n }\n when(doPop){\n pop := pop + 1\n if(!isPow2(depth)) when(pop === depth - 1){ pop := 0 }\n }\n\n when(io.flush){\n push := 0\n pop := 0\n }\n\n\n val forPow2 = (withExtraMsb && !forFMax) generate new Area{\n occupancy := push - popOnIo //if no extra msb, could be U(full ## (push - popOnIo))\n }\n\n val notPow2 = (!withExtraMsb && !forFMax) generate new Area{\n val counter = Reg(UInt(log2Up(depth + 1) bits)) init(0)\n counter := counter + U(io.push.fire) - U(io.pop.fire)\n occupancy := counter\n\n when(io.flush) { counter := 0 }\n }\n val fmax = forFMax generate new CounterUpDownFmax(depth + 1, 0){\n incr := io.push.fire\n decr := io.pop.fire\n occupancy := value\n }\n }\n\n val push = new Area {\n io.push.ready := !ptr.full\n ptr.doPush := io.push.fire\n val onRam = !useVec generate new Area {\n val write = ram.writePort()\n write.valid := io.push.fire\n write.address := ptr.push.resized\n write.data := io.push.payload\n }\n val onVec = useVec generate new Area {\n when(io.push.fire){\n vec.write(ptr.push.resized, io.push.payload)\n }\n }\n }\n\n val pop = new Area{\n val addressGen = Stream(UInt(log2Up(depth) bits))\n addressGen.valid := !ptr.empty\n addressGen.payload := ptr.pop.resized\n ptr.doPop := addressGen.fire\n\n val sync = !withAsyncRead generate new Area{\n assert(!useVec)\n val readArbitration = addressGen.m2sPipe(flush = io.flush) // valid和读地址打一拍\n val readPort = ram.readSyncPort // 同样的是1 cycle delay\n readPort.cmd := addressGen.toFlowFire // toFlowFire, 读的时候不需要ready\n io.pop << readArbitration.translateWith(readPort.rsp) // valid打一拍后payload替换成读数据\n\n val popReg = RegNextWhen(ptr.pop, readArbitration.fire) init(0)\n ptr.popOnIo := popReg // 读结束后采样pop指针\n when(io.flush){ popReg := 0 }\n }\n\n val async = withAsyncRead generate new Area{\n val readed = useVec match {\n case true => vec.read(addressGen.payload)\n case false => ram.readAsync(addressGen.payload)\n }\n io.pop << addressGen.translateWith(readed)\n ptr.popOnIo := ptr.pop\n\n if(withBypass){\n when(ptr.empty){\n io.pop.valid := io.push.valid\n io.pop.payload := io.push.payload\n ptr.doPush clearWhen(io.pop.ready)\n }\n }\n }\n }\n\n io.occupancy := ptr.occupancy\n if(!forFMax) io.availability := depth - ptr.occupancy\n val fmaxAvail = forFMax generate new CounterUpDownFmax(depth + 1, depth){\n incr := io.pop.fire\n decr := io.push.fire\n io.availability := value\n }\n }\n\n\n\n // check a condition against all valid payloads in the FIFO RAM\n def formalCheckRam(cond: T => Bool): Vec[Bool] = this rework new Composite(this){\n val condition = (0 until depth).map(x => cond(if (useVec) logic.vec(x) else logic.ram(x)))\n // create mask for all valid payloads in FIFO RAM\n // inclusive [popd_idx, push_idx) exclusive\n // assume FIFO RAM is full with valid payloads\n // [ ... push_idx ... ]\n // [ ... pop_idx ... ]\n // mask [ 1 1 1 1 1 1 1 1 1 ]\n val mask = Vec(True, depth)\n val push_idx = logic.ptr.push.resize(log2Up(depth))\n val pop_idx = logic.ptr.pop.resize(log2Up(depth))\n // pushMask(i)==0 indicates location i was popped\n val popMask = (~((U(1) << pop_idx) - 1)).asBits\n // pushMask(i)==1 indicates location i was pushed\n val pushMask = ((U(1) << push_idx) - 1).asBits\n // no wrap [ ... popd_idx ... push_idx ... ]\n // popMask [ 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1]\n // pushpMask [ 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0] &\n // mask [ 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0]\n when(pop_idx < push_idx) {\n mask.assignFromBits(pushMask & popMask)\n // wrapped [ ... push_idx ... popd_idx ... ]\n // popMask [ 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1]\n // pushpMask [ 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0] |\n // mask [ 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1]\n }.elsewhen(pop_idx > push_idx) {\n mask.assignFromBits(pushMask | popMask)\n // empty?\n // [ ... push_idx ... ]\n // [ ... pop_idx ... ]\n // mask [ 0 0 0 0 0 0 0 0 0 ]\n }.elsewhen(logic.ptr.empty) {\n mask := mask.getZero\n }\n val check = mask.zipWithIndex.map{case (x, id) => x & condition(id)}\n val vec = Vec(check)\n }.vec\n\n def formalCheckOutputStage(cond: T => Bool): Bool = this.rework {\n // only with sync RAM read, io.pop is directly connected to the m2sPipe() stage\n Bool(!withAsyncRead) & io.pop.valid & cond(io.pop.payload)\n }\n\n // verify this works, then we can simplify below\n //def formalCheck(cond: T => Bool): Vec[Bool] = this.rework {\n // Vec(formalCheckOutputStage(cond) +: formalCheckRam(cond))\n //}\n\n def formalContains(word: T): Bool = this.rework {\n formalCheckRam(_ === word.pull()).reduce(_ || _) || formalCheckOutputStage(_ === word.pull())\n }\n def formalContains(cond: T => Bool): Bool = this.rework {\n formalCheckRam(cond).reduce(_ || _) || formalCheckOutputStage(cond)\n }\n\n def formalCount(word: T): UInt = this.rework {\n // occurance count in RAM and in m2sPipe()\n CountOne(formalCheckRam(_ === word.pull())) +^ U(formalCheckOutputStage(_ === word.pull()))\n }\n def formalCount(cond: T => Bool): UInt = this.rework {\n // occurance count in RAM and in m2sPipe()\n CountOne(formalCheckRam(cond)) +^ U(formalCheckOutputStage(cond))\n }\n\n def formalFullToEmpty() = this.rework {\n val was_full = RegInit(False) setWhen(!io.push.ready)\n cover(was_full && logic.ptr.empty)\n }\n}\n</code></pre>\n",
"tags": [
"Technique",
"Draft",
"FPGA",
"SpinalHDL"
]
},
{
"id": "https://nightfury.top/2023/08/15/Memory%20Layout%20Analysis/",
"url": "https://nightfury.top/2023/08/15/Memory%20Layout%20Analysis/",
"title": "Memory Layout Analysis",
"date_published": "2023-08-15T02:38:12.000Z",
"content_html": "<h1 id=\"Memory-Layout-Analysis\"><a href=\"#Memory-Layout-Analysis\" class=\"headerlink\" title=\"Memory Layout Analysis\"></a>Memory Layout Analysis</h1><h2 id=\"Voltage-Membrane\"><a href=\"#Voltage-Membrane\" class=\"headerlink\" title=\"Voltage Membrane\"></a>Voltage Membrane</h2><p>$$<br>Total=Co \\times H_v \\times W_v<br>$$<br>$$<br>Burst=H_k<br>$$</p>\n<h2 id=\"Kernel-Weight\"><a href=\"#Kernel-Weight\" class=\"headerlink\" title=\"Kernel Weight\"></a>Kernel Weight</h2><p>$$<br>Total=Co \\times Ci \\times H_k \\times W_k<br>$$<br>$$<br>Burst=Ci \\times S \\times H_k \\times W_k<br>$$</p>\n<h2 id=\"Mixed\"><a href=\"#Mixed\" class=\"headerlink\" title=\"Mixed\"></a>Mixed</h2><p>$$<br>Parameter\\ Unit =\\frac{H_v \\times W_v}{H_k \\times W_k} + Ci<br>$$</p>\n",
"tags": [
"Algorithm",
"Draft",
"FPGA",
"SpinalHDL"
]
},
{
"id": "https://nightfury.top/2023/08/14/Hexo%E9%83%A8%E7%BD%B2%E5%88%B0VPS/",
"url": "https://nightfury.top/2023/08/14/Hexo%E9%83%A8%E7%BD%B2%E5%88%B0VPS/",
"title": "Hexo部署到VPS",
"date_published": "2023-08-14T07:17:08.000Z",
"content_html": "<p>网上大部分教程都是将 Hexo 部署到 GitHub Pages 上面,本文主要介绍如何部署到 VPS。</p>\n<p>VPS 环境:Ubuntu 18.04。</p>\n<h1 id=\"准备工作\"><a href=\"#准备工作\" class=\"headerlink\" title=\"准备工作\"></a>准备工作</h1><p>网上流传的武功秘籍分为两种:</p>\n<ul>\n<li>将 Hexo 项目上传到 VPS 上面后执行 <code>hexo server</code>,之后配置 Nginx 反向代理,让域名指向 <a href=\"http://localhost:4000。\">http://localhost:4000。</a></li>\n<li>将 Hexo 在本地通过 <code>hexo generate</code> 生成静态文件,在通过 <code>hexo deploy</code> 部署到 VPS 上面,使用 Nginx 直接做 Web 服务器。</li>\n<li>相比第二种方式,第一种每次写博客与更新博客时候的操作会很繁琐。所以我们使用第二种方式进行部署,这样既可以将静态文件 deploy 到 VPS 上,也可以上传到 Github 上用作备份,操作性和安全性上都要胜于前者。</li>\n<li>而对于第二种方式而言,常用的又有 <code>git hook</code> 和 <code>rsync </code>两种自动部署解决方案。</li>\n</ul>\n<p>本文主要介绍 <code>git hook</code> 部署过程。</p>\n<h1 id=\"Git-Hooks-自动部署\"><a href=\"#Git-Hooks-自动部署\" class=\"headerlink\" title=\"Git Hooks 自动部署\"></a>Git Hooks 自动部署</h1><h2 id=\"部署原理\"><a href=\"#部署原理\" class=\"headerlink\" title=\"部署原理\"></a>部署原理</h2><p>我们在本地编辑文本,然后使用 Git 远程部署到 VPS 的 Git 仓库。<code>hexo d</code> 命令实际上只 deploy 了本地的 public 文件夹,Git Hooks 实际上就是当 Git 仓库收到最新的 push 时,将 Git 仓库接受到的内容复制到 VPS 上的网站目录内。相当于完成了手动将 public 文件夹复制到 VPS 的网站根目录里。</p>\n<h2 id=\"安装配置-Git\"><a href=\"#安装配置-Git\" class=\"headerlink\" title=\"安装配置 Git\"></a>安装配置 Git</h2><h3 id=\"安装-Git\"><a href=\"#安装-Git\" class=\"headerlink\" title=\"安装 Git\"></a>安装 Git</h3><p>通过 SSH 连接 VPS,执行:<code>apt-get install git</code>,完成后通过 <code>git --version</code> 查看 Git 版本,若显示版本信息则说明安装成功。</p>\n<h3 id=\"创建-git-用户\"><a href=\"#创建-git-用户\" class=\"headerlink\" title=\"创建 git 用户\"></a>创建 git 用户</h3><p>执行:<code>adduser git</code>,根据提示设置密码。</p>\n<h3 id=\"赋予-git-用户-sudo-权限\"><a href=\"#赋予-git-用户-sudo-权限\" class=\"headerlink\" title=\"赋予 git 用户 sudo 权限\"></a>赋予 git 用户 sudo 权限</h3><p>执行:</p>\n<pre><code class=\"language-shell\">chmod 740 /etc/sudoers\nvim /etc/sudoers\n</code></pre>\n<p>找到以下内容:</p>\n<pre><code class=\"language-shell\">## User privilege specification\nroot ALL=(ALL:ALL) ALL\n# 在 root ALL=(ALL:ALL) ALL 这一行下面添加\ngit ALL=(ALL:ALL) ALL\n</code></pre>\n<p>保存退出后,修改回文件权限:</p>\n<pre><code class=\"language-shell\">chmod 440 /etc/sudoers\n</code></pre>\n<h3 id=\"关闭-git-用户-shell-权限\"><a href=\"#关闭-git-用户-shell-权限\" class=\"headerlink\" title=\"关闭 git 用户 shell 权限\"></a>关闭 git 用户 shell 权限</h3><p>我们也可以通过:</p>\n<pre><code class=\"language-shell\">ssh git@VPS IP\n</code></pre>\n<p>ssh 连接服务器,登录到服务器上,对服务器进行各种操作,这通常很不安全,也不合适,我们只需要能对仓库操作就可以了,不需要更大的权限。</p>\n<p>因此我们关闭 git 用户 shell 权限,执行:</p>\n<pre><code class=\"language-shell\">vim /etc/passwd\n</code></pre>\n<p>将最后一行的 <code>git:x:1001:1001:,,,:/home/git:/bin/bash</code> 修改为 <code>git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell</code></p>\n<p>这样,git 用户可以正常通过 ssh 使用 git,但无法登录 shell,因为我们为 git 用户指定的 git-shell 每次一登录就自动退出。</p>\n<h3 id=\"初始化-git-仓库\"><a href=\"#初始化-git-仓库\" class=\"headerlink\" title=\"初始化 git 仓库\"></a>初始化 git 仓库</h3><pre><code class=\"language-shell\">cd /home/git //切换到git用户目录\nmkdir blog.git //创建git仓库文件夹,以blog.git为例\ncd blog.git //进入仓库目录\ngit init --bare //使用--bare参数初始化为裸仓库,这样创建的仓库不包含工作区\n</code></pre>\n<p>注意:裸仓库没有工作区,因为服务器上的 Git 仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的 Git 仓库通常都以.git 结尾。</p>\n<h3 id=\"创建网站目录\"><a href=\"#创建网站目录\" class=\"headerlink\" title=\"创建网站目录\"></a>创建网站目录</h3><pre><code class=\"language-shell\">cd /home/wwwroot/ //切换目录\n# 由于我想直接将博客放在/home/wwwroot/下,所以并没有创建blog目录\nmkdir blog //创建网站目录,以blog为例\n</code></pre>\n<h3 id=\"配置-SSH\"><a href=\"#配置-SSH\" class=\"headerlink\" title=\"配置 SSH\"></a>配置 SSH</h3><pre><code class=\"language-shell\">cd /home/git //切换到git用户目录\nmkdir .ssh //创建.ssh目录\ncd .ssh\nvim authorized_keys\n</code></pre>\n<p>然后将本地的公钥复制到 <code>authorized_keys</code> 文件里 (公钥即本地执行 <code>cat ~/.ssh/id_rsa.pub</code> 查看的内容)。</p>\n<p>注意:收集所有需要登录的用户的公钥,就是他们自己的 <code>id_rsa.pub</code> 文件,把所有公钥导入到 <code>/home/git/.ssh/authorized_keys</code> 文件里,一行一个。</p>\n<h3 id=\"用户组管理\"><a href=\"#用户组管理\" class=\"headerlink\" title=\"用户组管理\"></a>用户组管理</h3><pre><code class=\"language-shell\">ll /home/git/\nll /home/wwwroot/\n</code></pre>\n<p>确保 <code>blog.git</code>、<code>.ssh</code>、<code>blog</code> 目录的用户组权限为 <code>git:git</code>,若不是,执行下列命令:</p>\n<p>修改用户权限的命令:</p>\n<pre><code class=\"language-shell\"># chown -R 用户名.组名 /目录\nchown -R git.git /home/git/blog.git/\nchown -R git.git /home/git/.ssh/\nchown -R git.git /home/wwwroot/\n</code></pre>\n<p>安装配置 Nginx</p>\n<p>由于我之前已经装过了nginx,nginx安装内容来源于网络,仅供参考。</p>\n<h3 id=\"安装-Nginx\"><a href=\"#安装-Nginx\" class=\"headerlink\" title=\"安装 Nginx\"></a>安装 Nginx</h3><p>执行:<code>apt-get install nginx</code>,若输入 <code>nginx -V</code> 可以看到 nginx 版本信息,则安装成功。</p>\n<h3 id=\"配置-nginx\"><a href=\"#配置-nginx\" class=\"headerlink\" title=\"配置 nginx\"></a>配置 nginx</h3><p>执行:</p>\n<pre><code class=\"language-shell\">cd /etc/nginx/sites-available //切换目录\ncp default default.bak //备份默认配置\nvim default //修改配置\n</code></pre>\n<p>参考配置文件内容:</p>\n<pre><code class=\"language-shell\">server {\n listen 80 default; #默认监听80端口\n root /home/wwwroot; #网站根目录\n server_name nightfury.top, www.nightfury.top; #网址\n access_log /var/log/nginx/blog_access.log;\n error_log /var/log/nginx/blog_error.log;\n error_page 404 = /404.html;\n \n location ~* ^.+\\.(ico|gif|jpg|jpeg|png)$ {\n root /home/wwwroot;\n access_log off;\n expires 1d;\n }\n\n location ~* ^.+\\.(css|js|txt|xml|swf|wav)$ {\n root /home/wwwroot;\n access_log off;\n expires 10m;\n }\n\n location / {\n root /home/wwwroot;\n if (-f $request_filename) {\n rewrite ^/(.*)$ /$1 break;\n }\n }\n\n location /nginx_status {\n stub_status on;\n access_log off;\n }\n}\n</code></pre>\n<p>保存退出后,启动 nginx:</p>\n<pre><code class=\"language-shell\">systemctl start nginx\n</code></pre>\n<p>设置开机自动启动:</p>\n<pre><code class=\"language-shell\">systemctl enable nginx\n</code></pre>\n<p>查看运行状态:</p>\n<pre><code class=\"language-shell\">systemctl status nginx\n</code></pre>\n<p>显示 running 表示成功运行。</p>\n<h2 id=\"配置-Git-Hooks\"><a href=\"#配置-Git-Hooks\" class=\"headerlink\" title=\"配置 Git Hooks\"></a>配置 Git Hooks</h2><h3 id=\"创建-post-receive-文件\"><a href=\"#创建-post-receive-文件\" class=\"headerlink\" title=\"创建 post-receive 文件\"></a>创建 post-receive 文件</h3><p>git 用户下执行(这里我用 root 用户执行上述命令,然后更改了文件所有者为 git.git,就是<code>chown -R git:git post-receive</code>):</p>\n<pre><code class=\"language-shell\">cd /home/git/blog.git/hooks //切换到hooks目录下\nvim post-receive //创建文件\n</code></pre>\n<p>复制下面的内容到 <code>post-receive</code> 文件中:</p>\n<pre><code class=\"language-shell\">#!/bin/bash\necho "post-receive hook is running..."\n\nGIT_REPO=/home/git/blog.git\nTMP_GIT_CLONE=/tmp/blog\nPUBLIC_WWW=/home/wwwroot\n\nrm -rf ${TMP_GIT_CLONE}\ngit clone $GIT_REPO $TMP_GIT_CLONE\nrm -rf ${PUBLIC_WWW}/*\ncp -rf ${TMP_GIT_CLONE}/* ${PUBLIC_WWW}\n</code></pre>\n<hr>\n<p>为什么不直接将裸仓库克隆到 Web 根目录下呢?我之前也一直被这个问题困扰,感觉先克隆到 tmp 目录再拷贝到 Web 根目录是多此一举。后来我觉得可能是出于项目安全的考虑,在执行 cp 命令的时候,.git 作为隐藏目录不会被拷贝到 Web 根目录下,也就避免了将整个仓库历史暴露在 Web 服务中。</p>\n<hr>\n<p>赋予可执行权限:</p>\n<pre><code class=\"language-shell\">chmod +x post-receive\n</code></pre>\n<h2 id=\"本地操作\"><a href=\"#本地操作\" class=\"headerlink\" title=\"本地操作\"></a>本地操作</h2><h3 id=\"尝试连接\"><a href=\"#尝试连接\" class=\"headerlink\" title=\"尝试连接\"></a>尝试连接</h3><p>在本地打开 Git Bash:</p>\n<pre><code class=\"language-shell\">ssh git@VPS的ip\n</code></pre>\n<p>若默认端口不是 22,则需要在后面加上 -p 端口号:</p>\n<pre><code class=\"language-shell\">ssh git@VPS的ip -p 2022\n</code></pre>\n<p>返回结果应该如下:</p>\n<pre><code class=\"language-shell\">->ssh [email protected]\n->Welcome to Ubuntu 18.04 LTS (GNU/Linux 4.14.129-bbrplus x86_64)\n\n * Documentation: https://help.ubuntu.com\n * Management: https://landscape.canonical.com\n * Support: https://ubuntu.com/advantage\n\n * Strictly confined Kubernetes makes edge and IoT secure. Learn how MicroK8s\n just raised the bar for easy, resilient and secure K8s cluster deployment.\n\n https://ubuntu.com/engage/secure-kubernetes-at-the-edge\n\n * Canonical Livepatch is available for installation.\n - Reduce system reboots and improve kernel security. Activate at:\n https://ubuntu.com/livepatch\nLast login: Mon Aug 14 15:56:58 2023 from 159.226.177.100\nfatal: Interactive git shell is not enabled.\nhint: ~/git-shell-commands should exist and have read and execute access.\nConnection to nightfury.top closed.\n</code></pre>\n<p>提示无法登录 shell 是正常的,因为我们在之前就为 git 用户指定了 git-shell 每次一登录就自动退出。</p>\n<h2 id=\"配置-Hexo\"><a href=\"#配置-Hexo\" class=\"headerlink\" title=\"配置 Hexo\"></a>配置 Hexo</h2><p>打开本地博客根目录下的_config.yml 文件,找到最后的 deploy 配置,修改为:</p>\n<pre><code class=\"language-shell\">## Deployment\n\n## Docs: https://hexo.io/docs/deployment.html\n\ndeploy:\n - type: git\n repo: [email protected]:szNightFury/szNightFury.github.io.git\n branch: master\n - type: git\n repo: [email protected]:/home/git/blog.git\n branch: master\n</code></pre>\n<p>到此,Hexo 建站就全部配置部署完毕了。</p>\n",
"tags": [
"Configuration",
"Hexo",
"Github",
"Ubuntu"
]
},
{
"id": "https://nightfury.top/2023/08/13/Markdown%E5%9B%BE%E7%89%87%E9%85%8D%E7%BD%AE/",
"url": "https://nightfury.top/2023/08/13/Markdown%E5%9B%BE%E7%89%87%E9%85%8D%E7%BD%AE/",
"title": "Markdown图片配置",
"date_published": "2023-08-13T12:38:10.000Z",
"content_html": "<h1 id=\"Hexo中插入图片的方案\"><a href=\"#Hexo中插入图片的方案\" class=\"headerlink\" title=\"Hexo中插入图片的方案\"></a>Hexo中插入图片的方案</h1><p>第一次使用 Hexo,对于 Markdown 中图片的处理不是很了解。由于 Markdown 中插入的图片多为本地或者网络链接的形式,而经过 Hexo 编译后,网页是不可能获取到本地图片,因此往往需要做额外的处理,经了解有以下几种方案:</p>\n<ol>\n<li>使用网络链接。这需要网上资源有你需要的图片,或者使用图床等手动上传,但该方法由于图片掌握在他人手上并不稳定/安全,因此果断抛弃。</li>\n<li>使用 base64 编码的方案。因为 Markdown 最后都会生成 html,因此可以将原本的 <code></code> 这种写法改成 <code></code>这种写法,这种写法不需要将图片存在任何除了这个文件以外的其它地方。但是,图片的 base64 编码非常之长,直接插入文件中间会影响 Markdown 编写过程中的美观。不过其实也可以使用间接的方法,在需要插入图片的地方写 <code>![image][reference_name]</code>,在其他地方(最好是文章末尾)写 <code>[reference_name]:<base64></code>,但个人觉得这种方式还是不够优雅。</li>\n<li>使用本地图片,同时上传至服务器。Hexo 本身提供了 <a href=\"https://hexo.io/zh-cn/docs/asset-folders.html\">解决方案 </a> ,需要配合 <code>hexo-asset-img</code> 一同食用,该方案比较优雅,只需将图片保存到本地(配合 Typora),然后一键三连即可(后文附上一键三连代码)</li>\n</ol>\n<h1 id=\"Hexo配置\"><a href=\"#Hexo配置\" class=\"headerlink\" title=\"Hexo配置\"></a>Hexo配置</h1><p>如 Hexo <a href=\"https://hexo.io/zh-cn/docs/asset-folders.html\">官方文档</a> 所说,我们首先对于 <code>_config.yml</code> 文件中的 <code>post_asset_folder: false</code> 改为 <code>true</code>,然后安装 <code>hexo-asset-img</code>(网上大多说是安装 <code>hexo-asset-image</code>,但据了解似乎不支持 Hexo5,已经弃用较久了,自己也尝试安装过,发现只有使用 <code>{Picture Filename}</code> 的形式才能在网页上正常显示,但在 Markdown 中却不行,需要使用 <code>{Markdown Title}/{Picture Filename}</code> 的形式才能在编写过程中显示出来,这很不优雅。而 <code>hexo-asset-img</code> 似乎是有个小伙对 <code>hexo-asset-image</code> 进行了魔改,看了下代码主要还是正则化等一些字符串处理,路径在 <code>{Your Hexo Filepath}\\node_modules\\hexo-asset-img\\index.js</code> 中)</p>\n<p>此时,通过 <code>hexo n "{Markdown Title}"</code> 会自动创建一个 Markdown 文件和相同名字的文件夹,这时只需要将 Markdown 中的图片存入对应的文件夹中,同时调用时使用 <code></code> 即可。注意,由于设置了 <code>post_asset_folder: true</code>,原来的 <code>front-matter</code> 中封面图片的根路径将从根目录 <code>.\\source\\</code> 变成 <code>.\\source\\_posts\\{Markdown Title}\\</code>,因此封面图片的路径直接使用 <code>{Cover Filename}</code> 即可。</p>\n<h1 id=\"Typora配置\"><a href=\"#Typora配置\" class=\"headerlink\" title=\"Typora配置\"></a>Typora配置</h1><p>打开 Typora,依次点击 <code>文件 - 偏好设置 - 图像</code>,设置插入图片时<code>复制到指定路径</code>,图片文件保存路径为 <code>./${filename}</code> 即保存到与当前正在编辑的文件名相同的同级文件夹下,此时,无论是复制的本地图片还是截图的图片,可以直接粘贴进 Typora 中,Typora 将会自动把图片复制到指定位置,当然,可能还需要修改一下图片名以及图片文件名(如果是截图了话)</p>\n<p><img loading=\"lazy\" data-src=\"/assets/Markdown%E5%9B%BE%E7%89%87%E9%85%8D%E7%BD%AE/Typora%E9%85%8D%E7%BD%AE.png\" alt=\"Typora配置\"></p>\n<h1 id=\"Hexo一键三连代码\"><a href=\"#Hexo一键三连代码\" class=\"headerlink\" title=\"Hexo一键三连代码\"></a>Hexo一键三连代码</h1><p>将以下代码保存到 Hexo 根目录的 <code>update.ps1</code> 文件中,然后用管理员权限打开 powershell,执行 <code>.\\update.ps1</code> 就会自动完成 <code>清理 - 编译 - 上传</code> 一键三连(当然前提是已经配置好了 hexo d 所需要的一些环境以及 git 帐号等信息填写)。</p>\n<pre><code class=\"language-powershell\"># 执行 hexo clean\nWrite-Host "Cleaning Hexo..."\nhexo clean\n\n# 执行 hexo g\nWrite-Host "Generating Hexo..."\nhexo g\n\n# 执行 hexo algolia(可选)\nWrite-Host "Running Hexo Algolia..."\nhexo algolia\n\n# 执行 hexo d\nWrite-Host "Deploying Hexo..."\nhexo d\n\nWrite-Host "Done!"\n</code></pre>\n",
"tags": [
"Configuration",
"Hexo",
"Shoka",
"Markdown",
"Typora"
]
},
{
"id": "https://nightfury.top/2023/08/13/Sbt%E7%BC%93%E5%AD%98%E7%9B%AE%E5%BD%95%E9%85%8D%E7%BD%AE/",
"url": "https://nightfury.top/2023/08/13/Sbt%E7%BC%93%E5%AD%98%E7%9B%AE%E5%BD%95%E9%85%8D%E7%BD%AE/",
"title": "Sbt缓存目录配置",
"date_published": "2023-08-13T10:17:24.000Z",
"content_html": "<h1 id=\"Sbt文件迁移\"><a href=\"#Sbt文件迁移\" class=\"headerlink\" title=\"Sbt文件迁移\"></a>Sbt文件迁移</h1><p>Sbt 原来的缓存路径为 <code>C:\\Users\\{Your User Name}\\AppData\\Local</code>,里面包含了 .sbt 和 .ivy2 文件夹,在这里我将其迁移至 <code>D:\\sbt</code> 路径下</p>\n<h1 id=\"IntelliJ-IDEA\"><a href=\"#IntelliJ-IDEA\" class=\"headerlink\" title=\"IntelliJ IDEA\"></a>IntelliJ IDEA</h1><p>打开 IntelliJ IDEA,依次点击 <code>File - Settings - Build, Execution, Deployment - sbt</code>,在 <code>VM parameters</code> 中输入:</p>\n<pre><code class=\"language-shell\">-Dsbt.boot.directionary=D:\\sbt\\.sbt\\boot\n-Dsbt.global.base=D:\\sbt\\.sbt\n-Dsbt.ivy.home=D:\\sbt\\.ivy2\n</code></pre>\n<p><img loading=\"lazy\" data-src=\"/assets/Sbt%E7%BC%93%E5%AD%98%E7%9B%AE%E5%BD%95%E9%85%8D%E7%BD%AE/%E7%9B%AE%E5%BD%95%E9%85%8D%E7%BD%AE.png\" alt=\"sbt缓存目录配置\"></p>\n<p>至此,sbt 的缓存文件将会保存在 D 盘中,但由于当初安装 sbt 时似乎选择在了 C 盘,因此如有需要还可以对 sbt 进行迁移(似乎安装在了 <code>C:\\Users\\{Your User Name}\\AppData\\Local\\Coursier\\cache\\arc\\https\\github.com\\sbt</code> 中),等后面有时间了再处理吧。</p>\n",
"tags": [
"Configuration",
"IntelliJ IDEA",
"Sbt"
]
},
{
"id": "https://nightfury.top/2023/07/27/m2sPipe%E4%B8%8Es2mPipe/",
"url": "https://nightfury.top/2023/07/27/m2sPipe%E4%B8%8Es2mPipe/",
"title": "m2sPipe与s2mPipe",
"date_published": "2023-07-27T07:21:12.000Z",
"content_html": "<h1 id=\"s2mPipe\"><a href=\"#s2mPipe\" class=\"headerlink\" title=\"s2mPipe\"></a>s2mPipe</h1><h2 id=\"关键点\"><a href=\"#关键点\" class=\"headerlink\" title=\"关键点\"></a>关键点</h2><p>self.ready为s2mPipe.ready打一拍,rValid和rData用于寄存输入valid和payload避免气泡。</p>\n<h2 id=\"开敲\"><a href=\"#开敲\" class=\"headerlink\" title=\"开敲\"></a>开敲</h2><p>如果有寄存数据,输出寄存数据;如果没有寄存数据,寄存数据 or 直接输出(视输出ready决定)</p>\n<pre><code class=\"language-scala\">val rData = RegNextWhen(self.payload, self.fire & ~s2mPipe.fire)\nval rValid = RegInit(False) setWhen(self.fire) clearWhen(s2mPipe.fire)\nval rReady = RegInit(True) setWhen(s2mPipe.ready) clearWhen(~s2mPipe.ready | rValid)\n\n\nself.ready := rReady\ns2mPipe.valid := self.valid | rValid\ns2mPipe.payload := Mux(rValid, rData, self.payload)\n</code></pre>\n<hr>\n<p><strong>修正1</strong>:rData只有在rValid拉高时才用到,而rValid只有self.fire时才为高,因此</p>\n<pre><code class=\"language-scala\">val rData = RegNextWhen(self.payload, self.fire)\n</code></pre>\n<hr>\n<p><strong>修正2</strong>:rValid说明是否包含寄存数据,rValid为高时说明无法再接收新寄存数据,这要求self.ready拉低,因为只能寄存-消化-直通 or 寄存-消化-寄存-消化 or 直通,即有寄存数据后只能消化,不能消化的同时寄存(因为rValid在消化的时候拉低了,就算寄存了rValid也为低,这与前面说法相违背,或者说这种情况应该是m2sPipe,因为payload和valid有一拍延时),因此实际上self.ready只在没有寄存数据的时候才能为高,即</p>\n<pre><code class=\"language-scala\">self.ready := ~rValid\n</code></pre>\n<hr>\n<p><strong>修正3</strong>:self.ready依赖于rValid,而rValid依赖self.fire,这形成了环路,存在问题. 实际上,rValid为高时,s2mPipe.valid为高,因此拉低条件s2mPipe.fire可以退化成s2mPipe.ready;而拉高条件self.fire & ~s2mPipe.ready是为了说明有数据寄存且没有消化,则原来的rValid为低,self.ready为高(self.ready := ~rValid),因此拉高条件退化为self.valid & ~s2mPipe.ready,则</p>\n<pre><code class=\"language-scala\">val rValid = RegInit(False) setWhen (self.valid) clearWhen(s2mPipe.ready)\n</code></pre>\n<hr>\n<p><strong>修正4</strong>:rData的寄存条件self.fire较为严格,因为实际输出依赖rValid选择payload,而rValid是在self.valid时拉高,因此寄存条件self.fire = self.valid & self.ready可以退化为self.ready,则</p>\n<pre><code class=\"language-scala\">val rData = RegNextWhen(self.payload, self.ready)\n</code></pre>\n<hr>\n<h2 id=\"完整代码\"><a href=\"#完整代码\" class=\"headerlink\" title=\"完整代码\"></a>完整代码</h2><pre><code class=\"language-scala\">val rData = RegNextWhen(self.payload, self.ready)\nval rValid = RegInit(False) setWhen(self.valid) clearWhen(s2mPipe.ready)\n\nself.ready := ~rValid\ns2mPipe.valid := self.valid | rValid\ns2mPipe.payload := Mux(rValid, rData, self.payload)\n</code></pre>\n<hr>\n<h2 id=\"补充\"><a href=\"#补充\" class=\"headerlink\" title=\"补充\"></a>补充</h2><ol>\n<li>s2mPipe() is ready to accept new data when the internal data register does not contain valid data.</li>\n<li>The ready becomes valid when rData is free, thus when it does not contain valid data already.</li>\n</ol>\n<hr>\n<h1 id=\"m2sPipe\"><a href=\"#m2sPipe\" class=\"headerlink\" title=\"m2sPipe\"></a>m2sPipe</h1><h2 id=\"关键点-1\"><a href=\"#关键点-1\" class=\"headerlink\" title=\"关键点\"></a>关键点</h2><p>rValid和rData寄存输入valid和payload, self.ready处理气泡拼接。</p>\n<hr>\n<h2 id=\"开敲-1\"><a href=\"#开敲-1\" class=\"headerlink\" title=\"开敲\"></a>开敲</h2><pre><code class=\"language-scala\">val rData = RegNextWhen(self.payload, self.ready)\nval rValid = RegNextWhen(self.valid, self.ready) init(False)\n\nself.ready := m2sPipe.ready | ~m2sPipe.valid\t// m2sPipe.isFree\nm2sPipe.valid := rValid\nm2sPipe.payload := rData\n</code></pre>\n",
"tags": [
"Technique",
"FPGA",
"SpinalHDL"
]
},
{
"id": "https://nightfury.top/2023/07/27/Php%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/",
"url": "https://nightfury.top/2023/07/27/Php%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/",
"title": "Php环境配置",
"date_published": "2023-07-27T03:49:11.000Z",
"content_html": "<h1 id=\"实验环境\"><a href=\"#实验环境\" class=\"headerlink\" title=\"实验环境\"></a>实验环境</h1><ul>\n<li>操作系统:Ubuntu 18.04</li>\n<li>Nginx:1.20.1</li>\n<li>PHP:7.2.24</li>\n<li>PHP-fpm:php7.2-fpm</li>\n</ul>\n<h1 id=\"实验步骤\"><a href=\"#实验步骤\" class=\"headerlink\" title=\"实验步骤\"></a>实验步骤</h1><h2 id=\"安装程序包与依赖\"><a href=\"#安装程序包与依赖\" class=\"headerlink\" title=\"安装程序包与依赖\"></a>安装程序包与依赖</h2><pre><code class=\"language-shell\"># 更新软件包列表\napt update\n\n# 安装程序包\napt install nginx\napt install php php-fpm php-xml php-json php-curl\n\n# 安装常用依赖\nsudo apt-get install php-json\nsudo apt-get install php-curl\nsudo apt-get install php-hash\nsudo apt-get install php-openssl\nsudo apt-get install php7.2-cgi\n</code></pre>\n<h2 id=\"编辑fpm配置文件\"><a href=\"#编辑fpm配置文件\" class=\"headerlink\" title=\"编辑fpm配置文件\"></a>编辑fpm配置文件</h2><pre><code class=\"language-shell\">sudo vim /etc/php/7.2/fpm/php.ini\n# 提升安全性\n# 修改参数如下:\n# 778行 ;cgi.fix_fathinfo=1 更改为 cgi.fix_fathinfo=0\n\nsudo vim /etc/php/7.2/fpm/pool.d/www.conf\n# 配置连接数量\n# 修改参数如下\n# 36行 listen = 127.0.0.1:9000 \n# 62行 listen.allowed_clients = 127.0.0.1\n# 113行 pm.max_children = 50\n# 139行 pm.max_requests = 500 \n# 340行 request_terminate_timeout = 0 \n# 344行 rlimit_files = 1024\n# 以上部分,包括但不限于去除前面的";"\n</code></pre>\n<h2 id=\"配置Nginx\"><a href=\"#配置Nginx\" class=\"headerlink\" title=\"配置Nginx\"></a>配置Nginx</h2><pre><code class=\"language-shell\">sudo vim /etc/nginx/conf/nginx.conf # 富强后用/etc/nginx/conf/conf.d/xxx.conf\n# 在index.html前面加入index.php\n# 增加:\nlocation ~ \\.php$ {\n root /home/wwwroot/;\n fastcgi_pass 127.0.0.1:9000;\n fastcgi_split_path_info ^(.+\\.php)(.*)$;\n fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n fastcgi_param PATH_INFO $fastcgi_path_info;\n include fastcgi_params; # include fastcgi.conf;\n}\n</code></pre>\n<p><code>fastcgi_params</code> 和 <code>fastcgi.conf</code> 的区别:</p>\n<ol>\n<li><strong>include fastcgi.conf</strong>: <code>include fastcgi.conf;</code> 通常用于包含 Nginx 默认提供的 FastCGI 配置参数文件。这个文件包含了一些通用的 FastCGI 参数设置,如 fastcgi_param、fastcgi_pass 等。这些参数适用于大多数 FastCGI 应用程序,包括 PHP、Python、Ruby 等。</li>\n<li><strong>include fastcgi_params</strong>: <code>include fastcgi_params;</code> 同样也是包含 FastCGI 配置参数的指令,但它通常比 <code>fastcgi.conf</code> 更加轻量级。<code>fastcgi_params</code> 文件包含了一些常见的 FastCGI 参数设置,同时避免了一些可能会引起安全问题的参数设置,如 <code>SCRIPT_FILENAME</code>、<code>PATH_TRANSLATED</code> 等。这使得 <code>fastcgi_params</code> 更适合用于共享主机环境或需要更严格安全设置的情况。</li>\n</ol>\n<p>总的来说,两者的区别在于:</p>\n<ul>\n<li><code>fastcgi.conf</code> 可能包含更多的参数设置,适用于更多类型的 FastCGI 应用程序,但可能包含一些较为宽松的安全设置。</li>\n<li><code>fastcgi_params</code> 较为轻量,适用于较为安全和精简的配置需求,特别适合共享主机环境。</li>\n</ul>\n<h2 id=\"重启服务\"><a href=\"#重启服务\" class=\"headerlink\" title=\"重启服务\"></a>重启服务</h2><pre><code class=\"language-shell\">sudo service php7.2-fpm restart # systemctl restart php7.2-fpm\nsudo service nginx restart # systemctl restart nginx\n</code></pre>\n<h1 id=\"验收\"><a href=\"#验收\" class=\"headerlink\" title=\"验收\"></a>验收</h1><p>编写任意 php 文件,比如说简单的有 index.php:</p>\n<pre><code class=\"language-html\"><h1>\n <span> Hello, this is test page </span>\n</h1>\n</code></pre>\n<p>或者 php 探针:</p>\n<pre><code class=\"language-php\"><?php \n\tphpinfo(); \n?>\n</code></pre>\n<p>访问该 php 地址,得到正确的返回结果。完结,Move On!</p>\n",
"tags": [
"Configuration",
"Php",
"Nginx",
"Ubuntu"
]
},
{
"id": "https://nightfury.top/2023/07/27/HelloWorld/",
"url": "https://nightfury.top/2023/07/27/HelloWorld/",
"title": "Hello World",
"date_published": "2023-07-27T01:44:02.894Z",
"content_html": "<p>Welcome to <a href=\"https://hexo.io/\">Hexo</a>! This is your very first post. Check <a href=\"https://hexo.io/docs/\">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href=\"https://hexo.io/docs/troubleshooting.html\">troubleshooting</a> or you can ask me on <a href=\"https://github.com/hexojs/hexo/issues\">GitHub</a>.</p>\n<h2 id=\"Quick-Start\"><a href=\"#Quick-Start\" class=\"headerlink\" title=\"Quick Start\"></a>Quick Start</h2><h3 id=\"Create-a-new-post\"><a href=\"#Create-a-new-post\" class=\"headerlink\" title=\"Create a new post\"></a>Create a new post</h3><pre><code class=\"language-bash\">$ hexo new "My New Post"\n</code></pre>\n<p>More info: <a href=\"https://hexo.io/docs/writing.html\">Writing</a></p>\n<h3 id=\"Run-server\"><a href=\"#Run-server\" class=\"headerlink\" title=\"Run server\"></a>Run server</h3><pre><code class=\"language-bash\">$ hexo server\n</code></pre>\n<p>More info: <a href=\"https://hexo.io/docs/server.html\">Server</a></p>\n<h3 id=\"Generate-static-files\"><a href=\"#Generate-static-files\" class=\"headerlink\" title=\"Generate static files\"></a>Generate static files</h3><pre><code class=\"language-bash\">$ hexo generate\n</code></pre>\n<p>More info: <a href=\"https://hexo.io/docs/generating.html\">Generating</a></p>\n<h3 id=\"Deploy-to-remote-sites\"><a href=\"#Deploy-to-remote-sites\" class=\"headerlink\" title=\"Deploy to remote sites\"></a>Deploy to remote sites</h3><pre><code class=\"language-bash\">$ hexo deploy\n</code></pre>\n<p>More info: <a href=\"https://hexo.io/docs/one-command-deployment.html\">Deployment</a></p>\n",
"tags": []
}
]
}