Cloudflare Tunnel Self WebHosting

由於不習慣用 Hugo 管理內容, 且文章漸漸多了起來, 我在家中的樹莓派上跑起了一個 WordPress. 一切都進行的很順利, 直到我發現發佈這個網站並不是我想像中的那麼容易.

這篇文章是一篇踩坑文, 就是說我會把我在設置中遇到的一切困難完整的紀錄在文章中, 因此你不應當將其當作教程來看. 如果需要教程的話, 我推薦 官方文檔 , 或者 土豆不好吃 的教程.

前言

失敗的嘗試

首先, 我的樹莓派跑在內網, 我需要在路由器上配置一個端口轉發. 借助 Openwrt 的圖像介面, 我輕鬆的完成了轉發.

其次, 我還需要一個公網 IP. 撥打了電信的電話, 我成功的要到了公網 IP.

因為 “維穩” 需求, 電信是封殺 80, 443, 等常見的端口的. 因此我將 443 端口轉發到了 2053 端口上. ( HTTPS 證書是 Cloudflare 的原站證書 ) 2053 端口確定可以訪問.

在然後, 每次撥號的時候 IP 都會改變, 我需要配置一個 DDNS. 為了解決這個問題, 我找到了一個 Cloudflare DDNS 腳本跑在 Openwrt 裡來動態更新我的 IP.

一個類似 ip.justin.education:2053 的地址為免有些太過不友好, 於是我使用 CF-Workers 反向代理網站到 proxy.justin-zhang.workers , 再將其 CNAME 到 justin.education.

如果是靜態頁面這個時候已經可以訪問了, 但是 WordPress 並不支持多個域名, 一直在執著的 301 重定向. 我不能配置其地址為 justin.education, 因為那樣我就沒辦法登錄後台: CF 反代不支持保存 Cookies.

我於是在 Openwrt 上用 Docker 跑了一個 Nginx 來反代網站, 放棄了簡單的端口轉發的方案. 這次成功了, 不過我遇到了一些圖片和CSS不能加載的問題. 安裝插件之後我配置了替換 Header 的規則, 總算是可以看了.

但是由於 CF Workers 的緣故, 評論還是不工作. 而且攻擊者只要知道了我的端口是 2053, 他可以全網搜索 ip.justin.education:2053 的證書來找到我的真是 ip; 這顯然太冒險.

這個方案失敗之後, 還剩下的就是使用 frp 內網穿透的方案. 然而我並不想用我的翻牆服務器來代理網站, 我經常換 ip, 而且不安全, 而且效率太低.

看起來我已經被逼到絕路了.

閱讀全文 Cloudflare Tunnel Self WebHosting

樹莓派 4B 安裝 Arm64 PiOS

安裝系統

  1. 在 Ubuntu 中安裝 Rpi-imager 來準備鏡像
sudo apt update -y && sudo apt upgrade -y
sudo snap install rpi-imager

2. 從 清華大學鏡像 下載 Arm64 的 PiOS_Lite.

3. 使用 Rpi-Imager 刷入鏡像. 使用 Ubuntu 自帶的磁碟工具擴展 rootfs 容量到最大. (也可以不要, 最新版本的系統第一次開機會自動 Resize Partitions 將 / 分區擴到最大)

4. 在 boot 目錄下新建空白的 ssh 文件

cd ~/media/boot/
touch ssh
  1. 彈出 SD 卡, 插入樹莓派並通電.

基礎配置

0. 連線到樹莓派.

ssh [email protected] #raspberry
  1. 修改登錄方式
# 設置密碼
sudo passwd pi
sudo passwd root
​
# 配置 SSH 登錄
su
cd ~/
mkdir .ssh
chmod 700 .ssh
vi /root/.ssh/authorized_keys # 複製你的公鑰進去
chmod 644 /root/.ssh/authorized_keys
vi /etc/ssh/sshd_config # 配置ssh登錄

2. 在 sshd_config 中修改以下幾句.

LoginGraceTime 10m                 # afk達10min自動登出
PermitRootLogin prohibit-password  # 只允許root用ssh登錄
MaxAuthTries 6                     # 最多允許三次錯誤登錄嘗試
MaxSessions 10                     # 最多允許三個client同時登錄
​
PubkeyAuthentication yes           # 開啓公鑰登錄
​
AuthorizedKeysFile      .ssh/authorized_keys #開啓公鑰
​
PasswordAuthentication no          # 不允許任何用戶使用密碼登錄
PermitEmptyPasswords no            # 不允許任何空密碼用戶登錄

3. 繼續執行一下語句, 確定 status 沒有報錯之後登出.

systemctl restart sshd.service
systemctl status sshd.service
exit

4. 重新以 root 身份登錄.

5. 更新/設置系統

apt update && apt upgrade -y
​
# 更新系統 (不推荐)
#rpi-update # 更新 Kernel
#reboot
#rpi-eeprom-update # 更新 Bootloader
#raspi-config # 6 -> A7 -> E1
​
# 設置系統
raspi-config
​
# 重新登錄
exit

更新: 不推荐执行 rpi-update 更新系统, 因为这行命令实际上是拉取了最新的不稳定的 Linux 内核. 如果已经执行了这个命令的同学, 可以考虑执行以下命令回滚内核:

sudo apt install --reinstall libraspberrypi0 libraspberrypi-{bin,dev,doc} raspberrypi-bootloader raspberrypi-kernel

具体的内容参考这篇官方文档: 点我跳转树莓派官网

設置系統的部分自己選擇, 畢竟選項和提示都寫的很清楚.

