Ubuntu搭建Gitea服务器

我这里服务器都是root权限,所以没有加入sudo,当我们在自己虚拟机的的时候,不一定是root权限,所以每条指令前面需要加入sudo

参考文章:https://blog.csdn.net/linshengxuan113/article/details/136722891

如下 Gitea版本 1.22.1

给Gitea添加一个专用的用户管理

方法一:

1. 创建一个用户,管理Gitea项目

1
adduser git

2.给Gitea用户添加文件的写权限

  1. 使用vim编辑/etc/sudoers (如果第3步设置了别的用户名,那么这里也需要设置别的用户明)
1
vim /etc/sudoers
  1. 找到User privilege specification部分,添加如下内容:
1
git    ALL=(ALL:ALL) ALL

如下图所示:
ubuntu添加用户.png

方法二:

  1. 创建一个用户,管理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代表一个群组

  1. 给该用户添加密码
1
2
----

查看已存在的用户

  1. 这个命令会列出系统上所有用户的信息,包括用户名和其他相关信息。
1
getent passwd
1
cat /etc/passwd

该命令会显示每个用户的详细信息,包括用户名、密码、用户ID、组ID、用户家目录、登录Shell等

  1. 这个命令对于查找特定用户所属的用户组或特定用户组的成员非常有用,没有上面命令那么多信息
1
getent group

安装Gitea

  1. Gitea 我放在 /opt/gitea 目录中; mkdir -p: 确保目录名称存在,不存在的就建一个。
1
mkdir -p /opt/gitea
1
cd /opt
  1. 修改 gitea 权限 (可读可写可执行)
1
chmod 777 ./gitea
  1. 切换 git 用户,进入gitea目录
1
su git
1
cd gitea
  1. 下载Gitea 文件,这里使用了 sudo 需要输入密码
1
sudo wget -O gitea https://dl.gitea.com/gitea/1.22.1/gitea-1.22.1-linux-amd64
  1. 修改 Gitea 权限,添加可执行权限
1
sudo chmod +x gitea

Mysql安装以及检查

安装依赖

1
2
wget http://repo.mysql.com/mysql-apt-config_0.8.32-1_all.deb
apt-get install ./mysql-apt-config_0.8.32-1_all.deb

会进入如下界面,可以通过上下 ↑↓ 控制光标,通过 TAB 选中,回车确认。

Ubuntu操作步骤显示具体操作

图中上下移动到 MySQL Server & Cluster (Currently selected: mysql-8.4-lts) 通过 TAB 选中,光标会自动到 OK 上,点击回车,到下一步按照图片操作

上下移动到 MySQL Server & Cluster (Currently selected: mysql-8.4-lts) 通过 TAB 选中,光标会自动到 OK 上,点击回车

Ubuntu操作步骤显示具体操作

上下移动到 mysql-8.4-lts 通过 TAB 选中,光标会自动到 OK 上,点击回车

Ubuntu操作步骤显示具体操作

上下移动到 OK 通过 TAB 选中,光标会自动到 OK 上,点击回车

Ubuntu操作步骤显示具体操作

执行更新程序

1
2
3
4
sudo apt-get upgrade
sudo apt-get update
sudo apt upgrade
sudo apt update

当执行更新指令后,可能会出现如下情况

1
2
3
E: 无法下载 http://repo.mysql.com/apt/ubuntu/dists/noble/InRelease  明文签署文件不可用,结果为‘NOSPLIT’(您的网络需要认证吗?)
E: 仓库 “http://repo.mysql.com/apt/ubuntu noble InRelease” 的签名不再生效。
N: 无法安全地用该源进行更新,所以默认禁用该源。

mysql 授权码 ; 获取并添加缺失的GPG密钥,可以试试如下密钥

1
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys B7B3B788A8D3785C
1
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32

安装 mysql

1
sudo apt install mysql-server -y
  1. 安装会出现如图,需要你输入一个 root 密码,这个密码后面要使用,要记下来,通过上下移动光标选择确认,然后回车。这里需要输入两次密码。

