跳到主要内容

基于Docker和Nginx搭建HTTPS Git服务器

· 阅读需 3 分钟

使用到的工具:

  1. Docker Compose
  2. Nginx
  3. Git
  4. fcgiwrap

Docker Compose 配置

services:
nginx:
restart: always
container_name: nginx
user: root
image: nginx
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- 80:80
- 443:443
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/mine.types:/etc/nginx/mine.types
- ./nginx/conf.d:/etc/nginx/conf.d
- ./nginx/html:/etc/nginx/html
- ./nginx/screen:/etc/nginx/screen
- ./nginx/logs:/etc/nginx/logs
# 重要:
- /run/fcgiwrap.socket:/var/run/fcgiwrap.socket
# 这里不需要映射进去,因为 fcgiwrap 是运行在宿主机里面的
# - "/usr/lib/git-core/:/usr/libexec/git-core/:ro"
environment:
- NGINX_PORT=80
- TZ=Asia/Shanghai
privileged: true

Nginx 配置

# 443 端口
server {
listen 443 ssl;
server_name git.wangzhy.com;
ssl_certificate /etc/nginx/ssl/wangzhy.com_ecc/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/wangzhy.com_ecc/wangzhy.com.key;

# 设置 SSL 会话缓存。存储 SSL 会话参数,以便在同一客户端的后续连接中重用,从而减少 SSL 握手的开销
# shared:SSL:1m 表示创建一个名为 "SSL" 的共享内存区,大小为 1MB。这个内存区可以被所有工作进程(worker process)共享,用于存储 SSL 会话参数。
ssl_session_cache shared:chat_ssl_cache:10m;
ssl_session_timeout 1h;

#请按照以下协议配置
ssl_protocols TLSv1.2 TLSv1.3;
#请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
# ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_ciphers EECDH+AESGCM:EECDH+CHACHA20:EECDH+AES256:!aNULL:!MD5:!RC4; # 高效率加密套件
ssl_prefer_server_ciphers on;

# 强制 HSTS (提高安全性)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

charset utf-8;
default_type text/plain;

include /etc/nginx/ip-conf/whitelist.conf;
deny all;

root /etc/nginx/html;

# 通过 https 请求 git
location ~ (/.*)$ {
# 使用 Basic 认证
# auth_basic "Restricted";
# auth_basic_user_file /etc/nginx/passwd;

# FastCGI 参数
include fastcgi_params;
fastcgi_pass unix:/var/run/fcgiwrap.socket;
# 因为 fastcgi 是通过 docker 将宿主机的程序映射过来的,fastcgi 实际是运行在宿主机的,所以在这里要是有宿主机的地址。
fastcgi_param SCRIPT_FILENAME "/usr/lib/git-core/git-http-backend";
fastcgi_param GIT_HTTP_EXPORT_ALL "";
# git 库在服务器上的根目录
fastcgi_param GIT_PROJECT_ROOT /wangzhy/gitrepo;
fastcgi_param PATH_INFO $1;
# 将认证用户信息传递给 fastcgi 程序
# fastcgi_param REMOTE_USER $remote_user;
# 将允许客户端 post 的最大值调整为 100 兆
}

error_page 400 402 403 404 500 502 503 504 /50x.html;
location = /50x.html {
}
}

Git 配置

允许远程访问 git 仓库:

git config --system http.receivepack true
git config --system http.uploadpack true

fcgiwrap 配置

安装

apt update && apt install fcgiwrap

启动

systemctl start fcgiwrap
systemctl enable fcgiwrap

问题处理

权限问题

  1. error: remote unpack failed: unable to create temporary object directory

检查下面文件、文件夹的权限

  • /run/fcgiwrap.socket
  • /usr/lib/git-core/git-http-backend
  • ps aux | grep fcgiwrap
  • Docker Compose Nginx 的主线线的用户
  • xxx.git 文件夹的权限,一般要求是 chmod -R 755 xxx.git
  1. fatal: unable to access 'https://xxxx/.git/': The requested URL returned error: 403