我是比較喜歡開啟 Overlay Filesystem + ReadOnly Boot Partition 的, 前者就是一個開機一鍵還原, 後者是防止突然斷電破壞 boot 分區. 我覺得這兩個都比較實用, 建議全部設置完之後開啟.

至於語言 (Locale) 設定, 我喜歡把所有 zh 開頭的都勾上, 然後選用 zh_CN.UTF8, 可以保證亂碼不會出現.

至於測試的話, 只要 mkdir 測試, 然後 ls 一看看一看中文是否正常顯示就可以了.

軟件安裝

  1. 基本設置於基礎軟件安裝
# 安裝基本軟件
apt install vim git make wget -y

# 設置 UFW
apt install ufw -y
ufw allow from 192.168.10.1/24
ufw default deny
ufw enable
ufw status

# 設置 NTP 服務
apt install ntpdate -y
echo '@reboot ntpdate 192.168.10.1' >> /etc/crontab

# 設置 開機掛載硬盤
mkdir /mnt/data
mount /dev/sda1 /mnt/data
blkid # 複製顯示的 uuid 和 type
echo 'UUID=2e2e58b1-8fe8-4f5c-9478-f5eef65c5aa5 /mnt/data ext4 defaults 1 2' >> /etc/fstab
mount -a # 確定沒有報錯!

# 開機掛載網絡硬盤
sudo apt install -y curlftpfs
echo '@reboot curlftpfs -o rw,allow_other ftp://username:[email protected]/ /media/ftpdrive' >> /etc/crontab

# 安裝 ZSH & OH MY ZSH
apt install zsh -y
sh -c "$(wget https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O -)"

# 重啟
reboot

簡單的解釋一下為什麼我上面是這樣配置:

  • 我因為樹莓派完全運行在家庭網絡裡面, 所有流量都要經過轉發, 所以防火牆只允許家裡設備訪問最穩妥.
  • 因為路由器的系統是 Openwrt, 開著 NTP 服務, 所以可以直接向它更新時間.
  • 插上硬盤之後, 最新的系統應該會自動創建一個掛載點 /media/pi/... , 我不想要它, 所以已經把它 unmount 掉了.

2. 安裝 Docker

# 安裝 Docker
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
docker -v
systemctl enable docker.service
systemctl enable containerd.service

# 將 Docker 數據卷掛到外接硬盤
systemctl stop docker
systemctl stop containerd

# 第一次裝系統使用這個方法
rsync -av /var/lib/docker /mnt/data/
rsync -av /var/lib/containerd /mnt/data/
rm -r /var/lib/docker /var/lib/containerd

# 如果重裝過系統想要恢復數據
# mv /var/lib/docker /var/lib/docker.bak
# mv /var/lib/containerd /var/lib/containerd.bak

ln -s /mnt/data/docker /var/lib/docker
ln -s /mnt/data/containerd /var/lib/containerd

systemctl start docker
systemctl start containerd

3. 安裝 Docker-compose ( 使用 pip3 因為 Github 上下載不到針對 Arm 的二進制包 )

apt install python3-venv python3-pip
apt install libffi-dev # 構建環境, 完成後可以卸載掉
pip3 install docker-compose
docker-compose -v

# 刪除構建緩存
rm -r ~/.cache/pip

4. 最後可以啟用 Overlay Filesystem + ReadOnly Boot Partition 了. 之後要是想裝東西可以直接使用 Docker, 改動會保存在外接硬碟中, 就算重裝系統也可以輕鬆恢復數據, 是目前我找到的最好的方案.

raspi-config

5月26日附: 在插拔 HDMI 线的时候不小心把电源也带出来了,赶紧插上,开机没发现任何问题,一分钟后博客恢复访问,lost+found下没添加任何文件。Overlay filesystem 确实是,高!

為官方 Nginx 鏡像安裝 Extras 插件

要用到 Nginx 的額外功能, 我需要安裝插件合集 nginx-extras . 但是官方 Nginx 鏡像並不提供插件功能, 而我也不想就為了這麽一個小功能就去用別人自己 Build 的鏡像, 鬼知道裡面有什麼東西.

我了解到 官方 Nginx, 如果沒有特殊指定的話, 是基於 Debian 的. 那麼在 Debian 裡面如果我想要安裝這個插件, 我只需要執行以下命令, 就可以用了

apt-get update && apt-get install -y nginx-extras

經過簡單的搜索, 我發現 Docker 提供 Dockerfile 的方法來構建自己的鏡像. 就是一個小腳本, 有點像 Git Actions 那樣的小腳本, 不過比那個還簡單.

Dockerfile Cheatsheet, 截取自 devhints.io

知道了 Dockerfile 怎麼使用之後, 就可以上手了

FROM nginx:latest
RUN apt-get update
RUN apt-get install -y nginx-extras
docker build -t nginx_extras .
docker image ls

因為我用了 Portainer, 我可以直接替換鏡像為我自己構建的 nginx_extras .

你也可以手動 rm 掉舊的容器然後用新的鏡像創建容器

到此為止, Nginx 已經可以用上這些插件了.

Docker 默認網段巨坑

簡單的紀錄一下我瞎搞Docker的時候碰到的一個坑:
我現在要配置一個Nginx, 跑在路由器上, 地址是 192.168.10.1 ;
我要用 Nginx 反向代理一個網站, 跑在這個局域網裡面, 地址是 192.168.10.10
這其實根本就超簡單, 正常情況下沒有 Docker, 我們就直接編輯 /etc/nginx/conf.d/default.conf, 然後在裡面扔以下配置, 就直接 Ok (緩存啥的都有照顧到, 我就拿這個作為模板)