输入密码前

Ubuntu安装mysql-server

输入密码后

Ubuntu安装mysql-server
  1. 会提示让你重新输入密码,输入密码后,按下上下按键到确认,按下回车
Ubuntu安装mysql-server
  1. 如下界面按 TAB 按键,再按回车按键
Ubuntu安装mysql-server
 4. 如下图所示,选择身份验证,加密方式。
Ubuntu安装mysql-server
  1. 接下来等待安装完成即可,Mysql 就安装完成了。

检查 Mysql 是否安装成功

1
sudo systemctl status mysql

安装成功照片

Ubuntu安装mysql-server成功

使用 Mysql 数据库创建用户

使用 root 用户连接数据库

1
sudo mysql -u root -p

效果如下图所示:

Ubuntu使用 toot 用户连接数据库

创建用户 用户和密码可以更改

1
CREATE USER 'gitealib' IDENTIFIED BY 'gitealib123@';

效果如下图所示:

Ubuntu创建用户 用户和密码

创建数据库 授权 刷新权限

1
2
3
4
CREATE DATABASE giteadb CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_unicode_ci';
GRANT ALL PRIVILEGES ON giteadb.* TO 'gitealib';
FLUSH PRIVILEGES;
exit

效果如下图所示:

Ubuntu mysql数据库 授权 刷新权限

尝试 gitealib 用户登录

1
2
mysql -u gitealib -p giteadb
exit

效果如下图所示:

Ubuntu尝试 gitealib 用户登录

通过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 确认服务已停止‌

定位原数据库存储路径

查询当前数据目录:

1
sudo mysql -u root -p 

输入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;"

回滚方案(可选)

若迁移后出现异常,可通过以下步骤恢复:

  1. 停止MySQL服务。
  2. 删除符号链接:
1
sudo rm /var/lib/mysql/gitea
  1. 将备份文件移回原路径:
1
sudo rsync -av /new_path/mysql/gitea /var/lib/mysql/
  1. 重启MySQL服务‌

修改文件夹或文件用户

方式一:登入该用户,再修改权限

1
2
su yourUsers
chown -R $USER:$USER FileFolderOrFile

方式二:直接修改

1
sudo chown -R yourUsers:yourUsers FileFolderOrFile

yourUsers 你要指定的用户

FileFolderOrFile 需要修改权限的文件夹以及文件

Gitea 默认配置

一般设置初始配置

我把 Gitea 文件放在 opt/gitea 目录下

仓库根目录

所有远程 Git 仓库将保存到此目录

1
/opt/gitea/data/gitea-repositories

LFS根目录

存储为Git LFS的文件将被存储在此目录。留空禁用LFS

1
/opt/gitea/data/lfs

日志路径

日志文件将写入此目录。

1
/opt/gitea/log

禁止git用户通过SSH登录服务器

方法一:

切换到root用户

1
su root

输入用户密码进入root权限;

查看gitpasswd配置

1
getent passwd

记录下来,接下来修改配置文件:

原始passwdgit的值

1
git:x:1002:1002:,,,:/home/git:/bin/bash

配置git用户禁止登录

  1. 第一种方法
1
sudo usermod -s /bin/nologin git

验证更改:

1
getent passwd git
  1. 第二种方法
1
sudo usermod -s /bin/false git

通过git运行用户gitea程序

到gitea程序存放的目录当中

1
sudo -u git ./gitea web

方法一:恢复git用户登录

  1. 打开passwd文件
1
nano nano /etc/passwd
  1. 恢复git用户
1
git:x:1002:1002:,,,:/home/git:/bin/bash

方法二:恢复git用户登录

1
sudo usermod -s /bin/bash git

方法二:修改PAM配置(适用于SSH登录)

如果你想要禁止通过SSH登录但允许运行程序,可以修改SSH的PAM配置文件。

  1. 编辑/etc/pam.d/sshd文件,添加或修改以下行:
1
account    required     pam_access.so access=deny list=git

这行配置将阻止用户git通过SSH登录。

  1. 确保你的SSH服务配置文件中也禁用了该用户的访问,例如在/etc/ssh/sshd_config中添加:
1
DenyUsers git

通过DenyUsers限制交互式登录权限

  1. 重启SSH服务使更改生效:
1
sudo systemctl restart ssh

Gitea app.ini 配置文件

参考

1
2
3
4
5
6
7
8
---







使用 Nginx 作为反向代理服务

具体章节可以点击此处查看

使用 Nginx 作为 Gitea 的反向代理服务,可以参照以下 Gitea.conf 配置中 serverHTTPHTTPS 部分。

确保 client_max_body_size 足够大,否则在上传大文件时会出现 “client_max_body_size” 错误。

1
---

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表示不限次数重启

    1
    StartLimitInterval = 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

如果你想同时停止并禁用服务,可以使用 stopdisable 命令的组合:

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 数据库位置

1
/var/lib/mysql

Gitea 数据库移动到指定位置存储

1
mv /var/lib/mysql/giteadb 

Gitea 数据库备份以及恢复

1
---

Gitea 仓库迁移

Gitea 数据库迁移

  1. 打包数据库,还要知道原始数据库位置
    备份数据库:使用mysqldump工具备份数据库到一个SQL文件。
1
2
3
4
mysqldump -u 用户名 -p 数据库名 > backup.sql



Gitea 迁移

Gitea 静态资源

1.下载对应版本源码包

2.安装 go 编译器,

1
---

3.下载对应 gitea 源码包,对应版本源码包下载点击此处

1
---

3.在源码包中复制 public/assets 中的所有文件存储到你想要存储的位置

1
2
cp -rfp public/assets directory

directory 目标位置可以是本机,也可以是其他服务器或者存储桶,只是对应 Nginx 配置不同

4. 使用 Nginx 直接提供静态资源,nginx 配置,参考对应 Gitea 版本,

通过将资源分为静态和动态两种类型来调节性能。

CSS 文件、JavaScript 文件、图片和字体是静态内容。首页、仓库视图和工单列表是动态内容。

Nginx 可以直接提供静态资源,并且只代理动态资源请求给 GiteaNginx 为提供静态内容进行了优化,而代理大响应可能与这一优化行为相反。 (见https://serverfault.com/q/587386)

将 Gitea 源代码仓库的一个快照下载到 /path/to/gitea。 在此之后,在本地仓库目录运行 make frontend 来生成静态资源。在这个情况下,我们只对 public/ 目录感兴趣,您可以删除剩下的其他目录。 (为了生成静态资源,您需要安装一个带 npm 的 Node make

取决于您的用户量的大小,您可以将流量分离到两个不同的服务器,或者为静态资源配置一个 cdn。

1
2
---

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:

使用非服务器提供静态资源

如果可以,最好使用服务器提供静态资源,

1
---

5.重新加载和重启 Nginx

1
sudo nginx -s reload

添加自定义 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">
<!--{{svg "octicon-shield"}}-->
<!-- 新增国徽图标链接 -->
<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="ui three column stackable grid info-cards">-->
<!-- 实时信息显示区域 -->
<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>
<!-- </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, // 是否处于工作状态

// DOM元素引用
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);

// 精准工作时间累计(仅在isWorking状态累加)
if(state.isWorking) {
state.elements.usageTime.textContent = workMinutes;
}
// 更新显示数值
state.elements.continuousUseTime.textContent = uiMinutes;

// 触发提醒条件(50分钟工作后)
if (workMinutes >= 50 && !state.alertTimer) {
state.isWorking = false;
state.elements.alertBox.style.display = 'flex';
startCountdown(600); // 10分钟倒计时
}
}

// 初始化定时器
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, 1fr));
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.tmplextra_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)