
最近在帮组里做一批服务器的系统迁移环境从 CentOS 换到了 KeyarchOS (KOS)。迁移本身不算复杂但后续的小工具适配一件接一件压过来。其中有个需求特别不起眼却又每天都绕不开查日程。服务器上没有图形界面装个 Web 日历又太重我最后锁定了 calendar-1.28——一个老牌的命令行文本日历工具正好能把日程查询完整塞进终端。下面这篇文章就是我在 KOS 上从 0 到 1 适配 calendar-1.28 的全过程包括选型理由、编译踩坑、文本日历配置、RPM 打包和 cron 联动给以后要在国产系统上移植同类命令行工具的同学当个参考。1. 为什么要在 KOS 上折腾一个文本日历选型思考1.1 服务器场景下的日程管理空白日常运维、值班排班、版本发布窗口这些事总得有个地方记。笔记本、手机日历都能记但真到了服务器上干活的时候我常常就是一条 ssh 连上去想顺手确认一下这周哪天有排班、哪天不能动生产。这时候打开手机、切日历应用、翻到对应日期成本其实挺高。更别说有些内网环境根本不方便用外部日历同步。图形日历Evolution、Kontact 之类在服务器上不是不能装但拖进来一大堆桌面组件硬盘内存倒无所谓运维模型会变复杂要维护 X 转发、要处理图形会话、还得时刻留意有没有人在上面开着 GUI。就为了查个日程不值当。Web 日历也是一条路但它意味着多维护一个服务涉及端口、认证、数据备份。在团队没有专门工具链的背景下真的是上线容易运维难。命令行日程工具的定位刚好补上这个空白不要守护进程、不要端口、不要图形会话一个二进制、一个文本文件ssh 进去敲一条命令结果就出来了。对于快速日程查询这种轻需求它刚刚好。1.2 calendar-1.28 与系统自带 cal 的根本区别KOS 默认带的cal大家都很熟cal 12 2025能打印出 2025 年 12 月的月历。但cal的定位是显示日历网格它不知道你哪天有会、哪天是发布窗口本质上是个印刷工具。calendar-1.28 则是一个日程引擎。它读入文本格式的日历文件解析12/25 圣诞节、Jan 1 元旦这类规则然后输出从今天开始往后 N 天的所有日程。它解决的是日期规则 → 事件的映射和cal完全是两码事。选 calendar-1.28 而不是自己写个 awk 脚本原因是它已经处理好了大量边界情况跨年、月份天数、日期排序、每年循环规则。自己实现这些逻辑要反复测试才能安心而这个工具被各发行版用了这么多年边界行为比临时脚本靠谱得多。1.3 源码级适配的必要性和总体思路我第一反应是dnf install calendar但 KOS 的默认软件源里并没有这个包。拿 CentOS 7 时代的旧 RPM 硬装又担心 glibc 和依赖符号对不上毕竟系统底子已经不一样了。与其在包依赖里纠缠不如直接走源码适配顺手把 RPM 也打出来后面分发到内网其他机器就不用再编译一次。完整链路是环境盘点 → 读源码、理依赖 → 编译并处理平台差异 → 安装配置 → 设计日程文件 → cron 联动 → rpmbuild 打包。每一步都不复杂但每一步都有值得记录的小坑尤其是老 C 代码碰上新工具链的时候。2. 适配前的环境盘点和依赖链路2.1 确认 KOS 版本、架构与基础工具链动手第一步不是解压源码而是先把目标机器看清楚。cat /etc/os-release uname -m gcc --version make --version rpm --version我这次拿到的是 x86_64 架构、gcc 8.x 的 KOS 环境。gcc 版本很关键老源码往往是在 gcc 4.x 时代写出来的到了 8.x 上C 标准默认值、变量声明检查都变严格了后面踩的坑大多来自这里。另外确认一下有没有装glibc-devel编译时缺头文件多半就是它没装全。顺带说一句架构。x86_64 跑通之后如果目标环境还有 arm64aarch64机器建议也实机验一遍。calendar 是纯 C 实现在 aarch64 上大概率没问题但 rpmbuild 的架构标签和依赖声明会跟着变早发现早处理能省掉后面交付时的疑神疑鬼。2.2 读懂 calendar-1.28 的代码结构与 Makefile拿到源码包之后别急着./configure make先花半小时看结构和 Makefile。calendar-1.28 的核心代码不大主要就是calendar.c加几个配套头文件和日历数据文件。它不像现代项目有一堆抽象层但正因为简单移植起来反而快。Makefile 里值得关注的重点变量是这几个PREFIX、ETCDIR、CC、CFLAGS。我第一遍看的时候注意到它默认PREFIX是/usr/local。如果直接装/usr/local/bin/calendar也能用但后面打 RPM 时路径会比较乱。干脆一开始就定好规范PREFIX/usr配置文件放/etc/calendar。后面所有编译参数、打包路径都按这个基线走。2.3 编译环境准备中容易被忽略的细节开发工具链至少要保证 gcc、make、glibc-devel。calendar 不需要内核头文件所以不用装 kernel-devel。KOS 默认一般带 gcc但make和glibc-devel不一定齐全。用 dnf 一次性装好dnf install -y gcc make glibc-devel rpm-build如果你后面打算打 RPMrpm-build现在装好省得回头再切环境。还有一个容易被忽略的细节locale。后面要写中文日程所以得确认系统里有zh_CN.UTF-8或至少en_US.UTF-8。用locale -a | grep UTF-8看一眼没有的话用localedef生成。这个步骤如果跳过中文日程后续能写进文件但显示出来全是乱码排查时很容易误判成程序本身的问题。3. 源码编译到命令行可用的完整链路3.1 configure 阶段平台识别的坑与处理calendar-1.28 的构建走的是 autoconf 那套老流程执行./configure --prefix/usr --sysconfdir/etc第一次跑 configure 的时候很不顺。检查strptime那一步报了个checking for strptime... no但我知道 glibc 里肯定有这个函数。这种明明有却检查不到的情况多半是头文件声明没暴露。configure 的测试程序默认按 ISO C 编译而strptime在 glibc 里要_XOPEN_SOURCE或_GNU_SOURCE打开才看得到声明。另一个经典问题是老 configure 脚本的cannot guess build type一般出现在 build 目录和新环境差异比较大的时候。处理方式很简单显式告诉它./configure --buildx86_64-unknown-linux-gnu --hostx86_64-unknown-linux-gnu这两个实例都说明一件事老 autoconf 项目在新系统上最常栽的不是功能问题而是识别机制跟不上时代。configure 脚本本质上是一堆探测小程序的组合我们要做的是给它正确的提示而不是一上来就改源码。3.2 make 阶段头文件、链接库和告警处理configure 勉强过了make 又炸了一轮。最经典的报错是这个calendar.c: error: implicit declaration of function strptime根因和 configure 检测失败完全一样默认 C 标准下没有开启strptime的声明。解法有两个任选其一改 Makefile 的CFLAGS加-D_GNU_SOURCE在calendar.c最顶部任何#include之前加#define _GNU_SOURCE我最后选择了打 patch 的方案这样以后重新编译不会忘记。另外还有一个timezone全局变量的兼容问题glibc 较新版本对timezone的处理有变化如果源码里直接extern long timezone;同时又开了_GNU_SOURCE可能和系统头文件冲突。处理办法是改用localtime_r返回的struct tm里的tm_gmtoff字段这对老代码算是比较大的改动patch 里要写清楚注释。patch 结构大概是这样的--- a/calendar.c b/calendar.c -1,4 1,9 #define _GNU_SOURCE #include stdio.h #include stdlib.h #include time.h /* KOS: expose strptime and avoid legacy timezone extern */其他告警也遇到了一些比如-Wformat-truncation、-Wdeprecated-declarations。我的处理原则是安全类告警不忽略格式类告警能改就改改不了先用-Wno-...压住记在 README 里。不要为了消除告警去大改上游逻辑否则后面跟随上游版本升级时会非常痛苦合并上游代码全是冲突。3.3 安装布局与 PATH 配置让它真正能用编译通过后执行make install。因为我 configure 时指定了--prefix/usr二进制落在/usr/bin/calendar数据文件在/usr/share/calendar配置文件目录在/etc/calendar。装完先验证 PATH 和基本执行which calendar calendar -h calendar第一次执行正常情况下会看到No events.这正是预期的。如果提示找不到日历目录手工建一下mkdir -p ~/.calendar到这一步命令行可用就完成了。剩下的核心工作是把日程真正塞进这个文本日历体系里。4. 文本日历的灵魂日程文件格式与中文适配4.1 日程文件的目录结构与解析规则calendar 读取的是纯文本文件。默认会看系统目录/etc/calendar下的全局日历文件以及当前用户~/.calendar下的个人文件。这个设计非常适合多用户服务器管理员可以放公司节假日、发布窗口这类全局事件每个人再放自己的排班和提醒。文件内容的基本格式拿我的实例来说12/25 圣诞节 12/26 版本发布窗口 W52 Jan 1 元旦日期部分兼容月/日和月名 日两种写法后面的内容就是事件描述一行一条。复杂的规则比如每个月的第一个周一每隔一周的周五不同版本支持程度不一样用之前先man calendar确认。我的建议是优先用最简单稳定的月/日格式维护成本最低。有一点必须提醒日程文件本身不要设置成系统对你的个人日程做自动chmod注意文件权限。~/.calendar下的文件默认建议600里面存的排班、会议安排也算敏感信息别让同机其他用户能直接cat出来。4.2 UTF-8 中文日程的兼容性处理老工具对多字节编码的处理是重灾区。calendar 内部很多地方会用isspace()、isalpha()判断字符类型在 UTF-8 locale 下这些函数按宽字符处理通常没问题但一旦 locale 是 C/POSIX多字节字符会被按单个字节解析中文就全废了。我踩的具体坑是这样的第一次写完中文日程文件后执行calendar -A 3日期能正常识别但事件描述出来是一串乱码。查了半天才发现是终端和 locale 不一致——文件本身是 UTF-8可 ssh 会话的LANG还是空的。处理办法两步走确保会话 localeexport LANGen_US.UTF-8或者zh_CN.UTF-8写进~/.bashrc。日程文件用 UTF-8、不要带 BOM 保存。我用 vim 写完后:set fileencodingutf-8确认过。另一个体验问题是对齐。中英文混排的时候calendar 原生输出不会帮你做wcwidth对齐所以终端里看起来可能歪歪扭扭。我常用做法是输出后过一遍column -t再显示或者干脆给 shell 配一个 alias让日常命令更顺。alias schedcalendar -A 3 | column -t4.3 配合 cron 做每日自动提醒把日程做成登录就能看到的形式比手动敲命令更贴近快速日程查询的初衷。我的做法是利用/etc/motd每次 ssh 登录都会显示这个文件的内容把它和 cron 结合起来就实现了每日自动更新日程看板。cron 配置30 8 * * * { /usr/bin/calendar; echo generated at $(date) ; } /etc/motd 21这样值班的人一查服务器ssh 上去第一眼就知道今天有没有排班、有没有发布窗口。如果想发邮件把命令换成30 8 * * * /usr/bin/calendar | mail -s daily schedule opslocalhost但邮件方案有个依赖问题KOS 最小安装不一定带mailx。相比之下写 motd 的方案零依赖无需额外装东西我反而更推荐它在服务器场景先用。5. 从源码到 RPM把日历工具变成 KOS 原生组件5.1 环境准备rpmbuild 目录与打包参数源码编译完后机器上能用这只是第一步。团队里还有好几台同样的 KOS 机器要一台台 configure、编译、安装既低效又难保证一致性。这时候就该打 RPM。先建标准目录树。rpmbuild默认会找~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}没有的话手动建或者装rpmdevtools后执行rpmdev-setuptree。dnf install -y rpm-build mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}把之前编译验证过的源码包原样打一个 tar.gz扔到SOURCES/下tar czf ~/rpmbuild/SOURCES/calendar-1.28.tar.gz calendar-1.28/5.2 spec 文件编写中的关键细节spec 是打包的核心。这里给出一个最小可用版本Name: calendar Version: 1.28 Release: 1%{?dist} Summary: Text-based calendar and reminder tool License: BSD URL: http://example.org/calendar Source0: calendar-1.28.tar.gz %description calendar prints upcoming events from plain-text calendar files. %prep %setup -q %build ./configure --prefix%{_prefix} --sysconfdir%{_sysconfdir} make %{?_smp_mflags} %install rm -rf %{buildroot} make install DESTDIR%{buildroot} %files %{_bindir}/calendar %{_mandir}/man1/calendar.1* %config(noreplace) %{_sysconfdir}/calendar/* %post test -d /etc/calendar || mkdir -p /etc/calendar几个关键点必须说明一下。第一%build里必须写--prefix%{_prefix}。不写的话configure 默认前缀是/usr/local最后 RPMS 里文件会落在/usr/local/bin虽然也是能用但不符常规发行版的 FHS 约定其他工具查找它的时候路径不一致很麻烦。第二make install DESTDIR%{buildroot}是打包的命门。源码的 Makefile 得支持DESTDIR变量calendar 这个老工具是支持的如果遇到不支持的工具就得在%install段手动install -D -m 755 可执行文件 %{buildroot}%{_bindir}/可执行文件。第三%files里配置目录用了%config(noreplace)。这是为了升级 RPM 时不会覆盖用户改过的系统配置。noreplace意味着如果管理员在旧包上改过/etc/calendar下的文件升级时会保留改动不会默默被默认文件顶掉。对带配置文件的小工具来说这是基本素养。第一次rpmbuild -ba calendar.spec常见报错是Installed (but unpackaged) file(s) found: /usr/share/calendar/data.utf8这说明 Makefile 多装了文件但你没声明进%files。处理方法是查看 buildroot 里的目录树把漏掉的文件路径补进%files。不建议为省事直接写%{_datadir}/calendar/*一把梭我一般还是尽量精确避免把不需要的数据也带进包。5.3 安装验证与回滚预案RPM 打完在~/rpmbuild/RPMS/x86_64/calendar-1.28-1.x86_64.rpm拿一台干净机器验证rpm -ivh calendar-1.28-1.x86_64.rpm rpm -qa | grep calendar calendar -A 3验证通过之后这个 RPM 就可以放进团队内部软件源后续其他机器直接dnf install calendar一条命令搞定。卸载也干净rpm -e calendar会按%files清单把文件清掉不留残骸。再补一个回滚场景如果新版本 RPM 有问题旧版还在源里直接dnf downgrade calendar-1.27就能退回。这就是打包带来的运维底气也是我坚持能打包就打包别贪图一时方便直接 make install的核心理由。6. 实测复盘三组测试场景和踩坑清单6.1 命令行查询、脚本调用、cron 触发的实测适配做完来一组贴近真实工作的测试。第一组手动查询。我的常用命令是calendar -A 7也就是看未来七天日程$ calendar -A 7 12/25 圣诞节 12/26 版本发布窗口 W52第二组脚本里调用。把 calendar 的输出接到 grep 上做筛选比如查最近一个月的发布窗口关键词calendar -A 30 | grep 发布这条在站会前的准备脚本里非常实用直接就能看到未来一个月哪些天不能动生产。第三组就是上文说的 motd 联动。我把 cron 写进系统 crontab模拟重新登录确认 motd 显示的是最新的当天日程。三组跑完calendar 的定位就很清晰了它适合做轻量、可脚本化、常驻文本的日程基座不适合替代带参与人、附件、多维提醒的复杂协作日历。拿它去管理一个完整项目组的多角色会议体系属于选型错误。6.2 编译与运行阶段的问题汇总表我把这次碰到的所有问题整理成一个表方便遇到类似情况的同学对号入座现象根因处理方式configure 检查 strptime 失败默认 C 标准未暴露 glibc 扩展声明CFLAGS 加-D_GNU_SOURCE或在源码顶部宏定义编译报 strptime 隐式声明同上与上一条合并处理中文日程显示乱码会话 locale 未设置或文件带 BOMexport LANGzh_CN.UTF-8文件存为无 BOM UTF-8提示找不到日历目录~/.calendar 不存在mkdir -p ~/.calendarrpmbuild 报未打包文件Makefile 装了多余文件未在 %files 声明补全 %files 清单motd 登录不显示部分 KOS 用动态 motd 脚本覆盖检查 PAM 配置与 motd 生成脚本把 cron 输出写到实际生效路径最后一行值得展开说。/etc/motd在某些发行版上会被pam_motd的动态渲染机制覆盖你写了内容登录后却看不到。我当时就是直接重定向到/etc/motd结果 ssh 登录后什么都没有。排错时先cat /etc/motd看文件内容到底更新没有判断是 cron 没跑还是 motd 被覆盖。KOS 上很可能是被/etc/update-motd.d/下的脚本机制接管了解决思路是把自己的生成脚本放进同样目录或者调整 PAM 配置让静态 motd 生效。6.3 这套适配流程对其他工具移植的启发calendar-1.28 这个案子虽然小但适配国产系统的完整套路是相通的。我总结成五步环境基线OS 版本、架构、gcc、glibc 版本先记牢。源码调研Makefile 的 PREFIX、依赖、特性检测逻辑搞清楚。编译修补老代码遇新工具链优先加特性宏、小范围 patch别重写。打包分发能用 rpmbuild 就打包所有机器走 dnf 安装。回归测试命令、脚本、cron 三场景都跑一遍把坑记进文档。这套流程不只对 calendar 有效。后续我继续在 KOS 上移植其他命令行小工具跑的都会是同一个方法论。真正的收获其实不是多了一个日历命令而是沉淀了一条从源码到可用组件的标准化流水线。整个适配过程断断续续做了两天真正写代码的时间其实很少大部分精力耗在搞清楚老 C 源码在新编译器下的脾气上。我想留给大家的经验是碰到老工具别急着抱怨和重写先花半小时看一遍 configure 和 Makefile把它的行为逻辑摸清楚再动手。另外一个很个人的体会是日程查询这种需求不是功能越丰富越好而是离手越近越好——一条命令行加一个纯文本文件比任何大型日程系统都更贴近我日常的工作流。现在我的~/.calendar里存着半年的排班和发布窗口每天登录就能看到更新这种从命令行到文本日历的轻量方案我预计还会在团队里用很久。