server {
    listen 80;
    listen 443 ssl http2;
    server_name 127.0.0.1;

    ssl_certificate /etc/ssl/fullchain.pem;
    ssl_certificate_key /etc/ssl/private.key;
    ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    add_header Strict-Transport-Security "max-age=31536000";
    error_page 497 https://$host$request_uri;


    location / {
        proxy_pass https://justin.education;
        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 REMOTE-HOST $remote_addr;

        add_header X-Cache $upstream_cache_status;

        #Set Nginx Cache

        proxy_set_header Accept-Encoding "";
        sub_filter "http://192.168.10.10" "https://192.168.10.1";
        sub_filter "https://192.168.10.10" "https://192.168.10.1";
        sub_filter_once off;


        proxy_ignore_headers Set-Cookie Cache-Control expires;
        proxy_cache_key $host$uri$is_args$args;
        proxy_cache_valid 200 304 301 302 10m;
    }
}

然後就一直提示我 502 Bad Gateway, 我就 …

光看截圖你就知道我該有多 Mad

愣是浪費我整整一個小時時間來檢查哪裡有問題, 所以說思維定勢真的不可取, 你說他每次用都好好的, 怎麼一到 Docker 上面就出問題?

不過萬幸的是我想起來起看了一眼日誌 (日誌一定得看啊) , 然後發現一直提示訪問 172.17.0.1 報錯…

於是我去谷歌上面搜了一下 鏈接 , 發現 172.17.0.1/16 是 Docker 自己的一個網段, 是通過 NAT 橋接到 Host 網絡的.

這就… 怪不得. 都不在一個網段裡面, 訪問得到才有鬼

知道就好辦, 直接在 Portainer 裡面從 Bridge 改為 Host 就解決了問題.

Docker 日誌塞滿服務器修復

早晨我例行的摸了一下鏈接到樹莓派的硬盤,結果發現它不轉了。Nani?
於是我立即嘗試登錄寶塔面板,結果發現卡在登錄頁面。
使用SSH重啓之後根本重連不上,網關提示根本沒給它分配IP,就是說它現在不能聯網。
我把樹莓派強制斷電後插電,可以連上了,但是提示的消息卻讓我比較震驚…

空間佔用 100% ?

於是我決心把他弄懂。

問題初步排查

首先肯定是要確定磁盤佔用是這樣…

$ df -h

Filesystem                                     Size  Used Avail Use% Mounted on
tmpfs                                          782M   60M  723M   8% /run
/dev/mmcblk0p2                                 117G  117G     0 100% /
tmpfs                                          3.9G     0  3.9G   0% /dev/shm
tmpfs                                          5.0M     0  5.0M   0% /run/lock
tmpfs                                          4.0M     0  4.0M   0% /sys/fs/cgroup
/dev/mmcblk0p1                                 253M  152M  101M  60% /boot/firmware
/dev/sda1                                      916G   95G  776G  11% /media/backup
tmpfs                                          782M  4.0K  782M   1% /run/user/0

是這樣沒錯,然後要定位到主目錄,看看到底那個文件夾出了問題。

$ cd /
$ du -h -x --max-depth=1 

74M    ./home
2.9G    ./usr
8.0K    ./.disk
24K    ./snap
4.0K    ./mnt
4.0K    ./srv
114M    ./root
72K    ./tmp
117M    ./boot
16K    ./opt
8.0K    ./patch
6.1M    ./etc
4.0K    ./media
4.1G    ./www
110G    ./var       <- 問題根源
16K    ./lost+found
117G    .

經過一番定位之後,現在可以發現問題的根源在這個文件夾裏:

/var/lib/docker/containers/6c8a****13f4/

進一步定位那個文件出了問題:

$ ll


total 112907424
drwx------ 4 root root         4096 Jan  7 04:41 ./
drwx------ 4 root root         4096 Feb  3 21:06 ../
-rw-r----- 1 root root 115617058816 Feb 18 13:05 6c8a****13f4-json.log
drwx------ 2 root root         4096 Feb  3 16:33 checkpoints/
-rw------- 1 root root         3958 Jan  7 04:41 config.v2.json
-rw-r--r-- 1 root root         1513 Jan  7 04:41 hostconfig.json
-rw-r--r-- 1 root root           13 Feb  5 13:29 hostname
-rw-r--r-- 1 root root          174 Feb  5 13:29 hosts
drwx------ 3 root root         4096 Feb  3 16:33 mounts/
-rw-r--r-- 1 root root          612 Feb  5 13:29 resolv.conf
-rw-r--r-- 1 root root           71 Feb  5 13:29 resolv.conf.hash

恐怖的發現…這個日誌文件竟然佔了這麼多的大小…

修復問題

看了一下日誌,整個日誌基本全是 P2P文件交換的記錄。
離譜的是這個日誌居然是以毫秒記錄的,有的地方甚至一毫秒幾十行…這個樹莓派是怎麼支撐過來的?
因爲沒有值得注意的東西,我就直接 rm 掉了。瞬間執行,滿足感還是很高的, WinServer不知道刪到猴年馬月去。
(Ext4文件系統刪除文件時並沒有對數據進行操作,我的理解中是將記錄文件信息的索引釋放掉了,在系統的想法裏這片空間又變回可寫的了)

$ df -h

