4
3
2019
0

正确的隐藏挂载点的方法

脚本需要挂载文件系统,但是不希望外部看到。正确的做法是:

mount --make-rprivate /

然后该干嘛干嘛。当然如果你不知道在执行之前先调用 unshare 或者等价的系统调用,说明这篇文章不适合你阅读。

错误的做法是在挂载的时候加 --make-private 或者把 / --make-private。这个标志(MS_PRIVATE)的意思是挂载/卸载事件在这里停止传播,而不是这个挂载点的事件是否传播出去。至于为什么需要使用 --make-rprivate(增加了 MS_REC 标志),暂时我还不理解。

这个用法是从 unshare 工具的 strace 结果里挖掘出来的。因为我的目的跟 unshare -m 一样嘛,当然首先想到的是看看它是怎么干的了。你问我为什么不用 unshare -m?你自己写脚本的时候试试看啰?

Category: Linux | Tags: linux 文件系统
3
31
2019
8

迁移系统到 SSD

最近一段时间,不知道是磁盘、缓存相关算法的更新,还是我开的服务太多,又或者是新软件占用内存太高,我的系统越来越卡了,尤其是更新系统的时候(备份系统时也特别卡,然后我用限制内存占用的办法解决了)。我当然知道最主要的原因是因为机械硬盘的处理能力就那么多,于是经过一些了解和计划之后,还是决定换 SSD 了。

刚才查看了一下历史数据。从去年七八月份起,平均内存使用量从2G多升高到了3G多。大概是火狐更占内存了吧。我都尽量减少内容进程数量了……也可能是 PHP / MediaWiki 的锅,因为使用 SQLite 存储时,经常发生错误也是这段时间的事情。不过也可以理解为由于磁盘负载重导致的。算了不管了。

准备工作

当然首先要去买块 SSD 啦。我买的是 LITEON T11 Plus 512,512GB,800块。实际操作系统得到的空间是 477GiB,因为硬盘产业还在沿用1000进制的单位词头。它比我预期的要小不少呢,不过拿在手里感觉比一般同样大小的电路板要重。

拆开我的 T470p,把空闲接口旁边的螺丝下下来,然后 SSD 标签朝外插进去。我也不清楚这个接口叫什么。插进去之后它是翘起来的,难怪要用螺丝固定。然后用下下来的螺丝固定好,再把机器装好,就好了。启动系统,可以看到 /dev/nvme0n1 设备在了~GNOME 磁盘软件不能读取到 SMART 信息,用 smartctl -a /dev/nvme0n1 命令就好了。

设备没问题了,接下来当然是备份系统啦。

开始迁移

备份妥当之后,我就开始格式化 SSD。计划是 EFI 分区 512M,400G 给我的 Arch Linux,然后剩下 76G 左右的空间预留给我的 Win10。

然后这 400G,首先上一层 LUKS 加密,然后格式化为 btrfs 文件系统。其实我想要 btrfs 很久了,快照、去重、压缩都挺棒的。但是听说它的性能比较差,而我已经在受磁盘 I/O 能力不足的苦了,所以到现在有了 SSD,是时候换 btrfs 了!

其实之前 zfs(zfsonlinux)也是候选项,并且已经在工作本上使用过了。然而最近我的 zfs 备份两度出现问题(磁盘掉线之后 zfs 元数据损坏,导致一整个 zfs 文件系统一写就卡住;近期莫名其妙 rsync 跑着跑着就卡在那里不动了,磁盘也没有什么活动),再加上之前遇到的各种大小问题(ARC 被算进内存使用量中;挂载期间一旦磁盘离线就卡死;克隆出来的文件系统无法摆脱原文件系统;不支持 overlayfs;因为是树外模块所以需要专门准备的支持 zfs 的系统来执行安装),以及 TRIM 支持刚刚才加入,我已经停用 zfs 并将其排除考虑范围了。

