结论:如果没有网络环境或数据隐私要求,使用免费的私有或公共仓库吧。比如:github.com,另外,也是有免费的组织。
由于 Git 协议本身只负责数据同步,没有远程创建仓库的 API。所以,客户端无法直接远程新建仓库,只有安装 gitea 等服务器后,才可以在 WEB 上创建仓库。
单人场景:祼仓
使用 git init --bare 命令,再通过 SSH 协议直接访问服务器上的裸仓库文件,无任何常驻服务进程。
| 优点 | 缺点 |
|---|---|
| – 零额外内存占用:没有后台服务,只有 SSH 连接时才短暂调用 git 进程 – 零安装成本,系统自带 git 即可 – 绝对稳定,不存在服务崩溃问题 | – 无 Web 界面,无法浏览器浏览代码、看提交记录 – 无细粒度权限:只能靠 Linux 系统用户 / 文件权限控制,所有人要么全读写要么全只读 – 仓库创建、密钥管理全部手动操作 |
快速搭建与使用
服务器配置与创建 bare 仓库
- 服务器:安装 git 客户端
$ git --version
# 使用PPA源安装吧,Ubuntu 是23年的2.43版本,而ppa里是2.53。
# 如果提示无 add-apt-repository 命令,需要先 `sudo apt install software-properties-common` 。另外,PPA 的源,在国内下载速度,都很慢。
$ sudo add-apt-repository ppa:git-core/ppa
$ sudo apt update
$ sudo apt install git
# 可选:Git LFS 用于二进制大文件支持,见附录
$ git lfs version
- 服务器:创建专用 git 账号
# 创建 git 用户,且切换 git
$ sudo useradd -m -s /bin/bash git
$ sudo passwd git
$ su - git
# git 用户下执行
$ mkdir .ssh/
$ touch .ssh/authorized_keys
$ chmod u=rw,g=,o= .ssh/authorized_keys
$ chmod u=rwx,g=,o= .ssh/
$ vim .ssh/authorized_keys # 把公钥放进去
$ exit
- 服务器:使用 git 登录后,创建 bare 仓库
# 配置默认分支名,执行一次就可以了,避免 git init --bare 的提示
$ git config --global init.defaultBranch master
# 创建祼仓
$ mkdir -p ~/repo/demo.git/
$ cd ~/repo/demo.git
$ git init --bare # 创建祼仓
$ exit
客户端:连接与首次同步
- [可跳过]配置
~/.ssh/config文件,作用是执行ssh git@t725.cn会自动去找相关配置,不需要在命令中指定。
Host t725.cn
HostName git.t725.cn # Host 是 ssh 命令中的域名,而 HostName 是实际SSH服务域名,如果一样可以省略
User git # ssh 命令中的用户
#Port 443 # 无则为22,有则以指定为准
PreferredAuthentications publickey # 指定认证方法=publickey
IdentityFile D:\vps_git.pem # 指定认证的私钥文件
IdentitiesOnly yes # 忽略 ssh-agent
- 和 Github 等不一样(init 时,会有添加 LICENSE 与 README.md 文件),而祼仓
git init --bare在创建后是空的,没有工作区,也没有分支名,所以:- 首次不能使用
git clone git@t725.cn:repo/demo.git d:\demo命令 - 首次需要先从客户端做一次 push 后,服务上才有内容,才会有分支名的,以及其他客户才可以使用 git cloen 命令
- 首次不能使用
# 本地仓库已初化完成
PS D:> git remote add origin git@t725.cn:repo/BookmarkSync
PS D:> git push -u origin master # -u 首次必须要有
PS D:> git branch -r # 在没有上传前,是无值的。
安全配置
注册 git-shell 及两种安全方式
无论哪一种安全方式,都要先让 git-shell 注册为合法 shell(否则 SSH 会拒绝登录),因为默认是不注册的。
$ cat /etc/shells # 没有注册前的内容
# /etc/shells: valid login shells
/bin/sh
/usr/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/usr/bin/dash
/usr/bin/screen
/usr/bin/tmux
# 确认路径 /usr/bin/git-shell
$ command -v git-shell
# 注册为合法的 shell
$ grep -qxF '/usr/bin/git-shell' /etc/shells || echo '/usr/bin/git-shell' | sudo tee -a /etc/shells
方式一:git 用户的 shell 为 /usr/bin/git-shell
- 优点:git 用户权限被严格限制
- 缺点:未来要调整 git 用户配置不太方便,因为 git-shell 里能运行命令,只有git相关的,文件管理等bash命令都没有。
# 创用户时,指定shell
$ sudo adduser --system --group --home /var/lib/git --shell /usr/bin/git-shell --comment "Git service" git
# 修改已有用户
$ sudo usermod -s /usr/bin/git-shell git
注意:
- home 是
/var/lib/git,目录默认权限是drwxr-xr-x 2 root root 4.0K Apr 23 2024 ./ - 后续,这个账号在终端里登录后,也只有git-shell
- 另外,Linux 目录权限规则:要进入一个子目录,必须对路径上每一级父目录都拥有
x(execute/search)权限,所以也要注意上级目录的权限。可以没有r权限,但要有x权限。
方式二:authorized_keys 里限制命令,也是 gitea 等 git 服务器的标准做法
- 正常添加用户:
- git 用户是可以正常使用 bash shell,但此证书,不做第2步的先处理,就是只有原PUB证书内容。
# -p '!' 是代表不能使用密码登录
$ sudo useradd -r -s /bin/bash -c 'Git Version Control' -m -d /home/git -U -p '!' git
- 在
.ssh/authorized_keys添加 git 客户端证书时,要先处理。比如,原PUB证书ssh-ed25519 xxxCret-Pubxxx Git@T725.CN,则完整内容如下:
command="git-shell -c \"$SSH_ORIGINAL_COMMAND\"",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 xxxCret-Pubxxx Git@T725.CN
echo 命令追加:
$ echo 'command="git-shell -c \"$SSH_ORIGINAL_COMMAND\"",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAA... user@host' | sudo tee /var/lib/git/.ssh/authorized_keys
| 选项 | 作用 |
|---|---|
command="..." | 强制无论客户端请求什么命令,都只执行引号内的命令;$SSH_ORIGINAL_COMMAND 保存了客户端实际请求的原始命令(如 git-upload-pack 'repo.git') |
no-port-forwarding | 禁止 SSH 端口转发 |
no-X11-forwarding | 禁止 X11 图形转发 |
no-agent-forwarding | 禁止 SSH Agent 转发 |
no-pty | 禁止分配伪终端(阻止交互式 shell) |
在其他用户里,以 git 用户来执行命令
- 命令
sudo -u git cat ~/.ssh/authorized_keys的写法是错误的,因为 shell 展开~发生在 sudo 之前,所以~仍然被展开为当前用户$home的路径,而非 git 用户的家目录。正确写法:
# 任选其一,分别是:使用绝对路径、让目标用户的 shell 来展开、直接以 root 读取(绕过权限检查)
$ sudo -u git cat /var/lib/git/.ssh/authorized_keys
$ sudo -u git bash -c 'cat ~/.ssh/authorized_keys'
$ sudo cat /var/lib/git/.ssh/authorized_keys
git-shell 特点与自定义命令
- 默认的白名单命令,且不能交互;其它一律拒绝(包括 git log、git status、scp、sftp)。
| 服务端命令 | 客户端在做什么 |
|---|---|
git-receive-pack / git receive-pack | git push |
git-upload-pack / git upload-pack | git fetch / git clone / git pull |
git-upload-archive / git upload-archive | git archive --remote |
cvs server | 模拟 CVS 服务(git-cvsserver,很少用) |
- 可扩展自定义命令:有
~/git-shell-commands之后,但默认是没有的。- 可执行该目录里的可执行文件:
git-shell-commands/<命令> <参数>,或者在ssh git@t725.cn "<命令> <参数>"直接执行(注意:<命令>前不需要 git-shell-commands)。 - 无参数登录会进
git>提示符;有help就先跑它;输入exit断开 - 若存在
no-interactive-login:先跑它,然后禁止交互登录(官方示例用来打印「认证成功但不提供 shell」)
- 可执行该目录里的可执行文件:
- 注意:只要建了
git-shell-commands目录,交互登录就打开了;若仍不想给人 shell,务必放上no-interactive-login。官方禁用交互登录的写法:
mkdir -p ~/git-shell-commands
cat > ~/git-shell-commands/no-interactive-login <<'EOF'
#!/bin/sh
printf '%s\n' "Hi $USER! You've successfully authenticated, but I do not"
printf '%s\n' "provide interactive shell access."
exit 128
EOF
chmod +x ~/git-shell-commands/no-interactive-login
- 为什么自定义命令可以进入交互模式
- 登录 Shell ≠ 脚本解释器,用户的 git-shell 限制是会话级别的,不是进程级别的。而这些自定义命令,是以用户身份,在创建的子进程中执行,所以自定义命令中,可以使用bash shell里所有命令,比如:文件管理等。
非交互登录的环境
如果想执行有x权限的
~/xxx.sh文件,需要使用ssh user@ip "~/xxx.sh"。如果使用ssh user@ip "xxx.sh"不会被执行,因为裸命令只按 PATH 查找,不会自动看当前目录或HOME目录。
非交互登录的环境:ecs@t725.cn的 shell 是 /bin/bash,且无 authorized_keys 限制
# ssh ecs@t725.cn "printenv" 看 SHELL/PWD/SHLVL/PATH 的值。
_=/usr/bin/printenv
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
HOME=/home/ecs
LANG=C.UTF-8
LOGNAME=ecs
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
PWD=/home/ecs
SHELL=/bin/bash
SHLVL=0
SSH_CLIENT=x.x.x.x 12962 22
SSH_CONNECTION=x.x.x.x 12962 x.x.x.x 22
USER=ecs
XDG_RUNTIME_DIR=/run/user/1001
XDG_SESSION_CLASS=user
XDG_SESSION_ID=13951
XDG_SESSION_TYPE=tty
非交互登录的环境:git@t725.cn的 shell 是 /bin/bash,但有 authorized_keys 的 git-shell 限制
# ~/git-shell-commands/printEnv 的内容,且有执行权限;注意:第一行很重要!!!
#!/bin/bash
printenv
# ssh git@t725.cn "printEnv" 看 SHELL/PWD/SHLVL/PATH 的值。
_=/usr/bin/printenv
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/108/bus
HOME=/home/git
LANG=C.UTF-8
LOGNAME=git
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
PWD=/home/git
SHELL=/bin/bash
SHLVL=1
SSH_CLIENT=x.x.x.x 13232 22
SSH_CONNECTION=x.x.x.x 13232 x.x.x.x 22
SSH_ORIGINAL_COMMAND=printEnv
USER=git
XDG_RUNTIME_DIR=/run/user/108
XDG_SESSION_CLASS=user
XDG_SESSION_ID=13961
XDG_SESSION_TYPE=tty
便捷部署脚本 init
这个脚本,让你不需要登录到服务器上交互执行,可以通过 ssh user@server "init youRepoName" 直接非交互执行。
# 方式一(git 用户 shell 限制)的文件目录,但注意:下面命令不能直接使用的,只是表达意思
$ vim ~/git-shell-commands/init
$ chmod +x ~/git-shell-commands/init
# 方式二(authorized_keys 限制)的 init 文件目录,但注意:要使用没有限制证书,否则连接后没有shell
$ vim ~/init
$ chmod +x ~/init
init内容
#!/bin/bash
set -euo pipefail
REPO_BASE="$HOME/repo" # 后续可在这里传入参数 $PUB_KEY_USER
if [[ $# -ne 1 ]]; then
echo "Usage: init <repo-name>" >&2
exit 1
fi
REPO_NAME="$1"
# 安全校验:禁止路径遍历、绝对路径、特殊字符
if [[ ! "$REPO_NAME" =~ ^[a-zA-Z0-9._-]+$ || "$REPO_NAME" == "." || "$REPO_NAME" == ".." ]]; then
echo "Error: Invalid repository name '${REPO_NAME}'" >&2
echo "Only alphanumeric characters, dots, hyphens and underscores are allowed." >&2
exit 1
fi
REPO_PATH="${REPO_BASE}/${REPO_NAME}"
# 检查是否已存在
if [[ -e "$REPO_PATH" ]]; then
echo "Error: Path already exists: ${REPO_PATH}" >&2
exit 1
fi
# 检查基础目录是否可写
if [[ ! -w "$REPO_BASE" ]]; then
echo "Error: No write permission to ${REPO_BASE}" >&2
exit 1
fi
# 检查并设置 init.defaultBranch
DEFAULT_BRANCH=$(git config init.defaultBranch)
if [[ -z "$DEFAULT_BRANCH" ]]; then
echo "INFO: init.defaultBranch masterr"
git config --global init.defaultBranch master
fi
# ========== 创建裸仓库 ==========
mkdir -p "$REPO_PATH"
git init --bare "$REPO_PATH"
REPO_URL="git@t725.cn:${REPO_PATH#$HOME/}"
echo "✔ Initialized bare repository: ${REPO_PATH}"
echo "First sync, choose one of the two: "
echo "git clone $REPO_URL D:\$REPO_NAME"
echo "git remote add origin $REPO_URL && git push -u origin master"
在客户端上,远程执行
PS D:\Downloads> ssh git@t725.cn "init youRepoName" # 注意不走 git-shell 是 ssh git@t725.cn "~/init youRepoName"
Initialized empty Git repository in /data/www/gitBare/youRepoName/
/home/git
[INFO] 仓库创建完成,目录内容如下:
HEAD config description hooks/ info/ objects/ refs/
首次同步: git remote add origin git@t725.cn:repo/youRepoName && git push -u origin master
PS D:\Downloads>
git-shell 查看仓库名称的脚本 list
#!/bin/bash
set -euo pipefail
if [[ -z "${PUB_KEY_USER:-}" ]]; then
echo "Error: PUB_KEY_USER is not set" >&2
exit 1
fi
REPO_BASE="$HOME/$PUB_KEY_USER"
ls -C "$REPO_BASE"
发表回复
要发表评论,您必须先登录。