我这里服务器都是root权限,所以没有加入sudo,当我们在自己虚拟机的的时候,不一定是root权限,所以每条指令前面需要加入sudo
参考文章:https://blog.csdn.net/linshengxuan113/article/details/136722891
如下 Gitea版本 1.22.1
Gitea仓库 点击此处进入仓库
gitea文档
给Gitea添加一个专用的用户管理 方法一: 1. 创建一个用户,管理Gitea项目
2.给Gitea用户添加文件的写权限
使用vim编辑/etc/sudoers (如果第3步设置了别的用户名,那么这里也需要设置别的用户明)
找到User privilege specification部分,添加如下内容:
如下图所示:
方法二:
创建一个用户,管理Gitea项目
1 2 3 useradd git usermod -a -G sudo git $ sudo deluser git sudo ##删除git的sudo权限
useradd 添加一般用户
usermod -a -G sudo 将git用户添加sudo组和权限 usermod是修改账号的指令,-a代表添加或者附加,-G代表一个群组
给该用户添加密码
创建专属 SSH 用户 建议创建一个仅用于 MySQL 隧道的专用用户(避免用 root):
1 2 3 4 5 6 7 8 9 10 # 创建用户(无登录Shell,仅用于隧道) useradd -m -s /usr/sbin/nologin mysql_tunnel # 设置用户SSH密钥(本地生成密钥对,将公钥上传到该用户) mkdir -p /home/mysql_tunnel/.ssh chmod 700 /home/mysql_tunnel/.ssh # 将本地生成的公钥(id_rsa.pub)内容写入authorized_keys vim /home/mysql_tunnel/.ssh/authorized_keys chmod 600 /home/mysql_tunnel/.ssh/authorized_keys chown -R mysql_tunnel:mysql_tunnel /home/mysql_tunnel/.ssh
命令片段
含义(大白话)
useradd
Ubuntu 中创建新用户的核心命令(区别于 adduser,是更底层的非交互式命令)。
-m
自动创建该用户的家目录(路径为 /home/mysql_tunnel),不加此参数则不创建家目录。
-s /usr/sbin/nologin
指定该用户的默认登录 Shell 为 nologin,核心作用是禁止该用户登录系统 。
mysql_tunnel
要创建的用户名(自定义,这里用于和 MySQL 隧道相关的用途)。
用 usermod 命令修改(推荐,最简单) 这是修改已有用户 Shell 的标准命令,无需手动编辑配置文件。
基础命令(禁止指定用户登录) 1 sudo usermod -s /usr/sbin/nologin 用户名
-s:shell 的缩写,用于指定用户的默认登录 Shell;
/usr/sbin/nologin:系统内置的 “无登录 Shell”,尝试登录会提示 This account is currently not available.;
用 /bin/false 禁止登录(更严格) /bin/false 比 nologin 更严格,尝试登录时无任何提示,直接退出:
1 sudo usermod -s /bin/false 用户名
验证修改是否生效 执行以下命令,查看用户的 Shell 配置:
1 cat /etc/passwd | grep 用户名
输出示例(修改成功):
1 mysql_tunnel:x:1001:1001::/home/mysql_tunnel:/usr/sbin/nologin
临时启动(测试用,终端关闭则进程停止) 直接用 sudo -u 指定以 mysql_tunnel 用户运行 Gitea,无需切换登录:
1 2 3 4 5 # 基础格式(替换为你的 Gitea 实际路径) sudo -u mysql_tunnel /path/to/gitea web -c /path/to/app.ini # 示例(常见路径) sudo -u mysql_tunnel /usr/local/bin/gitea web -c /etc/gitea/app.ini
关键参数解释:
sudo -u mysql_tunnel:以 mysql_tunnel 用户的权限执行后续命令;
/path/to/gitea:Gitea 可执行文件的绝对路径(比如 /usr/local/bin/gitea);
web:Gitea 的启动模式(web 服务);
-c /path/to/app.ini:指定 Gitea 配置文件路径(比如 /etc/gitea/app.ini)。
后台运行(避免终端关闭进程停止): 1 sudo -u mysql_tunnel nohup /usr/local/bin/gitea web -c /etc/gitea/app.ini > /var/log/gitea.log 2>&1 &
nohup:忽略终端关闭信号,让进程后台运行;
> /var/log/gitea.log 2>&1:将日志输出到指定文件,方便排查问题;
&:后台运行标记。
解除用户登录到系统 1 sudo usermod -s /bin/bash 用户名
查看已存在的用户
这个命令会列出系统上所有用户的信息,包括用户名和其他相关信息。
该命令会显示每个用户的详细信息,包括用户名、密码、用户ID、组ID、用户家目录、登录Shell等
这个命令对于查找特定用户所属的用户组或特定用户组的成员非常有用,没有上面命令那么多信息
安装Gitea
Gitea 我放在 /opt/gitea 目录中; mkdir -p: 确保目录名称存在,不存在的就建一个。
修改 gitea 权限 (可读可写可执行)
切换 git 用户,进入gitea目录
下载Gitea 文件,这里使用了 sudo 需要输入密码
1 sudo wget -O gitea https://dl.gitea.com/gitea/1.22.1/gitea-1.22.1-linux-amd64
修改 Gitea 权限,添加可执行权限
mysql安装 mysql安装步骤以及问题点击此处跳转
使用 Mysql 数据库创建用户 使用 root 用户连接数据库
效果如下图所示:
创建用户 用户和密码可以更改 1 CREATE USER 'gitealib' IDENTIFIED BY 'gitealib123@';
效果如下图所示:
创建数据库 授权 刷新权限 数据库的字符集和排序规则
1 CREATE DATABASE giteadb CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_0900_as_cs';
1 GRANT ALL PRIVILEGES ON giteadb.* TO 'gitealib';
效果如下图所示:
尝试 gitealib 用户登录 1 2 mysql -u gitealib -p giteadb exit
效果如下图所示:
通过SQL查询来确认数据库文件的位置 登录到MySQL,然后执行以下命令:
1 SHOW VARIABLES LIKE 'datadir';
备份MySQL指定数据库 1 mysqldump -u root -p --databases gitea > gitea_backup.sql
输入root密码后生成备份文件 gitea_backup.sql
迁移MySQL中Gitea数据库到指定位置(待测试) 停止MySQL服务 终止MySQL进程 1 sudo systemctl stop mysql
通过 systemctl status mysql 确认服务已停止
定位原数据库存储路径 查询当前数据目录:
输入root密码进入mysql,输入以下命令查看数据目录位置
1 SHOW VARIABLES LIKE 'datadir';
默认路径为 /var/lib/mysql,目标数据库文件位于 /var/lib/mysql/gitea(查看一下该文件权限)
复制数据库文件到新位置 将目标数据库文件夹迁移到新路径(如 /new_path/mysql):
1 sudo rsync -av /var/lib/mysql/gitea /new_path/mysql/
确保所有文件(包括隐藏文件)被完整复制
删除原数据库文件(可选) 1 sudo rm -rf /var/lib/mysql/gitea
配置符号链接(关键步骤) 创建软链接指向新位置 1 sudo ln -s /new_path/mysql/gitea /var/lib/mysql/gitea
此操作使MySQL仍通过原路径访问实际存储在新位置的数据
修正权限 1 2 sudo chown -R mysql:mysql /new_path/mysql/gitea sudo chmod -R 750 /new_path/mysql/gitea
确保所有权和权限与MySQL服务匹配
重启MySQL并验证 启动服务 1 sudo systemctl start mysql
验证数据完整性
1 sudo mysql -e "SHOW DATABASES;"
确认 gitea 数据库存在。
1 sudo mysql -e "USE gitea; SHOW TABLES;"
回滚方案(可选) 若迁移后出现异常,可通过以下步骤恢复:
停止MySQL服务。
删除符号链接:
1 sudo rm /var/lib/mysql/gitea
将备份文件移回原路径:
1 sudo rsync -av /new_path/mysql/gitea /var/lib/mysql/
重启MySQL服务
远程连接mysql数据库 nginx远程连接
1 2 3 4 5 6 7 8 ``` # 修改文件夹或文件用户 ## 方式一:登入该用户,再修改权限
su yourUsers chown -R $USER:$USER FileFolderOrFile
sudo chown -R yourUsers:yourUsers FileFolderOrFile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 `yourUsers` 你要指定的用户 `FileFolderOrFile` 需要修改权限的文件夹以及文件 # `Gitea` 默认配置 ## 一般设置初始配置 我把 `Gitea` 文件放在 `opt/gitea` 目录下 ### 仓库根目录 所有远程 Git 仓库将保存到此目录
/opt/gitea/data/gitea-repositories
1 2 3 4 5 ### LFS根目录 存储为Git LFS的文件将被存储在此目录。留空禁用LFS
/opt/gitea/data/lfs
/opt/gitea/log
1 2 3 4 5 6 7 8 9 10 11 # 禁止`git`用户通过`SSH`登录服务器 ## 方法一: ### 切换到`root`用户 ```shell su root
输入用户密码进入root权限;
查看git的passwd配置
记录下来,接下来修改配置文件:
原始passwd中git的值
1 git:x:1002:1002:,,,:/home/git:/bin/bash
配置git用户禁止登录
第一种方法
1 sudo usermod -s /bin/nologin git
验证更改:
第二种方法
1 sudo usermod -s /bin/false git
通过git运行用户gitea程序 到gitea程序存放的目录当中
方法一:恢复git用户登录
打开passwd文件
恢复git用户
1 git:x:1002:1002:,,,:/home/git:/bin/bash
方法二:恢复git用户登录 1 sudo usermod -s /bin/bash git
方法二:修改PAM配置(适用于SSH登录) 如果你想要禁止通过SSH登录但允许运行程序,可以修改SSH的PAM配置文件。
编辑/etc/pam.d/sshd文件,添加或修改以下行:
1 account required pam_access.so access=deny list=git
这行配置将阻止用户git通过SSH登录。
确保你的SSH服务配置文件中也禁用了该用户的访问,例如在/etc/ssh/sshd_config中添加:
通过DenyUsers限制交互式登录权限
重启SSH服务使更改生效:
1 sudo systemctl restart ssh
Gitea app.ini 配置文件参考
使用 Nginx 作为反向代理服务 具体章节可以点击此处查看
使用 Nginx 作为 Gitea 的反向代理服务,可以参照以下 Gitea.conf 配置中 server 的 HTTP或 HTTPS 部分。
确保 client_max_body_size 足够大,否则在上传大文件时会出现 “client_max_body_size” 错误。
Ubuntu Gitea后台运行要在Ubuntu上使Gitea以后台服务的方式运行,你可以使用systemd来创建一个服务单元。以下是简化的步骤和示例代码:
创建一个systemd服务文件: 创建一个新的服务单元文件 /etc/systemd/system/gitea.service。
官网文件:参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 # Gitea version: 1.22.1 [Unit] Description=Gitea (Git with a cup of tea) After=syslog.target After=network.target [Service] #如果您有包含大量文件的存储库,并且因此收到HTTP 500错误,请取消注释下一行 # LimitNOFILE=524288:524288 #重启秒=20秒 RestartSec=20s #类型=简单 Type=simple #用户=<运行Gitea的用户> User=<运行Gitea的用户> #组=<运行Gitea的用户> Group=<运行Gitea的用户> #配置Gitea的工作 #如果您计划将 Gitea 作为 Linux 服务运行,则可以跳过此步骤,因为服务文件允许您设置WorkingDirectory。否则,请考虑(半)永久设置此环境变量,以便 Gitea 始终使用正确的工作目录。 #export GITEA_WORK_DIR=/var/lib/gitea/ #工作目录=/var/lib/gitea/ #WorkingDirectory=/opt/DATA/gitea/ #运行时目录=gitea #RuntimeDirectory=gitea ExecStart=/opt/gitea/gitea web --config /opt/gitea/custom/conf/app.ini Environment=USER=<运行Gitea的用户> HOME=/home/<运行Gitea的用户> GITEA_WORK_DIR=/opt/DATA/gitea/ #重新启动=始终 Restart=always [Install] WantedBy=multi-user.target
User=这里设置创建用户的用户 git
Environment=USER=这里设置创建用户的用户 git
Restart=always: 只要不是通过systemctl stop来停止服务,任何情况下都必须要重启服务,默认值为no
RestartSec=20: 重启间隔,比如某次异常后,等待20(s)再进行启动,默认值0.1(s)
StartLimitInterval: 无限次重启,默认是10秒内如果重启超过5次则不再重启,设置为0表示不限次数重启
<Gitea的安装路径>:这里不要省略路径,比如我的路径是/opt/gitea ; ExecStart=/opt/gitea/gitea
重新加载systemd配置: 1 sudo systemctl daemon-reload
设置Gitea服务开机自启: 1 sudo systemctl enable gitea
启动Gitea服务: 1 sudo systemctl start gitea
以上步骤会将Gitea配置为一个后台服务,并在系统启动时自动运行。
systemctl停止运行某个任务 systemctl 是一个控制 systemd 系统和服务管理器的命令行工具。要停止运行服务,可以使用 systemctl stop 命令。
停止一个名为 service_name 的服务的命令: 1 sudo systemctl stop service_name
如果你遇到问题,检查gitea服务的状态来获取更多信息: 1 sudo systemctl status gitea
如果你想禁用这个服务,使得它不会在下次启动时自动启动,可以使用 disable 命令: 1 sudo systemctl disable service_name
如果你想同时停止并禁用服务,可以使用 stop 和 disable 命令的组合: 1 2 sudo systemctl stop service_name sudo systemctl disable service_name
请确保将 service_name 替换为你想要停止的服务的实际名称。
如停止上面Gitea,将service_name替换成Gitea;
我遇到的错误情况 Gitea 无法启动提示 (code=exited, status=203/exec) 错误是因为 gitea.serive 配置不对
Gitea 数据库位置以及自动备份Gitea 数据库位置
Gitea 数据库移动到指定位置存储1 mv /var/lib/mysql/giteadb
Gitea 数据库备份以及恢复
Gitea 仓库迁移Gitea 数据库迁移
打包数据库,还要知道原始数据库位置 备份数据库:使用mysqldump工具备份数据库到一个SQL文件。
1 2 3 4 mysqldump -u 用户名 -p 数据库名 > backup.sql
Gitea 迁移Gitea 静态资源1.下载对应版本源码包 2.安装 go 编译器,
3.下载对应 gitea 源码包,对应版本源码包下载点击此处
3.在源码包中复制 public/assets 中的所有文件存储到你想要存储的位置 1 2 cp -rfp public/assets directory
directory 目标位置可以是本机,也可以是其他服务器或者存储桶,只是对应 Nginx 配置不同
4. 使用 Nginx 直接提供静态资源,nginx 配置,参考对应 Gitea 版本, 通过将资源分为静态和动态两种类型来调节性能。
CSS 文件、JavaScript 文件、图片和字体是静态内容。首页、仓库视图和工单列表是动态内容。
Nginx 可以直接提供静态资源,并且只代理动态资源请求给 Gitea。 Nginx 为提供静态内容进行了优化,而代理大响应可能与这一优化行为相反。 (见https://serverfault.com/q/587386)
将 Gitea 源代码仓库的一个快照下载到 /path/to/gitea。 在此之后,在本地仓库目录运行 make frontend 来生成静态资源。在这个情况下,我们只对 public/ 目录感兴趣,您可以删除剩下的其他目录。 (为了生成静态资源,您需要安装一个带 npm 的 Node 和 make)
取决于您的用户量的大小,您可以将流量分离到两个不同的服务器,或者为静态资源配置一个 cdn。
1.单服务器节点,单域名 将 [server] STATIC_URL_PREFIX = /_/static/ 写入您的 Gitea 配置文件,并配置 nginx:
1 2 3 4 5 6 7 8 9 10 11 12 server { listen 80; server_name git.example.com; location /_/static/ { alias /path/to/gitea/public/; } location / { proxy_pass http://localhost:3000; } }
/path/to 替换成 gitea 存放的目录,例如 gitea 存放在 /mnt 路径下,那么 /path/to 替换成 /mnt;
2.双服务器节点,双域名 将 [server] STATIC_URL_PREFIX = http://cdn.example.com/gitea 写入您的 Gitea 配置文件,并配置 nginx:
使用非服务器提供静态资源 如果可以,最好使用服务器提供静态资源,
5.重新加载和重启 Nginx
添加自定义 Gitea页面[自选] 参考此处
界面链接到gitea中,先创建到如下目录
1 custom/templates/custom/
该目录下放置的模板如下,在这些文件中添加链接到自定义界面
1 2 3 4 5 6 7 8 9 header.tmpl body_inner_pre.tmpl body_inner_post.tmpl body_outer_post.tmpl body_outer_pre.tmpl extra_links.tmpl extra_links_footer.tmpl extra_tabs.tmpl footer.tmpl
header.tmpl,在 <head> 标记结束之前的模板,例如添加自定义CSS文件
body_outer_pre.tmpl,在 <body> 标记开始处的模板
body_inner_pre.tmpl,在顶部导航栏之前,但在主 container 内部的模板,例如添加一个 <div class="full height">
body_inner_post.tmpl,在主 container 结束处的模板
body_outer_post.tmpl,在底部 <footer> 元素之前.
footer.tmpl,在 <body> 标签结束处的模板,可以在这里填写一些附加的 Javascript 脚本。
添加自定义界面放置的目录:custom/public/assets/
修改 Gitea已存在的页面[自选] 修改后存放在Gitea的/custom/templates目录下,示例:home.tmpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 {{template "base/head" .}} <div role ="main" aria-label ="{{if .IsSigned}}{{ctx.Locale.Tr " dashboard "}}{{else }}{{ctx.Locale.Tr "home "}}{{end }}" class ="page-content home" > <div class ="auto-update-bar" > <img src ="https://imageblog.goupos.cn/icon/%E7%BD%91%E7%AB%99%E5%9B%BE%E6%A0%87.png" class ="site-icon" alt ="ICON" > <a href ="mailto:xgmcreate@foxmail.com" class ="admin-contact" > {{svg "octicon-mail"}} 管理员邮箱 </a > </div > <div class ="tw-mb-8 tw-px-8" > <div class ="center" > <img class ="logo" width ="220" height ="220" src ="{{AssetUrlPrefix}}/img/logo.svg" alt ="{{ctx.Locale.Tr " logo "}}"> <div class ="hero" > <h1 class ="ui icon header title" > {{AppName}}</h1 > <h2 > {{ctx.Locale.Tr "注册请通过邮箱联系管理员"}}</h2 > </div > </div > </div > <div class ="autonomous-panel" > <div class ="legal-info" > <div class ="icp-record" > {{svg "octicon-law"}} <a href ="https://beian.miit.gov.cn" target ="_blank" > 湘 ICP 备 号 - 1 </a > </div > <div class ="police-record" > <a href ="https://imageblog.goupos.cn/icon/beian_icon.png" target ="_blank" > <img src ="https://imageblog.goupos.cn/icon/beian_icon.png" width ="20" height ="20" alt ="国徽图标" > </a > <a href ="https://beian.mps.gov.cn/#/query/webSearch?code=xxxx" target ="_blank" > 粤公网安备 xxxx 号 </a > </div > </div > </div > <div class ="realtime-dashboard" > <div class ="info-grid" > <div class ="info-card clock-card" > <div class ="card-header" > {{svg "octicon-clock" 32}} <h3 > 本地时间</h3 > </div > <div class ="time-date-group" > <div id ="live-clock" class ="time-display" > 00:00:00</div > <div id ="live-date" class ="date-display" > 2023年1月1日</div > </div > <div class ="rest-hint" > <span > 连续工作<strong id ="usage-time" > 0</strong > 分钟</span > </div > </div > <div class ="info-card clock-card" > <div class ="card-header" > {{svg "octicon-clock" 32}} <h3 > 本地时间</h3 > </div > <div id ="live-clock" class ="time-display" > 00:00:00</div > <div class ="date-display" id ="live-date" > 2023年1月1日</div > <div class ="rest-hint" > 每50分钟建议休息10分钟</div > </div > <div class ="info-card clock-card" > <div class ="card-header" > {{svg "octicon-clock" 32}} <h3 > 本地时间</h3 > </div > <div id ="live-clock" class ="time-display" > 00:00:00</div > <div class ="date-display" id ="live-date" > 2023年1月1日</div > <div class ="rest-hint" > 每50分钟建议休息10分钟</div > </div > </div > </div > <div id ="auto-alert" class ="rest-alarm" > <div class ="alarm-content" > {{svg "octicon-alert" 24}} <div class ="text-group" > <span > 界面保持<strong id ="Continuous-use-time" > 0</strong > 分钟</span > <span class ="subtext" > 建议休息时间</span > </div > <div class ="countdown" id ="rest-countdown" > 10:00</div > </div > </div > {{template "base/footer" .}} <script > (() => { const formatTime = (date ) => date.toLocaleString ('zh-CN' , { year : 'numeric' , month : 'long' , day : 'numeric' , weekday : 'long' , hour : '2-digit' , minute : '2-digit' , second : '2-digit' , hour12 : false }).split (' ' ); const state = { workStart : Date .now (), uiStart : Date .now (), alertTimer : null , isWorking : true , elements : { clock : document .getElementById ('live-clock' ), date : document .getElementById ('live-date' ), usageTime : document .getElementById ('usage-time' ), alertBox : document .getElementById ('auto-alert' ), countdown : document .getElementById ('rest-countdown' ), continuousUseTime : document .getElementById ('Continuous-use-time' ) } }; const getMinutesDiff = (startTime ) => { return Math .floor ((Date .now () - startTime) / 60000 ); }; function startCountdown (seconds ) { clearInterval (state.alertTimer ); let countdownEnd = Date .now () + seconds * 1000 ; const update = ( ) => { const remaining = Math .max (0 , Math .ceil ((countdownEnd - Date .now ()) / 1000 )); state.elements .countdown .textContent = `${String (Math .floor(remaining/60 )).padStart(2 ,'0' )} :` + `${String (remaining%60 ).padStart(2 ,'0' )} ` ; if (remaining <= 0 ) { clearInterval (state.alertTimer ); state.alertTimer = null ; state.elements .alertBox .style .display = 'none' ; state.isWorking = true ; state.workStart = Date .now (); } }; state.alertTimer = setInterval (update, 500 ); update (); } function updateDisplay ( ) { const now = new Date (); const [dateStr, timeStr] = formatTime (now); state.elements .clock .textContent = timeStr; state.elements .date .textContent = dateStr; const workMinutes = getMinutesDiff (state.workStart ); const uiMinutes = getMinutesDiff (state.uiStart ); if (state.isWorking ) { state.elements .usageTime .textContent = workMinutes; } state.elements .continuousUseTime .textContent = uiMinutes; if (workMinutes >= 50 && !state.alertTimer ) { state.isWorking = false ; state.elements .alertBox .style .display = 'flex' ; startCountdown (600 ); } } function init ( ) { setInterval (updateDisplay, 1000 ); updateDisplay (); } window .__WORK_TIMER = { reset : () => { state.workStart = Date .now (); state.uiStart = Date .now (); state.isWorking = true ; clearInterval (state.alertTimer ); state.elements .alertBox .style .display = 'none' ; updateDisplay (); } }; init (); })(); </script > <style > .auto-update-bar { position : fixed; top : 10px ; right : 80px ; display : flex; align-items : center; gap : 15px ; z-index : 999 ; } .site-icon { width : 32px ; height : 32px ; border-radius : 6px ; box-shadow : 0 2px 8px rgba (0 ,0 ,0 ,0.1 ); } .admin-contact { display : flex; align-items : center; gap : 8px ; padding : 8px 16px ; background : rgba (255 ,255 ,255 ,0.9 ); border-radius : 25px ; box-shadow : 0 3px 6px rgba (0 ,0 ,0 ,0.1 ); color : #0366d6 ; } .autonomous-panel { max-width : 800px ; margin : 40px auto; padding : 0 20px ; } .legal-info { display : flex; justify-content : center; gap : 30px ; margin : 30px 0 ; font-size : 0.9em ; } .icp-record , .police-record { display : flex; align-items : center; gap : 8px ; //color : #666 ; } .rest-alarm { display : none; position : fixed; bottom : 100px ; left : 50% ; transform : translateX (-50% ); background : #fff3f3 ; border : 2px solid #ff647c ; border-radius : 12px ; padding : 16px 24px ; animation : pulse 1.5s infinite; } .alarm-content { display : flex; align-items : center; gap : 12px ; color : #d32f2f ; } .countdown { background : #ff647c ; color : white; padding : 4px 12px ; border-radius : 8px ; font-weight : bold; } @keyframes pulse { 0% { transform : translateX (-50% ) scale (1 ); } 50% { transform : translateX (-50% ) scale (1.05 ); } 100% { transform : translateX (-50% ) scale (1 ); } } .realtime-dashboard { max-width : 1200px ; margin : 40px auto; padding : 0 20px ; } .info-grid { display : grid; grid-template-columns : repeat (auto-fit, minmax (300px , 1 fr)); gap : 25px ; } .info-card { background : #fff ; border-radius : 12px ; padding : 20px ; box-shadow : 0 3px 6px rgba (0 ,0 ,0 ,0.1 ); transition : transform 0.2s ; } .info-card :hover { transform : translateY (-5px ); } .card-header { display : flex; align-items : center; gap : 10px ; margin-bottom : 15px ; } .time-date-group { display : flex; flex-direction : column; align-items : center; margin : 1.2rem 0 ; padding : 1rem ; background : linear-gradient (135deg , #f8f9fa 0% , #f1f3f5 100% ); border-radius : 16px ; box-shadow : inset 2px 2px 4px rgba (0 ,0 ,0 ,0.05 ); } .time-display { font : 600 3.2rem 'Roboto Mono' , monospace; color : #2b2d42 ; text-shadow : 1px 1px 2px rgba (0 ,0 ,0 ,0.1 ); letter-spacing : -2px ; position : relative; padding : 0 1rem ; } .time-display ::after { content : '' ; position : absolute; bottom : -8px ; left : 50% ; width : 85% ; height : 2px ; background : #e9ecef ; transform : translateX (-50% ); } .date-display { font : 500 1.1rem 'Segoe UI' , system-ui; color : #6c757d ; margin-top : 1.8rem ; display : flex; gap : 0.8rem ; align-items : center; } .date-display ::before { content : "📅" ; font-size : 0.9em ; opacity : 0.8 ; } .rest-hint { font-size : 0.95rem ; color : #868e96 ; margin-top : 2rem ; display : flex; align-items : center; gap : 0.6rem ; } .rest-hint strong { font : 600 1.4em 'Fira Sans' , sans-serif; color : #d62828 ; min-width : 2.2em ; display : inline-block; text-align : center; } } </style >
添加链接和页签 如果您只是想添加额外的链接到顶部导航栏或额外的选项卡到存储库视图,您可以将它们放在您 custom/templates/custom/ 目录下的 extra_links.tmpl 和 extra_tabs.tmpl 文件中。
举例说明:假设您需要在网站放置一个静态的“关于”页面,您只需将该页面放在您的 “custom/public/“目录下(比如 custom/public/impressum.html)并且将它与 custom/templates/custom/extra_links.tmpl 链接起来即可。
这个链接应当使用一个名为“item”的 class 来匹配当前样式,您可以使用 {{AppSubUrl}} 来获取 base URL: <a class="item" href="{{AppSubUrl}}/assets/impressum.html">Impressum</a>
同理,您可以将页签添加到 extra_tabs.tmpl 中,使用同样的方式来添加页签。它的具体样式需要与 templates/repo/header.tmpl 中已有的其他选项卡的样式匹配 (source in GitHub )