分类 折腾 下的文章

前言

我写博客真可谓三天打鱼,两天晒网。大概两个月没维护,服务器被挖矿病毒感染,2核2g服务器有什么利用价值呢?不知道我的ssh密码怎么泄露了。检查了一下最终决定重装系统,同时加强了安全防护,设置了SSH不允许密码登录,防火墙开启(我一直觉得阿里云安全组的作用和防火墙一样,之前没设置).

重装系统前我备份了typecho数据库和图片。之前typecho安装是手动安装,这次我用docker镜像拉取,主要是想了解docker使用,结果是我这个docker小白拉镜像比手动安装还耗时间。QWEN给我写了docker配置文件,但是docker镜像太难找了,而且阿里的官方docker镜像里没有typecho(阿里云的docker镜像只能用在阿里云的服务器上,镜像地址要自己申请)。 最后在渡渡鸟镜像同步站搜到了typecho,才成功拉取镜像。

导入备份数据库后,打开安装引导网页,安装引导的最后选择保存数据。一切顺利,但安装完成跳转到博客网站时显示server error,我重装了几次都是这样,最后查typecho日志显示问题时插件缺失。因为我之前安装并启用了一个目录插件,数据库里保存了这个插件,导入备份数据后,typecho以为有这个插件,想要运行,但是没有插件,所以报错。让AI写SQL把插件全设为不启用后就可以正常显示了。

我重装了好几次,配置文件config.inc.php也搞了好几次,我的经验是备份的时候可以把配置文件也备份一下,这样导入数据后就不用再重复安装引导了。或者整个typecho目录都做备份,直接导入,配置好数据库。

数据库迁移整个过程比较简单,这次迁移系统我顺便加了图床,用的也是博客里常用的免费方案CloudFare R2和PicGo。

重装系统,设置

重装系统时选择了ubuntu22,不禁感慨时间流逝,大二学计算机系统和操作系统时,ubuntu22还是新系统,据说ubuntu20不稳定,我都会安装ubuntu20,如今ubuntu20已不再维护。

阿里云的服务器ubuntu系统apt初始就已经换了阿里源

python换源
# 清华源
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

# 或阿里云源
pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/
pip config list   
docker 换源
echo ">>> 配置 Docker 镜像加速器..."
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json > /dev/null << EOF
{"registry-mirrors": ["https://docker.1ms.run"]}
EOF
sudo systemctl restart docker 2>/dev/null && echo "✓ Docker 源配置完成" || echo "⚠️ Docker 未安装或未运行"

创建用户

sudo adduser hhh
# 将用户加入 sudo 组(Ubuntu 默认配置)
sudo usermod -aG sudo hhh
# 验证
groups hhh  # 应包含 "sudo"
sudo usermod -aG docker hhh
# 重新加载组权限(当前会话立即生效)
newgrp docker  # 或直接退出重新登录 SSH
# 验证
su - hhh
docker ps  # 

新建用户创建密钥

# 本地终端执行(不要在服务器上!)
ssh-keygen -t ed25519 -C "hh@ali-ecs" -f ~/.ssh/id_ed25519_hh
# 按提示:
# 1. Enter passphrase(可选但推荐,增加安全性)
# 2. 确认 passphrase

docker 安装typecho

拉取并构建镜像

typecho镜像只包括typecho的前后端,还需要加入数据库镜像。nginx我也想加入镜像的,这样就可以一键重装了,但是网络有点麻烦。

services:
  typecho:
    image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/joyqi/typecho:nightly-php8.2-apache  # 镜像地址
    container_name: typecho-server # 容器名
    restart: always
    environment:
      - TYPECHO_SITE_URL=https://strangeloop.fun
      - TZ=Asia/Shanghai
    ports:
      - 8080:80  # 端口映射
    volumes:
      - /var/typecho:/app/usr  #  同步数据
      - /etc/localtime:/etc/localtime:ro  # 同步时间
      - /var/blog_backup/assets:/app/assets  # 图片
    depends_on:
      - db

  db:
    image: postgres:14-alpine
    container_name: typecho-db
    restart: always
    environment:
      - POSTGRES_USER=typecho
      - POSTGRES_PASSWORD=my_password
      - POSTGRES_DB=typecho
    volumes:
      - /var/pg_data:/var/lib/postgresql/data
      - /etc/localtime:/etc/localtime:ro
