结论:如果没有网络环境或数据隐私要求,使用免费的私有或公共仓库吧。比如:github.com,另外,也是有免费的组织。
Git 协议本身只负责数据同步,没有远程创建仓库的 API。所以,客户端无法直接远程新建仓库,只有安装 gitea 等服务器后,才可以在 WEB 上创建仓库。
自托管的运行环境要求
这里的内容,也是客户端所需要的。
$ 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
# 0. apt install 前,先添加 PackageCloud 仓库安装 https://github.com/git-lfs/git-lfs/blob/main/INSTALLING.md
$ curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash
# 1. 安装, https://packagecloud.io/github/git-lfs/ubuntu 下载速度慢。
$ sudo apt install git-lfs
# 2. 全局注册过滤器(只需执行一次)
$ git lfs install
单人场景:祼仓
使用 git init --bare 命令,再通过 SSH 协议直接访问服务器上的裸仓库文件,无任何常驻服务进程。
| 优点 | 缺点 |
|---|---|
| – 零额外内存占用:没有后台服务,只有 SSH 连接时才短暂调用 git 进程 – 零安装成本,系统自带 git 即可 – 绝对稳定,不存在服务崩溃问题 | – 无 Web 界面,无法浏览器浏览代码、看提交记录 – 无细粒度权限:只能靠 Linux 系统用户 / 文件权限控制,所有人要么全读写要么全只读 – 仓库创建、密钥管理全部手动操作 |
可选:创建专用账号
# 创建 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
快速部署过程
# ssh客户端连接
ssh.exe git@t725.cn -i abc.pem
# 创建祼仓
$ git config --global init.defaultBranch master
$ mkdir -p ~/repo/demo.git/
$ cd ~/repo/demo.git
$ git init --bare # 创建祼仓
$ exit
客户端连接与同步
- 配置
~/.ssh/config文件 git clone git@t725.cn:repo/demo.git d:\demo注意路径,要与服务器上一样- 没有在服务器上,提前创建目录与执行
git init --bare,git 客户端是不可用的。
安全配置
无论哪一种安全方式,都要先让 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
注册为shell
$ grep -qxF '/usr/bin/git-shell' /etc/shells || echo '/usr/bin/git-shell' | sudo tee -a /etc/shells
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 为 /usr/bin/git-shell
$ sudo adduser --system --group --home /var/lib/git --shell /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 服务器的标准做法
- 正常添加用户,比如:
# -p '!' 是代表不能使用密码登录
$ 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
- 原PUB证书:
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) |
便捷部置脚本
这个脚本不一定要登录到服务器上执行,也可以通过 ssh user@server "~/init wms_axure_html_to_md" 直接执行
$ vim ~/init
$ chmod +x ~/init
init内容:
#!/bin/bash
set -e
# 1. 检查并设置 init.defaultBranch
DEFAULT_BRANCH=$(git config init.defaultBranch)
if [[ -z "$DEFAULT_BRANCH" ]]; then
echo "[INFO] 配置默认分支名 master"
git config --global init.defaultBranch master
fi
# 2. 检查入参
REPO_NAME="$1"
if [[ -z "$REPO_NAME" ]]; then
echo "[ERROR] 请提供仓库名称!用法:$0 <仓库名>"
exit 1
fi
REPO_DIR="$HOME/repo/$REPO_NAME"
if [[ -d "$REPO_DIR" ]]; then
echo "[ERROR] 目录已存在:$REPO_DIR"
exit 1
fi
# 3. 初始化 bare 仓库
mkdir -p "$REPO_DIR"
cd "$REPO_DIR"
git init --bare
# 4. 切回原始目录并列出仓库内容
cd -
echo "[INFO] 仓库创建完成:"
ls -CF "$REPO_DIR"
echo ""
echo "首次同步,二选一:"
echo "git clone git@t725.cn:repo/$REPO_NAME D:\$REPO_NAME"
echo "git remote add origin git@t725.cn:repo/$REPO_NAME && git push -u origin master"
客户端执行:
PS D:\Downloads> gitShell "~/init axure_html_to_md"
Initialized empty Git repository in /data/www/gitBare/axure_html_to_md/
/home/git
[INFO] 仓库创建完成,目录内容如下:
HEAD config description hooks/ info/ objects/ refs/
首次同步: git remote add origin git@t725.cn:repo/axure_html_to_md && git push -u origin master
PS D:\Downloads>
首次同步
和Github等不一样(init时有LICENSE与README.md文件),祼仓 git init --bare 在创建后是空的,没有工作区,也没有分支名,所以是需要从客户端做一次push后,才有分支名的。
- 先在服务器上,执行祼仓
git init --bare创建 - 再到客户端上,执行push与分支跟踪
# 已初化完成
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 branch就有值了
在 git-shell 环境下实现远程初始化裸仓库
原理:
- 登录 Shell ≠ 脚本解释器,用户的 git-shell 限制是会话级别的,不是进程级别的。
-
git-shell以硬编码方式,允许了~/git-shell-commands/目录下的自定义命令。而这些自定义命令,是以用户身份,创建子进程执行。
管理员在服务器上配置:
$ sudo adduser --system --group --home /var/lib/git --shell /usr/bin/git-shell git
$ sudo mkdir -p /var/lib/git/git-shell-commands
$ sudo vim /var/lib/git/git-shell-commands/init
$ sudo chmod +x /var/lib/git/git-shell-commands/init
init文件内容:
#!/bin/bash
set -euo pipefail
REPO_BASE="/repo"
if [[ $# -ne 1 ]]; then
echo "Usage: init <repo-name>" >&2
exit 1
fi
REPO_NAME="$1"
# 安全校验:禁止路径遍历、绝对路径、特殊字符
if [[ "$REPO_NAME" =~ ^(/|\.\./|\.\/) ]] || [[ "$REPO_NAME" != "${REPO_NAME//[^a-zA-Z0-9._-]/}" ]]; 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
# ========== 创建裸仓库 ==========
mkdir -p "$REPO_PATH"
git init --bare "$REPO_PATH"
echo "✔ Initialized bare repository: ${REPO_PATH}"
echo "First sync, choose one of the two: "
echo "git clone git@t725.cn:${REPO_PATH} D:\$REPO_NAME"
echo "git remote add origin git@t725.cn:${REPO_PATH} && git push -u origin master"
用户在客户端执行,完成服务器上的祼仓库创建与初始化
ssh git@server "init myproject.git"
附录
Git LFS 是什么?
Git LFS (Large File Storage) 是 Git 的一个开源扩展,专门解决 Git 不擅长管理大文件 的问题。因为 Git 原生设计面向文本代码,对二进制大文件存在严重缺陷:
- 每次修改大文件都会完整存储一份副本 → 仓库体积爆炸
clone/fetch必须下载全部历史版本 → 极慢- 无法有效 diff / merge 二进制文件
LFS 的解决方案:
原始工作流: commit → 存储完整 100MB 视频文件 × N个版本 = 仓库膨胀
LFS 工作流: commit → 存储 ~130B 指针文件(存入 Git)
→ 实际文件上传到 LFS 服务器(独立存储)
clone → 按需拉取当前版本的大文件
🔍 检查 Git LFS 是否已使用
- 检查命令是否存在
git lfs version
# ✅ 已安装: git-lfs/3.5.1 (GitHub; linux amd64; go 1.21.0)
# ❌ 未安装: git: 'lfs' is not a git command. See 'git --help'.
- 检查全局过滤器是否注册
git config --global filter.lfs.smudge
# ✅ 应输出: git-lfs smudge -- %f
git config --global filter.lfs.clean
# ✅ 应输出: git-lfs clean -- %f
⚠️ 即使
git lfs命令存在,如果过滤器未注册,LFS 也不会生效。运行git lfs install可自动注册。
- 检查当前仓库是否启用了 LFS
cd /your/repo
# 方法一:查看是否有 LFS 跟踪规则
cat .gitattributes
# ✅ 包含类似行: *.png filter=lfs diff=lfs merge=lfs -text
# 方法二:列出所有被 LFS 跟踪的文件模式
git lfs track
# ✅ 输出: Listing tracked patterns
# *.psd (.gitattributes)
# *.mp4 (.gitattributes)
# 方法三:检查预提交钩子
ls .git/hooks/pre-push
# ✅ LFS 启用时该钩子由 git lfs install 自动创建
🛠️ LFS 初始化与注意事项
# 在仓库中启用并指定跟踪模式
cd /your/repo
git lfs track "*.bin"
git lfs track "*.model"
git add .gitattributes # ⚠️ 别忘了提交这个文件!
git commit -m "chore: enable git-lfs for binary assets"
.gitattributes必须提交到仓库:否则其他协作者 clone 后 LFS 不会生效- 已有大文件需迁移:直接
git lfs track只对新提交生效,历史中的大文件需用git lfs migrate import重写历史 - 服务端支持:GitHub/GitLab/Gitea 均内置 LFS 支持;自建 Gitea 需在
app.ini中确认[lfs]段已启用 - 克隆加速:首次 clone 可用
GIT_LFS_SKIP_SMUDGE=1 git clone跳过大文件下载,后续按需git lfs pull
git lfs env 一键诊断
git lfs env 输出示例及关键检查点:
git-lfs/3.5.1 (GitHub; linux amd64; go 1.21.0)
Endpoint=https://github.com/user/repo.git/info/lfs (auth=none) ← LFS 服务端地址
LocalWorkingDir=/home/user/repo ← 本地工作目录
LocalGitDir=/home/user/repo/.git ← Git 目录
LocalMediaDir=/home/user/repo/.git/lfs/objects ← 本地缓存目录
TempDir=/tmp ← 临时目录
ConcurrentTransfers=8 ← 并发传输数
BasicTransfersOnly=false ← 是否仅用基础传输
SkipDownloadErrors=false ← 是否跳过下载错误
FetchRecentAlways=false ← 是否总是获取最近引用
FetchRecentRefsDays=7 ← 获取最近引用的天数
FetchRecentCommitsDays=0 ← 获取最近提交的天数
FetchRecentRefsIncludeRemotes=true ← 是否包含远程引用
PruneOffsetDays=3 ← 修剪偏移天数
Access=download ← 访问权限
💡 如果
Endpoint为空或报错,说明 LFS 服务端未正确配置,即使客户端安装了也无法正常工作。
发表回复
要发表评论,您必须先登录。