然后就是规划子卷。参考了 openSUSE 的方案,最终决定分为这么几个子卷:/, /var/cache, /var/tmp, /var/log, /var/lib/lxc/lxc-debian/rootfs, /var/lib/lxc/lxc-centos6/rootfs, /home/lilydjwg, /home/lilydjwg/.cache。主要考虑的是快照。另外我给 /var/log/journal 和 /var/lib/postgres chattr +C 禁用了 CoW。这样也会禁用压缩,不过本来它们基本上就没什么可压缩的。需要排除的有:我的公开第三方源码和各类大文件用的 /ldata 还是放在机械硬盘上、/var/cache/pacman/pkg 缓存不要、/var/lib/pacman.fs 不用单独放连续的文件里了、/home/lilydjwg/.cache 缓存不要、/home/lilydjwg/.debug 这个 perf top 用的目录会有 libc 的硬链接,rsync 时会失败所以就不要了。

最终的同步命令如下:

sudo systemd-run -p MemoryMax=64M --scope \
  rsync -aviHAXKhPS --inplace --delete --exclude='*~' --one-file-system \
  / /mnt/root --exclude=/var/cache/pacman/pkg --exclude=/home/lilydjwg/.cache \
  --exclude=/var/lib/pacman.fs --exclude=/ldata --exclude=/home/lilydjwg/.debug

同步好之后,重启进入 live 系统再同步一次以保证最新数据也同步好了。然后把部分被排除的目录再同步一下:~/.cache/winetricks 这个以后不一定能够下到、~/.cache/sxiv 都是有效缓存(我有清理)而且生成耗 CPU、/var/lib/pacman 这个是被 --one-file-system 排除掉的。

然后是在 /etc/default/grub 里更新内核命令行 cryptdevice=/dev/disk/by-partlabel/ssd:ssd:allow-discards。这个 allow-discards 会轻微地降低安全性,不过在中国没什么用的。更新 /etc/fstab。

然后还有 /boot 要处理。其实就是把内核和 initrd 复制过去,然后重新安装 grub、生成 grub 配置。位于机械硬盘上的旧文件之后再删掉即可。

重启,使用 fallback 版 initrd 进入系统,开始修复各种问题。

首先是更新默认的 initrd。不过在更新它之前,我要修改一下我自己的 hook。之前这个 hook 里只有 partprobe 我解密之后的机械硬盘分区,因为我在它上边又分了 xfs 和 swap 两个区。现在因为 encrypt hook 解密的是 SSD 上的分区,所以这个机械硬盘上的加密分区的解密也要自己做。其实也很简单,给这个加密分区添加一下文件密钥,然后

cryptsetup open --type=luks --key-file=/etc/keys/hdd.luks /dev/disk/by-partlabel/main main

就可以了。不需要输入两次密码。

/ldata 使用 automount 延迟挂载,所以需要写 ldata.mount 和 ldata.automount 两个文件,然后 enable ldata.automount 那个。不知道写在 /etc/fstab 里是不是也行。然后把机械硬盘里的目录结构调整一下,把原来 /ldata 下的东西上移一级,旧的 / 里的其它东西都放到隐藏的 .oldroot 里去好了。

swap 本来我是保留着的,不过发现这样子我会时不时听到机械硬盘启动了。而且因为机械硬盘启动比较费时,所以系统会卡好一会儿(大概有一两秒)……所以我默认就不开 swap 了,但是 resume hook 还是保留,需要的时候打开 swap 就可以休眠了。这个 resume hook 也是我需要在启动的时候就解密机械硬盘上的加密分区的原因。

加了一个每周运行的 fstrim -v / cron 任务。没有使用 fstrim.timer 是因为它会 trim 所有设备。而我可不希望它去 trim 我挂载的机械硬盘上的 loop 设备,会造成大量碎片的。

还有一些小问题要处理。chattr +i /etc/resolv.conf 以避免 DNS 服务器被不知不觉修改了。我有用 dnsmasq 的所以这个文件不用动。我有一个 MediaWiki 实例的文件是使用 overlayfs 的,它现在挂载提示「failed to verify upper root origin」。后来才发现相关目录上有同步到几个 trusted. 开头的、overlayfs 使用的扩展属性。是它还挂载的时候被同步到的,不知道为什么最后一次同步时没有被清除掉。手动使用 setxattr 删除掉就好了。

rsync 还出了另外几个莫名其妙的问题。我在 /usr/local/sbin 下有个最近新加的文件的执行权限消失了,造成使用它的 systemd 服务失败。另外有个最近被删除的配置文件竟然还在。我不是有指定 --delete 选项吗?火狐缓存的网站图标也都没有了,需要访问之后才会重新出现。~/.cache 下有很多 root 所有的空目录,也许是我哪次忘记 --exclude 它然后又中断才加上?