Filesystem                                     Size  Used Avail Use% Mounted on
tmpfs                                          782M   79M  703M  11% /run
/dev/mmcblk0p2                                 117G  9.3G  103G   9% /
tmpfs                                          3.9G     0  3.9G   0% /dev/shm
tmpfs                                          5.0M     0  5.0M   0% /run/lock
tmpfs                                          4.0M     0  4.0M   0% /sys/fs/cgroup
/dev/mmcblk0p1                                 253M  152M  101M  60% /boot/firmware
/dev/sda1                                      916G   95G  776G  11% /media/backup
tmpfs                                          782M  4.0K  782M   1% /run/user/0

很好,空間佔用回到正常了。

防止問題重複

既然知道了是日誌文件的問題,現在就要搞清楚到底那個容器出了問題。

$ docker ps -a

CONTAINER ID        IMAGE
84e1a7934eb6        linuxserver/syncthing:latest
6c8a506e5aca        linuxserver/resilio-sync:latest

現在就知道哪裡出現問題了。可以看到,resilio-sync 的id對應了那個日誌文件的開頭。

我更改了resilio sync中的設置,分別更改爲

log_size = 0
log_ttl = 0
disk_low_priority = true

希望這樣可以有效果,我會在幾天後再來查看。

实测这样子并没有效果,看起來是Docker鏡像製作時留下來的問題, 直接在容器裡更改配置不起作用.

那這不簡單.

docker pause ResilioSync
rm 6c8a****13f4-json.log
ln -s /dev/null 6c8a****13f4-json.log
$ ll

總計 36K
drwx------ 2 root root 4.0K  1月  1  2038 checkpoints
-rw------- 1 root root 4.9K  5月  5 10:25 config.v2.json
lrwxrwxrwx 1 root root    9  5月  5 10:26 6c8a****13f4-json.log -> /dev/null
-rw-r--r-- 1 root root 2.2K  5月  5 10:25 hostconfig.json
-rw-r--r-- 1 root root   13  5月  4 22:14 hostname
-rw-r--r-- 1 root root  174  5月  4 22:14 hosts
drwx-----x 2 root root 4.0K  1月  1  2038 mounts
-rw-r--r-- 1 root root   61  5月  4 22:14 resolv.conf
-rw-r--r-- 1 root root   71  5月  4 22:14 resolv.conf.hash
docker unpause ResilioSync

问题到此解决

沒解決.

這樣做之後確實一時爽, 但是過了一天我回來看, 發現容器停止運行, 原因是

/mnt/data/docker/containers/6c8a****13f4-json.log/dev/null no file or directory

所以問題到底怎麼解決呢?

經過一番搜索(現在才想起來有谷歌這回事)之後, 我得到了結果, 問題完美解決.

原來是可以通過配置 docker-compose 來限制logsize的, 比如說我要限制 nginx 的 logsize 為5g, 我可以

nginx: 
  image: 
  restart: always 
  logging: 
    driver: “json-file” 
    options: 
      max-size: “5g” 

那麼我是用portainer管理docker的, 我可以

問題到此真正完美解決.

MacArm 用 Docker 安裝 WebAssembly 環境

需求

我正在配置 Qt wasm環境,
但是 emsdk 不支持 apple m1 issue 769 使用 Homebrew 安裝的 emsdk 一直報錯,
沒找到源碼編譯的 Guide 只好使用 Docker.

環境
MacOS Bigsur M1, nodejs

安裝 Docker Arm

官網指南: 點擊前往

根據官網的描述, Docker 是使用了 QEMU 來虛擬出 AMD/x86 的環境, 反正能正常工作.

首先需要安裝 Rosetta2, 應該都已經有了

 softwareupdate --install-rosetta

然後點擊下載 Docker Desktop RC3, 簡單的安裝後啟動, 會要求獲取 root 權限.

點擊 settings - Docker Engine, 在頁面中間的框框裡面加入這麼一行, 配置科大鏡像:

  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn/"
  ],
Docker 鏡像源的配置

至此, Docker 配置完畢了.

安裝 emsdk

emscripten/emsdk : Docker Hub

以上是兩個所需鏡像, 第二個是可選項. 如果你不是配合 qt 使用的話, 只選第一個就可以了.

但是如果你要配合 qt 使用, 兩個都得裝, 原因會在下文提及.

首先先 拉取 emsdk 的鏡像, 根據 Qt官網 的描述, 我需要的版本是 1.39.8 , 所以我的命令是:

docker pull emscripten/emsdk:1.39.8

沒有特殊要求的話, 後面的 :1.39.8 可以改成 :latest, 或者根本不加, 直接拉取最新版本.

測試一下鏡像:

cd /tmp

# create helloworld.cpp
cat << EOF > helloworld.cpp
#include <iostream>
int main() {
  std::cout << "Hello World!" << std::endl;
  return 0;
}
EOF

# compile with docker image
docker run \
  --rm \
  -v $(pwd):$(pwd) \
  -u $(id -u):$(id -g) \
  emscripten/emsdk \
  emcc helloworld.cpp -o helloworld.js

# execute on host machine
node helloworld.js

如果看到 輸出 Hello World! 的話, 配置成功了.

設置快捷方式

每次使用打這麼一大串肯定不現實, 一般的思路是寫一個別名:

# vim ~/.zshrc
alias emcc='docker run \
  --rm \
  -v $(pwd):$(pwd) \
  -u $(id -u):$(id -g) \
  emscripten/emsdk \
  emcc'
alias em++='docker run \
  --rm \
  -v $(pwd):$(pwd) \
  -u $(id -u):$(id -g) \
  emscripten/emsdk \
  em++'