检查 xxx.git/config 文件,查看是否配置了 http.receivepack true

修改运行 fcgiwrap 的用户

系统默认是 www-data,如果需要修改成其他用户,比如 nginx,可以使用下面命令:

systemctl edit --full fcgiwrap.service

AI 问题汇总

· 阅读需 1 分钟

记录询问 AI 的问题与答案

Docker

1. docker compose pull && docker compose down && docker compose up -d 命令优化

重新创建并重启服务

# 最简洁的方式 - 重新创建并启动服务
docker compose up -d --pull always --force-recreate

拉取镜像并重启有变化的服务

docker compose up -d --pull always

场景安全的滚动更新

docker compose pull && docker compose up -d --no-deps --force-recreate

Docker Image

· 阅读需 1 分钟

watchtower

自动检测并更新 docker image

version: "3"
services:
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --interval 3600 # 每小时检查一次更新

nvm

· 阅读需 1 分钟

nvm 设置默认 node 版本

nvm alias default 18

.npmrc 文件的作用

配置说明文档: https://docs.npmjs.com/cli/v9/using-npm/config

npm 命令的预设配置文件,在执行 npm 命令时,会加载并根据这些配置来执行命令。

audit=false
loglevel=error
registry=http://mirrors.tencent.com/npm/
engine-strict=true

Git 常用命令

· 阅读需 2 分钟

git log

--since 语法

  1. 绝对日期格式
    • YYYY-MM-DD
    • Month Day, Year
    • YYYY-MM-DD HH:mm:ss
  2. 相对时间格式
    • N year/months/weeks/days/hours/minutes/seconds ago
    • yesterday/today
  3. --until --before 结合
    • --since>=
    • --until, 即 <

从今天开始,过去 1 个月的提交

git log --since="1 month ago"

一行显示

git log --since="2025-06-01" --until="2025-07-01" --oneline

自定义输出

# %h commit hash 的缩写
# %an 作者
# %ar 提交的相对时间
# %s 提交信息
git log --since="1 month ago" --pretty=format:"%h - %an, %ar : %s"

图形化显示

git log --since="1 month ago" --graph --oneline

--pretty=format

--pretty:format"- %s" 格式化输出

  • %h commit hash
  • %an author name
  • %ad author date(format respects --date=option) 搭配 --date 使用--date=format:"%Y-%m-%d %H:%M:%S"
  • %ar author date, relative (相对时间)
  • %at author date, UNIX timestamp
  • %ai author date, ISO 8601
  • %ae author email
  • %s subject

查询指定日期之后提交记录并格式化显示

git log --since="2025-11-31" --author="wangzhy" --pretty=format:" - %s %ad" --date=format:"%Y-%m-%d %H:%M:%S"

git config

pull.rebase

git pull 命令的行为方式。

  • git config pull.rebase true 表示使用 rebase 方式来合并远程代码 (要求有干净的工作区,即没有未提交的代码)
  • git config pull.rebase false 表示使用 merge 方式来合并远程代码

自解释代码

· 阅读需 2 分钟

summary

  1. 使用命名常量而非晦涩的错误代码 | Using named constants instead of cryptic error codes
  2. 将复杂逻辑抽离成独立函数 | Extracting complex logic and putting it in its own function
  3. 运用短路求值使代码流程线性化 | Using short-circuit evaluation to make the code flow linear
  4. 引入类型注解辅助静态检查与实时编码反馈 | Introducing type annotations to help with static type checking and real-time coding feedback

original code

async function createUser(user) {
if (!validateUserInput(user)) {
throw new Error('u105');
}

const rules = [/[a-z]{1,}/, /[A-Z]{1,}/, /[0-9]{1,}/, /\W{1,}/];
if (user.password.length >= 8 && rules.every((rule) => rule.test(user.password))) {
if (await userService.getUserByEmail(user.email)) {
throw new Error('u212');
}
} else {
throw new Error('u201');
}

user.password = await hashPassword(user.password);
return userService.create(user);
}

命名常量与单一职责原则

const err = {
userValidationFailed: 'u105',
userExists: 'u212',
invalidPassword: 'u201',
};