Wine 有几个文件有几十 KiB 大的 user.wine.sd 扩展属性。太大了以至于 btrfs 里放不下,报「No space left on device」错误。我刚看到时还吓一跳,以为是我的 SSD 满了,仔细一看才发现只是扩展属性写不下而已。

我于是又带 --dry-run 参数同步了一次,确定再没有什么需要的东西被落下。这次 rsync 出现这些问题很是奇怪,不过我没有留日志,加上操作的时候其实是有不少修修改改的,所以就不深究了吧。

修好所有发现的问题,再次重启之后,systemctl status 和 systemctl --user status 没有失败项了~撒花 O(∩_∩)O~

后记

现在我的系统超快的!比如启动时间:

>>> systemd-analyze
Startup finished in 9.257s (firmware) + 1.466s (loader) + 15.110s (kernel) + 6.945s (userspace) = 32.780s 
graphical.target reached after 6.945s in userspace

firmware 和 loader 咱管不了。kernel 那儿包含了我输入密码解密,以及解密和探索机械硬盘上的分区,所以花了些时间。userspace 那里你别看花了好几秒,其实大部分时间都是花在联网上了。不依赖网络的服务在差不多一秒的时间内就全部启动好了。

之后我还要更新备份脚本,因为我用了 --one-file-system 而现在它们在不同的子卷上。再写一下每日快照的脚本,就不用一不小心删错文件啥的都要去备份里找了。

关于写入量,smartctl -a /dev/nvme0n1; sleep 300; smartctl -a /dev/nvme0n1 统计了一下,因为我开了 collectd 收集一些系统数据,每分钟大概会写入 60MiB 的数据。算下来,一年要写 20T 左右。这块 SSD 标称的是 280TBW,也就是可以写 280TB 的数据。这么算起来能用十年,所以就这样吧,不用再优化了。顺便说一下,SMART 信息里的「Data Units Written」数据,乘以 512000 之后是字节数。

就这样啦。最后还要说一句:SSD 超快的!

3
3
2019
16

使用 cgroups net_cls 来让 docker 走代理

我这里 docker hub 连不上或者连上了访问很慢,根本没法用。本来我常规代理的办法,要么是 proxychains,要么是用 iptables 代理特定的 IP 段。至于 docker 嘛,亚马逊的 IP 段那么多,它用到的域名我也不是很清楚,一点点加好麻烦。作为系统服务,用 proxychains 不仅得修改 systemd 服务配置,而且不知道会不会出什么幺蛾子。最近刚好在某个地方看到这一手,就试试啰。

其实用法很简单的。去 /sys/fs/cgroup/net_cls 下建立个目录,往 net_cls.classid 里写一个整数(支持十六进制的 0x 表示法),然后把 dockerd 的 pid 写到 cgroup.procs 里去。最后用 iptables 代理这部分流量即可。现在都用 443 端口啦,所以只要代理它便好,也避免影响了别的东西:

iptables -t nat -A OUTPUT -p tcp --dport 443 -m cgroup --cgroup 0x110001 -j REDIRECT --to-ports XXX

XXX 是 ss-redir 的端口啦。

注意不要把进程的 pid 往 tasks 文件里写。那里得写的是 task 的 id 而不是 process 的 id,也就是说(用内核的术语来说)是线程的 pid 而不是进程的 tgid(thread group id)。所以非要写 tasks 文件的话,得把 docker 所有的线程的 pid 都写进去才行。真是混乱呢……画个表格好了:

用户态 内核 相关系统调用
pid tgid getpid, kill
tid pid gettid, tgkill
process task group fork, clone without CLONE_THREAD
thread task clone with CLONE_THREAD

另外如果更新过内核的话,那句 iptables 有可能会找不到模块的。(所以更新内核之后还是重启一下以避免尴尬吧。)

Category: shell | Tags: iptables linux cgroups 网络 代理
3
2
2019
2

使用 cgroups 限制指定进程的内存使用

最近我的系统有这么个问题:在备份或者系统更新等高 I/O 负载的时候,系统整体性能下降严重,界面经常卡到动不了。经过分析发现此时比平常多了许多磁盘读操作。平常的时候磁盘读操作是很少的,会有大量的缓存命中,反倒是写操作一直都有(因为我本地搭了个监控系统)。啊对,分析用到的磁盘性能数据就是来自于这个监控系统。