# 應用配置
source ~/.zshrc

這樣手敲就沒問題了, 但是用諸如 vscode 之類的工具配置環境會比較煩, 更加穩妥的方式是將其寫進 bash 腳本裡面:

echo '#!/bin/bash
/usr/local/bin/docker run \
  --rm \
  -v $(pwd):$(pwd) \
  -u $(id -u):$(id -g) \
  emscripten/emsdk \
  emcc $*' > /usr/local/bin/emcc

echo '#!/bin/bash
/usr/local/bin/docker run \
  --rm \
  -v $(pwd):$(pwd) \
  -u $(id -u):$(id -g) \
  emscripten/emsdk \
  em++ $*' > /usr/local/bin/em++

sudo chmod +x /usr/local/bin/emcc
sudo chmod +x /usr/local/bin/em++

至此, emsdk 的安裝已經基本完成.

init.d rc.d rc*.d profile.d 都是些什麼?

啟動項匯總

定位到 /etc 目錄下, 我們可以發現在這個目錄下,至少有這幾個和啟動有關的文件(夾):

$ ls

cron.d/
cron.daily/
cron.deny/
cron.hourly/
cron.monthly/
cron.weekly/
crontab    

init.d/

inittab

profile.d/
profile

rc.d/
rc.local/
rc0.d/
rc1.d/
rc2.d/
rc3.d/
rc4.d/
rc5.d/
c6.d/    

反正我第一眼看到這張表是完全迷惑的.

那麼接下的博文, 就是要分別介紹, 上述的所有文件(夾).

功能介紹

crontab 組合

維基百科:

工具型软件cron是一款类Unix下的基于时间的任务管理系统。用户们可以通过cron在固定时间、日期、间隔下,运行定期任务(可以是命令和脚本)。cron常用于运维和管理,但也可用于其他地方,如:定期下载文件和邮件。cron该词来源于希腊语chronos(χρόνος),原意是时间。

文件位置

  • /var/spool/cron/ – 用戶層次
  • /etc/contab/etc/cron* – 系統層次

crontab 格式

# 文件格式說明
# ┌──分鐘(0 - 59)
# │ ┌──小時(0 - 23)
# │ │ ┌──日(1 - 31)
# │ │ │ ┌─月(1 - 12)
# │ │ │ │ ┌─星期(0 - 6,表示从周日到周六)
# │ │ │ │ │
# *  *  *  *  *  用户名 被執行的命令

管理方法

  • 直接編輯 /etc/crontab 文件
  • echo '指令' >> /etc/crontab
  • 使用 crontab -e 指令

crontab 和 cron.d

** Manpage(8): **

Like /etc/crontab, the files in the /etc/cron.d directory are monitored for changes. In general, the system administrator should not use /etc/cron.d/, but use the standard system crontab /etc/crontab.

/etc/crontab and the files in /etc/cron.d must be owned by root, and must not be group- or other-writable. In contrast to the spool area, the files under /etc/cron.d or the files under /etc/cron.hourly, /etc/cron.daily, /etc/cron.weekly and /etc/cron.monthly may also be symlinks, provided that both the symlink and the file it points to are owned by root. The files under /etc/cron.d do not need to be executable, while the files under /etc/cron.hourly, /etc/cron.daily, /etc/cron.weekly and /etc/cron.monthly do, as they are run by run-parts (see run-parts(8) for more information).

從文中我們可以獲取這麼一些信息:

  • 我們只需要使用 crontab 文件, 其餘的事情由系統管理
  • cron.d 和 其他的 cron* 文件夾都是放腳本用的, 準確說來, 是放符號連接
  • cron.dcron* 的區別是 放在 cron.d 目錄下的腳本必須可執行, 後者不需要

init.d

$ cat /etc/init.d/README

You are looking for the traditional init scripts in /etc/rc.d/init.d,
and they are gone?

Here's an explanation on what's going on:

You are running a systemd-based OS where traditional init scripts have
been replaced by native systemd services files. Service files provide
very similar functionality to init scripts. To make use of service
files simply invoke "systemctl", which will output a list of all
currently running services (and other units). Use "systemctl
list-unit-files" to get a listing of all known unit files, including
stopped, disabled and masked ones. Use "systemctl start
foobar.service" and "systemctl stop foobar.service" to start or stop a
service, respectively. For further details, please refer to
systemctl(1).

Note that traditional init scripts continue to function on a systemd
system. An init script /etc/rc.d/init.d/foobar is implicitly mapped
into a service unit foobar.service during system initialization.

Thank you!

Further reading:
        man:systemctl(1)
        man:systemd(1)
        http://0pointer.de/blog/projects/systemd-for-admins-3.html
        https://www.freedesktop.org/wiki/Software/systemd/Incompatibilities

上述陳述基本已經講清楚了情況:

  • 傳統的啟動腳本功能已經整合進了 systemd, 由 systemctl 程序提供給用戶操作
  • 現在的 /etc/init.d/ 其實是 /etc/rc.d/init.d/ 的符號鏈接
  • /etc/init.d/ 仍然可以藉由 systemd 提供的兼容正常工作

講清楚了這個, 下一個就是看 rc.d 文件夾掉內容了

rc 組合

rc 的意思是 “run command”, d 意味 “sub-directory”, 就是 /etc 下的字目錄的意思

首先要明確的一點是, 上述的 rc* 組合都是指向 /etc/rc.d/rc* 的符號鏈接, 因此我們只要研究 rc.d 文件夾內的結構就可以了. 這樣還不夠明確的話, 可以參考 ll 結果:

$ ll

drwxr-xr-x. 10 root  root     4096 Dec 18 07:31 rc.d
lrwxrwxrwx.  1 root  root       13 Dec 18 07:31 rc.local -> rc.d/rc.local
lrwxrwxrwx.  1 root  root       10 Aug  5  2020 rc0.d -> rc.d/rc0.d
lrwxrwxrwx.  1 root  root       10 Aug  5  2020 rc1.d -> rc.d/rc1.d
lrwxrwxrwx.  1 root  root       10 Aug  5  2020 rc2.d -> rc.d/rc2.d
lrwxrwxrwx.  1 root  root       10 Aug  5  2020 rc3.d -> rc.d/rc3.d
lrwxrwxrwx.  1 root  root       10 Aug  5  2020 rc4.d -> rc.d/rc4.d
lrwxrwxrwx.  1 root  root       10 Aug  5  2020 rc5.d -> rc.d/rc5.d
lrwxrwxrwx.  1 root  root       10 Aug  5  2020 rc6.d -> rc.d/rc6.d
lrwxrwxrwx.  1 root  root       11 Apr 27  2020 init.d -> rc.d/init.d

...

init.d 文件夾

如之前所述, 所有開機運行的腳本都放在 init.d 裡面,

$ ll

-rw-r--r--. 1 root root  1161 Dec 18 07:30 README
-rwxr-xr-x. 1 root root 10438 Jun 22  2018 bt
-rwxr-xr-x  1 root root  7039 Mar 29 21:37 fail2ban
-rw-r--r--. 1 root root 18434 Jul 24  2020 functions
-rwxr-xr-x  1 root root 10672 Mar 29 19:44 mysqld
-rwxr-xr-x  1 root root  2753 Dec  6  2018 nginx
-rwxr-xr-x  1 root root  2361 Mar 29 20:09 php-fpm-56
-rwxr-xr-x  1 root root  1447 Aug 17  2016 pure-ftpd

需要注意的是, init.d 僅僅是一個放腳本的地方, 要讓他開機啟動還是需要在 rc#.d 中調用到他的.

rc#.d 文件夾用途

  • 文件夾名稱中的數字, 代表了 run-level, 也就是 “運行級別”. 詳細的說明可以看 維基百科, 下面簡單的 copy 了維基百科的內容
    • 0: 停機, 關機
    • 1: 單用戶, 無網路鏈接, 無守護進程, 不允許 非超級用戶登陸 (僅root)
    • 2: 多用戶, 無網路鏈接, 無守護進程
    • 3: 多用戶, 正常啟動系統
    • 4: 用戶自定義
    • 5: 多用戶, 帶圖形介面
    • 6: 重啟
    在Debian上, 2-5 這四個運行級別都集中在 2上, 2也是系統默認的正常情況.
    現在在 systemd 體系中已經使用 target 來代替 runlevel, 如multi-user.target相当于init 3,graphical.target相当于init 5,但是 runlevel 仍然被 systemd 支持.
  • 舊版本可以使用 chkconfig 來配置不同的 runlevel 運行的服務.
  • init.d 不同, rc#.d 裡面不放腳本, 而是放對應到 init.d 中腳本的符號鏈接, 不同的名稱代表的含義不同. 比如說, “K” 開頭意思是 K 掉這個程序, “S” 開頭代表 開始這個程序. “K” 或者 “S” 後面的數字越小, 執行的優先級越高, 下面是我的樹莓派的配置:
[[email protected] rc3.d]$ ll

lrwxrwxrwx 1 root root 20 Mar 29 20:09 K50php-fpm-56 -> ../init.d/php-fpm-56
lrwxrwxrwx 1 root root 15 Mar 29 21:49 K55nginx -> ../init.d/nginx
lrwxrwxrwx 1 root root 16 Mar 29 19:44 K64mysqld -> ../init.d/mysqld
lrwxrwxrwx 1 root root 19 Mar 29 19:48 K85pure-ftpd -> ../init.d/pure-ftpd
lrwxrwxrwx 1 root root 18 Mar 29 21:38 S50fail2ban -> ../init.d/fail2ban
lrwxrwxrwx 1 root root 12 Mar 29 18:46 S55bt -> ../init.d/bt

(一大堆前面加K的是我禁止了這些服務的自啟, 暫時還用不到.)
不僅是 rc3, 在其他 rc# 文件夾裡面也有大概相同的文件, 所以規則是, 那個 runlevel 用到就加到 rc幾 , 不怕重複的.

  • 所以說, 除非有特殊的需求, 這幾個文件夾也是一般用戶不需要碰到的, 可以使用systemd 解決需求.

rc.local 文件用途

可以首先看一下 rc.local 文件裡面寫了什麼.

$ cat /etc/rc.d/rc.local

#!/bin/bash
# THIS FILE IS ADDED FOR COMPATIBILITY PURPOSES
#
# It is highly advisable to create own systemd services or udev rules
# to run scripts during boot instead of using this file.
#
# In contrast to previous versions due to parallel execution during boot
# this script will NOT be run after all other services.
#
# Please note that you must run 'chmod +x /etc/rc.d/rc.local' to ensure
# that this script will be executed during boot.

touch /var/lock/subsys/local

所以說, 這個文件是很久以前用於開機運行一些命令的(不是 sh文件, 而是一些自定義的命令組合), 現在只為了兼容性保留, 最好不要使用了.

inittab 文件

$ cat inittab