docker compose up -d

导入数据并进行配置

打开http://47.116.68.4:8080 不能打开,因为8080端口未配置安全组规则

我先导入了备份数据,显示数据库已存在,可能是数据库选错了,重新配置,或者修改配置文件。

docker exec typecho-db pg_restore -U typecho -d typecho --no-owner --no-acl /tmp/backup.dump  # 导入备份数据库
# 下面是我安装后出现server error后删掉数据库重新开始的指令,跳过
docker exec typecho-db psql -U typecho -d typecho -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
docker exec typecho-db psql -U typecho -d typecho -c "\dt"
docker exec typecho-server cat /app/config.inc.php | sudo tee /var/typecho/config.inc.php > /dev/null

初始化逻辑:Typecho 官方镜像在启动时,如果检测到 /app/config.inc.php 不存在,就会触发安装引导。

server error原因是插件缺失,可以把原插件复制过去,也可以直接修改数据库。所以usr文件夹应该全保存下来的,主题也在usr下面。

# 禁用所有插件(正确的格式)
docker exec typecho-db psql -U typecho -d typecho -c "UPDATE typecho_options SET value = 'a:2:{s:9:\"activated\";a:0:{}s:7:\"handles\";a:0:{}}' WHERE name = 'plugins';"

# 验证
docker exec typecho-db psql -U typecho -d typecho -c "SELECT value FROM typecho_options WHERE name = 'plugins';"

之后 ip可以访问博客,但是域名不能访问,并且排版混乱

排版混乱是因为浏览器阻止了 HTTP 的 CSS/JS 资源(混合内容)
申请证书后,你将 Typecho 的站点地址改为 https://,所有资源链接变成 HTTPS,浏览器允许加载 → 排版恢复正常

521 错误表示 Cloudflare 能连到你的服务器,但服务器 80/443 端口没有响应。根本原因:

IP:8080 能访问 → 说明 Typecho 容器正常运行
域名不能访问 → 说明服务器 80 端口没有服务(Cloudflare 默认访问 80/443 端口)
# 1. 安装 Nginx
sudo apt install -y nginx