所以原因很清楚了:备份和系统更新不仅造成了大量缓存未命中,还占用了本来存放着热数据的缓存,导致常规使用的缓存命中率也急速下降,结果我的机械硬盘就忙不过来了。

那么,要是能够限制这些操作占用的缓存,性能不就能好一点吗?那些新读进来的数据反正是短期内再也用不到了,缓存起来也只是浪费有限的内存空间啊。

研究了一下 /sys/fs/cgroup/memory/memory.stat,看起来 cgroups 内存限制是包含缓存部分的,于是就试试呗。正好 systemd 直接就能设置了:

$ sudo systemd-run -p MemoryMax=512M --scope pacman -Syu

本来我是设置的 256M 限制,结果发现 dkms 编译内核模块的时候超级慢,还用掉了不少 swap……于是分了 512M。效果还是不错的,常规操作偶尔还是卡一卡(毕竟还是有一些 I/O 操作),但比起不限制的时候要少很多。

要注意一点的是,不使用 cgroups v2 的话(Arch Linux 默认),这个命令不能加 --user 以在用户级的 systemd 下跑的。而使用 cgroups v2 的话,lxc 和 docker 都跑不了……

备份也是类似的,而且因为 rsync 自己用不到多少内存,这个效果更好:

$ systemd-run -p MemoryMax=256M --scope ./backup-my-system

终于又一次在半小时内完成了备份 QAQ 之前动不动就一两小时的。

