听网友说，汇丰香港银行新公布的规定：自2026年1月1日开“汇丰One”账户，且日均资产不足一万元的，会收取100元管理费。这如同一颗石子投到平静的水面，激起了“港卡”微信交流群里的波动：啊……我还没开卡呢，等等我。我决定17日星期三到香港，要办的事情是申请开通 FSMOne 证券投资和香港银行账户。当我想着做一件事时，心里就是老想着，要把事情的前前后后，如怎么过海关、怎么坐车、怎么连接WIFI、怎么在手机应用上申请银行账户等等事情都想一遍又一遍。心里想着事，觉就睡得不怎么好。第二天早上不到六点钟，我就起床，冲麦片、煮鸡蛋和收拾背包了。约六点四十五分，我在手机上，点开“智惠大巴 源平出行”的微信小程序，准备坐七点的车到“河源汽车客运站”，然后打车到“河源东站”，再坐高铁到“香港西九龙站”。没想到，票没了。我不甘心，背着包到“龙湖公园”路边等，或许有人退款或没坐上车，我来补个位。我每隔几秒反复地看手机上的时钟，看着数字从 01 分缓慢地变动，焦急等待着，心里越来越不安，能拦到车的希望越来越低。终于，我看到一辆白色的面包车，车身上有“河源”的字样，但它开得很快，仿佛没有“龙湖公园”这个上车点一样，开走了。唉……今天没人退票，我没能补票上车。我又尝试用手机应用打车，看能否打到“顺风车”到“河源东站”。我的报价是约 60 元，不承担高速费用。等了几分钟，没司机接单。我心里想，不能这么地等下去。我主动拨通上边司机的电话，问能否赶得上从“河源东站” 09:55 分开出的高铁。司机干脆地答，“不可能。你不出高速费，60 元怎么出车！”语气中带着些不耐烦。我袋好手机，调转头，往汽车站走，准备坐大巴车到深圳。我走到汽车站，车站内人很少。我在售票窗口，扫微信，买了张车票，90元。我说要去罗湖口岸，售票员说，上车时，要向跟车的人说明，因为要换车到罗湖的。车是 08:15 分开出的，还早。我站了一阵，觉得无聊，就掏出身份证在闸机里试，看能不能开到闸机。等了几秒，闸机没反应。我又尝试翻转身份证，也是不行。这时，对面路过一个大哥，对我说，“身份证没磁了”。我不信，将身份证从透明的塑料套取出，再放到感应器上，“滋——未到检票时间”，闸机有响应了。客车开出前，有几架三轮货车载来装着桔子的纸箱，跟车的人将纸箱等放到车厢下，拍照。车上乘客不多，约 5 个。可能因为要尽可能接客，所以中途客车先后在三个高速路口接了几个散客，如河源的源城汽车站、惠州等地。当客车走到深圳龙岗“大运”地铁站附近时，跟车的老板对我说，“下只站，尔可以落车，会有另一架车接”。过了一会，客车掉了个头，到了之前的路对面，我看到有几辆车停在路边，其中一辆是绿色的，车头贴有“和平 深圳”的标识。绿车司机从原客车上接过一箱物品，塞到车厢下，问我，“老板，到乃里？”“罗湖口岸”我答。“过香港啊？那可以在莲塘过关啊，还较近、方便。”“吖咪？莲塘还较近啊？从顶里到莲塘还爱几久？”“二十分钟左右。”司机瞄了瞄后视镜附近的电子时钟，此时显示 11:19 分。“过香港做惹业务？”司机边开车边问我。“聊下。想开证券户，投下‘港美股’。”“炒股啊？爱望下来啊。不好把人家骗了。之前，有帮人也系过香港，话系投资，就系投万把两万，有老板保本，每日有两三百收入。后边不了了之，估计拿归本都难。”“嗯，我睇下争，不投钱。”“今日赶吖归？香港伙食好贵嘅喔，一只快餐都爱六七拾。”“不知吖赶得归。我本想在‘河源东站’坐高铁直接到‘香港西九龙’的，但不曾赶到落河源嘅车，网约车又不肯接单。”“系啊。里下网约车，有时司机觉得做不过来，会推掉单子。就系约好嘅单，如果人不齐，第二工也会撤单。坐河源班车，爱提早一工订票争得。”这时，司机的手机响了，有人要上车，他们约好下一个停车地点。接到人后，司机就和他接着聊了。我坐在车窗旁，看路边的建筑。当看到一个凸出来的、波浪样的、写着“莲塘口岸”的建筑时，我知道，要下车了。司机在我背后说，“如果不知路，问下工作人员就得”。我走进“罗湖口岸”，跟着路标指示走，下楼梯，右转从一个小门进到一栋商场内。里边跟其它的商场有些不同，有一些银行的货币兑换店。我跟着“抵港”的指示走。当对面有人相向而行时，觉得离港往深圳的人比去香港的人要多。先刷身份证过闸，再刷通行证和指纹过闸。不知不觉，仿佛只过了一条不过 5 米长的室内天桥，我已经离境，到达香港境内了。我跟着人流，下电梯到了地面一层，这里如同在天桥底下，光线暗，回声大。映入我眼帘的是，各种巴士和目的地的指示牌，如“直达上水”等。路上，有成队、成团的游客，在集结，在嗡嗡响着，等着领队的下一个指令。我的手机收到信息提示，已经激活“香港一日流量包”。我打开“高德地图”，输入“尖沙咀”的目的地，它提示坐公交车，从 B8 经 “廣福邨”站 转 271 车，到“中港城”站下。我找到 B8 车的指示牌，跟着前边的人，排队等下一个班车。我按网友讲的，点亮“乘车码”，扫码上了 B8 车，约 16 元。公交车，有上、下两层。我选择站在后门附近。我盯着挡风玻璃后边的流动文字指示牌，那儿会显示下一个到站信息。车上除了文字指示，还有语音播报，粤语、普通话和英语，但我觉得声音偏小，有时会听不清，我还是看文字，准确些。路上看到有山，绿色的山。我还听到附近两个吖姨的对话，大概知道，其中一辆行李车上，装有三只鸡，可能是在深圳买到的。过了两个站，我从车窗看到，站台上有个坐着电动轮椅的，也要上车。这引起车头四周一阵小骚动，坐轮椅的时不时说，“sorry, 唔好意思……”，其他乘客纷纷让路。公交车的驾驶员，从座位站起来，对后边喊，“唔该，行李收下。”因为车上的轮椅专区里停满了行李小车。过道里的其他乘客，热心地帮忙，将行李车挪到其它位置，腾出地方来。这个过程，用了几分钟。最后，驾驶员帮乘客将座位上的安全带扣上，车继续前行。车上的文字提示，如果想短途换乘的，可以到车头再次刷卡或刷码，以分段结算车费。我不知道需要在哪个站点提前“刷码”，加上过道里站满了乘客，就放弃第二次刷码分段结算了。到“廣福邨”站下车时，约下午一点钟了。头顶上的太阳很亮，我左右一瞧，发现有人穿着短袖和短裤，要打篮球般。我才觉得，天是挺热的。在和平的早上，脚底还冷得疼，想不到，中午到了香港，像是从冬天切换到夏天了。当我尾随前一个人，准备上 271 号车时，看到旁边一个大姐站在那儿，微笑着。我心里有些打鼓，我不会是插队，抢人家头了吧？我上车，扫码，约 11 元。手机提示还有十几个站才到，所以我往车尾走，想找个座位坐。有个大哥，看见我要坐，起身挪了一下放在他前边的“蛇皮袋”，就是那种以前很流行的，出门外出打工常备的、红白条色的编织袋。我看到它，心里一暖：香港也有“蛇皮袋”，也有打工人啊。我坐下后，侧头看窗外的路和建筑。路有四个车道，相邻车道开过的车上，有坐着穿着时髦、戴小白帽的小姐；也有凸显肚腩的大叔；更有捷豹一般跑过的摩托车。哎呀……靓仔好威风，我心里赞叹道。香港没有禁止摩托车。路边的屋子，普遍不高，也不新。有的公家办公楼，旗杆是悬在二楼的檐上。是不是，如果旗杆放到一楼地面，会比较占地方？临近“中港城”站时，我起身，站到后门附近，等差不多看到路边站牌时，我摁了一下，车扶手上的“下车铃”按钮。前边传来女司机的声音，“系咪有落”？“系”，我答。“早滴搇啊，又要我扭返部车”。“唔好意思呀，”我低头。下车后，我站定，看地面，又看了看驶离的公交车，想了想，是不是，香港的公交车，停站时，需要将车驶近站台地面，让乘客一伸脚，就能踩到人行道上？我输入“港威大厦”，让“高德地图”步行导航。约十分钟后，我走到“五座”的铭牌前，发现玻璃门锁着，推、拉都不开到。路的那一边，是海水，景色挺好，但此时的我，只想推开这扇玻璃门。门边上，写着，“如需帮助，请拨打电话”。我没打电话，沿着来路找另外的门。附近是还有一个玻璃门，人来人往的。我瞧了瞧，好像是酒店的侧门。我没进去，急忙打开“小红书”应用，发私信，问 FSMOne 的人，怎么开门。但短时间内，没有回复。我又在两个玻璃门来回走了几次。突然，我看到有两个年轻人，同样，也是想进五座的玻璃门，没拉开，倒回来了。但他们在旁边的玻璃门上停留，交谈了一下，进去了。我模糊地听到，“是个商场，应该也能进”。我跟着，也进去了。没走几步，我转身，看到扶手电梯，豁然开朗，是这儿了。因为 FSMOne 在“小红书”里发的指路视频，就有这个扶手电梯。“请问，我预约，来开户”当我看到一个帅哥正刷卡准备进入时，我问。帅哥点点头，指着里边的一张凳子，说“等阵”。不一会儿，有个年轻的姑娘来问，我说我预约了四点的，现在提前到了，能不能行。姑娘把我领到一个小房间里，房间里放着一张小桌子，两张凳子和一台打印机。姑娘问我预约的编号后离开了。等了几分钟，打印机吐出一张纸来。姑娘再次出现，查看了我的身份证，然后问我表格上的信息是否正确，是否理解，是否同意。我签上名字后，姑娘说可以了。我起身离开，但走错了两次路，因为觉得屋里边的路都是一样大小的，认不到出口。经姑娘提醒后，我才走得出来。我回到面向海港的休息厅里，坐在一张黑色的艺术凳上，通过手机，开始网上申请开通香港的银行账户。当我收起背包，准备离开休息厅时，发现早已过了下班的时间。下到地面时，看到天已经黑了，亮灯了。我突然觉得很累，想起来，从早上六点到现在的下午六点，约十二个小时里，我几乎没吃过东西。我想回家，想躺下休息。经手机上查询，今天回“和平北站”的高铁票，买不到了。我脑中一片空白，懒得想其它方案了。如果这时，有人向我推荐一千元的床位，可以让我立刻放下背包躺下休息，我可能也会答应。“不管怎么样，先回到深圳再说”，我这么想。我跟着导航，路过戏曲中心，走到“香港西九龙站”。高铁站里的人不多，但这时我像掉了魂，没了思考能力。虽然有指路的牌子，但走了一阵后，灯光还是这么暗，我心里有些犯嘀咕，觉得迷了路。我赶忙问扶手梯边的一个工作人员。因为脑袋已经罢工，我没转换成粤语，直接用普通问了，“我想离开香港，请问，要往哪边走？”“上电梯，右手边就是售票大厅。”工作人员很热情地用普通话回答。我上电梯后，果然看到有人扎堆，我也跟过去排，学着前边的人刷身份证过闸。“嗞……”，闸机拒绝。“你还没买票啊？快到那边人工窗口买，还赶得上 15 分的车。”旁边的工作人员说。我赶忙向她指的方向小跑，跟在一个人身后排队。轮到我时，我觉得不对，因为我没看到前边那个人有拿票之类的。我探头问，“这是买票的地方吗？”里边的人一怔，指了指侧边，“那里才是”。我慌忙又小跑到人工售票区前，正当我轮流查看有没闲着的窗口时，后边响起了女声，“请排队买票”。我抬头看过去，忙说，“不好意思”。同时，接过她递过来的一张叫号纸。稍等一、两分钟，就叫到我号了。我点出手机上的付款扫码器，准备付钱。等了几秒，咦……没反应。里边的工作人员急忙说，“是你的付款码，让机器扫。”我才转过弯来，以为如同和平汽车站上，是让我扫微信的个人收款码。付钱后，工作人员笑着问，“是不是第一次坐高铁？”“不是，是太紧张，怕赶不上车了。谢谢。”我答。过第二次闸机时，我的身份证件识别不了。这时，在另一个队列的人，小声地说，“是通行证，拿错了。”我才反应过来，这是香港的闸机，要放通行证和摁指纹的。过闸机后，我继续向前走，突然发现我前边的人很少，才一、两个，但另一队列的人多，我怀疑是不是跟着队了。我转身想折返，没走两步，旁边的工作人员扬扬手说，“向前走，别回走”。原来通道的设计是只能向前，不能折返，避免造成混乱。当站在最后一个闸机前时，我捏了捏通行证，心里想，这回不会拿错证件了吧？“请站到黄线后边，保持距离。”一个工作人员从我面前走过。我看地下，果然，我紧跟着前一个人，到了闸机的前边，而不是 1 米外的等候黄线上。我慌忙退回到黄线后，同时左右看了看，同排有多个闸机，就我一个人踩进黄线前的区域，好尴尬。我有点纳闷，怎么我来香港的时候，不会犯这些低级的错误，走的时候，却一个接一个踩到雷点，仿佛是在演错误的范例。但……总算要上车了，我努力集中精神，看清车票上的座次，提醒自己不要坐错车。当我坐到靠过道的C座位时，我像只泄了气的皮球，无精打采地等待开车。“这是我的座位。”一个年轻的女声说。我没抬头，以为她是A座的，只是看错了位置。我起身从C位坐到A位，继续用手撑着面颊休息。就过了一会，又有一个大姐来问座位。我说我是C座。“这好了，有两个C”大姐笑着说。我突然怀疑是自己坐错了，慌忙从口袋里掏出车票，读着座位号，“06 车 03C ……”哎呀，我看成 06C 了。“不好意思，我看错了，坐错位了”。我边说边起身，准备离开。大姐抬手阻止我，爽快地说，“没关系，你就在这儿坐，我去你的座位。”唉……那时的我，像只没头的苍蝇，没了观察和判断力，持续地犯错。幸好他人能包容，帮助我，才能将这些错误降低到“小笑话”的级别。我坐高铁，在“福田站”下车。走到路边，坐下来，吃了些东西，喝了水。补充体力后，我觉得头脑能运作了，就尝试在手机上打车，看有没顺风车回和平。我报价约 150 元，不承担高速费用。等了约 30 分钟，觉得不会有司机接单了。我就在“美团”应用上，订了附近的一间住宿，110 元。我走过去，路过波浪顶的“市民中心”时，觉得好安静。前台的一个小伙子，拿了我的身份证登记，说，“可以了。806 房。电梯到了 7 楼后，再走一层”。我进到房间，看了看被单和卫生间，是干净的。我瘫坐在小沙发上，通过手机，用“淘宝闪购”点了份“塔斯汀”的汉堡套餐。然后，再在“美团”抢购明天 09:05 分从“深圳北站”到“和平北站”的高铁票。吃汉堡时，觉得辣，后边有点吃不下去了。我发觉长时间在家里吃，胃口不适应煎炸类的食品了。可乐的味道也不太合口，但因为渴，给喝完了。我躺下后，半睡半醒，因为窗外是城中村的过道，来往有电动车声，也有从店铺传来的人声、搬移桌凳的声音……正要睡沉些时，我被蚊子咬醒了。起来找到蚊香，但没找到点火的。索性就打着灯，半躺着睡了。相对于在路上不停地走，现在这么地躺着，觉得也是一种享受了。次日，不到七点，我在微信上问宿管，怎么退房，没回答。我走到楼下前台，问有没人，也没答。我拨打微信语音，宿管还是没有应答。等了几分钟后，我走到店外，看到玻璃门上贴有手机号码。打过去，有人睡意浓浓地问，是日租还是月租的。我答说是昨晚来的，现在要出门坐高铁。他说，“走吧，走吧”。我就走了，打开导航，去“岗厦”地铁入口，要到“深圳北站”了。可能还没到人流高峰期，坐地铁的人不多，我有位坐。约半个小时后到达“深圳北站”。到达“和平北站”，同一站下车的还有其他三、四个人。我们一出站，就有人热情地问要不要搭车，到县城十几吊钱。我笑着摇头，准备在手机里领优惠券后，骑“青桔”电动自行车回去。因为我是第一次骑，所以，我慢悠悠地，边骑边感受左右刹车的力度。当我骑到御水花园附近时，还车。因为前边的果园路段较多人，我怕控制不了电动车。流水式的文字记录后，现在罗列一些时间和费用：17日 07:35 分，和平，中国移动港澳台三地单日流量包，8.8 元 08:!5 分，和平，汽车站，往深圳客车，90 元 11:16 分，深圳，客车接驳，往莲塘口岸 11:57 分，深圳，到达莲塘口岸 12:30 分，香港，B8 线公交车，约 16 元 13:05 分，香港，“廣福邨”站，271 线公交车，约 11 元 14:04 分，香港，“中港城”站 14:13 分，香港，“港威大厦”五座 14:29 分，香港，FSMOne 公司接待厅 17:54 分，香港，离开“港威大厦” 18:34 分，香港，“西九龙站”， 约 68 元 22:04 分，深圳，岗厦“塔斯汀”，约 5 元18日 07:04 分，深圳，“岗厦”地铁，约 4 元 09:05 分，深圳，往“和平北站”，150 元 花销小计：和平：约 102 元 深圳：约 279 元 香港：约 95 元合计约 476 元。如果加上办港澳通行证的 105 元，那么，六百元左右，就可以来回香港“穷游”啦。思考的两个问题：一、为什么要去香港申请证券投资和银行账户？以前学校虽然有模拟投票交易的课程，但我没用心学，没听课。我心底里，认为国内的股市行情，反映不出真正的市场信息。我的个人看法很片面，也有点极端，但从行动上，我选择了在香港开户，而不是在国内开户。我不一定会投钱，但至少，当前我将信任投在了香港。香港银行的开户体验，比国内好得多。两地银行开户的流程差不多，都是要人面识别，要本人本地办理。要承诺是合规使用。但方式上，差别很大。香港的银行，如“中银香港”，是全程用手机操作，网上完成整个开户流程，不用跑到银行排队领号，也不用与银行工作人员面对面交流。反观国内，要签保证书、查社保等环节，堪称“苛刻”。二、在公交车上，香港人文明些。例如，坐轮椅的人搭公交车，公交车司机和其他乘客会提供帮助，他们做得很自然，像日常事情一样。图1：和平县汽车站班车发班时刻表 - sh_hp_sz_hk_1.jpg图2：客车载货图3：和平深圳班车，车票，90 元图4：中途上客，源城汽车站图5：深圳大运地铁站附近，客车接驳，司机在接货图6：莲塘口岸，入口图7：香港 “廣福邨” 公交站图8：红白条纹的“蛇皮袋”图9：“中港城” 公交站图10：海港图11：港威大夏第5座图12：休息厅，及厅外的维多利亚港图13：FSMOne 接待厅图14：“西九龙站”高铁站图15：戏曲中心外观图16：深圳“会展中心”图17：“岗厦”地铁入口附近的，“星河世纪”图18：“深圳北站”，候车厅图19：“和平北站”外的共享电动单车图20：骑共享单车归了
和平县出入境办证窗口搬到工业园妇幼保健院附近
昨天(25日)上午，我突然想去香港，打算下午去公安局办《来往港澳通行证》。下午走到和平大道公安局旁侧的“出入境自助一体办证服务厅”，一看，傻眼了——玻璃门上锁了，贴了张白纸，说：即日起入境所有业务搬迁至（新）和平县政务服务中心和平县公安局出入境管理大队2025年9月24日我想起在微信小程序里瞄过一眼，“和平融媒”的公众号好像发过一段视频，说有“新和平县政务服务中心”了。我赶紧拿出手机，来搜索视频，想知道办证厅具体搬到哪里了。这时，来了一位想办证的，拉着小孩上了台阶那儿的人工办证厅，看到锁了门，也退回到门口了，想知道办证厅的新地址在哪，问我是不是搬到九子岗了。我慌乱点击着手机屏幕，东找西找，终于找到视频，对大姐说，“搬到工业园了”。这时，门口又来一个大姐，笑着接话说，“搬到新妇幼保健院那儿了，昨天搬的”。她还问我要办什么业务，我答说办港澳证。她问，还没填表吧？我答说，没有。她说，那到路对面店里填表，也可以拍照。我问，拍照多少钱。她答说，一样。我再问，是多少钱。她才回答说，30 元，跟自助拍照一样。我说，如果价格是一样，那么就人工拍照吧。我就跟着她到了路对面，拍照，完成了办证的前提——采集数字相片。我还问，老板娘，那儿几点下班。答说是五点半，时间够。工业园的新和平县政务服务中心挺远的，大概就是在工业园主（大）路的尽头，我走到时，是 5 点钟。因为准备好了身份证和数字相片，材料具备，再加上现场采集指纹和摄像，约 5 分钟就搞好了。总结：费用：港澳证工本费 60 元，一年一次的签注是 15 元。在外头照数字相片是 30 元。总共是 105 元。个人体验：我从东山路走到新政务服务中心，很远，单程是约 1 个小时，来回那就是 2 个小时。印象中，这是办港澳证窗口第2次搬迁地点了，从解放路的老公安局到和平大道的新公安局，再到现在的工业园尽头的新政务服务中心。觉得一次比一次搬的远，感叹现在城镇化是很成功的，因为现在办事，光凭双脚走路到办事的地点，是吃力的。我看着新政务服务中心面前坪里停的汽车，心想，来这儿上班的，应该都是开车来的吧？
mailpit - 中文语言文件 - 中文设置 - server/ui-src/components/message/MessageItem.vue
Mailpit (1.27.1) 是一个挺好的邮件测试软件，如用于收取邮件。它的 Web 界面，默认是英文的，但可以简单将以下几个文件的少数英文字句替换成中文，就可以基本使用了。server/ui-src/components/NavMailbox.vue server/ui-src/components/ListMessages.vue server/ui-src/components/SearchForm.vue server/ui-src/components/message/MessageItem.vue参考：https://mailpit.axllent.org/docs/install/source/
poste.io - 中文语言文件 - 中文设置 - messages+intl-icu.zh.yml
环境：poste.io 版本 2.5.5 FREE # 3204操作步骤：在 docker 容器里，/opt/admin/translations 新增以下中文语言文件：├── messages+intl-icu.zh.yml├── date.zh.xliff├── Base/│ └── messages+intl-icu.zh.yml├── Base/│ └── validators+intl-icu.zh.yml在配置文件上，新增 zh ， docker exec -it mailserver nano /opt/admin/config/services_base.yaml App\Base\LocaleListener: arguments: - '@service_container' - ['en', 'zh', 'cs']重启容器， docker restart mailserver ， 如果顺利，那么使用默认为中文语言的浏览器访问 admin/ 会显示中文可选，执行 docker exec -it mailserver php /opt/admin/bin/console debug:translation zh ，来查看翻译文件是否有文件格式错误或没有翻译的语句参考：https://poste.io/https://symfony.com/doc/current/translation.htmlhttps://chat.qwen.ai/
在 Windows 系统里，通过鼠标双击 .exe 程序，连接远端的 iRedMail 邮局服务器，批量创建邮箱地址。使用说明：先确保 iRedMail 邮局服务器正常运行确认邮局管理员登录信息有效在自己的 Windows 系统里，运行 .exe 批量创建工具参考：https://iredmail.org/
环境：墙里只预留了一条网线，但现在需要同时使用 IPTV 和 LAN 两条网线。尝试：按网友说的，简单方法之一：是墙的两头分别用一个分线器，将原八芯的网线，分成两条四芯的网线使用。遇到的问题：IPTV 能正常使用，但 LAN 连接路由器和笔记本电脑测试时，会间隔着连续丢包。用网线测试仪测试，网线物理连接应该没有问题。尝试关闭 LAN 端连接着的路由器 DHCP 功能。正常了。
DCImanager 5 - Windows 2025 - Unattend.xml
<?xml version="1.0" encoding="utf-8"?> <unattend xmlns="urn:schemas-microsoft-com:unattend"> <settings pass="windowsPE"> <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <UserData> <AcceptEula>true</AcceptEula> </UserData> <DiskConfiguration> <WillShowUI>OnError</WillShowUI> <Disk wcm:action="add"> <CreatePartitions> <CreatePartition wcm:action="add"> <Order>1</Order> <Size>100</Size> <Type>Primary</Type> </CreatePartition> <CreatePartition wcm:action="add"> <Extend>true</Extend> <Order>2</Order> <Type>Primary</Type> </CreatePartition> </CreatePartitions> <ModifyPartitions> <ModifyPartition wcm:action="add"> <Active>true</Active> <Format>NTFS</Format> <Label>System <Order>1</Order> <PartitionID>1</PartitionID> </ModifyPartition> <ModifyPartition wcm:action="add"> <Order>2</Order> <PartitionID>2</PartitionID> <Label>Windows <Letter>C</Letter> <Format>NTFS</Format> </ModifyPartition> </ModifyPartitions> <DiskID>0</DiskID> <WillWipeDisk>true</WillWipeDisk> </Disk> </DiskConfiguration> <ImageInstall> <OSImage> <InstallTo> <DiskID>0</DiskID> <PartitionID>2</PartitionID> </InstallTo> <InstallFrom> <MetaData wcm:action="add"> <Key>/IMAGE/INDEX</Key> <Value>2</Value> </MetaData> </InstallFrom> <WillShowUI>OnError</WillShowUI> <InstallToAvailablePartition>false</InstallToAvailablePartition> </OSImage> </ImageInstall> </component> <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <SetupUILanguage> <UILanguage>en-US</UILanguage> </SetupUILanguage> <InputLocale>0409:00000409</InputLocale> <SystemLocale>en-US</SystemLocale> <UILanguage>en-US</UILanguage> <UILanguageFallback>en-US</UILanguageFallback> <UserLocale>en-US</UserLocale> </component> </settings> <settings pass="oobeSystem"> <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <UserAccounts> <LocalAccounts> <LocalAccount wcm:action="add"> <Password> <Value>($PASS)</Value> <PlainText>true</PlainText> </Password> <Description>Local Administrator</Description> <DisplayName>Administrator</DisplayName> <Group>Administrators</Group> <Name>Administrator</Name> </LocalAccount> </LocalAccounts> <AdministratorPassword> <Value>($PASS)</Value> </AdministratorPassword> </UserAccounts> <OOBE> <HideEULAPage>true</HideEULAPage> <SkipMachineOOBE>true</SkipMachineOOBE> <SkipUserOOBE>true</SkipUserOOBE> <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> </OOBE> </component> <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <InputLocale>en-US</InputLocale> <SystemLocale>en-US</SystemLocale> <UILanguage>en-US</UILanguage> <UserLocale>en-US</UserLocale> </component> </settings> <settings pass="offlineServicing"> </settings> <settings pass="specialize"> <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-TCPIP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <Interfaces> <Interface wcm:action="add"> <Ipv4Settings> <DhcpEnabled>false</DhcpEnabled> <RouterDiscoveryEnabled>false</RouterDiscoveryEnabled> </Ipv4Settings> <Ipv6Settings> <DhcpEnabled>false</DhcpEnabled> <RouterDiscoveryEnabled>false</RouterDiscoveryEnabled> </Ipv6Settings> <Identifier>($WIN_MAC)</Identifier> <UnicastIpAddresses> <IpAddress wcm:action="add" wcm:keyValue="1">($IP)/($NETMASK_SHORT)</IpAddress> <IpAddress wcm:action="add" wcm:keyValue="2">($IPv6)/($NETMASKv6)</IpAddress> </UnicastIpAddresses> <Routes> <Route wcm:action="add"> <Identifier>1</Identifier> <Metric>10</Metric> <NextHopAddress>($GATEWAYv4)</NextHopAddress> <Prefix>0.0.0.0/0</Prefix> </Route> <Route wcm:action="add"> <Identifier>2</Identifier> <Metric>10</Metric> <NextHopAddress>($GATEWAYv6)</NextHopAddress> <Prefix>::/0</Prefix> </Route> </Routes> </Interface> <!--Interface wcm:action="add"> <Ipv4Settings> <DhcpEnabled>false</DhcpEnabled> <RouterDiscoveryEnabled>false</RouterDiscoveryEnabled> </Ipv4Settings> <Ipv6Settings> <DhcpEnabled>false</DhcpEnabled> <RouterDiscoveryEnabled>false</RouterDiscoveryEnabled> </Ipv6Settings> <Identifier>Ethernet 2</Identifier> </Interface--> </Interfaces> </component> <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-DNS-Client" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <Interfaces> <Interface wcm:action="add"> <Identifier>($WIN_MAC)</Identifier> <DNSDomain>($HOSTNAME)</DNSDomain> <DNSServerSearchOrder> <IpAddress wcm:action="add" wcm:keyValue="1">($NAMESERVER)</IpAddress> </DNSServerSearchOrder> <DisableDynamicUpdate>false</DisableDynamicUpdate> <EnableAdapterDomainNameRegistration>false</EnableAdapterDomainNameRegistration> </Interface> </Interfaces> </component> <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <fDenyTSConnections>false</fDenyTSConnections> </component> <component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <UserAuthentication>0</UserAuthentication> </component> <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <FirewallGroups> <FirewallGroup wcm:action="add" wcm:keyValue="RemoteDesktop"> <Active>true</Active> <Group>@FirewallAPI.dll,-28752</Group> <Profile>all</Profile> </FirewallGroup> </FirewallGroups> </component> <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <RunSynchronous> <RunSynchronousCommand wcm:action="add"> <Description>Enable administrator</Description> <Order>1</Order> <Path>net user administrator /active:yes</Path> <WillReboot>OnRequest</WillReboot> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Description>Delete route</Description> <Order>2</Order> <Path>cmd /c echo IF "($IPv4)"=="" (route delete 0.0.0.0/0) ELSE IF "($IPv6)"=="" (route delete ::/0) >> %windir%\setup\scripts\SetupComplete.cmd</Path> <WillReboot>OnRequest</WillReboot> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>3</Order> <Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell" /v ExecutionPolicy /t REG_SZ /d RemoteSigned /f</Path> <WillReboot>OnRequest</WillReboot> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Description>Disable Network Discovery</Description> <Order>4</Order> <Path>cmd /c echo cmd /c %windir%\System32\WindowsPowerShell\v1.0\powershell.exe -command "& {&'Disable-NetFirewallRule' –DisplayName '@FirewallAPI.dll,-32752'}" >> %windir%\setup\scripts\SetupComplete.cmd</Path> <WillReboot>OnRequest</WillReboot> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Description>Setting multiple nameservers</Description> <Order>5</Order> <Path>cmd /c echo cmd /c %windir%\setup\scripts\nameservers.cmd ($NAMESERVERS) >> %windir%\setup\scripts\SetupComplete.cmd</Path> <WillReboot>OnRequest</WillReboot> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Description>Enable language</Description> <Order>6</Order> <Path>cmd /c %windir%\System32\WindowsPowerShell\v1.0\powershell.exe %windir%\setup\scripts\lang.ps1 "($LANG)" >>%windir%\setup\scripts\lang.log 2>&1</Path> <WillReboot>Never</WillReboot> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Description>Postinstall script listener</Description> <Order>7</Order> <Path>cmd /c IF "($HAS_RECIPE)"=="1" (echo cmd /c %windir%\System32\WindowsPowerShell\v1.0\powershell.exe %windir%\setup\scripts\winrm_listener.ps1 "($HOSTNAME)" "($LOCATIONIP)") >> %windir%\setup\scripts\SetupComplete.cmd</Path> <WillReboot>OnRequest</WillReboot> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Description>TZ setup</Description> <Order>8</Order> <Path>cmd /c IF NOT "($TIMEZONE)" == "()" ( IF NOT "($TIMEZONE)" == "" (tzutil /s "($TIMEZONE)"))</Path> <WillReboot>OnRequest</WillReboot> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Description>finish</Description> <Order>9</Order> <Path>cmd /c echo cmd /c %windir%\setup\scripts\wget.exe -O- "http://dcimserver/dcimgr?func=osinstall.finish&id=($AUTH_ID)" >> %windir%\setup\scripts\SetupComplete.cmd</Path> <WillReboot>OnRequest</WillReboot> </RunSynchronousCommand> </RunSynchronous> </component> </settings> <cpi:offlineImage xmlns:cpi="urn:schemas-microsoft-com:cpi" cpi:source="catalog:c:/distr/7601.17514.101119-1850_x64fre_server_eval_en-us-grmsxeval_en_dvd/sources/install_windows server 2008 r2 serverstandard.clg"/> </unattend>
批量设置域名 dkim 脚本 - iRedMail - dkim_setup.py - ptyhon - debian
环境：debian 12.10 ，iRedMail - 1.7.3目标：批量配置域名 dkim 步骤：将 dkim_setup.py 及 domains.txt 上传到邮局服务器文件系统里，如 /rootpython3 dkim_setup.py # 执行 dkim_setup.py 脚本。脚本会从 domains.txt （可自行创建，每行一个域名） 里逐行读取邮件域名，创建 dkim 文件、将 相应的域名和 key 追加到 /etc/amavis/conf.d/50-user 文件内容中。脚本每次执行前，会对当前的 /etc/amavis/conf.d/50-user 复制备份，预防出错后需要还原使用，最后脚本会将域名及对应的解析值追加到 dkim_dns_records.json ，方便您解析域名amavisd testkey # 如需验证 dkim 的查询结果，可执行此命令如需手工操作：amavisd-new genrsa /var/lib/dkim/iredmail.demo.anqun.org.pem 2048 # 为邮件域名 iredmail.demo.anqun.org 生成数字签名文件chown amavis:amavis amavisd genrsa /var/lib/dkim/iredmail.demo.anqun.org.pem # 更改签名文件属主chmod 0400 /var/lib/dkim/iredmail.demo.anqun.org.pem # 更改签名权限编辑 /etc/amavis/conf.d/50-user 文件内容，将签名的邮件域名和文件等追加进去# Add dkim_key here. dkim_key('iredmail.demo.anqun.org', 'dkim', '/var/lib/dkim/iredmail.demo.anqun.org.pem'); @dkim_signature_options_bysender_maps = ({ # 'd' defaults to a domain of an author/sender address, # 's' defaults to whatever selector is offered by a matching key # Per-domain dkim key #"domain.com" => { d => "domain.com", a => 'rsa-sha256', ttl => 10*24*3600 }, # catch-all (one dkim key for all domains) '.' => {d => 'iredmail.demo.anqun.org', a => 'rsa-sha256', c => 'relaxed/simple', ttl => 30*24*3600 }, });dkim_setup.py 的文件内容：#!/usr/bin/env python3 import subprocess import os import datetime import json AMAVIS_CONFIG_FILE = "/etc/amavis/conf.d/50-user" DKIM_BASE_PATH = "/var/lib/dkim" DKIM_KEY_SIZE = 2048 # Or 2048, if desired AMAVIS_COMMAND = "amavisd" # Changed from amavisd-new DNS_OUTPUT_FILE = "dkim_dns_records.json" def generate_dkim_key(domain): """Generates a DKIM key for the given domain.""" key_path = os.path.join(DKIM_BASE_PATH, f"{domain}.pem") command = [AMAVIS_COMMAND, "genrsa", key_path, str(DKIM_KEY_SIZE)] try: subprocess.run(command, check=True) subprocess.run(["chown", "amavis:amavis", key_path], check=True) subprocess.run(["chmod", "0400", key_path], check=True) print(f"DKIM key generated for {domain} at {key_path}") return key_path except subprocess.CalledProcessError as e: print(f"Error generating DKIM key for {domain}: {e}") return None def get_dkim_public_key(key_path): """Extracts the DKIM public key from the .pem file using openssl.""" try: command = ["openssl", "rsa", "-in", key_path, "-pubout", "-outform", "PEM"] process = subprocess.run(command, capture_output=True, text=True, check=True) public_key = process.stdout.strip() # Remove the BEGIN and END PUBLIC KEY lines and any newlines public_key = public_key.replace("-----BEGIN PUBLIC KEY-----", "") public_key = public_key.replace("-----END PUBLIC KEY-----", "") public_key = public_key.replace("\n", "") return public_key except subprocess.CalledProcessError as e: print(f"Error extracting DKIM public key using openssl: {e}") print(f"Stderr: {e.stderr}") # Print stderr for more details return None except Exception as e: print(f"Error extracting DKIM public key: {e}") return None def domain_exists(domain): """Checks if the domain is already configured in Amavis.""" try: with open(AMAVIS_CONFIG_FILE, "r") as f: config_content = f.read() return domain in config_content except FileNotFoundError: print(f"Error: Amavis config file not found at {AMAVIS_CONFIG_FILE}") return False def backup_amavis_config(): """Backs up the Amavis configuration file with a timestamp.""" timestamp = datetime.datetime.now().strftime("%Y.%m.%d.%H.%M.%S") backup_file = f"{AMAVIS_CONFIG_FILE}.{timestamp}" try: subprocess.run(["cp", AMAVIS_CONFIG_FILE, backup_file], check=True) print(f"Amavis config backed up to {backup_file}") return True except subprocess.CalledProcessError as e: print(f"Error backing up Amavis config: {e}") return False def update_amavis_config(domain, key_path): """Updates the Amavis configuration file with the DKIM settings.""" try: with open(AMAVIS_CONFIG_FILE, "r") as f: config_lines = f.readlines() dkim_key_insert_point = None start_index = None end_index = None last_entry_index = None for i, line in enumerate(config_lines): if "dkim_key(" in line: dkim_key_insert_point = i + 1 if "@dkim_signature_options_bysender_maps = (" in line: start_index = i if start_index is not None and "=>" in line: # 检查是否是一个 domain entry 的开始 if line.lstrip().startswith('"') or line.lstrip().startswith("'"): last_entry_index = i if "});" in line and start_index is not None: end_index = i break if dkim_key_insert_point is None: print("Could not find insertion point for dkim_key.") return False if start_index is None or end_index is None: print("Could not find signature options structure.") return False # 如果没有找到任何已有的 domain entry，则默认插入在 start_index + 1 insert_sig_index = last_entry_index + 1 if last_entry_index is not None else start_index + 1 # 构造新行 new_dkim_key_line = f"dkim_key('{domain}', 'dkim', '{key_path}');\n" new_dkim_sig_line = f' "{domain}" => {{ d => "{domain}", a => \'rsa-sha256\', ttl => 10*24*3600 }},\n' # 插入 dkim_key config_lines.insert(dkim_key_insert_point, new_dkim_key_line) # 如果不是第一个条目，检查前一行是否有逗号 if last_entry_index is not None: prev_line = config_lines[insert_sig_index - 1] if not prev_line.strip().endswith(','): config_lines[insert_sig_index - 1] = prev_line.rstrip('\n') + ',\n' # 插入新的签名选项 config_lines.insert(insert_sig_index, new_dkim_sig_line) # 写回文件 with open(AMAVIS_CONFIG_FILE, "w") as f: f.writelines(config_lines) print(f"Amavis config updated for {domain}") return True except FileNotFoundError: print(f"Error: Amavis config file not found at {AMAVIS_CONFIG_FILE}") return False except Exception as e: print(f"Error updating Amavis config: {e}") return False def restart_amavis(): """Restarts the Amavis service.""" try: subprocess.run(["systemctl", "restart", "amavis"], check=True) # Corrected command print("Amavis service restarted.") return True except subprocess.CalledProcessError as e: print(f"Error restarting Amavis: {e}") return False def write_dns_record(domain, public_key): """Writes the DNS record to a JSON file.""" dns_record_name = f"dkim._domainkey.{domain}" # Construct the TXT record value txt_record_value = f"v=DKIM1; p={public_key}" data = {dns_record_name: txt_record_value} try: # Check if the file exists and load existing data if os.path.exists(DNS_OUTPUT_FILE): with open(DNS_OUTPUT_FILE, "r") as f: try: existing_data = json.load(f) except json.JSONDecodeError: existing_data = {} # Handle empty or corrupted JSON file else: existing_data = {} # Update with the new record existing_data.update(data) # Write back to the file with open(DNS_OUTPUT_FILE, "w") as f: json.dump(existing_data, f, indent=4, ensure_ascii=False) # indent for readability, disable ASCII escaping print(f"DNS record written to {DNS_OUTPUT_FILE}") except Exception as e: print(f"Error writing DNS record to file: {e}") if __name__ == "__main__": import sys # Backup Amavis config *before* processing any domains if not backup_amavis_config(): print("Failed to backup Amavis config. Aborting.") sys.exit(1) try: with open("domains.txt", "r") as f: domains = [line.strip() for line in f.readlines()] except FileNotFoundError: print("Error: domains.txt not found") sys.exit(1) for domain in domains: print(f"Processing domain: {domain}") if domain_exists(domain): print(f"Domain {domain} already exists in Amavis config. Skipping.") continue key_path = generate_dkim_key(domain) if key_path: if update_amavis_config(domain, key_path): public_key = get_dkim_public_key(key_path) if public_key: write_dns_record(domain, public_key) else: print(f"Failed to get public key for {domain}") else: print(f"Failed to update amavis config for {domain}") if domains: #Only restart if there was at least one domain to process restart_amavis()演示视频：https://www.bilibili.com/video/BV1Jj7fztEN3/参考：https://docs.iredmail.org/sign.dkim.signature.for.new.domain.html
为 inbucket 的 web 界面添加一个“随机生成邮箱”的菜单标签
环境：inbucket_3.1.0-beta2_linux_amd64.deb目标：在已有的网页界面里，添加一个“随机生成邮箱”的菜单标签。访客点击这个标签，会自动生成一个随机邮箱用户名（8位，包含数字和小写字母），且会自动跳转到相应的收件箱页面，如“最近邮箱”标签的作用类似。方便访客使用。尝试：照着 inbucket 的文档，在 AI （poe 和 deepseek）的帮助下，对 ui/src 里的 Layout.elm 文件内容做了修改。尽可能不改动其它文件内容。这里是我修改后的 Layout.elm 文件内容：module Layout exposing (Model, Msg, Page(..), frame, init, reset, update) import Data.Session as Session exposing (Session) import Effect exposing (Effect) import Html exposing ( Attribute , Html , a , button , div , footer , form , h2 , header , i , input , li , nav , pre , span , td , text , th , tr , ul ) import Html.Attributes exposing ( attribute , class , classList , href , placeholder , rel , target , type_ , value ) import Html.Events as Events import Modal import Route import Timer exposing (Timer) import Random import Time generateRandomMailbox : Time.Posix -> Int -> String generateRandomMailbox time length = let chars = "abcdefghijklmnopqrstuvwxyz0123456789" charsLength = String.length chars -- 使用当前时间的毫秒数作为种子 seed = Random.initialSeed (Time.posixToMillis time) -- 生成随机索引列表 randomIndices = Random.step (Random.list length (Random.int 0 (charsLength - 1))) seed |> Tuple.first -- 将随机索引转换为随机字符 randomChars = List.map (\index -> String.slice index (index + 1) chars) randomIndices in String.fromList (List.concat (List.map String.toList randomChars)) {-| Used to highlight current page in navbar. -} type Page = Other | Mailbox | Monitor | Status type alias Model msg = { mapMsg : Msg -> msg , mainMenuVisible : Bool , recentMenuVisible : Bool , recentMenuTimer : Timer , mailboxName : String } init : (Msg -> msg) -> Model msg init mapMsg = { mapMsg = mapMsg , mainMenuVisible = False , recentMenuVisible = False , recentMenuTimer = Timer.empty , mailboxName = "" } {-| Resets layout state, used when navigating to a new page. -} reset : Model msg -> Model msg reset model = { model | mainMenuVisible = False , recentMenuVisible = False , recentMenuTimer = Timer.cancel model.recentMenuTimer , mailboxName = "" } type Msg = ClearFlash | MainMenuToggled | ModalFocused Modal.Msg | ModalUnfocused | OnMailboxNameInput String | OpenMailbox | RecentMenuMouseOver | RecentMenuMouseOut | RecentMenuTimeout Timer | RecentMenuToggled | RandomMailbox | GotTimeForRandomMailbox Time.Posix update : Msg -> Model msg -> ( Model msg, Effect msg ) update msg model = case msg of ClearFlash -> ( model, Effect.clearFlash ) MainMenuToggled -> ( { model | mainMenuVisible = not model.mainMenuVisible }, Effect.none ) ModalFocused message -> ( model, Effect.focusModalResult message ) ModalUnfocused -> ( model, Effect.focusModal (ModalFocused >> model.mapMsg) ) OnMailboxNameInput name -> ( { model | mailboxName = name }, Effect.none ) OpenMailbox -> if model.mailboxName == "" then ( model, Effect.none ) else ( model , Effect.navigateRoute True (Route.Mailbox model.mailboxName) ) RecentMenuMouseOver -> ( { model | recentMenuVisible = True , recentMenuTimer = Timer.cancel model.recentMenuTimer } , Effect.none ) RecentMenuMouseOut -> let -- Keep the recent menu open for a moment even if the mouse leaves it. newTimer = Timer.replace model.recentMenuTimer in ( { model | recentMenuTimer = newTimer } , Effect.schedule (RecentMenuTimeout >> model.mapMsg) newTimer 400 ) RecentMenuTimeout timer -> if timer == model.recentMenuTimer then ( { model | recentMenuVisible = False , recentMenuTimer = Timer.cancel timer } , Effect.none ) else -- Timer was no longer valid. ( model, Effect.none ) RecentMenuToggled -> ( { model | recentMenuVisible = not model.recentMenuVisible } , Effect.none ) RandomMailbox -> -- 获取当前时间，并通过 Effect.map 转换为 msg ( model , Effect.map model.mapMsg (Effect.posixTime GotTimeForRandomMailbox) ) GotTimeForRandomMailbox time -> -- 生成随机邮箱并导航 let randomMailbox = generateRandomMailbox time 8 in ( model , Effect.navigateRoute True (Route.Mailbox randomMailbox) ) type alias State msg = { model : Model msg , session : Session , activePage : Page , activeMailbox : String , modal : Maybe (Html msg) , content : List (Html msg) } frame : State msg -> Html msg frame { model, session, activePage, activeMailbox, modal, content } = div [ class "app" ] [ header [] [ nav [ class "navbar" ] [ button [ class "navbar-toggle", Events.onClick (MainMenuToggled |> model.mapMsg) ] [ i [ class "fas fa-bars" ] [] ] , span [ class "navbar-brand" ] [ a [ href <| session.router.toPath Route.Home ] [ text "@ inbucket" ] ] , ul [ class "main-nav", classList [ ( "active", model.mainMenuVisible ) ] ] [ if session.config.monitorVisible then navbarLink Monitor (session.router.toPath Route.Monitor) [ text "Monitor" ] activePage else text "" , li [] [ a [ href "#", Events.onClick (RandomMailbox |> model.mapMsg) ] [ text "Random Mailbox" ] ] , navbarLink Status (session.router.toPath Route.Status) [ text "Status" ] activePage , navbarRecent activePage activeMailbox model session , li [ class "navbar-mailbox" ] [ form [ Events.onSubmit (OpenMailbox |> model.mapMsg) ] [ input [ type_ "text" , placeholder "mailbox" , value model.mailboxName , Events.onInput (OnMailboxNameInput >> model.mapMsg) ] [] ] ] ] ] ] , div [ class "navbar-bg" ] [ text "" ] , Modal.view (ModalUnfocused |> model.mapMsg) modal , div [ class "page" ] (errorFlash model session.flash :: content) , footer [] [ div [ class "footer" ] [ externalLink "https://www.inbucket.org" "Inbucket" , text " is an open source project hosted on " , externalLink "https://github.com/inbucket/inbucket" "GitHub" , text "." ] ] ] errorFlash : Model msg -> Maybe Session.Flash -> Html msg errorFlash model maybeFlash = let row ( heading, message ) = tr [] [ th [] [ text (heading ++ ":") ] , td [] [ pre [] [ text message ] ] ] in case maybeFlash of Nothing -> text "" Just flash -> div [ class "well well-error" ] [ div [ class "flash-header" ] [ h2 [] [ text flash.title ] , a [ href "#", Events.onClick (ClearFlash |> model.mapMsg) ] [ text "Close" ] ] , div [ class "flash-table" ] (List.map row flash.table) ] externalLink : String -> String -> Html a externalLink url title = a [ href url, target "_blank", rel "noopener" ] [ text title ] navbarLink : Page -> String -> List (Html a) -> Page -> Html a navbarLink page url linkContent activePage = li [ classList [ ( "navbar-active", page == activePage ) ] ] [ a [ href url ] linkContent ] {-| Renders list of recent mailboxes, selecting the currently active mailbox. -} navbarRecent : Page -> String -> Model msg -> Session -> Html msg navbarRecent page activeMailbox model session = let -- Active means we are viewing a specific mailbox. active = page == Mailbox -- Recent tab title is the name of the current mailbox when active. title = if active then activeMailbox else "Recent Mailboxes" -- Mailboxes to show in recent list, doesn't include active mailbox. recentMailboxes = if active then List.tail session.persistent.recentMailboxes |> Maybe.withDefault [] else session.persistent.recentMailboxes recentLink mailbox = a [ href <| session.router.toPath <| Route.Mailbox mailbox ] [ text mailbox ] in li [ class "navbar-dropdown-container" , classList [ ( "navbar-active", active ) ] , attribute "aria-haspopup" "true" , ariaExpanded model.recentMenuVisible , Events.onMouseOver (RecentMenuMouseOver |> model.mapMsg) , Events.onMouseOut (RecentMenuMouseOut |> model.mapMsg) ] [ span [ class "navbar-dropdown" ] [ text title , button [ class "navbar-dropdown-button" , Events.onClick (RecentMenuToggled |> model.mapMsg) ] [ i [ class "fas fa-chevron-down" ] [] ] ] , div [ class "navbar-dropdown-content" ] (List.map recentLink recentMailboxes) ] ariaExpanded : Bool -> Attribute msg ariaExpanded value = attribute "aria-expanded" <| if value then "true" else "false"修改 Layout.elm 文件过程中，顺便也可以翻译一下 Web 界面的英文。参考：https://github.com/inbucket/inbucket/wiki/Development-Quickstarthttps://github.com/inbucket/inbucket/tree/main/ui/src
FOSSBilling - 0.6.22 - apache2 静态化规则文件 - install
问题：在 Hestiacp 的 nginx proxy + apache2 环境中，下载最新的 FOSSBilling 0.6.22 zip 版本文件，顺利安装。但安装后，无论访问 /admin 还是 news 等各个网址，都是显示 首页 的内容。尝试：在线安装成功后，网站根目录 .htaccess 的文件内容，关键的一条静态化规则是 RewriteRule ^(.*)$ index.php [QSA,L] ，但安装包里的，是 RewriteRule ^(.*)$ index.php?_url=/$1 [QSA,L] 。用安装包里的 .htaccess 替代，即可。参考：https://forum.hestiacp.com/t/htaccess-with-rewriterule/12686https://hestiacp.com/docs/server-administration/web-templateshttps://forum.hestiacp.com/t/fossbilling-nginx-template-in-need/13224