序章
中文 | English
下文将用“本书”来代指“本手册”。
声明
对于普通用户来说,本项目自身没有任何价值。
本书亦是如此。
这里没有轻小说中“剑与魔法”的奇幻异世界冒险历程, 更没有《诗经》中“堕山乔岳,允犹翕河”般辽阔壮美的风景。
有的只是无聊透顶的内容。
对于非普通用户来说,它的价值主要取决于使用场景。
在本章的“使用场景”中,我们将简单介绍相关内容。
若在同一场景下,存在更优的替代品,那么您不妨敞开胸怀,给本项目多提几个 issues。
或许有一天,世界会变得更加美好呢!
除了 android 外,对于某些功能或工具,本项目的开发者若未将其打成包(例如 deb 包),并且未在本书中对其进行详细解析,那么只建议您在容器中使用。
对于 android, 请翻阅 “容器/android”。
old-version 的内容会被新版所替代,新版将会放在 dev 分支。
Trust us. 未来会更好的!
如何阅读这本书
在阅读本书时,您需要了解的内容。
本页面由 mdbook 生成。
- 左上角的三条杠 "≡"
- 点击三条杠打开目录
- 页面最下方的评论区
- 您可以使用 github 帐号登录,您发表的内容将与 "github discussions" 保持同步。
- 同理,如果您在 discussions 中找到当前章节的讨论页面,并在里面发表内容,那么相关内容也会同步到下方的评论区。
- 左上角的画笔 "🖌️"
- 先点击画笔,再选择主题,最后完成切换
- 左下角的 "<" 符号
- 点击 "<" 跳转到上一页
- 右下角的 ">" 符号
- 点击 ">" 跳转到下一页
如果您的网页(屏幕)显示空间足够宽,那么"<" 和 ">" 将位于中间,而不是下方。
使用场景
本项目存在的意义:
- 在合适的场景下,您使用本项目去做一些有趣或有意义的事情。
对您来说有意义的事情,对本项目而言,亦是如此。
You can do something interesting or meaningful.
有意义与否
有意义与否并非如 bool
类型那般非 true
即 false
。
它是相对的,而非绝对。
在这里我们并不想深入去探求哲学问题,简而言之,这个问题的答案因人而异,没有绝对的标准。
问题:什么是意义不大的事情呢?
假设存在以下两个场景:
- 1.您在 arm64 设备上模拟 x64 环境,然后在上面打!游!戏!
- 2.您在 arm64 设备上远程连接到 x64 windows 设备,然后运行 windows x64 平台的游戏。
前者花了一小时,而后者花了五分钟。
主观回答 1: 前者可能是有趣的,但是意义相较于后者而言,可能没有那么大。
时间是很宝贵的,我希望大家能把时间花在更有意义的事情上,而不是浪费时间。
主观回答 2: 我既没有电脑,也租不起 x64 虚拟专用服务器,更玩不起云游戏,在手机上体验 windows 游戏让我感受到了快乐,我认为这是值得的,并且是有意义的。
在下文中,我们将会假设几个场景,您可以对其进行评价,判断其是否有意义。
android 、图书馆与 LaTex
- 地点:图书馆
- 设备:android 手机/平板 (无 root)
- 条件:无网络,或网络状态不佳 (网速很慢)
- 描述:您在图书馆里,带着 android 手机/平板,在离线环境下,运行 gnome + LaTex 环境(texlive-full) + LaTex 编辑器,在上面用 LaTex 编辑器写文章/排版。
iOS、旅馆与 manjaro+goland
- 地点:旅馆、酒店、餐厅、银行或电信营业厅(等网络良好的场所)
- 设备:iPhone/iPad (或其他带有浏览器的设备)
- 条件:网络环境优秀(至少要良好)
- 描述:您出门在外,只带了 ios 设备。可是您做梦都想要用
idea
,clion
和goland
。
github 的 codespace (在线版 vscode) 可以运行不同的环境,于是您将 tmoe 的 gui 容器直接作为 codespace 的 devcontainer。 在上面跑 gui (xfce), 再跑 goland。
在 vscode 上跑 jetbrains goland, 这何尝不是一种 PV 呢?关于 PV 的说明,详见本章的“题外话”。
- 教程:
- 说明:
- 截至 2022-06-15, github 官方并没有提供基于 manjaro 的 xfce 环境(容器镜像)。
- 尽管 github 的 codespace (vscode)插件自带了生成配置的功能,但是之后本项目开发者可能会写个类似功能的小工具。从而让大家更省心一点。
- 准备:
- 您拥有一个支持 codespace 的 github 账号
- 若显示区域不够宽,则您可能需要将浏览器(如 safari)切换为桌面版网站(视图)
- 开始:
- 成功连接到 codespace 后,打开 vscode 内置终端,并在项目目录下执行以下操作
- 说明:
mkdir -p .devcontainer
cd .devcontainer
cat >devcontainer.json<<-'EOFJSON'
// For format details, see https://aka.ms/devcontainer.json.
{
"name": "Manjaro",
"dockerFile": "Dockerfile",
"runArgs": [
"--cap-add=SYS_PTRACE",
"--security-opt",
"seccomp=unconfined"
],
// "mounts": [
// "source=dind-var-lib-docker,target=/var/lib/docker,type=volume"
// ],
"mounts": [
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
],
"overrideCommand": false,
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
// "MS-CEINTL.vscode-language-pack-zh-hans",
"ms-azuretools.vscode-docker"
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
5902
],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "docker --version",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
// "build": {
// "args": {
// "ENABLE_NONROOT_DOCKER": "false"
// }
// },
"remoteUser": "ddk"
}
EOFJSON
cat > Dockerfile<<-'EOFDKF'
# syntax=docker/dockerfile:1
#---------------------------
# FROM cake233/manjaro-zsh-amd64
FROM cake233/manjaro-xfce-amd64
# set username & group
ARG USERNAME=ddk
ARG GROUPNAME=ddk
# ARG USER_UID=1001
# ARG USER_GID=$USER_UID
# rm cn mirrorlist
RUN sed -e '/bfsu.edu.cn/d' \
-e '/tuna.tsinghua.edu.cn/d' \
-e '/opentuna.cn/d' \
-i /etc/pacman.conf
# install dependencies
# live server: https://docs.microsoft.com/en-us/visualstudio/liveshare/reference/linux#install-linux-prerequisites
RUN pacman -Syu \
--noconfirm \
--needed \
base \
base-devel \
git \
lib32-gcc-libs \
lib32-glibc \
gcr \
liburcu \
openssl-1.0 \
krb5 \
icu \
zlib \
gnome-keyring \
libsecret \
desktop-file-utils \
xorg-xprop \
xdg-utils
# locale: Chinese Simplified (China)
ENV LANG=zh_CN.UTF-8
# add new user
RUN groupadd --force ${GROUPNAME} \
&& useradd --create-home --gid ${GROUPNAME} ${USERNAME} \
&& mkdir -p /etc/sudoers.d \
&& echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/ddk \
&& chmod 400 /etc/sudoers.d/ddk
WORKDIR ["/home/$USERNAME"]
# clean cache
RUN yes | pacman -Scc; \
rm -rf /var/cache/pacman/pkg/* \
/tmp/* \
2>/dev/null
# command: sleep infinity
CMD [ "sleep", "inf" ]
EOFDKF
rebuild:
- 1.按下 F1 或 Ctrl+Shift+P 或 cmd+shift+p
- 2.搜索 rebuild
- 3.选择 Codespaces: Rebuild container
您也可以手动选择“远程资源管理器”,再选择 Codespace, 最后点击 rebuild container 的 图标。
ddk
可以修改为其他用户名。 关于上述命名的来源,详见“题外话”
关于 vnc
进入了 codespace 环境后,在内置终端里执行以下操作
- 运行
tmoe
- 先选择语言环境,再选择 tools
- 接着选 software, 然后选 dev
- 安装 goland, clion 或其他 IDEs
- 退出 tools
由于此容器镜像已经预装了 xfce, 因此您无需重复安装。
对于网页连接的 codespace:
- 运行
novnc
,设置密码 - 打开端口转发处的
36080
对应的 local address
对于本地 vscode 连接的 codespace:
- 运行
startvnc
- 打开 vnc 客户端,输入本地 vnc 地址(默认是 127.0.0.1:5902)。
其他场景
序章中描述的内容是有限的,更多内容分布于本书的其他章节。
对于其他场景,例如:您想要使用 github actions 来编译不同架构的软件,那么可以去看看 “容器/docker”。
题外话
注意:
题外话对您来说,可能是没有意义的,甚至有可能会引起您的反感。
开发者建议您打开目录,并跳转至其他章节。
Q: 为什么是 ddk
A: 有个叫 ddk
的用户,给本项目提了个与 codespace
相关的 issue, 于是他就“青史留名”了。
( ̄ ▽  ̄)
其实非本项目相关的 Issue 发在 discussions 里会更好。
Q: 什么是 PV
A: 这是理想气体状态方程。
PV=nTR
- 解析:
- P: 压强
- V: 气体体积
- n: 物质的量
- T: 热力学温度
- R: 气体常数
您可能不知道的冷知识:
2moe 之前在某个漫画网站上,看到过一本漫画,它的标题是 《wopd ybww bwpy qmbw PV levl, soyi yeyc PV qmbwde nvpgyz》
哎呀,这孩子物理应该学得还不错吧!
这只是标题吸引人而已,好孩子不要去看。
仓库
Welcome to TMOE.
在了解完本项目的应用场景后,不知您是否会对本项目感兴趣呢?
在本章中,我们将了解以下内容。
- 添加仓库
- 通过软件仓库来安装本项目自身,以及其他的工具。
- 镜像源
- 为世界上大多数国家和地区的 debian/ubuntu 用户提供更优秀的服务。
添加仓库
1. 快速上手
注意:
目前,尽管本项目相关仓库已经搭建完成,但是绝大多数软件包仍处于未完成状态。
之后,本项目的相关包名将可能会被命名为 tmoe-2021
, tmm
和 tmoe
1.1. apt
1.1.1. debian
- 系统要求
- 支持
- Debian GNU/Linux
- 目前支持的最低版本为 Debian 9 Stretch
- 由于 Stretch 已经 EOL, 之后最低版本可能会升级至 Buster
- Debian GNU/Linux
- 不支持
- Debian GNU/Hurd 和 kFreeBSD
- 支持
su -c "apt update; apt install doas curl"
su -c "echo 'permit nopass $(id -un) cmd apt' >> /etc/doas.conf"
curl -LO https://l.tmoe.me/neko.deb
doas apt install ./neko.deb
rm -v neko.deb
doas apt update
doas apt install toy-repo
在题外话中,本项目开发者将介绍:为什么是 OpenBSD
doas
而非sudo
1.1.2. ubuntu
sudo apt update
sudo apt install -y wget
wget l.tmoe.me/neko.deb
sudo apt install ./neko.deb
rm -v neko.deb
sudo apt update
sudo apt install uuu-repo
1.1.3. termux
File: termux.svg
License: CC BY-SA 4.0
Origin: Termux.com - https://termux.com/
curl -LO l.tmoe.me/tinor.deb
apt install ./tinor.deb
apt update
1.2. pacman
开发者还在咕咕咕中 ...
2. Debian-based (ubuntu, mint, kali)
从理论上说,您不应该混用 debian 和 ubuntu 的软件源。
因为这会破坏系统的依赖关系。
您如果之前一直都是这么干的,那么需要多了解一下 debian。
您可以阅读 debian 参考手册(Osamu Aoki 青木修 著)。
2.1. 通用
那么问题来了。
既然混用源的问题这么严重,那为什么本项目还是有通用仓库呢?
答案是:
- 静态编译
- 低版本依赖
- 以及 "all" 架构的软件包。
实际上,对于不能通用的包来说,开发者会为它们建立单独的仓库。
对于 debian-based 发行版来说,本项目通用的仓库名为 "neko"。
2.1.1. 详细说明
-
要求:
- 系统版本: debian 9 (stretch) & ubuntu 16.04(xenial)
- 依赖:
apt-transport-https
,ca-certificates
- 依赖:
- 系统版本:debian 10 (buster), ubuntu 18.04 (bionic) 及其以上的系统
- 依赖:
ca-certificates
- 依赖:
- 系统版本: debian 9 (stretch) & ubuntu 16.04(xenial)
以下例子将使用 curl
您也可以换用其他下载工具,例如 wget2
或 aria2
以下内容可以直接编辑
apt update
apt install -y curl
# 下载并安装 tmoe 的 neko 仓库
curl -LO https://l.tmoe.me/neko.deb
apt install ./neko.deb
# 这个 deb 包对您的系统做了什么呢?
# 在 /etc/apt/sources.list.d/ 目录下创建了源列表:
# neko-repo.sources (权限为644,所属为0:0)
# 在本书的换源部分将介绍这种格式
# 还有 OpenPGP(GnuPG) 公钥:
# /usr/share/keyrings/tmoe-archive-keyring.gpg (权限同上)
# 并且在 /etc/apt/preferences.d/50-neko-repository 中调整了软件包优先级
# 注:此 deb 包不依赖 gnupg(完整版), 只需要 apt 自身依赖的 gpgv(简化版gnupg) 就可以了
# 删除 deb 包
rm -fv ./neko.deb
# 更新索引信息
apt update
2.1.2. neko-repo 详情
apt show neko-repo
Package: neko-repo
Version: 0.0.1-12
Priority: standard
Section: misc
Maintainer: Moe Master <m@tmoe.me>
Installed-Size: 30.7 kB
Provides: set-src-list, update-neko-key
Depends: apt (>= 1.1.0)
Recommends: ca-certificates
Suggests: curl | wget
Homepage: https://packages.tmoe.me/deb/dists/neko/
Download-Size: 5,232 B
APT-Manual-Installed: yes
APT-Sources: https://packages.tmoe.me/deb neko/main amd64 Packages
Description: Neko repository, QwQ
It includes some useful packages.
The minimum supported version of this repository is debian 9 (Stretch),
you should not install it on debian 8 (Jessie).
让我们来看一下 neko-repo
提供了什么东西
set-src-list
- 我们将会在“仓库/更换镜像源”中介绍到它
update-neko-key
- 用于更新公钥
2.1.3. 更新公钥
在极其特殊的情況下,neko 仓库的 OpenPGP 公钥可能会失效。
在这种情况下,您就不能用 apt-get install
来更新 neko 仓库的 keyring 了,而要用 neko-repo
自带的一个命令。
运行
update-neko-key
它会输出以下内容
It can update the OpenPGP pub keyring: "/usr/share/keyrings/tmoe-archive-keyring.gpg"
-d | --downloader: You can specify the downloader.
For example, you can run "update-neko-key -d curl" to use curl.
Run "update-neko-key -d wget" to use wget.
简单来说,您需要手动指定一个下载器。
现在只支持
curl
wget
wget2
aria2c
用法很简单,以 root 身份运行 update-neko-key -d curl
。
如果没有 root 权限,那么它将无法修改 "/usr/share/keyrings/tmoe-archive-keyring.gpg"。
这个工具的逻辑非常简单。
- 若您的系统已经安装了
gnupg
,那么它将从相关仓库获取 ascii 格式的 OpenPGP 公钥,并将其转换为二进制格式,最后覆盖原来的公钥。 - 若您的系统没有安装
gnupg
, 或者转换过程出错了,那么它将直接从相关网站获取二进制格式的公钥。
如果没有意外的话,您可能十年都不需要调用此命令去更新公钥。
至于开发者能不能为本项目用爱发电十年就是另一回事了。
2.2. toy-repo
与 neko-repo 不同,toy-repo 并非完全通用的。 它只能给 debian 用,不能给 ubuntu 用。
2.2.1. 详情
apt show toy-repo
Package: toy-repo
Version: 0.0.1-4
Priority: optional
Section: misc
Maintainer: Moe Master <m@tmoe.me>
Installed-Size: 19.5 kB
Depends: apt (>= 1.1.0), neko-repo
Suggests: ca-certificates
Homepage: https://packages.tmoe.me/deb/dists/toy
Download-Size: 2,484 B
APT-Manual-Installed: yes
APT-Sources: https://packages.tmoe.me/deb neko/main amd64 Packages
Description: A repository for debian only
Toy is a wonderful thing.
Let's keep our childishness and optimism!
Go after the good things in the toy-repo!
The source file will be placed in "/etc/apt/sources.list.d/toy-repo.sources"
遗憾的是,toy-repo(玩具仓库)自身并没有什么有用的东西。
有用的东西都在仓库里面,您需要以 root 身份运行 apt install
来安装您心仪的玩具。
玩具是一个很美好的东西,开发者将其命名为 toy,并没有抱着做“玩具项目” 这种消极的想法。
与 debian 的 玩具总动员系列的代号类似。
保持童真,以及积极向上的态度是非常重要的。
toy 这个词还包含了开发者对美好生活的向往。
这个世界是非常复杂的,像孩童一样追求着简简单单的快乐是一件非常非常幸福的事情。
2.3. uuu-repo
与 neko-repo 和 toy-repo 都不同。
uuu 仓库只能给 ubuntu 用,不能给 debian 用。
2.3.1. 说明
uuu 仓库对于 ubuntu 来说是通用的。
本项目之后可能还会有 "focal-repo" 这种只能给单独的版本使用的仓库。
3. Android
3.1. termux
3.1.1. tinor
TINOR Is Not Official Repository.
把软件包拆出来,看看里面有什么东西吧!
├── control
│ ├── conffiles
│ ├── control
│ ├── md5sums
│ ├── postinst
│ └── postrm
└── data
└── data
└── data
└── com.termux
└── files
└── usr
├── bin
│ ├── set-src-list
│ └── update-tinor-key
├── etc
│ └── tmoe
│ └── repo
│ └── apt
│ ├── preferences.d
│ │ └── 50-tinor
│ └── sources.list.d
│ └── tinor.sources
└── share
├── doc
│ └── tinor-repo
│ ├── changelog.Debian
│ └── copyright
└── keyrings
└── tmoe-archive-keyring.gpg
在安装 deb 包,执行 postinst
的 configure 阶段时, postinst
会在 $PREFIX/etc/apt/sources.list.d
和 $PREFIX/etc/apt/preferences.d
处创建与 tinor 相关的软链接;
卸载软件包,执行 postrm
的 purge 或 remove 阶段时,postrm
会自动删掉软链接。
可以看出来,它的结构与 neko-repo
是极其相似的,只是路径不一样。
4. 题外话
4.1. 您可能不需要了解的知识
如果您一定要混用软件源, 那么请在高版本系统中使用低版本系统的源。
否则将出现以下的情况:
比如系统 A 的 glibc 版本是 2.33,B 是 2.35。
B 的某个软件 b 依赖了 glibc。
如果您在 A 上用了 B 的源,然后又安装了 b ,那么 glibc 可能也被升到了 2.35。
这时候 A 上一大堆系统相关软件要么跟着升级,要么可能会出现不可预料的“不稳定性”。
如果您想要将一个系统完全变成另一个系统的模样,那么结论与上面完全相反,您得要像上面那张流程图那样做才行。
这样做相当于从低版本系统升级到高版本。
比如说您想要将 debian 更换为 kali 源,这时候应该用低版本的 debian stable 添加高版本的 kali rolling 源,而不能用高版本的 debian sid 来添加低版本 kali rolling 源。
这里的高低是相对而言的。
在使用完 apt dist-upgrade
更新完所有软件包后,您的系统的内部就已经变成 kali 的形状了。
在没有快照或备份的情况下,这么做就已经回不了头了。
4.2. doas
与 sudo
Q: 为什么是 doas
, 而非 sudo
?
A: 截至 2022-06-20,相较于 sudo
, doas
的代码量更少,出现安全漏洞的频率也更低。
从客观的角度来看:因为用的人少,所以安全漏洞被揭露的可能性更低。
从 2moe 个人(主观)的角度来看:OpenBSD 那边对安全问题可能会更走心一点。
The doas tool was originally written for OpenBSD by Ted Unangst.
镜像源
您若在使用发行版的官方镜像源时,体验不佳,那不妨试试本项目的“更换发行版镜像源”功能。
1. debian-based
开发者为每一个镜像源都打了一个 deb 包。
对于 debian 和 ubuntu 通用的源的 deb 包,开发者把它们放到了 neko 仓库。
缺陷:
- 尽管您可以在 kali 和 mint 上使用,但是并非所有镜像源都支持它们,提供 ubuntu 镜像源的网站不一定会同时提供 mint 源。
- 目前,由于 debian-ports 的镜像源过于稀少,因此本功能未对 riscv64 等架构进行适配。
1.1. 快速上手
如果您不明白下面的命令的具体意义,那么请不要直接运行。
在下一小节中,我们将会对其进行解析。
sudo set-src-list dis
sudo apt update
sudo apt install ustc-linux-user-group-cn-repo
sudo apt update
1.2. 详细解析
1.2.1. set-src-list
set-src-list
由neko-repo
提供
首先,运行 set-src-list
它输出的内容为:
-d | dis | disable: disable src list
-e | en | enable: enable src list
Note: This is a dangerous operation.
If you run "set-src-list dis", then it will move your "/etc/apt/sources.list" to "/etc/apt/sources.list.bak"
If you run "set-src-list en", then it will move your "sources.list.bak" to "sources.list"
这个工具非常简单,简单到您会怀疑它是否能被称为“工具”。
以 root 身份执行 set-src-list dis
, 它将 /etc/apt/ 目录下的 "sources.list" 重命名为 "sources.list.bak"。
set-src-list en
与上面执行相反的操作。
作用:在换源前禁用原来的软件源。
1.2.2. region-code-repo
如果您不知道具体区域代号是什么,那么请翻阅“附录”中的“区域代号”章节。
使用 apt
搜索您所在国家或地区的镜像仓库。
"United States": US
apt search us-repo$
"Germany": DE
apt search de-repo$
"China": CN
apt search "cn-repo|tw-repo|hk-repo"
alibaba-cloud-computing-cn-repo/neko 0.0.1-2 all
阿里云镜像源(China)
bjtu-cn-repo/neko 0.0.1-2 all
北京交通大学镜像源(China)
blendbyte-inc-tw-repo/neko 0.0.1-2 all
Blendbyte Inc.(Taiwan)
capital-online-data-service-cn-repo/neko 0.0.1-2 all
Capital Online Data Service(China)
china-open-source-mirror-alliance-cn-repo/neko 0.0.1-2 all
China open source mirror Alliance(China)
chongqing-university-cn-repo/neko 0.0.1-2 all
重庆大学镜像源(China)
cn99-cn-repo/neko 0.0.1-2 all
CN99(China)
dalian-university-of-technology-cn-repo/neko 0.0.1-2 all
Dalian University of Technology 大连理工学院镜像源(China)
debian-cs-nctu-edu-tw-repo/toy 0.0.1-3 all
debian.cs.nctu.edu.tw(Taiwan)
debian-csie-ncku-edu-tw-repo/toy 0.0.1-3 all
debian.csie.ncku.edu.tw(Taiwan)
debian-csie-ntu-edu-tw-repo/toy 0.0.1-3 all
debian.csie.ntu.edu.tw(Taiwan)
dongguan-university-of-technology-gnu-linux-association-cn-repo/neko 0.0.1-2 all
Dongguan University of Technology GNU/Linux Association 东莞理工学院镜像源(China)
escience-center-nanjing-university-cn-repo/neko 0.0.1-2 all
eScience Center, Nanjing University 南京大学镜像源(China)
ftp-cn-debian-org-cn-repo/neko 0.0.1-2 all
ftp.cn.debian.org(China)
ftp-hk-debian-org-hk-repo/neko 0.0.1-2 all
ftp.hk.debian.org(Hong Kong)
ftp-tw-debian-org-tw-repo/neko 0.0.1-2 all
ftp.tw.debian.org(Taiwan)
harbin-institute-of-technology-cn-repo/neko 0.0.1-2 all
哈尔滨工业大学镜像源 Harbin Institute of Technology(China)
huawei-cloud-cn-repo/neko 0.0.1-2 all
Huawei Cloud 华为云镜像源(China)
institute-of-network-development-national-taiwan-ocean-university-tw-repo/neko 0.0.1-2 all
Institute of Network Development, National Taiwan Ocean University(Taiwan)
lanzhou-university-open-source-society-cn-repo/neko 0.0.1-2 all
Lanzhou University Open Source Society 兰州大学镜像源(China)
mirrors-163-com-cn-repo/neko 0.0.1-2 all
网易镜像源(China)
mirrors-bfsu-edu-cn-repo/neko 0.0.1-2 all
北京外国语大学镜像源(China)
mirrors-neusoft-edu-cn-repo/neko 0.0.1-2 all
大连东软信息学院镜像源(China)
mirrors-pku-edu-cn-repo/neko 0.0.1-2 all
北京大学镜像源(China)
mirrors-tuna-tsinghua-edu-cn-repo/neko 0.0.1-2 all
清华大学镜像源(China)
nchc-taiwan-tw-repo/neko 0.0.1-2 all
NCHC, Taiwan(Taiwan)
nic-beijing-university-of-posts-and-telecommunications-cn-repo/neko 0.0.1-2 all
NIC, Beijing University of Posts and Telecommunications 北京邮电大学镜像源(China)
njuptmirrorsgroup-cn-repo/neko 0.0.1-2 all
南京邮电大学镜像源(China)
opensource-nchc-org-tw-repo/neko 0.0.1-2 all
opensource.nchc.org.tw(Taiwan)
opentuna-cn-repo/neko 0.0.1-2 all
OpenTUNA(China)
shanghai-jiaotong-university-cn-repo/neko 0.0.1-2 all
Shanghai Jiaotong University 上海交通大学镜像源(China)
sohu-cn-repo/neko 0.0.1-2 all
搜狐镜像源(China)
tencent-cloud-cn-repo/neko 0.0.1-2 all
Tencent Cloud 腾讯云镜像源(China)
tku-tamkanguniversity-tw-repo/neko 0.0.1-2 all
TKU-TamKangUniversity(Taiwan)
ustc-linux-user-group-cn-repo/neko 0.0.1-2 all
中国科学技术大学镜像源(China)
xi-an-jiaotong-university-cn-repo/neko 0.0.1-2 all
Xi'an Jiaotong University(China)
xtom-hk-repo/neko 0.0.1-2 all
xTom(Hong Kong)
实际上,0.0.1-4 修复了 debian (old-stable) 的一些小细节问题,这里还是 0.0.1-2
在下文介绍源文件时,将会提到相关内容,因此不更新也没关系。
然后我们以 root 权限运行 apt
来安装软件包。
apt install opentuna-cn-repo
1.2.3. 软件包解析
先拆开来看看
├── control
│ ├── conffiles
│ ├── control
│ ├── md5sums
│ ├── postinst
│ └── postrm
└── data
├── etc
│ └── tmoe
│ └── repo
│ └── src
│ ├── debian
│ │ ├── opentuna-cn-repo_old.sources
│ │ ├── opentuna-cn-repo_sid.sources
│ │ └── opentuna-cn-repo_stable.sources
│ └── ubuntu
│ ├── opentuna-cn-repo_ports.sources
│ └── opentuna-cn-repo.sources
└── usr
└── share
└── doc
└── opentuna-cn-repo
└── changelog.Debian.gz
postinst
调用了 set-src-link
去创建软链接。
postrm
调用了 set-src-link
的 unlink
子命令去删除软链接。
假如您的系统是 ubuntu jammy (amd64), 那么它会将 opentuna-cn-repo.sources 修改为 jammy 的源,并将其软链接到 "/etc/apt/sources.list.d/cn-mirror.sources"。
如果您用的是 linuxmint vanessa, 那么它会自动合并 ubuntu 和 vanessa 的源,并将源文件软链接到 "/etc/apt/sources.list.d/cn-mirror.sources"。
如果您使用的是 us-repo, 而不是 cn-repo, 那么它就会将源文件软链接到 "/etc/apt/sources.list.d/us-mirror.sources"。
相同区域的镜像包会被上一个安装的包覆盖掉,不同区域的不会。
比如说,您现在安装了 mirrors-bfsu-edu-cn-repo
, 那么现在的 cn 源是 bfsu。
您再安装了 shanghai-jiaotong-university-cn-repo
,那么 cn 源就变成了 sjtu。
此时,您再安装了 xtom-de-repo
,/etc/apt/sources.list.d/ 会多出一个 de 源,它跟 cn 源并不冲突。
在一般情况下,您只需要安装您的服务器/pc 所在区域的镜像源即可。
除非您有充分的理由,否则请不要在一台设备上安装不同区域的镜像源。
1.2.4. set-src-link
在上一小节中,我们提到了 set-src-link
,在本小节中,我们将对其进行深入解析。
在您安装或卸载镜像源 的 deb 包时, set-src-link
会被自动调用,您无需手动去调用它。
简单来说,set-src-link
只做两件事。
- 1.创建软链接
- 在创建前,它会自动判断您的发行版。对于 ubuntu, 它还会判断您的架构。
- 2.删除软链接
运行 set-src-link -h
输出的内容是:
set-src-link 0.0.1
Set the symbolic link for the mirror source.
Usage:
set-src-link [flags]<string>
set-src-link [flags] [flags]
set-src-link <subcommand> [flags]<string>
Flags:
-n, --name <mirror-name> set the mirror name
-r, --region <iso-code> set the region <ISO 3166-1 Alpha-2 code>
-h, --help display help information
-V, --version display version
Subcommand:
unlink
Example:
set-src-link -n -h
set-src-link --region --help
set-src-link unlink -r us
set-src-link
需要以 root 身份运行,否则将无法修改/etc/apt/sources.list.d/*-mirror.sources
1.2.4.1. region & link
获取 region 的帮助信息
set-src-link -r -h
-n
后面接的是 deb 包的包名。
创建软链接
set-src-link -r cn -n opentuna-cn-repo
# os: debian
# code: sid
# '/etc/apt/sources.list.d/cn-mirror.sources' -> '/etc/tmoe/repo/src/debian/opentuna-cn-repo_sid.sources'
set-src-link -r us -n opentuna-cn-repo
# os: debian
# code: sid
# '/etc/apt/sources.list.d/us-mirror.sources' -> '/etc/tmoe/repo/src/debian/opentuna-cn-repo_sid.sources'
1.2.4.2. unlink
set-src-link unlink
输出了以下内容
Error, you should add "--region" to specify your region
只要指定区域就能解决了
set-src-link unlink -r cn
# unlink /etc/apt/sources.list.d/cn-mirror.sources
set-src-link unlink -r de
# unlink /etc/apt/sources.list.d/de-mirror.sources
set-src-link unlink -r us
# unlink /etc/apt/sources.list.d/us-mirror.sources
1.2.5. 源文件解析
您如果之前曾有过手动更换 debian/ubuntu 源的经历,那么应该会知道 debian 传统的 one-line-style 源格式。
deb http://mirrors.bfsu.edu.cn/debian/ sid main non-free contrib
与传统的 one-line-style 不同,本项目的“镜像源”功能使用的是更现代化的 deb822-style。
此格式要求 apt 的版本 >= 1.1.0。
因此它在默认情况下不兼容 debian 8(Jessie)。
让我们来看看里面有什么吧!
以 debian buster (old-stable)为例。
实际上,buster 的 suites 和 bullseye 是有区别的。
除了 security 源的区别外,backports 也应该使用不同的源。
不能简单地将 "stable-backports" 替换为 "old-stabe-backports"
此外,如果这个镜像源不包含 "debian-security" 镜像,那么它默认会启用官方的 security 源,并禁用镜像 security 源。
如果它不支持 https, 那么 uris 那里显示的是 http:// 开头的 uri 。
在使用 neko-repo 的镜像源 deb 包的情况下,您无需手动去判断它支不支持 https
等东西。
cat /etc/apt/sources.list.d/cn-mirror.sources
name: Debian
# yes or no
enabled: yes
# types: deb deb-src
types: deb
uris: https://mirrors.bfsu.edu.cn/debian/
suites: buster
components: main contrib non-free
# architectures: amd64 arm64 armhf i386 ppc64el s390x mipsel mips64el
# --------------------------------
name: Debian updates
enabled: yes
# types: deb deb-src
types: deb
uris: https://mirrors.bfsu.edu.cn/debian/
suites: buster-updates
components: main contrib non-free
# --------------------------------
name: Debian backports
enabled: yes
# types: deb deb-src
types: deb
uris: https://mirrors.bfsu.edu.cn/debian/
# For debian old-stable, you should use "old-stable-backports-sloppy", instead of "old-stable-backports".
# https://backports.debian.org/Instructions/#:~:text=Old-stable-sloppy
# suites: buster-backports
suites: buster-backports-sloppy
components: main contrib non-free
# --------------------------------
name: Debian security
enabled: yes
# types: deb deb-src
types: deb
uris: https://mirrors.bfsu.edu.cn/debian-security/
suites: buster/updates
components: main contrib non-free
# --------------------------------
name: Official security
enabled: no
# types: deb deb-src
types: deb
uris: https://deb.debian.org/debian-security/
suites: buster/updates
components: main contrib non-free
# --------------------------------
name: Proposed updates
enabled: no
# types: deb deb-src
types: deb
uris: https://mirrors.bfsu.edu.cn/debian/
suites: buster-proposed-updates
components: main contrib non-free
# --------------------------------
enabled
:是否需要启用这个源,可选 yes 或 no
types
: 类型,一般情况下用 deb, 若有获取源代码的要求,就用 deb deb-src
除了上面介绍到的内容外,deb822-style 还支持其他的 keys(键)。
key: value
左为键,右为值
例如:
使用 signed-by 指定 OpenPGP 公钥。
signed-by: /usr/share/keyrings/tmoe-archive-keyring.gpg
容器
在本篇中,我们将学习以下内容。
- docker
- 本项目提供了不同架构的预装 GUI 的容器,您可以快速上手
- android
- 我们将介绍如何在 android 设备上运行本项目,并使用容器
- systemd-nspawn
- 哪些环境更适合 systemd 容器
docker
阅读本节内容的要求:
- 了解 docker 的基础知识
- 了解 nginx 中关于反向代理的配置
- 了解 CI/CD 的操作
1. GUI 容器
在一般情况下,对于更新频繁的发行版,其对应的 GUI 容器每周会更新一次。
1.1. 表格
xfce | kde | |
---|---|---|
alpine | amd64,arm64 | amd64,arm64 |
arch | amd64,arm64,armv7 | amd64,arm64 |
debian | amd64,arm64 | amd64,arm64 |
fedora | amd64,arm64 | amd64,arm64 |
kali | amd64,arm64,armv7 | None |
manjaro | amd64,arm64 | None |
ubuntu | amd64,arm64 | amd64,arm64,armv7 |
mate | lxqt | |
---|---|---|
alpine | 386,amd64,arm64,armv7 | None |
arch | amd64,arm64 | None |
debian | amd64,arm64 | None |
fedora | amd64,arm64 | amd64,arm64 |
ubuntu | amd64,arm64 | amd64,arm64 |
lxde | |
---|---|
debian | 386,armv7 |
仓库命名风格 1: cake233/alpine-mate-386, cake233/debian-lxde-armv7
风格 2: cake233/xfce:kali, cake233/kde:fedora
注: cake233/alpine-mate-386 = --platform=linux/386 cake233/mate:alpine
1.2. 服务器用户
对于 GUI 容器来说,为了减小体积和缩短打包时间,开发者之后可能会将 novnc 和 tigervnc 服务分离为单独的容器,而不是每个容器都内置 vnc
。
届时,使用 docker run
就不太合适了,换用 docker-compose
或许会更好。
本小节的内容可能会重写。
你如果哪天想不开,想要干傻事,在服务器上安装桌面环境,那可以考虑一下 tmoe 的 GUI 容器。
假设您的 host(宿主机)是 debian 系的发行版(例如 ubuntu, mint 或 kali)
1.2.1. 安装 docker
sudo apt update
sudo apt install docker.io
WHOAMI=$(id -un)
sudo adduser $WHOAMI docker
# then reboot
1.2.2. 测试 alpine
docker run \
-it \
--rm \
--shm-size=512M \
-p 36081:36080 \
cake233/xfce:alpine
进入容器后,输入 tmoe
,并按下回车,接着选择语言环境,再选择 tools,接着退出。
然后运行 novnc
, 最后打开浏览器,输入 http://您的IP地址:36081
1.2.3. 关于 nginx 与 novnc 的安全问题
如果需要将 novnc 容器暴露到公网的话,那么不建议对其使用 -p
参数(暴露 36081 端口),建议走 nginx 的 443 端口。
请新建一个网络,将 novnc 容器 与 nginx 容器置于同一网络,并为前者设置 network-alias
(网络别名), 最后用 nginx 给它加上一层认证(例如auth_basic_user_file pw_file;
)并配置 reverse proxy。
注:proxy_pass 那里要写 http://novnc容器的网络别名:36080;
如果 nginx 那里套了 tls 证书,那么访问地址就是 https://您在nginx中配置的novnc的域名:端口
。(若端口为 443,则无需加 :端口 )
注 2: 若您在 nginx 中配置了 novnc 的域名,则处于相同网络环境下的 nginx 和 novnc 必须同时运行。 若 novnc 没有运行,则 nginx 的配置会加载失败,这可能会导致 nginx 无法正常运行。
如果您对 nginx + novnc 这块有疑问的话,请前往本项目的 github disscussion 发表话题。
1.2.4. 普通 vnc
您也可以使用普通的 vnc 客户端来连接,不过这时候 tcp 端口就不是 36081 了。
docker run \
-it \
--shm-size=1G \
-p 5903:5902 \
-u 1000:1000 \
--name uuu-mate \
cake233/mate:ubuntu
对于 debian 系发行版,执行 su -c "adduser yourusername"
创建新用户,先输入默认 root 密码: root,然后设置新用户的密码。
设置完密码后,执行 su -c "adduser yourusername sudo"
将您的用户加入到 sudo 用户组。
注 1:其他发行版与 debian 系不同。
注 2:您可以手动安装并换用其他类似于 sudo
的工具,例如:doas
或 calife
。
注 3:不一定要在容器内部开 vnc, 您可以在宿主或另一个容器开 vnc 服务,不过这样做会稍微麻烦一点。
执行完 startvnc
命令后,打开 vnc 客户端,并输入 您的IP:5903
1.3. 桌面用户
接下来将介绍一下桌面用户(非服务器用户)如何使用这些 GUI 容器。
将 docker 容器当作虚拟机来用或许是一种错误的用法。
实际上,对于 GUI 桌面容器,开发者更推荐您使用 systemd-nspawn,而不是 docker。
以下只是简单介绍,实际需要做更多的修改。
注: 有一些优秀的项目,如 x11docker,它们可以帮你做得更好。
或许,您可以将本项目相关的容器镜像与那些项目结合在一起,无需手动设置 WAYLAND_DISPLAY
等环境变量,也无需在意具体的小细节,就能更舒心地去使用 GUI 容器了。
1.3.1. xorg
对于 宿主 为 xorg 的环境:
在 宿主 中授予当前用户 xhost 权限。
xhost +SI:localuser:$(id -un)
_UID="$(id -u)"
_GID="$(id -g)"
docker run \
-it \
--rm \
-u $_UID:$_GID \
--shm-size=1G \
-v $XDG_RUNTIME_DIR/pulse/native:/run/pulse.sock \
-e PULSE_SERVER=unix:/run/pulse.sock \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix \
cake233/kde:ubuntu
在容器内部创建一个与宿主用户同名的用户。
最后启动 dbus-daemon, 并运行特定 Xsession,例如 /etc/X11/xinit/Xsession
1.3.2. wayland
对于 宿主 为 wayland 的环境,您需要对 docker 执行更多的操作。
例如:设置 WAYLAND_DISPLAY 变量,-e WAYLAND_DISPLAY=$WAYLAND_DISPLAY
设置 XDG_RUNTIME_DIR 环境变量
-e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR
绑定宿主的 wayland socket
-v $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY
设置其他与 wayland 相关的环境变量
-e QT_QPA_PLATFORM=wayland
注:您如果想要在隔离环境(容器/沙盒)中运行 GUI 应用,那么使用 flatpak
等成熟的方案可能会更简单。
2. noGUI
2.1. zsh
现阶段,对于与 tmoe 相关的 nogui 容器,从严格意义上来说,它们属于另外的项目。
因为它们并没有预装 tmoe tools。
您如果不想要 gui, 那么将 xfce/kde/mate 替换为 zsh 就可以了。
# 创建容器数据卷, 用于存储持久化数据
docker volume create sd
# sd: 此处的 sd 并不是 Secure Digital Memory Card,而是 Shared Dir,其实叫什么名字都无所谓
docker run \
-it \
--name zsh \
-v sd:/sd \
cake233/zsh:kali
2.2. Cross-Architecture 跨架构
Q: 如何运行其他架构的容器呢?
A: 安装 qemu-user-static
sudo apt install binfmt-support qemu-user-static
接下来轮到 tmoe 相关项目中,更新最积极的容器仓库登场了。
注:以下容器每周更新两次
docker-hub repo: cake233/rust
nightly(gnu): amd64, arm64, armv7, riscv64, ppc64le, s390x, mips64le
nightly(musl): amd64, arm64
_UID="$(id -u)"
_GID="$(id -g)"
mkdir -p tmp
# 若本地存在 hello 项目,则可跳过这一步。
docker run \
-t \
--rm \
-u "$_UID":"$_GID" \
-v "$PWD"/tmp:/app \
-w /app \
cake233/rust-riscv64 \
cargo new hello
# build
docker run \
-t \
--rm \
-u "$_UID":"$_GID" \
-v "$PWD"/tmp/hello:/app \
-w /app \
cake233/rust-riscv64 \
cargo b --release
# check file
FILE="tmp/hello/target/release/hello"
file "$FILE"
# output: ELF 64-bit LSB pie executable, UCB RISC-V, RVC, double-float ABI, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-riscv64-lp64d.so.1 ...
cat >>tmp/hello/Cargo.toml<<-'EOF'
[profile.release]
lto = "fat"
debug = false
strip = true
panic = "abort"
opt-level = "z"
EOF
docker run \
-t \
--rm \
-u "$_UID":"$_GID" \
-v "$PWD"/tmp/hello:/app \
-w /app \
--platform linux/arm64 \
cake233/rust:musl \
cargo b --release
file "$FILE"
# output: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, stripped
3. Continuous integration 持续集成
En somme, la Beauté est partout. Ce n'est point elle qui manque à nos yeux, mais nos yeux qui manquent à l'apercevoir.
世界上并不缺少美,而是缺少发现美的眼睛
--- 法国著名雕塑家: 罗丹
您如果抱着急功近利的心态去看待某些事物,那可能很难会发现它们的一些妙用。
在本节中,我们将会用到上文中提到的 rust 镜像, 并将其与 CI 结合,为您展示相关的用法。
3.1. Github Actions
您如果想要使用 github actions 来编译 "riscv64"、"mips64el"、"arm64" 和 "armv7" 等架构的 rust
应用,那会怎么做呢?
在本小节中,我们将通过 qemu-user
来编译不同架构的 rust 应用。
以下内容仅供参考,实际上需要做更多的修改。
mkdir -pv hello
cd hello
cargo init
3.1.1. dockerfile
mkdir -p build
file: build/hello.dockerfile
# syntax=docker/dockerfile:1
#---------------------------
ARG HUB_USER
ARG TAG
FROM --platform=${TARGETPLATFORM} ${HUB_USER}/rust:${TAG} AS Builder
WORKDIR /app
COPY . .
RUN test -e Cargo.toml
RUN --mount=type=tmpfs,target=/usr/local/cargo/registry cargo b --release
# CMD [ "sh" ]
# 以下将用到 docker 的多阶段构建(Multi-stage builds),实际上这是可选的。
# 对于 musl 或静态编译的 bin, 您可以将 debian 镜像更换为 alpine:edge
FROM --platform=${TARGETPLATFORM} debian:sid-slim
COPY --from=Builder /app/target/release /app
WORKDIR /app
3.1.2. workflow
mkdir -p .github/workflows
file: .github/workflows/rs.yml
name: build rust app
on:
push:
branches: [main]
# 只有当 main 分支的 Cargo.toml 发生变化并且 push 后,才会触发此 workflow
paths:
- "Cargo.toml"
jobs:
job1:
runs-on: ${{ matrix.os }}
env:
name: hello
user: cake233
platform: ${{ matrix.platform }}
arch: ${{ matrix.arch }}
tag: ${{ matrix.tag }}
strategy:
fail-fast: true
matrix:
include:
# 如果您使用的是“自托管服务器”的话,那么 os 需要改成相应的名称, 例如: self-hosted-debian
- os: ubuntu-latest
arch: riscv64
tag: nightly
platform: "linux/riscv64"
# 您可以为该矩阵指定不同的机器/系统,只需要修改 os 即可。
- os: ubuntu-latest
arch: mips64el
tag: nightly
platform: "linux/mips64le"
- os: ubuntu-latest
arch: amd64
tag: musl
platform: "linux/amd64"
- os: ubuntu-latest
arch: arm64
tag: musl
platform: "linux/arm64"
- os: ubuntu-latest
arch: armhf
tag: nightly
platform: "linux/arm/v7"
steps:
- uses: actions/checkout@v2
with:
# 您可以引用其他仓库,默认为当前项目所在的仓库
# repository: "xxx/yyy"
ref: "main"
fetch-depth: 1
# 对于 x64(amd64) 架构的设备来说,如果当前架构是 amd64 或 i386 架构,那么无需调用 qemu,否则需要调用。
# 在调用时,只需要配置当前平台即可,无需配置其他平台。
- name: set up qemu-user & binfmt
id: qemu
uses: docker/setup-qemu-action@v1
if: matrix.arch != 'amd64' && matrix.arch != 'i386'
with:
image: tonistiigi/binfmt:latest
platforms: ${{ matrix.platform }}
- name: set global env
run: |
echo "REPO=${{ env.name }}:${{ matrix.arch }}" >> "$GITHUB_ENV"
- name: build container
env:
file: "build/${{ env.name }}.dockerfile"
run: |
DOCKER_BUILDKIT=1 \
docker build \
--tag "${{ env.REPO }}" \
--file "${{ env.file }}" \
--build-arg HUB_USER=${{ env.user }} \
--build-arg TAG=${{ env.tag }} \
--build-arg BIN_NAME=${{ env.name }} \
--platform=${{ env.platform }} \
--pull \
--no-cache \
.
#编译完成的镜像为 "${{ env.name }}:${{ env.arch }}",对于 x64 架构,在本 workflow中,它是 "hello:amd64" ;对于 arm64 架构,则是 "hello:arm64"
- name: test container
run: |
docker run \
-t \
--rm \
"${{ env.REPO }}" \
ls -lah --color=auto /app
上文并没有介绍到 docker 登录和推送的流程。
您可以手动添加相应的流程
secrets (私密环境变量) 需要在当前仓库的 Settings 的 Actions secrets 里配置。
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: 您的 dockerhub 用户名
password: ${{ secrets.DOCKER_TOKEN }}
- name: Push to DockerHub
run: |
docker push -a ${{ env.REPO }}
4. 容器镜像是怎么来的
在本节中,我们将会为您解析容器的 dockerfile。
您可以从 "2moe/build-container" 中找到相关的文件。
4.1. rust
下面我们以 rust alpine (musl-libc) 容器为例。
# syntax=docker/dockerfile:1
#---------------------------
FROM --platform=${TARGETPLATFORM} alpine:edge
WORKDIR /root
# PATH=/usr/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENV LANG="C.UTF-8" \
TMOE_CHROOT=true \
TMOE_DOCKER=true \
TMOE_DIR="/usr/local/etc/tmoe-linux" \
RUSTUP_HOME="/usr/local/rustup" \
CARGO_HOME="/usr/local/cargo" \
PATH="/usr/local/cargo/bin:$PATH"
# install dependencies
COPY --chmod=755 install_alpine_deps /tmp
# install_alpine_deps 会安装相关依赖
# 相关依赖指的是 sudo,tar,grep,curl,wget,bash,tzdata,newt,shadow
# 实际上,只有 curl 是真正的依赖,bash 为可选依赖。 对于非交互式环境来说,默认 shell 为 ash 也没问题。
# 其他依赖是 tmoe manager 在初始化容器过程需要用到的东西。
# 对于 docker 来说,grep 和 tar 等命令使用 `busybox` 内置的精简版本就够了。
RUN . /tmp/install_alpine_deps
# install musl-dev
RUN apk add openssl-dev \
musl-dev \
gcc \
ca-certificates
# minimal, default, complete
ARG RUSTUP_PROFILE=minimal
# 对于不同的平台来说, MUSL_TARGET 是不一样的。
# 比如说:linux arm64: "aarch64-unknown-linux-musl"
# linux amd64: "x86_64-unknown-linux-musl"
ARG MUSL_TARGET
RUN export RUSTUP_URL="https://static.rust-lang.org/rustup/dist/${MUSL_TARGET}/rustup-init"; \
curl -LO ${RUSTUP_URL} || exit 1; \
chmod +x rustup-init \
&& ./rustup-init \
-y \
--profile ${RUSTUP_PROFILE} \
--no-modify-path \
--default-toolchain \
nightly \
&& rm rustup-init \
&& chmod -Rv a+w ${RUSTUP_HOME} ${CARGO_HOME}
# RUN rustup update
ARG OS
ARG TAG
ARG ARCH
COPY --chmod=755 set_container_txt /tmp
RUN . /tmp/set_container_txt
# export env to file
RUN cd ${TMOE_DIR}; \
printf "%s\n" \
'export PATH="/usr/local/cargo/bin${PATH:+:${PATH}}"' \
'export RUSTUP_HOME="/usr/local/rustup"' \
'export CARGO_HOME="/usr/local/cargo"' \
> environment/container.env; \
chmod -R a+rx environment/
# export version info to file
RUN cd /root; \
printf "%s\n" \
"" \
'[version]' \
"ldd = '$(ldd --version 2>&1 | head -n 2 | grep -vi copyright | sed ":a;N;s/\n/ /g;ta")'" \
"rustup = '$(rustup --version)'" \
"cargo = '$(cargo --version)'" \
"rustc = '$(rustc --version)'" \
"cc = '$(cc --version | head -n 1)'" \
"cargo_verbose = '''" \
"$(cargo -Vv)" \
"'''" \
"rustc_verbose = '''" \
"$(rustc -Vv)" \
"'''" \
> version.toml; \
cat version.toml
# clean: apk -v cache clean
RUN rm -rf /var/cache/apk/* \
~/.cache/* \
2>/dev/null
CMD ["bash"]
为了保留容器属性信息,容器内部需要新建几个环境变量或文件。
这个 dockerfile 之后可能会发生变更,比如说:砍掉 TMOE 相关的环境变量,将 "/usr/local/etc/tmoe-linux" 目录更改为 "/etc/tmoe"
android
本项目的本体分为两个部分,分别是“管理器”和“工具箱”。
对于 android,您可以使用“管理器”。
在下文中, rootless 指的是 “无 root 容器环境”,rootful 指的是“有 root 容器环境”。
1. 关于 docker
实际上,android 也可以运行 docker。
您如果想要使用原生 docker,那么需要手动编译内核,加上 docker 所需的一些特性。
如果您的内核没有开源,或者是您的设备无法解锁 bootloader, 那么您可能需要通过虚拟机来间接使用 docker 。
在本章中,我们并不会介绍以下内容:
- 如何为 android 重新编译内核
- 如何在 android 上运行 docker
2. 管理器
如果您想要使用 “管理器”,那么您可以使用脚本进行安装。
在 edition 2022 中,“天萌管理器” 叫做
tmm
现阶段,天萌的大部分内容仍处于 edition 2021
为了与旧版本的兼容,开发者之后会为 android 保留tmoe
命令
对于 android, 您可以用 termux 运行以下脚本。
之后,开发者可能会将相关内容打成 deb 包,也可能会适配其他的终端。
之所以使用
curl
, 是因为 termux 预装了它。
如果它没有被预装的话,那么您需要使用apt update; apt install -y curl
来安装。
-
方法 1
- 工具: curl
- 平台: github
- 条件: 您已经安装了
curl
, 并且记忆力惊人,可以访问 github - 命令:
curl -LO --compressed https://raw.githubusercontent.com/2moe/tmoe/2/2.awk
awk -f 2.awk
-
方法 2
- 工具: curl
- 平台: gitmoe
- 条件: 您无法访问 github
- 命令
curl -LO https://l.tmoe.me/2.awk; awk -f 2.awk
-
方法 3
- 工具: curl
- 平台: gitee
- 条件:以上方法都出错了
- 命令
curl -LO https://gitee.com/mo2/linux/raw/2/2.awk; awk -f 2.awk
3. 关于容器环境
如果您觉得天萌管理器的问题比其他“更优秀的管理器”更多,那么大概率是配置的问题。
您需要进行合理的配置,才能更好地使用。
3.1. 配置与环境
我们这里随便摘出一段 unshare
的配置
# The unshare command creates new namespaces and then executes the specified program.
# By default, a new namespace persists only as long as it has member processes.
# A new namespace can be mad e persistent even when it has no member processes by bind mounting /proc/pid/ns/type files to a filesystem path.
# A namespace that has been made persistent in this way can subsequently be entered with nsenter even after the program terminates
# (except PID namespaces where a permanently running init process is required).
# Once a persistent namespace is no longer needed, it can be unpersisted by using umount to remove the bind mount.
UNSHARE_ENABLED=true
# Unshare the IPC namespace. Default is false.
# IPC namespace: The process will have an independent namespace for POSIX
# message queues as well as System V message queues, semaphore sets and shared memory segments.
UNSHARE_IPC=false
# Unshare the PID namespace. Default is false.
# PID namespace: Children will have a distinct set of PID-to-process mappings from their parent.
UNSHARE_PID=false
# Unshare the UTS namespace. Default is false.
# UTS namespace: Setting hostname or domainname will not affect the rest of the system.
UNSHARE_UTS=false
# Unshare the mount namespace. Default is false.
# mount namespace: Mounting and unmounting filesystems will not affect the rest of the system, except for filesystems which are explicitly marked as shared.
UNSHARE_MOUNT=false
# When unshare terminates, have signame be sent to the forked child process. Combined with --pid this allows for an easy and reliable killing of the entire process tree below unshare. This option implies --fork.
# When the value is true and SIGNAME=SIGKILL, the process in the container cannot be terminated with ctrl+c.
# see this issue: https://github.com/2moe/tmoe/issues/44
KILL_CHILD=false
KILL_CHILD_SIGNAME="SIGKILL"
# Default is true.
# Just before running the program, mount the proc filesystem at mountpoint (default is /proc). This is useful when creating a new PID namespace. It also implies creating a new mount namespace since the /proc mount would otherwise mess up existing programs on the system. The new proc filesystem is explicitly mounted as private (with MS_PRIVATE|MS_REC).
SHARE_PROC=true
然后您可能会吐嘈:“我不理解 IPC namespace 是什么? 到底要不要开启相关的选项?”
对于默认配置,在有些情况下,保持默认就可以了。
有一本书叫做《UNIX 网络编程 卷 2:进程间通信(UNIX Network Programming,Vovum 2:Interprocess Communications)》,里面有介绍到 IPC 相关的内容。
对于 rootful 环境,其实小问题没有那么多,一直使用默认的配置也不会有太大的问题。
但对于 rootless 环境,有时候您甚至需要针对不同的环境使用不同的配置。
3.1.1. rootless 环境的一些小问题
您如果对天萌管理器不屑一顾的话,那么可以尝试用“更优秀的管理器”执行以下操作。
- 在 rootless 环境下运行 .NET SDK 6.0.202
- 在 rootless 环境下,运行
gnome-shell
。
您可能在使用 rootless GUI 容器的过程中会遇到卡住或崩溃等问题。
在一般情况下,这与 android 系统本身的限制有关。
您可能需要开启相应终端的后台运行与开机自启的权限,或者是开一个小悬浮窗。
又或者是通过 adb
去调整系统的资源调度与后台管理机制。
对于 android 12+,如果容器崩溃了,那请选择
fix android 12(signal 9) 选项
进行修复
其次,这与容器内部的服务有关。 比如说与 "power-manager" 相关的东西,它会尝试去调用宿主本身的东西,在有些情况下,这可能会导致整个容器进程崩溃。
最后与资源占用有关,如果部分系统资源已经被占满了,或者是无法被调用,那么它会变卡。
如果说,您在启动 gui 环境时卡住了,或者是 gui 环境特别不稳定。
那么与什么东西有关呢?
答案是 D-Bus。
Q: 关掉 D-Bus 会更好吗?
A: 恰恰相反,对于部分桌面环境来说,让它的 daemon 处于运行状态会更好。
比如说,对于 gnome,开启 dbus-daemon 后,您就可以在 rootless 环境下跑 gnome-shell 了。
Q: 如何关闭?
A: 与 D-Bus 相关的地方分布在以下三个位置。
- 容器自身的配置。您可以在环境变量与登录项管理处禁用掉
fake cap_last_cap
- session 的配置。将 /etc/X11/xinit/Xsession 中的
DBUS_CMD
的值修改为空。 - startvnc 或其他 vnc 服务的配置。将
AUTO_START_DBUS
的值修改为false
再比如,如果与“资源监控”相关的东西崩溃了,那么大概率与 "/proc" 相关的东西有关。
请在 edit script
选项中,手动禁用掉部分 伪造proc
的东西。
注:在默认情况下,只有当您的系统无权读取相关文件时,它才会自动伪造并挂载。
为了性能上的优化,只有容器初始化过程才会自动检测。
天萌里有很多很多的选项,您如果无法理解相关内容,那么可能会去喷它。
有问题应该尽早反馈,整天想着喷它,可是坏孩子哟! (╯°□°)╯︵ ┻━┻
解决方法很简单,对于 android,如果内核支持 docker ,那么您直接使用 docker 就可以了。
如果内核不支持 docker, 那么您可以考虑使用天萌管理器的 unshare
容器。
3.2. proot
problem | note |
---|---|
无法绑定 1024 以下的端口 | see this issue |
3.3. chroot/unshare
您在 android 上使用天萌来安装 unshare 容器前,它会让你选择“共享/挂载 sd 目录”。
对于 miui, 请不要选择整个 "/data/media/0" 或 "/sdcard"
请选择特定的子目录,例如 "/data/media/0/Download"。
挂载整个内置 sd 可能会导致它在 umount
时被一同卸载。
issue1
issue2
如果您坚持要做的话,那么请在挂载整个内置 sd 前,手动做个小测试。
cd $TMPDIR
mkdir -pv sd
su -c "/system/bin/mount -o bind /sdcard $PWD/sd"
sudo ls sd
su -c "/system/bin/umount -lvf $PWD/sd"
ls /sdcard
看看发生了什么?
4. 命令
4.1. 在宿主环境中
4.1.1. 对于 GUI 容器
startvnc
- 前提:您需要先安装 vnc viewer 或其他 vnc 客户端
- 对于 android, 只有 vnc viewer 才会 “连携”启动
- 作用:启动默认的 vnc 服务(一般是 tigervnc)
startx11vnc
- 前提:条件同上
- 作用:启动 x11vnc
stopvnc
- 此命令可以方便地干掉 rootless 容器(包括它的 vnc 服务进程)
- 对于 chroot/unshare(rootful)容器, 您需要单独停止容器进程。
startxsdl
- 前提:您需要先在宿主环境中安装 xserver app
- 作用:启动 xorg
novnc
- 前提:您无需安装常规的 vnc 客户端,只需有个浏览器就足够了
- 作用:同时启动宿主的浏览器和容器内部的服务
4.1.2. noGUI
对于 edition 2020:
debian
- 自动检测默认容器名称、类型和架构。在检测完成后,启动容器。
对于 edition 2021:
tmoe ls
- 自动判断默认容器类型,并列出容器列表
tmoe p
- 启动默认的 proot 容器
tmoe c
- 启动默认的 chroot/unshare 容器
tmoe
或tmoe m
- 启动 tmoe manager
对于 edition 2022:
tmm r <容器名称>
- 例如
tmm r uuu
- 例如
tmm
- 启动 tmoe manager
4.2. 在容器环境中
如果宿主支持“连携”启动,那么您无需在容器内单独启动 vnc 服务
tmoe
或tmoe t
- 启动 tmoe tools
startvnc
- 启动默认的 vnc 服务(一般是 tigervnc)
startx11vnc
- 启动 x11vnc 服务
stopvnc
- 停止 vnc 服务
startxsdl
- 启动 xorg
novnc
- 启动 novnc
4.2.1. 对于 debian-based 发行版
tigervnc
- 启动 tigervnc 服务
tightvnc
- 启动 tightvnc 服务
配置
在本篇中,我们将学习以下内容
- toml 的基本用法
- 各个配置选项的意义
- 如何修改配置
toml
二萌当初在写本章时,本来是打算给 edition 2022 准备的。
实际上,有部分内容已经下放给 edition 2021 了。
在之后的新版中,对于面向用户的配置,toml 会出现得更加频繁。
在天萌中,配置文件分为两种。
一种是只能看的,另一种则是可以写的。
只读 | 可读可写 |
---|---|
一般是软件包发行信息或数据索引信息 | 真正意义上的程序配置文件 |
1. 只读配置
1.1. 概念解读
天萌的“容器属性信息”就是只读配置。
此处所指的只读配置并不是文件权限的 "read only",而是逻辑上的只读。
实际上,您可以直接修改该文件的内容,只是不应该手动去修改。
举个例子:
假如您看到了版本号为"1.57.0"。
心想:“这版本号也忒低了吧!咱把它改成 114514.999.9 吧!”
于是,您很开心地修改了它本身的属性。
从主观的角度来看:您收获了快乐。这很棒!因为快乐是一种积极的生活态度。
从客观的角度来看:您修复了版本号过低的问题。这也不错!
从原开发者的角度来看:啊这。。。
于是乎,只有原开发者受伤的世界达成了。(ó﹏ò。)
注:以上修改的内容不会生效。
对于只读配置,如果修改真的生效了,那么容器在安装或使用过程中可能会出现问题。
我相信您能在下一小节中找到原因。
1.2. 分析
本节将对“容器属性信息”进行分析。
天萌每周构建容器输出的只读配置如下:
(之后可能会发生变更)
[main]
name = "rust"
tag = ["nightly", "unstable"]
os = "debian"
release = "sid"
arch = "arm64"
syntax_version = "0.0.1"
[file]
name = "rust-nightly-arm64_2021-09-17_20-39.tar.zst"
# 这个值可以用来校验文件的完整性
# 举个例子:假设存在两个同名文件,它们都叫a.tar.zst,大小也相同,只是所在目录不同: A/a.tar.zst, B/a.tar.zst
# 您可能无法直接判断它们是否属于同一个文件
# 这时候可以通过对比两者的 sha256 校验值来检测它们是否为同一个文件,若 sha256 值相同,则文件相同,反之不同。
# 注:此处忽略了哈希碰撞等问题。
sha256 = "acc668db456e94053322f7049469995ba20e9fe6dcb297367226dca4553b633e"
[file.size]
# Installed size ≈ tar-size
# 安装大小 约等于 tar文件的大小
# tar大小就是容器镜像打包后的大小,解包后占用的空间可能会比tar本身略大一点
# 具体大小与文件簇有关,而文件簇又与 file system(文件系统)有关。
tar = "1.6G"
tar-bytes = 1717986919
# Space occupied ≈ tar-size + zstd-size
# You will need to prepare a large enough space before installation.
# Download size: zstd-size
# 在tar打包完成后,开发者还对镜像进行了压缩。
# 您需要下载的文件大小就是zstd的大小,准确来说是tar.zst文件的大小
# 在安装一个容器前,您需要考虑预留一定的空间:
# 1.压缩包文件的大小,2.压缩包解压后的大小, 3.容器的初始化过程也需要占用一点空间。
zstd = "216M"
zstd-bytes = 226492416
# 对于2022版,用户只需要看版本号就能知道镜像的先后顺序了,知道哪个比较新哪个比较旧。
# 开发者忽然想到了天萌存在一个自动判断时间先后顺序的功能,就算是旧版也有这个功能。
# 其实time(时间)是给开发者看的,普通用户不需要了解服务器在构建镜像过程中的某个流程究竟花了多少时间。
# 如果您感兴趣的话,那我可以说一下流程。
# 1. 服务器构建完成镜像后,需要对镜像进行打包和压缩。
# 2. start-zstd指的是开始进行zstd压缩的时间点
# 3. 在压缩完成后,需要将文件传输到另一个节点。
# 3-1. 对于天萌的每周构建,并不是所有节点都从零开始构建,而是其中一个节点完成构建后,就把相关文件同步到另一个节点。
[time]
begin = 2021-09-17T20:08:33.801113258Z
start-zstd = 20:14:20
start-sync_0 = 20:39:22
start-sync_1 = 20:41:33
end = 2021-09-17T20:44:32.392018144Z
[server]
name = "tmoe-us"
node = 2
available = [1, 2, 3, 4]
# Environment variables inside the container.
# 容器内部的环境变量会影响容器内的环境,~~听君一席话,如听一席话~~(●>ω<●)
[env]
PATH = "/usr/local/cargo/bin${PATH:+:${PATH}}"
RUSTUP_HOME= "/usr/local/rustup"
CARGO_HOME = "/usr/local/cargo"
[version]
rustup = 'rustup 1.24.3 (ce5817a94 2021-05-31)'
cargo = 'cargo 1.56.0-nightly (e515c3277 2021-09-08)'
rustc = 'rustc 1.57.0-nightly (e4828d5b7 2021-09-16)'
toml 是一种优秀的配置文件格式。
接下来,我们将会介绍 toml 的理念以及常见用法。
最后,我们将简单了解“可写配置”的概念与用法。
2. toml
2.1. 什么是 toml
Tom's Obvious, Minimal Language.
Tom 的(语义)明显、(配置)最小化的语言。
TOML 旨在成为一个语义明显且易于阅读的最小化配置文件格式。
TOML 被设计成可以无歧义地映射为哈希表。
TOML 应该能很容易地被解析成各种语言中的数据结构。
以上说明来自于 toml 的官网,您可以在里面找到一些详细的说明。
2.2. toml 与 json
谈到 toml,很多人都会提及到 json
为什么不用json呢? 它跟json 比有什么优势吗?
github 上 toml-lang/toml 的第 2 条 issue 就跟这个话题有关。
But I don't know why too.
2.3. toml 的简单用法
本小节的内容将为(#4.3-可写配置) 打下基础。
在天萌容器的配置中,可能会涉及到以下知识点:
- 字符串
- 整数
- 浮点数
- 布尔值
- rfc3339
- 数组
- 标准表
- 内联表
其实这些也是 toml 本身的常见值类型。
很少涉及到的知识点是:
- 表数组
2.3.1. 表数组
表数组的话,虽然很好用,但是天萌的开发者 (想要偷懒) 由于某种原因就不用这种类型了。
其实是表数组解析起来稍微要麻烦一点。
举个简单的例子:
[[bin]]
name = "tmm"
[[bin]]
name = "value"
[[bin]]
name = "tmoe"
全删[[bin]]
或者是追加写入一个新的[[bin]]
都很简单。
但是呢!要修改包含指定"value"的数据,还得要再处理一下。
不像标准表,直接键值对操作,多简单啊!
2.3.2. 字符串
字符串可能是天萌容器的配置文件里最常见的类型了。
比如说
str = "value"
toml 的字符串类型既可以用双引号,也可以用单引号。
这跟 rust 不一样,在 rust 中,如果您使用单引号,并且没有指明类型,那么编译器默认会推断该值为 char 类型(单个字符)。
#![allow(unused)] fn main() { let c = 'c'; }
回到 toml,你如果需要输入多个引号的话,那就这样子写吧!
str = """我有'''''5个单引号,还有两个""双引号"""
前面和后面都有三个引号。
2.3.3. 整数
整数的话,就不需要引号了!
举个字符串的例子:
int1 = "233"
在上面的式子中,int 的值将被识别为字符串, 而不是整数。
再举个整数的例子:
int2 = 233
举个负整数的例子:
int3 = -233
toml 可以接受的整数范围是 i64 (从 −2^63 到 2^63−1)。
跟 rust 一样,对于特别大的数字,您可以用 _ 来增强可读性。
int4 = 114_514_233
上面那条式子等于下面那条
int4 = 114514233
举个二进制、八进制和十六进制整数的例子:
# 二进制的0b11011111101010010等于十进制的114514
bin = 0b11011111101010010
# 0o开头的值是八进制的数字,猜猜看这是哪个数
oct1 = 0o337522
# 0x开头是十六进制喔
hex1 = 0x1BF52
2.3.4. 浮点数
浮点数也不能加引号哦!
用小数和指数形式举个几个例子吧!
f1 = 3.14159265
f2 = -3.14159265
f3 = 314e-2
f4 = 0.1145e+4
2.3.5. 布尔值
布尔值只有两个值: true 和 false
true 为真,false 为假
true ✓
false X
bool1 = true
bool2 = false
2.3.6. rfc3339
rfc3339 是一种时间格式。
# 您可以只写时间
time1 = 01:25:57
time2 = 01:25:00.247810421
# 也可以只写日期
date1 = 2021-09-29
# 也可以都写
time3 = 2021-09-29T01:25:57Z
# 您可以把上面那个拆开来,并且无需加引号
time4 = 2021-09-29 01:25:57Z
# 末尾的Z代表的是UTC, 您可以换成+00:00
# 下面以纳秒级别来输出东八区的某一时间点。那么问题来了,您知道为什么是这个时间点吗?
time5 = 2021-09-29 01:29:13.598811802+08:00
因为开发者写文档写到很晚,都写到这个点啦!很辛苦的说。
2.3.7. 数组
简单来说,数组就是一个方括号,然后里面有 0 个或多个值。
# 您可以换行写
array1 = [233,
22,
33]
# 也可以不换行写
array2 = [ "你好", "世界" ]
2.3.8. 标准表
标准表也被称为哈希表。
举个 Cargo 里面的例子
[dependencies]
nom = "7.0.0"
您可以把上面的表写成下面的格式
[dependencies.nom]
version = "7.0.0"
2.3.9. 内联表
内联表可以把多行写成一行。
先举个标准表的例子
[dependencies.tokio]
version = "1.11.0"
features = ["macros", "tcp", "dns", "io-util"]
再举个内联表的例子
[dependencies]
tokio = { version = "1.11.0", features = ["macros", "tcp", "dns", "io-util"] }
怎么样?您喜欢标准表还是内联表呢?
3. 可写配置
3.1. 概念解读
咕咕咕,这个部分还没有开始写呢!
环境
在本篇中,我们将学习各种环境的用法。
除了本项目自身之外,我们还将学习其他知识。
- 编辑器
- vscode
- micro
编辑器
子曰:“工欲善其事,必先利其器。居是邦也,事其大夫之贤者,友其士之仁者。”
———《论语·卫灵公》
1. Visual Studio Code
虽然 tmm 里面有预装 code-server(web 网页服务器)的容器,但是本节将主要讲桌面版的 vscode。
对于本项目的用户,您之后可以用 apt install code-no-sandbox
来安装,它将包含两个启动图标。(现在还而没有打包啦)
除了 code-server 之外,目前比较流行的网页版 vscode 还有 2 个。
- 官方的 vscode.dev
- github dev, 假设某仓库为 github.com/xx/yy, 将 com 修改为 dev:(github.dev/xx/yy)。
1.1. 简介
vscode 是由微软(Microsoft)主导开发的一款开源编辑器。
Why is vscode?
有很多人都喜欢 JetBrains 家的 IDE(s), 还有 vscode 的老大哥 Visual Studio。
在本节中,我们并不会讨论 vscode 相较于其它编辑器或集成开发环境的优劣。
正如《周易·系辞(上)》中所言:“仁者见之谓之仁,智者见之谓之智。”
对于相同问题,不同用户站在不同角度有不同的看法。
您如果不喜欢 vscode 的话,那可以跳过本节的内容。
如果您对它感兴趣的话,那让我们带着愉快的心情,一起去了解 vscode 吧!
1.2. 快捷键
了解一款软件的快捷键,在某些方面能极大程度地提高您的效率。
以下表格是开发者根据 官方文档 整理出来的。
网上有很多基于官方文档的表格,但是应该很少有人将 windows 、linux & macos 三者的 vscode 整合在一起并进行比较吧?
截止 2021-10-23,以下表格会比官方的 pdf 文档多一些内容,之后 vscode 可能会进行更新, 具体内容请以官方文档为主。
对于 Linux 和 Windows 中相同的快捷键,Win 处留空。
对于冲突快捷键,或者是默认为空键位的地方,使用 🤔️。
例如: ctrl+alt+方向键:(Move editor into next/previous group) 与 cinnamon 的切换工作区(switch work space) 快捷键冲突。
其实正确的做法,不是看这个表格。
而是先按下 ctrl + K 组合键,再按下 ctrl + S, 最后进行搜索。
不管怎么说,只要能帮到您,开发者就觉得很开心了。
Linux | Win | Mac | General | 一般操作 |
---|---|---|---|---|
Ctrl+Shift+P, F1 | ⇧⌘P, F1 | Show Command Palette | 显示命令选项板 | |
Ctrl+P | ⌘P | Quick Open, Go to File… | 快速打开 | |
Ctrl+Shift+N | ⇧⌘N | New window/instance | 新窗口/实例 | |
Ctrl+W | Ctrl+Shift+W | ⌘W | Close window/instance | 关闭窗口/实例 |
Ctrl+, | ⌘, | User Settings | 用户设置 | |
Ctrl+K Ctrl+S | ⌘K ⌘S | Keyboard Shortcuts | 键盘快捷键 |
Linux | Win | Mac | Basic editing | 基础编辑 |
---|---|---|---|---|
Ctrl+X | ⌘X | Cut (if empty selection, cut line) | 剪切(若为空白则剪切整行,若非空则剪切选中内容) | |
Ctrl+C | ⌘C | Copy (if empty selection, copy line) | 复制 (若为空白则复制整行,若非空则复制选中内容) | |
Ctrl+V | ⌘V | Paste | 粘贴 | |
Ctrl+Z | ⌘Z | Undo | 撤销(回退到上一步) | |
Ctrl+Y, Ctrl+Shift+Z | ⇧⌘Z | Redo | 重做/反撤销/恢复 | |
Alt+ ↓ / Alt+ ↑ | ⌥↓ / ⌥↑ | Move line down/up | 移动行(向下/上) | |
Shift+Alt+ ↓ / Shift+Alt+ ↑ | ⇧⌥↓ / ⇧⌥↑ | Copy line down/up | 复制行(向下/上) | |
Ctrl+Shift+K | ⇧⌘K | Delete line | 删除行 | |
Ctrl+Enter / Ctrl+Shift+Enter | ⌘Enter / ⇧⌘Enter | Insert line below/above | 插入行(下方/上方) | |
Ctrl+Shift+\ | ⇧⌘\ | Jump to matching bracket | 跳到匹配的括号内 | |
Ctrl+] / Ctrl+[ | ⌘] / ⌘[ | Indent/outdent line | 缩进/缩出(取消缩进)行 | |
Home / End | Home / End | Go to beginning/end of line | 转到行首/行尾 | |
Ctrl+ Home / Ctrl+ End | ⌘↑ / ⌘↓ | Go to beginning/end of file | 转到文件的开头/结尾处 | |
Ctrl+ ↑ / Ctrl+ ↓ | ⌃PgUp / ⌃PgDn | Scroll line up/down | 滚动行(向上/下) | |
Alt+ PgUp / Alt+ PgDn | ⌘PgUp /⌘PgDn | Scroll page up/down | 滚动页面(向上/下) | |
Ctrl+Shift+ [ / Ctrl+Shift+ ] | ⌥⌘[ / ⌥⌘] | Fold/unfold region | 折叠/展开(解除折叠)区域(代码块) | |
Ctrl+K Ctrl+ [ / Ctrl+K Ctrl+ ] | ⌘K ⌘[ / ⌘K ⌘] | Fold/unfold all subregions | 折叠/展开所有子区域 | |
Ctrl+K Ctrl+0 / Ctrl+K Ctrl+J | ⌘K ⌘0 / ⌘K ⌘J | Fold/unfold all regions | 折叠/展开所有区域 | |
Ctrl+K Ctrl+C | ⌘K ⌘C | Add line comment | 添加行注释 | |
Ctrl+K Ctrl+U | ⌘K ⌘U | Remove line comment | 删除行注释 | |
Ctrl+/ | ⌘/ | Toggle line comment | 切换行注释(注释或取消注释) | |
Ctrl+Shift+A | Shift+Alt+A | ⇧⌥A | Toggle block comment | 切换块注释 |
Alt+Z | ⌥Z | Toggle word wrap | 切换自动换行 |
Linux | Win | Mac | Multi-cursor and selection | 多光标与选择(主要:列块编辑) |
---|---|---|---|---|
Alt+Click | ⌥ + click | Insert cursor | 插入光标 | |
Shift+Alt+↑ | Ctrl+Alt+↑ | ⌥⌘↑ | Insert cursor above | 在上方插入光标 |
Shift+Alt+↓ | Ctrl+Alt+↓ | ⌥⌘↓ | Insert cursor below | 在下方插入光标 |
Ctrl+U | ⌘U | Undo last cursor operation | 撤销上一次光标操作 | |
Shift+Alt+I | ⇧⌥I | Insert cursor at end of each line selected | 在选中行的末尾插入光标 | |
Ctrl+L | ⌘L | Select current line | 选择当前行 | |
Ctrl+Shift+L | ⇧⌘L | Select all occurrences of current selection | 选择当前选中项的所有匹配项 | |
Ctrl+F2 | ⌘F2 | Select all occurrences of current word | 选择当前词的所有匹配项 | |
Shift+Alt+ → / Shift+Alt+ ← | ⌃⇧⌘→ / ← | Expand / shrink selection | 扩大/缩小选择 | |
Shift+Alt + drag mouse | ⇧⌥ + drag mouse | Column (box) selection | 列块选择(先按住 shift+alt,再按住鼠标左键,最后拖拽鼠标选中指定列块) | |
🤔️ | Ctrl+Shift+Alt+ ↑ / Ctrl+Shift+Alt+ ↓ | ⇧⌥⌘↑ / ↓ | Column (box) selection up/down | 列块选择(向上/向下) |
🤔️ | Ctrl+Shift+Alt+ ← / Ctrl+Shift+Alt+ → | ⇧⌥⌘← / → | Column (box) selection left/right | 列块选择(左/右) |
🤔️ | Ctrl+Shift+Alt+ PgUp / Ctrl+Shift+Alt+ PgDn | ⇧⌥⌘PgUp / PgDn | Column (box) selection page up/down | 列块选择(页面 上/下 移) |
Ctrl+A | ⌘A | Select all | 全选 |
Linux | Win | Mac | Search and replace | 搜索与替换 |
---|---|---|---|---|
Ctrl+F | ⌘F | Find | 查找 | |
Ctrl+H | ⌥⌘F | Replace | 替换 | |
F3 / Shift+F3 | ⌘G / ⇧⌘G | Find next/previous | 查找下一个/上一个 | |
Alt+Enter | ⌥Enter | Select all occurrences of Find match | 选择所有匹配项 | |
Ctrl+D | ⌘D | Add selection to next Find match | 选择下一个匹配项 | |
Ctrl+K Ctrl+D | ⌘K ⌘D | Move last selection to next Find match | 跳过当前选择项 | |
Alt+C / Alt+R / Alt+W | ⌥⌘C/⌥⌘R/⌥⌘W | Toggle case-sensitive / regex / whole word | 切换 区分大小写/正则表达式/全字匹配 |
Linux | Win | Mac | Rich languages editing | 富文本编辑 |
---|---|---|---|---|
Ctrl+Space, Ctrl+I | ⌃Space, ⌘I | Trigger suggestion | 触发建议 | |
Ctrl+Shift+Space | ⇧⌘Space | Trigger parameter hints | 触发参数提示 | |
Ctrl+Shift+I | Shift+Alt+F | ⇧⌥F | Format document | 格式化文档 |
Ctrl+K Ctrl+F | ⌘K ⌘F | Format selection | 格式化所选部分 | |
F12 | F12 | Go to Definition | 转到定义 | |
Ctrl+Shift+F10 | Alt+F12 | ⌥F12 | Peek Definition | 速览定义 |
Ctrl+K F12 | ⌘K F12 | Open Definition to the side | 在侧边显示定义 | |
Ctrl+. | ⌘. | Quick Fix | 快速修复 | |
Shift+F12 | ⇧F12 | Show References | 显示引用 | |
F2 | F2 | Rename Symbol | 重命名符号 | |
Ctrl+K Ctrl+X | ⌘K ⌘X | Trim trailing whitespace | 裁剪尾随空格 | |
Ctrl+K M | ⌘K M | Change file language | 更改文件语言 |
Linux | Win | Mac | Navigation | 导航 |
---|---|---|---|---|
Ctrl+T | ⌘T | Show all Symbols | 显示所有符号 | |
Ctrl+G | ⌃G | Go to Line... | 转到指定行... | |
Ctrl+P | ⌘P | Go to File... | 转到文件... | |
Ctrl+Shift+O | ⇧⌘O | Go to Symbol... | 转到符号... | |
Ctrl+Shift+M | ⇧⌘M | Show Problems panel | 显示问题面板 | |
F8 /Shift+F8 | F8 / ⇧F8 | Go to next/previous error or warning | 转到下一个/上一个错误或警告 | |
Ctrl+Shift+Tab | ⌃⇧Tab | Navigate editor group history | 在编辑器组(窗口)历史记录间进行导航(先按住 Ctrl+Shift,再按 tab 切换) | |
Ctrl+Alt+ - /Ctrl+Shift+ - | Alt+ ← / Alt+ → | ⌃- / ⌃⇧- | Go back/forward | 后退/前进 |
Ctrl+M | ⌃⇧M | Toggle Tab moves focus | 切换 tab 键移动焦点(先按下 ⌃⇧M / Ctrl+M,再多按几次 tab 键 移动焦点 ) | |
Ctrl+Shift+B | ⌃⇧B | Run build task | 运行生成(构建)任务 | |
🤔️ | Run task | 运行任务,默认为空,您可以自定义为 ⌃⌘B |
Linux | Win | Mac | Editor management | 编辑器管理 |
---|---|---|---|---|
Ctrl+W | Ctrl+F4, Ctrl+W | ⌘W | Close editor(file/tag) | 关闭编辑器(文件/标签页) |
Ctrl+K F | ⌘K F | Close folder | 关闭文件夹 | |
Ctrl+\ | ⌘\ | Split editor | 拆分编辑器窗口 | |
Ctrl+1 / Ctrl+2 / Ctrl+3 | ⌘1 / ⌘2 / ⌘3 | Focus into 1st, 2nd, 3rd editor group | 聚焦(切换)到第 1、第 2 或 第 3 个编辑组(窗口) | |
Ctrl+K Ctrl+← / Ctrl+K Ctrl+→ | ⌘K ⌘← / ⌘K ⌘→ | Focus into previous/next editor group | 切换到上一个/下一个窗口 | |
Ctrl+Shift+PgUp / Ctrl+Shift+PgDn | ⌘K ⇧⌘← / ⌘K ⇧⌘→ | Move editor left/right | 向左/右移动编辑器 | |
Ctrl+K ← / Ctrl+K → | ⌘K ← / ⌘K → | Move active editor group | 向左/右移动正在使用(活动中)的窗口 | |
Ctrl+PgUp / Ctrl+PgDn | ⌥⌘←/⌥⌘→ | Focus into previous/next editor | 切换到上一个/下一个编辑器 | |
🤔️ | Ctrl+Alt+ → / Ctrl+Alt+ ← | ⌃⌘→/⌃⌘← | Move editor into next/previous group | 将编辑器移动到下一组/上一组 |
Alt+F4 | ⌘Q | Quit vscode | 退出 vscode |
Linux | Win | Mac | File management | 文件管理 |
---|---|---|---|---|
Ctrl+N | ⌘N | New file | 新文件 | |
Ctrl+O | ⌘O | Open file... | 打开文件... | |
Ctrl+S | ⌘S | Save | 保存 | |
Ctrl+Shift+S | ⇧⌘S | Save as... | 另存为... | |
🤔️ | Ctrl+K S | ⌥⌘S | Save all | 全部保存 |
Ctrl+W | Ctrl+F4 | ⌘W | Close | 关闭 |
Ctrl+K Ctrl+W | ⌘K ⌘W | Close all | 全部关闭 | |
Ctrl+Shift+T | ⇧⌘T | Reopen closed editor | 重新打开关闭的编辑器 | |
Ctrl+K Enter | ⌘K Enter | Keep preview mode editor open | 保持预览模式下的编辑器处于打开状态 | |
Ctrl+Tab / Ctrl+Shift+Tab | ⌃Tab / ⌃⇧Tab | Open next / previous | 打开下一个/上一个 | |
Ctrl+K P | ⌘K P | Copy path of active file | 复制活动文件的路径 | |
Ctrl+K R | ⌘K R | Reveal active file in File Explorer | 在文件管理器中显示活动文件 | |
Ctrl+K O | ⌘K O | Show active file in new window/instance | 在新窗口/实例中显示活动文件 | |
Space, Enter | Space | Open file in explorer | 先按下 ⇧⌘E 打开资源管理器,然后按方向键选择文件,最后按下空格打开文件 | |
Ctrl+Enter | ⌃Enter | Open file to the side | 在侧边打开文件 | |
F2 | Enter | Rename file in explorer | 在资源管理器中重命名文件 | |
Delete | ⌘Backspace | delete file in explorer | 在资源管理器中删除文件 |
Linux | Win | Mac | Display | 显示 |
---|---|---|---|---|
F11 | ⌃⌘F | Toggle full screen | 切换全屏 | |
Shift+Alt+0 | ⌥⌘0 | Toggle editor layout (horizontal/vertical) | 切换编辑器布局(水平/垂直) | |
Ctrl+ = / Ctrl+ - | ⌘= / ⇧⌘- | Zoom in/out | 放大/缩小 | |
Ctrl+B | ⌘B | Toggle Sidebar visibility | 切换侧边栏的可见性(打开/关闭侧边栏) | |
Ctrl+Shift+E | ⇧⌘E | Show Explorer / Toggle focus | 显示资源管理器/切换焦点 | |
Ctrl+Shift+F | ⇧⌘F | Show Search | 显示搜索 | |
Ctrl+Shift+G | ⌃⇧G | Show Source Control | 显示源代码控制 | |
Ctrl+Shift+D | ⇧⌘D | Show Debug | 显示调试 | |
Ctrl+Shift+X | ⇧⌘X | Show Extensions | 显示扩展 | |
Ctrl+Shift+H | ⇧⌘H | Replace in files | 替换文件中的内容 | |
Ctrl+Shift+J | ⇧⌘J | Toggle Search details | 切换搜索详情 | |
Ctrl+K Ctrl+H | Ctrl+Shift+U | ⇧⌘U | Show Output panel | 显示输出面板 |
Ctrl+Shift+V | ⇧⌘V | Open Markdown preview | 打开 markdown 预览 | |
Ctrl+K V | ⌘K V | Open Markdown preview to the side | 在侧边打开 markdown 预览 | |
Ctrl+K Z | ⌘K Z | Zen Mode (Esc to exit) | 禅模式(按 Esc 退出) | |
Ctrl+Shift+C | ⇧⌘C | Open new command prompt/terminal | 打开新的命令提示符/终端 |
Linux | Win | Mac | Debug | 调试 |
---|---|---|---|---|
F9 | F9 | Toggle breakpoint | 切换断点 | |
F5 | F5 | Start/Continue | 开始/继续 | |
F11 / Shift+F11 | F11 / ⇧F11 | Step into/ out | 单步调试:进入/跳出 | |
F10 | F10 | Step over | 单步跳过 | |
Shift+F5 | ⇧F5 | Stop | 停止 | |
Ctrl+K Ctrl+I | ⌘K ⌘I | Show hover | 显示悬停 |
Linux | Win | Mac | Integrated terminal | 集成终端 |
---|---|---|---|---|
Ctrl+` | ⌃` | Show integrated terminal | 显示集成(内置)终端 | |
Ctrl+Shift+` | ⌃⇧` | Create new terminal | 创建新的终端 | |
Ctrl+C | Interrupt foreground process | 中断运行中的前台进程 | ||
Ctrl+Shift+C | Ctrl+C | ⌘C | Copy selection | 先用鼠标选中,再按下“指定按键”进行复制 |
Ctrl+Shift+V | Ctrl+V | ⌘V | Paste into active terminal | 粘贴 |
Ctrl+Shift+ ↑ / Ctrl+Shift+ ↓ | Ctrl+Alt+PgUp / Ctrl+Alt+PgDn | ⌘↑ / ↓ | Scroll up/down | 向上/向下滚动(行) |
Shift+ PgUp / Shift+ PgDn | PgUp / PgDn | Scroll page up/down | 向上/向下滚动(页) | |
Shift+ Home / Shift+ End | Ctrl+ Home / Ctrl+ End | ⌘Home / End | Scroll to top/bottom | 滚动到顶部/底部 |
Ctrl+Shift+5 | ⌘\ , ⌃⇧5 | Split terminal | 拆分终端 | |
Ctrl+PgUp / Ctrl+PgDn | ⇧⌘[ / ] | Focus previous/next terminal | 聚焦(切换)上/下 一个 终端 | |
Alt+ ↑ / Alt+ ↓ | ⌥⌘↑ / ↓ | Focus previous/next terminal in group | 在终端组中切换 上/下 一个终端 | |
Ctrl+Shift+ ← / Ctrl+Shift+ → | 🤔️ | ⌃⌘← / → | Resize terminal left/right | 调整终端大小(左/右) |
🤔️ | ⌃⌘↑ / ↓ | Resize terminal up/down | 调整终端大小(上/下) |
附录
这里包含了一些额外的内容。
- 区域代号 <ISO 3166-1 Alpha-2 code>
- todo
- 本章将介绍本项目有可能会实现的一些思路
区域代号
"Afghanistan": AF
"Albania": AL
"Algeria": DZ
"American Samoa": AS
"Andorra": AD
"Angola": AO
"Anguilla": AI
"Antarctica": AQ
"Antigua and Barbuda": AG
"Argentina": AR
"Armenia": AM
"Aruba": AW
"Australia": AU
"Austria": AT
"Azerbaijan": AZ
"Bahamas": BS
"Bahrain": BH
"Bangladesh": BD
"Barbados": BB
"Belarus": BY
"Belgium": BE
"Belize": BZ
"Benin": BJ
"Bermuda": BM
"Bhutan": BT
"Bolivia": BO
"Bosnia and Herzegovina": BA
"Botswana": BW
"Brazil": BR
"British Indian Ocean Territory": IO
"British Virgin Islands": VG
"Brunei": BN
"Bulgaria": BG
"Burkina Faso": BF
"Burundi": BI
"Cambodia": KH
"Cameroon": CM
"Canada": CA
"Cape Verde": CV
"Cayman Islands": KY
"Central African Republic": CF
"Chad": TD
"Chile": CL
"China": CN
"Christmas Island": CX
"Cocos Islands": CC
"Colombia": CO
"Comoros": KM
"Cook Islands": CK
"Costa Rica": CR
"Croatia": HR
"Cuba": CU
"Curacao": CW
"Cyprus": CY
"Czech Republic": CZ
"Democratic Republic of the Congo": CD
"Denmark": DK
"Djibouti": DJ
"Dominica": DM
"Dominican Republic": DO
"East Timor": TL
"Ecuador": EC
"Egypt": EG
"El Salvador": SV
"Equatorial Guinea": GQ
"Eritrea": ER
"Estonia": EE
"Ethiopia": ET
"Falkland Islands": FK
"Faroe Islands": FO
"Fiji": FJ
"Finland": FI
"France": FR
"French Polynesia": PF
"Gabon": GA
"Gambia": GM
"Georgia": GE
"Germany": DE
"Ghana": GH
"Gibraltar": GI
"Greece": GR
"Greenland": GL
"Grenada": GD
"Guam": GU
"Guatemala": GT
"Guernsey": GG
"Guinea": GN
"Guinea-Bissau": GW
"Guyana": GY
"Haiti": HT
"Honduras": HN
"Hong Kong": HK
"Hungary": HU
"Iceland": IS
"India": IN
"Indonesia": ID
"Iran": IR
"Iraq": IQ
"Ireland": IE
"Isle of Man": IM
"Israel": IL
"Italy": IT
"Ivory Coast": CI
"Jamaica": JM
"Japan": JP
"Jersey": JE
"Jordan": JO
"Kazakhstan": KZ
"Kenya": KE
"Kiribati": KI
"Kosovo": XK
"Kuwait": KW
"Kyrgyzstan": KG
"Laos": LA
"Latvia": LV
"Lebanon": LB
"Lesotho": LS
"Liberia": LR
"Libya": LY
"Liechtenstein": LI
"Lithuania": LT
"Luxembourg": LU
"Macau": MO
"Macedonia": MK
"Madagascar": MG
"Malawi": MW
"Malaysia": MY
"Maldives": MV
"Mali": ML
"Malta": MT
"Marshall Islands": MH
"Mauritania": MR
"Mauritius": MU
"Mayotte": YT
"Mexico": MX
"Micronesia": FM
"Moldova": MD
"Monaco": MC
"Mongolia": MN
"Montenegro": ME
"Montserrat": MS
"Morocco": MA
"Mozambique": MZ
"Myanmar": MM
"Namibia": NA
"Nauru": NR set-src-link
"Nepal": NP
"Netherlands": NL
"Netherlands Antilles": AN
"New Caledonia": NC
"New Zealand": NZ
"Nicaragua": NI
"Niger": NE
"Nigeria": NG
"Niue": NU
"North Korea": KP
"Northern Mariana Islands": MP
"Norway": NO
"Oman": OM
"Pakistan": PK
"Palau": PW
"Palestine": PS
"Panama": PA
"Papua New Guinea": PG
"Paraguay": PY
"Peru": PE
"Philippines": PH
"Pitcairn": PN
"Poland": PL
"Portugal": PT
"Puerto Rico": PR
"Qatar": QA
"Republic of the Congo": CG
"Reunion": RE
"Romania": RO
"Russia": RU
"Rwanda": RW
"Saint Barthelemy": BL
"Saint Helena": SH
"Saint Kitts and Nevis": KN
"Saint Lucia": LC
"Saint Martin": MF
"Saint Pierre and Miquelon": PM
"Saint Vincent and the Grenadines": VC
"Samoa": WS
"San Marino": SM
"Sao Tome and Principe": ST
"Saudi Arabia": SA
"Senegal": SN
"Serbia": RS
"Seychelles": SC
"Sierra Leone": SL
"Singapore": SG
"Sint Maarten": SX
"Slovakia": SK
"Slovenia": SI
"Solomon Islands": SB
"Somalia": SO
"South Africa": ZA
"South Korea": KR
"South Sudan": SS
"Spain": ES
"Sri Lanka": LK
"Sudan": SD
"Suriname": SR
"Svalbard and Jan Mayen": SJ
"Swaziland": SZ
"Sweden": SE
"Switzerland": CH
"Syria": SY
"Taiwan": TW
"Tajikistan": TJ
"Tanzania": TZ
"Thailand": TH
"Togo": TG
"Tokelau": TK
"Tonga": TO
"Trinidad and Tobago": TT
"Tunisia": TN
"Turkey": TR
"Turkmenistan": TM
"Turks and Caicos Islands": TC
"Tuvalu": TV
"U.S. Virgin Islands": VI
"Uganda": UG
"Ukraine": UA
"United Arab Emirates": AE
"United Kingdom": GB
"United States": US
"Uruguay": UY
"Uzbekistan": UZ
"Vanuatu": VU
"Vatican": VA
"Venezuela": VE
"Vietnam": VN
"Wallis and Futuna": WF
"Western Sahara": EH
"Yemen": YE
"Zambia": ZM
"Zimbabwe": ZW
TODO
tmm
installation
- 方法 4
- 工具: apt
- 平台: tmoe
- 条件: 您已经添加了 neko/tinor 仓库
- 命令 1:
apt install tmm
- 命令 2:
apt install tmoe-2021
- 命令 1:
- 方法 5
- 工具: cargo
- 平台: crates.io
- 条件: 您想要手动编译
- 命令:
cargo install tmm
- 命令:
config
运行以下命令
tmm new uuu ubuntu:kinetic
然后它会输出 "ubuntu:kinetic" 的容器属性信息(只读), 接着会在当前目录下生成 uuu.toml 配置文件(可写)。
实际配置会比以下内容更加全面,以下内容仅供参考
name = "uuu"
arch = "arm64"
cmd = ["bash", "-l"]
# user = "root"
user = "0:0"
path = "/xxx/yyy/uuu"
[os]
name = "ubuntu"
code = "kinetic"
[image]
file = "/sdcard/Download/backup/ubuntu-22.10-rootfs.tar.zst"
name = "ubuntu"
tag = "kinetic"
sha256 = "2e72d56249c7b3894d9d5baef5f1fd8fd7aa0fcf8a5253d77ceb7bbfc40d660b"
[mount]
name = [
"sd",
"tf",
"pic",
]
[mount.sd]
enabled = true
type = "bind"
src = "/data/media/0/Download"
dst = "/media/sd"
[mount.pic]
enabled = false
[mount.tf]
enabled = false
[env]
# PATH = ""
# 这是一个小细节,对普通用户和 root 用户使用不同的 PATH。
# 普通用户的 PATH 不应该包含 /sbin
ROOT_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/sbin:/usr/bin:/bin"
NORMAL_PATH = "/usr/local/bin:/usr/games:/usr/bin:/bin"
手动修改这个配置
用 set 子命令修改
tmm set uuu path "/data/data/xxx/yyy/uuu"
用 get 获取
tmm get uuu image.tag
# 输出 kinetic
也可以直接修改配置文件。
最后运行tmm r uuu
或者是 tmm run uuu