我也不知道为什么这个问题近期才出现,总之现在是缓解了。(接下来有空继续计划换 SSD 硬盘的事情~

2020年10月06日更新:现在知道系统在内存不足、使用 swap 的时候出现的卡顿现象可能是这个 bug,并且在 5.5 版本中被修复。我在迁移到 SSD 之后也一直禁用 swap 也是因为这个问题。现在更新到 5.8 之后开启 swap,并没有在 swap 时发生明显的卡顿了。PS: 关于 swap 是干嘛的,可以参看 farseerfc 的这篇《【譯】替 swap 辯護:常見的誤解 》以及后续

Category: Linux | Tags: linux systemd cgroups
2
16
2019
4

在 Linux 下整理磁盘碎片

磁盘碎片其实有两种:文件碎了,和空闲空间碎了。使用 FIEMAP 命令可以获取到文件在磁盘(的逻辑地址上)的分布情况。也是 filefrag -v 命令输出的东西。比如我的 pacman.log 就很碎:

Filesystem type is: 58465342
File size of /var/log/pacman.log is 11052443 (2699 blocks of 4096 bytes)
 ext:     logical_offset:        physical_offset: length:   expected: flags:
   0:        0..    2015:  170210423.. 170212438:   2016:
   1:     2016..    2017:  170567879.. 170567880:      2:  170212439:
   2:     2018..    2027:  170569969.. 170569978:     10:  170567881:
   3:     2028..    2030:  170574582.. 170574584:      3:  170569979:
   4:     2031..    2031:  170574631.. 170574631:      1:  170574585:
   5:     2032..    2033:  170592662.. 170592663:      2:  170574632:
....
 123:     2683..    2687:   56903805..  56903809:      5:   56906403:
 124:     2688..    2698:   56903011..  56903021:     11:   56903810: last,eof
/var/log/pacman.log: 125 extents found

整理的办法也很简单,复制一下,基本上就好了。只要剩余空间足够,小文件会变成一整块,大文件也是少数几块。如果非要弄一整块大的,比如我存放 pacman 数据库的那个小文件系统,可以用 fallocate -l 200M pacman.fs2 这样子的命令分配空间,然后把数据 dd 进去(cp 不行,因为它会先截断文件再写入,之前分配的空间就释放掉了)。

介绍完毕,重点来了:怎么找到那些被写得很碎很碎的文件呢?

对每个文件调用 filefrag 肯定太慢了,所以我写了个库和工具 fiemap-rs 直接调用 FIEMAP。它提供两个工具。一个是 fraghist,统计碎片数量分布直方图,用来了解一下某群文件有多碎。另一个是 fragmorethan,用来寻找碎到一定程度的文件。运行起来是这样子的:

/var/log:
# Number of samples = 712
# Min = 1
# Max = 297
#
# Mean = 11.338483146067423
# Standard deviation = 40.138129228003045
# Variance = 1611.0694179238724
#
# Each ∎ is a count of 13
#
  1 ..  31 [ 658 ]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
 31 ..  61 [  11 ]:
 61 ..  91 [   9 ]:
 91 .. 121 [  10 ]:
121 .. 151 [   6 ]:
151 .. 181 [   5 ]:
181 .. 211 [   3 ]:
211 .. 241 [   2 ]:
241 .. 271 [   3 ]:
271 .. 301 [   5 ]:
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 271
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 277
/var/log/journal/00000000000000000000000000000000/system.journal: 274
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 297
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 274

我系统上最碎的两群文件是 journal 日志和 python2-carbon 的数据文件。carbon 优化做得挺不好的,明明是预分配的固定大小文件啊,不知道怎么的就弄得很碎了。部分程序的日志(如 pacman、getmail)和火狐的 SQLite 数据库也挺碎的。后边这些我已经处理掉了所以示例输出只好用 journal 的啦。

找到想要整理的过碎的文件之后,复制一下就好啦:

for f in $(<list); do sudo cp -a $f $f.new; sudo mv $f.new $f; done

啊对了,工具的编译方法是,获取源码并安装 Rust 之后,在项目根目录里 cargo build --release 然后就可以在 target/release 下找到新鲜的可执行文件了~顺便说一下,这东西是支持 Android 的哦。

Category: Linux | Tags: linux 文件系统 Rust
2
7
2019
9

docker 里几个基本概念的简单类比

首先说明一下,这是一位 docker 新手对于 docker 的粗浅理解。如有不对还请谅解。我很早之前就尝试过使用 docker,然而由于术语的差异,导致我每次运行东西时都傻乎乎地创建了一个新的容器……现在感觉用法终于是弄对了,所以整理一下,将其类比到 Linux 上的普通软件的概念上。

image
相当于软件分发中的软件(安装)包
Dockerfile
跟 PKGBUILD 类似,是用于制作一个 image 的打包脚本。用 docker build -t name:tag . 就可以制作。
container(容器)
一个容器就像是一个安装好了的软件包。该软件已经准备好,随时可以运行了。
docker run
安装」指定的 image。也就是从 image 制作出容器来,顺带着进行首次运行。如果反复使用,会把同一个软件给安装多次。
docker start
就像是「运行」一个已经安装好的软件,容器跑起来了。之前容器的状态(文件的修改)也会生效。
docker ps
列出运行中或者已安装(带 -a 参数)的软件们。前者和 UNIX 命令 ps 类似,后者则没什么相似之处了。
docker exec
在正在运行的软件的环境内执行命令。有点类似于 ssh。
repository
跟 Linux 的包含众多软件的软件源并不一样。这个东西跟软件名类似,用于标识为特定功能的 image 集。发布出来的 repository 名的格式通常是 `owner/name`,跟 GitHub 差不多的。
tag
软件的版本,跟什么 lite、pro、beta 之类区分类似。它并不是用于分类的标签,也不是 git 中对于指定版本的不变的称呼。它更像是 git 的分支在某些情况下的作用,比如 latest tag 就跟 git 仓库的 master 分支一样,总是指向最新的版本。

我经过以上这样的映射之后,docker 理解起来就容易多了,行为也更符合预期。

Category: docker | Tags: linux docker
1
2
2019
2

解析 zxinc IPv6 数据库

很久以前,我就开始使用纯真 IP 数据库来进行简单的 IP 归属查询。离线数据库的好处在于快速、可依赖,我可以使用 ipmarkup 程序来快速地给文本中的所有 IP 地址注解。

然后随着 IPv6 的普及,我越来越多地需要查询 IPv6 地址了。每次 whois 或者找 ipip.net 实在是太没效率了,还暴露隐私。于是看到这个IP地址查询网站提供离线数据库下载之后,就也做了一个和 LinuxToy 介绍的这个程序类似的工具。

数据格式部分参考《纯真IP数据库格式详解》以及该网站自带的简略说明。

ipdb 程序库(带命令行)使用截图:

ipdb 命令行使用截图

用于在文本中标注 IP 的 ipmarkup 工具链接开头给了。另外有一个 cip 可以根据情况调用 ipdb 或者 QQWry。

ipdb 解析部分只依赖 Python 3.5+,下载及更新部分会依赖我同一仓库中的 myutils 库。

顺便我也优化了一下 QQWry 模块:一是没必要保留一个低效率的版本,二是既然用 mmap 了,就去掉了所有的 seek 操作,目前大概是线程安全的了。这种跳转来跳转去的二进制格式,就算不用 mmap,也可以用 pread 来操作,免得要维护个当前文件位置,还线程不安全。

Category: python | Tags: python IP地址
12
25
2018
7

Ant Design 彩蛋事件之我见

事情是这样的:一个叫「Ant Design」的大概有挺多人用的前端框架加入了一个彩蛋,在12月25日这天会更改按钮样式并修改提示文字为无意义的消息。

由于使用广泛,此事搞得比较严重,听说有程序员因此被开除,还有不少老板在跟甲方解释。事发之后,作者发布了道歉,并称「这完全是我个人的一意孤行且愚蠢的决定」。

确实很愚蠢,因为当事人似乎还没有意识到自己为什么「愚蠢」。在程序库中加入未预期的行为,是十分不负责任的表现。

库应当提供机制而非策略,并且具有良好定义的行为。软件中彩蛋这种东西由来已久,为什么这次影响这么大呢?其根本原因不在于它是开源软件,也不在于使用广泛,而是在于——它是库。库能不能提供彩蛋呢?是可以的,只要它是以 opt-in 的形式提供的,并且有文档明确其行为,使用方需要显式启用就没有任何问题。库的作者不需要知道圣诞节还可能在1月,也不需要知道代码是运行在哪个国家,他的职责应当是提供清晰明确的行为,而不是某天给你耍个花招。只有最终面向用户的应用才知道什么样的彩蛋对于它的用户是合适的,所以决定权在于应用。

至于那些在 issue 下边滥骂的人,真是给中国人丢脸唉。

PS: 关于「洋节」,shell909090 有篇文章《关于抵制洋节》。关于阿里巴巴,shell909090 还有篇《最近的阿里月饼事件》。

PPS: 每当国外发生这种令程序员们关注的事件的时候,Internet ArchiveArchive.is 上都有会很多的存档来保留历史,而国内发生这种事件,却并没有多少人去存档。大概有保存意思的人们也只是自私地本地截图保存了吧。

11
24
2018
8

通过 Cloudflare DNS 验证来申请 Let's Encrypt 证书

我本地的 MediaWiki 的证书过期啦,干脆申请个免费证书好了。之所以用 HTTPS,是因为 MediaWiki 不喜欢不加密的 HTTP,会登录不了……

在网上寻找时,发现 certbot 就有 Cloudflare 的插件呢!这下就方便了。首先 pacman -S certbot-dns-cloudflare 装上,然后把自己的 Cloudflare 凭证信息写到一个 ini 文件里:

dns_cloudflare_email = cloudflare@example.com
dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234567

就这么两行。保存好,设置好权限,然后就可以申请证书了:

sudo certbot certonly -d 域名列表 --dns-cloudflare --dns-cloudflare-credentials 凭证ini文件路径

稍微回答几个问题,证书就在 /etc/letsencrypt/live 下准备好啦。在 nginx 里配置一下就好了。

然后还要加个 cron 任务来更新证书(dcron 格式):

@weekly         ID=cert-renew   certbot renew -q

这样就大功告成了。

Category: 网络 | Tags: HTTPS Cloudflare TLS
11
19
2018
2

正确地上传至 PyPI 并展示文档

数年过去了,今天经过多次尝试和询问,我终于成功让 nvchecker 的文档以富文件的形式展示在 PyPI 上了!

正确地打包上传命令是这样的:

rm -rf dist && python setup.py sdist && twine check dist/* && twine upload -s dist/*

如果 twine check 不通过,那么修正后重新执行。如果文档包含错误,PyPI 会以纯文本形式展示。

  • 听说要设置 long_description_content_type。好像 Markdown 才要设置的,rst 不用。
  • 听说不支持 # 开头的链接。实际上现在已经支持了。
  • 听说要用 twine,那就用吧。

原来我之前的文档一直没有正确地渲染,是因为有一个标题的「-」少了两个……

关于 PyPI 的资料是挺多的,但是细节不够详细,陈旧信息很多,唉。

Category: python | Tags: python

| Theme: Aeros 2.0 by TheBuckmaker.com