# inittab is no longer used.
#
# ADDING CONFIGURATION HERE WILL HAVE NO EFFECT ON YOUR SYSTEM.
#
# Ctrl-Alt-Delete is handled by /usr/lib/systemd/system/ctrl-alt-del.target
#
# systemd uses 'targets' instead of runlevels. By default, there are two main targets:
#
# multi-user.target: analogous to runlevel 3
# graphical.target: analogous to runlevel 5
#
# To view current default target, run:
# systemctl get-default
#
# To set a default target, run:
# systemctl set-default TARGET.target

這個文件告訴我們, inittab 已經被徹底廢棄了, 甚至都不會兼容它.

這也印證了前面的說法, runlevel 的機制已經被 target 機制取代了.

在廢棄前, inittab 是用來決定默認 runlevel 的.

profile 組合

這個就比較好理解了, 這個其實就是一個所有用戶的Bash變量設置, (不侷限於Bash), 功能於設置當前用戶的 ~/.profile 是相似的. 所以說他雖然是啟動項, 但是是專門設置終端環境的, 我也許會以後專門寫一篇來做介紹.

至於 profile.d , 就把 profile 裡面寫不下的專門拿出來模塊化加載, 可以看一下他的內容:

[[email protected] profile.d]$ ls
colorgrep.csh  colorls.sh       colorzgrep.csh  gawk.csh  lang.sh   sh.local  which2.csh
colorgrep.sh   colorxzgrep.csh  colorzgrep.sh   gawk.sh   less.csh  vim.csh   which2.sh
colorls.csh    colorxzgrep.sh   csh.local       lang.csh  less.sh   vim.sh

都是一些關於終端環境的設置.
明確它的功能之後, 以下討論不會再涉及 profile.

各個方案對比

那麼現在匯總一下開機運行程序的所有方案:

名稱隸屬於機制支持格式應用場景
/etc/crontabcrontab命令開機 @reboot
定時運行任務
/etc/cron.d/crontab腳本無須手動配置
/etc/cron.*/crontab腳本(符號鏈接)無須手動配置
/etc/init.d/rc.drunlevel可執行腳本放置要用到的腳本供rc#.d調用
/etc/rc#.d/rc.drunlevel指向init.d中腳本的符號鏈接指定腳本的運行場景和優先級
/etc/rc.localrc.drunlevel命令執行自定義指令
systemdtargetservice開機執行任務
定時執行任務
特定條件執行任務

systemd 對比 init

之前已經提到, init 已經被 systemd 取代了, 現在的 rc.d 目錄內的一切事務全部由 systemd 代為執行.

(就是說, systemd 替代 init 作為系統最開始啟動的進程 )

所以這沒什麼可比性. 你仍然可以往 init.d 裡面灌腳本然後到 rc#.d 裡面創建符號鏈接 — 一般用在服務器上, 用來啟動程序已經夠了, 但是顯然, 這樣的方式沒有 service 來的靈活.

systemd 對比 crontab

這兩個東西, 功能有重疊, 具體如下:

  • systemd timer 可以定時完成任務
  • crontab 設置條件為 @reboot 可以開機執行命令

在匯總了網上的問答之後, 以及我的親身體驗, 我總結出如下結論:

  • 機械化的定時執行任務, 首選 crontab, 直觀可靠
  • 一句話開機執行任務, 也可以使用 crontab, 原因如上
  • 執行複雜的腳本, 使用 systemd 更為方便.

也就是說, 能用 crontab 就避免使用 systemd, systemd timer 其實可以被 crontab 替代.

資料

在寫這篇博文時, 我搜索了很多資料, 也因此學到了很多, 以下列出我參考過的資料(可能有一點點亂):

  • rc.local
  • https://unix.stackexchange.com/questions/59929/whats-the-difference-between-etc-rc-local-and-etc-init-d-rc-local
  • http://c.biancheng.net/view/1023.html
  • https://mustgeorge.blogspot.com/2012/02/ubuntu-rclocal.html
  • Init.d
  • https://www.jianshu.com/p/0dff9247d878
  • inittab
  • https://www.networkworld.com/article/2693438/unix-how-to-the-linux-etc-inittab-file.html
  • https://docs.oracle.com/cd/E19683-01/817-3814/6mjcp0qgh/index.html
  • Cron vs systemd
  • https://www.reddit.com/r/linuxadmin/comments/7ea2mj/cron_vs_systemd/
  • https://serverfault.com/questions/92783/init-d-vs-cron-which-to-use
  • runlevel and target
  • https://blog.csdn.net/zyjiscainiao/article/details/54410286
  • https://blog.csdn.net/soonfly/article/details/72876001
  • systemd
  • https://blog.csdn.net/xing_huo95/article/details/90246050
  • http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html
  • http://www.ruanyifeng.com/blog/2018/03/systemd-timer.html
  • https://cloud.tencent.com/developer/article/1516125
  • https://www.linux.com/training-tutorials/understanding-and-using-systemd/
  • https://unix.stackexchange.com/questions/206315/whats-the-difference-between-usr-lib-systemd-system-and-etc-systemd-system
  • https://zh.wikipedia.org/wiki/Systemd
  • profile
  • https://unix.stackexchange.com/questions/64258/what-do-the-scripts-in-etc-profile-d-do

樹莓派安裝 CentOS8

我在樹莓派上跑 ubuntu + docker 同步文件有一段時間了, 期間報錯無數, 停機無數, 偶有出現cpu佔用到100%, 以為是被攻擊, 然後發現是 snapd 的bug.
也許 ubuntu 就是不適合做家用服務器?
所以有了下面這一篇文章, 紀錄一下我的快速配置.