# 2. 创建配置
sudo tee /etc/nginx/sites-available/strangeloop.fun <<'EOF'
server {
    listen 80;
    server_name strangeloop.fun www.strangeloop.fun;
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
EOF
sudo ln -sf /etc/nginx/sites-available/strangeloop.fun /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default 2>/dev/null

# 3. 重载 Nginx
sudo nginx -t && sudo systemctl reload nginx

# 4. 开放防火墙
sudo ufw allow 80/tcp

# 5. 浏览器访问
http://strangeloop.fun  # 应该正常显示博客!

服务器安全设置

Fail2ban(必备!)
防止 SSH/WordPress/Typecho 登录暴力破解
# 安装
sudo apt update && sudo apt install -y fail2ban

# 配置(创建自定义配置)
sudo tee /etc/fail2ban/jail.local > /dev/null <<'EOF'
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3          # 3次失败即封禁
bantime = 86400       # 封禁24小时
findtime = 600        # 10分钟内累计失败

[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
EOF

# 启动
sudo systemctl enable fail2ban && sudo systemctl restart fail2ban

# 验证
sudo fail2ban-client status sshd
# 输出示例:Currently banned: 2(已封禁2个IP)
# 2. 配置 SSH 密钥登录(本地电脑执行):
#    ssh-keygen -t ed25519
#    ssh-copy-id root@ip

# 3. 禁用密码登录(服务器执行)
echo "PasswordAuthentication no" | sudo tee -a /etc/ssh/sshd_config
sudo systemctl restart sshd
ufw 配置及修改SSH端口

我还修改了ssh端口,一朝被蛇咬...

ssh端口加入新端口后测试新端口可用再删除旧端口,否则后果不堪设想,又要重装了。

ufw启用的时候注意先添加规则,不然后果也不堪设想。

image-20260210154342611

# 1. 允许当前 SSH 端口(22)
sudo ufw allow 22/tcp comment 'SSH access'

# 2. 允许 Web 服务
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'

# 3. (可选)仅允许您的 IP 访问 SSH(更安全)
# sudo ufw delete allow 22/tcp          # 先删除全局规则
# sudo ufw allow from YOUR_IP to any port 22 proto tcp comment 'SSH from home'

# 4. 查看规则预览(确认无误再启用)
sudo ufw status verbose
# 1. 添加您的 IP 规则(假设 IP=123.123.123.123)
sudo ufw allow from 123.123.123.123 to any port 22 proto tcp

# 2. 删除全局 22 规则(编号1)
sudo ufw delete 1

# 3. 验证结果
$ sudo ufw status numbered
github ssh密钥设置

ssh连接下载代码快很多。

ssh-keygen -t ed25519 -C "szh-nine@outlook.com" 
# 把公钥添加到github
ssh -T git@github.com # 连接
git config --global user.email "szh-nine@outlook.com"
  git config --global user.name "Zihui Si"
git remote add origin git@github.com:amorfatix/blog.git
git push -f -u origin master # 强制推送(覆盖远程)

Cloudfare R2 + PicGo 替换图片上传

本地博客自动发布之钻木取火我这篇博客记录我之前上传图片和博客的方法,非常原始。当时我觉得这样很好。

但现在这样迁移到docker环境那些图片更麻烦,为了配合之前博客里的路径,我直接把图片文件夹共享到了docker里的/app/assets,这一点在前面的配置文件里可以看到。

我选择拥抱图床,有了图床博客发布的问题的问题也解决了。

在诸多图床服务里我选择了Cloudfare R2 + PicGo,R2免费提供10G存储空间,每月访问1M次数,用于我的博客是绰绰有余。PicGo是一个便捷的图片上传工具,它对Typora的适配也比较好。

R2需要验证信用卡信息或者paypal,普通借记卡也可以添加到paypal作为验证,我用paypal过了信息验证。

后续教程参考:

跟着教程和AI走,还是有一些需要自己摸索的地方:

  • 自定义输出URL

    我在cloudfare R2里设置了自定义的域名 img.strangeloop.fun我按照信息配置好之后上传图片返回的路径却是

    <img src="https://blog-image.366cd17fc303b00c17d2ee37b9cdf8fa.r2.cloudflarestorage.com/20260213224039332.png"/>

    需要自定义输出url模板如下:

    image-20260213232652368

  • 配置安全规则防盗链

    • 如果 Referer 不包含 strangeloop.fun
    • 并且 host 是 img.strangeloop.fun
    • 并且 Referer 不为空
    • → 阻止
    (not http.referer contains "strangeloop.fun" and http.host eq "img.strangeloop.fun" and http.referer ne "")
  • PicGo使用

    Typora里添加PicGo的路径后设置后可以自动上传,但是不是所有写作场景都需要自动上传图片——草稿、临时笔记、本地文档没必要占用 R2 空间。

    PicGo 独立使用(最通用,推荐⭐⭐⭐⭐⭐)

    核心思想:不依赖编辑器集成,用 快捷键/拖拽 主动上传,完全掌控何时上传。

    1. 设置 PicGo 快捷键

      • PicGo → 设置 → 快捷键上传
      • 推荐:Ctrl+Shift+U(Windows)/ Cmd+Shift+U(Mac)

        20260214150739844

    2. 工作流
    1. 截图 → 自动保存到剪贴板
    2. 按快捷键 Ctrl+Shift+U → PicGo 自动上传
    3. 链接自动复制到剪贴板
    4. 在任意编辑器(Typora/VS Code/Obsidian/Notion)中 Ctrl+V 粘贴链接

    使用体验:截图后使用快捷键上传很方便,上传时间大概几秒,略慢,不要重复上传。

  • 我现在改用了图床,之前的照片也要上传上去,上传之后博客里的路径替换

    旧路径: https://strangeloop.fun/assets/image-20250706145859046.png
    新路径: https://img.strangeloop.fun/image-20250706145859046.png
    ↓
    https://strangeloop.fun/assets/  →  https://img.strangeloop.fun/
    • 备份数据库
    • 路径批量替换
    • 刷新网站,照片能够显示,完成

      docker exec typecho-db pg_dump -U typecho -d typecho > backup_$(date +%Y%m%d).sql
      # 连接数据库
      # 进入容器并启动 psql
      docker exec -it typecho-db psql -U typecho -d typecho
      
      # 然后在 psql 中执行:
      UPDATE typecho_contents 
      SET text = REPLACE(
          text, 
          'https://strangeloop.fun/assets/',
          'https://img.strangeloop.fun/'
      )
      WHERE type IN ('post', 'page');
      
      -- 验证
      SELECT COUNT(*) as updated_count 
      FROM typecho_contents 
      WHERE text LIKE '%https://img.strangeloop.fun/%'
        AND type IN ('post', 'page');
      
      -- 检查是否还有旧路径
      SELECT cid, title 
      FROM typecho_contents 
      WHERE text LIKE '%https://strangeloop.fun/assets/%';
      
      -- 退出 psql
      \q

引言

typecho网站后台里可以撰写并发布markdown格式的文章,但是我大部分笔记是在本地typora里写的。直接复制到后台里发布是一种可行的方式,但是markdown里的图片比较难处理,要手动上传为附件,还要替换链接。我检索了typecho上传文章的方式,其中搞定 Obsidian 笔记一键发布到 Typecho 博客的方案是MetaWeblog API(基于 XML-RPC 的标准化接口规范,定义了文章、分类、标签等核心资源的 CRUD(增删改查)方法,使外部工具能以统一方式管理多平台博客)。我没有选择MetaWeblog API,因为在我实现我的思路模拟登录+post并写这篇博客之前还没有详细了解它,稍微了解它之后我认为MetaWeblog API应该比我的方案容易实现。相比之下,我的方案像是钻木取火。

目前我的方案成效差强人意,有空再尝试MetaWeblog API

我的方案主要分为两部分,首先是对文章里图片的处理,一般是托管到图床上,但是找价格合适的图床有点浪费时间,我决定直接将提取文章里的图片路径,然后上传到服务器上,上传文章前替换图片路径,上传图片时,我顺便把markdown原文也上传了,作为备份,同步到github仓库。

第二部分是如何登录并发布,我发现原来在浏览器里登录的链接并不是实际post的目标链接,这一点困住我好久,以及post以后链接的重定向,我在查看服务器nginx日志时才意识到链接重定向,需要在post时允许重定向。

上传图片

测试路径

首先用scp测试一下 ssh免密登录以及读写权限有没有配置好

scp -i D:\.ssh\id_rsa E:\2025\暑期学校报名\assets\image-20250630175256175.png hh@strangeloop.fun:/var/www/typecho/assets/test3.png  # hh用户对该目录没有权限
ssh -i D:\.ssh\id_rsa hh@strangeloop.fun

原本打算直接将图片上传到网站根目录,但是我用来上传图片的用户没有读写该位置的权限。安全起见,我不想直接用root用户上传或者修改目录权限,我选择将图片上传到当前用户(hh)有权限的目录,然后再通过软链接(symbolic link)暴露到网页服务目录中。如下:

mkdir /home/hh/blog && cd blog
mkdir assets
ln -s /home/hh/blog/assets /var/www/typecho/assets  # 软链接
scp -i D:\.ssh\id_rsa E:\2025\暑期学校报名\assets\image-20250630175256175.png hh@strangeloop.fun:/home/hh/blog/assets/test.png
#  blog用来备份博客
# strangeloop.fun/assets/test.png

image-20250706145859046

链接成功,访问网址,能访问图片,配置成功。正常情况下链接应该是绿色与蓝色,如果链接显示为红色,大概率是目标地址不存在,链接失败。

图片提取

这里直接用正则表达式匹配![图片描述](路径),typora里我设置图片存储路径为./assets/xxx.jpg,提取图片路径的正则表达式:

pattern =r'!\[.*?\]\(\.\/(assets/.*?)\)|<img\s+[^>]*src="\.\/(assets[^"]+)"'  
# 匹配 ![图片描述](https://img.strangeloop.fun/图片路径) 的正则表达式  输出 assets/2023-10-01-1.png 这种格式

AI写的正则表达式总是离需求差一点点,所以我不得不回忆一下正则表达式。🐿️😑

最初我只提取了markdown语法的图片格式,但是后来我发现typora里的图片插入有两种语法:md和html

![alt](src)
<img src="src" alt="alt" style="zoom:xx%; float:left;"/>

html图片语法支持更多的图片操作,如缩放、调整位置。所以在代码里加入了对html语法图片的提取。

  • re.findall(pattern, string, flags=0):在字符串中查找所有与正则表达式匹配的非重叠子串,返回列表
  • re.sub(pattern, repl, string, count=0, flags=0):查找匹配正则表达式的子串,用指定内容替换

上传图片与文件

paramiko库,创建ssh连接

ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(REMOTE_HOST, username=REMOTE_USER, key_filename=os.path.expanduser(SSH_KEY_PATH))
    sftp = ssh.open_sftp()

这一部分比较令人头疼的是文件路径处理,程序在windows上运行,上传图片到linux服务器上,但也就是调不同的库

# 文件路径
file_name = Path(local_md).name
remote_md_path = posixpath.join(REMOTE_BLOG_PATH, file_name)

图片链接替换

这里主要也是正则表达式,替换图片路径之后的md用于后续的上传

    content = re.sub(html_pattern, replace_html, md_content)
    content = re.sub(markdown_pattern, replace_markdown, content)

模拟登录

这里和后面的发布差不多,get登录时浏览器显示的链接,提取实际的登录链接,然后post

发布文章

image-20250705214442228

提取该链接

def extract_post_action_url(html):
    soup = BeautifulSoup(html, "html.parser")
    form = soup.find("form", {"name": "write_post"})
    if not form:
        raise Exception("❌ 未找到登录表单")
    return form.get("action")

测试

python .\publish2typecho.py --md E:\2025\博客\typecho设置.md --tags xx,xx,xx --category 折腾

image-20250707000702247

发布成功

思考

typecho里的markdown不支持文章目录,我尝试了加入tocbot ,效果一般而且难看,我不太了解前端。然后我发现两点:

  • typora能带格式导出html,并且支持目录和侧边栏
  • typora导出里可以设置运行自定义命令

所以显然我可以设置自定义命令为运行typecho自动发布的程序,这样导出的同时可以直接发布,甚至不用我手动执行脚本。实现这一点我需要做的是:读取html,上传图片,更换图片路径,发布(通过MetaWeblog API 实现)。等我有空再做。🐿️🤖

备案号 网站运行天数

修改文件 ./usr/themes/defult/footer.php

获取的时间是unix时间戳,从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数(不考虑闰秒)

运行天数 = round((当前时间 - 网站创建时间)/86400)

网站创建时间:2025-06-27 00:15 = 1750954500

<footer id="footer" role="contentinfo " style="line-height: 0.4;">  
    &copy; <?php echo date('Y'); ?> <a href="<?php $this->options->siteUrl(); ?>"><?php $this->options->title(); ?></a>.
    <?php _e('由 <a href="https://typecho.org">Typecho</a> 强力驱动'); ?>.
<div class="stats-container" style="margin: 5px 0;">
            <p>本站已运行 <?php echo round((time() - 1750954500) / 86400); ?> 天</p>
            <?php $this->widget('Widget_Stat')->to($stat); ?>
        </div>
 <p style="margin-top: 5px 0; font-size: 14px; color: #666;">
    备案号:<a href="http://www.miitbeian.gov.cn" target="_blank">豫ICP备2023035002号-1</a>
   </p>
</footer><!-- end #footer -->
  • style="line-height: 0.4;用于调整行间距,默认间距太大,不美观

最终效果如下:

image-20250706162834216

调整字号

./usr/themes/default/style.css

默认字号略小,修改下面的font-size

.post-content, .comment-content {
  line-height: 1.5;
  word-wrap: break-word;
  font-size:17px;
}

字体

./usr/themes/default/style.css

  • 下载喜欢的字体仓耳今楷,下载位置自定义,我这里的相对路径fonts/canger.woff2和style.css位于同一目录下,绝对路径就随便写
  • 转换为woff2格式,修改./usr/themes/default/style.css,把新字体的定义加到最前面

    @font-face {
        font-family: 'canger';
        src: url('fonts/canger.woff2') format('woff2'),
               url('fonts/canger.woff') format('woff'),
           url('fonts/canger.ttf')format('ttf'),
           url('fonts/canger.otf')format('otf');
        font-weight: normal;
        font-style: normal;
    }
  • font-family 里有很多字体,越在前面优先级越高,把canger放在最前面

    body {
      background-color: #FFF;
      color: #444;
      /*font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;*/
      font-family: "canger", "Droid Serif", Georgia, "Times New Roman", "PingFang SC", "Hiragino Sans GB", "Source Han Sans CN", "WenQuanYi Micro Hei","Microsoft Yahei", serif;
      font-size: 100%;  # 缩放比例
    }
    h1, h2, h3, h4, h5, h6 {
      font-family: "canger", "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "WenQuanYi Micro Hei","Microsoft Yahei", sans-serif;
    }

保存以后刷新网页,字体包比较大,一般要等一会才加载出来。这一点应该可以改进。
折腾一番后,发现还是原本的宋体更易读,于是我又回复为原来的字体。

今天醒来以后立刻看微生活,生理学68分,没挂科,这意味这我的本科学分已经修满了(忽略形策)。
最近在上小学期,比较清闲,3天做完了所有的实验,但是实验报告要用latex写(感觉报告比实验难写)。原本实验不用3天,有1天被hyper-tuner部署浪费,具体来说是服务器8086端口没配置安全组。这让我想起大二折腾的博客和vps。这就是我此刻重新创建博客的动机之一。服务器是大二在阿里云99元/年活动里买的2核2G,现在这个活动还在继续,或许我会考虑续费。域名也是那个时候买的,strangeloop.fun,strangeloop来源于侯世达《我是个怪圈》,那时我刚好在读这本书,还有《GEB》(《哥德尔、艾舍尔、巴赫:集异壁之大成》)。《怪圈》在探讨“我”是什么,我忘记结论是什么了。“忘记”大概也是动机之一。
恰好我又看到了一个令我心动的博客系统typecho,简洁且好看。我在linux.do闲逛,看到这个帖子独立博客写什么比较有意思,评论里提到了一个分享生活比较活跃的博客。这个博客的页面甚合我心,是的,就是typecho。
这次我的行动力很强,周三晚上产生想法,第二天就实践了。我发现大二的我已经折腾的差不多了,我记得那时候域名备案还没完成,今天发现域名竟然已经备案了,很神奇。总之,阻碍又减少了。周四完成了基本的配置,周五配置了证书域名。过程中出现了几个问题,最后都解决了。我觉得我的动手能力相比大二有些长进,当然还有chatGPT,大部分问题是它解决的。
建站时参考了2个教程:

  • Ubuntu typecho 建站教程 我的系统也是ubuntu20,所有大部分参考了这篇
  • typecho 手动本地部署教程 这篇比较详细,包括配置ssl证书
    当然建站过程中出现了一个令人熟悉又陌生的问题,配置ssl证书以后,443端口也会用到,但是443端口没加到安全组里,令人无语的问题

到此为止,我的博客🐿️就搭建完成啦。