function isPasswordValid(password) {
const rules = [/[a-z]{1,}/, /[A-Z]{1,}/, /[0-9]{1,}/, /\W{1,}/];
return password.length >= 8 && rules.every((rule) => rule.test(password));
}

async function createUser(user) {
if (!validateUserInput(user)) {
throw new Error(err.userValidationFailed);
}

if (isPasswordValid(user.password)) {
if (await userService.getUserByEmail(user.email)) {
throw new Error(err.userExists);
}
} else {
throw new Error(err.invalidPassword);
}

user.password = await hashPassword(user.password);
return userService.create(user);
}

短路求值

function throwError(error) {
throw new Error(error);
}

async function createUser(user) {
validateUserInput(user) || throwError(err.userValidationFailed);
isPasswordValid(user.password) || throwError(err.invalidPassword);
!(await userService.getUserByEmail(user.email)) || throwError(err.userExists);

user.password = await hashPassword(user.password);
return userService.create(user);
}

Keeping a Mistake Journal

· 阅读需 1 分钟

Entry Format

Name:名称,简明扼要的标题。
Tags:标签,分类
Context:背景,错误发生的具体环境或情境。
Problem:问题,对错误本身的描述。
Impacts:影响,可能导致的后果。
Lessons learned:经验总结,从这次错误中学到了什么。
Correction plan: 改进方案,今后如何避免重蹈覆辙。
Latest occurrence:最近发送,上次犯这个错误的时间。
Repetition: 重复次数,这个错误发送的概率。

Linux Shell 常用命令

· 阅读需 6 分钟

du

1、查看指定文件夹的大小

-h 以K,M,G为单位,提高信息的可读性。
-s 仅显示总计。

du -sh dir_path

2. 查询当前文件夹总大小

du -sh .

3. 查看当前文件夹所有子项的大小

du -sh *

df

df (disk filesystem)用于显示文件系统的磁盘空间使用情况.

查看 block 使用情况

df -h

查看 inode 使用情况

df -i

alias

alias gg='gg'
alias cls='clear'
alias ls='ls -alFh --ignore=. --ignore=.. --color=auto --group-directories-first'
alias acme.sh=~/.acme.sh/acme.sh

ln

ln -s source_file target_file

nginx

ln -s /wangzhy/cloud-conf/nginx/etc/nginx/conf /etc/nginx/conf
ln -s /wangzhy/.config/nginx_ip_conf /etc/nginx/ip-conf

把程序添加到 /usr/bin 下面

ln -s /source_path/source_name /usr/bin/target_name 

find

find 语法

find [path] [expression]

path:是要查找的目录路径,可以是一个目录或文件名,也可以是多个路径,多个路径之间用空格分隔,如果未指定路径,则默认为当前目录。

expression:是可选参数,用于指定查找的条件,可以是文件名、文件类型、文件大小等等。

-name pattern:按文件名查找,支持使用通配符 * 和 ?。

pattern 用引号括起来,例如

# 查找所有的 pdf 文件
find / -name '*.pdf*'

-type type:按文件类型查找,可以是 f(普通文件)、d(目录)、l(符号链接)等。

查看大于 100M 的文件

find / -type f -size +100M

-size [+-]size[cwbkMG]:按文件大小查找,支持使用 + 或 - 表示大于或小于指定大小,单位可以是 c(字节)、w(字数)、b(块数)、k(KB)、M(MB)或 G(GB)。

-mtime days:按修改时间查找,支持使用 + 或 - 表示在指定天数前或后,days 是一个整数表示天数。

-user username:按文件所有者查找。

-group groupname:按文件所属组查找。

find 命令中用于时间的参数如下:

-amin n:查找在 n 分钟内被访问过的文件。

-atime n:查找在 n*24 小时内被访问过的文件。

-cmin n:查找在 n 分钟内状态发生变化的文件(例如权限)。

-ctime n:查找在 n*24 小时内状态发生变化的文件(例如权限)。

-mmin n:查找在 n 分钟内被修改过的文件。