下載 Centos8 stream arch64 for pi
Copy連接下載
下載樹莓派官方刷機工具
點擊官網下載

刷入機器, ssh 連結.

  • 默認帳戶: root
  • 默認密碼: centos

基礎配置

# 擴展瓷盤
rootfs-expand

# 設置時間並開啟自動同步
# 你可能想要自己配置 ntp 服務器
# vi /etc/chrony.conf
# server s1a.time.edu.cn iburst
# server ntp.aliyun.com iburst

systemctl enable chronyd.service
systemctl restart chronyd.service
timedatectl set-timezone Asia/Shanghai
timedatectl set-ntp true
echo '@reboot timedatectl set-ntp true' >> /etc/crontab
timedatectl

# 安裝擴展庫
yum install epel-release -y
yum update -y

# 安裝基礎工具
yum install vim git make ufw -y

# 設置防火墻
ufw allow from 192.168.10.1/24 # ufw allow ssh
ufw default deny
ufw enable
ufw status # 將多餘的配置刪掉

# 設置網路
nmtui

那麼, 基礎設置已經完成了, ufw 已經配置只允許局域網訪問的緣故, 怕煩不設自定義密碼也是可以的.

 登陸配置

# 更改默認 root 密碼
passwd root

# 配置 ssh 連結
cd ~/
mkdir .ssh
chmod 700 .ssh
vi /root/.ssh/authorized_keys
chmod 644 /root/.ssh/authorized_keys # 將你的公鑰複製進去
vi /etc/ssh/sshd_config # 配置ssh登錄

sshd_config 中分別修改下面幾句

LoginGraceTime 10m                 #afk達10min自動登出
PermitRootLogin prohibit-password  #只允許root用ssh登錄
MaxAuthTries 6                     #最多允許三次錯誤登錄嘗試
MaxSessions 10                      #最多允許三個client同時登錄

PubkeyAuthentication yes           #開啓公鑰登錄

AuthorizedKeysFile      .ssh/authorized_keys #開啓公鑰

PasswordAuthentication no          #不允許任何用戶使用密碼登錄
PermitEmptyPasswords no            #不允許任何空密碼用戶登錄
systemctl restart sshd.service
systemctl status sshd.service
# 確認 status 沒問題
exit # 現在請重新登陸

我自己的設置 備忘

# 開機掛載網絡硬盤
@reboot curlftpfs -o rw,allow_other ftp://username:[email protected]/ /media/ftpdrive

# 開機掛載硬盤
# 卸載一定要 umount, rm 掛載點會刪掉你所有文件, 切記!
mkdir /media/backup
mount /dev/sda1 /media/backup
blkid # 複製顯示的 uuid 和 瓷盤類型
echo 'UUID=cc01ef19-892e-4eaf-862a-4f7b9cab3c0f /media/backup ext4 defaults 1 2' >> /etc/fstab
mount -a # 非常重要! 有報錯不解決 下次開不了機

# 安裝寶塔
cd /tmp
curl -sSO http://download.bt.cn/install/install_panel.sh && bash install_panel.sh
y

文件夾開放權限給用戶組

當訪問一個文件夾遇到 AccessDenied的時候:

chmod777 解決一切問題

但是內心總有一絲絲的不安:這好嗎?

這不好。

chmod + 數字的具體解釋

從上圖可以看到,chmod777其實給予了所有用戶對這個文件夾的完全權限,如果是PC還好,是Server的話,給/www來個chmod777,後果簡直不堪設想。

解決方案

我的臨時解決方案是,對於所有管理員賬戶(就是可以用sudo的)開放權限:

chgrp sudo folder
chmod 770 folder

權限命令

和權限有關的常用命令:

  • chmod [參數] [文件/目錄]: 修改某一個文件/目錄的權限
  • chgrp [組名] [文件/目錄]: 修改某一個文件/目錄的所有組
  • chown [用戶] [文件/目錄]: 修改某一個文件/目錄的所有者
  • ll: 類似於ls,但是列出了詳細信息,包括權限

Aptitude 命令

OS: Debian based systems

Wiki: aptitude-debianWiki

使用場景

這個玩意是一個 ncurses based 圖形界面包管理器(同時也可以命令行交互)

  • 可以使用鼠標和鍵盤操作(很方便)
  • 無論是gui還是tui都提供完整中文支持
  • 支持類正則方式匹配軟件包
  • 將軟件包分類,比如「自動安裝和手動安裝」,支持修改狀態爲自動安裝或手動安裝
  • 提供離線和在線的手冊(不要這麼友好啊啊啊),支持多語種
  • 这个软件包还内置掃雷 (悄悄说)

可以分析軟件包的依賴關係,找出沒有卸載徹底的軟件,沒有任何依賴關係的軟件,離線安裝的軟件(無source)等等。

安裝方式

可以使用apt 安裝

sudo apt update -y
sudo apt install aptitude -y

常用方法

清理廢棄包

常常是source不存在的包

# 查看 (以下2个等价)
aptitude search "~o"
aptitude search ?obsolete
# 清除
sudo aptitude purge "~o"

警告,一般來說這種包很少,有的如單個deb安裝的包(如wps),不要亂刪。

清理rc包

卸載不徹底,還殘留這配置文件的包

一般來說rc包的特征是卸载的时候会提示要卸载 0B,(就是只移除配置文件)

# 查看
aptitude search "~c"
# 清除
sudo aptitude purge "~c"