-mtime n:查找在 n*24 小时内被修改过的文件。

在这些参数中,n 可以是一个正数、负数或零。正数表示在指定的时间内修改或访问过的文件,负数表示在指定的时间之前修改或访问过的文件,零表示在当前时间点上修改或访问过的文件。

+n : n 天之前修改的文件 -n : n 天内修改的文件 n : n 天前修改的文件(第 n 天前的当天)

find 使用案例

  1. 在脚本中使用

find /opt/oracle/oradata/ORCLCDB/oracle_exp_dir -mtime +1 -name "WIKI_USER*" - rm -f exec {} \;

  1. 在 shell 使用

find /opt/oracle/oradata/ORCLCDB/oracle_exp_dir -mtime +1 -name "WIKI_USER*" - rm -f exec {} +

  1. 查找多种后缀的文件
find ~ \( -name "*.zip" -o -name "*.log" -o -name "*.txt" -o -name "*.jar" -o -name "*.json" -o -name "*.xls" -o -name "*.xlsx" -o -name "*.doc" -o -name "*.docx" \)
  1. 查找大于 10M 的 zip 文件
find /path/to/search -type f -name "*.zip" -size +10M -exec du -h {} + > ~/Desktop/a.txt

watch

每隔 1s 查询一下本目录下的,以 sql 结尾的文件的大小,单位是 MB。

watch -n 1 -d ls -lh --block-size=M *.sql

vim

1、vim 显示隐藏行号

# 显示行号
set number
set nu
# 隐藏行号
set nonumber
set nonu

2、vim 删除以 XX 开头的行

# 按 esc 进入命令输入模式
# 删除以 XX 开头的行
:g/^XX/d

tee

tee 的主要功能是从标准输入读取数据,然后将数据写入标准输出(即输出到终端上面)和文件。

echo 'hello world' | tee -a xxx.txt

tee>> 的区别?

tee 可以一次写入多个文件,并且可以在终端显示写入的内容。 >> 是静默的写入。

tar 压缩/解压文件

# 压缩
tar -zcvf test.tar.gz test
# 解压
tar -zxvf file.tar.gz

五选一

-c 压缩 -x 解压 -t 查看内容 -r 追加文件 -u 更新原压缩包的文件

可选

-z gzip -j bz2 -Z compress -v 显示过程 -O 标准输出

必选

-f 使用档案名字,这个参数是最后一个参数,后面只能接档案名。

systemd

Ubuntu 16.04 不再支持 rc.local 了。开机自启只能通过 systemd 来实现。

# 查看systemd 版本
systemctl --version
# 查看服务是否启用
systemctl is-enabled SERVICE_NAME

scp

[root@VM-16-10-centos ~]# scp
usage: scp [-12346BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]
[-l limit] [-o ssh_option] [-P port] [-S program]
[[user@]host1:]file1 ... [[user@]host2:]file2

scp 命令传输文件

# 复制远程机器上的 xxx.zip 到本地
scp cloud:/wangzhy/xxx.zip ~/backups/
# 复制远程机器上的文件夹到本地
# -r 表示递归复制整个目录
scp -r cloud:/wangzhy ~/backup/
  • -C:开启压缩传输

passwd

1、设置简单的密码

# 方法一
echo '123456' | passwd --stdin username
# 方法二
echo username:password | chpasswd

2、删除用户的登录密码

passwd -d root

3、清除 last、lastb 的信息

# 清除登录记录
echo > /var/log/wtmp # last
echo > /var/log/btmp # lastb

查看 linux 版本信息

# 查看架构信息
uname -a
# 查看发行版本信息
cat /etc/os-release
# 输出机器的体系结构
arch

ntp

同步操作系统的时间

# 安装 ntp
yum -y install ntp
# 同步时间,这个只是修改操作系统的时间,重启之后就会失效
ntpdate -u cn.pool.ntp.org
# 查看当前硬件时间
clock -r
# 把当前操作系统的时间写入硬件中
clock -w

curl

curl -L 参数的作用是让 curl 在遇到 HTTP 3xx 状态码(重定向)时,自动跟随新的位置(URL)

curl -L www.sina.com

使用 -k--insecure 参数让 curl 忽略 SSL 证书验证

curl -k https://wangzhy.com 

下载文件

-o 需要指定文件名

-O 使用 URL 中的文件名

brew

安装 xcode 才能升级 brew

xcode-select --install

brew 升级

brew update
brew upgrade

brew 查看安装的软件

brew list

brew 卸载软件

brew uninstall software_name

查看磁盘读写总量

brew install smartmontools

smartctl -a /dev/disk2

安装 svn

brew install subversion

svn help

grep

egrep 是 grep 的增强版本,支持更多的正则表达式

-v 告诉 egrep ,不现实匹配的行,即反向匹配。

egrep -v "^*#|^$" filebeat.yml

wget

-i 下载本地或外部文件中的 URL,如: wget -i file.txt

-O 将输出写入指定文件。 sh -c "$(wget https://xxx.sh -O -)", 末尾的 - 表示标准输出(stdout) 这句命令的作用是,通过 sh 执行 https://xxx.sh 脚本。

-b 后台下载。

-d 调试模式。

awk 命令基本使用

· 阅读需 3 分钟

awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。

默认分隔符是:空白键或者 tab 建,以 \n 为一行

$0:所有域
$n:第 n 个域

1、显示出最近登录成功的用户

使用默认分隔符

last -n 5 | awk '{print $1}'

2、查看用户

通过 -F ':' 指定分隔符

# 下面两个命令等效
cat /etc/passwd | awk -F ':' '{print $1}'
cat /etc/passwd | awk -F: '{print $1}'

3、查看用户的时候显示出 bash

通过双引号对 \t 转义

cat /etc/passwd | awk -F ':' '{print $1"\t"$7}'

4、BEGIN、END 使用

cat /etc/passwd | awk -F ':' 'BEGIN {"name,bash"} {print $1","$7} END {print "blue,/bin/nosh"}'

5、匹配含有关键字的行

'/pattern/action' 指定正则表达式和 action

awk -F ':' '/root/' /etc/passwd
awk -F ':' '/root/{print $7}' /etc/passwd

6、awk 内置变量

ARGC               命令行参数个数
ARGV 命令行参数排列
ENVIRON 支持队列中系统环境变量的使用
FILENAME awk浏览的文件名
FNR 浏览文件的记录数
FS 设置输入域分隔符,等价于命令行 -F选项
NF 浏览记录的域的个数
NR 已读的记录数
OFS 输出域分隔符
ORS 输出记录分隔符
RS 控制记录分隔符
awk -F ':' '{print $1 "\tfilename:" FILENAME "\t\tARGC:" ARGC "\tFNR:" FNR}' aa.txt 

7、print、printf

print函数的参数可以是变量、数值或者字符串。字符串必须用双引号引用,参数用逗号分隔。如果没有逗号,参数就串联在一起而无法区分。这里,逗号的作用与输出文件的分隔符的作用是一样的,只是后者是空格而已。

printf函数,其用法和c语言中printf基本相似,可以格式化字符串,输出复杂时,printf更加好用,代码更易懂。

8、自定义变量

awk -F ':' 'BEGIN {count=0; print "[start] user count is "count}{ count++; print $1"\t" count} END {print "user count is " count}' /etc/passwd

统计某个文件夹下的文件占用的字节数

ls -l |awk 'BEGIN {size=0;} {size=size+$5;} END{print "[end]size is ", size}'

如果以M为单位显示:

ls -l |awk 'BEGIN {size=0;} {size=size+$5;} END{print "[end]size is ", size/1024/1024,"M"}'

9、显示账户

awk -F ':' 'BEGIN {count=0;} {name[count] = $1;count++;}; END{for (i = 0; i < NR; i++) print i, name[i]}' /etc/passwd

查看拒绝访问的 IP

awk -F ':' '{print $2}' /etc/hosts.deny| sort | uniq -c | awk '{print $1"\t"$2}' | grep  '^[0-9]'