+
+ https://blog.udon.eu.org
+ カレーうどん屋
+
+ 2024-11-12T15:30:00.000Z
+
+
+
+
+
+
+
+
+
+
+ https://blog.udon.eu.org/archives/2c54052d.html
+ 我再也不能不假思索
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>正值下班时间,你拖着疲惫的身体漫步街头。地铁站的入口熙熙攘攘,那是回家的方向。</p>
+<p>正要走下地铁站路口的阶梯时,你发现了一个小孩子正朝着上行方向的电动扶梯走去。那是个连路都还走不清楚的孩子。</p>
+<p>他的父母呢?你正张望着,希望能找到那个冒失的,或许正在专心看着手机而放松了警惕的父亲或母亲。</p>
+<p>孩子朝着电梯摇摇晃晃地走了几步。想从上行的电梯往下走,这对于成年人都不是一件容易的事情,何况是小孩子。他大概刚踩上运行着的扶梯就会摔倒吧,真危险。你这么想着,稍微放慢了一些脚步,继续观察着孩子,也在观察着是否有人会出手拦住他。</p>
+<p>你看到一个上了些年纪,头发有些发白的老人。他双手都拎着东西,是柴米油盐,重量拉低了他的双肩。他会帮忙吗?</p>
+<p>你想起了上周,也是在下班的时间点,与即将乘坐的是同一个班次的地铁上,你幸运地找到了车厢里的最后一个座位。椅背上有没有贴着“爱心座位”的标志,你已经不记得了。你和今天一般疲惫,正在漫无目的地滑着手机屏幕,打发着无聊的通勤时间。视线越过手机屏幕,你瞥见一个老人,头发有些发白,双手都拎着重物,站在你的面前。</p>
+<p>要让出座位吗?你一边滑动手机,一边思考着。“刚下班,大家都很辛苦”,你想起了在食堂偶然间听到的同事间的对话,“虽然我怀孕了,很需要一个座位。不过看到大家都这么累,我也不好说些什么了”。是啊,刚刚完成了一天工作的你,正需要充分的休息,哪怕是在地铁里坐下十几分钟对你也是无比的重要。想到这里,你底下了头,继续看起了手机……</p>
+<p>也许是双手的重物让老人无暇顾及他人,迫使他加快步伐向下走去,他并没有发现那个正朝着上行扶梯走去的小孩子。此时孩子又向着扶梯迈出了几步。再往前几步,就踏上扶梯出口的金属盖板了。接下来会发生的事情难以想象。</p>
+<p>会有人帮忙的吧?你继续张望着,同时心中也在盘算着——拦住了这个孩子,就意味着得守着他,直到那个粗心的父母匆匆赶来。谁知道要等多久,绝对是赶不上马上要到站的地铁了。你尝试把目光瞥向其他地方,似乎不再去看那个孩子,他就会安然无恙。但你做不到。</p>
+<p>有一个阿姨快步走上前来,是要来帮忙的吗?你有一些即视感,仿佛之前在哪里见过这位大约四十岁出头,看上去有些憔悴的阿姨。</p>
+<p>那是前几个月的一个早晨,你刚入职公司不久。向来遵守规矩的你今天也准时来到地铁站,乘上与昨天、前天、大前天都是同一时刻到站的地铁,这样就能准时到达公司,给大家留下一个好印象。地铁缓缓驶入站台,车门在悦耳的提示声中打开。此时,隔壁车门处却传来不和谐的声音。你循声望去,有人摔倒在列车与站台的间隙处,是一个看着四十岁左右的女性。是不小心绊倒了吗?还是没有吃早饭,有些低血糖了?不管怎样,她瘫坐在门口,久久没有起身。</p>
+<p>要去扶一下吗?你迟疑了一会儿,没有上车。地铁站里有那么多保安,他们应该会处理好吧。扶起来之后,肯定得陪着她,直到有工作人员来照看才能离开吧,那今天早上要迟到了。你思考片刻,还是踏上了地铁,但还是转过身来,透过车窗继续观察。你看到有人和地铁站的保安一同将这位女性搀扶至阶梯边,地铁的门得以关闭,你可以准时到达公司了。想到这里,你的心安定了一些……</p>
+<p>可惜的是,阿姨也没有注意到一个孩子正全然无知地朝着危险走去,就走下了楼梯。孩子摇摇晃晃地向前迈步,顷刻间,一只脚已经踏上了扶梯的盖板,再向前几步,就到了不断运转着的扶梯。</p>
+<p>这样下去那个孩子肯定会摔下去的,你稍稍将行走的方向朝扶梯偏移了些许,做好了冲刺、抓住孩子的准备。同时,你也祈祷着能有人抢先你一步出手,并收拾剩下的残局。</p>
+<p>一步、又一步,真的没有人肯帮忙一下吗?或许此刻真的只有我一个人注意到了这个孩子?你的视线游离,大步朝着扶梯走去,却被一个飞跃而过的身影叫停了步伐。</p>
+<p>“小朋友,等一下。你的爸爸妈妈呢?”是一个年轻人,背着皮包,打扮时髦。他拦住了孩子,一只大手挡住了孩子前往扶梯的路,将孩子推离了危险。</p>
+<p>看到这里,悬在空中的石头落地,你便也随着大流走下了阶梯。只不过,在那之后,在等候地铁时、坐在地铁中、或是淋浴、发呆之际,你都会回忆起过去的你,那个会毫不犹豫拾起地板上的垃圾、主动让出座位、扶起摔倒的人、给予他人帮助的你。你会不由自主地问自己:</p>
+<p>我从什么时候开始,再也不能不假思索地做一件事情?</p>
+
+
+ 2024-11-12T15:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/95479b1f.html
+ Soundcore C30i 耳机
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>我目前在使用的耳机是 Sony LinkBuds,就是那款中间带了个孔的奇怪耳机。</p>
+<p>它本是为了商务人士在办公场所使用而设计,收听耳机中声音的同时不会影响听取他人的谈话。它却被我这个耳道经常发炎的人看上了。</p>
+<p>我想,中间带了个孔,那耳道内外的空气就是完全流通的,那耳道内肯定不会闷了吧!为了验证这一点,我特地去 Sony 的线下店体验这款耳机。佩戴上 LinkBuds,确实完全没有耳道被封闭的感觉,我便在网上欣然下单了。</p>
+<p>然而,经过一段时间的佩戴,才发现我的想法过于天真:看似畅通无阻的气流,在直径不够大的圆环处有阻塞。内部产生的水蒸气无法排出,导致耳道仍然会有潮湿感。佩戴20分钟以上,摘下耳机放入耳机舱,一段时间后取出,会发现耳机舱内有冷凝水。</p>
+<p>Sony LinkBuds 仍然不能解决我耳朵发炎的困扰。我便又踏上了寻找新耳机的旅途。</p>
+<p>今天便是要介绍我找到的其中一款,有望成为最终解决方案的耳机 —— SoundCore C30i。</p>
+<p><img src="/images/2024-06-13/01.jpeg" alt="外包装"></p>
+<h2 id="设计"><a href="#设计" class="headerlink" title="设计"></a>设计</h2><p><img src="/images/2024-06-13/02.jpeg" alt="充电盒外观"></p>
+<p><img src="/images/2024-06-13/03.jpeg" alt="充电盒开盖"></p>
+<p>耳机充电盒的外观,以及开盖后的样子如上图所示,就不多介绍了。</p>
+<p>开放式耳机相对于入耳式、半入耳式耳机,设计上花样多了不少。我看到的所有款式中,大致可以分为两类:</p>
+<ul>
+<li>使用挂钩悬挂在耳朵之后,将扬声器覆盖于耳道口,通过空气传导声音进入耳道的;</li>
+<li>夹在耳垂上,耳机本身不覆盖或仅部分覆盖耳道口,通过侧向开口将声音导向耳道的。</li>
+</ul>
+<p>SoundCore C30i 属于后者。</p>
+<p><img src="/images/2024-06-13/04.jpeg" alt="耳机本体"></p>
+<p>我选择的是透明外壳的款式,耳机内部结构清晰可见。</p>
+<p>左边耳机的扬声器处有一个开口,这便是朝向耳道的声音出口。</p>
+<p>右耳朝上的是耳机背面,金色的圆片是触控传感器。</p>
+<h2 id="佩戴感"><a href="#佩戴感" class="headerlink" title="佩戴感"></a>佩戴感</h2><p>夹耳的耳机其实还可以再分类。一种是本体柔软、有弹力,有点像悬挂在耳朵上的,将扬声器更多地覆盖于耳道口,以提升传音效率的耳机。</p>
+<p>C30i 本体则不可以弯曲,采用虎口般的结构卡在耳垂上。在佩戴时需要找到耳垂比较薄的部分,将耳机从那里套入耳垂,再将耳机推向耳垂较厚的地方,就能牢固固定。看似还是有些松松垮垮,但因为耳机足够轻,不容易被甩掉。</p>
+<p>我的感觉是,佩戴上没有什么异物感,但难以做到所谓“戴久了会忘记它的存在”这么夸张。</p>
+<p>我尝试连续佩戴了两个多小时,期间耳朵没有任何被堵塞的感觉,且取下后耳朵没有丝毫的潮湿感。确实是完全的开放了!</p>
+<h2 id="音质"><a href="#音质" class="headerlink" title="音质"></a>音质</h2><p>我的评价是:出奇的好。</p>
+<p>因为是一种妥协,我对耳机的音质便不抱任何希望。实际听起来,虽然也有这非封闭式耳机缺乏低音等问题,其实音质相当不错。</p>
+<p><img src="/images/2024-06-13/05.jpeg" alt="EQ 方案"></p>
+<p>通过调整 EQ 配置,将高低频都拉高(经典两头高调法),效果更是好了许多!</p>
+<p>我也听了一段博客,听清说话人的语音也没有任何困难。</p>
+<h2 id="多设备连接"><a href="#多设备连接" class="headerlink" title="多设备连接"></a>多设备连接</h2><p>我原本买的是飞利浦的一款开放式耳机,到手之后才发现耳机没有双设备连接的功能。我平常有在手机和电脑之间切换使用的需求,少了这个功能实在不能接受。</p>
+<p>C30i 支持同时连接两台设备,可以在两台设备间无缝切换。</p>
+<h2 id="续航"><a href="#续航" class="headerlink" title="续航"></a>续航</h2><p>商品说明上标明了耳机单次续航是 10 个小时,这应该是相当厉害的了。加上充电盒,总共可以提供 30 个小时的续航。</p>
+<p>实际用下来,在 2 小时的使用后,耳机的电量没有明显的减少。续航应当是很强劲的。</p>
+<h2 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h2><p>目前我看到的一个缺点是,耳机缺少自动休眠功能。将充电盒打开,耳机便会连接所有设备,摘下后仍保持着连接。直到耳机放回充电盒,并盖上盖子后,耳机才会关机。</p>
+<p>且耳机缺少佩戴检测,摘下后不会停止音乐播放。似乎耳夹式耳机比较少有这个功能。</p>
+<p>这可能会抢占了一些设备的音频输出,不过不算是很大的缺点。</p>
+<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>SoundCore C30i 有效解决了我耳道容易发炎的痛点,音质不错,且有多设备连接的功能,满足了作为我主力使用的耳机的所有需求。</p>
+
+
+ 2024-06-13T06:40:00.000Z
+
+
+ https://blog.udon.eu.org/archives/adc5a61e.html
+ 日本之行-第二站-奈良
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>23年12月底,我踏上了前往日本的旅途。这是我第一次去日本,是我第一次出国旅行,且只身一人。</p>
+<p>本次旅行为14天,我将分多个章节完成所有的游记,记录下我旅行中的点点滴滴、各种感想。这是第二章节,奈良之旅。</p>
+<h2 id="近铁、巴士与鹿"><a href="#近铁、巴士与鹿" class="headerlink" title="近铁、巴士与鹿"></a>近铁、巴士与鹿</h2><p><img src="/images/2024-03-11/00.jpeg" alt="使用 Suica 乘坐近铁"></p>
+<p>奈良距离大阪仅有 30km,乘坐近铁奈良线,摇摇晃晃一个多小时便能到达奈良。</p>
+<p>这条线路有分区间准急、准急、急行、快速急行四个速度,每个速度停靠的站点数量不同,票价都相同的。如果想尽快到达奈良,乘坐快速急性是最好的选择。</p>
+<p>这天,我纯凭心情来到近铁难波站,坐上了最近一班开往奈良的近铁,似乎是急行。</p>
+<p>近铁的运行速度不是很快,叮叮当当地在楼房间穿行着。</p>
+<p>在近铁奈良站,奈良线的终点站下了车,距离旅馆所在的奈良公园还有一段距离。由于拖着行李,我选择搭乘公交车。</p>
+<p>公交车到站后会向站台一侧倾斜车身,方便乘客上下;上车的门并不固定,可以看车身的贴纸判断;司机会很耐心地等待所有人上车、落座之后才摆正车身、继续前进。</p>
+<p>有一些线路是分段计费的,就需要在上车的时候领取一张整理卷,或者先刷一下卡,在下车的时候将钱和整理券一起投入投币机,或者刷一下公交卡就可以了。</p>
+<p>在每一个座位的旁边都有一个按钮,如果下一站要下车,按一下就会提醒司机,并且在车头的显示屏上也会显示,车上的所有按钮都会亮起。在公交车到站停稳之前无需先起身,车子停稳后司机也会很耐心地等大家付钱、下车,还会一个一个道谢哦。</p>
+<p>相比于地铁,公交的到站时间显得没有那么准确,但不会差太多。在地面运行的公交会受到交通的影响,等候乘客上下车、付钱有时候也会用掉不少的时间,能做到按照时间表运行已经相当厉害了。</p>
+<p><img src="/images/2024-03-11/01.jpeg" alt="路边的鹿"></p>
+<p>虽然在车上就有看到,果然很多啊 —— 奈良的鹿。</p>
+<p>奈良的鹿是散养的,主要集中在奈良公园和春日大社。在奈良设有鹿苑,将受伤或者发情的鹿收养,待它们恢复正常再放回乡中散养。</p>
+<p>从车站走到旅馆,一路上都有鹿儿好奇地打量着来自异国他乡的我。</p>
+<p>每走一段距离,能看到提示观光客不要“挑逗”鹿儿的提示牌,有些鹿的性格比较差,会用头顶你,追着你不放的。好在大部分鹿都去掉了角,不至于顶伤人。</p>
+<p><img src="/images/2024-03-11/02.jpeg" alt="鹿园宾馆"></p>
+<p>今天的旅馆确实有些简陋 —— 私人活动空间仅有这么一小间,洗浴和方便都是公用的。不过旅店的氛围很好,有一个共用的客厅,客人们可以在这里吹暖气(冬天还是挺冷的)、喝饮料、聊天。</p>
+<p>旅店的工作人员非常热情,替我办理好入住手续后,立刻拿出一张奈良公园的地图,用简单的中文(很厉害哦!)告诉我这附近有哪些好玩的地方。奈良一天的行程就是参考着这张地图安排的。</p>
+<p>整理好行装,已是傍晚五点多。按照旅店工作人员以及 Google Maps 的说法,奈良公园里的大部分饮食店都要下班了!遂立刻出门,寻找晚饭的地点。</p>
+<p>因为今天是周五吗,明明还有一些旅客在此游玩的,旅店对面的旅游纪念品店已经关门了!沿街打量了几家店铺,也都在收拾着大堂,准备打烊了,完全不像是会接客的样子。大危机,要没晚饭吃了,得走个几公里处了奈良公园,到达奈良市区才有便利店。</p>
+<p>路过了一家同样是卖纪念品的商店,发现店内的空间还挺宽敞,也摆着一些桌椅,便问了一下老板还有没有饭吃。运气很好,这家店还没有打烊。</p>
+<p><img src="/images/2024-03-11/03.jpeg" alt="親子丼"></p>
+<p>可以选择的菜品并不是很多,大多数都是日本的家常菜。我选择了这道绝大部分和食餐馆都会有的家常菜 —— 親子丼。</p>
+<p>从厨房门帘的缝隙向里望去,可以隐约看见老板在烹饪着饭菜。明明用得都是同样的工具和食材,有技艺的人做出来的东西就能端上桌卖钱呢。</p>
+<p>蛋是我们所谓“半熟”的柔滑状态,鸡肉有一些小烧焦,主要的风味是自古以来人们就离不开的味道 —— 咸味。风味不能说突出,却很平和。它就是一碗很普通、很平常,此时此刻会出现在任意一张餐桌上的饭。</p>
+<p>饱饭后,我根据地图的指引,前往东大寺的二月台,观赏日落之景。</p>
+<p><img src="/images/2024-03-11/04.jpeg" alt="二月台的日落1"></p>
+<p><img src="/images/2024-03-11/05.jpeg" alt="二月台的日落2"></p>
+<p><img src="/images/2024-03-11/06.jpeg" alt="二月台的日落3"></p>
+<p><img src="/images/2024-03-11/07.jpeg" alt="二月台的日落4"></p>
+<p>只用手机相机无法捕捉暗光下的美景。</p>
+<p>夕阳柔和而昏暗地照着二月台,四周一片静寂,只能听见洗手亭的潺潺水声。在日落后还停留在奈良公园的游客寥寥,大家都保持着绝对的安静,共享这片难得的静谧。</p>
+<p>在台上驻足数分钟,我轻轻踏着脚步走下楼梯,沿着寺院的小路漫步着,离开了东大寺。</p>
+<p>此时天已完全黑了,不论是寺院还是神社都已关门,我就这么在奈良公园里漫步着。时不时碰见一群鹿,便从口袋中掏出一块鹿仙贝,掰给鹿儿吃。</p>
+<p><img src="/images/2024-03-11/08.jpeg" alt="果汁"></p>
+<p>会旅店前在路边的售货机买了一瓶果汁饮料,是不二家的。日本的饮料会标注果汁含量,我觉得很神奇。</p>
+<p><img src="/images/2024-03-11/09.jpeg" alt="春日大社"></p>
+<p>第二天,去参观了十分出名的春日大社。不过不恰巧,主殿正在维修,将赛钱箱设置在了原处,只能眺望主殿。</p>
+<p>在巫女那儿买了点纪念商品,是两只鹿型的小玩偶。一只是木质的,一只是陶制的,鹿儿的嘴中叼着签。忘记留下照片了。</p>
+<p><img src="/images/2024-03-11/10.jpeg" alt="林间小道"></p>
+<p>离开主殿,走上铺着碎石的小道。</p>
+<p><img src="/images/2024-03-11/11.jpeg" alt="路旁的神社"></p>
+<p>每走几步,便会出现一间迷你神社,感觉十分奇妙。在这片林海中不知道供奉着多少的神明。</p>
+<p>有些人也许是提前来做新年参拜,途中遇到的神社都会十分虔诚地参拜。</p>
+<p>在林间漫步了将近一个小时,吸饱了新鲜的空气,我振奋精神,回旅馆取了行李,准备离开奈良。</p>
+<p><img src="/images/2024-03-11/12.jpeg" alt="若草山"></p>
+<p>拖着行李箱漫步在奈良公园里,我又一次路过了若草山。</p>
+<p>若草山也不高,在满是丘陵的福建甚至都算不上山,只是个小土坡。山上的草长得格外整齐,是一座越看越顺眼的山呢。</p>
+<p>若草山每年12月初开始封山,直到第二年三月举行烧山仪式后才重新开放。没能上去走走真是可惜。</p>
+<p><img src="/images/2024-03-11/13.jpeg" alt="柿叶寿司 set"></p>
+<p>在前往近铁奈良站离开奈良前,我路过了一家在网上有着不少讨论的柿叶寿司店,便决定在这里解决午饭。</p>
+<p>柿叶寿司其实就是用柿子叶将寿司包起来烹饪。据说柿子叶不仅可以杀菌消毒,还能给寿司提升风味。</p>
+<p>想着,既然是奈良的特色菜,肯定要好好品尝一下。我点了一份 2500円 的大套餐,奢侈一下~</p>
+<p>左下角的五枚寿司,上方两枚便是柿叶寿司了。下方三角形的寿司也是用不能吃的叶子包着,但好像不是柿子叶;右侧是用腌制过的紫苏叶包的寿司,紫苏叶是可以吃的,风味很独特,我很喜欢;左侧是茶味的卷寿司,也是第一次吃。</p>
+<p>柿叶寿司用的是腌制过的鱼,确实带有叶子的香味,很有特色!</p>
+<p>除此之外,套餐内还有精致的小菜、一份天妇罗拼盘和一碗素面。吃的我好饱~</p>
+<p><img src="/images/2024-03-11/14.jpeg" alt="线上买的特急卷"></p>
+<p>乘坐上前往京都的特急电车时还发生了一些小插曲。</p>
+<p>坐上车后我才意识到自己坐的是特急的电车,进站时我只刷了基本票,没有购买特急卷。</p>
+<p>好在列车上有通过扫描二维码购买特急卷的渠道,也支持借记卡付款。在等待列车发车时,我赶紧买了特急卷,带着行李移动到了指定座位,正式踏上前往京都的旅途。</p>
+
+
+ 2024-03-10T16:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/a7050149.html
+ 23 年对我影响最大的硬件与软件
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>原文投稿在自留地频道的新年活动里。</p>
+<p>对于我来说,23 年给我带来影响最大的莫过于 Meta Quest 3 与 VRChat 了。</p>
+<h2 id="Quest-3"><a href="#Quest-3" class="headerlink" title="Quest 3"></a>Quest 3</h2><p><img src="/images/2024-03-01/01.png" alt="订单截图"></p>
+<p>Quest 3 在 23 年 6 月面世,于 9 月 28 日开放预购。我早在数年前就有了尝试 VR 的念头,在经过一天的调查研究后确定了 Quest 3 不会踩坑,便在日亚下了单,直邮回国。日亚上标价为 78k 円,日本商品出口不收税,再加上直邮的运费和中国的关税,最后到手的价格和标价差不多。(顺便一提,现在日亚也可以以相同的价格直邮国内)</p>
+<p>外观我就不展示了,网上的图很多。到手第一件事,便是尝试它对于上一代产品相比提升最大的地方——显示与穿透(Pass Through)。</p>
+<h3 id="显示效果"><a href="#显示效果" class="headerlink" title="显示效果"></a>显示效果</h3><p>显示效果上,Quest 3 采用了 Pancake 棱镜,甜点位置(Sweet Spot)相对于上一代更大,也就是说仅需简单调整头戴的位置,便能找到画面最为清晰的位置,我的使用体验上感觉也是如此,每次佩戴上头显很快就能调整好画面。</p>
+<h3 id="穿透-Pass-Through"><a href="#穿透-Pass-Through" class="headerlink" title="穿透 (Pass Through)"></a>穿透 (Pass Through)</h3><p><img src="/images/2024-03-01/02.jpeg" alt="穿透效果"></p>
+<p>穿透方面,由于我没有用过 Quest 2、Quest Pro 等拥有穿透功能的 VR 头显,无法进行比较。图片中看起来比较糊,是因为我用了 Quest 自带的截图功能,分辨率很低。实际观感上,在光线充足的环境中可以毫无压力地辨认各种物体,但细小的字是看不清楚的。戴着 Quest 使用手机并不是很舒服,因为物体距离头显过近会因摄像头视角的问题产生扭曲,看字会很吃力。将手机拿远,又会因为显示清晰度不够而看不清字。不过听说 Quest 2 的穿透是黑白且十分模糊,Quest Pro 也好不了很多,Quest 3 在这个方面应是有十足的进步。(似乎离 avp 还有很大的差距)</p>
+<hr>
+<p>相比于头显中不带有处理芯片的 VR 眼镜,Quest 可以使用无线串流软件将 PCVR 的画面投影至头显中显示,再也不用担心玩游戏时绊倒数据线了。</p>
+<p>总而言之,Quest 3 是一副功能完善,各方面实力均不弱的“六边形战士”,但毕竟定位是廉价头显,也就缺少了 Quest Pro 的自定位手柄、眼部追踪,Valve Index 的基站定位、手部追踪,更没有 Apple Vision Pro 的高 ppi 显示屏、低延迟的穿透与精准的手势识别。但综合来看,Quest 3 的性价比高,适合刚刚步入虚拟世界,想要体验各式各样 VR 游戏的人购买。</p>
+<h2 id="VRChat"><a href="#VRChat" class="headerlink" title="VRChat"></a>VRChat</h2><p>谈到 VR 游戏,有人会想到 Half-Life:Alyx,有人会说起(已经凉掉的) Beat Saber。根据 SteamDB 的数据,此刻在线数量最多的 VR 端游戏还得是 VRChat(主要为 PC 端的战争雷霆不算)。</p>
+<p>在我看来,VRChat 里有大概有四类人。</p>
+<h3 id="第一类人:虚拟世界的旅行家"><a href="#第一类人:虚拟世界的旅行家" class="headerlink" title="第一类人:虚拟世界的旅行家"></a>第一类人:虚拟世界的旅行家</h3><p><img src="/images/2024-03-01/03.jpg" alt="风景大好"></p>
+<p>有些人想“逃避现实”,来到虚拟世界欣赏美景、转换心情。在 VRChat 里有各种各样风格的地图:有些风景大好、音乐舒缓,适合在快节奏的生活之余找到一个宁静之地放松紧绷的精神;有的地图灯红酒绿,如果在夜晚你还是激情满满,不妨来这里听听虚拟 DJ 的表演,大家一起蹦迪、饮酒;甚至还有环境昏暗、一片寂静,十分适合睡眠休息的卧室地图,虽然深处异地,也能在虚拟世界里与好友共眠,在清晨醒来的那刻发现自己的眼前并不是早已习惯了的天花板,耳边是仍在熟睡的友人的呼吸声,将会是一种全新的体验。</p>
+<h3 id="第二类人:虚拟世界的摄影师"><a href="#第二类人:虚拟世界的摄影师" class="headerlink" title="第二类人:虚拟世界的摄影师"></a>第二类人:虚拟世界的摄影师</h3><p><img src="/images/2024-03-01/04.jpg" alt="人也很美"></p>
+<p>也有些人想在虚拟世界里做一个摄影师。美景的照片中怎么能少了美人,如果现实中找不到美人,那就自己来扮吧(心死)。VRChat 中使用的人物模型可以由玩家自行上传,如果你恰巧会使用 Unity 与 Blender,便可以为自己量身定制一个人物模型;如果不会也没有关系,在 Booth 上有许多预制好的人物模型与衣服,只要按照教程将其组合,便可打造出你心目中的理想形象(不管男女)。为了拍出满意的照片,你会主动去学习各种各样的新技能:为了调整照片的后期效果,我就学习了 Lightroom 的使用。</p>
+<h3 id="第三类人:人,不过是在虚拟世界里"><a href="#第三类人:人,不过是在虚拟世界里" class="headerlink" title="第三类人:人,不过是在虚拟世界里"></a>第三类人:人,不过是在虚拟世界里</h3><p><img src="/images/2024-03-01/05.png" alt="和朋友聊天"></p>
+<p>这或许才是 VRChat 的核心内容 —— 当然是和朋友聊天啦。在 VRChat 中有一些专门用于聊天、交友的地图,例如以中文为主的“中文吧”;也有一些比较热门的地图会聚集起各个语言的人群一起聊天,例如“Japan Shrine[spring]”,一张风景优美的日本神社地图;更有各种各样以个人或小团体主办的咖啡厅、运动吧、跳舞房、游戏房等着你来加入。也许你和我一样有些小“社恐”,在现实生活中不大善于和陌生人交际,不妨试试在 VRChat 中,与素未谋面但已经熟络的朋友,或是围坐在篝火旁闲聊,或是在电闪雷鸣、乌云滚滚的夜空中乘坐飞机探险,相信这对你一定会给你带来从所未有的新鲜感。</p>
+<h3 id="第四类人:OOOO"><a href="#第四类人:OOOO" class="headerlink" title="第四类人:OOOO"></a>第四类人:OOOO</h3><p>还有一类是搞色色的,就不多说了。</p>
+<p>以上只是从我的眼中看到的 VRChat。一千个人的心中有一千个哈姆雷特,你的 VRChat 又将会是什么样的呢?</p>
+<hr>
+<p>自从 23 年 10 月 3 日加入 VRChat 以来,我学会了怎么修改模型、如何使用全身追踪让自己在游戏中灵活运动;尝试在跳舞房里学习舞蹈,在 KTV 房里与朋友唱歌,与来自全球的人使用中、英、日闲聊;在 VRChat 中结交了新的朋友,在 X 上发布了拍摄的照片。VRChat 确确实实已经融入了我的生活,仿佛在另一个世界塑造了另一个不同的我。</p>
+
+
+ 2024-03-02T12:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/a534d51a.html
+ 旅行与军粮
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="军粮的特点"><a href="#军粮的特点" class="headerlink" title="军粮的特点"></a>军粮的特点</h2><h3 id="1-便携"><a href="#1-便携" class="headerlink" title="1. 便携"></a>1. 便携</h3><p>一般来说,军粮都会被较为紧凑地包装,以便于大批量运输与随身携带。如确认将有一餐饭需要在外解决,可以在出发时往包里塞上一份军粮。</p>
+<h3 id="2-分量足"><a href="#2-分量足" class="headerlink" title="2. 分量足"></a>2. 分量足</h3><p>军粮的营养构成一般都是按照一个运动量中上的成年男性一餐所需要的卡路里来设计的。因此,即使不能完全饱腹,其所含的营养足够填补在外游玩半天空空的肚子了。</p>
+<h3 id="3-便于烹饪"><a href="#3-便于烹饪" class="headerlink" title="3. 便于烹饪"></a>3. 便于烹饪</h3><p>相比于需要加入开水的方便食品、甚至是冻干食品,军粮一般自带加热设备,仅需冷水,甚至不需要水都可以加热食物。若是去荒郊野岭的地方露营,除非有携带便携式汽炉,想获取到热水是比较难的,这时候有自加热的军粮会很方便。</p>
+<h2 id="俄罗斯单兵口粮普餐"><a href="#俄罗斯单兵口粮普餐" class="headerlink" title="俄罗斯单兵口粮普餐"></a>俄罗斯单兵口粮普餐</h2><p><img src="/images/2024-02-12/01.jpg" alt="外包装"></p>
+<p>这次买到的6号餐谱,包含五包干粮、牛肉丸、牛肉荞麦饭、牛肉炖豆角、牛肝酱、午餐肉、蔬菜丁罐头以及饮料、果酱、零食等散件,还有一套由三个燃料药丸、一个铝制支架和几支防风火柴组成的明火加热套装。</p>
+<p><img src="/images/2024-02-12/02.jpg" alt="塞满的盒子"></p>
+<p>旅行的第一天是登山,午餐便携带了部分的食物作为午餐。</p>
+<p>有些小雨,便找了一处有雨遮的地方开始准备午饭。</p>
+<p><img src="/images/2024-02-12/03.jpg" alt="加热装置"></p>
+<p>将铝板弯曲成炉子的形状,在炉子的中央放上燃料药丸,再用防风火柴点燃,便是一个功能完备的加热装置。</p>
+<p>小贴士,我试着用火柴从上方点燃药丸,尝试了两次都失败了。将火柴放在炉子里,然后将药丸放在火柴上,便能很轻易地将其点燃。</p>
+<p><img src="/images/2024-02-12/04.jpg" alt="正在加热的蔬菜丁罐头"></p>
+<p>点燃固体燃料后,将盛有食物的铝制罐子放在火上直接加热即可。如果有带铝或者钛制的杯子,也可以直接放在火上烧水,泡些饮料喝。</p>
+<p><img src="/images/2024-02-12/05.jpg" alt="干粮、芝士与肝酱"></p>
+<p>在等待罐头加热时,我先掏出了干粮与蘸酱。除了照片里的芝士与肝酱,还有一包苹果果酱。</p>
+<p>芝士的味道与常见的芝士片如出一辙,在我看来稍微淡了些,因此也比较适合直接吃。</p>
+<p>牛肝酱则是特别的腥,单独吃我有些吃不来,和着芝士与果酱吃味道倒是还不错。</p>
+<p>拿出一片干粮(其实就是苏打饼干),涂上果酱,挖一勺芝士,再放上一点点牛肝酱,一口塞进嘴里,味道还算不错。</p>
+<p>饼干有些干硬,嚼起来有些费劲儿,需要配水。</p>
+<p>最后,完吃完了一份饼干、一盒芝士和一包果酱,牛肝酱剩下了一大半。</p>
+<p><img src="/images/2024-02-12/06.jpg" alt="牛肉丸"></p>
+<p>接下来是牛肉丸。看到红色的外表就能猜到是罗宋汤风味的。</p>
+<p>应该是罐藏的缘故吧,牛肉已经泡得很软了,吸收了罗宋汤酸酸甜甜的风味,味道还算不错。就是牛肉味已经不是很浓,能吃得出来是肉,但没有什么特殊的肉的风味。</p>
+<p>顺便一提,用这种炉子加热,铝盒子受热举起不均匀,中间已经滚烫,但四周还是冰的,需要多多翻搅。</p>
+<p><img src="/images/2024-02-12/07.jpg" alt="蔬菜丁罐头"></p>
+<p>从外表看不出来里面有啥,红红的肯定也是罗宋汤的味道。里头的蔬菜主要是胡萝卜,也有青椒、青豆等蔬菜。但这一碗的味道就不好了,青椒的味道和西红柿(或者是甜菜)的酸甜并不搭。</p>
+<p>把饼干蘸着牛肉丸的汤汁吃,味道也不错。涂涂果酱、涂涂芝士,饼干便吃完了一包。</p>
+<p>又吃了点麻麻那边的自热口粮,便已饱腹,我的食量确实不大啊。</p>
+<p><img src="/images/2024-02-12/08.jpg" alt="蔬菜丁罐头上的うさこ"></p>
+<p>这一餐其实只吃了整套餐的 1/3 左右,剩下的量再吃两次正餐不成问题。</p>
+<p>回到家后,又拿出了些零食品尝了一下。</p>
+<p><img src="/images/2024-02-12/09.jpg" alt="巧克力棒"></p>
+<p>来自俄罗斯的巧克力棒,偏甜,一股代可可脂的廉价感,属于不大好吃的巧克力。</p>
+<p><img src="/images/2024-02-12/10.jpg" alt="速溶咖啡、奶与糖"></p>
+<p>右上角是速溶咖啡,右下角是奶粉,左边的一大包是糖。糖的量很大,明显不止是一次饮料的量,也可以放在茶里喝。</p>
+<p>咖啡很苦,一股烧焦味,不好喝。奶粉大概也是植脂末吧,没什么奶味。</p>
+<p>剩下的食品我分成了两餐解决。</p>
+<p><img src="/images/2024-02-12/12.jpg" alt="第一餐"></p>
+<p>第一餐的搭配是:牛肉荞麦饭、肥肉罐头和干粮(饼干)。</p>
+<p><img src="/images/2024-02-12/13.jpg" alt="第一餐的内容"></p>
+<p>也许我加热的还不够,但考虑到在野地里使用便携式炉子加热的能力,士兵们能加热到中间完全热乎,周围有些凉是平均水平了。荞麦饭很硬,风味也不是很好,除了咸味和一点牛肉味,尝不出别的滋味了。加了一些套餐内的黑胡椒粉,才改善了一些风味。</p>
+<p>不过这一大碗饭确实很能填饱肚子,适合放在午餐食用。</p>
+<p>肥肉罐头里自然是盐腌风味的很肥的肉啦。味道我还挺喜欢的,肥肥的肉很好吃,十分下饭。</p>
+<p><img src="/images/2024-02-12/14.jpg" alt="第二餐"></p>
+<p>第二餐的搭配是:牛肉煮豆子、午餐肉、苹果泥、酱牛肉(来自国产 MRE)和干粮(饼干)。</p>
+<p><img src="/images/2024-02-12/15.jpg" alt="第二餐的内容"></p>
+<p>牛肉煮豆子用的会是什么豆呢,豌豆吗?啊,原来是黄豆。</p>
+<p>并没有延续牛肉丸、蔬菜罐头的罗宋汤风味,只是单纯的咸味,不过味道我还挺喜欢的。</p>
+<p>牛肉其实不多,就一大块带筋、软烂的牛肉,剩下都是碎块。除了黄豆外还有一些胡萝卜。</p>
+<p>午餐肉很小一罐,但确实是肉泥,淀粉含量并不多,味道也不错。</p>
+<p>苹果泥是我整个套餐中最喜欢的一样食物了。口感绵密,酸甜度适中,汁水十足。配合干粮一起吃,很大地改善了干粮过干、过硬的缺点。而且好大一罐,吃得很满足。使得这一餐中我干掉了两袋干粮。</p>
+<h2 id="国内的军粮-——-以北戴河的自热口粮为例"><a href="#国内的军粮-——-以北戴河的自热口粮为例" class="headerlink" title="国内的军粮 —— 以北戴河的自热口粮为例"></a>国内的军粮 —— 以北戴河的自热口粮为例</h2><p>09式单兵自热口粮我吃过几份,13式这种两餐包装在一起的也吃过一次。</p>
+<p>这次选购的是北戴河生产的自热口粮。虽然不是军品,但北戴河前身是军工厂,产品会比较接近军品吧。包装风格与09式也很相像。</p>
+<p><img src="/images/2024-02-12/11.jpg" alt="酒店里的自热口粮"></p>
+<p>那天感冒的发烧刚退,人还比较虚弱。住的度假酒店位置比较偏,不想跑太远去吃晚饭,便取了车上的自热口粮回房间吃。</p>
+<p>我吃过太多次了,便没有拍照片,下面就口述一下感受。</p>
+<p>相比于09式单兵自热口粮,北戴河出品的民用口粮内容少了很多 —— 没有了耐贮蛋糕、糖水黄桃/菠萝、调味辣椒酱和固体饮料,只有一份主食,一份配菜(酱牛肉和午餐肉二选一)和一份小菜(榨菜)。</p>
+<p>09式的餐谱相当多样,有 12 个餐谱,有炒面也有炒饭,也有一些主食是素食的,满足部分人群的需求。</p>
+<p>北戴河的仅有三种餐谱,且都是荤食的炒饭。</p>
+<p>主食包外套着加热袋,用注水袋取对应量的水(生水、脏水皆可)倒入加热包即可完成主食的加热。</p>
+<p>味道上,我认为北戴河做得是不如当年的09式的。饭总体都偏咸,但肉给的十分足。</p>
+<p>但相对于俄罗斯的军粮,不知道是口味上有着主场的优势,还是俄罗斯人烹饪技术真的不佳,还是中国的军粮更胜一筹。</p>
+
+
+ 2024-02-12T14:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/7777583b.html
+ 被我整坏的路由器和服务器
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>为了搭建 PalWorld 的私服,我又一次踏上了折腾之旅,并成功将路由器和家里的服务器都搞崩了。</p>
+<p>好在最后两台设备都恢复如初。我就想来做一下这次“事故”的复盘。</p>
+<h2 id="我的设备"><a href="#我的设备" class="headerlink" title="我的设备"></a>我的设备</h2><p>本次操作的有两台设备,一台是 x86 软路由,运行 Openwrt;一台是配置比较奇怪的 x86 服务器,运行 PVE。</p>
+<h2 id="Openwrt-的网络隔离配置"><a href="#Openwrt-的网络隔离配置" class="headerlink" title="Openwrt 的网络隔离配置"></a>Openwrt 的网络隔离配置</h2><p>说在前头,我对 Openwrt 的操作真的是一窍不通。网络相关的知识兴许懂一些,但一到实操环境就纰漏百出了。</p>
+<p>由于 PalWorld 服务端启动后便会在游戏的服务器列表公开自己的 IP,且无法关闭这个功能,有比较大的安全隐患。在配置好 PalWorld 服务端所运行的虚拟机后,我决定将这台虚拟机的网络与局域网内其他设备隔离。这也是所有折腾的起源。</p>
+<h3 id="配置-VLAN-导致的失联"><a href="#配置-VLAN-导致的失联" class="headerlink" title="配置 VLAN 导致的失联"></a>配置 VLAN 导致的失联</h3><p>我能想到的第一个方案便是,给这台虚拟机分配一个与当前局域网不同的网段,并将两个网段隔离。</p>
+<p>VLAN 我还是知道的,便着手开始创建新的 VLAN,并保存…连不上路由器了。</p>
+<p>急了啊,创建了 VLAN 就代表开启网桥的 VLAN 过滤。目前这个 VLAN 没有分配给任何一个接口,自然是啥都连不上了。好在 Openwrt 有自动回滚的功能,90 秒若设备连不上路由器,便会将刚刚的更改回滚…但不是每次都能成功。</p>
+<p>有反复折腾了几次:创建 VLAN、创建接口、创建防火墙的 Zone,分配来分配去,中途也搞砸了几次,配置也都回滚了,直到最后一次尝试,luci 弹出了令人感到安心的“正在回滚配置”的提示,然后就…卡住了。</p>
+<p>考虑到 Openwrt 使用的是 squashfs,怀疑是设备上的设置确实回滚了,但内存中的配置没能正确地回去,我将设备断电重启。果不其然,配置回到了应用前的样子。</p>
+<p>在尝试配置 VLAN 数次后,我放弃了。即使给虚拟机分配了新网段,也设置好了防火墙规则,虚拟机依旧可以访问到内网网段的设备。</p>
+<p>目前我还没有闲心思慢慢研究 Openwrt 的种种配置,便打算换一个方式实现。</p>
+<h2 id="配置-PVE-防火墙"><a href="#配置-PVE-防火墙" class="headerlink" title="配置 PVE 防火墙"></a>配置 PVE 防火墙</h2><p>第二个方案便是,在 PVE 的防火墙中禁止虚拟机连接内部网段的其他设备了。</p>
+<h3 id="启动-Datacenter-防火墙但没有添加允许规则导致的失联"><a href="#启动-Datacenter-防火墙但没有添加允许规则导致的失联" class="headerlink" title="启动 Datacenter 防火墙但没有添加允许规则导致的失联"></a>启动 Datacenter 防火墙但没有添加允许规则导致的失联</h3><p>我启用了虚拟机的防火墙,发现配置并没有生效。简单查询后发现,需要将 Datacenter 的防火墙启用,VM 防火墙才有效果。我便看也没看地就开启了 Datacenter 防火墙,发现管理后台页面无法刷新了。此时我才看到屏幕上 Datacenter 防火墙的默认配置 —— IN: DROP.</p>
+<p>这下好了,外部连接统统被阻断,也就无谈通过控制面板将防火墙再关闭。</p>
+<p>这台服务器是无头的,安装有一张 P100 显卡,但没有显示输出。所以在不动硬件的情况下,我没法通过显示器访问终端的。</p>
+<p>通过检索,我了解到了好几种奇技淫巧来关闭 PVE 的防火墙。</p>
+<h3 id="挂载并修改-crontab"><a href="#挂载并修改-crontab" class="headerlink" title="挂载并修改 crontab"></a>挂载并修改 crontab</h3><p>正好手上有一个硬盘盒,我就将系统盘取下,通过硬盘盒连接到了软路由,尝试修改系统盘里的文件。</p>
+<p>使用 <code>fdisk</code> 查看这块系统盘的分区情况,但没有看到熟悉的 <code>ext4</code> 字样。取而代之的是 <code>Linux LVM</code>。LVM 相当于是 Linux 对磁盘进行了再一次的分区,因此挂载就不能是简单的 <code>mount</code>,得用 <code>lvm2</code> 工具。</p>
+<p>再敲了几个命令后,我成功将 PVE 分区的 <code>root</code> 文件夹挂载,并在 crontab 中添加了关闭 PVE 防火墙相关的命令。</p>
+<p><code>umount</code>,取下系统盘并装回服务器,开机…没有任何反应,依旧打不开管理面板。是教程给的方法有误吗?</p>
+<p>遂又取下盘,挂载到软路由上,却提示该分区忙…我是相当忌惮挂载并修改分区的,生怕损坏了分区,服务器的数据可就全丢了。不敢再继续操作,我得另想出路。</p>
+<h3 id="连接显示器,但还是个瞎子"><a href="#连接显示器,但还是个瞎子" class="headerlink" title="连接显示器,但还是个瞎子"></a>连接显示器,但还是个瞎子</h3><p>现在能做的就是在服务器上通过 PVE 的 rescue terminal,来修改防火墙的配置了。</p>
+<p>我关闭了服务器,取下 P100,换上了 R7 240 这张十年老兵,插上了便携显示器…</p>
+<blockquote>
+<p>error: No suitable video mode found. Booting in blind mode.</p>
+</blockquote>
+<p>你这不是输出字了吗,怎么就进瞎子模式了???</p>
+<p>经过查询,我了解到系统正在寻找到显示模式,正是古早电脑终端使用的 <code>80-Column</code> 这样的显示模式。</p>
+<p>至于什么是 Blind Mode,我尝试在 GRUB 和 Linux 源码中搜索,都没有结果;为什么 PVE 系统找不到我的 R7 240,可能是缺少驱动吧,现在也无处知晓。</p>
+<p>按照网上的教程尝试在 GRUB 引导系统启动时添加显示模式的支持,并没有效果;当我尝试打印支持的显示模式时,发现根本不存在正常的显示模式,故只能放弃直接启动 PVE 的 rescue terminal.</p>
+<h3 id="还是得靠救援盘"><a href="#还是得靠救援盘" class="headerlink" title="还是得靠救援盘"></a>还是得靠救援盘</h3><p>给服务器插上救援盘,我先是打开了基于 Windows 的 PE 系统,发现显卡是有输出的,但分辨率非常低,且只有黑白画面。看来这张老显卡与这块寨板的相性真的不大好。</p>
+<p>在确认 Windows PE 系统下什么都做不了,操作还及其不便,我便退出了 PE 系统,打开了 Ubuntu LiveCD。</p>
+<p>这回,显卡倒是可以正常运行。以 <code>80-Column</code> 模式输出文字还是可以轻松办到的。有了命令行,操作也简单了不少。</p>
+<p>同为 Linux,操作 LVM 就简单了许多。使用 <code>lvm2</code> 挂载 PVE 的系统分区,并用 <code>chroot</code> 将用户空间切换至 PVE 系统,直接用 <code>systemctl disable pve-firewall</code> 把防火墙关了。</p>
+<p>再次进入 PVE,这下开机防火墙就不会启动,赶紧进 Datacenter 防火墙设置里还原误操作的配置。</p>
+<h2 id="事后的反思"><a href="#事后的反思" class="headerlink" title="事后的反思"></a>事后的反思</h2><h3 id="VLAN-配置的失误"><a href="#VLAN-配置的失误" class="headerlink" title="VLAN 配置的失误"></a>VLAN 配置的失误</h3><p>我对 VLAN 配置没有经验,便想着参考网上其他人的配置方法来做。但我查到的都是创建访客 Wi-Fi 这类的配置教程。与我的网络环境的差异在于,访客 Wi-Fi 用的 Interface 与 LAN 不一样,而我需要在 LAN 下配置两个 VLAN,配置方法就有些不同,不能直接搬配置。</p>
+<p>我应当要找的是较为通用的,同一个 Interface 下 VLAN 的配置教程(可能单臂路由配置和这个就有些像),再迁移到 Openwrt 上进行配置。</p>
+<p>由于软路由只有两个网口,也没法像有四五个网口的路由器一样留下一个作为”不死“的管理口,以降低把路由器配置挂掉的风险。</p>
+<h3 id="PVE-原本可以修得更快"><a href="#PVE-原本可以修得更快" class="headerlink" title="PVE 原本可以修得更快"></a>PVE 原本可以修得更快</h3><p>原本在将 PVE 系统盘挂载到软路由时,便可以用 <code>systemd</code> 停掉 <code>pve-firewall</code> 但当时我看到 PVE 论坛里有人说在 crontab 里加上 PVE 关闭防火墙的指令便能访问控制面板了,也有人附和说可以用。但我实际操作后发现并没有效果。</p>
+<p>而再次尝试挂载 LVM 时,提示分区忙让我不敢继续操作。虽然在服务器上用 LiveCD 直接挂载并没有出现分区忙的情况。</p>
+
+
+ 2024-02-12T10:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/aca48136.html
+ 日本之行-第一站-大阪
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>23年12月底,我踏上了前往日本的旅途。这是我第一次去日本,是我第一次出国旅行,且只身一人。</p>
+<p>本次旅行为14天,我将分多个章节完成所有的游记,记录下我旅行中的点点滴滴、各种感想。这是第一章节,出境与大阪之旅。</p>
+<h2 id="第一次出境"><a href="#第一次出境" class="headerlink" title="第一次出境"></a>第一次出境</h2><p>我从上海浦东机场出境。</p>
+<p>航班预计上午 9:40 起飞,根据网上的经验教训,国际航班建议提早三个小时到达机场办理值机手续。我早早地起了床,搭上了第一班磁浮列车,在六点五十分左右到达了机场。</p>
+<p>拖着行李箱直奔值机柜台,发现队伍已经很长。排了一个多小时的队才轮到我。</p>
+<p>我提前在网上进行了值机选座,现场只需要将行李箱托运,打印一下登机牌。工作人员会确认护照和日本 eVisa(电子签证)。</p>
+<p>我提前将电子产品和需要独立安检的物品取出,安检也十分顺利。</p>
+<p>不过在过海关的时候,工作人员看到我护照崭新,又是独自一人,不免有些担心,向我索取了酒店预订记录和行程单。酒店预订记录只要出示订购软件的订单界面即可;我没有做行程单,就将记事本里的旅行计划给工作人员看,第一天在哪里啊,过两天又跑去哪里玩…得知我全程的酒店都已订好,行程也安排清楚,便允许我出境了。</p>
+<p>在候机厅的长椅上坐下时,距离飞机的预定起飞时间只有半个小时了。看来国际航班确实需要早点来机场办手续(虽然最后延误了两个小时)。</p>
+<p>这是我第二次坐飞机。上一次是我还在读小学的时候,不明不白地上了飞机、又下了飞机,除了耳朵有些不舒服。没有其他的感想。</p>
+<p>这次,我特地选择了靠窗的位置,仔细观察着飞机的起降与飞行时的动作。看到了起飞和降落时襟翼的展开,看到了遇到乱流时机翼“夸张”的摆动,感叹着这种看似“简易”的机器是如何将一机人送上天。</p>
+<p><img src="/images/2024-01-21/01.jpeg" alt="日本上空"></p>
+<p>飞机餐的话…上一次坐飞机是国内航线,没有在饭点所以没有供餐,这是我第一次体验飞机餐。</p>
+<p><img src="/images/2024-01-21/02.jpeg" alt="鱼肉米饭套餐"></p>
+<p>没有选择的余地,大家统一都是鱼肉米饭套餐。和我能想象到的飞机餐没有太多区别 —— 一般咸味的鱼肉,煮得软烂的米饭(也可能是再加热的缘故)与味道平平的配菜。坐的毕竟是经济舱,不能期待太高~</p>
+<p><img src="/images/2024-01-21/03.jpeg" alt="鱼肉米饭的特写"></p>
+<p>经过两个半小时的飞行,飞机平稳降落在了关西国际机场。飞机缓慢驶向空桥的途中,我更换了日本的流量卡,给手表切换了时区。</p>
+<p>入境相对出境更加简单。出发前我就在网页中预先填写好了入境信息表与海关申报表,在对应的窗口或机器扫码即可完成申报。在入境窗口只需出示一下 eVisa,录入一下人脸和指纹就可以了。</p>
+<p>工作人员会用英语引导你操作,录入机器上也有中文提示。机场的指示牌都是四语的(日语、英文、中文和韩文),按照指示牌走就没有问题。</p>
+<h2 id="大阪之行"><a href="#大阪之行" class="headerlink" title="大阪之行"></a>大阪之行</h2><h3 id="Day-1-舒适的酒店与海游馆之旅"><a href="#Day-1-舒适的酒店与海游馆之旅" class="headerlink" title="Day 1 - 舒适的酒店与海游馆之旅"></a>Day 1 - 舒适的酒店与海游馆之旅</h3><h4 id="APA-酒店"><a href="#APA-酒店" class="headerlink" title="APA 酒店"></a>APA 酒店</h4><p>起飞延迟了两个小时,降落也迟了一个多小时。达到关西国际机场已是下午 3:30(这之后都是 UTC+9 时间)。我乘坐南海机场线前往难波站附近的 APA 酒店。</p>
+<p>插一嘴,在日本用 Google Maps 相当方便,交通工具的规划和信息展示都做得很棒。“通勤案内”这款官方推荐的交通软件我也有下载,不过用的最多的还是 Google Maps。</p>
+<p>等地铁、电车、新干线时,只要看 Google Maps 上的到站时间,到了哪辆车就上哪辆车。除非两个方向的车同时到站,否则是绝对不会坐错方向的。</p>
+<p><img src="/images/2024-01-21/04.jpeg" alt="APA酒店大楼"></p>
+<p>APA 酒店大楼的装修很有特色,在远处就能看到橙色的屋顶和 APA 的标志,而且在全国的风格都是统一的。</p>
+<p><img src="/images/2024-01-21/05.jpeg" alt="APA酒店房间"></p>
+<p>内部的装横令我十分惊喜:房间不是很大,但各种设施一应俱全,在床铺正对的墙上甚至有一台大屏电视。浴室毫不意外地配备了浴缸,还有定量放水系统,不用盯着浴缸有没有水漫金山了。寝具也相当的舒服,那两晚都睡得很好。</p>
+<p>对于一间一晚 300CNY 左右的旅馆,能有这样的体验我十分满意。</p>
+<h4 id="咖喱与牛排"><a href="#咖喱与牛排" class="headerlink" title="咖喱与牛排"></a>咖喱与牛排</h4><p>办好入住手续,放下行李,已是五点多。飞机餐的显然填不饱我的肚子,我早已饥肠辘辘,是时候出门觅食了。</p>
+<p>大阪海游馆是我计划中必去的一站,但它位于海港村,和其他景点在相反的方向,便安排在今晚游玩了。</p>
+<p>日本人很奇特的一个习惯出现了:有些店铺傍晚 6:00 到 6:30 就收摊了,最晚的会开到七点八点左右,再迟就只剩下营业到深夜的家庭餐厅与居酒屋。</p>
+<p>用“食べログ (tabelog)”这款软件在海游馆附近搜索评价比较高的店铺。</p>
+<p>在海游馆隔壁有一个小小的综合体,里面有一些店铺还有在营业。我选择了这家评价很高的牛排咖喱餐馆。</p>
+<p><img src="/images/2024-01-21/06.jpeg" alt="牛排咖喱"></p>
+<p>一份牛排咖喱饭、一碟酱菜、一碗味增汤。价格有些忘记了,在 1500円 左右。</p>
+<p>日本人很喜欢吃这种细长的青椒,不会辣,在天妇罗中也会出现。</p>
+<p>咖喱的甜口的,味道自然比百梦多咖喱块要好上数倍。牛肉很嫩,没有怎么调味,展现的是肉本身的鲜味。总之,相当的美味。</p>
+<p>对了,需要使用现金哦~ 商家没有准备 POS 机。</p>
+<h4 id="大阪海游馆"><a href="#大阪海游馆" class="headerlink" title="大阪海游馆"></a>大阪海游馆</h4><p>已经记不清上一次去水族馆是多少年前。听说大阪海游馆的设施很棒,场馆也很大,我就来体验一下。</p>
+<p>门票的价格是 2600 円,入场有分时段,我就在售票处购买了当前时段的票。</p>
+<p>海游馆很大,步行参观的总距离在 1km 左右,按照地理位置划分成了好几个区域。</p>
+<p><img src="/images/2024-01-21/07.jpeg" alt="趴在水箱顶的鳐鱼"></p>
+<p>进入场馆便是一个巨大的拱形水箱。是玻璃比较薄吗,还是用了什么技术,几乎看不见玻璃带来的重影,鱼仿佛真的在空中悬浮。</p>
+<p><img src="/images/2024-01-21/08.jpeg" alt="正在睡觉的海狮"></p>
+<p>一群正在睡觉的海狮。抬着头睡觉不会落枕么。</p>
+<p>![巨型水箱]/images/2024-01-21/(9.jpeg)</p>
+<p>在场馆的中央有一个巨大的水箱,有两头鲸鲨、几头锤头鲨和许多小鱼生活在其中。</p>
+<p><img src="/images/2024-01-21/10.jpeg" alt="花园鳗~"></p>
+<p>还有好多可爱的花园鳗,黄色的尤其可爱~</p>
+<p>除此之外,还有来自各个大洲的鱼、南极的企鹅,在地下还有水母馆。</p>
+<p>总共逛了一个多小时,可以说大饱眼福了。</p>
+<p><img src="/images/2024-01-21/11.jpeg" alt="天保山摩天轮"></p>
+<p>在海游馆的隔壁是天保山摩天轮,夜晚被彩色的灯光照亮特别好看。</p>
+<p>此时已是 19:30,经历了一天奔波的我有些劳累,便回到酒店养精蓄然,计划第二天的行程。</p>
+<h3 id="Day-2-天守阁、天满宫、天筋桥与大阪烧"><a href="#Day-2-天守阁、天满宫、天筋桥与大阪烧" class="headerlink" title="Day 2 - 天守阁、天满宫、天筋桥与大阪烧"></a>Day 2 - 天守阁、天满宫、天筋桥与大阪烧</h3><p><img src="/images/2024-01-21/12.jpeg" alt="酒店的自助早餐"></p>
+<p>在日本的第一顿早餐,在酒店隔壁的参观享用了自助餐。</p>
+<p>炒蛋和上次去香港在澳洲牛奶公司吃到的有点像,并没有完全做熟,非常的软嫩~</p>
+<p>茶泡饭就很有日本的特色了。</p>
+<p>饭后,我就前往难波站附近的旅游中心,购买大阪周游卡。我选择的是二日卡,价格是 3600 円。在两天内,可以免费乘坐大阪地铁与巴士(相较于一日卡,不能乘坐私铁),以及免费参观好多景点。</p>
+<p>大阪地铁网络十分发达,基本覆盖了所有想去的地方,这两天我没有搭乘过私铁或者巴士,因此不用担心是否要选择一日卡。</p>
+<p>买到卡之后,第一站便是天守阁。</p>
+<p><img src="/images/2024-01-21/13.jpeg" alt="天守阁外围"></p>
+<p>从外边看,与只狼里的苇名城有几分相像。</p>
+<p><img src="/images/2024-01-21/14.jpeg" alt="天守阁下"></p>
+<p>天守阁内陈列着许多颇有历史的物件,只可惜我对日本的历史并不熟悉,也看不太懂书法家的笔墨,只是走马观花感受一下文化的氛围。</p>
+<p><img src="/images/2024-01-21/15.jpeg" alt="天守阁顶"></p>
+<p>最后在楼顶吹了吹风,便离开了天守阁。</p>
+<p><img src="/1/images/2024-01-21/6,.jpeg" alt="一轮彩虹"></p>
+<p>虽然有些冷,但天气真的很好,万里无云。下一站是天神筋桥,一个商业街。</p>
+<p><img src="/images/2024-01-21/17.jpeg" alt="天神筋桥商业街"></p>
+<p>日本有很多 OOばし(桥)这样的地名呢。也有很多像天神筋桥这样,上方覆盖着遮雨棚的商业街,天神筋桥是其中最长的一条,从一丁目延伸到七丁目,光是主干就有 2.6 km,更有密密麻麻的小巷。</p>
+<p>乘坐堺筋线,在天神筋桥三丁目下了车。我先是去拜访了天满宫。</p>
+<p><img src="/images/2024-01-21/18.jpeg" alt="大阪天满宫"></p>
+<p>似乎内部在翻修,将赛钱箱放到了外边供大家参拜。</p>
+<p>在这里,我给身边参加考研的人做了参拜,希望他们可以拿到好成绩。</p>
+<p>接着,我便从二丁目开始,一路边逛边思考着午饭的去处,寻找着吃饭的店铺。</p>
+<p>沿途,看到了大排长龙的可乐饼摊子,转了一圈再想回来买,发现已经卖完收摊了。有一家天妇罗的店门口也站着好多人,大排长龙。肚子好饿,肯定排不了这么长的队。</p>
+<p>边走边用 食べログ 搜索着。来大阪就得吃些有大阪特色的,那就是大阪烧了。</p>
+<p><img src="/images/2024-01-21/19.jpeg" alt="千草大阪烧"></p>
+<p>并不在主街,而是藏在小巷子里的千草大阪烧,似乎是 食べログ 23年的百大名店呢。</p>
+<p>我选择了以店铺名字所称的招牌菜 —— 千草大阪烧。</p>
+<p>核心是一大片厚切的猪肉,在上下两面都倒上面粉与卷心菜混合的泥,便开始煎烤。</p>
+<p>接待我的服务员会一些简单的英语,告诉我等着他们来翻面就好了。当地人也许会选择以自己的喜好来摊大阪烧,我作为门外汉只要静静欣赏就好。</p>
+<p>当两面都煎至金黄,便会涂上大阪烧酱、沙拉酱和黄芥末酱,撒上不知道是什么的籽,就可以享用。</p>
+<p>大阪烧整体的口感是软糯的,夹心的猪肉排很嫩,肉汁十足。</p>
+<p>唯一可惜的是,量实在有些少,填不饱我的肚子啊~</p>
+<p><img src="/images/2024-01-21/20.jpeg" alt="鲷鱼烧"></p>
+<p>饭后继续在天神筋桥闲逛,发现了一家鲷鱼烧店。我还以为鲷鱼烧是软软的,但实际外壳是偏脆的,甜甜的红豆馅十分美味。</p>
+<p>今天真的走了好久的路,对于第一次来到异国他乡的人可以说是有些得意忘形。从天神筋桥二丁目逛到六丁目,又折返了回来。中途还去 melonbooks 看了一圈,又跑到周游卡可以免门票的天王寺动物园里逛了逛…还没逛完,人就开始有些不舒服了。</p>
+<p>在动物园里稍微休息了一下,我还是决定吃完晚饭就回酒店休息。</p>
+<p><img src="/images/2024-01-21/21.jpeg" alt="大起水产回转寿司"></p>
+<p>一次比较“失败”的体验。不要误会,寿司还是很好吃的,鱼类十分新鲜,但我一不小心就在 iPad 上点了太多的寿司,都吃进肚子之后已经很饱了,完全没有在回转的转盘上取过寿司!这和普通的寿司店不就没差别了吗!</p>
+<p><img src="/images/2024-01-21/22.jpeg" alt="道顿堀"></p>
+<p>回转寿司店出门便是热闹的道顿堀,然而我不是喜好这一口的人。路过蟹道乐,周围停着好几台旅游大巴,店门口密密麻麻的全部都是在等待的游客,大约有数百人。真是疯狂呐。</p>
+<p>受不住喧嚣,我在附近的药店买了一支体温计和一盒退烧药,便回了酒店。</p>
+<p>躺床休息了一阵子再测体温已经正常,看来真的是疲劳导致的体温失调。之后的旅途安排就宽松一些吧。</p>
+<h3 id="Day-3-梅田蓝天大厦、天空美术馆与一兰拉面"><a href="#Day-3-梅田蓝天大厦、天空美术馆与一兰拉面" class="headerlink" title="Day 3 - 梅田蓝天大厦、天空美术馆与一兰拉面"></a>Day 3 - 梅田蓝天大厦、天空美术馆与一兰拉面</h3><p>睡了一个好觉,但人还是有些疲劳。前往异国他乡果然不能过于放肆,得做好身体的管理。今天就悠闲地度过吧!</p>
+<p><img src="/images/2024-01-21/23.jpeg" alt="通往观景台的扶梯"></p>
+<p>在酒店寄存行李后,搭乘地铁来到了梅田蓝天大厦。从三楼有快速电梯可以通往顶楼,然后乘坐扶梯来到屋顶的圆形观景台。</p>
+<p>对了,有周游卡门票免费哦~</p>
+<p><img src="/images/2024-01-21/24.jpeg" alt="梅田蓝天大厦楼顶观景台1"></p>
+<p><img src="/images/2024-01-21/25.jpeg" alt="梅田蓝天大厦楼顶观景台2"></p>
+<p>相比于其他观景塔,梅田蓝天大厦不算很高,但好在有开放式的观景台,视野特别棒。今天风有些大,在楼顶不是很站得住,欣赏了几分钟美景便回到了室内,点了一杯咖啡,坐在窗边静静欣赏着窗外景色。</p>
+<p>梅田蓝天大厦外侧是镜面玻璃,倒映着天空、与天空融为一体,因此称作蓝天大厦。尼尔:机械纪元的开发公司白金工作室的总部就在这栋大楼里。</p>
+<p><img src="/images/2024-01-21/26.jpeg" alt="天空美术馆1"></p>
+<p><img src="/images/2024-01-21/27.jpeg" alt="天空美术馆2"></p>
+<p>我还顺道参观了另一座楼的天空美术馆,虽然不怎么懂艺术,但画作依旧能感染我。</p>
+<p>参观美术馆后,已是正午。该去找吃的了~</p>
+<p><img src="/images/2024-01-21/28.jpeg" alt="一兰拉面的“考试”"></p>
+<p>百闻不如一见,我来品尝一兰了。</p>
+<p>每个人第一次来吃日式拉面都要面临一场“大考” —— 单应该怎么点。(背面有中文,我填完了才发现)</p>
+<p>除了在点餐机器上确认要吃东西,在排位时服务员会给你一张纸片,让你选择拉面的喜好。</p>
+<p>浓郁度我选择了加浓,确实有些过浓过咸了。吃着相当地过瘾,豚骨的香味充满嘴巴,真的很幸福。如果平常吃得比较清淡,正常的浓郁度可能就有些偏咸了,可以考虑减少一些浓郁度。</p>
+<p>油脂的丰富度我也加了一档,但感觉不是特别明显。可能是看到日本有一种表面铺满油渣的拉面,一兰的面在油脂的方面反而显得“寡淡”了吧。</p>
+<p>其余的选项便是看个人喜好。一兰的辣椒粉不会很辣,加 1/2 倍感受不出辣味,但可以提升香味。</p>
+<p><img src="/images/2024-01-21/29.jpeg" alt="一兰拉面"></p>
+<p>交卷后,一碗一兰拉面,四片叉烧和一颗盐味溏心蛋上桌啦。</p>
+<p>一兰号称在面出锅后15秒内就会传递到食客的面前,以体验拉面最新鲜的味道。我赶忙用手机拍了张照,便开始享用。</p>
+<p>拉面和我之前吃过的感觉都不大一样,特别有筋道,麦香味也很浓。在浓厚的汤汁里蘸一下就有了豚骨的鲜味,没几口就把拉面吃完了,便又加了半份面。</p>
+<p>上溏心蛋的时候同时给了个小碟子,如果蛋不好剥或者咸味不合适,可以让服务员帮忙换一个。</p>
+<p>一口拉面,配上一口汤汁。再咬一口叉烧、一口糖心蛋,至福啊。</p>
+<h3 id="前往奈良"><a href="#前往奈良" class="headerlink" title="前往奈良"></a>前往奈良</h3><p>饭后,回酒店取了行李,便是去难波站坐近铁奈良线,前往奈良了。</p>
+<h2 id="一点感想"><a href="#一点感想" class="headerlink" title="一点感想"></a>一点感想</h2><p>语言不是问题,打招呼、点菜这种比较简单的对话就用手势与塑料日语,更复杂的对话用英语就好了。碰到的酒店前台经理、便利店员工、车站管理员都是会英语的。再不济,就用翻译软件吧,能达意就行~</p>
+<p>至于习惯问题,按照当地人的做法来做就好了。在公共场合,例如楼梯应该走哪个方向,等地铁的时候应当怎么站,在地铁上有哪些地方不能使用手机,都有明确的标识和多语言的提示。多留意,照着做,就不会有事儿啦。</p>
+
+
+ 2024-01-21T15:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/2ef8bd61.html
+ 深圳-香港三日行
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="出行原因"><a href="#出行原因" class="headerlink" title="出行原因"></a>出行原因</h2><p>其实我很早就有去香港看看的念头,但有时间出游的机会并不多(机会很多,是我比较懒,更喜欢宅家),一直没去成。</p>
+<p>前一段时间,在朋友的帮助下我注册了英国 Wise 账户,拿到了两张漂亮的实体卡,但后期如何入金成了个问题。了解到可以通过香港账户低损耗入金 Wise,我便有了前往香港开户的念头。</p>
+<p>12 月底我将独自前往日本游玩,但这将是我第一次独自一人出去旅游。第一次单人旅行还是出国旅行,不禁让我有点担心。</p>
+<p>在这两个背景的驱使下,我迅速定下了这次深圳-香港的旅行,既可以前去开户,也可以作为出国旅行的预演,提前暴露一些问题。</p>
+<h2 id="流水账"><a href="#流水账" class="headerlink" title="流水账"></a>流水账</h2><h3 id="Day-1-Thu-深圳"><a href="#Day-1-Thu-深圳" class="headerlink" title="Day 1 - Thu - 深圳"></a>Day 1 - Thu - 深圳</h3><p>第一天中午到的深圳。我在前往酒店的半路上下了地铁,前往名为“臻品鲜粿·粿条世家”的店铺吃午餐。</p>
+<p><img src="/images/2023-12-19/01.jpg" alt="潮汕粿条"></p>
+<p>粿条本身没有很特殊,有点细河粉的感觉。不过八成熟的牛肉非常非常的嫩,味道很棒。</p>
+<p><img src="/images/2023-12-19/02.jpg" alt="炸豆腐"></p>
+<p>炸豆腐是赠送的,第一眼并太高的期望。但一口咬下,十分软绵的里芯呼之欲出,佐以略微有些咸的沾汁,味道很棒。</p>
+<p><img src="/images/2023-12-19/03.jpg" alt="像海蛎煎一样的东西"></p>
+<p>我还点了一份长得有点像海蛎煎的东西,记不得名字了。应该是用油炸的,比海蛎煎更加酥脆。</p>
+<p>美美地享用午饭后,我继续登上地铁,前往酒店。</p>
+<p>下榻酒店后,我前去参观深圳世界之窗景区…总之就是很后悔。</p>
+<p>比起人造景观,我还是更喜欢自然景观一些。世界之窗没有给我带来惊喜,只有满满的失望。今天是周四,许多游乐设施都没有开放,可以参观的仅有一个个微缩复原的建筑物。</p>
+<p>在世界之窗转了约两个小时,我悻悻离去,前往凤凰楼食茶点晚餐。</p>
+<p><img src="/images/2023-12-19/04.jpg" alt="香煎多春鱼"></p>
+<p>多春鱼的鱼籽很多,非常鲜美。</p>
+<p>一个人出来吃饭确实有些不便。有些菜品一次只能点这么一大盘,不过还是美美的享用了。</p>
+<p><img src="/images/2023-12-19/05.jpg" alt="鲜虾烧麦"></p>
+<p>鲜虾烧麦就是吃鲜,特别的鲜甜。</p>
+<p><img src="/images/2023-12-19/06.jpg" alt="牛杂炒肠粉?"></p>
+<p>菜单上的字看得不是很懂,大概写的是炒肠粉吧,没想到上了这么大一盆长得也不像肠粉的东西。</p>
+<p>吃了一下,似乎确实是切成一节一节的肠粉。味道不错,但量好大最后没吃完。</p>
+<p><img src="/images/2023-12-19/07.jpg" alt="华强电子世界"></p>
+<p>饭后,我顶着吃撑的肚子徒步前往华强北,这片传说之地。可惜的是,大部分店铺八点就关门了,简单逛了几栋电子市场后我便回酒店计划第二天的行程。</p>
+<h3 id="Day-2-Fri-HK-开户"><a href="#Day-2-Fri-HK-开户" class="headerlink" title="Day 2 - Fri - HK 开户"></a>Day 2 - Fri - HK 开户</h3><p>一大早我便乘上了前往福田口岸的地铁。</p>
+<p>7:30 左右的口岸没有什么人,大部分是赴港上学的学生,通关很快。</p>
+<p>我在落马洲站选择乘坐 B1 双层巴士前往元朗区,开始了开户之旅。</p>
+<p>沿途…说实话没有什么风光,元朗算是比较偏僻的地方,好风景还要等到第二天的香港岛之行。</p>
+<p>8:00 我到达了中行门口,发现只有前来上班的员工,还没有来排队的人。如果希望第一个办理业务的朋友可以选择这个点就来等候。时间还早,我就去吃了个早餐,回来发现已经有两个人在排队,便加入了队伍。</p>
+<p>9:00 准时进入了银行,取到了号。在我之前有提前预约的人,我排在了第 5 号。</p>
+<p>在工作人员的引导下使用手机填写了信息,然后等待柜员办理剩下的手续。共有三个柜台在同时办理开户,一个人需要 30 - 60 分钟的时间。</p>
+<p>11:00 左右,我完成了中行的开户。</p>
+<p>我现场就拿到了银行卡,便在 ATM 存入了 500 元人民币,并兑换成了港币。</p>
+<p>顺便在同一条路上的南洋商业银行和汇丰银行也完成了开户手续。这两家银行皆需要几个工作日审核信息,审核成功后会将卡片寄至通讯地址。</p>
+<p>穿行于各家银行网点的途中,我发现了一家在他人游记上看到过的店铺——胜利牛丸,便暗自定下了午饭的去处。</p>
+<p><img src="/images/2023-12-19/08.jpg" alt="牛丸、牛筋、牛腩三合一河粉"></p>
+<p>三个愿望,一次满足。</p>
+<p>面汤的颜色相当的“可怕”——仿佛是直接将卤水呈上来一般。但实际入口并没有很咸。牛肉们就不一样了,比较重口味的我也觉得有一点点咸。不过风味很棒,牛肉吃透了各种香辛料。</p>
+<p>河粉藏在了对得满满的牛丸和牛肉之下,不是我常吃的宽河粉,比较窄。</p>
+<p>虽然店名以牛丸冠名,我却没觉得牛丸有多么的美味,反倒是牛筋更胜一筹。</p>
+<p>饭后,我在坐满老者的公园里稍稍歇息,并且计划着下午的行程……</p>
+<p>此次香港之行除了开户,其实还有另一个目标——碧蓝档案与香港吃茶三千的联动~</p>
+<p>在出发前的几天得知了联动的消息,运气真的很好。</p>
+<p><img src="/images/2023-12-19/09.jpg" alt="电车"></p>
+<p>虽然有更优的路线,我还是选择了体验一下地面上的有轨电车。有轨电车有点像巴士,也是由司机驾驶,也会受到其他车辆与行人的干扰,但行驶路线是沿着铁轨固定的。</p>
+<p>随后乘坐地铁,前往金紫荆广场…的对面,星光大道。在这里陈列了许多明星的手印和画像。海港边上的风景大好。</p>
+<p>在此稍停,开户还没有结束。大概是因为元朗距离内地太近,许多虚拟银行都需要前往更靠近香港中心一些的地方才可以开户。我在一家星巴克坐下,开通了 ZA Bank 和 livi Bank 的账户。</p>
+<p>随后,便是前往海港城的吃茶三千点了一杯联动奶茶,从中心一步步移动回福田口岸——晚上要和群友面基。</p>
+<p>我们俩聊得非常尽兴,聊到了十一点才分手,回到了酒店,速速洗漱完毕,预定了第二天早上从福田站前往香港西九龙的火车,便沉沉睡去。</p>
+<h3 id="Day-3-Sta-HK-游玩"><a href="#Day-3-Sta-HK-游玩" class="headerlink" title="Day 3 - Sta - HK 游玩"></a>Day 3 - Sta - HK 游玩</h3><p>昨晚火车票订得比较迟,最迟的火车也是 7:45 的,更迟的都被人订光了。我又被迫起了个大早(这三天都是 6:30 之前起床的)坐地铁前往福田火车站,搭上前往香港西九龙站的火车。可能是比较早乘火车的人并不对,这一趟通关流程比较顺利,再迟一点可就不好说了……</p>
+<p>到达九龙,我空着肚子在街上漫步,寻找可以填饱肚子的早餐店。打开地图,“澳洲牛奶公司”几个字印入眼帘,这不是前几天看到的网红店吗?我记得这家店只收现金,便前往沿途的便利店,在 ATM 用中银香港的卡取了 100 港币现金。没有注意到我用的是汇丰的 ATM,与银联网络并不互通,被收取了 15 港币的手续费 QAQ。(众安银行的卡在全港 ATM 可以免手续费取现,经常到港玩的话可以办一张实体卡)</p>
+<p><img src="/images/2023-12-19/10.jpg" alt="九龙街头漫步"></p>
+<p>带着现金,我来到澳洲牛奶公司的门口,果不其然,排起了长队。好在我是一个人,这家店是强制拼桌的,顺利超过了大团的游客,来到了店内。</p>
+<p>给我的第一印象嘛…很拥挤,人挤着人,甚至是背贴着背。小小的一张圆桌围坐着四个人,各自享用着早餐。穿着白大褂的服务员在人群缝隙间穿梭着,高效地记录着到店客人的订单,飞速地将菜品传递到桌前。</p>
+<p>落座,菜单压在桌子的玻璃之下,写满了粤语,说实话我看不大懂。纠结了几十秒,发现菜单上还写着“早餐”、“下午茶”等套餐,我便对服务员说:“早餐套餐来一份吧。”</p>
+<p>话音未落,餐具和一杯茶便着陆在我的桌上。剩下的菜品也并我让我久等。</p>
+<p><img src="/images/2023-12-19/11.jpg" alt="火腿通心粉"></p>
+<p>首先呈上的是一碗火腿通心粉。味道比较清淡,喝不出黄色的汤底是什么熬制的,不过比较鲜。</p>
+<p><img src="/images/2023-12-19/12.jpg" alt="牛油吐司与炒双蛋"></p>
+<p>接着呈上的是一个盘子,两片吐司和炒鸡蛋各占半壁江山。牛油烘烤的吐司暖暖的,软软的;这份炒双蛋则是套餐的点睛之笔——调味微咸的鸡蛋并没有炒得很熟,在生与全熟之间掌握了平衡,做到了入口软绵,回味无穷,堪称是绝品。</p>
+<p>如果有路过,一定要来尝一下这份炒鸡蛋~</p>
+<p>一顿饱餐之后,我继续踏上旅途,前往太平山顶的观景台。</p>
+<p><img src="/images/2023-12-19/13.jpg" alt="山顶缆车"></p>
+<p>上山的路可以选择用脚走完,不过,还是要体验一下富有特色的山顶缆车~</p>
+<p>缆车的斜度估摸着有三四十度,是我见过最陡的。缆车是建在山坡边上的,方便沿路欣赏香港的风景。</p>
+<p><img src="/images/2023-12-19/14.jpg" alt="摩天台观景台"></p>
+<p>缆车的终点站是太平山的山顶,下车后直接来到了凌霄阁摩天台的二层。沿着盘旋向上的电动扶梯,海拔逐渐升高,走着走着,最终到达了海拔428米的摩天台观景台。</p>
+<p>在这里可以俯览香港的景色,听说夜景更美,不过我晚上并不住在香港,也就没有机会亲眼目睹,实在可惜。</p>
+<p>摩天台上的风很大,吹着十分舒服。待了大约四十分钟,我便沿着原路返回,坐着缆车回到了山下。</p>
+<p><img src="/images/2023-12-19/15.jpg" alt="漫步中环"></p>
+<p>早餐吃得有够饱,到了饭点我还不是很饿,便在街道与小巷间漫步,寻找着午饭的好地点。我想找家正宗点的茶餐厅。</p>
+<p>跟随地图,我来到了就近的广芳园…可等候的队伍早已排出店外,目测有三四十人在队伍中。因为下午就要坐动车回去了,我无心加入他们,便继续游荡着,寻找着。</p>
+<p><img src="/images/2023-12-19/16.jpg" alt="菠萝包与阿华田"></p>
+<p>附近的茶餐厅真的不多,跟随地图的指引,我来到了一家街边的小店。店内只收现金,而我身上只剩下不到五十港币,便选择了菠萝油和冰阿华田作为午餐,反正肚子不是特别饿。(冰的阿华田比热的还要贵上几块钱哦)</p>
+<p><img src="/images/2023-12-19/17.jpg" alt="City Walk"></p>
+<p>饭后,我一时兴起决定走路去西九龙火车站,而非乘坐地铁,便开始一趟 City Walk…差点把我 Walk 没了。</p>
+<p>街上好多地方都在施工,地图的数据并没有新到可以帮我绕开因为施工而禁止通行的路段…再加上出海关也需要一些时间,我差点以为要赶不上车。</p>
+<p>好在我还是在开始检票前就赶到了,算是有惊无险,顺利踏上了返乡的路,结束了此次旅程。</p>
+<h2 id="我都带了些什么"><a href="#我都带了些什么" class="headerlink" title="我都带了些什么"></a>我都带了些什么</h2><p>此次我轻装上阵,只带了一个双肩包。</p>
+<p>这次旅行我尝试了内衣、袜子和洗澡用具(浴巾与毛巾)都使用一次性的,省去了携带大量衣物且需要带回大量脏衣物的麻烦,也可以保证洗澡用具足够干净(对酒店不是很信任)。浴巾和毛巾都是压缩的,体积非常小。</p>
+<p>其余的,便是两晚更换的衣服和电子产品的充电器了。</p>
+<h2 id="开户的那些事"><a href="#开户的那些事" class="headerlink" title="开户的那些事"></a>开户的那些事</h2><p>我挺担心中银香港会开不下来,因为我还是学生也没有带地址证明。在大堂接待客户的小姐姐也询问了我有没有带上地址证明,没有的话只能碰碰运气,弄得我更慌了。</p>
+<p>不过真正坐进柜台和柜员大哥开始走流程,直到大堂经理过来刷卡完成审核、将卡递到我手中,都没有向我索取地址证明。不知道是因为得知我是学生,还是因为我身份证上写的是具体住址,反正过了这关,顺利拿到了卡。</p>
+<p>而汇丰之行则运气不佳,提交的资料被送至总行审核,当天拿不到账户也无法完成所有流程,只能等卡寄到居住地址,并且等下一次到港签字完成所有开户流程了。</p>
+<p>P.S. 在跨境转账到时候一定要填清楚收款人的名字,要与中银香港的账户名称相同。我填反了名字被收了一笔手续费 QAQ</p>
+<h2 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h2><p>这次旅行顺利得可怕(除了差点没赶上返程的火车),在深圳与香港复杂的地铁网中也没有坐错一趟列车,似乎没能暴露出什么问题。希望 12 月底的日本之旅也能一样顺利。</p>
+<p>一个人出来玩真的很自由,在出发前先查好可以去哪里玩、去哪里吃,到达之后就随心安排,不用迁就其他人,走累了随时可以停下休息,在咖啡店坐上一两个钟头也不会有人抱怨。</p>
+<p>我也许爱上了一个人旅行的感觉,之后也会继续尝试这种旅行的方法。</p>
+
+
+ 2023-12-19T14:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/42ec6146.html
+ My Second Attempt To ARM Servers
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>Last weekend, a full month without my Telegram account, I thought I had to do something. I believe I got banned because someone clicked the “spam” button on my messages, but I have nowhere to appeal.</p>
+<p>I lost the faith in centralization. This time, I made up my mind to dive into decentralization.</p>
+<p>First, I need a server.</p>
+<h2 id="Where-To-Purchase"><a href="#Where-To-Purchase" class="headerlink" title="Where To Purchase"></a>Where To Purchase</h2><p>I used to buy servers from distributors of the DC for lower prices, but it always ended up with disappointment. The servers are just not stable and I can not upgrade the hardware configuration on demand.</p>
+<p>So this time, I chose Hetzner for my server. Hetzner is a huge IDC in Germany, I must have to pay a fairly high price for the server, am I right?</p>
+<p>The following images show the price of Hetzner’s traditional CX series - with x86 CPU.</p>
+<p><img src="/images/2023-09-24/01.jpg" alt="Hetzner CX Series"></p>
+<p>A 2 CPU and 4GB RAM CX21 can fit my needs, it costs 5.35 EUR per month. By the way, the 40GB disk is far less than my requirement.</p>
+<p>The CX series are using Intel Xeon CPU. The GB6 benchmark score of an Intel core is around 850, which is significantly lower than AMD EPYC of around 1200.</p>
+<p>However, when I switch to the CAX series…</p>
+<p><img src="/images/2023-09-24/02.jpg" alt="Hetzner CAX Series"></p>
+<p>I can get the same number of CPU cores and RAM with only 3.79 EUR per month, 30% lower than the x86 one.</p>
+<p>The GB6 benchmark score of ARM CPU is around 1050, even higher than the Intel ones.</p>
+<p>The disk space is still 40GB, but I can purchase a 3.2EUR/month 1TB Storage Box to solve this problem.</p>
+<p>With a total of 6.99 EUR per month, I can get a server with 2 CPU cores, 4GB RAM, and 1TB disk. That is a great deal.</p>
+<h2 id="Wait-ARM"><a href="#Wait-ARM" class="headerlink" title="Wait, ARM?"></a>Wait, ARM?</h2><p>About a year ago, when Hetzner first released the ARM series, I was interested in it and evaluated its compatibility.</p>
+<p>I always use Docker containers to host my ~25 services. It turned out that more than half of the Docker images I was using were only compiled for the x86 CPU.</p>
+<p>That means you have to build the image by yourself if you want to run it on an ARM CPU. The time and effort were not worth it, so I gave it up.</p>
+<p>However, this time, I found all of the Docker images already supporting the ARM CPU. It is time to give it a try.</p>
+<h2 id="The-Experience"><a href="#The-Experience" class="headerlink" title="The Experience"></a>The Experience</h2><p>Here is my final hardware configuration:</p>
+<p>I chose the CAX11 server, which has 2 Ampere ARM CPU cores, 4GB RAM, and 40GB disk. I added 2GB swap space to store cached files and reduce the load of the RAM.</p>
+<p>I also purchased a 1TB Storage Box to store my data. Mounted to the server with NFS, just like a local disk.</p>
+<p>I am using the latest Debian 12 Bookworm and I can’t feel any difference between x86 and ARM. My daily use software from APT source is all compiled for ARM. The installation speed is as fast as x86.</p>
+<p>As to the Docker images, I am using Portainer to manage them. What I need to do is just click the “Recover” button on the settings page, Portianer will automatically recover the configuration from CloudFlare R2 storage and all the containers just work as before, with no need to change any settings.</p>
+<p>Today, when I am writing this article, My services are running for a week without any problem.</p>
+<p><img src="/images/2023-09-24/03.jpg" alt="Portainer"></p>
+<h2 id="Well-Still-Not-Perfect"><a href="#Well-Still-Not-Perfect" class="headerlink" title="Well, Still Not Perfect"></a>Well, Still Not Perfect</h2><p>The ARM server just works fine, but I want to share some problems I encountered when building the ARM Docker images.</p>
+<p>The official software repositories of Linux distributions can offer full support for ARM CPU, but the repositories of other sources such as PyPi can not.</p>
+<p><img src="/images/2023-09-24/04.jpg" alt="Build time difference"></p>
+<p>There are still some packages that don’t have prebuilt ARM wheels, and the building process can take a long time on the 2-core ARM machine.</p>
+<p>That is not a huge problem, but it makes the experience of ARM arch different from x86.</p>
+<p>If you are a developer, try to include ARM in your build pipeline next time, that will be a great help for the ARM community.</p>
+<h2 id="Final-Conclusion"><a href="#Final-Conclusion" class="headerlink" title="Final Conclusion"></a>Final Conclusion</h2><p>In my opinion, the ARM server is ready for daily use today. They offer a high quality-price ratio. I didn’t come across any compatibility problems.</p>
+<p>Next time when you want to purchase a server, you can consider the ARM ones.</p>
+
+
+ 2023-09-24T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/8b68ddd6.html
+ 修复 UEFI 引导的 GRUB
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="问题与解决方法"><a href="#问题与解决方法" class="headerlink" title="问题与解决方法"></a>问题与解决方法</h2><h3 id="环境"><a href="#环境" class="headerlink" title="环境"></a>环境</h3><p>Manjaro Linux x86_64</p>
+<p>Kernel: 6.2.10-1-MANJARO</p>
+<p>使用 UEFI 引导</p>
+<h3 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h3><p>在 GRUB 尝试引导 Linux 内核时,出现如下错误:</p>
+<figure class="highlight smali"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs smali">error:<span class="hljs-built_in"> sparse </span>file<span class="hljs-built_in"> not </span>allowed.<br><br>452: out of range pointer: xxxxxxxxxx<br><br>Aborted. Press any key to exit.<br></code></pre></td></tr></table></figure>
+
+<p>用户将无法进入系统。</p>
+<h3 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h3><h4 id="进入恢复系统"><a href="#进入恢复系统" class="headerlink" title="进入恢复系统"></a>进入恢复系统</h4><p>插入 Manjaro LiveCD, 启动 Live 系统。</p>
+<h4 id="确定磁盘分区"><a href="#确定磁盘分区" class="headerlink" title="确定磁盘分区"></a>确定磁盘分区</h4><p>在 Live 系统中,使用 <code>fdisk -l</code> 查看磁盘分区情况,找到安装 Manjaro 的磁盘,假设为 <code>/dev/sda</code>。</p>
+<p>我的磁盘分区如下:</p>
+<figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs tap">设备 起点 末尾 扇区 大小 类型<br>/dev/sda1 <span class="hljs-number"> 2048 </span> <span class="hljs-number"> 821247 </span> <span class="hljs-number"> 819200 </span> 400M EFI 系统<br>/dev/sda2 <span class="hljs-number"> 821248 </span><span class="hljs-number"> 723390463 </span>722569216 344.5G Linux 文件系统<br>/dev/sda3 <span class="hljs-number"> 723390464 </span><span class="hljs-number"> 983437311 </span>260046848 124G Linux 文件系统<br>/dev/sda4 <span class="hljs-number"> 983437312 </span>1000214527 <span class="hljs-number"> 16777216 </span> 8G Linux 文件系统<br></code></pre></td></tr></table></figure>
+
+<p>可以确定,<code>/dev/sda1</code> 是 EFI 系统分区,<code>/dev/sda2</code> 是系统所在分区。</p>
+<h4 id="挂载分区"><a href="#挂载分区" class="headerlink" title="挂载分区"></a>挂载分区</h4><p>挂载系统分区:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">mount /dev/sda2 /mnt<br></code></pre></td></tr></table></figure>
+
+<p>将当前系统的工具分区挂载到 <code>/mnt</code> 下:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">mount --<span class="hljs-built_in">bind</span> /dev /mnt/dev<br>mount --<span class="hljs-built_in">bind</span> /proc /mnt/proc<br>mount --<span class="hljs-built_in">bind</span> /sys /mnt/sys<br></code></pre></td></tr></table></figure>
+
+<p>将 EFI 分区挂载到 <code>/mnt/boot/efi</code> 下:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">mount /dev/sda1 /mnt/boot/efi<br></code></pre></td></tr></table></figure>
+
+<h4 id="进入系统"><a href="#进入系统" class="headerlink" title="进入系统"></a>进入系统</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">chroot</span> /mnt<br></code></pre></td></tr></table></figure>
+
+<h4 id="重新安装-GRUB"><a href="#重新安装-GRUB" class="headerlink" title="重新安装 GRUB"></a>重新安装 GRUB</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable<br></code></pre></td></tr></table></figure>
+
+<p>具体参数需要根据实际情况进行修改。</p>
+<h4 id="在这之后"><a href="#在这之后" class="headerlink" title="在这之后"></a>在这之后</h4><p>重启,进入通过 GRUB 引导系统。</p>
+<p>在系统中使用 <code>sudo grub-install --recheck /dev/sda</code> 命令再次安装 GRUB,确保系统能够正常启动。</p>
+<h2 id="一些思考"><a href="#一些思考" class="headerlink" title="一些思考"></a>一些思考</h2><p>接下来的内容是我的整个修复流程,包含了如何在搜索引擎查找问题、根据文章内容调整目标等杂碎的内容,和我在修复过程中的一些感想。</p>
+<h3 id="为什么会出现这个问题"><a href="#为什么会出现这个问题" class="headerlink" title="为什么会出现这个问题"></a>为什么会出现这个问题</h3><p>不是很清楚。</p>
+<p>在启动 Manjaro 前我不小心打开了电脑里的 Windows 系统,但没有连接移动硬盘。</p>
+<p>按照以往的经验,这最多只会导致找不到 GRUB 的位置,手动指定 GRUB 所在分区就可以启动系统。</p>
+<p>但这次不大一样。</p>
+<p>在打开 GRUB 之后,尝试引导内核,就发现了这个问题。</p>
+<h3 id="初步解决思路"><a href="#初步解决思路" class="headerlink" title="初步解决思路"></a>初步解决思路</h3><p><del>立刻格式化磁盘,重新安装 Manjaro。</del></p>
+<p>我已经不是曾经那个只会重装的我了,这次我希望可以解决问题,而不是简单地重装。</p>
+<p>首先,我 Google 了这个错误,发现了几篇内容相关的文章。</p>
+<p><a href="https://forum.artixlinux.org/index.php/topic,4668.0.html">报错与我一致的文章</a>,但没有给出解决方案。</p>
+<p><a href="https://www.reddit.com/r/archlinux/comments/x2qb4w/grub_aborts_loading_linux_because_of_an_out_of/">要我删除 GRUB 和 UEFI 所在分区所有内容的文章</a>,有点可怕,不敢这么干。</p>
+<p><a href="https://bbs.archlinux.org/viewtopic.php?id=280230">提到应该重新安装 GRUB 的文章</a>,这还有点道理。</p>
+<p>于是,我的目标转变为重新安装 GRUB。</p>
+<h3 id="重新安装-GRUB-1"><a href="#重新安装-GRUB-1" class="headerlink" title="重新安装 GRUB"></a>重新安装 GRUB</h3><p>在之前遇到找不到 GRUB 分区的问题时,在手动引导然后进入系统后,我会执行 <code>sudo grub-install --recheck /dev/sda</code> 重新安装 GRUB,解决这个问题。</p>
+<p>那这次的觉得方案应该是差不多的……吧?</p>
+<p>不对啊,这次是在 LiveCD 的系统里操作,怎么能直接安装 GRUB 呢?</p>
+<p>这个问题比较难描述。</p>
+<p>我先是 Google <code>grub-install 修复 GRUB</code>,中文网站的内容都是关于在可以启动的系统下修复 GRUB 的,没有我需要的内容。</p>
+<p>然后我开始求助于 ChatGPT,输入的 Prompts 是:</p>
+<figure class="highlight livecodeserver"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs livecodeserver">I am <span class="hljs-keyword">using</span> Manjaro <span class="hljs-keyword">with</span> GRUB.<br>When I booted <span class="hljs-keyword">into</span> <span class="hljs-keyword">the</span> <span class="hljs-keyword">system</span>, <span class="hljs-keyword">it</span> says <span class="hljs-string">"sparse file not allowed 452 out of range pointer"</span>. How <span class="hljs-built_in">to</span> fix <span class="hljs-keyword">it</span>?<br></code></pre></td></tr></table></figure>
+
+<p>不难发现,我并没有说明我使用的是 UEFI 引导,这直接影响到了 ChatGPT 回复的准确性。</p>
+<p>ChatGPT 给出的修复步骤与上述的相差不大,只是在挂载系统分区和工具分区后,直接尝试执行 <code>grub-install</code> 命令,尝试修复。</p>
+<p><code>grub-install</code> 返回错误 <code>this gpt partition label contains no bios boot partition</code> 把我弄得更懵了。</p>
+<p>再次 Google 这个问题,发现了 <a href="https://superuser.com/questions/903112/grub2-install-this-gpt-partition-label-contains-no-bios-boot-partition">这篇在长篇大论讲 GRUB 的文章</a>,虽然没有给出解决方案,但它让我意识到 UEFI 和 Legacy BIOS 两种启动方式的区别。</p>
+<h3 id="UEFI-和-Legacy-BIOS"><a href="#UEFI-和-Legacy-BIOS" class="headerlink" title="UEFI 和 Legacy BIOS"></a>UEFI 和 Legacy BIOS</h3><p>UEFI 和 Legacy BIOS 是两种启动方式,它们的区别在于,Legacy BIOS 是在 BIOS 中直接加载内核,而 UEFI 是在 BIOS 中加载 EFI 系统,然后由 EFI 系统加载内核。</p>
+<p>使用 UEFI 引导的系统一般都有一个 200MB 到 400MB 的 EFI 系统分区,用于存放 EFI 系统。在上述的,我的硬盘分区中可以看到。</p>
+<p>使用 <code>findmnt</code> 命令可以查看当前系统的挂载情况,其中 <code>TARGET</code> 列就是挂载点,<code>SOURCE</code> 列就是挂载的分区。</p>
+<p>EFI 分区的挂载情况为:</p>
+<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs gradle">TARGET <span class="hljs-keyword">SOURCE</span> FSTYPE <span class="hljs-keyword">OPTIONS</span><br><span class="hljs-regexp">/boot/</span>efi <span class="hljs-regexp">/dev/</span>sda1 vfat rw,relatime,fm<br></code></pre></td></tr></table></figure>
+
+<p>可以看到,<code>/boot/efi</code> 里的内容正是 EFI 系统分区的内容。(我也是刚学到这个知识的)</p>
+<h3 id="解决-UEFI-相关问题"><a href="#解决-UEFI-相关问题" class="headerlink" title="解决 UEFI 相关问题"></a>解决 UEFI 相关问题</h3><p>在修复过程中,我是通过 Google 发现上述的问题。</p>
+<p><a href="https://superuser.com/questions/1390428/grub-install-warning-this-gpt-partition-label-contains-no-bios-boot-partition">这篇文章</a> 给了我莫大的帮助。</p>
+<p>其中提到了 EFI 分区,也提到了如何正确安装 UEFI 引导的 GRUB:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> grub-install --target=x86_64-efi --efi-directory=/boot/efi<br><span class="hljs-built_in">sudo</span> grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable<br></code></pre></td></tr></table></figure>
+
+<p>在补充挂载 EFI 分区、切换 Root 目录后,执行第一条命令,发现有错误。尝试执行第二条命令,发现没有错误,代表 GRUB 已经重新安装成功。</p>
+<p>此时我想起来,在之前安装 GRUB 时,会提示 <code>正在为 x86_64-efi 平台进行安装</code>,我才意识到前面的修复过程并没有去指定平台。</p>
+<h3 id="总结一下"><a href="#总结一下" class="headerlink" title="总结一下"></a>总结一下</h3><p>总之,这就是我此次修复的心路历程。我没有研究过 UEFI 和 Legacy BIOS 的区别,也没有研究过 GRUB 的安装过程,所以在修复过程中,我是通过 Google 和 ChatGPT 的帮助才解决了这个问题。</p>
+<p>希望这个探索过程能给你一些启发吧。</p>
+<hr>
+<p>此文章以 <em>我无所谓 By 不 By 什么 AI,对我有帮助的文章就是好文章</em> 标识发布。</p>
+
+
+ 2023-04-15T16:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/8b115688.html
+ 使用 Docker Compose 部署音乐服务器 Navidrome
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="服务介绍"><a href="#服务介绍" class="headerlink" title="服务介绍"></a>服务介绍</h2><p>Navidrome 是一款兼容 Subsonic API 的开源音乐服务器软件,它提供了一个不错的 WebUI,也可以将支持 Subsonic API 的客户端接入。</p>
+<p>目前项目正处在活跃开发中,各种各样的新功能正在陆续加入。</p>
+<h2 id="我的客户端选择"><a href="#我的客户端选择" class="headerlink" title="我的客户端选择"></a>我的客户端选择</h2><h3 id="电脑端"><a href="#电脑端" class="headerlink" title="电脑端"></a>电脑端</h3><p>自带 WebUI, <a href="https://github.com/jeffvli/sonixd">Sonixd</a> 【跨平台】</p>
+<h3 id="iOS"><a href="#iOS" class="headerlink" title="iOS"></a>iOS</h3><p><a href="https://apps.apple.com/us/app/play-sub-music-streamer/id955329386">play:sub</a> 【付费软件 4.99$】</p>
+<h2 id="部署方式"><a href="#部署方式" class="headerlink" title="部署方式"></a>部署方式</h2><p>采用 Docker Compose 部署 Navidrome,使用 Nginx 作为反向代理。</p>
+<h2 id="Docker-Compose-配置文件"><a href="#Docker-Compose-配置文件" class="headerlink" title="Docker Compose 配置文件"></a>Docker Compose 配置文件</h2><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3"</span><br><span class="hljs-attr">services:</span><br> <span class="hljs-attr">navidrome:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">deluan/navidrome:latest</span><br> <span class="hljs-attr">container_name:</span> <span class="hljs-string">navidrome</span><br> <span class="hljs-attr">user:</span> <span class="hljs-number">1000</span><span class="hljs-string">:1000</span> <span class="hljs-comment"># should be owner of volumes</span><br> <span class="hljs-attr">ports:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">"127.0.0.1:4533:4533"</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span><br> <span class="hljs-attr">environment:</span><br> <span class="hljs-attr">ND_SCANSCHEDULE:</span> <span class="hljs-string">1h</span><br> <span class="hljs-attr">ND_LOGLEVEL:</span> <span class="hljs-string">info</span> <br> <span class="hljs-attr">ND_SESSIONTIMEOUT:</span> <span class="hljs-string">24h</span><br> <span class="hljs-attr">ND_BASEURL:</span> <span class="hljs-string">""</span><br> <span class="hljs-attr">ND_SEARCHFULLSTRING:</span> <span class="hljs-literal">true</span><br> <span class="hljs-comment"># Optional: fetch artist images from spotify</span><br> <span class="hljs-attr">ND_SPOTIFY_ID:</span><br> <span class="hljs-attr">ND_SPOTIFY_SECRET:</span><br> <span class="hljs-comment"># Optional: fetch artist information from last.fm</span><br> <span class="hljs-attr">ND_LASTFM_APIKEY:</span><br> <span class="hljs-attr">ND_LASTFM_SECRET:</span><br> <span class="hljs-attr">ND_LASTFM_LANGUAGE:</span> <span class="hljs-string">en</span><br> <span class="hljs-attr">volumes:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">"./data:/data"</span> <span class="hljs-comment"># Navidrome data</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">"/APTH-TO/navidrome-music:/music:ro"</span> <span class="hljs-comment"># Music folder</span><br></code></pre></td></tr></table></figure>
+<p>使用命令 <code>docker compose up -d</code> 启动服务。</p>
+<h2 id="Nginx-配置文件"><a href="#Nginx-配置文件" class="headerlink" title="Nginx 配置文件"></a>Nginx 配置文件</h2><p>我建议使用 DigitalOcean 的 Nginx 配置生产工具,示例配置如下:</p>
+<p><a href="https://www.digitalocean.com/community/tools/nginx?domains.0.server.domain=music.example.com&domains.0.php.php=false&domains.0.reverseProxy.reverseProxy=true&domains.0.reverseProxy.proxyPass=http://127.0.0.1:4533&domains.0.routing.root=false&domains.0.routing.index=index.html&domains.0.routing.fallbackHtml=true&global.app.lang=zhCN%5C">示例配置</a></p>
+<p>也可参考下述配置,此为 DigitalOcean 生成配置的简化版:</p>
+<figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-section">server</span> {<br> <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">server_name</span> music.example.com;<br><br> <span class="hljs-comment"># SSL</span><br> <span class="hljs-attribute">ssl_certificate</span> /etc/letsencrypt/live/music.example.com/fullchain.pem;<br> <span class="hljs-attribute">ssl_certificate_key</span> /etc/letsencrypt/live/music.example.com/privkey.pem;<br> <span class="hljs-attribute">ssl_trusted_certificate</span> /etc/letsencrypt/live/music.example.com/chain.pem;<br><br> <span class="hljs-comment"># logging</span><br> <span class="hljs-attribute">access_log</span> /var/log/nginx/access.log combined buffer=<span class="hljs-number">512k</span> flush=<span class="hljs-number">1m</span>;<br> <span class="hljs-attribute">error_log</span> /var/log/nginx/<span class="hljs-literal">error</span>.log <span class="hljs-literal">warn</span>;<br><br> <span class="hljs-comment"># reverse proxy</span><br> <span class="hljs-section">location</span> / {<br> <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;<br> <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:4533;<br> }<br>}<br><br><span class="hljs-comment"># subdomains redirect</span><br><span class="hljs-section">server</span> {<br> <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">server_name</span> <span class="hljs-regexp">*.music.example.com</span>;<br><br> <span class="hljs-comment"># SSL</span><br> <span class="hljs-attribute">ssl_certificate</span> /etc/letsencrypt/live/music.example.com/fullchain.pem;<br> <span class="hljs-attribute">ssl_certificate_key</span> /etc/letsencrypt/live/music.example.com/privkey.pem;<br> <span class="hljs-attribute">ssl_trusted_certificate</span> /etc/letsencrypt/live/music.example.com/chain.pem;<br> <span class="hljs-attribute">return</span> <span class="hljs-number">301</span> https://music.example.com<span class="hljs-variable">$request_uri</span>;<br>}<br></code></pre></td></tr></table></figure>
+
+<h2 id="音乐管理"><a href="#音乐管理" class="headerlink" title="音乐管理"></a>音乐管理</h2><p>我一直以文件夹分类的方式管理音乐,但 Navidrome 并不支持文件夹分类。它是根据音乐标签来分类的,例如按照歌手、专辑等依据分类歌曲。</p>
+<p>因此,若想使用 Navidrome,需要对音乐进行标签管理。</p>
+<p>大约两年前,我写了 <a href="https://blog.udon.eu.org/archives/6b40e5ad.html">一篇文章</a> 介绍使用 Music Tag 和 Foobar2000 两款软件来管理音乐。</p>
+<p>Music Tag 的标签源是网易云音乐、豆瓣音乐、QQ 音乐等国内音乐平台,说实话,这些平台的音乐标签质量都不是很好,所以我一直在寻找更好的音乐标签源。</p>
+<p>直到我发现了 <a href="https://musicbrainz.org/">MusicBrainz</a>,这是一个开源的音乐标签数据库,任何人都可以为它贡献标签。在体验之后,我发现 MusicBrainz 的音乐标签质量要比国内音乐平台的标签质量好很多,所以我决定将 MusicBrainz 作为我的音乐标签源。</p>
+<p>我使用 <a href="https://picard.musicbrainz.org/">Picard</a> 这款软件来从 MusicBrainz 获取音乐标签。</p>
+<p>将音乐导入 Picard 后,它会自动从 MusicBrainz 获取音乐标签,然后将标签写入音乐文件,十分方便。</p>
+<h2 id="开始使用"><a href="#开始使用" class="headerlink" title="开始使用"></a>开始使用</h2><p>不论是使用 Navidrome 自带的 Web 界面,还是使用兼容 Subsonic API 的客户端,只要连接到 Navidrome,便可开始享受你的私人音乐库。</p>
+
+
+ 2023-01-31T04:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/f9bfe16a.html
+ 使用 Docker Compose 部署 Keycloak 20
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="部署方式"><a href="#部署方式" class="headerlink" title="部署方式"></a>部署方式</h2><p>采用 Docker Compose 部署,使用 Postgres 作为数据库,使用 Nginx 作为反向代理。</p>
+<h2 id="Docker-Compose-配置文件"><a href="#Docker-Compose-配置文件" class="headerlink" title="Docker Compose 配置文件"></a>Docker Compose 配置文件</h2><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3'</span><br><br><span class="hljs-attr">services:</span><br> <span class="hljs-attr">keycloak:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">quay.io/keycloak/keycloak:latest</span><br> <span class="hljs-attr">environment:</span><br> <span class="hljs-attr">KC_DB:</span> <span class="hljs-string">postgres</span><br> <span class="hljs-attr">KC_DB_URL:</span> <span class="hljs-string">jdbc:postgresql://db:5432/keycloak</span><br> <span class="hljs-attr">KC_DB_USERNAME:</span> <span class="hljs-string">keycloak</span><br> <span class="hljs-attr">KC_DB_PASSWORD:</span> <span class="hljs-string">keycloak</span><br> <span class="hljs-attr">KC_HTTP_ENABLED:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># 开启 HTTP</span><br> <span class="hljs-attr">KC_HOSTNAME_STRICT:</span> <span class="hljs-literal">false</span><br> <span class="hljs-attr">KC_HOSTNAME_STRICT_HTTPS:</span> <span class="hljs-literal">false</span><br> <span class="hljs-attr">KC_HTTP_RELATIVE_PATH:</span> <span class="hljs-string">'/'</span> <span class="hljs-comment"># Keycloak 应用的相对路径</span><br> <span class="hljs-attr">KC_HTTP_PORT:</span> <span class="hljs-number">8080</span> <span class="hljs-comment"># HTTP 端口</span><br> <span class="hljs-attr">KEYCLOAK_ADMIN:</span> <span class="hljs-string">MY_USERNAME</span> <span class="hljs-comment"># 管理员账号,仅初始化时使用</span><br> <span class="hljs-attr">KEYCLOAK_ADMIN_PASSWORD:</span> <span class="hljs-string">MY_PASSWORD</span> <span class="hljs-comment"># 管理员密码,仅初始化时使用</span><br> <span class="hljs-attr">PROXY_ADDRESS_FORWARDING:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># 使用反向代理必须开启</span><br> <span class="hljs-attr">KC_PROXY:</span> <span class="hljs-string">edge</span> <span class="hljs-comment"># 反向代理模式,详见文档</span><br> <span class="hljs-attr">entrypoint:</span> <span class="hljs-string">/opt/keycloak/bin/kc.sh</span> <span class="hljs-string">start</span> <span class="hljs-comment"># 第一次运行后可以加上 --optimized 参数,加快二次启动速度</span><br> <span class="hljs-attr">ports:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:18080:8080</span> <span class="hljs-comment"># Keycloak 应用端口</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span><br> <span class="hljs-attr">db:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:14</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span><br> <span class="hljs-attr">environment:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_USER=keycloak</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_PASSWORD=keycloak</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_DB=keycloak</span><br> <span class="hljs-attr">volumes:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">./postgres-data:/var/lib/postgresql/data</span> <span class="hljs-comment"># 数据库数据保存位置</span><br></code></pre></td></tr></table></figure>
+
+<p>使用命令 <code>docker compose up -d</code> 启动服务。</p>
+<h2 id="Nginx-配置"><a href="#Nginx-配置" class="headerlink" title="Nginx 配置"></a>Nginx 配置</h2><p>我建议使用 DigitalOcean 的 Nginx 配置生产工具,示例配置如下:</p>
+<p><a href="https://www.digitalocean.com/community/tools/nginx?domains.0.server.domain=auth.example.com&domains.0.php.php=false&domains.0.reverseProxy.reverseProxy=true&domains.0.reverseProxy.proxyPass=http://127.0.0.1:18080&domains.0.routing.root=false&global.app.lang=zhCN">示例配置</a></p>
+<p>也可参考下述配置,此为 DigitalOcean 生成配置的简化版:</p>
+<figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-section">server</span> {<br> <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">server_name</span> auth.example.com;<br><br> <span class="hljs-comment"># SSL</span><br> <span class="hljs-attribute">ssl_certificate</span> /etc/letsencrypt/live/auth.example.com/fullchain.pem;<br> <span class="hljs-attribute">ssl_certificate_key</span> /etc/letsencrypt/live/auth.example.com/privkey.pem;<br> <span class="hljs-attribute">ssl_trusted_certificate</span> /etc/letsencrypt/live/auth.example.com/chain.pem;<br><br> <span class="hljs-comment"># logging</span><br> <span class="hljs-attribute">access_log</span> /var/log/nginx/access.log combined buffer=<span class="hljs-number">512k</span> flush=<span class="hljs-number">1m</span>;<br> <span class="hljs-attribute">error_log</span> /var/log/nginx/<span class="hljs-literal">error</span>.log <span class="hljs-literal">warn</span>;<br><br> <span class="hljs-comment"># reverse proxy</span><br> <span class="hljs-section">location</span> / {<br> <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Auth-Request-Redirect <span class="hljs-variable">$request_uri</span>;<br> <span class="hljs-attribute">proxy_buffer_size</span> <span class="hljs-number">128k</span>;<br> <span class="hljs-attribute">proxy_buffers</span> <span class="hljs-number">4</span> <span class="hljs-number">256k</span>;<br> <span class="hljs-attribute">proxy_busy_buffers_size</span> <span class="hljs-number">256k</span>;<br> <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:18080;<br> }<br><br> <span class="hljs-section">location</span> /auth/realms {<br> <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Auth-Request-Redirect <span class="hljs-variable">$request_uri</span>;<br> <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:18080;<br> }<br><br> <span class="hljs-section">location</span> /auth/resources {<br> <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Auth-Request-Redirect <span class="hljs-variable">$request_uri</span>;<br> <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:18080;<br> }<br><br> <span class="hljs-section">location</span> /auth/js {<br> <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Auth-Request-Redirect <span class="hljs-variable">$request_uri</span>;<br> <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:18080;<br> }<br>}<br><br><span class="hljs-comment"># subdomains redirect</span><br><span class="hljs-section">server</span> {<br> <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">server_name</span> <span class="hljs-regexp">*.auth.example.com</span>;<br><br> <span class="hljs-comment"># SSL</span><br> <span class="hljs-attribute">ssl_certificate</span> /etc/letsencrypt/live/auth.example.com/fullchain.pem;<br> <span class="hljs-attribute">ssl_certificate_key</span> /etc/letsencrypt/live/auth.example.com/privkey.pem;<br> <span class="hljs-attribute">ssl_trusted_certificate</span> /etc/letsencrypt/live/auth.example.com/chain.pem;<br> <span class="hljs-attribute">return</span> <span class="hljs-number">301</span> https://auth.example.com<span class="hljs-variable">$request_uri</span>;<br>}<br></code></pre></td></tr></table></figure>
+
+<h2 id="配置-Keycloak"><a href="#配置-Keycloak" class="headerlink" title="配置 Keycloak"></a>配置 Keycloak</h2><h3 id="创建-Realm"><a href="#创建-Realm" class="headerlink" title="创建 Realm"></a>创建 Realm</h3><p>打开 <a href="http://127.0.0.1:18080/">Keycloak 地址</a>,界面如下。</p>
+<p><img src="/images/2023-01-22/01.jpg" alt="Keycloak 界面"></p>
+<p>选择 <code>Administration Console</code>,进入管理界面。</p>
+<p><img src="/images/2023-01-22/02.jpg" alt="管理界面"></p>
+<p>选择箭头指向的下拉菜单,选择 <code>Add realm</code>,创建一个新的 Realm。</p>
+<p><img src="/images/2023-01-22/03.jpg" alt="创建 Realm"></p>
+<p>填写 Realm 名称,点击 <code>Create</code>。</p>
+<h3 id="创建-Client"><a href="#创建-Client" class="headerlink" title="创建 Client"></a>创建 Client</h3><p><img src="/images/2023-01-22/04.jpg" alt="管理界面"></p>
+<p>选择 <code>Clients</code>。</p>
+<p><img src="/images/2023-01-22/05.jpg" alt="Client 管理界面"></p>
+<p>点击 <code>Create client</code>。</p>
+<p><img src="/images/2023-01-22/06.jpg" alt="创建 Client"></p>
+<p>填写 Client 相关信息,点击 <code>Next</code>。</p>
+<p><img src="/images/2023-01-22/07.jpg" alt="配置 Client"></p>
+<p>按需求选择 Client 的配置,点击 <code>Save</code>。</p>
+<p><img src="/images/2023-01-22/08.jpg" alt="Client 创建完成"></p>
+<p>至此,Keycloak 配置完成,且创建了第一个测试用 Client。</p>
+<h3 id="测试-Client"><a href="#测试-Client" class="headerlink" title="测试 Client"></a>测试 Client</h3><p>可根据 <a href="https://www.keycloak.org/getting-started/getting-started-docker#_secure_your_first_app">官方教程</a> 测试该 Client。</p>
+<h2 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h2><p>上述便是使用 Docker Compose 部署 Keycloak 20 的方法,我们顺利创建了第一个测试用 Client,接下来可以根据自己的需求进行配置。</p>
+
+
+ 2023-01-22T12:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/9b78ad2a.html
+ 寻梦穿越机 - 入门浅谈
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>暑假开始之前,我就朦朦胧胧有购入一台穿越机的想法。起因为何?我也不是很清楚。是看到了<a href="https://www.youtube.com/watch?v=b0JZxm7ajcs">酷炫的穿越机航拍视频</a>?还是童年时期的航模魂蠢蠢欲动,将要苏醒?</p>
+<p>兴趣,倘若不立刻抓住,很快就会被抛至脑后。为了不让自己后悔,那就立刻开始筹备吧!攥紧并不是特别充盈的钱包,我踏上了寻梦之路。</p>
+<h2 id="什么是穿越机"><a href="#什么是穿越机" class="headerlink" title="什么是穿越机"></a>什么是穿越机</h2><p>FPV (First-Person View),是指通过第一人称视角远程控制无人飞行设备 (UAV) 的控制方式,也是一项运动,也指代了一类设备。</p>
+<p>FPV 设备包含固定翼 (Fixed Wing) 与多轴 (Multi-rotor) 两种。穿越机多指多轴 FPV (常见为四轴)。</p>
+<p><img src="/images/2022-09-05/%E5%9B%BA%E5%AE%9A%E7%BF%BC%E4%B8%8E%E5%A4%9A%E8%BD%B4.jpg" alt="四轴 FPV (上) 与固定翼 FPV (下)"></p>
+<h2 id="一盆冷水"><a href="#一盆冷水" class="headerlink" title="一盆冷水"></a>一盆冷水</h2><p>在详细介绍穿越机构成之前,请允许我泼一盆冷水。</p>
+<p>穿越机与一些航拍无人机,例如大疆 (DJI) 的四轴航拍机与航拍 FPV 相比,颇有原始人见到现代人的感觉。</p>
+<p>大疆无人机上搭载了各种各样的传感器,各系列大都配备了二向 (前后)、四向 (前后左右) 或五向 (增加一个上方) 的视觉避障功能、红外定高悬停功能,以及低电量 GPS 自动返航功能。</p>
+<p>上述这些看似是无人机应当“标配”的功能,追求轻量化的穿越机时常一个也没有。一台最小安装的穿越机全机的传感器仅有集成于飞控的陀螺仪和地磁传感器 (电调的电流传感器一般不算在内),飞控仅知道当前机身的朝向、倾斜角度与加速度,无法感知周围的环境,没有实现避障、悬停与自动返航的能力。飞机的运动状态完全取决于飞手的操控,电池的剩余电量甚至需要飞手根据电芯电压来推断。</p>
+<p><img src="/images/2022-09-05/%E7%A9%BF%E8%B6%8A%E6%9C%BA%E4%BC%A0%E6%84%9F%E5%99%A8.jpg" alt="穿越机少得可怜的传感器"></p>
+<p>再者,为了能获得更大的速度与更高的机动性,穿越机飞行过程一般处于手动模式,对飞机四向的倾斜没有限制,飞行过程中出现危险操作的概率大,事故概率也大。而大疆无人机对飞行速度、角度有严格的限制,能有效地降低炸机的概率。</p>
+<p>因此,飞行穿越机的难度将是飞行大疆等航拍无人机的十倍、甚至百倍。请务必不要抱着随便玩一玩、随心飞的态度入坑穿越机,发生事故后轻则炸机提 (遥) 控回家,重则伤到自己或他人。对于喜欢日常随心飞行的玩家,大疆或类似牌子的无人机 (包括航拍机和穿越机) 更为合适。</p>
+<hr>
+<p>下文将详细介绍入门穿越机所需要的技术知识与穿越机的重要模块。在明白风险之后仍打算入坑穿越机,或对穿越机有兴趣的你,欢迎继续往下阅读。</p>
+<h2 id="技术储备"><a href="#技术储备" class="headerlink" title="技术储备"></a>技术储备</h2><p>上文提到了我童年时期的航模魂。我在小学就跟着老师学习无线电和航模知识,练习了焊接技巧和单旋翼的航模直升机的飞行技巧。曾参加某个航模比赛并拿到了铜牌的好成绩 (我好自豪)。</p>
+<p>因此,我对我的焊接技术和遥控操控能力比较有信心。而这两个能力恰好是入门穿越机不可或缺的。下面我列举一些入门穿越机需要掌握的技能。</p>
+<h3 id="锡焊"><a href="#锡焊" class="headerlink" title="锡焊"></a>锡焊</h3><p> 除了购买真·到手飞套餐 (机子已组装完成,接收机已与遥控器完成配对),其余情况大概率需要焊接一些导线。</p>
+<p> 大部分组装机套餐都不包含接收机,因为接收机与遥控器一一对应,一般是和遥控一起购买的。就需要用户自行焊接至飞控上。</p>
+<p> 锡焊所用到的工具有电烙铁和焊锡丝,最好能佐以松香,增加焊接成功率。电烙铁推荐购买 T12 或更好的,因为飞控散热设计,很多焊盘为通孔的形式,热量会非常快地传到到全板,导致焊锡较难达到融化的温度。若使用功率太低的电烙铁,可能无法融化焊锡。</p>
+<p><img src="/images/2022-09-05/T12%E7%84%8A%E5%8F%B0.jpg" alt="T12 焊台"></p>
+<p> 由于飞控比较小巧,焊盘也十分迷你,对焊接技术要求较高。</p>
+<h3 id="使用搜索引擎"><a href="#使用搜索引擎" class="headerlink" title="使用搜索引擎"></a>使用搜索引擎</h3><p> 不懂不可怕,不懂得学习才可怕。</p>
+<p> 穿越机有关的零碎知识犹如满天星,散布于互联网的每一个角落,需要一定的搜索技巧才能挖掘到有用的知识。</p>
+<p> 国内有关穿越机的网站数量比较少,建议使用英文搜索。</p>
+<h3 id="编译"><a href="#编译" class="headerlink" title="编译"></a>编译</h3><p> 若想要更新 ELRS 接收机和高频头的固件,需要从源码编译 ELRS 固件。虽然 ELRS 团队提供了一套图形化的编译工具,但难免会遇到一些疑难杂症 (博客中就有 ELRS 固件编译的踩坑记录)。需要对编译有一定的知识,能自行解决简单的问题。或者选择使用出厂固件,不自行升级。</p>
+<h3 id="操控能力-协调性"><a href="#操控能力-协调性" class="headerlink" title="操控能力 (协调性)"></a>操控能力 (协调性)</h3><p> 像音游 (音乐游戏) 一样,微操遥控器并不是所有人都能很好地做到。穿越机对遥控指令的反应极为灵敏,需要你能精细地操控遥控器。</p>
+<p> 但这项技能可以通过电脑模拟飞行来习得。建议在正式开始飞行前,先在电脑上使用模拟器熟悉飞机的控制。</p>
+<h3 id="一颗勇敢的心"><a href="#一颗勇敢的心" class="headerlink" title="一颗勇敢的心"></a>一颗勇敢的心</h3><p> 在飞行穿越机的过程中,不可能不出现炸机 (飞机以异常姿态落地) 的情况,难免会留下一点阴影。</p>
+<p> 我在小学时飞过固定翼飞机,曾不慎撞到电线,导致飞机起落架损坏脱落,最后只能由老师操控迫降。此事给我留下了不小的心理阴影,有好几个月我都不敢再飞行固定翼。不过最后还是克服了恐惧,再次拾起了遥控。</p>
+<p> 炸机并不可怕,每一次炸机都记录着你的成长。</p>
+<h2 id="深入了解"><a href="#深入了解" class="headerlink" title="深入了解"></a>深入了解</h2><p>类似 Linux (比较奇怪的类比),穿越机也分为最小安装和额外拓展两部分。</p>
+<p><img src="/images/2022-09-05/%E7%A9%BF%E8%B6%8A%E6%9C%BA%E6%9E%B6%E6%9E%84.jpg" alt="穿越机架构"></p>
+<p>最小安装:</p>
+<ul>
+<li>机架 - 硬连接其他组件,保护脆弱的电路板;</li>
+<li>电机 (和桨叶) - 提供飞行动力;</li>
+<li>飞控 - 控制飞行状态,是穿越机的大脑;</li>
+<li>电调 (电子调速器) - 驱动电机、分隔电路 (防止电机减速时产生的反向电流烧坏其他电子设备);</li>
+<li>接收机 - 接受遥控器控制信号 (有 2.4GHz、915MHz、 868MHz 等频段,互不兼容);</li>
+<li>图传 - 发送图像信号 (多为 5.8GHz 频段);</li>
+<li>摄像头 - 提供第一人称视角;</li>
+</ul>
+<p>额外拓展:</p>
+<ul>
+<li>GPS 模块;</li>
+<li>BB 响 (蜂鸣器 Beeper,用于寻找飞机);</li>
+<li>LED 灯珠、灯带 (好看 XD);</li>
+<li>红外避障模块;</li>
+<li>运动摄像机 (拍摄更清晰、帧率更高的视频);</li>
+</ul>
+<p>挑几个比较重要的模块介绍一下:</p>
+<h3 id="机架"><a href="#机架" class="headerlink" title="机架"></a>机架</h3><p>穿越机机架一般由碳纤维板制成,质地轻盈且强度极高,可以物理连接不同的模块,并保护脆弱的电子设备 (飞控、图传等裸板)。</p>
+<p>挑选时,机架有几个要主要关注的核心参数:</p>
+<ul>
+<li><p>外形:</p>
+<p>机架分为普通式与涵道式 (Duct Propeller) 两种。</p>
+<p>普通式机架的螺旋桨桨叶裸露在外,而涵道式机架的螺旋桨外侧有涵道 (类圆筒) 包裹。</p>
+<p>有一些普通式机架可以通过额外安装涵道,变身成为涵道机架。</p>
+</li>
+</ul>
+<p><img src="/images/2022-09-05/%E6%9C%BA%E6%9E%B6%E7%A7%8D%E7%B1%BB.jpg" alt="普通机架 (左) 与涵道机架 (右)"></p>
+<p> 普通式机架采用开放式的设计,尽可能少地使用材料以减轻重量。由于螺旋桨暴露在外,危险性较大。普通机架可被用于任何竞速与花飞机型。</p>
+<p> 涵道式机架通过涵道结构可以提供更好的升力、稳定性和安全性,但额外增添了重量和封阻,一般被用于小型机与花飞机型。</p>
+<p><img src="/images/2022-09-05/%E6%9C%BA%E6%9E%B6%E5%A4%96%E5%BD%A2.jpg" alt="机架外形"></p>
+<p> 此外,机架还可以再细分为 X 型、H 型等,在此就不多展开,有兴趣的读者可以自行查阅。</p>
+<ul>
+<li><p>大小:</p>
+<p>穿越机大小各异,从最小的一寸机,到体型较大的五寸、六寸机。</p>
+<p>二寸以下的机子防风能力较差,但体型轻盈且常使用涵道机架,适合在室内飞行,且不易伤人。</p>
+<p>三寸以上的机子动力充沛,飞行速度快,但体型大、螺旋桨裸露,无法在室内飞行。适合在场地广阔的室外进行竞速或花飞 (花样飞行)。</p>
+</li>
+</ul>
+<h3 id="飞控与电调"><a href="#飞控与电调" class="headerlink" title="飞控与电调"></a>飞控与电调</h3><p>飞控与电调 (有时还有图传) 几块板子大小相当,时常纵向堆叠安装,称为飞塔。在多块 PCB 间有硅胶柱减震。</p>
+<p><img src="/images/2022-09-05/%E9%A3%9E%E5%A1%94.jpg" alt="双层飞塔"></p>
+<p>飞控需要注意的参数不多:</p>
+<ul>
+<li><p>飞控 SoC:</p>
+<p>常见飞控 SoC 的型号有 F405 和 F722,他们的全称应为 STM32F405 和 STM32F722. 差别主要在主频和内存大小上。</p>
+</li>
+</ul>
+<p><img src="/images/2022-09-05/%E9%A3%9E%E6%8E%A7SoC.jpg" alt="飞控 SoC"></p>
+<p> 由于穿越机飞行对计算性能不是很敏感 (类似 3D 打印机),一般选择搭载 F405 SoC 的飞控性能就足够使用了。</p>
+<ul>
+<li><p>串口数量:</p>
+<p>新手会用到的串口数量不会超过两个 (外接部分型号的图传和接收机),绝大部分飞控都能满足这个要求。若有需要安装 GPS 和其他传感器,则要注意查看飞控支持的串口数量了。</p>
+</li>
+</ul>
+<p>电调最重要的参数就是最大放电电流了。</p>
+<p>电调的电流选择需要根据机身大小、电机和桨叶大小、形状进行估计,通常电机厂家会给出推荐值,按照推荐选购即可。</p>
+<h3 id="接收机与图传"><a href="#接收机与图传" class="headerlink" title="接收机与图传"></a>接收机与图传</h3><p><img src="/images/2022-09-05/%E6%8E%A5%E6%94%B6%E6%9C%BA%E4%B8%8E%E5%9B%BE%E4%BC%A0.jpg" alt="接收机 (左) 与图传 (右)"></p>
+<p>接收机与图传共享一个重要的特性,协议:</p>
+<p>常见的接收机有 ELRS (ExpressLRS) 和黑羊 TBS 两类。应该注意的是,<strong>不同种类的接收机使用的通信协议和频段不同,能与其配对的遥控器和高频头也不同</strong>,因此在挑选接收机的时候一定要看清楚协议和频段。</p>
+<p>接收机和飞控的串口通讯协议也各异,有 UART、SBUS 等数种,再购买接收机前需确认飞控有相对应的串口。</p>
+<p>图传分数字图传和模拟图传两种。</p>
+<p>数字图传以大疆的最为出名,需要使用配套的飞行眼镜;模拟图传大多使用 5.8G 频段通讯,和大部分接收模拟信号的飞行眼镜通用。</p>
+<p>图传连接至飞控的串口通讯协议也很多,购买的时候请多加留意。</p>
+<p>除此之外,接收机有遥测功率、内/外置天线、天线接口等参数,在挑选时都需要多加留意。</p>
+<p>图传的另一主要参数则是发射功率。发射功率越大,能稳定接受图传信号的距离一般就越大。小型穿越机一般选择发射功率在 200mW 到 500mW 的图传即可 (部分图传发射功率可调);若有远航要求,也可选择发射功率大于 1W 的图传 (价格较高、发热也较大,一般带有主动散热)。</p>
+<h3 id="机子之外的配件"><a href="#机子之外的配件" class="headerlink" title="机子之外的配件"></a>机子之外的配件</h3><p>除了穿越机本体之外,想要拥有完整的飞性体验,还需要遥控器、高频头、电池、平衡充电器、飞行眼镜等配件。</p>
+<p>上述的每一种配件都可以写作一篇介绍文章。由于篇幅有限,本文就不再介绍上述的外围配件,请善用搜索引擎,学习相关知识。</p>
+<h3 id="配套软件"><a href="#配套软件" class="headerlink" title="配套软件"></a>配套软件</h3><p>除了硬件,穿越机配套的控制软件也尤为重要。目前主流的控制软件是开源的 Betaflight。</p>
+<p><img src="/images/2022-09-05/Betaflight.jpg" alt="Betaflight 地面站界面"></p>
+<p>Betaflight 分为嵌入端 (安装在飞控中嵌入式系统) 和地面站 (安装在电脑里的软件)。将飞控通过线缆连接至电脑,并打开 Betaflight 地面站软件,即可对飞控参数进行调整。</p>
+<p>Betaflight 调参也是一门大课,新手不建议自定义太多的参数。待熟悉飞机之后,才建议调整 PID 等高级控制参数。</p>
+<h2 id="新手的第一台飞机"><a href="#新手的第一台飞机" class="headerlink" title="新手的第一台飞机"></a>新手的第一台飞机</h2><p>说了这么多,要上某宝挑选、下单穿越机的种种配件了吗?</p>
+<p>我的建议是否定的。</p>
+<p>我咨询了一些老玩家,他们建议新手购买他人已完成调参的二手机器,或者购买商家大部分已组装完成套机,以绕过纷繁复杂且状况百出的 DIY 过程,降低还未入坑就弃坑的风险。</p>
+<p>此外,自行购买散件的总价常常会高于购买整机的价格。对于钱包不是特别充盈的我,购买整机也是一个省钱的选择。</p>
+<p>待熟悉了穿越机的飞行与调试之后,再学习他人经验,设计并组装一台自己心目中的机器也不迟,这才是 DIY 的浪漫。</p>
+<h2 id="我的成果"><a href="#我的成果" class="headerlink" title="我的成果"></a>我的成果</h2><p><img src="/images/2022-09-05/%E6%88%91%E7%9A%84%E6%88%90%E6%9E%9C.jpg" alt="我的成果"></p>
+<p>我选择购买一位老玩家完成大部分组装工作的“半”整机——包含了机架、电机、飞控和电调。图传、摄像头和接收机则是我自行购买和焊接安装的。</p>
+<p>飞机到手、外围装备齐全,只待一飞冲天。</p>
+
+
+ 2022-09-04T04:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/e74a90f2.html
+ 使用再生龙 Clonezilla 备份操作系统
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>近日购入了一只闪迪的 CZ880 Extreme PRO 固态U盘来装 Manjaro。</p>
+<p>虽然U盘本体是终身质保的,但数据无价,配置一遍系统就要花大把的时间。遂有了备份U盘内整个系统的念头。</p>
+<p>下面跟着我使用再生龙 Clonezilla 把U盘里的系统备份一下吧!</p>
+<h3 id="事先准备"><a href="#事先准备" class="headerlink" title="事先准备"></a>事先准备</h3><p>再生龙是运行在独立操作系统 (Debian/Ubuntu) 上的一套软件,因此需要制作启动盘,或使用 Ventoy 等多系统启动方案。(实测 YUMI 无法启动再生龙,故建议使用 Ventoy)</p>
+<h4 id="制作启动盘"><a href="#制作启动盘" class="headerlink" title="制作启动盘"></a>制作启动盘</h4><p>前往 <a href="https://rufus.ie/zh/">Rufus 官网</a> 下载 Rufus 启动盘制作工具。</p>
+<p>前往 <a href="https://clonezilla.org/downloads/download.php?branch=stable">Clonezilla 官网</a> 下载再生龙映像。</p>
+<p>使用 Rufus 将再生龙映像烧写进另一个U盘即可。</p>
+<p><strong>U盘内数据将丢失,请做好备份!</strong></p>
+<h4 id="使用多系统启动"><a href="#使用多系统启动" class="headerlink" title="使用多系统启动"></a>使用多系统启动</h4><p>前往 <a href="https://www.ventoy.net/">Ventoy 官网</a> 下载安装包(兼容 Windows 与 Linux),将你的另一个U盘制作为 Ventoy 多启动盘。</p>
+<p>前往 <a href="https://clonezilla.org/downloads/download.php?branch=stable">Clonezilla 官网</a> 下载再生龙映像。</p>
+<p>将再生龙映像拷贝至 Ventoy 多启动盘中。</p>
+<p>选择你想存放备份数据的目的地,创建一个存放备份映像的文件夹(注意目录名称中不能带有空格)。</p>
+<p>剧透一下,40G 的系统盘备份之后大约占了 16-17G. 请留出足够的空间(建议和待备份的数据等大小)。</p>
+<h3 id="开始备份"><a href="#开始备份" class="headerlink" title="开始备份"></a>开始备份</h3><p>瞎眼警告:由于没有合适的截屏方式,我很不愿意地采取了 拍 屏 的方式,敬请谅解。</p>
+<h4 id="启动再生龙系统"><a href="#启动再生龙系统" class="headerlink" title="启动再生龙系统"></a>启动再生龙系统</h4><p>确保你的电脑关闭了安全启动,若还打开着,需要在 BIOS 中将其关闭。</p>
+<p>插入刚刚制作好的启动盘/Ventoy 多启动盘,在电脑启动时猛敲键盘的…某个键,这因电脑型号而异,打开启动菜单。</p>
+<p>选择插入的启动盘/多启动盘。</p>
+<p>启动盘用户若没有太大的兼容性问题,就能看到再生龙的启动菜单。</p>
+<p>多启动盘用户还要再多一步,在 Ventoy 菜单内选中再生龙的映像,如下图所示,即可打开再生龙的启动菜单。</p>
+<p><img src="/images/2022-08-12/1.jpeg" alt="Ventoy 多启动菜单"></p>
+<p>P.S. 我的笔记本兴许和 Ventoy 的 UEFI 模式相性不大好,在 BIOS 中开启了 Lagacy 兼容模式后,使用 Legacy 模式才能开启 Ventoy。</p>
+<h4 id="选择再生龙启动方式"><a href="#选择再生龙启动方式" class="headerlink" title="选择再生龙启动方式"></a>选择再生龙启动方式</h4><p><img src="/images/2022-08-12/2.jpeg" alt="再生龙启动菜单"></p>
+<p>经典的 GRUB 启动菜单,一般来说选择默认的第一项启动方式即可。</p>
+<h5 id="VGA-启动花屏"><a href="#VGA-启动花屏" class="headerlink" title="VGA 启动花屏"></a>VGA 启动花屏</h5><p>我的电脑遇到了在 VGA 800x600 模式下花屏的问题。</p>
+<p>最终进入 <code>Other mods of Clonezilla live</code> 菜单,</p>
+<p><img src="/images/2022-08-12/3.jpeg" alt="其他启动模式"></p>
+<p>选择了上图中的 KVM & To RAM 模式,可以正常启动了。</p>
+<h5 id="USB-口不够用的用户"><a href="#USB-口不够用的用户" class="headerlink" title="USB 口不够用的用户"></a>USB 口不够用的用户</h5><p>我这台笔记本只有两个 USB 口,其中一个要给备份源头 CZ880,另一个则要给移动硬盘,故选择了 <code>To RAM</code> 模式,将再生龙载入内存,就可以拔掉多启动U盘,空出 USB 口给移动硬盘了。</p>
+<h4 id="语言配置"><a href="#语言配置" class="headerlink" title="语言配置"></a>语言配置</h4><p><img src="/images/2022-08-12/4.jpeg" alt="语言配置"></p>
+<p>选择自己想用的语言即可。</p>
+<p><img src="/images/2022-08-12/5.jpeg" alt="键盘配置"></p>
+<p>保持默认配置即可。</p>
+<h4 id="备份配置"><a href="#备份配置" class="headerlink" title="备份配置"></a>备份配置</h4><p><img src="/images/2022-08-12/6.jpeg" alt="功能选择"></p>
+<p>我们选 <code>Start Clonezilla 使用再生龙</code>。</p>
+<p>命令行可以在熟悉了配置之后使用。</p>
+<p><img src="/images/2022-08-12/7.jpeg" alt="备份模式选择"></p>
+<p>此处我们选择第一项 <code>device-image 硬盘/分区[存到/来自]镜像文件</code>。</p>
+<p>若想进行两盘对拷,可以选择第二项。我还没有尝试过。</p>
+<h4 id="挂载存储目录"><a href="#挂载存储目录" class="headerlink" title="挂载存储目录"></a>挂载存储目录</h4><p><img src="/images/2022-08-12/8.jpeg" alt="存储目录选择"></p>
+<p>这次我打算使用移动硬盘备份系统,故选择第一项 <code>local dev 使用本机的分区</code>。</p>
+<p><img src="/images/2022-08-12/10.jpeg" alt="插入 USB 设备提示"></p>
+<p>随后,再生龙会提示插入想要挂载的 USB 设备,按照提示做即可。</p>
+<p><img src="/images/2022-08-12/11.jpeg" alt="检测到的存储设备"></p>
+<p>此时画面会动态显示系统识别到的存储设备。看到期望的目标设备时,按下 <code>Ctrl-C</code> 停止搜索。</p>
+<p><img src="/images/2022-08-12/12.jpeg" alt="分区选择"></p>
+<p>在扫描完电脑当前安装的所有硬盘的分区后,你需要选择备份镜像文件存放的那个分区。</p>
+<p>如图,我希望备份到大小为 1.8T 的移动硬盘上,故选择最后一项 <code>sdc2</code>。</p>
+<p><img src="/images/2022-08-12/13.jpeg" alt="是否检查并修复文件系统"></p>
+<p>随后,再生龙询问你是否需要检查并修复挂载的文件系统,我们选第一项否就好了。</p>
+<p><img src="/images/2022-08-12/14.jpeg" alt="备份位置选择"></p>
+<p>接着,就是选择备份镜像存放的位置。</p>
+<p>使用键盘的方向键选择目录,使用 <code>Tab</code> 跳转到下方的选项,选择 <code>Browse</code> 并敲击回车就可以进入到此目录。</p>
+<p>若希望在选中目录下存放备份镜像文件(是一个文件夹),就可以选择 <code>Done</code> 选项,回车确认。</p>
+<p><img src="/images/2022-08-12/15.jpeg" alt="是否检查镜像可还原性"></p>
+<p>系统询问是否检查生成的备份镜像的可还原性,这里我们选是,多花一点时间能确保备份的完整性。</p>
+<p><img src="/images/2022-08-12/16.jpeg" alt="是否对镜像加密"></p>
+<p>镜像加密,依个人喜好选择。</p>
+<p><img src="/images/2022-08-12/18.jpeg" alt="备份模式确认"></p>
+<p>待上述配置完成后,系统会向你再次确认备份的内容与目的地。</p>
+<p>确认无误后输入 <code>y</code> 并敲击回车继续。</p>
+<h4 id="简单模式-高级模式"><a href="#简单模式-高级模式" class="headerlink" title="简单模式/高级模式"></a>简单模式/高级模式</h4><p>此时应该有一个模式选择,问你想要使用简单模式还是专家模式。</p>
+<p>我建议选择 专家模式,简单模式能选择的参数较少。</p>
+<p><img src="/images/2022-08-12/20.jpeg" alt="模式选择"></p>
+<p>接下来的三个选项,全部保持默认配置即可。</p>
+<p><img src="/images/2022-08-12/21.jpeg" alt="高级设置1"></p>
+<p><img src="/images/2022-08-12/22.jpeg" alt="高级设置2"></p>
+<p><img src="/images/2022-08-12/23.jpeg" alt="高级设置3"></p>
+<h4 id="压缩方式选择"><a href="#压缩方式选择" class="headerlink" title="压缩方式选择"></a>压缩方式选择</h4><p><img src="/images/2022-08-12/24.jpeg" alt="压缩方式选择"></p>
+<p>此处选择第三项 <code>-z2p 使用并行 bzip2 压缩</code>。</p>
+<p>实测 bzip2 压缩速度比较快,产生的备份镜像的体积也不算大。</p>
+<p>下图为选择了第一项 <code>-z1p 使用并行的 gzip 压缩</code> 的速度:</p>
+<p><img src="/images/2022-08-12/19.jpeg" alt="并行 gzip 压缩速度"></p>
+<p>下图为选择了第三项 <code>-z2p 使用并行 bzip2 压缩</code> 的速度:</p>
+<p><img src="/images/2022-08-12/26.jpeg" alt="并行 bzip2 压缩速度"></p>
+<p>可以看出 bzip2 压缩速度比 gzip 快了8倍。</p>
+<p>其他压缩方式的速度,待我测试之后更新文章。</p>
+<p><img src="/images/2022-08-12/25.jpeg" alt="分卷大小配置"></p>
+<p>分卷大小配置保持默认即可。</p>
+<h4 id="备份镜像检查"><a href="#备份镜像检查" class="headerlink" title="备份镜像检查"></a>备份镜像检查</h4><p>待备份完成后,再生龙还会进行一次备份镜像的可还原性检查,如下图:</p>
+<p><img src="/images/2022-08-12/27.jpeg" alt="可还原性检查"></p>
+<p>若得到下图的提示,则备份镜像生成成功了。</p>
+<p><img src="/images/2022-08-12/28.jpeg" alt="可还原性检查完成"></p>
+<p>随后,选择按照意愿选择备份结束后的操作即可。</p>
+<p><img src="/images/2022-08-12/29.jpeg" alt="备份结束后操作选择"></p>
+<p>至此,再生龙 CLonezilla 的基础教学就结束了,你已经学会了如何使用再生龙的图形界面进行备份。</p>
+<p>下面是一些再生龙的高阶(大概很高级)使用方法。</p>
+<h3 id="高级操作"><a href="#高级操作" class="headerlink" title="高级操作"></a>高级操作</h3><h4 id="使用无线网络备份"><a href="#使用无线网络备份" class="headerlink" title="使用无线网络备份"></a>使用无线网络备份</h4><p>上文中,我的电脑仅有两个 USB 口,为备份的流程增添了不必要的麻烦。</p>
+<p>能否使用 Wi-Fi 将备份镜像推送至家中的 NAS 呢?</p>
+<p>再生龙内置了许多通过无线/有线网络备份的方法,如下图:</p>
+<p><img src="/images/2022-08-12/30.jpeg" alt="备份选项"></p>
+<p>我们尝试使用 Webdav 来远程备份吧!</p>
+<h5 id="利与弊"><a href="#利与弊" class="headerlink" title="利与弊"></a>利与弊</h5><p>使用 Wi-Fi 备份可以摆脱线缆,更加轻松而优雅地进行备份。</p>
+<p>然而,由于通过 Wi-Fi 或者一切网络传输数据的速度仍然无法比肩有线传输,备份所消耗的时间将是备份至本地磁盘的 3-4 倍。</p>
+<p>备份我U盘中的 40G 的 Manjaro 系统用时 30min 左右。</p>
+<p>倘若你有大把的时间,或家中的内网速度足够快,大可使用无线备份。品着咖啡,看着数据上云(笑)。</p>
+<h5 id="预先准备"><a href="#预先准备" class="headerlink" title="预先准备"></a>预先准备</h5><p>上文中我们选择了基于 Debian 的 Clonezilla Stable 版本,遗憾的是 Debian 系统中并未携带太多驱动程序,因此识别不到我的 Intel AX200 无线网卡。</p>
+<p>经过测试,基于 Ubuntu 的 <a href="https://clonezilla.org/downloads/download.php?branch=alternative">Clonezilla Alternative Stable</a> 版本可以识别到 AX200 网卡。</p>
+<p>点击上方链接即可下载 Clonezilla Alternative Stable 版本的映像。</p>
+<p>重新烧写启动U盘/拷贝映像至多启动U盘即可。</p>
+<h6 id="又遇到了启动问题"><a href="#又遇到了启动问题" class="headerlink" title="又遇到了启动问题"></a>又遇到了启动问题</h6><p>使用基于 Ubuntu 的再生龙,上文中使用的 <code>KVM</code> 模式变得无法打开了,且 <code>VGA 800x600</code> 模式是一样的花屏。</p>
+<p>在一番尝试之后,我发现藏在更多启动选项菜单里的 <code>VGA 1024x768</code> 模式可以正常显示。看来基于 Debian 的再生龙也可以使用这个模式。</p>
+<h5 id="开始备份-1"><a href="#开始备份-1" class="headerlink" title="开始备份"></a>开始备份</h5><p><img src="/images/2022-08-12/31.jpeg" alt="网络管理"></p>
+<p>选择了非本地的镜像存储位置后,系统将开启上图的网络管理菜单。</p>
+<p>选择第一项 <code>Edit a connection</code>。</p>
+<p><img src="/images/2022-08-12/32.jpeg" alt="连接管理"></p>
+<p>选择 <code>Add</code> 选项,在弹出菜单中 <code>Wi-Fi</code>。</p>
+<p><img src="/images/2022-08-12/33.jpeg" alt="添加 Wi-Fi"></p>
+<p><code>Profile name</code> 随意填写;</p>
+<p><code>Device</code> 一般填写 <code>wlan0</code>,系统的第一块无线网卡;</p>
+<p>接着,按照自己的情况填写图中划线的三个配置即可。</p>
+<p><img src="/images/2022-08-12/34.jpeg" alt="连接状态"></p>
+<p>保存 Wi-Fi 配置后,就能看到当前配置的连接状态。</p>
+<p>若当前配置名前带 <code>*</code>,且右侧选项为 <code>Deactivate</code>,则 Wi-Fi 已连接成功。</p>
+<p><img src="/images/2022-08-12/35.jpeg" alt="填写 Webdav 服务器地址"></p>
+<p>接着,系统要求填写 Webdav 地址。</p>
+<p><img src="/images/2022-08-12/36.jpeg" alt="确认 Webdav 配置"></p>
+<p>最后,系统会向你确认 Webdav 是否正确。</p>
+<p>若确认无误,即可敲击回车继续。</p>
+<p>接下来的步骤和上述初级教程硬盘挂载之后的流程是完全一样的,请参考上文继续配置。</p>
+
+
+ 2022-08-12T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/f82a3103.html
+ BETAFPV 高频头固件编译 AttributeError
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="错误原因"><a href="#错误原因" class="headerlink" title="错误原因"></a>错误原因</h2><p>Python 模块 <code>pypandoc</code> 版本过新,1.8.0 及更高版本已移除了 BETAFPV 高频头固件中仍在使用的 <code>convert</code> 函数。</p>
+<h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><p>安装旧版的 <code>pypandoc</code> 模块。</p>
+<p><code>pip install pypandoc==1.7.0</code></p>
+
+
+
+ 2022-08-06T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/38942a16.html
+ DIY 显示器音箱
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>新买的显示器(LG 27UL500,虽然因为屏幕问题已经退货了)没有内置音箱,虽然大部分时间都在用耳机,但别人有的东西我不能没有嘛,就买了些材料,DIY 一个外接音箱。</p>
+<p>写作此文章分享一下制作的过程~</p>
+<h3 id="物料清单"><a href="#物料清单" class="headerlink" title="物料清单"></a>物料清单</h3><p><img src="/images/2022-06-03/01.JPG" alt="物料清单"></p>
+<ul>
+<li>PAM8403 数字功放板 5RMB</li>
+</ul>
+<p>接受 5V 电压,可以驱动两个 3W 的喇叭。商品详情页面吹的很厉害,确实底噪很小,而且输出的音量非常高。相当 OK 的功放板。</p>
+<ul>
+<li>8Ω 3W 喇叭两只(带音腔) 2*4RMB + 运费</li>
+</ul>
+<p>音质很不错,声音很大也不会破音,因为是广告机用的喇叭么?</p>
+<ul>
+<li>3.5MM 公头 0.5RMB</li>
+</ul>
+<p>我选的型号是 PJ392,只要是 3.5MM 双声道的公头就行了。</p>
+<ul>
+<li>Type C 母座 0.4RMB</li>
+</ul>
+<p>这个随意选。</p>
+<ul>
+<li>屏蔽线缆 2RMB/m</li>
+</ul>
+<p>我买的是4芯的屏蔽线,可供 Type C 头使用(2 power 2 data),音频线只需要三芯(2 data 1 GND),屏蔽线是为了更小的干扰、更好的音质。</p>
+<h3 id="开始组装"><a href="#开始组装" class="headerlink" title="开始组装"></a>开始组装</h3><h4 id="3-5MM-线缆"><a href="#3-5MM-线缆" class="headerlink" title="3.5MM 线缆"></a>3.5MM 线缆</h4><p>剥除一段屏蔽线的外皮,做工还是很不错的,有金属丝和铝箔的屏蔽,塑料膜防水,还有一根抗拉扯的填充芯。</p>
+<p>我选择使用红绿蓝三根线,黄线悬空。线色对应如下:</p>
+<p>红 - 左声道;绿 - 右声道;蓝 - 接地。</p>
+<p><img src="/images/2022-06-03/02.JPG" alt="屏蔽线"></p>
+<p>可以预先套上一段热缩管。</p>
+<p><img src="/images/2022-06-03/03.JPG" alt="热缩管"></p>
+<p>取一枚 3.5mm 公头,旋下插头。</p>
+<p>最长的一段一般是接地。若将接地朝下,我这款公头左侧为左声道,右侧为右声道。具体的接线方式可以用万用表测量接头和接口得知。</p>
+<p>将线穿入孔中,上一坨焊锡即可。</p>
+<p><img src="/images/2022-06-03/04.JPG" alt="公头焊接"></p>
+<p>再使用万用表测量接头与线末端的连通性,注意不能与其他线短路了。</p>
+<p>确认无误后可以打上热熔胶固定。</p>
+<p><img src="/images/2022-06-03/05.JPG" alt="热熔胶固定"></p>
+<p>再打点热熔胶,旋回外壳,将热缩管套好,加热热缩管使其收缩。</p>
+<p>3.5mm 线缆就制作完成了。</p>
+<h4 id="驱动板焊接"><a href="#驱动板焊接" class="headerlink" title="驱动板焊接"></a>驱动板焊接</h4><p>驱动板上有三组线需要焊接:</p>
+<ul>
+<li>音频输入线(3.5mm 线缆)</li>
+<li>电源输入线(Type C 线)</li>
+<li>音频输出线(喇叭线)</li>
+</ul>
+<p>Type C 线我没有再用屏蔽线,用两根导线连接 Type C 母头和驱动板即可。</p>
+<p>焊接方法就不多说了,线穿过孔,上锡即可。</p>
+<p><img src="/images/2022-06-03/06.JPG" alt="焊接中的驱动板"></p>
+<p>全部线缆焊接完成如下:</p>
+<p><img src="/images/2022-06-03/07.JPG" alt="焊接完的驱动板"></p>
+<h4 id="热熔胶填充"><a href="#热熔胶填充" class="headerlink" title="热熔胶填充"></a>热熔胶填充</h4><p>完成接线后,确认无短路,即可连接电脑测试音箱。</p>
+<p>若没有问题,考虑到需要长期使用,就可以用热熔胶覆盖焊接处,防止焊点脱落。</p>
+<p>用热熔胶覆盖之后的驱动板:</p>
+<p><img src="/images/2022-06-03/08.JPG" alt="热熔胶覆盖的驱动板"></p>
+<p>嘛…手艺不是很行。</p>
+<hr>
+<p>就此,外接音箱组装完成啦!</p>
+
+
+
+ 2022-06-03T07:15:00.000Z
+
+
+ https://blog.udon.eu.org/archives/2e528779.html
+ 迁移 Hexo 渲染环境至 GitHub Actions
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>本博客使用的是 Hexo 静态博客框架,我将渲染环境搭建在家中 NAS 之上,部署了一个 Ubuntu Docker 并安装好了 Node.js 环境,正常使用了一两年。</p>
+<p>上周末,写完新文章的我心血来潮,打算在更新博客的时候顺便更新一下老旧的 Node 和 Hexo(Node 12, Hexo 5.2)。</p>
+<p>一番折腾之后,以 npm 和 hexo 环境全部被破坏(事后想想也许只是环境变量掉了)的下场结束。</p>
+<p>鉴于 NAS 性能低下,更新一次 node module 都需要 30 分钟,我放弃了在 NAS 上重新部署渲染环境的念头,转而使用 GitHub Actions 渲染,并同时部署于 GitHub Pages 与 CloudFlare Pages。</p>
+<h2 id="将渲染环境迁至-GitHub-Actions"><a href="#将渲染环境迁至-GitHub-Actions" class="headerlink" title="将渲染环境迁至 GitHub Actions"></a>将渲染环境迁至 GitHub Actions</h2><p>不久之前,CloudFlare Pages 悄悄下架了 Hexo 框架的部署功能,只能用 GitHub Actions 渲染,然后再部署至 CloudFlare Pages 了。</p>
+<h3 id="项目结构的修改"><a href="#项目结构的修改" class="headerlink" title="项目结构的修改"></a>项目结构的修改</h3><p>若想使用 GitHub Actions,需要将博客的源码上传至 GitHub。考虑到隐私和安全的问题,建议创建一个私有仓库管理源码。</p>
+<p>对于项目没有什么需要修改的,因为 Actions 渲染的流程和本地渲染的流程没有区别。</p>
+<p>唯一需要改动的,是引入的主题。由于两个 Git 仓库不能嵌套,我们需要以 Git submodule 的形式引入主题仓库。</p>
+<p>我使用的是 Fluid 主题。采用 <a href="https://hexo.fluid-dev.com/docs/guide/#%E8%A6%86%E7%9B%96%E9%85%8D%E7%BD%AE">覆盖配置</a> 的方法,即在根目录之下有一份配置会覆盖主题内的配置文件,便于在 Actions 中渲染。</p>
+<p>以下也将以 Fluid 为例,请根据你使用的主题修改命令或代码。</p>
+<p>首先,删除原来的主题(若使用的是主题内的配置,注意备份配置文件!)</p>
+<p>返回博客源码的根目录,执行:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git submodule add https://github.com/fluid-dev/hexo-theme-fluid.git themes/fluid<br></code></pre></td></tr></table></figure>
+
+<p>末尾的 <code>themes/fluid</code> 为此 submodule 在项目中的位置与名字,与先前本地渲染时的配置相同即可。</p>
+<p>删除子模块的过程较为繁琐,请参考网上的文章进行操作。</p>
+<p>在 Clone 此项目时,submodule 默认不会被下载,需要使用指令:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git submodule update --init --recursive<br></code></pre></td></tr></table></figure>
+
+<p>下载 submodule。在下面会提到的 Actions 配置文件中会出现这条指令。</p>
+<p>接着,就可以将博客源码上传至 GitHub。</p>
+<h3 id="GitHub-Actions-相关文件"><a href="#GitHub-Actions-相关文件" class="headerlink" title="GitHub Actions 相关文件"></a>GitHub Actions 相关文件</h3><p>在博客源码根目录创建 <code>.github/workflows/submit.yml</code> 和 <code>.github/script/blog-update.sh</code> 两个文件,填入下列代码。</p>
+<p>以下代码参考文章 <a href="https://blog.kukmoon.com/f8bb4ee.html#23-%E7%BC%96%E5%86%99-workflow">GitHub Actions 自动部署 Hexo 博客到 cPanel 虚拟主机 - 谷中望月</a>,有所修改。</p>
+<p><code>submit.yml</code>:</p>
+<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">CI</span><br><br><span class="hljs-comment"># 监听 main 分支的改动与 Release 的发布</span><br><span class="hljs-attr">on:</span><br> <span class="hljs-attr">push:</span><br> <span class="hljs-attr">branches:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">main</span><br> <span class="hljs-attr">release:</span><br> <span class="hljs-attr">types:</span> [<span class="hljs-string">published</span>]<br><br><span class="hljs-comment"># 自定义环境变量</span><br><span class="hljs-attr">env:</span><br> <span class="hljs-attr">GIT_USER:</span> <span class="hljs-string">Lao-Liu233</span> <span class="hljs-comment"># 改成你自己的 GitHub 用户名</span><br> <span class="hljs-attr">GIT_EMAIL:</span> <span class="hljs-string">blog@udon.eu.org</span> <span class="hljs-comment"># 改成你自己的 GitHub 注册邮箱</span><br><br><span class="hljs-attr">jobs:</span><br> <span class="hljs-attr">build:</span><br> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">on</span> <span class="hljs-string">node</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node_version</span> <span class="hljs-string">}}</span> <span class="hljs-string">and</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.os</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span><br> <span class="hljs-attr">strategy:</span><br> <span class="hljs-attr">matrix:</span><br> <span class="hljs-attr">os:</span> [<span class="hljs-string">ubuntu-latest</span>]<br> <span class="hljs-attr">node_version:</span> [<span class="hljs-number">16.15</span>] <span class="hljs-comment"># 改成你本地的 Node.js 版本,可以用 `node --version` 命令查询</span><br><br> <span class="hljs-attr">steps:</span><br> <span class="hljs-comment"># 获取博客源码</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span><br> <br> <span class="hljs-comment"># 用 Node.js 渲染</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Use</span> <span class="hljs-string">Node.js</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node_version</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v1</span><br> <span class="hljs-attr">with:</span><br> <span class="hljs-attr">node-version:</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node_version</span> <span class="hljs-string">}}</span><br><br> <span class="hljs-comment"># 安装 Hexo-cli </span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">hexo</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> npm install -g hexo-cli</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 安装依赖</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span> <br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> npm install</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 导入 submodule</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Clone</span> <span class="hljs-string">submodule</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> git submodule update --init --recursive</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 配置环境</span><br> <span class="hljs-comment"># ssh-kenscan github.com >> ~/.ssh/known_hosts # 从 GitHub 获取公钥并保存到 known_hosts 文件</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Configuration</span> <span class="hljs-string">environment</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> sudo timedatectl set-timezone "Asia/Shanghai"</span><br><span class="hljs-string"> mkdir -p ~/.ssh/</span><br><span class="hljs-string"> echo "$HEXO_DEPLOY_PRI" > ~/.ssh/id_rsa</span><br><span class="hljs-string"> chmod 600 ~/.ssh/id_rsa</span><br><span class="hljs-string"> ssh-keyscan github.com >> ~/.ssh/known_hosts</span><br><span class="hljs-string"> git config --global user.name $GIT_USER</span><br><span class="hljs-string"> git config --global user.email $GIT_EMAIL</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 生成并部署</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">hexo</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> hexo clean</span><br><span class="hljs-string"> hexo g -d</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 部署后更新博客源码,用于添加 abbrlink,如果不用 abbrlink,需要删除</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Update</span> <span class="hljs-string">Blog</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br> <span class="hljs-string">sh</span> <span class="hljs-string">"${GITHUB_WORKSPACE}/.github/script/blog-update.sh"</span><br></code></pre></td></tr></table></figure>
+
+<p><code>.github/script/blog-update.sh</code>:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-meta">#!/bin/sh</span><br><br><span class="hljs-keyword">if</span> [ -z <span class="hljs-string">"<span class="hljs-subst">$(git status --porcelain)</span>"</span> ]; <span class="hljs-keyword">then</span><br> <span class="hljs-built_in">echo</span> <span class="hljs-string">"nothing to update."</span><br><span class="hljs-keyword">else</span><br> git add <span class="hljs-built_in">source</span>/_posts/ <span class="hljs-comment">#仅对文章源码所在文件夹进行修改</span><br> git commit -m <span class="hljs-string">"triggle by commit <span class="hljs-variable">${GITHUB_SHA}</span>"</span> -a<br> git push origin main<br><span class="hljs-keyword">fi</span><br></code></pre></td></tr></table></figure>
+
+<p>Commit + Push,打开 Actions 界面,就能看到正在运行的 Action 啦。</p>
+<p>不出意外,Action 成功执行,1分钟内博客就能渲染成功、部署至 GitHub Pages。</p>
+<h2 id="同时部署至-CloudFlare-Pages"><a href="#同时部署至-CloudFlare-Pages" class="headerlink" title="同时部署至 CloudFlare Pages"></a>同时部署至 CloudFlare Pages</h2><p>步骤较为简单,我简述一下。</p>
+<p>打开 CloudFlare Pages, 连接至存放 <strong>渲染后</strong> 的静态文件的仓库,渲染的框架选择 <strong>None</strong>,执行的指令填写 <code>exit 0;</code> 就可以了。</p>
+<p>执行部署后,渲染后的静态文件就被部署至 CloudFlare Pages 啦。</p>
+
+
+
+
+ 2022-05-23T11:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/9d319c54.html
+ DELL 灵越 15 5547 拆解与更换硅脂
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>DELL 灵越 15 5547,Intel i5-6200U,Nvidia Geforce 630M,8G DDR3-1600 + 256G SATA SSD(后期改装),是我的第一台笔记本。这台笔记本的拆机过程比较繁琐,是我目前遇见的最难拆解的电脑,故作此文章,分享一下如何拆解一台笔记本,并为其更换硅脂。</p>
+<p><img src="/images/2022-05-22/01.JPG" alt="A面"></p>
+<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>本次拆机共耗时 1H 30Min,是拆解正常机器所花费时间的两到三倍。</p>
+<p>拆解步骤包括:卸下后盖、拆除电池、拆除硬盘、拆除风扇、拆除键盘、卸下基座、卸下散热器、更换硅脂,然后是逆序执行上述拆解步骤,组装电脑。</p>
+<h2 id="拆解准备"><a href="#拆解准备" class="headerlink" title="拆解准备"></a>拆解准备</h2><p>拆机少不了一套好工具。</p>
+<p>为了无伤拆机,需要准备一套好用的螺丝刀防止螺丝滑牙;还需要一套塑料拆机工具,用于撬开后盖。</p>
+<p>笔记本的硅脂,我选择了霍尼韦尔 7950 相变硅脂,不容易挥发,不需要经常更换。</p>
+<h2 id="开始拆机"><a href="#开始拆机" class="headerlink" title="开始拆机"></a>开始拆机</h2><h3 id="怎么拆呢"><a href="#怎么拆呢" class="headerlink" title="怎么拆呢"></a>怎么拆呢</h3><p>其实几个月前我就有给这台设备清灰、换硅脂的念头,但拆开机器之后我发现主板被塑料框架罩住了,无法拆下散热器。我研究了半天也没找到拆下框架的方法,便只清理了风扇的灰尘,没有更换硅脂。</p>
+<p>而这次,我有备而来:我查到了 <a href="https://downloads.dell.com/manuals/all-products/esuprt_laptop/esuprt_inspiron_laptop/inspiron-15-5547-laptop_owner%27s%20manual_zh-cn.pdf">DELL 官方的用户手册</a> ,其中详细记载了拆解这台机器的方法。接下来的拆解步骤就严格按照官方的教程啦。</p>
+<h3 id="第一步:卸下后盖"><a href="#第一步:卸下后盖" class="headerlink" title="第一步:卸下后盖"></a>第一步:卸下后盖</h3><p>翻到 D 面,拧下两颗固定螺丝,就能将后盖拆下,可以触及无线网卡、内存条、2.5 寸硬盘、电池和散热风扇,日常需要维护的部件都能轻松触及,好评。散热器和主板则在塑料框架之下。</p>
+<p><img src="/images/2022-05-22/03.JPG" alt="D面后盖之下"></p>
+<h3 id="第二步:拆除电池"><a href="#第二步:拆除电池" class="headerlink" title="第二步:拆除电池"></a>第二步:拆除电池</h3><p>释放主板电荷是电脑拆机中至关重要的一步!</p>
+<p>在主板带电的情况下拔插任何端子都是不明智的做法,很可能会将主板上的高电压线路误接入低电压线路(例如拔插屏线时没有正对接口),烧坏一片元件。</p>
+<p>这台机子的电池没有排线,直接接入主板。卸下围绕电池的四颗螺丝,手提塑料片,就可将电池卸下。</p>
+<p>翻到 C 面,长按电源键 10s,可重复两到三次,确保主板中的电荷完全释放。</p>
+<p><img src="/images/2022-05-22/04.JPG" alt="电池模块"></p>
+<p>第一次见这种电池模块,比起长条状的电池可以更好地利用机身空间。</p>
+<h3 id="第三到五步:拆除硬盘、散热风扇和键盘"><a href="#第三到五步:拆除硬盘、散热风扇和键盘" class="headerlink" title="第三到五步:拆除硬盘、散热风扇和键盘"></a>第三到五步:拆除硬盘、散热风扇和键盘</h3><p>卸下固定硬盘座的螺丝,抽出硬盘,再断开 SATA 与供电二合一的线缆,即可取下硬盘。</p>
+<p>拔下主板上散热风扇的端子(位于风扇左侧),卸下固定螺丝,即可取下散热风扇。</p>
+<p>翻到 C 面,用手或塑料工具扣出键盘模块,注意不要大力提起键盘!小心地提起一段距离,断开机身上的排线,键盘就取下来了。裸 C 面上还有两条排线,都需要断开。竖直方向排线需从孔洞中穿回 D 面。</p>
+<p><img src="/images/2022-05-22/02.JPG" alt="裸C面"></p>
+<h3 id="第六步:卸下基座"><a href="#第六步:卸下基座" class="headerlink" title="第六步:卸下基座"></a>第六步:卸下基座</h3><p>这是我第一次见笔记本中的基座,没有手册的指导很难拆下。</p>
+<p>首先,确保 C 面两根排线已断开。</p>
+<p>翻到 D 面,小心地断开屏幕排线(位于散热风扇左侧)。</p>
+<p>再翻到 C 面,按手册图示卸下所有螺丝。</p>
+<p>翻回 D 面,同样地卸下一堆螺丝,注意这两面的螺丝都是 M2.5x7 规格,长于主板用螺丝,混用可能会戳穿主板。</p>
+<p>DELL 在机身和框架上都有标注螺丝孔对应螺丝的规格,非常好评!</p>
+<p>确认卸下所有螺丝后,用塑料工具插入基座与机身的缝隙,划开卡口。</p>
+<p>待大部分卡口都解开时,小心提起基座,注意将基座上的线缆和排线取下,基座就彻底与机身分离了。</p>
+<p><img src="/images/2022-05-22/05.JPG" alt="基座背面"></p>
+<p>基座的背面,可以看到是 PC + ABS 材料,分量很足。</p>
+<p><img src="/images/2022-05-22/06.JPG" alt="拆下基座后的机身"></p>
+<p>拆下基座之后的机身,暴露出了主板和子板。散热器是梦幻单热管,不过压这两个破芯片足够了。</p>
+<h3 id="第七步:卸下散热器"><a href="#第七步:卸下散热器" class="headerlink" title="第七步:卸下散热器"></a>第七步:卸下散热器</h3><p>散热器共有六颗螺丝,四颗在 CPU 上,两颗在显卡上。</p>
+<p>为了使散热器均匀受力,不能一次性直接拧下一颗螺丝,而是平均为每颗螺丝卸力,可以每个螺丝一次转两圈,直到所有螺丝都被卸下。裸晶脆弱,有必要好好保护一下。</p>
+<p><img src="/images/2022-05-22/07.JPG" alt="CPU的旧硅脂"></p>
+<p>不难看出,CPU 上的硅脂全部凝固,且裸晶上的硅脂基本流失殆尽(散热器上也没剩多少)。</p>
+<p>去除大部分凝固的硅脂,再用酒精片擦拭、用布擦干。</p>
+<p>更换硅脂的步骤我就不在此赘述,商家一般会提供详细的视频介绍,按着教程做就可以了。</p>
+<p><img src="/images/2022-05-22/08.JPG" alt="干净的CPU"></p>
+<p>i5-6200U 的全貌,右侧长条状的裸晶应该是集成显卡吧。</p>
+<p><img src="/images/2022-05-22/09.JPG" alt="擦干净的GPU"></p>
+<p>Geforce 930M 显卡,非常小个。</p>
+<p><img src="/images/2022-05-22/10.JPG" alt="显存芯片"></p>
+<p>DDR3 显存,封装方式和 DDR4 不一样,更扁一些。</p>
+<h3 id="后续步骤"><a href="#后续步骤" class="headerlink" title="后续步骤"></a>后续步骤</h3><p>接下来就是逆序刚刚的拆解步骤,我就不再赘述。</p>
+<p>注意几点:</p>
+<ul>
+<li>安装散热器时也需保证受力均匀,且不能过分用力,可能会压碎裸晶。</li>
+<li>安装基座时注意将线缆与排线置于合适的位置,不要被基座压到了。</li>
+</ul>
+<p>拼装完成后,插电,开机,轻松点亮。</p>
+<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>拆解一台电脑并没有想象中的那么困难,拆解不同电脑的方式也都大同小异,上述的步骤一般都能适用。</p>
+<p>我已拆解了不下 10 台/次 笔记本,目前还未翻过车。</p>
+<p>下次清灰、更换硅脂,试着自己动手吧!</p>
+
+
+ 2022-05-22T02:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/28fa0729.html
+ Virmach Japan
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>老朋友 Virmach 最近搞了场日本的预售。上游是 XTom,线路走的 IIJ。配置从 1C 384M 到 2C 2.5G 都有,价格则是 11.11刀/年 起(预售打 8 折, 8.89刀/年起)。</p>
+<p>我买的这台是折后 9.7刀/年,1C 768M 20G NVME 2T 双向流量的配置。</p>
+<span id="more"></span>
+
+<hr>
+<p>直观感受:性能强劲,白天网络不错。晚高峰也能用,不爆炸。</p>
+<p>下面放测试数据:</p>
+<h3 id="综合测试"><a href="#综合测试" class="headerlink" title="综合测试"></a>综合测试</h3><p><img src="/images/2022-04-06/Bench.png"></p>
+<p>CPU 很幸运地抽中了 5900X,可以看到硬盘速度非常 OK。</p>
+<p>早上的网络情况很不错,没指望能跑满 G 口,毕竟这么多人分 10G 的口子。</p>
+<h3 id="性能测试"><a href="#性能测试" class="headerlink" title="性能测试"></a>性能测试</h3><p><img src="/images/2022-04-06/UNIXBench.jpg"></p>
+<p>不愧是 5900X,单核跑出了部分志强将近3倍的成绩!AMD, YES!</p>
+<p>开一台 2C 2G 的机子完全可以当作开发服务器使了。</p>
+<h3 id="国内网络测试"><a href="#国内网络测试" class="headerlink" title="国内网络测试"></a>国内网络测试</h3><h4 id="白天"><a href="#白天" class="headerlink" title="白天"></a>白天</h4><p><img src="/images/2022-04-06/SuperSpeed-Morning.png"></p>
+<p>三网表现均不错呢。</p>
+<h4 id="晚高峰"><a href="#晚高峰" class="headerlink" title="晚高峰"></a>晚高峰</h4><p><img src="/images/2022-04-06/SuperSpeed-Night.png"></p>
+<p>惊了,晚高峰表现很不错耶。</p>
+<h3 id="流媒体解锁"><a href="#流媒体解锁" class="headerlink" title="流媒体解锁"></a>流媒体解锁</h3><p><img src="/images/2022-04-06/Streaming.png"></p>
+<p>Virmach 只字没提流媒体,就别报多大希望。</p>
+<p>不过作为日本的 VPS 自家游戏都不能解锁……这 IP 优化的不大行。</p>
+<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>网络不错,性能强劲,如此便宜的价格可以说性价超高。</p>
+
+
+ 2022-04-06T14:15:00.000Z
+
+
+ https://blog.udon.eu.org/archives/dbf21067.html
+ 玩一玩 DN42
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>两个月前,我向 DN42 提交了申请,并于4个小时后通过了审核,获得了自己的 AS 和 IP。</p>
+<p>作此文分享一下把玩 DN42 的心得,也作为我的备忘录。</p>
+<h2 id="我的信息"><a href="#我的信息" class="headerlink" title="我的信息"></a>我的信息</h2><figure class="highlight maxima"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs maxima">AS4242423490<br>IPv4: <span class="hljs-number">172.23</span><span class="hljs-number">.13</span><span class="hljs-number">.64</span>/<span class="hljs-number">28</span><br>IPv6: fd44:<span class="hljs-number">6b93</span>:4eaa::/<span class="hljs-number">48</span><br></code></pre></td></tr></table></figure>
+
+<p>目前仅一个洛杉矶节点开放 Peer,后期还将添加韩国和日本的节点。</p>
+<h2 id="如何把玩"><a href="#如何把玩" class="headerlink" title="如何把玩"></a>如何把玩</h2><h3 id="注册"><a href="#注册" class="headerlink" title="注册"></a>注册</h3><p>有关注册的文章很多,推荐这两篇:</p>
+<p><a href="https://lantian.pub/article/modify-website/dn42-experimental-network-2020.lantian/">DN42 实验网络介绍及注册教程(2022-02 更新) | Lan Tian @ Blog</a></p>
+<p><a href="https://blog.baoshuo.ren/post/dn42-network/#">初探 DN42 网络 - 宝硕博客 (baoshuo.ren)</a></p>
+<p>需要使用 Git 和 PGP,在 DN42 的 Git 仓库提交你的个人信息即可完成注册。</p>
+<h3 id="搭建内网"><a href="#搭建内网" class="headerlink" title="搭建内网"></a>搭建内网</h3><p>在和其他 AS 建立对等连接之前,我们先要把内网整理好:</p>
+<p>各台服务器的地理位置和网络位置各不相同,需要使用 VPN 创建虚拟局域网。</p>
+<p>课堂上讲了两种内网路由协议:</p>
+<ul>
+<li><p>RIP 是“真”内网用的,不适用于这种物理位置距离较远(路由跳数多)的虚拟内网;</p>
+</li>
+<li><p>可以使用 OSPF,但我在配置的时候遇到了不少问题,因此也不建议你使用。</p>
+</li>
+</ul>
+<p>有一位老朋友可以轻松解决以上两个问题:<strong>Zerotier</strong>。</p>
+<p>Zerotier 的虚拟网络可以使用自己的 IP,只需在 <strong>Managed Routes</strong> 中设置你在 DN42 申请的 IPv4 和 IPv6,即可为每台加入虚拟网络的主机自动或手动配置 DN42 IP。</p>
+<p>在机器之间使用 DN42 IP 互 ping 测试连通性。</p>
+<h3 id="准备-BGP-相关软件"><a href="#准备-BGP-相关软件" class="headerlink" title="准备 BGP 相关软件"></a>准备 BGP 相关软件</h3><p>搭建好内网之后,就可以开始配置 BGP 发言人啦。</p>
+<p>选择一台或多台服务器,作为自治域向外宣告路由的发言人。</p>
+<p>在每台服务器上都需要配置 BGP 相关的软件,以及和其他 BGP 发言人建立连接(一般是 VPN 连接)的软件。</p>
+<p>目前在 DN42 网络用的比较多的 VPN 软件是 Wireguard,BGP 软件则可以从 bird 2、bird 1、quagga 等软件中选择。</p>
+<p>我使用的是 bird 2。</p>
+<h4 id="安装与配置-BIRD-2"><a href="#安装与配置-BIRD-2" class="headerlink" title="安装与配置 BIRD 2"></a>安装与配置 BIRD 2</h4><p>安装命令:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">apt update<br>apt install bird2 -y<br></code></pre></td></tr></table></figure>
+
+<p>bird 2 的配置文件位于 <code>/etc/bird</code>,名为 <code>bird.conf</code>。</p>
+<p>配置文件可以参考(<del>照抄</del>)DN42 官方给出的配置:<a href="https://dn42.dev/howto/Bird2#example-configuration">howto/Bird2 (dn42.dev)</a></p>
+<p>喂到嘴边的配置方法:</p>
+<ul>
+<li>将官方配置填入 <code>/etc/bird/bird.conf</code></li>
+<li>在 <code>/etc/bird</code> 目录下新建名为 <code>peers</code> 的文件夹</li>
+<li>下载 ROA 配置(命令来自<a href="https://blog.baoshuo.ren/">宝硕的博客</a>)</li>
+</ul>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">wget -4 -O /tmp/dn42_roa.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf && <span class="hljs-built_in">mv</span> -f /tmp/dn42_roa.conf /etc/bird/dn42_roa.conf<br>wget -4 -O /tmp/dn42_roa_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf && <span class="hljs-built_in">mv</span> -f /tmp/dn42_roa_v6.conf /etc/bird/dn42_roa_v6.conf<br></code></pre></td></tr></table></figure>
+
+<p> 并配置 crontab,每小时自动下载并重载新配置:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">0 */1 * * * wget -4 -O /tmp/dn42_roa.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf && <span class="hljs-built_in">mv</span> -f /tmp/dn42_roa.conf /etc/bird/dn42_roa.conf<br>0 */1 * * * wget -4 -O /tmp/dn42_roa_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf && <span class="hljs-built_in">mv</span> -f /tmp/dn42_roa_v6.conf /etc/bird/dn42_roa_v6.conf<br>0 */1 * * * birdc configure<br></code></pre></td></tr></table></figure>
+
+<h4 id="安装并配置-Wireguard"><a href="#安装并配置-Wireguard" class="headerlink" title="安装并配置 Wireguard"></a>安装并配置 Wireguard</h4><p>安装命令:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">apt update<br>apt install wireguard -y<br></code></pre></td></tr></table></figure>
+
+<p>这样就安装了 <code>Wireguard</code> 和名为 <code>wg-quick</code> 的管理工具。</p>
+<p>使用命令:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">wg genkey | <span class="hljs-built_in">tee</span> privatekey | wg pubkey > publickey<br></code></pre></td></tr></table></figure>
+
+<p>在当前目录下创建 Wireguard 建立连接所用的密钥对(公钥和私钥)。</p>
+<p>就此 Wireguard 安装完成。</p>
+<h4 id="配置系统内核"><a href="#配置系统内核" class="headerlink" title="配置系统内核"></a>配置系统内核</h4><p>打开内核的数据包转发功能:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv4.ip_forward=1"</span> >> /etc/sysctl.conf<br><span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv6.conf.default.forwarding=1"</span> >> /etc/sysctl.conf<br><span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv6.conf.all.forwarding=1"</span> >> /etc/sysctl.conf<br>sysctl -p<br></code></pre></td></tr></table></figure>
+
+<p>关闭内核 <code>rp_filter</code> 的严格模式:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv4.conf.default.rp_filter=0"</span> >> /etc/sysctl.conf<br><span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv4.conf.all.rp_filter=0"</span> >> /etc/sysctl.conf<br>sysctl -p<br></code></pre></td></tr></table></figure>
+
+<p>如果有 ufw 等防火墙自动配置工具,务必关闭。</p>
+<p>p.s. 我拿到任何机器后会立刻执行的指令是:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> ufw <span class="hljs-built_in">disable</span><br></code></pre></td></tr></table></figure>
+
+<h4 id="创建-Dummy-网卡"><a href="#创建-Dummy-网卡" class="headerlink" title="创建 Dummy 网卡"></a>创建 Dummy 网卡</h4><p>dummy 网卡具体的作用我不是很清楚…</p>
+<p>只知道如果要用链路本地地址进行通讯,要把 DN42 的 IP 地址绑定到 dummy 网卡上。</p>
+<p>dummy 网卡配置指令如下:([ ] 中为需要你填写的内容)</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash">ip <span class="hljs-built_in">link</span> del dummy<br>ip <span class="hljs-built_in">link</span> add dummy <span class="hljs-built_in">type</span> dummy<br>ip addr add [你的 DN42 IPv4 地址]/32 dev dummy<br>ip addr add [你的 DN42 IPv6 地址]/128 dev dummy<br>ip <span class="hljs-built_in">link</span> <span class="hljs-built_in">set</span> dummy up<br></code></pre></td></tr></table></figure>
+
+<h3 id="和小伙伴建立对等连接(peer)"><a href="#和小伙伴建立对等连接(peer)" class="headerlink" title="和小伙伴建立对等连接(peer)"></a>和小伙伴建立对等连接(peer)</h3><h4 id="需要和对方分享的"><a href="#需要和对方分享的" class="headerlink" title="需要和对方分享的"></a>需要和对方分享的</h4><ul>
+<li>你的 DN42 信息,包括 AS 号和发言人的 DN42 IPv4(IPv6)地址;</li>
+<li>若使用链路本地地址,还需提供这东西,一般为一个本地 IPv6 地址,常取 <code>fe80::[你的 AS 号后4位]</code>;</li>
+<li>发言人的外网 IPv4 地址(或域名)和 Wireguard 隧道的通讯端口;</li>
+<li>Wireguard 公钥。</li>
+</ul>
+<p>有一些信息会在以下的配置中获得。</p>
+<h4 id="Wireguard-相关的"><a href="#Wireguard-相关的" class="headerlink" title="Wireguard 相关的"></a>Wireguard 相关的</h4><p>在 <code>/etc/wireguard</code> 目录下创建 Wireguard 配置文件,每一个配置文件对应着一个 Wireguard 隧道。</p>
+<p>例如你要和 AS114514 <del>臭</del> 建立对等连接,可以在 <code>peers</code> 文件夹下新建一个名为 <code>wg_114514.conf</code> (文件名即为 wireguard 隧道名)的配置文件。</p>
+<p>配置的模板如下:([ ] 中为需要你填写的内容)</p>
+<figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs ini"><span class="hljs-section">[Interface]</span><br><span class="hljs-attr">Table</span> = <span class="hljs-literal">off</span><br><span class="hljs-attr">ListenPort</span> = [我们的监听端口,可以用对方 AS 号的后五位]<br><span class="hljs-attr">PrivateKey</span> = [刚刚生成的 Wireguard 私钥]<br><span class="hljs-attr">PostUp</span> = ip addr add [本机的 DN42 IPv4 地址]/<span class="hljs-number">32</span> peer [对方机器的 DN42 IPv4 地址]/<span class="hljs-number">32</span> dev %i<br><span class="hljs-attr">PostUp</span> = ip addr add [本机的链路本地地址(见 BIRD 相关配置)]/<span class="hljs-number">64</span> dev %i<br><br><span class="hljs-section">[Peer]</span><br><span class="hljs-attr">PublicKey</span> = [对方的 Wireguard 公钥]<br><span class="hljs-attr">AllowedIPs</span> = <span class="hljs-number">10.0</span>.<span class="hljs-number">0.0</span>/<span class="hljs-number">8</span>, <span class="hljs-number">172.20</span>.<span class="hljs-number">0.0</span>/<span class="hljs-number">14</span>, <span class="hljs-number">172.31</span>.<span class="hljs-number">0.0</span>/<span class="hljs-number">16</span>, fd00::/<span class="hljs-number">8</span>, fe80::/<span class="hljs-number">64</span><br><span class="hljs-attr">Endpoint</span> = [对方机器的公网 IP 地址或域名 : 端口号]<br></code></pre></td></tr></table></figure>
+
+<p>然后使用 <code>wg-quick up [wireguard 隧道名(刚刚的配置文件名)]</code> 启动 Wireguard 隧道。</p>
+<p>可以 ping 一下对方的 DN42 IP 看看 Wireguard 隧道是否连接成功。</p>
+<p>使用 <code>wg</code> 命令查看各隧道的连接情况。若有显示 <code>last handshake</code>,一般情况下隧道就已成功建立。</p>
+<h4 id="BIRD-相关的"><a href="#BIRD-相关的" class="headerlink" title="BIRD 相关的"></a>BIRD 相关的</h4><p>在先前导入的 bird 2 配置中定义了一个 <code>peers</code> 文件夹,就是用来存放 peer 相关的配置。</p>
+<p>例如你要和 AS114514 <del>又臭</del> 建立对等连接,可以在 <code>peers</code> 文件夹下新建一个名为 <code>114514.conf</code> (文件名可自定义)的配置文件。</p>
+<p>我采用的是<a href="https://zh.wikipedia.org/wiki/%E9%93%BE%E8%B7%AF%E6%9C%AC%E5%9C%B0%E5%9C%B0%E5%9D%80">链路本地地址(Link-Local)</a>的配置方法。配置的模板如下:([ ] 中为需要你填写的内容)</p>
+<figure class="highlight inform7"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs inform7">protocol bgp <span class="hljs-comment">[peer的名字]</span> from dnpeers {<br> neighbor <span class="hljs-comment">[对方的链路本地地址]</span> % '<span class="hljs-comment">[通向对方的 Wiregurad 隧道名]</span>' as <span class="hljs-comment">[对方的 AS 号]</span>;<br>}<br></code></pre></td></tr></table></figure>
+
+<p>添加完配置之后别忘了用 <code>birdc configure</code> 重载 bird 2 配置。</p>
+<p>使用命令 <code>birdc s p</code> 可以查看 BIRD 2 软件下所有协议的通信情况。</p>
+<p>若显示为:</p>
+<figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">dn42_xxxx</span> BGP --- up <span class="hljs-number">20</span>:<span class="hljs-number">36</span>:<span class="hljs-number">30</span>.<span class="hljs-number">984</span> Established<br></code></pre></td></tr></table></figure>
+
+<p>则表示 BGP 连接已建立。</p>
+<h2 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h2><p>我还在写 DN42 相关的站点,在上面分享节点信息,方便大家 peer。</p>
+<p>但目前进度缓慢(悲)。</p>
+
+
+ 2022-04-01T04:30:00.000Z
+
+
diff --git "a/categories/3D-\346\211\223\345\215\260/index.html" "b/categories/3D-\346\211\223\345\215\260/index.html"
new file mode 100644
index 00000000..9388e4bd
--- /dev/null
+++ "b/categories/3D-\346\211\223\345\215\260/index.html"
@@ -0,0 +1,379 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 分类 - 3D 打印 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git a/categories/index.html b/categories/index.html
new file mode 100644
index 00000000..91046ea9
--- /dev/null
+++ b/categories/index.html
@@ -0,0 +1,758 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 分类 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/categories/\345\260\217\351\270\241\346\265\213\350\257\204/index.html" "b/categories/\345\260\217\351\270\241\346\265\213\350\257\204/index.html"
new file mode 100644
index 00000000..dc70edd4
--- /dev/null
+++ "b/categories/\345\260\217\351\270\241\346\265\213\350\257\204/index.html"
@@ -0,0 +1,418 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 分类 - 小鸡测评 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/categories/\346\225\231\347\250\213/index.html" "b/categories/\346\225\231\347\250\213/index.html"
new file mode 100644
index 00000000..0753215a
--- /dev/null
+++ "b/categories/\346\225\231\347\250\213/index.html"
@@ -0,0 +1,442 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 分类 - 教程 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/categories/\346\225\231\347\250\213/page/2/index.html" "b/categories/\346\225\231\347\250\213/page/2/index.html"
new file mode 100644
index 00000000..13f5f343
--- /dev/null
+++ "b/categories/\346\225\231\347\250\213/page/2/index.html"
@@ -0,0 +1,448 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 分类 - 教程 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/categories/\346\225\231\347\250\213/page/3/index.html" "b/categories/\346\225\231\347\250\213/page/3/index.html"
new file mode 100644
index 00000000..cfa691ae
--- /dev/null
+++ "b/categories/\346\225\231\347\250\213/page/3/index.html"
@@ -0,0 +1,385 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 分类 - 教程 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/categories/\350\275\257\344\273\266\346\216\250\350\215\220/index.html" "b/categories/\350\275\257\344\273\266\346\216\250\350\215\220/index.html"
new file mode 100644
index 00000000..bfc720f7
--- /dev/null
+++ "b/categories/\350\275\257\344\273\266\346\216\250\350\215\220/index.html"
@@ -0,0 +1,379 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 分类 - 软件推荐 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/categories/\351\232\217\347\254\224/index.html" "b/categories/\351\232\217\347\254\224/index.html"
new file mode 100644
index 00000000..eaad815a
--- /dev/null
+++ "b/categories/\351\232\217\347\254\224/index.html"
@@ -0,0 +1,445 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 分类 - 随笔 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/categories/\351\232\217\347\254\224/page/2/index.html" "b/categories/\351\232\217\347\254\224/page/2/index.html"
new file mode 100644
index 00000000..8a0876f1
--- /dev/null
+++ "b/categories/\351\232\217\347\254\224/page/2/index.html"
@@ -0,0 +1,445 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 分类 - 随笔 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/categories/\351\232\217\347\254\224/page/3/index.html" "b/categories/\351\232\217\347\254\224/page/3/index.html"
new file mode 100644
index 00000000..034992fc
--- /dev/null
+++ "b/categories/\351\232\217\347\254\224/page/3/index.html"
@@ -0,0 +1,385 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 分类 - 随笔 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/category/3d \346\211\223\345\215\260/atom.xml" "b/category/3d \346\211\223\345\215\260/atom.xml"
new file mode 100644
index 00000000..8e2e54f8
--- /dev/null
+++ "b/category/3d \346\211\223\345\215\260/atom.xml"
@@ -0,0 +1,64 @@
+
+
+ https://blog.udon.eu.org
+ カレーうどん屋 • Posts by "3d 打印" category
+
+ 2022-02-06T15:00:00.000Z
+
+
+
+
+
+
+
+
+
+
+ https://blog.udon.eu.org/archives/19f3c1e1.html
+ 我与 3D 打印
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>玩 3D 打印有大半年了,虽然还谈不上资深,但已有了一些自己的想法,作此文分享一下自己的心得。</p>
+<span id="more"></span>
+
+<h2 id="3D-打印机介绍"><a href="#3D-打印机介绍" class="headerlink" title="3D 打印机介绍"></a>3D 打印机介绍</h2><h3 id="3D-打印机的分类"><a href="#3D-打印机的分类" class="headerlink" title="3D 打印机的分类"></a>3D 打印机的分类</h3><p>民用 3D 打印无非两个流派:熔融沉积成型(FDM)与立体平板印刷(SLA,又称光固化),工业用途的还有金属粉末打印的 SLS。</p>
+<p>光固化就不多提了,其优点是精度高,缺点则是机器与耗材价格昂贵,且打印时有比较大的气味(可能对人体有害),对通风的要求比较高,个人认为不适合在家里玩。</p>
+<p>更适合摆在工作室里的 FDM 则是百花齐放,诞生出了各种各样的架构:从最简单的单臂型,到 I3 龙门架、圆形打印平台的三角洲,再到高速高精度的 CoreXY/XZ/YZ 架构、性能超强但组装价格昂贵的 UM 结构。最出名的开源设计则是 Voron 团队,旗下有小型的 Voron 0.1 和大型的 Voron 2.4,且还在持续更新中。</p>
+<h3 id="3D-打印控制系统"><a href="#3D-打印控制系统" class="headerlink" title="3D 打印控制系统"></a>3D 打印控制系统</h3><p>目前我见到的国内用的最多的 3D 打印机控制板(主板)是 MKS GEN L 系列(1.0/1.2/2.1),其搭载的是 ATmega 2560 芯片,常见的控制系统无非两种:Marlin 与 Klipper</p>
+<h4 id="Marlin-1-0-2-0"><a href="#Marlin-1-0-2-0" class="headerlink" title="Marlin 1.0/2.0"></a>Marlin 1.0/2.0</h4><p>Marlin 是一款片上储存,支持脱机打印、屏幕控制的系统。由 C 语言编写,配置较容易修改,但需要手动编译并烧录至控制板。目前已知的编译方法是 Arduino 编译与 VSCode 插件编译,我使用的是后者,但给我的体验不是很好。</p>
+<p>Marlin 的优点是(若不需要自定义)安装简单,仅需用数据线连接电脑烧录即可。缺点则是对打印机的任何重新配置(除了能在屏幕上直接修改设置,如电机方向等),特别是之后要更换静音驱动,就需要手动编译系统,门槛较高且操作繁琐、坑多。</p>
+<h4 id="Klipper"><a href="#Klipper" class="headerlink" title="Klipper"></a>Klipper</h4><p>Klipper 是一款需要上位机控制的系统,主板上仅烧录了一个接收上位机控制指令的程序,大小大概是 Marlin 系统的 1/5。</p>
+<p>据 Klipper 的开发团队<a href="https://github.com/Klipper3d/klipper/blob/master/docs/Benchmarks.md">描述</a>,大部分单片机的性能比较弱,每秒能处理的指令(打印机控制指令)数不高,固采用上位机的高性能 CPU 来处理更高精度但更多指令数的控制指令可以有效地提高打印质量。</p>
+<p>根据跑分表来看,其实 Mega 2560 的性能没有比树莓派的 CPU 弱很多,但考虑到树莓派还可以安装 API 服务器与 Web 控制界面,可以给打印机拓展更多的功能,连接一台上位机还是很具性价比的。</p>
+<p>Klipper 在我看来就像是 3D 打印界的 Ardupilot (开源无人机控制套件),用户可以接触到很多底层的设置、直接控制主板上的所有输出,且修改任何配置都不用重新烧录系统,非常适合创客使用。</p>
+<p>但高级权限也意味着更大的风险。若配置不当,则可能烧坏硬件。</p>
+<p>目前我正在使用的是 Klipper 固件,在两三天的配置调试之后基本可以不用动配置文件了。配置过程中踩了一些坑,后面会单独发一篇文章分享一下 Klipper 配置过程。</p>
+<h3 id="3D-打印材料"><a href="#3D-打印材料" class="headerlink" title="3D 打印材料"></a>3D 打印材料</h3><p>SLA 打印用的是各种光敏树脂,我没有用过就不评价了。</p>
+<p>FDM 使用的材料更加多种多样:PLA、PETG、ABS 三者为主流,TPE 柔性材料也见过有人在用。PLA 是最好打印的一类材料,PETG 需要较高的打印/热床温度,而 ABS 需要保温打印(需封箱)。剩下的就是一些改进/特种材料。例如在 PLA 中添加少量 PC、ABS 以加强硬度,这些材料名字多样,一般是在 PLA 之后加代号,如 PLA-F/AF/AT/G/Pro 等等;或是在 PLA 中添加少量木屑或杂质,使打印机外观像木制品/大理石制品,这类材料比较容易堵头,不是很推荐使用;甚至还有人打印碳纤维,但价格昂贵。</p>
+<h2 id="购买-3D-打印机的理由"><a href="#购买-3D-打印机的理由" class="headerlink" title="购买 3D 打印机的理由"></a>购买 3D 打印机的理由</h2><p>我不想做一个 “KOL”,把 3D 打印吹得天花乱坠。谈一谈我购买 3D 打印机的理由:</p>
+<ol>
+<li>可以动手</li>
+</ol>
+<p>我是一个酷爱动手的人,看见 DIY 项目就忍不住去尝试。或许这辈子开不上高达,但能亲手组装一台能由我操控的机械(甚至还能打印出高达),也算是一点小小的慰藉。</p>
+<ol start="2">
+<li>让梦想中的模型成真</li>
+</ol>
+<p>我有购买一台 3D 打印机的念头,来自一张网图,是一个制作精美的太阳时钟 —— 不是日晷,而是通过巧妙设计的结构,能投射出数字形式的大致时间。显然,这个太阳时钟是 3D 打印的。当时我就觉得,如果我能有一台 3D 打印机,我就能做各种各样的小玩意儿,多美妙 :)</p>
+<ol start="3">
+<li>建模小帮手</li>
+</ol>
+<p>很奇怪的理由,但 3D 打印机确实促使我提升了 3D 建模技术。机器可以创造任何物品,但网上的模型毕竟有限,很多你想要的物品就需要你亲手去建模了。初中时我有学习 3D 建模的念头,当时选择了 C4D,但因为难度太高没多久就放弃了。现在我选择了学习 SolidWorks,自行设计并打印成品,我就能最直观的感受到模型的问题所在,并在软件里修正错误。</p>
+<h2 id="挑选打印机"><a href="#挑选打印机" class="headerlink" title="挑选打印机"></a>挑选打印机</h2><p>于是我就定下了买一台 3D 打印机的计划。这么一计划就是一年多。</p>
+<p>在这一年里我找了很多店家,比对他们的性价比(我的博客里最经常出现的一个词),最后挑中了这家 —— 小树科技。</p>
+<p>我不适合想买成品机,理由很简单:性价比较低,且没有组装的乐趣。这两年国内做开源机器的团队越来越多,小树科技算是比较早的一家。我最看重的莫过于详细的安装文档和一个可靠的社区,毕竟这次我要涉猎的是一个完全陌生的领域。</p>
+<p>逛了逛他们的<a href="(https://www.minitree.fun/)">官网</a>,确定他们有提供安装文档,用户社区也达到数千人规模,我决定了要买这一家的机器。</p>
+<p>新手入门,我选择了 T3 型号的机器。单臂架构,3D 打印机中最简陋、缺点最多的架构,但也是消耗材料最少,价格最便宜的结构。</p>
+<p>DIY 之路必然是坎坷的,如果你决定要自己组装一台 3D 打印机,过程中必然会遇到种种问题。做好心理准备,等待迎接解决问题之后的喜悦吧。</p>
+<p>经过半年的调机与改装之后,这台 T3 已经能稳定地产出质量尚可的打印件,我认为再去改装这台机器所带来的提升已经不大,便决定将其升级为 CoreXY 架构。</p>
+<p>升级 CoreXY 就是这个寒假的事情了,组装花了四五天,调机则还在进行中。目前来看这台机子潜力很大,请期待后续分享调机过程的文章。</p>
+<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>感觉没什么好结的。</p>
+<p>说句实话,调机真的挺痛苦的,刚装完机打两次很可能就会错一次层;但调完之后,再也不会错层的成就感经久不衰。</p>
+
+
+ 2022-02-06T15:00:00.000Z
+
+
diff --git "a/category/3d \346\211\223\345\215\260/feed.json" "b/category/3d \346\211\223\345\215\260/feed.json"
new file mode 100644
index 00000000..5b4bfc3f
--- /dev/null
+++ "b/category/3d \346\211\223\345\215\260/feed.json"
@@ -0,0 +1,18 @@
+{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "カレーうどん屋 • All posts by \"3d 打印\" category",
+ "description": "カレーうどん屋.",
+ "home_page_url": "https://blog.udon.eu.org",
+ "items": [
+ {
+ "id": "https://blog.udon.eu.org/archives/19f3c1e1.html",
+ "url": "https://blog.udon.eu.org/archives/19f3c1e1.html",
+ "title": "我与 3D 打印",
+ "date_published": "2022-02-06T15:00:00.000Z",
+ "content_html": "玩 3D 打印有大半年了,虽然还谈不上资深,但已有了一些自己的想法,作此文分享一下自己的心得。
\n \n\n3D 打印机介绍 3D 打印机的分类 民用 3D 打印无非两个流派:熔融沉积成型(FDM)与立体平板印刷(SLA,又称光固化),工业用途的还有金属粉末打印的 SLS。
\n光固化就不多提了,其优点是精度高,缺点则是机器与耗材价格昂贵,且打印时有比较大的气味(可能对人体有害),对通风的要求比较高,个人认为不适合在家里玩。
\n更适合摆在工作室里的 FDM 则是百花齐放,诞生出了各种各样的架构:从最简单的单臂型,到 I3 龙门架、圆形打印平台的三角洲,再到高速高精度的 CoreXY/XZ/YZ 架构、性能超强但组装价格昂贵的 UM 结构。最出名的开源设计则是 Voron 团队,旗下有小型的 Voron 0.1 和大型的 Voron 2.4,且还在持续更新中。
\n3D 打印控制系统 目前我见到的国内用的最多的 3D 打印机控制板(主板)是 MKS GEN L 系列(1.0/1.2/2.1),其搭载的是 ATmega 2560 芯片,常见的控制系统无非两种:Marlin 与 Klipper
\nMarlin 1.0/2.0 Marlin 是一款片上储存,支持脱机打印、屏幕控制的系统。由 C 语言编写,配置较容易修改,但需要手动编译并烧录至控制板。目前已知的编译方法是 Arduino 编译与 VSCode 插件编译,我使用的是后者,但给我的体验不是很好。
\nMarlin 的优点是(若不需要自定义)安装简单,仅需用数据线连接电脑烧录即可。缺点则是对打印机的任何重新配置(除了能在屏幕上直接修改设置,如电机方向等),特别是之后要更换静音驱动,就需要手动编译系统,门槛较高且操作繁琐、坑多。
\nKlipper Klipper 是一款需要上位机控制的系统,主板上仅烧录了一个接收上位机控制指令的程序,大小大概是 Marlin 系统的 1/5。
\n据 Klipper 的开发团队描述 ,大部分单片机的性能比较弱,每秒能处理的指令(打印机控制指令)数不高,固采用上位机的高性能 CPU 来处理更高精度但更多指令数的控制指令可以有效地提高打印质量。
\n根据跑分表来看,其实 Mega 2560 的性能没有比树莓派的 CPU 弱很多,但考虑到树莓派还可以安装 API 服务器与 Web 控制界面,可以给打印机拓展更多的功能,连接一台上位机还是很具性价比的。
\nKlipper 在我看来就像是 3D 打印界的 Ardupilot (开源无人机控制套件),用户可以接触到很多底层的设置、直接控制主板上的所有输出,且修改任何配置都不用重新烧录系统,非常适合创客使用。
\n但高级权限也意味着更大的风险。若配置不当,则可能烧坏硬件。
\n目前我正在使用的是 Klipper 固件,在两三天的配置调试之后基本可以不用动配置文件了。配置过程中踩了一些坑,后面会单独发一篇文章分享一下 Klipper 配置过程。
\n3D 打印材料 SLA 打印用的是各种光敏树脂,我没有用过就不评价了。
\nFDM 使用的材料更加多种多样:PLA、PETG、ABS 三者为主流,TPE 柔性材料也见过有人在用。PLA 是最好打印的一类材料,PETG 需要较高的打印/热床温度,而 ABS 需要保温打印(需封箱)。剩下的就是一些改进/特种材料。例如在 PLA 中添加少量 PC、ABS 以加强硬度,这些材料名字多样,一般是在 PLA 之后加代号,如 PLA-F/AF/AT/G/Pro 等等;或是在 PLA 中添加少量木屑或杂质,使打印机外观像木制品/大理石制品,这类材料比较容易堵头,不是很推荐使用;甚至还有人打印碳纤维,但价格昂贵。
\n购买 3D 打印机的理由 我不想做一个 “KOL”,把 3D 打印吹得天花乱坠。谈一谈我购买 3D 打印机的理由:
\n\n可以动手 \n \n我是一个酷爱动手的人,看见 DIY 项目就忍不住去尝试。或许这辈子开不上高达,但能亲手组装一台能由我操控的机械(甚至还能打印出高达),也算是一点小小的慰藉。
\n\n让梦想中的模型成真 \n \n我有购买一台 3D 打印机的念头,来自一张网图,是一个制作精美的太阳时钟 —— 不是日晷,而是通过巧妙设计的结构,能投射出数字形式的大致时间。显然,这个太阳时钟是 3D 打印的。当时我就觉得,如果我能有一台 3D 打印机,我就能做各种各样的小玩意儿,多美妙 :)
\n\n建模小帮手 \n \n很奇怪的理由,但 3D 打印机确实促使我提升了 3D 建模技术。机器可以创造任何物品,但网上的模型毕竟有限,很多你想要的物品就需要你亲手去建模了。初中时我有学习 3D 建模的念头,当时选择了 C4D,但因为难度太高没多久就放弃了。现在我选择了学习 SolidWorks,自行设计并打印成品,我就能最直观的感受到模型的问题所在,并在软件里修正错误。
\n挑选打印机 于是我就定下了买一台 3D 打印机的计划。这么一计划就是一年多。
\n在这一年里我找了很多店家,比对他们的性价比(我的博客里最经常出现的一个词),最后挑中了这家 —— 小树科技。
\n我不适合想买成品机,理由很简单:性价比较低,且没有组装的乐趣。这两年国内做开源机器的团队越来越多,小树科技算是比较早的一家。我最看重的莫过于详细的安装文档和一个可靠的社区,毕竟这次我要涉猎的是一个完全陌生的领域。
\n逛了逛他们的官网 ,确定他们有提供安装文档,用户社区也达到数千人规模,我决定了要买这一家的机器。
\n新手入门,我选择了 T3 型号的机器。单臂架构,3D 打印机中最简陋、缺点最多的架构,但也是消耗材料最少,价格最便宜的结构。
\nDIY 之路必然是坎坷的,如果你决定要自己组装一台 3D 打印机,过程中必然会遇到种种问题。做好心理准备,等待迎接解决问题之后的喜悦吧。
\n经过半年的调机与改装之后,这台 T3 已经能稳定地产出质量尚可的打印件,我认为再去改装这台机器所带来的提升已经不大,便决定将其升级为 CoreXY 架构。
\n升级 CoreXY 就是这个寒假的事情了,组装花了四五天,调机则还在进行中。目前来看这台机子潜力很大,请期待后续分享调机过程的文章。
\n结语 感觉没什么好结的。
\n说句实话,调机真的挺痛苦的,刚装完机打两次很可能就会错一次层;但调完之后,再也不会错层的成就感经久不衰。
\n",
+ "tags": [
+ "3D 打印"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/category/3d \346\211\223\345\215\260/rss.xml" "b/category/3d \346\211\223\345\215\260/rss.xml"
new file mode 100644
index 00000000..d4606268
--- /dev/null
+++ "b/category/3d \346\211\223\345\215\260/rss.xml"
@@ -0,0 +1,68 @@
+
+
+
+ カレーうどん屋 • Posts by "3d 打印" category
+ https://blog.udon.eu.org
+ カレーうどん屋.
+ zh-CN
+ Sun, 06 Feb 2022 23:00:00 +0800
+ Sun, 06 Feb 2022 23:00:00 +0800
+ 教程
+ 随笔
+ 软件
+ 小鸡测评
+ 3D 打印
+ 嵌入式开发
+ GitHub Actions
+ Hexo
+ DIY
+ -
+
https://blog.udon.eu.org/archives/19f3c1e1.html
+ 我与 3D 打印
+ https://blog.udon.eu.org/archives/19f3c1e1.html
+ 3D 打印
+ Sun, 06 Feb 2022 23:00:00 +0800
+
+
+
+
diff --git "a/category/\345\260\217\351\270\241\346\265\213\350\257\204/atom.xml" "b/category/\345\260\217\351\270\241\346\265\213\350\257\204/atom.xml"
new file mode 100644
index 00000000..1ebfd4ed
--- /dev/null
+++ "b/category/\345\260\217\351\270\241\346\265\213\350\257\204/atom.xml"
@@ -0,0 +1,217 @@
+
+
+ https://blog.udon.eu.org
+ カレーうどん屋 • Posts by "小鸡测评" category
+
+ 2022-04-06T14:15:00.000Z
+
+
+
+
+
+
+
+
+
+
+ https://blog.udon.eu.org/archives/28fa0729.html
+ Virmach Japan
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>老朋友 Virmach 最近搞了场日本的预售。上游是 XTom,线路走的 IIJ。配置从 1C 384M 到 2C 2.5G 都有,价格则是 11.11刀/年 起(预售打 8 折, 8.89刀/年起)。</p>
+<p>我买的这台是折后 9.7刀/年,1C 768M 20G NVME 2T 双向流量的配置。</p>
+<span id="more"></span>
+
+<hr>
+<p>直观感受:性能强劲,白天网络不错。晚高峰也能用,不爆炸。</p>
+<p>下面放测试数据:</p>
+<h3 id="综合测试"><a href="#综合测试" class="headerlink" title="综合测试"></a>综合测试</h3><p><img src="/images/2022-04-06/Bench.png"></p>
+<p>CPU 很幸运地抽中了 5900X,可以看到硬盘速度非常 OK。</p>
+<p>早上的网络情况很不错,没指望能跑满 G 口,毕竟这么多人分 10G 的口子。</p>
+<h3 id="性能测试"><a href="#性能测试" class="headerlink" title="性能测试"></a>性能测试</h3><p><img src="/images/2022-04-06/UNIXBench.jpg"></p>
+<p>不愧是 5900X,单核跑出了部分志强将近3倍的成绩!AMD, YES!</p>
+<p>开一台 2C 2G 的机子完全可以当作开发服务器使了。</p>
+<h3 id="国内网络测试"><a href="#国内网络测试" class="headerlink" title="国内网络测试"></a>国内网络测试</h3><h4 id="白天"><a href="#白天" class="headerlink" title="白天"></a>白天</h4><p><img src="/images/2022-04-06/SuperSpeed-Morning.png"></p>
+<p>三网表现均不错呢。</p>
+<h4 id="晚高峰"><a href="#晚高峰" class="headerlink" title="晚高峰"></a>晚高峰</h4><p><img src="/images/2022-04-06/SuperSpeed-Night.png"></p>
+<p>惊了,晚高峰表现很不错耶。</p>
+<h3 id="流媒体解锁"><a href="#流媒体解锁" class="headerlink" title="流媒体解锁"></a>流媒体解锁</h3><p><img src="/images/2022-04-06/Streaming.png"></p>
+<p>Virmach 只字没提流媒体,就别报多大希望。</p>
+<p>不过作为日本的 VPS 自家游戏都不能解锁……这 IP 优化的不大行。</p>
+<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>网络不错,性能强劲,如此便宜的价格可以说性价超高。</p>
+
+
+ 2022-04-06T14:15:00.000Z
+
+
+ https://blog.udon.eu.org/archives/6e832212.html
+ Deepvm 9929
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>本以为不会再购买新的机子了,今天看到有一台挺便宜的 9929 线路的机子,就买了一个月尝尝鲜。</p>
+<span id="more"></span>
+
+<p>配置:1core 512Mb 20G-SSD 500G@150Mbps</p>
+<p>价格:16元/月</p>
+<p>总结:Something strange.</p>
+<hr>
+<h3 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h3><p><img src="/images/2022-01-10/Deepvm9929.png"></p>
+<p>晚高峰测速全部都能跑满 150Mbps,看起来很不错。</p>
+<p>AS9929 是联通最新的专线,好像比 AS4837 要高级那么一点点,应该体验要比 4837 来得好一点吧,看延迟会比 4837 低十几毫秒。</p>
+<p>但实际体验下来,双向延迟会比 4837 高上 200ms 左右,speed.cloudflare.com 的成绩也不如 4837 来的好,众人迷信的 Youtube 4K 视频(电脑解码带不动 8K)跑分在 56k,和 4837 基本一致。 </p>
+<p>这就是我上述的 something strange 了。Deepvm 家的上游好像是 Spartan,按道理网络是不会差的。我猜测是这家的机子超售的稍微厉害了一点点,导致总体体验不如 Wikihost 家的 4837.</p>
+<h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p><img src="/images/2022-01-10/Deepvm9929Bench.png"></p>
+<p>跑分上来看,性能是十分不错的。性能这一块又没有超售太多???把我整懵了。</p>
+<hr>
+<p>综合价格和性能来看,这台机子的性价比还是很能打的。由于商家是去年年初刚成立的,服务器的可靠性还是未知数,也没有赠送自动备份(在折腾坏系统的时候有备份可以还原是很美妙的事情)。因此我建议月付购买这家的机子。</p>
+<p>放上 AFF:<a href="https://www.deepvm.net/aff/HVQAFOVA">https://www.deepvm.net/aff/HVQAFOVA</a></p>
+
+
+ 2022-01-10T13:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/be3776eb.html
+ WikiHost CU4837
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>今天要介绍的是一台来自 WikiHost 家的机器。</p>
+<span id="more"></span>
+
+<p>配置:Los Angeles - CU4837 Lite KVM</p>
+<p>1 core (5900X)@30% 基准性能 512Mb 20G-SSD 500G@1Gbps</p>
+<p>价格:月付 18 元(首月附加 5 元初装费)/ 年付 200 元(免初装费)</p>
+<p>总结:性能优异,网络为最大卖点。性价比很高。</p>
+<hr>
+<h3 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h3><p><img src="/images/2021-12-29/WikiHost4837.png"></p>
+<p>G 口没有虚标。</p>
+<p>测试特意挑在晚高峰跑的,与前面测评的 Cloudcone 相比,上海与广州出口三线的表现都很优秀。</p>
+<p><img src="/images/2021-12-29/WikiHostMtr.png"></p>
+<p>这台机子的去程是 163 骨干网,回程是联通专线 AS4837. 图中为回程路由 —— 进入电信的骨干网之前的流量就是 4837 承载的。</p>
+<h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p><img src="/images/2021-12-29/WikiHostBench.png"></p>
+<p>听到 30% 基准性能,你会觉得这是一台跑 apt 都嫌卡的机子。</p>
+<p>你错了!这台母鸡配备的可是 AMD R9-5900X。AMD YES!</p>
+<p>即使只有单核的三成性能,跑分依旧十分接近 WebHorizon 的单核成绩,轻松超过了 Cloudcone 的单核成绩。</p>
+<p>所以这台机器在日常使用时不会觉得 CPU 性能太差。主要的瓶颈还是在 512MB 内存上。</p>
+<hr>
+<p>综合性能和网络考虑,这台机子作为远程开发机器,安装 VSCode 跑一些轻量的项目还是很不错的。这台机器最合适的工作还是优化国内连接国外的网络情况,例如加速 GitHub 下载速度等。这个性能建立个人网站也完全足够了,140ms 较低的延迟与晚高峰的超低丢包可以带来很不错的体验。</p>
+<p>你问我推不推荐?那肯定推荐啊!</p>
+<p>鸡总的 WikiHost 是以高质量服务著称的,我在 WikiHost 购买过网站空间和流量转发,使用期间给我的体验很不错,发工单很快就能得到回复或技术人员的直接处理。缺点就是大部分机型价格都比较高(月付 30 元以上),而这台 Lite 机型以超低的价格(还附赠了7天全盘备份,前几天把机子玩炸了就用上了还原功能),一出来就被抢光了。目前还有存货的是直连韩国 VPS(高峰时段三网全炸,平时延迟非常低),有需求的朋友也可以考虑。</p>
+<p>放上 AFF:<a href="https://idc.wiki/aff.php?aff=2182">https://idc.wiki/aff.php?aff=2182</a></p>
+<p>这台机子让我非常满意,再配合 Cloudcone 的高性能、较大硬盘的机器,因此短时间内我应该不会购入新的机子,尽请期待未来的机器测评。</p>
+
+
+ 2021-12-31T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/a7b78eea.html
+ Cloudcone 双十一特价鸡
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>今天来介绍一下来自(依旧是)高性价比商家 Cloudcone 的机器。</p>
+<span id="more"></span>
+
+<p>配置:2cores 2GB 70G-SSD(RAID 10)4T@1Gbps</p>
+<p>价格:年付23.8刀</p>
+<p>来个总结:性能优异、硬盘性能尤为突出;网络质量尚可;性价比高。</p>
+<hr>
+<h3 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h3><p><img src="/images/2021-12-24/Cloudcone.jpg"></p>
+<p>测试是在晚高峰做的,只能说炸的很彻底,国内基本没有什么速度。</p>
+<p><img src="/images/2021-12-24/CloudconeMorning.png"></p>
+<p>中午再测一次,白天的网络还是很不错的。</p>
+<p><img src="/images/2021-12-24/CloudconeRoute.png"></p>
+<p>根据路由测试来看,走的是最普通的163骨干网,没有线路可言。</p>
+<p><img src="/images/2021-12-24/CloudconeMtr.png"></p>
+<p>Mtr 显示丢包率在 10-20%,延迟在 160-190ms,中规中矩。</p>
+<h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p><img src="/images/2021-12-24/CloudconeBench.jpg"></p>
+<p>单核性能说不上强,甚至打不过 Webhorizon 的 NAT 机子。</p>
+<p>但是有俩核啊~</p>
+<p>在编译一个 Node.js 项目时,其他机子要花将近一分钟,这台机子只要 32s 左右,有效提高了我的工作效率。</p>
+<hr>
+<p>一个月前,手持 Virmach 的我流下了不争气的泪水,发誓要买一台好一点的机子做开发、跑服务。</p>
+<p>于是我就在网上找性价比商家了(笑)。</p>
+<p>有人推荐我买 GreenCloud 的机子,500G 大盘鸡,价格也不贵。我当时就很心动,几欲下单,最后上网一查,发现 GC 家的 IP 段不大好,会被 Google 查岗(验证是否是机器人),遂放弃。</p>
+<p>持币徘徊的我遇见了 Cloudcone。网上一查,没有什么黑历史,我就和同学“拼鸡”下单了。(没错,我只花了一半的钱 XD)</p>
+<p>24刀的价格平常只能买到 1c 1G 的机子,双十一特价可以让性能翻倍,性价比一下子上来了。70G 的硬盘也可以用来挂一些需要存储数据的服务(例如现在正在跑的 H@H)。</p>
+<p>至于网络嘛……因为没有特殊线路,晚上还是挺糟糕的,有时候 SSH 都会断连。</p>
+<p>不得不提的是,就在我开通机器一周后,Cloudcone 貌似出现了部分机器数据丢失的问题……因为范围不大,似乎没有引起什么讨论。因为没有附带异地备份的功能,使用高性价机器要承担一定风险,重要数据就不要存放在此类没有备份的 VPS 中。</p>
+<hr>
+<p>总结来说,如果遇到活动,Cloudcone 的机子还是很值得入手的!</p>
+<p>正好圣诞节促销到了,力度比双十一还大一点点。放上 AFF Link~</p>
+<p><a href="https://app.cloudcone.com/?ref=7447">https://app.cloudcone.com/?ref=7447</a></p>
+
+
+ 2021-12-24T14:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/9732665c.html
+ WebHorizon NAT JP
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>第一篇正式测评:来自印度的性价比商家 WebHorizon 的特价 NAT 小鸡。</p>
+<span id="more"></span>
+
+<p>配置:1core 256Mb 4G-HDD 500G 1Gbps,机房位于日本,有 IPv6,仅有 NAT IPv4.</p>
+<p>价格:……4刀一年。</p>
+<p>先来个总结:买了吃灰的机器。</p>
+<h3 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h3><p><img src="/images/2021-12-23/WebHorizonJP.png"></p>
+<p>小鸡号称的 G 口是货真价实的。</p>
+<p>没有独立 IPv4 带来的限制很大。WebHorizon 提供了四个 IPv4 地址用于转发 VPS 的端口。但经我的随机(随便)测试,我开不到任何能用的 TCP 转发端口,仅能开到 HTTP/HTTPS 的转发(80/443 端口)。</p>
+<p>因此,想要连接这台机器需要用到 IPv6,能提供的服务也仅限 HTTP。</p>
+<p>NAT 机器还有一点不好:若四个转发地址都被墙了,那这台机器在国内就没得用了。目前四个地址被墙了一个。</p>
+<p><img src="/images/2021-12-23/WebHorizonJPRoute.png"></p>
+<p>从国内访问的线路是双向 NTT。白天效果尚可,晚高峰时间炸得厉害。全天高延迟 160-240ms。网络只是可以用的级别。</p>
+<h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p><img src="/images/2021-12-23/WebHorizonBench.png"></p>
+<p>从跑分上来看,这台机子的 CPU 性能出奇的可以。</p>
+<p><del>可能是买来的都吃灰了,没人抢性能</del></p>
+<p>硬盘的表现则是中规中矩,能用级别。</p>
+<hr>
+<p>总结上述来看,这台机子挺不行的。那为什么我买了呢?</p>
+<p>看到4刀一年的价格,我的捡垃圾之魂就按耐不住了啊!</p>
+<p><del>我现在只是非常的后悔。</del></p>
+<p>256Mb 的超迷你内存让这台机器承担不了什么任务。挂一点小脚本大概能吃得消。适合钱包真的很空/垃圾佬购买。</p>
+<p>虽说不推荐购买,我还是厚着脸皮放一个 AFF 连接:</p>
+<p><a href="https://my.webhorizon.in/order/forms/a/MzQ1Nw==">https://my.webhorizon.in/order/forms/a/MzQ1Nw==</a></p>
+<p>P.S. 4刀一年是黑五期间的 50% OFF 优惠。</p>
+<h3 id="附赠"><a href="#附赠" class="headerlink" title="附赠"></a>附赠</h3><p>这台机器的虚拟化技术是 OpenVZ,也带来了一些不足:</p>
+<ol>
+<li>超售严重,高峰期性能可能不如预期。</li>
+<li>与母鸡共用内核,因此无法自行修改内核。有一些服务,例如 BBR、锐速等,需要修改内核才能开启,在 OVZ 的机型上就无法使用。有此类需求可以购买 KVM 虚拟化的 VPS。母鸡内核可能没有开启某些功能,例如我在尝试通过 RClone 挂载虚拟硬盘时,被告知内核未开启 fuse,需要联系服务商开启(我没有去联系,不保证商家会同意开启)。</li>
+</ol>
+
+
+ 2021-12-23T15:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/6f963444.html
+ Virmach 7.5刀年付特价机
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>虽然我在前文的结尾奉劝大家不要买太廉价的机器……</p>
+<p>但是我一介学生是真的穷啊!</p>
+<p>所以今天来谈一谈一家以性价比著称的 VPS 服务商:Virmach。</p>
+<span id="more"></span>
+
+<hr>
+<p>开门见山,先说直观感受:网络很一般、机器性能差。机器适合穷苦学生党使用。</p>
+<p>我开的机型是 Virmach 黑五特价机型:年付7.5刀,1core 256Mb(免费升级至 384mb)10Gb-SSD 1Gbps。</p>
+<p>这台机器在上个月到期,我又有了新欢就没有续费,因此还是没有跑分截图。</p>
+<p>这机子的性能差到 apt 都嫌慢,网络情况也很是糟糕,应该就是最普通的国际线路 + 国内163,丢包率高,全天 SSH 连接皆不稳定。</p>
+<p>不过一年50块左右的价格也没什么好抱怨的了,用来挂点需要访问世界互联网的服务,例如 Telegram Bot、RSSHub 等;或者建个站,套一层 Cloudflare CDN 之后众生平等,可以说勉强能用。</p>
+<p>较小的内存有时也会带来不便,可能遇到部分应用、组件无法安装/编译的情况。据观察 RSSHub 运行时即占用 300Mb 左右内存,因此我还是建议内存在 512Mb 以上的 VPS,1Gb 为佳。</p>
+<p>多提一嘴,现在不少应用都能以 PaaS 的形式部署在诸如 Heroku 的平台上,完全可以做到免费运行一个服务,没有必要租用 VPS、配置环境。</p>
+<p>VPS 的硬盘也是一个挺重要的指标。按我的经验,VPS 至少要有 20G 的硬盘才够用。Debian 11 系统 + LNMP + NodeJS 环境就占用了 7-8G 的硬盘空间,剩下的 10G 可以用于存储应用数据。至于硬盘性能,只要不是太可怜,影响都不大。</p>
+
+
+ 2021-12-22T09:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/afe45e8a.html
+ 我的第一台 VPS
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>开一个新坑,测评一下所有我用过的小鸡,<del>如果能赚到点 AFF 就更好了。</del></p>
+<span id="more"></span>
+
+<p>来谈一谈我的第一台 VPS 吧。</p>
+<p>我的第一台 VPS 购于初中,仅用了一年便没有再续费了。因此没有任何性能、网络方面的测评,仅剩下我的感想了。没有续费的原因很简单:太™垃圾了。</p>
+<p><del>年幼无知</del>,对 VPS 性能、路由线路都一无所知的我被超低的价格蒙骗,入手了一台来自无名国人商家的小鸡。</p>
+<p>印象中,那是一台 1core 512Mb 的小鸡,位于太平洋彼岸、再跨过美国全境的美东海岸。VPS 的网络差到经常连接不上 SSH。</p>
+<p>说了这么多(并不多),就是想奉劝各位:别贪便宜。</p>
+
+
+ 2021-12-21T15:30:00.000Z
+
+
diff --git "a/category/\345\260\217\351\270\241\346\265\213\350\257\204/feed.json" "b/category/\345\260\217\351\270\241\346\265\213\350\257\204/feed.json"
new file mode 100644
index 00000000..73a55865
--- /dev/null
+++ "b/category/\345\260\217\351\270\241\346\265\213\350\257\204/feed.json"
@@ -0,0 +1,78 @@
+{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "カレーうどん屋 • All posts by \"小鸡测评\" category",
+ "description": "カレーうどん屋.",
+ "home_page_url": "https://blog.udon.eu.org",
+ "items": [
+ {
+ "id": "https://blog.udon.eu.org/archives/28fa0729.html",
+ "url": "https://blog.udon.eu.org/archives/28fa0729.html",
+ "title": "Virmach Japan",
+ "date_published": "2022-04-06T14:15:00.000Z",
+ "content_html": "老朋友 Virmach 最近搞了场日本的预售。上游是 XTom,线路走的 IIJ。配置从 1C 384M 到 2C 2.5G 都有,价格则是 11.11刀/年 起(预售打 8 折, 8.89刀/年起)。
\n我买的这台是折后 9.7刀/年,1C 768M 20G NVME 2T 双向流量的配置。
\n \n\n \n直观感受:性能强劲,白天网络不错。晚高峰也能用,不爆炸。
\n下面放测试数据:
\n综合测试
\nCPU 很幸运地抽中了 5900X,可以看到硬盘速度非常 OK。
\n早上的网络情况很不错,没指望能跑满 G 口,毕竟这么多人分 10G 的口子。
\n性能测试
\n不愧是 5900X,单核跑出了部分志强将近3倍的成绩!AMD, YES!
\n开一台 2C 2G 的机子完全可以当作开发服务器使了。
\n国内网络测试 白天
\n三网表现均不错呢。
\n晚高峰
\n惊了,晚高峰表现很不错耶。
\n流媒体解锁
\nVirmach 只字没提流媒体,就别报多大希望。
\n不过作为日本的 VPS 自家游戏都不能解锁……这 IP 优化的不大行。
\n总结 网络不错,性能强劲,如此便宜的价格可以说性价超高。
\n",
+ "tags": [
+ "小鸡测评"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/6e832212.html",
+ "url": "https://blog.udon.eu.org/archives/6e832212.html",
+ "title": "Deepvm 9929",
+ "date_published": "2022-01-10T13:00:00.000Z",
+ "content_html": "本以为不会再购买新的机子了,今天看到有一台挺便宜的 9929 线路的机子,就买了一个月尝尝鲜。
\n \n\n配置:1core 512Mb 20G-SSD 500G@150Mbps
\n价格:16元/月
\n总结:Something strange.
\n \n网络
\n晚高峰测速全部都能跑满 150Mbps,看起来很不错。
\nAS9929 是联通最新的专线,好像比 AS4837 要高级那么一点点,应该体验要比 4837 来得好一点吧,看延迟会比 4837 低十几毫秒。
\n但实际体验下来,双向延迟会比 4837 高上 200ms 左右,speed.cloudflare.com 的成绩也不如 4837 来的好,众人迷信的 Youtube 4K 视频(电脑解码带不动 8K)跑分在 56k,和 4837 基本一致。
\n这就是我上述的 something strange 了。Deepvm 家的上游好像是 Spartan,按道理网络是不会差的。我猜测是这家的机子超售的稍微厉害了一点点,导致总体体验不如 Wikihost 家的 4837.
\n性能
\n跑分上来看,性能是十分不错的。性能这一块又没有超售太多???把我整懵了。
\n \n综合价格和性能来看,这台机子的性价比还是很能打的。由于商家是去年年初刚成立的,服务器的可靠性还是未知数,也没有赠送自动备份(在折腾坏系统的时候有备份可以还原是很美妙的事情)。因此我建议月付购买这家的机子。
\n放上 AFF:https://www.deepvm.net/aff/HVQAFOVA
\n",
+ "tags": [
+ "小鸡测评"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/be3776eb.html",
+ "url": "https://blog.udon.eu.org/archives/be3776eb.html",
+ "title": "WikiHost CU4837",
+ "date_published": "2021-12-31T04:30:00.000Z",
+ "content_html": "今天要介绍的是一台来自 WikiHost 家的机器。
\n \n\n配置:Los Angeles - CU4837 Lite KVM
\n1 core (5900X)@30% 基准性能 512Mb 20G-SSD 500G@1Gbps
\n价格:月付 18 元(首月附加 5 元初装费)/ 年付 200 元(免初装费)
\n总结:性能优异,网络为最大卖点。性价比很高。
\n \n网络
\nG 口没有虚标。
\n测试特意挑在晚高峰跑的,与前面测评的 Cloudcone 相比,上海与广州出口三线的表现都很优秀。
\n
\n这台机子的去程是 163 骨干网,回程是联通专线 AS4837. 图中为回程路由 —— 进入电信的骨干网之前的流量就是 4837 承载的。
\n性能
\n听到 30% 基准性能,你会觉得这是一台跑 apt 都嫌卡的机子。
\n你错了!这台母鸡配备的可是 AMD R9-5900X。AMD YES!
\n即使只有单核的三成性能,跑分依旧十分接近 WebHorizon 的单核成绩,轻松超过了 Cloudcone 的单核成绩。
\n所以这台机器在日常使用时不会觉得 CPU 性能太差。主要的瓶颈还是在 512MB 内存上。
\n \n综合性能和网络考虑,这台机子作为远程开发机器,安装 VSCode 跑一些轻量的项目还是很不错的。这台机器最合适的工作还是优化国内连接国外的网络情况,例如加速 GitHub 下载速度等。这个性能建立个人网站也完全足够了,140ms 较低的延迟与晚高峰的超低丢包可以带来很不错的体验。
\n你问我推不推荐?那肯定推荐啊!
\n鸡总的 WikiHost 是以高质量服务著称的,我在 WikiHost 购买过网站空间和流量转发,使用期间给我的体验很不错,发工单很快就能得到回复或技术人员的直接处理。缺点就是大部分机型价格都比较高(月付 30 元以上),而这台 Lite 机型以超低的价格(还附赠了7天全盘备份,前几天把机子玩炸了就用上了还原功能),一出来就被抢光了。目前还有存货的是直连韩国 VPS(高峰时段三网全炸,平时延迟非常低),有需求的朋友也可以考虑。
\n放上 AFF:https://idc.wiki/aff.php?aff=2182
\n这台机子让我非常满意,再配合 Cloudcone 的高性能、较大硬盘的机器,因此短时间内我应该不会购入新的机子,尽请期待未来的机器测评。
\n",
+ "tags": [
+ "小鸡测评"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/a7b78eea.html",
+ "url": "https://blog.udon.eu.org/archives/a7b78eea.html",
+ "title": "Cloudcone 双十一特价鸡",
+ "date_published": "2021-12-24T14:00:00.000Z",
+ "content_html": "今天来介绍一下来自(依旧是)高性价比商家 Cloudcone 的机器。
\n \n\n配置:2cores 2GB 70G-SSD(RAID 10)4T@1Gbps
\n价格:年付23.8刀
\n来个总结:性能优异、硬盘性能尤为突出;网络质量尚可;性价比高。
\n \n网络
\n测试是在晚高峰做的,只能说炸的很彻底,国内基本没有什么速度。
\n
\n中午再测一次,白天的网络还是很不错的。
\n
\n根据路由测试来看,走的是最普通的163骨干网,没有线路可言。
\n
\nMtr 显示丢包率在 10-20%,延迟在 160-190ms,中规中矩。
\n性能
\n单核性能说不上强,甚至打不过 Webhorizon 的 NAT 机子。
\n但是有俩核啊~
\n在编译一个 Node.js 项目时,其他机子要花将近一分钟,这台机子只要 32s 左右,有效提高了我的工作效率。
\n \n一个月前,手持 Virmach 的我流下了不争气的泪水,发誓要买一台好一点的机子做开发、跑服务。
\n于是我就在网上找性价比商家了(笑)。
\n有人推荐我买 GreenCloud 的机子,500G 大盘鸡,价格也不贵。我当时就很心动,几欲下单,最后上网一查,发现 GC 家的 IP 段不大好,会被 Google 查岗(验证是否是机器人),遂放弃。
\n持币徘徊的我遇见了 Cloudcone。网上一查,没有什么黑历史,我就和同学“拼鸡”下单了。(没错,我只花了一半的钱 XD)
\n24刀的价格平常只能买到 1c 1G 的机子,双十一特价可以让性能翻倍,性价比一下子上来了。70G 的硬盘也可以用来挂一些需要存储数据的服务(例如现在正在跑的 H@H)。
\n至于网络嘛……因为没有特殊线路,晚上还是挺糟糕的,有时候 SSH 都会断连。
\n不得不提的是,就在我开通机器一周后,Cloudcone 貌似出现了部分机器数据丢失的问题……因为范围不大,似乎没有引起什么讨论。因为没有附带异地备份的功能,使用高性价机器要承担一定风险,重要数据就不要存放在此类没有备份的 VPS 中。
\n \n总结来说,如果遇到活动,Cloudcone 的机子还是很值得入手的!
\n正好圣诞节促销到了,力度比双十一还大一点点。放上 AFF Link~
\nhttps://app.cloudcone.com/?ref=7447
\n",
+ "tags": [
+ "小鸡测评"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/9732665c.html",
+ "url": "https://blog.udon.eu.org/archives/9732665c.html",
+ "title": "WebHorizon NAT JP",
+ "date_published": "2021-12-23T15:00:00.000Z",
+ "content_html": "第一篇正式测评:来自印度的性价比商家 WebHorizon 的特价 NAT 小鸡。
\n \n\n配置:1core 256Mb 4G-HDD 500G 1Gbps,机房位于日本,有 IPv6,仅有 NAT IPv4.
\n价格:……4刀一年。
\n先来个总结:买了吃灰的机器。
\n网络
\n小鸡号称的 G 口是货真价实的。
\n没有独立 IPv4 带来的限制很大。WebHorizon 提供了四个 IPv4 地址用于转发 VPS 的端口。但经我的随机(随便)测试,我开不到任何能用的 TCP 转发端口,仅能开到 HTTP/HTTPS 的转发(80/443 端口)。
\n因此,想要连接这台机器需要用到 IPv6,能提供的服务也仅限 HTTP。
\nNAT 机器还有一点不好:若四个转发地址都被墙了,那这台机器在国内就没得用了。目前四个地址被墙了一个。
\n
\n从国内访问的线路是双向 NTT。白天效果尚可,晚高峰时间炸得厉害。全天高延迟 160-240ms。网络只是可以用的级别。
\n性能
\n从跑分上来看,这台机子的 CPU 性能出奇的可以。
\n可能是买来的都吃灰了,没人抢性能
\n硬盘的表现则是中规中矩,能用级别。
\n \n总结上述来看,这台机子挺不行的。那为什么我买了呢?
\n看到4刀一年的价格,我的捡垃圾之魂就按耐不住了啊!
\n我现在只是非常的后悔。
\n256Mb 的超迷你内存让这台机器承担不了什么任务。挂一点小脚本大概能吃得消。适合钱包真的很空/垃圾佬购买。
\n虽说不推荐购买,我还是厚着脸皮放一个 AFF 连接:
\nhttps://my.webhorizon.in/order/forms/a/MzQ1Nw==
\nP.S. 4刀一年是黑五期间的 50% OFF 优惠。
\n附赠 这台机器的虚拟化技术是 OpenVZ,也带来了一些不足:
\n\n超售严重,高峰期性能可能不如预期。 \n与母鸡共用内核,因此无法自行修改内核。有一些服务,例如 BBR、锐速等,需要修改内核才能开启,在 OVZ 的机型上就无法使用。有此类需求可以购买 KVM 虚拟化的 VPS。母鸡内核可能没有开启某些功能,例如我在尝试通过 RClone 挂载虚拟硬盘时,被告知内核未开启 fuse,需要联系服务商开启(我没有去联系,不保证商家会同意开启)。 \n \n",
+ "tags": [
+ "小鸡测评"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/6f963444.html",
+ "url": "https://blog.udon.eu.org/archives/6f963444.html",
+ "title": "Virmach 7.5刀年付特价机",
+ "date_published": "2021-12-22T09:30:00.000Z",
+ "content_html": "虽然我在前文的结尾奉劝大家不要买太廉价的机器……
\n但是我一介学生是真的穷啊!
\n所以今天来谈一谈一家以性价比著称的 VPS 服务商:Virmach。
\n \n\n \n开门见山,先说直观感受:网络很一般、机器性能差。机器适合穷苦学生党使用。
\n我开的机型是 Virmach 黑五特价机型:年付7.5刀,1core 256Mb(免费升级至 384mb)10Gb-SSD 1Gbps。
\n这台机器在上个月到期,我又有了新欢就没有续费,因此还是没有跑分截图。
\n这机子的性能差到 apt 都嫌慢,网络情况也很是糟糕,应该就是最普通的国际线路 + 国内163,丢包率高,全天 SSH 连接皆不稳定。
\n不过一年50块左右的价格也没什么好抱怨的了,用来挂点需要访问世界互联网的服务,例如 Telegram Bot、RSSHub 等;或者建个站,套一层 Cloudflare CDN 之后众生平等,可以说勉强能用。
\n较小的内存有时也会带来不便,可能遇到部分应用、组件无法安装/编译的情况。据观察 RSSHub 运行时即占用 300Mb 左右内存,因此我还是建议内存在 512Mb 以上的 VPS,1Gb 为佳。
\n多提一嘴,现在不少应用都能以 PaaS 的形式部署在诸如 Heroku 的平台上,完全可以做到免费运行一个服务,没有必要租用 VPS、配置环境。
\nVPS 的硬盘也是一个挺重要的指标。按我的经验,VPS 至少要有 20G 的硬盘才够用。Debian 11 系统 + LNMP + NodeJS 环境就占用了 7-8G 的硬盘空间,剩下的 10G 可以用于存储应用数据。至于硬盘性能,只要不是太可怜,影响都不大。
\n",
+ "tags": [
+ "小鸡测评"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/afe45e8a.html",
+ "url": "https://blog.udon.eu.org/archives/afe45e8a.html",
+ "title": "我的第一台 VPS",
+ "date_published": "2021-12-21T15:30:00.000Z",
+ "content_html": "开一个新坑,测评一下所有我用过的小鸡,如果能赚到点 AFF 就更好了。
\n \n\n来谈一谈我的第一台 VPS 吧。
\n我的第一台 VPS 购于初中,仅用了一年便没有再续费了。因此没有任何性能、网络方面的测评,仅剩下我的感想了。没有续费的原因很简单:太™垃圾了。
\n年幼无知,对 VPS 性能、路由线路都一无所知的我被超低的价格蒙骗,入手了一台来自无名国人商家的小鸡。
\n印象中,那是一台 1core 512Mb 的小鸡,位于太平洋彼岸、再跨过美国全境的美东海岸。VPS 的网络差到经常连接不上 SSH。
\n说了这么多(并不多),就是想奉劝各位:别贪便宜。
\n",
+ "tags": [
+ "小鸡测评"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/category/\345\260\217\351\270\241\346\265\213\350\257\204/rss.xml" "b/category/\345\260\217\351\270\241\346\265\213\350\257\204/rss.xml"
new file mode 100644
index 00000000..743acb65
--- /dev/null
+++ "b/category/\345\260\217\351\270\241\346\265\213\350\257\204/rss.xml"
@@ -0,0 +1,221 @@
+
+
+
+ カレーうどん屋 • Posts by "小鸡测评" category
+ https://blog.udon.eu.org
+ カレーうどん屋.
+ zh-CN
+ Wed, 06 Apr 2022 22:15:00 +0800
+ Wed, 06 Apr 2022 22:15:00 +0800
+ 教程
+ 随笔
+ 软件
+ 小鸡测评
+ 3D 打印
+ 嵌入式开发
+ GitHub Actions
+ Hexo
+ DIY
+ -
+
https://blog.udon.eu.org/archives/28fa0729.html
+ Virmach Japan
+ https://blog.udon.eu.org/archives/28fa0729.html
+ 小鸡测评
+ Wed, 06 Apr 2022 22:15:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/6e832212.html
+ Deepvm 9929
+ https://blog.udon.eu.org/archives/6e832212.html
+ 小鸡测评
+ Mon, 10 Jan 2022 21:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/be3776eb.html
+ WikiHost CU4837
+ https://blog.udon.eu.org/archives/be3776eb.html
+ 小鸡测评
+ Fri, 31 Dec 2021 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/a7b78eea.html
+ Cloudcone 双十一特价鸡
+ https://blog.udon.eu.org/archives/a7b78eea.html
+ 小鸡测评
+ Fri, 24 Dec 2021 22:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/9732665c.html
+ WebHorizon NAT JP
+ https://blog.udon.eu.org/archives/9732665c.html
+ 小鸡测评
+ Thu, 23 Dec 2021 23:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/6f963444.html
+ Virmach 7.5刀年付特价机
+ https://blog.udon.eu.org/archives/6f963444.html
+ 小鸡测评
+ Wed, 22 Dec 2021 17:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/afe45e8a.html
+ 我的第一台 VPS
+ https://blog.udon.eu.org/archives/afe45e8a.html
+ 小鸡测评
+ Tue, 21 Dec 2021 23:30:00 +0800
+
+
+
+
diff --git "a/category/\346\225\231\347\250\213/atom.xml" "b/category/\346\225\231\347\250\213/atom.xml"
new file mode 100644
index 00000000..6a0023a1
--- /dev/null
+++ "b/category/\346\225\231\347\250\213/atom.xml"
@@ -0,0 +1,1007 @@
+
+
+ https://blog.udon.eu.org
+ カレーうどん屋 • Posts by "教程" category
+
+ 2023-04-15T16:00:00.000Z
+
+
+
+
+
+
+
+
+
+
+ https://blog.udon.eu.org/archives/8b68ddd6.html
+ 修复 UEFI 引导的 GRUB
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="问题与解决方法"><a href="#问题与解决方法" class="headerlink" title="问题与解决方法"></a>问题与解决方法</h2><h3 id="环境"><a href="#环境" class="headerlink" title="环境"></a>环境</h3><p>Manjaro Linux x86_64</p>
+<p>Kernel: 6.2.10-1-MANJARO</p>
+<p>使用 UEFI 引导</p>
+<h3 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h3><p>在 GRUB 尝试引导 Linux 内核时,出现如下错误:</p>
+<figure class="highlight smali"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs smali">error:<span class="hljs-built_in"> sparse </span>file<span class="hljs-built_in"> not </span>allowed.<br><br>452: out of range pointer: xxxxxxxxxx<br><br>Aborted. Press any key to exit.<br></code></pre></td></tr></table></figure>
+
+<p>用户将无法进入系统。</p>
+<h3 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h3><h4 id="进入恢复系统"><a href="#进入恢复系统" class="headerlink" title="进入恢复系统"></a>进入恢复系统</h4><p>插入 Manjaro LiveCD, 启动 Live 系统。</p>
+<h4 id="确定磁盘分区"><a href="#确定磁盘分区" class="headerlink" title="确定磁盘分区"></a>确定磁盘分区</h4><p>在 Live 系统中,使用 <code>fdisk -l</code> 查看磁盘分区情况,找到安装 Manjaro 的磁盘,假设为 <code>/dev/sda</code>。</p>
+<p>我的磁盘分区如下:</p>
+<figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs tap">设备 起点 末尾 扇区 大小 类型<br>/dev/sda1 <span class="hljs-number"> 2048 </span> <span class="hljs-number"> 821247 </span> <span class="hljs-number"> 819200 </span> 400M EFI 系统<br>/dev/sda2 <span class="hljs-number"> 821248 </span><span class="hljs-number"> 723390463 </span>722569216 344.5G Linux 文件系统<br>/dev/sda3 <span class="hljs-number"> 723390464 </span><span class="hljs-number"> 983437311 </span>260046848 124G Linux 文件系统<br>/dev/sda4 <span class="hljs-number"> 983437312 </span>1000214527 <span class="hljs-number"> 16777216 </span> 8G Linux 文件系统<br></code></pre></td></tr></table></figure>
+
+<p>可以确定,<code>/dev/sda1</code> 是 EFI 系统分区,<code>/dev/sda2</code> 是系统所在分区。</p>
+<h4 id="挂载分区"><a href="#挂载分区" class="headerlink" title="挂载分区"></a>挂载分区</h4><p>挂载系统分区:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">mount /dev/sda2 /mnt<br></code></pre></td></tr></table></figure>
+
+<p>将当前系统的工具分区挂载到 <code>/mnt</code> 下:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">mount --<span class="hljs-built_in">bind</span> /dev /mnt/dev<br>mount --<span class="hljs-built_in">bind</span> /proc /mnt/proc<br>mount --<span class="hljs-built_in">bind</span> /sys /mnt/sys<br></code></pre></td></tr></table></figure>
+
+<p>将 EFI 分区挂载到 <code>/mnt/boot/efi</code> 下:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">mount /dev/sda1 /mnt/boot/efi<br></code></pre></td></tr></table></figure>
+
+<h4 id="进入系统"><a href="#进入系统" class="headerlink" title="进入系统"></a>进入系统</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">chroot</span> /mnt<br></code></pre></td></tr></table></figure>
+
+<h4 id="重新安装-GRUB"><a href="#重新安装-GRUB" class="headerlink" title="重新安装 GRUB"></a>重新安装 GRUB</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable<br></code></pre></td></tr></table></figure>
+
+<p>具体参数需要根据实际情况进行修改。</p>
+<h4 id="在这之后"><a href="#在这之后" class="headerlink" title="在这之后"></a>在这之后</h4><p>重启,进入通过 GRUB 引导系统。</p>
+<p>在系统中使用 <code>sudo grub-install --recheck /dev/sda</code> 命令再次安装 GRUB,确保系统能够正常启动。</p>
+<h2 id="一些思考"><a href="#一些思考" class="headerlink" title="一些思考"></a>一些思考</h2><p>接下来的内容是我的整个修复流程,包含了如何在搜索引擎查找问题、根据文章内容调整目标等杂碎的内容,和我在修复过程中的一些感想。</p>
+<h3 id="为什么会出现这个问题"><a href="#为什么会出现这个问题" class="headerlink" title="为什么会出现这个问题"></a>为什么会出现这个问题</h3><p>不是很清楚。</p>
+<p>在启动 Manjaro 前我不小心打开了电脑里的 Windows 系统,但没有连接移动硬盘。</p>
+<p>按照以往的经验,这最多只会导致找不到 GRUB 的位置,手动指定 GRUB 所在分区就可以启动系统。</p>
+<p>但这次不大一样。</p>
+<p>在打开 GRUB 之后,尝试引导内核,就发现了这个问题。</p>
+<h3 id="初步解决思路"><a href="#初步解决思路" class="headerlink" title="初步解决思路"></a>初步解决思路</h3><p><del>立刻格式化磁盘,重新安装 Manjaro。</del></p>
+<p>我已经不是曾经那个只会重装的我了,这次我希望可以解决问题,而不是简单地重装。</p>
+<p>首先,我 Google 了这个错误,发现了几篇内容相关的文章。</p>
+<p><a href="https://forum.artixlinux.org/index.php/topic,4668.0.html">报错与我一致的文章</a>,但没有给出解决方案。</p>
+<p><a href="https://www.reddit.com/r/archlinux/comments/x2qb4w/grub_aborts_loading_linux_because_of_an_out_of/">要我删除 GRUB 和 UEFI 所在分区所有内容的文章</a>,有点可怕,不敢这么干。</p>
+<p><a href="https://bbs.archlinux.org/viewtopic.php?id=280230">提到应该重新安装 GRUB 的文章</a>,这还有点道理。</p>
+<p>于是,我的目标转变为重新安装 GRUB。</p>
+<h3 id="重新安装-GRUB-1"><a href="#重新安装-GRUB-1" class="headerlink" title="重新安装 GRUB"></a>重新安装 GRUB</h3><p>在之前遇到找不到 GRUB 分区的问题时,在手动引导然后进入系统后,我会执行 <code>sudo grub-install --recheck /dev/sda</code> 重新安装 GRUB,解决这个问题。</p>
+<p>那这次的觉得方案应该是差不多的……吧?</p>
+<p>不对啊,这次是在 LiveCD 的系统里操作,怎么能直接安装 GRUB 呢?</p>
+<p>这个问题比较难描述。</p>
+<p>我先是 Google <code>grub-install 修复 GRUB</code>,中文网站的内容都是关于在可以启动的系统下修复 GRUB 的,没有我需要的内容。</p>
+<p>然后我开始求助于 ChatGPT,输入的 Prompts 是:</p>
+<figure class="highlight livecodeserver"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs livecodeserver">I am <span class="hljs-keyword">using</span> Manjaro <span class="hljs-keyword">with</span> GRUB.<br>When I booted <span class="hljs-keyword">into</span> <span class="hljs-keyword">the</span> <span class="hljs-keyword">system</span>, <span class="hljs-keyword">it</span> says <span class="hljs-string">"sparse file not allowed 452 out of range pointer"</span>. How <span class="hljs-built_in">to</span> fix <span class="hljs-keyword">it</span>?<br></code></pre></td></tr></table></figure>
+
+<p>不难发现,我并没有说明我使用的是 UEFI 引导,这直接影响到了 ChatGPT 回复的准确性。</p>
+<p>ChatGPT 给出的修复步骤与上述的相差不大,只是在挂载系统分区和工具分区后,直接尝试执行 <code>grub-install</code> 命令,尝试修复。</p>
+<p><code>grub-install</code> 返回错误 <code>this gpt partition label contains no bios boot partition</code> 把我弄得更懵了。</p>
+<p>再次 Google 这个问题,发现了 <a href="https://superuser.com/questions/903112/grub2-install-this-gpt-partition-label-contains-no-bios-boot-partition">这篇在长篇大论讲 GRUB 的文章</a>,虽然没有给出解决方案,但它让我意识到 UEFI 和 Legacy BIOS 两种启动方式的区别。</p>
+<h3 id="UEFI-和-Legacy-BIOS"><a href="#UEFI-和-Legacy-BIOS" class="headerlink" title="UEFI 和 Legacy BIOS"></a>UEFI 和 Legacy BIOS</h3><p>UEFI 和 Legacy BIOS 是两种启动方式,它们的区别在于,Legacy BIOS 是在 BIOS 中直接加载内核,而 UEFI 是在 BIOS 中加载 EFI 系统,然后由 EFI 系统加载内核。</p>
+<p>使用 UEFI 引导的系统一般都有一个 200MB 到 400MB 的 EFI 系统分区,用于存放 EFI 系统。在上述的,我的硬盘分区中可以看到。</p>
+<p>使用 <code>findmnt</code> 命令可以查看当前系统的挂载情况,其中 <code>TARGET</code> 列就是挂载点,<code>SOURCE</code> 列就是挂载的分区。</p>
+<p>EFI 分区的挂载情况为:</p>
+<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs gradle">TARGET <span class="hljs-keyword">SOURCE</span> FSTYPE <span class="hljs-keyword">OPTIONS</span><br><span class="hljs-regexp">/boot/</span>efi <span class="hljs-regexp">/dev/</span>sda1 vfat rw,relatime,fm<br></code></pre></td></tr></table></figure>
+
+<p>可以看到,<code>/boot/efi</code> 里的内容正是 EFI 系统分区的内容。(我也是刚学到这个知识的)</p>
+<h3 id="解决-UEFI-相关问题"><a href="#解决-UEFI-相关问题" class="headerlink" title="解决 UEFI 相关问题"></a>解决 UEFI 相关问题</h3><p>在修复过程中,我是通过 Google 发现上述的问题。</p>
+<p><a href="https://superuser.com/questions/1390428/grub-install-warning-this-gpt-partition-label-contains-no-bios-boot-partition">这篇文章</a> 给了我莫大的帮助。</p>
+<p>其中提到了 EFI 分区,也提到了如何正确安装 UEFI 引导的 GRUB:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> grub-install --target=x86_64-efi --efi-directory=/boot/efi<br><span class="hljs-built_in">sudo</span> grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable<br></code></pre></td></tr></table></figure>
+
+<p>在补充挂载 EFI 分区、切换 Root 目录后,执行第一条命令,发现有错误。尝试执行第二条命令,发现没有错误,代表 GRUB 已经重新安装成功。</p>
+<p>此时我想起来,在之前安装 GRUB 时,会提示 <code>正在为 x86_64-efi 平台进行安装</code>,我才意识到前面的修复过程并没有去指定平台。</p>
+<h3 id="总结一下"><a href="#总结一下" class="headerlink" title="总结一下"></a>总结一下</h3><p>总之,这就是我此次修复的心路历程。我没有研究过 UEFI 和 Legacy BIOS 的区别,也没有研究过 GRUB 的安装过程,所以在修复过程中,我是通过 Google 和 ChatGPT 的帮助才解决了这个问题。</p>
+<p>希望这个探索过程能给你一些启发吧。</p>
+<hr>
+<p>此文章以 <em>我无所谓 By 不 By 什么 AI,对我有帮助的文章就是好文章</em> 标识发布。</p>
+
+
+ 2023-04-15T16:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/8b115688.html
+ 使用 Docker Compose 部署音乐服务器 Navidrome
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="服务介绍"><a href="#服务介绍" class="headerlink" title="服务介绍"></a>服务介绍</h2><p>Navidrome 是一款兼容 Subsonic API 的开源音乐服务器软件,它提供了一个不错的 WebUI,也可以将支持 Subsonic API 的客户端接入。</p>
+<p>目前项目正处在活跃开发中,各种各样的新功能正在陆续加入。</p>
+<h2 id="我的客户端选择"><a href="#我的客户端选择" class="headerlink" title="我的客户端选择"></a>我的客户端选择</h2><h3 id="电脑端"><a href="#电脑端" class="headerlink" title="电脑端"></a>电脑端</h3><p>自带 WebUI, <a href="https://github.com/jeffvli/sonixd">Sonixd</a> 【跨平台】</p>
+<h3 id="iOS"><a href="#iOS" class="headerlink" title="iOS"></a>iOS</h3><p><a href="https://apps.apple.com/us/app/play-sub-music-streamer/id955329386">play:sub</a> 【付费软件 4.99$】</p>
+<h2 id="部署方式"><a href="#部署方式" class="headerlink" title="部署方式"></a>部署方式</h2><p>采用 Docker Compose 部署 Navidrome,使用 Nginx 作为反向代理。</p>
+<h2 id="Docker-Compose-配置文件"><a href="#Docker-Compose-配置文件" class="headerlink" title="Docker Compose 配置文件"></a>Docker Compose 配置文件</h2><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3"</span><br><span class="hljs-attr">services:</span><br> <span class="hljs-attr">navidrome:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">deluan/navidrome:latest</span><br> <span class="hljs-attr">container_name:</span> <span class="hljs-string">navidrome</span><br> <span class="hljs-attr">user:</span> <span class="hljs-number">1000</span><span class="hljs-string">:1000</span> <span class="hljs-comment"># should be owner of volumes</span><br> <span class="hljs-attr">ports:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">"127.0.0.1:4533:4533"</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span><br> <span class="hljs-attr">environment:</span><br> <span class="hljs-attr">ND_SCANSCHEDULE:</span> <span class="hljs-string">1h</span><br> <span class="hljs-attr">ND_LOGLEVEL:</span> <span class="hljs-string">info</span> <br> <span class="hljs-attr">ND_SESSIONTIMEOUT:</span> <span class="hljs-string">24h</span><br> <span class="hljs-attr">ND_BASEURL:</span> <span class="hljs-string">""</span><br> <span class="hljs-attr">ND_SEARCHFULLSTRING:</span> <span class="hljs-literal">true</span><br> <span class="hljs-comment"># Optional: fetch artist images from spotify</span><br> <span class="hljs-attr">ND_SPOTIFY_ID:</span><br> <span class="hljs-attr">ND_SPOTIFY_SECRET:</span><br> <span class="hljs-comment"># Optional: fetch artist information from last.fm</span><br> <span class="hljs-attr">ND_LASTFM_APIKEY:</span><br> <span class="hljs-attr">ND_LASTFM_SECRET:</span><br> <span class="hljs-attr">ND_LASTFM_LANGUAGE:</span> <span class="hljs-string">en</span><br> <span class="hljs-attr">volumes:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">"./data:/data"</span> <span class="hljs-comment"># Navidrome data</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">"/APTH-TO/navidrome-music:/music:ro"</span> <span class="hljs-comment"># Music folder</span><br></code></pre></td></tr></table></figure>
+<p>使用命令 <code>docker compose up -d</code> 启动服务。</p>
+<h2 id="Nginx-配置文件"><a href="#Nginx-配置文件" class="headerlink" title="Nginx 配置文件"></a>Nginx 配置文件</h2><p>我建议使用 DigitalOcean 的 Nginx 配置生产工具,示例配置如下:</p>
+<p><a href="https://www.digitalocean.com/community/tools/nginx?domains.0.server.domain=music.example.com&domains.0.php.php=false&domains.0.reverseProxy.reverseProxy=true&domains.0.reverseProxy.proxyPass=http://127.0.0.1:4533&domains.0.routing.root=false&domains.0.routing.index=index.html&domains.0.routing.fallbackHtml=true&global.app.lang=zhCN%5C">示例配置</a></p>
+<p>也可参考下述配置,此为 DigitalOcean 生成配置的简化版:</p>
+<figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-section">server</span> {<br> <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">server_name</span> music.example.com;<br><br> <span class="hljs-comment"># SSL</span><br> <span class="hljs-attribute">ssl_certificate</span> /etc/letsencrypt/live/music.example.com/fullchain.pem;<br> <span class="hljs-attribute">ssl_certificate_key</span> /etc/letsencrypt/live/music.example.com/privkey.pem;<br> <span class="hljs-attribute">ssl_trusted_certificate</span> /etc/letsencrypt/live/music.example.com/chain.pem;<br><br> <span class="hljs-comment"># logging</span><br> <span class="hljs-attribute">access_log</span> /var/log/nginx/access.log combined buffer=<span class="hljs-number">512k</span> flush=<span class="hljs-number">1m</span>;<br> <span class="hljs-attribute">error_log</span> /var/log/nginx/<span class="hljs-literal">error</span>.log <span class="hljs-literal">warn</span>;<br><br> <span class="hljs-comment"># reverse proxy</span><br> <span class="hljs-section">location</span> / {<br> <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;<br> <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:4533;<br> }<br>}<br><br><span class="hljs-comment"># subdomains redirect</span><br><span class="hljs-section">server</span> {<br> <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">server_name</span> <span class="hljs-regexp">*.music.example.com</span>;<br><br> <span class="hljs-comment"># SSL</span><br> <span class="hljs-attribute">ssl_certificate</span> /etc/letsencrypt/live/music.example.com/fullchain.pem;<br> <span class="hljs-attribute">ssl_certificate_key</span> /etc/letsencrypt/live/music.example.com/privkey.pem;<br> <span class="hljs-attribute">ssl_trusted_certificate</span> /etc/letsencrypt/live/music.example.com/chain.pem;<br> <span class="hljs-attribute">return</span> <span class="hljs-number">301</span> https://music.example.com<span class="hljs-variable">$request_uri</span>;<br>}<br></code></pre></td></tr></table></figure>
+
+<h2 id="音乐管理"><a href="#音乐管理" class="headerlink" title="音乐管理"></a>音乐管理</h2><p>我一直以文件夹分类的方式管理音乐,但 Navidrome 并不支持文件夹分类。它是根据音乐标签来分类的,例如按照歌手、专辑等依据分类歌曲。</p>
+<p>因此,若想使用 Navidrome,需要对音乐进行标签管理。</p>
+<p>大约两年前,我写了 <a href="https://blog.udon.eu.org/archives/6b40e5ad.html">一篇文章</a> 介绍使用 Music Tag 和 Foobar2000 两款软件来管理音乐。</p>
+<p>Music Tag 的标签源是网易云音乐、豆瓣音乐、QQ 音乐等国内音乐平台,说实话,这些平台的音乐标签质量都不是很好,所以我一直在寻找更好的音乐标签源。</p>
+<p>直到我发现了 <a href="https://musicbrainz.org/">MusicBrainz</a>,这是一个开源的音乐标签数据库,任何人都可以为它贡献标签。在体验之后,我发现 MusicBrainz 的音乐标签质量要比国内音乐平台的标签质量好很多,所以我决定将 MusicBrainz 作为我的音乐标签源。</p>
+<p>我使用 <a href="https://picard.musicbrainz.org/">Picard</a> 这款软件来从 MusicBrainz 获取音乐标签。</p>
+<p>将音乐导入 Picard 后,它会自动从 MusicBrainz 获取音乐标签,然后将标签写入音乐文件,十分方便。</p>
+<h2 id="开始使用"><a href="#开始使用" class="headerlink" title="开始使用"></a>开始使用</h2><p>不论是使用 Navidrome 自带的 Web 界面,还是使用兼容 Subsonic API 的客户端,只要连接到 Navidrome,便可开始享受你的私人音乐库。</p>
+
+
+ 2023-01-31T04:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/f9bfe16a.html
+ 使用 Docker Compose 部署 Keycloak 20
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="部署方式"><a href="#部署方式" class="headerlink" title="部署方式"></a>部署方式</h2><p>采用 Docker Compose 部署,使用 Postgres 作为数据库,使用 Nginx 作为反向代理。</p>
+<h2 id="Docker-Compose-配置文件"><a href="#Docker-Compose-配置文件" class="headerlink" title="Docker Compose 配置文件"></a>Docker Compose 配置文件</h2><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3'</span><br><br><span class="hljs-attr">services:</span><br> <span class="hljs-attr">keycloak:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">quay.io/keycloak/keycloak:latest</span><br> <span class="hljs-attr">environment:</span><br> <span class="hljs-attr">KC_DB:</span> <span class="hljs-string">postgres</span><br> <span class="hljs-attr">KC_DB_URL:</span> <span class="hljs-string">jdbc:postgresql://db:5432/keycloak</span><br> <span class="hljs-attr">KC_DB_USERNAME:</span> <span class="hljs-string">keycloak</span><br> <span class="hljs-attr">KC_DB_PASSWORD:</span> <span class="hljs-string">keycloak</span><br> <span class="hljs-attr">KC_HTTP_ENABLED:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># 开启 HTTP</span><br> <span class="hljs-attr">KC_HOSTNAME_STRICT:</span> <span class="hljs-literal">false</span><br> <span class="hljs-attr">KC_HOSTNAME_STRICT_HTTPS:</span> <span class="hljs-literal">false</span><br> <span class="hljs-attr">KC_HTTP_RELATIVE_PATH:</span> <span class="hljs-string">'/'</span> <span class="hljs-comment"># Keycloak 应用的相对路径</span><br> <span class="hljs-attr">KC_HTTP_PORT:</span> <span class="hljs-number">8080</span> <span class="hljs-comment"># HTTP 端口</span><br> <span class="hljs-attr">KEYCLOAK_ADMIN:</span> <span class="hljs-string">MY_USERNAME</span> <span class="hljs-comment"># 管理员账号,仅初始化时使用</span><br> <span class="hljs-attr">KEYCLOAK_ADMIN_PASSWORD:</span> <span class="hljs-string">MY_PASSWORD</span> <span class="hljs-comment"># 管理员密码,仅初始化时使用</span><br> <span class="hljs-attr">PROXY_ADDRESS_FORWARDING:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># 使用反向代理必须开启</span><br> <span class="hljs-attr">KC_PROXY:</span> <span class="hljs-string">edge</span> <span class="hljs-comment"># 反向代理模式,详见文档</span><br> <span class="hljs-attr">entrypoint:</span> <span class="hljs-string">/opt/keycloak/bin/kc.sh</span> <span class="hljs-string">start</span> <span class="hljs-comment"># 第一次运行后可以加上 --optimized 参数,加快二次启动速度</span><br> <span class="hljs-attr">ports:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:18080:8080</span> <span class="hljs-comment"># Keycloak 应用端口</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span><br> <span class="hljs-attr">db:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:14</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span><br> <span class="hljs-attr">environment:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_USER=keycloak</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_PASSWORD=keycloak</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_DB=keycloak</span><br> <span class="hljs-attr">volumes:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">./postgres-data:/var/lib/postgresql/data</span> <span class="hljs-comment"># 数据库数据保存位置</span><br></code></pre></td></tr></table></figure>
+
+<p>使用命令 <code>docker compose up -d</code> 启动服务。</p>
+<h2 id="Nginx-配置"><a href="#Nginx-配置" class="headerlink" title="Nginx 配置"></a>Nginx 配置</h2><p>我建议使用 DigitalOcean 的 Nginx 配置生产工具,示例配置如下:</p>
+<p><a href="https://www.digitalocean.com/community/tools/nginx?domains.0.server.domain=auth.example.com&domains.0.php.php=false&domains.0.reverseProxy.reverseProxy=true&domains.0.reverseProxy.proxyPass=http://127.0.0.1:18080&domains.0.routing.root=false&global.app.lang=zhCN">示例配置</a></p>
+<p>也可参考下述配置,此为 DigitalOcean 生成配置的简化版:</p>
+<figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-section">server</span> {<br> <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">server_name</span> auth.example.com;<br><br> <span class="hljs-comment"># SSL</span><br> <span class="hljs-attribute">ssl_certificate</span> /etc/letsencrypt/live/auth.example.com/fullchain.pem;<br> <span class="hljs-attribute">ssl_certificate_key</span> /etc/letsencrypt/live/auth.example.com/privkey.pem;<br> <span class="hljs-attribute">ssl_trusted_certificate</span> /etc/letsencrypt/live/auth.example.com/chain.pem;<br><br> <span class="hljs-comment"># logging</span><br> <span class="hljs-attribute">access_log</span> /var/log/nginx/access.log combined buffer=<span class="hljs-number">512k</span> flush=<span class="hljs-number">1m</span>;<br> <span class="hljs-attribute">error_log</span> /var/log/nginx/<span class="hljs-literal">error</span>.log <span class="hljs-literal">warn</span>;<br><br> <span class="hljs-comment"># reverse proxy</span><br> <span class="hljs-section">location</span> / {<br> <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Auth-Request-Redirect <span class="hljs-variable">$request_uri</span>;<br> <span class="hljs-attribute">proxy_buffer_size</span> <span class="hljs-number">128k</span>;<br> <span class="hljs-attribute">proxy_buffers</span> <span class="hljs-number">4</span> <span class="hljs-number">256k</span>;<br> <span class="hljs-attribute">proxy_busy_buffers_size</span> <span class="hljs-number">256k</span>;<br> <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:18080;<br> }<br><br> <span class="hljs-section">location</span> /auth/realms {<br> <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Auth-Request-Redirect <span class="hljs-variable">$request_uri</span>;<br> <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:18080;<br> }<br><br> <span class="hljs-section">location</span> /auth/resources {<br> <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Auth-Request-Redirect <span class="hljs-variable">$request_uri</span>;<br> <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:18080;<br> }<br><br> <span class="hljs-section">location</span> /auth/js {<br> <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Auth-Request-Redirect <span class="hljs-variable">$request_uri</span>;<br> <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:18080;<br> }<br>}<br><br><span class="hljs-comment"># subdomains redirect</span><br><span class="hljs-section">server</span> {<br> <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">server_name</span> <span class="hljs-regexp">*.auth.example.com</span>;<br><br> <span class="hljs-comment"># SSL</span><br> <span class="hljs-attribute">ssl_certificate</span> /etc/letsencrypt/live/auth.example.com/fullchain.pem;<br> <span class="hljs-attribute">ssl_certificate_key</span> /etc/letsencrypt/live/auth.example.com/privkey.pem;<br> <span class="hljs-attribute">ssl_trusted_certificate</span> /etc/letsencrypt/live/auth.example.com/chain.pem;<br> <span class="hljs-attribute">return</span> <span class="hljs-number">301</span> https://auth.example.com<span class="hljs-variable">$request_uri</span>;<br>}<br></code></pre></td></tr></table></figure>
+
+<h2 id="配置-Keycloak"><a href="#配置-Keycloak" class="headerlink" title="配置 Keycloak"></a>配置 Keycloak</h2><h3 id="创建-Realm"><a href="#创建-Realm" class="headerlink" title="创建 Realm"></a>创建 Realm</h3><p>打开 <a href="http://127.0.0.1:18080/">Keycloak 地址</a>,界面如下。</p>
+<p><img src="/images/2023-01-22/01.jpg" alt="Keycloak 界面"></p>
+<p>选择 <code>Administration Console</code>,进入管理界面。</p>
+<p><img src="/images/2023-01-22/02.jpg" alt="管理界面"></p>
+<p>选择箭头指向的下拉菜单,选择 <code>Add realm</code>,创建一个新的 Realm。</p>
+<p><img src="/images/2023-01-22/03.jpg" alt="创建 Realm"></p>
+<p>填写 Realm 名称,点击 <code>Create</code>。</p>
+<h3 id="创建-Client"><a href="#创建-Client" class="headerlink" title="创建 Client"></a>创建 Client</h3><p><img src="/images/2023-01-22/04.jpg" alt="管理界面"></p>
+<p>选择 <code>Clients</code>。</p>
+<p><img src="/images/2023-01-22/05.jpg" alt="Client 管理界面"></p>
+<p>点击 <code>Create client</code>。</p>
+<p><img src="/images/2023-01-22/06.jpg" alt="创建 Client"></p>
+<p>填写 Client 相关信息,点击 <code>Next</code>。</p>
+<p><img src="/images/2023-01-22/07.jpg" alt="配置 Client"></p>
+<p>按需求选择 Client 的配置,点击 <code>Save</code>。</p>
+<p><img src="/images/2023-01-22/08.jpg" alt="Client 创建完成"></p>
+<p>至此,Keycloak 配置完成,且创建了第一个测试用 Client。</p>
+<h3 id="测试-Client"><a href="#测试-Client" class="headerlink" title="测试 Client"></a>测试 Client</h3><p>可根据 <a href="https://www.keycloak.org/getting-started/getting-started-docker#_secure_your_first_app">官方教程</a> 测试该 Client。</p>
+<h2 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h2><p>上述便是使用 Docker Compose 部署 Keycloak 20 的方法,我们顺利创建了第一个测试用 Client,接下来可以根据自己的需求进行配置。</p>
+
+
+ 2023-01-22T12:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/e74a90f2.html
+ 使用再生龙 Clonezilla 备份操作系统
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>近日购入了一只闪迪的 CZ880 Extreme PRO 固态U盘来装 Manjaro。</p>
+<p>虽然U盘本体是终身质保的,但数据无价,配置一遍系统就要花大把的时间。遂有了备份U盘内整个系统的念头。</p>
+<p>下面跟着我使用再生龙 Clonezilla 把U盘里的系统备份一下吧!</p>
+<h3 id="事先准备"><a href="#事先准备" class="headerlink" title="事先准备"></a>事先准备</h3><p>再生龙是运行在独立操作系统 (Debian/Ubuntu) 上的一套软件,因此需要制作启动盘,或使用 Ventoy 等多系统启动方案。(实测 YUMI 无法启动再生龙,故建议使用 Ventoy)</p>
+<h4 id="制作启动盘"><a href="#制作启动盘" class="headerlink" title="制作启动盘"></a>制作启动盘</h4><p>前往 <a href="https://rufus.ie/zh/">Rufus 官网</a> 下载 Rufus 启动盘制作工具。</p>
+<p>前往 <a href="https://clonezilla.org/downloads/download.php?branch=stable">Clonezilla 官网</a> 下载再生龙映像。</p>
+<p>使用 Rufus 将再生龙映像烧写进另一个U盘即可。</p>
+<p><strong>U盘内数据将丢失,请做好备份!</strong></p>
+<h4 id="使用多系统启动"><a href="#使用多系统启动" class="headerlink" title="使用多系统启动"></a>使用多系统启动</h4><p>前往 <a href="https://www.ventoy.net/">Ventoy 官网</a> 下载安装包(兼容 Windows 与 Linux),将你的另一个U盘制作为 Ventoy 多启动盘。</p>
+<p>前往 <a href="https://clonezilla.org/downloads/download.php?branch=stable">Clonezilla 官网</a> 下载再生龙映像。</p>
+<p>将再生龙映像拷贝至 Ventoy 多启动盘中。</p>
+<p>选择你想存放备份数据的目的地,创建一个存放备份映像的文件夹(注意目录名称中不能带有空格)。</p>
+<p>剧透一下,40G 的系统盘备份之后大约占了 16-17G. 请留出足够的空间(建议和待备份的数据等大小)。</p>
+<h3 id="开始备份"><a href="#开始备份" class="headerlink" title="开始备份"></a>开始备份</h3><p>瞎眼警告:由于没有合适的截屏方式,我很不愿意地采取了 拍 屏 的方式,敬请谅解。</p>
+<h4 id="启动再生龙系统"><a href="#启动再生龙系统" class="headerlink" title="启动再生龙系统"></a>启动再生龙系统</h4><p>确保你的电脑关闭了安全启动,若还打开着,需要在 BIOS 中将其关闭。</p>
+<p>插入刚刚制作好的启动盘/Ventoy 多启动盘,在电脑启动时猛敲键盘的…某个键,这因电脑型号而异,打开启动菜单。</p>
+<p>选择插入的启动盘/多启动盘。</p>
+<p>启动盘用户若没有太大的兼容性问题,就能看到再生龙的启动菜单。</p>
+<p>多启动盘用户还要再多一步,在 Ventoy 菜单内选中再生龙的映像,如下图所示,即可打开再生龙的启动菜单。</p>
+<p><img src="/images/2022-08-12/1.jpeg" alt="Ventoy 多启动菜单"></p>
+<p>P.S. 我的笔记本兴许和 Ventoy 的 UEFI 模式相性不大好,在 BIOS 中开启了 Lagacy 兼容模式后,使用 Legacy 模式才能开启 Ventoy。</p>
+<h4 id="选择再生龙启动方式"><a href="#选择再生龙启动方式" class="headerlink" title="选择再生龙启动方式"></a>选择再生龙启动方式</h4><p><img src="/images/2022-08-12/2.jpeg" alt="再生龙启动菜单"></p>
+<p>经典的 GRUB 启动菜单,一般来说选择默认的第一项启动方式即可。</p>
+<h5 id="VGA-启动花屏"><a href="#VGA-启动花屏" class="headerlink" title="VGA 启动花屏"></a>VGA 启动花屏</h5><p>我的电脑遇到了在 VGA 800x600 模式下花屏的问题。</p>
+<p>最终进入 <code>Other mods of Clonezilla live</code> 菜单,</p>
+<p><img src="/images/2022-08-12/3.jpeg" alt="其他启动模式"></p>
+<p>选择了上图中的 KVM & To RAM 模式,可以正常启动了。</p>
+<h5 id="USB-口不够用的用户"><a href="#USB-口不够用的用户" class="headerlink" title="USB 口不够用的用户"></a>USB 口不够用的用户</h5><p>我这台笔记本只有两个 USB 口,其中一个要给备份源头 CZ880,另一个则要给移动硬盘,故选择了 <code>To RAM</code> 模式,将再生龙载入内存,就可以拔掉多启动U盘,空出 USB 口给移动硬盘了。</p>
+<h4 id="语言配置"><a href="#语言配置" class="headerlink" title="语言配置"></a>语言配置</h4><p><img src="/images/2022-08-12/4.jpeg" alt="语言配置"></p>
+<p>选择自己想用的语言即可。</p>
+<p><img src="/images/2022-08-12/5.jpeg" alt="键盘配置"></p>
+<p>保持默认配置即可。</p>
+<h4 id="备份配置"><a href="#备份配置" class="headerlink" title="备份配置"></a>备份配置</h4><p><img src="/images/2022-08-12/6.jpeg" alt="功能选择"></p>
+<p>我们选 <code>Start Clonezilla 使用再生龙</code>。</p>
+<p>命令行可以在熟悉了配置之后使用。</p>
+<p><img src="/images/2022-08-12/7.jpeg" alt="备份模式选择"></p>
+<p>此处我们选择第一项 <code>device-image 硬盘/分区[存到/来自]镜像文件</code>。</p>
+<p>若想进行两盘对拷,可以选择第二项。我还没有尝试过。</p>
+<h4 id="挂载存储目录"><a href="#挂载存储目录" class="headerlink" title="挂载存储目录"></a>挂载存储目录</h4><p><img src="/images/2022-08-12/8.jpeg" alt="存储目录选择"></p>
+<p>这次我打算使用移动硬盘备份系统,故选择第一项 <code>local dev 使用本机的分区</code>。</p>
+<p><img src="/images/2022-08-12/10.jpeg" alt="插入 USB 设备提示"></p>
+<p>随后,再生龙会提示插入想要挂载的 USB 设备,按照提示做即可。</p>
+<p><img src="/images/2022-08-12/11.jpeg" alt="检测到的存储设备"></p>
+<p>此时画面会动态显示系统识别到的存储设备。看到期望的目标设备时,按下 <code>Ctrl-C</code> 停止搜索。</p>
+<p><img src="/images/2022-08-12/12.jpeg" alt="分区选择"></p>
+<p>在扫描完电脑当前安装的所有硬盘的分区后,你需要选择备份镜像文件存放的那个分区。</p>
+<p>如图,我希望备份到大小为 1.8T 的移动硬盘上,故选择最后一项 <code>sdc2</code>。</p>
+<p><img src="/images/2022-08-12/13.jpeg" alt="是否检查并修复文件系统"></p>
+<p>随后,再生龙询问你是否需要检查并修复挂载的文件系统,我们选第一项否就好了。</p>
+<p><img src="/images/2022-08-12/14.jpeg" alt="备份位置选择"></p>
+<p>接着,就是选择备份镜像存放的位置。</p>
+<p>使用键盘的方向键选择目录,使用 <code>Tab</code> 跳转到下方的选项,选择 <code>Browse</code> 并敲击回车就可以进入到此目录。</p>
+<p>若希望在选中目录下存放备份镜像文件(是一个文件夹),就可以选择 <code>Done</code> 选项,回车确认。</p>
+<p><img src="/images/2022-08-12/15.jpeg" alt="是否检查镜像可还原性"></p>
+<p>系统询问是否检查生成的备份镜像的可还原性,这里我们选是,多花一点时间能确保备份的完整性。</p>
+<p><img src="/images/2022-08-12/16.jpeg" alt="是否对镜像加密"></p>
+<p>镜像加密,依个人喜好选择。</p>
+<p><img src="/images/2022-08-12/18.jpeg" alt="备份模式确认"></p>
+<p>待上述配置完成后,系统会向你再次确认备份的内容与目的地。</p>
+<p>确认无误后输入 <code>y</code> 并敲击回车继续。</p>
+<h4 id="简单模式-高级模式"><a href="#简单模式-高级模式" class="headerlink" title="简单模式/高级模式"></a>简单模式/高级模式</h4><p>此时应该有一个模式选择,问你想要使用简单模式还是专家模式。</p>
+<p>我建议选择 专家模式,简单模式能选择的参数较少。</p>
+<p><img src="/images/2022-08-12/20.jpeg" alt="模式选择"></p>
+<p>接下来的三个选项,全部保持默认配置即可。</p>
+<p><img src="/images/2022-08-12/21.jpeg" alt="高级设置1"></p>
+<p><img src="/images/2022-08-12/22.jpeg" alt="高级设置2"></p>
+<p><img src="/images/2022-08-12/23.jpeg" alt="高级设置3"></p>
+<h4 id="压缩方式选择"><a href="#压缩方式选择" class="headerlink" title="压缩方式选择"></a>压缩方式选择</h4><p><img src="/images/2022-08-12/24.jpeg" alt="压缩方式选择"></p>
+<p>此处选择第三项 <code>-z2p 使用并行 bzip2 压缩</code>。</p>
+<p>实测 bzip2 压缩速度比较快,产生的备份镜像的体积也不算大。</p>
+<p>下图为选择了第一项 <code>-z1p 使用并行的 gzip 压缩</code> 的速度:</p>
+<p><img src="/images/2022-08-12/19.jpeg" alt="并行 gzip 压缩速度"></p>
+<p>下图为选择了第三项 <code>-z2p 使用并行 bzip2 压缩</code> 的速度:</p>
+<p><img src="/images/2022-08-12/26.jpeg" alt="并行 bzip2 压缩速度"></p>
+<p>可以看出 bzip2 压缩速度比 gzip 快了8倍。</p>
+<p>其他压缩方式的速度,待我测试之后更新文章。</p>
+<p><img src="/images/2022-08-12/25.jpeg" alt="分卷大小配置"></p>
+<p>分卷大小配置保持默认即可。</p>
+<h4 id="备份镜像检查"><a href="#备份镜像检查" class="headerlink" title="备份镜像检查"></a>备份镜像检查</h4><p>待备份完成后,再生龙还会进行一次备份镜像的可还原性检查,如下图:</p>
+<p><img src="/images/2022-08-12/27.jpeg" alt="可还原性检查"></p>
+<p>若得到下图的提示,则备份镜像生成成功了。</p>
+<p><img src="/images/2022-08-12/28.jpeg" alt="可还原性检查完成"></p>
+<p>随后,选择按照意愿选择备份结束后的操作即可。</p>
+<p><img src="/images/2022-08-12/29.jpeg" alt="备份结束后操作选择"></p>
+<p>至此,再生龙 CLonezilla 的基础教学就结束了,你已经学会了如何使用再生龙的图形界面进行备份。</p>
+<p>下面是一些再生龙的高阶(大概很高级)使用方法。</p>
+<h3 id="高级操作"><a href="#高级操作" class="headerlink" title="高级操作"></a>高级操作</h3><h4 id="使用无线网络备份"><a href="#使用无线网络备份" class="headerlink" title="使用无线网络备份"></a>使用无线网络备份</h4><p>上文中,我的电脑仅有两个 USB 口,为备份的流程增添了不必要的麻烦。</p>
+<p>能否使用 Wi-Fi 将备份镜像推送至家中的 NAS 呢?</p>
+<p>再生龙内置了许多通过无线/有线网络备份的方法,如下图:</p>
+<p><img src="/images/2022-08-12/30.jpeg" alt="备份选项"></p>
+<p>我们尝试使用 Webdav 来远程备份吧!</p>
+<h5 id="利与弊"><a href="#利与弊" class="headerlink" title="利与弊"></a>利与弊</h5><p>使用 Wi-Fi 备份可以摆脱线缆,更加轻松而优雅地进行备份。</p>
+<p>然而,由于通过 Wi-Fi 或者一切网络传输数据的速度仍然无法比肩有线传输,备份所消耗的时间将是备份至本地磁盘的 3-4 倍。</p>
+<p>备份我U盘中的 40G 的 Manjaro 系统用时 30min 左右。</p>
+<p>倘若你有大把的时间,或家中的内网速度足够快,大可使用无线备份。品着咖啡,看着数据上云(笑)。</p>
+<h5 id="预先准备"><a href="#预先准备" class="headerlink" title="预先准备"></a>预先准备</h5><p>上文中我们选择了基于 Debian 的 Clonezilla Stable 版本,遗憾的是 Debian 系统中并未携带太多驱动程序,因此识别不到我的 Intel AX200 无线网卡。</p>
+<p>经过测试,基于 Ubuntu 的 <a href="https://clonezilla.org/downloads/download.php?branch=alternative">Clonezilla Alternative Stable</a> 版本可以识别到 AX200 网卡。</p>
+<p>点击上方链接即可下载 Clonezilla Alternative Stable 版本的映像。</p>
+<p>重新烧写启动U盘/拷贝映像至多启动U盘即可。</p>
+<h6 id="又遇到了启动问题"><a href="#又遇到了启动问题" class="headerlink" title="又遇到了启动问题"></a>又遇到了启动问题</h6><p>使用基于 Ubuntu 的再生龙,上文中使用的 <code>KVM</code> 模式变得无法打开了,且 <code>VGA 800x600</code> 模式是一样的花屏。</p>
+<p>在一番尝试之后,我发现藏在更多启动选项菜单里的 <code>VGA 1024x768</code> 模式可以正常显示。看来基于 Debian 的再生龙也可以使用这个模式。</p>
+<h5 id="开始备份-1"><a href="#开始备份-1" class="headerlink" title="开始备份"></a>开始备份</h5><p><img src="/images/2022-08-12/31.jpeg" alt="网络管理"></p>
+<p>选择了非本地的镜像存储位置后,系统将开启上图的网络管理菜单。</p>
+<p>选择第一项 <code>Edit a connection</code>。</p>
+<p><img src="/images/2022-08-12/32.jpeg" alt="连接管理"></p>
+<p>选择 <code>Add</code> 选项,在弹出菜单中 <code>Wi-Fi</code>。</p>
+<p><img src="/images/2022-08-12/33.jpeg" alt="添加 Wi-Fi"></p>
+<p><code>Profile name</code> 随意填写;</p>
+<p><code>Device</code> 一般填写 <code>wlan0</code>,系统的第一块无线网卡;</p>
+<p>接着,按照自己的情况填写图中划线的三个配置即可。</p>
+<p><img src="/images/2022-08-12/34.jpeg" alt="连接状态"></p>
+<p>保存 Wi-Fi 配置后,就能看到当前配置的连接状态。</p>
+<p>若当前配置名前带 <code>*</code>,且右侧选项为 <code>Deactivate</code>,则 Wi-Fi 已连接成功。</p>
+<p><img src="/images/2022-08-12/35.jpeg" alt="填写 Webdav 服务器地址"></p>
+<p>接着,系统要求填写 Webdav 地址。</p>
+<p><img src="/images/2022-08-12/36.jpeg" alt="确认 Webdav 配置"></p>
+<p>最后,系统会向你确认 Webdav 是否正确。</p>
+<p>若确认无误,即可敲击回车继续。</p>
+<p>接下来的步骤和上述初级教程硬盘挂载之后的流程是完全一样的,请参考上文继续配置。</p>
+
+
+ 2022-08-12T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/f82a3103.html
+ BETAFPV 高频头固件编译 AttributeError
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="错误原因"><a href="#错误原因" class="headerlink" title="错误原因"></a>错误原因</h2><p>Python 模块 <code>pypandoc</code> 版本过新,1.8.0 及更高版本已移除了 BETAFPV 高频头固件中仍在使用的 <code>convert</code> 函数。</p>
+<h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><p>安装旧版的 <code>pypandoc</code> 模块。</p>
+<p><code>pip install pypandoc==1.7.0</code></p>
+
+
+
+ 2022-08-06T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/38942a16.html
+ DIY 显示器音箱
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>新买的显示器(LG 27UL500,虽然因为屏幕问题已经退货了)没有内置音箱,虽然大部分时间都在用耳机,但别人有的东西我不能没有嘛,就买了些材料,DIY 一个外接音箱。</p>
+<p>写作此文章分享一下制作的过程~</p>
+<h3 id="物料清单"><a href="#物料清单" class="headerlink" title="物料清单"></a>物料清单</h3><p><img src="/images/2022-06-03/01.JPG" alt="物料清单"></p>
+<ul>
+<li>PAM8403 数字功放板 5RMB</li>
+</ul>
+<p>接受 5V 电压,可以驱动两个 3W 的喇叭。商品详情页面吹的很厉害,确实底噪很小,而且输出的音量非常高。相当 OK 的功放板。</p>
+<ul>
+<li>8Ω 3W 喇叭两只(带音腔) 2*4RMB + 运费</li>
+</ul>
+<p>音质很不错,声音很大也不会破音,因为是广告机用的喇叭么?</p>
+<ul>
+<li>3.5MM 公头 0.5RMB</li>
+</ul>
+<p>我选的型号是 PJ392,只要是 3.5MM 双声道的公头就行了。</p>
+<ul>
+<li>Type C 母座 0.4RMB</li>
+</ul>
+<p>这个随意选。</p>
+<ul>
+<li>屏蔽线缆 2RMB/m</li>
+</ul>
+<p>我买的是4芯的屏蔽线,可供 Type C 头使用(2 power 2 data),音频线只需要三芯(2 data 1 GND),屏蔽线是为了更小的干扰、更好的音质。</p>
+<h3 id="开始组装"><a href="#开始组装" class="headerlink" title="开始组装"></a>开始组装</h3><h4 id="3-5MM-线缆"><a href="#3-5MM-线缆" class="headerlink" title="3.5MM 线缆"></a>3.5MM 线缆</h4><p>剥除一段屏蔽线的外皮,做工还是很不错的,有金属丝和铝箔的屏蔽,塑料膜防水,还有一根抗拉扯的填充芯。</p>
+<p>我选择使用红绿蓝三根线,黄线悬空。线色对应如下:</p>
+<p>红 - 左声道;绿 - 右声道;蓝 - 接地。</p>
+<p><img src="/images/2022-06-03/02.JPG" alt="屏蔽线"></p>
+<p>可以预先套上一段热缩管。</p>
+<p><img src="/images/2022-06-03/03.JPG" alt="热缩管"></p>
+<p>取一枚 3.5mm 公头,旋下插头。</p>
+<p>最长的一段一般是接地。若将接地朝下,我这款公头左侧为左声道,右侧为右声道。具体的接线方式可以用万用表测量接头和接口得知。</p>
+<p>将线穿入孔中,上一坨焊锡即可。</p>
+<p><img src="/images/2022-06-03/04.JPG" alt="公头焊接"></p>
+<p>再使用万用表测量接头与线末端的连通性,注意不能与其他线短路了。</p>
+<p>确认无误后可以打上热熔胶固定。</p>
+<p><img src="/images/2022-06-03/05.JPG" alt="热熔胶固定"></p>
+<p>再打点热熔胶,旋回外壳,将热缩管套好,加热热缩管使其收缩。</p>
+<p>3.5mm 线缆就制作完成了。</p>
+<h4 id="驱动板焊接"><a href="#驱动板焊接" class="headerlink" title="驱动板焊接"></a>驱动板焊接</h4><p>驱动板上有三组线需要焊接:</p>
+<ul>
+<li>音频输入线(3.5mm 线缆)</li>
+<li>电源输入线(Type C 线)</li>
+<li>音频输出线(喇叭线)</li>
+</ul>
+<p>Type C 线我没有再用屏蔽线,用两根导线连接 Type C 母头和驱动板即可。</p>
+<p>焊接方法就不多说了,线穿过孔,上锡即可。</p>
+<p><img src="/images/2022-06-03/06.JPG" alt="焊接中的驱动板"></p>
+<p>全部线缆焊接完成如下:</p>
+<p><img src="/images/2022-06-03/07.JPG" alt="焊接完的驱动板"></p>
+<h4 id="热熔胶填充"><a href="#热熔胶填充" class="headerlink" title="热熔胶填充"></a>热熔胶填充</h4><p>完成接线后,确认无短路,即可连接电脑测试音箱。</p>
+<p>若没有问题,考虑到需要长期使用,就可以用热熔胶覆盖焊接处,防止焊点脱落。</p>
+<p>用热熔胶覆盖之后的驱动板:</p>
+<p><img src="/images/2022-06-03/08.JPG" alt="热熔胶覆盖的驱动板"></p>
+<p>嘛…手艺不是很行。</p>
+<hr>
+<p>就此,外接音箱组装完成啦!</p>
+
+
+
+ 2022-06-03T07:15:00.000Z
+
+
+ https://blog.udon.eu.org/archives/2e528779.html
+ 迁移 Hexo 渲染环境至 GitHub Actions
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>本博客使用的是 Hexo 静态博客框架,我将渲染环境搭建在家中 NAS 之上,部署了一个 Ubuntu Docker 并安装好了 Node.js 环境,正常使用了一两年。</p>
+<p>上周末,写完新文章的我心血来潮,打算在更新博客的时候顺便更新一下老旧的 Node 和 Hexo(Node 12, Hexo 5.2)。</p>
+<p>一番折腾之后,以 npm 和 hexo 环境全部被破坏(事后想想也许只是环境变量掉了)的下场结束。</p>
+<p>鉴于 NAS 性能低下,更新一次 node module 都需要 30 分钟,我放弃了在 NAS 上重新部署渲染环境的念头,转而使用 GitHub Actions 渲染,并同时部署于 GitHub Pages 与 CloudFlare Pages。</p>
+<h2 id="将渲染环境迁至-GitHub-Actions"><a href="#将渲染环境迁至-GitHub-Actions" class="headerlink" title="将渲染环境迁至 GitHub Actions"></a>将渲染环境迁至 GitHub Actions</h2><p>不久之前,CloudFlare Pages 悄悄下架了 Hexo 框架的部署功能,只能用 GitHub Actions 渲染,然后再部署至 CloudFlare Pages 了。</p>
+<h3 id="项目结构的修改"><a href="#项目结构的修改" class="headerlink" title="项目结构的修改"></a>项目结构的修改</h3><p>若想使用 GitHub Actions,需要将博客的源码上传至 GitHub。考虑到隐私和安全的问题,建议创建一个私有仓库管理源码。</p>
+<p>对于项目没有什么需要修改的,因为 Actions 渲染的流程和本地渲染的流程没有区别。</p>
+<p>唯一需要改动的,是引入的主题。由于两个 Git 仓库不能嵌套,我们需要以 Git submodule 的形式引入主题仓库。</p>
+<p>我使用的是 Fluid 主题。采用 <a href="https://hexo.fluid-dev.com/docs/guide/#%E8%A6%86%E7%9B%96%E9%85%8D%E7%BD%AE">覆盖配置</a> 的方法,即在根目录之下有一份配置会覆盖主题内的配置文件,便于在 Actions 中渲染。</p>
+<p>以下也将以 Fluid 为例,请根据你使用的主题修改命令或代码。</p>
+<p>首先,删除原来的主题(若使用的是主题内的配置,注意备份配置文件!)</p>
+<p>返回博客源码的根目录,执行:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git submodule add https://github.com/fluid-dev/hexo-theme-fluid.git themes/fluid<br></code></pre></td></tr></table></figure>
+
+<p>末尾的 <code>themes/fluid</code> 为此 submodule 在项目中的位置与名字,与先前本地渲染时的配置相同即可。</p>
+<p>删除子模块的过程较为繁琐,请参考网上的文章进行操作。</p>
+<p>在 Clone 此项目时,submodule 默认不会被下载,需要使用指令:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git submodule update --init --recursive<br></code></pre></td></tr></table></figure>
+
+<p>下载 submodule。在下面会提到的 Actions 配置文件中会出现这条指令。</p>
+<p>接着,就可以将博客源码上传至 GitHub。</p>
+<h3 id="GitHub-Actions-相关文件"><a href="#GitHub-Actions-相关文件" class="headerlink" title="GitHub Actions 相关文件"></a>GitHub Actions 相关文件</h3><p>在博客源码根目录创建 <code>.github/workflows/submit.yml</code> 和 <code>.github/script/blog-update.sh</code> 两个文件,填入下列代码。</p>
+<p>以下代码参考文章 <a href="https://blog.kukmoon.com/f8bb4ee.html#23-%E7%BC%96%E5%86%99-workflow">GitHub Actions 自动部署 Hexo 博客到 cPanel 虚拟主机 - 谷中望月</a>,有所修改。</p>
+<p><code>submit.yml</code>:</p>
+<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">CI</span><br><br><span class="hljs-comment"># 监听 main 分支的改动与 Release 的发布</span><br><span class="hljs-attr">on:</span><br> <span class="hljs-attr">push:</span><br> <span class="hljs-attr">branches:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">main</span><br> <span class="hljs-attr">release:</span><br> <span class="hljs-attr">types:</span> [<span class="hljs-string">published</span>]<br><br><span class="hljs-comment"># 自定义环境变量</span><br><span class="hljs-attr">env:</span><br> <span class="hljs-attr">GIT_USER:</span> <span class="hljs-string">Lao-Liu233</span> <span class="hljs-comment"># 改成你自己的 GitHub 用户名</span><br> <span class="hljs-attr">GIT_EMAIL:</span> <span class="hljs-string">blog@udon.eu.org</span> <span class="hljs-comment"># 改成你自己的 GitHub 注册邮箱</span><br><br><span class="hljs-attr">jobs:</span><br> <span class="hljs-attr">build:</span><br> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">on</span> <span class="hljs-string">node</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node_version</span> <span class="hljs-string">}}</span> <span class="hljs-string">and</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.os</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span><br> <span class="hljs-attr">strategy:</span><br> <span class="hljs-attr">matrix:</span><br> <span class="hljs-attr">os:</span> [<span class="hljs-string">ubuntu-latest</span>]<br> <span class="hljs-attr">node_version:</span> [<span class="hljs-number">16.15</span>] <span class="hljs-comment"># 改成你本地的 Node.js 版本,可以用 `node --version` 命令查询</span><br><br> <span class="hljs-attr">steps:</span><br> <span class="hljs-comment"># 获取博客源码</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span><br> <br> <span class="hljs-comment"># 用 Node.js 渲染</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Use</span> <span class="hljs-string">Node.js</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node_version</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v1</span><br> <span class="hljs-attr">with:</span><br> <span class="hljs-attr">node-version:</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node_version</span> <span class="hljs-string">}}</span><br><br> <span class="hljs-comment"># 安装 Hexo-cli </span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">hexo</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> npm install -g hexo-cli</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 安装依赖</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span> <br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> npm install</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 导入 submodule</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Clone</span> <span class="hljs-string">submodule</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> git submodule update --init --recursive</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 配置环境</span><br> <span class="hljs-comment"># ssh-kenscan github.com >> ~/.ssh/known_hosts # 从 GitHub 获取公钥并保存到 known_hosts 文件</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Configuration</span> <span class="hljs-string">environment</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> sudo timedatectl set-timezone "Asia/Shanghai"</span><br><span class="hljs-string"> mkdir -p ~/.ssh/</span><br><span class="hljs-string"> echo "$HEXO_DEPLOY_PRI" > ~/.ssh/id_rsa</span><br><span class="hljs-string"> chmod 600 ~/.ssh/id_rsa</span><br><span class="hljs-string"> ssh-keyscan github.com >> ~/.ssh/known_hosts</span><br><span class="hljs-string"> git config --global user.name $GIT_USER</span><br><span class="hljs-string"> git config --global user.email $GIT_EMAIL</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 生成并部署</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">hexo</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> hexo clean</span><br><span class="hljs-string"> hexo g -d</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 部署后更新博客源码,用于添加 abbrlink,如果不用 abbrlink,需要删除</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Update</span> <span class="hljs-string">Blog</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br> <span class="hljs-string">sh</span> <span class="hljs-string">"${GITHUB_WORKSPACE}/.github/script/blog-update.sh"</span><br></code></pre></td></tr></table></figure>
+
+<p><code>.github/script/blog-update.sh</code>:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-meta">#!/bin/sh</span><br><br><span class="hljs-keyword">if</span> [ -z <span class="hljs-string">"<span class="hljs-subst">$(git status --porcelain)</span>"</span> ]; <span class="hljs-keyword">then</span><br> <span class="hljs-built_in">echo</span> <span class="hljs-string">"nothing to update."</span><br><span class="hljs-keyword">else</span><br> git add <span class="hljs-built_in">source</span>/_posts/ <span class="hljs-comment">#仅对文章源码所在文件夹进行修改</span><br> git commit -m <span class="hljs-string">"triggle by commit <span class="hljs-variable">${GITHUB_SHA}</span>"</span> -a<br> git push origin main<br><span class="hljs-keyword">fi</span><br></code></pre></td></tr></table></figure>
+
+<p>Commit + Push,打开 Actions 界面,就能看到正在运行的 Action 啦。</p>
+<p>不出意外,Action 成功执行,1分钟内博客就能渲染成功、部署至 GitHub Pages。</p>
+<h2 id="同时部署至-CloudFlare-Pages"><a href="#同时部署至-CloudFlare-Pages" class="headerlink" title="同时部署至 CloudFlare Pages"></a>同时部署至 CloudFlare Pages</h2><p>步骤较为简单,我简述一下。</p>
+<p>打开 CloudFlare Pages, 连接至存放 <strong>渲染后</strong> 的静态文件的仓库,渲染的框架选择 <strong>None</strong>,执行的指令填写 <code>exit 0;</code> 就可以了。</p>
+<p>执行部署后,渲染后的静态文件就被部署至 CloudFlare Pages 啦。</p>
+
+
+
+
+ 2022-05-23T11:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/dbf21067.html
+ 玩一玩 DN42
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>两个月前,我向 DN42 提交了申请,并于4个小时后通过了审核,获得了自己的 AS 和 IP。</p>
+<p>作此文分享一下把玩 DN42 的心得,也作为我的备忘录。</p>
+<h2 id="我的信息"><a href="#我的信息" class="headerlink" title="我的信息"></a>我的信息</h2><figure class="highlight maxima"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs maxima">AS4242423490<br>IPv4: <span class="hljs-number">172.23</span><span class="hljs-number">.13</span><span class="hljs-number">.64</span>/<span class="hljs-number">28</span><br>IPv6: fd44:<span class="hljs-number">6b93</span>:4eaa::/<span class="hljs-number">48</span><br></code></pre></td></tr></table></figure>
+
+<p>目前仅一个洛杉矶节点开放 Peer,后期还将添加韩国和日本的节点。</p>
+<h2 id="如何把玩"><a href="#如何把玩" class="headerlink" title="如何把玩"></a>如何把玩</h2><h3 id="注册"><a href="#注册" class="headerlink" title="注册"></a>注册</h3><p>有关注册的文章很多,推荐这两篇:</p>
+<p><a href="https://lantian.pub/article/modify-website/dn42-experimental-network-2020.lantian/">DN42 实验网络介绍及注册教程(2022-02 更新) | Lan Tian @ Blog</a></p>
+<p><a href="https://blog.baoshuo.ren/post/dn42-network/#">初探 DN42 网络 - 宝硕博客 (baoshuo.ren)</a></p>
+<p>需要使用 Git 和 PGP,在 DN42 的 Git 仓库提交你的个人信息即可完成注册。</p>
+<h3 id="搭建内网"><a href="#搭建内网" class="headerlink" title="搭建内网"></a>搭建内网</h3><p>在和其他 AS 建立对等连接之前,我们先要把内网整理好:</p>
+<p>各台服务器的地理位置和网络位置各不相同,需要使用 VPN 创建虚拟局域网。</p>
+<p>课堂上讲了两种内网路由协议:</p>
+<ul>
+<li><p>RIP 是“真”内网用的,不适用于这种物理位置距离较远(路由跳数多)的虚拟内网;</p>
+</li>
+<li><p>可以使用 OSPF,但我在配置的时候遇到了不少问题,因此也不建议你使用。</p>
+</li>
+</ul>
+<p>有一位老朋友可以轻松解决以上两个问题:<strong>Zerotier</strong>。</p>
+<p>Zerotier 的虚拟网络可以使用自己的 IP,只需在 <strong>Managed Routes</strong> 中设置你在 DN42 申请的 IPv4 和 IPv6,即可为每台加入虚拟网络的主机自动或手动配置 DN42 IP。</p>
+<p>在机器之间使用 DN42 IP 互 ping 测试连通性。</p>
+<h3 id="准备-BGP-相关软件"><a href="#准备-BGP-相关软件" class="headerlink" title="准备 BGP 相关软件"></a>准备 BGP 相关软件</h3><p>搭建好内网之后,就可以开始配置 BGP 发言人啦。</p>
+<p>选择一台或多台服务器,作为自治域向外宣告路由的发言人。</p>
+<p>在每台服务器上都需要配置 BGP 相关的软件,以及和其他 BGP 发言人建立连接(一般是 VPN 连接)的软件。</p>
+<p>目前在 DN42 网络用的比较多的 VPN 软件是 Wireguard,BGP 软件则可以从 bird 2、bird 1、quagga 等软件中选择。</p>
+<p>我使用的是 bird 2。</p>
+<h4 id="安装与配置-BIRD-2"><a href="#安装与配置-BIRD-2" class="headerlink" title="安装与配置 BIRD 2"></a>安装与配置 BIRD 2</h4><p>安装命令:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">apt update<br>apt install bird2 -y<br></code></pre></td></tr></table></figure>
+
+<p>bird 2 的配置文件位于 <code>/etc/bird</code>,名为 <code>bird.conf</code>。</p>
+<p>配置文件可以参考(<del>照抄</del>)DN42 官方给出的配置:<a href="https://dn42.dev/howto/Bird2#example-configuration">howto/Bird2 (dn42.dev)</a></p>
+<p>喂到嘴边的配置方法:</p>
+<ul>
+<li>将官方配置填入 <code>/etc/bird/bird.conf</code></li>
+<li>在 <code>/etc/bird</code> 目录下新建名为 <code>peers</code> 的文件夹</li>
+<li>下载 ROA 配置(命令来自<a href="https://blog.baoshuo.ren/">宝硕的博客</a>)</li>
+</ul>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">wget -4 -O /tmp/dn42_roa.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf && <span class="hljs-built_in">mv</span> -f /tmp/dn42_roa.conf /etc/bird/dn42_roa.conf<br>wget -4 -O /tmp/dn42_roa_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf && <span class="hljs-built_in">mv</span> -f /tmp/dn42_roa_v6.conf /etc/bird/dn42_roa_v6.conf<br></code></pre></td></tr></table></figure>
+
+<p> 并配置 crontab,每小时自动下载并重载新配置:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">0 */1 * * * wget -4 -O /tmp/dn42_roa.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf && <span class="hljs-built_in">mv</span> -f /tmp/dn42_roa.conf /etc/bird/dn42_roa.conf<br>0 */1 * * * wget -4 -O /tmp/dn42_roa_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf && <span class="hljs-built_in">mv</span> -f /tmp/dn42_roa_v6.conf /etc/bird/dn42_roa_v6.conf<br>0 */1 * * * birdc configure<br></code></pre></td></tr></table></figure>
+
+<h4 id="安装并配置-Wireguard"><a href="#安装并配置-Wireguard" class="headerlink" title="安装并配置 Wireguard"></a>安装并配置 Wireguard</h4><p>安装命令:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">apt update<br>apt install wireguard -y<br></code></pre></td></tr></table></figure>
+
+<p>这样就安装了 <code>Wireguard</code> 和名为 <code>wg-quick</code> 的管理工具。</p>
+<p>使用命令:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">wg genkey | <span class="hljs-built_in">tee</span> privatekey | wg pubkey > publickey<br></code></pre></td></tr></table></figure>
+
+<p>在当前目录下创建 Wireguard 建立连接所用的密钥对(公钥和私钥)。</p>
+<p>就此 Wireguard 安装完成。</p>
+<h4 id="配置系统内核"><a href="#配置系统内核" class="headerlink" title="配置系统内核"></a>配置系统内核</h4><p>打开内核的数据包转发功能:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv4.ip_forward=1"</span> >> /etc/sysctl.conf<br><span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv6.conf.default.forwarding=1"</span> >> /etc/sysctl.conf<br><span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv6.conf.all.forwarding=1"</span> >> /etc/sysctl.conf<br>sysctl -p<br></code></pre></td></tr></table></figure>
+
+<p>关闭内核 <code>rp_filter</code> 的严格模式:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv4.conf.default.rp_filter=0"</span> >> /etc/sysctl.conf<br><span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv4.conf.all.rp_filter=0"</span> >> /etc/sysctl.conf<br>sysctl -p<br></code></pre></td></tr></table></figure>
+
+<p>如果有 ufw 等防火墙自动配置工具,务必关闭。</p>
+<p>p.s. 我拿到任何机器后会立刻执行的指令是:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> ufw <span class="hljs-built_in">disable</span><br></code></pre></td></tr></table></figure>
+
+<h4 id="创建-Dummy-网卡"><a href="#创建-Dummy-网卡" class="headerlink" title="创建 Dummy 网卡"></a>创建 Dummy 网卡</h4><p>dummy 网卡具体的作用我不是很清楚…</p>
+<p>只知道如果要用链路本地地址进行通讯,要把 DN42 的 IP 地址绑定到 dummy 网卡上。</p>
+<p>dummy 网卡配置指令如下:([ ] 中为需要你填写的内容)</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash">ip <span class="hljs-built_in">link</span> del dummy<br>ip <span class="hljs-built_in">link</span> add dummy <span class="hljs-built_in">type</span> dummy<br>ip addr add [你的 DN42 IPv4 地址]/32 dev dummy<br>ip addr add [你的 DN42 IPv6 地址]/128 dev dummy<br>ip <span class="hljs-built_in">link</span> <span class="hljs-built_in">set</span> dummy up<br></code></pre></td></tr></table></figure>
+
+<h3 id="和小伙伴建立对等连接(peer)"><a href="#和小伙伴建立对等连接(peer)" class="headerlink" title="和小伙伴建立对等连接(peer)"></a>和小伙伴建立对等连接(peer)</h3><h4 id="需要和对方分享的"><a href="#需要和对方分享的" class="headerlink" title="需要和对方分享的"></a>需要和对方分享的</h4><ul>
+<li>你的 DN42 信息,包括 AS 号和发言人的 DN42 IPv4(IPv6)地址;</li>
+<li>若使用链路本地地址,还需提供这东西,一般为一个本地 IPv6 地址,常取 <code>fe80::[你的 AS 号后4位]</code>;</li>
+<li>发言人的外网 IPv4 地址(或域名)和 Wireguard 隧道的通讯端口;</li>
+<li>Wireguard 公钥。</li>
+</ul>
+<p>有一些信息会在以下的配置中获得。</p>
+<h4 id="Wireguard-相关的"><a href="#Wireguard-相关的" class="headerlink" title="Wireguard 相关的"></a>Wireguard 相关的</h4><p>在 <code>/etc/wireguard</code> 目录下创建 Wireguard 配置文件,每一个配置文件对应着一个 Wireguard 隧道。</p>
+<p>例如你要和 AS114514 <del>臭</del> 建立对等连接,可以在 <code>peers</code> 文件夹下新建一个名为 <code>wg_114514.conf</code> (文件名即为 wireguard 隧道名)的配置文件。</p>
+<p>配置的模板如下:([ ] 中为需要你填写的内容)</p>
+<figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs ini"><span class="hljs-section">[Interface]</span><br><span class="hljs-attr">Table</span> = <span class="hljs-literal">off</span><br><span class="hljs-attr">ListenPort</span> = [我们的监听端口,可以用对方 AS 号的后五位]<br><span class="hljs-attr">PrivateKey</span> = [刚刚生成的 Wireguard 私钥]<br><span class="hljs-attr">PostUp</span> = ip addr add [本机的 DN42 IPv4 地址]/<span class="hljs-number">32</span> peer [对方机器的 DN42 IPv4 地址]/<span class="hljs-number">32</span> dev %i<br><span class="hljs-attr">PostUp</span> = ip addr add [本机的链路本地地址(见 BIRD 相关配置)]/<span class="hljs-number">64</span> dev %i<br><br><span class="hljs-section">[Peer]</span><br><span class="hljs-attr">PublicKey</span> = [对方的 Wireguard 公钥]<br><span class="hljs-attr">AllowedIPs</span> = <span class="hljs-number">10.0</span>.<span class="hljs-number">0.0</span>/<span class="hljs-number">8</span>, <span class="hljs-number">172.20</span>.<span class="hljs-number">0.0</span>/<span class="hljs-number">14</span>, <span class="hljs-number">172.31</span>.<span class="hljs-number">0.0</span>/<span class="hljs-number">16</span>, fd00::/<span class="hljs-number">8</span>, fe80::/<span class="hljs-number">64</span><br><span class="hljs-attr">Endpoint</span> = [对方机器的公网 IP 地址或域名 : 端口号]<br></code></pre></td></tr></table></figure>
+
+<p>然后使用 <code>wg-quick up [wireguard 隧道名(刚刚的配置文件名)]</code> 启动 Wireguard 隧道。</p>
+<p>可以 ping 一下对方的 DN42 IP 看看 Wireguard 隧道是否连接成功。</p>
+<p>使用 <code>wg</code> 命令查看各隧道的连接情况。若有显示 <code>last handshake</code>,一般情况下隧道就已成功建立。</p>
+<h4 id="BIRD-相关的"><a href="#BIRD-相关的" class="headerlink" title="BIRD 相关的"></a>BIRD 相关的</h4><p>在先前导入的 bird 2 配置中定义了一个 <code>peers</code> 文件夹,就是用来存放 peer 相关的配置。</p>
+<p>例如你要和 AS114514 <del>又臭</del> 建立对等连接,可以在 <code>peers</code> 文件夹下新建一个名为 <code>114514.conf</code> (文件名可自定义)的配置文件。</p>
+<p>我采用的是<a href="https://zh.wikipedia.org/wiki/%E9%93%BE%E8%B7%AF%E6%9C%AC%E5%9C%B0%E5%9C%B0%E5%9D%80">链路本地地址(Link-Local)</a>的配置方法。配置的模板如下:([ ] 中为需要你填写的内容)</p>
+<figure class="highlight inform7"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs inform7">protocol bgp <span class="hljs-comment">[peer的名字]</span> from dnpeers {<br> neighbor <span class="hljs-comment">[对方的链路本地地址]</span> % '<span class="hljs-comment">[通向对方的 Wiregurad 隧道名]</span>' as <span class="hljs-comment">[对方的 AS 号]</span>;<br>}<br></code></pre></td></tr></table></figure>
+
+<p>添加完配置之后别忘了用 <code>birdc configure</code> 重载 bird 2 配置。</p>
+<p>使用命令 <code>birdc s p</code> 可以查看 BIRD 2 软件下所有协议的通信情况。</p>
+<p>若显示为:</p>
+<figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">dn42_xxxx</span> BGP --- up <span class="hljs-number">20</span>:<span class="hljs-number">36</span>:<span class="hljs-number">30</span>.<span class="hljs-number">984</span> Established<br></code></pre></td></tr></table></figure>
+
+<p>则表示 BGP 连接已建立。</p>
+<h2 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h2><p>我还在写 DN42 相关的站点,在上面分享节点信息,方便大家 peer。</p>
+<p>但目前进度缓慢(悲)。</p>
+
+
+ 2022-04-01T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/9b58c98e.html
+ 合宙 EPS32-C3 把玩记录(二):WiFi 与一个 Web 程序
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="了解一下相关的库"><a href="#了解一下相关的库" class="headerlink" title="了解一下相关的库"></a>了解一下相关的库</h2><ul>
+<li><h3 id="串口通信"><a href="#串口通信" class="headerlink" title="串口通信"></a>串口通信</h3></li>
+</ul>
+<p>这个库是自带的,不需要引入。</p>
+<p>据我的理解,单片机的串口就是控制台程序的控制台,可以返回一些信息给上位机。</p>
+<p>会用到的几个指令:</p>
+<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c">Serial.begin(Baudrate); <span class="hljs-comment">//参数为串口通信的波特率</span><br>Serial.end();<br>Serial.read(); <span class="hljs-comment">//读取串口收到数据的第一个字节</span><br>Serial.peek(); <span class="hljs-comment">//读取串口数据中下一字节的内容</span><br>Serial.flush(); <span class="hljs-comment">//清空缓冲区</span><br>Serial.print/println(); <span class="hljs-comment">//不用多说</span><br>Serial.write(); <span class="hljs-comment">//写二进制数据</span><br></code></pre></td></tr></table></figure>
+
+<ul>
+<li><h3 id="WiFi-h"><a href="#WiFi-h" class="headerlink" title="WiFi.h"></a>WiFi.h</h3></li>
+</ul>
+<p><code>#include <WiFi.h></code></p>
+<h4 id="AP(接入点)-Mode"><a href="#AP(接入点)-Mode" class="headerlink" title="AP(接入点) Mode"></a>AP(接入点) Mode</h4><p>创建一个接入点。</p>
+<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c">WiFi.mode(WiFi_AP); <span class="hljs-comment">//设置工作在 AP 模式</span><br>WiFi.softAPConfig(local_IP, gateway, subnet);<br><span class="hljs-comment">//定义本机 IP(这个不大确定)、网关 IP 和子网掩码</span><br><span class="hljs-comment">//IPAddress 数据类型格式:IPAddress local_IP(192,168,4,22);</span><br>WiFi.softAP(SSID,PASSWD); <span class="hljs-comment">//启动 AP,参数不多解释,返回 bool </span><br></code></pre></td></tr></table></figure>
+
+<p>更多函数见 <a href="https://blog.csdn.net/Naisu_kun/article/details/86165403#_28">WiFi.h AP 常用方法说明</a></p>
+<h4 id="STA(站点)-Mode"><a href="#STA(站点)-Mode" class="headerlink" title="STA(站点) Mode"></a>STA(站点) Mode</h4><p>接入一个 AP。</p>
+<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c">WiFi.mode(WIFI_STA); <span class="hljs-comment">//设置工作在 STA 模式</span><br>WiFi.start(SSID,PASSWD) <span class="hljs-comment">//连接至 AP,参数不多解释</span><br>Serial.println(WiFi.localIP()); <span class="hljs-comment">//打印本机 IP,省的去路由器管理界面看</span><br></code></pre></td></tr></table></figure>
+
+<p>更多函数见 <a href="https://blog.csdn.net/Naisu_kun/article/details/86165403#_130">WiFi.h STA 常用方法说明</a></p>
+<ul>
+<li><h2 id="WebServer-h"><a href="#WebServer-h" class="headerlink" title="WebServer.h"></a>WebServer.h</h2></li>
+</ul>
+<p><code>#include <WebServer.h></code></p>
+<p>创建一个简单的网站服务器。真的很简单。</p>
+<p>一个个函数讲有点难理解,我放在这节的例程里面说明。</p>
+<h2 id="写一个测试程序吧"><a href="#写一个测试程序吧" class="headerlink" title="写一个测试程序吧"></a>写一个测试程序吧</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><WiFi.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><WebServer.h></span></span><br><span class="hljs-comment">//引入所需要的两个库</span><br><br><span class="hljs-type">const</span> <span class="hljs-type">char</span> *SSID = <span class="hljs-string">"YOUR_SSID"</span>;<br><span class="hljs-type">const</span> <span class="hljs-type">char</span> *PASSWORD = <span class="hljs-string">"YOUR_PASSWORD"</span>;<br><br>WebServer <span class="hljs-title function_">server</span><span class="hljs-params">(<span class="hljs-number">80</span>)</span>; <span class="hljs-comment">//网站服务器将运行在 80 端口</span><br><br><span class="hljs-type">void</span> <span class="hljs-title function_">handleIndex</span><span class="hljs-params">()</span> <span class="hljs-comment">//收到 HTTP 请求的回调函数</span><br>{<br> server.send(<span class="hljs-number">200</span>, <span class="hljs-string">"text/plain"</span>, <span class="hljs-string">"Hello from ESP32!"</span>);<br> <span class="hljs-comment">//发送 HTTP 相应,内容分别为:状态码,Content-Type,响应体</span><br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">setup</span><span class="hljs-params">()</span><br>{<br> Serial.begin(<span class="hljs-number">9600</span>); <span class="hljs-comment">//开启串口,波特率设置为 9600</span><br> Serial.println();<br><br> WiFi.mode(WIFI_STA); <span class="hljs-comment">//设置工作在 STA 模式</span><br> WiFi.begin(SSID, PASSWORD); <span class="hljs-comment">//连接至指定 AP</span><br> <span class="hljs-keyword">while</span> (WiFi.status() != WL_CONNECTED) <span class="hljs-comment">//等待网络连接成功</span><br> {<br> delay(<span class="hljs-number">500</span>);<br> Serial.print(<span class="hljs-string">"."</span>); <span class="hljs-comment">//将连接信息输出至串口</span><br> }<br> Serial.println(<span class="hljs-string">"WiFi connected!"</span>);<br><br> Serial.println(<span class="hljs-string">"IP address: "</span>);<br> Serial.println(WiFi.localIP()); <span class="hljs-comment">//打印本机 IP</span><br><br> server.on(<span class="hljs-string">"/"</span>, handleIndex); <span class="hljs-comment">//注册链接(类似与注册一个路由),并选择回调函数</span><br> <span class="hljs-comment">//同样的,还可以注册别的链接,如</span><br> <span class="hljs-comment">//server.on("/test", handleIndexTest);</span><br> <br> server.begin(); <span class="hljs-comment">//开启 HTTP 服务器</span><br> Serial.println(<span class="hljs-string">"WebServer begin!"</span>);<br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">loop</span><span class="hljs-params">()</span><br>{<br> server.handleClient(); <span class="hljs-comment">//不断相应 HTTP 请求</span><br>}<br></code></pre></td></tr></table></figure>
+
+<p>访问串口返回的 IP,即可看到 <code>Hello from ESP32!</code> 这句话啦。</p>
+<h2 id="还有个-Web-Server-叫-ESPAsyncWebServer"><a href="#还有个-Web-Server-叫-ESPAsyncWebServer" class="headerlink" title="还有个 Web Server 叫 ESPAsyncWebServer"></a>还有个 Web Server 叫 ESPAsyncWebServer</h2><p>自带的 WebServer 是同步的,只支持处理一个连接。对于这种体量的机器其实足够了。</p>
+<p>顺便学习一下一个第三方库吧。</p>
+<h3 id="添加库"><a href="#添加库" class="headerlink" title="添加库"></a>添加库</h3><p>对于这款 ESP32,需要下载并导入两个库(源码 ZIP 即可):</p>
+<p><a href="https://github.com/me-no-dev/AsyncTCP">me-no-dev/AsyncTCP: Async TCP Library for ESP32</a></p>
+<p><a href="https://github.com/me-no-dev/ESPAsyncWebServer">me-no-dev/ESPAsyncWebServer: Async Web Server for ESP8266 and ESP32</a></p>
+<p>在 Arduino 的<code>项目 > 加载库 > 添加 .ZIP 库</code>中导入这两个库。</p>
+<h3 id="用-ESPAsyncWebServer-重写刚刚的例程吧"><a href="#用-ESPAsyncWebServer-重写刚刚的例程吧" class="headerlink" title="用 ESPAsyncWebServer 重写刚刚的例程吧"></a>用 ESPAsyncWebServer 重写刚刚的例程吧</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><WiFi.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><ESPAsyncWebServer.h></span></span><br><span class="hljs-comment">//注意替换为新的库</span><br><br><span class="hljs-type">const</span> <span class="hljs-type">char</span> *SSID = <span class="hljs-string">"YOUR_SSID"</span>;<br><span class="hljs-type">const</span> <span class="hljs-type">char</span> *PASSWORD = <span class="hljs-string">"YOUR_PASSWORD"</span>;<br><br>ESPAsyncWebServer <span class="hljs-title function_">server</span><span class="hljs-params">(<span class="hljs-number">80</span>)</span>; <span class="hljs-comment">//同样替换为新的对象</span><br><br><span class="hljs-type">void</span> <span class="hljs-title function_">handleIndex</span><span class="hljs-params">(AsyncWebServerRequest *request)</span> <span class="hljs-comment">//回调函数有更改</span><br>{<br> request->send(<span class="hljs-number">200</span>, <span class="hljs-string">"text/plain"</span>, <span class="hljs-string">"Hello, world!"</span>);<br> <span class="hljs-comment">//发送 HTTP 相应,内容相同</span><br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">setup</span><span class="hljs-params">()</span><br>{<br> Serial.begin(<span class="hljs-number">9600</span>); <span class="hljs-comment">//开启串口,波特率设置为 9600</span><br> Serial.println();<br><br> WiFi.mode(WIFI_STA); <span class="hljs-comment">//设置工作在 STA 模式</span><br> WiFi.begin(SSID, PASSWORD); <span class="hljs-comment">//连接至指定 AP</span><br> <span class="hljs-keyword">while</span> (WiFi.status() != WL_CONNECTED) <span class="hljs-comment">//等待网络连接成功</span><br> {<br> delay(<span class="hljs-number">500</span>);<br> Serial.print(<span class="hljs-string">"."</span>); <span class="hljs-comment">//将连接信息输出至串口</span><br> }<br> Serial.println(<span class="hljs-string">"WiFi connected!"</span>);<br><br> Serial.println(<span class="hljs-string">"IP address: "</span>);<br> Serial.println(WiFi.localIP()); <span class="hljs-comment">//打印本机 IP</span><br><br> server.on(<span class="hljs-string">"/"</span>, handleIndex); <span class="hljs-comment">//注册链接(类似与注册一个路由),并选择回调函数</span><br> <span class="hljs-comment">//同样的,还可以注册别的链接,如</span><br> <span class="hljs-comment">//server.on("/test", handleIndexTest);</span><br> <br> server.begin(); <span class="hljs-comment">//开启 HTTP 服务器</span><br> Serial.println(<span class="hljs-string">"WebServer begin!"</span>);<br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">loop</span><span class="hljs-params">()</span><br>{<br><span class="hljs-comment">//不用在这儿监听 HTTP 请求了</span><br>}<br></code></pre></td></tr></table></figure>
+
+<p>理论上来讲,上面的代码应该是正确的……</p>
+<p>但 Arduino 在编译的时候报错,内容是 ESPAsyncWebServer 库中的某些代码。</p>
+<p>有待我弄清楚出错的原因。</p>
+
+
+
+ 2022-03-26T16:15:00.000Z
+
+
+ https://blog.udon.eu.org/archives/7f7bd4a5.html
+ 合宙 EPS32-C3 把玩记录(一):环境搭建与第一个程序
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>为了贯彻本博客最重要的关键词:<strong>性价比</strong>,我看到性价如此高的开发板,想都没想就剁手了。</p>
+<p>嘛其实也没有这么冲动,我在购买 3D 打印机之后就一直在计划着做一些网上现成的电子项目,但碍于这段时间 MCU 和大尺寸屏幕价格的飙升,一直没能开始动手。</p>
+<p>正好最近我学习了 iPad 上的 3D 建模软件 Sharp3D,项目的外壳建模变得有可能;又遇到了这块便宜的板子,立即开工!</p>
+<p>因为1.8寸的 TFT 显示屏还没到货,3D 建模就先放一边,先来研究一下这块开发板。</p>
+<h3 id="事先声明"><a href="#事先声明" class="headerlink" title="事先声明"></a>事先声明</h3><p>本教程是我一边从零开始学习嵌入式开发一边作成的,有逻辑混乱、内容浅显和成吨的错误,还请已经熟悉嵌入式开发的大佬多多包涵与斧正)</p>
+<h2 id="问题:什么?开发环境不是按语言分的嘛?"><a href="#问题:什么?开发环境不是按语言分的嘛?" class="headerlink" title="问题:什么?开发环境不是按语言分的嘛?"></a>问题:什么?开发环境不是按语言分的嘛?</h2><p>在开始学习嵌入式开发之前,我简单地认为嵌入式开发因语言而已,分为用 C/Cpp 开发(Arduino)和用 Python 开发(MicroPython)。</p>
+<p>直到我遇见了 ESP-IDF 这个东西。</p>
+<p>啥啊,为啥这家伙用的也是 C,代码我还一点都看不懂。</p>
+<h3 id="解答"><a href="#解答" class="headerlink" title="解答"></a>解答</h3><p>嵌入式开发选用的语言和语法因选择的框架而异。</p>
+<p>ESP-EDF 更靠近底层,因而编写更复杂;Arduino 对底层进行封装,更靠上层且对用户更友好;MicroPython 则是在开发板上还原了一个 Python 的开发环境,继承了 Python 的诸多优点(简单的语法、无需编译就能执行新代码等)。</p>
+<p>此外,还能用 JS、Java、Lua 等等语言进行开发。</p>
+<h3 id="我的选择"><a href="#我的选择" class="headerlink" title="我的选择"></a>我的选择</h3><p>我手上有两块板子,一块被我刷成了 MicroPython,但目前不打算去用它。</p>
+<p>我打算用 Arduino + C 进行开发。</p>
+<h3 id="配置-VSCode-Arduino-开发环境"><a href="#配置-VSCode-Arduino-开发环境" class="headerlink" title="配置 VSCode + Arduino 开发环境"></a>配置 VSCode + Arduino 开发环境</h3><p>Arduino 没有代码补全,太难用。简述一下如何使用 VSCode 进行开发:</p>
+<ul>
+<li>VSC 安装 Arduino 插件;</li>
+<li>在 首选项-设置 中配置 Arduino 的路径 <code>Arduino.path</code></li>
+<li>打开项目后选择 MCU 类型和串口</li>
+</ul>
+<p>就能用啦。</p>
+<h2 id="第一个项目"><a href="#第一个项目" class="headerlink" title="第一个项目"></a>第一个项目</h2><p>第一个项目就不选输出 Hello World 了,一点硬件的感觉都没有。</p>
+<p>据<a href="https://wiki.luatos.com/chips/esp32c3/board.html"> 官方文档 </a>,主板板载的两个 LED 灯对应的 GPIO 为 <code>IO12 IO13</code>,高电平有效。</p>
+<p>就此编写一个<del>无稳态多协振荡电路</del>让 LED 灯交替闪烁的程序:</p>
+<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">setup</span><span class="hljs-params">()</span> <span class="hljs-comment">//初始化函数,只会在开发板上电或复位时被调用一次</span><br>{ <br> pinMode(<span class="hljs-number">12</span>, OUTPUT); <span class="hljs-comment">//初始化 IO12 为输出口</span><br> pinMode(<span class="hljs-number">13</span>, OUTPUT); <span class="hljs-comment">//初始化 IO13 为输出口</span><br> digitalWrite(<span class="hljs-number">12</span>, LOW); <span class="hljs-comment">//初始化设为低电平,则灯灭</span><br> digitalWrite(<span class="hljs-number">13</span>, LOW); <span class="hljs-comment">//初始化设为低电平,则灯灭</span><br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">loop</span><span class="hljs-params">()</span> <span class="hljs-comment">//上电之后一直循环执行的函数</span><br>{ digitalWrite(<span class="hljs-number">12</span>, HIGH); <span class="hljs-comment">//亮左灯</span><br> digitalWrite(<span class="hljs-number">13</span>, LOW); <span class="hljs-comment">//关右灯</span><br> delay(<span class="hljs-number">1000</span>); <span class="hljs-comment">//等待1秒</span><br> digitalWrite(<span class="hljs-number">12</span>, LOW); <span class="hljs-comment">//关左灯</span><br> digitalWrite(<span class="hljs-number">13</span>, HIGH); <span class="hljs-comment">//亮右灯</span><br> delay(<span class="hljs-number">1000</span>); <span class="hljs-comment">//等待1秒</span><br>}<br></code></pre></td></tr></table></figure>
+
+<p>编译+上传即可。</p>
+<p>结果就不展示了,两个灯在交替闪烁。</p>
+
+
+
+ 2022-03-26T09:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/d01399e6.html
+ Code-Server 的代理配置
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>一年前,我介绍了如何在群晖上使用 Docker 部署 Code-Server,在外也能轻松使用已经配置好的开发环境。<a href="https://blog.udon.eu.org/archives/375e7789.html">群晖搭建 VSCode 服务器与 Syncthing 服务</a></p>
+<p>最近我换了 iPad,琢磨如何发挥她的生产力。除了使用网页版的 IDE(Codepen、Gitpod等),就是自建网页版的 VSCode 了。下面简要介绍一下我是如何给 Code-Server Docker 配置代理,使其成为一个完备的开发平台。</p>
+<span id="more"></span>
+
+<p>将 Code-Server 部署在国内服务器(例如我家里的 NAS),可以获得稳定的连接,这对于开发平台是尤其重要的,VSCode 遇到连接不顺畅就会要求你刷新界面,很可能会丢失数据。</p>
+<p>但由于众所周知的原因,在国内的网络环境做开发可以说是寸步难行,我便采用 Clash Docker 来给 Code-Server 加上代理。</p>
+<h3 id="Clash-Docker-安装"><a href="#Clash-Docker-安装" class="headerlink" title="Clash Docker 安装"></a>Clash Docker 安装</h3><p>Clash Core 普通版 Image:<a href="https://hub.docker.com/r/dreamacro/clash">dreamacro/clash - Docker Image | Docker Hub</a></p>
+<p>Clash Core Premium Image:<a href="https://hub.docker.com/r/dreamacro/clash-premium">dreamacro/clash-premium - Docker Image | Docker Hub</a></p>
+<p>Clash Core Premium 二进制文件: <a href="https://github.com/Dreamacro/clash/releases/tag/premium">Premium release (github.com)</a></p>
+<p>Clash Core 有普通版和 Premium 版之分,目前我能体验到的二者的区别是普通版的 Clash Core 不支持 RULE-SET 功能。</p>
+<p>我常用的配置文件大量使用了 RULE-SET,所以我必须得用 Clash Core Premium。</p>
+<p>但 Pre Build 的 Image 似乎不支持 X86-64 v3 之下的 CPU(例如我的 J1900),所以我采取了部署普通版 Image,然后 attach 进 Docker 手动更换 Premium 内核的曲线救国方法。(更换 <code>/</code> 根目录下名为 <code>Clash</code>的二进制文件)</p>
+<p>部署 Docker 时注意一下几点:</p>
+<ul>
+<li>开放 7890(或你定义的代理端口)和 9090(Clash Core 管理面板)端口。</li>
+<li>将 <code>/root/.config/clash</code> 文件夹挂载到本地,存放 <code>config.yaml</code> 及其他配置文件。</li>
+</ul>
+<p>请勿将 Clash Core 的管理面板暴露到公网。我选择用 Tailscale 建立 VPN 访问家中的服务器进行配置。</p>
+<h3 id="Code-Server-的配置"><a href="#Code-Server-的配置" class="headerlink" title="Code-Server 的配置"></a>Code-Server 的配置</h3><p>需要在 Code-Server Docker 里添加两个环境变量,实现开机自动连接代理:</p>
+<ul>
+<li><code>http_proxy=http://clash_docker_ip:7890</code></li>
+<li><code>https_proxy=http://clash_docker_ip:7890</code></li>
+</ul>
+<p>可以使用同样的方法给其他 Docker 添加代理。</p>
+<hr>
+<p>在一顿折腾之后,Code-Server 终于可以顺畅访问 Github 等网站了。</p>
+<p>可喜可贺,可喜可贺。</p>
+
+
+ 2022-03-19T14:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/7263a385.html
+ Klipper 的外网访问
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>网络上有关 Klipper 的中文教程少之又少,固作此教程介绍一下如何在外网优雅地访问家中装有 Klipper 的 3D 打印机。</p>
+<h2 id="方法一:端口转发"><a href="#方法一:端口转发" class="headerlink" title="方法一:端口转发"></a>方法一:端口转发</h2><p><strong>此方法仅适用于拥有公网 IP 的用户</strong></p>
+<p>首先,使用 DDNS 将家庭宽带动态变化的 IP 绑定至域名,教程如下:</p>
+<ul>
+<li><a href="https://blog..eu.org/archives/87bacf3f.html">群晖-外网访问一站式教程2 DDNS</a></li>
+<li><a href="https://blog..eu.org/archives/27f2d840.html">iPv6下绝佳的DDNS方法-dynv6</a></li>
+</ul>
+<p>在配置端口映射之前,先介绍一下 Klipper 的网络结构:</p>
+<pre><code class=" mermaid">graph LR;
+ A("你的设备") <--80-->
+ B("前端网页(Fluidd/Mainsail/Octoprint)") <--7125-->
+ C("API 服务器 Moonraker") <-->
+ D("你的 3D 打印机");
+</code></pre>
+
+<p>线上的数字便是通讯的端口。</p>
+<p>由上图,我们不难看出,若需要在外网访问家中的 Klipper,就需要映射 <strong>80</strong> 和 <strong>7125</strong> 两个端口。</p>
+<p>于路由器的 <strong>端口转发/端口映射</strong> 界面配置 80 和 7125 的转发即可。家庭宽带的公网 IP 不会开放 80 端口,可将外网端口配置为 8080,对应的内网端口为 80 即可。</p>
+<p>接着,在 Moonraker 配置 <code>moonraker.conf</code> <code>[authorization]</code> 模块的 <code>cors_domains</code> 模块中添加你的域名,格式为 <code>*://你们域名</code></p>
+<p>也可以选择不使用自己搭建的前端网页,而使用 <a href="http://app.fluidd.xyz/">Fluidd</a> 或者 <a href="%5BMainsail%5D(http://my.mainsail.xyz/)">Mainsail</a> 作者搭建的前端网页。在 Moonraker 配置 <code>moonraker.conf</code> <code>[authorization]</code> 模块的 <code>cors_domains</code> 模块中添加 <code>*://my.mainsail.xyz 与 *://app.fluidd.xyz</code></p>
+<h2 id="方法二:内网穿透"><a href="#方法二:内网穿透" class="headerlink" title="方法二:内网穿透"></a>方法二:内网穿透</h2><p><strong>本人不推荐使用这个方法,固仅简述一下</strong></p>
+<p>可以使用花生壳等内网穿透服务,但给的带宽太小,只能使用控制界面,不能使用摄像头。</p>
+<p>也可以选择自建内网穿透,例如 Frp, Ngrok 等服务。但最近越来越多 Frp 服务器遭到攻击,固不建议自建。</p>
+<h2 id="方法三:使用-VPN"><a href="#方法三:使用-VPN" class="headerlink" title="方法三:使用 VPN"></a>方法三:使用 VPN</h2><p>这是本人推荐的方法。</p>
+<p>与 Octoprint + Marlin 仅需要映射 80 端口不同,Klipper 还需要映射 Moonraker 的 7125 端口,配置端口转发与实际使用都不如前者来的方便。</p>
+<p>我个人推荐用诸如 Zerotier, Tailscale 一类的 VPN 软件,搭建自己的小内网,通过内网 IP 直接访问 Klipper,既安全又方便。</p>
+<p>Zerotier 或者 Tailscale 的使用技巧网上一大片,我就不赘述了。</p>
+
+
+
+ 2022-02-12T06:50:00.000Z
+
+
+ https://blog.udon.eu.org/archives/375e7789.html
+ 群晖搭建 VSCode 服务器与 Syncthing 服务
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>这次我尝试在群晖上搭建 VSCode 服务器与 Syncthing 服务,实现电脑与 NAS 间的代码同步与网页中的 Coding。</p>
+<span id="more"></span>
+
+<hr>
+<h2 id="VSCode-网页版的实现"><a href="#VSCode-网页版的实现" class="headerlink" title="VSCode 网页版的实现"></a>VSCode 网页版的实现</h2><h3 id="偶遇服务器软件"><a href="#偶遇服务器软件" class="headerlink" title="偶遇服务器软件"></a>偶遇服务器软件</h3><p>刷 RSS 时我看到 V2EX 上一个帖子分享了一个实用工具:<a href="%22https://github.com/conwnet/github1s%22">github+1s</a></p>
+<p>这个项目可以实现在 <strong>网页版 VSCode</strong> 中打开 GitHhub 上的代码。</p>
+<p>这个项目使用的 <a href="%22https://github.com/cdr/code-server%22">code-server</a> 引起了我的兴趣。</p>
+<h3 id="code-server-的部署"><a href="#code-server-的部署" class="headerlink" title="code-server 的部署"></a>code-server 的部署</h3><p>群晖自带的 Docker 套件简化了部署的过程。</p>
+<p>在注册表中搜索 <a href="https://registry.hub.docker.com/r/codercom/code-server/">code-server</a> 下载 image;</p>
+<p>打开 image 进行配置:</p>
+<ul>
+<li>使用高权限执行容器</li>
+<li>在 <code>高级设置-环境</code> 页面中添加环境变量 <code>PASSWORD</code>,值设定为你的登陆密码(由于在 Docker 页面中以明文保存,请注意密码安全)。</li>
+</ul>
+<p>启动容器,并使用 Docker 内置的 <code>终端机</code> 打开一个新的 <code>bash</code>。换源、更新 apt 、安装常用软件我就不再赘述。</p>
+<h3 id="code-server-的外网访问"><a href="#code-server-的外网访问" class="headerlink" title="code-server 的外网访问"></a>code-server 的外网访问</h3><p>code-server 没有自带 HTTPS 相关的配置,需要使用网站服务器进行反向代理。</p>
+<p>目前比较流行的有 Caddy 和 NGINX 两款。</p>
+<p>鉴于 Caddy 配置简单且 HTTPS 优先,我这次使用 Caddy。</p>
+<p><a href="https://caddyserver.com/docs/install#debian-ubuntu-raspbian">Caddy 官方安装文档</a></p>
+<p>或使用一键安装脚本</p>
+<p><code>curl https://getcaddy.com | bash -s personal</code></p>
+<p>如果有开放的 443 端口,则可使用 Caddy 的自动 HTTPS 功能进行快速配置。</p>
+<p>若像我一样在家中的 NAS 上配置 code-server,则需要自己申请 tls 证书 (如 Let`s Encrypt),并按照 <a href="https://dengxiaolong.com/caddy/zh/tls.html">Caddy-tls</a> 配置。</p>
+<p>反向代理配置可参考 <a href="https://github.com/cdr/code-server/blob/main/docs/guide.md#lets-encrypt">code-server 官方的反代配置教程</a>。</p>
+<h3 id="一些疑难杂症"><a href="#一些疑难杂症" class="headerlink" title="一些疑难杂症"></a>一些疑难杂症</h3><h4 id="一些插件无法安装"><a href="#一些插件无法安装" class="headerlink" title="一些插件无法安装"></a>一些插件无法安装</h4><p>目前 code-server 的 VSCode 版本为 1.51.1, VSCode 官方则为 1.54.3 ,因此某些较新的插件可能无法使用。</p>
+<p>可以前往 <a href="https://marketplace.visualstudio.com/">VS插件市场</a> 下载旧版插件并手动安装。</p>
+<h4 id="Docker-内挂载的目录无写权限"><a href="#Docker-内挂载的目录无写权限" class="headerlink" title="Docker 内挂载的目录无写权限"></a>Docker 内挂载的目录无写权限</h4><p>使用 <code>sudo chmod 777 ./</code> 给 coder 用户赋予读写权力。</p>
+<h4 id="Docker-内-Caddy-无法自启"><a href="#Docker-内-Caddy-无法自启" class="headerlink" title="Docker 内 Caddy 无法自启"></a>Docker 内 Caddy 无法自启</h4><p>这个我也还没有解决。暂且手动启动。</p>
+<h4 id="code-server-的各种性能问题"><a href="#code-server-的各种性能问题" class="headerlink" title="code-server 的各种性能问题"></a>code-server 的各种性能问题</h4><p>等待更多的更新吧,我接下来会尝试在 Docker 里编译原版 VSCode 并开启 Web 模式,对比二者性能。</p>
+<h2 id="Syncthing-服务搭建"><a href="#Syncthing-服务搭建" class="headerlink" title="Syncthing 服务搭建"></a>Syncthing 服务搭建</h2><p><a href="https://syncthing.net/">Syncthing 官网</a> 已经给出了十分详尽的安装教程,也有群晖的安装包,我就不再赘述安装过程。</p>
+<p>Syncthing 的管理页面端口为 <code>8384</code>,若想在外网访问请使用 HTTPS。可以使用群晖内置的反向代理服务器进行反代。</p>
+<p>要注意把 <code>22000</code> 端口的 <code>TCP</code> 与 <code>UDP</code> 全部开放,才可在外网顺利与 NAS 同步。</p>
+
+
+ 2021-03-19T16:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/a455b52c.html
+ 逃离国产软件 - 虚拟机计划
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>使用 Hyper-V 运行 Windows LTSC 虚拟机,以隔离流氓的国产软件们。</p>
+<h3 id="为何大费周章?"><a href="#为何大费周章?" class="headerlink" title="为何大费周章?"></a>为何大费周章?</h3><p>我试过网络上的不少方法来隔离鹅厂的软件 —— 刚开源的 Sandboxie 也好,利用 Windows ACL 机制通过低权账户加以限制也好 —— 都因为权限问题失败了。最后决定用虚拟环境直接隔离这些软件。</p>
+<span id="more"></span>
+
+<h3 id="为什么是-Hyper-V-和-LTSC?"><a href="#为什么是-Hyper-V-和-LTSC?" class="headerlink" title="为什么是 Hyper-V 和 LTSC?"></a>为什么是 Hyper-V 和 LTSC?</h3><p>我用过 VMWare,觉得还是 Windows 原生的 Hyper-V 启动最快。还不用钱。</p>
+<p>Windows LTSC 是企业定制版,官方精简了系统,性能开销更少。</p>
+<h3 id="事前准备"><a href="#事前准备" class="headerlink" title="事前准备"></a>事前准备</h3><p>拥有 16G 以上内存及 NVME 高速硬盘的用户可以放心采用该方案,虚拟机运行时不会影响其他软件的流畅运行。</p>
+<h4 id="下载-MSDN-上的-Windows-LTSC"><a href="#下载-MSDN-上的-Windows-LTSC" class="headerlink" title="下载 MSDN 上的 Windows LTSC:"></a>下载 <a href="https://msdn.itellyou.cn/">MSDN</a> 上的 Windows LTSC:</h4><p>侧边栏选择 <strong>操作系统</strong> ;选择 <strong>Windows 10 Enterprise LTSC 2019</strong>。</p>
+<h4 id="安装-Hyper-V:"><a href="#安装-Hyper-V:" class="headerlink" title="安装 Hyper-V:"></a>安装 Hyper-V:</h4><p>对于 Windows 专业版 用户,在 控制面板 - 程序与功能 - 启动或关闭Windows功能 中找到 <strong>Hyper-V</strong> 并打开。</p>
+<p>对于 其他版本 Windows 的用户,则稍微有些麻烦:</p>
+<ol>
+<li>在记事本中输入如下代码</li>
+</ol>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">pushd</span> <span class="hljs-string">"%~dp0"</span><br><br><span class="hljs-built_in">dir</span> /b %SystemRoot%\servicing\Packages\*Hyper-V*.mum >hyper-v.txt<br><br><span class="hljs-keyword">for</span> /f %%i <span class="hljs-keyword">in</span> (<span class="hljs-string">'findstr /i . hyper-v.txt 2^>nul'</span>) <span class="hljs-keyword">do</span> dism /online /norestart /add-package:<span class="hljs-string">"%SystemRoot%\servicing\Packages\%%i"</span><br><br>del hyper-v.txt<br><br>Dism /online /enable-feature /featurename:Microsoft-Hyper-V-All /LimitAccess /ALL<br></code></pre></td></tr></table></figure>
+
+<ol start="2">
+<li>把文件保存为Hyper-V.cmd</li>
+<li>右键该文件,选择 <strong>以管理员身份运行</strong></li>
+</ol>
+<p>根据提示完成安装。</p>
+<blockquote>
+<p>摘自知乎 <a href="https://zhuanlan.zhihu.com/p/51939654">没人不认识我</a> 的回答</p>
+</blockquote>
+<h3 id="安装虚拟机"><a href="#安装虚拟机" class="headerlink" title="安装虚拟机"></a>安装虚拟机</h3><p>打开 Hyper-V ,选择 <strong>新建 - 虚拟机</strong> ;</p>
+<p>根据向导提示设置虚拟机,选择 <strong>第一代虚拟机</strong> ;</p>
+<p>内存分配我选了 2G (共16G);CPU 分配我选了 4线程 (共12线程)【需要完成配置后在 <strong>设置</strong> 中更改】;</p>
+<p>其余设置默认或自定;</p>
+<p>安装选项选择 <strong>从 CD/DVD-ROM 安装操作系统</strong> ,选择刚刚下载好的 Windows LTSC ISO镜像;</p>
+<p>完成配置后,启动虚拟机,安装 Windows LTSC 到硬盘。</p>
+<h3 id="配置环境"><a href="#配置环境" class="headerlink" title="配置环境"></a>配置环境</h3><p>装好系统后要干什么不用我说了吧。</p>
+<p>把垃圾们倒进去就好啦。</p>
+<p>实测空载消耗 CPU 算力在 0%-3% 浮动;内存占用 2.2G,实际使用 1.2G 。</p>
+
+
+
+ 2020-08-07T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/e3c95af8.html
+ BGP初体验-Linux,Openwrt与Quagga
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>好久没写博客了!今天抽出点时间分享一下我的 BGP 初体验。</p>
+<p>这一切的一切都要从一个叫鹤伞 Ria 的 Vtuber 说起……</p>
+<span id="more"></span>
+
+<h2 id="环境配置"><a href="#环境配置" class="headerlink" title="环境配置"></a>环境配置</h2><h3 id="操作环境"><a href="#操作环境" class="headerlink" title="操作环境"></a>操作环境</h3><p>Debian 系 Linux(其实是 Kali):</p>
+<p><code>sudo apt update & sudo apt install quagga</code></p>
+<p>Openwrt:</p>
+<p><code>opkg update & opkg install quagga quagga-zebra quagga-bgpd quagga-vtysh</code></p>
+<h3 id="网络配置"><a href="#网络配置" class="headerlink" title="网络配置"></a>网络配置</h3><p>采用 Zerotier 建立内网环境模拟真实网络。</p>
+<p>Zerotier 的安装及配置不再赘述,官网有详尽教程。</p>
+<h2 id="网络环境"><a href="#网络环境" class="headerlink" title="网络环境"></a>网络环境</h2><p>本来想做三台设备两个 AS 间的通讯,有一台设备无法安装任何 BGP 软件也就作罢;也没有画拓扑图的必要了(悲)。</p>
+<p>AS114514 Debian 10.0.1.1,命名为 R1,享有 10.0.1.0/24 网段;</p>
+<p>AS1919810 Openwrt 10.0.2.1,命名为 R2,享有 10.0.2.0/24 网段。</p>
+<h2 id="Quagga配置"><a href="#Quagga配置" class="headerlink" title="Quagga配置"></a>Quagga配置</h2><p>下面才是重头戏。Quagga 的配置文件位于 <code>/etc/quagga/</code></p>
+<p>据测试,Openwrt 安装 quagga 后会带有初始配置,而 Debian 不带初始配置,可自行创建。</p>
+<p><code>/etc/quagga/zebra.conf</code> 配置(可不用修改)</p>
+<figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs pgsql">! 登陆密码<br><span class="hljs-keyword">password</span> zebra<br>!<br><span class="hljs-keyword">access</span>-list vty permit <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>/<span class="hljs-number">8</span><br><span class="hljs-keyword">access</span>-list vty deny <span class="hljs-keyword">any</span><br>!<br><span class="hljs-type">line</span> vty<br> <span class="hljs-keyword">access</span>-<span class="hljs-keyword">class</span> vty<br></code></pre></td></tr></table></figure>
+
+<p><code>/etc/quagga/bgpd.conf</code> 配置(需要根据情境修改)</p>
+<figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs diff"><span class="hljs-addition">! 密码</span><br>password zebra<br><span class="hljs-addition">! AS号</span><br>router bgp 114514<br><span class="hljs-addition">! 本机公网(VPN网络)ip</span><br> bgp router-id 10.0.1.1<br><span class="hljs-addition">! 本路由享有网段(需要交换的网段)</span><br> network 10.0.1.0/24<br><span class="hljs-addition">! peer信息(建立连接的机器的公网(VPN网络)ip,AS及称呼)</span><br> neighbor 10.0.2.1 remote-as 1919810<br> neighbor 10.0.2.1 description R2<br></code></pre></td></tr></table></figure>
+
+<p> 另一台机器的配置只需依葫芦画瓢,我就不再赘述。</p>
+<p>配置之后,运行</p>
+<p><code>/etc/init.d/zebra restart</code></p>
+<p><code>/etc/init.d/bgpd restart</code>(Debian)</p>
+<p>或者</p>
+<p><code>/etc/init.d/quagga restart</code>(Openwrt)</p>
+<p>重启 quagga 服务。</p>
+<h2 id="欣赏结果"><a href="#欣赏结果" class="headerlink" title="欣赏结果"></a>欣赏结果</h2><p>忙活了这么久,终于能看到结果了!</p>
+<p>运行</p>
+<p><code>vtysh</code></p>
+<p>进入 quagga 控制台(指令模拟 Cisco,这块不大了解)</p>
+<p>输入</p>
+<p><code>show ip bgp neighbor</code></p>
+<p>就会看到</p>
+<figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs pgsql">BGP neighbor <span class="hljs-keyword">is</span> <span class="hljs-number">10.0</span><span class="hljs-number">.2</span><span class="hljs-number">.1</span>, remote <span class="hljs-keyword">AS</span> <span class="hljs-number">1919810</span>, <span class="hljs-keyword">local</span> <span class="hljs-keyword">AS</span> <span class="hljs-number">114514</span>, <span class="hljs-keyword">external</span> link<br>Description: R2<br>BGP <span class="hljs-keyword">version</span> <span class="hljs-number">4</span>, remote router ID <span class="hljs-number">10.0</span><span class="hljs-number">.2</span><span class="hljs-number">.1</span><br></code></pre></td></tr></table></figure>
+
+<p>还有这么一张表</p>
+<pre><code class="hljs"> Sent Rcvd
+Opens: 2 0
+Notifications: 0 0
+Updates: 2 2
+Keepalives: 1050 1049
+Route Refresh: 0 0
+Capability: 0 0
+Total: 1054 1051
+</code></pre>
+<p>再看看路由器内的活动ipv4路由表</p>
+<table>
+<thead>
+<tr>
+<th align="center">网络</th>
+<th align="center">对象</th>
+<th align="center">IPv4 网关</th>
+<th align="center">跃点数</th>
+<th align="center">表</th>
+</tr>
+</thead>
+<tbody><tr>
+<td align="center">xxx</td>
+<td align="center">10.0.2.0/24</td>
+<td align="center">10.0.2.1</td>
+<td align="center">20</td>
+<td align="center">main</td>
+</tr>
+</tbody></table>
+<p>就算大功告成了!</p>
+<h2 id="有什么用处呢?"><a href="#有什么用处呢?" class="headerlink" title="有什么用处呢?"></a>有什么用处呢?</h2><p><strong>没有。</strong></p>
+<p>内网测试唯一能享受的就是看着这条无形的链接,想象自己也是网络工程师。</p>
+<p>但是还是感觉很爽!</p>
+<p>而且你已经学会(大概)了 BGP,获取全球路由表也能办到了!</p>
+<p>参考 Ria 的爸爸<del>(我的岳父)</del>的文章</p>
+<p><a href="https://blog.foxsar.black/?p=246">使用bird配置bgp网络互连</a></p>
+<p>至于这一切与 Ria 有啥关系?欢迎关注 Ria 了解详情(滑稽)</p>
+<p><a href="https://t.me/kanaria_group">Telegram群组</a> <a href="https://space.bilibili.com/2450927">bilibli</a> <a href="https://www.youtube.com/channel/UCC12ijOcPxnRSQPLvjWYXUg">Youtube</a></p>
+
+
+ 2020-02-10T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/9d1c6fa4.html
+ Joplin+Webdav同步问题的解决方案
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="问题内容"><a href="#问题内容" class="headerlink" title="问题内容"></a>问题内容</h2><p>在群晖上搭建了 Webdav 服务器,使用 Joplin 连接后无法同步笔记。</p>
+<span id="more"></span>
+
+<h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><h3 id="错误示范:"><a href="#错误示范:" class="headerlink" title="错误示范:"></a>错误示范:</h3><p>‘ <a href="https://your.domain.com:[your">https://your.domain.com:[your</a> port] ‘</p>
+<p>以此路径访问的是群晖文件系统的 <code>/ </code> 目录,由于没有权限读写,同步失败。</p>
+<h3 id="正确示范:"><a href="#正确示范:" class="headerlink" title="正确示范:"></a>正确示范:</h3><p>在群晖控制面板内新建一个名为 <code>Joplin</code> 的共享文件夹。以域名:</p>
+<p>‘ <a href="https://your.domain.com:[your">https://your.domain.com:[your</a> port] /Joplin’</p>
+<p>访问即可同步。</p>
+<h3 id="自定义配置:"><a href="#自定义配置:" class="headerlink" title="自定义配置:"></a>自定义配置:</h3><p>若想在已经存在的文件夹下同步 Joplin 笔记,按照正确示范所书写的 URL 书写路径即可在想要的地方同步。</p>
+<h2 id="解决方案的探索"><a href="#解决方案的探索" class="headerlink" title="解决方案的探索"></a>解决方案的探索</h2><p>(我并未了解过 Webdav 的原理)<br>遇到此问题时,我在上 Google 查找解决方法前试着自行分析。思考原因后我选择了抓包分析。</p>
+<p>结合抓包结果和 Joplin 同步日志可以看到 Joplin 在 <code>/ </code> 目录下查找了 <code>.lock</code> 等文件。结合其他 Webdav 软件可以看到访问的 URL 指向的是目标文件夹,即“域名:端口/目标文件夹”,由此推测需要在配置内为 Joplin 指明同步目录,否则将在没有权限的根目录下同步,导致失败。</p>
+<p>这是一次没有什么技术含量但能启发我的尝试。若你也遇到了类似的问题,希望也能启发到你。</p>
+
+
+ 2020-01-06T10:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/27f2d840.html
+ iPv6下绝佳的DDNS方法-dynv6
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>下面我将介绍一种适用于 IPv6-DDNS 的绝佳方法!</p>
+<span id="more"></span>
+
+<h3 id="通过-IPv6-访问的优点"><a href="#通过-IPv6-访问的优点" class="headerlink" title="通过 IPv6 访问的优点"></a>通过 IPv6 访问的优点</h3><p>没有端口限制!!!可以通过 80/443 访问 web 服务器了!不用带着烦人的端口号!</p>
+<p>每台设备有独立的 IPv6,配置更加方便,无需路由器充当网关进行端口转发!</p>
+<h3 id="IPv6-也有不足之处"><a href="#IPv6-也有不足之处" class="headerlink" title="IPv6 也有不足之处"></a>IPv6 也有不足之处</h3><p>最严重的问题:很多家宽并没有开启 IPv6 的获取。但访问 IPv6 的设备必须要拥有 IPv6 地址!</p>
+<p>技术问题,难以解决。一般来说手机的移动网络都已分发 IPv6 地址。</p>
+<p>想要家宽拥有 IPv6?或许你需要修改光猫设置(超级管理),亦或是将光猫改为桥接模式,用路由器拨号从而获取 IPv6 地址。</p>
+<h3 id="如何实现iPv6-DDNS"><a href="#如何实现iPv6-DDNS" class="headerlink" title="如何实现iPv6-DDNS"></a>如何实现iPv6-DDNS</h3><ul>
+<li>在 <a href="https://dynv6.com/">dynv6</a>注册一个账号</li>
+<li>在 <strong>Instructions</strong> 界面查看API与你的DDNS域名</li>
+<li>下载官方提供的 <a href="https://gist.github.com/corny/7a07f5ac901844bd20c9">a nice script</a></li>
+</ul>
+<blockquote>
+<p>这点尤其重要!我在网络上寻寻觅觅无数脚本,总是失败。最后才发现官方有提供脚本也!一试马上就成功了。</p>
+</blockquote>
+<ul>
+<li>按照页面内提供的代码执行脚本</li>
+</ul>
+<figure class="highlight stata"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs stata"><span class="hljs-keyword">token</span>= *your <span class="hljs-keyword">token</span>* ./dynv6.<span class="hljs-keyword">sh</span> *your DDNS domain*<br></code></pre></td></tr></table></figure>
+
+<ul>
+<li>可以将其添加到 crontab 一类的软件内,规定时间自动执行脚本(每 10分钟一次为宜)</li>
+</ul>
+<p><strong>大功告成!</strong></p>
+<h3 id="事后"><a href="#事后" class="headerlink" title="事后"></a>事后</h3><ul>
+<li>你可以把自己的域名 CNAME 过去</li>
+<li>也可以用 dynv6 提供的域名</li>
+</ul>
+<p>随心所欲!</p>
+<p>Over.</p>
+
+
+ 2019-10-03T15:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/a4c81e8f.html
+ 搭建Calibre-Web电子书网页端管理服务
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h4 id="说在前面"><a href="#说在前面" class="headerlink" title="说在前面"></a>说在前面</h4><p>Kindle 是一样阅读利器,但若是没有一个强大的书库,它也只能用来压泡面(笑)</p>
+<p>今天我们利用 Docker 在群晖(任意系统)上搭建 Calibre-web 服务</p>
+<span id="more"></span>
+
+<h4 id="docker选择"><a href="#docker选择" class="headerlink" title="docker选择"></a>docker选择</h4><blockquote>
+<p><a href="https://hub.docker.com/r/technosoft2000/calibre-web/">technosoft2000-calibre-web</a></p>
+</blockquote>
+<p>我在尝试了 3 款 Docker Image 后,决定使用这款。</p>
+<h4 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h4><h5 id="Calibre-web"><a href="#Calibre-web" class="headerlink" title="Calibre-web"></a>Calibre-web</h5><ul>
+<li><p>基于 Python,性能高(低)</p>
+</li>
+<li><p>与 Calibre 软件的数据库等文件完全互通</p>
+</li>
+<li><p>支持推送至 Kindle</p>
+</li>
+<li><p>支持在线转码书籍(虽然问题较多)</p>
+</li>
+</ul>
+<h5 id="technosoft2000-calibre-web"><a href="#technosoft2000-calibre-web" class="headerlink" title="technosoft2000-calibre-web"></a>technosoft2000-calibre-web</h5><ul>
+<li><p>比起其他 Image 版本更新更加稳定(稳定很多)</p>
+</li>
+<li><p>支持在线转码(其他 Image 不行)</p>
+</li>
+<li><p>版本较新</p>
+</li>
+</ul>
+<h4 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h4><p>首先,将你的 Calibre 数据库的位置挂载到 Docker 内的 <strong>/books</strong>。</p>
+<p>启动 Docker 后不要着急,等待 Docker 内软件安装完毕后,访问您设置的端口,访问 Calibre-Web。</p>
+<p>在 <strong>Calibre 数据库位置</strong> 一栏填写</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">/books<br></code></pre></td></tr></table></figure>
+
+<p><strong>特性配置</strong> 可以依情况而变,不一定要按我的配置。</p>
+<p>若想要使用在线转码功能,<strong>外部二进制</strong> 一栏中,选择 <strong>使用 calibre 的电子书转换器</strong> 。 <strong>转换工具路径</strong> 按图片中填写。</p>
+<figure class="highlight armasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs armasm">/<span class="hljs-meta">opt</span>/calibre/ebook-convert<br></code></pre></td></tr></table></figure>
+
+<p>点击 <strong>提交</strong> 即可完成安装。</p>
+<p><strong>注意</strong></p>
+<p>若提示无法读取数据库,尝试将数据库所处的文件夹的权限改为<strong>755</strong> 。</p>
+<p>如何操作?请打开 SSH,在 Terminal 内操作。</p>
+<p>若还是失败,尝试在本地 Calibre 软件新建一个书库,将<strong>空的</strong>数据库文件移动到你挂载的目录下。</p>
+<p><strong>安装界面示意图</strong></p>
+<p><img src="/images/2019-10-02/1.jpg"></p>
+<h4 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h4><p>安装后的操作我就不多提了。该上传书籍的上传,该push的push。</p>
+<p>我的几个建议:</p>
+<ul>
+<li>推送邮箱推荐使用 Outlook,限制少。</li>
+<li>可以用 Calibre 软件管理数据库。不知道为什么,在本地做好更改后,网页版并没有任何变化。我试着备份又还原了数据库后,网页里再重新加载数据库才成功了。</li>
+<li>网页转码会遇到种种问题,例如电子书有加密。转码失败实属正常。</li>
+</ul>
+
+
+ 2019-10-02T10:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/87bacf3f.html
+ 群晖-外网访问一站式教程2 DDNS
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>简述配置 DDNS 的方法。</p>
+<h4 id="什么是DDNS"><a href="#什么是DDNS" class="headerlink" title="什么是DDNS"></a>什么是DDNS</h4><p><a href="https://zh.m.wikipedia.org/zh/%E5%8B%95%E6%85%8BDNS">维基百科-动态DNS</a><br>鉴于 IPv4 地址的枯竭,运营商开始给家用宽带分配动态 IP 地址,即 IP 会随时间或重新拨号而改变。DDNS 可以允许用户通过API动态地将变化的 IP 地址传送给域名解析商,达到域名外网访问的效果。<br>由于带宽分配原因,家用宽带的上传带宽一般在 20-30Mbps 间,故外网访问速度并没有达到如签约的 100Mbps 属正常。</p>
+<h4 id="事先准备"><a href="#事先准备" class="headerlink" title="事先准备"></a>事先准备</h4><p>打电话给 ISP(运营商)的小姐姐,让她给你公网 IP,如果问起原因可以回答家里装监控。没有开启公网 IP 将无法从外网访问家庭的内部网络。<br>由于运营商(指大部分,如电信)封锁了 80(HTTP)和 443(HTTPS)端口,我们将使用其他的端口进行访问。挑选一些你喜欢的端口,预备使用(如 8080-8090,4431-4439等)。</p>
+<h4 id="选择支持DDNS的域名解析服务商"><a href="#选择支持DDNS的域名解析服务商" class="headerlink" title="选择支持DDNS的域名解析服务商"></a>选择支持DDNS的域名解析服务商</h4><h5 id="CloudFlare"><a href="#CloudFlare" class="headerlink" title="CloudFlare"></a>CloudFlare</h5><p>老牌的域名解析商,也是少有的免费提供 CDN 的服务商。<br>我推荐 CloudFlare 的原因有三点</p>
+<ol>
+<li>可以使用 CDN,保证网络质量始终处于较好状态。例如,我的 Blog 搭建在 Github 上,若有时因网络抽风无法访问 Github,CDN 能助你一臂之力。</li>
+<li>可以查看连接数,数据量,访客量等详细数据。</li>
+<li>API 获取方便。(2022 Update: CloudFlare 的 API 服务器接近半墙,国内很难再访问了,不推荐使用)</li>
+</ol>
+<h5 id="Dynv6"><a href="#Dynv6" class="headerlink" title="Dynv6"></a>Dynv6</h5><p>提供 IPv4 与 IPv6 DDNS 的服务商,在 21 年有一次较长时间的故障,平常都非常稳定。</p>
+<h5 id="DNSPod"><a href="#DNSPod" class="headerlink" title="DNSPod"></a>DNSPod</h5><p>被腾讯收购的 DNS 服务商,使用需实名。</p>
+<h4 id="配置-DDNS-服务"><a href="#配置-DDNS-服务" class="headerlink" title="配置 DDNS 服务"></a>配置 DDNS 服务</h4><p>家用路由器的 DDNS 功能一般仅支持国内大型服务商,例如花生壳。</p>
+<p>有两种方法可以配置自己的 DDNS 服务:</p>
+<ol>
+<li>将负责拨号的路由器刷成 Openwrt 系统,安装 DDNS 插件以配置自定义脚本的 DDNS 服务;</li>
+<li>在一台 24x7 运行的设备上,通过 API 获取 IP 地址,并定时执行脚本更新 IP 地址;</li>
+</ol>
+<p>前者虽然更加麻烦,但可以实现仅在 IP 更换时发起更新解析的请求,而不需要定期(如每十分钟)请求一次 API,减小账户被封的风险,并尽可能地缩短从 IP 更换到新的解析生效的时间。</p>
+<p>不管是路由器也好,Linux 上的脚本也好,可以在 GitHub 上寻找对应 DDNS 服务商的更新脚本,填上配置就能使用啦~</p>
+
+
+ 2019-08-31T10:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/8776c6d2.html
+ 群晖-外网访问一站式教程1 Zerotier
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>说在前面:有一个月没更新文章了。今后尽量做到 1-2 周更一篇吧。Goo 酱终于把我的 Blog 收录了(其实是我主动提交的),这些教程也能被大家看到啦!有任何问题可以在文章结尾的 Gitalk 处留言,我会尽快回复的!</p>
+<p>这篇教程无图,省流。(其实经过 Gzip 压缩后已经能省 80% 的数据量,我也被惊到了)</p>
+<span id="more"></span>
+
+<hr>
+<p>在上一回的教程里,我们成功搭建了MC基岩版服务器。</p>
+<p><a href="https://blog.udon.icu/2019/07/27/MC_Server/">群晖-MC基岩版服务器教程</a></p>
+<p>但这个服务器仅存在于本地,若朋友不在同一个局域网下,就无法连接此服务器(这不是肯定的嘛)。</p>
+<p>下面我将介绍几种从往外访问家里的群晖服务器的方法。</p>
+<hr>
+<h4 id="最简单的方法-Zerotier-VPN-法"><a href="#最简单的方法-Zerotier-VPN-法" class="headerlink" title="最简单的方法 - Zerotier - VPN 法"></a>最简单的方法 - Zerotier - VPN 法</h4><p>有兴趣了解 VPN 技术的朋友可以点击查看 <a href="https://zh.wikipedia.org/wiki/%E8%99%9B%E6%93%AC%E7%A7%81%E4%BA%BA%E7%B6%B2%E8%B7%AF">维基百科 - VPN</a></p>
+<p>Zerotier 可以提供免费的VPN服务,无需复杂配置即可在任何地方连接本地服务器</p>
+<h5 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h5><p>第一步:<a href="https://download.zerotier.com/dist/synology/">下载对应版本的 Zerotier 软件</a> 并于软件市场安装</p>
+<p>示范:</p>
+<p>例如我的群晖的CPU是 64位 的 J1900,我选择了这一款软件<a href="https://download.zerotier.com/dist/synology/zerotier_x64-6.1_1.4.0-0.spk">zerotier_x64-6.1_1.4.0-0.spk</a></p>
+<p>接下来,在群晖的 <code>套件中心-设置-信任层级</code> 中选择 <code>任何发行者</code></p>
+<p>然后在 <code>套件中心-手动安装</code> 里选择刚刚下载好的 <code>zerotier_xxxxxx.spk</code> 安装包进行安装</p>
+<p>第二步:注册Zerotier。网络上教程极多,我就不再赘述,可以参考这一篇文章。</p>
+<p><a href="https://www.jianshu.com/p/eefddb256ad7">内网穿透工具 — ZeroTier One 的使用</a> 作者 <a href="https://www.jianshu.com/u/b28343f20873">BiteMan</a></p>
+<p>第三步:记住刚刚注册完拿到的 Network ID 了么,打开 Zerotier 套件,在右下角输入 Network ID 点击 join,就加入这个网络(Networks)啦。记得在 Zerotier 网站控制台里勾选这个新连接(新创建的网络默认使用加入需要验证的规则,我也建议开启,增强安全性)(我强烈建议每添加一台设备即为其命名)。</p>
+<p>第四步:在需要访问群晖服务器的设备上也安装好 Zerotier 软件(比起手动安装要简单不少,各大应用市场里也都有,建议下载官方提供的安装包),重复步骤三,加入同一个网络(Networks)下。</p>
+<p>到此,Zerotier 双端已经配置完毕。</p>
+<h5 id="如何使用呢?"><a href="#如何使用呢?" class="headerlink" title="如何使用呢?"></a>如何使用呢?</h5><p>在 Zerotier 网页控制台里,若已添加两台设备,你会看到 Members 分栏中有两台设备,他们的右边都有一个 IP 地址。当两个设备都打开 Zerotier 软件并成功连接了 VPN,你就可以把他们当作在一个局域网下,IP 地址就是 Zerotier 提供的那个,端口号原封不动。</p>
+<p>例如我想访问群晖的DSM管理界面,输入 <code>xxx.xxx.xxx.xxx:5000</code> 即可。(Zerotier提供了几种保留网段,可以从 10、172、192 网段里选择你喜欢的 IP 段)</p>
+<h5 id="已知问题"><a href="#已知问题" class="headerlink" title="已知问题"></a>已知问题</h5><p>Zerotier 适合访问网页/SSH 等简单服务,若用其连接游戏,可能会有某些时刻丢包率过高(原因不明)导致强制登出,严重影响游戏体验。</p>
+<p>下一篇文章里我会介绍第二种方法 - DDNS,此方法可以带来极致体验(笑)。</p>
+
+
+ 2019-08-20T04:00:00.000Z
+
+
diff --git "a/category/\346\225\231\347\250\213/feed.json" "b/category/\346\225\231\347\250\213/feed.json"
new file mode 100644
index 00000000..f7b3d300
--- /dev/null
+++ "b/category/\346\225\231\347\250\213/feed.json"
@@ -0,0 +1,216 @@
+{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "カレーうどん屋 • All posts by \"教程\" category",
+ "description": "カレーうどん屋.",
+ "home_page_url": "https://blog.udon.eu.org",
+ "items": [
+ {
+ "id": "https://blog.udon.eu.org/archives/8b68ddd6.html",
+ "url": "https://blog.udon.eu.org/archives/8b68ddd6.html",
+ "title": "修复 UEFI 引导的 GRUB",
+ "date_published": "2023-04-15T16:00:00.000Z",
+ "content_html": "问题与解决方法 环境 Manjaro Linux x86_64
\nKernel: 6.2.10-1-MANJARO
\n使用 UEFI 引导
\n问题 在 GRUB 尝试引导 Linux 内核时,出现如下错误:
\n1 2 3 4 5 error: sparse file not allowed. 452: out of range pointer: xxxxxxxxxx Aborted. Press any key to exit.
\n\n用户将无法进入系统。
\n解决方法 进入恢复系统 插入 Manjaro LiveCD, 启动 Live 系统。
\n确定磁盘分区 在 Live 系统中,使用 fdisk -l
查看磁盘分区情况,找到安装 Manjaro 的磁盘,假设为 /dev/sda
。
\n我的磁盘分区如下:
\n1 2 3 4 5 设备 起点 末尾 扇区 大小 类型 /dev/sda1 2048 821247 819200 400M EFI 系统 /dev/sda2 821248 723390463 722569216 344.5G Linux 文件系统 /dev/sda3 723390464 983437311 260046848 124G Linux 文件系统 /dev/sda4 983437312 1000214527 16777216 8G Linux 文件系统
\n\n可以确定,/dev/sda1
是 EFI 系统分区,/dev/sda2
是系统所在分区。
\n挂载分区 挂载系统分区:
\n \n\n将当前系统的工具分区挂载到 /mnt
下:
\n1 2 3 mount --bind /dev /mnt/dev mount --bind /proc /mnt/proc mount --bind /sys /mnt/sys
\n\n将 EFI 分区挂载到 /mnt/boot/efi
下:
\n1 mount /dev/sda1 /mnt/boot/efi
\n\n进入系统 \n\n重新安装 GRUB 1 grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable
\n\n具体参数需要根据实际情况进行修改。
\n在这之后 重启,进入通过 GRUB 引导系统。
\n在系统中使用 sudo grub-install --recheck /dev/sda
命令再次安装 GRUB,确保系统能够正常启动。
\n一些思考 接下来的内容是我的整个修复流程,包含了如何在搜索引擎查找问题、根据文章内容调整目标等杂碎的内容,和我在修复过程中的一些感想。
\n为什么会出现这个问题 不是很清楚。
\n在启动 Manjaro 前我不小心打开了电脑里的 Windows 系统,但没有连接移动硬盘。
\n按照以往的经验,这最多只会导致找不到 GRUB 的位置,手动指定 GRUB 所在分区就可以启动系统。
\n但这次不大一样。
\n在打开 GRUB 之后,尝试引导内核,就发现了这个问题。
\n初步解决思路 立刻格式化磁盘,重新安装 Manjaro。
\n我已经不是曾经那个只会重装的我了,这次我希望可以解决问题,而不是简单地重装。
\n首先,我 Google 了这个错误,发现了几篇内容相关的文章。
\n报错与我一致的文章 ,但没有给出解决方案。
\n要我删除 GRUB 和 UEFI 所在分区所有内容的文章 ,有点可怕,不敢这么干。
\n提到应该重新安装 GRUB 的文章 ,这还有点道理。
\n于是,我的目标转变为重新安装 GRUB。
\n重新安装 GRUB 在之前遇到找不到 GRUB 分区的问题时,在手动引导然后进入系统后,我会执行 sudo grub-install --recheck /dev/sda
重新安装 GRUB,解决这个问题。
\n那这次的觉得方案应该是差不多的……吧?
\n不对啊,这次是在 LiveCD 的系统里操作,怎么能直接安装 GRUB 呢?
\n这个问题比较难描述。
\n我先是 Google grub-install 修复 GRUB
,中文网站的内容都是关于在可以启动的系统下修复 GRUB 的,没有我需要的内容。
\n然后我开始求助于 ChatGPT,输入的 Prompts 是:
\n1 2 I am using Manjaro with GRUB. When I booted into the system , it says "sparse file not allowed 452 out of range pointer" . How to fix it ?
\n\n不难发现,我并没有说明我使用的是 UEFI 引导,这直接影响到了 ChatGPT 回复的准确性。
\nChatGPT 给出的修复步骤与上述的相差不大,只是在挂载系统分区和工具分区后,直接尝试执行 grub-install
命令,尝试修复。
\ngrub-install
返回错误 this gpt partition label contains no bios boot partition
把我弄得更懵了。
\n再次 Google 这个问题,发现了 这篇在长篇大论讲 GRUB 的文章 ,虽然没有给出解决方案,但它让我意识到 UEFI 和 Legacy BIOS 两种启动方式的区别。
\nUEFI 和 Legacy BIOS UEFI 和 Legacy BIOS 是两种启动方式,它们的区别在于,Legacy BIOS 是在 BIOS 中直接加载内核,而 UEFI 是在 BIOS 中加载 EFI 系统,然后由 EFI 系统加载内核。
\n使用 UEFI 引导的系统一般都有一个 200MB 到 400MB 的 EFI 系统分区,用于存放 EFI 系统。在上述的,我的硬盘分区中可以看到。
\n使用 findmnt
命令可以查看当前系统的挂载情况,其中 TARGET
列就是挂载点,SOURCE
列就是挂载的分区。
\nEFI 分区的挂载情况为:
\n1 2 TARGET SOURCE FSTYPE OPTIONS /boot/ efi /dev/ sda1 vfat rw,relatime,fm
\n\n可以看到,/boot/efi
里的内容正是 EFI 系统分区的内容。(我也是刚学到这个知识的)
\n解决 UEFI 相关问题 在修复过程中,我是通过 Google 发现上述的问题。
\n这篇文章 给了我莫大的帮助。
\n其中提到了 EFI 分区,也提到了如何正确安装 UEFI 引导的 GRUB:
\n1 2 sudo grub-install --target=x86_64-efi --efi-directory=/boot/efisudo grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable
\n\n在补充挂载 EFI 分区、切换 Root 目录后,执行第一条命令,发现有错误。尝试执行第二条命令,发现没有错误,代表 GRUB 已经重新安装成功。
\n此时我想起来,在之前安装 GRUB 时,会提示 正在为 x86_64-efi 平台进行安装
,我才意识到前面的修复过程并没有去指定平台。
\n总结一下 总之,这就是我此次修复的心路历程。我没有研究过 UEFI 和 Legacy BIOS 的区别,也没有研究过 GRUB 的安装过程,所以在修复过程中,我是通过 Google 和 ChatGPT 的帮助才解决了这个问题。
\n希望这个探索过程能给你一些启发吧。
\n \n此文章以 我无所谓 By 不 By 什么 AI,对我有帮助的文章就是好文章 标识发布。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/8b115688.html",
+ "url": "https://blog.udon.eu.org/archives/8b115688.html",
+ "title": "使用 Docker Compose 部署音乐服务器 Navidrome",
+ "date_published": "2023-01-31T04:00:00.000Z",
+ "content_html": "服务介绍 Navidrome 是一款兼容 Subsonic API 的开源音乐服务器软件,它提供了一个不错的 WebUI,也可以将支持 Subsonic API 的客户端接入。
\n目前项目正处在活跃开发中,各种各样的新功能正在陆续加入。
\n我的客户端选择 电脑端 自带 WebUI, Sonixd 【跨平台】
\niOS play:sub 【付费软件 4.99$】
\n部署方式 采用 Docker Compose 部署 Navidrome,使用 Nginx 作为反向代理。
\nDocker Compose 配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 version: "3" services: navidrome: image: deluan/navidrome:latest container_name: navidrome user: 1000 :1000 ports: - "127.0.0.1:4533:4533" restart: unless-stopped environment: ND_SCANSCHEDULE: 1h ND_LOGLEVEL: info ND_SESSIONTIMEOUT: 24h ND_BASEURL: "" ND_SEARCHFULLSTRING: true ND_SPOTIFY_ID: ND_SPOTIFY_SECRET: ND_LASTFM_APIKEY: ND_LASTFM_SECRET: ND_LASTFM_LANGUAGE: en volumes: - "./data:/data" - "/APTH-TO/navidrome-music:/music:ro"
\n使用命令 docker compose up -d
启动服务。
\nNginx 配置文件 我建议使用 DigitalOcean 的 Nginx 配置生产工具,示例配置如下:
\n示例配置
\n也可参考下述配置,此为 DigitalOcean 生成配置的简化版:
\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name music.example.com; ssl_certificate /etc/letsencrypt/live/music.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/music.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/music.example.com/chain.pem; access_log /var/log/nginx/access.log combined buffer=512k flush=1m ; error_log /var/log/nginx/error .log warn ; location / { proxy_set_header Host $host ; proxy_pass http://127.0.0.1:4533; } }server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name *.music.example.com ; ssl_certificate /etc/letsencrypt/live/music.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/music.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/music.example.com/chain.pem; return 301 https://music.example.com$request_uri ; }
\n\n音乐管理 我一直以文件夹分类的方式管理音乐,但 Navidrome 并不支持文件夹分类。它是根据音乐标签来分类的,例如按照歌手、专辑等依据分类歌曲。
\n因此,若想使用 Navidrome,需要对音乐进行标签管理。
\n大约两年前,我写了 一篇文章 介绍使用 Music Tag 和 Foobar2000 两款软件来管理音乐。
\nMusic Tag 的标签源是网易云音乐、豆瓣音乐、QQ 音乐等国内音乐平台,说实话,这些平台的音乐标签质量都不是很好,所以我一直在寻找更好的音乐标签源。
\n直到我发现了 MusicBrainz ,这是一个开源的音乐标签数据库,任何人都可以为它贡献标签。在体验之后,我发现 MusicBrainz 的音乐标签质量要比国内音乐平台的标签质量好很多,所以我决定将 MusicBrainz 作为我的音乐标签源。
\n我使用 Picard 这款软件来从 MusicBrainz 获取音乐标签。
\n将音乐导入 Picard 后,它会自动从 MusicBrainz 获取音乐标签,然后将标签写入音乐文件,十分方便。
\n开始使用 不论是使用 Navidrome 自带的 Web 界面,还是使用兼容 Subsonic API 的客户端,只要连接到 Navidrome,便可开始享受你的私人音乐库。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/f9bfe16a.html",
+ "url": "https://blog.udon.eu.org/archives/f9bfe16a.html",
+ "title": "使用 Docker Compose 部署 Keycloak 20",
+ "date_published": "2023-01-22T12:00:00.000Z",
+ "content_html": "部署方式 采用 Docker Compose 部署,使用 Postgres 作为数据库,使用 Nginx 作为反向代理。
\nDocker Compose 配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 version: '3' services: keycloak: image: quay.io/keycloak/keycloak:latest environment: KC_DB: postgres KC_DB_URL: jdbc:postgresql://db:5432/keycloak KC_DB_USERNAME: keycloak KC_DB_PASSWORD: keycloak KC_HTTP_ENABLED: true KC_HOSTNAME_STRICT: false KC_HOSTNAME_STRICT_HTTPS: false KC_HTTP_RELATIVE_PATH: '/' KC_HTTP_PORT: 8080 KEYCLOAK_ADMIN: MY_USERNAME KEYCLOAK_ADMIN_PASSWORD: MY_PASSWORD PROXY_ADDRESS_FORWARDING: true KC_PROXY: edge entrypoint: /opt/keycloak/bin/kc.sh start ports: - 127.0 .0 .1 :18080:8080 restart: unless-stopped db: image: postgres:14 restart: unless-stopped environment: - POSTGRES_USER=keycloak - POSTGRES_PASSWORD=keycloak - POSTGRES_DB=keycloak volumes: - ./postgres-data:/var/lib/postgresql/data
\n\n使用命令 docker compose up -d
启动服务。
\nNginx 配置 我建议使用 DigitalOcean 的 Nginx 配置生产工具,示例配置如下:
\n示例配置
\n也可参考下述配置,此为 DigitalOcean 生成配置的简化版:
\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name auth.example.com; ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/auth.example.com/chain.pem; access_log /var/log/nginx/access.log combined buffer=512k flush=1m ; error_log /var/log/nginx/error .log warn ; location / { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_buffer_size 128k ; proxy_buffers 4 256k ; proxy_busy_buffers_size 256k ; proxy_pass http://127.0.0.1:18080; } location /auth/realms { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_pass http://127.0.0.1:18080; } location /auth/resources { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_pass http://127.0.0.1:18080; } location /auth/js { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_pass http://127.0.0.1:18080; } }server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name *.auth.example.com ; ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/auth.example.com/chain.pem; return 301 https://auth.example.com$request_uri ; }
\n\n配置 Keycloak 创建 Realm 打开 Keycloak 地址 ,界面如下。
\n
\n选择 Administration Console
,进入管理界面。
\n
\n选择箭头指向的下拉菜单,选择 Add realm
,创建一个新的 Realm。
\n
\n填写 Realm 名称,点击 Create
。
\n创建 Client
\n选择 Clients
。
\n
\n点击 Create client
。
\n
\n填写 Client 相关信息,点击 Next
。
\n
\n按需求选择 Client 的配置,点击 Save
。
\n
\n至此,Keycloak 配置完成,且创建了第一个测试用 Client。
\n测试 Client 可根据 官方教程 测试该 Client。
\n尾声 上述便是使用 Docker Compose 部署 Keycloak 20 的方法,我们顺利创建了第一个测试用 Client,接下来可以根据自己的需求进行配置。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/e74a90f2.html",
+ "url": "https://blog.udon.eu.org/archives/e74a90f2.html",
+ "title": "使用再生龙 Clonezilla 备份操作系统",
+ "date_published": "2022-08-12T04:30:00.000Z",
+ "content_html": "近日购入了一只闪迪的 CZ880 Extreme PRO 固态U盘来装 Manjaro。
\n虽然U盘本体是终身质保的,但数据无价,配置一遍系统就要花大把的时间。遂有了备份U盘内整个系统的念头。
\n下面跟着我使用再生龙 Clonezilla 把U盘里的系统备份一下吧!
\n事先准备 再生龙是运行在独立操作系统 (Debian/Ubuntu) 上的一套软件,因此需要制作启动盘,或使用 Ventoy 等多系统启动方案。(实测 YUMI 无法启动再生龙,故建议使用 Ventoy)
\n制作启动盘 前往 Rufus 官网 下载 Rufus 启动盘制作工具。
\n前往 Clonezilla 官网 下载再生龙映像。
\n使用 Rufus 将再生龙映像烧写进另一个U盘即可。
\nU盘内数据将丢失,请做好备份!
\n使用多系统启动 前往 Ventoy 官网 下载安装包(兼容 Windows 与 Linux),将你的另一个U盘制作为 Ventoy 多启动盘。
\n前往 Clonezilla 官网 下载再生龙映像。
\n将再生龙映像拷贝至 Ventoy 多启动盘中。
\n选择你想存放备份数据的目的地,创建一个存放备份映像的文件夹(注意目录名称中不能带有空格)。
\n剧透一下,40G 的系统盘备份之后大约占了 16-17G. 请留出足够的空间(建议和待备份的数据等大小)。
\n开始备份 瞎眼警告:由于没有合适的截屏方式,我很不愿意地采取了 拍 屏 的方式,敬请谅解。
\n启动再生龙系统 确保你的电脑关闭了安全启动,若还打开着,需要在 BIOS 中将其关闭。
\n插入刚刚制作好的启动盘/Ventoy 多启动盘,在电脑启动时猛敲键盘的…某个键,这因电脑型号而异,打开启动菜单。
\n选择插入的启动盘/多启动盘。
\n启动盘用户若没有太大的兼容性问题,就能看到再生龙的启动菜单。
\n多启动盘用户还要再多一步,在 Ventoy 菜单内选中再生龙的映像,如下图所示,即可打开再生龙的启动菜单。
\n
\nP.S. 我的笔记本兴许和 Ventoy 的 UEFI 模式相性不大好,在 BIOS 中开启了 Lagacy 兼容模式后,使用 Legacy 模式才能开启 Ventoy。
\n选择再生龙启动方式
\n经典的 GRUB 启动菜单,一般来说选择默认的第一项启动方式即可。
\nVGA 启动花屏 我的电脑遇到了在 VGA 800x600 模式下花屏的问题。
\n最终进入 Other mods of Clonezilla live
菜单,
\n
\n选择了上图中的 KVM & To RAM 模式,可以正常启动了。
\nUSB 口不够用的用户 我这台笔记本只有两个 USB 口,其中一个要给备份源头 CZ880,另一个则要给移动硬盘,故选择了 To RAM
模式,将再生龙载入内存,就可以拔掉多启动U盘,空出 USB 口给移动硬盘了。
\n语言配置
\n选择自己想用的语言即可。
\n
\n保持默认配置即可。
\n备份配置
\n我们选 Start Clonezilla 使用再生龙
。
\n命令行可以在熟悉了配置之后使用。
\n
\n此处我们选择第一项 device-image 硬盘/分区[存到/来自]镜像文件
。
\n若想进行两盘对拷,可以选择第二项。我还没有尝试过。
\n挂载存储目录
\n这次我打算使用移动硬盘备份系统,故选择第一项 local dev 使用本机的分区
。
\n
\n随后,再生龙会提示插入想要挂载的 USB 设备,按照提示做即可。
\n
\n此时画面会动态显示系统识别到的存储设备。看到期望的目标设备时,按下 Ctrl-C
停止搜索。
\n
\n在扫描完电脑当前安装的所有硬盘的分区后,你需要选择备份镜像文件存放的那个分区。
\n如图,我希望备份到大小为 1.8T 的移动硬盘上,故选择最后一项 sdc2
。
\n
\n随后,再生龙询问你是否需要检查并修复挂载的文件系统,我们选第一项否就好了。
\n
\n接着,就是选择备份镜像存放的位置。
\n使用键盘的方向键选择目录,使用 Tab
跳转到下方的选项,选择 Browse
并敲击回车就可以进入到此目录。
\n若希望在选中目录下存放备份镜像文件(是一个文件夹),就可以选择 Done
选项,回车确认。
\n
\n系统询问是否检查生成的备份镜像的可还原性,这里我们选是,多花一点时间能确保备份的完整性。
\n
\n镜像加密,依个人喜好选择。
\n
\n待上述配置完成后,系统会向你再次确认备份的内容与目的地。
\n确认无误后输入 y
并敲击回车继续。
\n简单模式/高级模式 此时应该有一个模式选择,问你想要使用简单模式还是专家模式。
\n我建议选择 专家模式,简单模式能选择的参数较少。
\n
\n接下来的三个选项,全部保持默认配置即可。
\n
\n
\n
\n压缩方式选择
\n此处选择第三项 -z2p 使用并行 bzip2 压缩
。
\n实测 bzip2 压缩速度比较快,产生的备份镜像的体积也不算大。
\n下图为选择了第一项 -z1p 使用并行的 gzip 压缩
的速度:
\n
\n下图为选择了第三项 -z2p 使用并行 bzip2 压缩
的速度:
\n
\n可以看出 bzip2 压缩速度比 gzip 快了8倍。
\n其他压缩方式的速度,待我测试之后更新文章。
\n
\n分卷大小配置保持默认即可。
\n备份镜像检查 待备份完成后,再生龙还会进行一次备份镜像的可还原性检查,如下图:
\n
\n若得到下图的提示,则备份镜像生成成功了。
\n
\n随后,选择按照意愿选择备份结束后的操作即可。
\n
\n至此,再生龙 CLonezilla 的基础教学就结束了,你已经学会了如何使用再生龙的图形界面进行备份。
\n下面是一些再生龙的高阶(大概很高级)使用方法。
\n高级操作 使用无线网络备份 上文中,我的电脑仅有两个 USB 口,为备份的流程增添了不必要的麻烦。
\n能否使用 Wi-Fi 将备份镜像推送至家中的 NAS 呢?
\n再生龙内置了许多通过无线/有线网络备份的方法,如下图:
\n
\n我们尝试使用 Webdav 来远程备份吧!
\n利与弊 使用 Wi-Fi 备份可以摆脱线缆,更加轻松而优雅地进行备份。
\n然而,由于通过 Wi-Fi 或者一切网络传输数据的速度仍然无法比肩有线传输,备份所消耗的时间将是备份至本地磁盘的 3-4 倍。
\n备份我U盘中的 40G 的 Manjaro 系统用时 30min 左右。
\n倘若你有大把的时间,或家中的内网速度足够快,大可使用无线备份。品着咖啡,看着数据上云(笑)。
\n预先准备 上文中我们选择了基于 Debian 的 Clonezilla Stable 版本,遗憾的是 Debian 系统中并未携带太多驱动程序,因此识别不到我的 Intel AX200 无线网卡。
\n经过测试,基于 Ubuntu 的 Clonezilla Alternative Stable 版本可以识别到 AX200 网卡。
\n点击上方链接即可下载 Clonezilla Alternative Stable 版本的映像。
\n重新烧写启动U盘/拷贝映像至多启动U盘即可。
\n又遇到了启动问题 使用基于 Ubuntu 的再生龙,上文中使用的 KVM
模式变得无法打开了,且 VGA 800x600
模式是一样的花屏。
\n在一番尝试之后,我发现藏在更多启动选项菜单里的 VGA 1024x768
模式可以正常显示。看来基于 Debian 的再生龙也可以使用这个模式。
\n开始备份
\n选择了非本地的镜像存储位置后,系统将开启上图的网络管理菜单。
\n选择第一项 Edit a connection
。
\n
\n选择 Add
选项,在弹出菜单中 Wi-Fi
。
\n
\nProfile name
随意填写;
\nDevice
一般填写 wlan0
,系统的第一块无线网卡;
\n接着,按照自己的情况填写图中划线的三个配置即可。
\n
\n保存 Wi-Fi 配置后,就能看到当前配置的连接状态。
\n若当前配置名前带 *
,且右侧选项为 Deactivate
,则 Wi-Fi 已连接成功。
\n
\n接着,系统要求填写 Webdav 地址。
\n
\n最后,系统会向你确认 Webdav 是否正确。
\n若确认无误,即可敲击回车继续。
\n接下来的步骤和上述初级教程硬盘挂载之后的流程是完全一样的,请参考上文继续配置。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/f82a3103.html",
+ "url": "https://blog.udon.eu.org/archives/f82a3103.html",
+ "title": "BETAFPV 高频头固件编译 AttributeError",
+ "date_published": "2022-08-06T04:30:00.000Z",
+ "content_html": "错误原因 Python 模块 pypandoc
版本过新,1.8.0 及更高版本已移除了 BETAFPV 高频头固件中仍在使用的 convert
函数。
\n解决方法 安装旧版的 pypandoc
模块。
\npip install pypandoc==1.7.0
\n",
+ "tags": [
+ "教程",
+ "DIY"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/38942a16.html",
+ "url": "https://blog.udon.eu.org/archives/38942a16.html",
+ "title": "DIY 显示器音箱",
+ "date_published": "2022-06-03T07:15:00.000Z",
+ "content_html": "新买的显示器(LG 27UL500,虽然因为屏幕问题已经退货了)没有内置音箱,虽然大部分时间都在用耳机,但别人有的东西我不能没有嘛,就买了些材料,DIY 一个外接音箱。
\n写作此文章分享一下制作的过程~
\n物料清单
\n\n接受 5V 电压,可以驱动两个 3W 的喇叭。商品详情页面吹的很厉害,确实底噪很小,而且输出的音量非常高。相当 OK 的功放板。
\n\n8Ω 3W 喇叭两只(带音腔) 2*4RMB + 运费 \n \n音质很不错,声音很大也不会破音,因为是广告机用的喇叭么?
\n\n我选的型号是 PJ392,只要是 3.5MM 双声道的公头就行了。
\n\n这个随意选。
\n\n我买的是4芯的屏蔽线,可供 Type C 头使用(2 power 2 data),音频线只需要三芯(2 data 1 GND),屏蔽线是为了更小的干扰、更好的音质。
\n开始组装 3.5MM 线缆 剥除一段屏蔽线的外皮,做工还是很不错的,有金属丝和铝箔的屏蔽,塑料膜防水,还有一根抗拉扯的填充芯。
\n我选择使用红绿蓝三根线,黄线悬空。线色对应如下:
\n红 - 左声道;绿 - 右声道;蓝 - 接地。
\n
\n可以预先套上一段热缩管。
\n
\n取一枚 3.5mm 公头,旋下插头。
\n最长的一段一般是接地。若将接地朝下,我这款公头左侧为左声道,右侧为右声道。具体的接线方式可以用万用表测量接头和接口得知。
\n将线穿入孔中,上一坨焊锡即可。
\n
\n再使用万用表测量接头与线末端的连通性,注意不能与其他线短路了。
\n确认无误后可以打上热熔胶固定。
\n
\n再打点热熔胶,旋回外壳,将热缩管套好,加热热缩管使其收缩。
\n3.5mm 线缆就制作完成了。
\n驱动板焊接 驱动板上有三组线需要焊接:
\n\n音频输入线(3.5mm 线缆) \n电源输入线(Type C 线) \n音频输出线(喇叭线) \n \nType C 线我没有再用屏蔽线,用两根导线连接 Type C 母头和驱动板即可。
\n焊接方法就不多说了,线穿过孔,上锡即可。
\n
\n全部线缆焊接完成如下:
\n
\n热熔胶填充 完成接线后,确认无短路,即可连接电脑测试音箱。
\n若没有问题,考虑到需要长期使用,就可以用热熔胶覆盖焊接处,防止焊点脱落。
\n用热熔胶覆盖之后的驱动板:
\n
\n嘛…手艺不是很行。
\n \n就此,外接音箱组装完成啦!
\n",
+ "tags": [
+ "教程",
+ "DIY"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/2e528779.html",
+ "url": "https://blog.udon.eu.org/archives/2e528779.html",
+ "title": "迁移 Hexo 渲染环境至 GitHub Actions",
+ "date_published": "2022-05-23T11:30:00.000Z",
+ "content_html": "本博客使用的是 Hexo 静态博客框架,我将渲染环境搭建在家中 NAS 之上,部署了一个 Ubuntu Docker 并安装好了 Node.js 环境,正常使用了一两年。
\n上周末,写完新文章的我心血来潮,打算在更新博客的时候顺便更新一下老旧的 Node 和 Hexo(Node 12, Hexo 5.2)。
\n一番折腾之后,以 npm 和 hexo 环境全部被破坏(事后想想也许只是环境变量掉了)的下场结束。
\n鉴于 NAS 性能低下,更新一次 node module 都需要 30 分钟,我放弃了在 NAS 上重新部署渲染环境的念头,转而使用 GitHub Actions 渲染,并同时部署于 GitHub Pages 与 CloudFlare Pages。
\n将渲染环境迁至 GitHub Actions 不久之前,CloudFlare Pages 悄悄下架了 Hexo 框架的部署功能,只能用 GitHub Actions 渲染,然后再部署至 CloudFlare Pages 了。
\n项目结构的修改 若想使用 GitHub Actions,需要将博客的源码上传至 GitHub。考虑到隐私和安全的问题,建议创建一个私有仓库管理源码。
\n对于项目没有什么需要修改的,因为 Actions 渲染的流程和本地渲染的流程没有区别。
\n唯一需要改动的,是引入的主题。由于两个 Git 仓库不能嵌套,我们需要以 Git submodule 的形式引入主题仓库。
\n我使用的是 Fluid 主题。采用 覆盖配置 的方法,即在根目录之下有一份配置会覆盖主题内的配置文件,便于在 Actions 中渲染。
\n以下也将以 Fluid 为例,请根据你使用的主题修改命令或代码。
\n首先,删除原来的主题(若使用的是主题内的配置,注意备份配置文件!)
\n返回博客源码的根目录,执行:
\n1 git submodule add https://github.com/fluid-dev/hexo-theme-fluid.git themes/fluid
\n\n末尾的 themes/fluid
为此 submodule 在项目中的位置与名字,与先前本地渲染时的配置相同即可。
\n删除子模块的过程较为繁琐,请参考网上的文章进行操作。
\n在 Clone 此项目时,submodule 默认不会被下载,需要使用指令:
\n1 git submodule update --init --recursive
\n\n下载 submodule。在下面会提到的 Actions 配置文件中会出现这条指令。
\n接着,就可以将博客源码上传至 GitHub。
\nGitHub Actions 相关文件 在博客源码根目录创建 .github/workflows/submit.yml
和 .github/script/blog-update.sh
两个文件,填入下列代码。
\n以下代码参考文章 GitHub Actions 自动部署 Hexo 博客到 cPanel 虚拟主机 - 谷中望月 ,有所修改。
\nsubmit.yml
:
\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 name: CI on: push: branches: - main release: types: [published ]env: GIT_USER: Lao-Liu233 GIT_EMAIL: blog@udon.eu.org jobs: build: name: Build on node ${{ matrix.node_version }} and ${{ matrix.os }} runs-on: ubuntu-latest strategy: matrix: os: [ubuntu-latest ] node_version: [16.15 ] steps: - name: Checkout uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node_version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node_version }} - name: Install hexo run: | npm install -g hexo-cli - name: Install dependencies run: | npm install - name: Clone submodule run: | git submodule update --init --recursive - name: Configuration environment run: | sudo timedatectl set-timezone "Asia/Shanghai" mkdir -p ~/.ssh/ echo "$HEXO_DEPLOY_PRI" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan github.com >> ~/.ssh/known_hosts git config --global user.name $GIT_USER git config --global user.email $GIT_EMAIL - name: Deploy hexo run: | hexo clean hexo g -d - name: Update Blog run: | sh "${GITHUB_WORKSPACE}/.github/script/blog-update.sh"
\n\n.github/script/blog-update.sh
:
\n1 2 3 4 5 6 7 8 9 #!/bin/sh if [ -z "$(git status --porcelain) " ]; then echo "nothing to update." else git add source /_posts/\t git commit -m "triggle by commit ${GITHUB_SHA} " -a git push origin mainfi
\n\nCommit + Push,打开 Actions 界面,就能看到正在运行的 Action 啦。
\n不出意外,Action 成功执行,1分钟内博客就能渲染成功、部署至 GitHub Pages。
\n同时部署至 CloudFlare Pages 步骤较为简单,我简述一下。
\n打开 CloudFlare Pages, 连接至存放 渲染后 的静态文件的仓库,渲染的框架选择 None ,执行的指令填写 exit 0;
就可以了。
\n执行部署后,渲染后的静态文件就被部署至 CloudFlare Pages 啦。
\n",
+ "tags": [
+ "教程",
+ "GitHub Actions",
+ "Hexo"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/dbf21067.html",
+ "url": "https://blog.udon.eu.org/archives/dbf21067.html",
+ "title": "玩一玩 DN42",
+ "date_published": "2022-04-01T04:30:00.000Z",
+ "content_html": "两个月前,我向 DN42 提交了申请,并于4个小时后通过了审核,获得了自己的 AS 和 IP。
\n作此文分享一下把玩 DN42 的心得,也作为我的备忘录。
\n我的信息 1 2 3 AS4242423490 IPv4: 172.23 .13 .64 /28 IPv6: fd44:6b93 :4eaa::/48
\n\n目前仅一个洛杉矶节点开放 Peer,后期还将添加韩国和日本的节点。
\n如何把玩 注册 有关注册的文章很多,推荐这两篇:
\nDN42 实验网络介绍及注册教程(2022-02 更新) | Lan Tian @ Blog
\n初探 DN42 网络 - 宝硕博客 (baoshuo.ren)
\n需要使用 Git 和 PGP,在 DN42 的 Git 仓库提交你的个人信息即可完成注册。
\n搭建内网 在和其他 AS 建立对等连接之前,我们先要把内网整理好:
\n各台服务器的地理位置和网络位置各不相同,需要使用 VPN 创建虚拟局域网。
\n课堂上讲了两种内网路由协议:
\n\n有一位老朋友可以轻松解决以上两个问题:Zerotier 。
\nZerotier 的虚拟网络可以使用自己的 IP,只需在 Managed Routes 中设置你在 DN42 申请的 IPv4 和 IPv6,即可为每台加入虚拟网络的主机自动或手动配置 DN42 IP。
\n在机器之间使用 DN42 IP 互 ping 测试连通性。
\n准备 BGP 相关软件 搭建好内网之后,就可以开始配置 BGP 发言人啦。
\n选择一台或多台服务器,作为自治域向外宣告路由的发言人。
\n在每台服务器上都需要配置 BGP 相关的软件,以及和其他 BGP 发言人建立连接(一般是 VPN 连接)的软件。
\n目前在 DN42 网络用的比较多的 VPN 软件是 Wireguard,BGP 软件则可以从 bird 2、bird 1、quagga 等软件中选择。
\n我使用的是 bird 2。
\n安装与配置 BIRD 2 安装命令:
\n1 2 apt update apt install bird2 -y
\n\nbird 2 的配置文件位于 /etc/bird
,名为 bird.conf
。
\n配置文件可以参考(照抄)DN42 官方给出的配置:howto/Bird2 (dn42.dev)
\n喂到嘴边的配置方法:
\n\n将官方配置填入 /etc/bird/bird.conf
\n在 /etc/bird
目录下新建名为 peers
的文件夹 \n下载 ROA 配置(命令来自宝硕的博客 ) \n \n1 2 wget -4 -O /tmp/dn42_roa.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf && mv -f /tmp/dn42_roa.conf /etc/bird/dn42_roa.conf wget -4 -O /tmp/dn42_roa_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf && mv -f /tmp/dn42_roa_v6.conf /etc/bird/dn42_roa_v6.conf
\n\n\t并配置 crontab,每小时自动下载并重载新配置:
\n1 2 3 0 */1 * * * wget -4 -O /tmp/dn42_roa.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf && mv -f /tmp/dn42_roa.conf /etc/bird/dn42_roa.conf 0 */1 * * * wget -4 -O /tmp/dn42_roa_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf && mv -f /tmp/dn42_roa_v6.conf /etc/bird/dn42_roa_v6.conf 0 */1 * * * birdc configure
\n\n安装并配置 Wireguard 安装命令:
\n1 2 apt update apt install wireguard -y
\n\n这样就安装了 Wireguard
和名为 wg-quick
的管理工具。
\n使用命令:
\n1 wg genkey | tee privatekey | wg pubkey > publickey
\n\n在当前目录下创建 Wireguard 建立连接所用的密钥对(公钥和私钥)。
\n就此 Wireguard 安装完成。
\n配置系统内核 打开内核的数据包转发功能:
\n1 2 3 4 echo "net.ipv4.ip_forward=1" >> /etc/sysctl.confecho "net.ipv6.conf.default.forwarding=1" >> /etc/sysctl.confecho "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf sysctl -p
\n\n关闭内核 rp_filter
的严格模式:
\n1 2 3 echo "net.ipv4.conf.default.rp_filter=0" >> /etc/sysctl.confecho "net.ipv4.conf.all.rp_filter=0" >> /etc/sysctl.conf sysctl -p
\n\n如果有 ufw 等防火墙自动配置工具,务必关闭。
\np.s. 我拿到任何机器后会立刻执行的指令是:
\n \n\n创建 Dummy 网卡 dummy 网卡具体的作用我不是很清楚…
\n只知道如果要用链路本地地址进行通讯,要把 DN42 的 IP 地址绑定到 dummy 网卡上。
\ndummy 网卡配置指令如下:([ ] 中为需要你填写的内容)
\n1 2 3 4 5 ip link del dummy ip link add dummy type dummy ip addr add [你的 DN42 IPv4 地址]/32 dev dummy ip addr add [你的 DN42 IPv6 地址]/128 dev dummy ip link set dummy up
\n\n和小伙伴建立对等连接(peer) 需要和对方分享的 \n你的 DN42 信息,包括 AS 号和发言人的 DN42 IPv4(IPv6)地址; \n若使用链路本地地址,还需提供这东西,一般为一个本地 IPv6 地址,常取 fe80::[你的 AS 号后4位]
; \n发言人的外网 IPv4 地址(或域名)和 Wireguard 隧道的通讯端口; \nWireguard 公钥。 \n \n有一些信息会在以下的配置中获得。
\nWireguard 相关的 在 /etc/wireguard
目录下创建 Wireguard 配置文件,每一个配置文件对应着一个 Wireguard 隧道。
\n例如你要和 AS114514 臭 建立对等连接,可以在 peers
文件夹下新建一个名为 wg_114514.conf
(文件名即为 wireguard 隧道名)的配置文件。
\n配置的模板如下:([ ] 中为需要你填写的内容)
\n1 2 3 4 5 6 7 8 9 10 11 [Interface] Table = off ListenPort = [我们的监听端口,可以用对方 AS 号的后五位]PrivateKey = [刚刚生成的 Wireguard 私钥]PostUp = ip addr add [本机的 DN42 IPv4 地址]/32 peer [对方机器的 DN42 IPv4 地址]/32 dev %iPostUp = ip addr add [本机的链路本地地址(见 BIRD 相关配置)]/64 dev %i[Peer] PublicKey = [对方的 Wireguard 公钥]AllowedIPs = 10.0 .0.0 /8 , 172.20 .0.0 /14 , 172.31 .0.0 /16 , fd00::/8 , fe80::/64 Endpoint = [对方机器的公网 IP 地址或域名 : 端口号]
\n\n然后使用 wg-quick up [wireguard 隧道名(刚刚的配置文件名)]
启动 Wireguard 隧道。
\n可以 ping 一下对方的 DN42 IP 看看 Wireguard 隧道是否连接成功。
\n使用 wg
命令查看各隧道的连接情况。若有显示 last handshake
,一般情况下隧道就已成功建立。
\nBIRD 相关的 在先前导入的 bird 2 配置中定义了一个 peers
文件夹,就是用来存放 peer 相关的配置。
\n例如你要和 AS114514 又臭 建立对等连接,可以在 peers
文件夹下新建一个名为 114514.conf
(文件名可自定义)的配置文件。
\n我采用的是链路本地地址(Link-Local) 的配置方法。配置的模板如下:([ ] 中为需要你填写的内容)
\n1 2 3 protocol bgp from dnpeers { neighbor % '' as ; }
\n\n添加完配置之后别忘了用 birdc configure
重载 bird 2 配置。
\n使用命令 birdc s p
可以查看 BIRD 2 软件下所有协议的通信情况。
\n若显示为:
\n1 dn42_xxxx BGP --- up 20 :36 :30 .984 Established
\n\n则表示 BGP 连接已建立。
\n尾声 我还在写 DN42 相关的站点,在上面分享节点信息,方便大家 peer。
\n但目前进度缓慢(悲)。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/9b58c98e.html",
+ "url": "https://blog.udon.eu.org/archives/9b58c98e.html",
+ "title": "合宙 EPS32-C3 把玩记录(二):WiFi 与一个 Web 程序",
+ "date_published": "2022-03-26T16:15:00.000Z",
+ "content_html": "了解一下相关的库 \n这个库是自带的,不需要引入。
\n据我的理解,单片机的串口就是控制台程序的控制台,可以返回一些信息给上位机。
\n会用到的几个指令:
\n1 2 3 4 5 6 7 Serial.begin(Baudrate);\t Serial.end(); Serial.read();\t\t\t Serial.peek();\t\t\t Serial.flush();\t\t\t Serial.print/println();\t Serial.write();\t\t\t
\n\n\n#include <WiFi.h>
\nAP(接入点) Mode 创建一个接入点。
\n1 2 3 4 5 WiFi.mode(WiFi_AP);\t\t\t WiFi.softAPConfig(local_IP, gateway, subnet); WiFi.softAP(SSID,PASSWD);\t
\n\n更多函数见 WiFi.h AP 常用方法说明
\nSTA(站点) Mode 接入一个 AP。
\n1 2 3 WiFi.mode(WIFI_STA); \t\t WiFi.start(SSID,PASSWD)\t\t Serial.println(WiFi.localIP());\t\t
\n\n更多函数见 WiFi.h STA 常用方法说明
\n\n#include <WebServer.h>
\n创建一个简单的网站服务器。真的很简单。
\n一个个函数讲有点难理解,我放在这节的例程里面说明。
\n写一个测试程序吧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <WiFi.h> #include <WebServer.h> const char *SSID = "YOUR_SSID" ;const char *PASSWORD = "YOUR_PASSWORD" ; WebServer server (80 ) ;\t\tvoid handleIndex () \t\t\t { server.send(200 , "text/plain" , "Hello from ESP32!" ); }void setup () { Serial.begin(9600 );\t\t Serial.println(); WiFi.mode(WIFI_STA);\t WiFi.begin(SSID, PASSWORD);\t\t\t\t while (WiFi.status() != WL_CONNECTED) \t { delay(500 ); Serial.print("." );\t } Serial.println("WiFi connected!" ); Serial.println("IP address: " ); Serial.println(WiFi.localIP()); \t\t server.on("/" , handleIndex);\t\t\t server.begin();\t\t\t Serial.println("WebServer begin!" ); }void loop () { server.handleClient();\t }
\n\n访问串口返回的 IP,即可看到 Hello from ESP32!
这句话啦。
\n还有个 Web Server 叫 ESPAsyncWebServer 自带的 WebServer 是同步的,只支持处理一个连接。对于这种体量的机器其实足够了。
\n顺便学习一下一个第三方库吧。
\n添加库 对于这款 ESP32,需要下载并导入两个库(源码 ZIP 即可):
\nme-no-dev/AsyncTCP: Async TCP Library for ESP32
\nme-no-dev/ESPAsyncWebServer: Async Web Server for ESP8266 and ESP32
\n在 Arduino 的项目 > 加载库 > 添加 .ZIP 库
中导入这两个库。
\n用 ESPAsyncWebServer 重写刚刚的例程吧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <WiFi.h> #include <ESPAsyncWebServer.h> const char *SSID = "YOUR_SSID" ;const char *PASSWORD = "YOUR_PASSWORD" ; ESPAsyncWebServer server (80 ) ;\t\t void handleIndex (AsyncWebServerRequest *request) { request->send(200 , "text/plain" , "Hello, world!" ); }void setup () { Serial.begin(9600 );\t\t Serial.println(); WiFi.mode(WIFI_STA);\t WiFi.begin(SSID, PASSWORD);\t\t\t\t while (WiFi.status() != WL_CONNECTED) \t { delay(500 ); Serial.print("." );\t } Serial.println("WiFi connected!" ); Serial.println("IP address: " ); Serial.println(WiFi.localIP()); \t\t server.on("/" , handleIndex);\t\t\t server.begin();\t\t\t Serial.println("WebServer begin!" ); }void loop () { }
\n\n理论上来讲,上面的代码应该是正确的……
\n但 Arduino 在编译的时候报错,内容是 ESPAsyncWebServer 库中的某些代码。
\n有待我弄清楚出错的原因。
\n",
+ "tags": [
+ "教程",
+ "嵌入式开发"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/7f7bd4a5.html",
+ "url": "https://blog.udon.eu.org/archives/7f7bd4a5.html",
+ "title": "合宙 EPS32-C3 把玩记录(一):环境搭建与第一个程序",
+ "date_published": "2022-03-26T09:30:00.000Z",
+ "content_html": "为了贯彻本博客最重要的关键词:性价比 ,我看到性价如此高的开发板,想都没想就剁手了。
\n嘛其实也没有这么冲动,我在购买 3D 打印机之后就一直在计划着做一些网上现成的电子项目,但碍于这段时间 MCU 和大尺寸屏幕价格的飙升,一直没能开始动手。
\n正好最近我学习了 iPad 上的 3D 建模软件 Sharp3D,项目的外壳建模变得有可能;又遇到了这块便宜的板子,立即开工!
\n因为1.8寸的 TFT 显示屏还没到货,3D 建模就先放一边,先来研究一下这块开发板。
\n事先声明 本教程是我一边从零开始学习嵌入式开发一边作成的,有逻辑混乱、内容浅显和成吨的错误,还请已经熟悉嵌入式开发的大佬多多包涵与斧正)
\n问题:什么?开发环境不是按语言分的嘛? 在开始学习嵌入式开发之前,我简单地认为嵌入式开发因语言而已,分为用 C/Cpp 开发(Arduino)和用 Python 开发(MicroPython)。
\n直到我遇见了 ESP-IDF 这个东西。
\n啥啊,为啥这家伙用的也是 C,代码我还一点都看不懂。
\n解答 嵌入式开发选用的语言和语法因选择的框架而异。
\nESP-EDF 更靠近底层,因而编写更复杂;Arduino 对底层进行封装,更靠上层且对用户更友好;MicroPython 则是在开发板上还原了一个 Python 的开发环境,继承了 Python 的诸多优点(简单的语法、无需编译就能执行新代码等)。
\n此外,还能用 JS、Java、Lua 等等语言进行开发。
\n我的选择 我手上有两块板子,一块被我刷成了 MicroPython,但目前不打算去用它。
\n我打算用 Arduino + C 进行开发。
\n配置 VSCode + Arduino 开发环境 Arduino 没有代码补全,太难用。简述一下如何使用 VSCode 进行开发:
\n\nVSC 安装 Arduino 插件; \n在 首选项-设置 中配置 Arduino 的路径 Arduino.path
\n打开项目后选择 MCU 类型和串口 \n \n就能用啦。
\n第一个项目 第一个项目就不选输出 Hello World 了,一点硬件的感觉都没有。
\n据 官方文档 ,主板板载的两个 LED 灯对应的 GPIO 为 IO12 IO13
,高电平有效。
\n就此编写一个无稳态多协振荡电路让 LED 灯交替闪烁的程序:
\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void setup () { pinMode(12 , OUTPUT); pinMode(13 , OUTPUT); digitalWrite(12 , LOW); digitalWrite(13 , LOW); }void loop () { digitalWrite(12 , HIGH); digitalWrite(13 , LOW); delay(1000 ); digitalWrite(12 , LOW); digitalWrite(13 , HIGH); delay(1000 ); }
\n\n编译+上传即可。
\n结果就不展示了,两个灯在交替闪烁。
\n",
+ "tags": [
+ "教程",
+ "嵌入式开发"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/d01399e6.html",
+ "url": "https://blog.udon.eu.org/archives/d01399e6.html",
+ "title": "Code-Server 的代理配置",
+ "date_published": "2022-03-19T14:30:00.000Z",
+ "content_html": "一年前,我介绍了如何在群晖上使用 Docker 部署 Code-Server,在外也能轻松使用已经配置好的开发环境。群晖搭建 VSCode 服务器与 Syncthing 服务
\n最近我换了 iPad,琢磨如何发挥她的生产力。除了使用网页版的 IDE(Codepen、Gitpod等),就是自建网页版的 VSCode 了。下面简要介绍一下我是如何给 Code-Server Docker 配置代理,使其成为一个完备的开发平台。
\n \n\n将 Code-Server 部署在国内服务器(例如我家里的 NAS),可以获得稳定的连接,这对于开发平台是尤其重要的,VSCode 遇到连接不顺畅就会要求你刷新界面,很可能会丢失数据。
\n但由于众所周知的原因,在国内的网络环境做开发可以说是寸步难行,我便采用 Clash Docker 来给 Code-Server 加上代理。
\nClash Docker 安装 Clash Core 普通版 Image:dreamacro/clash - Docker Image | Docker Hub
\nClash Core Premium Image:dreamacro/clash-premium - Docker Image | Docker Hub
\nClash Core Premium 二进制文件: Premium release (github.com)
\nClash Core 有普通版和 Premium 版之分,目前我能体验到的二者的区别是普通版的 Clash Core 不支持 RULE-SET 功能。
\n我常用的配置文件大量使用了 RULE-SET,所以我必须得用 Clash Core Premium。
\n但 Pre Build 的 Image 似乎不支持 X86-64 v3 之下的 CPU(例如我的 J1900),所以我采取了部署普通版 Image,然后 attach 进 Docker 手动更换 Premium 内核的曲线救国方法。(更换 /
根目录下名为 Clash
的二进制文件)
\n部署 Docker 时注意一下几点:
\n\n开放 7890(或你定义的代理端口)和 9090(Clash Core 管理面板)端口。 \n将 /root/.config/clash
文件夹挂载到本地,存放 config.yaml
及其他配置文件。 \n \n请勿将 Clash Core 的管理面板暴露到公网。我选择用 Tailscale 建立 VPN 访问家中的服务器进行配置。
\nCode-Server 的配置 需要在 Code-Server Docker 里添加两个环境变量,实现开机自动连接代理:
\n\nhttp_proxy=http://clash_docker_ip:7890
\nhttps_proxy=http://clash_docker_ip:7890
\n \n可以使用同样的方法给其他 Docker 添加代理。
\n \n在一顿折腾之后,Code-Server 终于可以顺畅访问 Github 等网站了。
\n可喜可贺,可喜可贺。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/7263a385.html",
+ "url": "https://blog.udon.eu.org/archives/7263a385.html",
+ "title": "Klipper 的外网访问",
+ "date_published": "2022-02-12T06:50:00.000Z",
+ "content_html": "网络上有关 Klipper 的中文教程少之又少,固作此教程介绍一下如何在外网优雅地访问家中装有 Klipper 的 3D 打印机。
\n方法一:端口转发 此方法仅适用于拥有公网 IP 的用户
\n首先,使用 DDNS 将家庭宽带动态变化的 IP 绑定至域名,教程如下:
\n\n在配置端口映射之前,先介绍一下 Klipper 的网络结构:
\ngraph LR;\n\tA("你的设备") <--80-->\n\tB("前端网页(Fluidd/Mainsail/Octoprint)") <--7125-->\n\tC("API 服务器 Moonraker") \t<-->\n\tD("你的 3D 打印机");\n
\n\n线上的数字便是通讯的端口。
\n由上图,我们不难看出,若需要在外网访问家中的 Klipper,就需要映射 80 和 7125 两个端口。
\n于路由器的 端口转发/端口映射 界面配置 80 和 7125 的转发即可。家庭宽带的公网 IP 不会开放 80 端口,可将外网端口配置为 8080,对应的内网端口为 80 即可。
\n接着,在 Moonraker 配置 moonraker.conf
[authorization]
模块的 cors_domains
模块中添加你的域名,格式为 *://你们域名
\n也可以选择不使用自己搭建的前端网页,而使用 Fluidd 或者 Mainsail 作者搭建的前端网页。在 Moonraker 配置 moonraker.conf
[authorization]
模块的 cors_domains
模块中添加 *://my.mainsail.xyz 与 *://app.fluidd.xyz
\n方法二:内网穿透 本人不推荐使用这个方法,固仅简述一下
\n可以使用花生壳等内网穿透服务,但给的带宽太小,只能使用控制界面,不能使用摄像头。
\n也可以选择自建内网穿透,例如 Frp, Ngrok 等服务。但最近越来越多 Frp 服务器遭到攻击,固不建议自建。
\n方法三:使用 VPN 这是本人推荐的方法。
\n与 Octoprint + Marlin 仅需要映射 80 端口不同,Klipper 还需要映射 Moonraker 的 7125 端口,配置端口转发与实际使用都不如前者来的方便。
\n我个人推荐用诸如 Zerotier, Tailscale 一类的 VPN 软件,搭建自己的小内网,通过内网 IP 直接访问 Klipper,既安全又方便。
\nZerotier 或者 Tailscale 的使用技巧网上一大片,我就不赘述了。
\n",
+ "tags": [
+ "教程",
+ "3D 打印"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/375e7789.html",
+ "url": "https://blog.udon.eu.org/archives/375e7789.html",
+ "title": "群晖搭建 VSCode 服务器与 Syncthing 服务",
+ "date_published": "2021-03-19T16:00:00.000Z",
+ "content_html": "这次我尝试在群晖上搭建 VSCode 服务器与 Syncthing 服务,实现电脑与 NAS 间的代码同步与网页中的 Coding。
\n \n\n \nVSCode 网页版的实现 偶遇服务器软件 刷 RSS 时我看到 V2EX 上一个帖子分享了一个实用工具:github+1s
\n这个项目可以实现在 网页版 VSCode 中打开 GitHhub 上的代码。
\n这个项目使用的 code-server 引起了我的兴趣。
\ncode-server 的部署 群晖自带的 Docker 套件简化了部署的过程。
\n在注册表中搜索 code-server 下载 image;
\n打开 image 进行配置:
\n\n使用高权限执行容器 \n在 高级设置-环境
页面中添加环境变量 PASSWORD
,值设定为你的登陆密码(由于在 Docker 页面中以明文保存,请注意密码安全)。 \n \n启动容器,并使用 Docker 内置的 终端机
打开一个新的 bash
。换源、更新 apt 、安装常用软件我就不再赘述。
\ncode-server 的外网访问 code-server 没有自带 HTTPS 相关的配置,需要使用网站服务器进行反向代理。
\n目前比较流行的有 Caddy 和 NGINX 两款。
\n鉴于 Caddy 配置简单且 HTTPS 优先,我这次使用 Caddy。
\nCaddy 官方安装文档
\n或使用一键安装脚本
\ncurl https://getcaddy.com | bash -s personal
\n如果有开放的 443 端口,则可使用 Caddy 的自动 HTTPS 功能进行快速配置。
\n若像我一样在家中的 NAS 上配置 code-server,则需要自己申请 tls 证书 (如 Let`s Encrypt),并按照 Caddy-tls 配置。
\n反向代理配置可参考 code-server 官方的反代配置教程 。
\n一些疑难杂症 一些插件无法安装 目前 code-server 的 VSCode 版本为 1.51.1, VSCode 官方则为 1.54.3 ,因此某些较新的插件可能无法使用。
\n可以前往 VS插件市场 下载旧版插件并手动安装。
\nDocker 内挂载的目录无写权限 使用 sudo chmod 777 ./
给 coder 用户赋予读写权力。
\nDocker 内 Caddy 无法自启 这个我也还没有解决。暂且手动启动。
\ncode-server 的各种性能问题 等待更多的更新吧,我接下来会尝试在 Docker 里编译原版 VSCode 并开启 Web 模式,对比二者性能。
\nSyncthing 服务搭建 Syncthing 官网 已经给出了十分详尽的安装教程,也有群晖的安装包,我就不再赘述安装过程。
\nSyncthing 的管理页面端口为 8384
,若想在外网访问请使用 HTTPS。可以使用群晖内置的反向代理服务器进行反代。
\n要注意把 22000
端口的 TCP
与 UDP
全部开放,才可在外网顺利与 NAS 同步。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/a455b52c.html",
+ "url": "https://blog.udon.eu.org/archives/a455b52c.html",
+ "title": "逃离国产软件 - 虚拟机计划",
+ "date_published": "2020-08-07T04:30:00.000Z",
+ "content_html": "使用 Hyper-V 运行 Windows LTSC 虚拟机,以隔离流氓的国产软件们。
\n为何大费周章? 我试过网络上的不少方法来隔离鹅厂的软件 —— 刚开源的 Sandboxie 也好,利用 Windows ACL 机制通过低权账户加以限制也好 —— 都因为权限问题失败了。最后决定用虚拟环境直接隔离这些软件。
\n \n\n为什么是 Hyper-V 和 LTSC? 我用过 VMWare,觉得还是 Windows 原生的 Hyper-V 启动最快。还不用钱。
\nWindows LTSC 是企业定制版,官方精简了系统,性能开销更少。
\n事前准备 拥有 16G 以上内存及 NVME 高速硬盘的用户可以放心采用该方案,虚拟机运行时不会影响其他软件的流畅运行。
\n下载 MSDN 上的 Windows LTSC: 侧边栏选择 操作系统 ;选择 Windows 10 Enterprise LTSC 2019 。
\n安装 Hyper-V: 对于 Windows 专业版 用户,在 控制面板 - 程序与功能 - 启动或关闭Windows功能 中找到 Hyper-V 并打开。
\n对于 其他版本 Windows 的用户,则稍微有些麻烦:
\n\n在记事本中输入如下代码 \n \n1 2 3 4 5 6 7 8 9 pushd "%~dp0" dir /b %SystemRoot%\\servicing\\Packages\\*Hyper-V*.mum >hyper-v.txtfor /f %%i in ('findstr /i . hyper-v.txt 2^>nul' ) do dism /online /norestart /add-package:"%SystemRoot%\\servicing\\Packages\\%%i" del hyper-v.txt Dism /online /enable-feature /featurename:Microsoft-Hyper-V-All /LimitAccess /ALL
\n\n\n把文件保存为Hyper-V.cmd \n右键该文件,选择 以管理员身份运行 \n \n根据提示完成安装。
\n\n摘自知乎 没人不认识我 的回答
\n \n安装虚拟机 打开 Hyper-V ,选择 新建 - 虚拟机 ;
\n根据向导提示设置虚拟机,选择 第一代虚拟机 ;
\n内存分配我选了 2G (共16G);CPU 分配我选了 4线程 (共12线程)【需要完成配置后在 设置 中更改】;
\n其余设置默认或自定;
\n安装选项选择 从 CD/DVD-ROM 安装操作系统 ,选择刚刚下载好的 Windows LTSC ISO镜像;
\n完成配置后,启动虚拟机,安装 Windows LTSC 到硬盘。
\n配置环境 装好系统后要干什么不用我说了吧。
\n把垃圾们倒进去就好啦。
\n实测空载消耗 CPU 算力在 0%-3% 浮动;内存占用 2.2G,实际使用 1.2G 。
\n",
+ "tags": [
+ "教程",
+ "软件"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/e3c95af8.html",
+ "url": "https://blog.udon.eu.org/archives/e3c95af8.html",
+ "title": "BGP初体验-Linux,Openwrt与Quagga",
+ "date_published": "2020-02-10T04:30:00.000Z",
+ "content_html": "好久没写博客了!今天抽出点时间分享一下我的 BGP 初体验。
\n这一切的一切都要从一个叫鹤伞 Ria 的 Vtuber 说起……
\n \n\n环境配置 操作环境 Debian 系 Linux(其实是 Kali):
\nsudo apt update & sudo apt install quagga
\nOpenwrt:
\nopkg update & opkg install quagga quagga-zebra quagga-bgpd quagga-vtysh
\n网络配置 采用 Zerotier 建立内网环境模拟真实网络。
\nZerotier 的安装及配置不再赘述,官网有详尽教程。
\n网络环境 本来想做三台设备两个 AS 间的通讯,有一台设备无法安装任何 BGP 软件也就作罢;也没有画拓扑图的必要了(悲)。
\nAS114514 Debian 10.0.1.1,命名为 R1,享有 10.0.1.0/24 网段;
\nAS1919810 Openwrt 10.0.2.1,命名为 R2,享有 10.0.2.0/24 网段。
\nQuagga配置 下面才是重头戏。Quagga 的配置文件位于 /etc/quagga/
\n据测试,Openwrt 安装 quagga 后会带有初始配置,而 Debian 不带初始配置,可自行创建。
\n/etc/quagga/zebra.conf
配置(可不用修改)
\n1 2 3 4 5 6 7 8 ! 登陆密码password zebra !access -list vty permit 127.0 .0 .0 /8 access -list vty deny any !line vty access -class vty
\n\n/etc/quagga/bgpd.conf
配置(需要根据情境修改)
\n1 2 3 4 5 6 7 8 9 10 11 ! 密码 password zebra! AS号 router bgp 114514! 本机公网(VPN网络)ip bgp router-id 10.0.1.1! 本路由享有网段(需要交换的网段) network 10.0.1.0/24! peer信息(建立连接的机器的公网(VPN网络)ip,AS及称呼) neighbor 10.0.2.1 remote-as 1919810 neighbor 10.0.2.1 description R2
\n\n 另一台机器的配置只需依葫芦画瓢,我就不再赘述。
\n配置之后,运行
\n/etc/init.d/zebra restart
\n/etc/init.d/bgpd restart
(Debian)
\n或者
\n/etc/init.d/quagga restart
(Openwrt)
\n重启 quagga 服务。
\n欣赏结果 忙活了这么久,终于能看到结果了!
\n运行
\nvtysh
\n进入 quagga 控制台(指令模拟 Cisco,这块不大了解)
\n输入
\nshow ip bgp neighbor
\n就会看到
\n1 2 3 BGP neighbor is 10.0 .2 .1 , remote AS 1919810 , local AS 114514 , external link Description: R2 BGP version 4 , remote router ID 10.0 .2 .1
\n\n还有这么一张表
\n Sent Rcvd\nOpens: 2 0\nNotifications: 0 0\nUpdates: 2 2\nKeepalives: 1050 1049\nRoute Refresh: 0 0\nCapability: 0 0\nTotal: 1054 1051\n
\n再看看路由器内的活动ipv4路由表
\n\n\n\n网络 \n对象 \nIPv4 网关 \n跃点数 \n表 \n \n \n\nxxx \n10.0.2.0/24 \n10.0.2.1 \n20 \nmain \n \n
\n就算大功告成了!
\n有什么用处呢? 没有。
\n内网测试唯一能享受的就是看着这条无形的链接,想象自己也是网络工程师。
\n但是还是感觉很爽!
\n而且你已经学会(大概)了 BGP,获取全球路由表也能办到了!
\n参考 Ria 的爸爸(我的岳父)的文章
\n使用bird配置bgp网络互连
\n至于这一切与 Ria 有啥关系?欢迎关注 Ria 了解详情(滑稽)
\nTelegram群组 bilibli Youtube
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/9d1c6fa4.html",
+ "url": "https://blog.udon.eu.org/archives/9d1c6fa4.html",
+ "title": "Joplin+Webdav同步问题的解决方案",
+ "date_published": "2020-01-06T10:00:00.000Z",
+ "content_html": "问题内容 在群晖上搭建了 Webdav 服务器,使用 Joplin 连接后无法同步笔记。
\n \n\n解决方案 错误示范: ‘ https://your.domain.com:[your port] ‘
\n以此路径访问的是群晖文件系统的 /
目录,由于没有权限读写,同步失败。
\n正确示范: 在群晖控制面板内新建一个名为 Joplin
的共享文件夹。以域名:
\n‘ https://your.domain.com:[your port] /Joplin’
\n访问即可同步。
\n自定义配置: 若想在已经存在的文件夹下同步 Joplin 笔记,按照正确示范所书写的 URL 书写路径即可在想要的地方同步。
\n解决方案的探索 (我并未了解过 Webdav 的原理) 遇到此问题时,我在上 Google 查找解决方法前试着自行分析。思考原因后我选择了抓包分析。
\n结合抓包结果和 Joplin 同步日志可以看到 Joplin 在 /
目录下查找了 .lock
等文件。结合其他 Webdav 软件可以看到访问的 URL 指向的是目标文件夹,即“域名:端口/目标文件夹”,由此推测需要在配置内为 Joplin 指明同步目录,否则将在没有权限的根目录下同步,导致失败。
\n这是一次没有什么技术含量但能启发我的尝试。若你也遇到了类似的问题,希望也能启发到你。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/27f2d840.html",
+ "url": "https://blog.udon.eu.org/archives/27f2d840.html",
+ "title": "iPv6下绝佳的DDNS方法-dynv6",
+ "date_published": "2019-10-03T15:30:00.000Z",
+ "content_html": "下面我将介绍一种适用于 IPv6-DDNS 的绝佳方法!
\n \n\n通过 IPv6 访问的优点 没有端口限制!!!可以通过 80/443 访问 web 服务器了!不用带着烦人的端口号!
\n每台设备有独立的 IPv6,配置更加方便,无需路由器充当网关进行端口转发!
\nIPv6 也有不足之处 最严重的问题:很多家宽并没有开启 IPv6 的获取。但访问 IPv6 的设备必须要拥有 IPv6 地址!
\n技术问题,难以解决。一般来说手机的移动网络都已分发 IPv6 地址。
\n想要家宽拥有 IPv6?或许你需要修改光猫设置(超级管理),亦或是将光猫改为桥接模式,用路由器拨号从而获取 IPv6 地址。
\n如何实现iPv6-DDNS \n\n这点尤其重要!我在网络上寻寻觅觅无数脚本,总是失败。最后才发现官方有提供脚本也!一试马上就成功了。
\n \n\n1 token = *your token * ./dynv6.sh *your DDNS domain*
\n\n\n可以将其添加到 crontab 一类的软件内,规定时间自动执行脚本(每 10分钟一次为宜) \n \n大功告成!
\n事后 \n你可以把自己的域名 CNAME 过去 \n也可以用 dynv6 提供的域名 \n \n随心所欲!
\nOver.
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/a4c81e8f.html",
+ "url": "https://blog.udon.eu.org/archives/a4c81e8f.html",
+ "title": "搭建Calibre-Web电子书网页端管理服务",
+ "date_published": "2019-10-02T10:30:00.000Z",
+ "content_html": "说在前面 Kindle 是一样阅读利器,但若是没有一个强大的书库,它也只能用来压泡面(笑)
\n今天我们利用 Docker 在群晖(任意系统)上搭建 Calibre-web 服务
\n \n\ndocker选择 \ntechnosoft2000-calibre-web
\n \n我在尝试了 3 款 Docker Image 后,决定使用这款。
\n优点 Calibre-web \n基于 Python,性能高(低)
\n \n与 Calibre 软件的数据库等文件完全互通
\n \n支持推送至 Kindle
\n \n支持在线转码书籍(虽然问题较多)
\n \n \ntechnosoft2000-calibre-web \n安装 首先,将你的 Calibre 数据库的位置挂载到 Docker 内的 /books 。
\n启动 Docker 后不要着急,等待 Docker 内软件安装完毕后,访问您设置的端口,访问 Calibre-Web。
\n在 Calibre 数据库位置 一栏填写
\n \n\n特性配置 可以依情况而变,不一定要按我的配置。
\n若想要使用在线转码功能,外部二进制 一栏中,选择 使用 calibre 的电子书转换器 。 转换工具路径 按图片中填写。
\n1 /opt /calibre/ebook-convert
\n\n点击 提交 即可完成安装。
\n注意
\n若提示无法读取数据库,尝试将数据库所处的文件夹的权限改为755 。
\n如何操作?请打开 SSH,在 Terminal 内操作。
\n若还是失败,尝试在本地 Calibre 软件新建一个书库,将空的 数据库文件移动到你挂载的目录下。
\n安装界面示意图
\n
\n尾声 安装后的操作我就不多提了。该上传书籍的上传,该push的push。
\n我的几个建议:
\n\n推送邮箱推荐使用 Outlook,限制少。 \n可以用 Calibre 软件管理数据库。不知道为什么,在本地做好更改后,网页版并没有任何变化。我试着备份又还原了数据库后,网页里再重新加载数据库才成功了。 \n网页转码会遇到种种问题,例如电子书有加密。转码失败实属正常。 \n \n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/87bacf3f.html",
+ "url": "https://blog.udon.eu.org/archives/87bacf3f.html",
+ "title": "群晖-外网访问一站式教程2 DDNS",
+ "date_published": "2019-08-31T10:30:00.000Z",
+ "content_html": "简述配置 DDNS 的方法。
\n什么是DDNS 维基百科-动态DNS 鉴于 IPv4 地址的枯竭,运营商开始给家用宽带分配动态 IP 地址,即 IP 会随时间或重新拨号而改变。DDNS 可以允许用户通过API动态地将变化的 IP 地址传送给域名解析商,达到域名外网访问的效果。 由于带宽分配原因,家用宽带的上传带宽一般在 20-30Mbps 间,故外网访问速度并没有达到如签约的 100Mbps 属正常。
\n事先准备 打电话给 ISP(运营商)的小姐姐,让她给你公网 IP,如果问起原因可以回答家里装监控。没有开启公网 IP 将无法从外网访问家庭的内部网络。 由于运营商(指大部分,如电信)封锁了 80(HTTP)和 443(HTTPS)端口,我们将使用其他的端口进行访问。挑选一些你喜欢的端口,预备使用(如 8080-8090,4431-4439等)。
\n选择支持DDNS的域名解析服务商 CloudFlare 老牌的域名解析商,也是少有的免费提供 CDN 的服务商。 我推荐 CloudFlare 的原因有三点
\n\n可以使用 CDN,保证网络质量始终处于较好状态。例如,我的 Blog 搭建在 Github 上,若有时因网络抽风无法访问 Github,CDN 能助你一臂之力。 \n可以查看连接数,数据量,访客量等详细数据。 \nAPI 获取方便。(2022 Update: CloudFlare 的 API 服务器接近半墙,国内很难再访问了,不推荐使用) \n \nDynv6 提供 IPv4 与 IPv6 DDNS 的服务商,在 21 年有一次较长时间的故障,平常都非常稳定。
\nDNSPod 被腾讯收购的 DNS 服务商,使用需实名。
\n配置 DDNS 服务 家用路由器的 DDNS 功能一般仅支持国内大型服务商,例如花生壳。
\n有两种方法可以配置自己的 DDNS 服务:
\n\n将负责拨号的路由器刷成 Openwrt 系统,安装 DDNS 插件以配置自定义脚本的 DDNS 服务; \n在一台 24x7 运行的设备上,通过 API 获取 IP 地址,并定时执行脚本更新 IP 地址; \n \n前者虽然更加麻烦,但可以实现仅在 IP 更换时发起更新解析的请求,而不需要定期(如每十分钟)请求一次 API,减小账户被封的风险,并尽可能地缩短从 IP 更换到新的解析生效的时间。
\n不管是路由器也好,Linux 上的脚本也好,可以在 GitHub 上寻找对应 DDNS 服务商的更新脚本,填上配置就能使用啦~
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/8776c6d2.html",
+ "url": "https://blog.udon.eu.org/archives/8776c6d2.html",
+ "title": "群晖-外网访问一站式教程1 Zerotier",
+ "date_published": "2019-08-20T04:00:00.000Z",
+ "content_html": "说在前面:有一个月没更新文章了。今后尽量做到 1-2 周更一篇吧。Goo 酱终于把我的 Blog 收录了(其实是我主动提交的),这些教程也能被大家看到啦!有任何问题可以在文章结尾的 Gitalk 处留言,我会尽快回复的!
\n这篇教程无图,省流。(其实经过 Gzip 压缩后已经能省 80% 的数据量,我也被惊到了)
\n \n\n \n在上一回的教程里,我们成功搭建了MC基岩版服务器。
\n群晖-MC基岩版服务器教程
\n但这个服务器仅存在于本地,若朋友不在同一个局域网下,就无法连接此服务器(这不是肯定的嘛)。
\n下面我将介绍几种从往外访问家里的群晖服务器的方法。
\n \n最简单的方法 - Zerotier - VPN 法 有兴趣了解 VPN 技术的朋友可以点击查看 维基百科 - VPN
\nZerotier 可以提供免费的VPN服务,无需复杂配置即可在任何地方连接本地服务器
\n安装 第一步:下载对应版本的 Zerotier 软件 并于软件市场安装
\n示范:
\n例如我的群晖的CPU是 64位 的 J1900,我选择了这一款软件zerotier_x64-6.1_1.4.0-0.spk
\n接下来,在群晖的 套件中心-设置-信任层级
中选择 任何发行者
\n然后在 套件中心-手动安装
里选择刚刚下载好的 zerotier_xxxxxx.spk
安装包进行安装
\n第二步:注册Zerotier。网络上教程极多,我就不再赘述,可以参考这一篇文章。
\n内网穿透工具 — ZeroTier One 的使用 作者 BiteMan
\n第三步:记住刚刚注册完拿到的 Network ID 了么,打开 Zerotier 套件,在右下角输入 Network ID 点击 join,就加入这个网络(Networks)啦。记得在 Zerotier 网站控制台里勾选这个新连接(新创建的网络默认使用加入需要验证的规则,我也建议开启,增强安全性)(我强烈建议每添加一台设备即为其命名)。
\n第四步:在需要访问群晖服务器的设备上也安装好 Zerotier 软件(比起手动安装要简单不少,各大应用市场里也都有,建议下载官方提供的安装包),重复步骤三,加入同一个网络(Networks)下。
\n到此,Zerotier 双端已经配置完毕。
\n如何使用呢? 在 Zerotier 网页控制台里,若已添加两台设备,你会看到 Members 分栏中有两台设备,他们的右边都有一个 IP 地址。当两个设备都打开 Zerotier 软件并成功连接了 VPN,你就可以把他们当作在一个局域网下,IP 地址就是 Zerotier 提供的那个,端口号原封不动。
\n例如我想访问群晖的DSM管理界面,输入 xxx.xxx.xxx.xxx:5000
即可。(Zerotier提供了几种保留网段,可以从 10、172、192 网段里选择你喜欢的 IP 段)
\n已知问题 Zerotier 适合访问网页/SSH 等简单服务,若用其连接游戏,可能会有某些时刻丢包率过高(原因不明)导致强制登出,严重影响游戏体验。
\n下一篇文章里我会介绍第二种方法 - DDNS,此方法可以带来极致体验(笑)。
\n",
+ "tags": [
+ "教程"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/category/\346\225\231\347\250\213/rss.xml" "b/category/\346\225\231\347\250\213/rss.xml"
new file mode 100644
index 00000000..0b35ed79
--- /dev/null
+++ "b/category/\346\225\231\347\250\213/rss.xml"
@@ -0,0 +1,1011 @@
+
+
+
+ カレーうどん屋 • Posts by "教程" category
+ https://blog.udon.eu.org
+ カレーうどん屋.
+ zh-CN
+ Sun, 16 Apr 2023 00:00:00 +0800
+ Sun, 16 Apr 2023 00:00:00 +0800
+ 教程
+ 随笔
+ 软件
+ 小鸡测评
+ 3D 打印
+ 嵌入式开发
+ GitHub Actions
+ Hexo
+ DIY
+ -
+
https://blog.udon.eu.org/archives/8b68ddd6.html
+ 修复 UEFI 引导的 GRUB
+ https://blog.udon.eu.org/archives/8b68ddd6.html
+ 教程
+ Sun, 16 Apr 2023 00:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/8b115688.html
+ 使用 Docker Compose 部署音乐服务器 Navidrome
+ https://blog.udon.eu.org/archives/8b115688.html
+ 教程
+ Tue, 31 Jan 2023 12:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/f9bfe16a.html
+ 使用 Docker Compose 部署 Keycloak 20
+ https://blog.udon.eu.org/archives/f9bfe16a.html
+ 教程
+ Sun, 22 Jan 2023 20:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/e74a90f2.html
+ 使用再生龙 Clonezilla 备份操作系统
+ https://blog.udon.eu.org/archives/e74a90f2.html
+ 教程
+ Fri, 12 Aug 2022 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/f82a3103.html
+ BETAFPV 高频头固件编译 AttributeError
+ https://blog.udon.eu.org/archives/f82a3103.html
+ 教程
+ DIY
+ Sat, 06 Aug 2022 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/38942a16.html
+ DIY 显示器音箱
+ https://blog.udon.eu.org/archives/38942a16.html
+ 教程
+ DIY
+ Fri, 03 Jun 2022 15:15:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/2e528779.html
+ 迁移 Hexo 渲染环境至 GitHub Actions
+ https://blog.udon.eu.org/archives/2e528779.html
+ 教程
+ GitHub Actions
+ Hexo
+ Mon, 23 May 2022 19:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/dbf21067.html
+ 玩一玩 DN42
+ https://blog.udon.eu.org/archives/dbf21067.html
+ 教程
+ Fri, 01 Apr 2022 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/9b58c98e.html
+ 合宙 EPS32-C3 把玩记录(二):WiFi 与一个 Web 程序
+ https://blog.udon.eu.org/archives/9b58c98e.html
+ 教程
+ 嵌入式开发
+ Sun, 27 Mar 2022 00:15:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/7f7bd4a5.html
+ 合宙 EPS32-C3 把玩记录(一):环境搭建与第一个程序
+ https://blog.udon.eu.org/archives/7f7bd4a5.html
+ 教程
+ 嵌入式开发
+ Sat, 26 Mar 2022 17:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/d01399e6.html
+ Code-Server 的代理配置
+ https://blog.udon.eu.org/archives/d01399e6.html
+ 教程
+ Sat, 19 Mar 2022 22:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/7263a385.html
+ Klipper 的外网访问
+ https://blog.udon.eu.org/archives/7263a385.html
+ 教程
+ 3D 打印
+ Sat, 12 Feb 2022 14:50:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/375e7789.html
+ 群晖搭建 VSCode 服务器与 Syncthing 服务
+ https://blog.udon.eu.org/archives/375e7789.html
+ 教程
+ Sat, 20 Mar 2021 00:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/a455b52c.html
+ 逃离国产软件 - 虚拟机计划
+ https://blog.udon.eu.org/archives/a455b52c.html
+ 教程
+ 软件
+ Fri, 07 Aug 2020 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/e3c95af8.html
+ BGP初体验-Linux,Openwrt与Quagga
+ https://blog.udon.eu.org/archives/e3c95af8.html
+ 教程
+ Mon, 10 Feb 2020 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/9d1c6fa4.html
+ Joplin+Webdav同步问题的解决方案
+ https://blog.udon.eu.org/archives/9d1c6fa4.html
+ 教程
+ Mon, 06 Jan 2020 18:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/27f2d840.html
+ iPv6下绝佳的DDNS方法-dynv6
+ https://blog.udon.eu.org/archives/27f2d840.html
+ 教程
+ Thu, 03 Oct 2019 23:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/a4c81e8f.html
+ 搭建Calibre-Web电子书网页端管理服务
+ https://blog.udon.eu.org/archives/a4c81e8f.html
+ 教程
+ Wed, 02 Oct 2019 18:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/87bacf3f.html
+ 群晖-外网访问一站式教程2 DDNS
+ https://blog.udon.eu.org/archives/87bacf3f.html
+ 教程
+ Sat, 31 Aug 2019 18:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/8776c6d2.html
+ 群晖-外网访问一站式教程1 Zerotier
+ https://blog.udon.eu.org/archives/8776c6d2.html
+ 教程
+ Tue, 20 Aug 2019 12:00:00 +0800
+
+
+
+
diff --git "a/category/\350\275\257\344\273\266\346\216\250\350\215\220/atom.xml" "b/category/\350\275\257\344\273\266\346\216\250\350\215\220/atom.xml"
new file mode 100644
index 00000000..9f7d8cf8
--- /dev/null
+++ "b/category/\350\275\257\344\273\266\346\216\250\350\215\220/atom.xml"
@@ -0,0 +1,63 @@
+
+
+ https://blog.udon.eu.org
+ カレーうどん屋 • Posts by "软件推荐" category
+
+ 2020-05-05T05:40:06.000Z
+
+
+
+
+
+
+
+
+
+
+ https://blog.udon.eu.org/archives/6b40e5ad.html
+ 提升音乐体验-本地音乐标签/歌词匹配与回放增益
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>推荐两款能让听歌体验变得更好的软件 —— Music Tag / Foobar2000 。</p>
+<p>附带使用教程。</p>
+<span id="more"></span>
+
+<h2 id="音乐标签-Music-Tag"><a href="#音乐标签-Music-Tag" class="headerlink" title="音乐标签 Music Tag"></a>音乐标签 Music Tag</h2><h3 id="官方网站-下载地址"><a href="#官方网站-下载地址" class="headerlink" title="官方网站/下载地址"></a>官方网站/下载地址</h3><p><a href="https://www.cnblogs.com/vinlxc/p/11347744.html">https://www.cnblogs.com/vinlxc/p/11347744.html</a></p>
+<h3 id="软件特点"><a href="#软件特点" class="headerlink" title="软件特点"></a>软件特点</h3><p>古人云,专辑封面是一首歌的灵魂。(我乱说的)</p>
+<p>Music Tag 是一款可以自动匹配本地音乐的标签与歌词的软件。</p>
+<p>一键从多家音乐网站拉取元数据/封面图/歌词,不能再爽了。</p>
+<h3 id="使用教程-建议"><a href="#使用教程-建议" class="headerlink" title="使用教程/建议"></a>使用教程/建议</h3><p>导入一批歌曲后,选择 自动匹配标签 :(如下图)</p>
+<p><img src="/images/2020-05-05/music-tag-1.png"></p>
+<p>然后按下图配置,在原有元数据下添加更多信息:</p>
+<p><img src="/images/2020-05-05/music-tag-2.png"></p>
+<p>在第一轮匹配后,建议再进行第二轮封面图片匹配,并覆盖原图片,配置如下图:</p>
+<p><img src="/images/2020-05-05/music-tag-3.png"></p>
+<p>接着,就需要你耐心地查看每首歌的元数据(善用方向键),检查是否有匹配错误的歌曲,并在 标签源-组合标签 处手动搜索,选择正确的元数据,如下图所示:</p>
+<p><img src="/images/2020-05-05/music-tag-4.png"></p>
+<p>建议检查一下歌曲的歌词,特别是较小众的歌曲。Music Tag 的歌词搜索错误率较高。</p>
+<p>如下图所示,选择并查看歌词,若有误可以手动搜索:</p>
+<p><img src="/images/2020-05-05/music-tag-5.png"></p>
+<p><img src="/images/2020-05-05/music-tag-6.png"></p>
+<p>最后选择导出 LRC 歌词:</p>
+<p><img src="/images/2020-05-05/music-tag-7.png"></p>
+<p>所有歌曲的 元数据-封面图-歌词 就此已匹配完毕。</p>
+<p>最终效果如下:</p>
+<p><img src="/images/2020-05-05/foobar2000-m.jpg"></p>
+<h2 id="Foobar2000"><a href="#Foobar2000" class="headerlink" title="Foobar2000"></a>Foobar2000</h2><h3 id="官方网站"><a href="#官方网站" class="headerlink" title="官方网站"></a>官方网站</h3><p><a href="http://www.foobar2000.org/">http://www.foobar2000.org/</a></p>
+<h3 id="回放增益介绍"><a href="#回放增益介绍" class="headerlink" title="回放增益介绍"></a>回放增益介绍</h3><p><a href="https://zh.wikipedia.org/wiki/%E5%9B%9E%E6%94%BE%E5%A2%9E%E7%9B%8A">维基百科-回放增益</a></p>
+<p>回放增益可以使音量大小各不相同的音乐向统一标准靠齐。</p>
+<p>将所有音乐扫描并打上回放增益 tag 后,再也不用担心下一首歌震破耳膜了。</p>
+<h3 id="使用教程"><a href="#使用教程" class="headerlink" title="使用教程"></a>使用教程</h3><p>导入并全选歌曲,右键,选择 ReplayGain :</p>
+<p><img src="/images/2020-05-05/foobar2000-1.png"></p>
+<p>下列三种扫描方式均可。个人喜欢将全部歌曲的音量统一,故选择第一种:</p>
+<p><img src="/images/2020-05-05/foobar2000-2.png"></p>
+<p>待扫描结束后,点击 Update File Tags ,将回放增益数据写入文件 Tag :</p>
+<p><img src="/images/2020-05-05/foobar2000-3.png"></p>
+<p>回放增益扫描完毕。</p>
+<h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>我是一周不往曲库里添加新曲就受不了的类型。</p>
+<p>这几天往曲库里添加曲子的时候查阅了这些能提升音乐体验的方法,希望能帮到你。</p>
+
+
+
+ 2020-05-05T05:40:06.000Z
+
+
diff --git "a/category/\350\275\257\344\273\266\346\216\250\350\215\220/feed.json" "b/category/\350\275\257\344\273\266\346\216\250\350\215\220/feed.json"
new file mode 100644
index 00000000..719af4c3
--- /dev/null
+++ "b/category/\350\275\257\344\273\266\346\216\250\350\215\220/feed.json"
@@ -0,0 +1,19 @@
+{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "カレーうどん屋 • All posts by \"软件推荐\" category",
+ "description": "カレーうどん屋.",
+ "home_page_url": "https://blog.udon.eu.org",
+ "items": [
+ {
+ "id": "https://blog.udon.eu.org/archives/6b40e5ad.html",
+ "url": "https://blog.udon.eu.org/archives/6b40e5ad.html",
+ "title": "提升音乐体验-本地音乐标签/歌词匹配与回放增益",
+ "date_published": "2020-05-05T05:40:06.000Z",
+ "content_html": "推荐两款能让听歌体验变得更好的软件 —— Music Tag / Foobar2000 。
\n附带使用教程。
\n \n\n音乐标签 Music Tag 官方网站/下载地址 https://www.cnblogs.com/vinlxc/p/11347744.html
\n软件特点 古人云,专辑封面是一首歌的灵魂。(我乱说的)
\nMusic Tag 是一款可以自动匹配本地音乐的标签与歌词的软件。
\n一键从多家音乐网站拉取元数据/封面图/歌词,不能再爽了。
\n使用教程/建议 导入一批歌曲后,选择 自动匹配标签 :(如下图)
\n
\n然后按下图配置,在原有元数据下添加更多信息:
\n
\n在第一轮匹配后,建议再进行第二轮封面图片匹配,并覆盖原图片,配置如下图:
\n
\n接着,就需要你耐心地查看每首歌的元数据(善用方向键),检查是否有匹配错误的歌曲,并在 标签源-组合标签 处手动搜索,选择正确的元数据,如下图所示:
\n
\n建议检查一下歌曲的歌词,特别是较小众的歌曲。Music Tag 的歌词搜索错误率较高。
\n如下图所示,选择并查看歌词,若有误可以手动搜索:
\n
\n
\n最后选择导出 LRC 歌词:
\n
\n所有歌曲的 元数据-封面图-歌词 就此已匹配完毕。
\n最终效果如下:
\n
\nFoobar2000 官方网站 http://www.foobar2000.org/
\n回放增益介绍 维基百科-回放增益
\n回放增益可以使音量大小各不相同的音乐向统一标准靠齐。
\n将所有音乐扫描并打上回放增益 tag 后,再也不用担心下一首歌震破耳膜了。
\n使用教程 导入并全选歌曲,右键,选择 ReplayGain :
\n
\n下列三种扫描方式均可。个人喜欢将全部歌曲的音量统一,故选择第一种:
\n
\n待扫描结束后,点击 Update File Tags ,将回放增益数据写入文件 Tag :
\n
\n回放增益扫描完毕。
\n小结 我是一周不往曲库里添加新曲就受不了的类型。
\n这几天往曲库里添加曲子的时候查阅了这些能提升音乐体验的方法,希望能帮到你。
\n",
+ "tags": [
+ "教程",
+ "软件"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/category/\350\275\257\344\273\266\346\216\250\350\215\220/rss.xml" "b/category/\350\275\257\344\273\266\346\216\250\350\215\220/rss.xml"
new file mode 100644
index 00000000..344e8df6
--- /dev/null
+++ "b/category/\350\275\257\344\273\266\346\216\250\350\215\220/rss.xml"
@@ -0,0 +1,67 @@
+
+
+
+ カレーうどん屋 • Posts by "软件推荐" category
+ https://blog.udon.eu.org
+ カレーうどん屋.
+ zh-CN
+ Tue, 05 May 2020 13:40:06 +0800
+ Tue, 05 May 2020 13:40:06 +0800
+ 教程
+ 随笔
+ 软件
+ 小鸡测评
+ 3D 打印
+ 嵌入式开发
+ GitHub Actions
+ Hexo
+ DIY
+ -
+
https://blog.udon.eu.org/archives/6b40e5ad.html
+ 提升音乐体验-本地音乐标签/歌词匹配与回放增益
+ https://blog.udon.eu.org/archives/6b40e5ad.html
+ 教程
+ 软件
+ Tue, 05 May 2020 13:40:06 +0800
+
+
+
+
diff --git "a/category/\351\232\217\347\254\224/atom.xml" "b/category/\351\232\217\347\254\224/atom.xml"
new file mode 100644
index 00000000..1558c50f
--- /dev/null
+++ "b/category/\351\232\217\347\254\224/atom.xml"
@@ -0,0 +1,1117 @@
+
+
+ https://blog.udon.eu.org
+ カレーうどん屋 • Posts by "随笔" category
+
+ 2024-06-13T06:40:00.000Z
+
+
+
+
+
+
+
+
+
+
+ https://blog.udon.eu.org/archives/95479b1f.html
+ Soundcore C30i 耳机
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>我目前在使用的耳机是 Sony LinkBuds,就是那款中间带了个孔的奇怪耳机。</p>
+<p>它本是为了商务人士在办公场所使用而设计,收听耳机中声音的同时不会影响听取他人的谈话。它却被我这个耳道经常发炎的人看上了。</p>
+<p>我想,中间带了个孔,那耳道内外的空气就是完全流通的,那耳道内肯定不会闷了吧!为了验证这一点,我特地去 Sony 的线下店体验这款耳机。佩戴上 LinkBuds,确实完全没有耳道被封闭的感觉,我便在网上欣然下单了。</p>
+<p>然而,经过一段时间的佩戴,才发现我的想法过于天真:看似畅通无阻的气流,在直径不够大的圆环处有阻塞。内部产生的水蒸气无法排出,导致耳道仍然会有潮湿感。佩戴20分钟以上,摘下耳机放入耳机舱,一段时间后取出,会发现耳机舱内有冷凝水。</p>
+<p>Sony LinkBuds 仍然不能解决我耳朵发炎的困扰。我便又踏上了寻找新耳机的旅途。</p>
+<p>今天便是要介绍我找到的其中一款,有望成为最终解决方案的耳机 —— SoundCore C30i。</p>
+<p><img src="/images/2024-06-13/01.jpeg" alt="外包装"></p>
+<h2 id="设计"><a href="#设计" class="headerlink" title="设计"></a>设计</h2><p><img src="/images/2024-06-13/02.jpeg" alt="充电盒外观"></p>
+<p><img src="/images/2024-06-13/03.jpeg" alt="充电盒开盖"></p>
+<p>耳机充电盒的外观,以及开盖后的样子如上图所示,就不多介绍了。</p>
+<p>开放式耳机相对于入耳式、半入耳式耳机,设计上花样多了不少。我看到的所有款式中,大致可以分为两类:</p>
+<ul>
+<li>使用挂钩悬挂在耳朵之后,将扬声器覆盖于耳道口,通过空气传导声音进入耳道的;</li>
+<li>夹在耳垂上,耳机本身不覆盖或仅部分覆盖耳道口,通过侧向开口将声音导向耳道的。</li>
+</ul>
+<p>SoundCore C30i 属于后者。</p>
+<p><img src="/images/2024-06-13/04.jpeg" alt="耳机本体"></p>
+<p>我选择的是透明外壳的款式,耳机内部结构清晰可见。</p>
+<p>左边耳机的扬声器处有一个开口,这便是朝向耳道的声音出口。</p>
+<p>右耳朝上的是耳机背面,金色的圆片是触控传感器。</p>
+<h2 id="佩戴感"><a href="#佩戴感" class="headerlink" title="佩戴感"></a>佩戴感</h2><p>夹耳的耳机其实还可以再分类。一种是本体柔软、有弹力,有点像悬挂在耳朵上的,将扬声器更多地覆盖于耳道口,以提升传音效率的耳机。</p>
+<p>C30i 本体则不可以弯曲,采用虎口般的结构卡在耳垂上。在佩戴时需要找到耳垂比较薄的部分,将耳机从那里套入耳垂,再将耳机推向耳垂较厚的地方,就能牢固固定。看似还是有些松松垮垮,但因为耳机足够轻,不容易被甩掉。</p>
+<p>我的感觉是,佩戴上没有什么异物感,但难以做到所谓“戴久了会忘记它的存在”这么夸张。</p>
+<p>我尝试连续佩戴了两个多小时,期间耳朵没有任何被堵塞的感觉,且取下后耳朵没有丝毫的潮湿感。确实是完全的开放了!</p>
+<h2 id="音质"><a href="#音质" class="headerlink" title="音质"></a>音质</h2><p>我的评价是:出奇的好。</p>
+<p>因为是一种妥协,我对耳机的音质便不抱任何希望。实际听起来,虽然也有这非封闭式耳机缺乏低音等问题,其实音质相当不错。</p>
+<p><img src="/images/2024-06-13/05.jpeg" alt="EQ 方案"></p>
+<p>通过调整 EQ 配置,将高低频都拉高(经典两头高调法),效果更是好了许多!</p>
+<p>我也听了一段博客,听清说话人的语音也没有任何困难。</p>
+<h2 id="多设备连接"><a href="#多设备连接" class="headerlink" title="多设备连接"></a>多设备连接</h2><p>我原本买的是飞利浦的一款开放式耳机,到手之后才发现耳机没有双设备连接的功能。我平常有在手机和电脑之间切换使用的需求,少了这个功能实在不能接受。</p>
+<p>C30i 支持同时连接两台设备,可以在两台设备间无缝切换。</p>
+<h2 id="续航"><a href="#续航" class="headerlink" title="续航"></a>续航</h2><p>商品说明上标明了耳机单次续航是 10 个小时,这应该是相当厉害的了。加上充电盒,总共可以提供 30 个小时的续航。</p>
+<p>实际用下来,在 2 小时的使用后,耳机的电量没有明显的减少。续航应当是很强劲的。</p>
+<h2 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h2><p>目前我看到的一个缺点是,耳机缺少自动休眠功能。将充电盒打开,耳机便会连接所有设备,摘下后仍保持着连接。直到耳机放回充电盒,并盖上盖子后,耳机才会关机。</p>
+<p>且耳机缺少佩戴检测,摘下后不会停止音乐播放。似乎耳夹式耳机比较少有这个功能。</p>
+<p>这可能会抢占了一些设备的音频输出,不过不算是很大的缺点。</p>
+<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>SoundCore C30i 有效解决了我耳道容易发炎的痛点,音质不错,且有多设备连接的功能,满足了作为我主力使用的耳机的所有需求。</p>
+
+
+ 2024-06-13T06:40:00.000Z
+
+
+ https://blog.udon.eu.org/archives/adc5a61e.html
+ 日本之行-第二站-奈良
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>23年12月底,我踏上了前往日本的旅途。这是我第一次去日本,是我第一次出国旅行,且只身一人。</p>
+<p>本次旅行为14天,我将分多个章节完成所有的游记,记录下我旅行中的点点滴滴、各种感想。这是第二章节,奈良之旅。</p>
+<h2 id="近铁、巴士与鹿"><a href="#近铁、巴士与鹿" class="headerlink" title="近铁、巴士与鹿"></a>近铁、巴士与鹿</h2><p><img src="/images/2024-03-11/00.jpeg" alt="使用 Suica 乘坐近铁"></p>
+<p>奈良距离大阪仅有 30km,乘坐近铁奈良线,摇摇晃晃一个多小时便能到达奈良。</p>
+<p>这条线路有分区间准急、准急、急行、快速急行四个速度,每个速度停靠的站点数量不同,票价都相同的。如果想尽快到达奈良,乘坐快速急性是最好的选择。</p>
+<p>这天,我纯凭心情来到近铁难波站,坐上了最近一班开往奈良的近铁,似乎是急行。</p>
+<p>近铁的运行速度不是很快,叮叮当当地在楼房间穿行着。</p>
+<p>在近铁奈良站,奈良线的终点站下了车,距离旅馆所在的奈良公园还有一段距离。由于拖着行李,我选择搭乘公交车。</p>
+<p>公交车到站后会向站台一侧倾斜车身,方便乘客上下;上车的门并不固定,可以看车身的贴纸判断;司机会很耐心地等待所有人上车、落座之后才摆正车身、继续前进。</p>
+<p>有一些线路是分段计费的,就需要在上车的时候领取一张整理卷,或者先刷一下卡,在下车的时候将钱和整理券一起投入投币机,或者刷一下公交卡就可以了。</p>
+<p>在每一个座位的旁边都有一个按钮,如果下一站要下车,按一下就会提醒司机,并且在车头的显示屏上也会显示,车上的所有按钮都会亮起。在公交车到站停稳之前无需先起身,车子停稳后司机也会很耐心地等大家付钱、下车,还会一个一个道谢哦。</p>
+<p>相比于地铁,公交的到站时间显得没有那么准确,但不会差太多。在地面运行的公交会受到交通的影响,等候乘客上下车、付钱有时候也会用掉不少的时间,能做到按照时间表运行已经相当厉害了。</p>
+<p><img src="/images/2024-03-11/01.jpeg" alt="路边的鹿"></p>
+<p>虽然在车上就有看到,果然很多啊 —— 奈良的鹿。</p>
+<p>奈良的鹿是散养的,主要集中在奈良公园和春日大社。在奈良设有鹿苑,将受伤或者发情的鹿收养,待它们恢复正常再放回乡中散养。</p>
+<p>从车站走到旅馆,一路上都有鹿儿好奇地打量着来自异国他乡的我。</p>
+<p>每走一段距离,能看到提示观光客不要“挑逗”鹿儿的提示牌,有些鹿的性格比较差,会用头顶你,追着你不放的。好在大部分鹿都去掉了角,不至于顶伤人。</p>
+<p><img src="/images/2024-03-11/02.jpeg" alt="鹿园宾馆"></p>
+<p>今天的旅馆确实有些简陋 —— 私人活动空间仅有这么一小间,洗浴和方便都是公用的。不过旅店的氛围很好,有一个共用的客厅,客人们可以在这里吹暖气(冬天还是挺冷的)、喝饮料、聊天。</p>
+<p>旅店的工作人员非常热情,替我办理好入住手续后,立刻拿出一张奈良公园的地图,用简单的中文(很厉害哦!)告诉我这附近有哪些好玩的地方。奈良一天的行程就是参考着这张地图安排的。</p>
+<p>整理好行装,已是傍晚五点多。按照旅店工作人员以及 Google Maps 的说法,奈良公园里的大部分饮食店都要下班了!遂立刻出门,寻找晚饭的地点。</p>
+<p>因为今天是周五吗,明明还有一些旅客在此游玩的,旅店对面的旅游纪念品店已经关门了!沿街打量了几家店铺,也都在收拾着大堂,准备打烊了,完全不像是会接客的样子。大危机,要没晚饭吃了,得走个几公里处了奈良公园,到达奈良市区才有便利店。</p>
+<p>路过了一家同样是卖纪念品的商店,发现店内的空间还挺宽敞,也摆着一些桌椅,便问了一下老板还有没有饭吃。运气很好,这家店还没有打烊。</p>
+<p><img src="/images/2024-03-11/03.jpeg" alt="親子丼"></p>
+<p>可以选择的菜品并不是很多,大多数都是日本的家常菜。我选择了这道绝大部分和食餐馆都会有的家常菜 —— 親子丼。</p>
+<p>从厨房门帘的缝隙向里望去,可以隐约看见老板在烹饪着饭菜。明明用得都是同样的工具和食材,有技艺的人做出来的东西就能端上桌卖钱呢。</p>
+<p>蛋是我们所谓“半熟”的柔滑状态,鸡肉有一些小烧焦,主要的风味是自古以来人们就离不开的味道 —— 咸味。风味不能说突出,却很平和。它就是一碗很普通、很平常,此时此刻会出现在任意一张餐桌上的饭。</p>
+<p>饱饭后,我根据地图的指引,前往东大寺的二月台,观赏日落之景。</p>
+<p><img src="/images/2024-03-11/04.jpeg" alt="二月台的日落1"></p>
+<p><img src="/images/2024-03-11/05.jpeg" alt="二月台的日落2"></p>
+<p><img src="/images/2024-03-11/06.jpeg" alt="二月台的日落3"></p>
+<p><img src="/images/2024-03-11/07.jpeg" alt="二月台的日落4"></p>
+<p>只用手机相机无法捕捉暗光下的美景。</p>
+<p>夕阳柔和而昏暗地照着二月台,四周一片静寂,只能听见洗手亭的潺潺水声。在日落后还停留在奈良公园的游客寥寥,大家都保持着绝对的安静,共享这片难得的静谧。</p>
+<p>在台上驻足数分钟,我轻轻踏着脚步走下楼梯,沿着寺院的小路漫步着,离开了东大寺。</p>
+<p>此时天已完全黑了,不论是寺院还是神社都已关门,我就这么在奈良公园里漫步着。时不时碰见一群鹿,便从口袋中掏出一块鹿仙贝,掰给鹿儿吃。</p>
+<p><img src="/images/2024-03-11/08.jpeg" alt="果汁"></p>
+<p>会旅店前在路边的售货机买了一瓶果汁饮料,是不二家的。日本的饮料会标注果汁含量,我觉得很神奇。</p>
+<p><img src="/images/2024-03-11/09.jpeg" alt="春日大社"></p>
+<p>第二天,去参观了十分出名的春日大社。不过不恰巧,主殿正在维修,将赛钱箱设置在了原处,只能眺望主殿。</p>
+<p>在巫女那儿买了点纪念商品,是两只鹿型的小玩偶。一只是木质的,一只是陶制的,鹿儿的嘴中叼着签。忘记留下照片了。</p>
+<p><img src="/images/2024-03-11/10.jpeg" alt="林间小道"></p>
+<p>离开主殿,走上铺着碎石的小道。</p>
+<p><img src="/images/2024-03-11/11.jpeg" alt="路旁的神社"></p>
+<p>每走几步,便会出现一间迷你神社,感觉十分奇妙。在这片林海中不知道供奉着多少的神明。</p>
+<p>有些人也许是提前来做新年参拜,途中遇到的神社都会十分虔诚地参拜。</p>
+<p>在林间漫步了将近一个小时,吸饱了新鲜的空气,我振奋精神,回旅馆取了行李,准备离开奈良。</p>
+<p><img src="/images/2024-03-11/12.jpeg" alt="若草山"></p>
+<p>拖着行李箱漫步在奈良公园里,我又一次路过了若草山。</p>
+<p>若草山也不高,在满是丘陵的福建甚至都算不上山,只是个小土坡。山上的草长得格外整齐,是一座越看越顺眼的山呢。</p>
+<p>若草山每年12月初开始封山,直到第二年三月举行烧山仪式后才重新开放。没能上去走走真是可惜。</p>
+<p><img src="/images/2024-03-11/13.jpeg" alt="柿叶寿司 set"></p>
+<p>在前往近铁奈良站离开奈良前,我路过了一家在网上有着不少讨论的柿叶寿司店,便决定在这里解决午饭。</p>
+<p>柿叶寿司其实就是用柿子叶将寿司包起来烹饪。据说柿子叶不仅可以杀菌消毒,还能给寿司提升风味。</p>
+<p>想着,既然是奈良的特色菜,肯定要好好品尝一下。我点了一份 2500円 的大套餐,奢侈一下~</p>
+<p>左下角的五枚寿司,上方两枚便是柿叶寿司了。下方三角形的寿司也是用不能吃的叶子包着,但好像不是柿子叶;右侧是用腌制过的紫苏叶包的寿司,紫苏叶是可以吃的,风味很独特,我很喜欢;左侧是茶味的卷寿司,也是第一次吃。</p>
+<p>柿叶寿司用的是腌制过的鱼,确实带有叶子的香味,很有特色!</p>
+<p>除此之外,套餐内还有精致的小菜、一份天妇罗拼盘和一碗素面。吃的我好饱~</p>
+<p><img src="/images/2024-03-11/14.jpeg" alt="线上买的特急卷"></p>
+<p>乘坐上前往京都的特急电车时还发生了一些小插曲。</p>
+<p>坐上车后我才意识到自己坐的是特急的电车,进站时我只刷了基本票,没有购买特急卷。</p>
+<p>好在列车上有通过扫描二维码购买特急卷的渠道,也支持借记卡付款。在等待列车发车时,我赶紧买了特急卷,带着行李移动到了指定座位,正式踏上前往京都的旅途。</p>
+
+
+ 2024-03-10T16:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/a7050149.html
+ 23 年对我影响最大的硬件与软件
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>原文投稿在自留地频道的新年活动里。</p>
+<p>对于我来说,23 年给我带来影响最大的莫过于 Meta Quest 3 与 VRChat 了。</p>
+<h2 id="Quest-3"><a href="#Quest-3" class="headerlink" title="Quest 3"></a>Quest 3</h2><p><img src="/images/2024-03-01/01.png" alt="订单截图"></p>
+<p>Quest 3 在 23 年 6 月面世,于 9 月 28 日开放预购。我早在数年前就有了尝试 VR 的念头,在经过一天的调查研究后确定了 Quest 3 不会踩坑,便在日亚下了单,直邮回国。日亚上标价为 78k 円,日本商品出口不收税,再加上直邮的运费和中国的关税,最后到手的价格和标价差不多。(顺便一提,现在日亚也可以以相同的价格直邮国内)</p>
+<p>外观我就不展示了,网上的图很多。到手第一件事,便是尝试它对于上一代产品相比提升最大的地方——显示与穿透(Pass Through)。</p>
+<h3 id="显示效果"><a href="#显示效果" class="headerlink" title="显示效果"></a>显示效果</h3><p>显示效果上,Quest 3 采用了 Pancake 棱镜,甜点位置(Sweet Spot)相对于上一代更大,也就是说仅需简单调整头戴的位置,便能找到画面最为清晰的位置,我的使用体验上感觉也是如此,每次佩戴上头显很快就能调整好画面。</p>
+<h3 id="穿透-Pass-Through"><a href="#穿透-Pass-Through" class="headerlink" title="穿透 (Pass Through)"></a>穿透 (Pass Through)</h3><p><img src="/images/2024-03-01/02.jpeg" alt="穿透效果"></p>
+<p>穿透方面,由于我没有用过 Quest 2、Quest Pro 等拥有穿透功能的 VR 头显,无法进行比较。图片中看起来比较糊,是因为我用了 Quest 自带的截图功能,分辨率很低。实际观感上,在光线充足的环境中可以毫无压力地辨认各种物体,但细小的字是看不清楚的。戴着 Quest 使用手机并不是很舒服,因为物体距离头显过近会因摄像头视角的问题产生扭曲,看字会很吃力。将手机拿远,又会因为显示清晰度不够而看不清字。不过听说 Quest 2 的穿透是黑白且十分模糊,Quest Pro 也好不了很多,Quest 3 在这个方面应是有十足的进步。(似乎离 avp 还有很大的差距)</p>
+<hr>
+<p>相比于头显中不带有处理芯片的 VR 眼镜,Quest 可以使用无线串流软件将 PCVR 的画面投影至头显中显示,再也不用担心玩游戏时绊倒数据线了。</p>
+<p>总而言之,Quest 3 是一副功能完善,各方面实力均不弱的“六边形战士”,但毕竟定位是廉价头显,也就缺少了 Quest Pro 的自定位手柄、眼部追踪,Valve Index 的基站定位、手部追踪,更没有 Apple Vision Pro 的高 ppi 显示屏、低延迟的穿透与精准的手势识别。但综合来看,Quest 3 的性价比高,适合刚刚步入虚拟世界,想要体验各式各样 VR 游戏的人购买。</p>
+<h2 id="VRChat"><a href="#VRChat" class="headerlink" title="VRChat"></a>VRChat</h2><p>谈到 VR 游戏,有人会想到 Half-Life:Alyx,有人会说起(已经凉掉的) Beat Saber。根据 SteamDB 的数据,此刻在线数量最多的 VR 端游戏还得是 VRChat(主要为 PC 端的战争雷霆不算)。</p>
+<p>在我看来,VRChat 里有大概有四类人。</p>
+<h3 id="第一类人:虚拟世界的旅行家"><a href="#第一类人:虚拟世界的旅行家" class="headerlink" title="第一类人:虚拟世界的旅行家"></a>第一类人:虚拟世界的旅行家</h3><p><img src="/images/2024-03-01/03.jpg" alt="风景大好"></p>
+<p>有些人想“逃避现实”,来到虚拟世界欣赏美景、转换心情。在 VRChat 里有各种各样风格的地图:有些风景大好、音乐舒缓,适合在快节奏的生活之余找到一个宁静之地放松紧绷的精神;有的地图灯红酒绿,如果在夜晚你还是激情满满,不妨来这里听听虚拟 DJ 的表演,大家一起蹦迪、饮酒;甚至还有环境昏暗、一片寂静,十分适合睡眠休息的卧室地图,虽然深处异地,也能在虚拟世界里与好友共眠,在清晨醒来的那刻发现自己的眼前并不是早已习惯了的天花板,耳边是仍在熟睡的友人的呼吸声,将会是一种全新的体验。</p>
+<h3 id="第二类人:虚拟世界的摄影师"><a href="#第二类人:虚拟世界的摄影师" class="headerlink" title="第二类人:虚拟世界的摄影师"></a>第二类人:虚拟世界的摄影师</h3><p><img src="/images/2024-03-01/04.jpg" alt="人也很美"></p>
+<p>也有些人想在虚拟世界里做一个摄影师。美景的照片中怎么能少了美人,如果现实中找不到美人,那就自己来扮吧(心死)。VRChat 中使用的人物模型可以由玩家自行上传,如果你恰巧会使用 Unity 与 Blender,便可以为自己量身定制一个人物模型;如果不会也没有关系,在 Booth 上有许多预制好的人物模型与衣服,只要按照教程将其组合,便可打造出你心目中的理想形象(不管男女)。为了拍出满意的照片,你会主动去学习各种各样的新技能:为了调整照片的后期效果,我就学习了 Lightroom 的使用。</p>
+<h3 id="第三类人:人,不过是在虚拟世界里"><a href="#第三类人:人,不过是在虚拟世界里" class="headerlink" title="第三类人:人,不过是在虚拟世界里"></a>第三类人:人,不过是在虚拟世界里</h3><p><img src="/images/2024-03-01/05.png" alt="和朋友聊天"></p>
+<p>这或许才是 VRChat 的核心内容 —— 当然是和朋友聊天啦。在 VRChat 中有一些专门用于聊天、交友的地图,例如以中文为主的“中文吧”;也有一些比较热门的地图会聚集起各个语言的人群一起聊天,例如“Japan Shrine[spring]”,一张风景优美的日本神社地图;更有各种各样以个人或小团体主办的咖啡厅、运动吧、跳舞房、游戏房等着你来加入。也许你和我一样有些小“社恐”,在现实生活中不大善于和陌生人交际,不妨试试在 VRChat 中,与素未谋面但已经熟络的朋友,或是围坐在篝火旁闲聊,或是在电闪雷鸣、乌云滚滚的夜空中乘坐飞机探险,相信这对你一定会给你带来从所未有的新鲜感。</p>
+<h3 id="第四类人:OOOO"><a href="#第四类人:OOOO" class="headerlink" title="第四类人:OOOO"></a>第四类人:OOOO</h3><p>还有一类是搞色色的,就不多说了。</p>
+<p>以上只是从我的眼中看到的 VRChat。一千个人的心中有一千个哈姆雷特,你的 VRChat 又将会是什么样的呢?</p>
+<hr>
+<p>自从 23 年 10 月 3 日加入 VRChat 以来,我学会了怎么修改模型、如何使用全身追踪让自己在游戏中灵活运动;尝试在跳舞房里学习舞蹈,在 KTV 房里与朋友唱歌,与来自全球的人使用中、英、日闲聊;在 VRChat 中结交了新的朋友,在 X 上发布了拍摄的照片。VRChat 确确实实已经融入了我的生活,仿佛在另一个世界塑造了另一个不同的我。</p>
+
+
+ 2024-03-02T12:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/a534d51a.html
+ 旅行与军粮
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="军粮的特点"><a href="#军粮的特点" class="headerlink" title="军粮的特点"></a>军粮的特点</h2><h3 id="1-便携"><a href="#1-便携" class="headerlink" title="1. 便携"></a>1. 便携</h3><p>一般来说,军粮都会被较为紧凑地包装,以便于大批量运输与随身携带。如确认将有一餐饭需要在外解决,可以在出发时往包里塞上一份军粮。</p>
+<h3 id="2-分量足"><a href="#2-分量足" class="headerlink" title="2. 分量足"></a>2. 分量足</h3><p>军粮的营养构成一般都是按照一个运动量中上的成年男性一餐所需要的卡路里来设计的。因此,即使不能完全饱腹,其所含的营养足够填补在外游玩半天空空的肚子了。</p>
+<h3 id="3-便于烹饪"><a href="#3-便于烹饪" class="headerlink" title="3. 便于烹饪"></a>3. 便于烹饪</h3><p>相比于需要加入开水的方便食品、甚至是冻干食品,军粮一般自带加热设备,仅需冷水,甚至不需要水都可以加热食物。若是去荒郊野岭的地方露营,除非有携带便携式汽炉,想获取到热水是比较难的,这时候有自加热的军粮会很方便。</p>
+<h2 id="俄罗斯单兵口粮普餐"><a href="#俄罗斯单兵口粮普餐" class="headerlink" title="俄罗斯单兵口粮普餐"></a>俄罗斯单兵口粮普餐</h2><p><img src="/images/2024-02-12/01.jpg" alt="外包装"></p>
+<p>这次买到的6号餐谱,包含五包干粮、牛肉丸、牛肉荞麦饭、牛肉炖豆角、牛肝酱、午餐肉、蔬菜丁罐头以及饮料、果酱、零食等散件,还有一套由三个燃料药丸、一个铝制支架和几支防风火柴组成的明火加热套装。</p>
+<p><img src="/images/2024-02-12/02.jpg" alt="塞满的盒子"></p>
+<p>旅行的第一天是登山,午餐便携带了部分的食物作为午餐。</p>
+<p>有些小雨,便找了一处有雨遮的地方开始准备午饭。</p>
+<p><img src="/images/2024-02-12/03.jpg" alt="加热装置"></p>
+<p>将铝板弯曲成炉子的形状,在炉子的中央放上燃料药丸,再用防风火柴点燃,便是一个功能完备的加热装置。</p>
+<p>小贴士,我试着用火柴从上方点燃药丸,尝试了两次都失败了。将火柴放在炉子里,然后将药丸放在火柴上,便能很轻易地将其点燃。</p>
+<p><img src="/images/2024-02-12/04.jpg" alt="正在加热的蔬菜丁罐头"></p>
+<p>点燃固体燃料后,将盛有食物的铝制罐子放在火上直接加热即可。如果有带铝或者钛制的杯子,也可以直接放在火上烧水,泡些饮料喝。</p>
+<p><img src="/images/2024-02-12/05.jpg" alt="干粮、芝士与肝酱"></p>
+<p>在等待罐头加热时,我先掏出了干粮与蘸酱。除了照片里的芝士与肝酱,还有一包苹果果酱。</p>
+<p>芝士的味道与常见的芝士片如出一辙,在我看来稍微淡了些,因此也比较适合直接吃。</p>
+<p>牛肝酱则是特别的腥,单独吃我有些吃不来,和着芝士与果酱吃味道倒是还不错。</p>
+<p>拿出一片干粮(其实就是苏打饼干),涂上果酱,挖一勺芝士,再放上一点点牛肝酱,一口塞进嘴里,味道还算不错。</p>
+<p>饼干有些干硬,嚼起来有些费劲儿,需要配水。</p>
+<p>最后,完吃完了一份饼干、一盒芝士和一包果酱,牛肝酱剩下了一大半。</p>
+<p><img src="/images/2024-02-12/06.jpg" alt="牛肉丸"></p>
+<p>接下来是牛肉丸。看到红色的外表就能猜到是罗宋汤风味的。</p>
+<p>应该是罐藏的缘故吧,牛肉已经泡得很软了,吸收了罗宋汤酸酸甜甜的风味,味道还算不错。就是牛肉味已经不是很浓,能吃得出来是肉,但没有什么特殊的肉的风味。</p>
+<p>顺便一提,用这种炉子加热,铝盒子受热举起不均匀,中间已经滚烫,但四周还是冰的,需要多多翻搅。</p>
+<p><img src="/images/2024-02-12/07.jpg" alt="蔬菜丁罐头"></p>
+<p>从外表看不出来里面有啥,红红的肯定也是罗宋汤的味道。里头的蔬菜主要是胡萝卜,也有青椒、青豆等蔬菜。但这一碗的味道就不好了,青椒的味道和西红柿(或者是甜菜)的酸甜并不搭。</p>
+<p>把饼干蘸着牛肉丸的汤汁吃,味道也不错。涂涂果酱、涂涂芝士,饼干便吃完了一包。</p>
+<p>又吃了点麻麻那边的自热口粮,便已饱腹,我的食量确实不大啊。</p>
+<p><img src="/images/2024-02-12/08.jpg" alt="蔬菜丁罐头上的うさこ"></p>
+<p>这一餐其实只吃了整套餐的 1/3 左右,剩下的量再吃两次正餐不成问题。</p>
+<p>回到家后,又拿出了些零食品尝了一下。</p>
+<p><img src="/images/2024-02-12/09.jpg" alt="巧克力棒"></p>
+<p>来自俄罗斯的巧克力棒,偏甜,一股代可可脂的廉价感,属于不大好吃的巧克力。</p>
+<p><img src="/images/2024-02-12/10.jpg" alt="速溶咖啡、奶与糖"></p>
+<p>右上角是速溶咖啡,右下角是奶粉,左边的一大包是糖。糖的量很大,明显不止是一次饮料的量,也可以放在茶里喝。</p>
+<p>咖啡很苦,一股烧焦味,不好喝。奶粉大概也是植脂末吧,没什么奶味。</p>
+<p>剩下的食品我分成了两餐解决。</p>
+<p><img src="/images/2024-02-12/12.jpg" alt="第一餐"></p>
+<p>第一餐的搭配是:牛肉荞麦饭、肥肉罐头和干粮(饼干)。</p>
+<p><img src="/images/2024-02-12/13.jpg" alt="第一餐的内容"></p>
+<p>也许我加热的还不够,但考虑到在野地里使用便携式炉子加热的能力,士兵们能加热到中间完全热乎,周围有些凉是平均水平了。荞麦饭很硬,风味也不是很好,除了咸味和一点牛肉味,尝不出别的滋味了。加了一些套餐内的黑胡椒粉,才改善了一些风味。</p>
+<p>不过这一大碗饭确实很能填饱肚子,适合放在午餐食用。</p>
+<p>肥肉罐头里自然是盐腌风味的很肥的肉啦。味道我还挺喜欢的,肥肥的肉很好吃,十分下饭。</p>
+<p><img src="/images/2024-02-12/14.jpg" alt="第二餐"></p>
+<p>第二餐的搭配是:牛肉煮豆子、午餐肉、苹果泥、酱牛肉(来自国产 MRE)和干粮(饼干)。</p>
+<p><img src="/images/2024-02-12/15.jpg" alt="第二餐的内容"></p>
+<p>牛肉煮豆子用的会是什么豆呢,豌豆吗?啊,原来是黄豆。</p>
+<p>并没有延续牛肉丸、蔬菜罐头的罗宋汤风味,只是单纯的咸味,不过味道我还挺喜欢的。</p>
+<p>牛肉其实不多,就一大块带筋、软烂的牛肉,剩下都是碎块。除了黄豆外还有一些胡萝卜。</p>
+<p>午餐肉很小一罐,但确实是肉泥,淀粉含量并不多,味道也不错。</p>
+<p>苹果泥是我整个套餐中最喜欢的一样食物了。口感绵密,酸甜度适中,汁水十足。配合干粮一起吃,很大地改善了干粮过干、过硬的缺点。而且好大一罐,吃得很满足。使得这一餐中我干掉了两袋干粮。</p>
+<h2 id="国内的军粮-——-以北戴河的自热口粮为例"><a href="#国内的军粮-——-以北戴河的自热口粮为例" class="headerlink" title="国内的军粮 —— 以北戴河的自热口粮为例"></a>国内的军粮 —— 以北戴河的自热口粮为例</h2><p>09式单兵自热口粮我吃过几份,13式这种两餐包装在一起的也吃过一次。</p>
+<p>这次选购的是北戴河生产的自热口粮。虽然不是军品,但北戴河前身是军工厂,产品会比较接近军品吧。包装风格与09式也很相像。</p>
+<p><img src="/images/2024-02-12/11.jpg" alt="酒店里的自热口粮"></p>
+<p>那天感冒的发烧刚退,人还比较虚弱。住的度假酒店位置比较偏,不想跑太远去吃晚饭,便取了车上的自热口粮回房间吃。</p>
+<p>我吃过太多次了,便没有拍照片,下面就口述一下感受。</p>
+<p>相比于09式单兵自热口粮,北戴河出品的民用口粮内容少了很多 —— 没有了耐贮蛋糕、糖水黄桃/菠萝、调味辣椒酱和固体饮料,只有一份主食,一份配菜(酱牛肉和午餐肉二选一)和一份小菜(榨菜)。</p>
+<p>09式的餐谱相当多样,有 12 个餐谱,有炒面也有炒饭,也有一些主食是素食的,满足部分人群的需求。</p>
+<p>北戴河的仅有三种餐谱,且都是荤食的炒饭。</p>
+<p>主食包外套着加热袋,用注水袋取对应量的水(生水、脏水皆可)倒入加热包即可完成主食的加热。</p>
+<p>味道上,我认为北戴河做得是不如当年的09式的。饭总体都偏咸,但肉给的十分足。</p>
+<p>但相对于俄罗斯的军粮,不知道是口味上有着主场的优势,还是俄罗斯人烹饪技术真的不佳,还是中国的军粮更胜一筹。</p>
+
+
+ 2024-02-12T14:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/7777583b.html
+ 被我整坏的路由器和服务器
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>为了搭建 PalWorld 的私服,我又一次踏上了折腾之旅,并成功将路由器和家里的服务器都搞崩了。</p>
+<p>好在最后两台设备都恢复如初。我就想来做一下这次“事故”的复盘。</p>
+<h2 id="我的设备"><a href="#我的设备" class="headerlink" title="我的设备"></a>我的设备</h2><p>本次操作的有两台设备,一台是 x86 软路由,运行 Openwrt;一台是配置比较奇怪的 x86 服务器,运行 PVE。</p>
+<h2 id="Openwrt-的网络隔离配置"><a href="#Openwrt-的网络隔离配置" class="headerlink" title="Openwrt 的网络隔离配置"></a>Openwrt 的网络隔离配置</h2><p>说在前头,我对 Openwrt 的操作真的是一窍不通。网络相关的知识兴许懂一些,但一到实操环境就纰漏百出了。</p>
+<p>由于 PalWorld 服务端启动后便会在游戏的服务器列表公开自己的 IP,且无法关闭这个功能,有比较大的安全隐患。在配置好 PalWorld 服务端所运行的虚拟机后,我决定将这台虚拟机的网络与局域网内其他设备隔离。这也是所有折腾的起源。</p>
+<h3 id="配置-VLAN-导致的失联"><a href="#配置-VLAN-导致的失联" class="headerlink" title="配置 VLAN 导致的失联"></a>配置 VLAN 导致的失联</h3><p>我能想到的第一个方案便是,给这台虚拟机分配一个与当前局域网不同的网段,并将两个网段隔离。</p>
+<p>VLAN 我还是知道的,便着手开始创建新的 VLAN,并保存…连不上路由器了。</p>
+<p>急了啊,创建了 VLAN 就代表开启网桥的 VLAN 过滤。目前这个 VLAN 没有分配给任何一个接口,自然是啥都连不上了。好在 Openwrt 有自动回滚的功能,90 秒若设备连不上路由器,便会将刚刚的更改回滚…但不是每次都能成功。</p>
+<p>有反复折腾了几次:创建 VLAN、创建接口、创建防火墙的 Zone,分配来分配去,中途也搞砸了几次,配置也都回滚了,直到最后一次尝试,luci 弹出了令人感到安心的“正在回滚配置”的提示,然后就…卡住了。</p>
+<p>考虑到 Openwrt 使用的是 squashfs,怀疑是设备上的设置确实回滚了,但内存中的配置没能正确地回去,我将设备断电重启。果不其然,配置回到了应用前的样子。</p>
+<p>在尝试配置 VLAN 数次后,我放弃了。即使给虚拟机分配了新网段,也设置好了防火墙规则,虚拟机依旧可以访问到内网网段的设备。</p>
+<p>目前我还没有闲心思慢慢研究 Openwrt 的种种配置,便打算换一个方式实现。</p>
+<h2 id="配置-PVE-防火墙"><a href="#配置-PVE-防火墙" class="headerlink" title="配置 PVE 防火墙"></a>配置 PVE 防火墙</h2><p>第二个方案便是,在 PVE 的防火墙中禁止虚拟机连接内部网段的其他设备了。</p>
+<h3 id="启动-Datacenter-防火墙但没有添加允许规则导致的失联"><a href="#启动-Datacenter-防火墙但没有添加允许规则导致的失联" class="headerlink" title="启动 Datacenter 防火墙但没有添加允许规则导致的失联"></a>启动 Datacenter 防火墙但没有添加允许规则导致的失联</h3><p>我启用了虚拟机的防火墙,发现配置并没有生效。简单查询后发现,需要将 Datacenter 的防火墙启用,VM 防火墙才有效果。我便看也没看地就开启了 Datacenter 防火墙,发现管理后台页面无法刷新了。此时我才看到屏幕上 Datacenter 防火墙的默认配置 —— IN: DROP.</p>
+<p>这下好了,外部连接统统被阻断,也就无谈通过控制面板将防火墙再关闭。</p>
+<p>这台服务器是无头的,安装有一张 P100 显卡,但没有显示输出。所以在不动硬件的情况下,我没法通过显示器访问终端的。</p>
+<p>通过检索,我了解到了好几种奇技淫巧来关闭 PVE 的防火墙。</p>
+<h3 id="挂载并修改-crontab"><a href="#挂载并修改-crontab" class="headerlink" title="挂载并修改 crontab"></a>挂载并修改 crontab</h3><p>正好手上有一个硬盘盒,我就将系统盘取下,通过硬盘盒连接到了软路由,尝试修改系统盘里的文件。</p>
+<p>使用 <code>fdisk</code> 查看这块系统盘的分区情况,但没有看到熟悉的 <code>ext4</code> 字样。取而代之的是 <code>Linux LVM</code>。LVM 相当于是 Linux 对磁盘进行了再一次的分区,因此挂载就不能是简单的 <code>mount</code>,得用 <code>lvm2</code> 工具。</p>
+<p>再敲了几个命令后,我成功将 PVE 分区的 <code>root</code> 文件夹挂载,并在 crontab 中添加了关闭 PVE 防火墙相关的命令。</p>
+<p><code>umount</code>,取下系统盘并装回服务器,开机…没有任何反应,依旧打不开管理面板。是教程给的方法有误吗?</p>
+<p>遂又取下盘,挂载到软路由上,却提示该分区忙…我是相当忌惮挂载并修改分区的,生怕损坏了分区,服务器的数据可就全丢了。不敢再继续操作,我得另想出路。</p>
+<h3 id="连接显示器,但还是个瞎子"><a href="#连接显示器,但还是个瞎子" class="headerlink" title="连接显示器,但还是个瞎子"></a>连接显示器,但还是个瞎子</h3><p>现在能做的就是在服务器上通过 PVE 的 rescue terminal,来修改防火墙的配置了。</p>
+<p>我关闭了服务器,取下 P100,换上了 R7 240 这张十年老兵,插上了便携显示器…</p>
+<blockquote>
+<p>error: No suitable video mode found. Booting in blind mode.</p>
+</blockquote>
+<p>你这不是输出字了吗,怎么就进瞎子模式了???</p>
+<p>经过查询,我了解到系统正在寻找到显示模式,正是古早电脑终端使用的 <code>80-Column</code> 这样的显示模式。</p>
+<p>至于什么是 Blind Mode,我尝试在 GRUB 和 Linux 源码中搜索,都没有结果;为什么 PVE 系统找不到我的 R7 240,可能是缺少驱动吧,现在也无处知晓。</p>
+<p>按照网上的教程尝试在 GRUB 引导系统启动时添加显示模式的支持,并没有效果;当我尝试打印支持的显示模式时,发现根本不存在正常的显示模式,故只能放弃直接启动 PVE 的 rescue terminal.</p>
+<h3 id="还是得靠救援盘"><a href="#还是得靠救援盘" class="headerlink" title="还是得靠救援盘"></a>还是得靠救援盘</h3><p>给服务器插上救援盘,我先是打开了基于 Windows 的 PE 系统,发现显卡是有输出的,但分辨率非常低,且只有黑白画面。看来这张老显卡与这块寨板的相性真的不大好。</p>
+<p>在确认 Windows PE 系统下什么都做不了,操作还及其不便,我便退出了 PE 系统,打开了 Ubuntu LiveCD。</p>
+<p>这回,显卡倒是可以正常运行。以 <code>80-Column</code> 模式输出文字还是可以轻松办到的。有了命令行,操作也简单了不少。</p>
+<p>同为 Linux,操作 LVM 就简单了许多。使用 <code>lvm2</code> 挂载 PVE 的系统分区,并用 <code>chroot</code> 将用户空间切换至 PVE 系统,直接用 <code>systemctl disable pve-firewall</code> 把防火墙关了。</p>
+<p>再次进入 PVE,这下开机防火墙就不会启动,赶紧进 Datacenter 防火墙设置里还原误操作的配置。</p>
+<h2 id="事后的反思"><a href="#事后的反思" class="headerlink" title="事后的反思"></a>事后的反思</h2><h3 id="VLAN-配置的失误"><a href="#VLAN-配置的失误" class="headerlink" title="VLAN 配置的失误"></a>VLAN 配置的失误</h3><p>我对 VLAN 配置没有经验,便想着参考网上其他人的配置方法来做。但我查到的都是创建访客 Wi-Fi 这类的配置教程。与我的网络环境的差异在于,访客 Wi-Fi 用的 Interface 与 LAN 不一样,而我需要在 LAN 下配置两个 VLAN,配置方法就有些不同,不能直接搬配置。</p>
+<p>我应当要找的是较为通用的,同一个 Interface 下 VLAN 的配置教程(可能单臂路由配置和这个就有些像),再迁移到 Openwrt 上进行配置。</p>
+<p>由于软路由只有两个网口,也没法像有四五个网口的路由器一样留下一个作为”不死“的管理口,以降低把路由器配置挂掉的风险。</p>
+<h3 id="PVE-原本可以修得更快"><a href="#PVE-原本可以修得更快" class="headerlink" title="PVE 原本可以修得更快"></a>PVE 原本可以修得更快</h3><p>原本在将 PVE 系统盘挂载到软路由时,便可以用 <code>systemd</code> 停掉 <code>pve-firewall</code> 但当时我看到 PVE 论坛里有人说在 crontab 里加上 PVE 关闭防火墙的指令便能访问控制面板了,也有人附和说可以用。但我实际操作后发现并没有效果。</p>
+<p>而再次尝试挂载 LVM 时,提示分区忙让我不敢继续操作。虽然在服务器上用 LiveCD 直接挂载并没有出现分区忙的情况。</p>
+
+
+ 2024-02-12T10:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/aca48136.html
+ 日本之行-第一站-大阪
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>23年12月底,我踏上了前往日本的旅途。这是我第一次去日本,是我第一次出国旅行,且只身一人。</p>
+<p>本次旅行为14天,我将分多个章节完成所有的游记,记录下我旅行中的点点滴滴、各种感想。这是第一章节,出境与大阪之旅。</p>
+<h2 id="第一次出境"><a href="#第一次出境" class="headerlink" title="第一次出境"></a>第一次出境</h2><p>我从上海浦东机场出境。</p>
+<p>航班预计上午 9:40 起飞,根据网上的经验教训,国际航班建议提早三个小时到达机场办理值机手续。我早早地起了床,搭上了第一班磁浮列车,在六点五十分左右到达了机场。</p>
+<p>拖着行李箱直奔值机柜台,发现队伍已经很长。排了一个多小时的队才轮到我。</p>
+<p>我提前在网上进行了值机选座,现场只需要将行李箱托运,打印一下登机牌。工作人员会确认护照和日本 eVisa(电子签证)。</p>
+<p>我提前将电子产品和需要独立安检的物品取出,安检也十分顺利。</p>
+<p>不过在过海关的时候,工作人员看到我护照崭新,又是独自一人,不免有些担心,向我索取了酒店预订记录和行程单。酒店预订记录只要出示订购软件的订单界面即可;我没有做行程单,就将记事本里的旅行计划给工作人员看,第一天在哪里啊,过两天又跑去哪里玩…得知我全程的酒店都已订好,行程也安排清楚,便允许我出境了。</p>
+<p>在候机厅的长椅上坐下时,距离飞机的预定起飞时间只有半个小时了。看来国际航班确实需要早点来机场办手续(虽然最后延误了两个小时)。</p>
+<p>这是我第二次坐飞机。上一次是我还在读小学的时候,不明不白地上了飞机、又下了飞机,除了耳朵有些不舒服。没有其他的感想。</p>
+<p>这次,我特地选择了靠窗的位置,仔细观察着飞机的起降与飞行时的动作。看到了起飞和降落时襟翼的展开,看到了遇到乱流时机翼“夸张”的摆动,感叹着这种看似“简易”的机器是如何将一机人送上天。</p>
+<p><img src="/images/2024-01-21/01.jpeg" alt="日本上空"></p>
+<p>飞机餐的话…上一次坐飞机是国内航线,没有在饭点所以没有供餐,这是我第一次体验飞机餐。</p>
+<p><img src="/images/2024-01-21/02.jpeg" alt="鱼肉米饭套餐"></p>
+<p>没有选择的余地,大家统一都是鱼肉米饭套餐。和我能想象到的飞机餐没有太多区别 —— 一般咸味的鱼肉,煮得软烂的米饭(也可能是再加热的缘故)与味道平平的配菜。坐的毕竟是经济舱,不能期待太高~</p>
+<p><img src="/images/2024-01-21/03.jpeg" alt="鱼肉米饭的特写"></p>
+<p>经过两个半小时的飞行,飞机平稳降落在了关西国际机场。飞机缓慢驶向空桥的途中,我更换了日本的流量卡,给手表切换了时区。</p>
+<p>入境相对出境更加简单。出发前我就在网页中预先填写好了入境信息表与海关申报表,在对应的窗口或机器扫码即可完成申报。在入境窗口只需出示一下 eVisa,录入一下人脸和指纹就可以了。</p>
+<p>工作人员会用英语引导你操作,录入机器上也有中文提示。机场的指示牌都是四语的(日语、英文、中文和韩文),按照指示牌走就没有问题。</p>
+<h2 id="大阪之行"><a href="#大阪之行" class="headerlink" title="大阪之行"></a>大阪之行</h2><h3 id="Day-1-舒适的酒店与海游馆之旅"><a href="#Day-1-舒适的酒店与海游馆之旅" class="headerlink" title="Day 1 - 舒适的酒店与海游馆之旅"></a>Day 1 - 舒适的酒店与海游馆之旅</h3><h4 id="APA-酒店"><a href="#APA-酒店" class="headerlink" title="APA 酒店"></a>APA 酒店</h4><p>起飞延迟了两个小时,降落也迟了一个多小时。达到关西国际机场已是下午 3:30(这之后都是 UTC+9 时间)。我乘坐南海机场线前往难波站附近的 APA 酒店。</p>
+<p>插一嘴,在日本用 Google Maps 相当方便,交通工具的规划和信息展示都做得很棒。“通勤案内”这款官方推荐的交通软件我也有下载,不过用的最多的还是 Google Maps。</p>
+<p>等地铁、电车、新干线时,只要看 Google Maps 上的到站时间,到了哪辆车就上哪辆车。除非两个方向的车同时到站,否则是绝对不会坐错方向的。</p>
+<p><img src="/images/2024-01-21/04.jpeg" alt="APA酒店大楼"></p>
+<p>APA 酒店大楼的装修很有特色,在远处就能看到橙色的屋顶和 APA 的标志,而且在全国的风格都是统一的。</p>
+<p><img src="/images/2024-01-21/05.jpeg" alt="APA酒店房间"></p>
+<p>内部的装横令我十分惊喜:房间不是很大,但各种设施一应俱全,在床铺正对的墙上甚至有一台大屏电视。浴室毫不意外地配备了浴缸,还有定量放水系统,不用盯着浴缸有没有水漫金山了。寝具也相当的舒服,那两晚都睡得很好。</p>
+<p>对于一间一晚 300CNY 左右的旅馆,能有这样的体验我十分满意。</p>
+<h4 id="咖喱与牛排"><a href="#咖喱与牛排" class="headerlink" title="咖喱与牛排"></a>咖喱与牛排</h4><p>办好入住手续,放下行李,已是五点多。飞机餐的显然填不饱我的肚子,我早已饥肠辘辘,是时候出门觅食了。</p>
+<p>大阪海游馆是我计划中必去的一站,但它位于海港村,和其他景点在相反的方向,便安排在今晚游玩了。</p>
+<p>日本人很奇特的一个习惯出现了:有些店铺傍晚 6:00 到 6:30 就收摊了,最晚的会开到七点八点左右,再迟就只剩下营业到深夜的家庭餐厅与居酒屋。</p>
+<p>用“食べログ (tabelog)”这款软件在海游馆附近搜索评价比较高的店铺。</p>
+<p>在海游馆隔壁有一个小小的综合体,里面有一些店铺还有在营业。我选择了这家评价很高的牛排咖喱餐馆。</p>
+<p><img src="/images/2024-01-21/06.jpeg" alt="牛排咖喱"></p>
+<p>一份牛排咖喱饭、一碟酱菜、一碗味增汤。价格有些忘记了,在 1500円 左右。</p>
+<p>日本人很喜欢吃这种细长的青椒,不会辣,在天妇罗中也会出现。</p>
+<p>咖喱的甜口的,味道自然比百梦多咖喱块要好上数倍。牛肉很嫩,没有怎么调味,展现的是肉本身的鲜味。总之,相当的美味。</p>
+<p>对了,需要使用现金哦~ 商家没有准备 POS 机。</p>
+<h4 id="大阪海游馆"><a href="#大阪海游馆" class="headerlink" title="大阪海游馆"></a>大阪海游馆</h4><p>已经记不清上一次去水族馆是多少年前。听说大阪海游馆的设施很棒,场馆也很大,我就来体验一下。</p>
+<p>门票的价格是 2600 円,入场有分时段,我就在售票处购买了当前时段的票。</p>
+<p>海游馆很大,步行参观的总距离在 1km 左右,按照地理位置划分成了好几个区域。</p>
+<p><img src="/images/2024-01-21/07.jpeg" alt="趴在水箱顶的鳐鱼"></p>
+<p>进入场馆便是一个巨大的拱形水箱。是玻璃比较薄吗,还是用了什么技术,几乎看不见玻璃带来的重影,鱼仿佛真的在空中悬浮。</p>
+<p><img src="/images/2024-01-21/08.jpeg" alt="正在睡觉的海狮"></p>
+<p>一群正在睡觉的海狮。抬着头睡觉不会落枕么。</p>
+<p>![巨型水箱]/images/2024-01-21/(9.jpeg)</p>
+<p>在场馆的中央有一个巨大的水箱,有两头鲸鲨、几头锤头鲨和许多小鱼生活在其中。</p>
+<p><img src="/images/2024-01-21/10.jpeg" alt="花园鳗~"></p>
+<p>还有好多可爱的花园鳗,黄色的尤其可爱~</p>
+<p>除此之外,还有来自各个大洲的鱼、南极的企鹅,在地下还有水母馆。</p>
+<p>总共逛了一个多小时,可以说大饱眼福了。</p>
+<p><img src="/images/2024-01-21/11.jpeg" alt="天保山摩天轮"></p>
+<p>在海游馆的隔壁是天保山摩天轮,夜晚被彩色的灯光照亮特别好看。</p>
+<p>此时已是 19:30,经历了一天奔波的我有些劳累,便回到酒店养精蓄然,计划第二天的行程。</p>
+<h3 id="Day-2-天守阁、天满宫、天筋桥与大阪烧"><a href="#Day-2-天守阁、天满宫、天筋桥与大阪烧" class="headerlink" title="Day 2 - 天守阁、天满宫、天筋桥与大阪烧"></a>Day 2 - 天守阁、天满宫、天筋桥与大阪烧</h3><p><img src="/images/2024-01-21/12.jpeg" alt="酒店的自助早餐"></p>
+<p>在日本的第一顿早餐,在酒店隔壁的参观享用了自助餐。</p>
+<p>炒蛋和上次去香港在澳洲牛奶公司吃到的有点像,并没有完全做熟,非常的软嫩~</p>
+<p>茶泡饭就很有日本的特色了。</p>
+<p>饭后,我就前往难波站附近的旅游中心,购买大阪周游卡。我选择的是二日卡,价格是 3600 円。在两天内,可以免费乘坐大阪地铁与巴士(相较于一日卡,不能乘坐私铁),以及免费参观好多景点。</p>
+<p>大阪地铁网络十分发达,基本覆盖了所有想去的地方,这两天我没有搭乘过私铁或者巴士,因此不用担心是否要选择一日卡。</p>
+<p>买到卡之后,第一站便是天守阁。</p>
+<p><img src="/images/2024-01-21/13.jpeg" alt="天守阁外围"></p>
+<p>从外边看,与只狼里的苇名城有几分相像。</p>
+<p><img src="/images/2024-01-21/14.jpeg" alt="天守阁下"></p>
+<p>天守阁内陈列着许多颇有历史的物件,只可惜我对日本的历史并不熟悉,也看不太懂书法家的笔墨,只是走马观花感受一下文化的氛围。</p>
+<p><img src="/images/2024-01-21/15.jpeg" alt="天守阁顶"></p>
+<p>最后在楼顶吹了吹风,便离开了天守阁。</p>
+<p><img src="/1/images/2024-01-21/6,.jpeg" alt="一轮彩虹"></p>
+<p>虽然有些冷,但天气真的很好,万里无云。下一站是天神筋桥,一个商业街。</p>
+<p><img src="/images/2024-01-21/17.jpeg" alt="天神筋桥商业街"></p>
+<p>日本有很多 OOばし(桥)这样的地名呢。也有很多像天神筋桥这样,上方覆盖着遮雨棚的商业街,天神筋桥是其中最长的一条,从一丁目延伸到七丁目,光是主干就有 2.6 km,更有密密麻麻的小巷。</p>
+<p>乘坐堺筋线,在天神筋桥三丁目下了车。我先是去拜访了天满宫。</p>
+<p><img src="/images/2024-01-21/18.jpeg" alt="大阪天满宫"></p>
+<p>似乎内部在翻修,将赛钱箱放到了外边供大家参拜。</p>
+<p>在这里,我给身边参加考研的人做了参拜,希望他们可以拿到好成绩。</p>
+<p>接着,我便从二丁目开始,一路边逛边思考着午饭的去处,寻找着吃饭的店铺。</p>
+<p>沿途,看到了大排长龙的可乐饼摊子,转了一圈再想回来买,发现已经卖完收摊了。有一家天妇罗的店门口也站着好多人,大排长龙。肚子好饿,肯定排不了这么长的队。</p>
+<p>边走边用 食べログ 搜索着。来大阪就得吃些有大阪特色的,那就是大阪烧了。</p>
+<p><img src="/images/2024-01-21/19.jpeg" alt="千草大阪烧"></p>
+<p>并不在主街,而是藏在小巷子里的千草大阪烧,似乎是 食べログ 23年的百大名店呢。</p>
+<p>我选择了以店铺名字所称的招牌菜 —— 千草大阪烧。</p>
+<p>核心是一大片厚切的猪肉,在上下两面都倒上面粉与卷心菜混合的泥,便开始煎烤。</p>
+<p>接待我的服务员会一些简单的英语,告诉我等着他们来翻面就好了。当地人也许会选择以自己的喜好来摊大阪烧,我作为门外汉只要静静欣赏就好。</p>
+<p>当两面都煎至金黄,便会涂上大阪烧酱、沙拉酱和黄芥末酱,撒上不知道是什么的籽,就可以享用。</p>
+<p>大阪烧整体的口感是软糯的,夹心的猪肉排很嫩,肉汁十足。</p>
+<p>唯一可惜的是,量实在有些少,填不饱我的肚子啊~</p>
+<p><img src="/images/2024-01-21/20.jpeg" alt="鲷鱼烧"></p>
+<p>饭后继续在天神筋桥闲逛,发现了一家鲷鱼烧店。我还以为鲷鱼烧是软软的,但实际外壳是偏脆的,甜甜的红豆馅十分美味。</p>
+<p>今天真的走了好久的路,对于第一次来到异国他乡的人可以说是有些得意忘形。从天神筋桥二丁目逛到六丁目,又折返了回来。中途还去 melonbooks 看了一圈,又跑到周游卡可以免门票的天王寺动物园里逛了逛…还没逛完,人就开始有些不舒服了。</p>
+<p>在动物园里稍微休息了一下,我还是决定吃完晚饭就回酒店休息。</p>
+<p><img src="/images/2024-01-21/21.jpeg" alt="大起水产回转寿司"></p>
+<p>一次比较“失败”的体验。不要误会,寿司还是很好吃的,鱼类十分新鲜,但我一不小心就在 iPad 上点了太多的寿司,都吃进肚子之后已经很饱了,完全没有在回转的转盘上取过寿司!这和普通的寿司店不就没差别了吗!</p>
+<p><img src="/images/2024-01-21/22.jpeg" alt="道顿堀"></p>
+<p>回转寿司店出门便是热闹的道顿堀,然而我不是喜好这一口的人。路过蟹道乐,周围停着好几台旅游大巴,店门口密密麻麻的全部都是在等待的游客,大约有数百人。真是疯狂呐。</p>
+<p>受不住喧嚣,我在附近的药店买了一支体温计和一盒退烧药,便回了酒店。</p>
+<p>躺床休息了一阵子再测体温已经正常,看来真的是疲劳导致的体温失调。之后的旅途安排就宽松一些吧。</p>
+<h3 id="Day-3-梅田蓝天大厦、天空美术馆与一兰拉面"><a href="#Day-3-梅田蓝天大厦、天空美术馆与一兰拉面" class="headerlink" title="Day 3 - 梅田蓝天大厦、天空美术馆与一兰拉面"></a>Day 3 - 梅田蓝天大厦、天空美术馆与一兰拉面</h3><p>睡了一个好觉,但人还是有些疲劳。前往异国他乡果然不能过于放肆,得做好身体的管理。今天就悠闲地度过吧!</p>
+<p><img src="/images/2024-01-21/23.jpeg" alt="通往观景台的扶梯"></p>
+<p>在酒店寄存行李后,搭乘地铁来到了梅田蓝天大厦。从三楼有快速电梯可以通往顶楼,然后乘坐扶梯来到屋顶的圆形观景台。</p>
+<p>对了,有周游卡门票免费哦~</p>
+<p><img src="/images/2024-01-21/24.jpeg" alt="梅田蓝天大厦楼顶观景台1"></p>
+<p><img src="/images/2024-01-21/25.jpeg" alt="梅田蓝天大厦楼顶观景台2"></p>
+<p>相比于其他观景塔,梅田蓝天大厦不算很高,但好在有开放式的观景台,视野特别棒。今天风有些大,在楼顶不是很站得住,欣赏了几分钟美景便回到了室内,点了一杯咖啡,坐在窗边静静欣赏着窗外景色。</p>
+<p>梅田蓝天大厦外侧是镜面玻璃,倒映着天空、与天空融为一体,因此称作蓝天大厦。尼尔:机械纪元的开发公司白金工作室的总部就在这栋大楼里。</p>
+<p><img src="/images/2024-01-21/26.jpeg" alt="天空美术馆1"></p>
+<p><img src="/images/2024-01-21/27.jpeg" alt="天空美术馆2"></p>
+<p>我还顺道参观了另一座楼的天空美术馆,虽然不怎么懂艺术,但画作依旧能感染我。</p>
+<p>参观美术馆后,已是正午。该去找吃的了~</p>
+<p><img src="/images/2024-01-21/28.jpeg" alt="一兰拉面的“考试”"></p>
+<p>百闻不如一见,我来品尝一兰了。</p>
+<p>每个人第一次来吃日式拉面都要面临一场“大考” —— 单应该怎么点。(背面有中文,我填完了才发现)</p>
+<p>除了在点餐机器上确认要吃东西,在排位时服务员会给你一张纸片,让你选择拉面的喜好。</p>
+<p>浓郁度我选择了加浓,确实有些过浓过咸了。吃着相当地过瘾,豚骨的香味充满嘴巴,真的很幸福。如果平常吃得比较清淡,正常的浓郁度可能就有些偏咸了,可以考虑减少一些浓郁度。</p>
+<p>油脂的丰富度我也加了一档,但感觉不是特别明显。可能是看到日本有一种表面铺满油渣的拉面,一兰的面在油脂的方面反而显得“寡淡”了吧。</p>
+<p>其余的选项便是看个人喜好。一兰的辣椒粉不会很辣,加 1/2 倍感受不出辣味,但可以提升香味。</p>
+<p><img src="/images/2024-01-21/29.jpeg" alt="一兰拉面"></p>
+<p>交卷后,一碗一兰拉面,四片叉烧和一颗盐味溏心蛋上桌啦。</p>
+<p>一兰号称在面出锅后15秒内就会传递到食客的面前,以体验拉面最新鲜的味道。我赶忙用手机拍了张照,便开始享用。</p>
+<p>拉面和我之前吃过的感觉都不大一样,特别有筋道,麦香味也很浓。在浓厚的汤汁里蘸一下就有了豚骨的鲜味,没几口就把拉面吃完了,便又加了半份面。</p>
+<p>上溏心蛋的时候同时给了个小碟子,如果蛋不好剥或者咸味不合适,可以让服务员帮忙换一个。</p>
+<p>一口拉面,配上一口汤汁。再咬一口叉烧、一口糖心蛋,至福啊。</p>
+<h3 id="前往奈良"><a href="#前往奈良" class="headerlink" title="前往奈良"></a>前往奈良</h3><p>饭后,回酒店取了行李,便是去难波站坐近铁奈良线,前往奈良了。</p>
+<h2 id="一点感想"><a href="#一点感想" class="headerlink" title="一点感想"></a>一点感想</h2><p>语言不是问题,打招呼、点菜这种比较简单的对话就用手势与塑料日语,更复杂的对话用英语就好了。碰到的酒店前台经理、便利店员工、车站管理员都是会英语的。再不济,就用翻译软件吧,能达意就行~</p>
+<p>至于习惯问题,按照当地人的做法来做就好了。在公共场合,例如楼梯应该走哪个方向,等地铁的时候应当怎么站,在地铁上有哪些地方不能使用手机,都有明确的标识和多语言的提示。多留意,照着做,就不会有事儿啦。</p>
+
+
+ 2024-01-21T15:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/2ef8bd61.html
+ 深圳-香港三日行
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="出行原因"><a href="#出行原因" class="headerlink" title="出行原因"></a>出行原因</h2><p>其实我很早就有去香港看看的念头,但有时间出游的机会并不多(机会很多,是我比较懒,更喜欢宅家),一直没去成。</p>
+<p>前一段时间,在朋友的帮助下我注册了英国 Wise 账户,拿到了两张漂亮的实体卡,但后期如何入金成了个问题。了解到可以通过香港账户低损耗入金 Wise,我便有了前往香港开户的念头。</p>
+<p>12 月底我将独自前往日本游玩,但这将是我第一次独自一人出去旅游。第一次单人旅行还是出国旅行,不禁让我有点担心。</p>
+<p>在这两个背景的驱使下,我迅速定下了这次深圳-香港的旅行,既可以前去开户,也可以作为出国旅行的预演,提前暴露一些问题。</p>
+<h2 id="流水账"><a href="#流水账" class="headerlink" title="流水账"></a>流水账</h2><h3 id="Day-1-Thu-深圳"><a href="#Day-1-Thu-深圳" class="headerlink" title="Day 1 - Thu - 深圳"></a>Day 1 - Thu - 深圳</h3><p>第一天中午到的深圳。我在前往酒店的半路上下了地铁,前往名为“臻品鲜粿·粿条世家”的店铺吃午餐。</p>
+<p><img src="/images/2023-12-19/01.jpg" alt="潮汕粿条"></p>
+<p>粿条本身没有很特殊,有点细河粉的感觉。不过八成熟的牛肉非常非常的嫩,味道很棒。</p>
+<p><img src="/images/2023-12-19/02.jpg" alt="炸豆腐"></p>
+<p>炸豆腐是赠送的,第一眼并太高的期望。但一口咬下,十分软绵的里芯呼之欲出,佐以略微有些咸的沾汁,味道很棒。</p>
+<p><img src="/images/2023-12-19/03.jpg" alt="像海蛎煎一样的东西"></p>
+<p>我还点了一份长得有点像海蛎煎的东西,记不得名字了。应该是用油炸的,比海蛎煎更加酥脆。</p>
+<p>美美地享用午饭后,我继续登上地铁,前往酒店。</p>
+<p>下榻酒店后,我前去参观深圳世界之窗景区…总之就是很后悔。</p>
+<p>比起人造景观,我还是更喜欢自然景观一些。世界之窗没有给我带来惊喜,只有满满的失望。今天是周四,许多游乐设施都没有开放,可以参观的仅有一个个微缩复原的建筑物。</p>
+<p>在世界之窗转了约两个小时,我悻悻离去,前往凤凰楼食茶点晚餐。</p>
+<p><img src="/images/2023-12-19/04.jpg" alt="香煎多春鱼"></p>
+<p>多春鱼的鱼籽很多,非常鲜美。</p>
+<p>一个人出来吃饭确实有些不便。有些菜品一次只能点这么一大盘,不过还是美美的享用了。</p>
+<p><img src="/images/2023-12-19/05.jpg" alt="鲜虾烧麦"></p>
+<p>鲜虾烧麦就是吃鲜,特别的鲜甜。</p>
+<p><img src="/images/2023-12-19/06.jpg" alt="牛杂炒肠粉?"></p>
+<p>菜单上的字看得不是很懂,大概写的是炒肠粉吧,没想到上了这么大一盆长得也不像肠粉的东西。</p>
+<p>吃了一下,似乎确实是切成一节一节的肠粉。味道不错,但量好大最后没吃完。</p>
+<p><img src="/images/2023-12-19/07.jpg" alt="华强电子世界"></p>
+<p>饭后,我顶着吃撑的肚子徒步前往华强北,这片传说之地。可惜的是,大部分店铺八点就关门了,简单逛了几栋电子市场后我便回酒店计划第二天的行程。</p>
+<h3 id="Day-2-Fri-HK-开户"><a href="#Day-2-Fri-HK-开户" class="headerlink" title="Day 2 - Fri - HK 开户"></a>Day 2 - Fri - HK 开户</h3><p>一大早我便乘上了前往福田口岸的地铁。</p>
+<p>7:30 左右的口岸没有什么人,大部分是赴港上学的学生,通关很快。</p>
+<p>我在落马洲站选择乘坐 B1 双层巴士前往元朗区,开始了开户之旅。</p>
+<p>沿途…说实话没有什么风光,元朗算是比较偏僻的地方,好风景还要等到第二天的香港岛之行。</p>
+<p>8:00 我到达了中行门口,发现只有前来上班的员工,还没有来排队的人。如果希望第一个办理业务的朋友可以选择这个点就来等候。时间还早,我就去吃了个早餐,回来发现已经有两个人在排队,便加入了队伍。</p>
+<p>9:00 准时进入了银行,取到了号。在我之前有提前预约的人,我排在了第 5 号。</p>
+<p>在工作人员的引导下使用手机填写了信息,然后等待柜员办理剩下的手续。共有三个柜台在同时办理开户,一个人需要 30 - 60 分钟的时间。</p>
+<p>11:00 左右,我完成了中行的开户。</p>
+<p>我现场就拿到了银行卡,便在 ATM 存入了 500 元人民币,并兑换成了港币。</p>
+<p>顺便在同一条路上的南洋商业银行和汇丰银行也完成了开户手续。这两家银行皆需要几个工作日审核信息,审核成功后会将卡片寄至通讯地址。</p>
+<p>穿行于各家银行网点的途中,我发现了一家在他人游记上看到过的店铺——胜利牛丸,便暗自定下了午饭的去处。</p>
+<p><img src="/images/2023-12-19/08.jpg" alt="牛丸、牛筋、牛腩三合一河粉"></p>
+<p>三个愿望,一次满足。</p>
+<p>面汤的颜色相当的“可怕”——仿佛是直接将卤水呈上来一般。但实际入口并没有很咸。牛肉们就不一样了,比较重口味的我也觉得有一点点咸。不过风味很棒,牛肉吃透了各种香辛料。</p>
+<p>河粉藏在了对得满满的牛丸和牛肉之下,不是我常吃的宽河粉,比较窄。</p>
+<p>虽然店名以牛丸冠名,我却没觉得牛丸有多么的美味,反倒是牛筋更胜一筹。</p>
+<p>饭后,我在坐满老者的公园里稍稍歇息,并且计划着下午的行程……</p>
+<p>此次香港之行除了开户,其实还有另一个目标——碧蓝档案与香港吃茶三千的联动~</p>
+<p>在出发前的几天得知了联动的消息,运气真的很好。</p>
+<p><img src="/images/2023-12-19/09.jpg" alt="电车"></p>
+<p>虽然有更优的路线,我还是选择了体验一下地面上的有轨电车。有轨电车有点像巴士,也是由司机驾驶,也会受到其他车辆与行人的干扰,但行驶路线是沿着铁轨固定的。</p>
+<p>随后乘坐地铁,前往金紫荆广场…的对面,星光大道。在这里陈列了许多明星的手印和画像。海港边上的风景大好。</p>
+<p>在此稍停,开户还没有结束。大概是因为元朗距离内地太近,许多虚拟银行都需要前往更靠近香港中心一些的地方才可以开户。我在一家星巴克坐下,开通了 ZA Bank 和 livi Bank 的账户。</p>
+<p>随后,便是前往海港城的吃茶三千点了一杯联动奶茶,从中心一步步移动回福田口岸——晚上要和群友面基。</p>
+<p>我们俩聊得非常尽兴,聊到了十一点才分手,回到了酒店,速速洗漱完毕,预定了第二天早上从福田站前往香港西九龙的火车,便沉沉睡去。</p>
+<h3 id="Day-3-Sta-HK-游玩"><a href="#Day-3-Sta-HK-游玩" class="headerlink" title="Day 3 - Sta - HK 游玩"></a>Day 3 - Sta - HK 游玩</h3><p>昨晚火车票订得比较迟,最迟的火车也是 7:45 的,更迟的都被人订光了。我又被迫起了个大早(这三天都是 6:30 之前起床的)坐地铁前往福田火车站,搭上前往香港西九龙站的火车。可能是比较早乘火车的人并不对,这一趟通关流程比较顺利,再迟一点可就不好说了……</p>
+<p>到达九龙,我空着肚子在街上漫步,寻找可以填饱肚子的早餐店。打开地图,“澳洲牛奶公司”几个字印入眼帘,这不是前几天看到的网红店吗?我记得这家店只收现金,便前往沿途的便利店,在 ATM 用中银香港的卡取了 100 港币现金。没有注意到我用的是汇丰的 ATM,与银联网络并不互通,被收取了 15 港币的手续费 QAQ。(众安银行的卡在全港 ATM 可以免手续费取现,经常到港玩的话可以办一张实体卡)</p>
+<p><img src="/images/2023-12-19/10.jpg" alt="九龙街头漫步"></p>
+<p>带着现金,我来到澳洲牛奶公司的门口,果不其然,排起了长队。好在我是一个人,这家店是强制拼桌的,顺利超过了大团的游客,来到了店内。</p>
+<p>给我的第一印象嘛…很拥挤,人挤着人,甚至是背贴着背。小小的一张圆桌围坐着四个人,各自享用着早餐。穿着白大褂的服务员在人群缝隙间穿梭着,高效地记录着到店客人的订单,飞速地将菜品传递到桌前。</p>
+<p>落座,菜单压在桌子的玻璃之下,写满了粤语,说实话我看不大懂。纠结了几十秒,发现菜单上还写着“早餐”、“下午茶”等套餐,我便对服务员说:“早餐套餐来一份吧。”</p>
+<p>话音未落,餐具和一杯茶便着陆在我的桌上。剩下的菜品也并我让我久等。</p>
+<p><img src="/images/2023-12-19/11.jpg" alt="火腿通心粉"></p>
+<p>首先呈上的是一碗火腿通心粉。味道比较清淡,喝不出黄色的汤底是什么熬制的,不过比较鲜。</p>
+<p><img src="/images/2023-12-19/12.jpg" alt="牛油吐司与炒双蛋"></p>
+<p>接着呈上的是一个盘子,两片吐司和炒鸡蛋各占半壁江山。牛油烘烤的吐司暖暖的,软软的;这份炒双蛋则是套餐的点睛之笔——调味微咸的鸡蛋并没有炒得很熟,在生与全熟之间掌握了平衡,做到了入口软绵,回味无穷,堪称是绝品。</p>
+<p>如果有路过,一定要来尝一下这份炒鸡蛋~</p>
+<p>一顿饱餐之后,我继续踏上旅途,前往太平山顶的观景台。</p>
+<p><img src="/images/2023-12-19/13.jpg" alt="山顶缆车"></p>
+<p>上山的路可以选择用脚走完,不过,还是要体验一下富有特色的山顶缆车~</p>
+<p>缆车的斜度估摸着有三四十度,是我见过最陡的。缆车是建在山坡边上的,方便沿路欣赏香港的风景。</p>
+<p><img src="/images/2023-12-19/14.jpg" alt="摩天台观景台"></p>
+<p>缆车的终点站是太平山的山顶,下车后直接来到了凌霄阁摩天台的二层。沿着盘旋向上的电动扶梯,海拔逐渐升高,走着走着,最终到达了海拔428米的摩天台观景台。</p>
+<p>在这里可以俯览香港的景色,听说夜景更美,不过我晚上并不住在香港,也就没有机会亲眼目睹,实在可惜。</p>
+<p>摩天台上的风很大,吹着十分舒服。待了大约四十分钟,我便沿着原路返回,坐着缆车回到了山下。</p>
+<p><img src="/images/2023-12-19/15.jpg" alt="漫步中环"></p>
+<p>早餐吃得有够饱,到了饭点我还不是很饿,便在街道与小巷间漫步,寻找着午饭的好地点。我想找家正宗点的茶餐厅。</p>
+<p>跟随地图,我来到了就近的广芳园…可等候的队伍早已排出店外,目测有三四十人在队伍中。因为下午就要坐动车回去了,我无心加入他们,便继续游荡着,寻找着。</p>
+<p><img src="/images/2023-12-19/16.jpg" alt="菠萝包与阿华田"></p>
+<p>附近的茶餐厅真的不多,跟随地图的指引,我来到了一家街边的小店。店内只收现金,而我身上只剩下不到五十港币,便选择了菠萝油和冰阿华田作为午餐,反正肚子不是特别饿。(冰的阿华田比热的还要贵上几块钱哦)</p>
+<p><img src="/images/2023-12-19/17.jpg" alt="City Walk"></p>
+<p>饭后,我一时兴起决定走路去西九龙火车站,而非乘坐地铁,便开始一趟 City Walk…差点把我 Walk 没了。</p>
+<p>街上好多地方都在施工,地图的数据并没有新到可以帮我绕开因为施工而禁止通行的路段…再加上出海关也需要一些时间,我差点以为要赶不上车。</p>
+<p>好在我还是在开始检票前就赶到了,算是有惊无险,顺利踏上了返乡的路,结束了此次旅程。</p>
+<h2 id="我都带了些什么"><a href="#我都带了些什么" class="headerlink" title="我都带了些什么"></a>我都带了些什么</h2><p>此次我轻装上阵,只带了一个双肩包。</p>
+<p>这次旅行我尝试了内衣、袜子和洗澡用具(浴巾与毛巾)都使用一次性的,省去了携带大量衣物且需要带回大量脏衣物的麻烦,也可以保证洗澡用具足够干净(对酒店不是很信任)。浴巾和毛巾都是压缩的,体积非常小。</p>
+<p>其余的,便是两晚更换的衣服和电子产品的充电器了。</p>
+<h2 id="开户的那些事"><a href="#开户的那些事" class="headerlink" title="开户的那些事"></a>开户的那些事</h2><p>我挺担心中银香港会开不下来,因为我还是学生也没有带地址证明。在大堂接待客户的小姐姐也询问了我有没有带上地址证明,没有的话只能碰碰运气,弄得我更慌了。</p>
+<p>不过真正坐进柜台和柜员大哥开始走流程,直到大堂经理过来刷卡完成审核、将卡递到我手中,都没有向我索取地址证明。不知道是因为得知我是学生,还是因为我身份证上写的是具体住址,反正过了这关,顺利拿到了卡。</p>
+<p>而汇丰之行则运气不佳,提交的资料被送至总行审核,当天拿不到账户也无法完成所有流程,只能等卡寄到居住地址,并且等下一次到港签字完成所有开户流程了。</p>
+<p>P.S. 在跨境转账到时候一定要填清楚收款人的名字,要与中银香港的账户名称相同。我填反了名字被收了一笔手续费 QAQ</p>
+<h2 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h2><p>这次旅行顺利得可怕(除了差点没赶上返程的火车),在深圳与香港复杂的地铁网中也没有坐错一趟列车,似乎没能暴露出什么问题。希望 12 月底的日本之旅也能一样顺利。</p>
+<p>一个人出来玩真的很自由,在出发前先查好可以去哪里玩、去哪里吃,到达之后就随心安排,不用迁就其他人,走累了随时可以停下休息,在咖啡店坐上一两个钟头也不会有人抱怨。</p>
+<p>我也许爱上了一个人旅行的感觉,之后也会继续尝试这种旅行的方法。</p>
+
+
+ 2023-12-19T14:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/42ec6146.html
+ My Second Attempt To ARM Servers
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>Last weekend, a full month without my Telegram account, I thought I had to do something. I believe I got banned because someone clicked the “spam” button on my messages, but I have nowhere to appeal.</p>
+<p>I lost the faith in centralization. This time, I made up my mind to dive into decentralization.</p>
+<p>First, I need a server.</p>
+<h2 id="Where-To-Purchase"><a href="#Where-To-Purchase" class="headerlink" title="Where To Purchase"></a>Where To Purchase</h2><p>I used to buy servers from distributors of the DC for lower prices, but it always ended up with disappointment. The servers are just not stable and I can not upgrade the hardware configuration on demand.</p>
+<p>So this time, I chose Hetzner for my server. Hetzner is a huge IDC in Germany, I must have to pay a fairly high price for the server, am I right?</p>
+<p>The following images show the price of Hetzner’s traditional CX series - with x86 CPU.</p>
+<p><img src="/images/2023-09-24/01.jpg" alt="Hetzner CX Series"></p>
+<p>A 2 CPU and 4GB RAM CX21 can fit my needs, it costs 5.35 EUR per month. By the way, the 40GB disk is far less than my requirement.</p>
+<p>The CX series are using Intel Xeon CPU. The GB6 benchmark score of an Intel core is around 850, which is significantly lower than AMD EPYC of around 1200.</p>
+<p>However, when I switch to the CAX series…</p>
+<p><img src="/images/2023-09-24/02.jpg" alt="Hetzner CAX Series"></p>
+<p>I can get the same number of CPU cores and RAM with only 3.79 EUR per month, 30% lower than the x86 one.</p>
+<p>The GB6 benchmark score of ARM CPU is around 1050, even higher than the Intel ones.</p>
+<p>The disk space is still 40GB, but I can purchase a 3.2EUR/month 1TB Storage Box to solve this problem.</p>
+<p>With a total of 6.99 EUR per month, I can get a server with 2 CPU cores, 4GB RAM, and 1TB disk. That is a great deal.</p>
+<h2 id="Wait-ARM"><a href="#Wait-ARM" class="headerlink" title="Wait, ARM?"></a>Wait, ARM?</h2><p>About a year ago, when Hetzner first released the ARM series, I was interested in it and evaluated its compatibility.</p>
+<p>I always use Docker containers to host my ~25 services. It turned out that more than half of the Docker images I was using were only compiled for the x86 CPU.</p>
+<p>That means you have to build the image by yourself if you want to run it on an ARM CPU. The time and effort were not worth it, so I gave it up.</p>
+<p>However, this time, I found all of the Docker images already supporting the ARM CPU. It is time to give it a try.</p>
+<h2 id="The-Experience"><a href="#The-Experience" class="headerlink" title="The Experience"></a>The Experience</h2><p>Here is my final hardware configuration:</p>
+<p>I chose the CAX11 server, which has 2 Ampere ARM CPU cores, 4GB RAM, and 40GB disk. I added 2GB swap space to store cached files and reduce the load of the RAM.</p>
+<p>I also purchased a 1TB Storage Box to store my data. Mounted to the server with NFS, just like a local disk.</p>
+<p>I am using the latest Debian 12 Bookworm and I can’t feel any difference between x86 and ARM. My daily use software from APT source is all compiled for ARM. The installation speed is as fast as x86.</p>
+<p>As to the Docker images, I am using Portainer to manage them. What I need to do is just click the “Recover” button on the settings page, Portianer will automatically recover the configuration from CloudFlare R2 storage and all the containers just work as before, with no need to change any settings.</p>
+<p>Today, when I am writing this article, My services are running for a week without any problem.</p>
+<p><img src="/images/2023-09-24/03.jpg" alt="Portainer"></p>
+<h2 id="Well-Still-Not-Perfect"><a href="#Well-Still-Not-Perfect" class="headerlink" title="Well, Still Not Perfect"></a>Well, Still Not Perfect</h2><p>The ARM server just works fine, but I want to share some problems I encountered when building the ARM Docker images.</p>
+<p>The official software repositories of Linux distributions can offer full support for ARM CPU, but the repositories of other sources such as PyPi can not.</p>
+<p><img src="/images/2023-09-24/04.jpg" alt="Build time difference"></p>
+<p>There are still some packages that don’t have prebuilt ARM wheels, and the building process can take a long time on the 2-core ARM machine.</p>
+<p>That is not a huge problem, but it makes the experience of ARM arch different from x86.</p>
+<p>If you are a developer, try to include ARM in your build pipeline next time, that will be a great help for the ARM community.</p>
+<h2 id="Final-Conclusion"><a href="#Final-Conclusion" class="headerlink" title="Final Conclusion"></a>Final Conclusion</h2><p>In my opinion, the ARM server is ready for daily use today. They offer a high quality-price ratio. I didn’t come across any compatibility problems.</p>
+<p>Next time when you want to purchase a server, you can consider the ARM ones.</p>
+
+
+ 2023-09-24T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/9b78ad2a.html
+ 寻梦穿越机 - 入门浅谈
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>暑假开始之前,我就朦朦胧胧有购入一台穿越机的想法。起因为何?我也不是很清楚。是看到了<a href="https://www.youtube.com/watch?v=b0JZxm7ajcs">酷炫的穿越机航拍视频</a>?还是童年时期的航模魂蠢蠢欲动,将要苏醒?</p>
+<p>兴趣,倘若不立刻抓住,很快就会被抛至脑后。为了不让自己后悔,那就立刻开始筹备吧!攥紧并不是特别充盈的钱包,我踏上了寻梦之路。</p>
+<h2 id="什么是穿越机"><a href="#什么是穿越机" class="headerlink" title="什么是穿越机"></a>什么是穿越机</h2><p>FPV (First-Person View),是指通过第一人称视角远程控制无人飞行设备 (UAV) 的控制方式,也是一项运动,也指代了一类设备。</p>
+<p>FPV 设备包含固定翼 (Fixed Wing) 与多轴 (Multi-rotor) 两种。穿越机多指多轴 FPV (常见为四轴)。</p>
+<p><img src="/images/2022-09-05/%E5%9B%BA%E5%AE%9A%E7%BF%BC%E4%B8%8E%E5%A4%9A%E8%BD%B4.jpg" alt="四轴 FPV (上) 与固定翼 FPV (下)"></p>
+<h2 id="一盆冷水"><a href="#一盆冷水" class="headerlink" title="一盆冷水"></a>一盆冷水</h2><p>在详细介绍穿越机构成之前,请允许我泼一盆冷水。</p>
+<p>穿越机与一些航拍无人机,例如大疆 (DJI) 的四轴航拍机与航拍 FPV 相比,颇有原始人见到现代人的感觉。</p>
+<p>大疆无人机上搭载了各种各样的传感器,各系列大都配备了二向 (前后)、四向 (前后左右) 或五向 (增加一个上方) 的视觉避障功能、红外定高悬停功能,以及低电量 GPS 自动返航功能。</p>
+<p>上述这些看似是无人机应当“标配”的功能,追求轻量化的穿越机时常一个也没有。一台最小安装的穿越机全机的传感器仅有集成于飞控的陀螺仪和地磁传感器 (电调的电流传感器一般不算在内),飞控仅知道当前机身的朝向、倾斜角度与加速度,无法感知周围的环境,没有实现避障、悬停与自动返航的能力。飞机的运动状态完全取决于飞手的操控,电池的剩余电量甚至需要飞手根据电芯电压来推断。</p>
+<p><img src="/images/2022-09-05/%E7%A9%BF%E8%B6%8A%E6%9C%BA%E4%BC%A0%E6%84%9F%E5%99%A8.jpg" alt="穿越机少得可怜的传感器"></p>
+<p>再者,为了能获得更大的速度与更高的机动性,穿越机飞行过程一般处于手动模式,对飞机四向的倾斜没有限制,飞行过程中出现危险操作的概率大,事故概率也大。而大疆无人机对飞行速度、角度有严格的限制,能有效地降低炸机的概率。</p>
+<p>因此,飞行穿越机的难度将是飞行大疆等航拍无人机的十倍、甚至百倍。请务必不要抱着随便玩一玩、随心飞的态度入坑穿越机,发生事故后轻则炸机提 (遥) 控回家,重则伤到自己或他人。对于喜欢日常随心飞行的玩家,大疆或类似牌子的无人机 (包括航拍机和穿越机) 更为合适。</p>
+<hr>
+<p>下文将详细介绍入门穿越机所需要的技术知识与穿越机的重要模块。在明白风险之后仍打算入坑穿越机,或对穿越机有兴趣的你,欢迎继续往下阅读。</p>
+<h2 id="技术储备"><a href="#技术储备" class="headerlink" title="技术储备"></a>技术储备</h2><p>上文提到了我童年时期的航模魂。我在小学就跟着老师学习无线电和航模知识,练习了焊接技巧和单旋翼的航模直升机的飞行技巧。曾参加某个航模比赛并拿到了铜牌的好成绩 (我好自豪)。</p>
+<p>因此,我对我的焊接技术和遥控操控能力比较有信心。而这两个能力恰好是入门穿越机不可或缺的。下面我列举一些入门穿越机需要掌握的技能。</p>
+<h3 id="锡焊"><a href="#锡焊" class="headerlink" title="锡焊"></a>锡焊</h3><p> 除了购买真·到手飞套餐 (机子已组装完成,接收机已与遥控器完成配对),其余情况大概率需要焊接一些导线。</p>
+<p> 大部分组装机套餐都不包含接收机,因为接收机与遥控器一一对应,一般是和遥控一起购买的。就需要用户自行焊接至飞控上。</p>
+<p> 锡焊所用到的工具有电烙铁和焊锡丝,最好能佐以松香,增加焊接成功率。电烙铁推荐购买 T12 或更好的,因为飞控散热设计,很多焊盘为通孔的形式,热量会非常快地传到到全板,导致焊锡较难达到融化的温度。若使用功率太低的电烙铁,可能无法融化焊锡。</p>
+<p><img src="/images/2022-09-05/T12%E7%84%8A%E5%8F%B0.jpg" alt="T12 焊台"></p>
+<p> 由于飞控比较小巧,焊盘也十分迷你,对焊接技术要求较高。</p>
+<h3 id="使用搜索引擎"><a href="#使用搜索引擎" class="headerlink" title="使用搜索引擎"></a>使用搜索引擎</h3><p> 不懂不可怕,不懂得学习才可怕。</p>
+<p> 穿越机有关的零碎知识犹如满天星,散布于互联网的每一个角落,需要一定的搜索技巧才能挖掘到有用的知识。</p>
+<p> 国内有关穿越机的网站数量比较少,建议使用英文搜索。</p>
+<h3 id="编译"><a href="#编译" class="headerlink" title="编译"></a>编译</h3><p> 若想要更新 ELRS 接收机和高频头的固件,需要从源码编译 ELRS 固件。虽然 ELRS 团队提供了一套图形化的编译工具,但难免会遇到一些疑难杂症 (博客中就有 ELRS 固件编译的踩坑记录)。需要对编译有一定的知识,能自行解决简单的问题。或者选择使用出厂固件,不自行升级。</p>
+<h3 id="操控能力-协调性"><a href="#操控能力-协调性" class="headerlink" title="操控能力 (协调性)"></a>操控能力 (协调性)</h3><p> 像音游 (音乐游戏) 一样,微操遥控器并不是所有人都能很好地做到。穿越机对遥控指令的反应极为灵敏,需要你能精细地操控遥控器。</p>
+<p> 但这项技能可以通过电脑模拟飞行来习得。建议在正式开始飞行前,先在电脑上使用模拟器熟悉飞机的控制。</p>
+<h3 id="一颗勇敢的心"><a href="#一颗勇敢的心" class="headerlink" title="一颗勇敢的心"></a>一颗勇敢的心</h3><p> 在飞行穿越机的过程中,不可能不出现炸机 (飞机以异常姿态落地) 的情况,难免会留下一点阴影。</p>
+<p> 我在小学时飞过固定翼飞机,曾不慎撞到电线,导致飞机起落架损坏脱落,最后只能由老师操控迫降。此事给我留下了不小的心理阴影,有好几个月我都不敢再飞行固定翼。不过最后还是克服了恐惧,再次拾起了遥控。</p>
+<p> 炸机并不可怕,每一次炸机都记录着你的成长。</p>
+<h2 id="深入了解"><a href="#深入了解" class="headerlink" title="深入了解"></a>深入了解</h2><p>类似 Linux (比较奇怪的类比),穿越机也分为最小安装和额外拓展两部分。</p>
+<p><img src="/images/2022-09-05/%E7%A9%BF%E8%B6%8A%E6%9C%BA%E6%9E%B6%E6%9E%84.jpg" alt="穿越机架构"></p>
+<p>最小安装:</p>
+<ul>
+<li>机架 - 硬连接其他组件,保护脆弱的电路板;</li>
+<li>电机 (和桨叶) - 提供飞行动力;</li>
+<li>飞控 - 控制飞行状态,是穿越机的大脑;</li>
+<li>电调 (电子调速器) - 驱动电机、分隔电路 (防止电机减速时产生的反向电流烧坏其他电子设备);</li>
+<li>接收机 - 接受遥控器控制信号 (有 2.4GHz、915MHz、 868MHz 等频段,互不兼容);</li>
+<li>图传 - 发送图像信号 (多为 5.8GHz 频段);</li>
+<li>摄像头 - 提供第一人称视角;</li>
+</ul>
+<p>额外拓展:</p>
+<ul>
+<li>GPS 模块;</li>
+<li>BB 响 (蜂鸣器 Beeper,用于寻找飞机);</li>
+<li>LED 灯珠、灯带 (好看 XD);</li>
+<li>红外避障模块;</li>
+<li>运动摄像机 (拍摄更清晰、帧率更高的视频);</li>
+</ul>
+<p>挑几个比较重要的模块介绍一下:</p>
+<h3 id="机架"><a href="#机架" class="headerlink" title="机架"></a>机架</h3><p>穿越机机架一般由碳纤维板制成,质地轻盈且强度极高,可以物理连接不同的模块,并保护脆弱的电子设备 (飞控、图传等裸板)。</p>
+<p>挑选时,机架有几个要主要关注的核心参数:</p>
+<ul>
+<li><p>外形:</p>
+<p>机架分为普通式与涵道式 (Duct Propeller) 两种。</p>
+<p>普通式机架的螺旋桨桨叶裸露在外,而涵道式机架的螺旋桨外侧有涵道 (类圆筒) 包裹。</p>
+<p>有一些普通式机架可以通过额外安装涵道,变身成为涵道机架。</p>
+</li>
+</ul>
+<p><img src="/images/2022-09-05/%E6%9C%BA%E6%9E%B6%E7%A7%8D%E7%B1%BB.jpg" alt="普通机架 (左) 与涵道机架 (右)"></p>
+<p> 普通式机架采用开放式的设计,尽可能少地使用材料以减轻重量。由于螺旋桨暴露在外,危险性较大。普通机架可被用于任何竞速与花飞机型。</p>
+<p> 涵道式机架通过涵道结构可以提供更好的升力、稳定性和安全性,但额外增添了重量和封阻,一般被用于小型机与花飞机型。</p>
+<p><img src="/images/2022-09-05/%E6%9C%BA%E6%9E%B6%E5%A4%96%E5%BD%A2.jpg" alt="机架外形"></p>
+<p> 此外,机架还可以再细分为 X 型、H 型等,在此就不多展开,有兴趣的读者可以自行查阅。</p>
+<ul>
+<li><p>大小:</p>
+<p>穿越机大小各异,从最小的一寸机,到体型较大的五寸、六寸机。</p>
+<p>二寸以下的机子防风能力较差,但体型轻盈且常使用涵道机架,适合在室内飞行,且不易伤人。</p>
+<p>三寸以上的机子动力充沛,飞行速度快,但体型大、螺旋桨裸露,无法在室内飞行。适合在场地广阔的室外进行竞速或花飞 (花样飞行)。</p>
+</li>
+</ul>
+<h3 id="飞控与电调"><a href="#飞控与电调" class="headerlink" title="飞控与电调"></a>飞控与电调</h3><p>飞控与电调 (有时还有图传) 几块板子大小相当,时常纵向堆叠安装,称为飞塔。在多块 PCB 间有硅胶柱减震。</p>
+<p><img src="/images/2022-09-05/%E9%A3%9E%E5%A1%94.jpg" alt="双层飞塔"></p>
+<p>飞控需要注意的参数不多:</p>
+<ul>
+<li><p>飞控 SoC:</p>
+<p>常见飞控 SoC 的型号有 F405 和 F722,他们的全称应为 STM32F405 和 STM32F722. 差别主要在主频和内存大小上。</p>
+</li>
+</ul>
+<p><img src="/images/2022-09-05/%E9%A3%9E%E6%8E%A7SoC.jpg" alt="飞控 SoC"></p>
+<p> 由于穿越机飞行对计算性能不是很敏感 (类似 3D 打印机),一般选择搭载 F405 SoC 的飞控性能就足够使用了。</p>
+<ul>
+<li><p>串口数量:</p>
+<p>新手会用到的串口数量不会超过两个 (外接部分型号的图传和接收机),绝大部分飞控都能满足这个要求。若有需要安装 GPS 和其他传感器,则要注意查看飞控支持的串口数量了。</p>
+</li>
+</ul>
+<p>电调最重要的参数就是最大放电电流了。</p>
+<p>电调的电流选择需要根据机身大小、电机和桨叶大小、形状进行估计,通常电机厂家会给出推荐值,按照推荐选购即可。</p>
+<h3 id="接收机与图传"><a href="#接收机与图传" class="headerlink" title="接收机与图传"></a>接收机与图传</h3><p><img src="/images/2022-09-05/%E6%8E%A5%E6%94%B6%E6%9C%BA%E4%B8%8E%E5%9B%BE%E4%BC%A0.jpg" alt="接收机 (左) 与图传 (右)"></p>
+<p>接收机与图传共享一个重要的特性,协议:</p>
+<p>常见的接收机有 ELRS (ExpressLRS) 和黑羊 TBS 两类。应该注意的是,<strong>不同种类的接收机使用的通信协议和频段不同,能与其配对的遥控器和高频头也不同</strong>,因此在挑选接收机的时候一定要看清楚协议和频段。</p>
+<p>接收机和飞控的串口通讯协议也各异,有 UART、SBUS 等数种,再购买接收机前需确认飞控有相对应的串口。</p>
+<p>图传分数字图传和模拟图传两种。</p>
+<p>数字图传以大疆的最为出名,需要使用配套的飞行眼镜;模拟图传大多使用 5.8G 频段通讯,和大部分接收模拟信号的飞行眼镜通用。</p>
+<p>图传连接至飞控的串口通讯协议也很多,购买的时候请多加留意。</p>
+<p>除此之外,接收机有遥测功率、内/外置天线、天线接口等参数,在挑选时都需要多加留意。</p>
+<p>图传的另一主要参数则是发射功率。发射功率越大,能稳定接受图传信号的距离一般就越大。小型穿越机一般选择发射功率在 200mW 到 500mW 的图传即可 (部分图传发射功率可调);若有远航要求,也可选择发射功率大于 1W 的图传 (价格较高、发热也较大,一般带有主动散热)。</p>
+<h3 id="机子之外的配件"><a href="#机子之外的配件" class="headerlink" title="机子之外的配件"></a>机子之外的配件</h3><p>除了穿越机本体之外,想要拥有完整的飞性体验,还需要遥控器、高频头、电池、平衡充电器、飞行眼镜等配件。</p>
+<p>上述的每一种配件都可以写作一篇介绍文章。由于篇幅有限,本文就不再介绍上述的外围配件,请善用搜索引擎,学习相关知识。</p>
+<h3 id="配套软件"><a href="#配套软件" class="headerlink" title="配套软件"></a>配套软件</h3><p>除了硬件,穿越机配套的控制软件也尤为重要。目前主流的控制软件是开源的 Betaflight。</p>
+<p><img src="/images/2022-09-05/Betaflight.jpg" alt="Betaflight 地面站界面"></p>
+<p>Betaflight 分为嵌入端 (安装在飞控中嵌入式系统) 和地面站 (安装在电脑里的软件)。将飞控通过线缆连接至电脑,并打开 Betaflight 地面站软件,即可对飞控参数进行调整。</p>
+<p>Betaflight 调参也是一门大课,新手不建议自定义太多的参数。待熟悉飞机之后,才建议调整 PID 等高级控制参数。</p>
+<h2 id="新手的第一台飞机"><a href="#新手的第一台飞机" class="headerlink" title="新手的第一台飞机"></a>新手的第一台飞机</h2><p>说了这么多,要上某宝挑选、下单穿越机的种种配件了吗?</p>
+<p>我的建议是否定的。</p>
+<p>我咨询了一些老玩家,他们建议新手购买他人已完成调参的二手机器,或者购买商家大部分已组装完成套机,以绕过纷繁复杂且状况百出的 DIY 过程,降低还未入坑就弃坑的风险。</p>
+<p>此外,自行购买散件的总价常常会高于购买整机的价格。对于钱包不是特别充盈的我,购买整机也是一个省钱的选择。</p>
+<p>待熟悉了穿越机的飞行与调试之后,再学习他人经验,设计并组装一台自己心目中的机器也不迟,这才是 DIY 的浪漫。</p>
+<h2 id="我的成果"><a href="#我的成果" class="headerlink" title="我的成果"></a>我的成果</h2><p><img src="/images/2022-09-05/%E6%88%91%E7%9A%84%E6%88%90%E6%9E%9C.jpg" alt="我的成果"></p>
+<p>我选择购买一位老玩家完成大部分组装工作的“半”整机——包含了机架、电机、飞控和电调。图传、摄像头和接收机则是我自行购买和焊接安装的。</p>
+<p>飞机到手、外围装备齐全,只待一飞冲天。</p>
+
+
+ 2022-09-04T04:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/9d319c54.html
+ DELL 灵越 15 5547 拆解与更换硅脂
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>DELL 灵越 15 5547,Intel i5-6200U,Nvidia Geforce 630M,8G DDR3-1600 + 256G SATA SSD(后期改装),是我的第一台笔记本。这台笔记本的拆机过程比较繁琐,是我目前遇见的最难拆解的电脑,故作此文章,分享一下如何拆解一台笔记本,并为其更换硅脂。</p>
+<p><img src="/images/2022-05-22/01.JPG" alt="A面"></p>
+<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>本次拆机共耗时 1H 30Min,是拆解正常机器所花费时间的两到三倍。</p>
+<p>拆解步骤包括:卸下后盖、拆除电池、拆除硬盘、拆除风扇、拆除键盘、卸下基座、卸下散热器、更换硅脂,然后是逆序执行上述拆解步骤,组装电脑。</p>
+<h2 id="拆解准备"><a href="#拆解准备" class="headerlink" title="拆解准备"></a>拆解准备</h2><p>拆机少不了一套好工具。</p>
+<p>为了无伤拆机,需要准备一套好用的螺丝刀防止螺丝滑牙;还需要一套塑料拆机工具,用于撬开后盖。</p>
+<p>笔记本的硅脂,我选择了霍尼韦尔 7950 相变硅脂,不容易挥发,不需要经常更换。</p>
+<h2 id="开始拆机"><a href="#开始拆机" class="headerlink" title="开始拆机"></a>开始拆机</h2><h3 id="怎么拆呢"><a href="#怎么拆呢" class="headerlink" title="怎么拆呢"></a>怎么拆呢</h3><p>其实几个月前我就有给这台设备清灰、换硅脂的念头,但拆开机器之后我发现主板被塑料框架罩住了,无法拆下散热器。我研究了半天也没找到拆下框架的方法,便只清理了风扇的灰尘,没有更换硅脂。</p>
+<p>而这次,我有备而来:我查到了 <a href="https://downloads.dell.com/manuals/all-products/esuprt_laptop/esuprt_inspiron_laptop/inspiron-15-5547-laptop_owner%27s%20manual_zh-cn.pdf">DELL 官方的用户手册</a> ,其中详细记载了拆解这台机器的方法。接下来的拆解步骤就严格按照官方的教程啦。</p>
+<h3 id="第一步:卸下后盖"><a href="#第一步:卸下后盖" class="headerlink" title="第一步:卸下后盖"></a>第一步:卸下后盖</h3><p>翻到 D 面,拧下两颗固定螺丝,就能将后盖拆下,可以触及无线网卡、内存条、2.5 寸硬盘、电池和散热风扇,日常需要维护的部件都能轻松触及,好评。散热器和主板则在塑料框架之下。</p>
+<p><img src="/images/2022-05-22/03.JPG" alt="D面后盖之下"></p>
+<h3 id="第二步:拆除电池"><a href="#第二步:拆除电池" class="headerlink" title="第二步:拆除电池"></a>第二步:拆除电池</h3><p>释放主板电荷是电脑拆机中至关重要的一步!</p>
+<p>在主板带电的情况下拔插任何端子都是不明智的做法,很可能会将主板上的高电压线路误接入低电压线路(例如拔插屏线时没有正对接口),烧坏一片元件。</p>
+<p>这台机子的电池没有排线,直接接入主板。卸下围绕电池的四颗螺丝,手提塑料片,就可将电池卸下。</p>
+<p>翻到 C 面,长按电源键 10s,可重复两到三次,确保主板中的电荷完全释放。</p>
+<p><img src="/images/2022-05-22/04.JPG" alt="电池模块"></p>
+<p>第一次见这种电池模块,比起长条状的电池可以更好地利用机身空间。</p>
+<h3 id="第三到五步:拆除硬盘、散热风扇和键盘"><a href="#第三到五步:拆除硬盘、散热风扇和键盘" class="headerlink" title="第三到五步:拆除硬盘、散热风扇和键盘"></a>第三到五步:拆除硬盘、散热风扇和键盘</h3><p>卸下固定硬盘座的螺丝,抽出硬盘,再断开 SATA 与供电二合一的线缆,即可取下硬盘。</p>
+<p>拔下主板上散热风扇的端子(位于风扇左侧),卸下固定螺丝,即可取下散热风扇。</p>
+<p>翻到 C 面,用手或塑料工具扣出键盘模块,注意不要大力提起键盘!小心地提起一段距离,断开机身上的排线,键盘就取下来了。裸 C 面上还有两条排线,都需要断开。竖直方向排线需从孔洞中穿回 D 面。</p>
+<p><img src="/images/2022-05-22/02.JPG" alt="裸C面"></p>
+<h3 id="第六步:卸下基座"><a href="#第六步:卸下基座" class="headerlink" title="第六步:卸下基座"></a>第六步:卸下基座</h3><p>这是我第一次见笔记本中的基座,没有手册的指导很难拆下。</p>
+<p>首先,确保 C 面两根排线已断开。</p>
+<p>翻到 D 面,小心地断开屏幕排线(位于散热风扇左侧)。</p>
+<p>再翻到 C 面,按手册图示卸下所有螺丝。</p>
+<p>翻回 D 面,同样地卸下一堆螺丝,注意这两面的螺丝都是 M2.5x7 规格,长于主板用螺丝,混用可能会戳穿主板。</p>
+<p>DELL 在机身和框架上都有标注螺丝孔对应螺丝的规格,非常好评!</p>
+<p>确认卸下所有螺丝后,用塑料工具插入基座与机身的缝隙,划开卡口。</p>
+<p>待大部分卡口都解开时,小心提起基座,注意将基座上的线缆和排线取下,基座就彻底与机身分离了。</p>
+<p><img src="/images/2022-05-22/05.JPG" alt="基座背面"></p>
+<p>基座的背面,可以看到是 PC + ABS 材料,分量很足。</p>
+<p><img src="/images/2022-05-22/06.JPG" alt="拆下基座后的机身"></p>
+<p>拆下基座之后的机身,暴露出了主板和子板。散热器是梦幻单热管,不过压这两个破芯片足够了。</p>
+<h3 id="第七步:卸下散热器"><a href="#第七步:卸下散热器" class="headerlink" title="第七步:卸下散热器"></a>第七步:卸下散热器</h3><p>散热器共有六颗螺丝,四颗在 CPU 上,两颗在显卡上。</p>
+<p>为了使散热器均匀受力,不能一次性直接拧下一颗螺丝,而是平均为每颗螺丝卸力,可以每个螺丝一次转两圈,直到所有螺丝都被卸下。裸晶脆弱,有必要好好保护一下。</p>
+<p><img src="/images/2022-05-22/07.JPG" alt="CPU的旧硅脂"></p>
+<p>不难看出,CPU 上的硅脂全部凝固,且裸晶上的硅脂基本流失殆尽(散热器上也没剩多少)。</p>
+<p>去除大部分凝固的硅脂,再用酒精片擦拭、用布擦干。</p>
+<p>更换硅脂的步骤我就不在此赘述,商家一般会提供详细的视频介绍,按着教程做就可以了。</p>
+<p><img src="/images/2022-05-22/08.JPG" alt="干净的CPU"></p>
+<p>i5-6200U 的全貌,右侧长条状的裸晶应该是集成显卡吧。</p>
+<p><img src="/images/2022-05-22/09.JPG" alt="擦干净的GPU"></p>
+<p>Geforce 930M 显卡,非常小个。</p>
+<p><img src="/images/2022-05-22/10.JPG" alt="显存芯片"></p>
+<p>DDR3 显存,封装方式和 DDR4 不一样,更扁一些。</p>
+<h3 id="后续步骤"><a href="#后续步骤" class="headerlink" title="后续步骤"></a>后续步骤</h3><p>接下来就是逆序刚刚的拆解步骤,我就不再赘述。</p>
+<p>注意几点:</p>
+<ul>
+<li>安装散热器时也需保证受力均匀,且不能过分用力,可能会压碎裸晶。</li>
+<li>安装基座时注意将线缆与排线置于合适的位置,不要被基座压到了。</li>
+</ul>
+<p>拼装完成后,插电,开机,轻松点亮。</p>
+<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>拆解一台电脑并没有想象中的那么困难,拆解不同电脑的方式也都大同小异,上述的步骤一般都能适用。</p>
+<p>我已拆解了不下 10 台/次 笔记本,目前还未翻过车。</p>
+<p>下次清灰、更换硅脂,试着自己动手吧!</p>
+
+
+ 2022-05-22T02:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/245ab175.html
+ 厌烦了软件的我上了硬件的车
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>好久不见,回想三个月前的我还在享受暑假。开学后,我将大部分精力转移至学业,折腾的时间便少了。</p>
+<p>机缘巧合,在显卡价格最高的时候坏了显卡的我打算趁双十一打折买个焊台,自己修显卡。</p>
+<p>同时,我有了玩一玩硬件的想法。</p>
+<h2 id="焊台体验"><a href="#焊台体验" class="headerlink" title="焊台体验"></a>焊台体验</h2><p>六七年以来,我一直在用直插 220V 不可调温的电烙铁焊接。当时,无线电老师告诉我这是质量很好的紫铜烙铁。就是这把烙铁陪着我入门了锡焊。</p>
+<p>放在今天,这把烙铁加热慢,且烙铁头非常容易氧化,体验极差。由于加热效果不佳,我还弄坏了一台灵车无人机的主板和一个 IR 摄像头(都是掉焊盘了)。</p>
+<p>新焊台则完美的解决了之前的痛点:新烙铁加热快、温度准确、刀头耐用。热风枪则开启了贴片元件焊接时代。</p>
+<h2 id="第一个项目"><a href="#第一个项目" class="headerlink" title="第一个项目"></a>第一个项目</h2><p>说在前头,选择一个比较复杂的项目来入门是个错误的决定。</p>
+<p>十月中,我选择了一个加热台项目。对,就是可以用来焊接贴片元件的加热台。用工具制造工具,多美妙的事情啊~带着兴奋,我开始采购元件……</p>
+<p>直至今天,这个项目的进度仍然停滞不前:元件全部焊接完毕(见下图),接通电源可以点亮一秒,随即就烧坏了一个电阻。初步判断是 5V 供电区域有短路,具体情况还需继续检测……</p>
+<p><img src="/images/2021-11-07/1.JPG"></p>
+<p>事实证明,第一个项目应该选择简单点的,以防止打击信心🌚</p>
+<h2 id="赶超第一个项目的第二个项目"><a href="#赶超第一个项目的第二个项目" class="headerlink" title="赶超第一个项目的第二个项目"></a>赶超第一个项目的第二个项目</h2><p>转眼到了十一月,又可以在嘉立创愉快地免费打板了~</p>
+<p>这次,我给自己设计了一张 PCB IC 校园卡,只需要焊接一个 CUID 芯片。</p>
+<p>没有比这更加简单的项目了!</p>
+<p>成品如图~</p>
+<p><img src="/images/2021-11-07/2.png"></p>
+<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>前几天整了一台性能不错的服务器,小项目又可以整起来;</p>
+<p>DIY PCB 这方面,我还想制作一个带编码器的小键盘,以提高 Premiere 剪辑的效率;</p>
+<p>群星真好玩,铁心灭绝者真不错😇</p>
+<p>真是丰富多彩的课余生活~</p>
+
+
+ 2021-11-07T13:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/67d41c7c.html
+ LOOPERS 简评
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>对 LOOPERS 的整体评价:B-</p>
+<hr>
+<p>这一作挺特殊的,没有任何选择支,全程自动播放耗时仅8小时左右。在这样短小的作品里塞下一日无限循环这样富有可能性的世界观,大概是本次剧情质量不尽人意的主要原因。</p>
+<p>中盘倒不觉得冗长,还可以继续细化,加深对人物的刻画;就是后期重复的内容与话语太多了,让想尽快看到结局的我着急。</p>
+<p>音乐方面,我全程用电脑扬声器玩的。说实话,BGM 听得不是很清楚。待我细听之后再作出评价。女二真的很吵,有 SP 那头恶鬼的吵闹级别了。</p>
+<p>制作方面,望月老师的画好看(哧溜)。</p>
+<hr>
+<p>谈一谈一些细节上的感受(可能有点剧透):</p>
+<p>作为已经退坑的老 Ingress 玩家,再度跟随主角一行人踏上寻宝之路,着实让人兴奋。游戏中着力刻画的,寻找到藏匿的宝物时的喜悦我能切身体会到。</p>
+<p>这让我想起了两三年前,我为了拔掉敌方的一个大 菊花 Portal,在旧屋区狭窄的巷子里穿行。找了一两个小时,终于到达 Portal 的中心点,开始狂轰滥炸。当时的兴奋与满足是溢于言表的。或许你要亲身玩过寻宝游戏,才能体会主角一行人的感受吧。</p>
+<p>对于“神经大条”的男主,我最大的感受就是这种人不大可能存在吧。遇到任何事情都能乐观面对、看似大大咧咧但做起事来注重细节、有着强大的领导力和鼓舞力……这种人若真的存在,绝对耀眼。</p>
+<p>最后看作品的主题。在我看来, 龟 龙骑士这回倒不是想写一个很催泪的故事,而是继续宣扬 Key 社所推崇的友谊、团结和爱的主题。我个人是百看不厌,我也想要知心朋友,甚至是一群知心朋友。</p>
+<p>男主全程口边挂着寻宝二字,听了耳朵确实起茧,但这文章放在考试说不定能拿挺高的分数——不断地扣紧中心。人生的意义?寻宝;友情的价值?寻宝;生命的救赎?还是寻宝。</p>
+<p>这寻宝真有意思,只要将寻宝的过程与寻找到宝物稍作改变,就能解释这么多难以用语言表述的东西。倘若能在游戏性上再下点功夫,加个交互型的寻宝游戏,整体效果又会再上一层。但加了这些就不是一款短小精湛的游戏了,对吧。</p>
+<hr>
+<p>最后,这款游戏值得推荐吗?对于没有接触过 Key 社作品的人,可以一玩。</p>
+<p>对于 Key 社老玩家,对这部作品报以极高的期待,希望它能超过正作水准的,还是别玩了。</p>
+
+
+ 2021-08-02T02:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/202ebc76.html
+ 2021 第二学期总结
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>在去厦门的动车上,写一下这个学期的总结。</p>
+<hr>
+<p>刚开始我想形容这个学期是浑浑噩噩的,感觉我想做、想学的东西都没做成;</p>
+<p>但再冷静想一想,这学期在学校的安排下打了很多基础:解决了 C++ 这一心头大恨;还学习了数电,为硬件打了点基础。</p>
+<p>C++ 实在很基础、很枯燥,若不是学校压着要学习,光凭我一人自学是难以解决的。以前也不是没有尝试过自学,最终都败下阵来。</p>
+<p>至于硬件这块的知识,软件工程专业一般是不去接触的。学校大概是觉得下学期要学的 计算机组成原理 需要有数电的预备知识吧。</p>
+<p>至于高数、线代,对编程越是了解,越会知道他们的重要性,知道他们贯穿着整个编程的过程。</p>
+<p>这个学期最满意的还是全科通过,包括让我非常头疼的大学物理和高数。</p>
+<p>下个学期我还是希望能腾出更多的时间给自己喜欢的事物,而不是把全身心都投进学校的课业,感觉亏待了自己的梦想……</p>
+<p>生活方面,■■■■■■■■■,整体闷在学校里把我憋得难受。宿舍环境虽然不错,但整体偏老旧——六人间改四人间,■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<p>下个学期又可以搬回主校区,一定要把宿舍和<del>工位</del>书桌好好装修一下,过精致生活~</p>
+<hr>
+<p>至于暑假的安排,挺杂乱的。</p>
+<p>买了不少东西,从小到大,从 IR 摄像头模组到 3D 打印机,打算将折腾二字写满整个假期;</p>
+<p>学习内容则是安排了 JavaScript 和 CTF 相关的内容,如果还有时间还想看看 React。CTF 没有人带,想要入门挺困难的……校队是不打算招新人么 :(</p>
+
+
+ 2021-07-15T01:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/43a7a15c.html
+ Osprey Commet 简评
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>我的威戈双肩包——没错,就是烂大街的那个牌子——已经用了四年多了。不得不承认她的质量之好,用了这么久只坏了包前侧的拉链(到裁缝店换了一条拉链),外加水壶网袋破了几个洞。在上个月,她的一条拉链绳(不知道怎么叫那个部件)的脱落让我有了更换通勤背包的念头。</p>
+<span id="more"></span>
+
+<p>为了挑选一款最称心的通勤背包,我先对前任威戈包进行了评估:</p>
+<p>优点</p>
+<ul>
+<li><p>威戈的分仓做的不错,充电宝、卡包等物件都能找到存放的位置,钥匙有也专门的链子系挂;</p>
+</li>
+<li><p>储存空间大(30L+),主仓有足够的空间放下装在内胆包里的14寸笔记本,也有专门的笔记本仓位;</p>
+</li>
+</ul>
+<p>缺点</p>
+<ul>
+<li><p>背负系统拉垮,光是背电脑(1.4kg)就觉得非常沉,更别说偶尔的电脑+平板组合了(2.2kg);</p>
+</li>
+<li><p>没有太多存放小物件的仓位,所有的小东西(U盘、读卡器、耳机、线材 etc.)都放在包的前仓里,且前仓还是竖向开合,分类与取物皆不便;</p>
+</li>
+<li><p>包身不防水,但送防雨套。下雨时出门要提前戴套,生怕包内电子产品淋湿;</p>
+</li>
+</ul>
+<p>因此,我对下一款背包有如下要求:</p>
+<ul>
+<li><p>自重轻;</p>
+</li>
+<li><p>分仓多且位置合理,能分类存放小物件;</p>
+</li>
+<li><p>包身最好能防水;</p>
+</li>
+<li><p>最好能同时收纳笔记本电脑和平板;</p>
+</li>
+</ul>
+<p>在挑选了不下30款背包后,我选择了这款 Osprey Commet,下面来简要点评一下。</p>
+<h4 id="全局"><a href="#全局" class="headerlink" title="全局"></a>全局</h4><p>包身自重 0.85kg,容积 30L,在这个大小的包中算比较轻的了。包身全部防水,拉链虽然没有做密封处理,但有一些延伸出来的防水面料挡住拉链,可以防止进水。</p>
+<h4 id="分仓"><a href="#分仓" class="headerlink" title="分仓"></a>分仓</h4><p>这款包的分仓可以算是一个亮点。我简要说一下我对每个分仓的使用情况:</p>
+<p>包的最前方是提手(颜值比较一般,偏向实用),因此第一个仓是小主仓。仓内有三个兜袋,用于存放我的卡包、证件、充电宝和 MP3,仓内还装着小记事本和钱包。此外,仓内还有一根红色的钥匙绳,采用的是快挂钩,可以把现有的钥匙串挂上去。</p>
+<p>往里一层是第一个眼镜/小物品收纳仓。仓内使用了特殊面料防止刮花眼镜,在登山/骑行时存放眼镜还是很方便的。我就拿来存放经常取用的小物件了,例如耳机、U盘、Lto3.5mm 转接线等。</p>
+<p>下一层是一号主仓,外加主仓内的第二个眼镜/小物品收纳仓。一号主仓有一个兜袋方便收纳平板和 A4 大小的文件,我便拿来放文件袋和平板。课本、笔盒也都放在这个仓位。小物品收纳仓则收纳比较少取放的小物件,例如订书机、凤尾夹和一些药品(达喜)。</p>
+<p>最后一个仓位是笔记本仓。作为整个包最大的仓位,能轻松容纳 15.6 寸的笔记本。在需要携带笔记本时,这个仓位当然用来装笔记本;平常上课不带笔记本,也可以拿来装外套。这个仓位里还有一个网袋,我用来装线材(一根 0.2m CtoC、一根 MFi AtoL、一根 0.2m AtoB)。</p>
+<h4 id="背负系统"><a href="#背负系统" class="headerlink" title="背负系统"></a>背负系统</h4><p>虽然没能用上 Osprey 专业的空景系统,但 Commet 的背负系统依旧优秀。Osprey 的背板偏硬,可以比较好地贴合后背,以减轻肩带对肩膀的压力。在日常上课背负几本书时,基本没有负重的感觉。若是同时背负笔记本+平板(3.6kg 左右),能感觉到背板压在后背上,分担了部分重量。</p>
+<p>胸带与腰带这种登山包才有的配置出现在了这款通勤包上。在背负比较重的物品时系上腰带可以有效减少包身晃动。</p>
+<h4 id="总评"><a href="#总评" class="headerlink" title="总评"></a>总评</h4><p>相当满意!</p>
+
+
+ 2021-06-13T07:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/4a562e35.html
+ 近日败家:Chrombook Duet 与 小米手环6
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>今天来分享一下最近败家的几样电子产品。</p>
+<h2 id="Chromebook-与-USI-触控笔"><a href="#Chromebook-与-USI-触控笔" class="headerlink" title="Chromebook 与 USI 触控笔"></a>Chromebook 与 USI 触控笔</h2><p>在大学课堂上发现很多人都持有一台 iPad 和 一支 Apple Pencil 在做着笔记,令我很是羡慕。</p>
+<p>在咨询过“业内人士”,并对现有产品及我的钱包进行评估之后,我放弃了购买 iPad 及其昂贵的配件的念头,选择拥抱 Chromebook。</p>
+<p>为了追求极致的性价比,我选择了这款 联想 Chromebook Dute 加上 联想 USI 触控笔,1730 + 330 合计 2060 元。</p>
+<h3 id="购买"><a href="#购买" class="headerlink" title="购买"></a>购买</h3><p>我是在亚马逊海外购下单的,货从美国仓库发出。平板花费了8天来到了我的手上,而触控笔因为海关查验,比平板迟了两天。跨国快递的速度比我想象的要快不少。</p>
+<h3 id="设计与体验"><a href="#设计与体验" class="headerlink" title="设计与体验"></a>设计与体验</h3><p>Duet,意为合二为一。CB Duet 的设计理念和 Surface Book 一致——打造一台既可以当作平板又可以当作电脑使用的设备。</p>
+<p>CB Duet 附赠了磁吸键盘与可以作为支架的保护套,这一点比 iPad 高不少。</p>
+<p>但是,键盘与触控板的体验实在是一言难尽。</p>
+<p>键盘在不平整的环境下偶尔会出现多次触发(按一次键打出两次字母)的情况,不过放在平整桌面就不会了。键程尚可,但按键布局一般——为了在10寸大小的区域容纳全部按键,联想选择了将键盘右侧的按键(大部分是符号键)缩小,这让打出正确的符号变得十分困难。</p>
+<p>触控板的体验更加一言难尽。手感很差,似乎没做过亲肤处理,滑动阻力非常大;定位也很不准确。但 CB Duet 有一块10寸的触摸屏,为何不使用触控操作呢?</p>
+<p>总而言之,CB Duet 附赠的键盘属于“能用”的级别,就像现在的我敲着博客——做一点文字工作完全没问题。Coding ?别想了,我替你试过了,如果只是简单改几行代码,比如 Caddy 的配置文件,完全没问题;如果想跑完整的开发环境,性能可能不够;如果使用 code-server……也不是不行,但10寸的屏幕实在不能施展浑身解数啊。</p>
+<h3 id="续航"><a href="#续航" class="headerlink" title="续航"></a>续航</h3><p>部分比较“卷”的朋友可能比较担心这个问题,是我先倒下还是 CB Duet 先倒下?</p>
+<p>根据我的实际体验——当然这可能很不准确,你大概率撑不过 CB Duet。</p>
+<p>实际测试下来,一个上午,四节课下来,大概使用 15%-20% 电量。</p>
+<p>ARM 的超低功耗让 CB Duet 有官方标称的将近 11 小时的续航;如果你像我一样调低屏幕亮度,并只使用笔记软件,系统提示的理论续航甚至达到 20 小时;倘若是高强度使用,例如使用 ssh、打开 Linux 虚拟机等,续航则在 6-8 小时左右。</p>
+<h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p>翻到上面再看一眼价格,还要谈什么性能吗!</p>
+<p>本人不喜欢的 MTK P60T,8c 2.0Ghz。</p>
+<p>不过实际体验来看,这颗小芯片的性能完全能喂饱 ChromeOS 和它的安卓容器,至于 Linux ,只要不跑 GUI 应用(Linux 容器暂无图形硬件加速)也没问题。</p>
+<p>听说联想计划推出搭载高通骁龙 7c 的同款 CB ,性能有略微升级,如果价格依旧能持平,那会是更好的选择。</p>
+<h3 id="系统与体验"><a href="#系统与体验" class="headerlink" title="系统与体验"></a>系统与体验</h3><p>ChromeOS 的操作需要适应一段时间。</p>
+<p>你应该要改变对应用的认知——减少安装实体应用,更多地拥抱 PWA ,可以让这个专门为 Chrome 优化的操作系统发挥全部实力。</p>
+<p>安卓容器的性能真的很棒,可能是安卓和 ARM 相性较好的缘故吧,安卓应用在 CB Duet 上表现极佳,我觉得和一台安卓平板几乎没有差异。这台安卓平板却还能跑完整的 Chrome 浏览器和 Linux 容器。</p>
+<p>系统的其他体验可以在网络上看看其他人的博客。如果我在后续体验中有什么心得体会,会考虑写成博客。</p>
+<h3 id="还有触控笔"><a href="#还有触控笔" class="headerlink" title="还有触控笔"></a>还有触控笔</h3><p>联想这只 USI 触控笔是亚马逊上最便宜的,没有侧边按键、没有笔擦。触控笔没有太多要说的,昨晚用它写了一份作业,体验良好。</p>
+<p>使用一周之后,偶尔会出现笔凌空识别的情况,无法测试出是硬件还是软件的问题,略微影响使用体验。</p>
+<h2 id="小米手环6"><a href="#小米手环6" class="headerlink" title="小米手环6"></a>小米手环6</h2><p>这是我第一次体验手环产品,之前戴的都是智能/半智能手表。</p>
+<p>小米手环6的屏幕在手环产品中是比较大的,且能自定义壁纸,这是它最吸引我的地方。</p>
+<p>至于续航,的确是硬伤。在打开除了全天心率监测以外的所有监测项目,心率检测频率设为1分钟一次后,手环一天消耗 20% 左右电量。</p>
+<p>总体体验还算满意的。</p>
+
+
+ 2021-04-30T13:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/2c0bfb1a.html
+ 校色初体验 / 寒假总结
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>好久不见,甚是想念。今天来分享一下初次校色的体验以及寒假我都干了些什么。</p>
+<span id="more"></span>
+
+<hr>
+<h2 id="校色初体验"><a href="#校色初体验" class="headerlink" title="校色初体验"></a>校色初体验</h2><h3 id="为什么要校色"><a href="#为什么要校色" class="headerlink" title="为什么要校色"></a>为什么要校色</h3><p>我的笔记本电脑型号是 联想小新 Air 14 2020,拥有一块 14寸 100% sRGB 色域(实际为 94% sRGB)的屏幕,面板型号是 友达 B140HAN06.8。很幸运,不仅抽中了三星 SSD,还抽中了友达的屏幕。</p>
+<p>为了更舒服地阅读代码,我又淘了一台 17.3寸 的 DIY 便携显示器,同样也是 100% sRGB 色域(实际为 95% sRGB),面板型号是 友达 B173han01.1。</p>
+<p>由于便携屏的驱动板都是通用的,并没有对某块面板有调教,也不可能有屏幕出厂时的调教,因此这块便携屏的色差非常明显。就校色结果来看,光度 (gamma) 值就有 70% 左右的偏差。</p>
+<p>通过校色,两块屏幕色彩更加接近,且更接近真实的颜色,看起来也会更舒服一些。</p>
+<h3 id="关于价格"><a href="#关于价格" class="headerlink" title="关于价格"></a>关于价格</h3><p>我想很多人和我有同样的顾虑,感觉租用校色仪价格不菲。</p>
+<p>这次租用时长为 3天,我仅使用了一天半就归还了。总共支出为 校色仪租金 40元 + 回程运费 18元。押金原本是 750元,和店家商量后爽快地降到了 500元。</p>
+<p>虽然商家划定了许多可能导致押金被扣的规则,但只要你小心一点、爱护一点,完璧归赵、取回押金是很简单的。</p>
+<h3 id="色温选择"><a href="#色温选择" class="headerlink" title="色温选择"></a>色温选择</h3><p>刚开始我选择的目标色温是 6500K,在校色后发现偏黄许多。最后我选择的目标色温是 7500K。当然,两块屏幕 6500K 色温的校色文件我都保存下来了。</p>
+<h3 id="最终校色效果"><a href="#最终校色效果" class="headerlink" title="最终校色效果"></a>最终校色效果</h3><p>按照红蜘蛛给出的报告,联想笔记本的这块屏幕在色准上要优于便携屏,白点与灰度的 △E <= 0.2 ,便携屏则是 △E <= 0.5。</p>
+<p>联想这块屏出厂也比较准,值得表扬~</p>
+<p>下面是校色完的合影:</p>
+<p><img src="/images/2021-2-26/1.jpg"></p>
+<hr>
+<h2 id="寒假我都干了啥"><a href="#寒假我都干了啥" class="headerlink" title="寒假我都干了啥"></a>寒假我都干了啥</h2><p>列举一下:</p>
+<ul>
+<li>学习(重温)了 C语言;</li>
+<li>学习了 PHP 基础;</li>
+<li>学习了 Burp Suite 基础;</li>
+<li>学习了 After Effect;</li>
+<li>学习了 Audition 基本操作;</li>
+<li>完成了一个 5分钟 的视频项目,比较灵活地运用了 PS Pr AE 及 Au;</li>
+<li>当了两个星期的补习老师,挣来的钱买了 飞利浦 X2HR HiFi 耳机和一块 2242 SSD;</li>
+<li>学习了 Solidworks 基本操作(这两天学的);</li>
+</ul>
+<p>感觉还是做了不少事的。</p>
+<p>马上要开学了,下学期总算能学些我想学的了。</p>
+<p>总之,好好干吧!</p>
+
+
+ 2021-02-26T14:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/cd7dffda.html
+ 2020 年终总结
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>2020 马上就要过去了,我想做一个年终总结。</p>
+<span id="more"></span>
+
+<hr>
+<p>非常抱歉鸽了博客四个月。</p>
+<p>其实十月我忙中偷闲写了一篇文章打算发上来,却被 Hexo 吞了,我也没了重写的热情,就这么让它消失了。</p>
+<h3 id="2020-我做了什么"><a href="#2020-我做了什么" class="headerlink" title="2020 我做了什么"></a>2020 我做了什么</h3><p>■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<p>■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<p>■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<p>■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<p>■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<h3 id="2021-我打算做什么"><a href="#2021-我打算做什么" class="headerlink" title="2021 我打算做什么"></a>2021 我打算做什么</h3><p>First, 我打算加入学校的 CTF 战队。自学 CTF 的过程中一定会遇到不少有趣的事情,我会抽空写作博文与大家分享的。</p>
+<p>Second, 我想和同学合作开发一些有趣的小项目乃至小软件,点子已经有几个存在脑子里了。</p>
+<p>Then, 我打算磨练一下业务能力,而不是简单地编写脚本一样存在的代码,CSS 也不能写得歪歪扭扭的了(捂脸)。</p>
+<p>在一切都回归正轨之后,我也会跟上各位的步伐,继续探寻有趣的项目,还请大家多多期待!</p>
+<h3 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h3><p>2020 发生了太多糟糕的事情,祈愿 2021 能遇见更多美好的事情。</p>
+
+
+ 2020-12-31T16:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/911a91db.html
+ 博客迁移-主题更换与近况报告
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>又好久不见。</p>
+<p>这两天抽空更换了博客的主题,并迁移至永久域名 ■■■■■■■■■■■ 。</p>
+<p>以及谈一谈新开的项目的设计思路,有兴趣的读者还请慢慢阅读。</p>
+<span id="more"></span>
+
+<h2 id="博客变更"><a href="#博客变更" class="headerlink" title="博客变更"></a>博客变更</h2><h3 id="更换域名"><a href="#更换域名" class="headerlink" title="更换域名"></a>更换域名</h3><p>■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<h3 id="更换-Hexo-主题"><a href="#更换-Hexo-主题" class="headerlink" title="更换 Hexo 主题"></a>更换 Hexo 主题</h3><p>主题的名字叫 <a href="https://github.com/fluid-dev/hexo-theme-fluid">Fluid</a> ,是一款 Material Design 风格的主题。</p>
+<p>之前我用的主题是 Yilia ,但作者弃坑已久,感觉已跟不上时代变化,就下定决心换了(还把手上所有猫羽雫图全部塞进去了)。</p>
+<p>同时,我还将评论系统 Gitalk 更换为 <a href="https://utteranc.es/">utteranc</a> ,对于国内用户加载速度应该会更快,同样需要 GitHub 登录。</p>
+<h2 id="近况报告"><a href="#近况报告" class="headerlink" title="近况报告"></a>近况报告</h2><p>快要高考了好忙啊,完全没有时间研究技术了,也到了应该要努力的时候了(笑)。</p>
+<p>超级长的寒假里我还是有学习新知识的(指计算机):</p>
+<h3 id="成绩查询网站"><a href="#成绩查询网站" class="headerlink" title="成绩查询网站"></a>成绩查询网站</h3><p>开了一个坑:构建一个成绩查询网站以代替学校那套繁琐的校务系统。</p>
+<p>■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<p>项目的前端用了 jQuery、Bootstrap 等框架;</p>
+<p>后端则是采用我比较熟悉的 Python 后端框架 Django 。</p>
+<hr>
+<p>校园官网查询成绩的流程如下:</p>
+<pre><code class=" mermaid">graph TB
+校园网主页--输入账号密码 选择身份-->
+校园网内网--找到侧边栏的按钮-->
+成绩查询页面--选择年份 学期 考试场次-->
+得到成绩
+</code></pre>
+
+<p>如何取代学校的系统呢?思路很简单:<del>黑掉学校的服务器直接把成绩取出来</del></p>
+<p>模拟登录就好了。</p>
+<p>学校的破烂网站采用的是最简单的 GET 表单登录,可以轻松获取 Cookie ,方便进一步操作;</p>
+<p>我将查询步骤简化为:</p>
+<pre><code class=" mermaid">graph TB
+查询页面--输入账号密码-->
+下一步1--选择年份 学期-->
+下一步2--选择考试场次-->
+获得成绩
+</code></pre>
+
+<p>看起来没简化多少,其实 选择身份 和 找到侧边栏按钮 就已经足够烦人了。</p>
+<p>校园网采用了大量下拉框选择,我将其替换为按钮选择,甚至不用选择,一定程度上提高了查询效率。</p>
+<hr>
+<p>除此之外,我也写了另一个 API 负责查询某成绩查询 APP 上的成绩。</p>
+<p>我认为这个 API 贡献较校网查询应该更大,这让我摆脱了手机 APP 查询这一烦人的设定。</p>
+<p>然而开发过程中,后者所花费的力气远小于前者,大概是校网建设得太差的缘故吧。</p>
+<hr>
+<p>除此之外,我还尝试将 API 封装进 Docker,使部署更加简单、快捷。</p>
+<p>Docker 的使用非常简单,若有兴趣还请多多尝试。</p>
+<p><strong>果然造轮子学习比干看教程效果要好啊!</strong></p>
+<h2 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h2><p>没有尾声 : )</p>
+
+
+ 2020-05-04T01:58:17.000Z
+
+
+ https://blog.udon.eu.org/archives/c7a0a8db.html
+ 莱卡依然想要回家
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>有幸在<a href="https://t.me/rynif_music">/home/rynco/music</a> Channel 遇见这张专辑。第一次听,或许是风格合口,我立刻爱上了这首名为“Laika Still Wants Go Home”,与专辑同名的轻音乐。<br>很有力量。这是这张专辑给我的第一印象。<br>我几乎没有乐理知识,对于音乐的评论总是如此无力。这首歌从旋律看也好,从节奏看也好,感觉整体把控得很恰当,张弛有度。我贫瘠的语言真的无法形容那种美的感觉。</p>
+<span id="more"></span>
+
+<hr>
+
+ <div id="aplayer-fzAHKHpD" class="aplayer aplayer-tag-marker" style="margin-bottom: 20px;">
+ <pre class="aplayer-lrc-content"></pre>
+ </div>
+ <script>
+ var ap = new APlayer({
+ element: document.getElementById("aplayer-fzAHKHpD"),
+ narrow: false,
+ autoplay: false,
+ showlrc: false,
+ music: {
+ title: "Laika Still Wants Go Home",
+ author: "Powder! Go Away",
+ url: "/music/Laika_Still_Wants_Go_Home.mp3",
+ pic: "https://p2.music.126.net/KFmivwKj3h18NGYKJlYcsw==/3229265650821776.jpg",
+ lrc: ""
+ }
+ });
+ window.aplayers || (window.aplayers = []);
+ window.aplayers.push(ap);
+ </script>
+
+<p>我刷新了对这张专辑的认识,已是数十分钟后。我在听新曲时总会翻翻网易云音乐的评论区,有数条评论对专辑封面的解释。<br><img src="http://p2.music.126.net/KFmivwKj3h18NGYKJlYcsw==/3229265650821776.jpg"><br>我第一次听说太空犬这个词是在Littile Buster中,能美·库特莉亚芙卡的名字的由来。库特莉亚芙卡是一头太空犬的本名。<br>它常被我们称作:莱卡。</p>
+<hr>
+<p>鉴于很少人知道这个故事,我就讲讲吧。<br>当时正逢美苏争霸,太空竞赛也是美苏交锋的重点。当时的苏联宇航技术不是很发达,但想要胜过美国就要先把航天员送上天。总不能让宇航员乘着从未试验过的飞船就这么上去吧,苏联的科学家就想用动物代替人类完成测试。科学家们在街上找来了几条流浪狗——因为他们觉得流浪狗比家犬够能受冻——这几条狗就被钦定为太空犬了。训练的艰苦不必多说,看看训练人类就能明白,何况是动物。几个月的训练之后,莱卡脱颖而出。到了发射前几天,一位科学家把莱卡带回了家里,让它和孩子们玩耍,因为他们很清楚,这是一次不含回程票的旅行。<br>苏联当时并未掌握从地球轨道重返地面的技术。<br>不知道当时在场的所有人的心情是怎样的。应该是兴奋的,毕竟超越老美了;又有点担心,因为即将发射的飞船是赫鲁晓夫下令两周内造出来的;或许有的人会有些不舍吧,他们将要亲手送走一条鲜活的生命。<br>很可惜,莱卡并不是在氧气或食物耗尽前被安乐死的。飞船的温控系统因赶工与设计问题出现故障。即使风扇再怎样转,舱内俨然成为一个火炉。用好听的话说莱卡是中暑而死;难听点,活活热死。仅管死亡是不可避免的,我也希望它能少一点痛苦啊。</p>
+<hr>
+<p>我想到了安德。在指挥完舰队完成模拟战斗后,在他得知他亲手消灭了虫族的舰队时,他崩溃了。他回忆起刚才他指挥数个小队作为诱饵白白牺牲。他崩溃了。我想当时,我也希望当时,火箭点火升空之时,二级火箭脱离失败之时,舱内温度急剧升高之时,有人能为这条生命感到一丝绝望。</p>
+<hr>
+<p>这张专辑的风格与“OPUS-灵魂之桥”的 OST 有异曲同工之妙。(独立工作室的游戏,OST 不方便放出,希望大家能一起购买。游戏也很棒啊!)都给我以一种末世感。究竟是世界对人绝望还是人对世界绝望呢?</p>
+<hr>
+<p>想象你站在专辑封面的那一点上,眺望地球,这该是多么孤独与悲伤。有评论说载着莱卡的火箭现在仍绕着地球旋转,但很可惜,那台火箭已经在大气层燃尽,这也算是最高规格的葬礼了吧。</p>
+<hr>
+<p>再说说专辑的名称吧。“Laika Still Wants Go Home”,英文,是没有一点问题的。我的母语是中文,因此英文的名称对我无感。但当我要将它翻译成中文,那一刻,我真的愣住了。我脑袋里已经将这几个单词转化为了中文,再一次理解了它们的意思。我僵住了。就几个字停留在嘴边就是说不出来。说不出来……在对面的人疑惑不解我为何停住,思考是否因为自己没有在认真听导致我生气时,一滴眼泪从我疲惫的左眼划过。立刻调动理智。情绪再次平稳,我又调回了内存里的那几个字,念了出来……</p>
+<hr>
+<p>后记</p>
+<p>从听到这首歌到构思这篇文章再到陆陆续续写出来,我花了数天。因为事情真的很多,脑内的一些想法被琐事代换了。<br>文字很凌乱,几乎是想到什么写什么。因此我用分割线分开了。<br>若有新想法我会继续补充的。</p>
+
+
+ 2019-10-31T15:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/ff412ce9.html
+ GOROGOA-精华,你的成果如何
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h3 id="说在前面"><a href="#说在前面" class="headerlink" title="说在前面"></a>说在前面</h3><p>我是怀着激动的心情来写这篇文章的。<br>我如何定义一部作品是否优秀?很简单。它能达到它的目的,它和优秀沾上边了。就像搞笑视频逗笑你,治愈视频使人放松,致郁视频让你流泪。<br>而这是一部富有哲理的优秀作品。</p>
+<span id="more"></span>
+
+<h3 id="预备知识"><a href="#预备知识" class="headerlink" title="预备知识"></a>预备知识</h3><h4 id="什么是-GOROGOA"><a href="#什么是-GOROGOA" class="headerlink" title="什么是 GOROGOA"></a>什么是 GOROGOA</h4><p><a href="https://en.m.wikipedia.org/wiki/Gorogoa">GOROGOA</a></p>
+<p>中文译名 画中世界<br>一款解谜类游戏。我还没能抽出时间拜读一下这个故事。</p>
+<h4 id="什么是Voiceroid、Voiceroid剧场"><a href="#什么是Voiceroid、Voiceroid剧场" class="headerlink" title="什么是Voiceroid、Voiceroid剧场"></a>什么是Voiceroid、Voiceroid剧场</h4><p>Voiceroid 是 Vocaloid 的姐妹产品。一个负责朗读,一个负责唱歌。<br>Voiceroid 剧场是利用 Voiceroid 朗读故事,制作成的动画剧场。</p>
+<h3 id="亮点"><a href="#亮点" class="headerlink" title="亮点"></a>亮点</h3><h4 id="出场人物"><a href="#出场人物" class="headerlink" title="出场人物"></a>出场人物</h4><p><img src="/images/2019-10-25/1.png"></p>
+<blockquote>
+<p><strong>第一章第一节出现的标题</strong></p>
+</blockquote>
+<p>这是一部挺长的剧场动画,好像是12集,每集20分钟,若是翻译组周更那就是一部番剧了。<br>由于故事长,本次出演人数也不少。<br>以下是出场人物<br><strong>京町精华(京町セイカ)、东北切蒲英(東北きりたん)、绁星灯(紲星あかり)、结月缘(結月 ゆかり)、琴叶茜、葵(琴葉 茜・葵)、东北俊子(東北 ずん子),IA等为配角配音。</strong><br>出场人物基本包括了所有 Voiceriod. 以精华为主人公的作品还是第一次见,绁星灯作为女二号,立绘很好看~</p>
+<p><img src="/images/2019-10-25/2.png"></p>
+<blockquote>
+<p><strong>精华、俊子和俊子的母亲(配角均为黑脸)</strong></p>
+</blockquote>
+<h4 id="作品特色"><a href="#作品特色" class="headerlink" title="作品特色"></a>作品特色</h4><p>本作特色之一是制作良心。精华和灯的立绘说话时嘴巴有动作,虽不是 Live2D,却不失动画感。用于一个故事性强的剧场效果反而比吸睛的 Live2D要好。</p>
+<p><img src="/images/2019-10-25/3.png"></p>
+<blockquote>
+<p><strong>总是板着脸的精华</strong></p>
+</blockquote>
+<p>第二个亮点就是故事了。GOROGOA 本是一款解谜游戏,听说通关时长也不是很长。作者以游戏为背景设定,巧妙的建立起一个故事框架,用扑朔迷离的剧情吸引了我。</p>
+<h3 id="观后之感"><a href="#观后之感" class="headerlink" title="观后之感"></a>观后之感</h3><p><strong>震撼</strong><br>理由大致有两点。<br>其一,作者精心设计的剧情和出乎意料的表现方式让我深感佩服。或许冷静下来看这个故事并不能与那些真正的大作匹敌,但一部能令人深思的剧场已是少见,更应得到关注。<br>其二,作者敢于直面社会/生活问题,直叙心中之言。能借 Voiceroid 之口说出自己的心声也是我梦想已久的。</p>
+<p><img src="/images/2019-10-25/4.png"></p>
+<blockquote>
+<p><strong>小茜在精华的床上听她讲故事</strong></p>
+</blockquote>
+<p>若是做一些通俗的赏析。作者选取的立绘真的很好看啊( ̄▽ ̄)。其中谈到的一些问题也深有感触,能引起我的思考。</p>
+<h3 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h3><p>很期待接下来的剧情走向!<br>我将我喜爱的作品推荐给你们,希望你们能喜欢!</p>
+<p><img src="/images/2019-10-25/5.png"></p>
+<blockquote>
+<p>附观看地址(已完结)</p>
+</blockquote>
+<ul>
+<li><a href="https://www.bilibili.com/video/av67411535">1-1</a></li>
+<li><a href="https://www.bilibili.com/video/av68618084">1-2</a></li>
+<li><a href="https://www.bilibili.com/video/av69760203">1-3</a></li>
+<li><a href="https://www.bilibili.com/video/av71923539">2-1</a></li>
+<li><a href="https://www.bilibili.com/video/av75076293">2-2</a></li>
+<li><a href="https://www.bilibili.com/video/av77466396">2-3</a></li>
+<li><a href="https://www.bilibili.com/video/av79444470">3-1</a></li>
+<li><a href="https://www.bilibili.com/video/av83327515">3-2</a></li>
+<li><a href="https://www.bilibili.com/video/av85369541">3-3</a></li>
+<li><a href="https://www.bilibili.com/video/av86153262">4-1</a></li>
+<li><a href="https://www.bilibili.com/video/av86435388">4-2</a></li>
+<li><a href="https://www.bilibili.com/video/av86506615">5</a></li>
+<li><a href="https://www.bilibili.com/video/av86918052">6</a></li>
+<li><a href="https://www.bilibili.com/video/av87038098">7</a></li>
+</ul>
+
+
+ 2019-10-25T14:30:00.000Z
+
+
diff --git "a/category/\351\232\217\347\254\224/feed.json" "b/category/\351\232\217\347\254\224/feed.json"
new file mode 100644
index 00000000..632f15b9
--- /dev/null
+++ "b/category/\351\232\217\347\254\224/feed.json"
@@ -0,0 +1,208 @@
+{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "カレーうどん屋 • All posts by \"随笔\" category",
+ "description": "カレーうどん屋.",
+ "home_page_url": "https://blog.udon.eu.org",
+ "items": [
+ {
+ "id": "https://blog.udon.eu.org/archives/95479b1f.html",
+ "url": "https://blog.udon.eu.org/archives/95479b1f.html",
+ "title": "Soundcore C30i 耳机",
+ "date_published": "2024-06-13T06:40:00.000Z",
+ "content_html": "我目前在使用的耳机是 Sony LinkBuds,就是那款中间带了个孔的奇怪耳机。
\n它本是为了商务人士在办公场所使用而设计,收听耳机中声音的同时不会影响听取他人的谈话。它却被我这个耳道经常发炎的人看上了。
\n我想,中间带了个孔,那耳道内外的空气就是完全流通的,那耳道内肯定不会闷了吧!为了验证这一点,我特地去 Sony 的线下店体验这款耳机。佩戴上 LinkBuds,确实完全没有耳道被封闭的感觉,我便在网上欣然下单了。
\n然而,经过一段时间的佩戴,才发现我的想法过于天真:看似畅通无阻的气流,在直径不够大的圆环处有阻塞。内部产生的水蒸气无法排出,导致耳道仍然会有潮湿感。佩戴20分钟以上,摘下耳机放入耳机舱,一段时间后取出,会发现耳机舱内有冷凝水。
\nSony LinkBuds 仍然不能解决我耳朵发炎的困扰。我便又踏上了寻找新耳机的旅途。
\n今天便是要介绍我找到的其中一款,有望成为最终解决方案的耳机 —— SoundCore C30i。
\n
\n设计
\n
\n耳机充电盒的外观,以及开盖后的样子如上图所示,就不多介绍了。
\n开放式耳机相对于入耳式、半入耳式耳机,设计上花样多了不少。我看到的所有款式中,大致可以分为两类:
\n\n使用挂钩悬挂在耳朵之后,将扬声器覆盖于耳道口,通过空气传导声音进入耳道的; \n夹在耳垂上,耳机本身不覆盖或仅部分覆盖耳道口,通过侧向开口将声音导向耳道的。 \n \nSoundCore C30i 属于后者。
\n
\n我选择的是透明外壳的款式,耳机内部结构清晰可见。
\n左边耳机的扬声器处有一个开口,这便是朝向耳道的声音出口。
\n右耳朝上的是耳机背面,金色的圆片是触控传感器。
\n佩戴感 夹耳的耳机其实还可以再分类。一种是本体柔软、有弹力,有点像悬挂在耳朵上的,将扬声器更多地覆盖于耳道口,以提升传音效率的耳机。
\nC30i 本体则不可以弯曲,采用虎口般的结构卡在耳垂上。在佩戴时需要找到耳垂比较薄的部分,将耳机从那里套入耳垂,再将耳机推向耳垂较厚的地方,就能牢固固定。看似还是有些松松垮垮,但因为耳机足够轻,不容易被甩掉。
\n我的感觉是,佩戴上没有什么异物感,但难以做到所谓“戴久了会忘记它的存在”这么夸张。
\n我尝试连续佩戴了两个多小时,期间耳朵没有任何被堵塞的感觉,且取下后耳朵没有丝毫的潮湿感。确实是完全的开放了!
\n音质 我的评价是:出奇的好。
\n因为是一种妥协,我对耳机的音质便不抱任何希望。实际听起来,虽然也有这非封闭式耳机缺乏低音等问题,其实音质相当不错。
\n
\n通过调整 EQ 配置,将高低频都拉高(经典两头高调法),效果更是好了许多!
\n我也听了一段博客,听清说话人的语音也没有任何困难。
\n多设备连接 我原本买的是飞利浦的一款开放式耳机,到手之后才发现耳机没有双设备连接的功能。我平常有在手机和电脑之间切换使用的需求,少了这个功能实在不能接受。
\nC30i 支持同时连接两台设备,可以在两台设备间无缝切换。
\n续航 商品说明上标明了耳机单次续航是 10 个小时,这应该是相当厉害的了。加上充电盒,总共可以提供 30 个小时的续航。
\n实际用下来,在 2 小时的使用后,耳机的电量没有明显的减少。续航应当是很强劲的。
\n缺点 目前我看到的一个缺点是,耳机缺少自动休眠功能。将充电盒打开,耳机便会连接所有设备,摘下后仍保持着连接。直到耳机放回充电盒,并盖上盖子后,耳机才会关机。
\n且耳机缺少佩戴检测,摘下后不会停止音乐播放。似乎耳夹式耳机比较少有这个功能。
\n这可能会抢占了一些设备的音频输出,不过不算是很大的缺点。
\n总结 SoundCore C30i 有效解决了我耳道容易发炎的痛点,音质不错,且有多设备连接的功能,满足了作为我主力使用的耳机的所有需求。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/adc5a61e.html",
+ "url": "https://blog.udon.eu.org/archives/adc5a61e.html",
+ "title": "日本之行-第二站-奈良",
+ "date_published": "2024-03-10T16:30:00.000Z",
+ "content_html": "写在前面 23年12月底,我踏上了前往日本的旅途。这是我第一次去日本,是我第一次出国旅行,且只身一人。
\n本次旅行为14天,我将分多个章节完成所有的游记,记录下我旅行中的点点滴滴、各种感想。这是第二章节,奈良之旅。
\n近铁、巴士与鹿
\n奈良距离大阪仅有 30km,乘坐近铁奈良线,摇摇晃晃一个多小时便能到达奈良。
\n这条线路有分区间准急、准急、急行、快速急行四个速度,每个速度停靠的站点数量不同,票价都相同的。如果想尽快到达奈良,乘坐快速急性是最好的选择。
\n这天,我纯凭心情来到近铁难波站,坐上了最近一班开往奈良的近铁,似乎是急行。
\n近铁的运行速度不是很快,叮叮当当地在楼房间穿行着。
\n在近铁奈良站,奈良线的终点站下了车,距离旅馆所在的奈良公园还有一段距离。由于拖着行李,我选择搭乘公交车。
\n公交车到站后会向站台一侧倾斜车身,方便乘客上下;上车的门并不固定,可以看车身的贴纸判断;司机会很耐心地等待所有人上车、落座之后才摆正车身、继续前进。
\n有一些线路是分段计费的,就需要在上车的时候领取一张整理卷,或者先刷一下卡,在下车的时候将钱和整理券一起投入投币机,或者刷一下公交卡就可以了。
\n在每一个座位的旁边都有一个按钮,如果下一站要下车,按一下就会提醒司机,并且在车头的显示屏上也会显示,车上的所有按钮都会亮起。在公交车到站停稳之前无需先起身,车子停稳后司机也会很耐心地等大家付钱、下车,还会一个一个道谢哦。
\n相比于地铁,公交的到站时间显得没有那么准确,但不会差太多。在地面运行的公交会受到交通的影响,等候乘客上下车、付钱有时候也会用掉不少的时间,能做到按照时间表运行已经相当厉害了。
\n
\n虽然在车上就有看到,果然很多啊 —— 奈良的鹿。
\n奈良的鹿是散养的,主要集中在奈良公园和春日大社。在奈良设有鹿苑,将受伤或者发情的鹿收养,待它们恢复正常再放回乡中散养。
\n从车站走到旅馆,一路上都有鹿儿好奇地打量着来自异国他乡的我。
\n每走一段距离,能看到提示观光客不要“挑逗”鹿儿的提示牌,有些鹿的性格比较差,会用头顶你,追着你不放的。好在大部分鹿都去掉了角,不至于顶伤人。
\n
\n今天的旅馆确实有些简陋 —— 私人活动空间仅有这么一小间,洗浴和方便都是公用的。不过旅店的氛围很好,有一个共用的客厅,客人们可以在这里吹暖气(冬天还是挺冷的)、喝饮料、聊天。
\n旅店的工作人员非常热情,替我办理好入住手续后,立刻拿出一张奈良公园的地图,用简单的中文(很厉害哦!)告诉我这附近有哪些好玩的地方。奈良一天的行程就是参考着这张地图安排的。
\n整理好行装,已是傍晚五点多。按照旅店工作人员以及 Google Maps 的说法,奈良公园里的大部分饮食店都要下班了!遂立刻出门,寻找晚饭的地点。
\n因为今天是周五吗,明明还有一些旅客在此游玩的,旅店对面的旅游纪念品店已经关门了!沿街打量了几家店铺,也都在收拾着大堂,准备打烊了,完全不像是会接客的样子。大危机,要没晚饭吃了,得走个几公里处了奈良公园,到达奈良市区才有便利店。
\n路过了一家同样是卖纪念品的商店,发现店内的空间还挺宽敞,也摆着一些桌椅,便问了一下老板还有没有饭吃。运气很好,这家店还没有打烊。
\n
\n可以选择的菜品并不是很多,大多数都是日本的家常菜。我选择了这道绝大部分和食餐馆都会有的家常菜 —— 親子丼。
\n从厨房门帘的缝隙向里望去,可以隐约看见老板在烹饪着饭菜。明明用得都是同样的工具和食材,有技艺的人做出来的东西就能端上桌卖钱呢。
\n蛋是我们所谓“半熟”的柔滑状态,鸡肉有一些小烧焦,主要的风味是自古以来人们就离不开的味道 —— 咸味。风味不能说突出,却很平和。它就是一碗很普通、很平常,此时此刻会出现在任意一张餐桌上的饭。
\n饱饭后,我根据地图的指引,前往东大寺的二月台,观赏日落之景。
\n
\n
\n
\n
\n只用手机相机无法捕捉暗光下的美景。
\n夕阳柔和而昏暗地照着二月台,四周一片静寂,只能听见洗手亭的潺潺水声。在日落后还停留在奈良公园的游客寥寥,大家都保持着绝对的安静,共享这片难得的静谧。
\n在台上驻足数分钟,我轻轻踏着脚步走下楼梯,沿着寺院的小路漫步着,离开了东大寺。
\n此时天已完全黑了,不论是寺院还是神社都已关门,我就这么在奈良公园里漫步着。时不时碰见一群鹿,便从口袋中掏出一块鹿仙贝,掰给鹿儿吃。
\n
\n会旅店前在路边的售货机买了一瓶果汁饮料,是不二家的。日本的饮料会标注果汁含量,我觉得很神奇。
\n
\n第二天,去参观了十分出名的春日大社。不过不恰巧,主殿正在维修,将赛钱箱设置在了原处,只能眺望主殿。
\n在巫女那儿买了点纪念商品,是两只鹿型的小玩偶。一只是木质的,一只是陶制的,鹿儿的嘴中叼着签。忘记留下照片了。
\n
\n离开主殿,走上铺着碎石的小道。
\n
\n每走几步,便会出现一间迷你神社,感觉十分奇妙。在这片林海中不知道供奉着多少的神明。
\n有些人也许是提前来做新年参拜,途中遇到的神社都会十分虔诚地参拜。
\n在林间漫步了将近一个小时,吸饱了新鲜的空气,我振奋精神,回旅馆取了行李,准备离开奈良。
\n
\n拖着行李箱漫步在奈良公园里,我又一次路过了若草山。
\n若草山也不高,在满是丘陵的福建甚至都算不上山,只是个小土坡。山上的草长得格外整齐,是一座越看越顺眼的山呢。
\n若草山每年12月初开始封山,直到第二年三月举行烧山仪式后才重新开放。没能上去走走真是可惜。
\n
\n在前往近铁奈良站离开奈良前,我路过了一家在网上有着不少讨论的柿叶寿司店,便决定在这里解决午饭。
\n柿叶寿司其实就是用柿子叶将寿司包起来烹饪。据说柿子叶不仅可以杀菌消毒,还能给寿司提升风味。
\n想着,既然是奈良的特色菜,肯定要好好品尝一下。我点了一份 2500円 的大套餐,奢侈一下~
\n左下角的五枚寿司,上方两枚便是柿叶寿司了。下方三角形的寿司也是用不能吃的叶子包着,但好像不是柿子叶;右侧是用腌制过的紫苏叶包的寿司,紫苏叶是可以吃的,风味很独特,我很喜欢;左侧是茶味的卷寿司,也是第一次吃。
\n柿叶寿司用的是腌制过的鱼,确实带有叶子的香味,很有特色!
\n除此之外,套餐内还有精致的小菜、一份天妇罗拼盘和一碗素面。吃的我好饱~
\n
\n乘坐上前往京都的特急电车时还发生了一些小插曲。
\n坐上车后我才意识到自己坐的是特急的电车,进站时我只刷了基本票,没有购买特急卷。
\n好在列车上有通过扫描二维码购买特急卷的渠道,也支持借记卡付款。在等待列车发车时,我赶紧买了特急卷,带着行李移动到了指定座位,正式踏上前往京都的旅途。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/a7050149.html",
+ "url": "https://blog.udon.eu.org/archives/a7050149.html",
+ "title": "23 年对我影响最大的硬件与软件",
+ "date_published": "2024-03-02T12:30:00.000Z",
+ "content_html": "原文投稿在自留地频道的新年活动里。
\n对于我来说,23 年给我带来影响最大的莫过于 Meta Quest 3 与 VRChat 了。
\nQuest 3
\nQuest 3 在 23 年 6 月面世,于 9 月 28 日开放预购。我早在数年前就有了尝试 VR 的念头,在经过一天的调查研究后确定了 Quest 3 不会踩坑,便在日亚下了单,直邮回国。日亚上标价为 78k 円,日本商品出口不收税,再加上直邮的运费和中国的关税,最后到手的价格和标价差不多。(顺便一提,现在日亚也可以以相同的价格直邮国内)
\n外观我就不展示了,网上的图很多。到手第一件事,便是尝试它对于上一代产品相比提升最大的地方——显示与穿透(Pass Through)。
\n显示效果 显示效果上,Quest 3 采用了 Pancake 棱镜,甜点位置(Sweet Spot)相对于上一代更大,也就是说仅需简单调整头戴的位置,便能找到画面最为清晰的位置,我的使用体验上感觉也是如此,每次佩戴上头显很快就能调整好画面。
\n穿透 (Pass Through)
\n穿透方面,由于我没有用过 Quest 2、Quest Pro 等拥有穿透功能的 VR 头显,无法进行比较。图片中看起来比较糊,是因为我用了 Quest 自带的截图功能,分辨率很低。实际观感上,在光线充足的环境中可以毫无压力地辨认各种物体,但细小的字是看不清楚的。戴着 Quest 使用手机并不是很舒服,因为物体距离头显过近会因摄像头视角的问题产生扭曲,看字会很吃力。将手机拿远,又会因为显示清晰度不够而看不清字。不过听说 Quest 2 的穿透是黑白且十分模糊,Quest Pro 也好不了很多,Quest 3 在这个方面应是有十足的进步。(似乎离 avp 还有很大的差距)
\n \n相比于头显中不带有处理芯片的 VR 眼镜,Quest 可以使用无线串流软件将 PCVR 的画面投影至头显中显示,再也不用担心玩游戏时绊倒数据线了。
\n总而言之,Quest 3 是一副功能完善,各方面实力均不弱的“六边形战士”,但毕竟定位是廉价头显,也就缺少了 Quest Pro 的自定位手柄、眼部追踪,Valve Index 的基站定位、手部追踪,更没有 Apple Vision Pro 的高 ppi 显示屏、低延迟的穿透与精准的手势识别。但综合来看,Quest 3 的性价比高,适合刚刚步入虚拟世界,想要体验各式各样 VR 游戏的人购买。
\nVRChat 谈到 VR 游戏,有人会想到 Half-Life:Alyx,有人会说起(已经凉掉的) Beat Saber。根据 SteamDB 的数据,此刻在线数量最多的 VR 端游戏还得是 VRChat(主要为 PC 端的战争雷霆不算)。
\n在我看来,VRChat 里有大概有四类人。
\n第一类人:虚拟世界的旅行家
\n有些人想“逃避现实”,来到虚拟世界欣赏美景、转换心情。在 VRChat 里有各种各样风格的地图:有些风景大好、音乐舒缓,适合在快节奏的生活之余找到一个宁静之地放松紧绷的精神;有的地图灯红酒绿,如果在夜晚你还是激情满满,不妨来这里听听虚拟 DJ 的表演,大家一起蹦迪、饮酒;甚至还有环境昏暗、一片寂静,十分适合睡眠休息的卧室地图,虽然深处异地,也能在虚拟世界里与好友共眠,在清晨醒来的那刻发现自己的眼前并不是早已习惯了的天花板,耳边是仍在熟睡的友人的呼吸声,将会是一种全新的体验。
\n第二类人:虚拟世界的摄影师
\n也有些人想在虚拟世界里做一个摄影师。美景的照片中怎么能少了美人,如果现实中找不到美人,那就自己来扮吧(心死)。VRChat 中使用的人物模型可以由玩家自行上传,如果你恰巧会使用 Unity 与 Blender,便可以为自己量身定制一个人物模型;如果不会也没有关系,在 Booth 上有许多预制好的人物模型与衣服,只要按照教程将其组合,便可打造出你心目中的理想形象(不管男女)。为了拍出满意的照片,你会主动去学习各种各样的新技能:为了调整照片的后期效果,我就学习了 Lightroom 的使用。
\n第三类人:人,不过是在虚拟世界里
\n这或许才是 VRChat 的核心内容 —— 当然是和朋友聊天啦。在 VRChat 中有一些专门用于聊天、交友的地图,例如以中文为主的“中文吧”;也有一些比较热门的地图会聚集起各个语言的人群一起聊天,例如“Japan Shrine[spring]”,一张风景优美的日本神社地图;更有各种各样以个人或小团体主办的咖啡厅、运动吧、跳舞房、游戏房等着你来加入。也许你和我一样有些小“社恐”,在现实生活中不大善于和陌生人交际,不妨试试在 VRChat 中,与素未谋面但已经熟络的朋友,或是围坐在篝火旁闲聊,或是在电闪雷鸣、乌云滚滚的夜空中乘坐飞机探险,相信这对你一定会给你带来从所未有的新鲜感。
\n第四类人:OOOO 还有一类是搞色色的,就不多说了。
\n以上只是从我的眼中看到的 VRChat。一千个人的心中有一千个哈姆雷特,你的 VRChat 又将会是什么样的呢?
\n \n自从 23 年 10 月 3 日加入 VRChat 以来,我学会了怎么修改模型、如何使用全身追踪让自己在游戏中灵活运动;尝试在跳舞房里学习舞蹈,在 KTV 房里与朋友唱歌,与来自全球的人使用中、英、日闲聊;在 VRChat 中结交了新的朋友,在 X 上发布了拍摄的照片。VRChat 确确实实已经融入了我的生活,仿佛在另一个世界塑造了另一个不同的我。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/a534d51a.html",
+ "url": "https://blog.udon.eu.org/archives/a534d51a.html",
+ "title": "旅行与军粮",
+ "date_published": "2024-02-12T14:30:00.000Z",
+ "content_html": "军粮的特点 1. 便携 一般来说,军粮都会被较为紧凑地包装,以便于大批量运输与随身携带。如确认将有一餐饭需要在外解决,可以在出发时往包里塞上一份军粮。
\n2. 分量足 军粮的营养构成一般都是按照一个运动量中上的成年男性一餐所需要的卡路里来设计的。因此,即使不能完全饱腹,其所含的营养足够填补在外游玩半天空空的肚子了。
\n3. 便于烹饪 相比于需要加入开水的方便食品、甚至是冻干食品,军粮一般自带加热设备,仅需冷水,甚至不需要水都可以加热食物。若是去荒郊野岭的地方露营,除非有携带便携式汽炉,想获取到热水是比较难的,这时候有自加热的军粮会很方便。
\n俄罗斯单兵口粮普餐
\n这次买到的6号餐谱,包含五包干粮、牛肉丸、牛肉荞麦饭、牛肉炖豆角、牛肝酱、午餐肉、蔬菜丁罐头以及饮料、果酱、零食等散件,还有一套由三个燃料药丸、一个铝制支架和几支防风火柴组成的明火加热套装。
\n
\n旅行的第一天是登山,午餐便携带了部分的食物作为午餐。
\n有些小雨,便找了一处有雨遮的地方开始准备午饭。
\n
\n将铝板弯曲成炉子的形状,在炉子的中央放上燃料药丸,再用防风火柴点燃,便是一个功能完备的加热装置。
\n小贴士,我试着用火柴从上方点燃药丸,尝试了两次都失败了。将火柴放在炉子里,然后将药丸放在火柴上,便能很轻易地将其点燃。
\n
\n点燃固体燃料后,将盛有食物的铝制罐子放在火上直接加热即可。如果有带铝或者钛制的杯子,也可以直接放在火上烧水,泡些饮料喝。
\n
\n在等待罐头加热时,我先掏出了干粮与蘸酱。除了照片里的芝士与肝酱,还有一包苹果果酱。
\n芝士的味道与常见的芝士片如出一辙,在我看来稍微淡了些,因此也比较适合直接吃。
\n牛肝酱则是特别的腥,单独吃我有些吃不来,和着芝士与果酱吃味道倒是还不错。
\n拿出一片干粮(其实就是苏打饼干),涂上果酱,挖一勺芝士,再放上一点点牛肝酱,一口塞进嘴里,味道还算不错。
\n饼干有些干硬,嚼起来有些费劲儿,需要配水。
\n最后,完吃完了一份饼干、一盒芝士和一包果酱,牛肝酱剩下了一大半。
\n
\n接下来是牛肉丸。看到红色的外表就能猜到是罗宋汤风味的。
\n应该是罐藏的缘故吧,牛肉已经泡得很软了,吸收了罗宋汤酸酸甜甜的风味,味道还算不错。就是牛肉味已经不是很浓,能吃得出来是肉,但没有什么特殊的肉的风味。
\n顺便一提,用这种炉子加热,铝盒子受热举起不均匀,中间已经滚烫,但四周还是冰的,需要多多翻搅。
\n
\n从外表看不出来里面有啥,红红的肯定也是罗宋汤的味道。里头的蔬菜主要是胡萝卜,也有青椒、青豆等蔬菜。但这一碗的味道就不好了,青椒的味道和西红柿(或者是甜菜)的酸甜并不搭。
\n把饼干蘸着牛肉丸的汤汁吃,味道也不错。涂涂果酱、涂涂芝士,饼干便吃完了一包。
\n又吃了点麻麻那边的自热口粮,便已饱腹,我的食量确实不大啊。
\n
\n这一餐其实只吃了整套餐的 1/3 左右,剩下的量再吃两次正餐不成问题。
\n回到家后,又拿出了些零食品尝了一下。
\n
\n来自俄罗斯的巧克力棒,偏甜,一股代可可脂的廉价感,属于不大好吃的巧克力。
\n
\n右上角是速溶咖啡,右下角是奶粉,左边的一大包是糖。糖的量很大,明显不止是一次饮料的量,也可以放在茶里喝。
\n咖啡很苦,一股烧焦味,不好喝。奶粉大概也是植脂末吧,没什么奶味。
\n剩下的食品我分成了两餐解决。
\n
\n第一餐的搭配是:牛肉荞麦饭、肥肉罐头和干粮(饼干)。
\n
\n也许我加热的还不够,但考虑到在野地里使用便携式炉子加热的能力,士兵们能加热到中间完全热乎,周围有些凉是平均水平了。荞麦饭很硬,风味也不是很好,除了咸味和一点牛肉味,尝不出别的滋味了。加了一些套餐内的黑胡椒粉,才改善了一些风味。
\n不过这一大碗饭确实很能填饱肚子,适合放在午餐食用。
\n肥肉罐头里自然是盐腌风味的很肥的肉啦。味道我还挺喜欢的,肥肥的肉很好吃,十分下饭。
\n
\n第二餐的搭配是:牛肉煮豆子、午餐肉、苹果泥、酱牛肉(来自国产 MRE)和干粮(饼干)。
\n
\n牛肉煮豆子用的会是什么豆呢,豌豆吗?啊,原来是黄豆。
\n并没有延续牛肉丸、蔬菜罐头的罗宋汤风味,只是单纯的咸味,不过味道我还挺喜欢的。
\n牛肉其实不多,就一大块带筋、软烂的牛肉,剩下都是碎块。除了黄豆外还有一些胡萝卜。
\n午餐肉很小一罐,但确实是肉泥,淀粉含量并不多,味道也不错。
\n苹果泥是我整个套餐中最喜欢的一样食物了。口感绵密,酸甜度适中,汁水十足。配合干粮一起吃,很大地改善了干粮过干、过硬的缺点。而且好大一罐,吃得很满足。使得这一餐中我干掉了两袋干粮。
\n国内的军粮 —— 以北戴河的自热口粮为例 09式单兵自热口粮我吃过几份,13式这种两餐包装在一起的也吃过一次。
\n这次选购的是北戴河生产的自热口粮。虽然不是军品,但北戴河前身是军工厂,产品会比较接近军品吧。包装风格与09式也很相像。
\n
\n那天感冒的发烧刚退,人还比较虚弱。住的度假酒店位置比较偏,不想跑太远去吃晚饭,便取了车上的自热口粮回房间吃。
\n我吃过太多次了,便没有拍照片,下面就口述一下感受。
\n相比于09式单兵自热口粮,北戴河出品的民用口粮内容少了很多 —— 没有了耐贮蛋糕、糖水黄桃/菠萝、调味辣椒酱和固体饮料,只有一份主食,一份配菜(酱牛肉和午餐肉二选一)和一份小菜(榨菜)。
\n09式的餐谱相当多样,有 12 个餐谱,有炒面也有炒饭,也有一些主食是素食的,满足部分人群的需求。
\n北戴河的仅有三种餐谱,且都是荤食的炒饭。
\n主食包外套着加热袋,用注水袋取对应量的水(生水、脏水皆可)倒入加热包即可完成主食的加热。
\n味道上,我认为北戴河做得是不如当年的09式的。饭总体都偏咸,但肉给的十分足。
\n但相对于俄罗斯的军粮,不知道是口味上有着主场的优势,还是俄罗斯人烹饪技术真的不佳,还是中国的军粮更胜一筹。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/7777583b.html",
+ "url": "https://blog.udon.eu.org/archives/7777583b.html",
+ "title": "被我整坏的路由器和服务器",
+ "date_published": "2024-02-12T10:00:00.000Z",
+ "content_html": "为了搭建 PalWorld 的私服,我又一次踏上了折腾之旅,并成功将路由器和家里的服务器都搞崩了。
\n好在最后两台设备都恢复如初。我就想来做一下这次“事故”的复盘。
\n我的设备 本次操作的有两台设备,一台是 x86 软路由,运行 Openwrt;一台是配置比较奇怪的 x86 服务器,运行 PVE。
\nOpenwrt 的网络隔离配置 说在前头,我对 Openwrt 的操作真的是一窍不通。网络相关的知识兴许懂一些,但一到实操环境就纰漏百出了。
\n由于 PalWorld 服务端启动后便会在游戏的服务器列表公开自己的 IP,且无法关闭这个功能,有比较大的安全隐患。在配置好 PalWorld 服务端所运行的虚拟机后,我决定将这台虚拟机的网络与局域网内其他设备隔离。这也是所有折腾的起源。
\n配置 VLAN 导致的失联 我能想到的第一个方案便是,给这台虚拟机分配一个与当前局域网不同的网段,并将两个网段隔离。
\nVLAN 我还是知道的,便着手开始创建新的 VLAN,并保存…连不上路由器了。
\n急了啊,创建了 VLAN 就代表开启网桥的 VLAN 过滤。目前这个 VLAN 没有分配给任何一个接口,自然是啥都连不上了。好在 Openwrt 有自动回滚的功能,90 秒若设备连不上路由器,便会将刚刚的更改回滚…但不是每次都能成功。
\n有反复折腾了几次:创建 VLAN、创建接口、创建防火墙的 Zone,分配来分配去,中途也搞砸了几次,配置也都回滚了,直到最后一次尝试,luci 弹出了令人感到安心的“正在回滚配置”的提示,然后就…卡住了。
\n考虑到 Openwrt 使用的是 squashfs,怀疑是设备上的设置确实回滚了,但内存中的配置没能正确地回去,我将设备断电重启。果不其然,配置回到了应用前的样子。
\n在尝试配置 VLAN 数次后,我放弃了。即使给虚拟机分配了新网段,也设置好了防火墙规则,虚拟机依旧可以访问到内网网段的设备。
\n目前我还没有闲心思慢慢研究 Openwrt 的种种配置,便打算换一个方式实现。
\n配置 PVE 防火墙 第二个方案便是,在 PVE 的防火墙中禁止虚拟机连接内部网段的其他设备了。
\n启动 Datacenter 防火墙但没有添加允许规则导致的失联 我启用了虚拟机的防火墙,发现配置并没有生效。简单查询后发现,需要将 Datacenter 的防火墙启用,VM 防火墙才有效果。我便看也没看地就开启了 Datacenter 防火墙,发现管理后台页面无法刷新了。此时我才看到屏幕上 Datacenter 防火墙的默认配置 —— IN: DROP.
\n这下好了,外部连接统统被阻断,也就无谈通过控制面板将防火墙再关闭。
\n这台服务器是无头的,安装有一张 P100 显卡,但没有显示输出。所以在不动硬件的情况下,我没法通过显示器访问终端的。
\n通过检索,我了解到了好几种奇技淫巧来关闭 PVE 的防火墙。
\n挂载并修改 crontab 正好手上有一个硬盘盒,我就将系统盘取下,通过硬盘盒连接到了软路由,尝试修改系统盘里的文件。
\n使用 fdisk
查看这块系统盘的分区情况,但没有看到熟悉的 ext4
字样。取而代之的是 Linux LVM
。LVM 相当于是 Linux 对磁盘进行了再一次的分区,因此挂载就不能是简单的 mount
,得用 lvm2
工具。
\n再敲了几个命令后,我成功将 PVE 分区的 root
文件夹挂载,并在 crontab 中添加了关闭 PVE 防火墙相关的命令。
\numount
,取下系统盘并装回服务器,开机…没有任何反应,依旧打不开管理面板。是教程给的方法有误吗?
\n遂又取下盘,挂载到软路由上,却提示该分区忙…我是相当忌惮挂载并修改分区的,生怕损坏了分区,服务器的数据可就全丢了。不敢再继续操作,我得另想出路。
\n连接显示器,但还是个瞎子 现在能做的就是在服务器上通过 PVE 的 rescue terminal,来修改防火墙的配置了。
\n我关闭了服务器,取下 P100,换上了 R7 240 这张十年老兵,插上了便携显示器…
\n\nerror: No suitable video mode found. Booting in blind mode.
\n \n你这不是输出字了吗,怎么就进瞎子模式了???
\n经过查询,我了解到系统正在寻找到显示模式,正是古早电脑终端使用的 80-Column
这样的显示模式。
\n至于什么是 Blind Mode,我尝试在 GRUB 和 Linux 源码中搜索,都没有结果;为什么 PVE 系统找不到我的 R7 240,可能是缺少驱动吧,现在也无处知晓。
\n按照网上的教程尝试在 GRUB 引导系统启动时添加显示模式的支持,并没有效果;当我尝试打印支持的显示模式时,发现根本不存在正常的显示模式,故只能放弃直接启动 PVE 的 rescue terminal.
\n还是得靠救援盘 给服务器插上救援盘,我先是打开了基于 Windows 的 PE 系统,发现显卡是有输出的,但分辨率非常低,且只有黑白画面。看来这张老显卡与这块寨板的相性真的不大好。
\n在确认 Windows PE 系统下什么都做不了,操作还及其不便,我便退出了 PE 系统,打开了 Ubuntu LiveCD。
\n这回,显卡倒是可以正常运行。以 80-Column
模式输出文字还是可以轻松办到的。有了命令行,操作也简单了不少。
\n同为 Linux,操作 LVM 就简单了许多。使用 lvm2
挂载 PVE 的系统分区,并用 chroot
将用户空间切换至 PVE 系统,直接用 systemctl disable pve-firewall
把防火墙关了。
\n再次进入 PVE,这下开机防火墙就不会启动,赶紧进 Datacenter 防火墙设置里还原误操作的配置。
\n事后的反思 VLAN 配置的失误 我对 VLAN 配置没有经验,便想着参考网上其他人的配置方法来做。但我查到的都是创建访客 Wi-Fi 这类的配置教程。与我的网络环境的差异在于,访客 Wi-Fi 用的 Interface 与 LAN 不一样,而我需要在 LAN 下配置两个 VLAN,配置方法就有些不同,不能直接搬配置。
\n我应当要找的是较为通用的,同一个 Interface 下 VLAN 的配置教程(可能单臂路由配置和这个就有些像),再迁移到 Openwrt 上进行配置。
\n由于软路由只有两个网口,也没法像有四五个网口的路由器一样留下一个作为”不死“的管理口,以降低把路由器配置挂掉的风险。
\nPVE 原本可以修得更快 原本在将 PVE 系统盘挂载到软路由时,便可以用 systemd
停掉 pve-firewall
但当时我看到 PVE 论坛里有人说在 crontab 里加上 PVE 关闭防火墙的指令便能访问控制面板了,也有人附和说可以用。但我实际操作后发现并没有效果。
\n而再次尝试挂载 LVM 时,提示分区忙让我不敢继续操作。虽然在服务器上用 LiveCD 直接挂载并没有出现分区忙的情况。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/aca48136.html",
+ "url": "https://blog.udon.eu.org/archives/aca48136.html",
+ "title": "日本之行-第一站-大阪",
+ "date_published": "2024-01-21T15:00:00.000Z",
+ "content_html": "写在前面 23年12月底,我踏上了前往日本的旅途。这是我第一次去日本,是我第一次出国旅行,且只身一人。
\n本次旅行为14天,我将分多个章节完成所有的游记,记录下我旅行中的点点滴滴、各种感想。这是第一章节,出境与大阪之旅。
\n第一次出境 我从上海浦东机场出境。
\n航班预计上午 9:40 起飞,根据网上的经验教训,国际航班建议提早三个小时到达机场办理值机手续。我早早地起了床,搭上了第一班磁浮列车,在六点五十分左右到达了机场。
\n拖着行李箱直奔值机柜台,发现队伍已经很长。排了一个多小时的队才轮到我。
\n我提前在网上进行了值机选座,现场只需要将行李箱托运,打印一下登机牌。工作人员会确认护照和日本 eVisa(电子签证)。
\n我提前将电子产品和需要独立安检的物品取出,安检也十分顺利。
\n不过在过海关的时候,工作人员看到我护照崭新,又是独自一人,不免有些担心,向我索取了酒店预订记录和行程单。酒店预订记录只要出示订购软件的订单界面即可;我没有做行程单,就将记事本里的旅行计划给工作人员看,第一天在哪里啊,过两天又跑去哪里玩…得知我全程的酒店都已订好,行程也安排清楚,便允许我出境了。
\n在候机厅的长椅上坐下时,距离飞机的预定起飞时间只有半个小时了。看来国际航班确实需要早点来机场办手续(虽然最后延误了两个小时)。
\n这是我第二次坐飞机。上一次是我还在读小学的时候,不明不白地上了飞机、又下了飞机,除了耳朵有些不舒服。没有其他的感想。
\n这次,我特地选择了靠窗的位置,仔细观察着飞机的起降与飞行时的动作。看到了起飞和降落时襟翼的展开,看到了遇到乱流时机翼“夸张”的摆动,感叹着这种看似“简易”的机器是如何将一机人送上天。
\n
\n飞机餐的话…上一次坐飞机是国内航线,没有在饭点所以没有供餐,这是我第一次体验飞机餐。
\n
\n没有选择的余地,大家统一都是鱼肉米饭套餐。和我能想象到的飞机餐没有太多区别 —— 一般咸味的鱼肉,煮得软烂的米饭(也可能是再加热的缘故)与味道平平的配菜。坐的毕竟是经济舱,不能期待太高~
\n
\n经过两个半小时的飞行,飞机平稳降落在了关西国际机场。飞机缓慢驶向空桥的途中,我更换了日本的流量卡,给手表切换了时区。
\n入境相对出境更加简单。出发前我就在网页中预先填写好了入境信息表与海关申报表,在对应的窗口或机器扫码即可完成申报。在入境窗口只需出示一下 eVisa,录入一下人脸和指纹就可以了。
\n工作人员会用英语引导你操作,录入机器上也有中文提示。机场的指示牌都是四语的(日语、英文、中文和韩文),按照指示牌走就没有问题。
\n大阪之行 Day 1 - 舒适的酒店与海游馆之旅 APA 酒店 起飞延迟了两个小时,降落也迟了一个多小时。达到关西国际机场已是下午 3:30(这之后都是 UTC+9 时间)。我乘坐南海机场线前往难波站附近的 APA 酒店。
\n插一嘴,在日本用 Google Maps 相当方便,交通工具的规划和信息展示都做得很棒。“通勤案内”这款官方推荐的交通软件我也有下载,不过用的最多的还是 Google Maps。
\n等地铁、电车、新干线时,只要看 Google Maps 上的到站时间,到了哪辆车就上哪辆车。除非两个方向的车同时到站,否则是绝对不会坐错方向的。
\n
\nAPA 酒店大楼的装修很有特色,在远处就能看到橙色的屋顶和 APA 的标志,而且在全国的风格都是统一的。
\n
\n内部的装横令我十分惊喜:房间不是很大,但各种设施一应俱全,在床铺正对的墙上甚至有一台大屏电视。浴室毫不意外地配备了浴缸,还有定量放水系统,不用盯着浴缸有没有水漫金山了。寝具也相当的舒服,那两晚都睡得很好。
\n对于一间一晚 300CNY 左右的旅馆,能有这样的体验我十分满意。
\n咖喱与牛排 办好入住手续,放下行李,已是五点多。飞机餐的显然填不饱我的肚子,我早已饥肠辘辘,是时候出门觅食了。
\n大阪海游馆是我计划中必去的一站,但它位于海港村,和其他景点在相反的方向,便安排在今晚游玩了。
\n日本人很奇特的一个习惯出现了:有些店铺傍晚 6:00 到 6:30 就收摊了,最晚的会开到七点八点左右,再迟就只剩下营业到深夜的家庭餐厅与居酒屋。
\n用“食べログ (tabelog)”这款软件在海游馆附近搜索评价比较高的店铺。
\n在海游馆隔壁有一个小小的综合体,里面有一些店铺还有在营业。我选择了这家评价很高的牛排咖喱餐馆。
\n
\n一份牛排咖喱饭、一碟酱菜、一碗味增汤。价格有些忘记了,在 1500円 左右。
\n日本人很喜欢吃这种细长的青椒,不会辣,在天妇罗中也会出现。
\n咖喱的甜口的,味道自然比百梦多咖喱块要好上数倍。牛肉很嫩,没有怎么调味,展现的是肉本身的鲜味。总之,相当的美味。
\n对了,需要使用现金哦~ 商家没有准备 POS 机。
\n大阪海游馆 已经记不清上一次去水族馆是多少年前。听说大阪海游馆的设施很棒,场馆也很大,我就来体验一下。
\n门票的价格是 2600 円,入场有分时段,我就在售票处购买了当前时段的票。
\n海游馆很大,步行参观的总距离在 1km 左右,按照地理位置划分成了好几个区域。
\n
\n进入场馆便是一个巨大的拱形水箱。是玻璃比较薄吗,还是用了什么技术,几乎看不见玻璃带来的重影,鱼仿佛真的在空中悬浮。
\n
\n一群正在睡觉的海狮。抬着头睡觉不会落枕么。
\n![巨型水箱]/images/2024-01-21/(9.jpeg)
\n在场馆的中央有一个巨大的水箱,有两头鲸鲨、几头锤头鲨和许多小鱼生活在其中。
\n
\n还有好多可爱的花园鳗,黄色的尤其可爱~
\n除此之外,还有来自各个大洲的鱼、南极的企鹅,在地下还有水母馆。
\n总共逛了一个多小时,可以说大饱眼福了。
\n
\n在海游馆的隔壁是天保山摩天轮,夜晚被彩色的灯光照亮特别好看。
\n此时已是 19:30,经历了一天奔波的我有些劳累,便回到酒店养精蓄然,计划第二天的行程。
\nDay 2 - 天守阁、天满宫、天筋桥与大阪烧
\n在日本的第一顿早餐,在酒店隔壁的参观享用了自助餐。
\n炒蛋和上次去香港在澳洲牛奶公司吃到的有点像,并没有完全做熟,非常的软嫩~
\n茶泡饭就很有日本的特色了。
\n饭后,我就前往难波站附近的旅游中心,购买大阪周游卡。我选择的是二日卡,价格是 3600 円。在两天内,可以免费乘坐大阪地铁与巴士(相较于一日卡,不能乘坐私铁),以及免费参观好多景点。
\n大阪地铁网络十分发达,基本覆盖了所有想去的地方,这两天我没有搭乘过私铁或者巴士,因此不用担心是否要选择一日卡。
\n买到卡之后,第一站便是天守阁。
\n
\n从外边看,与只狼里的苇名城有几分相像。
\n
\n天守阁内陈列着许多颇有历史的物件,只可惜我对日本的历史并不熟悉,也看不太懂书法家的笔墨,只是走马观花感受一下文化的氛围。
\n
\n最后在楼顶吹了吹风,便离开了天守阁。
\n
\n虽然有些冷,但天气真的很好,万里无云。下一站是天神筋桥,一个商业街。
\n
\n日本有很多 OOばし(桥)这样的地名呢。也有很多像天神筋桥这样,上方覆盖着遮雨棚的商业街,天神筋桥是其中最长的一条,从一丁目延伸到七丁目,光是主干就有 2.6 km,更有密密麻麻的小巷。
\n乘坐堺筋线,在天神筋桥三丁目下了车。我先是去拜访了天满宫。
\n
\n似乎内部在翻修,将赛钱箱放到了外边供大家参拜。
\n在这里,我给身边参加考研的人做了参拜,希望他们可以拿到好成绩。
\n接着,我便从二丁目开始,一路边逛边思考着午饭的去处,寻找着吃饭的店铺。
\n沿途,看到了大排长龙的可乐饼摊子,转了一圈再想回来买,发现已经卖完收摊了。有一家天妇罗的店门口也站着好多人,大排长龙。肚子好饿,肯定排不了这么长的队。
\n边走边用 食べログ 搜索着。来大阪就得吃些有大阪特色的,那就是大阪烧了。
\n
\n并不在主街,而是藏在小巷子里的千草大阪烧,似乎是 食べログ 23年的百大名店呢。
\n我选择了以店铺名字所称的招牌菜 —— 千草大阪烧。
\n核心是一大片厚切的猪肉,在上下两面都倒上面粉与卷心菜混合的泥,便开始煎烤。
\n接待我的服务员会一些简单的英语,告诉我等着他们来翻面就好了。当地人也许会选择以自己的喜好来摊大阪烧,我作为门外汉只要静静欣赏就好。
\n当两面都煎至金黄,便会涂上大阪烧酱、沙拉酱和黄芥末酱,撒上不知道是什么的籽,就可以享用。
\n大阪烧整体的口感是软糯的,夹心的猪肉排很嫩,肉汁十足。
\n唯一可惜的是,量实在有些少,填不饱我的肚子啊~
\n
\n饭后继续在天神筋桥闲逛,发现了一家鲷鱼烧店。我还以为鲷鱼烧是软软的,但实际外壳是偏脆的,甜甜的红豆馅十分美味。
\n今天真的走了好久的路,对于第一次来到异国他乡的人可以说是有些得意忘形。从天神筋桥二丁目逛到六丁目,又折返了回来。中途还去 melonbooks 看了一圈,又跑到周游卡可以免门票的天王寺动物园里逛了逛…还没逛完,人就开始有些不舒服了。
\n在动物园里稍微休息了一下,我还是决定吃完晚饭就回酒店休息。
\n
\n一次比较“失败”的体验。不要误会,寿司还是很好吃的,鱼类十分新鲜,但我一不小心就在 iPad 上点了太多的寿司,都吃进肚子之后已经很饱了,完全没有在回转的转盘上取过寿司!这和普通的寿司店不就没差别了吗!
\n
\n回转寿司店出门便是热闹的道顿堀,然而我不是喜好这一口的人。路过蟹道乐,周围停着好几台旅游大巴,店门口密密麻麻的全部都是在等待的游客,大约有数百人。真是疯狂呐。
\n受不住喧嚣,我在附近的药店买了一支体温计和一盒退烧药,便回了酒店。
\n躺床休息了一阵子再测体温已经正常,看来真的是疲劳导致的体温失调。之后的旅途安排就宽松一些吧。
\nDay 3 - 梅田蓝天大厦、天空美术馆与一兰拉面 睡了一个好觉,但人还是有些疲劳。前往异国他乡果然不能过于放肆,得做好身体的管理。今天就悠闲地度过吧!
\n
\n在酒店寄存行李后,搭乘地铁来到了梅田蓝天大厦。从三楼有快速电梯可以通往顶楼,然后乘坐扶梯来到屋顶的圆形观景台。
\n对了,有周游卡门票免费哦~
\n
\n
\n相比于其他观景塔,梅田蓝天大厦不算很高,但好在有开放式的观景台,视野特别棒。今天风有些大,在楼顶不是很站得住,欣赏了几分钟美景便回到了室内,点了一杯咖啡,坐在窗边静静欣赏着窗外景色。
\n梅田蓝天大厦外侧是镜面玻璃,倒映着天空、与天空融为一体,因此称作蓝天大厦。尼尔:机械纪元的开发公司白金工作室的总部就在这栋大楼里。
\n
\n
\n我还顺道参观了另一座楼的天空美术馆,虽然不怎么懂艺术,但画作依旧能感染我。
\n参观美术馆后,已是正午。该去找吃的了~
\n
\n百闻不如一见,我来品尝一兰了。
\n每个人第一次来吃日式拉面都要面临一场“大考” —— 单应该怎么点。(背面有中文,我填完了才发现)
\n除了在点餐机器上确认要吃东西,在排位时服务员会给你一张纸片,让你选择拉面的喜好。
\n浓郁度我选择了加浓,确实有些过浓过咸了。吃着相当地过瘾,豚骨的香味充满嘴巴,真的很幸福。如果平常吃得比较清淡,正常的浓郁度可能就有些偏咸了,可以考虑减少一些浓郁度。
\n油脂的丰富度我也加了一档,但感觉不是特别明显。可能是看到日本有一种表面铺满油渣的拉面,一兰的面在油脂的方面反而显得“寡淡”了吧。
\n其余的选项便是看个人喜好。一兰的辣椒粉不会很辣,加 1/2 倍感受不出辣味,但可以提升香味。
\n
\n交卷后,一碗一兰拉面,四片叉烧和一颗盐味溏心蛋上桌啦。
\n一兰号称在面出锅后15秒内就会传递到食客的面前,以体验拉面最新鲜的味道。我赶忙用手机拍了张照,便开始享用。
\n拉面和我之前吃过的感觉都不大一样,特别有筋道,麦香味也很浓。在浓厚的汤汁里蘸一下就有了豚骨的鲜味,没几口就把拉面吃完了,便又加了半份面。
\n上溏心蛋的时候同时给了个小碟子,如果蛋不好剥或者咸味不合适,可以让服务员帮忙换一个。
\n一口拉面,配上一口汤汁。再咬一口叉烧、一口糖心蛋,至福啊。
\n前往奈良 饭后,回酒店取了行李,便是去难波站坐近铁奈良线,前往奈良了。
\n一点感想 语言不是问题,打招呼、点菜这种比较简单的对话就用手势与塑料日语,更复杂的对话用英语就好了。碰到的酒店前台经理、便利店员工、车站管理员都是会英语的。再不济,就用翻译软件吧,能达意就行~
\n至于习惯问题,按照当地人的做法来做就好了。在公共场合,例如楼梯应该走哪个方向,等地铁的时候应当怎么站,在地铁上有哪些地方不能使用手机,都有明确的标识和多语言的提示。多留意,照着做,就不会有事儿啦。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/2ef8bd61.html",
+ "url": "https://blog.udon.eu.org/archives/2ef8bd61.html",
+ "title": "深圳-香港三日行",
+ "date_published": "2023-12-19T14:00:00.000Z",
+ "content_html": "出行原因 其实我很早就有去香港看看的念头,但有时间出游的机会并不多(机会很多,是我比较懒,更喜欢宅家),一直没去成。
\n前一段时间,在朋友的帮助下我注册了英国 Wise 账户,拿到了两张漂亮的实体卡,但后期如何入金成了个问题。了解到可以通过香港账户低损耗入金 Wise,我便有了前往香港开户的念头。
\n12 月底我将独自前往日本游玩,但这将是我第一次独自一人出去旅游。第一次单人旅行还是出国旅行,不禁让我有点担心。
\n在这两个背景的驱使下,我迅速定下了这次深圳-香港的旅行,既可以前去开户,也可以作为出国旅行的预演,提前暴露一些问题。
\n流水账 Day 1 - Thu - 深圳 第一天中午到的深圳。我在前往酒店的半路上下了地铁,前往名为“臻品鲜粿·粿条世家”的店铺吃午餐。
\n
\n粿条本身没有很特殊,有点细河粉的感觉。不过八成熟的牛肉非常非常的嫩,味道很棒。
\n
\n炸豆腐是赠送的,第一眼并太高的期望。但一口咬下,十分软绵的里芯呼之欲出,佐以略微有些咸的沾汁,味道很棒。
\n
\n我还点了一份长得有点像海蛎煎的东西,记不得名字了。应该是用油炸的,比海蛎煎更加酥脆。
\n美美地享用午饭后,我继续登上地铁,前往酒店。
\n下榻酒店后,我前去参观深圳世界之窗景区…总之就是很后悔。
\n比起人造景观,我还是更喜欢自然景观一些。世界之窗没有给我带来惊喜,只有满满的失望。今天是周四,许多游乐设施都没有开放,可以参观的仅有一个个微缩复原的建筑物。
\n在世界之窗转了约两个小时,我悻悻离去,前往凤凰楼食茶点晚餐。
\n
\n多春鱼的鱼籽很多,非常鲜美。
\n一个人出来吃饭确实有些不便。有些菜品一次只能点这么一大盘,不过还是美美的享用了。
\n
\n鲜虾烧麦就是吃鲜,特别的鲜甜。
\n
\n菜单上的字看得不是很懂,大概写的是炒肠粉吧,没想到上了这么大一盆长得也不像肠粉的东西。
\n吃了一下,似乎确实是切成一节一节的肠粉。味道不错,但量好大最后没吃完。
\n
\n饭后,我顶着吃撑的肚子徒步前往华强北,这片传说之地。可惜的是,大部分店铺八点就关门了,简单逛了几栋电子市场后我便回酒店计划第二天的行程。
\nDay 2 - Fri - HK 开户 一大早我便乘上了前往福田口岸的地铁。
\n7:30 左右的口岸没有什么人,大部分是赴港上学的学生,通关很快。
\n我在落马洲站选择乘坐 B1 双层巴士前往元朗区,开始了开户之旅。
\n沿途…说实话没有什么风光,元朗算是比较偏僻的地方,好风景还要等到第二天的香港岛之行。
\n8:00 我到达了中行门口,发现只有前来上班的员工,还没有来排队的人。如果希望第一个办理业务的朋友可以选择这个点就来等候。时间还早,我就去吃了个早餐,回来发现已经有两个人在排队,便加入了队伍。
\n9:00 准时进入了银行,取到了号。在我之前有提前预约的人,我排在了第 5 号。
\n在工作人员的引导下使用手机填写了信息,然后等待柜员办理剩下的手续。共有三个柜台在同时办理开户,一个人需要 30 - 60 分钟的时间。
\n11:00 左右,我完成了中行的开户。
\n我现场就拿到了银行卡,便在 ATM 存入了 500 元人民币,并兑换成了港币。
\n顺便在同一条路上的南洋商业银行和汇丰银行也完成了开户手续。这两家银行皆需要几个工作日审核信息,审核成功后会将卡片寄至通讯地址。
\n穿行于各家银行网点的途中,我发现了一家在他人游记上看到过的店铺——胜利牛丸,便暗自定下了午饭的去处。
\n
\n三个愿望,一次满足。
\n面汤的颜色相当的“可怕”——仿佛是直接将卤水呈上来一般。但实际入口并没有很咸。牛肉们就不一样了,比较重口味的我也觉得有一点点咸。不过风味很棒,牛肉吃透了各种香辛料。
\n河粉藏在了对得满满的牛丸和牛肉之下,不是我常吃的宽河粉,比较窄。
\n虽然店名以牛丸冠名,我却没觉得牛丸有多么的美味,反倒是牛筋更胜一筹。
\n饭后,我在坐满老者的公园里稍稍歇息,并且计划着下午的行程……
\n此次香港之行除了开户,其实还有另一个目标——碧蓝档案与香港吃茶三千的联动~
\n在出发前的几天得知了联动的消息,运气真的很好。
\n
\n虽然有更优的路线,我还是选择了体验一下地面上的有轨电车。有轨电车有点像巴士,也是由司机驾驶,也会受到其他车辆与行人的干扰,但行驶路线是沿着铁轨固定的。
\n随后乘坐地铁,前往金紫荆广场…的对面,星光大道。在这里陈列了许多明星的手印和画像。海港边上的风景大好。
\n在此稍停,开户还没有结束。大概是因为元朗距离内地太近,许多虚拟银行都需要前往更靠近香港中心一些的地方才可以开户。我在一家星巴克坐下,开通了 ZA Bank 和 livi Bank 的账户。
\n随后,便是前往海港城的吃茶三千点了一杯联动奶茶,从中心一步步移动回福田口岸——晚上要和群友面基。
\n我们俩聊得非常尽兴,聊到了十一点才分手,回到了酒店,速速洗漱完毕,预定了第二天早上从福田站前往香港西九龙的火车,便沉沉睡去。
\nDay 3 - Sta - HK 游玩 昨晚火车票订得比较迟,最迟的火车也是 7:45 的,更迟的都被人订光了。我又被迫起了个大早(这三天都是 6:30 之前起床的)坐地铁前往福田火车站,搭上前往香港西九龙站的火车。可能是比较早乘火车的人并不对,这一趟通关流程比较顺利,再迟一点可就不好说了……
\n到达九龙,我空着肚子在街上漫步,寻找可以填饱肚子的早餐店。打开地图,“澳洲牛奶公司”几个字印入眼帘,这不是前几天看到的网红店吗?我记得这家店只收现金,便前往沿途的便利店,在 ATM 用中银香港的卡取了 100 港币现金。没有注意到我用的是汇丰的 ATM,与银联网络并不互通,被收取了 15 港币的手续费 QAQ。(众安银行的卡在全港 ATM 可以免手续费取现,经常到港玩的话可以办一张实体卡)
\n
\n带着现金,我来到澳洲牛奶公司的门口,果不其然,排起了长队。好在我是一个人,这家店是强制拼桌的,顺利超过了大团的游客,来到了店内。
\n给我的第一印象嘛…很拥挤,人挤着人,甚至是背贴着背。小小的一张圆桌围坐着四个人,各自享用着早餐。穿着白大褂的服务员在人群缝隙间穿梭着,高效地记录着到店客人的订单,飞速地将菜品传递到桌前。
\n落座,菜单压在桌子的玻璃之下,写满了粤语,说实话我看不大懂。纠结了几十秒,发现菜单上还写着“早餐”、“下午茶”等套餐,我便对服务员说:“早餐套餐来一份吧。”
\n话音未落,餐具和一杯茶便着陆在我的桌上。剩下的菜品也并我让我久等。
\n
\n首先呈上的是一碗火腿通心粉。味道比较清淡,喝不出黄色的汤底是什么熬制的,不过比较鲜。
\n
\n接着呈上的是一个盘子,两片吐司和炒鸡蛋各占半壁江山。牛油烘烤的吐司暖暖的,软软的;这份炒双蛋则是套餐的点睛之笔——调味微咸的鸡蛋并没有炒得很熟,在生与全熟之间掌握了平衡,做到了入口软绵,回味无穷,堪称是绝品。
\n如果有路过,一定要来尝一下这份炒鸡蛋~
\n一顿饱餐之后,我继续踏上旅途,前往太平山顶的观景台。
\n
\n上山的路可以选择用脚走完,不过,还是要体验一下富有特色的山顶缆车~
\n缆车的斜度估摸着有三四十度,是我见过最陡的。缆车是建在山坡边上的,方便沿路欣赏香港的风景。
\n
\n缆车的终点站是太平山的山顶,下车后直接来到了凌霄阁摩天台的二层。沿着盘旋向上的电动扶梯,海拔逐渐升高,走着走着,最终到达了海拔428米的摩天台观景台。
\n在这里可以俯览香港的景色,听说夜景更美,不过我晚上并不住在香港,也就没有机会亲眼目睹,实在可惜。
\n摩天台上的风很大,吹着十分舒服。待了大约四十分钟,我便沿着原路返回,坐着缆车回到了山下。
\n
\n早餐吃得有够饱,到了饭点我还不是很饿,便在街道与小巷间漫步,寻找着午饭的好地点。我想找家正宗点的茶餐厅。
\n跟随地图,我来到了就近的广芳园…可等候的队伍早已排出店外,目测有三四十人在队伍中。因为下午就要坐动车回去了,我无心加入他们,便继续游荡着,寻找着。
\n
\n附近的茶餐厅真的不多,跟随地图的指引,我来到了一家街边的小店。店内只收现金,而我身上只剩下不到五十港币,便选择了菠萝油和冰阿华田作为午餐,反正肚子不是特别饿。(冰的阿华田比热的还要贵上几块钱哦)
\n
\n饭后,我一时兴起决定走路去西九龙火车站,而非乘坐地铁,便开始一趟 City Walk…差点把我 Walk 没了。
\n街上好多地方都在施工,地图的数据并没有新到可以帮我绕开因为施工而禁止通行的路段…再加上出海关也需要一些时间,我差点以为要赶不上车。
\n好在我还是在开始检票前就赶到了,算是有惊无险,顺利踏上了返乡的路,结束了此次旅程。
\n我都带了些什么 此次我轻装上阵,只带了一个双肩包。
\n这次旅行我尝试了内衣、袜子和洗澡用具(浴巾与毛巾)都使用一次性的,省去了携带大量衣物且需要带回大量脏衣物的麻烦,也可以保证洗澡用具足够干净(对酒店不是很信任)。浴巾和毛巾都是压缩的,体积非常小。
\n其余的,便是两晚更换的衣服和电子产品的充电器了。
\n开户的那些事 我挺担心中银香港会开不下来,因为我还是学生也没有带地址证明。在大堂接待客户的小姐姐也询问了我有没有带上地址证明,没有的话只能碰碰运气,弄得我更慌了。
\n不过真正坐进柜台和柜员大哥开始走流程,直到大堂经理过来刷卡完成审核、将卡递到我手中,都没有向我索取地址证明。不知道是因为得知我是学生,还是因为我身份证上写的是具体住址,反正过了这关,顺利拿到了卡。
\n而汇丰之行则运气不佳,提交的资料被送至总行审核,当天拿不到账户也无法完成所有流程,只能等卡寄到居住地址,并且等下一次到港签字完成所有开户流程了。
\nP.S. 在跨境转账到时候一定要填清楚收款人的名字,要与中银香港的账户名称相同。我填反了名字被收了一笔手续费 QAQ
\n尾声 这次旅行顺利得可怕(除了差点没赶上返程的火车),在深圳与香港复杂的地铁网中也没有坐错一趟列车,似乎没能暴露出什么问题。希望 12 月底的日本之旅也能一样顺利。
\n一个人出来玩真的很自由,在出发前先查好可以去哪里玩、去哪里吃,到达之后就随心安排,不用迁就其他人,走累了随时可以停下休息,在咖啡店坐上一两个钟头也不会有人抱怨。
\n我也许爱上了一个人旅行的感觉,之后也会继续尝试这种旅行的方法。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/42ec6146.html",
+ "url": "https://blog.udon.eu.org/archives/42ec6146.html",
+ "title": "My Second Attempt To ARM Servers",
+ "date_published": "2023-09-24T04:30:00.000Z",
+ "content_html": "Last weekend, a full month without my Telegram account, I thought I had to do something. I believe I got banned because someone clicked the “spam” button on my messages, but I have nowhere to appeal.
\nI lost the faith in centralization. This time, I made up my mind to dive into decentralization.
\nFirst, I need a server.
\nWhere To Purchase I used to buy servers from distributors of the DC for lower prices, but it always ended up with disappointment. The servers are just not stable and I can not upgrade the hardware configuration on demand.
\nSo this time, I chose Hetzner for my server. Hetzner is a huge IDC in Germany, I must have to pay a fairly high price for the server, am I right?
\nThe following images show the price of Hetzner’s traditional CX series - with x86 CPU.
\n
\nA 2 CPU and 4GB RAM CX21 can fit my needs, it costs 5.35 EUR per month. By the way, the 40GB disk is far less than my requirement.
\nThe CX series are using Intel Xeon CPU. The GB6 benchmark score of an Intel core is around 850, which is significantly lower than AMD EPYC of around 1200.
\nHowever, when I switch to the CAX series…
\n
\nI can get the same number of CPU cores and RAM with only 3.79 EUR per month, 30% lower than the x86 one.
\nThe GB6 benchmark score of ARM CPU is around 1050, even higher than the Intel ones.
\nThe disk space is still 40GB, but I can purchase a 3.2EUR/month 1TB Storage Box to solve this problem.
\nWith a total of 6.99 EUR per month, I can get a server with 2 CPU cores, 4GB RAM, and 1TB disk. That is a great deal.
\nWait, ARM? About a year ago, when Hetzner first released the ARM series, I was interested in it and evaluated its compatibility.
\nI always use Docker containers to host my ~25 services. It turned out that more than half of the Docker images I was using were only compiled for the x86 CPU.
\nThat means you have to build the image by yourself if you want to run it on an ARM CPU. The time and effort were not worth it, so I gave it up.
\nHowever, this time, I found all of the Docker images already supporting the ARM CPU. It is time to give it a try.
\nThe Experience Here is my final hardware configuration:
\nI chose the CAX11 server, which has 2 Ampere ARM CPU cores, 4GB RAM, and 40GB disk. I added 2GB swap space to store cached files and reduce the load of the RAM.
\nI also purchased a 1TB Storage Box to store my data. Mounted to the server with NFS, just like a local disk.
\nI am using the latest Debian 12 Bookworm and I can’t feel any difference between x86 and ARM. My daily use software from APT source is all compiled for ARM. The installation speed is as fast as x86.
\nAs to the Docker images, I am using Portainer to manage them. What I need to do is just click the “Recover” button on the settings page, Portianer will automatically recover the configuration from CloudFlare R2 storage and all the containers just work as before, with no need to change any settings.
\nToday, when I am writing this article, My services are running for a week without any problem.
\n
\nWell, Still Not Perfect The ARM server just works fine, but I want to share some problems I encountered when building the ARM Docker images.
\nThe official software repositories of Linux distributions can offer full support for ARM CPU, but the repositories of other sources such as PyPi can not.
\n
\nThere are still some packages that don’t have prebuilt ARM wheels, and the building process can take a long time on the 2-core ARM machine.
\nThat is not a huge problem, but it makes the experience of ARM arch different from x86.
\nIf you are a developer, try to include ARM in your build pipeline next time, that will be a great help for the ARM community.
\nFinal Conclusion In my opinion, the ARM server is ready for daily use today. They offer a high quality-price ratio. I didn’t come across any compatibility problems.
\nNext time when you want to purchase a server, you can consider the ARM ones.
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/9b78ad2a.html",
+ "url": "https://blog.udon.eu.org/archives/9b78ad2a.html",
+ "title": "寻梦穿越机 - 入门浅谈",
+ "date_published": "2022-09-04T04:00:00.000Z",
+ "content_html": "暑假开始之前,我就朦朦胧胧有购入一台穿越机的想法。起因为何?我也不是很清楚。是看到了酷炫的穿越机航拍视频 ?还是童年时期的航模魂蠢蠢欲动,将要苏醒?
\n兴趣,倘若不立刻抓住,很快就会被抛至脑后。为了不让自己后悔,那就立刻开始筹备吧!攥紧并不是特别充盈的钱包,我踏上了寻梦之路。
\n什么是穿越机 FPV (First-Person View),是指通过第一人称视角远程控制无人飞行设备 (UAV) 的控制方式,也是一项运动,也指代了一类设备。
\nFPV 设备包含固定翼 (Fixed Wing) 与多轴 (Multi-rotor) 两种。穿越机多指多轴 FPV (常见为四轴)。
\n
\n一盆冷水 在详细介绍穿越机构成之前,请允许我泼一盆冷水。
\n穿越机与一些航拍无人机,例如大疆 (DJI) 的四轴航拍机与航拍 FPV 相比,颇有原始人见到现代人的感觉。
\n大疆无人机上搭载了各种各样的传感器,各系列大都配备了二向 (前后)、四向 (前后左右) 或五向 (增加一个上方) 的视觉避障功能、红外定高悬停功能,以及低电量 GPS 自动返航功能。
\n上述这些看似是无人机应当“标配”的功能,追求轻量化的穿越机时常一个也没有。一台最小安装的穿越机全机的传感器仅有集成于飞控的陀螺仪和地磁传感器 (电调的电流传感器一般不算在内),飞控仅知道当前机身的朝向、倾斜角度与加速度,无法感知周围的环境,没有实现避障、悬停与自动返航的能力。飞机的运动状态完全取决于飞手的操控,电池的剩余电量甚至需要飞手根据电芯电压来推断。
\n
\n再者,为了能获得更大的速度与更高的机动性,穿越机飞行过程一般处于手动模式,对飞机四向的倾斜没有限制,飞行过程中出现危险操作的概率大,事故概率也大。而大疆无人机对飞行速度、角度有严格的限制,能有效地降低炸机的概率。
\n因此,飞行穿越机的难度将是飞行大疆等航拍无人机的十倍、甚至百倍。请务必不要抱着随便玩一玩、随心飞的态度入坑穿越机,发生事故后轻则炸机提 (遥) 控回家,重则伤到自己或他人。对于喜欢日常随心飞行的玩家,大疆或类似牌子的无人机 (包括航拍机和穿越机) 更为合适。
\n \n下文将详细介绍入门穿越机所需要的技术知识与穿越机的重要模块。在明白风险之后仍打算入坑穿越机,或对穿越机有兴趣的你,欢迎继续往下阅读。
\n技术储备 上文提到了我童年时期的航模魂。我在小学就跟着老师学习无线电和航模知识,练习了焊接技巧和单旋翼的航模直升机的飞行技巧。曾参加某个航模比赛并拿到了铜牌的好成绩 (我好自豪)。
\n因此,我对我的焊接技术和遥控操控能力比较有信心。而这两个能力恰好是入门穿越机不可或缺的。下面我列举一些入门穿越机需要掌握的技能。
\n锡焊 除了购买真·到手飞套餐 (机子已组装完成,接收机已与遥控器完成配对),其余情况大概率需要焊接一些导线。
\n 大部分组装机套餐都不包含接收机,因为接收机与遥控器一一对应,一般是和遥控一起购买的。就需要用户自行焊接至飞控上。
\n 锡焊所用到的工具有电烙铁和焊锡丝,最好能佐以松香,增加焊接成功率。电烙铁推荐购买 T12 或更好的,因为飞控散热设计,很多焊盘为通孔的形式,热量会非常快地传到到全板,导致焊锡较难达到融化的温度。若使用功率太低的电烙铁,可能无法融化焊锡。
\n
\n 由于飞控比较小巧,焊盘也十分迷你,对焊接技术要求较高。
\n使用搜索引擎 不懂不可怕,不懂得学习才可怕。
\n 穿越机有关的零碎知识犹如满天星,散布于互联网的每一个角落,需要一定的搜索技巧才能挖掘到有用的知识。
\n 国内有关穿越机的网站数量比较少,建议使用英文搜索。
\n编译 若想要更新 ELRS 接收机和高频头的固件,需要从源码编译 ELRS 固件。虽然 ELRS 团队提供了一套图形化的编译工具,但难免会遇到一些疑难杂症 (博客中就有 ELRS 固件编译的踩坑记录)。需要对编译有一定的知识,能自行解决简单的问题。或者选择使用出厂固件,不自行升级。
\n操控能力 (协调性) 像音游 (音乐游戏) 一样,微操遥控器并不是所有人都能很好地做到。穿越机对遥控指令的反应极为灵敏,需要你能精细地操控遥控器。
\n 但这项技能可以通过电脑模拟飞行来习得。建议在正式开始飞行前,先在电脑上使用模拟器熟悉飞机的控制。
\n一颗勇敢的心 在飞行穿越机的过程中,不可能不出现炸机 (飞机以异常姿态落地) 的情况,难免会留下一点阴影。
\n 我在小学时飞过固定翼飞机,曾不慎撞到电线,导致飞机起落架损坏脱落,最后只能由老师操控迫降。此事给我留下了不小的心理阴影,有好几个月我都不敢再飞行固定翼。不过最后还是克服了恐惧,再次拾起了遥控。
\n 炸机并不可怕,每一次炸机都记录着你的成长。
\n深入了解 类似 Linux (比较奇怪的类比),穿越机也分为最小安装和额外拓展两部分。
\n
\n最小安装:
\n\n机架 - 硬连接其他组件,保护脆弱的电路板; \n电机 (和桨叶) - 提供飞行动力; \n飞控 - 控制飞行状态,是穿越机的大脑; \n电调 (电子调速器) - 驱动电机、分隔电路 (防止电机减速时产生的反向电流烧坏其他电子设备); \n接收机 - 接受遥控器控制信号 (有 2.4GHz、915MHz、 868MHz 等频段,互不兼容); \n图传 - 发送图像信号 (多为 5.8GHz 频段); \n摄像头 - 提供第一人称视角; \n \n额外拓展:
\n\nGPS 模块; \nBB 响 (蜂鸣器 Beeper,用于寻找飞机); \nLED 灯珠、灯带 (好看 XD); \n红外避障模块; \n运动摄像机 (拍摄更清晰、帧率更高的视频); \n \n挑几个比较重要的模块介绍一下:
\n机架 穿越机机架一般由碳纤维板制成,质地轻盈且强度极高,可以物理连接不同的模块,并保护脆弱的电子设备 (飞控、图传等裸板)。
\n挑选时,机架有几个要主要关注的核心参数:
\n\n外形:
\n机架分为普通式与涵道式 (Duct Propeller) 两种。
\n普通式机架的螺旋桨桨叶裸露在外,而涵道式机架的螺旋桨外侧有涵道 (类圆筒) 包裹。
\n有一些普通式机架可以通过额外安装涵道,变身成为涵道机架。
\n \n \n
\n 普通式机架采用开放式的设计,尽可能少地使用材料以减轻重量。由于螺旋桨暴露在外,危险性较大。普通机架可被用于任何竞速与花飞机型。
\n 涵道式机架通过涵道结构可以提供更好的升力、稳定性和安全性,但额外增添了重量和封阻,一般被用于小型机与花飞机型。
\n
\n 此外,机架还可以再细分为 X 型、H 型等,在此就不多展开,有兴趣的读者可以自行查阅。
\n\n大小:
\n穿越机大小各异,从最小的一寸机,到体型较大的五寸、六寸机。
\n二寸以下的机子防风能力较差,但体型轻盈且常使用涵道机架,适合在室内飞行,且不易伤人。
\n三寸以上的机子动力充沛,飞行速度快,但体型大、螺旋桨裸露,无法在室内飞行。适合在场地广阔的室外进行竞速或花飞 (花样飞行)。
\n \n \n飞控与电调 飞控与电调 (有时还有图传) 几块板子大小相当,时常纵向堆叠安装,称为飞塔。在多块 PCB 间有硅胶柱减震。
\n
\n飞控需要注意的参数不多:
\n\n
\n 由于穿越机飞行对计算性能不是很敏感 (类似 3D 打印机),一般选择搭载 F405 SoC 的飞控性能就足够使用了。
\n\n电调最重要的参数就是最大放电电流了。
\n电调的电流选择需要根据机身大小、电机和桨叶大小、形状进行估计,通常电机厂家会给出推荐值,按照推荐选购即可。
\n接收机与图传
\n接收机与图传共享一个重要的特性,协议:
\n常见的接收机有 ELRS (ExpressLRS) 和黑羊 TBS 两类。应该注意的是,不同种类的接收机使用的通信协议和频段不同,能与其配对的遥控器和高频头也不同 ,因此在挑选接收机的时候一定要看清楚协议和频段。
\n接收机和飞控的串口通讯协议也各异,有 UART、SBUS 等数种,再购买接收机前需确认飞控有相对应的串口。
\n图传分数字图传和模拟图传两种。
\n数字图传以大疆的最为出名,需要使用配套的飞行眼镜;模拟图传大多使用 5.8G 频段通讯,和大部分接收模拟信号的飞行眼镜通用。
\n图传连接至飞控的串口通讯协议也很多,购买的时候请多加留意。
\n除此之外,接收机有遥测功率、内/外置天线、天线接口等参数,在挑选时都需要多加留意。
\n图传的另一主要参数则是发射功率。发射功率越大,能稳定接受图传信号的距离一般就越大。小型穿越机一般选择发射功率在 200mW 到 500mW 的图传即可 (部分图传发射功率可调);若有远航要求,也可选择发射功率大于 1W 的图传 (价格较高、发热也较大,一般带有主动散热)。
\n机子之外的配件 除了穿越机本体之外,想要拥有完整的飞性体验,还需要遥控器、高频头、电池、平衡充电器、飞行眼镜等配件。
\n上述的每一种配件都可以写作一篇介绍文章。由于篇幅有限,本文就不再介绍上述的外围配件,请善用搜索引擎,学习相关知识。
\n配套软件 除了硬件,穿越机配套的控制软件也尤为重要。目前主流的控制软件是开源的 Betaflight。
\n
\nBetaflight 分为嵌入端 (安装在飞控中嵌入式系统) 和地面站 (安装在电脑里的软件)。将飞控通过线缆连接至电脑,并打开 Betaflight 地面站软件,即可对飞控参数进行调整。
\nBetaflight 调参也是一门大课,新手不建议自定义太多的参数。待熟悉飞机之后,才建议调整 PID 等高级控制参数。
\n新手的第一台飞机 说了这么多,要上某宝挑选、下单穿越机的种种配件了吗?
\n我的建议是否定的。
\n我咨询了一些老玩家,他们建议新手购买他人已完成调参的二手机器,或者购买商家大部分已组装完成套机,以绕过纷繁复杂且状况百出的 DIY 过程,降低还未入坑就弃坑的风险。
\n此外,自行购买散件的总价常常会高于购买整机的价格。对于钱包不是特别充盈的我,购买整机也是一个省钱的选择。
\n待熟悉了穿越机的飞行与调试之后,再学习他人经验,设计并组装一台自己心目中的机器也不迟,这才是 DIY 的浪漫。
\n我的成果
\n我选择购买一位老玩家完成大部分组装工作的“半”整机——包含了机架、电机、飞控和电调。图传、摄像头和接收机则是我自行购买和焊接安装的。
\n飞机到手、外围装备齐全,只待一飞冲天。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/9d319c54.html",
+ "url": "https://blog.udon.eu.org/archives/9d319c54.html",
+ "title": "DELL 灵越 15 5547 拆解与更换硅脂",
+ "date_published": "2022-05-22T02:30:00.000Z",
+ "content_html": "DELL 灵越 15 5547,Intel i5-6200U,Nvidia Geforce 630M,8G DDR3-1600 + 256G SATA SSD(后期改装),是我的第一台笔记本。这台笔记本的拆机过程比较繁琐,是我目前遇见的最难拆解的电脑,故作此文章,分享一下如何拆解一台笔记本,并为其更换硅脂。
\n
\n概述 本次拆机共耗时 1H 30Min,是拆解正常机器所花费时间的两到三倍。
\n拆解步骤包括:卸下后盖、拆除电池、拆除硬盘、拆除风扇、拆除键盘、卸下基座、卸下散热器、更换硅脂,然后是逆序执行上述拆解步骤,组装电脑。
\n拆解准备 拆机少不了一套好工具。
\n为了无伤拆机,需要准备一套好用的螺丝刀防止螺丝滑牙;还需要一套塑料拆机工具,用于撬开后盖。
\n笔记本的硅脂,我选择了霍尼韦尔 7950 相变硅脂,不容易挥发,不需要经常更换。
\n开始拆机 怎么拆呢 其实几个月前我就有给这台设备清灰、换硅脂的念头,但拆开机器之后我发现主板被塑料框架罩住了,无法拆下散热器。我研究了半天也没找到拆下框架的方法,便只清理了风扇的灰尘,没有更换硅脂。
\n而这次,我有备而来:我查到了 DELL 官方的用户手册 ,其中详细记载了拆解这台机器的方法。接下来的拆解步骤就严格按照官方的教程啦。
\n第一步:卸下后盖 翻到 D 面,拧下两颗固定螺丝,就能将后盖拆下,可以触及无线网卡、内存条、2.5 寸硬盘、电池和散热风扇,日常需要维护的部件都能轻松触及,好评。散热器和主板则在塑料框架之下。
\n
\n第二步:拆除电池 释放主板电荷是电脑拆机中至关重要的一步!
\n在主板带电的情况下拔插任何端子都是不明智的做法,很可能会将主板上的高电压线路误接入低电压线路(例如拔插屏线时没有正对接口),烧坏一片元件。
\n这台机子的电池没有排线,直接接入主板。卸下围绕电池的四颗螺丝,手提塑料片,就可将电池卸下。
\n翻到 C 面,长按电源键 10s,可重复两到三次,确保主板中的电荷完全释放。
\n
\n第一次见这种电池模块,比起长条状的电池可以更好地利用机身空间。
\n第三到五步:拆除硬盘、散热风扇和键盘 卸下固定硬盘座的螺丝,抽出硬盘,再断开 SATA 与供电二合一的线缆,即可取下硬盘。
\n拔下主板上散热风扇的端子(位于风扇左侧),卸下固定螺丝,即可取下散热风扇。
\n翻到 C 面,用手或塑料工具扣出键盘模块,注意不要大力提起键盘!小心地提起一段距离,断开机身上的排线,键盘就取下来了。裸 C 面上还有两条排线,都需要断开。竖直方向排线需从孔洞中穿回 D 面。
\n
\n第六步:卸下基座 这是我第一次见笔记本中的基座,没有手册的指导很难拆下。
\n首先,确保 C 面两根排线已断开。
\n翻到 D 面,小心地断开屏幕排线(位于散热风扇左侧)。
\n再翻到 C 面,按手册图示卸下所有螺丝。
\n翻回 D 面,同样地卸下一堆螺丝,注意这两面的螺丝都是 M2.5x7 规格,长于主板用螺丝,混用可能会戳穿主板。
\nDELL 在机身和框架上都有标注螺丝孔对应螺丝的规格,非常好评!
\n确认卸下所有螺丝后,用塑料工具插入基座与机身的缝隙,划开卡口。
\n待大部分卡口都解开时,小心提起基座,注意将基座上的线缆和排线取下,基座就彻底与机身分离了。
\n
\n基座的背面,可以看到是 PC + ABS 材料,分量很足。
\n
\n拆下基座之后的机身,暴露出了主板和子板。散热器是梦幻单热管,不过压这两个破芯片足够了。
\n第七步:卸下散热器 散热器共有六颗螺丝,四颗在 CPU 上,两颗在显卡上。
\n为了使散热器均匀受力,不能一次性直接拧下一颗螺丝,而是平均为每颗螺丝卸力,可以每个螺丝一次转两圈,直到所有螺丝都被卸下。裸晶脆弱,有必要好好保护一下。
\n
\n不难看出,CPU 上的硅脂全部凝固,且裸晶上的硅脂基本流失殆尽(散热器上也没剩多少)。
\n去除大部分凝固的硅脂,再用酒精片擦拭、用布擦干。
\n更换硅脂的步骤我就不在此赘述,商家一般会提供详细的视频介绍,按着教程做就可以了。
\n
\ni5-6200U 的全貌,右侧长条状的裸晶应该是集成显卡吧。
\n
\nGeforce 930M 显卡,非常小个。
\n
\nDDR3 显存,封装方式和 DDR4 不一样,更扁一些。
\n后续步骤 接下来就是逆序刚刚的拆解步骤,我就不再赘述。
\n注意几点:
\n\n安装散热器时也需保证受力均匀,且不能过分用力,可能会压碎裸晶。 \n安装基座时注意将线缆与排线置于合适的位置,不要被基座压到了。 \n \n拼装完成后,插电,开机,轻松点亮。
\n总结 拆解一台电脑并没有想象中的那么困难,拆解不同电脑的方式也都大同小异,上述的步骤一般都能适用。
\n我已拆解了不下 10 台/次 笔记本,目前还未翻过车。
\n下次清灰、更换硅脂,试着自己动手吧!
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/245ab175.html",
+ "url": "https://blog.udon.eu.org/archives/245ab175.html",
+ "title": "厌烦了软件的我上了硬件的车",
+ "date_published": "2021-11-07T13:00:00.000Z",
+ "content_html": "好久不见,回想三个月前的我还在享受暑假。开学后,我将大部分精力转移至学业,折腾的时间便少了。
\n机缘巧合,在显卡价格最高的时候坏了显卡的我打算趁双十一打折买个焊台,自己修显卡。
\n同时,我有了玩一玩硬件的想法。
\n焊台体验 六七年以来,我一直在用直插 220V 不可调温的电烙铁焊接。当时,无线电老师告诉我这是质量很好的紫铜烙铁。就是这把烙铁陪着我入门了锡焊。
\n放在今天,这把烙铁加热慢,且烙铁头非常容易氧化,体验极差。由于加热效果不佳,我还弄坏了一台灵车无人机的主板和一个 IR 摄像头(都是掉焊盘了)。
\n新焊台则完美的解决了之前的痛点:新烙铁加热快、温度准确、刀头耐用。热风枪则开启了贴片元件焊接时代。
\n第一个项目 说在前头,选择一个比较复杂的项目来入门是个错误的决定。
\n十月中,我选择了一个加热台项目。对,就是可以用来焊接贴片元件的加热台。用工具制造工具,多美妙的事情啊~带着兴奋,我开始采购元件……
\n直至今天,这个项目的进度仍然停滞不前:元件全部焊接完毕(见下图),接通电源可以点亮一秒,随即就烧坏了一个电阻。初步判断是 5V 供电区域有短路,具体情况还需继续检测……
\n
\n事实证明,第一个项目应该选择简单点的,以防止打击信心🌚
\n赶超第一个项目的第二个项目 转眼到了十一月,又可以在嘉立创愉快地免费打板了~
\n这次,我给自己设计了一张 PCB IC 校园卡,只需要焊接一个 CUID 芯片。
\n没有比这更加简单的项目了!
\n成品如图~
\n
\n结语 前几天整了一台性能不错的服务器,小项目又可以整起来;
\nDIY PCB 这方面,我还想制作一个带编码器的小键盘,以提高 Premiere 剪辑的效率;
\n群星真好玩,铁心灭绝者真不错😇
\n真是丰富多彩的课余生活~
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/67d41c7c.html",
+ "url": "https://blog.udon.eu.org/archives/67d41c7c.html",
+ "title": "LOOPERS 简评",
+ "date_published": "2021-08-02T02:00:00.000Z",
+ "content_html": "对 LOOPERS 的整体评价:B-
\n \n这一作挺特殊的,没有任何选择支,全程自动播放耗时仅8小时左右。在这样短小的作品里塞下一日无限循环这样富有可能性的世界观,大概是本次剧情质量不尽人意的主要原因。
\n中盘倒不觉得冗长,还可以继续细化,加深对人物的刻画;就是后期重复的内容与话语太多了,让想尽快看到结局的我着急。
\n音乐方面,我全程用电脑扬声器玩的。说实话,BGM 听得不是很清楚。待我细听之后再作出评价。女二真的很吵,有 SP 那头恶鬼的吵闹级别了。
\n制作方面,望月老师的画好看(哧溜)。
\n \n谈一谈一些细节上的感受(可能有点剧透):
\n作为已经退坑的老 Ingress 玩家,再度跟随主角一行人踏上寻宝之路,着实让人兴奋。游戏中着力刻画的,寻找到藏匿的宝物时的喜悦我能切身体会到。
\n这让我想起了两三年前,我为了拔掉敌方的一个大 菊花 Portal,在旧屋区狭窄的巷子里穿行。找了一两个小时,终于到达 Portal 的中心点,开始狂轰滥炸。当时的兴奋与满足是溢于言表的。或许你要亲身玩过寻宝游戏,才能体会主角一行人的感受吧。
\n对于“神经大条”的男主,我最大的感受就是这种人不大可能存在吧。遇到任何事情都能乐观面对、看似大大咧咧但做起事来注重细节、有着强大的领导力和鼓舞力……这种人若真的存在,绝对耀眼。
\n最后看作品的主题。在我看来, 龟 龙骑士这回倒不是想写一个很催泪的故事,而是继续宣扬 Key 社所推崇的友谊、团结和爱的主题。我个人是百看不厌,我也想要知心朋友,甚至是一群知心朋友。
\n男主全程口边挂着寻宝二字,听了耳朵确实起茧,但这文章放在考试说不定能拿挺高的分数——不断地扣紧中心。人生的意义?寻宝;友情的价值?寻宝;生命的救赎?还是寻宝。
\n这寻宝真有意思,只要将寻宝的过程与寻找到宝物稍作改变,就能解释这么多难以用语言表述的东西。倘若能在游戏性上再下点功夫,加个交互型的寻宝游戏,整体效果又会再上一层。但加了这些就不是一款短小精湛的游戏了,对吧。
\n \n最后,这款游戏值得推荐吗?对于没有接触过 Key 社作品的人,可以一玩。
\n对于 Key 社老玩家,对这部作品报以极高的期待,希望它能超过正作水准的,还是别玩了。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/202ebc76.html",
+ "url": "https://blog.udon.eu.org/archives/202ebc76.html",
+ "title": "2021 第二学期总结",
+ "date_published": "2021-07-15T01:00:00.000Z",
+ "content_html": "在去厦门的动车上,写一下这个学期的总结。
\n \n刚开始我想形容这个学期是浑浑噩噩的,感觉我想做、想学的东西都没做成;
\n但再冷静想一想,这学期在学校的安排下打了很多基础:解决了 C++ 这一心头大恨;还学习了数电,为硬件打了点基础。
\nC++ 实在很基础、很枯燥,若不是学校压着要学习,光凭我一人自学是难以解决的。以前也不是没有尝试过自学,最终都败下阵来。
\n至于硬件这块的知识,软件工程专业一般是不去接触的。学校大概是觉得下学期要学的 计算机组成原理 需要有数电的预备知识吧。
\n至于高数、线代,对编程越是了解,越会知道他们的重要性,知道他们贯穿着整个编程的过程。
\n这个学期最满意的还是全科通过,包括让我非常头疼的大学物理和高数。
\n下个学期我还是希望能腾出更多的时间给自己喜欢的事物,而不是把全身心都投进学校的课业,感觉亏待了自己的梦想……
\n生活方面,■■■■■■■■■,整体闷在学校里把我憋得难受。宿舍环境虽然不错,但整体偏老旧——六人间改四人间,■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n下个学期又可以搬回主校区,一定要把宿舍和工位书桌好好装修一下,过精致生活~
\n \n至于暑假的安排,挺杂乱的。
\n买了不少东西,从小到大,从 IR 摄像头模组到 3D 打印机,打算将折腾二字写满整个假期;
\n学习内容则是安排了 JavaScript 和 CTF 相关的内容,如果还有时间还想看看 React。CTF 没有人带,想要入门挺困难的……校队是不打算招新人么 :(
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/43a7a15c.html",
+ "url": "https://blog.udon.eu.org/archives/43a7a15c.html",
+ "title": "Osprey Commet 简评",
+ "date_published": "2021-06-13T07:00:00.000Z",
+ "content_html": "我的威戈双肩包——没错,就是烂大街的那个牌子——已经用了四年多了。不得不承认她的质量之好,用了这么久只坏了包前侧的拉链(到裁缝店换了一条拉链),外加水壶网袋破了几个洞。在上个月,她的一条拉链绳(不知道怎么叫那个部件)的脱落让我有了更换通勤背包的念头。
\n \n\n为了挑选一款最称心的通勤背包,我先对前任威戈包进行了评估:
\n优点
\n\n缺点
\n\n背负系统拉垮,光是背电脑(1.4kg)就觉得非常沉,更别说偶尔的电脑+平板组合了(2.2kg);
\n \n没有太多存放小物件的仓位,所有的小东西(U盘、读卡器、耳机、线材 etc.)都放在包的前仓里,且前仓还是竖向开合,分类与取物皆不便;
\n \n包身不防水,但送防雨套。下雨时出门要提前戴套,生怕包内电子产品淋湿;
\n \n \n因此,我对下一款背包有如下要求:
\n\n自重轻;
\n \n分仓多且位置合理,能分类存放小物件;
\n \n包身最好能防水;
\n \n最好能同时收纳笔记本电脑和平板;
\n \n \n在挑选了不下30款背包后,我选择了这款 Osprey Commet,下面来简要点评一下。
\n全局 包身自重 0.85kg,容积 30L,在这个大小的包中算比较轻的了。包身全部防水,拉链虽然没有做密封处理,但有一些延伸出来的防水面料挡住拉链,可以防止进水。
\n分仓 这款包的分仓可以算是一个亮点。我简要说一下我对每个分仓的使用情况:
\n包的最前方是提手(颜值比较一般,偏向实用),因此第一个仓是小主仓。仓内有三个兜袋,用于存放我的卡包、证件、充电宝和 MP3,仓内还装着小记事本和钱包。此外,仓内还有一根红色的钥匙绳,采用的是快挂钩,可以把现有的钥匙串挂上去。
\n往里一层是第一个眼镜/小物品收纳仓。仓内使用了特殊面料防止刮花眼镜,在登山/骑行时存放眼镜还是很方便的。我就拿来存放经常取用的小物件了,例如耳机、U盘、Lto3.5mm 转接线等。
\n下一层是一号主仓,外加主仓内的第二个眼镜/小物品收纳仓。一号主仓有一个兜袋方便收纳平板和 A4 大小的文件,我便拿来放文件袋和平板。课本、笔盒也都放在这个仓位。小物品收纳仓则收纳比较少取放的小物件,例如订书机、凤尾夹和一些药品(达喜)。
\n最后一个仓位是笔记本仓。作为整个包最大的仓位,能轻松容纳 15.6 寸的笔记本。在需要携带笔记本时,这个仓位当然用来装笔记本;平常上课不带笔记本,也可以拿来装外套。这个仓位里还有一个网袋,我用来装线材(一根 0.2m CtoC、一根 MFi AtoL、一根 0.2m AtoB)。
\n背负系统 虽然没能用上 Osprey 专业的空景系统,但 Commet 的背负系统依旧优秀。Osprey 的背板偏硬,可以比较好地贴合后背,以减轻肩带对肩膀的压力。在日常上课背负几本书时,基本没有负重的感觉。若是同时背负笔记本+平板(3.6kg 左右),能感觉到背板压在后背上,分担了部分重量。
\n胸带与腰带这种登山包才有的配置出现在了这款通勤包上。在背负比较重的物品时系上腰带可以有效减少包身晃动。
\n总评 相当满意!
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/4a562e35.html",
+ "url": "https://blog.udon.eu.org/archives/4a562e35.html",
+ "title": "近日败家:Chrombook Duet 与 小米手环6",
+ "date_published": "2021-04-30T13:00:00.000Z",
+ "content_html": "今天来分享一下最近败家的几样电子产品。
\nChromebook 与 USI 触控笔 在大学课堂上发现很多人都持有一台 iPad 和 一支 Apple Pencil 在做着笔记,令我很是羡慕。
\n在咨询过“业内人士”,并对现有产品及我的钱包进行评估之后,我放弃了购买 iPad 及其昂贵的配件的念头,选择拥抱 Chromebook。
\n为了追求极致的性价比,我选择了这款 联想 Chromebook Dute 加上 联想 USI 触控笔,1730 + 330 合计 2060 元。
\n购买 我是在亚马逊海外购下单的,货从美国仓库发出。平板花费了8天来到了我的手上,而触控笔因为海关查验,比平板迟了两天。跨国快递的速度比我想象的要快不少。
\n设计与体验 Duet,意为合二为一。CB Duet 的设计理念和 Surface Book 一致——打造一台既可以当作平板又可以当作电脑使用的设备。
\nCB Duet 附赠了磁吸键盘与可以作为支架的保护套,这一点比 iPad 高不少。
\n但是,键盘与触控板的体验实在是一言难尽。
\n键盘在不平整的环境下偶尔会出现多次触发(按一次键打出两次字母)的情况,不过放在平整桌面就不会了。键程尚可,但按键布局一般——为了在10寸大小的区域容纳全部按键,联想选择了将键盘右侧的按键(大部分是符号键)缩小,这让打出正确的符号变得十分困难。
\n触控板的体验更加一言难尽。手感很差,似乎没做过亲肤处理,滑动阻力非常大;定位也很不准确。但 CB Duet 有一块10寸的触摸屏,为何不使用触控操作呢?
\n总而言之,CB Duet 附赠的键盘属于“能用”的级别,就像现在的我敲着博客——做一点文字工作完全没问题。Coding ?别想了,我替你试过了,如果只是简单改几行代码,比如 Caddy 的配置文件,完全没问题;如果想跑完整的开发环境,性能可能不够;如果使用 code-server……也不是不行,但10寸的屏幕实在不能施展浑身解数啊。
\n续航 部分比较“卷”的朋友可能比较担心这个问题,是我先倒下还是 CB Duet 先倒下?
\n根据我的实际体验——当然这可能很不准确,你大概率撑不过 CB Duet。
\n实际测试下来,一个上午,四节课下来,大概使用 15%-20% 电量。
\nARM 的超低功耗让 CB Duet 有官方标称的将近 11 小时的续航;如果你像我一样调低屏幕亮度,并只使用笔记软件,系统提示的理论续航甚至达到 20 小时;倘若是高强度使用,例如使用 ssh、打开 Linux 虚拟机等,续航则在 6-8 小时左右。
\n性能 翻到上面再看一眼价格,还要谈什么性能吗!
\n本人不喜欢的 MTK P60T,8c 2.0Ghz。
\n不过实际体验来看,这颗小芯片的性能完全能喂饱 ChromeOS 和它的安卓容器,至于 Linux ,只要不跑 GUI 应用(Linux 容器暂无图形硬件加速)也没问题。
\n听说联想计划推出搭载高通骁龙 7c 的同款 CB ,性能有略微升级,如果价格依旧能持平,那会是更好的选择。
\n系统与体验 ChromeOS 的操作需要适应一段时间。
\n你应该要改变对应用的认知——减少安装实体应用,更多地拥抱 PWA ,可以让这个专门为 Chrome 优化的操作系统发挥全部实力。
\n安卓容器的性能真的很棒,可能是安卓和 ARM 相性较好的缘故吧,安卓应用在 CB Duet 上表现极佳,我觉得和一台安卓平板几乎没有差异。这台安卓平板却还能跑完整的 Chrome 浏览器和 Linux 容器。
\n系统的其他体验可以在网络上看看其他人的博客。如果我在后续体验中有什么心得体会,会考虑写成博客。
\n还有触控笔 联想这只 USI 触控笔是亚马逊上最便宜的,没有侧边按键、没有笔擦。触控笔没有太多要说的,昨晚用它写了一份作业,体验良好。
\n使用一周之后,偶尔会出现笔凌空识别的情况,无法测试出是硬件还是软件的问题,略微影响使用体验。
\n小米手环6 这是我第一次体验手环产品,之前戴的都是智能/半智能手表。
\n小米手环6的屏幕在手环产品中是比较大的,且能自定义壁纸,这是它最吸引我的地方。
\n至于续航,的确是硬伤。在打开除了全天心率监测以外的所有监测项目,心率检测频率设为1分钟一次后,手环一天消耗 20% 左右电量。
\n总体体验还算满意的。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/2c0bfb1a.html",
+ "url": "https://blog.udon.eu.org/archives/2c0bfb1a.html",
+ "title": "校色初体验 / 寒假总结",
+ "date_published": "2021-02-26T14:00:00.000Z",
+ "content_html": "好久不见,甚是想念。今天来分享一下初次校色的体验以及寒假我都干了些什么。
\n \n\n \n校色初体验 为什么要校色 我的笔记本电脑型号是 联想小新 Air 14 2020,拥有一块 14寸 100% sRGB 色域(实际为 94% sRGB)的屏幕,面板型号是 友达 B140HAN06.8。很幸运,不仅抽中了三星 SSD,还抽中了友达的屏幕。
\n为了更舒服地阅读代码,我又淘了一台 17.3寸 的 DIY 便携显示器,同样也是 100% sRGB 色域(实际为 95% sRGB),面板型号是 友达 B173han01.1。
\n由于便携屏的驱动板都是通用的,并没有对某块面板有调教,也不可能有屏幕出厂时的调教,因此这块便携屏的色差非常明显。就校色结果来看,光度 (gamma) 值就有 70% 左右的偏差。
\n通过校色,两块屏幕色彩更加接近,且更接近真实的颜色,看起来也会更舒服一些。
\n关于价格 我想很多人和我有同样的顾虑,感觉租用校色仪价格不菲。
\n这次租用时长为 3天,我仅使用了一天半就归还了。总共支出为 校色仪租金 40元 + 回程运费 18元。押金原本是 750元,和店家商量后爽快地降到了 500元。
\n虽然商家划定了许多可能导致押金被扣的规则,但只要你小心一点、爱护一点,完璧归赵、取回押金是很简单的。
\n色温选择 刚开始我选择的目标色温是 6500K,在校色后发现偏黄许多。最后我选择的目标色温是 7500K。当然,两块屏幕 6500K 色温的校色文件我都保存下来了。
\n最终校色效果 按照红蜘蛛给出的报告,联想笔记本的这块屏幕在色准上要优于便携屏,白点与灰度的 △E <= 0.2 ,便携屏则是 △E <= 0.5。
\n联想这块屏出厂也比较准,值得表扬~
\n下面是校色完的合影:
\n
\n \n寒假我都干了啥 列举一下:
\n\n学习(重温)了 C语言; \n学习了 PHP 基础; \n学习了 Burp Suite 基础; \n学习了 After Effect; \n学习了 Audition 基本操作; \n完成了一个 5分钟 的视频项目,比较灵活地运用了 PS Pr AE 及 Au; \n当了两个星期的补习老师,挣来的钱买了 飞利浦 X2HR HiFi 耳机和一块 2242 SSD; \n学习了 Solidworks 基本操作(这两天学的); \n \n感觉还是做了不少事的。
\n马上要开学了,下学期总算能学些我想学的了。
\n总之,好好干吧!
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/cd7dffda.html",
+ "url": "https://blog.udon.eu.org/archives/cd7dffda.html",
+ "title": "2020 年终总结",
+ "date_published": "2020-12-31T16:00:00.000Z",
+ "content_html": "2020 马上就要过去了,我想做一个年终总结。
\n \n\n \n非常抱歉鸽了博客四个月。
\n其实十月我忙中偷闲写了一篇文章打算发上来,却被 Hexo 吞了,我也没了重写的热情,就这么让它消失了。
\n2020 我做了什么 ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n2021 我打算做什么 First, 我打算加入学校的 CTF 战队。自学 CTF 的过程中一定会遇到不少有趣的事情,我会抽空写作博文与大家分享的。
\nSecond, 我想和同学合作开发一些有趣的小项目乃至小软件,点子已经有几个存在脑子里了。
\nThen, 我打算磨练一下业务能力,而不是简单地编写脚本一样存在的代码,CSS 也不能写得歪歪扭扭的了(捂脸)。
\n在一切都回归正轨之后,我也会跟上各位的步伐,继续探寻有趣的项目,还请大家多多期待!
\n结语 2020 发生了太多糟糕的事情,祈愿 2021 能遇见更多美好的事情。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/911a91db.html",
+ "url": "https://blog.udon.eu.org/archives/911a91db.html",
+ "title": "博客迁移-主题更换与近况报告",
+ "date_published": "2020-05-04T01:58:17.000Z",
+ "content_html": "又好久不见。
\n这两天抽空更换了博客的主题,并迁移至永久域名 ■■■■■■■■■■■ 。
\n以及谈一谈新开的项目的设计思路,有兴趣的读者还请慢慢阅读。
\n \n\n博客变更 更换域名 ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n更换 Hexo 主题 主题的名字叫 Fluid ,是一款 Material Design 风格的主题。
\n之前我用的主题是 Yilia ,但作者弃坑已久,感觉已跟不上时代变化,就下定决心换了(还把手上所有猫羽雫图全部塞进去了)。
\n同时,我还将评论系统 Gitalk 更换为 utteranc ,对于国内用户加载速度应该会更快,同样需要 GitHub 登录。
\n近况报告 快要高考了好忙啊,完全没有时间研究技术了,也到了应该要努力的时候了(笑)。
\n超级长的寒假里我还是有学习新知识的(指计算机):
\n成绩查询网站 开了一个坑:构建一个成绩查询网站以代替学校那套繁琐的校务系统。
\n■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n项目的前端用了 jQuery、Bootstrap 等框架;
\n后端则是采用我比较熟悉的 Python 后端框架 Django 。
\n \n校园官网查询成绩的流程如下:
\ngraph TB\n校园网主页--输入账号密码 选择身份-->\n校园网内网--找到侧边栏的按钮-->\n成绩查询页面--选择年份 学期 考试场次-->\n得到成绩\n
\n\n如何取代学校的系统呢?思路很简单:黑掉学校的服务器直接把成绩取出来
\n模拟登录就好了。
\n学校的破烂网站采用的是最简单的 GET 表单登录,可以轻松获取 Cookie ,方便进一步操作;
\n我将查询步骤简化为:
\ngraph TB\n查询页面--输入账号密码-->\n下一步1--选择年份 学期-->\n下一步2--选择考试场次-->\n获得成绩\n
\n\n看起来没简化多少,其实 选择身份 和 找到侧边栏按钮 就已经足够烦人了。
\n校园网采用了大量下拉框选择,我将其替换为按钮选择,甚至不用选择,一定程度上提高了查询效率。
\n \n除此之外,我也写了另一个 API 负责查询某成绩查询 APP 上的成绩。
\n我认为这个 API 贡献较校网查询应该更大,这让我摆脱了手机 APP 查询这一烦人的设定。
\n然而开发过程中,后者所花费的力气远小于前者,大概是校网建设得太差的缘故吧。
\n \n除此之外,我还尝试将 API 封装进 Docker,使部署更加简单、快捷。
\nDocker 的使用非常简单,若有兴趣还请多多尝试。
\n果然造轮子学习比干看教程效果要好啊!
\n尾声 没有尾声 : )
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/c7a0a8db.html",
+ "url": "https://blog.udon.eu.org/archives/c7a0a8db.html",
+ "title": "莱卡依然想要回家",
+ "date_published": "2019-10-31T15:00:00.000Z",
+ "content_html": "有幸在/home/rynco/music Channel 遇见这张专辑。第一次听,或许是风格合口,我立刻爱上了这首名为“Laika Still Wants Go Home”,与专辑同名的轻音乐。 很有力量。这是这张专辑给我的第一印象。 我几乎没有乐理知识,对于音乐的评论总是如此无力。这首歌从旋律看也好,从节奏看也好,感觉整体把控得很恰当,张弛有度。我贫瘠的语言真的无法形容那种美的感觉。
\n \n\n \n\n \n \n\n我刷新了对这张专辑的认识,已是数十分钟后。我在听新曲时总会翻翻网易云音乐的评论区,有数条评论对专辑封面的解释。 我第一次听说太空犬这个词是在Littile Buster中,能美·库特莉亚芙卡的名字的由来。库特莉亚芙卡是一头太空犬的本名。 它常被我们称作:莱卡。
\n \n鉴于很少人知道这个故事,我就讲讲吧。 当时正逢美苏争霸,太空竞赛也是美苏交锋的重点。当时的苏联宇航技术不是很发达,但想要胜过美国就要先把航天员送上天。总不能让宇航员乘着从未试验过的飞船就这么上去吧,苏联的科学家就想用动物代替人类完成测试。科学家们在街上找来了几条流浪狗——因为他们觉得流浪狗比家犬够能受冻——这几条狗就被钦定为太空犬了。训练的艰苦不必多说,看看训练人类就能明白,何况是动物。几个月的训练之后,莱卡脱颖而出。到了发射前几天,一位科学家把莱卡带回了家里,让它和孩子们玩耍,因为他们很清楚,这是一次不含回程票的旅行。 苏联当时并未掌握从地球轨道重返地面的技术。 不知道当时在场的所有人的心情是怎样的。应该是兴奋的,毕竟超越老美了;又有点担心,因为即将发射的飞船是赫鲁晓夫下令两周内造出来的;或许有的人会有些不舍吧,他们将要亲手送走一条鲜活的生命。 很可惜,莱卡并不是在氧气或食物耗尽前被安乐死的。飞船的温控系统因赶工与设计问题出现故障。即使风扇再怎样转,舱内俨然成为一个火炉。用好听的话说莱卡是中暑而死;难听点,活活热死。仅管死亡是不可避免的,我也希望它能少一点痛苦啊。
\n \n我想到了安德。在指挥完舰队完成模拟战斗后,在他得知他亲手消灭了虫族的舰队时,他崩溃了。他回忆起刚才他指挥数个小队作为诱饵白白牺牲。他崩溃了。我想当时,我也希望当时,火箭点火升空之时,二级火箭脱离失败之时,舱内温度急剧升高之时,有人能为这条生命感到一丝绝望。
\n \n这张专辑的风格与“OPUS-灵魂之桥”的 OST 有异曲同工之妙。(独立工作室的游戏,OST 不方便放出,希望大家能一起购买。游戏也很棒啊!)都给我以一种末世感。究竟是世界对人绝望还是人对世界绝望呢?
\n \n想象你站在专辑封面的那一点上,眺望地球,这该是多么孤独与悲伤。有评论说载着莱卡的火箭现在仍绕着地球旋转,但很可惜,那台火箭已经在大气层燃尽,这也算是最高规格的葬礼了吧。
\n \n再说说专辑的名称吧。“Laika Still Wants Go Home”,英文,是没有一点问题的。我的母语是中文,因此英文的名称对我无感。但当我要将它翻译成中文,那一刻,我真的愣住了。我脑袋里已经将这几个单词转化为了中文,再一次理解了它们的意思。我僵住了。就几个字停留在嘴边就是说不出来。说不出来……在对面的人疑惑不解我为何停住,思考是否因为自己没有在认真听导致我生气时,一滴眼泪从我疲惫的左眼划过。立刻调动理智。情绪再次平稳,我又调回了内存里的那几个字,念了出来……
\n \n后记
\n从听到这首歌到构思这篇文章再到陆陆续续写出来,我花了数天。因为事情真的很多,脑内的一些想法被琐事代换了。 文字很凌乱,几乎是想到什么写什么。因此我用分割线分开了。 若有新想法我会继续补充的。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/ff412ce9.html",
+ "url": "https://blog.udon.eu.org/archives/ff412ce9.html",
+ "title": "GOROGOA-精华,你的成果如何",
+ "date_published": "2019-10-25T14:30:00.000Z",
+ "content_html": "说在前面 我是怀着激动的心情来写这篇文章的。 我如何定义一部作品是否优秀?很简单。它能达到它的目的,它和优秀沾上边了。就像搞笑视频逗笑你,治愈视频使人放松,致郁视频让你流泪。 而这是一部富有哲理的优秀作品。
\n \n\n预备知识 什么是 GOROGOA GOROGOA
\n中文译名 画中世界 一款解谜类游戏。我还没能抽出时间拜读一下这个故事。
\n什么是Voiceroid、Voiceroid剧场 Voiceroid 是 Vocaloid 的姐妹产品。一个负责朗读,一个负责唱歌。 Voiceroid 剧场是利用 Voiceroid 朗读故事,制作成的动画剧场。
\n亮点 出场人物
\n\n第一章第一节出现的标题
\n \n这是一部挺长的剧场动画,好像是12集,每集20分钟,若是翻译组周更那就是一部番剧了。 由于故事长,本次出演人数也不少。 以下是出场人物京町精华(京町セイカ)、东北切蒲英(東北きりたん)、绁星灯(紲星あかり)、结月缘(結月 ゆかり)、琴叶茜、葵(琴葉 茜・葵)、东北俊子(東北 ずん子),IA等为配角配音。 出场人物基本包括了所有 Voiceriod. 以精华为主人公的作品还是第一次见,绁星灯作为女二号,立绘很好看~
\n
\n\n精华、俊子和俊子的母亲(配角均为黑脸)
\n \n作品特色 本作特色之一是制作良心。精华和灯的立绘说话时嘴巴有动作,虽不是 Live2D,却不失动画感。用于一个故事性强的剧场效果反而比吸睛的 Live2D要好。
\n
\n\n总是板着脸的精华
\n \n第二个亮点就是故事了。GOROGOA 本是一款解谜游戏,听说通关时长也不是很长。作者以游戏为背景设定,巧妙的建立起一个故事框架,用扑朔迷离的剧情吸引了我。
\n观后之感 震撼 理由大致有两点。 其一,作者精心设计的剧情和出乎意料的表现方式让我深感佩服。或许冷静下来看这个故事并不能与那些真正的大作匹敌,但一部能令人深思的剧场已是少见,更应得到关注。 其二,作者敢于直面社会/生活问题,直叙心中之言。能借 Voiceroid 之口说出自己的心声也是我梦想已久的。
\n
\n\n小茜在精华的床上听她讲故事
\n \n若是做一些通俗的赏析。作者选取的立绘真的很好看啊( ̄▽ ̄)。其中谈到的一些问题也深有感触,能引起我的思考。
\n尾声 很期待接下来的剧情走向! 我将我喜爱的作品推荐给你们,希望你们能喜欢!
\n
\n\n附观看地址(已完结)
\n \n\n",
+ "tags": [
+ "随笔"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/category/\351\232\217\347\254\224/rss.xml" "b/category/\351\232\217\347\254\224/rss.xml"
new file mode 100644
index 00000000..ccb7110d
--- /dev/null
+++ "b/category/\351\232\217\347\254\224/rss.xml"
@@ -0,0 +1,1121 @@
+
+
+
+ カレーうどん屋 • Posts by "随笔" category
+ https://blog.udon.eu.org
+ カレーうどん屋.
+ zh-CN
+ Thu, 13 Jun 2024 14:40:00 +0800
+ Thu, 13 Jun 2024 14:40:00 +0800
+ 教程
+ 随笔
+ 软件
+ 小鸡测评
+ 3D 打印
+ 嵌入式开发
+ GitHub Actions
+ Hexo
+ DIY
+ -
+
https://blog.udon.eu.org/archives/95479b1f.html
+ Soundcore C30i 耳机
+ https://blog.udon.eu.org/archives/95479b1f.html
+ 随笔
+ Thu, 13 Jun 2024 14:40:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/adc5a61e.html
+ 日本之行-第二站-奈良
+ https://blog.udon.eu.org/archives/adc5a61e.html
+ 随笔
+ Mon, 11 Mar 2024 00:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/a7050149.html
+ 23 年对我影响最大的硬件与软件
+ https://blog.udon.eu.org/archives/a7050149.html
+ 随笔
+ Sat, 02 Mar 2024 20:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/a534d51a.html
+ 旅行与军粮
+ https://blog.udon.eu.org/archives/a534d51a.html
+ 随笔
+ Mon, 12 Feb 2024 22:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/7777583b.html
+ 被我整坏的路由器和服务器
+ https://blog.udon.eu.org/archives/7777583b.html
+ 随笔
+ Mon, 12 Feb 2024 18:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/aca48136.html
+ 日本之行-第一站-大阪
+ https://blog.udon.eu.org/archives/aca48136.html
+ 随笔
+ Sun, 21 Jan 2024 23:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/2ef8bd61.html
+ 深圳-香港三日行
+ https://blog.udon.eu.org/archives/2ef8bd61.html
+ 随笔
+ Tue, 19 Dec 2023 22:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/42ec6146.html
+ My Second Attempt To ARM Servers
+ https://blog.udon.eu.org/archives/42ec6146.html
+ 随笔
+ Sun, 24 Sep 2023 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/9b78ad2a.html
+ 寻梦穿越机 - 入门浅谈
+ https://blog.udon.eu.org/archives/9b78ad2a.html
+ 随笔
+ Sun, 04 Sep 2022 12:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/9d319c54.html
+ DELL 灵越 15 5547 拆解与更换硅脂
+ https://blog.udon.eu.org/archives/9d319c54.html
+ 随笔
+ Sun, 22 May 2022 10:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/245ab175.html
+ 厌烦了软件的我上了硬件的车
+ https://blog.udon.eu.org/archives/245ab175.html
+ 随笔
+ Sun, 07 Nov 2021 21:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/67d41c7c.html
+ LOOPERS 简评
+ https://blog.udon.eu.org/archives/67d41c7c.html
+ 随笔
+ Mon, 02 Aug 2021 10:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/202ebc76.html
+ 2021 第二学期总结
+ https://blog.udon.eu.org/archives/202ebc76.html
+ 随笔
+ Thu, 15 Jul 2021 09:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/43a7a15c.html
+ Osprey Commet 简评
+ https://blog.udon.eu.org/archives/43a7a15c.html
+ 随笔
+ Sun, 13 Jun 2021 15:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/4a562e35.html
+ 近日败家:Chrombook Duet 与 小米手环6
+ https://blog.udon.eu.org/archives/4a562e35.html
+ 随笔
+ Fri, 30 Apr 2021 21:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/2c0bfb1a.html
+ 校色初体验 / 寒假总结
+ https://blog.udon.eu.org/archives/2c0bfb1a.html
+ 随笔
+ Fri, 26 Feb 2021 22:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/cd7dffda.html
+ 2020 年终总结
+ https://blog.udon.eu.org/archives/cd7dffda.html
+ 随笔
+ Fri, 01 Jan 2021 00:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/911a91db.html
+ 博客迁移-主题更换与近况报告
+ https://blog.udon.eu.org/archives/911a91db.html
+ 随笔
+ Mon, 04 May 2020 09:58:17 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/c7a0a8db.html
+ 莱卡依然想要回家
+ https://blog.udon.eu.org/archives/c7a0a8db.html
+ 随笔
+ Thu, 31 Oct 2019 23:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/ff412ce9.html
+ GOROGOA-精华,你的成果如何
+ https://blog.udon.eu.org/archives/ff412ce9.html
+ 随笔
+ Fri, 25 Oct 2019 22:30:00 +0800
+
+
+
+
diff --git a/content.json b/content.json
new file mode 100644
index 00000000..9c10833e
--- /dev/null
+++ b/content.json
@@ -0,0 +1 @@
+[{"title":"我再也不能不假思索","date":"2024-11-12T15:30:00.000Z","path":"archives/2c54052d.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"Soundcore C30i 耳机","date":"2024-06-13T06:40:00.000Z","path":"archives/95479b1f.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"日本之行-第二站-奈良","date":"2024-03-10T16:30:00.000Z","path":"archives/adc5a61e.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"23 年对我影响最大的硬件与软件","date":"2024-03-02T12:30:00.000Z","path":"archives/a7050149.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"旅行与军粮","date":"2024-02-12T14:30:00.000Z","path":"archives/a534d51a.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"被我整坏的路由器和服务器","date":"2024-02-12T10:00:00.000Z","path":"archives/7777583b.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"日本之行-第一站-大阪","date":"2024-01-21T15:00:00.000Z","path":"archives/aca48136.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"深圳-香港三日行","date":"2023-12-19T14:00:00.000Z","path":"archives/2ef8bd61.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"My Second Attempt To ARM Servers","date":"2023-09-24T04:30:00.000Z","path":"archives/42ec6146.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"修复 UEFI 引导的 GRUB","date":"2023-04-15T16:00:00.000Z","path":"archives/8b68ddd6.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"}]},{"title":"使用 Docker Compose 部署音乐服务器 Navidrome","date":"2023-01-31T04:00:00.000Z","path":"archives/8b115688.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"}]},{"title":"使用 Docker Compose 部署 Keycloak 20","date":"2023-01-22T12:00:00.000Z","path":"archives/f9bfe16a.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"}]},{"title":"寻梦穿越机 - 入门浅谈","date":"2022-09-04T04:00:00.000Z","path":"archives/9b78ad2a.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"使用再生龙 Clonezilla 备份操作系统","date":"2022-08-12T04:30:00.000Z","path":"archives/e74a90f2.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"}]},{"title":"BETAFPV 高频头固件编译 AttributeError","date":"2022-08-06T04:30:00.000Z","path":"archives/f82a3103.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"},{"name":"DIY","slug":"DIY","permalink":"https://blog.udon.eu.org/tags/DIY/"}]},{"title":"DIY 显示器音箱","date":"2022-06-03T07:15:00.000Z","path":"archives/38942a16.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"},{"name":"DIY","slug":"DIY","permalink":"https://blog.udon.eu.org/tags/DIY/"}]},{"title":"迁移 Hexo 渲染环境至 GitHub Actions","date":"2022-05-23T11:30:00.000Z","path":"archives/2e528779.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"},{"name":"GitHub Actions","slug":"GitHub-Actions","permalink":"https://blog.udon.eu.org/tags/GitHub-Actions/"},{"name":"Hexo","slug":"Hexo","permalink":"https://blog.udon.eu.org/tags/Hexo/"}]},{"title":"DELL 灵越 15 5547 拆解与更换硅脂","date":"2022-05-22T02:30:00.000Z","path":"archives/9d319c54.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"Virmach Japan","date":"2022-04-06T14:15:00.000Z","path":"archives/28fa0729.html","tags":[{"name":"小鸡测评","slug":"小鸡测评","permalink":"https://blog.udon.eu.org/tags/%E5%B0%8F%E9%B8%A1%E6%B5%8B%E8%AF%84/"}]},{"title":"玩一玩 DN42","date":"2022-04-01T04:30:00.000Z","path":"archives/dbf21067.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"}]},{"title":"合宙 EPS32-C3 把玩记录(二):WiFi 与一个 Web 程序","date":"2022-03-26T16:15:00.000Z","path":"archives/9b58c98e.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"},{"name":"嵌入式开发","slug":"嵌入式开发","permalink":"https://blog.udon.eu.org/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91/"}]},{"title":"合宙 EPS32-C3 把玩记录(一):环境搭建与第一个程序","date":"2022-03-26T09:30:00.000Z","path":"archives/7f7bd4a5.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"},{"name":"嵌入式开发","slug":"嵌入式开发","permalink":"https://blog.udon.eu.org/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91/"}]},{"title":"Code-Server 的代理配置","date":"2022-03-19T14:30:00.000Z","path":"archives/d01399e6.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"}]},{"title":"Klipper 的外网访问","date":"2022-02-12T06:50:00.000Z","path":"archives/7263a385.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"},{"name":"3D 打印","slug":"3D-打印","permalink":"https://blog.udon.eu.org/tags/3D-%E6%89%93%E5%8D%B0/"}]},{"title":"我与 3D 打印","date":"2022-02-06T15:00:00.000Z","path":"archives/19f3c1e1.html","tags":[{"name":"3D 打印","slug":"3D-打印","permalink":"https://blog.udon.eu.org/tags/3D-%E6%89%93%E5%8D%B0/"}]},{"title":"Deepvm 9929","date":"2022-01-10T13:00:00.000Z","path":"archives/6e832212.html","tags":[{"name":"小鸡测评","slug":"小鸡测评","permalink":"https://blog.udon.eu.org/tags/%E5%B0%8F%E9%B8%A1%E6%B5%8B%E8%AF%84/"}]},{"title":"WikiHost CU4837","date":"2021-12-31T04:30:00.000Z","path":"archives/be3776eb.html","tags":[{"name":"小鸡测评","slug":"小鸡测评","permalink":"https://blog.udon.eu.org/tags/%E5%B0%8F%E9%B8%A1%E6%B5%8B%E8%AF%84/"}]},{"title":"Cloudcone 双十一特价鸡","date":"2021-12-24T14:00:00.000Z","path":"archives/a7b78eea.html","tags":[{"name":"小鸡测评","slug":"小鸡测评","permalink":"https://blog.udon.eu.org/tags/%E5%B0%8F%E9%B8%A1%E6%B5%8B%E8%AF%84/"}]},{"title":"WebHorizon NAT JP","date":"2021-12-23T15:00:00.000Z","path":"archives/9732665c.html","tags":[{"name":"小鸡测评","slug":"小鸡测评","permalink":"https://blog.udon.eu.org/tags/%E5%B0%8F%E9%B8%A1%E6%B5%8B%E8%AF%84/"}]},{"title":"Virmach 7.5刀年付特价机","date":"2021-12-22T09:30:00.000Z","path":"archives/6f963444.html","tags":[{"name":"小鸡测评","slug":"小鸡测评","permalink":"https://blog.udon.eu.org/tags/%E5%B0%8F%E9%B8%A1%E6%B5%8B%E8%AF%84/"}]},{"title":"我的第一台 VPS","date":"2021-12-21T15:30:00.000Z","path":"archives/afe45e8a.html","tags":[{"name":"小鸡测评","slug":"小鸡测评","permalink":"https://blog.udon.eu.org/tags/%E5%B0%8F%E9%B8%A1%E6%B5%8B%E8%AF%84/"}]},{"title":"厌烦了软件的我上了硬件的车","date":"2021-11-07T13:00:00.000Z","path":"archives/245ab175.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"LOOPERS 简评","date":"2021-08-02T02:00:00.000Z","path":"archives/67d41c7c.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"2021 第二学期总结","date":"2021-07-15T01:00:00.000Z","path":"archives/202ebc76.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"Osprey Commet 简评","date":"2021-06-13T07:00:00.000Z","path":"archives/43a7a15c.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"近日败家:Chrombook Duet 与 小米手环6","date":"2021-04-30T13:00:00.000Z","path":"archives/4a562e35.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"群晖搭建 VSCode 服务器与 Syncthing 服务","date":"2021-03-19T16:00:00.000Z","path":"archives/375e7789.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"}]},{"title":"校色初体验 / 寒假总结","date":"2021-02-26T14:00:00.000Z","path":"archives/2c0bfb1a.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"2020 年终总结","date":"2020-12-31T16:00:00.000Z","path":"archives/cd7dffda.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"逃离国产软件 - 虚拟机计划","date":"2020-08-07T04:30:00.000Z","path":"archives/a455b52c.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"},{"name":"软件","slug":"软件","permalink":"https://blog.udon.eu.org/tags/%E8%BD%AF%E4%BB%B6/"}]},{"title":"提升音乐体验-本地音乐标签/歌词匹配与回放增益","date":"2020-05-05T05:40:06.000Z","path":"archives/6b40e5ad.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"},{"name":"软件","slug":"软件","permalink":"https://blog.udon.eu.org/tags/%E8%BD%AF%E4%BB%B6/"}]},{"title":"博客迁移-主题更换与近况报告","date":"2020-05-04T01:58:17.000Z","path":"archives/911a91db.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"BGP初体验-Linux,Openwrt与Quagga","date":"2020-02-10T04:30:00.000Z","path":"archives/e3c95af8.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"}]},{"title":"Joplin+Webdav同步问题的解决方案","date":"2020-01-06T10:00:00.000Z","path":"archives/9d1c6fa4.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"}]},{"title":"莱卡依然想要回家","date":"2019-10-31T15:00:00.000Z","path":"archives/c7a0a8db.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"GOROGOA-精华,你的成果如何","date":"2019-10-25T14:30:00.000Z","path":"archives/ff412ce9.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"iPv6下绝佳的DDNS方法-dynv6","date":"2019-10-03T15:30:00.000Z","path":"archives/27f2d840.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"}]},{"title":"搭建Calibre-Web电子书网页端管理服务","date":"2019-10-02T10:30:00.000Z","path":"archives/a4c81e8f.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"}]},{"title":"秒知APP消息发送教程","date":"2019-09-22T00:00:00.000Z","path":"archives/8ca2a7b3.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"对于“智慧校园”与人脸识别的一些看法","date":"2019-09-14T10:00:00.000Z","path":"archives/330720fc.html","tags":[{"name":"随笔","slug":"随笔","permalink":"https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/"}]},{"title":"群晖-外网访问一站式教程2 DDNS","date":"2019-08-31T10:30:00.000Z","path":"archives/87bacf3f.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"}]},{"title":"群晖-外网访问一站式教程1 Zerotier","date":"2019-08-20T04:00:00.000Z","path":"archives/8776c6d2.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"}]},{"title":"群晖-MC基岩版服务器教程","date":"2019-07-27T15:30:00.000Z","path":"archives/2716e842.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"}]},{"title":"年轻人的第一台NAS(误)-硬件选购","date":"2019-07-19T01:30:46.000Z","path":"archives/757c72a1.html","tags":[{"name":"教程","slug":"教程","permalink":"https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/"}]}]
\ No newline at end of file
diff --git a/css/gitalk.css b/css/gitalk.css
new file mode 100644
index 00000000..a268f1d2
--- /dev/null
+++ b/css/gitalk.css
@@ -0,0 +1,546 @@
+@font-face {
+ font-family: octicons-link;
+ src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff');
+}
+/* variables */
+/* functions & mixins */
+/* variables - calculated */
+/* styles */
+.gt-container {
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ font-size: 16px;
+ /* loader */
+ /* error */
+ /* initing */
+ /* no int */
+ /* link */
+ /* meta */
+ /* popup */
+ /* header */
+ /* comments */
+ /* comment */
+}
+.gt-container * {
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.gt-container a {
+ color: #6190e8;
+}
+.gt-container a:hover {
+ color: #81a6ed;
+ border-color: #81a6ed;
+}
+.gt-container a.is--active {
+ color: #333;
+ cursor: default !important;
+}
+.gt-container a.is--active:hover {
+ color: #333;
+}
+.gt-container .hide {
+ display: none !important;
+}
+.gt-container .gt-svg {
+ display: inline-block;
+ width: 1em;
+ height: 1em;
+ vertical-align: sub;
+}
+.gt-container .gt-svg svg {
+ width: 100%;
+ height: 100%;
+ fill: #6190e8;
+}
+.gt-container .gt-ico {
+ display: inline-block;
+}
+.gt-container .gt-ico-text {
+ margin-left: 0.3125em;
+}
+.gt-container .gt-ico-github {
+ width: 100%;
+ height: 100%;
+}
+.gt-container .gt-ico-github .gt-svg {
+ width: 100%;
+ height: 100%;
+}
+.gt-container .gt-ico-github svg {
+ fill: inherit;
+}
+.gt-container .gt-spinner {
+ position: relative;
+}
+.gt-container .gt-spinner::before {
+ content: '';
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ position: absolute;
+ top: 3px;
+ width: 0.75em;
+ height: 0.75em;
+ margin-top: -0.1875em;
+ margin-left: -0.375em;
+ border-radius: 50%;
+ border: 1px solid #fff;
+ border-top-color: #6190e8;
+ -webkit-animation: gt-kf-rotate 0.6s linear infinite;
+ animation: gt-kf-rotate 0.6s linear infinite;
+}
+.gt-container .gt-loader {
+ position: relative;
+ border: 1px solid #999;
+ -webkit-animation: ease gt-kf-rotate 1.5s infinite;
+ animation: ease gt-kf-rotate 1.5s infinite;
+ display: inline-block;
+ font-style: normal;
+ width: 1.75em;
+ height: 1.75em;
+ line-height: 1.75em;
+ border-radius: 50%;
+}
+.gt-container .gt-loader:before {
+ content: '';
+ position: absolute;
+ display: block;
+ top: 0;
+ left: 50%;
+ margin-top: -0.1875em;
+ margin-left: -0.1875em;
+ width: 0.375em;
+ height: 0.375em;
+ background-color: #999;
+ border-radius: 50%;
+}
+.gt-container .gt-avatar {
+ display: inline-block;
+ width: 3.125em;
+ height: 3.125em;
+}
+@media (max-width: 479px) {
+ .gt-container .gt-avatar {
+ width: 2em;
+ height: 2em;
+ }
+}
+.gt-container .gt-avatar img {
+ width: 100%;
+ height: auto;
+ border-radius: 3px;
+}
+.gt-container .gt-avatar-github {
+ width: 3em;
+ height: 3em;
+}
+@media (max-width: 479px) {
+ .gt-container .gt-avatar-github {
+ width: 1.875em;
+ height: 1.875em;
+ }
+}
+.gt-container .gt-btn {
+ padding: 0.75em 1.25em;
+ display: inline-block;
+ line-height: 1;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ border: 1px solid #6190e8;
+ border-radius: 5px;
+ background-color: #6190e8;
+ color: #fff;
+ outline: none;
+ font-size: 0.75em;
+}
+.gt-container .gt-btn-text {
+ font-weight: 400;
+}
+.gt-container .gt-btn-loading {
+ position: relative;
+ margin-left: 0.5em;
+ display: inline-block;
+ width: 0.75em;
+ height: 1em;
+ vertical-align: top;
+}
+.gt-container .gt-btn.is--disable {
+ cursor: not-allowed;
+ opacity: 0.5;
+}
+.gt-container .gt-btn-login {
+ margin-right: 0;
+}
+.gt-container .gt-btn-preview {
+ background-color: #fff;
+ color: #6190e8;
+}
+.gt-container .gt-btn-preview:hover {
+ background-color: #f2f2f2;
+ border-color: #81a6ed;
+}
+.gt-container .gt-btn-public:hover {
+ background-color: #81a6ed;
+ border-color: #81a6ed;
+}
+.gt-container .gt-error {
+ text-align: center;
+ margin: 0.625em;
+ color: #ff3860;
+}
+.gt-container .gt-initing {
+ padding: 1.25em 0;
+ text-align: center;
+}
+.gt-container .gt-initing-text {
+ margin: 0.625em auto;
+ font-size: 92%;
+}
+.gt-container .gt-no-init {
+ padding: 1.25em 0;
+ text-align: center;
+}
+.gt-container .gt-link {
+ border-bottom: 1px dotted #6190e8;
+}
+.gt-container .gt-link-counts,
+.gt-container .gt-link-project {
+ text-decoration: none;
+}
+.gt-container .gt-meta {
+ margin: 1.25em 0;
+ padding: 1em 0;
+ position: relative;
+ border-bottom: 1px solid #e9e9e9;
+ font-size: 1em;
+ position: relative;
+ z-index: 10;
+}
+.gt-container .gt-meta:before,
+.gt-container .gt-meta:after {
+ content: " ";
+ display: table;
+}
+.gt-container .gt-meta:after {
+ clear: both;
+}
+.gt-container .gt-counts {
+ margin: 0 0.625em 0 0;
+}
+.gt-container .gt-user {
+ float: right;
+ margin: 0;
+ font-size: 92%;
+}
+.gt-container .gt-user-pic {
+ width: 16px;
+ height: 16px;
+ vertical-align: top;
+ margin-right: 0.5em;
+}
+.gt-container .gt-user-inner {
+ display: inline-block;
+ cursor: pointer;
+}
+.gt-container .gt-user .gt-ico {
+ margin: 0 0 0 0.3125em;
+}
+.gt-container .gt-user .gt-ico svg {
+ fill: inherit;
+}
+.gt-container .gt-user .is--poping .gt-ico svg {
+ fill: #6190e8;
+}
+.gt-container .gt-version {
+ color: #a1a1a1;
+ margin-left: 0.375em;
+}
+.gt-container .gt-copyright {
+ margin: 0 0.9375em 0.5em;
+ border-top: 1px solid #e9e9e9;
+ padding-top: 0.5em;
+}
+.gt-container .gt-popup {
+ position: absolute;
+ right: 0;
+ top: 2.375em;
+ background: #fff;
+ display: inline-block;
+ border: 1px solid #e9e9e9;
+ padding: 0.625em 0;
+ font-size: 0.875em;
+ letter-spacing: 0.5px;
+}
+.gt-container .gt-popup .gt-action {
+ cursor: pointer;
+ display: block;
+ margin: 0.5em 0;
+ padding: 0 1.125em;
+ position: relative;
+ text-decoration: none;
+}
+.gt-container .gt-popup .gt-action.is--active:before {
+ content: '';
+ width: 0.25em;
+ height: 0.25em;
+ background: #6190e8;
+ position: absolute;
+ left: 0.5em;
+ top: 0.4375em;
+}
+.gt-container .gt-header {
+ position: relative;
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+}
+.gt-container .gt-header-comment {
+ -webkit-box-flex: 1;
+ -ms-flex: 1;
+ flex: 1;
+ margin-left: 1.25em;
+}
+@media (max-width: 479px) {
+ .gt-container .gt-header-comment {
+ margin-left: 0.875em;
+ }
+}
+.gt-container .gt-header-textarea {
+ padding: 0.75em;
+ display: block;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 100%;
+ min-height: 5.125em;
+ max-height: 15em;
+ border-radius: 5px;
+ border: 1px solid rgba(0,0,0,0.1);
+ font-size: 0.875em;
+ word-wrap: break-word;
+ resize: vertical;
+ background-color: #f6f6f6;
+ outline: none;
+ -webkit-transition: all 0.25s ease;
+ transition: all 0.25s ease;
+}
+.gt-container .gt-header-textarea:hover {
+ background-color: #fbfbfb;
+}
+.gt-container .gt-header-preview {
+ padding: 0.75em;
+ border-radius: 5px;
+ border: 1px solid rgba(0,0,0,0.1);
+ background-color: #f6f6f6;
+}
+.gt-container .gt-header-controls {
+ position: relative;
+ margin: 0.75em 0 0;
+}
+.gt-container .gt-header-controls:before,
+.gt-container .gt-header-controls:after {
+ content: " ";
+ display: table;
+}
+.gt-container .gt-header-controls:after {
+ clear: both;
+}
+@media (max-width: 479px) {
+ .gt-container .gt-header-controls {
+ margin: 0;
+ }
+}
+.gt-container .gt-header-controls-tip {
+ font-size: 0.875em;
+ color: #6190e8;
+ text-decoration: none;
+ vertical-align: sub;
+}
+@media (max-width: 479px) {
+ .gt-container .gt-header-controls-tip {
+ display: none;
+ }
+}
+.gt-container .gt-header-controls .gt-btn {
+ float: right;
+ margin-left: 1.25em;
+}
+@media (max-width: 479px) {
+ .gt-container .gt-header-controls .gt-btn {
+ float: none;
+ width: 100%;
+ margin: 0.75em 0 0;
+ }
+}
+.gt-container:after {
+ content: '';
+ position: fixed;
+ bottom: 100%;
+ left: 0;
+ right: 0;
+ top: 0;
+ opacity: 0;
+}
+.gt-container.gt-input-focused {
+ position: relative;
+}
+.gt-container.gt-input-focused:after {
+ content: '';
+ position: fixed;
+ bottom: 0%;
+ left: 0;
+ right: 0;
+ top: 0;
+ background: #000;
+ opacity: 0.6;
+ -webkit-transition: opacity 0.3s, bottom 0s;
+ transition: opacity 0.3s, bottom 0s;
+ z-index: 9999;
+}
+.gt-container.gt-input-focused .gt-header-comment {
+ z-index: 10000;
+}
+.gt-container .gt-comments {
+ padding-top: 1.25em;
+}
+.gt-container .gt-comments-null {
+ text-align: center;
+}
+.gt-container .gt-comments-controls {
+ margin: 1.25em 0;
+ text-align: center;
+}
+.gt-container .gt-comment {
+ position: relative;
+ padding: 0.625em 0;
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+}
+.gt-container .gt-comment-content {
+ -webkit-box-flex: 1;
+ -ms-flex: 1;
+ flex: 1;
+ margin-left: 1.25em;
+ padding: 0.75em 1em;
+ background-color: #f9f9f9;
+ overflow: auto;
+ -webkit-transition: all ease 0.25s;
+ transition: all ease 0.25s;
+}
+.gt-container .gt-comment-content:hover {
+ -webkit-box-shadow: 0 0.625em 3.75em 0 #f4f4f4;
+ box-shadow: 0 0.625em 3.75em 0 #f4f4f4;
+}
+@media (max-width: 479px) {
+ .gt-container .gt-comment-content {
+ margin-left: 0.875em;
+ padding: 0.625em 0.75em;
+ }
+}
+.gt-container .gt-comment-header {
+ margin-bottom: 0.5em;
+ font-size: 0.875em;
+ position: relative;
+}
+.gt-container .gt-comment-block-1 {
+ float: right;
+ height: 1.375em;
+ width: 2em;
+}
+.gt-container .gt-comment-block-2 {
+ float: right;
+ height: 1.375em;
+ width: 4em;
+}
+.gt-container .gt-comment-username {
+ font-weight: 500;
+ color: #6190e8;
+ text-decoration: none;
+}
+.gt-container .gt-comment-username:hover {
+ text-decoration: underline;
+}
+.gt-container .gt-comment-text {
+ margin-left: 0.5em;
+ color: #a1a1a1;
+}
+.gt-container .gt-comment-date {
+ margin-left: 0.5em;
+ color: #a1a1a1;
+}
+.gt-container .gt-comment-like,
+.gt-container .gt-comment-edit,
+.gt-container .gt-comment-reply {
+ position: absolute;
+ height: 1.375em;
+}
+.gt-container .gt-comment-like:hover,
+.gt-container .gt-comment-edit:hover,
+.gt-container .gt-comment-reply:hover {
+ cursor: pointer;
+}
+.gt-container .gt-comment-like {
+ top: 0;
+ right: 2em;
+}
+.gt-container .gt-comment-edit,
+.gt-container .gt-comment-reply {
+ top: 0;
+ right: 0;
+}
+.gt-container .gt-comment-body {
+ color: #333 !important;
+}
+.gt-container .gt-comment-body .email-hidden-toggle a {
+ display: inline-block;
+ height: 12px;
+ padding: 0 9px;
+ font-size: 12px;
+ font-weight: 600;
+ line-height: 6px;
+ color: #444d56;
+ text-decoration: none;
+ vertical-align: middle;
+ background: #dfe2e5;
+ border-radius: 1px;
+}
+.gt-container .gt-comment-body .email-hidden-toggle a:hover {
+ background-color: #c6cbd1;
+}
+.gt-container .gt-comment-body .email-hidden-reply {
+ display: none;
+ white-space: pre-wrap;
+}
+.gt-container .gt-comment-body .email-hidden-reply .email-signature-reply {
+ padding: 0 15px;
+ margin: 15px 0;
+ color: #586069;
+ border-left: 4px solid #dfe2e5;
+}
+.gt-container .gt-comment-body .email-hidden-reply.expanded {
+ display: block;
+}
+.gt-container .gt-comment-admin .gt-comment-content {
+ background-color: #f6f9fe;
+}
+@-webkit-keyframes gt-kf-rotate {
+ 0% {
+ -webkit-transform: rotate(0);
+ transform: rotate(0);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+@keyframes gt-kf-rotate {
+ 0% {
+ -webkit-transform: rotate(0);
+ transform: rotate(0);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
diff --git a/css/highlight-dark.css b/css/highlight-dark.css
new file mode 100644
index 00000000..13d6ac81
--- /dev/null
+++ b/css/highlight-dark.css
@@ -0,0 +1,62 @@
+pre code.hljs {
+ display: block;
+ overflow-x: auto;
+ padding: 1em
+}
+code.hljs {
+ padding: 3px 5px
+}
+/*
+
+Dark style from softwaremaniacs.org (c) Ivan Sagalaev
+
+*/
+.hljs {
+ color: #ddd;
+ background: #303030
+}
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-literal,
+.hljs-section,
+.hljs-link {
+ color: white
+}
+.hljs-subst {
+ /* default */
+
+}
+.hljs-string,
+.hljs-title,
+.hljs-name,
+.hljs-type,
+.hljs-attribute,
+.hljs-symbol,
+.hljs-bullet,
+.hljs-built_in,
+.hljs-addition,
+.hljs-variable,
+.hljs-template-tag,
+.hljs-template-variable {
+ color: #d88
+}
+.hljs-comment,
+.hljs-quote,
+.hljs-deletion,
+.hljs-meta {
+ color: #979797
+}
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-literal,
+.hljs-title,
+.hljs-section,
+.hljs-doctag,
+.hljs-type,
+.hljs-name,
+.hljs-strong {
+ font-weight: bold
+}
+.hljs-emphasis {
+ font-style: italic
+}
diff --git a/css/highlight.css b/css/highlight.css
new file mode 100644
index 00000000..acd5468e
--- /dev/null
+++ b/css/highlight.css
@@ -0,0 +1,118 @@
+pre code.hljs {
+ display: block;
+ overflow-x: auto;
+ padding: 1em
+}
+code.hljs {
+ padding: 3px 5px
+}
+/*!
+ Theme: GitHub
+ Description: Light theme as seen on github.com
+ Author: github.com
+ Maintainer: @Hirse
+ Updated: 2021-05-15
+
+ Outdated base version: https://github.com/primer/github-syntax-light
+ Current colors taken from GitHub's CSS
+*/
+.hljs {
+ color: #24292e;
+ background: #ffffff
+}
+.hljs-doctag,
+.hljs-keyword,
+.hljs-meta .hljs-keyword,
+.hljs-template-tag,
+.hljs-template-variable,
+.hljs-type,
+.hljs-variable.language_ {
+ /* prettylights-syntax-keyword */
+ color: #d73a49
+}
+.hljs-title,
+.hljs-title.class_,
+.hljs-title.class_.inherited__,
+.hljs-title.function_ {
+ /* prettylights-syntax-entity */
+ color: #6f42c1
+}
+.hljs-attr,
+.hljs-attribute,
+.hljs-literal,
+.hljs-meta,
+.hljs-number,
+.hljs-operator,
+.hljs-variable,
+.hljs-selector-attr,
+.hljs-selector-class,
+.hljs-selector-id {
+ /* prettylights-syntax-constant */
+ color: #005cc5
+}
+.hljs-regexp,
+.hljs-string,
+.hljs-meta .hljs-string {
+ /* prettylights-syntax-string */
+ color: #032f62
+}
+.hljs-built_in,
+.hljs-symbol {
+ /* prettylights-syntax-variable */
+ color: #e36209
+}
+.hljs-comment,
+.hljs-code,
+.hljs-formula {
+ /* prettylights-syntax-comment */
+ color: #6a737d
+}
+.hljs-name,
+.hljs-quote,
+.hljs-selector-tag,
+.hljs-selector-pseudo {
+ /* prettylights-syntax-entity-tag */
+ color: #22863a
+}
+.hljs-subst {
+ /* prettylights-syntax-storage-modifier-import */
+ color: #24292e
+}
+.hljs-section {
+ /* prettylights-syntax-markup-heading */
+ color: #005cc5;
+ font-weight: bold
+}
+.hljs-bullet {
+ /* prettylights-syntax-markup-list */
+ color: #735c0f
+}
+.hljs-emphasis {
+ /* prettylights-syntax-markup-italic */
+ color: #24292e;
+ font-style: italic
+}
+.hljs-strong {
+ /* prettylights-syntax-markup-bold */
+ color: #24292e;
+ font-weight: bold
+}
+.hljs-addition {
+ /* prettylights-syntax-markup-inserted */
+ color: #22863a;
+ background-color: #f0fff4
+}
+.hljs-deletion {
+ /* prettylights-syntax-markup-deleted */
+ color: #b31d28;
+ background-color: #ffeef0
+}
+.hljs-char.escape_,
+.hljs-link,
+.hljs-params,
+.hljs-property,
+.hljs-punctuation,
+.hljs-tag {
+ /* purposely ignored */
+
+}
diff --git a/css/main.css b/css/main.css
new file mode 100644
index 00000000..6d582e06
--- /dev/null
+++ b/css/main.css
@@ -0,0 +1,2241 @@
+.anchorjs-link {
+ text-decoration: none !important;
+ transition: opacity 0.2s ease-in-out;
+}
+.markdown-body h1:hover > .anchorjs-link,
+h2:hover > .anchorjs-link,
+h3:hover > .anchorjs-link,
+h4:hover > .anchorjs-link,
+h5:hover > .anchorjs-link,
+h6:hover > .anchorjs-link {
+ opacity: 1;
+}
+.banner {
+ height: 100%;
+ position: relative;
+ overflow: hidden;
+ cursor: default;
+}
+.banner .mask {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0,0,0,0.3);
+}
+.banner[parallax="true"] {
+ will-change: transform;
+ -webkit-transform-style: preserve-3d;
+ -webkit-backface-visibility: hidden;
+ transition: transform 0.05s ease-out;
+}
+@media (max-width: 100vh) {
+ .header-inner {
+ max-height: 100vw;
+ }
+ #board {
+ margin-top: -1rem !important;
+ }
+}
+@media (max-width: 79.99vh) {
+ .scroll-down-bar {
+ display: none;
+ }
+}
+#board {
+ position: relative;
+ margin-top: -2rem;
+ padding: 3rem 0;
+ background-color: var(--board-bg-color);
+ transition: background-color 0.2s ease-in-out;
+ border-radius: 0.5rem;
+ z-index: 3;
+ -webkit-box-shadow: 0 12px 15px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);
+ box-shadow: 0 12px 15px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);
+}
+.code-widget {
+ display: inline-block;
+ background-color: transparent;
+ font-size: 0.75rem;
+ line-height: 1;
+ font-weight: bold;
+ padding: 0.3rem 0.1rem 0.1rem 0.1rem;
+ position: absolute;
+ right: 0.45rem;
+ top: 0.15rem;
+ z-index: 1;
+}
+.code-widget-light {
+ color: #999;
+}
+.code-widget-dark {
+ color: #bababa;
+}
+.copy-btn {
+ cursor: pointer;
+ user-select: none;
+ -webkit-appearance: none;
+ outline: none;
+}
+.copy-btn > i {
+ font-size: 0.75rem !important;
+ font-weight: 400;
+ margin-right: 0.15rem;
+ opacity: 0;
+ transition: opacity 0.2s ease-in-out;
+}
+.markdown-body pre:hover > .copy-btn > i {
+ opacity: 0.9;
+}
+.markdown-body pre:hover > .copy-btn,
+.markdown-body pre:not(:hover) > .copy-btn {
+ outline: none;
+}
+.license-box {
+ background-color: rgba(27,31,35,0.05);
+ transition: background-color 0.2s ease-in-out;
+ border-radius: 4px;
+ font-size: 0.9rem;
+ overflow: hidden;
+ padding: 1.25rem;
+ position: relative;
+ z-index: 1;
+}
+.license-box .license-icon {
+ position: absolute;
+ top: 50%;
+ left: 100%;
+}
+.license-box .license-icon::after {
+ content: "\e8e4";
+ font-size: 12.5rem;
+ line-height: 1;
+ opacity: 0.1;
+ position: relative;
+ left: -0.85em;
+ bottom: 0.5em;
+ z-index: -1;
+}
+.license-box .license-title {
+ margin-bottom: 1rem;
+}
+.license-box .license-title div:nth-child(1) {
+ line-height: 1.2;
+ margin-bottom: 0.25rem;
+}
+.license-box .license-title div:nth-child(2) {
+ color: var(--sec-text-color);
+ font-size: 0.8rem;
+}
+.license-box .license-meta {
+ align-items: center;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: flex-start;
+}
+.license-box .license-meta .license-meta-item {
+ align-items: center;
+ justify-content: center;
+ margin-right: 1.5rem;
+}
+.license-box .license-meta .license-meta-item div:nth-child(1) {
+ color: var(--sec-text-color);
+ font-size: 0.8rem;
+ font-weight: normal;
+}
+.license-box .license-meta .license-meta-item i.iconfont {
+ font-size: 1rem;
+}
+@media (max-width: 575px) and (min-width: 425px) {
+ .license-box .license-meta .license-meta-item {
+ display: flex;
+ justify-content: flex-start;
+ flex-wrap: wrap;
+ font-size: 0.8rem;
+ flex: 0 0 50%;
+ max-width: 50%;
+ margin-right: 0;
+ }
+ .license-box .license-meta .license-meta-item div:nth-child(1) {
+ margin-right: 0.5rem;
+ }
+ .license-box .license-meta .license-meta-date {
+ order: -1;
+ }
+}
+@media (max-width: 424px) {
+ .license-box::after {
+ top: -65px;
+ }
+ .license-box .license-meta {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+ .license-box .license-meta .license-meta-item {
+ display: flex;
+ flex-wrap: wrap;
+ font-size: 0.8rem;
+ }
+ .license-box .license-meta .license-meta-item div:nth-child(1) {
+ margin-right: 0.5rem;
+ }
+}
+.footer-inner {
+ padding: 3rem 0 1rem 0;
+ text-align: center;
+}
+.footer-inner > div:not(:first-child) {
+ margin: 0.25rem 0;
+ font-size: 0.85rem;
+}
+.footer-inner .statistics {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+}
+.footer-inner .statistics > span {
+ flex: 1;
+ margin: 0 0.25rem;
+}
+.footer-inner .statistics > *:nth-last-child(2):first-child {
+ text-align: right;
+}
+.footer-inner .statistics > *:nth-last-child(2):first-child ~ * {
+ text-align: left;
+}
+.footer-inner .beian {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+}
+.footer-inner .beian > * {
+ margin: 0 0.25rem;
+}
+.footer-inner .beian-police {
+ position: relative;
+ overflow: hidden;
+ display: inline-flex;
+ align-items: center;
+ justify-content: left;
+}
+.footer-inner .beian-police img {
+ margin-right: 3px;
+ width: 1rem;
+ height: 1rem;
+ margin-bottom: 0.1rem;
+}
+@media (max-width: 424px) {
+ .footer-inner .statistics {
+ flex-direction: column;
+ }
+ .footer-inner .statistics > *:nth-last-child(2):first-child {
+ text-align: center;
+ }
+ .footer-inner .statistics > *:nth-last-child(2):first-child ~ * {
+ text-align: center;
+ }
+ .footer-inner .beian {
+ flex-direction: column;
+ }
+ .footer-inner .beian .beian-police {
+ justify-content: center;
+ }
+ .footer-inner .beian > *:nth-last-child(2):first-child {
+ text-align: center;
+ }
+ .footer-inner .beian > *:nth-last-child(2):first-child ~ * {
+ text-align: center;
+ }
+}
+sup > a::before,
+.footnote-text::before {
+ display: block;
+ content: "";
+ margin-top: -5rem;
+ height: 5rem;
+ width: 1px;
+ visibility: hidden;
+}
+sup > a::before,
+.footnote-text::before {
+ display: inline-block;
+}
+.footnote-item::before {
+ display: block;
+ content: "";
+ margin-top: -5rem;
+ height: 5rem;
+ width: 1px;
+ visibility: hidden;
+}
+.footnote-list ol {
+ list-style-type: none;
+ counter-reset: sectioncounter;
+ padding-left: 0.5rem;
+ font-size: 0.95rem;
+}
+.footnote-list ol li:before {
+ font-family: "Helvetica Neue", monospace, "Monaco";
+ content: "[" counter(sectioncounter) "]";
+ counter-increment: sectioncounter;
+}
+.footnote-list ol li+li {
+ margin-top: 0.5rem;
+}
+.footnote-text {
+ padding-left: 0.5em;
+}
+.navbar {
+ background-color: transparent;
+ font-size: 0.875rem;
+ box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16), 0 2px 10px 0 rgba(0,0,0,0.12);
+ -webkit-box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16), 0 2px 10px 0 rgba(0,0,0,0.12);
+}
+.navbar .navbar-brand {
+ color: var(--navbar-text-color);
+}
+.navbar .navbar-toggler .animated-icon span {
+ background-color: var(--navbar-text-color);
+}
+.navbar .nav-item .nav-link {
+ display: block;
+ color: var(--navbar-text-color);
+ transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out;
+}
+.navbar .nav-item .nav-link:hover {
+ color: var(--link-hover-color);
+}
+.navbar .nav-item .nav-link:focus {
+ color: var(--navbar-text-color);
+}
+.navbar .nav-item .nav-link i {
+ font-size: 0.875rem;
+ line-height: inherit;
+}
+.navbar .nav-item .nav-link i:only-child {
+ margin: 0 0.2rem;
+}
+.navbar .navbar-toggler {
+ border-width: 0;
+ outline: 0;
+}
+.navbar.scrolling-navbar {
+ will-change: background, padding;
+ -webkit-transition: background 0.5s ease-in-out, padding 0.5s ease-in-out;
+ transition: background 0.5s ease-in-out, padding 0.5s ease-in-out;
+}
+@media (min-width: 600px) {
+ .navbar.scrolling-navbar {
+ padding-top: 12px;
+ padding-bottom: 12px;
+ }
+ .navbar.scrolling-navbar .navbar-nav > li {
+ -webkit-transition-duration: 1s;
+ transition-duration: 1s;
+ }
+}
+.navbar.scrolling-navbar.top-nav-collapse {
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+.navbar .dropdown-menu {
+ font-size: 0.875rem;
+ color: var(--navbar-text-color);
+ background-color: rgba(0,0,0,0.3);
+ border: none;
+ min-width: 8rem;
+ -webkit-transition: background 0.5s ease-in-out, padding 0.5s ease-in-out;
+ transition: background 0.5s ease-in-out, padding 0.5s ease-in-out;
+}
+@media (max-width: 991.98px) {
+ .navbar .dropdown-menu {
+ text-align: center;
+ }
+}
+.navbar .dropdown-item {
+ color: var(--navbar-text-color);
+}
+.navbar .dropdown-item:hover,
+.navbar .dropdown-item:focus {
+ color: var(--link-hover-color);
+ background-color: rgba(0,0,0,0.1);
+}
+@media (min-width: 992px) {
+ .navbar .dropdown:hover > .dropdown-menu {
+ display: block;
+ }
+ .navbar .dropdown > .dropdown-toggle:active {
+ pointer-events: none;
+ }
+ .navbar .dropdown-menu {
+ top: 95%;
+ }
+}
+.navbar .animated-icon {
+ width: 30px;
+ height: 20px;
+ position: relative;
+ margin: 0;
+ -webkit-transform: rotate(0deg);
+ -moz-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ transform: rotate(0deg);
+ -webkit-transition: 0.5s ease-in-out;
+ -moz-transition: 0.5s ease-in-out;
+ -o-transition: 0.5s ease-in-out;
+ transition: 0.5s ease-in-out;
+ cursor: pointer;
+}
+.navbar .animated-icon span {
+ display: block;
+ position: absolute;
+ height: 3px;
+ width: 100%;
+ border-radius: 9px;
+ opacity: 1;
+ left: 0;
+ -webkit-transform: rotate(0deg);
+ -moz-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ transform: rotate(0deg);
+ -webkit-transition: 0.25s ease-in-out;
+ -moz-transition: 0.25s ease-in-out;
+ -o-transition: 0.25s ease-in-out;
+ transition: 0.25s ease-in-out;
+ background: #fff;
+}
+.navbar .animated-icon span:nth-child(1) {
+ top: 0;
+}
+.navbar .animated-icon span:nth-child(2) {
+ top: 10px;
+}
+.navbar .animated-icon span:nth-child(3) {
+ top: 20px;
+}
+.navbar .animated-icon.open span:nth-child(1) {
+ top: 11px;
+ -webkit-transform: rotate(135deg);
+ -moz-transform: rotate(135deg);
+ -o-transform: rotate(135deg);
+ transform: rotate(135deg);
+}
+.navbar .animated-icon.open span:nth-child(2) {
+ opacity: 0;
+ left: -60px;
+}
+.navbar .animated-icon.open span:nth-child(3) {
+ top: 11px;
+ -webkit-transform: rotate(-135deg);
+ -moz-transform: rotate(-135deg);
+ -o-transform: rotate(-135deg);
+ transform: rotate(-135deg);
+}
+.navbar .dropdown-collapse,
+.top-nav-collapse,
+.navbar-col-show {
+ background-color: var(--navbar-bg-color);
+}
+@media (max-width: 767px) {
+ .navbar {
+ font-size: 1rem;
+ line-height: 2.5rem;
+ }
+}
+.banner-text {
+ color: var(--subtitle-color);
+ max-width: calc(960px - 6rem);
+ width: 80%;
+ overflow-wrap: break-word;
+}
+.banner-text .typed-cursor {
+ margin: 0 0.2rem;
+}
+@media (max-width: 767px) {
+ #subtitle,
+ .typed-cursor {
+ font-size: 1.5rem;
+ }
+}
+@media (max-width: 575px) {
+ .banner-text {
+ font-size: 0.9rem;
+ }
+ #subtitle,
+ .typed-cursor {
+ font-size: 1.35rem;
+ }
+}
+.modal-dialog .modal-content {
+ background-color: var(--board-bg-color);
+ border: 0;
+ border-radius: 0.125rem;
+ -webkit-box-shadow: 0 5px 11px 0 rgba(0,0,0,0.18), 0 4px 15px 0 rgba(0,0,0,0.15);
+ box-shadow: 0 5px 11px 0 rgba(0,0,0,0.18), 0 4px 15px 0 rgba(0,0,0,0.15);
+}
+.modal-dialog .modal-content .modal-header {
+ border-bottom-color: var(--line-color);
+ transition: border-bottom-color 0.2s ease-in-out;
+}
+.close {
+ color: var(--text-color);
+}
+.close:hover {
+ color: var(--link-hover-color);
+}
+.close:focus {
+ outline: 0;
+}
+.modal-dialog .modal-content .modal-header {
+ border-top-left-radius: 0.125rem;
+ border-top-right-radius: 0.125rem;
+ border-bottom: 1px solid #dee2e6;
+}
+.md-form {
+ position: relative;
+ margin-top: 1.5rem;
+ margin-bottom: 1.5rem;
+}
+.md-form input[type] {
+ -webkit-box-sizing: content-box;
+ box-sizing: content-box;
+ background-color: transparent;
+ border: none;
+ border-bottom: 1px solid #ced4da;
+ border-radius: 0;
+ outline: none;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+}
+.md-form input[type]:focus:not([readonly]) {
+ border-bottom: 1px solid #4285f4;
+ -webkit-box-shadow: 0 1px 0 0 #4285f4;
+ box-shadow: 0 1px 0 0 #4285f4;
+}
+.md-form input[type]:focus:not([readonly]) + label {
+ color: #4285f4;
+}
+.md-form input[type].valid,
+.md-form input[type]:focus.valid {
+ border-bottom: 1px solid #00c851;
+ -webkit-box-shadow: 0 1px 0 0 #00c851;
+ box-shadow: 0 1px 0 0 #00c851;
+}
+.md-form input[type].valid + label,
+.md-form input[type]:focus.valid + label {
+ color: #00c851;
+}
+.md-form input[type].invalid,
+.md-form input[type]:focus.invalid {
+ border-bottom: 1px solid #f44336;
+ -webkit-box-shadow: 0 1px 0 0 #f44336;
+ box-shadow: 0 1px 0 0 #f44336;
+}
+.md-form input[type].invalid + label,
+.md-form input[type]:focus.invalid + label {
+ color: #f44336;
+}
+.md-form input[type].validate {
+ margin-bottom: 2.5rem;
+}
+.md-form input[type].form-control {
+ height: auto;
+ padding: 0.6rem 0 0.4rem 0;
+ margin: 0 0 0.5rem 0;
+ color: var(--text-color);
+ background-color: transparent;
+ border-radius: 0;
+}
+.md-form label {
+ font-size: 0.8rem;
+ position: absolute;
+ top: -1rem;
+ left: 0;
+ color: #757575;
+ cursor: text;
+ transition: color 0.2s ease-out;
+}
+.modal-open[style] {
+ padding-right: 0 !important;
+ overflow: auto;
+}
+.modal-open[style] #navbar[style] {
+ padding-right: 1rem !important;
+}
+#nprogress .bar {
+ height: 3px !important;
+ background-color: #29d !important;
+}
+#nprogress .peg {
+ box-shadow: 0 0 14px #29d, 0 0 8px #29d !important;
+}
+@media (max-width: 575px) {
+ #nprogress .bar {
+ display: none;
+ }
+}
+.noscript-warning {
+ background-color: #f55;
+ color: #fff;
+ font-family: sans-serif;
+ font-size: 1rem;
+ font-weight: bold;
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ text-align: center;
+ width: 100%;
+ z-index: 99;
+}
+.pagination {
+ margin-top: 3rem;
+ justify-content: center;
+}
+.pagination .space {
+ align-self: flex-end;
+}
+.pagination .page-number,
+.pagination .current,
+.pagination .extend {
+ outline: 0;
+ border: 0;
+ background-color: transparent;
+ font-size: 0.9rem;
+ padding: 0.5rem 0.75rem;
+ line-height: 1.25;
+ border-radius: 0.125rem;
+}
+.pagination .page-number {
+ margin: 0 0.05rem;
+}
+.pagination .page-number:hover,
+.pagination .current {
+ transition: background-color 0.2s ease-in-out;
+ background-color: var(--link-hover-bg-color);
+}
+.qr-trigger {
+ cursor: pointer;
+ position: relative;
+}
+.qr-trigger:hover .qr-img {
+ display: block;
+ transition: all 0.3s;
+}
+.qr-img {
+ max-width: 12rem;
+ position: absolute;
+ right: -5.25rem;
+ z-index: 99;
+ display: none;
+ border-radius: 0.2rem;
+ background-color: transparent;
+ box-shadow: 0 0 20px -5px rgba(158,158,158,0.2);
+}
+.scroll-down-bar {
+ position: absolute;
+ width: 100%;
+ height: 6rem;
+ text-align: center;
+ cursor: pointer;
+ bottom: 0;
+}
+.scroll-down-bar i.iconfont {
+ font-size: 2rem;
+ font-weight: bold;
+ display: inline-block;
+ position: relative;
+ padding-top: 2rem;
+ color: var(--subtitle-color);
+ transform: translateZ(0);
+ animation: scroll-down 1.5s infinite;
+}
+#scroll-top-button {
+ position: fixed;
+ z-index: 99;
+ background: var(--board-bg-color);
+ transition: background-color 0.2s ease-in-out, bottom 0.3s ease;
+ border-radius: 4px;
+ min-width: 40px;
+ min-height: 40px;
+ bottom: -60px;
+ outline: none;
+ display: flex;
+ display: -webkit-flex;
+ align-items: center;
+ box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16), 0 2px 10px 0 rgba(0,0,0,0.12);
+}
+#scroll-top-button i {
+ font-size: 32px;
+ margin: auto;
+ color: var(--sec-text-color);
+}
+#scroll-top-button:hover i,
+#scroll-top-button:active i {
+ animation-name: scroll-top;
+ animation-duration: 1s;
+ animation-delay: 0.1s;
+ animation-timing-function: ease-in-out;
+ animation-iteration-count: infinite;
+ animation-fill-mode: forwards;
+ animation-direction: alternate;
+}
+#local-search-result .search-list-title {
+ border-left: 3px solid #0d47a1;
+}
+#local-search-result .search-list-content {
+ padding: 0 1.25rem;
+}
+#local-search-result .search-word {
+ color: #ff4500;
+}
+#toc {
+ visibility: hidden;
+}
+.toc-header {
+ margin-bottom: 0.5rem;
+ font-weight: bold;
+ line-height: 1.2;
+}
+.toc-header,
+.toc-header > i {
+ font-size: 1.25rem;
+}
+.toc-body {
+ max-height: 75vh;
+ overflow-y: auto;
+ overflow: -moz-scrollbars-none;
+ -ms-overflow-style: none;
+}
+.toc-body ol {
+ list-style: none;
+ padding-inline-start: 1rem;
+}
+.toc-body::-webkit-scrollbar {
+ display: none;
+}
+.tocbot-list {
+ position: relative;
+}
+.tocbot-list ol {
+ list-style: none;
+ padding-left: 1rem;
+}
+.tocbot-list a {
+ font-size: 0.95rem;
+}
+.tocbot-link {
+ color: var(--text-color);
+}
+.tocbot-active-link {
+ font-weight: bold;
+ color: var(--link-hover-color);
+}
+.tocbot-is-collapsed {
+ max-height: 0;
+}
+.tocbot-is-collapsible {
+ overflow: hidden;
+ transition: all 0.3s ease-in-out;
+}
+.toc-list-item {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.toc-list-item.is-active-li::before {
+ height: 1rem;
+ margin: 0.25rem 0;
+ visibility: visible;
+}
+.toc-list-item::before {
+ width: 0.15rem;
+ height: 0.2rem;
+ position: absolute;
+ left: 0.25rem;
+ content: "";
+ border-radius: 2px;
+ margin: 0.65rem 0;
+ background: var(--link-hover-color);
+ visibility: hidden;
+ transition: height 0.1s ease-in-out, margin 0.1s ease-in-out, visibility 0.1s ease-in-out;
+}
+.sidebar {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 2rem;
+ padding: 3rem 0;
+}
+html {
+ font-size: 16px;
+ letter-spacing: 0.02em;
+}
+html,
+body {
+ height: 100%;
+ font-family: var(--font-family-sans-serif);
+ overflow-wrap: break-word;
+}
+body {
+ transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out;
+ background-color: var(--body-bg-color);
+ color: var(--text-color);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+body a {
+ color: var(--text-color);
+ text-decoration: none;
+ cursor: pointer;
+ transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out;
+}
+body a:hover {
+ color: var(--link-hover-color);
+ text-decoration: none;
+ transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out;
+}
+code {
+ color: inherit;
+}
+table {
+ font-size: inherit;
+ color: var(--post-text-color);
+}
+img[lazyload] {
+ object-fit: cover;
+}
+*[align="left"] {
+ text-align: left;
+}
+*[align="center"] {
+ text-align: center;
+}
+*[align="right"] {
+ text-align: right;
+}
+::-webkit-scrollbar {
+ width: 6px;
+ height: 6px;
+}
+::-webkit-scrollbar-thumb {
+ background-color: var(--scrollbar-color);
+ border-radius: 6px;
+}
+::-webkit-scrollbar-thumb:hover {
+ background-color: var(--scrollbar-hover-color);
+}
+::-webkit-scrollbar-corner {
+ background-color: transparent;
+}
+label {
+ margin-bottom: 0;
+}
+i.iconfont {
+ font-size: 1em;
+ line-height: 1;
+}
+:root {
+ --color-mode: "light";
+ --body-bg-color: #eee;
+ --board-bg-color: #fff;
+ --text-color: #3c4858;
+ --sec-text-color: #718096;
+ --post-text-color: #2c3e50;
+ --post-heading-color: #1a202c;
+ --post-link-color: #0366d6;
+ --link-hover-color: #30a9de;
+ --link-hover-bg-color: #f8f9fa;
+ --line-color: #eaecef;
+ --navbar-bg-color: #2f4154;
+ --navbar-text-color: #fff;
+ --subtitle-color: #fff;
+ --scrollbar-color: #c4c6c9;
+ --scrollbar-hover-color: #a6a6a6;
+ --button-bg-color: transparent;
+ --button-hover-bg-color: #f2f3f5;
+ --highlight-bg-color: #f6f8fa;
+ --inlinecode-bg-color: rgba(175,184,193,0.2);
+ --fold-title-color: #3c4858;
+ --fold-border-color: #eaecef;
+}
+@media (prefers-color-scheme: dark) {
+ :root {
+ --color-mode: "dark";
+ }
+ :root:not([data-user-color-scheme]) {
+ --body-bg-color: #181c27;
+ --board-bg-color: #252d38;
+ --text-color: #c4c6c9;
+ --sec-text-color: #a7a9ad;
+ --post-text-color: #c4c6c9;
+ --post-heading-color: #c4c6c9;
+ --post-link-color: #1589e9;
+ --link-hover-color: #30a9de;
+ --link-hover-bg-color: #364151;
+ --line-color: #435266;
+ --navbar-bg-color: #1f3144;
+ --navbar-text-color: #d0d0d0;
+ --subtitle-color: #d0d0d0;
+ --scrollbar-color: #687582;
+ --scrollbar-hover-color: #9da8b3;
+ --button-bg-color: transparent;
+ --button-hover-bg-color: #46647e;
+ --highlight-bg-color: #303030;
+ --inlinecode-bg-color: rgba(99,110,123,0.4);
+ --fold-title-color: #c4c6c9;
+ --fold-border-color: #435266;
+ }
+ :root:not([data-user-color-scheme]) img {
+ -webkit-filter: brightness(0.9);
+ filter: brightness(0.9);
+ transition: filter 0.2s ease-in-out;
+ }
+ :root:not([data-user-color-scheme]) .license-box {
+ background-color: rgba(62,75,94,0.35);
+ transition: background-color 0.2s ease-in-out;
+ }
+ :root:not([data-user-color-scheme]) .gt-comment-admin .gt-comment-content {
+ background-color: transparent;
+ transition: background-color 0.2s ease-in-out;
+ }
+}
+@media not print {
+ [data-user-color-scheme="dark"] {
+ --body-bg-color: #181c27;
+ --board-bg-color: #252d38;
+ --text-color: #c4c6c9;
+ --sec-text-color: #a7a9ad;
+ --post-text-color: #c4c6c9;
+ --post-heading-color: #c4c6c9;
+ --post-link-color: #1589e9;
+ --link-hover-color: #30a9de;
+ --link-hover-bg-color: #364151;
+ --line-color: #435266;
+ --navbar-bg-color: #1f3144;
+ --navbar-text-color: #d0d0d0;
+ --subtitle-color: #d0d0d0;
+ --scrollbar-color: #687582;
+ --scrollbar-hover-color: #9da8b3;
+ --button-bg-color: transparent;
+ --button-hover-bg-color: #46647e;
+ --highlight-bg-color: #303030;
+ --inlinecode-bg-color: rgba(99,110,123,0.4);
+ --fold-title-color: #c4c6c9;
+ --fold-border-color: #435266;
+ }
+ [data-user-color-scheme="dark"] img {
+ -webkit-filter: brightness(0.9);
+ filter: brightness(0.9);
+ transition: filter 0.2s ease-in-out;
+ }
+ [data-user-color-scheme="dark"] .license-box {
+ background-color: rgba(62,75,94,0.35);
+ transition: background-color 0.2s ease-in-out;
+ }
+ [data-user-color-scheme="dark"] .gt-comment-admin .gt-comment-content {
+ background-color: transparent;
+ transition: background-color 0.2s ease-in-out;
+ }
+}
+@media print {
+ :root {
+ --color-mode: "light";
+ }
+}
+.fade-in-up {
+ -webkit-animation-name: fade-in-up;
+ animation-name: fade-in-up;
+}
+.hidden-mobile {
+ display: block;
+}
+.visible-mobile {
+ display: none;
+}
+@media (max-width: 575px) {
+ .hidden-mobile {
+ display: none;
+ }
+ .visible-mobile {
+ display: block;
+ }
+}
+.nomargin-x {
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+}
+.nopadding-x {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+}
+@media (max-width: 767px) {
+ .nopadding-x-md {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+ }
+}
+.flex-center {
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ height: 100%;
+}
+.hover-with-bg {
+ display: inline-block;
+ line-height: 1;
+}
+.hover-with-bg:hover {
+ background-color: var(--link-hover-bg-color);
+ transition-duration: 0.2s;
+ transition-timing-function: ease-in-out;
+ border-radius: 0.2rem;
+}
+@-moz-keyframes fade-in-up {
+ from {
+ opacity: 0;
+ -webkit-transform: translate3d(0, 100%, 0);
+ transform: translate3d(0, 100%, 0);
+ }
+ to {
+ opacity: 1;
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
+ }
+}
+@-webkit-keyframes fade-in-up {
+ from {
+ opacity: 0;
+ -webkit-transform: translate3d(0, 100%, 0);
+ transform: translate3d(0, 100%, 0);
+ }
+ to {
+ opacity: 1;
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
+ }
+}
+@-o-keyframes fade-in-up {
+ from {
+ opacity: 0;
+ -webkit-transform: translate3d(0, 100%, 0);
+ transform: translate3d(0, 100%, 0);
+ }
+ to {
+ opacity: 1;
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
+ }
+}
+@keyframes fade-in-up {
+ from {
+ opacity: 0;
+ -webkit-transform: translate3d(0, 100%, 0);
+ transform: translate3d(0, 100%, 0);
+ }
+ to {
+ opacity: 1;
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
+ }
+}
+@-moz-keyframes scroll-down {
+ 0% {
+ opacity: 0.8;
+ top: 0;
+ }
+ 50% {
+ opacity: 0.4;
+ top: -1em;
+ }
+ 100% {
+ opacity: 0.8;
+ top: 0;
+ }
+}
+@-webkit-keyframes scroll-down {
+ 0% {
+ opacity: 0.8;
+ top: 0;
+ }
+ 50% {
+ opacity: 0.4;
+ top: -1em;
+ }
+ 100% {
+ opacity: 0.8;
+ top: 0;
+ }
+}
+@-o-keyframes scroll-down {
+ 0% {
+ opacity: 0.8;
+ top: 0;
+ }
+ 50% {
+ opacity: 0.4;
+ top: -1em;
+ }
+ 100% {
+ opacity: 0.8;
+ top: 0;
+ }
+}
+@keyframes scroll-down {
+ 0% {
+ opacity: 0.8;
+ top: 0;
+ }
+ 50% {
+ opacity: 0.4;
+ top: -1em;
+ }
+ 100% {
+ opacity: 0.8;
+ top: 0;
+ }
+}
+@-moz-keyframes scroll-top {
+ 0% {
+ -webkit-transform: translateY(0);
+ transform: translateY(0);
+ }
+ 50% {
+ -webkit-transform: translateY(-0.35rem);
+ transform: translateY(-0.35rem);
+ }
+ 100% {
+ -webkit-transform: translateY(0);
+ transform: translateY(0);
+ }
+}
+@-webkit-keyframes scroll-top {
+ 0% {
+ -webkit-transform: translateY(0);
+ transform: translateY(0);
+ }
+ 50% {
+ -webkit-transform: translateY(-0.35rem);
+ transform: translateY(-0.35rem);
+ }
+ 100% {
+ -webkit-transform: translateY(0);
+ transform: translateY(0);
+ }
+}
+@-o-keyframes scroll-top {
+ 0% {
+ -webkit-transform: translateY(0);
+ transform: translateY(0);
+ }
+ 50% {
+ -webkit-transform: translateY(-0.35rem);
+ transform: translateY(-0.35rem);
+ }
+ 100% {
+ -webkit-transform: translateY(0);
+ transform: translateY(0);
+ }
+}
+@keyframes scroll-top {
+ 0% {
+ -webkit-transform: translateY(0);
+ transform: translateY(0);
+ }
+ 50% {
+ -webkit-transform: translateY(-0.35rem);
+ transform: translateY(-0.35rem);
+ }
+ 100% {
+ -webkit-transform: translateY(0);
+ transform: translateY(0);
+ }
+}
+@media print {
+ header,
+ footer,
+ .side-col,
+ #scroll-top-button,
+ .post-prevnext,
+ #comments {
+ display: none !important;
+ }
+ .markdown-body a:not([href^='#']):not([href^='javascript:']):not(.print-no-link)::after {
+ content: ' (' attr(href) ')';
+ font-size: 0.8rem;
+ color: var(--post-text-color);
+ opacity: 0.8;
+ }
+ .markdown-body > h1,
+ .markdown-body h2 {
+ border-bottom-color: transparent !important;
+ }
+ .markdown-body > h1,
+ .markdown-body h2,
+ .markdown-body h3,
+ .markdown-body h4,
+ .markdown-body h5,
+ .markdown-body h6 {
+ margin-top: 1.25em !important;
+ margin-bottom: 0.25em !important;
+ }
+ .markdown-body [data-anchorjs-icon]::after {
+ display: none;
+ }
+ .markdown-body figure.highlight table,
+ .markdown-body figure.highlight tbody,
+ .markdown-body figure.highlight tr,
+ .markdown-body figure.highlight td.code,
+ .markdown-body figure.highlight td.code pre {
+ width: 100% !important;
+ display: block !important;
+ }
+ .markdown-body figure.highlight pre > code {
+ white-space: pre-wrap;
+ }
+ .markdown-body figure.highlight .gutter,
+ .markdown-body figure.highlight .code-widget {
+ display: none !important;
+ }
+ .post-metas a {
+ text-decoration: none;
+ }
+}
+@media not print {
+ #seo-header {
+ display: none;
+ }
+}
+.index-card {
+ margin-bottom: 2.5rem;
+}
+.index-img img {
+ display: block;
+ width: 100%;
+ height: 10rem;
+ object-fit: cover;
+ box-shadow: 0 5px 11px 0 rgba(0,0,0,0.18), 0 4px 15px 0 rgba(0,0,0,0.15);
+ border-radius: 0.25rem;
+ background-color: transparent;
+}
+.index-info {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+.index-header {
+ color: var(--text-color);
+ font-size: 1.5rem;
+ font-weight: bold;
+ line-height: 1.4;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin-bottom: 0.25rem;
+}
+.index-header .index-pin {
+ color: var(--text-color);
+ font-size: 1.5rem;
+ margin-right: 0.15rem;
+}
+.index-btm {
+ color: var(--sec-text-color);
+}
+.index-btm a {
+ color: var(--sec-text-color);
+}
+.index-excerpt {
+ color: var(--sec-text-color);
+ margin: 0.5rem 0;
+ height: calc(1.4rem * 3);
+ overflow: hidden;
+ display: flex;
+}
+.index-excerpt > div {
+ width: 100%;
+ line-height: 1.4rem;
+ word-break: break-word;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 3;
+}
+.index-excerpt__noimg {
+ height: auto;
+ max-height: calc(1.4rem * 3);
+}
+@media (max-width: 767px) {
+ .index-info {
+ padding-top: 1.25rem;
+ }
+ .index-header {
+ font-size: 1.25rem;
+ white-space: normal;
+ overflow: hidden;
+ word-break: break-word;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ }
+ .index-header .index-pin {
+ font-size: 1.25rem;
+ }
+ .index-excerpt {
+ height: auto;
+ max-height: calc(1.4rem * 3);
+ margin: 0.25rem 0;
+ }
+}
+#valine.v[data-class=v] .status-bar,
+#valine.v[data-class=v] .veditor,
+#valine.v[data-class=v] .vinput,
+#valine.v[data-class=v] .vbtn,
+#valine.v[data-class=v] p,
+#valine.v[data-class=v] pre code {
+ color: var(--text-color);
+}
+#valine.v[data-class=v] .vinput::placeholder {
+ color: var(--sec-text-color);
+}
+#valine.v[data-class=v] .vicon {
+ fill: var(--text-color);
+}
+.gt-container .gt-comment-content:hover {
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+.gt-container .gt-comment-body {
+ color: var(--text-color) !important;
+ transition: color 0.2s ease-in-out;
+}
+#remark-km423lmfdslkm34-back {
+ z-index: 1030;
+}
+#remark-km423lmfdslkm34-node {
+ z-index: 1031;
+}
+.markdown-body .highlight pre,
+.markdown-body pre {
+ padding: 1.45rem 1rem;
+}
+.markdown-body pre code.hljs {
+ padding: 0;
+}
+.markdown-body pre[class*="language-"] {
+ padding-top: 1.45rem;
+ padding-bottom: 1.45rem;
+ padding-right: 1rem;
+ line-height: 1.5;
+ margin-bottom: 1rem;
+}
+.markdown-body .code-wrapper {
+ position: relative;
+ border-radius: 4px;
+ margin-bottom: 1rem;
+}
+.markdown-body .hljs,
+.markdown-body .highlight pre,
+.markdown-body .code-wrapper pre,
+.markdown-body figure.highlight td.gutter {
+ transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out;
+ background-color: var(--highlight-bg-color);
+}
+pre[class*=language-].line-numbers {
+ position: initial;
+}
+figure {
+ margin: 1rem 0;
+}
+figure.highlight {
+ position: relative;
+}
+figure.highlight table {
+ border: 0;
+ margin: 0;
+ width: auto;
+ border-radius: 4px;
+}
+figure.highlight td {
+ border: 0;
+ padding: 0;
+}
+figure.highlight tr {
+ border: 0;
+}
+figure.highlight td.code {
+ width: 100%;
+}
+figure.highlight td.gutter {
+ display: table-cell;
+ position: -webkit-sticky;
+ position: sticky;
+ left: 0;
+ z-index: 1;
+}
+figure.highlight td.gutter pre {
+ text-align: right;
+ padding: 0 0.75rem;
+ border-radius: initial;
+ border-right: 1px solid #999;
+}
+figure.highlight td.gutter pre span.line {
+ color: #999;
+}
+figure.highlight td.code > pre {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.markdown-body {
+ font-size: 1rem;
+ line-height: 1.6;
+ font-family: var(--font-family-sans-serif);
+ margin-bottom: 2rem;
+ color: var(--post-text-color);
+}
+.markdown-body > h1,
+.markdown-body h2 {
+ border-bottom-color: var(--line-color);
+}
+.markdown-body > h1,
+.markdown-body h2,
+.markdown-body h3,
+.markdown-body h4,
+.markdown-body h5,
+.markdown-body h6 {
+ color: var(--post-heading-color);
+ transition: color 0.2s ease-in-out, border-bottom-color 0.2s ease-in-out;
+ font-weight: bold;
+ margin-bottom: 0.75em;
+ margin-top: 2em;
+}
+.markdown-body > h1::before,
+.markdown-body h2::before,
+.markdown-body h3::before,
+.markdown-body h4::before,
+.markdown-body h5::before,
+.markdown-body h6::before {
+ display: block;
+ content: "";
+ margin-top: -5rem;
+ height: 5rem;
+ width: 1px;
+ visibility: hidden;
+}
+.markdown-body > h1:focus,
+.markdown-body h2:focus,
+.markdown-body h3:focus,
+.markdown-body h4:focus,
+.markdown-body h5:focus,
+.markdown-body h6:focus {
+ outline: none;
+}
+.markdown-body a {
+ color: var(--post-link-color);
+}
+.markdown-body strong {
+ font-weight: bold;
+}
+.markdown-body code {
+ tab-size: 4;
+ background-color: var(--inlinecode-bg-color);
+ transition: background-color 0.2s ease-in-out;
+}
+.markdown-body table tr {
+ background-color: var(--board-bg-color);
+ transition: background-color 0.2s ease-in-out;
+}
+.markdown-body table tr:nth-child(2n) {
+ background-color: var(--board-bg-color);
+ transition: background-color 0.2s ease-in-out;
+}
+.markdown-body table th,
+.markdown-body table td {
+ border-color: var(--line-color);
+ transition: border-color 0.2s ease-in-out;
+}
+.markdown-body pre {
+ font-size: 85% !important;
+}
+.markdown-body pre .mermaid {
+ text-align: center;
+}
+.markdown-body pre .mermaid > svg {
+ min-width: 100%;
+}
+.markdown-body p > img,
+.markdown-body p > a > img,
+.markdown-body figure > img,
+.markdown-body figure > a > img {
+ max-width: 90%;
+ margin: 1.5rem auto;
+ display: block;
+ box-shadow: 0 5px 11px 0 rgba(0,0,0,0.18), 0 4px 15px 0 rgba(0,0,0,0.15);
+ border-radius: 4px;
+ background-color: transparent;
+}
+.markdown-body blockquote {
+ color: var(--sec-text-color);
+}
+.markdown-body details {
+ cursor: pointer;
+}
+.markdown-body details summary {
+ outline: none;
+}
+hr,
+.markdown-body hr {
+ background-color: initial;
+ border-top: 1px solid var(--line-color);
+ transition: border-top-color 0.2s ease-in-out;
+}
+.markdown-body hr {
+ height: 0;
+ margin: 2rem 0;
+}
+.markdown-body figcaption.image-caption {
+ font-size: 0.8rem;
+ color: var(--post-text-color);
+ opacity: 0.65;
+ line-height: 1;
+ margin: -0.75rem auto 2rem;
+ text-align: center;
+}
+.markdown-body figcaption:not(.image-caption) {
+ display: none;
+}
+.post-content,
+post-custom {
+ box-sizing: border-box;
+ padding-left: 10%;
+ padding-right: 10%;
+}
+@media (max-width: 767px) {
+ .post-content,
+ post-custom {
+ padding-left: 2rem;
+ padding-right: 2rem;
+ }
+ .page-content,
+ .post-content {
+ overflow: hidden;
+ }
+}
+@media (max-width: 424px) {
+ .post-content,
+ post-custom {
+ padding-left: 1rem;
+ padding-right: 1rem;
+ }
+ .page-content,
+ .post-content {
+ overflow: hidden;
+ }
+ .anchorjs-link-left {
+ opacity: 0 !important;
+ }
+}
+.page-content strong,
+.post-content strong {
+ font-weight: bold;
+}
+.page-content > *:nth-child(2),
+.post-content > *:nth-child(2) {
+ margin-top: 0;
+}
+.page-content img,
+.post-content img {
+ object-fit: cover;
+ max-width: 100%;
+}
+.post-metas {
+ display: flex;
+ flex-wrap: wrap;
+ font-size: 0.9rem;
+}
+.post-meta > *:not(.hover-with-bg) {
+ margin-right: 0.2rem;
+}
+.post-prevnext {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ font-size: 0.9rem;
+ margin-left: -0.35rem;
+ margin-right: -0.35rem;
+}
+.post-prevnext .post-prev,
+.post-prevnext .post-next {
+ display: flex;
+ padding-left: 0;
+ padding-right: 0;
+}
+.post-prevnext .post-prev i,
+.post-prevnext .post-next i {
+ font-size: 1.5rem;
+}
+.post-prevnext .post-prev a,
+.post-prevnext .post-next a {
+ display: flex;
+ align-items: center;
+}
+.post-prevnext .post-prev .hidden-mobile,
+.post-prevnext .post-next .hidden-mobile {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+@media (max-width: 575px) {
+ .post-prevnext .post-prev .hidden-mobile,
+ .post-prevnext .post-next .hidden-mobile {
+ display: none;
+ }
+}
+.post-prevnext .post-prev:hover i,
+.post-prevnext .post-prev:active i,
+.post-prevnext .post-next:hover i,
+.post-prevnext .post-next:active i {
+ -webkit-animation-duration: 1s;
+ animation-duration: 1s;
+ -webkit-animation-delay: 0.1s;
+ animation-delay: 0.1s;
+ -webkit-animation-timing-function: ease-in-out;
+ animation-timing-function: ease-in-out;
+ -webkit-animation-iteration-count: infinite;
+ animation-iteration-count: infinite;
+ -webkit-animation-fill-mode: forwards;
+ animation-fill-mode: forwards;
+ -webkit-animation-direction: alternate;
+ animation-direction: alternate;
+}
+.post-prevnext .post-prev:hover i,
+.post-prevnext .post-prev:active i {
+ -webkit-animation-name: post-prev-anim;
+ animation-name: post-prev-anim;
+}
+.post-prevnext .post-next:hover i,
+.post-prevnext .post-next:active i {
+ -webkit-animation-name: post-next-anim;
+ animation-name: post-next-anim;
+}
+.post-prevnext .post-next {
+ justify-content: flex-end;
+}
+.post-prevnext .fa-chevron-left {
+ margin-right: 0.5rem;
+}
+.post-prevnext .fa-chevron-right {
+ margin-left: 0.5rem;
+}
+#seo-header {
+ color: var(--post-heading-color);
+ font-weight: bold;
+ margin-top: 0.5em;
+ margin-bottom: 0.75em;
+ border-bottom-color: var(--line-color);
+ border-bottom-style: solid;
+ border-bottom-width: 2px;
+ line-height: 1.5;
+}
+.custom,
+#comments {
+ margin-top: 2rem;
+}
+#comments noscript {
+ display: block;
+ text-align: center;
+ padding: 2rem 0;
+}
+.visitors {
+ font-size: 0.8em;
+ padding: 0.45rem;
+ float: right;
+}
+a.fancybox:hover {
+ text-decoration: none;
+}
+mjx-container,
+.mjx-container {
+ overflow-x: auto;
+ overflow-y: hidden !important;
+ padding: 0.5em 0;
+}
+mjx-container:focus,
+.mjx-container:focus,
+mjx-container svg:focus,
+.mjx-container svg:focus {
+ outline: none;
+}
+.mjx-char {
+ line-height: 1;
+}
+.katex-block {
+ overflow-x: auto;
+}
+.katex,
+.mjx-mrow {
+ white-space: pre-wrap !important;
+}
+.footnote-ref [class*=hint--][aria-label]:after {
+ max-width: 12rem;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+@-moz-keyframes post-prev-anim {
+ 0% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 50% {
+ -webkit-transform: translateX(-0.35rem);
+ transform: translateX(-0.35rem);
+ }
+ 100% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+}
+@-webkit-keyframes post-prev-anim {
+ 0% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 50% {
+ -webkit-transform: translateX(-0.35rem);
+ transform: translateX(-0.35rem);
+ }
+ 100% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+}
+@-o-keyframes post-prev-anim {
+ 0% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 50% {
+ -webkit-transform: translateX(-0.35rem);
+ transform: translateX(-0.35rem);
+ }
+ 100% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+}
+@keyframes post-prev-anim {
+ 0% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 50% {
+ -webkit-transform: translateX(-0.35rem);
+ transform: translateX(-0.35rem);
+ }
+ 100% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+}
+@-moz-keyframes post-next-anim {
+ 0% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 50% {
+ -webkit-transform: translateX(0.35rem);
+ transform: translateX(0.35rem);
+ }
+ 100% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+}
+@-webkit-keyframes post-next-anim {
+ 0% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 50% {
+ -webkit-transform: translateX(0.35rem);
+ transform: translateX(0.35rem);
+ }
+ 100% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+}
+@-o-keyframes post-next-anim {
+ 0% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 50% {
+ -webkit-transform: translateX(0.35rem);
+ transform: translateX(0.35rem);
+ }
+ 100% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+}
+@keyframes post-next-anim {
+ 0% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 50% {
+ -webkit-transform: translateX(0.35rem);
+ transform: translateX(0.35rem);
+ }
+ 100% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+}
+.fold {
+ margin: 1rem 0;
+ border: 0.5px solid var(--fold-border-color);
+ position: relative;
+ clear: both;
+ border-radius: 0.125rem;
+}
+.fold .fold-title {
+ color: var(--fold-title-color);
+ padding: 0.5rem 0.75rem;
+ font-size: 0.9rem;
+ font-weight: bold;
+ border-radius: 0.125rem;
+}
+.fold .fold-title:not(.collapsed) > .fold-arrow {
+ transform: rotate(90deg);
+ transform-origin: center center;
+}
+.fold .fold-title .fold-arrow {
+ display: inline-block;
+ margin-right: 0.35rem;
+ transition: transform 0.3s ease-out;
+}
+.fold .fold-content {
+ padding: 1rem 1rem;
+}
+.fold .fold-content > *:last-child {
+ margin-bottom: 0;
+}
+.fold-default,
+.fold-secondary {
+ background-color: rgba(187,187,187,0.25);
+}
+.fold-primary {
+ background-color: rgba(183,160,224,0.25);
+}
+.fold-info {
+ background-color: rgba(160,197,228,0.25);
+}
+.fold-success {
+ background-color: rgba(174,220,174,0.25);
+}
+.fold-warning {
+ background-color: rgba(248,214,166,0.25);
+}
+.fold-danger {
+ background-color: rgba(236,169,167,0.25);
+}
+.fold-light {
+ background-color: rgba(254,254,254,0.25);
+}
+.note {
+ padding: 0.75rem;
+ border-left: 0.35rem solid;
+ border-radius: 0.25rem;
+ margin: 1.5rem 0;
+ color: var(--text-color);
+ transition: color 0.2s ease-in-out;
+ font-size: 0.9rem;
+}
+.note a {
+ color: var(--text-color);
+ transition: color 0.2s ease-in-out;
+}
+.note *:last-child {
+ margin-bottom: 0;
+}
+.note-default,
+.note-secondary {
+ background-color: rgba(187,187,187,0.25);
+ border-color: #777;
+}
+.note-primary {
+ background-color: rgba(183,160,224,0.25);
+ border-color: #6f42c1;
+}
+.note-success {
+ background-color: rgba(174,220,174,0.25);
+ border-color: #5cb85c;
+}
+.note-danger {
+ background-color: rgba(236,169,167,0.25);
+ border-color: #d9534f;
+}
+.note-warning {
+ background-color: rgba(248,214,166,0.25);
+ border-color: #f0ad4e;
+}
+.note-info {
+ background-color: rgba(160,197,228,0.25);
+ border-color: #428bca;
+}
+.note-light {
+ background-color: rgba(254,254,254,0.25);
+ border-color: #0f0f0f;
+}
+.label {
+ display: inline;
+ border-radius: 3px;
+ font-size: 85%;
+ margin: 0;
+ padding: 0.2em 0.4em;
+ color: var(--text-color);
+ transition: color 0.2s ease-in-out;
+}
+.label-default,
+.label-secondary {
+ background-color: rgba(187,187,187,0.25);
+}
+.label-primary {
+ background-color: rgba(183,160,224,0.25);
+}
+.label-info {
+ background-color: rgba(160,197,228,0.25);
+}
+.label-success {
+ background-color: rgba(174,220,174,0.25);
+}
+.label-warning {
+ background-color: rgba(248,214,166,0.25);
+}
+.label-danger {
+ background-color: rgba(236,169,167,0.25);
+}
+.markdown-body .btn {
+ border: 1px solid var(--line-color);
+ background-color: var(--button-bg-color);
+ color: var(--text-color);
+ transition: color 0.2s ease-in-out, background 0.2s ease-in-out, border-color 0.2s ease-in-out;
+ border-radius: 0.25rem;
+ display: inline-block;
+ font-size: 0.875em;
+ line-height: 2;
+ padding: 0 0.75rem;
+ margin-bottom: 1rem;
+}
+.markdown-body .btn:hover {
+ background-color: var(--button-hover-bg-color);
+ text-decoration: none;
+}
+.group-image-container {
+ margin: 1.5rem auto;
+}
+.group-image-container img {
+ margin: 0 auto;
+ border-radius: 3px;
+ background-color: transparent;
+ box-shadow: 0 3px 9px 0 rgba(0,0,0,0.15), 0 3px 9px 0 rgba(0,0,0,0.15);
+}
+.group-image-row {
+ margin-bottom: 0.5rem;
+ display: flex;
+ justify-content: center;
+}
+.group-image-wrap {
+ flex: 1;
+ display: flex;
+ justify-content: center;
+}
+.group-image-wrap:not(:last-child) {
+ margin-right: 0.25rem;
+}
+input[type=checkbox] {
+ margin: 0 0.2em 0.2em 0;
+ vertical-align: middle;
+}
+.list-group a ~ p.h5 {
+ margin-top: 1rem;
+}
+.list-group-item {
+ display: flex;
+ background-color: transparent;
+ border: 0;
+}
+.list-group-item time {
+ flex: 0 0 5rem;
+}
+.list-group-item .list-group-item-title {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+@media (max-width: 575px) {
+ .list-group-item {
+ font-size: 0.95rem;
+ padding: 0.5rem 0.75rem;
+ }
+ .list-group-item time {
+ flex: 0 0 4rem;
+ }
+}
+.list-group-item-action {
+ color: var(--text-color);
+}
+.list-group-item-action:focus,
+.list-group-item-action:hover {
+ color: var(--link-hover-color);
+ background-color: var(--link-hover-bg-color);
+}
+.about-avatar {
+ position: relative;
+ margin: -8rem auto 1rem;
+ width: 10rem;
+ height: 10rem;
+ z-index: 3;
+}
+.about-avatar img {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ background-color: transparent;
+ object-fit: cover;
+ box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16), 0 2px 10px 0 rgba(0,0,0,0.12);
+}
+.about-info > div {
+ margin-bottom: 0.5rem;
+}
+.about-name {
+ font-size: 1.75rem;
+ font-weight: bold;
+}
+.about-intro {
+ font-size: 1rem;
+}
+.about-icons > a:not(:last-child) {
+ margin-right: 0.5rem;
+}
+.about-icons > a > i {
+ font-size: 1.5rem;
+}
+.category-bar .category-list {
+ max-height: 85vh;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+.category-bar .category-list::-webkit-scrollbar {
+ display: none;
+}
+.category-bar .category-list > .category-sub > a {
+ font-weight: bold;
+ font-size: 1.2rem;
+}
+.category-bar .category-list .category-item-action i {
+ margin: 0;
+}
+.category-bar .category-list .category-subitem.list-group-item {
+ padding-left: 0.5rem;
+ padding-right: 0;
+}
+.category-bar .category-list .category-collapse .category-post-list {
+ margin-top: 0.25rem;
+ margin-bottom: 0.5rem;
+}
+.category-bar .category-list .category-collapse .category-post {
+ font-size: 0.9rem;
+ line-height: 1.75;
+}
+.category-bar .category-list .category-item-action:hover {
+ background-color: initial;
+}
+.category-bar .list-group-item {
+ padding: 0;
+}
+.category-bar .list-group-item.active {
+ color: var(--link-hover-color);
+ background-color: initial;
+ font-weight: bold;
+ font-family: "iconfont";
+ font-style: normal;
+ -webkit-font-smoothing: antialiased;
+}
+.category-bar .list-group-item.active::before {
+ content: "\e61f";
+ font-weight: initial;
+ margin-right: 0.25rem;
+}
+.category-bar .list-group-count {
+ margin-left: 0.2rem;
+ margin-right: 0.2rem;
+ font-size: 0.9em;
+}
+.category-bar .list-group-item-action:focus,
+.category-bar .list-group-item-action:hover {
+ background-color: initial;
+}
+.category-chains {
+ display: flex;
+ flex-wrap: wrap;
+}
+.category-chains > *:not(:last-child) {
+ margin-right: 1em;
+}
+.category:not(:last-child) {
+ margin-bottom: 1rem;
+}
+.category .category-item,
+.category .category-subitem {
+ font-weight: bold;
+ display: flex;
+ align-items: center;
+}
+.category .category-item {
+ font-size: 1.25rem;
+}
+.category .category-subitem {
+ font-size: 1.1rem;
+}
+.category .category-collapse {
+ padding-left: 1.25rem;
+ width: 100%;
+}
+.category .category-count {
+ font-size: 0.9rem;
+ font-weight: initial;
+ min-width: 1.3em;
+ line-height: 1.3em;
+ display: flex;
+ align-items: center;
+}
+.category .category-count i {
+ padding-right: 0.25rem;
+}
+.category .category-count span {
+ width: 2rem;
+}
+.category .category-post {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.category .category-item-action:not(.collapsed) > i {
+ transform: rotate(90deg);
+ transform-origin: center center;
+}
+.category .category-item-action i {
+ transition: transform 0.3s ease-out;
+ display: inline-block;
+ margin-left: 0.25rem;
+}
+.category .category-item-action .category:hover {
+ z-index: 1;
+ color: var(--link-hover-color);
+ text-decoration: none;
+ background-color: var(--link-hover-bg-color);
+}
+.category .row {
+ margin-left: 0;
+ margin-right: 0;
+}
+.tagcloud {
+ padding: 1rem 5%;
+}
+.tagcloud a {
+ display: inline-block;
+ padding: 0.5rem;
+}
+.tagcloud a:hover {
+ color: var(--link-hover-color) !important;
+}
+.links .card {
+ box-shadow: none;
+ min-width: 33%;
+ background-color: transparent;
+ border: 0;
+}
+.links .card-body {
+ margin: 1rem 0;
+ padding: 1rem;
+ border-radius: 0.3rem;
+ display: block;
+ width: 100%;
+ height: 100%;
+}
+.links .card-body:hover .link-avatar {
+ transform: scale(1.1);
+}
+.links .card-content {
+ display: flex;
+ flex-wrap: nowrap;
+ width: 100%;
+ height: 3.5rem;
+}
+.link-avatar {
+ flex: none;
+ width: 3rem;
+ height: 3rem;
+ margin-right: 0.75rem;
+ object-fit: cover;
+ transition-duration: 0.2s;
+ transition-timing-function: ease-in-out;
+}
+.link-avatar img {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ background-color: transparent;
+ object-fit: cover;
+}
+.link-text {
+ flex: 1;
+ display: grid;
+ flex-direction: column;
+ line-height: 1.5;
+}
+.link-title {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--text-color);
+ font-weight: bold;
+}
+.link-intro {
+ max-height: 2rem;
+ font-size: 0.85rem;
+ line-height: 1.2;
+ color: var(--sec-text-color);
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+@media (max-width: 767px) {
+ .links {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ }
+ .links .card {
+ padding-left: 2rem;
+ padding-right: 2rem;
+ }
+}
+@media (min-width: 768px) {
+ .link-text:only-child {
+ margin-left: 1rem;
+ }
+}
diff --git a/feed.json b/feed.json
new file mode 100644
index 00000000..75c32519
--- /dev/null
+++ b/feed.json
@@ -0,0 +1,212 @@
+{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "カレーうどん屋",
+ "description": "カレーうどん屋.",
+ "home_page_url": "https://blog.udon.eu.org",
+ "items": [
+ {
+ "id": "https://blog.udon.eu.org/archives/2c54052d.html",
+ "url": "https://blog.udon.eu.org/archives/2c54052d.html",
+ "title": "我再也不能不假思索",
+ "date_published": "2024-11-12T15:30:00.000Z",
+ "content_html": "正值下班时间,你拖着疲惫的身体漫步街头。地铁站的入口熙熙攘攘,那是回家的方向。
\n正要走下地铁站路口的阶梯时,你发现了一个小孩子正朝着上行方向的电动扶梯走去。那是个连路都还走不清楚的孩子。
\n他的父母呢?你正张望着,希望能找到那个冒失的,或许正在专心看着手机而放松了警惕的父亲或母亲。
\n孩子朝着电梯摇摇晃晃地走了几步。想从上行的电梯往下走,这对于成年人都不是一件容易的事情,何况是小孩子。他大概刚踩上运行着的扶梯就会摔倒吧,真危险。你这么想着,稍微放慢了一些脚步,继续观察着孩子,也在观察着是否有人会出手拦住他。
\n你看到一个上了些年纪,头发有些发白的老人。他双手都拎着东西,是柴米油盐,重量拉低了他的双肩。他会帮忙吗?
\n你想起了上周,也是在下班的时间点,与即将乘坐的是同一个班次的地铁上,你幸运地找到了车厢里的最后一个座位。椅背上有没有贴着“爱心座位”的标志,你已经不记得了。你和今天一般疲惫,正在漫无目的地滑着手机屏幕,打发着无聊的通勤时间。视线越过手机屏幕,你瞥见一个老人,头发有些发白,双手都拎着重物,站在你的面前。
\n要让出座位吗?你一边滑动手机,一边思考着。“刚下班,大家都很辛苦”,你想起了在食堂偶然间听到的同事间的对话,“虽然我怀孕了,很需要一个座位。不过看到大家都这么累,我也不好说些什么了”。是啊,刚刚完成了一天工作的你,正需要充分的休息,哪怕是在地铁里坐下十几分钟对你也是无比的重要。想到这里,你底下了头,继续看起了手机……
\n也许是双手的重物让老人无暇顾及他人,迫使他加快步伐向下走去,他并没有发现那个正朝着上行扶梯走去的小孩子。此时孩子又向着扶梯迈出了几步。再往前几步,就踏上扶梯出口的金属盖板了。接下来会发生的事情难以想象。
\n会有人帮忙的吧?你继续张望着,同时心中也在盘算着——拦住了这个孩子,就意味着得守着他,直到那个粗心的父母匆匆赶来。谁知道要等多久,绝对是赶不上马上要到站的地铁了。你尝试把目光瞥向其他地方,似乎不再去看那个孩子,他就会安然无恙。但你做不到。
\n有一个阿姨快步走上前来,是要来帮忙的吗?你有一些即视感,仿佛之前在哪里见过这位大约四十岁出头,看上去有些憔悴的阿姨。
\n那是前几个月的一个早晨,你刚入职公司不久。向来遵守规矩的你今天也准时来到地铁站,乘上与昨天、前天、大前天都是同一时刻到站的地铁,这样就能准时到达公司,给大家留下一个好印象。地铁缓缓驶入站台,车门在悦耳的提示声中打开。此时,隔壁车门处却传来不和谐的声音。你循声望去,有人摔倒在列车与站台的间隙处,是一个看着四十岁左右的女性。是不小心绊倒了吗?还是没有吃早饭,有些低血糖了?不管怎样,她瘫坐在门口,久久没有起身。
\n要去扶一下吗?你迟疑了一会儿,没有上车。地铁站里有那么多保安,他们应该会处理好吧。扶起来之后,肯定得陪着她,直到有工作人员来照看才能离开吧,那今天早上要迟到了。你思考片刻,还是踏上了地铁,但还是转过身来,透过车窗继续观察。你看到有人和地铁站的保安一同将这位女性搀扶至阶梯边,地铁的门得以关闭,你可以准时到达公司了。想到这里,你的心安定了一些……
\n可惜的是,阿姨也没有注意到一个孩子正全然无知地朝着危险走去,就走下了楼梯。孩子摇摇晃晃地向前迈步,顷刻间,一只脚已经踏上了扶梯的盖板,再向前几步,就到了不断运转着的扶梯。
\n这样下去那个孩子肯定会摔下去的,你稍稍将行走的方向朝扶梯偏移了些许,做好了冲刺、抓住孩子的准备。同时,你也祈祷着能有人抢先你一步出手,并收拾剩下的残局。
\n一步、又一步,真的没有人肯帮忙一下吗?或许此刻真的只有我一个人注意到了这个孩子?你的视线游离,大步朝着扶梯走去,却被一个飞跃而过的身影叫停了步伐。
\n“小朋友,等一下。你的爸爸妈妈呢?”是一个年轻人,背着皮包,打扮时髦。他拦住了孩子,一只大手挡住了孩子前往扶梯的路,将孩子推离了危险。
\n看到这里,悬在空中的石头落地,你便也随着大流走下了阶梯。只不过,在那之后,在等候地铁时、坐在地铁中、或是淋浴、发呆之际,你都会回忆起过去的你,那个会毫不犹豫拾起地板上的垃圾、主动让出座位、扶起摔倒的人、给予他人帮助的你。你会不由自主地问自己:
\n我从什么时候开始,再也不能不假思索地做一件事情?
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/95479b1f.html",
+ "url": "https://blog.udon.eu.org/archives/95479b1f.html",
+ "title": "Soundcore C30i 耳机",
+ "date_published": "2024-06-13T06:40:00.000Z",
+ "content_html": "我目前在使用的耳机是 Sony LinkBuds,就是那款中间带了个孔的奇怪耳机。
\n它本是为了商务人士在办公场所使用而设计,收听耳机中声音的同时不会影响听取他人的谈话。它却被我这个耳道经常发炎的人看上了。
\n我想,中间带了个孔,那耳道内外的空气就是完全流通的,那耳道内肯定不会闷了吧!为了验证这一点,我特地去 Sony 的线下店体验这款耳机。佩戴上 LinkBuds,确实完全没有耳道被封闭的感觉,我便在网上欣然下单了。
\n然而,经过一段时间的佩戴,才发现我的想法过于天真:看似畅通无阻的气流,在直径不够大的圆环处有阻塞。内部产生的水蒸气无法排出,导致耳道仍然会有潮湿感。佩戴20分钟以上,摘下耳机放入耳机舱,一段时间后取出,会发现耳机舱内有冷凝水。
\nSony LinkBuds 仍然不能解决我耳朵发炎的困扰。我便又踏上了寻找新耳机的旅途。
\n今天便是要介绍我找到的其中一款,有望成为最终解决方案的耳机 —— SoundCore C30i。
\n
\n设计
\n
\n耳机充电盒的外观,以及开盖后的样子如上图所示,就不多介绍了。
\n开放式耳机相对于入耳式、半入耳式耳机,设计上花样多了不少。我看到的所有款式中,大致可以分为两类:
\n\n使用挂钩悬挂在耳朵之后,将扬声器覆盖于耳道口,通过空气传导声音进入耳道的; \n夹在耳垂上,耳机本身不覆盖或仅部分覆盖耳道口,通过侧向开口将声音导向耳道的。 \n \nSoundCore C30i 属于后者。
\n
\n我选择的是透明外壳的款式,耳机内部结构清晰可见。
\n左边耳机的扬声器处有一个开口,这便是朝向耳道的声音出口。
\n右耳朝上的是耳机背面,金色的圆片是触控传感器。
\n佩戴感 夹耳的耳机其实还可以再分类。一种是本体柔软、有弹力,有点像悬挂在耳朵上的,将扬声器更多地覆盖于耳道口,以提升传音效率的耳机。
\nC30i 本体则不可以弯曲,采用虎口般的结构卡在耳垂上。在佩戴时需要找到耳垂比较薄的部分,将耳机从那里套入耳垂,再将耳机推向耳垂较厚的地方,就能牢固固定。看似还是有些松松垮垮,但因为耳机足够轻,不容易被甩掉。
\n我的感觉是,佩戴上没有什么异物感,但难以做到所谓“戴久了会忘记它的存在”这么夸张。
\n我尝试连续佩戴了两个多小时,期间耳朵没有任何被堵塞的感觉,且取下后耳朵没有丝毫的潮湿感。确实是完全的开放了!
\n音质 我的评价是:出奇的好。
\n因为是一种妥协,我对耳机的音质便不抱任何希望。实际听起来,虽然也有这非封闭式耳机缺乏低音等问题,其实音质相当不错。
\n
\n通过调整 EQ 配置,将高低频都拉高(经典两头高调法),效果更是好了许多!
\n我也听了一段博客,听清说话人的语音也没有任何困难。
\n多设备连接 我原本买的是飞利浦的一款开放式耳机,到手之后才发现耳机没有双设备连接的功能。我平常有在手机和电脑之间切换使用的需求,少了这个功能实在不能接受。
\nC30i 支持同时连接两台设备,可以在两台设备间无缝切换。
\n续航 商品说明上标明了耳机单次续航是 10 个小时,这应该是相当厉害的了。加上充电盒,总共可以提供 30 个小时的续航。
\n实际用下来,在 2 小时的使用后,耳机的电量没有明显的减少。续航应当是很强劲的。
\n缺点 目前我看到的一个缺点是,耳机缺少自动休眠功能。将充电盒打开,耳机便会连接所有设备,摘下后仍保持着连接。直到耳机放回充电盒,并盖上盖子后,耳机才会关机。
\n且耳机缺少佩戴检测,摘下后不会停止音乐播放。似乎耳夹式耳机比较少有这个功能。
\n这可能会抢占了一些设备的音频输出,不过不算是很大的缺点。
\n总结 SoundCore C30i 有效解决了我耳道容易发炎的痛点,音质不错,且有多设备连接的功能,满足了作为我主力使用的耳机的所有需求。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/adc5a61e.html",
+ "url": "https://blog.udon.eu.org/archives/adc5a61e.html",
+ "title": "日本之行-第二站-奈良",
+ "date_published": "2024-03-10T16:30:00.000Z",
+ "content_html": "写在前面 23年12月底,我踏上了前往日本的旅途。这是我第一次去日本,是我第一次出国旅行,且只身一人。
\n本次旅行为14天,我将分多个章节完成所有的游记,记录下我旅行中的点点滴滴、各种感想。这是第二章节,奈良之旅。
\n近铁、巴士与鹿
\n奈良距离大阪仅有 30km,乘坐近铁奈良线,摇摇晃晃一个多小时便能到达奈良。
\n这条线路有分区间准急、准急、急行、快速急行四个速度,每个速度停靠的站点数量不同,票价都相同的。如果想尽快到达奈良,乘坐快速急性是最好的选择。
\n这天,我纯凭心情来到近铁难波站,坐上了最近一班开往奈良的近铁,似乎是急行。
\n近铁的运行速度不是很快,叮叮当当地在楼房间穿行着。
\n在近铁奈良站,奈良线的终点站下了车,距离旅馆所在的奈良公园还有一段距离。由于拖着行李,我选择搭乘公交车。
\n公交车到站后会向站台一侧倾斜车身,方便乘客上下;上车的门并不固定,可以看车身的贴纸判断;司机会很耐心地等待所有人上车、落座之后才摆正车身、继续前进。
\n有一些线路是分段计费的,就需要在上车的时候领取一张整理卷,或者先刷一下卡,在下车的时候将钱和整理券一起投入投币机,或者刷一下公交卡就可以了。
\n在每一个座位的旁边都有一个按钮,如果下一站要下车,按一下就会提醒司机,并且在车头的显示屏上也会显示,车上的所有按钮都会亮起。在公交车到站停稳之前无需先起身,车子停稳后司机也会很耐心地等大家付钱、下车,还会一个一个道谢哦。
\n相比于地铁,公交的到站时间显得没有那么准确,但不会差太多。在地面运行的公交会受到交通的影响,等候乘客上下车、付钱有时候也会用掉不少的时间,能做到按照时间表运行已经相当厉害了。
\n
\n虽然在车上就有看到,果然很多啊 —— 奈良的鹿。
\n奈良的鹿是散养的,主要集中在奈良公园和春日大社。在奈良设有鹿苑,将受伤或者发情的鹿收养,待它们恢复正常再放回乡中散养。
\n从车站走到旅馆,一路上都有鹿儿好奇地打量着来自异国他乡的我。
\n每走一段距离,能看到提示观光客不要“挑逗”鹿儿的提示牌,有些鹿的性格比较差,会用头顶你,追着你不放的。好在大部分鹿都去掉了角,不至于顶伤人。
\n
\n今天的旅馆确实有些简陋 —— 私人活动空间仅有这么一小间,洗浴和方便都是公用的。不过旅店的氛围很好,有一个共用的客厅,客人们可以在这里吹暖气(冬天还是挺冷的)、喝饮料、聊天。
\n旅店的工作人员非常热情,替我办理好入住手续后,立刻拿出一张奈良公园的地图,用简单的中文(很厉害哦!)告诉我这附近有哪些好玩的地方。奈良一天的行程就是参考着这张地图安排的。
\n整理好行装,已是傍晚五点多。按照旅店工作人员以及 Google Maps 的说法,奈良公园里的大部分饮食店都要下班了!遂立刻出门,寻找晚饭的地点。
\n因为今天是周五吗,明明还有一些旅客在此游玩的,旅店对面的旅游纪念品店已经关门了!沿街打量了几家店铺,也都在收拾着大堂,准备打烊了,完全不像是会接客的样子。大危机,要没晚饭吃了,得走个几公里处了奈良公园,到达奈良市区才有便利店。
\n路过了一家同样是卖纪念品的商店,发现店内的空间还挺宽敞,也摆着一些桌椅,便问了一下老板还有没有饭吃。运气很好,这家店还没有打烊。
\n
\n可以选择的菜品并不是很多,大多数都是日本的家常菜。我选择了这道绝大部分和食餐馆都会有的家常菜 —— 親子丼。
\n从厨房门帘的缝隙向里望去,可以隐约看见老板在烹饪着饭菜。明明用得都是同样的工具和食材,有技艺的人做出来的东西就能端上桌卖钱呢。
\n蛋是我们所谓“半熟”的柔滑状态,鸡肉有一些小烧焦,主要的风味是自古以来人们就离不开的味道 —— 咸味。风味不能说突出,却很平和。它就是一碗很普通、很平常,此时此刻会出现在任意一张餐桌上的饭。
\n饱饭后,我根据地图的指引,前往东大寺的二月台,观赏日落之景。
\n
\n
\n
\n
\n只用手机相机无法捕捉暗光下的美景。
\n夕阳柔和而昏暗地照着二月台,四周一片静寂,只能听见洗手亭的潺潺水声。在日落后还停留在奈良公园的游客寥寥,大家都保持着绝对的安静,共享这片难得的静谧。
\n在台上驻足数分钟,我轻轻踏着脚步走下楼梯,沿着寺院的小路漫步着,离开了东大寺。
\n此时天已完全黑了,不论是寺院还是神社都已关门,我就这么在奈良公园里漫步着。时不时碰见一群鹿,便从口袋中掏出一块鹿仙贝,掰给鹿儿吃。
\n
\n会旅店前在路边的售货机买了一瓶果汁饮料,是不二家的。日本的饮料会标注果汁含量,我觉得很神奇。
\n
\n第二天,去参观了十分出名的春日大社。不过不恰巧,主殿正在维修,将赛钱箱设置在了原处,只能眺望主殿。
\n在巫女那儿买了点纪念商品,是两只鹿型的小玩偶。一只是木质的,一只是陶制的,鹿儿的嘴中叼着签。忘记留下照片了。
\n
\n离开主殿,走上铺着碎石的小道。
\n
\n每走几步,便会出现一间迷你神社,感觉十分奇妙。在这片林海中不知道供奉着多少的神明。
\n有些人也许是提前来做新年参拜,途中遇到的神社都会十分虔诚地参拜。
\n在林间漫步了将近一个小时,吸饱了新鲜的空气,我振奋精神,回旅馆取了行李,准备离开奈良。
\n
\n拖着行李箱漫步在奈良公园里,我又一次路过了若草山。
\n若草山也不高,在满是丘陵的福建甚至都算不上山,只是个小土坡。山上的草长得格外整齐,是一座越看越顺眼的山呢。
\n若草山每年12月初开始封山,直到第二年三月举行烧山仪式后才重新开放。没能上去走走真是可惜。
\n
\n在前往近铁奈良站离开奈良前,我路过了一家在网上有着不少讨论的柿叶寿司店,便决定在这里解决午饭。
\n柿叶寿司其实就是用柿子叶将寿司包起来烹饪。据说柿子叶不仅可以杀菌消毒,还能给寿司提升风味。
\n想着,既然是奈良的特色菜,肯定要好好品尝一下。我点了一份 2500円 的大套餐,奢侈一下~
\n左下角的五枚寿司,上方两枚便是柿叶寿司了。下方三角形的寿司也是用不能吃的叶子包着,但好像不是柿子叶;右侧是用腌制过的紫苏叶包的寿司,紫苏叶是可以吃的,风味很独特,我很喜欢;左侧是茶味的卷寿司,也是第一次吃。
\n柿叶寿司用的是腌制过的鱼,确实带有叶子的香味,很有特色!
\n除此之外,套餐内还有精致的小菜、一份天妇罗拼盘和一碗素面。吃的我好饱~
\n
\n乘坐上前往京都的特急电车时还发生了一些小插曲。
\n坐上车后我才意识到自己坐的是特急的电车,进站时我只刷了基本票,没有购买特急卷。
\n好在列车上有通过扫描二维码购买特急卷的渠道,也支持借记卡付款。在等待列车发车时,我赶紧买了特急卷,带着行李移动到了指定座位,正式踏上前往京都的旅途。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/a7050149.html",
+ "url": "https://blog.udon.eu.org/archives/a7050149.html",
+ "title": "23 年对我影响最大的硬件与软件",
+ "date_published": "2024-03-02T12:30:00.000Z",
+ "content_html": "原文投稿在自留地频道的新年活动里。
\n对于我来说,23 年给我带来影响最大的莫过于 Meta Quest 3 与 VRChat 了。
\nQuest 3
\nQuest 3 在 23 年 6 月面世,于 9 月 28 日开放预购。我早在数年前就有了尝试 VR 的念头,在经过一天的调查研究后确定了 Quest 3 不会踩坑,便在日亚下了单,直邮回国。日亚上标价为 78k 円,日本商品出口不收税,再加上直邮的运费和中国的关税,最后到手的价格和标价差不多。(顺便一提,现在日亚也可以以相同的价格直邮国内)
\n外观我就不展示了,网上的图很多。到手第一件事,便是尝试它对于上一代产品相比提升最大的地方——显示与穿透(Pass Through)。
\n显示效果 显示效果上,Quest 3 采用了 Pancake 棱镜,甜点位置(Sweet Spot)相对于上一代更大,也就是说仅需简单调整头戴的位置,便能找到画面最为清晰的位置,我的使用体验上感觉也是如此,每次佩戴上头显很快就能调整好画面。
\n穿透 (Pass Through)
\n穿透方面,由于我没有用过 Quest 2、Quest Pro 等拥有穿透功能的 VR 头显,无法进行比较。图片中看起来比较糊,是因为我用了 Quest 自带的截图功能,分辨率很低。实际观感上,在光线充足的环境中可以毫无压力地辨认各种物体,但细小的字是看不清楚的。戴着 Quest 使用手机并不是很舒服,因为物体距离头显过近会因摄像头视角的问题产生扭曲,看字会很吃力。将手机拿远,又会因为显示清晰度不够而看不清字。不过听说 Quest 2 的穿透是黑白且十分模糊,Quest Pro 也好不了很多,Quest 3 在这个方面应是有十足的进步。(似乎离 avp 还有很大的差距)
\n \n相比于头显中不带有处理芯片的 VR 眼镜,Quest 可以使用无线串流软件将 PCVR 的画面投影至头显中显示,再也不用担心玩游戏时绊倒数据线了。
\n总而言之,Quest 3 是一副功能完善,各方面实力均不弱的“六边形战士”,但毕竟定位是廉价头显,也就缺少了 Quest Pro 的自定位手柄、眼部追踪,Valve Index 的基站定位、手部追踪,更没有 Apple Vision Pro 的高 ppi 显示屏、低延迟的穿透与精准的手势识别。但综合来看,Quest 3 的性价比高,适合刚刚步入虚拟世界,想要体验各式各样 VR 游戏的人购买。
\nVRChat 谈到 VR 游戏,有人会想到 Half-Life:Alyx,有人会说起(已经凉掉的) Beat Saber。根据 SteamDB 的数据,此刻在线数量最多的 VR 端游戏还得是 VRChat(主要为 PC 端的战争雷霆不算)。
\n在我看来,VRChat 里有大概有四类人。
\n第一类人:虚拟世界的旅行家
\n有些人想“逃避现实”,来到虚拟世界欣赏美景、转换心情。在 VRChat 里有各种各样风格的地图:有些风景大好、音乐舒缓,适合在快节奏的生活之余找到一个宁静之地放松紧绷的精神;有的地图灯红酒绿,如果在夜晚你还是激情满满,不妨来这里听听虚拟 DJ 的表演,大家一起蹦迪、饮酒;甚至还有环境昏暗、一片寂静,十分适合睡眠休息的卧室地图,虽然深处异地,也能在虚拟世界里与好友共眠,在清晨醒来的那刻发现自己的眼前并不是早已习惯了的天花板,耳边是仍在熟睡的友人的呼吸声,将会是一种全新的体验。
\n第二类人:虚拟世界的摄影师
\n也有些人想在虚拟世界里做一个摄影师。美景的照片中怎么能少了美人,如果现实中找不到美人,那就自己来扮吧(心死)。VRChat 中使用的人物模型可以由玩家自行上传,如果你恰巧会使用 Unity 与 Blender,便可以为自己量身定制一个人物模型;如果不会也没有关系,在 Booth 上有许多预制好的人物模型与衣服,只要按照教程将其组合,便可打造出你心目中的理想形象(不管男女)。为了拍出满意的照片,你会主动去学习各种各样的新技能:为了调整照片的后期效果,我就学习了 Lightroom 的使用。
\n第三类人:人,不过是在虚拟世界里
\n这或许才是 VRChat 的核心内容 —— 当然是和朋友聊天啦。在 VRChat 中有一些专门用于聊天、交友的地图,例如以中文为主的“中文吧”;也有一些比较热门的地图会聚集起各个语言的人群一起聊天,例如“Japan Shrine[spring]”,一张风景优美的日本神社地图;更有各种各样以个人或小团体主办的咖啡厅、运动吧、跳舞房、游戏房等着你来加入。也许你和我一样有些小“社恐”,在现实生活中不大善于和陌生人交际,不妨试试在 VRChat 中,与素未谋面但已经熟络的朋友,或是围坐在篝火旁闲聊,或是在电闪雷鸣、乌云滚滚的夜空中乘坐飞机探险,相信这对你一定会给你带来从所未有的新鲜感。
\n第四类人:OOOO 还有一类是搞色色的,就不多说了。
\n以上只是从我的眼中看到的 VRChat。一千个人的心中有一千个哈姆雷特,你的 VRChat 又将会是什么样的呢?
\n \n自从 23 年 10 月 3 日加入 VRChat 以来,我学会了怎么修改模型、如何使用全身追踪让自己在游戏中灵活运动;尝试在跳舞房里学习舞蹈,在 KTV 房里与朋友唱歌,与来自全球的人使用中、英、日闲聊;在 VRChat 中结交了新的朋友,在 X 上发布了拍摄的照片。VRChat 确确实实已经融入了我的生活,仿佛在另一个世界塑造了另一个不同的我。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/a534d51a.html",
+ "url": "https://blog.udon.eu.org/archives/a534d51a.html",
+ "title": "旅行与军粮",
+ "date_published": "2024-02-12T14:30:00.000Z",
+ "content_html": "军粮的特点 1. 便携 一般来说,军粮都会被较为紧凑地包装,以便于大批量运输与随身携带。如确认将有一餐饭需要在外解决,可以在出发时往包里塞上一份军粮。
\n2. 分量足 军粮的营养构成一般都是按照一个运动量中上的成年男性一餐所需要的卡路里来设计的。因此,即使不能完全饱腹,其所含的营养足够填补在外游玩半天空空的肚子了。
\n3. 便于烹饪 相比于需要加入开水的方便食品、甚至是冻干食品,军粮一般自带加热设备,仅需冷水,甚至不需要水都可以加热食物。若是去荒郊野岭的地方露营,除非有携带便携式汽炉,想获取到热水是比较难的,这时候有自加热的军粮会很方便。
\n俄罗斯单兵口粮普餐
\n这次买到的6号餐谱,包含五包干粮、牛肉丸、牛肉荞麦饭、牛肉炖豆角、牛肝酱、午餐肉、蔬菜丁罐头以及饮料、果酱、零食等散件,还有一套由三个燃料药丸、一个铝制支架和几支防风火柴组成的明火加热套装。
\n
\n旅行的第一天是登山,午餐便携带了部分的食物作为午餐。
\n有些小雨,便找了一处有雨遮的地方开始准备午饭。
\n
\n将铝板弯曲成炉子的形状,在炉子的中央放上燃料药丸,再用防风火柴点燃,便是一个功能完备的加热装置。
\n小贴士,我试着用火柴从上方点燃药丸,尝试了两次都失败了。将火柴放在炉子里,然后将药丸放在火柴上,便能很轻易地将其点燃。
\n
\n点燃固体燃料后,将盛有食物的铝制罐子放在火上直接加热即可。如果有带铝或者钛制的杯子,也可以直接放在火上烧水,泡些饮料喝。
\n
\n在等待罐头加热时,我先掏出了干粮与蘸酱。除了照片里的芝士与肝酱,还有一包苹果果酱。
\n芝士的味道与常见的芝士片如出一辙,在我看来稍微淡了些,因此也比较适合直接吃。
\n牛肝酱则是特别的腥,单独吃我有些吃不来,和着芝士与果酱吃味道倒是还不错。
\n拿出一片干粮(其实就是苏打饼干),涂上果酱,挖一勺芝士,再放上一点点牛肝酱,一口塞进嘴里,味道还算不错。
\n饼干有些干硬,嚼起来有些费劲儿,需要配水。
\n最后,完吃完了一份饼干、一盒芝士和一包果酱,牛肝酱剩下了一大半。
\n
\n接下来是牛肉丸。看到红色的外表就能猜到是罗宋汤风味的。
\n应该是罐藏的缘故吧,牛肉已经泡得很软了,吸收了罗宋汤酸酸甜甜的风味,味道还算不错。就是牛肉味已经不是很浓,能吃得出来是肉,但没有什么特殊的肉的风味。
\n顺便一提,用这种炉子加热,铝盒子受热举起不均匀,中间已经滚烫,但四周还是冰的,需要多多翻搅。
\n
\n从外表看不出来里面有啥,红红的肯定也是罗宋汤的味道。里头的蔬菜主要是胡萝卜,也有青椒、青豆等蔬菜。但这一碗的味道就不好了,青椒的味道和西红柿(或者是甜菜)的酸甜并不搭。
\n把饼干蘸着牛肉丸的汤汁吃,味道也不错。涂涂果酱、涂涂芝士,饼干便吃完了一包。
\n又吃了点麻麻那边的自热口粮,便已饱腹,我的食量确实不大啊。
\n
\n这一餐其实只吃了整套餐的 1/3 左右,剩下的量再吃两次正餐不成问题。
\n回到家后,又拿出了些零食品尝了一下。
\n
\n来自俄罗斯的巧克力棒,偏甜,一股代可可脂的廉价感,属于不大好吃的巧克力。
\n
\n右上角是速溶咖啡,右下角是奶粉,左边的一大包是糖。糖的量很大,明显不止是一次饮料的量,也可以放在茶里喝。
\n咖啡很苦,一股烧焦味,不好喝。奶粉大概也是植脂末吧,没什么奶味。
\n剩下的食品我分成了两餐解决。
\n
\n第一餐的搭配是:牛肉荞麦饭、肥肉罐头和干粮(饼干)。
\n
\n也许我加热的还不够,但考虑到在野地里使用便携式炉子加热的能力,士兵们能加热到中间完全热乎,周围有些凉是平均水平了。荞麦饭很硬,风味也不是很好,除了咸味和一点牛肉味,尝不出别的滋味了。加了一些套餐内的黑胡椒粉,才改善了一些风味。
\n不过这一大碗饭确实很能填饱肚子,适合放在午餐食用。
\n肥肉罐头里自然是盐腌风味的很肥的肉啦。味道我还挺喜欢的,肥肥的肉很好吃,十分下饭。
\n
\n第二餐的搭配是:牛肉煮豆子、午餐肉、苹果泥、酱牛肉(来自国产 MRE)和干粮(饼干)。
\n
\n牛肉煮豆子用的会是什么豆呢,豌豆吗?啊,原来是黄豆。
\n并没有延续牛肉丸、蔬菜罐头的罗宋汤风味,只是单纯的咸味,不过味道我还挺喜欢的。
\n牛肉其实不多,就一大块带筋、软烂的牛肉,剩下都是碎块。除了黄豆外还有一些胡萝卜。
\n午餐肉很小一罐,但确实是肉泥,淀粉含量并不多,味道也不错。
\n苹果泥是我整个套餐中最喜欢的一样食物了。口感绵密,酸甜度适中,汁水十足。配合干粮一起吃,很大地改善了干粮过干、过硬的缺点。而且好大一罐,吃得很满足。使得这一餐中我干掉了两袋干粮。
\n国内的军粮 —— 以北戴河的自热口粮为例 09式单兵自热口粮我吃过几份,13式这种两餐包装在一起的也吃过一次。
\n这次选购的是北戴河生产的自热口粮。虽然不是军品,但北戴河前身是军工厂,产品会比较接近军品吧。包装风格与09式也很相像。
\n
\n那天感冒的发烧刚退,人还比较虚弱。住的度假酒店位置比较偏,不想跑太远去吃晚饭,便取了车上的自热口粮回房间吃。
\n我吃过太多次了,便没有拍照片,下面就口述一下感受。
\n相比于09式单兵自热口粮,北戴河出品的民用口粮内容少了很多 —— 没有了耐贮蛋糕、糖水黄桃/菠萝、调味辣椒酱和固体饮料,只有一份主食,一份配菜(酱牛肉和午餐肉二选一)和一份小菜(榨菜)。
\n09式的餐谱相当多样,有 12 个餐谱,有炒面也有炒饭,也有一些主食是素食的,满足部分人群的需求。
\n北戴河的仅有三种餐谱,且都是荤食的炒饭。
\n主食包外套着加热袋,用注水袋取对应量的水(生水、脏水皆可)倒入加热包即可完成主食的加热。
\n味道上,我认为北戴河做得是不如当年的09式的。饭总体都偏咸,但肉给的十分足。
\n但相对于俄罗斯的军粮,不知道是口味上有着主场的优势,还是俄罗斯人烹饪技术真的不佳,还是中国的军粮更胜一筹。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/7777583b.html",
+ "url": "https://blog.udon.eu.org/archives/7777583b.html",
+ "title": "被我整坏的路由器和服务器",
+ "date_published": "2024-02-12T10:00:00.000Z",
+ "content_html": "为了搭建 PalWorld 的私服,我又一次踏上了折腾之旅,并成功将路由器和家里的服务器都搞崩了。
\n好在最后两台设备都恢复如初。我就想来做一下这次“事故”的复盘。
\n我的设备 本次操作的有两台设备,一台是 x86 软路由,运行 Openwrt;一台是配置比较奇怪的 x86 服务器,运行 PVE。
\nOpenwrt 的网络隔离配置 说在前头,我对 Openwrt 的操作真的是一窍不通。网络相关的知识兴许懂一些,但一到实操环境就纰漏百出了。
\n由于 PalWorld 服务端启动后便会在游戏的服务器列表公开自己的 IP,且无法关闭这个功能,有比较大的安全隐患。在配置好 PalWorld 服务端所运行的虚拟机后,我决定将这台虚拟机的网络与局域网内其他设备隔离。这也是所有折腾的起源。
\n配置 VLAN 导致的失联 我能想到的第一个方案便是,给这台虚拟机分配一个与当前局域网不同的网段,并将两个网段隔离。
\nVLAN 我还是知道的,便着手开始创建新的 VLAN,并保存…连不上路由器了。
\n急了啊,创建了 VLAN 就代表开启网桥的 VLAN 过滤。目前这个 VLAN 没有分配给任何一个接口,自然是啥都连不上了。好在 Openwrt 有自动回滚的功能,90 秒若设备连不上路由器,便会将刚刚的更改回滚…但不是每次都能成功。
\n有反复折腾了几次:创建 VLAN、创建接口、创建防火墙的 Zone,分配来分配去,中途也搞砸了几次,配置也都回滚了,直到最后一次尝试,luci 弹出了令人感到安心的“正在回滚配置”的提示,然后就…卡住了。
\n考虑到 Openwrt 使用的是 squashfs,怀疑是设备上的设置确实回滚了,但内存中的配置没能正确地回去,我将设备断电重启。果不其然,配置回到了应用前的样子。
\n在尝试配置 VLAN 数次后,我放弃了。即使给虚拟机分配了新网段,也设置好了防火墙规则,虚拟机依旧可以访问到内网网段的设备。
\n目前我还没有闲心思慢慢研究 Openwrt 的种种配置,便打算换一个方式实现。
\n配置 PVE 防火墙 第二个方案便是,在 PVE 的防火墙中禁止虚拟机连接内部网段的其他设备了。
\n启动 Datacenter 防火墙但没有添加允许规则导致的失联 我启用了虚拟机的防火墙,发现配置并没有生效。简单查询后发现,需要将 Datacenter 的防火墙启用,VM 防火墙才有效果。我便看也没看地就开启了 Datacenter 防火墙,发现管理后台页面无法刷新了。此时我才看到屏幕上 Datacenter 防火墙的默认配置 —— IN: DROP.
\n这下好了,外部连接统统被阻断,也就无谈通过控制面板将防火墙再关闭。
\n这台服务器是无头的,安装有一张 P100 显卡,但没有显示输出。所以在不动硬件的情况下,我没法通过显示器访问终端的。
\n通过检索,我了解到了好几种奇技淫巧来关闭 PVE 的防火墙。
\n挂载并修改 crontab 正好手上有一个硬盘盒,我就将系统盘取下,通过硬盘盒连接到了软路由,尝试修改系统盘里的文件。
\n使用 fdisk
查看这块系统盘的分区情况,但没有看到熟悉的 ext4
字样。取而代之的是 Linux LVM
。LVM 相当于是 Linux 对磁盘进行了再一次的分区,因此挂载就不能是简单的 mount
,得用 lvm2
工具。
\n再敲了几个命令后,我成功将 PVE 分区的 root
文件夹挂载,并在 crontab 中添加了关闭 PVE 防火墙相关的命令。
\numount
,取下系统盘并装回服务器,开机…没有任何反应,依旧打不开管理面板。是教程给的方法有误吗?
\n遂又取下盘,挂载到软路由上,却提示该分区忙…我是相当忌惮挂载并修改分区的,生怕损坏了分区,服务器的数据可就全丢了。不敢再继续操作,我得另想出路。
\n连接显示器,但还是个瞎子 现在能做的就是在服务器上通过 PVE 的 rescue terminal,来修改防火墙的配置了。
\n我关闭了服务器,取下 P100,换上了 R7 240 这张十年老兵,插上了便携显示器…
\n\nerror: No suitable video mode found. Booting in blind mode.
\n \n你这不是输出字了吗,怎么就进瞎子模式了???
\n经过查询,我了解到系统正在寻找到显示模式,正是古早电脑终端使用的 80-Column
这样的显示模式。
\n至于什么是 Blind Mode,我尝试在 GRUB 和 Linux 源码中搜索,都没有结果;为什么 PVE 系统找不到我的 R7 240,可能是缺少驱动吧,现在也无处知晓。
\n按照网上的教程尝试在 GRUB 引导系统启动时添加显示模式的支持,并没有效果;当我尝试打印支持的显示模式时,发现根本不存在正常的显示模式,故只能放弃直接启动 PVE 的 rescue terminal.
\n还是得靠救援盘 给服务器插上救援盘,我先是打开了基于 Windows 的 PE 系统,发现显卡是有输出的,但分辨率非常低,且只有黑白画面。看来这张老显卡与这块寨板的相性真的不大好。
\n在确认 Windows PE 系统下什么都做不了,操作还及其不便,我便退出了 PE 系统,打开了 Ubuntu LiveCD。
\n这回,显卡倒是可以正常运行。以 80-Column
模式输出文字还是可以轻松办到的。有了命令行,操作也简单了不少。
\n同为 Linux,操作 LVM 就简单了许多。使用 lvm2
挂载 PVE 的系统分区,并用 chroot
将用户空间切换至 PVE 系统,直接用 systemctl disable pve-firewall
把防火墙关了。
\n再次进入 PVE,这下开机防火墙就不会启动,赶紧进 Datacenter 防火墙设置里还原误操作的配置。
\n事后的反思 VLAN 配置的失误 我对 VLAN 配置没有经验,便想着参考网上其他人的配置方法来做。但我查到的都是创建访客 Wi-Fi 这类的配置教程。与我的网络环境的差异在于,访客 Wi-Fi 用的 Interface 与 LAN 不一样,而我需要在 LAN 下配置两个 VLAN,配置方法就有些不同,不能直接搬配置。
\n我应当要找的是较为通用的,同一个 Interface 下 VLAN 的配置教程(可能单臂路由配置和这个就有些像),再迁移到 Openwrt 上进行配置。
\n由于软路由只有两个网口,也没法像有四五个网口的路由器一样留下一个作为”不死“的管理口,以降低把路由器配置挂掉的风险。
\nPVE 原本可以修得更快 原本在将 PVE 系统盘挂载到软路由时,便可以用 systemd
停掉 pve-firewall
但当时我看到 PVE 论坛里有人说在 crontab 里加上 PVE 关闭防火墙的指令便能访问控制面板了,也有人附和说可以用。但我实际操作后发现并没有效果。
\n而再次尝试挂载 LVM 时,提示分区忙让我不敢继续操作。虽然在服务器上用 LiveCD 直接挂载并没有出现分区忙的情况。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/aca48136.html",
+ "url": "https://blog.udon.eu.org/archives/aca48136.html",
+ "title": "日本之行-第一站-大阪",
+ "date_published": "2024-01-21T15:00:00.000Z",
+ "content_html": "写在前面 23年12月底,我踏上了前往日本的旅途。这是我第一次去日本,是我第一次出国旅行,且只身一人。
\n本次旅行为14天,我将分多个章节完成所有的游记,记录下我旅行中的点点滴滴、各种感想。这是第一章节,出境与大阪之旅。
\n第一次出境 我从上海浦东机场出境。
\n航班预计上午 9:40 起飞,根据网上的经验教训,国际航班建议提早三个小时到达机场办理值机手续。我早早地起了床,搭上了第一班磁浮列车,在六点五十分左右到达了机场。
\n拖着行李箱直奔值机柜台,发现队伍已经很长。排了一个多小时的队才轮到我。
\n我提前在网上进行了值机选座,现场只需要将行李箱托运,打印一下登机牌。工作人员会确认护照和日本 eVisa(电子签证)。
\n我提前将电子产品和需要独立安检的物品取出,安检也十分顺利。
\n不过在过海关的时候,工作人员看到我护照崭新,又是独自一人,不免有些担心,向我索取了酒店预订记录和行程单。酒店预订记录只要出示订购软件的订单界面即可;我没有做行程单,就将记事本里的旅行计划给工作人员看,第一天在哪里啊,过两天又跑去哪里玩…得知我全程的酒店都已订好,行程也安排清楚,便允许我出境了。
\n在候机厅的长椅上坐下时,距离飞机的预定起飞时间只有半个小时了。看来国际航班确实需要早点来机场办手续(虽然最后延误了两个小时)。
\n这是我第二次坐飞机。上一次是我还在读小学的时候,不明不白地上了飞机、又下了飞机,除了耳朵有些不舒服。没有其他的感想。
\n这次,我特地选择了靠窗的位置,仔细观察着飞机的起降与飞行时的动作。看到了起飞和降落时襟翼的展开,看到了遇到乱流时机翼“夸张”的摆动,感叹着这种看似“简易”的机器是如何将一机人送上天。
\n
\n飞机餐的话…上一次坐飞机是国内航线,没有在饭点所以没有供餐,这是我第一次体验飞机餐。
\n
\n没有选择的余地,大家统一都是鱼肉米饭套餐。和我能想象到的飞机餐没有太多区别 —— 一般咸味的鱼肉,煮得软烂的米饭(也可能是再加热的缘故)与味道平平的配菜。坐的毕竟是经济舱,不能期待太高~
\n
\n经过两个半小时的飞行,飞机平稳降落在了关西国际机场。飞机缓慢驶向空桥的途中,我更换了日本的流量卡,给手表切换了时区。
\n入境相对出境更加简单。出发前我就在网页中预先填写好了入境信息表与海关申报表,在对应的窗口或机器扫码即可完成申报。在入境窗口只需出示一下 eVisa,录入一下人脸和指纹就可以了。
\n工作人员会用英语引导你操作,录入机器上也有中文提示。机场的指示牌都是四语的(日语、英文、中文和韩文),按照指示牌走就没有问题。
\n大阪之行 Day 1 - 舒适的酒店与海游馆之旅 APA 酒店 起飞延迟了两个小时,降落也迟了一个多小时。达到关西国际机场已是下午 3:30(这之后都是 UTC+9 时间)。我乘坐南海机场线前往难波站附近的 APA 酒店。
\n插一嘴,在日本用 Google Maps 相当方便,交通工具的规划和信息展示都做得很棒。“通勤案内”这款官方推荐的交通软件我也有下载,不过用的最多的还是 Google Maps。
\n等地铁、电车、新干线时,只要看 Google Maps 上的到站时间,到了哪辆车就上哪辆车。除非两个方向的车同时到站,否则是绝对不会坐错方向的。
\n
\nAPA 酒店大楼的装修很有特色,在远处就能看到橙色的屋顶和 APA 的标志,而且在全国的风格都是统一的。
\n
\n内部的装横令我十分惊喜:房间不是很大,但各种设施一应俱全,在床铺正对的墙上甚至有一台大屏电视。浴室毫不意外地配备了浴缸,还有定量放水系统,不用盯着浴缸有没有水漫金山了。寝具也相当的舒服,那两晚都睡得很好。
\n对于一间一晚 300CNY 左右的旅馆,能有这样的体验我十分满意。
\n咖喱与牛排 办好入住手续,放下行李,已是五点多。飞机餐的显然填不饱我的肚子,我早已饥肠辘辘,是时候出门觅食了。
\n大阪海游馆是我计划中必去的一站,但它位于海港村,和其他景点在相反的方向,便安排在今晚游玩了。
\n日本人很奇特的一个习惯出现了:有些店铺傍晚 6:00 到 6:30 就收摊了,最晚的会开到七点八点左右,再迟就只剩下营业到深夜的家庭餐厅与居酒屋。
\n用“食べログ (tabelog)”这款软件在海游馆附近搜索评价比较高的店铺。
\n在海游馆隔壁有一个小小的综合体,里面有一些店铺还有在营业。我选择了这家评价很高的牛排咖喱餐馆。
\n
\n一份牛排咖喱饭、一碟酱菜、一碗味增汤。价格有些忘记了,在 1500円 左右。
\n日本人很喜欢吃这种细长的青椒,不会辣,在天妇罗中也会出现。
\n咖喱的甜口的,味道自然比百梦多咖喱块要好上数倍。牛肉很嫩,没有怎么调味,展现的是肉本身的鲜味。总之,相当的美味。
\n对了,需要使用现金哦~ 商家没有准备 POS 机。
\n大阪海游馆 已经记不清上一次去水族馆是多少年前。听说大阪海游馆的设施很棒,场馆也很大,我就来体验一下。
\n门票的价格是 2600 円,入场有分时段,我就在售票处购买了当前时段的票。
\n海游馆很大,步行参观的总距离在 1km 左右,按照地理位置划分成了好几个区域。
\n
\n进入场馆便是一个巨大的拱形水箱。是玻璃比较薄吗,还是用了什么技术,几乎看不见玻璃带来的重影,鱼仿佛真的在空中悬浮。
\n
\n一群正在睡觉的海狮。抬着头睡觉不会落枕么。
\n![巨型水箱]/images/2024-01-21/(9.jpeg)
\n在场馆的中央有一个巨大的水箱,有两头鲸鲨、几头锤头鲨和许多小鱼生活在其中。
\n
\n还有好多可爱的花园鳗,黄色的尤其可爱~
\n除此之外,还有来自各个大洲的鱼、南极的企鹅,在地下还有水母馆。
\n总共逛了一个多小时,可以说大饱眼福了。
\n
\n在海游馆的隔壁是天保山摩天轮,夜晚被彩色的灯光照亮特别好看。
\n此时已是 19:30,经历了一天奔波的我有些劳累,便回到酒店养精蓄然,计划第二天的行程。
\nDay 2 - 天守阁、天满宫、天筋桥与大阪烧
\n在日本的第一顿早餐,在酒店隔壁的参观享用了自助餐。
\n炒蛋和上次去香港在澳洲牛奶公司吃到的有点像,并没有完全做熟,非常的软嫩~
\n茶泡饭就很有日本的特色了。
\n饭后,我就前往难波站附近的旅游中心,购买大阪周游卡。我选择的是二日卡,价格是 3600 円。在两天内,可以免费乘坐大阪地铁与巴士(相较于一日卡,不能乘坐私铁),以及免费参观好多景点。
\n大阪地铁网络十分发达,基本覆盖了所有想去的地方,这两天我没有搭乘过私铁或者巴士,因此不用担心是否要选择一日卡。
\n买到卡之后,第一站便是天守阁。
\n
\n从外边看,与只狼里的苇名城有几分相像。
\n
\n天守阁内陈列着许多颇有历史的物件,只可惜我对日本的历史并不熟悉,也看不太懂书法家的笔墨,只是走马观花感受一下文化的氛围。
\n
\n最后在楼顶吹了吹风,便离开了天守阁。
\n
\n虽然有些冷,但天气真的很好,万里无云。下一站是天神筋桥,一个商业街。
\n
\n日本有很多 OOばし(桥)这样的地名呢。也有很多像天神筋桥这样,上方覆盖着遮雨棚的商业街,天神筋桥是其中最长的一条,从一丁目延伸到七丁目,光是主干就有 2.6 km,更有密密麻麻的小巷。
\n乘坐堺筋线,在天神筋桥三丁目下了车。我先是去拜访了天满宫。
\n
\n似乎内部在翻修,将赛钱箱放到了外边供大家参拜。
\n在这里,我给身边参加考研的人做了参拜,希望他们可以拿到好成绩。
\n接着,我便从二丁目开始,一路边逛边思考着午饭的去处,寻找着吃饭的店铺。
\n沿途,看到了大排长龙的可乐饼摊子,转了一圈再想回来买,发现已经卖完收摊了。有一家天妇罗的店门口也站着好多人,大排长龙。肚子好饿,肯定排不了这么长的队。
\n边走边用 食べログ 搜索着。来大阪就得吃些有大阪特色的,那就是大阪烧了。
\n
\n并不在主街,而是藏在小巷子里的千草大阪烧,似乎是 食べログ 23年的百大名店呢。
\n我选择了以店铺名字所称的招牌菜 —— 千草大阪烧。
\n核心是一大片厚切的猪肉,在上下两面都倒上面粉与卷心菜混合的泥,便开始煎烤。
\n接待我的服务员会一些简单的英语,告诉我等着他们来翻面就好了。当地人也许会选择以自己的喜好来摊大阪烧,我作为门外汉只要静静欣赏就好。
\n当两面都煎至金黄,便会涂上大阪烧酱、沙拉酱和黄芥末酱,撒上不知道是什么的籽,就可以享用。
\n大阪烧整体的口感是软糯的,夹心的猪肉排很嫩,肉汁十足。
\n唯一可惜的是,量实在有些少,填不饱我的肚子啊~
\n
\n饭后继续在天神筋桥闲逛,发现了一家鲷鱼烧店。我还以为鲷鱼烧是软软的,但实际外壳是偏脆的,甜甜的红豆馅十分美味。
\n今天真的走了好久的路,对于第一次来到异国他乡的人可以说是有些得意忘形。从天神筋桥二丁目逛到六丁目,又折返了回来。中途还去 melonbooks 看了一圈,又跑到周游卡可以免门票的天王寺动物园里逛了逛…还没逛完,人就开始有些不舒服了。
\n在动物园里稍微休息了一下,我还是决定吃完晚饭就回酒店休息。
\n
\n一次比较“失败”的体验。不要误会,寿司还是很好吃的,鱼类十分新鲜,但我一不小心就在 iPad 上点了太多的寿司,都吃进肚子之后已经很饱了,完全没有在回转的转盘上取过寿司!这和普通的寿司店不就没差别了吗!
\n
\n回转寿司店出门便是热闹的道顿堀,然而我不是喜好这一口的人。路过蟹道乐,周围停着好几台旅游大巴,店门口密密麻麻的全部都是在等待的游客,大约有数百人。真是疯狂呐。
\n受不住喧嚣,我在附近的药店买了一支体温计和一盒退烧药,便回了酒店。
\n躺床休息了一阵子再测体温已经正常,看来真的是疲劳导致的体温失调。之后的旅途安排就宽松一些吧。
\nDay 3 - 梅田蓝天大厦、天空美术馆与一兰拉面 睡了一个好觉,但人还是有些疲劳。前往异国他乡果然不能过于放肆,得做好身体的管理。今天就悠闲地度过吧!
\n
\n在酒店寄存行李后,搭乘地铁来到了梅田蓝天大厦。从三楼有快速电梯可以通往顶楼,然后乘坐扶梯来到屋顶的圆形观景台。
\n对了,有周游卡门票免费哦~
\n
\n
\n相比于其他观景塔,梅田蓝天大厦不算很高,但好在有开放式的观景台,视野特别棒。今天风有些大,在楼顶不是很站得住,欣赏了几分钟美景便回到了室内,点了一杯咖啡,坐在窗边静静欣赏着窗外景色。
\n梅田蓝天大厦外侧是镜面玻璃,倒映着天空、与天空融为一体,因此称作蓝天大厦。尼尔:机械纪元的开发公司白金工作室的总部就在这栋大楼里。
\n
\n
\n我还顺道参观了另一座楼的天空美术馆,虽然不怎么懂艺术,但画作依旧能感染我。
\n参观美术馆后,已是正午。该去找吃的了~
\n
\n百闻不如一见,我来品尝一兰了。
\n每个人第一次来吃日式拉面都要面临一场“大考” —— 单应该怎么点。(背面有中文,我填完了才发现)
\n除了在点餐机器上确认要吃东西,在排位时服务员会给你一张纸片,让你选择拉面的喜好。
\n浓郁度我选择了加浓,确实有些过浓过咸了。吃着相当地过瘾,豚骨的香味充满嘴巴,真的很幸福。如果平常吃得比较清淡,正常的浓郁度可能就有些偏咸了,可以考虑减少一些浓郁度。
\n油脂的丰富度我也加了一档,但感觉不是特别明显。可能是看到日本有一种表面铺满油渣的拉面,一兰的面在油脂的方面反而显得“寡淡”了吧。
\n其余的选项便是看个人喜好。一兰的辣椒粉不会很辣,加 1/2 倍感受不出辣味,但可以提升香味。
\n
\n交卷后,一碗一兰拉面,四片叉烧和一颗盐味溏心蛋上桌啦。
\n一兰号称在面出锅后15秒内就会传递到食客的面前,以体验拉面最新鲜的味道。我赶忙用手机拍了张照,便开始享用。
\n拉面和我之前吃过的感觉都不大一样,特别有筋道,麦香味也很浓。在浓厚的汤汁里蘸一下就有了豚骨的鲜味,没几口就把拉面吃完了,便又加了半份面。
\n上溏心蛋的时候同时给了个小碟子,如果蛋不好剥或者咸味不合适,可以让服务员帮忙换一个。
\n一口拉面,配上一口汤汁。再咬一口叉烧、一口糖心蛋,至福啊。
\n前往奈良 饭后,回酒店取了行李,便是去难波站坐近铁奈良线,前往奈良了。
\n一点感想 语言不是问题,打招呼、点菜这种比较简单的对话就用手势与塑料日语,更复杂的对话用英语就好了。碰到的酒店前台经理、便利店员工、车站管理员都是会英语的。再不济,就用翻译软件吧,能达意就行~
\n至于习惯问题,按照当地人的做法来做就好了。在公共场合,例如楼梯应该走哪个方向,等地铁的时候应当怎么站,在地铁上有哪些地方不能使用手机,都有明确的标识和多语言的提示。多留意,照着做,就不会有事儿啦。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/2ef8bd61.html",
+ "url": "https://blog.udon.eu.org/archives/2ef8bd61.html",
+ "title": "深圳-香港三日行",
+ "date_published": "2023-12-19T14:00:00.000Z",
+ "content_html": "出行原因 其实我很早就有去香港看看的念头,但有时间出游的机会并不多(机会很多,是我比较懒,更喜欢宅家),一直没去成。
\n前一段时间,在朋友的帮助下我注册了英国 Wise 账户,拿到了两张漂亮的实体卡,但后期如何入金成了个问题。了解到可以通过香港账户低损耗入金 Wise,我便有了前往香港开户的念头。
\n12 月底我将独自前往日本游玩,但这将是我第一次独自一人出去旅游。第一次单人旅行还是出国旅行,不禁让我有点担心。
\n在这两个背景的驱使下,我迅速定下了这次深圳-香港的旅行,既可以前去开户,也可以作为出国旅行的预演,提前暴露一些问题。
\n流水账 Day 1 - Thu - 深圳 第一天中午到的深圳。我在前往酒店的半路上下了地铁,前往名为“臻品鲜粿·粿条世家”的店铺吃午餐。
\n
\n粿条本身没有很特殊,有点细河粉的感觉。不过八成熟的牛肉非常非常的嫩,味道很棒。
\n
\n炸豆腐是赠送的,第一眼并太高的期望。但一口咬下,十分软绵的里芯呼之欲出,佐以略微有些咸的沾汁,味道很棒。
\n
\n我还点了一份长得有点像海蛎煎的东西,记不得名字了。应该是用油炸的,比海蛎煎更加酥脆。
\n美美地享用午饭后,我继续登上地铁,前往酒店。
\n下榻酒店后,我前去参观深圳世界之窗景区…总之就是很后悔。
\n比起人造景观,我还是更喜欢自然景观一些。世界之窗没有给我带来惊喜,只有满满的失望。今天是周四,许多游乐设施都没有开放,可以参观的仅有一个个微缩复原的建筑物。
\n在世界之窗转了约两个小时,我悻悻离去,前往凤凰楼食茶点晚餐。
\n
\n多春鱼的鱼籽很多,非常鲜美。
\n一个人出来吃饭确实有些不便。有些菜品一次只能点这么一大盘,不过还是美美的享用了。
\n
\n鲜虾烧麦就是吃鲜,特别的鲜甜。
\n
\n菜单上的字看得不是很懂,大概写的是炒肠粉吧,没想到上了这么大一盆长得也不像肠粉的东西。
\n吃了一下,似乎确实是切成一节一节的肠粉。味道不错,但量好大最后没吃完。
\n
\n饭后,我顶着吃撑的肚子徒步前往华强北,这片传说之地。可惜的是,大部分店铺八点就关门了,简单逛了几栋电子市场后我便回酒店计划第二天的行程。
\nDay 2 - Fri - HK 开户 一大早我便乘上了前往福田口岸的地铁。
\n7:30 左右的口岸没有什么人,大部分是赴港上学的学生,通关很快。
\n我在落马洲站选择乘坐 B1 双层巴士前往元朗区,开始了开户之旅。
\n沿途…说实话没有什么风光,元朗算是比较偏僻的地方,好风景还要等到第二天的香港岛之行。
\n8:00 我到达了中行门口,发现只有前来上班的员工,还没有来排队的人。如果希望第一个办理业务的朋友可以选择这个点就来等候。时间还早,我就去吃了个早餐,回来发现已经有两个人在排队,便加入了队伍。
\n9:00 准时进入了银行,取到了号。在我之前有提前预约的人,我排在了第 5 号。
\n在工作人员的引导下使用手机填写了信息,然后等待柜员办理剩下的手续。共有三个柜台在同时办理开户,一个人需要 30 - 60 分钟的时间。
\n11:00 左右,我完成了中行的开户。
\n我现场就拿到了银行卡,便在 ATM 存入了 500 元人民币,并兑换成了港币。
\n顺便在同一条路上的南洋商业银行和汇丰银行也完成了开户手续。这两家银行皆需要几个工作日审核信息,审核成功后会将卡片寄至通讯地址。
\n穿行于各家银行网点的途中,我发现了一家在他人游记上看到过的店铺——胜利牛丸,便暗自定下了午饭的去处。
\n
\n三个愿望,一次满足。
\n面汤的颜色相当的“可怕”——仿佛是直接将卤水呈上来一般。但实际入口并没有很咸。牛肉们就不一样了,比较重口味的我也觉得有一点点咸。不过风味很棒,牛肉吃透了各种香辛料。
\n河粉藏在了对得满满的牛丸和牛肉之下,不是我常吃的宽河粉,比较窄。
\n虽然店名以牛丸冠名,我却没觉得牛丸有多么的美味,反倒是牛筋更胜一筹。
\n饭后,我在坐满老者的公园里稍稍歇息,并且计划着下午的行程……
\n此次香港之行除了开户,其实还有另一个目标——碧蓝档案与香港吃茶三千的联动~
\n在出发前的几天得知了联动的消息,运气真的很好。
\n
\n虽然有更优的路线,我还是选择了体验一下地面上的有轨电车。有轨电车有点像巴士,也是由司机驾驶,也会受到其他车辆与行人的干扰,但行驶路线是沿着铁轨固定的。
\n随后乘坐地铁,前往金紫荆广场…的对面,星光大道。在这里陈列了许多明星的手印和画像。海港边上的风景大好。
\n在此稍停,开户还没有结束。大概是因为元朗距离内地太近,许多虚拟银行都需要前往更靠近香港中心一些的地方才可以开户。我在一家星巴克坐下,开通了 ZA Bank 和 livi Bank 的账户。
\n随后,便是前往海港城的吃茶三千点了一杯联动奶茶,从中心一步步移动回福田口岸——晚上要和群友面基。
\n我们俩聊得非常尽兴,聊到了十一点才分手,回到了酒店,速速洗漱完毕,预定了第二天早上从福田站前往香港西九龙的火车,便沉沉睡去。
\nDay 3 - Sta - HK 游玩 昨晚火车票订得比较迟,最迟的火车也是 7:45 的,更迟的都被人订光了。我又被迫起了个大早(这三天都是 6:30 之前起床的)坐地铁前往福田火车站,搭上前往香港西九龙站的火车。可能是比较早乘火车的人并不对,这一趟通关流程比较顺利,再迟一点可就不好说了……
\n到达九龙,我空着肚子在街上漫步,寻找可以填饱肚子的早餐店。打开地图,“澳洲牛奶公司”几个字印入眼帘,这不是前几天看到的网红店吗?我记得这家店只收现金,便前往沿途的便利店,在 ATM 用中银香港的卡取了 100 港币现金。没有注意到我用的是汇丰的 ATM,与银联网络并不互通,被收取了 15 港币的手续费 QAQ。(众安银行的卡在全港 ATM 可以免手续费取现,经常到港玩的话可以办一张实体卡)
\n
\n带着现金,我来到澳洲牛奶公司的门口,果不其然,排起了长队。好在我是一个人,这家店是强制拼桌的,顺利超过了大团的游客,来到了店内。
\n给我的第一印象嘛…很拥挤,人挤着人,甚至是背贴着背。小小的一张圆桌围坐着四个人,各自享用着早餐。穿着白大褂的服务员在人群缝隙间穿梭着,高效地记录着到店客人的订单,飞速地将菜品传递到桌前。
\n落座,菜单压在桌子的玻璃之下,写满了粤语,说实话我看不大懂。纠结了几十秒,发现菜单上还写着“早餐”、“下午茶”等套餐,我便对服务员说:“早餐套餐来一份吧。”
\n话音未落,餐具和一杯茶便着陆在我的桌上。剩下的菜品也并我让我久等。
\n
\n首先呈上的是一碗火腿通心粉。味道比较清淡,喝不出黄色的汤底是什么熬制的,不过比较鲜。
\n
\n接着呈上的是一个盘子,两片吐司和炒鸡蛋各占半壁江山。牛油烘烤的吐司暖暖的,软软的;这份炒双蛋则是套餐的点睛之笔——调味微咸的鸡蛋并没有炒得很熟,在生与全熟之间掌握了平衡,做到了入口软绵,回味无穷,堪称是绝品。
\n如果有路过,一定要来尝一下这份炒鸡蛋~
\n一顿饱餐之后,我继续踏上旅途,前往太平山顶的观景台。
\n
\n上山的路可以选择用脚走完,不过,还是要体验一下富有特色的山顶缆车~
\n缆车的斜度估摸着有三四十度,是我见过最陡的。缆车是建在山坡边上的,方便沿路欣赏香港的风景。
\n
\n缆车的终点站是太平山的山顶,下车后直接来到了凌霄阁摩天台的二层。沿着盘旋向上的电动扶梯,海拔逐渐升高,走着走着,最终到达了海拔428米的摩天台观景台。
\n在这里可以俯览香港的景色,听说夜景更美,不过我晚上并不住在香港,也就没有机会亲眼目睹,实在可惜。
\n摩天台上的风很大,吹着十分舒服。待了大约四十分钟,我便沿着原路返回,坐着缆车回到了山下。
\n
\n早餐吃得有够饱,到了饭点我还不是很饿,便在街道与小巷间漫步,寻找着午饭的好地点。我想找家正宗点的茶餐厅。
\n跟随地图,我来到了就近的广芳园…可等候的队伍早已排出店外,目测有三四十人在队伍中。因为下午就要坐动车回去了,我无心加入他们,便继续游荡着,寻找着。
\n
\n附近的茶餐厅真的不多,跟随地图的指引,我来到了一家街边的小店。店内只收现金,而我身上只剩下不到五十港币,便选择了菠萝油和冰阿华田作为午餐,反正肚子不是特别饿。(冰的阿华田比热的还要贵上几块钱哦)
\n
\n饭后,我一时兴起决定走路去西九龙火车站,而非乘坐地铁,便开始一趟 City Walk…差点把我 Walk 没了。
\n街上好多地方都在施工,地图的数据并没有新到可以帮我绕开因为施工而禁止通行的路段…再加上出海关也需要一些时间,我差点以为要赶不上车。
\n好在我还是在开始检票前就赶到了,算是有惊无险,顺利踏上了返乡的路,结束了此次旅程。
\n我都带了些什么 此次我轻装上阵,只带了一个双肩包。
\n这次旅行我尝试了内衣、袜子和洗澡用具(浴巾与毛巾)都使用一次性的,省去了携带大量衣物且需要带回大量脏衣物的麻烦,也可以保证洗澡用具足够干净(对酒店不是很信任)。浴巾和毛巾都是压缩的,体积非常小。
\n其余的,便是两晚更换的衣服和电子产品的充电器了。
\n开户的那些事 我挺担心中银香港会开不下来,因为我还是学生也没有带地址证明。在大堂接待客户的小姐姐也询问了我有没有带上地址证明,没有的话只能碰碰运气,弄得我更慌了。
\n不过真正坐进柜台和柜员大哥开始走流程,直到大堂经理过来刷卡完成审核、将卡递到我手中,都没有向我索取地址证明。不知道是因为得知我是学生,还是因为我身份证上写的是具体住址,反正过了这关,顺利拿到了卡。
\n而汇丰之行则运气不佳,提交的资料被送至总行审核,当天拿不到账户也无法完成所有流程,只能等卡寄到居住地址,并且等下一次到港签字完成所有开户流程了。
\nP.S. 在跨境转账到时候一定要填清楚收款人的名字,要与中银香港的账户名称相同。我填反了名字被收了一笔手续费 QAQ
\n尾声 这次旅行顺利得可怕(除了差点没赶上返程的火车),在深圳与香港复杂的地铁网中也没有坐错一趟列车,似乎没能暴露出什么问题。希望 12 月底的日本之旅也能一样顺利。
\n一个人出来玩真的很自由,在出发前先查好可以去哪里玩、去哪里吃,到达之后就随心安排,不用迁就其他人,走累了随时可以停下休息,在咖啡店坐上一两个钟头也不会有人抱怨。
\n我也许爱上了一个人旅行的感觉,之后也会继续尝试这种旅行的方法。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/42ec6146.html",
+ "url": "https://blog.udon.eu.org/archives/42ec6146.html",
+ "title": "My Second Attempt To ARM Servers",
+ "date_published": "2023-09-24T04:30:00.000Z",
+ "content_html": "Last weekend, a full month without my Telegram account, I thought I had to do something. I believe I got banned because someone clicked the “spam” button on my messages, but I have nowhere to appeal.
\nI lost the faith in centralization. This time, I made up my mind to dive into decentralization.
\nFirst, I need a server.
\nWhere To Purchase I used to buy servers from distributors of the DC for lower prices, but it always ended up with disappointment. The servers are just not stable and I can not upgrade the hardware configuration on demand.
\nSo this time, I chose Hetzner for my server. Hetzner is a huge IDC in Germany, I must have to pay a fairly high price for the server, am I right?
\nThe following images show the price of Hetzner’s traditional CX series - with x86 CPU.
\n
\nA 2 CPU and 4GB RAM CX21 can fit my needs, it costs 5.35 EUR per month. By the way, the 40GB disk is far less than my requirement.
\nThe CX series are using Intel Xeon CPU. The GB6 benchmark score of an Intel core is around 850, which is significantly lower than AMD EPYC of around 1200.
\nHowever, when I switch to the CAX series…
\n
\nI can get the same number of CPU cores and RAM with only 3.79 EUR per month, 30% lower than the x86 one.
\nThe GB6 benchmark score of ARM CPU is around 1050, even higher than the Intel ones.
\nThe disk space is still 40GB, but I can purchase a 3.2EUR/month 1TB Storage Box to solve this problem.
\nWith a total of 6.99 EUR per month, I can get a server with 2 CPU cores, 4GB RAM, and 1TB disk. That is a great deal.
\nWait, ARM? About a year ago, when Hetzner first released the ARM series, I was interested in it and evaluated its compatibility.
\nI always use Docker containers to host my ~25 services. It turned out that more than half of the Docker images I was using were only compiled for the x86 CPU.
\nThat means you have to build the image by yourself if you want to run it on an ARM CPU. The time and effort were not worth it, so I gave it up.
\nHowever, this time, I found all of the Docker images already supporting the ARM CPU. It is time to give it a try.
\nThe Experience Here is my final hardware configuration:
\nI chose the CAX11 server, which has 2 Ampere ARM CPU cores, 4GB RAM, and 40GB disk. I added 2GB swap space to store cached files and reduce the load of the RAM.
\nI also purchased a 1TB Storage Box to store my data. Mounted to the server with NFS, just like a local disk.
\nI am using the latest Debian 12 Bookworm and I can’t feel any difference between x86 and ARM. My daily use software from APT source is all compiled for ARM. The installation speed is as fast as x86.
\nAs to the Docker images, I am using Portainer to manage them. What I need to do is just click the “Recover” button on the settings page, Portianer will automatically recover the configuration from CloudFlare R2 storage and all the containers just work as before, with no need to change any settings.
\nToday, when I am writing this article, My services are running for a week without any problem.
\n
\nWell, Still Not Perfect The ARM server just works fine, but I want to share some problems I encountered when building the ARM Docker images.
\nThe official software repositories of Linux distributions can offer full support for ARM CPU, but the repositories of other sources such as PyPi can not.
\n
\nThere are still some packages that don’t have prebuilt ARM wheels, and the building process can take a long time on the 2-core ARM machine.
\nThat is not a huge problem, but it makes the experience of ARM arch different from x86.
\nIf you are a developer, try to include ARM in your build pipeline next time, that will be a great help for the ARM community.
\nFinal Conclusion In my opinion, the ARM server is ready for daily use today. They offer a high quality-price ratio. I didn’t come across any compatibility problems.
\nNext time when you want to purchase a server, you can consider the ARM ones.
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/8b68ddd6.html",
+ "url": "https://blog.udon.eu.org/archives/8b68ddd6.html",
+ "title": "修复 UEFI 引导的 GRUB",
+ "date_published": "2023-04-15T16:00:00.000Z",
+ "content_html": "问题与解决方法 环境 Manjaro Linux x86_64
\nKernel: 6.2.10-1-MANJARO
\n使用 UEFI 引导
\n问题 在 GRUB 尝试引导 Linux 内核时,出现如下错误:
\n1 2 3 4 5 error: sparse file not allowed. 452: out of range pointer: xxxxxxxxxx Aborted. Press any key to exit.
\n\n用户将无法进入系统。
\n解决方法 进入恢复系统 插入 Manjaro LiveCD, 启动 Live 系统。
\n确定磁盘分区 在 Live 系统中,使用 fdisk -l
查看磁盘分区情况,找到安装 Manjaro 的磁盘,假设为 /dev/sda
。
\n我的磁盘分区如下:
\n1 2 3 4 5 设备 起点 末尾 扇区 大小 类型 /dev/sda1 2048 821247 819200 400M EFI 系统 /dev/sda2 821248 723390463 722569216 344.5G Linux 文件系统 /dev/sda3 723390464 983437311 260046848 124G Linux 文件系统 /dev/sda4 983437312 1000214527 16777216 8G Linux 文件系统
\n\n可以确定,/dev/sda1
是 EFI 系统分区,/dev/sda2
是系统所在分区。
\n挂载分区 挂载系统分区:
\n \n\n将当前系统的工具分区挂载到 /mnt
下:
\n1 2 3 mount --bind /dev /mnt/dev mount --bind /proc /mnt/proc mount --bind /sys /mnt/sys
\n\n将 EFI 分区挂载到 /mnt/boot/efi
下:
\n1 mount /dev/sda1 /mnt/boot/efi
\n\n进入系统 \n\n重新安装 GRUB 1 grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable
\n\n具体参数需要根据实际情况进行修改。
\n在这之后 重启,进入通过 GRUB 引导系统。
\n在系统中使用 sudo grub-install --recheck /dev/sda
命令再次安装 GRUB,确保系统能够正常启动。
\n一些思考 接下来的内容是我的整个修复流程,包含了如何在搜索引擎查找问题、根据文章内容调整目标等杂碎的内容,和我在修复过程中的一些感想。
\n为什么会出现这个问题 不是很清楚。
\n在启动 Manjaro 前我不小心打开了电脑里的 Windows 系统,但没有连接移动硬盘。
\n按照以往的经验,这最多只会导致找不到 GRUB 的位置,手动指定 GRUB 所在分区就可以启动系统。
\n但这次不大一样。
\n在打开 GRUB 之后,尝试引导内核,就发现了这个问题。
\n初步解决思路 立刻格式化磁盘,重新安装 Manjaro。
\n我已经不是曾经那个只会重装的我了,这次我希望可以解决问题,而不是简单地重装。
\n首先,我 Google 了这个错误,发现了几篇内容相关的文章。
\n报错与我一致的文章 ,但没有给出解决方案。
\n要我删除 GRUB 和 UEFI 所在分区所有内容的文章 ,有点可怕,不敢这么干。
\n提到应该重新安装 GRUB 的文章 ,这还有点道理。
\n于是,我的目标转变为重新安装 GRUB。
\n重新安装 GRUB 在之前遇到找不到 GRUB 分区的问题时,在手动引导然后进入系统后,我会执行 sudo grub-install --recheck /dev/sda
重新安装 GRUB,解决这个问题。
\n那这次的觉得方案应该是差不多的……吧?
\n不对啊,这次是在 LiveCD 的系统里操作,怎么能直接安装 GRUB 呢?
\n这个问题比较难描述。
\n我先是 Google grub-install 修复 GRUB
,中文网站的内容都是关于在可以启动的系统下修复 GRUB 的,没有我需要的内容。
\n然后我开始求助于 ChatGPT,输入的 Prompts 是:
\n1 2 I am using Manjaro with GRUB. When I booted into the system , it says "sparse file not allowed 452 out of range pointer" . How to fix it ?
\n\n不难发现,我并没有说明我使用的是 UEFI 引导,这直接影响到了 ChatGPT 回复的准确性。
\nChatGPT 给出的修复步骤与上述的相差不大,只是在挂载系统分区和工具分区后,直接尝试执行 grub-install
命令,尝试修复。
\ngrub-install
返回错误 this gpt partition label contains no bios boot partition
把我弄得更懵了。
\n再次 Google 这个问题,发现了 这篇在长篇大论讲 GRUB 的文章 ,虽然没有给出解决方案,但它让我意识到 UEFI 和 Legacy BIOS 两种启动方式的区别。
\nUEFI 和 Legacy BIOS UEFI 和 Legacy BIOS 是两种启动方式,它们的区别在于,Legacy BIOS 是在 BIOS 中直接加载内核,而 UEFI 是在 BIOS 中加载 EFI 系统,然后由 EFI 系统加载内核。
\n使用 UEFI 引导的系统一般都有一个 200MB 到 400MB 的 EFI 系统分区,用于存放 EFI 系统。在上述的,我的硬盘分区中可以看到。
\n使用 findmnt
命令可以查看当前系统的挂载情况,其中 TARGET
列就是挂载点,SOURCE
列就是挂载的分区。
\nEFI 分区的挂载情况为:
\n1 2 TARGET SOURCE FSTYPE OPTIONS /boot/ efi /dev/ sda1 vfat rw,relatime,fm
\n\n可以看到,/boot/efi
里的内容正是 EFI 系统分区的内容。(我也是刚学到这个知识的)
\n解决 UEFI 相关问题 在修复过程中,我是通过 Google 发现上述的问题。
\n这篇文章 给了我莫大的帮助。
\n其中提到了 EFI 分区,也提到了如何正确安装 UEFI 引导的 GRUB:
\n1 2 sudo grub-install --target=x86_64-efi --efi-directory=/boot/efisudo grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable
\n\n在补充挂载 EFI 分区、切换 Root 目录后,执行第一条命令,发现有错误。尝试执行第二条命令,发现没有错误,代表 GRUB 已经重新安装成功。
\n此时我想起来,在之前安装 GRUB 时,会提示 正在为 x86_64-efi 平台进行安装
,我才意识到前面的修复过程并没有去指定平台。
\n总结一下 总之,这就是我此次修复的心路历程。我没有研究过 UEFI 和 Legacy BIOS 的区别,也没有研究过 GRUB 的安装过程,所以在修复过程中,我是通过 Google 和 ChatGPT 的帮助才解决了这个问题。
\n希望这个探索过程能给你一些启发吧。
\n \n此文章以 我无所谓 By 不 By 什么 AI,对我有帮助的文章就是好文章 标识发布。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/8b115688.html",
+ "url": "https://blog.udon.eu.org/archives/8b115688.html",
+ "title": "使用 Docker Compose 部署音乐服务器 Navidrome",
+ "date_published": "2023-01-31T04:00:00.000Z",
+ "content_html": "服务介绍 Navidrome 是一款兼容 Subsonic API 的开源音乐服务器软件,它提供了一个不错的 WebUI,也可以将支持 Subsonic API 的客户端接入。
\n目前项目正处在活跃开发中,各种各样的新功能正在陆续加入。
\n我的客户端选择 电脑端 自带 WebUI, Sonixd 【跨平台】
\niOS play:sub 【付费软件 4.99$】
\n部署方式 采用 Docker Compose 部署 Navidrome,使用 Nginx 作为反向代理。
\nDocker Compose 配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 version: "3" services: navidrome: image: deluan/navidrome:latest container_name: navidrome user: 1000 :1000 ports: - "127.0.0.1:4533:4533" restart: unless-stopped environment: ND_SCANSCHEDULE: 1h ND_LOGLEVEL: info ND_SESSIONTIMEOUT: 24h ND_BASEURL: "" ND_SEARCHFULLSTRING: true ND_SPOTIFY_ID: ND_SPOTIFY_SECRET: ND_LASTFM_APIKEY: ND_LASTFM_SECRET: ND_LASTFM_LANGUAGE: en volumes: - "./data:/data" - "/APTH-TO/navidrome-music:/music:ro"
\n使用命令 docker compose up -d
启动服务。
\nNginx 配置文件 我建议使用 DigitalOcean 的 Nginx 配置生产工具,示例配置如下:
\n示例配置
\n也可参考下述配置,此为 DigitalOcean 生成配置的简化版:
\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name music.example.com; ssl_certificate /etc/letsencrypt/live/music.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/music.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/music.example.com/chain.pem; access_log /var/log/nginx/access.log combined buffer=512k flush=1m ; error_log /var/log/nginx/error .log warn ; location / { proxy_set_header Host $host ; proxy_pass http://127.0.0.1:4533; } }server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name *.music.example.com ; ssl_certificate /etc/letsencrypt/live/music.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/music.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/music.example.com/chain.pem; return 301 https://music.example.com$request_uri ; }
\n\n音乐管理 我一直以文件夹分类的方式管理音乐,但 Navidrome 并不支持文件夹分类。它是根据音乐标签来分类的,例如按照歌手、专辑等依据分类歌曲。
\n因此,若想使用 Navidrome,需要对音乐进行标签管理。
\n大约两年前,我写了 一篇文章 介绍使用 Music Tag 和 Foobar2000 两款软件来管理音乐。
\nMusic Tag 的标签源是网易云音乐、豆瓣音乐、QQ 音乐等国内音乐平台,说实话,这些平台的音乐标签质量都不是很好,所以我一直在寻找更好的音乐标签源。
\n直到我发现了 MusicBrainz ,这是一个开源的音乐标签数据库,任何人都可以为它贡献标签。在体验之后,我发现 MusicBrainz 的音乐标签质量要比国内音乐平台的标签质量好很多,所以我决定将 MusicBrainz 作为我的音乐标签源。
\n我使用 Picard 这款软件来从 MusicBrainz 获取音乐标签。
\n将音乐导入 Picard 后,它会自动从 MusicBrainz 获取音乐标签,然后将标签写入音乐文件,十分方便。
\n开始使用 不论是使用 Navidrome 自带的 Web 界面,还是使用兼容 Subsonic API 的客户端,只要连接到 Navidrome,便可开始享受你的私人音乐库。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/f9bfe16a.html",
+ "url": "https://blog.udon.eu.org/archives/f9bfe16a.html",
+ "title": "使用 Docker Compose 部署 Keycloak 20",
+ "date_published": "2023-01-22T12:00:00.000Z",
+ "content_html": "部署方式 采用 Docker Compose 部署,使用 Postgres 作为数据库,使用 Nginx 作为反向代理。
\nDocker Compose 配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 version: '3' services: keycloak: image: quay.io/keycloak/keycloak:latest environment: KC_DB: postgres KC_DB_URL: jdbc:postgresql://db:5432/keycloak KC_DB_USERNAME: keycloak KC_DB_PASSWORD: keycloak KC_HTTP_ENABLED: true KC_HOSTNAME_STRICT: false KC_HOSTNAME_STRICT_HTTPS: false KC_HTTP_RELATIVE_PATH: '/' KC_HTTP_PORT: 8080 KEYCLOAK_ADMIN: MY_USERNAME KEYCLOAK_ADMIN_PASSWORD: MY_PASSWORD PROXY_ADDRESS_FORWARDING: true KC_PROXY: edge entrypoint: /opt/keycloak/bin/kc.sh start ports: - 127.0 .0 .1 :18080:8080 restart: unless-stopped db: image: postgres:14 restart: unless-stopped environment: - POSTGRES_USER=keycloak - POSTGRES_PASSWORD=keycloak - POSTGRES_DB=keycloak volumes: - ./postgres-data:/var/lib/postgresql/data
\n\n使用命令 docker compose up -d
启动服务。
\nNginx 配置 我建议使用 DigitalOcean 的 Nginx 配置生产工具,示例配置如下:
\n示例配置
\n也可参考下述配置,此为 DigitalOcean 生成配置的简化版:
\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name auth.example.com; ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/auth.example.com/chain.pem; access_log /var/log/nginx/access.log combined buffer=512k flush=1m ; error_log /var/log/nginx/error .log warn ; location / { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_buffer_size 128k ; proxy_buffers 4 256k ; proxy_busy_buffers_size 256k ; proxy_pass http://127.0.0.1:18080; } location /auth/realms { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_pass http://127.0.0.1:18080; } location /auth/resources { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_pass http://127.0.0.1:18080; } location /auth/js { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_pass http://127.0.0.1:18080; } }server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name *.auth.example.com ; ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/auth.example.com/chain.pem; return 301 https://auth.example.com$request_uri ; }
\n\n配置 Keycloak 创建 Realm 打开 Keycloak 地址 ,界面如下。
\n
\n选择 Administration Console
,进入管理界面。
\n
\n选择箭头指向的下拉菜单,选择 Add realm
,创建一个新的 Realm。
\n
\n填写 Realm 名称,点击 Create
。
\n创建 Client
\n选择 Clients
。
\n
\n点击 Create client
。
\n
\n填写 Client 相关信息,点击 Next
。
\n
\n按需求选择 Client 的配置,点击 Save
。
\n
\n至此,Keycloak 配置完成,且创建了第一个测试用 Client。
\n测试 Client 可根据 官方教程 测试该 Client。
\n尾声 上述便是使用 Docker Compose 部署 Keycloak 20 的方法,我们顺利创建了第一个测试用 Client,接下来可以根据自己的需求进行配置。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/9b78ad2a.html",
+ "url": "https://blog.udon.eu.org/archives/9b78ad2a.html",
+ "title": "寻梦穿越机 - 入门浅谈",
+ "date_published": "2022-09-04T04:00:00.000Z",
+ "content_html": "暑假开始之前,我就朦朦胧胧有购入一台穿越机的想法。起因为何?我也不是很清楚。是看到了酷炫的穿越机航拍视频 ?还是童年时期的航模魂蠢蠢欲动,将要苏醒?
\n兴趣,倘若不立刻抓住,很快就会被抛至脑后。为了不让自己后悔,那就立刻开始筹备吧!攥紧并不是特别充盈的钱包,我踏上了寻梦之路。
\n什么是穿越机 FPV (First-Person View),是指通过第一人称视角远程控制无人飞行设备 (UAV) 的控制方式,也是一项运动,也指代了一类设备。
\nFPV 设备包含固定翼 (Fixed Wing) 与多轴 (Multi-rotor) 两种。穿越机多指多轴 FPV (常见为四轴)。
\n
\n一盆冷水 在详细介绍穿越机构成之前,请允许我泼一盆冷水。
\n穿越机与一些航拍无人机,例如大疆 (DJI) 的四轴航拍机与航拍 FPV 相比,颇有原始人见到现代人的感觉。
\n大疆无人机上搭载了各种各样的传感器,各系列大都配备了二向 (前后)、四向 (前后左右) 或五向 (增加一个上方) 的视觉避障功能、红外定高悬停功能,以及低电量 GPS 自动返航功能。
\n上述这些看似是无人机应当“标配”的功能,追求轻量化的穿越机时常一个也没有。一台最小安装的穿越机全机的传感器仅有集成于飞控的陀螺仪和地磁传感器 (电调的电流传感器一般不算在内),飞控仅知道当前机身的朝向、倾斜角度与加速度,无法感知周围的环境,没有实现避障、悬停与自动返航的能力。飞机的运动状态完全取决于飞手的操控,电池的剩余电量甚至需要飞手根据电芯电压来推断。
\n
\n再者,为了能获得更大的速度与更高的机动性,穿越机飞行过程一般处于手动模式,对飞机四向的倾斜没有限制,飞行过程中出现危险操作的概率大,事故概率也大。而大疆无人机对飞行速度、角度有严格的限制,能有效地降低炸机的概率。
\n因此,飞行穿越机的难度将是飞行大疆等航拍无人机的十倍、甚至百倍。请务必不要抱着随便玩一玩、随心飞的态度入坑穿越机,发生事故后轻则炸机提 (遥) 控回家,重则伤到自己或他人。对于喜欢日常随心飞行的玩家,大疆或类似牌子的无人机 (包括航拍机和穿越机) 更为合适。
\n \n下文将详细介绍入门穿越机所需要的技术知识与穿越机的重要模块。在明白风险之后仍打算入坑穿越机,或对穿越机有兴趣的你,欢迎继续往下阅读。
\n技术储备 上文提到了我童年时期的航模魂。我在小学就跟着老师学习无线电和航模知识,练习了焊接技巧和单旋翼的航模直升机的飞行技巧。曾参加某个航模比赛并拿到了铜牌的好成绩 (我好自豪)。
\n因此,我对我的焊接技术和遥控操控能力比较有信心。而这两个能力恰好是入门穿越机不可或缺的。下面我列举一些入门穿越机需要掌握的技能。
\n锡焊 除了购买真·到手飞套餐 (机子已组装完成,接收机已与遥控器完成配对),其余情况大概率需要焊接一些导线。
\n 大部分组装机套餐都不包含接收机,因为接收机与遥控器一一对应,一般是和遥控一起购买的。就需要用户自行焊接至飞控上。
\n 锡焊所用到的工具有电烙铁和焊锡丝,最好能佐以松香,增加焊接成功率。电烙铁推荐购买 T12 或更好的,因为飞控散热设计,很多焊盘为通孔的形式,热量会非常快地传到到全板,导致焊锡较难达到融化的温度。若使用功率太低的电烙铁,可能无法融化焊锡。
\n
\n 由于飞控比较小巧,焊盘也十分迷你,对焊接技术要求较高。
\n使用搜索引擎 不懂不可怕,不懂得学习才可怕。
\n 穿越机有关的零碎知识犹如满天星,散布于互联网的每一个角落,需要一定的搜索技巧才能挖掘到有用的知识。
\n 国内有关穿越机的网站数量比较少,建议使用英文搜索。
\n编译 若想要更新 ELRS 接收机和高频头的固件,需要从源码编译 ELRS 固件。虽然 ELRS 团队提供了一套图形化的编译工具,但难免会遇到一些疑难杂症 (博客中就有 ELRS 固件编译的踩坑记录)。需要对编译有一定的知识,能自行解决简单的问题。或者选择使用出厂固件,不自行升级。
\n操控能力 (协调性) 像音游 (音乐游戏) 一样,微操遥控器并不是所有人都能很好地做到。穿越机对遥控指令的反应极为灵敏,需要你能精细地操控遥控器。
\n 但这项技能可以通过电脑模拟飞行来习得。建议在正式开始飞行前,先在电脑上使用模拟器熟悉飞机的控制。
\n一颗勇敢的心 在飞行穿越机的过程中,不可能不出现炸机 (飞机以异常姿态落地) 的情况,难免会留下一点阴影。
\n 我在小学时飞过固定翼飞机,曾不慎撞到电线,导致飞机起落架损坏脱落,最后只能由老师操控迫降。此事给我留下了不小的心理阴影,有好几个月我都不敢再飞行固定翼。不过最后还是克服了恐惧,再次拾起了遥控。
\n 炸机并不可怕,每一次炸机都记录着你的成长。
\n深入了解 类似 Linux (比较奇怪的类比),穿越机也分为最小安装和额外拓展两部分。
\n
\n最小安装:
\n\n机架 - 硬连接其他组件,保护脆弱的电路板; \n电机 (和桨叶) - 提供飞行动力; \n飞控 - 控制飞行状态,是穿越机的大脑; \n电调 (电子调速器) - 驱动电机、分隔电路 (防止电机减速时产生的反向电流烧坏其他电子设备); \n接收机 - 接受遥控器控制信号 (有 2.4GHz、915MHz、 868MHz 等频段,互不兼容); \n图传 - 发送图像信号 (多为 5.8GHz 频段); \n摄像头 - 提供第一人称视角; \n \n额外拓展:
\n\nGPS 模块; \nBB 响 (蜂鸣器 Beeper,用于寻找飞机); \nLED 灯珠、灯带 (好看 XD); \n红外避障模块; \n运动摄像机 (拍摄更清晰、帧率更高的视频); \n \n挑几个比较重要的模块介绍一下:
\n机架 穿越机机架一般由碳纤维板制成,质地轻盈且强度极高,可以物理连接不同的模块,并保护脆弱的电子设备 (飞控、图传等裸板)。
\n挑选时,机架有几个要主要关注的核心参数:
\n\n外形:
\n机架分为普通式与涵道式 (Duct Propeller) 两种。
\n普通式机架的螺旋桨桨叶裸露在外,而涵道式机架的螺旋桨外侧有涵道 (类圆筒) 包裹。
\n有一些普通式机架可以通过额外安装涵道,变身成为涵道机架。
\n \n \n
\n 普通式机架采用开放式的设计,尽可能少地使用材料以减轻重量。由于螺旋桨暴露在外,危险性较大。普通机架可被用于任何竞速与花飞机型。
\n 涵道式机架通过涵道结构可以提供更好的升力、稳定性和安全性,但额外增添了重量和封阻,一般被用于小型机与花飞机型。
\n
\n 此外,机架还可以再细分为 X 型、H 型等,在此就不多展开,有兴趣的读者可以自行查阅。
\n\n大小:
\n穿越机大小各异,从最小的一寸机,到体型较大的五寸、六寸机。
\n二寸以下的机子防风能力较差,但体型轻盈且常使用涵道机架,适合在室内飞行,且不易伤人。
\n三寸以上的机子动力充沛,飞行速度快,但体型大、螺旋桨裸露,无法在室内飞行。适合在场地广阔的室外进行竞速或花飞 (花样飞行)。
\n \n \n飞控与电调 飞控与电调 (有时还有图传) 几块板子大小相当,时常纵向堆叠安装,称为飞塔。在多块 PCB 间有硅胶柱减震。
\n
\n飞控需要注意的参数不多:
\n\n
\n 由于穿越机飞行对计算性能不是很敏感 (类似 3D 打印机),一般选择搭载 F405 SoC 的飞控性能就足够使用了。
\n\n电调最重要的参数就是最大放电电流了。
\n电调的电流选择需要根据机身大小、电机和桨叶大小、形状进行估计,通常电机厂家会给出推荐值,按照推荐选购即可。
\n接收机与图传
\n接收机与图传共享一个重要的特性,协议:
\n常见的接收机有 ELRS (ExpressLRS) 和黑羊 TBS 两类。应该注意的是,不同种类的接收机使用的通信协议和频段不同,能与其配对的遥控器和高频头也不同 ,因此在挑选接收机的时候一定要看清楚协议和频段。
\n接收机和飞控的串口通讯协议也各异,有 UART、SBUS 等数种,再购买接收机前需确认飞控有相对应的串口。
\n图传分数字图传和模拟图传两种。
\n数字图传以大疆的最为出名,需要使用配套的飞行眼镜;模拟图传大多使用 5.8G 频段通讯,和大部分接收模拟信号的飞行眼镜通用。
\n图传连接至飞控的串口通讯协议也很多,购买的时候请多加留意。
\n除此之外,接收机有遥测功率、内/外置天线、天线接口等参数,在挑选时都需要多加留意。
\n图传的另一主要参数则是发射功率。发射功率越大,能稳定接受图传信号的距离一般就越大。小型穿越机一般选择发射功率在 200mW 到 500mW 的图传即可 (部分图传发射功率可调);若有远航要求,也可选择发射功率大于 1W 的图传 (价格较高、发热也较大,一般带有主动散热)。
\n机子之外的配件 除了穿越机本体之外,想要拥有完整的飞性体验,还需要遥控器、高频头、电池、平衡充电器、飞行眼镜等配件。
\n上述的每一种配件都可以写作一篇介绍文章。由于篇幅有限,本文就不再介绍上述的外围配件,请善用搜索引擎,学习相关知识。
\n配套软件 除了硬件,穿越机配套的控制软件也尤为重要。目前主流的控制软件是开源的 Betaflight。
\n
\nBetaflight 分为嵌入端 (安装在飞控中嵌入式系统) 和地面站 (安装在电脑里的软件)。将飞控通过线缆连接至电脑,并打开 Betaflight 地面站软件,即可对飞控参数进行调整。
\nBetaflight 调参也是一门大课,新手不建议自定义太多的参数。待熟悉飞机之后,才建议调整 PID 等高级控制参数。
\n新手的第一台飞机 说了这么多,要上某宝挑选、下单穿越机的种种配件了吗?
\n我的建议是否定的。
\n我咨询了一些老玩家,他们建议新手购买他人已完成调参的二手机器,或者购买商家大部分已组装完成套机,以绕过纷繁复杂且状况百出的 DIY 过程,降低还未入坑就弃坑的风险。
\n此外,自行购买散件的总价常常会高于购买整机的价格。对于钱包不是特别充盈的我,购买整机也是一个省钱的选择。
\n待熟悉了穿越机的飞行与调试之后,再学习他人经验,设计并组装一台自己心目中的机器也不迟,这才是 DIY 的浪漫。
\n我的成果
\n我选择购买一位老玩家完成大部分组装工作的“半”整机——包含了机架、电机、飞控和电调。图传、摄像头和接收机则是我自行购买和焊接安装的。
\n飞机到手、外围装备齐全,只待一飞冲天。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/e74a90f2.html",
+ "url": "https://blog.udon.eu.org/archives/e74a90f2.html",
+ "title": "使用再生龙 Clonezilla 备份操作系统",
+ "date_published": "2022-08-12T04:30:00.000Z",
+ "content_html": "近日购入了一只闪迪的 CZ880 Extreme PRO 固态U盘来装 Manjaro。
\n虽然U盘本体是终身质保的,但数据无价,配置一遍系统就要花大把的时间。遂有了备份U盘内整个系统的念头。
\n下面跟着我使用再生龙 Clonezilla 把U盘里的系统备份一下吧!
\n事先准备 再生龙是运行在独立操作系统 (Debian/Ubuntu) 上的一套软件,因此需要制作启动盘,或使用 Ventoy 等多系统启动方案。(实测 YUMI 无法启动再生龙,故建议使用 Ventoy)
\n制作启动盘 前往 Rufus 官网 下载 Rufus 启动盘制作工具。
\n前往 Clonezilla 官网 下载再生龙映像。
\n使用 Rufus 将再生龙映像烧写进另一个U盘即可。
\nU盘内数据将丢失,请做好备份!
\n使用多系统启动 前往 Ventoy 官网 下载安装包(兼容 Windows 与 Linux),将你的另一个U盘制作为 Ventoy 多启动盘。
\n前往 Clonezilla 官网 下载再生龙映像。
\n将再生龙映像拷贝至 Ventoy 多启动盘中。
\n选择你想存放备份数据的目的地,创建一个存放备份映像的文件夹(注意目录名称中不能带有空格)。
\n剧透一下,40G 的系统盘备份之后大约占了 16-17G. 请留出足够的空间(建议和待备份的数据等大小)。
\n开始备份 瞎眼警告:由于没有合适的截屏方式,我很不愿意地采取了 拍 屏 的方式,敬请谅解。
\n启动再生龙系统 确保你的电脑关闭了安全启动,若还打开着,需要在 BIOS 中将其关闭。
\n插入刚刚制作好的启动盘/Ventoy 多启动盘,在电脑启动时猛敲键盘的…某个键,这因电脑型号而异,打开启动菜单。
\n选择插入的启动盘/多启动盘。
\n启动盘用户若没有太大的兼容性问题,就能看到再生龙的启动菜单。
\n多启动盘用户还要再多一步,在 Ventoy 菜单内选中再生龙的映像,如下图所示,即可打开再生龙的启动菜单。
\n
\nP.S. 我的笔记本兴许和 Ventoy 的 UEFI 模式相性不大好,在 BIOS 中开启了 Lagacy 兼容模式后,使用 Legacy 模式才能开启 Ventoy。
\n选择再生龙启动方式
\n经典的 GRUB 启动菜单,一般来说选择默认的第一项启动方式即可。
\nVGA 启动花屏 我的电脑遇到了在 VGA 800x600 模式下花屏的问题。
\n最终进入 Other mods of Clonezilla live
菜单,
\n
\n选择了上图中的 KVM & To RAM 模式,可以正常启动了。
\nUSB 口不够用的用户 我这台笔记本只有两个 USB 口,其中一个要给备份源头 CZ880,另一个则要给移动硬盘,故选择了 To RAM
模式,将再生龙载入内存,就可以拔掉多启动U盘,空出 USB 口给移动硬盘了。
\n语言配置
\n选择自己想用的语言即可。
\n
\n保持默认配置即可。
\n备份配置
\n我们选 Start Clonezilla 使用再生龙
。
\n命令行可以在熟悉了配置之后使用。
\n
\n此处我们选择第一项 device-image 硬盘/分区[存到/来自]镜像文件
。
\n若想进行两盘对拷,可以选择第二项。我还没有尝试过。
\n挂载存储目录
\n这次我打算使用移动硬盘备份系统,故选择第一项 local dev 使用本机的分区
。
\n
\n随后,再生龙会提示插入想要挂载的 USB 设备,按照提示做即可。
\n
\n此时画面会动态显示系统识别到的存储设备。看到期望的目标设备时,按下 Ctrl-C
停止搜索。
\n
\n在扫描完电脑当前安装的所有硬盘的分区后,你需要选择备份镜像文件存放的那个分区。
\n如图,我希望备份到大小为 1.8T 的移动硬盘上,故选择最后一项 sdc2
。
\n
\n随后,再生龙询问你是否需要检查并修复挂载的文件系统,我们选第一项否就好了。
\n
\n接着,就是选择备份镜像存放的位置。
\n使用键盘的方向键选择目录,使用 Tab
跳转到下方的选项,选择 Browse
并敲击回车就可以进入到此目录。
\n若希望在选中目录下存放备份镜像文件(是一个文件夹),就可以选择 Done
选项,回车确认。
\n
\n系统询问是否检查生成的备份镜像的可还原性,这里我们选是,多花一点时间能确保备份的完整性。
\n
\n镜像加密,依个人喜好选择。
\n
\n待上述配置完成后,系统会向你再次确认备份的内容与目的地。
\n确认无误后输入 y
并敲击回车继续。
\n简单模式/高级模式 此时应该有一个模式选择,问你想要使用简单模式还是专家模式。
\n我建议选择 专家模式,简单模式能选择的参数较少。
\n
\n接下来的三个选项,全部保持默认配置即可。
\n
\n
\n
\n压缩方式选择
\n此处选择第三项 -z2p 使用并行 bzip2 压缩
。
\n实测 bzip2 压缩速度比较快,产生的备份镜像的体积也不算大。
\n下图为选择了第一项 -z1p 使用并行的 gzip 压缩
的速度:
\n
\n下图为选择了第三项 -z2p 使用并行 bzip2 压缩
的速度:
\n
\n可以看出 bzip2 压缩速度比 gzip 快了8倍。
\n其他压缩方式的速度,待我测试之后更新文章。
\n
\n分卷大小配置保持默认即可。
\n备份镜像检查 待备份完成后,再生龙还会进行一次备份镜像的可还原性检查,如下图:
\n
\n若得到下图的提示,则备份镜像生成成功了。
\n
\n随后,选择按照意愿选择备份结束后的操作即可。
\n
\n至此,再生龙 CLonezilla 的基础教学就结束了,你已经学会了如何使用再生龙的图形界面进行备份。
\n下面是一些再生龙的高阶(大概很高级)使用方法。
\n高级操作 使用无线网络备份 上文中,我的电脑仅有两个 USB 口,为备份的流程增添了不必要的麻烦。
\n能否使用 Wi-Fi 将备份镜像推送至家中的 NAS 呢?
\n再生龙内置了许多通过无线/有线网络备份的方法,如下图:
\n
\n我们尝试使用 Webdav 来远程备份吧!
\n利与弊 使用 Wi-Fi 备份可以摆脱线缆,更加轻松而优雅地进行备份。
\n然而,由于通过 Wi-Fi 或者一切网络传输数据的速度仍然无法比肩有线传输,备份所消耗的时间将是备份至本地磁盘的 3-4 倍。
\n备份我U盘中的 40G 的 Manjaro 系统用时 30min 左右。
\n倘若你有大把的时间,或家中的内网速度足够快,大可使用无线备份。品着咖啡,看着数据上云(笑)。
\n预先准备 上文中我们选择了基于 Debian 的 Clonezilla Stable 版本,遗憾的是 Debian 系统中并未携带太多驱动程序,因此识别不到我的 Intel AX200 无线网卡。
\n经过测试,基于 Ubuntu 的 Clonezilla Alternative Stable 版本可以识别到 AX200 网卡。
\n点击上方链接即可下载 Clonezilla Alternative Stable 版本的映像。
\n重新烧写启动U盘/拷贝映像至多启动U盘即可。
\n又遇到了启动问题 使用基于 Ubuntu 的再生龙,上文中使用的 KVM
模式变得无法打开了,且 VGA 800x600
模式是一样的花屏。
\n在一番尝试之后,我发现藏在更多启动选项菜单里的 VGA 1024x768
模式可以正常显示。看来基于 Debian 的再生龙也可以使用这个模式。
\n开始备份
\n选择了非本地的镜像存储位置后,系统将开启上图的网络管理菜单。
\n选择第一项 Edit a connection
。
\n
\n选择 Add
选项,在弹出菜单中 Wi-Fi
。
\n
\nProfile name
随意填写;
\nDevice
一般填写 wlan0
,系统的第一块无线网卡;
\n接着,按照自己的情况填写图中划线的三个配置即可。
\n
\n保存 Wi-Fi 配置后,就能看到当前配置的连接状态。
\n若当前配置名前带 *
,且右侧选项为 Deactivate
,则 Wi-Fi 已连接成功。
\n
\n接着,系统要求填写 Webdav 地址。
\n
\n最后,系统会向你确认 Webdav 是否正确。
\n若确认无误,即可敲击回车继续。
\n接下来的步骤和上述初级教程硬盘挂载之后的流程是完全一样的,请参考上文继续配置。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/f82a3103.html",
+ "url": "https://blog.udon.eu.org/archives/f82a3103.html",
+ "title": "BETAFPV 高频头固件编译 AttributeError",
+ "date_published": "2022-08-06T04:30:00.000Z",
+ "content_html": "错误原因 Python 模块 pypandoc
版本过新,1.8.0 及更高版本已移除了 BETAFPV 高频头固件中仍在使用的 convert
函数。
\n解决方法 安装旧版的 pypandoc
模块。
\npip install pypandoc==1.7.0
\n",
+ "tags": [
+ "教程",
+ "DIY"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/38942a16.html",
+ "url": "https://blog.udon.eu.org/archives/38942a16.html",
+ "title": "DIY 显示器音箱",
+ "date_published": "2022-06-03T07:15:00.000Z",
+ "content_html": "新买的显示器(LG 27UL500,虽然因为屏幕问题已经退货了)没有内置音箱,虽然大部分时间都在用耳机,但别人有的东西我不能没有嘛,就买了些材料,DIY 一个外接音箱。
\n写作此文章分享一下制作的过程~
\n物料清单
\n\n接受 5V 电压,可以驱动两个 3W 的喇叭。商品详情页面吹的很厉害,确实底噪很小,而且输出的音量非常高。相当 OK 的功放板。
\n\n8Ω 3W 喇叭两只(带音腔) 2*4RMB + 运费 \n \n音质很不错,声音很大也不会破音,因为是广告机用的喇叭么?
\n\n我选的型号是 PJ392,只要是 3.5MM 双声道的公头就行了。
\n\n这个随意选。
\n\n我买的是4芯的屏蔽线,可供 Type C 头使用(2 power 2 data),音频线只需要三芯(2 data 1 GND),屏蔽线是为了更小的干扰、更好的音质。
\n开始组装 3.5MM 线缆 剥除一段屏蔽线的外皮,做工还是很不错的,有金属丝和铝箔的屏蔽,塑料膜防水,还有一根抗拉扯的填充芯。
\n我选择使用红绿蓝三根线,黄线悬空。线色对应如下:
\n红 - 左声道;绿 - 右声道;蓝 - 接地。
\n
\n可以预先套上一段热缩管。
\n
\n取一枚 3.5mm 公头,旋下插头。
\n最长的一段一般是接地。若将接地朝下,我这款公头左侧为左声道,右侧为右声道。具体的接线方式可以用万用表测量接头和接口得知。
\n将线穿入孔中,上一坨焊锡即可。
\n
\n再使用万用表测量接头与线末端的连通性,注意不能与其他线短路了。
\n确认无误后可以打上热熔胶固定。
\n
\n再打点热熔胶,旋回外壳,将热缩管套好,加热热缩管使其收缩。
\n3.5mm 线缆就制作完成了。
\n驱动板焊接 驱动板上有三组线需要焊接:
\n\n音频输入线(3.5mm 线缆) \n电源输入线(Type C 线) \n音频输出线(喇叭线) \n \nType C 线我没有再用屏蔽线,用两根导线连接 Type C 母头和驱动板即可。
\n焊接方法就不多说了,线穿过孔,上锡即可。
\n
\n全部线缆焊接完成如下:
\n
\n热熔胶填充 完成接线后,确认无短路,即可连接电脑测试音箱。
\n若没有问题,考虑到需要长期使用,就可以用热熔胶覆盖焊接处,防止焊点脱落。
\n用热熔胶覆盖之后的驱动板:
\n
\n嘛…手艺不是很行。
\n \n就此,外接音箱组装完成啦!
\n",
+ "tags": [
+ "教程",
+ "DIY"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/2e528779.html",
+ "url": "https://blog.udon.eu.org/archives/2e528779.html",
+ "title": "迁移 Hexo 渲染环境至 GitHub Actions",
+ "date_published": "2022-05-23T11:30:00.000Z",
+ "content_html": "本博客使用的是 Hexo 静态博客框架,我将渲染环境搭建在家中 NAS 之上,部署了一个 Ubuntu Docker 并安装好了 Node.js 环境,正常使用了一两年。
\n上周末,写完新文章的我心血来潮,打算在更新博客的时候顺便更新一下老旧的 Node 和 Hexo(Node 12, Hexo 5.2)。
\n一番折腾之后,以 npm 和 hexo 环境全部被破坏(事后想想也许只是环境变量掉了)的下场结束。
\n鉴于 NAS 性能低下,更新一次 node module 都需要 30 分钟,我放弃了在 NAS 上重新部署渲染环境的念头,转而使用 GitHub Actions 渲染,并同时部署于 GitHub Pages 与 CloudFlare Pages。
\n将渲染环境迁至 GitHub Actions 不久之前,CloudFlare Pages 悄悄下架了 Hexo 框架的部署功能,只能用 GitHub Actions 渲染,然后再部署至 CloudFlare Pages 了。
\n项目结构的修改 若想使用 GitHub Actions,需要将博客的源码上传至 GitHub。考虑到隐私和安全的问题,建议创建一个私有仓库管理源码。
\n对于项目没有什么需要修改的,因为 Actions 渲染的流程和本地渲染的流程没有区别。
\n唯一需要改动的,是引入的主题。由于两个 Git 仓库不能嵌套,我们需要以 Git submodule 的形式引入主题仓库。
\n我使用的是 Fluid 主题。采用 覆盖配置 的方法,即在根目录之下有一份配置会覆盖主题内的配置文件,便于在 Actions 中渲染。
\n以下也将以 Fluid 为例,请根据你使用的主题修改命令或代码。
\n首先,删除原来的主题(若使用的是主题内的配置,注意备份配置文件!)
\n返回博客源码的根目录,执行:
\n1 git submodule add https://github.com/fluid-dev/hexo-theme-fluid.git themes/fluid
\n\n末尾的 themes/fluid
为此 submodule 在项目中的位置与名字,与先前本地渲染时的配置相同即可。
\n删除子模块的过程较为繁琐,请参考网上的文章进行操作。
\n在 Clone 此项目时,submodule 默认不会被下载,需要使用指令:
\n1 git submodule update --init --recursive
\n\n下载 submodule。在下面会提到的 Actions 配置文件中会出现这条指令。
\n接着,就可以将博客源码上传至 GitHub。
\nGitHub Actions 相关文件 在博客源码根目录创建 .github/workflows/submit.yml
和 .github/script/blog-update.sh
两个文件,填入下列代码。
\n以下代码参考文章 GitHub Actions 自动部署 Hexo 博客到 cPanel 虚拟主机 - 谷中望月 ,有所修改。
\nsubmit.yml
:
\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 name: CI on: push: branches: - main release: types: [published ]env: GIT_USER: Lao-Liu233 GIT_EMAIL: blog@udon.eu.org jobs: build: name: Build on node ${{ matrix.node_version }} and ${{ matrix.os }} runs-on: ubuntu-latest strategy: matrix: os: [ubuntu-latest ] node_version: [16.15 ] steps: - name: Checkout uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node_version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node_version }} - name: Install hexo run: | npm install -g hexo-cli - name: Install dependencies run: | npm install - name: Clone submodule run: | git submodule update --init --recursive - name: Configuration environment run: | sudo timedatectl set-timezone "Asia/Shanghai" mkdir -p ~/.ssh/ echo "$HEXO_DEPLOY_PRI" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan github.com >> ~/.ssh/known_hosts git config --global user.name $GIT_USER git config --global user.email $GIT_EMAIL - name: Deploy hexo run: | hexo clean hexo g -d - name: Update Blog run: | sh "${GITHUB_WORKSPACE}/.github/script/blog-update.sh"
\n\n.github/script/blog-update.sh
:
\n1 2 3 4 5 6 7 8 9 #!/bin/sh if [ -z "$(git status --porcelain) " ]; then echo "nothing to update." else git add source /_posts/\t git commit -m "triggle by commit ${GITHUB_SHA} " -a git push origin mainfi
\n\nCommit + Push,打开 Actions 界面,就能看到正在运行的 Action 啦。
\n不出意外,Action 成功执行,1分钟内博客就能渲染成功、部署至 GitHub Pages。
\n同时部署至 CloudFlare Pages 步骤较为简单,我简述一下。
\n打开 CloudFlare Pages, 连接至存放 渲染后 的静态文件的仓库,渲染的框架选择 None ,执行的指令填写 exit 0;
就可以了。
\n执行部署后,渲染后的静态文件就被部署至 CloudFlare Pages 啦。
\n",
+ "tags": [
+ "教程",
+ "GitHub Actions",
+ "Hexo"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/9d319c54.html",
+ "url": "https://blog.udon.eu.org/archives/9d319c54.html",
+ "title": "DELL 灵越 15 5547 拆解与更换硅脂",
+ "date_published": "2022-05-22T02:30:00.000Z",
+ "content_html": "DELL 灵越 15 5547,Intel i5-6200U,Nvidia Geforce 630M,8G DDR3-1600 + 256G SATA SSD(后期改装),是我的第一台笔记本。这台笔记本的拆机过程比较繁琐,是我目前遇见的最难拆解的电脑,故作此文章,分享一下如何拆解一台笔记本,并为其更换硅脂。
\n
\n概述 本次拆机共耗时 1H 30Min,是拆解正常机器所花费时间的两到三倍。
\n拆解步骤包括:卸下后盖、拆除电池、拆除硬盘、拆除风扇、拆除键盘、卸下基座、卸下散热器、更换硅脂,然后是逆序执行上述拆解步骤,组装电脑。
\n拆解准备 拆机少不了一套好工具。
\n为了无伤拆机,需要准备一套好用的螺丝刀防止螺丝滑牙;还需要一套塑料拆机工具,用于撬开后盖。
\n笔记本的硅脂,我选择了霍尼韦尔 7950 相变硅脂,不容易挥发,不需要经常更换。
\n开始拆机 怎么拆呢 其实几个月前我就有给这台设备清灰、换硅脂的念头,但拆开机器之后我发现主板被塑料框架罩住了,无法拆下散热器。我研究了半天也没找到拆下框架的方法,便只清理了风扇的灰尘,没有更换硅脂。
\n而这次,我有备而来:我查到了 DELL 官方的用户手册 ,其中详细记载了拆解这台机器的方法。接下来的拆解步骤就严格按照官方的教程啦。
\n第一步:卸下后盖 翻到 D 面,拧下两颗固定螺丝,就能将后盖拆下,可以触及无线网卡、内存条、2.5 寸硬盘、电池和散热风扇,日常需要维护的部件都能轻松触及,好评。散热器和主板则在塑料框架之下。
\n
\n第二步:拆除电池 释放主板电荷是电脑拆机中至关重要的一步!
\n在主板带电的情况下拔插任何端子都是不明智的做法,很可能会将主板上的高电压线路误接入低电压线路(例如拔插屏线时没有正对接口),烧坏一片元件。
\n这台机子的电池没有排线,直接接入主板。卸下围绕电池的四颗螺丝,手提塑料片,就可将电池卸下。
\n翻到 C 面,长按电源键 10s,可重复两到三次,确保主板中的电荷完全释放。
\n
\n第一次见这种电池模块,比起长条状的电池可以更好地利用机身空间。
\n第三到五步:拆除硬盘、散热风扇和键盘 卸下固定硬盘座的螺丝,抽出硬盘,再断开 SATA 与供电二合一的线缆,即可取下硬盘。
\n拔下主板上散热风扇的端子(位于风扇左侧),卸下固定螺丝,即可取下散热风扇。
\n翻到 C 面,用手或塑料工具扣出键盘模块,注意不要大力提起键盘!小心地提起一段距离,断开机身上的排线,键盘就取下来了。裸 C 面上还有两条排线,都需要断开。竖直方向排线需从孔洞中穿回 D 面。
\n
\n第六步:卸下基座 这是我第一次见笔记本中的基座,没有手册的指导很难拆下。
\n首先,确保 C 面两根排线已断开。
\n翻到 D 面,小心地断开屏幕排线(位于散热风扇左侧)。
\n再翻到 C 面,按手册图示卸下所有螺丝。
\n翻回 D 面,同样地卸下一堆螺丝,注意这两面的螺丝都是 M2.5x7 规格,长于主板用螺丝,混用可能会戳穿主板。
\nDELL 在机身和框架上都有标注螺丝孔对应螺丝的规格,非常好评!
\n确认卸下所有螺丝后,用塑料工具插入基座与机身的缝隙,划开卡口。
\n待大部分卡口都解开时,小心提起基座,注意将基座上的线缆和排线取下,基座就彻底与机身分离了。
\n
\n基座的背面,可以看到是 PC + ABS 材料,分量很足。
\n
\n拆下基座之后的机身,暴露出了主板和子板。散热器是梦幻单热管,不过压这两个破芯片足够了。
\n第七步:卸下散热器 散热器共有六颗螺丝,四颗在 CPU 上,两颗在显卡上。
\n为了使散热器均匀受力,不能一次性直接拧下一颗螺丝,而是平均为每颗螺丝卸力,可以每个螺丝一次转两圈,直到所有螺丝都被卸下。裸晶脆弱,有必要好好保护一下。
\n
\n不难看出,CPU 上的硅脂全部凝固,且裸晶上的硅脂基本流失殆尽(散热器上也没剩多少)。
\n去除大部分凝固的硅脂,再用酒精片擦拭、用布擦干。
\n更换硅脂的步骤我就不在此赘述,商家一般会提供详细的视频介绍,按着教程做就可以了。
\n
\ni5-6200U 的全貌,右侧长条状的裸晶应该是集成显卡吧。
\n
\nGeforce 930M 显卡,非常小个。
\n
\nDDR3 显存,封装方式和 DDR4 不一样,更扁一些。
\n后续步骤 接下来就是逆序刚刚的拆解步骤,我就不再赘述。
\n注意几点:
\n\n安装散热器时也需保证受力均匀,且不能过分用力,可能会压碎裸晶。 \n安装基座时注意将线缆与排线置于合适的位置,不要被基座压到了。 \n \n拼装完成后,插电,开机,轻松点亮。
\n总结 拆解一台电脑并没有想象中的那么困难,拆解不同电脑的方式也都大同小异,上述的步骤一般都能适用。
\n我已拆解了不下 10 台/次 笔记本,目前还未翻过车。
\n下次清灰、更换硅脂,试着自己动手吧!
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/28fa0729.html",
+ "url": "https://blog.udon.eu.org/archives/28fa0729.html",
+ "title": "Virmach Japan",
+ "date_published": "2022-04-06T14:15:00.000Z",
+ "content_html": "老朋友 Virmach 最近搞了场日本的预售。上游是 XTom,线路走的 IIJ。配置从 1C 384M 到 2C 2.5G 都有,价格则是 11.11刀/年 起(预售打 8 折, 8.89刀/年起)。
\n我买的这台是折后 9.7刀/年,1C 768M 20G NVME 2T 双向流量的配置。
\n \n\n \n直观感受:性能强劲,白天网络不错。晚高峰也能用,不爆炸。
\n下面放测试数据:
\n综合测试
\nCPU 很幸运地抽中了 5900X,可以看到硬盘速度非常 OK。
\n早上的网络情况很不错,没指望能跑满 G 口,毕竟这么多人分 10G 的口子。
\n性能测试
\n不愧是 5900X,单核跑出了部分志强将近3倍的成绩!AMD, YES!
\n开一台 2C 2G 的机子完全可以当作开发服务器使了。
\n国内网络测试 白天
\n三网表现均不错呢。
\n晚高峰
\n惊了,晚高峰表现很不错耶。
\n流媒体解锁
\nVirmach 只字没提流媒体,就别报多大希望。
\n不过作为日本的 VPS 自家游戏都不能解锁……这 IP 优化的不大行。
\n总结 网络不错,性能强劲,如此便宜的价格可以说性价超高。
\n",
+ "tags": [
+ "小鸡测评"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/dbf21067.html",
+ "url": "https://blog.udon.eu.org/archives/dbf21067.html",
+ "title": "玩一玩 DN42",
+ "date_published": "2022-04-01T04:30:00.000Z",
+ "content_html": "两个月前,我向 DN42 提交了申请,并于4个小时后通过了审核,获得了自己的 AS 和 IP。
\n作此文分享一下把玩 DN42 的心得,也作为我的备忘录。
\n我的信息 1 2 3 AS4242423490 IPv4: 172.23 .13 .64 /28 IPv6: fd44:6b93 :4eaa::/48
\n\n目前仅一个洛杉矶节点开放 Peer,后期还将添加韩国和日本的节点。
\n如何把玩 注册 有关注册的文章很多,推荐这两篇:
\nDN42 实验网络介绍及注册教程(2022-02 更新) | Lan Tian @ Blog
\n初探 DN42 网络 - 宝硕博客 (baoshuo.ren)
\n需要使用 Git 和 PGP,在 DN42 的 Git 仓库提交你的个人信息即可完成注册。
\n搭建内网 在和其他 AS 建立对等连接之前,我们先要把内网整理好:
\n各台服务器的地理位置和网络位置各不相同,需要使用 VPN 创建虚拟局域网。
\n课堂上讲了两种内网路由协议:
\n\n有一位老朋友可以轻松解决以上两个问题:Zerotier 。
\nZerotier 的虚拟网络可以使用自己的 IP,只需在 Managed Routes 中设置你在 DN42 申请的 IPv4 和 IPv6,即可为每台加入虚拟网络的主机自动或手动配置 DN42 IP。
\n在机器之间使用 DN42 IP 互 ping 测试连通性。
\n准备 BGP 相关软件 搭建好内网之后,就可以开始配置 BGP 发言人啦。
\n选择一台或多台服务器,作为自治域向外宣告路由的发言人。
\n在每台服务器上都需要配置 BGP 相关的软件,以及和其他 BGP 发言人建立连接(一般是 VPN 连接)的软件。
\n目前在 DN42 网络用的比较多的 VPN 软件是 Wireguard,BGP 软件则可以从 bird 2、bird 1、quagga 等软件中选择。
\n我使用的是 bird 2。
\n安装与配置 BIRD 2 安装命令:
\n1 2 apt update apt install bird2 -y
\n\nbird 2 的配置文件位于 /etc/bird
,名为 bird.conf
。
\n配置文件可以参考(照抄)DN42 官方给出的配置:howto/Bird2 (dn42.dev)
\n喂到嘴边的配置方法:
\n\n将官方配置填入 /etc/bird/bird.conf
\n在 /etc/bird
目录下新建名为 peers
的文件夹 \n下载 ROA 配置(命令来自宝硕的博客 ) \n \n1 2 wget -4 -O /tmp/dn42_roa.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf && mv -f /tmp/dn42_roa.conf /etc/bird/dn42_roa.conf wget -4 -O /tmp/dn42_roa_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf && mv -f /tmp/dn42_roa_v6.conf /etc/bird/dn42_roa_v6.conf
\n\n\t并配置 crontab,每小时自动下载并重载新配置:
\n1 2 3 0 */1 * * * wget -4 -O /tmp/dn42_roa.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf && mv -f /tmp/dn42_roa.conf /etc/bird/dn42_roa.conf 0 */1 * * * wget -4 -O /tmp/dn42_roa_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf && mv -f /tmp/dn42_roa_v6.conf /etc/bird/dn42_roa_v6.conf 0 */1 * * * birdc configure
\n\n安装并配置 Wireguard 安装命令:
\n1 2 apt update apt install wireguard -y
\n\n这样就安装了 Wireguard
和名为 wg-quick
的管理工具。
\n使用命令:
\n1 wg genkey | tee privatekey | wg pubkey > publickey
\n\n在当前目录下创建 Wireguard 建立连接所用的密钥对(公钥和私钥)。
\n就此 Wireguard 安装完成。
\n配置系统内核 打开内核的数据包转发功能:
\n1 2 3 4 echo "net.ipv4.ip_forward=1" >> /etc/sysctl.confecho "net.ipv6.conf.default.forwarding=1" >> /etc/sysctl.confecho "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf sysctl -p
\n\n关闭内核 rp_filter
的严格模式:
\n1 2 3 echo "net.ipv4.conf.default.rp_filter=0" >> /etc/sysctl.confecho "net.ipv4.conf.all.rp_filter=0" >> /etc/sysctl.conf sysctl -p
\n\n如果有 ufw 等防火墙自动配置工具,务必关闭。
\np.s. 我拿到任何机器后会立刻执行的指令是:
\n \n\n创建 Dummy 网卡 dummy 网卡具体的作用我不是很清楚…
\n只知道如果要用链路本地地址进行通讯,要把 DN42 的 IP 地址绑定到 dummy 网卡上。
\ndummy 网卡配置指令如下:([ ] 中为需要你填写的内容)
\n1 2 3 4 5 ip link del dummy ip link add dummy type dummy ip addr add [你的 DN42 IPv4 地址]/32 dev dummy ip addr add [你的 DN42 IPv6 地址]/128 dev dummy ip link set dummy up
\n\n和小伙伴建立对等连接(peer) 需要和对方分享的 \n你的 DN42 信息,包括 AS 号和发言人的 DN42 IPv4(IPv6)地址; \n若使用链路本地地址,还需提供这东西,一般为一个本地 IPv6 地址,常取 fe80::[你的 AS 号后4位]
; \n发言人的外网 IPv4 地址(或域名)和 Wireguard 隧道的通讯端口; \nWireguard 公钥。 \n \n有一些信息会在以下的配置中获得。
\nWireguard 相关的 在 /etc/wireguard
目录下创建 Wireguard 配置文件,每一个配置文件对应着一个 Wireguard 隧道。
\n例如你要和 AS114514 臭 建立对等连接,可以在 peers
文件夹下新建一个名为 wg_114514.conf
(文件名即为 wireguard 隧道名)的配置文件。
\n配置的模板如下:([ ] 中为需要你填写的内容)
\n1 2 3 4 5 6 7 8 9 10 11 [Interface] Table = off ListenPort = [我们的监听端口,可以用对方 AS 号的后五位]PrivateKey = [刚刚生成的 Wireguard 私钥]PostUp = ip addr add [本机的 DN42 IPv4 地址]/32 peer [对方机器的 DN42 IPv4 地址]/32 dev %iPostUp = ip addr add [本机的链路本地地址(见 BIRD 相关配置)]/64 dev %i[Peer] PublicKey = [对方的 Wireguard 公钥]AllowedIPs = 10.0 .0.0 /8 , 172.20 .0.0 /14 , 172.31 .0.0 /16 , fd00::/8 , fe80::/64 Endpoint = [对方机器的公网 IP 地址或域名 : 端口号]
\n\n然后使用 wg-quick up [wireguard 隧道名(刚刚的配置文件名)]
启动 Wireguard 隧道。
\n可以 ping 一下对方的 DN42 IP 看看 Wireguard 隧道是否连接成功。
\n使用 wg
命令查看各隧道的连接情况。若有显示 last handshake
,一般情况下隧道就已成功建立。
\nBIRD 相关的 在先前导入的 bird 2 配置中定义了一个 peers
文件夹,就是用来存放 peer 相关的配置。
\n例如你要和 AS114514 又臭 建立对等连接,可以在 peers
文件夹下新建一个名为 114514.conf
(文件名可自定义)的配置文件。
\n我采用的是链路本地地址(Link-Local) 的配置方法。配置的模板如下:([ ] 中为需要你填写的内容)
\n1 2 3 protocol bgp from dnpeers { neighbor % '' as ; }
\n\n添加完配置之后别忘了用 birdc configure
重载 bird 2 配置。
\n使用命令 birdc s p
可以查看 BIRD 2 软件下所有协议的通信情况。
\n若显示为:
\n1 dn42_xxxx BGP --- up 20 :36 :30 .984 Established
\n\n则表示 BGP 连接已建立。
\n尾声 我还在写 DN42 相关的站点,在上面分享节点信息,方便大家 peer。
\n但目前进度缓慢(悲)。
\n",
+ "tags": [
+ "教程"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/gitment.browser.js b/gitment.browser.js
new file mode 100644
index 00000000..168d0f2c
--- /dev/null
+++ b/gitment.browser.js
@@ -0,0 +1,3758 @@
+var Gitment =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ i: moduleId,
+/******/ l: false,
+/******/ exports: {}
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.l = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // identity function for calling harmony imports with the correct context
+/******/ __webpack_require__.i = function(value) { return value; };
+/******/
+/******/ // define getter function for harmony exports
+/******/ __webpack_require__.d = function(exports, name, getter) {
+/******/ if(!__webpack_require__.o(exports, name)) {
+/******/ Object.defineProperty(exports, name, {
+/******/ configurable: false,
+/******/ enumerable: true,
+/******/ get: getter
+/******/ });
+/******/ }
+/******/ };
+/******/
+/******/ // getDefaultExport function for compatibility with non-harmony modules
+/******/ __webpack_require__.n = function(module) {
+/******/ var getter = module && module.__esModule ?
+/******/ function getDefault() { return module['default']; } :
+/******/ function getModuleExports() { return module; };
+/******/ __webpack_require__.d(getter, 'a', getter);
+/******/ return getter;
+/******/ };
+/******/
+/******/ // Object.prototype.hasOwnProperty.call
+/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(__webpack_require__.s = 5);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var LS_ACCESS_TOKEN_KEY = exports.LS_ACCESS_TOKEN_KEY = 'gitment-comments-token';
+var LS_USER_KEY = exports.LS_USER_KEY = 'gitment-user-info';
+
+var NOT_INITIALIZED_ERROR = exports.NOT_INITIALIZED_ERROR = new Error('Comments Not Initialized');
+
+/***/ }),
+/* 1 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+/* WEBPACK VAR INJECTION */(function(global) {
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+var __extends = undefined && undefined.__extends || function () {
+ var extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) {
+ d.__proto__ = b;
+ } || function (d, b) {
+ for (var p in b) {
+ if (b.hasOwnProperty(p)) d[p] = b[p];
+ }
+ };
+ return function (d, b) {
+ extendStatics(d, b);
+ function __() {
+ this.constructor = d;
+ }
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+ };
+}();
+Object.defineProperty(exports, "__esModule", { value: true });
+registerGlobals();
+exports.extras = {
+ allowStateChanges: allowStateChanges,
+ deepEqual: deepEqual,
+ getAtom: getAtom,
+ getDebugName: getDebugName,
+ getDependencyTree: getDependencyTree,
+ getAdministration: getAdministration,
+ getGlobalState: getGlobalState,
+ getObserverTree: getObserverTree,
+ isComputingDerivation: isComputingDerivation,
+ isSpyEnabled: isSpyEnabled,
+ onReactionError: onReactionError,
+ resetGlobalState: resetGlobalState,
+ shareGlobalState: shareGlobalState,
+ spyReport: spyReport,
+ spyReportEnd: spyReportEnd,
+ spyReportStart: spyReportStart,
+ setReactionScheduler: setReactionScheduler
+};
+if ((typeof __MOBX_DEVTOOLS_GLOBAL_HOOK__ === "undefined" ? "undefined" : _typeof(__MOBX_DEVTOOLS_GLOBAL_HOOK__)) === "object") {
+ __MOBX_DEVTOOLS_GLOBAL_HOOK__.injectMobx(module.exports);
+}
+module.exports.default = module.exports;
+var actionFieldDecorator = createClassPropertyDecorator(function (target, key, value, args, originalDescriptor) {
+ var actionName = args && args.length === 1 ? args[0] : value.name || key || "";
+ var wrappedAction = action(actionName, value);
+ addHiddenProp(target, key, wrappedAction);
+}, function (key) {
+ return this[key];
+}, function () {
+ invariant(false, getMessage("m001"));
+}, false, true);
+var boundActionDecorator = createClassPropertyDecorator(function (target, key, value) {
+ defineBoundAction(target, key, value);
+}, function (key) {
+ return this[key];
+}, function () {
+ invariant(false, getMessage("m001"));
+}, false, false);
+var action = function action(arg1, arg2, arg3, arg4) {
+ if (arguments.length === 1 && typeof arg1 === "function") return createAction(arg1.name || "", arg1);
+ if (arguments.length === 2 && typeof arg2 === "function") return createAction(arg1, arg2);
+ if (arguments.length === 1 && typeof arg1 === "string") return namedActionDecorator(arg1);
+ return namedActionDecorator(arg2).apply(null, arguments);
+};
+exports.action = action;
+action.bound = function boundAction(arg1, arg2, arg3) {
+ if (typeof arg1 === "function") {
+ var action_1 = createAction("", arg1);
+ action_1.autoBind = true;
+ return action_1;
+ }
+ return boundActionDecorator.apply(null, arguments);
+};
+function namedActionDecorator(name) {
+ return function (target, prop, descriptor) {
+ if (descriptor && typeof descriptor.value === "function") {
+ descriptor.value = createAction(name, descriptor.value);
+ descriptor.enumerable = false;
+ descriptor.configurable = true;
+ return descriptor;
+ }
+ return actionFieldDecorator(name).apply(this, arguments);
+ };
+}
+function runInAction(arg1, arg2, arg3) {
+ var actionName = typeof arg1 === "string" ? arg1 : arg1.name || "";
+ var fn = typeof arg1 === "function" ? arg1 : arg2;
+ var scope = typeof arg1 === "function" ? arg2 : arg3;
+ invariant(typeof fn === "function", getMessage("m002"));
+ invariant(fn.length === 0, getMessage("m003"));
+ invariant(typeof actionName === "string" && actionName.length > 0, "actions should have valid names, got: '" + actionName + "'");
+ return executeAction(actionName, fn, scope, undefined);
+}
+exports.runInAction = runInAction;
+function isAction(thing) {
+ return typeof thing === "function" && thing.isMobxAction === true;
+}
+exports.isAction = isAction;
+function defineBoundAction(target, propertyName, fn) {
+ var res = function res() {
+ return executeAction(propertyName, fn, target, arguments);
+ };
+ res.isMobxAction = true;
+ addHiddenProp(target, propertyName, res);
+}
+function autorun(arg1, arg2, arg3) {
+ var name, view, scope;
+ if (typeof arg1 === "string") {
+ name = arg1;
+ view = arg2;
+ scope = arg3;
+ } else {
+ name = arg1.name || "Autorun@" + getNextId();
+ view = arg1;
+ scope = arg2;
+ }
+ invariant(typeof view === "function", getMessage("m004"));
+ invariant(isAction(view) === false, getMessage("m005"));
+ if (scope) view = view.bind(scope);
+ var reaction = new Reaction(name, function () {
+ this.track(reactionRunner);
+ });
+ function reactionRunner() {
+ view(reaction);
+ }
+ reaction.schedule();
+ return reaction.getDisposer();
+}
+exports.autorun = autorun;
+function when(arg1, arg2, arg3, arg4) {
+ var name, predicate, effect, scope;
+ if (typeof arg1 === "string") {
+ name = arg1;
+ predicate = arg2;
+ effect = arg3;
+ scope = arg4;
+ } else {
+ name = "When@" + getNextId();
+ predicate = arg1;
+ effect = arg2;
+ scope = arg3;
+ }
+ var disposer = autorun(name, function (r) {
+ if (predicate.call(scope)) {
+ r.dispose();
+ var prevUntracked = untrackedStart();
+ effect.call(scope);
+ untrackedEnd(prevUntracked);
+ }
+ });
+ return disposer;
+}
+exports.when = when;
+function autorunAsync(arg1, arg2, arg3, arg4) {
+ var name, func, delay, scope;
+ if (typeof arg1 === "string") {
+ name = arg1;
+ func = arg2;
+ delay = arg3;
+ scope = arg4;
+ } else {
+ name = arg1.name || "AutorunAsync@" + getNextId();
+ func = arg1;
+ delay = arg2;
+ scope = arg3;
+ }
+ invariant(isAction(func) === false, getMessage("m006"));
+ if (delay === void 0) delay = 1;
+ if (scope) func = func.bind(scope);
+ var isScheduled = false;
+ var r = new Reaction(name, function () {
+ if (!isScheduled) {
+ isScheduled = true;
+ setTimeout(function () {
+ isScheduled = false;
+ if (!r.isDisposed) r.track(reactionRunner);
+ }, delay);
+ }
+ });
+ function reactionRunner() {
+ func(r);
+ }
+ r.schedule();
+ return r.getDisposer();
+}
+exports.autorunAsync = autorunAsync;
+function reaction(expression, effect, arg3) {
+ if (arguments.length > 3) {
+ fail(getMessage("m007"));
+ }
+ if (isModifierDescriptor(expression)) {
+ fail(getMessage("m008"));
+ }
+ var opts;
+ if ((typeof arg3 === "undefined" ? "undefined" : _typeof(arg3)) === "object") {
+ opts = arg3;
+ } else {
+ opts = {};
+ }
+ opts.name = opts.name || expression.name || effect.name || "Reaction@" + getNextId();
+ opts.fireImmediately = arg3 === true || opts.fireImmediately === true;
+ opts.delay = opts.delay || 0;
+ opts.compareStructural = opts.compareStructural || opts.struct || false;
+ effect = action(opts.name, opts.context ? effect.bind(opts.context) : effect);
+ if (opts.context) {
+ expression = expression.bind(opts.context);
+ }
+ var firstTime = true;
+ var isScheduled = false;
+ var nextValue;
+ var r = new Reaction(opts.name, function () {
+ if (firstTime || opts.delay < 1) {
+ reactionRunner();
+ } else if (!isScheduled) {
+ isScheduled = true;
+ setTimeout(function () {
+ isScheduled = false;
+ reactionRunner();
+ }, opts.delay);
+ }
+ });
+ function reactionRunner() {
+ if (r.isDisposed) return;
+ var changed = false;
+ r.track(function () {
+ var v = expression(r);
+ changed = valueDidChange(opts.compareStructural, nextValue, v);
+ nextValue = v;
+ });
+ if (firstTime && opts.fireImmediately) effect(nextValue, r);
+ if (!firstTime && changed === true) effect(nextValue, r);
+ if (firstTime) firstTime = false;
+ }
+ r.schedule();
+ return r.getDisposer();
+}
+exports.reaction = reaction;
+function createComputedDecorator(compareStructural) {
+ return createClassPropertyDecorator(function (target, name, _, __, originalDescriptor) {
+ invariant(typeof originalDescriptor !== "undefined", getMessage("m009"));
+ invariant(typeof originalDescriptor.get === "function", getMessage("m010"));
+ var adm = asObservableObject(target, "");
+ defineComputedProperty(adm, name, originalDescriptor.get, originalDescriptor.set, compareStructural, false);
+ }, function (name) {
+ var observable = this.$mobx.values[name];
+ if (observable === undefined) return undefined;
+ return observable.get();
+ }, function (name, value) {
+ this.$mobx.values[name].set(value);
+ }, false, false);
+}
+var computedDecorator = createComputedDecorator(false);
+var computedStructDecorator = createComputedDecorator(true);
+var computed = function computed(arg1, arg2, arg3) {
+ if (typeof arg2 === "string") {
+ return computedDecorator.apply(null, arguments);
+ }
+ invariant(typeof arg1 === "function", getMessage("m011"));
+ invariant(arguments.length < 3, getMessage("m012"));
+ var opts = (typeof arg2 === "undefined" ? "undefined" : _typeof(arg2)) === "object" ? arg2 : {};
+ opts.setter = typeof arg2 === "function" ? arg2 : opts.setter;
+ return new ComputedValue(arg1, opts.context, opts.compareStructural || opts.struct || false, opts.name || arg1.name || "", opts.setter);
+};
+exports.computed = computed;
+computed.struct = computedStructDecorator;
+function createTransformer(transformer, onCleanup) {
+ invariant(typeof transformer === "function" && transformer.length < 2, "createTransformer expects a function that accepts one argument");
+ var objectCache = {};
+ var resetId = globalState.resetId;
+ var Transformer = function (_super) {
+ __extends(Transformer, _super);
+ function Transformer(sourceIdentifier, sourceObject) {
+ var _this = _super.call(this, function () {
+ return transformer(sourceObject);
+ }, undefined, false, "Transformer-" + transformer.name + "-" + sourceIdentifier, undefined) || this;
+ _this.sourceIdentifier = sourceIdentifier;
+ _this.sourceObject = sourceObject;
+ return _this;
+ }
+ Transformer.prototype.onBecomeUnobserved = function () {
+ var lastValue = this.value;
+ _super.prototype.onBecomeUnobserved.call(this);
+ delete objectCache[this.sourceIdentifier];
+ if (onCleanup) onCleanup(lastValue, this.sourceObject);
+ };
+ return Transformer;
+ }(ComputedValue);
+ return function (object) {
+ if (resetId !== globalState.resetId) {
+ objectCache = {};
+ resetId = globalState.resetId;
+ }
+ var identifier = getMemoizationId(object);
+ var reactiveTransformer = objectCache[identifier];
+ if (reactiveTransformer) return reactiveTransformer.get();
+ reactiveTransformer = objectCache[identifier] = new Transformer(identifier, object);
+ return reactiveTransformer.get();
+ };
+}
+exports.createTransformer = createTransformer;
+function getMemoizationId(object) {
+ if (object === null || (typeof object === "undefined" ? "undefined" : _typeof(object)) !== "object") throw new Error("[mobx] transform expected some kind of object, got: " + object);
+ var tid = object.$transformId;
+ if (tid === undefined) {
+ tid = getNextId();
+ addHiddenProp(object, "$transformId", tid);
+ }
+ return tid;
+}
+function expr(expr, scope) {
+ if (!isComputingDerivation()) console.warn(getMessage("m013"));
+ return computed(expr, { context: scope }).get();
+}
+exports.expr = expr;
+function extendObservable(target) {
+ var properties = [];
+ for (var _i = 1; _i < arguments.length; _i++) {
+ properties[_i - 1] = arguments[_i];
+ }
+ return extendObservableHelper(target, deepEnhancer, properties);
+}
+exports.extendObservable = extendObservable;
+function extendShallowObservable(target) {
+ var properties = [];
+ for (var _i = 1; _i < arguments.length; _i++) {
+ properties[_i - 1] = arguments[_i];
+ }
+ return extendObservableHelper(target, referenceEnhancer, properties);
+}
+exports.extendShallowObservable = extendShallowObservable;
+function extendObservableHelper(target, defaultEnhancer, properties) {
+ invariant(arguments.length >= 2, getMessage("m014"));
+ invariant((typeof target === "undefined" ? "undefined" : _typeof(target)) === "object", getMessage("m015"));
+ invariant(!isObservableMap(target), getMessage("m016"));
+ properties.forEach(function (propSet) {
+ invariant((typeof propSet === "undefined" ? "undefined" : _typeof(propSet)) === "object", getMessage("m017"));
+ invariant(!isObservable(propSet), getMessage("m018"));
+ });
+ var adm = asObservableObject(target);
+ var definedProps = {};
+ for (var i = properties.length - 1; i >= 0; i--) {
+ var propSet = properties[i];
+ for (var key in propSet) {
+ if (definedProps[key] !== true && hasOwnProperty(propSet, key)) {
+ definedProps[key] = true;
+ if (target === propSet && !isPropertyConfigurable(target, key)) continue;
+ var descriptor = Object.getOwnPropertyDescriptor(propSet, key);
+ defineObservablePropertyFromDescriptor(adm, key, descriptor, defaultEnhancer);
+ }
+ }
+ }
+ return target;
+}
+function getDependencyTree(thing, property) {
+ return nodeToDependencyTree(getAtom(thing, property));
+}
+function nodeToDependencyTree(node) {
+ var result = {
+ name: node.name
+ };
+ if (node.observing && node.observing.length > 0) result.dependencies = unique(node.observing).map(nodeToDependencyTree);
+ return result;
+}
+function getObserverTree(thing, property) {
+ return nodeToObserverTree(getAtom(thing, property));
+}
+function nodeToObserverTree(node) {
+ var result = {
+ name: node.name
+ };
+ if (hasObservers(node)) result.observers = getObservers(node).map(nodeToObserverTree);
+ return result;
+}
+function intercept(thing, propOrHandler, handler) {
+ if (typeof handler === "function") return interceptProperty(thing, propOrHandler, handler);else return interceptInterceptable(thing, propOrHandler);
+}
+exports.intercept = intercept;
+function interceptInterceptable(thing, handler) {
+ return getAdministration(thing).intercept(handler);
+}
+function interceptProperty(thing, property, handler) {
+ return getAdministration(thing, property).intercept(handler);
+}
+function isComputed(value, property) {
+ if (value === null || value === undefined) return false;
+ if (property !== undefined) {
+ if (isObservableObject(value) === false) return false;
+ var atom = getAtom(value, property);
+ return isComputedValue(atom);
+ }
+ return isComputedValue(value);
+}
+exports.isComputed = isComputed;
+function isObservable(value, property) {
+ if (value === null || value === undefined) return false;
+ if (property !== undefined) {
+ if (isObservableArray(value) || isObservableMap(value)) throw new Error(getMessage("m019"));else if (isObservableObject(value)) {
+ var o = value.$mobx;
+ return o.values && !!o.values[property];
+ }
+ return false;
+ }
+ return isObservableObject(value) || !!value.$mobx || isAtom(value) || isReaction(value) || isComputedValue(value);
+}
+exports.isObservable = isObservable;
+var deepDecorator = createDecoratorForEnhancer(deepEnhancer);
+var shallowDecorator = createDecoratorForEnhancer(shallowEnhancer);
+var refDecorator = createDecoratorForEnhancer(referenceEnhancer);
+var deepStructDecorator = createDecoratorForEnhancer(deepStructEnhancer);
+var refStructDecorator = createDecoratorForEnhancer(refStructEnhancer);
+function createObservable(v) {
+ if (v === void 0) {
+ v = undefined;
+ }
+ if (typeof arguments[1] === "string") return deepDecorator.apply(null, arguments);
+ invariant(arguments.length <= 1, getMessage("m021"));
+ invariant(!isModifierDescriptor(v), getMessage("m020"));
+ if (isObservable(v)) return v;
+ var res = deepEnhancer(v, undefined, undefined);
+ if (res !== v) return res;
+ return observable.box(v);
+}
+var IObservableFactories = function () {
+ function IObservableFactories() {}
+ IObservableFactories.prototype.box = function (value, name) {
+ if (arguments.length > 2) incorrectlyUsedAsDecorator("box");
+ return new ObservableValue(value, deepEnhancer, name);
+ };
+ IObservableFactories.prototype.shallowBox = function (value, name) {
+ if (arguments.length > 2) incorrectlyUsedAsDecorator("shallowBox");
+ return new ObservableValue(value, referenceEnhancer, name);
+ };
+ IObservableFactories.prototype.array = function (initialValues, name) {
+ if (arguments.length > 2) incorrectlyUsedAsDecorator("array");
+ return new ObservableArray(initialValues, deepEnhancer, name);
+ };
+ IObservableFactories.prototype.shallowArray = function (initialValues, name) {
+ if (arguments.length > 2) incorrectlyUsedAsDecorator("shallowArray");
+ return new ObservableArray(initialValues, referenceEnhancer, name);
+ };
+ IObservableFactories.prototype.map = function (initialValues, name) {
+ if (arguments.length > 2) incorrectlyUsedAsDecorator("map");
+ return new ObservableMap(initialValues, deepEnhancer, name);
+ };
+ IObservableFactories.prototype.shallowMap = function (initialValues, name) {
+ if (arguments.length > 2) incorrectlyUsedAsDecorator("shallowMap");
+ return new ObservableMap(initialValues, referenceEnhancer, name);
+ };
+ IObservableFactories.prototype.object = function (props, name) {
+ if (arguments.length > 2) incorrectlyUsedAsDecorator("object");
+ var res = {};
+ asObservableObject(res, name);
+ extendObservable(res, props);
+ return res;
+ };
+ IObservableFactories.prototype.shallowObject = function (props, name) {
+ if (arguments.length > 2) incorrectlyUsedAsDecorator("shallowObject");
+ var res = {};
+ asObservableObject(res, name);
+ extendShallowObservable(res, props);
+ return res;
+ };
+ IObservableFactories.prototype.ref = function () {
+ if (arguments.length < 2) {
+ return createModifierDescriptor(referenceEnhancer, arguments[0]);
+ } else {
+ return refDecorator.apply(null, arguments);
+ }
+ };
+ IObservableFactories.prototype.shallow = function () {
+ if (arguments.length < 2) {
+ return createModifierDescriptor(shallowEnhancer, arguments[0]);
+ } else {
+ return shallowDecorator.apply(null, arguments);
+ }
+ };
+ IObservableFactories.prototype.deep = function () {
+ if (arguments.length < 2) {
+ return createModifierDescriptor(deepEnhancer, arguments[0]);
+ } else {
+ return deepDecorator.apply(null, arguments);
+ }
+ };
+ IObservableFactories.prototype.struct = function () {
+ if (arguments.length < 2) {
+ return createModifierDescriptor(deepStructEnhancer, arguments[0]);
+ } else {
+ return deepStructDecorator.apply(null, arguments);
+ }
+ };
+ return IObservableFactories;
+}();
+exports.IObservableFactories = IObservableFactories;
+var observable = createObservable;
+exports.observable = observable;
+Object.keys(IObservableFactories.prototype).forEach(function (key) {
+ return observable[key] = IObservableFactories.prototype[key];
+});
+observable.deep.struct = observable.struct;
+observable.ref.struct = function () {
+ if (arguments.length < 2) {
+ return createModifierDescriptor(refStructEnhancer, arguments[0]);
+ } else {
+ return refStructDecorator.apply(null, arguments);
+ }
+};
+function incorrectlyUsedAsDecorator(methodName) {
+ fail("Expected one or two arguments to observable." + methodName + ". Did you accidentally try to use observable." + methodName + " as decorator?");
+}
+function createDecoratorForEnhancer(enhancer) {
+ invariant(!!enhancer, ":(");
+ return createClassPropertyDecorator(function (target, name, baseValue, _, baseDescriptor) {
+ assertPropertyConfigurable(target, name);
+ invariant(!baseDescriptor || !baseDescriptor.get, getMessage("m022"));
+ var adm = asObservableObject(target, undefined);
+ defineObservableProperty(adm, name, baseValue, enhancer);
+ }, function (name) {
+ var observable = this.$mobx.values[name];
+ if (observable === undefined) return undefined;
+ return observable.get();
+ }, function (name, value) {
+ setPropertyValue(this, name, value);
+ }, true, false);
+}
+function observe(thing, propOrCb, cbOrFire, fireImmediately) {
+ if (typeof cbOrFire === "function") return observeObservableProperty(thing, propOrCb, cbOrFire, fireImmediately);else return observeObservable(thing, propOrCb, cbOrFire);
+}
+exports.observe = observe;
+function observeObservable(thing, listener, fireImmediately) {
+ return getAdministration(thing).observe(listener, fireImmediately);
+}
+function observeObservableProperty(thing, property, listener, fireImmediately) {
+ return getAdministration(thing, property).observe(listener, fireImmediately);
+}
+function toJS(source, detectCycles, __alreadySeen) {
+ if (detectCycles === void 0) {
+ detectCycles = true;
+ }
+ if (__alreadySeen === void 0) {
+ __alreadySeen = [];
+ }
+ function cache(value) {
+ if (detectCycles) __alreadySeen.push([source, value]);
+ return value;
+ }
+ if (isObservable(source)) {
+ if (detectCycles && __alreadySeen === null) __alreadySeen = [];
+ if (detectCycles && source !== null && (typeof source === "undefined" ? "undefined" : _typeof(source)) === "object") {
+ for (var i = 0, l = __alreadySeen.length; i < l; i++) {
+ if (__alreadySeen[i][0] === source) return __alreadySeen[i][1];
+ }
+ }
+ if (isObservableArray(source)) {
+ var res = cache([]);
+ var toAdd = source.map(function (value) {
+ return toJS(value, detectCycles, __alreadySeen);
+ });
+ res.length = toAdd.length;
+ for (var i = 0, l = toAdd.length; i < l; i++) {
+ res[i] = toAdd[i];
+ }return res;
+ }
+ if (isObservableObject(source)) {
+ var res = cache({});
+ for (var key in source) {
+ res[key] = toJS(source[key], detectCycles, __alreadySeen);
+ }return res;
+ }
+ if (isObservableMap(source)) {
+ var res_1 = cache({});
+ source.forEach(function (value, key) {
+ return res_1[key] = toJS(value, detectCycles, __alreadySeen);
+ });
+ return res_1;
+ }
+ if (isObservableValue(source)) return toJS(source.get(), detectCycles, __alreadySeen);
+ }
+ return source;
+}
+exports.toJS = toJS;
+function transaction(action, thisArg) {
+ if (thisArg === void 0) {
+ thisArg = undefined;
+ }
+ deprecated(getMessage("m023"));
+ return runInTransaction.apply(undefined, arguments);
+}
+exports.transaction = transaction;
+function runInTransaction(action, thisArg) {
+ if (thisArg === void 0) {
+ thisArg = undefined;
+ }
+ return executeAction("", action);
+}
+function log(msg) {
+ console.log(msg);
+ return msg;
+}
+function whyRun(thing, prop) {
+ switch (arguments.length) {
+ case 0:
+ thing = globalState.trackingDerivation;
+ if (!thing) return log(getMessage("m024"));
+ break;
+ case 2:
+ thing = getAtom(thing, prop);
+ break;
+ }
+ thing = getAtom(thing);
+ if (isComputedValue(thing)) return log(thing.whyRun());else if (isReaction(thing)) return log(thing.whyRun());
+ return fail(getMessage("m025"));
+}
+exports.whyRun = whyRun;
+function createAction(actionName, fn) {
+ invariant(typeof fn === "function", getMessage("m026"));
+ invariant(typeof actionName === "string" && actionName.length > 0, "actions should have valid names, got: '" + actionName + "'");
+ var res = function res() {
+ return executeAction(actionName, fn, this, arguments);
+ };
+ res.originalFn = fn;
+ res.isMobxAction = true;
+ return res;
+}
+function executeAction(actionName, fn, scope, args) {
+ var runInfo = startAction(actionName, fn, scope, args);
+ try {
+ return fn.apply(scope, args);
+ } finally {
+ endAction(runInfo);
+ }
+}
+function startAction(actionName, fn, scope, args) {
+ var notifySpy = isSpyEnabled() && !!actionName;
+ var startTime = 0;
+ if (notifySpy) {
+ startTime = Date.now();
+ var l = args && args.length || 0;
+ var flattendArgs = new Array(l);
+ if (l > 0) for (var i = 0; i < l; i++) {
+ flattendArgs[i] = args[i];
+ }spyReportStart({
+ type: "action",
+ name: actionName,
+ fn: fn,
+ object: scope,
+ arguments: flattendArgs
+ });
+ }
+ var prevDerivation = untrackedStart();
+ startBatch();
+ var prevAllowStateChanges = allowStateChangesStart(true);
+ return {
+ prevDerivation: prevDerivation,
+ prevAllowStateChanges: prevAllowStateChanges,
+ notifySpy: notifySpy,
+ startTime: startTime
+ };
+}
+function endAction(runInfo) {
+ allowStateChangesEnd(runInfo.prevAllowStateChanges);
+ endBatch();
+ untrackedEnd(runInfo.prevDerivation);
+ if (runInfo.notifySpy) spyReportEnd({ time: Date.now() - runInfo.startTime });
+}
+function useStrict(strict) {
+ invariant(globalState.trackingDerivation === null, getMessage("m028"));
+ globalState.strictMode = strict;
+ globalState.allowStateChanges = !strict;
+}
+exports.useStrict = useStrict;
+function isStrictModeEnabled() {
+ return globalState.strictMode;
+}
+exports.isStrictModeEnabled = isStrictModeEnabled;
+function allowStateChanges(allowStateChanges, func) {
+ var prev = allowStateChangesStart(allowStateChanges);
+ var res;
+ try {
+ res = func();
+ } finally {
+ allowStateChangesEnd(prev);
+ }
+ return res;
+}
+function allowStateChangesStart(allowStateChanges) {
+ var prev = globalState.allowStateChanges;
+ globalState.allowStateChanges = allowStateChanges;
+ return prev;
+}
+function allowStateChangesEnd(prev) {
+ globalState.allowStateChanges = prev;
+}
+var BaseAtom = function () {
+ function BaseAtom(name) {
+ if (name === void 0) {
+ name = "Atom@" + getNextId();
+ }
+ this.name = name;
+ this.isPendingUnobservation = true;
+ this.observers = [];
+ this.observersIndexes = {};
+ this.diffValue = 0;
+ this.lastAccessedBy = 0;
+ this.lowestObserverState = IDerivationState.NOT_TRACKING;
+ }
+ BaseAtom.prototype.onBecomeUnobserved = function () {};
+ BaseAtom.prototype.reportObserved = function () {
+ reportObserved(this);
+ };
+ BaseAtom.prototype.reportChanged = function () {
+ startBatch();
+ propagateChanged(this);
+ endBatch();
+ };
+ BaseAtom.prototype.toString = function () {
+ return this.name;
+ };
+ return BaseAtom;
+}();
+exports.BaseAtom = BaseAtom;
+var Atom = function (_super) {
+ __extends(Atom, _super);
+ function Atom(name, onBecomeObservedHandler, onBecomeUnobservedHandler) {
+ if (name === void 0) {
+ name = "Atom@" + getNextId();
+ }
+ if (onBecomeObservedHandler === void 0) {
+ onBecomeObservedHandler = noop;
+ }
+ if (onBecomeUnobservedHandler === void 0) {
+ onBecomeUnobservedHandler = noop;
+ }
+ var _this = _super.call(this, name) || this;
+ _this.name = name;
+ _this.onBecomeObservedHandler = onBecomeObservedHandler;
+ _this.onBecomeUnobservedHandler = onBecomeUnobservedHandler;
+ _this.isPendingUnobservation = false;
+ _this.isBeingTracked = false;
+ return _this;
+ }
+ Atom.prototype.reportObserved = function () {
+ startBatch();
+ _super.prototype.reportObserved.call(this);
+ if (!this.isBeingTracked) {
+ this.isBeingTracked = true;
+ this.onBecomeObservedHandler();
+ }
+ endBatch();
+ return !!globalState.trackingDerivation;
+ };
+ Atom.prototype.onBecomeUnobserved = function () {
+ this.isBeingTracked = false;
+ this.onBecomeUnobservedHandler();
+ };
+ return Atom;
+}(BaseAtom);
+exports.Atom = Atom;
+var isAtom = createInstanceofPredicate("Atom", BaseAtom);
+var ComputedValue = function () {
+ function ComputedValue(derivation, scope, compareStructural, name, setter) {
+ this.derivation = derivation;
+ this.scope = scope;
+ this.compareStructural = compareStructural;
+ this.dependenciesState = IDerivationState.NOT_TRACKING;
+ this.observing = [];
+ this.newObserving = null;
+ this.isPendingUnobservation = false;
+ this.observers = [];
+ this.observersIndexes = {};
+ this.diffValue = 0;
+ this.runId = 0;
+ this.lastAccessedBy = 0;
+ this.lowestObserverState = IDerivationState.UP_TO_DATE;
+ this.unboundDepsCount = 0;
+ this.__mapid = "#" + getNextId();
+ this.value = undefined;
+ this.isComputing = false;
+ this.isRunningSetter = false;
+ this.name = name || "ComputedValue@" + getNextId();
+ if (setter) this.setter = createAction(name + "-setter", setter);
+ }
+ ComputedValue.prototype.onBecomeStale = function () {
+ propagateMaybeChanged(this);
+ };
+ ComputedValue.prototype.onBecomeUnobserved = function () {
+ invariant(this.dependenciesState !== IDerivationState.NOT_TRACKING, getMessage("m029"));
+ clearObserving(this);
+ this.value = undefined;
+ };
+ ComputedValue.prototype.get = function () {
+ invariant(!this.isComputing, "Cycle detected in computation " + this.name, this.derivation);
+ if (globalState.inBatch === 0) {
+ startBatch();
+ if (shouldCompute(this)) this.value = this.computeValue(false);
+ endBatch();
+ } else {
+ reportObserved(this);
+ if (shouldCompute(this)) if (this.trackAndCompute()) propagateChangeConfirmed(this);
+ }
+ var result = this.value;
+ if (isCaughtException(result)) throw result.cause;
+ return result;
+ };
+ ComputedValue.prototype.peek = function () {
+ var res = this.computeValue(false);
+ if (isCaughtException(res)) throw res.cause;
+ return res;
+ };
+ ComputedValue.prototype.set = function (value) {
+ if (this.setter) {
+ invariant(!this.isRunningSetter, "The setter of computed value '" + this.name + "' is trying to update itself. Did you intend to update an _observable_ value, instead of the computed property?");
+ this.isRunningSetter = true;
+ try {
+ this.setter.call(this.scope, value);
+ } finally {
+ this.isRunningSetter = false;
+ }
+ } else invariant(false, "[ComputedValue '" + this.name + "'] It is not possible to assign a new value to a computed value.");
+ };
+ ComputedValue.prototype.trackAndCompute = function () {
+ if (isSpyEnabled()) {
+ spyReport({
+ object: this.scope,
+ type: "compute",
+ fn: this.derivation
+ });
+ }
+ var oldValue = this.value;
+ var newValue = this.value = this.computeValue(true);
+ return isCaughtException(newValue) || valueDidChange(this.compareStructural, newValue, oldValue);
+ };
+ ComputedValue.prototype.computeValue = function (track) {
+ this.isComputing = true;
+ globalState.computationDepth++;
+ var res;
+ if (track) {
+ res = trackDerivedFunction(this, this.derivation, this.scope);
+ } else {
+ try {
+ res = this.derivation.call(this.scope);
+ } catch (e) {
+ res = new CaughtException(e);
+ }
+ }
+ globalState.computationDepth--;
+ this.isComputing = false;
+ return res;
+ };
+ ;
+ ComputedValue.prototype.observe = function (listener, fireImmediately) {
+ var _this = this;
+ var firstTime = true;
+ var prevValue = undefined;
+ return autorun(function () {
+ var newValue = _this.get();
+ if (!firstTime || fireImmediately) {
+ var prevU = untrackedStart();
+ listener({
+ type: "update",
+ object: _this,
+ newValue: newValue,
+ oldValue: prevValue
+ });
+ untrackedEnd(prevU);
+ }
+ firstTime = false;
+ prevValue = newValue;
+ });
+ };
+ ComputedValue.prototype.toJSON = function () {
+ return this.get();
+ };
+ ComputedValue.prototype.toString = function () {
+ return this.name + "[" + this.derivation.toString() + "]";
+ };
+ ComputedValue.prototype.valueOf = function () {
+ return toPrimitive(this.get());
+ };
+ ;
+ ComputedValue.prototype.whyRun = function () {
+ var isTracking = Boolean(globalState.trackingDerivation);
+ var observing = unique(this.isComputing ? this.newObserving : this.observing).map(function (dep) {
+ return dep.name;
+ });
+ var observers = unique(getObservers(this).map(function (dep) {
+ return dep.name;
+ }));
+ return "\nWhyRun? computation '" + this.name + "':\n * Running because: " + (isTracking ? "[active] the value of this computation is needed by a reaction" : this.isComputing ? "[get] The value of this computed was requested outside a reaction" : "[idle] not running at the moment") + "\n" + (this.dependenciesState === IDerivationState.NOT_TRACKING ? getMessage("m032") : " * This computation will re-run if any of the following observables changes:\n " + joinStrings(observing) + "\n " + (this.isComputing && isTracking ? " (... or any observable accessed during the remainder of the current run)" : "") + "\n\t" + getMessage("m038") + "\n\n * If the outcome of this computation changes, the following observers will be re-run:\n " + joinStrings(observers) + "\n");
+ };
+ return ComputedValue;
+}();
+ComputedValue.prototype[primitiveSymbol()] = ComputedValue.prototype.valueOf;
+var isComputedValue = createInstanceofPredicate("ComputedValue", ComputedValue);
+var IDerivationState;
+(function (IDerivationState) {
+ IDerivationState[IDerivationState["NOT_TRACKING"] = -1] = "NOT_TRACKING";
+ IDerivationState[IDerivationState["UP_TO_DATE"] = 0] = "UP_TO_DATE";
+ IDerivationState[IDerivationState["POSSIBLY_STALE"] = 1] = "POSSIBLY_STALE";
+ IDerivationState[IDerivationState["STALE"] = 2] = "STALE";
+})(IDerivationState || (IDerivationState = {}));
+exports.IDerivationState = IDerivationState;
+var CaughtException = function () {
+ function CaughtException(cause) {
+ this.cause = cause;
+ }
+ return CaughtException;
+}();
+function isCaughtException(e) {
+ return e instanceof CaughtException;
+}
+function shouldCompute(derivation) {
+ switch (derivation.dependenciesState) {
+ case IDerivationState.UP_TO_DATE:
+ return false;
+ case IDerivationState.NOT_TRACKING:
+ case IDerivationState.STALE:
+ return true;
+ case IDerivationState.POSSIBLY_STALE:
+ {
+ var prevUntracked = untrackedStart();
+ var obs = derivation.observing,
+ l = obs.length;
+ for (var i = 0; i < l; i++) {
+ var obj = obs[i];
+ if (isComputedValue(obj)) {
+ try {
+ obj.get();
+ } catch (e) {
+ untrackedEnd(prevUntracked);
+ return true;
+ }
+ if (derivation.dependenciesState === IDerivationState.STALE) {
+ untrackedEnd(prevUntracked);
+ return true;
+ }
+ }
+ }
+ changeDependenciesStateTo0(derivation);
+ untrackedEnd(prevUntracked);
+ return false;
+ }
+ }
+}
+function isComputingDerivation() {
+ return globalState.trackingDerivation !== null;
+}
+function checkIfStateModificationsAreAllowed(atom) {
+ var hasObservers = atom.observers.length > 0;
+ if (globalState.computationDepth > 0 && hasObservers) fail(getMessage("m031") + atom.name);
+ if (!globalState.allowStateChanges && hasObservers) fail(getMessage(globalState.strictMode ? "m030a" : "m030b") + atom.name);
+}
+function trackDerivedFunction(derivation, f, context) {
+ changeDependenciesStateTo0(derivation);
+ derivation.newObserving = new Array(derivation.observing.length + 100);
+ derivation.unboundDepsCount = 0;
+ derivation.runId = ++globalState.runId;
+ var prevTracking = globalState.trackingDerivation;
+ globalState.trackingDerivation = derivation;
+ var result;
+ try {
+ result = f.call(context);
+ } catch (e) {
+ result = new CaughtException(e);
+ }
+ globalState.trackingDerivation = prevTracking;
+ bindDependencies(derivation);
+ return result;
+}
+function bindDependencies(derivation) {
+ var prevObserving = derivation.observing;
+ var observing = derivation.observing = derivation.newObserving;
+ derivation.newObserving = null;
+ var i0 = 0,
+ l = derivation.unboundDepsCount;
+ for (var i = 0; i < l; i++) {
+ var dep = observing[i];
+ if (dep.diffValue === 0) {
+ dep.diffValue = 1;
+ if (i0 !== i) observing[i0] = dep;
+ i0++;
+ }
+ }
+ observing.length = i0;
+ l = prevObserving.length;
+ while (l--) {
+ var dep = prevObserving[l];
+ if (dep.diffValue === 0) {
+ removeObserver(dep, derivation);
+ }
+ dep.diffValue = 0;
+ }
+ while (i0--) {
+ var dep = observing[i0];
+ if (dep.diffValue === 1) {
+ dep.diffValue = 0;
+ addObserver(dep, derivation);
+ }
+ }
+}
+function clearObserving(derivation) {
+ var obs = derivation.observing;
+ var i = obs.length;
+ while (i--) {
+ removeObserver(obs[i], derivation);
+ }derivation.dependenciesState = IDerivationState.NOT_TRACKING;
+ obs.length = 0;
+}
+function untracked(action) {
+ var prev = untrackedStart();
+ var res = action();
+ untrackedEnd(prev);
+ return res;
+}
+exports.untracked = untracked;
+function untrackedStart() {
+ var prev = globalState.trackingDerivation;
+ globalState.trackingDerivation = null;
+ return prev;
+}
+function untrackedEnd(prev) {
+ globalState.trackingDerivation = prev;
+}
+function changeDependenciesStateTo0(derivation) {
+ if (derivation.dependenciesState === IDerivationState.UP_TO_DATE) return;
+ derivation.dependenciesState = IDerivationState.UP_TO_DATE;
+ var obs = derivation.observing;
+ var i = obs.length;
+ while (i--) {
+ obs[i].lowestObserverState = IDerivationState.UP_TO_DATE;
+ }
+}
+var persistentKeys = ["mobxGuid", "resetId", "spyListeners", "strictMode", "runId"];
+var MobXGlobals = function () {
+ function MobXGlobals() {
+ this.version = 5;
+ this.trackingDerivation = null;
+ this.computationDepth = 0;
+ this.runId = 0;
+ this.mobxGuid = 0;
+ this.inBatch = 0;
+ this.pendingUnobservations = [];
+ this.pendingReactions = [];
+ this.isRunningReactions = false;
+ this.allowStateChanges = true;
+ this.strictMode = false;
+ this.resetId = 0;
+ this.spyListeners = [];
+ this.globalReactionErrorHandlers = [];
+ }
+ return MobXGlobals;
+}();
+var globalState = new MobXGlobals();
+function shareGlobalState() {
+ var global = getGlobal();
+ var ownState = globalState;
+ if (global.__mobservableTrackingStack || global.__mobservableViewStack) throw new Error("[mobx] An incompatible version of mobservable is already loaded.");
+ if (global.__mobxGlobal && global.__mobxGlobal.version !== ownState.version) throw new Error("[mobx] An incompatible version of mobx is already loaded.");
+ if (global.__mobxGlobal) globalState = global.__mobxGlobal;else global.__mobxGlobal = ownState;
+}
+function getGlobalState() {
+ return globalState;
+}
+function registerGlobals() {}
+function resetGlobalState() {
+ globalState.resetId++;
+ var defaultGlobals = new MobXGlobals();
+ for (var key in defaultGlobals) {
+ if (persistentKeys.indexOf(key) === -1) globalState[key] = defaultGlobals[key];
+ }globalState.allowStateChanges = !globalState.strictMode;
+}
+function hasObservers(observable) {
+ return observable.observers && observable.observers.length > 0;
+}
+function getObservers(observable) {
+ return observable.observers;
+}
+function invariantObservers(observable) {
+ var list = observable.observers;
+ var map = observable.observersIndexes;
+ var l = list.length;
+ for (var i = 0; i < l; i++) {
+ var id = list[i].__mapid;
+ if (i) {
+ invariant(map[id] === i, "INTERNAL ERROR maps derivation.__mapid to index in list");
+ } else {
+ invariant(!(id in map), "INTERNAL ERROR observer on index 0 shouldnt be held in map.");
+ }
+ }
+ invariant(list.length === 0 || Object.keys(map).length === list.length - 1, "INTERNAL ERROR there is no junk in map");
+}
+function addObserver(observable, node) {
+ var l = observable.observers.length;
+ if (l) {
+ observable.observersIndexes[node.__mapid] = l;
+ }
+ observable.observers[l] = node;
+ if (observable.lowestObserverState > node.dependenciesState) observable.lowestObserverState = node.dependenciesState;
+}
+function removeObserver(observable, node) {
+ if (observable.observers.length === 1) {
+ observable.observers.length = 0;
+ queueForUnobservation(observable);
+ } else {
+ var list = observable.observers;
+ var map_1 = observable.observersIndexes;
+ var filler = list.pop();
+ if (filler !== node) {
+ var index = map_1[node.__mapid] || 0;
+ if (index) {
+ map_1[filler.__mapid] = index;
+ } else {
+ delete map_1[filler.__mapid];
+ }
+ list[index] = filler;
+ }
+ delete map_1[node.__mapid];
+ }
+}
+function queueForUnobservation(observable) {
+ if (!observable.isPendingUnobservation) {
+ observable.isPendingUnobservation = true;
+ globalState.pendingUnobservations.push(observable);
+ }
+}
+function startBatch() {
+ globalState.inBatch++;
+}
+function endBatch() {
+ if (--globalState.inBatch === 0) {
+ runReactions();
+ var list = globalState.pendingUnobservations;
+ for (var i = 0; i < list.length; i++) {
+ var observable_1 = list[i];
+ observable_1.isPendingUnobservation = false;
+ if (observable_1.observers.length === 0) {
+ observable_1.onBecomeUnobserved();
+ }
+ }
+ globalState.pendingUnobservations = [];
+ }
+}
+function reportObserved(observable) {
+ var derivation = globalState.trackingDerivation;
+ if (derivation !== null) {
+ if (derivation.runId !== observable.lastAccessedBy) {
+ observable.lastAccessedBy = derivation.runId;
+ derivation.newObserving[derivation.unboundDepsCount++] = observable;
+ }
+ } else if (observable.observers.length === 0) {
+ queueForUnobservation(observable);
+ }
+}
+function invariantLOS(observable, msg) {
+ var min = getObservers(observable).reduce(function (a, b) {
+ return Math.min(a, b.dependenciesState);
+ }, 2);
+ if (min >= observable.lowestObserverState) return;
+ throw new Error("lowestObserverState is wrong for " + msg + " because " + min + " < " + observable.lowestObserverState);
+}
+function propagateChanged(observable) {
+ if (observable.lowestObserverState === IDerivationState.STALE) return;
+ observable.lowestObserverState = IDerivationState.STALE;
+ var observers = observable.observers;
+ var i = observers.length;
+ while (i--) {
+ var d = observers[i];
+ if (d.dependenciesState === IDerivationState.UP_TO_DATE) d.onBecomeStale();
+ d.dependenciesState = IDerivationState.STALE;
+ }
+}
+function propagateChangeConfirmed(observable) {
+ if (observable.lowestObserverState === IDerivationState.STALE) return;
+ observable.lowestObserverState = IDerivationState.STALE;
+ var observers = observable.observers;
+ var i = observers.length;
+ while (i--) {
+ var d = observers[i];
+ if (d.dependenciesState === IDerivationState.POSSIBLY_STALE) d.dependenciesState = IDerivationState.STALE;else if (d.dependenciesState === IDerivationState.UP_TO_DATE) observable.lowestObserverState = IDerivationState.UP_TO_DATE;
+ }
+}
+function propagateMaybeChanged(observable) {
+ if (observable.lowestObserverState !== IDerivationState.UP_TO_DATE) return;
+ observable.lowestObserverState = IDerivationState.POSSIBLY_STALE;
+ var observers = observable.observers;
+ var i = observers.length;
+ while (i--) {
+ var d = observers[i];
+ if (d.dependenciesState === IDerivationState.UP_TO_DATE) {
+ d.dependenciesState = IDerivationState.POSSIBLY_STALE;
+ d.onBecomeStale();
+ }
+ }
+}
+var Reaction = function () {
+ function Reaction(name, onInvalidate) {
+ if (name === void 0) {
+ name = "Reaction@" + getNextId();
+ }
+ this.name = name;
+ this.onInvalidate = onInvalidate;
+ this.observing = [];
+ this.newObserving = [];
+ this.dependenciesState = IDerivationState.NOT_TRACKING;
+ this.diffValue = 0;
+ this.runId = 0;
+ this.unboundDepsCount = 0;
+ this.__mapid = "#" + getNextId();
+ this.isDisposed = false;
+ this._isScheduled = false;
+ this._isTrackPending = false;
+ this._isRunning = false;
+ }
+ Reaction.prototype.onBecomeStale = function () {
+ this.schedule();
+ };
+ Reaction.prototype.schedule = function () {
+ if (!this._isScheduled) {
+ this._isScheduled = true;
+ globalState.pendingReactions.push(this);
+ runReactions();
+ }
+ };
+ Reaction.prototype.isScheduled = function () {
+ return this._isScheduled;
+ };
+ Reaction.prototype.runReaction = function () {
+ if (!this.isDisposed) {
+ startBatch();
+ this._isScheduled = false;
+ if (shouldCompute(this)) {
+ this._isTrackPending = true;
+ this.onInvalidate();
+ if (this._isTrackPending && isSpyEnabled()) {
+ spyReport({
+ object: this,
+ type: "scheduled-reaction"
+ });
+ }
+ }
+ endBatch();
+ }
+ };
+ Reaction.prototype.track = function (fn) {
+ startBatch();
+ var notify = isSpyEnabled();
+ var startTime;
+ if (notify) {
+ startTime = Date.now();
+ spyReportStart({
+ object: this,
+ type: "reaction",
+ fn: fn
+ });
+ }
+ this._isRunning = true;
+ var result = trackDerivedFunction(this, fn, undefined);
+ this._isRunning = false;
+ this._isTrackPending = false;
+ if (this.isDisposed) {
+ clearObserving(this);
+ }
+ if (isCaughtException(result)) this.reportExceptionInDerivation(result.cause);
+ if (notify) {
+ spyReportEnd({
+ time: Date.now() - startTime
+ });
+ }
+ endBatch();
+ };
+ Reaction.prototype.reportExceptionInDerivation = function (error) {
+ var _this = this;
+ if (this.errorHandler) {
+ this.errorHandler(error, this);
+ return;
+ }
+ var message = "[mobx] Encountered an uncaught exception that was thrown by a reaction or observer component, in: '" + this;
+ var messageToUser = getMessage("m037");
+ console.error(message || messageToUser, error);
+ if (isSpyEnabled()) {
+ spyReport({
+ type: "error",
+ message: message,
+ error: error,
+ object: this
+ });
+ }
+ globalState.globalReactionErrorHandlers.forEach(function (f) {
+ return f(error, _this);
+ });
+ };
+ Reaction.prototype.dispose = function () {
+ if (!this.isDisposed) {
+ this.isDisposed = true;
+ if (!this._isRunning) {
+ startBatch();
+ clearObserving(this);
+ endBatch();
+ }
+ }
+ };
+ Reaction.prototype.getDisposer = function () {
+ var r = this.dispose.bind(this);
+ r.$mobx = this;
+ r.onError = registerErrorHandler;
+ return r;
+ };
+ Reaction.prototype.toString = function () {
+ return "Reaction[" + this.name + "]";
+ };
+ Reaction.prototype.whyRun = function () {
+ var observing = unique(this._isRunning ? this.newObserving : this.observing).map(function (dep) {
+ return dep.name;
+ });
+ return "\nWhyRun? reaction '" + this.name + "':\n * Status: [" + (this.isDisposed ? "stopped" : this._isRunning ? "running" : this.isScheduled() ? "scheduled" : "idle") + "]\n * This reaction will re-run if any of the following observables changes:\n " + joinStrings(observing) + "\n " + (this._isRunning ? " (... or any observable accessed during the remainder of the current run)" : "") + "\n\t" + getMessage("m038") + "\n";
+ };
+ return Reaction;
+}();
+exports.Reaction = Reaction;
+function registerErrorHandler(handler) {
+ invariant(this && this.$mobx && isReaction(this.$mobx), "Invalid `this`");
+ invariant(!this.$mobx.errorHandler, "Only one onErrorHandler can be registered");
+ this.$mobx.errorHandler = handler;
+}
+function onReactionError(handler) {
+ globalState.globalReactionErrorHandlers.push(handler);
+ return function () {
+ var idx = globalState.globalReactionErrorHandlers.indexOf(handler);
+ if (idx >= 0) globalState.globalReactionErrorHandlers.splice(idx, 1);
+ };
+}
+var MAX_REACTION_ITERATIONS = 100;
+var reactionScheduler = function reactionScheduler(f) {
+ return f();
+};
+function runReactions() {
+ if (globalState.inBatch > 0 || globalState.isRunningReactions) return;
+ reactionScheduler(runReactionsHelper);
+}
+function runReactionsHelper() {
+ globalState.isRunningReactions = true;
+ var allReactions = globalState.pendingReactions;
+ var iterations = 0;
+ while (allReactions.length > 0) {
+ if (++iterations === MAX_REACTION_ITERATIONS) {
+ console.error("Reaction doesn't converge to a stable state after " + MAX_REACTION_ITERATIONS + " iterations." + (" Probably there is a cycle in the reactive function: " + allReactions[0]));
+ allReactions.splice(0);
+ }
+ var remainingReactions = allReactions.splice(0);
+ for (var i = 0, l = remainingReactions.length; i < l; i++) {
+ remainingReactions[i].runReaction();
+ }
+ }
+ globalState.isRunningReactions = false;
+}
+var isReaction = createInstanceofPredicate("Reaction", Reaction);
+function setReactionScheduler(fn) {
+ var baseScheduler = reactionScheduler;
+ reactionScheduler = function reactionScheduler(f) {
+ return fn(function () {
+ return baseScheduler(f);
+ });
+ };
+}
+function isSpyEnabled() {
+ return !!globalState.spyListeners.length;
+}
+function spyReport(event) {
+ if (!globalState.spyListeners.length) return;
+ var listeners = globalState.spyListeners;
+ for (var i = 0, l = listeners.length; i < l; i++) {
+ listeners[i](event);
+ }
+}
+function spyReportStart(event) {
+ var change = objectAssign({}, event, { spyReportStart: true });
+ spyReport(change);
+}
+var END_EVENT = { spyReportEnd: true };
+function spyReportEnd(change) {
+ if (change) spyReport(objectAssign({}, change, END_EVENT));else spyReport(END_EVENT);
+}
+function spy(listener) {
+ globalState.spyListeners.push(listener);
+ return once(function () {
+ var idx = globalState.spyListeners.indexOf(listener);
+ if (idx !== -1) globalState.spyListeners.splice(idx, 1);
+ });
+}
+exports.spy = spy;
+function hasInterceptors(interceptable) {
+ return interceptable.interceptors && interceptable.interceptors.length > 0;
+}
+function registerInterceptor(interceptable, handler) {
+ var interceptors = interceptable.interceptors || (interceptable.interceptors = []);
+ interceptors.push(handler);
+ return once(function () {
+ var idx = interceptors.indexOf(handler);
+ if (idx !== -1) interceptors.splice(idx, 1);
+ });
+}
+function interceptChange(interceptable, change) {
+ var prevU = untrackedStart();
+ try {
+ var interceptors = interceptable.interceptors;
+ if (interceptors) for (var i = 0, l = interceptors.length; i < l; i++) {
+ change = interceptors[i](change);
+ invariant(!change || change.type, "Intercept handlers should return nothing or a change object");
+ if (!change) break;
+ }
+ return change;
+ } finally {
+ untrackedEnd(prevU);
+ }
+}
+function hasListeners(listenable) {
+ return listenable.changeListeners && listenable.changeListeners.length > 0;
+}
+function registerListener(listenable, handler) {
+ var listeners = listenable.changeListeners || (listenable.changeListeners = []);
+ listeners.push(handler);
+ return once(function () {
+ var idx = listeners.indexOf(handler);
+ if (idx !== -1) listeners.splice(idx, 1);
+ });
+}
+function notifyListeners(listenable, change) {
+ var prevU = untrackedStart();
+ var listeners = listenable.changeListeners;
+ if (!listeners) return;
+ listeners = listeners.slice();
+ for (var i = 0, l = listeners.length; i < l; i++) {
+ listeners[i](change);
+ }
+ untrackedEnd(prevU);
+}
+function asReference(value) {
+ deprecated("asReference is deprecated, use observable.ref instead");
+ return observable.ref(value);
+}
+exports.asReference = asReference;
+function asStructure(value) {
+ deprecated("asStructure is deprecated. Use observable.struct, computed.struct or reaction options instead.");
+ return observable.struct(value);
+}
+exports.asStructure = asStructure;
+function asFlat(value) {
+ deprecated("asFlat is deprecated, use observable.shallow instead");
+ return observable.shallow(value);
+}
+exports.asFlat = asFlat;
+function asMap(data) {
+ deprecated("asMap is deprecated, use observable.map or observable.shallowMap instead");
+ return observable.map(data || {});
+}
+exports.asMap = asMap;
+function isModifierDescriptor(thing) {
+ return (typeof thing === "undefined" ? "undefined" : _typeof(thing)) === "object" && thing !== null && thing.isMobxModifierDescriptor === true;
+}
+exports.isModifierDescriptor = isModifierDescriptor;
+function createModifierDescriptor(enhancer, initialValue) {
+ invariant(!isModifierDescriptor(initialValue), "Modifiers cannot be nested");
+ return {
+ isMobxModifierDescriptor: true,
+ initialValue: initialValue,
+ enhancer: enhancer
+ };
+}
+function deepEnhancer(v, _, name) {
+ if (isModifierDescriptor(v)) fail("You tried to assign a modifier wrapped value to a collection, please define modifiers when creating the collection, not when modifying it");
+ if (isObservable(v)) return v;
+ if (Array.isArray(v)) return observable.array(v, name);
+ if (isPlainObject(v)) return observable.object(v, name);
+ if (isES6Map(v)) return observable.map(v, name);
+ return v;
+}
+function shallowEnhancer(v, _, name) {
+ if (isModifierDescriptor(v)) fail("You tried to assign a modifier wrapped value to a collection, please define modifiers when creating the collection, not when modifying it");
+ if (v === undefined || v === null) return v;
+ if (isObservableObject(v) || isObservableArray(v) || isObservableMap(v)) return v;
+ if (Array.isArray(v)) return observable.shallowArray(v, name);
+ if (isPlainObject(v)) return observable.shallowObject(v, name);
+ if (isES6Map(v)) return observable.shallowMap(v, name);
+ return fail("The shallow modifier / decorator can only used in combination with arrays, objects and maps");
+}
+function referenceEnhancer(newValue) {
+ return newValue;
+}
+function deepStructEnhancer(v, oldValue, name) {
+ if (deepEqual(v, oldValue)) return oldValue;
+ if (isObservable(v)) return v;
+ if (Array.isArray(v)) return new ObservableArray(v, deepStructEnhancer, name);
+ if (isES6Map(v)) return new ObservableMap(v, deepStructEnhancer, name);
+ if (isPlainObject(v)) {
+ var res = {};
+ asObservableObject(res, name);
+ extendObservableHelper(res, deepStructEnhancer, [v]);
+ return res;
+ }
+ return v;
+}
+function refStructEnhancer(v, oldValue, name) {
+ if (deepEqual(v, oldValue)) return oldValue;
+ return v;
+}
+var MAX_SPLICE_SIZE = 10000;
+var safariPrototypeSetterInheritanceBug = function () {
+ var v = false;
+ var p = {};
+ Object.defineProperty(p, "0", { set: function set() {
+ v = true;
+ } });
+ Object.create(p)["0"] = 1;
+ return v === false;
+}();
+var OBSERVABLE_ARRAY_BUFFER_SIZE = 0;
+var StubArray = function () {
+ function StubArray() {}
+ return StubArray;
+}();
+StubArray.prototype = [];
+var ObservableArrayAdministration = function () {
+ function ObservableArrayAdministration(name, enhancer, array, owned) {
+ this.array = array;
+ this.owned = owned;
+ this.lastKnownLength = 0;
+ this.interceptors = null;
+ this.changeListeners = null;
+ this.atom = new BaseAtom(name || "ObservableArray@" + getNextId());
+ this.enhancer = function (newV, oldV) {
+ return enhancer(newV, oldV, name + "[..]");
+ };
+ }
+ ObservableArrayAdministration.prototype.intercept = function (handler) {
+ return registerInterceptor(this, handler);
+ };
+ ObservableArrayAdministration.prototype.observe = function (listener, fireImmediately) {
+ if (fireImmediately === void 0) {
+ fireImmediately = false;
+ }
+ if (fireImmediately) {
+ listener({
+ object: this.array,
+ type: "splice",
+ index: 0,
+ added: this.values.slice(),
+ addedCount: this.values.length,
+ removed: [],
+ removedCount: 0
+ });
+ }
+ return registerListener(this, listener);
+ };
+ ObservableArrayAdministration.prototype.getArrayLength = function () {
+ this.atom.reportObserved();
+ return this.values.length;
+ };
+ ObservableArrayAdministration.prototype.setArrayLength = function (newLength) {
+ if (typeof newLength !== "number" || newLength < 0) throw new Error("[mobx.array] Out of range: " + newLength);
+ var currentLength = this.values.length;
+ if (newLength === currentLength) return;else if (newLength > currentLength) {
+ var newItems = new Array(newLength - currentLength);
+ for (var i = 0; i < newLength - currentLength; i++) {
+ newItems[i] = undefined;
+ }this.spliceWithArray(currentLength, 0, newItems);
+ } else this.spliceWithArray(newLength, currentLength - newLength);
+ };
+ ObservableArrayAdministration.prototype.updateArrayLength = function (oldLength, delta) {
+ if (oldLength !== this.lastKnownLength) throw new Error("[mobx] Modification exception: the internal structure of an observable array was changed. Did you use peek() to change it?");
+ this.lastKnownLength += delta;
+ if (delta > 0 && oldLength + delta + 1 > OBSERVABLE_ARRAY_BUFFER_SIZE) reserveArrayBuffer(oldLength + delta + 1);
+ };
+ ObservableArrayAdministration.prototype.spliceWithArray = function (index, deleteCount, newItems) {
+ var _this = this;
+ checkIfStateModificationsAreAllowed(this.atom);
+ var length = this.values.length;
+ if (index === undefined) index = 0;else if (index > length) index = length;else if (index < 0) index = Math.max(0, length + index);
+ if (arguments.length === 1) deleteCount = length - index;else if (deleteCount === undefined || deleteCount === null) deleteCount = 0;else deleteCount = Math.max(0, Math.min(deleteCount, length - index));
+ if (newItems === undefined) newItems = [];
+ if (hasInterceptors(this)) {
+ var change = interceptChange(this, {
+ object: this.array,
+ type: "splice",
+ index: index,
+ removedCount: deleteCount,
+ added: newItems
+ });
+ if (!change) return EMPTY_ARRAY;
+ deleteCount = change.removedCount;
+ newItems = change.added;
+ }
+ newItems = newItems.map(function (v) {
+ return _this.enhancer(v, undefined);
+ });
+ var lengthDelta = newItems.length - deleteCount;
+ this.updateArrayLength(length, lengthDelta);
+ var res = this.spliceItemsIntoValues(index, deleteCount, newItems);
+ if (deleteCount !== 0 || newItems.length !== 0) this.notifyArraySplice(index, newItems, res);
+ return res;
+ };
+ ObservableArrayAdministration.prototype.spliceItemsIntoValues = function (index, deleteCount, newItems) {
+ if (newItems.length < MAX_SPLICE_SIZE) {
+ return (_a = this.values).splice.apply(_a, [index, deleteCount].concat(newItems));
+ } else {
+ var res = this.values.slice(index, index + deleteCount);
+ this.values = this.values.slice(0, index).concat(newItems, this.values.slice(index + deleteCount));
+ return res;
+ }
+ var _a;
+ };
+ ObservableArrayAdministration.prototype.notifyArrayChildUpdate = function (index, newValue, oldValue) {
+ var notifySpy = !this.owned && isSpyEnabled();
+ var notify = hasListeners(this);
+ var change = notify || notifySpy ? {
+ object: this.array,
+ type: "update",
+ index: index, newValue: newValue, oldValue: oldValue
+ } : null;
+ if (notifySpy) spyReportStart(change);
+ this.atom.reportChanged();
+ if (notify) notifyListeners(this, change);
+ if (notifySpy) spyReportEnd();
+ };
+ ObservableArrayAdministration.prototype.notifyArraySplice = function (index, added, removed) {
+ var notifySpy = !this.owned && isSpyEnabled();
+ var notify = hasListeners(this);
+ var change = notify || notifySpy ? {
+ object: this.array,
+ type: "splice",
+ index: index, removed: removed, added: added,
+ removedCount: removed.length,
+ addedCount: added.length
+ } : null;
+ if (notifySpy) spyReportStart(change);
+ this.atom.reportChanged();
+ if (notify) notifyListeners(this, change);
+ if (notifySpy) spyReportEnd();
+ };
+ return ObservableArrayAdministration;
+}();
+var ObservableArray = function (_super) {
+ __extends(ObservableArray, _super);
+ function ObservableArray(initialValues, enhancer, name, owned) {
+ if (name === void 0) {
+ name = "ObservableArray@" + getNextId();
+ }
+ if (owned === void 0) {
+ owned = false;
+ }
+ var _this = _super.call(this) || this;
+ var adm = new ObservableArrayAdministration(name, enhancer, _this, owned);
+ addHiddenFinalProp(_this, "$mobx", adm);
+ if (initialValues && initialValues.length) {
+ adm.updateArrayLength(0, initialValues.length);
+ adm.values = initialValues.map(function (v) {
+ return enhancer(v, undefined, name + "[..]");
+ });
+ adm.notifyArraySplice(0, adm.values.slice(), EMPTY_ARRAY);
+ } else {
+ adm.values = [];
+ }
+ if (safariPrototypeSetterInheritanceBug) {
+ Object.defineProperty(adm.array, "0", ENTRY_0);
+ }
+ return _this;
+ }
+ ObservableArray.prototype.intercept = function (handler) {
+ return this.$mobx.intercept(handler);
+ };
+ ObservableArray.prototype.observe = function (listener, fireImmediately) {
+ if (fireImmediately === void 0) {
+ fireImmediately = false;
+ }
+ return this.$mobx.observe(listener, fireImmediately);
+ };
+ ObservableArray.prototype.clear = function () {
+ return this.splice(0);
+ };
+ ObservableArray.prototype.concat = function () {
+ var arrays = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ arrays[_i] = arguments[_i];
+ }
+ this.$mobx.atom.reportObserved();
+ return Array.prototype.concat.apply(this.peek(), arrays.map(function (a) {
+ return isObservableArray(a) ? a.peek() : a;
+ }));
+ };
+ ObservableArray.prototype.replace = function (newItems) {
+ return this.$mobx.spliceWithArray(0, this.$mobx.values.length, newItems);
+ };
+ ObservableArray.prototype.toJS = function () {
+ return this.slice();
+ };
+ ObservableArray.prototype.toJSON = function () {
+ return this.toJS();
+ };
+ ObservableArray.prototype.peek = function () {
+ return this.$mobx.values;
+ };
+ ObservableArray.prototype.find = function (predicate, thisArg, fromIndex) {
+ if (fromIndex === void 0) {
+ fromIndex = 0;
+ }
+ this.$mobx.atom.reportObserved();
+ var items = this.$mobx.values,
+ l = items.length;
+ for (var i = fromIndex; i < l; i++) {
+ if (predicate.call(thisArg, items[i], i, this)) return items[i];
+ }return undefined;
+ };
+ ObservableArray.prototype.splice = function (index, deleteCount) {
+ var newItems = [];
+ for (var _i = 2; _i < arguments.length; _i++) {
+ newItems[_i - 2] = arguments[_i];
+ }
+ switch (arguments.length) {
+ case 0:
+ return [];
+ case 1:
+ return this.$mobx.spliceWithArray(index);
+ case 2:
+ return this.$mobx.spliceWithArray(index, deleteCount);
+ }
+ return this.$mobx.spliceWithArray(index, deleteCount, newItems);
+ };
+ ObservableArray.prototype.spliceWithArray = function (index, deleteCount, newItems) {
+ return this.$mobx.spliceWithArray(index, deleteCount, newItems);
+ };
+ ObservableArray.prototype.push = function () {
+ var items = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ items[_i] = arguments[_i];
+ }
+ var adm = this.$mobx;
+ adm.spliceWithArray(adm.values.length, 0, items);
+ return adm.values.length;
+ };
+ ObservableArray.prototype.pop = function () {
+ return this.splice(Math.max(this.$mobx.values.length - 1, 0), 1)[0];
+ };
+ ObservableArray.prototype.shift = function () {
+ return this.splice(0, 1)[0];
+ };
+ ObservableArray.prototype.unshift = function () {
+ var items = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ items[_i] = arguments[_i];
+ }
+ var adm = this.$mobx;
+ adm.spliceWithArray(0, 0, items);
+ return adm.values.length;
+ };
+ ObservableArray.prototype.reverse = function () {
+ this.$mobx.atom.reportObserved();
+ var clone = this.slice();
+ return clone.reverse.apply(clone, arguments);
+ };
+ ObservableArray.prototype.sort = function (compareFn) {
+ this.$mobx.atom.reportObserved();
+ var clone = this.slice();
+ return clone.sort.apply(clone, arguments);
+ };
+ ObservableArray.prototype.remove = function (value) {
+ var idx = this.$mobx.values.indexOf(value);
+ if (idx > -1) {
+ this.splice(idx, 1);
+ return true;
+ }
+ return false;
+ };
+ ObservableArray.prototype.move = function (fromIndex, toIndex) {
+ function checkIndex(index) {
+ if (index < 0) {
+ throw new Error("[mobx.array] Index out of bounds: " + index + " is negative");
+ }
+ var length = this.$mobx.values.length;
+ if (index >= length) {
+ throw new Error("[mobx.array] Index out of bounds: " + index + " is not smaller than " + length);
+ }
+ }
+ checkIndex.call(this, fromIndex);
+ checkIndex.call(this, toIndex);
+ if (fromIndex === toIndex) {
+ return;
+ }
+ var oldItems = this.$mobx.values;
+ var newItems;
+ if (fromIndex < toIndex) {
+ newItems = oldItems.slice(0, fromIndex).concat(oldItems.slice(fromIndex + 1, toIndex + 1), [oldItems[fromIndex]], oldItems.slice(toIndex + 1));
+ } else {
+ newItems = oldItems.slice(0, toIndex).concat([oldItems[fromIndex]], oldItems.slice(toIndex, fromIndex), oldItems.slice(fromIndex + 1));
+ }
+ this.replace(newItems);
+ };
+ ObservableArray.prototype.toString = function () {
+ this.$mobx.atom.reportObserved();
+ return Array.prototype.toString.apply(this.$mobx.values, arguments);
+ };
+ ObservableArray.prototype.toLocaleString = function () {
+ this.$mobx.atom.reportObserved();
+ return Array.prototype.toLocaleString.apply(this.$mobx.values, arguments);
+ };
+ return ObservableArray;
+}(StubArray);
+declareIterator(ObservableArray.prototype, function () {
+ return arrayAsIterator(this.slice());
+});
+makeNonEnumerable(ObservableArray.prototype, ["constructor", "intercept", "observe", "clear", "concat", "replace", "toJS", "toJSON", "peek", "find", "splice", "spliceWithArray", "push", "pop", "shift", "unshift", "reverse", "sort", "remove", "move", "toString", "toLocaleString"]);
+Object.defineProperty(ObservableArray.prototype, "length", {
+ enumerable: false,
+ configurable: true,
+ get: function get() {
+ return this.$mobx.getArrayLength();
+ },
+ set: function set(newLength) {
+ this.$mobx.setArrayLength(newLength);
+ }
+});
+["every", "filter", "forEach", "indexOf", "join", "lastIndexOf", "map", "reduce", "reduceRight", "slice", "some"].forEach(function (funcName) {
+ var baseFunc = Array.prototype[funcName];
+ invariant(typeof baseFunc === "function", "Base function not defined on Array prototype: '" + funcName + "'");
+ addHiddenProp(ObservableArray.prototype, funcName, function () {
+ this.$mobx.atom.reportObserved();
+ return baseFunc.apply(this.$mobx.values, arguments);
+ });
+});
+var ENTRY_0 = {
+ configurable: true,
+ enumerable: false,
+ set: createArraySetter(0),
+ get: createArrayGetter(0)
+};
+function createArrayBufferItem(index) {
+ var set = createArraySetter(index);
+ var get = createArrayGetter(index);
+ Object.defineProperty(ObservableArray.prototype, "" + index, {
+ enumerable: false,
+ configurable: true,
+ set: set, get: get
+ });
+}
+function createArraySetter(index) {
+ return function (newValue) {
+ var adm = this.$mobx;
+ var values = adm.values;
+ if (index < values.length) {
+ checkIfStateModificationsAreAllowed(adm.atom);
+ var oldValue = values[index];
+ if (hasInterceptors(adm)) {
+ var change = interceptChange(adm, {
+ type: "update",
+ object: adm.array,
+ index: index, newValue: newValue
+ });
+ if (!change) return;
+ newValue = change.newValue;
+ }
+ newValue = adm.enhancer(newValue, oldValue);
+ var changed = newValue !== oldValue;
+ if (changed) {
+ values[index] = newValue;
+ adm.notifyArrayChildUpdate(index, newValue, oldValue);
+ }
+ } else if (index === values.length) {
+ adm.spliceWithArray(index, 0, [newValue]);
+ } else throw new Error("[mobx.array] Index out of bounds, " + index + " is larger than " + values.length);
+ };
+}
+function createArrayGetter(index) {
+ return function () {
+ var impl = this.$mobx;
+ if (impl) {
+ if (index < impl.values.length) {
+ impl.atom.reportObserved();
+ return impl.values[index];
+ }
+ console.warn("[mobx.array] Attempt to read an array index (" + index + ") that is out of bounds (" + impl.values.length + "). Please check length first. Out of bound indices will not be tracked by MobX");
+ }
+ return undefined;
+ };
+}
+function reserveArrayBuffer(max) {
+ for (var index = OBSERVABLE_ARRAY_BUFFER_SIZE; index < max; index++) {
+ createArrayBufferItem(index);
+ }OBSERVABLE_ARRAY_BUFFER_SIZE = max;
+}
+reserveArrayBuffer(1000);
+var isObservableArrayAdministration = createInstanceofPredicate("ObservableArrayAdministration", ObservableArrayAdministration);
+function isObservableArray(thing) {
+ return isObject(thing) && isObservableArrayAdministration(thing.$mobx);
+}
+exports.isObservableArray = isObservableArray;
+var ObservableMapMarker = {};
+var ObservableMap = function () {
+ function ObservableMap(initialData, enhancer, name) {
+ if (enhancer === void 0) {
+ enhancer = deepEnhancer;
+ }
+ if (name === void 0) {
+ name = "ObservableMap@" + getNextId();
+ }
+ this.enhancer = enhancer;
+ this.name = name;
+ this.$mobx = ObservableMapMarker;
+ this._data = {};
+ this._hasMap = {};
+ this._keys = new ObservableArray(undefined, referenceEnhancer, this.name + ".keys()", true);
+ this.interceptors = null;
+ this.changeListeners = null;
+ this.merge(initialData);
+ }
+ ObservableMap.prototype._has = function (key) {
+ return typeof this._data[key] !== "undefined";
+ };
+ ObservableMap.prototype.has = function (key) {
+ if (!this.isValidKey(key)) return false;
+ key = "" + key;
+ if (this._hasMap[key]) return this._hasMap[key].get();
+ return this._updateHasMapEntry(key, false).get();
+ };
+ ObservableMap.prototype.set = function (key, value) {
+ this.assertValidKey(key);
+ key = "" + key;
+ var hasKey = this._has(key);
+ if (hasInterceptors(this)) {
+ var change = interceptChange(this, {
+ type: hasKey ? "update" : "add",
+ object: this,
+ newValue: value,
+ name: key
+ });
+ if (!change) return this;
+ value = change.newValue;
+ }
+ if (hasKey) {
+ this._updateValue(key, value);
+ } else {
+ this._addValue(key, value);
+ }
+ return this;
+ };
+ ObservableMap.prototype.delete = function (key) {
+ var _this = this;
+ this.assertValidKey(key);
+ key = "" + key;
+ if (hasInterceptors(this)) {
+ var change = interceptChange(this, {
+ type: "delete",
+ object: this,
+ name: key
+ });
+ if (!change) return false;
+ }
+ if (this._has(key)) {
+ var notifySpy = isSpyEnabled();
+ var notify = hasListeners(this);
+ var change = notify || notifySpy ? {
+ type: "delete",
+ object: this,
+ oldValue: this._data[key].value,
+ name: key
+ } : null;
+ if (notifySpy) spyReportStart(change);
+ runInTransaction(function () {
+ _this._keys.remove(key);
+ _this._updateHasMapEntry(key, false);
+ var observable = _this._data[key];
+ observable.setNewValue(undefined);
+ _this._data[key] = undefined;
+ });
+ if (notify) notifyListeners(this, change);
+ if (notifySpy) spyReportEnd();
+ return true;
+ }
+ return false;
+ };
+ ObservableMap.prototype._updateHasMapEntry = function (key, value) {
+ var entry = this._hasMap[key];
+ if (entry) {
+ entry.setNewValue(value);
+ } else {
+ entry = this._hasMap[key] = new ObservableValue(value, referenceEnhancer, this.name + "." + key + "?", false);
+ }
+ return entry;
+ };
+ ObservableMap.prototype._updateValue = function (name, newValue) {
+ var observable = this._data[name];
+ newValue = observable.prepareNewValue(newValue);
+ if (newValue !== UNCHANGED) {
+ var notifySpy = isSpyEnabled();
+ var notify = hasListeners(this);
+ var change = notify || notifySpy ? {
+ type: "update",
+ object: this,
+ oldValue: observable.value,
+ name: name, newValue: newValue
+ } : null;
+ if (notifySpy) spyReportStart(change);
+ observable.setNewValue(newValue);
+ if (notify) notifyListeners(this, change);
+ if (notifySpy) spyReportEnd();
+ }
+ };
+ ObservableMap.prototype._addValue = function (name, newValue) {
+ var _this = this;
+ runInTransaction(function () {
+ var observable = _this._data[name] = new ObservableValue(newValue, _this.enhancer, _this.name + "." + name, false);
+ newValue = observable.value;
+ _this._updateHasMapEntry(name, true);
+ _this._keys.push(name);
+ });
+ var notifySpy = isSpyEnabled();
+ var notify = hasListeners(this);
+ var change = notify || notifySpy ? {
+ type: "add",
+ object: this,
+ name: name, newValue: newValue
+ } : null;
+ if (notifySpy) spyReportStart(change);
+ if (notify) notifyListeners(this, change);
+ if (notifySpy) spyReportEnd();
+ };
+ ObservableMap.prototype.get = function (key) {
+ key = "" + key;
+ if (this.has(key)) return this._data[key].get();
+ return undefined;
+ };
+ ObservableMap.prototype.keys = function () {
+ return arrayAsIterator(this._keys.slice());
+ };
+ ObservableMap.prototype.values = function () {
+ return arrayAsIterator(this._keys.map(this.get, this));
+ };
+ ObservableMap.prototype.entries = function () {
+ var _this = this;
+ return arrayAsIterator(this._keys.map(function (key) {
+ return [key, _this.get(key)];
+ }));
+ };
+ ObservableMap.prototype.forEach = function (callback, thisArg) {
+ var _this = this;
+ this.keys().forEach(function (key) {
+ return callback.call(thisArg, _this.get(key), key, _this);
+ });
+ };
+ ObservableMap.prototype.merge = function (other) {
+ var _this = this;
+ if (isObservableMap(other)) {
+ other = other.toJS();
+ }
+ runInTransaction(function () {
+ if (isPlainObject(other)) Object.keys(other).forEach(function (key) {
+ return _this.set(key, other[key]);
+ });else if (Array.isArray(other)) other.forEach(function (_a) {
+ var key = _a[0],
+ value = _a[1];
+ return _this.set(key, value);
+ });else if (isES6Map(other)) other.forEach(function (value, key) {
+ return _this.set(key, value);
+ });else if (other !== null && other !== undefined) fail("Cannot initialize map from " + other);
+ });
+ return this;
+ };
+ ObservableMap.prototype.clear = function () {
+ var _this = this;
+ runInTransaction(function () {
+ untracked(function () {
+ _this.keys().forEach(_this.delete, _this);
+ });
+ });
+ };
+ ObservableMap.prototype.replace = function (values) {
+ var _this = this;
+ runInTransaction(function () {
+ _this.clear();
+ _this.merge(values);
+ });
+ return this;
+ };
+ Object.defineProperty(ObservableMap.prototype, "size", {
+ get: function get() {
+ return this._keys.length;
+ },
+ enumerable: true,
+ configurable: true
+ });
+ ObservableMap.prototype.toJS = function () {
+ var _this = this;
+ var res = {};
+ this.keys().forEach(function (key) {
+ return res[key] = _this.get(key);
+ });
+ return res;
+ };
+ ObservableMap.prototype.toJSON = function () {
+ return this.toJS();
+ };
+ ObservableMap.prototype.isValidKey = function (key) {
+ if (key === null || key === undefined) return false;
+ if (typeof key === "string" || typeof key === "number" || typeof key === "boolean") return true;
+ return false;
+ };
+ ObservableMap.prototype.assertValidKey = function (key) {
+ if (!this.isValidKey(key)) throw new Error("[mobx.map] Invalid key: '" + key + "', only strings, numbers and booleans are accepted as key in observable maps.");
+ };
+ ObservableMap.prototype.toString = function () {
+ var _this = this;
+ return this.name + "[{ " + this.keys().map(function (key) {
+ return key + ": " + ("" + _this.get(key));
+ }).join(", ") + " }]";
+ };
+ ObservableMap.prototype.observe = function (listener, fireImmediately) {
+ invariant(fireImmediately !== true, getMessage("m033"));
+ return registerListener(this, listener);
+ };
+ ObservableMap.prototype.intercept = function (handler) {
+ return registerInterceptor(this, handler);
+ };
+ return ObservableMap;
+}();
+exports.ObservableMap = ObservableMap;
+declareIterator(ObservableMap.prototype, function () {
+ return this.entries();
+});
+function map(initialValues) {
+ deprecated("`mobx.map` is deprecated, use `new ObservableMap` or `mobx.observable.map` instead");
+ return observable.map(initialValues);
+}
+exports.map = map;
+var isObservableMap = createInstanceofPredicate("ObservableMap", ObservableMap);
+exports.isObservableMap = isObservableMap;
+var ObservableObjectAdministration = function () {
+ function ObservableObjectAdministration(target, name) {
+ this.target = target;
+ this.name = name;
+ this.values = {};
+ this.changeListeners = null;
+ this.interceptors = null;
+ }
+ ObservableObjectAdministration.prototype.observe = function (callback, fireImmediately) {
+ invariant(fireImmediately !== true, "`observe` doesn't support the fire immediately property for observable objects.");
+ return registerListener(this, callback);
+ };
+ ObservableObjectAdministration.prototype.intercept = function (handler) {
+ return registerInterceptor(this, handler);
+ };
+ return ObservableObjectAdministration;
+}();
+function asObservableObject(target, name) {
+ if (isObservableObject(target)) return target.$mobx;
+ invariant(Object.isExtensible(target), getMessage("m035"));
+ if (!isPlainObject(target)) name = (target.constructor.name || "ObservableObject") + "@" + getNextId();
+ if (!name) name = "ObservableObject@" + getNextId();
+ var adm = new ObservableObjectAdministration(target, name);
+ addHiddenFinalProp(target, "$mobx", adm);
+ return adm;
+}
+function defineObservablePropertyFromDescriptor(adm, propName, descriptor, defaultEnhancer) {
+ if (adm.values[propName]) {
+ invariant("value" in descriptor, "The property " + propName + " in " + adm.name + " is already observable, cannot redefine it as computed property");
+ adm.target[propName] = descriptor.value;
+ return;
+ }
+ if ("value" in descriptor) {
+ if (isModifierDescriptor(descriptor.value)) {
+ var modifierDescriptor = descriptor.value;
+ defineObservableProperty(adm, propName, modifierDescriptor.initialValue, modifierDescriptor.enhancer);
+ } else if (isAction(descriptor.value) && descriptor.value.autoBind === true) {
+ defineBoundAction(adm.target, propName, descriptor.value.originalFn);
+ } else if (isComputedValue(descriptor.value)) {
+ defineComputedPropertyFromComputedValue(adm, propName, descriptor.value);
+ } else {
+ defineObservableProperty(adm, propName, descriptor.value, defaultEnhancer);
+ }
+ } else {
+ defineComputedProperty(adm, propName, descriptor.get, descriptor.set, false, true);
+ }
+}
+function defineObservableProperty(adm, propName, newValue, enhancer) {
+ assertPropertyConfigurable(adm.target, propName);
+ if (hasInterceptors(adm)) {
+ var change = interceptChange(adm, {
+ object: adm.target,
+ name: propName,
+ type: "add",
+ newValue: newValue
+ });
+ if (!change) return;
+ newValue = change.newValue;
+ }
+ var observable = adm.values[propName] = new ObservableValue(newValue, enhancer, adm.name + "." + propName, false);
+ newValue = observable.value;
+ Object.defineProperty(adm.target, propName, generateObservablePropConfig(propName));
+ notifyPropertyAddition(adm, adm.target, propName, newValue);
+}
+function defineComputedProperty(adm, propName, getter, setter, compareStructural, asInstanceProperty) {
+ if (asInstanceProperty) assertPropertyConfigurable(adm.target, propName);
+ adm.values[propName] = new ComputedValue(getter, adm.target, compareStructural, adm.name + "." + propName, setter);
+ if (asInstanceProperty) {
+ Object.defineProperty(adm.target, propName, generateComputedPropConfig(propName));
+ }
+}
+function defineComputedPropertyFromComputedValue(adm, propName, computedValue) {
+ var name = adm.name + "." + propName;
+ computedValue.name = name;
+ if (!computedValue.scope) computedValue.scope = adm.target;
+ adm.values[propName] = computedValue;
+ Object.defineProperty(adm.target, propName, generateComputedPropConfig(propName));
+}
+var observablePropertyConfigs = {};
+var computedPropertyConfigs = {};
+function generateObservablePropConfig(propName) {
+ return observablePropertyConfigs[propName] || (observablePropertyConfigs[propName] = {
+ configurable: true,
+ enumerable: true,
+ get: function get() {
+ return this.$mobx.values[propName].get();
+ },
+ set: function set(v) {
+ setPropertyValue(this, propName, v);
+ }
+ });
+}
+function generateComputedPropConfig(propName) {
+ return computedPropertyConfigs[propName] || (computedPropertyConfigs[propName] = {
+ configurable: true,
+ enumerable: false,
+ get: function get() {
+ return this.$mobx.values[propName].get();
+ },
+ set: function set(v) {
+ return this.$mobx.values[propName].set(v);
+ }
+ });
+}
+function setPropertyValue(instance, name, newValue) {
+ var adm = instance.$mobx;
+ var observable = adm.values[name];
+ if (hasInterceptors(adm)) {
+ var change = interceptChange(adm, {
+ type: "update",
+ object: instance,
+ name: name, newValue: newValue
+ });
+ if (!change) return;
+ newValue = change.newValue;
+ }
+ newValue = observable.prepareNewValue(newValue);
+ if (newValue !== UNCHANGED) {
+ var notify = hasListeners(adm);
+ var notifySpy = isSpyEnabled();
+ var change = notify || notifySpy ? {
+ type: "update",
+ object: instance,
+ oldValue: observable.value,
+ name: name, newValue: newValue
+ } : null;
+ if (notifySpy) spyReportStart(change);
+ observable.setNewValue(newValue);
+ if (notify) notifyListeners(adm, change);
+ if (notifySpy) spyReportEnd();
+ }
+}
+function notifyPropertyAddition(adm, object, name, newValue) {
+ var notify = hasListeners(adm);
+ var notifySpy = isSpyEnabled();
+ var change = notify || notifySpy ? {
+ type: "add",
+ object: object, name: name, newValue: newValue
+ } : null;
+ if (notifySpy) spyReportStart(change);
+ if (notify) notifyListeners(adm, change);
+ if (notifySpy) spyReportEnd();
+}
+var isObservableObjectAdministration = createInstanceofPredicate("ObservableObjectAdministration", ObservableObjectAdministration);
+function isObservableObject(thing) {
+ if (isObject(thing)) {
+ runLazyInitializers(thing);
+ return isObservableObjectAdministration(thing.$mobx);
+ }
+ return false;
+}
+exports.isObservableObject = isObservableObject;
+var UNCHANGED = {};
+var ObservableValue = function (_super) {
+ __extends(ObservableValue, _super);
+ function ObservableValue(value, enhancer, name, notifySpy) {
+ if (name === void 0) {
+ name = "ObservableValue@" + getNextId();
+ }
+ if (notifySpy === void 0) {
+ notifySpy = true;
+ }
+ var _this = _super.call(this, name) || this;
+ _this.enhancer = enhancer;
+ _this.hasUnreportedChange = false;
+ _this.value = enhancer(value, undefined, name);
+ if (notifySpy && isSpyEnabled()) {
+ spyReport({ type: "create", object: _this, newValue: _this.value });
+ }
+ return _this;
+ }
+ ObservableValue.prototype.set = function (newValue) {
+ var oldValue = this.value;
+ newValue = this.prepareNewValue(newValue);
+ if (newValue !== UNCHANGED) {
+ var notifySpy = isSpyEnabled();
+ if (notifySpy) {
+ spyReportStart({
+ type: "update",
+ object: this,
+ newValue: newValue, oldValue: oldValue
+ });
+ }
+ this.setNewValue(newValue);
+ if (notifySpy) spyReportEnd();
+ }
+ };
+ ObservableValue.prototype.prepareNewValue = function (newValue) {
+ checkIfStateModificationsAreAllowed(this);
+ if (hasInterceptors(this)) {
+ var change = interceptChange(this, { object: this, type: "update", newValue: newValue });
+ if (!change) return UNCHANGED;
+ newValue = change.newValue;
+ }
+ newValue = this.enhancer(newValue, this.value, this.name);
+ return this.value !== newValue ? newValue : UNCHANGED;
+ };
+ ObservableValue.prototype.setNewValue = function (newValue) {
+ var oldValue = this.value;
+ this.value = newValue;
+ this.reportChanged();
+ if (hasListeners(this)) {
+ notifyListeners(this, {
+ type: "update",
+ object: this,
+ newValue: newValue,
+ oldValue: oldValue
+ });
+ }
+ };
+ ObservableValue.prototype.get = function () {
+ this.reportObserved();
+ return this.value;
+ };
+ ObservableValue.prototype.intercept = function (handler) {
+ return registerInterceptor(this, handler);
+ };
+ ObservableValue.prototype.observe = function (listener, fireImmediately) {
+ if (fireImmediately) listener({
+ object: this,
+ type: "update",
+ newValue: this.value,
+ oldValue: undefined
+ });
+ return registerListener(this, listener);
+ };
+ ObservableValue.prototype.toJSON = function () {
+ return this.get();
+ };
+ ObservableValue.prototype.toString = function () {
+ return this.name + "[" + this.value + "]";
+ };
+ ObservableValue.prototype.valueOf = function () {
+ return toPrimitive(this.get());
+ };
+ return ObservableValue;
+}(BaseAtom);
+ObservableValue.prototype[primitiveSymbol()] = ObservableValue.prototype.valueOf;
+var isObservableValue = createInstanceofPredicate("ObservableValue", ObservableValue);
+exports.isBoxedObservable = isObservableValue;
+function getAtom(thing, property) {
+ if ((typeof thing === "undefined" ? "undefined" : _typeof(thing)) === "object" && thing !== null) {
+ if (isObservableArray(thing)) {
+ invariant(property === undefined, getMessage("m036"));
+ return thing.$mobx.atom;
+ }
+ if (isObservableMap(thing)) {
+ var anyThing = thing;
+ if (property === undefined) return getAtom(anyThing._keys);
+ var observable_2 = anyThing._data[property] || anyThing._hasMap[property];
+ invariant(!!observable_2, "the entry '" + property + "' does not exist in the observable map '" + getDebugName(thing) + "'");
+ return observable_2;
+ }
+ runLazyInitializers(thing);
+ if (isObservableObject(thing)) {
+ if (!property) return fail("please specify a property");
+ var observable_3 = thing.$mobx.values[property];
+ invariant(!!observable_3, "no observable property '" + property + "' found on the observable object '" + getDebugName(thing) + "'");
+ return observable_3;
+ }
+ if (isAtom(thing) || isComputedValue(thing) || isReaction(thing)) {
+ return thing;
+ }
+ } else if (typeof thing === "function") {
+ if (isReaction(thing.$mobx)) {
+ return thing.$mobx;
+ }
+ }
+ return fail("Cannot obtain atom from " + thing);
+}
+function getAdministration(thing, property) {
+ invariant(thing, "Expecting some object");
+ if (property !== undefined) return getAdministration(getAtom(thing, property));
+ if (isAtom(thing) || isComputedValue(thing) || isReaction(thing)) return thing;
+ if (isObservableMap(thing)) return thing;
+ runLazyInitializers(thing);
+ if (thing.$mobx) return thing.$mobx;
+ invariant(false, "Cannot obtain administration from " + thing);
+}
+function getDebugName(thing, property) {
+ var named;
+ if (property !== undefined) named = getAtom(thing, property);else if (isObservableObject(thing) || isObservableMap(thing)) named = getAdministration(thing);else named = getAtom(thing);
+ return named.name;
+}
+function createClassPropertyDecorator(onInitialize, _get, _set, enumerable, allowCustomArguments) {
+ function classPropertyDecorator(target, key, descriptor, customArgs, argLen) {
+ if (argLen === void 0) {
+ argLen = 0;
+ }
+ invariant(allowCustomArguments || quacksLikeADecorator(arguments), "This function is a decorator, but it wasn't invoked like a decorator");
+ if (!descriptor) {
+ var newDescriptor = {
+ enumerable: enumerable,
+ configurable: true,
+ get: function get() {
+ if (!this.__mobxInitializedProps || this.__mobxInitializedProps[key] !== true) typescriptInitializeProperty(this, key, undefined, onInitialize, customArgs, descriptor);
+ return _get.call(this, key);
+ },
+ set: function set(v) {
+ if (!this.__mobxInitializedProps || this.__mobxInitializedProps[key] !== true) {
+ typescriptInitializeProperty(this, key, v, onInitialize, customArgs, descriptor);
+ } else {
+ _set.call(this, key, v);
+ }
+ }
+ };
+ if (arguments.length < 3 || arguments.length === 5 && argLen < 3) {
+ Object.defineProperty(target, key, newDescriptor);
+ }
+ return newDescriptor;
+ } else {
+ if (!hasOwnProperty(target, "__mobxLazyInitializers")) {
+ addHiddenProp(target, "__mobxLazyInitializers", target.__mobxLazyInitializers && target.__mobxLazyInitializers.slice() || []);
+ }
+ var value_1 = descriptor.value,
+ initializer_1 = descriptor.initializer;
+ target.__mobxLazyInitializers.push(function (instance) {
+ onInitialize(instance, key, initializer_1 ? initializer_1.call(instance) : value_1, customArgs, descriptor);
+ });
+ return {
+ enumerable: enumerable, configurable: true,
+ get: function get() {
+ if (this.__mobxDidRunLazyInitializers !== true) runLazyInitializers(this);
+ return _get.call(this, key);
+ },
+ set: function set(v) {
+ if (this.__mobxDidRunLazyInitializers !== true) runLazyInitializers(this);
+ _set.call(this, key, v);
+ }
+ };
+ }
+ }
+ if (allowCustomArguments) {
+ return function () {
+ if (quacksLikeADecorator(arguments)) return classPropertyDecorator.apply(null, arguments);
+ var outerArgs = arguments;
+ var argLen = arguments.length;
+ return function (target, key, descriptor) {
+ return classPropertyDecorator(target, key, descriptor, outerArgs, argLen);
+ };
+ };
+ }
+ return classPropertyDecorator;
+}
+function typescriptInitializeProperty(instance, key, v, onInitialize, customArgs, baseDescriptor) {
+ if (!hasOwnProperty(instance, "__mobxInitializedProps")) addHiddenProp(instance, "__mobxInitializedProps", {});
+ instance.__mobxInitializedProps[key] = true;
+ onInitialize(instance, key, v, customArgs, baseDescriptor);
+}
+function runLazyInitializers(instance) {
+ if (instance.__mobxDidRunLazyInitializers === true) return;
+ if (instance.__mobxLazyInitializers) {
+ addHiddenProp(instance, "__mobxDidRunLazyInitializers", true);
+ instance.__mobxDidRunLazyInitializers && instance.__mobxLazyInitializers.forEach(function (initializer) {
+ return initializer(instance);
+ });
+ }
+}
+function quacksLikeADecorator(args) {
+ return (args.length === 2 || args.length === 3) && typeof args[1] === "string";
+}
+function iteratorSymbol() {
+ return typeof Symbol === "function" && Symbol.iterator || "@@iterator";
+}
+var IS_ITERATING_MARKER = "__$$iterating";
+function arrayAsIterator(array) {
+ invariant(array[IS_ITERATING_MARKER] !== true, "Illegal state: cannot recycle array as iterator");
+ addHiddenFinalProp(array, IS_ITERATING_MARKER, true);
+ var idx = -1;
+ addHiddenFinalProp(array, "next", function next() {
+ idx++;
+ return {
+ done: idx >= this.length,
+ value: idx < this.length ? this[idx] : undefined
+ };
+ });
+ return array;
+}
+function declareIterator(prototType, iteratorFactory) {
+ addHiddenFinalProp(prototType, iteratorSymbol(), iteratorFactory);
+}
+var messages = {
+ "m001": "It is not allowed to assign new values to @action fields",
+ "m002": "`runInAction` expects a function",
+ "m003": "`runInAction` expects a function without arguments",
+ "m004": "autorun expects a function",
+ "m005": "Warning: attempted to pass an action to autorun. Actions are untracked and will not trigger on state changes. Use `reaction` or wrap only your state modification code in an action.",
+ "m006": "Warning: attempted to pass an action to autorunAsync. Actions are untracked and will not trigger on state changes. Use `reaction` or wrap only your state modification code in an action.",
+ "m007": "reaction only accepts 2 or 3 arguments. If migrating from MobX 2, please provide an options object",
+ "m008": "wrapping reaction expression in `asReference` is no longer supported, use options object instead",
+ "m009": "@computed can only be used on getter functions, like: '@computed get myProps() { return ...; }'. It looks like it was used on a property.",
+ "m010": "@computed can only be used on getter functions, like: '@computed get myProps() { return ...; }'",
+ "m011": "First argument to `computed` should be an expression. If using computed as decorator, don't pass it arguments",
+ "m012": "computed takes one or two arguments if used as function",
+ "m013": "[mobx.expr] 'expr' should only be used inside other reactive functions.",
+ "m014": "extendObservable expected 2 or more arguments",
+ "m015": "extendObservable expects an object as first argument",
+ "m016": "extendObservable should not be used on maps, use map.merge instead",
+ "m017": "all arguments of extendObservable should be objects",
+ "m018": "extending an object with another observable (object) is not supported. Please construct an explicit propertymap, using `toJS` if need. See issue #540",
+ "m019": "[mobx.isObservable] isObservable(object, propertyName) is not supported for arrays and maps. Use map.has or array.length instead.",
+ "m020": "modifiers can only be used for individual object properties",
+ "m021": "observable expects zero or one arguments",
+ "m022": "@observable can not be used on getters, use @computed instead",
+ "m023": "Using `transaction` is deprecated, use `runInAction` or `(@)action` instead.",
+ "m024": "whyRun() can only be used if a derivation is active, or by passing an computed value / reaction explicitly. If you invoked whyRun from inside a computation; the computation is currently suspended but re-evaluating because somebody requested its value.",
+ "m025": "whyRun can only be used on reactions and computed values",
+ "m026": "`action` can only be invoked on functions",
+ "m028": "It is not allowed to set `useStrict` when a derivation is running",
+ "m029": "INTERNAL ERROR only onBecomeUnobserved shouldn't be called twice in a row",
+ "m030a": "Since strict-mode is enabled, changing observed observable values outside actions is not allowed. Please wrap the code in an `action` if this change is intended. Tried to modify: ",
+ "m030b": "Side effects like changing state are not allowed at this point. Are you trying to modify state from, for example, the render function of a React component? Tried to modify: ",
+ "m031": "Computed values are not allowed to not cause side effects by changing observables that are already being observed. Tried to modify: ",
+ "m032": "* This computation is suspended (not in use by any reaction) and won't run automatically.\n Didn't expect this computation to be suspended at this point?\n 1. Make sure this computation is used by a reaction (reaction, autorun, observer).\n 2. Check whether you are using this computation synchronously (in the same stack as they reaction that needs it).",
+ "m033": "`observe` doesn't support the fire immediately property for observable maps.",
+ "m034": "`mobx.map` is deprecated, use `new ObservableMap` or `mobx.observable.map` instead",
+ "m035": "Cannot make the designated object observable; it is not extensible",
+ "m036": "It is not possible to get index atoms from arrays",
+ "m037": "Hi there! I'm sorry you have just run into an exception.\nIf your debugger ends up here, know that some reaction (like the render() of an observer component, autorun or reaction)\nthrew an exception and that mobx caught it, to avoid that it brings the rest of your application down.\nThe original cause of the exception (the code that caused this reaction to run (again)), is still in the stack.\n\nHowever, more interesting is the actual stack trace of the error itself.\nHopefully the error is an instanceof Error, because in that case you can inspect the original stack of the error from where it was thrown.\nSee `error.stack` property, or press the very subtle \"(...)\" link you see near the console.error message that probably brought you here.\nThat stack is more interesting than the stack of this console.error itself.\n\nIf the exception you see is an exception you created yourself, make sure to use `throw new Error(\"Oops\")` instead of `throw \"Oops\"`,\nbecause the javascript environment will only preserve the original stack trace in the first form.\n\nYou can also make sure the debugger pauses the next time this very same exception is thrown by enabling \"Pause on caught exception\".\n(Note that it might pause on many other, unrelated exception as well).\n\nIf that all doesn't help you out, feel free to open an issue https://github.com/mobxjs/mobx/issues!\n",
+ "m038": "Missing items in this list?\n 1. Check whether all used values are properly marked as observable (use isObservable to verify)\n 2. Make sure you didn't dereference values too early. MobX observes props, not primitives. E.g: use 'person.name' instead of 'name' in your computation.\n"
+};
+function getMessage(id) {
+ return messages[id];
+}
+var EMPTY_ARRAY = [];
+Object.freeze(EMPTY_ARRAY);
+function getGlobal() {
+ return global;
+}
+function getNextId() {
+ return ++globalState.mobxGuid;
+}
+function fail(message, thing) {
+ invariant(false, message, thing);
+ throw "X";
+}
+function invariant(check, message, thing) {
+ if (!check) throw new Error("[mobx] Invariant failed: " + message + (thing ? " in '" + thing + "'" : ""));
+}
+var deprecatedMessages = [];
+function deprecated(msg) {
+ if (deprecatedMessages.indexOf(msg) !== -1) return false;
+ deprecatedMessages.push(msg);
+ console.error("[mobx] Deprecated: " + msg);
+ return true;
+}
+function once(func) {
+ var invoked = false;
+ return function () {
+ if (invoked) return;
+ invoked = true;
+ return func.apply(this, arguments);
+ };
+}
+var noop = function noop() {};
+function unique(list) {
+ var res = [];
+ list.forEach(function (item) {
+ if (res.indexOf(item) === -1) res.push(item);
+ });
+ return res;
+}
+function joinStrings(things, limit, separator) {
+ if (limit === void 0) {
+ limit = 100;
+ }
+ if (separator === void 0) {
+ separator = " - ";
+ }
+ if (!things) return "";
+ var sliced = things.slice(0, limit);
+ return "" + sliced.join(separator) + (things.length > limit ? " (... and " + (things.length - limit) + "more)" : "");
+}
+function isObject(value) {
+ return value !== null && (typeof value === "undefined" ? "undefined" : _typeof(value)) === "object";
+}
+function isPlainObject(value) {
+ if (value === null || (typeof value === "undefined" ? "undefined" : _typeof(value)) !== "object") return false;
+ var proto = Object.getPrototypeOf(value);
+ return proto === Object.prototype || proto === null;
+}
+function objectAssign() {
+ var res = arguments[0];
+ for (var i = 1, l = arguments.length; i < l; i++) {
+ var source = arguments[i];
+ for (var key in source) {
+ if (hasOwnProperty(source, key)) {
+ res[key] = source[key];
+ }
+ }
+ }
+ return res;
+}
+function valueDidChange(compareStructural, oldValue, newValue) {
+ if (typeof oldValue === 'number' && isNaN(oldValue)) {
+ return typeof newValue !== 'number' || !isNaN(newValue);
+ }
+ return compareStructural ? !deepEqual(oldValue, newValue) : oldValue !== newValue;
+}
+var prototypeHasOwnProperty = Object.prototype.hasOwnProperty;
+function hasOwnProperty(object, propName) {
+ return prototypeHasOwnProperty.call(object, propName);
+}
+function makeNonEnumerable(object, propNames) {
+ for (var i = 0; i < propNames.length; i++) {
+ addHiddenProp(object, propNames[i], object[propNames[i]]);
+ }
+}
+function addHiddenProp(object, propName, value) {
+ Object.defineProperty(object, propName, {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+ value: value
+ });
+}
+function addHiddenFinalProp(object, propName, value) {
+ Object.defineProperty(object, propName, {
+ enumerable: false,
+ writable: false,
+ configurable: true,
+ value: value
+ });
+}
+function isPropertyConfigurable(object, prop) {
+ var descriptor = Object.getOwnPropertyDescriptor(object, prop);
+ return !descriptor || descriptor.configurable !== false && descriptor.writable !== false;
+}
+function assertPropertyConfigurable(object, prop) {
+ invariant(isPropertyConfigurable(object, prop), "Cannot make property '" + prop + "' observable, it is not configurable and writable in the target object");
+}
+function getEnumerableKeys(obj) {
+ var res = [];
+ for (var key in obj) {
+ res.push(key);
+ }return res;
+}
+function deepEqual(a, b) {
+ if (a === null && b === null) return true;
+ if (a === undefined && b === undefined) return true;
+ if ((typeof a === "undefined" ? "undefined" : _typeof(a)) !== "object") return a === b;
+ var aIsArray = isArrayLike(a);
+ var aIsMap = isMapLike(a);
+ if (aIsArray !== isArrayLike(b)) {
+ return false;
+ } else if (aIsMap !== isMapLike(b)) {
+ return false;
+ } else if (aIsArray) {
+ if (a.length !== b.length) return false;
+ for (var i = a.length - 1; i >= 0; i--) {
+ if (!deepEqual(a[i], b[i])) return false;
+ }return true;
+ } else if (aIsMap) {
+ if (a.size !== b.size) return false;
+ var equals_1 = true;
+ a.forEach(function (value, key) {
+ equals_1 = equals_1 && deepEqual(b.get(key), value);
+ });
+ return equals_1;
+ } else if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object" && (typeof b === "undefined" ? "undefined" : _typeof(b)) === "object") {
+ if (a === null || b === null) return false;
+ if (isMapLike(a) && isMapLike(b)) {
+ if (a.size !== b.size) return false;
+ return deepEqual(observable.shallowMap(a).entries(), observable.shallowMap(b).entries());
+ }
+ if (getEnumerableKeys(a).length !== getEnumerableKeys(b).length) return false;
+ for (var prop in a) {
+ if (!(prop in b)) return false;
+ if (!deepEqual(a[prop], b[prop])) return false;
+ }
+ return true;
+ }
+ return false;
+}
+function createInstanceofPredicate(name, clazz) {
+ var propName = "isMobX" + name;
+ clazz.prototype[propName] = true;
+ return function (x) {
+ return isObject(x) && x[propName] === true;
+ };
+}
+function isArrayLike(x) {
+ return Array.isArray(x) || isObservableArray(x);
+}
+exports.isArrayLike = isArrayLike;
+function isMapLike(x) {
+ return isES6Map(x) || isObservableMap(x);
+}
+function isES6Map(thing) {
+ if (getGlobal().Map !== undefined && thing instanceof getGlobal().Map) return true;
+ return false;
+}
+function primitiveSymbol() {
+ return typeof Symbol === "function" && Symbol.toPrimitive || "@@toPrimitive";
+}
+function toPrimitive(value) {
+ return value === null ? null : (typeof value === "undefined" ? "undefined" : _typeof(value)) === "object" ? "" + value : value;
+}
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
+
+/***/ }),
+/* 2 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
+
+var _icons = __webpack_require__(6);
+
+var _constants = __webpack_require__(0);
+
+function renderHeader(_ref, instance) {
+ var meta = _ref.meta,
+ user = _ref.user,
+ reactions = _ref.reactions;
+
+ var container = document.createElement('div');
+ container.lang = "en-US";
+ container.className = 'gitment-container gitment-header-container';
+
+ var likeButton = document.createElement('span');
+ var likedReaction = reactions.find(function (reaction) {
+ return reaction.content === 'heart' && reaction.user.login === user.login;
+ });
+ likeButton.className = 'gitment-header-like-btn';
+ likeButton.innerHTML = '\n ' + _icons.heart + '\n ' + (likedReaction ? 'Unlike' : 'Like') + '\n ' + (meta.reactions && meta.reactions.heart ? ' \u2022 ' + meta.reactions.heart + ' Liked' : '') + '\n ';
+
+ if (likedReaction) {
+ likeButton.classList.add('liked');
+ likeButton.onclick = function () {
+ return instance.unlike();
+ };
+ } else {
+ likeButton.classList.remove('liked');
+ likeButton.onclick = function () {
+ return instance.like();
+ };
+ }
+ container.appendChild(likeButton);
+
+ var commentsCount = document.createElement('span');
+ commentsCount.innerHTML = '\n ' + (meta.comments ? ' \u2022 ' + meta.comments + ' Comments' : '') + '\n ';
+ container.appendChild(commentsCount);
+
+ var issueLink = document.createElement('a');
+ issueLink.className = 'gitment-header-issue-link';
+ issueLink.href = meta.html_url;
+ issueLink.target = '_blank';
+ issueLink.innerText = 'Issue Page';
+ container.appendChild(issueLink);
+
+ return container;
+}
+
+function renderComments(_ref2, instance) {
+ var meta = _ref2.meta,
+ comments = _ref2.comments,
+ commentReactions = _ref2.commentReactions,
+ currentPage = _ref2.currentPage,
+ user = _ref2.user,
+ error = _ref2.error;
+
+ var container = document.createElement('div');
+ container.lang = "en-US";
+ container.className = 'gitment-container gitment-comments-container';
+
+ if (error) {
+ var errorBlock = document.createElement('div');
+ errorBlock.className = 'gitment-comments-error';
+
+ if (error === _constants.NOT_INITIALIZED_ERROR && user.login && user.login.toLowerCase() === instance.owner.toLowerCase()) {
+ var initHint = document.createElement('div');
+ var initButton = document.createElement('button');
+ initButton.className = 'gitment-comments-init-btn';
+ initButton.onclick = function () {
+ initButton.setAttribute('disabled', true);
+ instance.init().catch(function (e) {
+ initButton.removeAttribute('disabled');
+ alert(e);
+ });
+ };
+ initButton.innerText = 'Initialize Comments';
+ initHint.appendChild(initButton);
+ errorBlock.appendChild(initHint);
+ } else {
+ errorBlock.innerText = error;
+ }
+ container.appendChild(errorBlock);
+ return container;
+ } else if (comments === undefined) {
+ var loading = document.createElement('div');
+ loading.innerText = 'Loading comments...';
+ loading.className = 'gitment-comments-loading';
+ container.appendChild(loading);
+ return container;
+ } else if (!comments.length) {
+ var emptyBlock = document.createElement('div');
+ emptyBlock.className = 'gitment-comments-empty';
+ emptyBlock.innerText = 'No Comment Yet';
+ container.appendChild(emptyBlock);
+ return container;
+ }
+
+ var commentsList = document.createElement('ul');
+ commentsList.className = 'gitment-comments-list';
+
+ comments.forEach(function (comment) {
+ var createDate = new Date(comment.created_at);
+ var updateDate = new Date(comment.updated_at);
+ var commentItem = document.createElement('li');
+ commentItem.className = 'gitment-comment';
+ commentItem.innerHTML = '\n \n \n \n
' + comment.body_html + '
\n
\n ';
+ var likeButton = commentItem.querySelector('.gitment-comment-like-btn');
+ var likedReaction = commentReactions[comment.id] && commentReactions[comment.id].find(function (reaction) {
+ return reaction.content === 'heart' && reaction.user.login === user.login;
+ });
+ if (likedReaction) {
+ likeButton.classList.add('liked');
+ likeButton.onclick = function () {
+ return instance.unlikeAComment(comment.id);
+ };
+ } else {
+ likeButton.classList.remove('liked');
+ likeButton.onclick = function () {
+ return instance.likeAComment(comment.id);
+ };
+ }
+
+ // dirty
+ // use a blank image to trigger height calculating when element rendered
+ var imgTrigger = document.createElement('img');
+ var markdownBody = commentItem.querySelector('.gitment-comment-body');
+ imgTrigger.className = 'gitment-hidden';
+ imgTrigger.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
+ imgTrigger.onload = function () {
+ if (markdownBody.clientHeight > instance.maxCommentHeight) {
+ markdownBody.classList.add('gitment-comment-body-folded');
+ markdownBody.style.maxHeight = instance.maxCommentHeight + 'px';
+ markdownBody.title = 'Click to Expand';
+ markdownBody.onclick = function () {
+ markdownBody.classList.remove('gitment-comment-body-folded');
+ markdownBody.style.maxHeight = '';
+ markdownBody.title = '';
+ markdownBody.onclick = null;
+ };
+ }
+ };
+ commentItem.appendChild(imgTrigger);
+
+ commentsList.appendChild(commentItem);
+ });
+
+ container.appendChild(commentsList);
+
+ if (meta) {
+ var pageCount = Math.ceil(meta.comments / instance.perPage);
+ if (pageCount > 1) {
+ var pagination = document.createElement('ul');
+ pagination.className = 'gitment-comments-pagination';
+
+ if (currentPage > 1) {
+ var previousButton = document.createElement('li');
+ previousButton.className = 'gitment-comments-page-item';
+ previousButton.innerText = 'Previous';
+ previousButton.onclick = function () {
+ return instance.goto(currentPage - 1);
+ };
+ pagination.appendChild(previousButton);
+ }
+
+ var _loop = function _loop(i) {
+ var pageItem = document.createElement('li');
+ pageItem.className = 'gitment-comments-page-item';
+ pageItem.innerText = i;
+ pageItem.onclick = function () {
+ return instance.goto(i);
+ };
+ if (currentPage === i) pageItem.classList.add('gitment-selected');
+ pagination.appendChild(pageItem);
+ };
+
+ for (var i = 1; i <= pageCount; i++) {
+ _loop(i);
+ }
+
+ if (currentPage < pageCount) {
+ var nextButton = document.createElement('li');
+ nextButton.className = 'gitment-comments-page-item';
+ nextButton.innerText = 'Next';
+ nextButton.onclick = function () {
+ return instance.goto(currentPage + 1);
+ };
+ pagination.appendChild(nextButton);
+ }
+
+ container.appendChild(pagination);
+ }
+ }
+
+ return container;
+}
+
+function renderEditor(_ref3, instance) {
+ var user = _ref3.user,
+ error = _ref3.error;
+
+ var container = document.createElement('div');
+ container.lang = "en-US";
+ container.className = 'gitment-container gitment-editor-container';
+
+ var shouldDisable = user.login && !error ? '' : 'disabled';
+ var disabledTip = user.login ? '' : 'Login to Comment';
+ container.innerHTML = '\n ' + (user.login ? '\n \n ' : user.isLoggingIn ? '' + _icons.spinner + '
' : '\n ' + _icons.github + '\n ') + '\n \n \n \n ';
+ if (user.login) {
+ container.querySelector('.gitment-editor-logout-link').onclick = function () {
+ return instance.logout();
+ };
+ }
+
+ var writeField = container.querySelector('.gitment-editor-write-field');
+ var previewField = container.querySelector('.gitment-editor-preview-field');
+
+ var textarea = writeField.querySelector('textarea');
+ textarea.oninput = function () {
+ textarea.style.height = 'auto';
+ var style = window.getComputedStyle(textarea, null);
+ var height = parseInt(style.height, 10);
+ var clientHeight = textarea.clientHeight;
+ var scrollHeight = textarea.scrollHeight;
+ if (clientHeight < scrollHeight) {
+ textarea.style.height = height + scrollHeight - clientHeight + 'px';
+ }
+ };
+
+ var _container$querySelec = container.querySelectorAll('.gitment-editor-tab'),
+ _container$querySelec2 = _slicedToArray(_container$querySelec, 2),
+ writeTab = _container$querySelec2[0],
+ previewTab = _container$querySelec2[1];
+
+ writeTab.onclick = function () {
+ writeTab.classList.add('gitment-selected');
+ previewTab.classList.remove('gitment-selected');
+ writeField.classList.remove('gitment-hidden');
+ previewField.classList.add('gitment-hidden');
+
+ textarea.focus();
+ };
+ previewTab.onclick = function () {
+ previewTab.classList.add('gitment-selected');
+ writeTab.classList.remove('gitment-selected');
+ previewField.classList.remove('gitment-hidden');
+ writeField.classList.add('gitment-hidden');
+
+ var preview = previewField.querySelector('.gitment-editor-preview');
+ var content = textarea.value.trim();
+ if (!content) {
+ preview.innerText = 'Nothing to preview';
+ return;
+ }
+
+ preview.innerText = 'Loading preview...';
+ instance.markdown(content).then(function (html) {
+ return preview.innerHTML = html;
+ });
+ };
+
+ var submitButton = container.querySelector('.gitment-editor-submit');
+ submitButton.onclick = function () {
+ submitButton.innerText = 'Submitting...';
+ submitButton.setAttribute('disabled', true);
+ instance.post(textarea.value.trim()).then(function (data) {
+ textarea.value = '';
+ textarea.style.height = 'auto';
+ submitButton.removeAttribute('disabled');
+ submitButton.innerText = 'Comment';
+ }).catch(function (e) {
+ alert(e);
+ submitButton.removeAttribute('disabled');
+ submitButton.innerText = 'Comment';
+ });
+ };
+
+ return container;
+}
+
+function renderFooter() {
+ var container = document.createElement('div');
+ container.lang = "en-US";
+ container.className = 'gitment-container gitment-footer-container';
+ container.innerHTML = '\n Powered by\n \n ';
+ return container;
+}
+
+function render(state, instance) {
+ var container = document.createElement('div');
+ container.lang = "en-US";
+ container.className = 'gitment-container gitment-root-container';
+ container.appendChild(instance.renderHeader(state, instance));
+ container.appendChild(instance.renderComments(state, instance));
+ container.appendChild(instance.renderEditor(state, instance));
+ container.appendChild(instance.renderFooter(state, instance));
+ return container;
+}
+
+exports.default = { render: render, renderHeader: renderHeader, renderComments: renderComments, renderEditor: renderEditor, renderFooter: renderFooter };
+
+/***/ }),
+/* 3 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.http = exports.Query = exports.isString = undefined;
+
+var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
+
+exports.getTargetContainer = getTargetContainer;
+
+var _constants = __webpack_require__(0);
+
+var isString = exports.isString = function isString(s) {
+ return toString.call(s) === '[object String]';
+};
+
+function getTargetContainer(container) {
+ var targetContainer = void 0;
+ if (container instanceof Element) {
+ targetContainer = container;
+ } else if (isString(container)) {
+ targetContainer = document.getElementById(container);
+ } else {
+ targetContainer = document.createElement('div');
+ }
+
+ return targetContainer;
+}
+
+var Query = exports.Query = {
+ parse: function parse() {
+ var search = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window.location.search;
+
+ if (!search) return {};
+ var queryString = search[0] === '?' ? search.substring(1) : search;
+ var query = {};
+ queryString.split('&').forEach(function (queryStr) {
+ var _queryStr$split = queryStr.split('='),
+ _queryStr$split2 = _slicedToArray(_queryStr$split, 2),
+ key = _queryStr$split2[0],
+ value = _queryStr$split2[1];
+
+ if (key) query[key] = value;
+ });
+
+ return query;
+ },
+ stringify: function stringify(query) {
+ var prefix = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '?';
+
+ var queryString = Object.keys(query).map(function (key) {
+ return key + '=' + encodeURIComponent(query[key] || '');
+ }).join('&');
+ return queryString ? prefix + queryString : '';
+ }
+};
+
+function ajaxFactory(method) {
+ return function (apiPath) {
+ var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+ var base = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'https://api.github.com';
+
+ var headers = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
+
+ var req = new XMLHttpRequest();
+ var token = localStorage.getItem(_constants.LS_ACCESS_TOKEN_KEY);
+
+ var url = '' + base + apiPath;
+ var body = null;
+ if (method === 'GET' || method === 'DELETE') {
+ url += Query.stringify(data);
+ }
+
+ var p = new Promise(function (resolve, reject) {
+ req.addEventListener('load', function () {
+ var contentType = req.getResponseHeader('content-type');
+ var res = req.responseText;
+ if (!/json/.test(contentType)) {
+ resolve(res);
+ return;
+ }
+ var data = req.responseText ? JSON.parse(res) : {};
+ if (data.message) {
+ reject(new Error(data.message));
+ } else {
+ resolve(data);
+ }
+ });
+ req.addEventListener('error', function (error) {
+ return reject(error);
+ });
+ });
+ req.open(method, url, true);
+
+ req.setRequestHeader('Accept', 'application/vnd.github.squirrel-girl-preview, application/vnd.github.html+json');
+ if (token) {
+ req.setRequestHeader('Authorization', 'token ' + token);
+ }
+ if (method !== 'GET' && method !== 'DELETE') {
+ body = JSON.stringify(data);
+ req.setRequestHeader('Content-Type', 'application/json');
+ }
+
+ //设置自定义的 header
+ for(let header in headers){
+ req.setRequestHeader([header], headers[header]);
+ }
+
+ req.send(body);
+ return p;
+ };
+}
+
+var http = exports.http = {
+ get: ajaxFactory('GET'),
+ post: ajaxFactory('POST'),
+ delete: ajaxFactory('DELETE'),
+ put: ajaxFactory('PUT')
+};
+
+/***/ }),
+/* 4 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+var g;
+
+// This works in non-strict mode
+g = function () {
+ return this;
+}();
+
+try {
+ // This works if eval is allowed (see CSP)
+ g = g || Function("return this")() || (1, eval)("this");
+} catch (e) {
+ // This works if the window reference is available
+ if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === "object") g = window;
+}
+
+// g can still be undefined, but nothing to do about it...
+// We return undefined, instead of nothing here, so it's
+// easier to handle this case. if(!global) { ...}
+
+module.exports = g;
+
+/***/ }),
+/* 5 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+var _mobx = __webpack_require__(1);
+
+var _constants = __webpack_require__(0);
+
+var _utils = __webpack_require__(3);
+
+var _default = __webpack_require__(2);
+
+var _default2 = _interopRequireDefault(_default);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var scope = 'public_repo';
+
+function extendRenderer(instance, renderer) {
+ instance[renderer] = function (container) {
+ var targetContainer = (0, _utils.getTargetContainer)(container);
+ var render = instance.theme[renderer] || instance.defaultTheme[renderer];
+
+ (0, _mobx.autorun)(function () {
+ var e = render(instance.state, instance);
+ if (targetContainer.firstChild) {
+ targetContainer.replaceChild(e, targetContainer.firstChild);
+ } else {
+ targetContainer.appendChild(e);
+ }
+ });
+
+ return targetContainer;
+ };
+}
+
+var Gitment = function () {
+ _createClass(Gitment, [{
+ key: 'accessToken',
+ get: function get() {
+ return localStorage.getItem(_constants.LS_ACCESS_TOKEN_KEY);
+ },
+ set: function set(token) {
+ localStorage.setItem(_constants.LS_ACCESS_TOKEN_KEY, token);
+ }
+ }, {
+ key: 'loginLink',
+ get: function get() {
+ var oauthUri = 'https://github.com/login/oauth/authorize';
+ var redirect_uri = this.oauth.redirect_uri || window.location.href;
+
+ var oauthParams = Object.assign({
+ scope: scope,
+ redirect_uri: redirect_uri
+ }, this.oauth);
+
+ return '' + oauthUri + _utils.Query.stringify(oauthParams);
+ }
+ }]);
+
+ function Gitment() {
+ var _this = this;
+
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+
+ _classCallCheck(this, Gitment);
+
+ this.defaultTheme = _default2.default;
+ this.useTheme(_default2.default);
+
+ Object.assign(this, {
+ id: window.location.href,
+ title: window.document.title,
+ link: window.location.href,
+ desc: '',
+ labels: [],
+ theme: _default2.default,
+ oauth: {},
+ perPage: 20,
+ maxCommentHeight: 250
+ }, options);
+
+ this.useTheme(this.theme);
+
+ var user = {};
+ try {
+ var userInfo = localStorage.getItem(_constants.LS_USER_KEY);
+ if (this.accessToken && userInfo) {
+ Object.assign(user, JSON.parse(userInfo), {
+ fromCache: true
+ });
+ }
+ } catch (e) {
+ localStorage.removeItem(_constants.LS_USER_KEY);
+ }
+
+ this.state = (0, _mobx.observable)({
+ user: user,
+ error: null,
+ meta: {},
+ comments: undefined,
+ reactions: [],
+ commentReactions: {},
+ currentPage: 1
+ });
+
+ var query = _utils.Query.parse();
+ if (query.code) {
+ var _oauth = this.oauth,
+ client_id = _oauth.client_id,
+ client_secret = _oauth.client_secret;
+
+ var code = query.code;
+ delete query.code;
+ var search = _utils.Query.stringify(query);
+ var replacedUrl = '' + window.location.origin + window.location.pathname + search + window.location.hash;
+ history.replaceState({}, '', replacedUrl);
+
+ Object.assign(this, {
+ id: replacedUrl,
+ link: replacedUrl
+ }, options);
+
+ this.state.user.isLoggingIn = true;
+ _utils.http.post('http://nas.laoliu.icu:3000', {
+ code: code,
+ client_id: client_id,
+ client_secret: client_secret
+ }, '',{Accept:'application/json'}).then(function (data) {
+ _this.accessToken = data.access_token;
+ _this.update();
+ }).catch(function (e) {
+ _this.state.user.isLoggingIn = false;
+ alert(e);
+ });
+ } else {
+ this.update();
+ }
+ }
+
+ _createClass(Gitment, [{
+ key: 'init',
+ value: function init() {
+ var _this2 = this;
+
+ return this.createIssue().then(function () {
+ return _this2.loadComments();
+ }).then(function (comments) {
+ _this2.state.error = null;
+ return comments;
+ });
+ }
+ }, {
+ key: 'useTheme',
+ value: function useTheme() {
+ var _this3 = this;
+
+ var theme = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+
+ this.theme = theme;
+
+ var renderers = Object.keys(this.theme);
+ renderers.forEach(function (renderer) {
+ return extendRenderer(_this3, renderer);
+ });
+ }
+ }, {
+ key: 'update',
+ value: function update() {
+ var _this4 = this;
+
+ return Promise.all([this.loadMeta(), this.loadUserInfo()]).then(function () {
+ return Promise.all([_this4.loadComments().then(function () {
+ return _this4.loadCommentReactions();
+ }), _this4.loadReactions()]);
+ }).catch(function (e) {
+ return _this4.state.error = e;
+ });
+ }
+ }, {
+ key: 'markdown',
+ value: function markdown(text) {
+ return _utils.http.post('/markdown', {
+ text: text,
+ mode: 'gfm'
+ });
+ }
+ }, {
+ key: 'createIssue',
+ value: function createIssue() {
+ var _this5 = this;
+
+ var id = this.id,
+ owner = this.owner,
+ repo = this.repo,
+ title = this.title,
+ link = this.link,
+ desc = this.desc,
+ labels = this.labels;
+
+
+ return _utils.http.post('/repos/' + owner + '/' + repo + '/issues', {
+ title: title,
+ labels: labels.concat(['gitment', id]),
+ body: link + '\n\n' + desc
+ }).then(function (meta) {
+ _this5.state.meta = meta;
+ return meta;
+ });
+ }
+ }, {
+ key: 'getIssue',
+ value: function getIssue() {
+ if (this.state.meta.id) return Promise.resolve(this.state.meta);
+
+ return this.loadMeta();
+ }
+ }, {
+ key: 'post',
+ value: function post(body) {
+ var _this6 = this;
+
+ return this.getIssue().then(function (issue) {
+ return _utils.http.post(issue.comments_url, { body: body }, '');
+ }).then(function (data) {
+ _this6.state.meta.comments++;
+ var pageCount = Math.ceil(_this6.state.meta.comments / _this6.perPage);
+ if (_this6.state.currentPage === pageCount) {
+ _this6.state.comments.push(data);
+ }
+ return data;
+ });
+ }
+ }, {
+ key: 'loadMeta',
+ value: function loadMeta() {
+ var _this7 = this;
+
+ var id = this.id,
+ owner = this.owner,
+ repo = this.repo;
+
+ return _utils.http.get('/repos/' + owner + '/' + repo + '/issues', {
+ creator: owner,
+ labels: id
+ }).then(function (issues) {
+ if (!issues.length) return Promise.reject(_constants.NOT_INITIALIZED_ERROR);
+ _this7.state.meta = issues[0];
+ return issues[0];
+ });
+ }
+ }, {
+ key: 'loadComments',
+ value: function loadComments() {
+ var _this8 = this;
+
+ var page = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.state.currentPage;
+
+ return this.getIssue().then(function (issue) {
+ return _utils.http.get(issue.comments_url, { page: page, per_page: _this8.perPage }, '');
+ }).then(function (comments) {
+ _this8.state.comments = comments;
+ return comments;
+ });
+ }
+ }, {
+ key: 'loadUserInfo',
+ value: function loadUserInfo() {
+ var _this9 = this;
+
+ if (!this.accessToken) {
+ this.logout();
+ return Promise.resolve({});
+ }
+
+ return _utils.http.get('/user').then(function (user) {
+ _this9.state.user = user;
+ localStorage.setItem(_constants.LS_USER_KEY, JSON.stringify(user));
+ return user;
+ });
+ }
+ }, {
+ key: 'loadReactions',
+ value: function loadReactions() {
+ var _this10 = this;
+
+ if (!this.accessToken) {
+ this.state.reactions = [];
+ return Promise.resolve([]);
+ }
+
+ return this.getIssue().then(function (issue) {
+ if (!issue.reactions.total_count) return [];
+ return _utils.http.get(issue.reactions.url, {}, '');
+ }).then(function (reactions) {
+ _this10.state.reactions = reactions;
+ return reactions;
+ });
+ }
+ }, {
+ key: 'loadCommentReactions',
+ value: function loadCommentReactions() {
+ var _this11 = this;
+
+ if (!this.accessToken) {
+ this.state.commentReactions = {};
+ return Promise.resolve([]);
+ }
+
+ var comments = this.state.comments;
+ var comentReactions = {};
+
+ return Promise.all(comments.map(function (comment) {
+ if (!comment.reactions.total_count) return [];
+
+ var owner = _this11.owner,
+ repo = _this11.repo;
+
+ return _utils.http.get('/repos/' + owner + '/' + repo + '/issues/comments/' + comment.id + '/reactions', {});
+ })).then(function (reactionsArray) {
+ comments.forEach(function (comment, index) {
+ comentReactions[comment.id] = reactionsArray[index];
+ });
+ _this11.state.commentReactions = comentReactions;
+
+ return comentReactions;
+ });
+ }
+ }, {
+ key: 'login',
+ value: function login() {
+ window.location.href = this.loginLink;
+ }
+ }, {
+ key: 'logout',
+ value: function logout() {
+ localStorage.removeItem(_constants.LS_ACCESS_TOKEN_KEY);
+ localStorage.removeItem(_constants.LS_USER_KEY);
+ this.state.user = {};
+ }
+ }, {
+ key: 'goto',
+ value: function goto(page) {
+ this.state.currentPage = page;
+ this.state.comments = undefined;
+ return this.loadComments(page);
+ }
+ }, {
+ key: 'like',
+ value: function like() {
+ var _this12 = this;
+
+ if (!this.accessToken) {
+ alert('Login to Like');
+ return Promise.reject();
+ }
+
+ var owner = this.owner,
+ repo = this.repo;
+
+
+ return _utils.http.post('/repos/' + owner + '/' + repo + '/issues/' + this.state.meta.number + '/reactions', {
+ content: 'heart'
+ }).then(function (reaction) {
+ _this12.state.reactions.push(reaction);
+ _this12.state.meta.reactions.heart++;
+ });
+ }
+ }, {
+ key: 'unlike',
+ value: function unlike() {
+ var _this13 = this;
+
+ if (!this.accessToken) return Promise.reject();
+
+ var _state = this.state,
+ user = _state.user,
+ reactions = _state.reactions;
+
+ var index = reactions.findIndex(function (reaction) {
+ return reaction.user.login === user.login;
+ });
+ return _utils.http.delete('/reactions/' + reactions[index].id).then(function () {
+ reactions.splice(index, 1);
+ _this13.state.meta.reactions.heart--;
+ });
+ }
+ }, {
+ key: 'likeAComment',
+ value: function likeAComment(commentId) {
+ var _this14 = this;
+
+ if (!this.accessToken) {
+ alert('Login to Like');
+ return Promise.reject();
+ }
+
+ var owner = this.owner,
+ repo = this.repo;
+
+ var comment = this.state.comments.find(function (comment) {
+ return comment.id === commentId;
+ });
+
+ return _utils.http.post('/repos/' + owner + '/' + repo + '/issues/comments/' + commentId + '/reactions', {
+ content: 'heart'
+ }).then(function (reaction) {
+ _this14.state.commentReactions[commentId].push(reaction);
+ comment.reactions.heart++;
+ });
+ }
+ }, {
+ key: 'unlikeAComment',
+ value: function unlikeAComment(commentId) {
+ if (!this.accessToken) return Promise.reject();
+
+ var reactions = this.state.commentReactions[commentId];
+ var comment = this.state.comments.find(function (comment) {
+ return comment.id === commentId;
+ });
+ var user = this.state.user;
+
+ var index = reactions.findIndex(function (reaction) {
+ return reaction.user.login === user.login;
+ });
+
+ return _utils.http.delete('/reactions/' + reactions[index].id).then(function () {
+ reactions.splice(index, 1);
+ comment.reactions.heart--;
+ });
+ }
+ }]);
+
+ return Gitment;
+}();
+
+module.exports = Gitment;
+
+/***/ }),
+/* 6 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+/**
+ * Modified from https://github.com/evil-icons/evil-icons
+ */
+
+var close = exports.close = ' ';
+var github = exports.github = ' ';
+var heart = exports.heart = ' ';
+var spinner = exports.spinner = ' ';
+
+/***/ })
+/******/ ]);
+//# sourceMappingURL=gitment.browser.js.map
\ No newline at end of file
diff --git a/images/2019-07-27/1.png b/images/2019-07-27/1.png
new file mode 100644
index 00000000..ada065a7
Binary files /dev/null and b/images/2019-07-27/1.png differ
diff --git a/images/2019-07-27/2.png b/images/2019-07-27/2.png
new file mode 100644
index 00000000..28668c74
Binary files /dev/null and b/images/2019-07-27/2.png differ
diff --git a/images/2019-07-27/3.png b/images/2019-07-27/3.png
new file mode 100644
index 00000000..933e8ef5
Binary files /dev/null and b/images/2019-07-27/3.png differ
diff --git a/images/2019-07-27/4.png b/images/2019-07-27/4.png
new file mode 100644
index 00000000..1fb34920
Binary files /dev/null and b/images/2019-07-27/4.png differ
diff --git a/images/2019-07-27/5.png b/images/2019-07-27/5.png
new file mode 100644
index 00000000..e84c20ac
Binary files /dev/null and b/images/2019-07-27/5.png differ
diff --git a/images/2019-09-22/1.png b/images/2019-09-22/1.png
new file mode 100644
index 00000000..76b11492
Binary files /dev/null and b/images/2019-09-22/1.png differ
diff --git a/images/2019-09-22/2.jpg b/images/2019-09-22/2.jpg
new file mode 100644
index 00000000..964647e6
Binary files /dev/null and b/images/2019-09-22/2.jpg differ
diff --git a/images/2019-09-22/3.png b/images/2019-09-22/3.png
new file mode 100644
index 00000000..79b84bd9
Binary files /dev/null and b/images/2019-09-22/3.png differ
diff --git a/images/2019-09-22/4.png b/images/2019-09-22/4.png
new file mode 100644
index 00000000..36f0596e
Binary files /dev/null and b/images/2019-09-22/4.png differ
diff --git a/images/2019-09-22/5.jpg b/images/2019-09-22/5.jpg
new file mode 100644
index 00000000..3f2283f4
Binary files /dev/null and b/images/2019-09-22/5.jpg differ
diff --git a/images/2019-10-02/1.jpg b/images/2019-10-02/1.jpg
new file mode 100644
index 00000000..d2c2726f
Binary files /dev/null and b/images/2019-10-02/1.jpg differ
diff --git a/images/2019-10-25/1.png b/images/2019-10-25/1.png
new file mode 100644
index 00000000..44908f21
Binary files /dev/null and b/images/2019-10-25/1.png differ
diff --git a/images/2019-10-25/2.png b/images/2019-10-25/2.png
new file mode 100644
index 00000000..467e8537
Binary files /dev/null and b/images/2019-10-25/2.png differ
diff --git a/images/2019-10-25/3.png b/images/2019-10-25/3.png
new file mode 100644
index 00000000..2517cb17
Binary files /dev/null and b/images/2019-10-25/3.png differ
diff --git a/images/2019-10-25/5.png b/images/2019-10-25/5.png
new file mode 100644
index 00000000..e73f59c7
Binary files /dev/null and b/images/2019-10-25/5.png differ
diff --git a/images/2020-05-05/foobar2000-1.png b/images/2020-05-05/foobar2000-1.png
new file mode 100644
index 00000000..68564cdb
Binary files /dev/null and b/images/2020-05-05/foobar2000-1.png differ
diff --git a/images/2020-05-05/foobar2000-2.png b/images/2020-05-05/foobar2000-2.png
new file mode 100644
index 00000000..cefc7d04
Binary files /dev/null and b/images/2020-05-05/foobar2000-2.png differ
diff --git a/images/2020-05-05/foobar2000-3.png b/images/2020-05-05/foobar2000-3.png
new file mode 100644
index 00000000..981c47bf
Binary files /dev/null and b/images/2020-05-05/foobar2000-3.png differ
diff --git a/images/2020-05-05/foobar2000-m.jpg b/images/2020-05-05/foobar2000-m.jpg
new file mode 100644
index 00000000..a538e25f
Binary files /dev/null and b/images/2020-05-05/foobar2000-m.jpg differ
diff --git a/images/2020-05-05/music-tag-1.png b/images/2020-05-05/music-tag-1.png
new file mode 100644
index 00000000..6b2f3166
Binary files /dev/null and b/images/2020-05-05/music-tag-1.png differ
diff --git a/images/2020-05-05/music-tag-2.png b/images/2020-05-05/music-tag-2.png
new file mode 100644
index 00000000..44c30ca6
Binary files /dev/null and b/images/2020-05-05/music-tag-2.png differ
diff --git a/images/2020-05-05/music-tag-3.png b/images/2020-05-05/music-tag-3.png
new file mode 100644
index 00000000..051eebeb
Binary files /dev/null and b/images/2020-05-05/music-tag-3.png differ
diff --git a/images/2020-05-05/music-tag-4.png b/images/2020-05-05/music-tag-4.png
new file mode 100644
index 00000000..1e10ad38
Binary files /dev/null and b/images/2020-05-05/music-tag-4.png differ
diff --git a/images/2020-05-05/music-tag-5.png b/images/2020-05-05/music-tag-5.png
new file mode 100644
index 00000000..7df20b3b
Binary files /dev/null and b/images/2020-05-05/music-tag-5.png differ
diff --git a/images/2020-05-05/music-tag-6.png b/images/2020-05-05/music-tag-6.png
new file mode 100644
index 00000000..22424631
Binary files /dev/null and b/images/2020-05-05/music-tag-6.png differ
diff --git a/images/2020-05-05/music-tag-7.png b/images/2020-05-05/music-tag-7.png
new file mode 100644
index 00000000..821b41a9
Binary files /dev/null and b/images/2020-05-05/music-tag-7.png differ
diff --git a/images/2021-11-07/1.JPG b/images/2021-11-07/1.JPG
new file mode 100644
index 00000000..644cd63d
Binary files /dev/null and b/images/2021-11-07/1.JPG differ
diff --git a/images/2021-11-07/2.png b/images/2021-11-07/2.png
new file mode 100644
index 00000000..bf5061a3
Binary files /dev/null and b/images/2021-11-07/2.png differ
diff --git a/images/2021-12-23/WebHorizonBench.png b/images/2021-12-23/WebHorizonBench.png
new file mode 100644
index 00000000..26b87f8c
Binary files /dev/null and b/images/2021-12-23/WebHorizonBench.png differ
diff --git a/images/2021-12-23/WebHorizonJP.png b/images/2021-12-23/WebHorizonJP.png
new file mode 100644
index 00000000..403542b3
Binary files /dev/null and b/images/2021-12-23/WebHorizonJP.png differ
diff --git a/images/2021-12-23/WebHorizonJPRoute.png b/images/2021-12-23/WebHorizonJPRoute.png
new file mode 100644
index 00000000..2d66553f
Binary files /dev/null and b/images/2021-12-23/WebHorizonJPRoute.png differ
diff --git a/images/2021-12-24/Cloudcone.jpg b/images/2021-12-24/Cloudcone.jpg
new file mode 100644
index 00000000..0c58e00b
Binary files /dev/null and b/images/2021-12-24/Cloudcone.jpg differ
diff --git a/images/2021-12-24/CloudconeBench.jpg b/images/2021-12-24/CloudconeBench.jpg
new file mode 100644
index 00000000..279af8b8
Binary files /dev/null and b/images/2021-12-24/CloudconeBench.jpg differ
diff --git a/images/2021-12-24/CloudconeMorning.png b/images/2021-12-24/CloudconeMorning.png
new file mode 100644
index 00000000..5ee8bc89
Binary files /dev/null and b/images/2021-12-24/CloudconeMorning.png differ
diff --git a/images/2021-12-24/CloudconeMtr.png b/images/2021-12-24/CloudconeMtr.png
new file mode 100644
index 00000000..857280db
Binary files /dev/null and b/images/2021-12-24/CloudconeMtr.png differ
diff --git a/images/2021-12-24/CloudconeRoute.png b/images/2021-12-24/CloudconeRoute.png
new file mode 100644
index 00000000..593f152e
Binary files /dev/null and b/images/2021-12-24/CloudconeRoute.png differ
diff --git a/images/2021-12-29/WikiHost4837.png b/images/2021-12-29/WikiHost4837.png
new file mode 100644
index 00000000..402baeba
Binary files /dev/null and b/images/2021-12-29/WikiHost4837.png differ
diff --git a/images/2021-12-29/WikiHostBench.png b/images/2021-12-29/WikiHostBench.png
new file mode 100644
index 00000000..6b41c3ac
Binary files /dev/null and b/images/2021-12-29/WikiHostBench.png differ
diff --git a/images/2021-12-29/WikiHostMtr.png b/images/2021-12-29/WikiHostMtr.png
new file mode 100644
index 00000000..debd9646
Binary files /dev/null and b/images/2021-12-29/WikiHostMtr.png differ
diff --git a/images/2021-2-26/1.jpg b/images/2021-2-26/1.jpg
new file mode 100644
index 00000000..ccc530a8
Binary files /dev/null and b/images/2021-2-26/1.jpg differ
diff --git a/images/2022-01-10/Deepvm9929.png b/images/2022-01-10/Deepvm9929.png
new file mode 100644
index 00000000..bb4f3aa4
Binary files /dev/null and b/images/2022-01-10/Deepvm9929.png differ
diff --git a/images/2022-01-10/Deepvm9929Bench.png b/images/2022-01-10/Deepvm9929Bench.png
new file mode 100644
index 00000000..cdfcf65f
Binary files /dev/null and b/images/2022-01-10/Deepvm9929Bench.png differ
diff --git a/images/2022-04-06/Bench.png b/images/2022-04-06/Bench.png
new file mode 100644
index 00000000..df6d3d08
Binary files /dev/null and b/images/2022-04-06/Bench.png differ
diff --git a/images/2022-04-06/Streaming.png b/images/2022-04-06/Streaming.png
new file mode 100644
index 00000000..ebbc6e92
Binary files /dev/null and b/images/2022-04-06/Streaming.png differ
diff --git a/images/2022-04-06/SuperSpeed-Morning.png b/images/2022-04-06/SuperSpeed-Morning.png
new file mode 100644
index 00000000..28b644f5
Binary files /dev/null and b/images/2022-04-06/SuperSpeed-Morning.png differ
diff --git a/images/2022-04-06/SuperSpeed-Night.png b/images/2022-04-06/SuperSpeed-Night.png
new file mode 100644
index 00000000..04bcbecb
Binary files /dev/null and b/images/2022-04-06/SuperSpeed-Night.png differ
diff --git a/images/2022-04-06/UNIXBench.jpg b/images/2022-04-06/UNIXBench.jpg
new file mode 100644
index 00000000..05711238
Binary files /dev/null and b/images/2022-04-06/UNIXBench.jpg differ
diff --git a/images/2022-05-22/01.JPG b/images/2022-05-22/01.JPG
new file mode 100644
index 00000000..64475bc2
Binary files /dev/null and b/images/2022-05-22/01.JPG differ
diff --git a/images/2022-05-22/02.JPG b/images/2022-05-22/02.JPG
new file mode 100644
index 00000000..ed26d8d9
Binary files /dev/null and b/images/2022-05-22/02.JPG differ
diff --git a/images/2022-05-22/03.JPG b/images/2022-05-22/03.JPG
new file mode 100644
index 00000000..dbe0df94
Binary files /dev/null and b/images/2022-05-22/03.JPG differ
diff --git a/images/2022-05-22/04.JPG b/images/2022-05-22/04.JPG
new file mode 100644
index 00000000..58765548
Binary files /dev/null and b/images/2022-05-22/04.JPG differ
diff --git a/images/2022-05-22/05.JPG b/images/2022-05-22/05.JPG
new file mode 100644
index 00000000..3f3437ac
Binary files /dev/null and b/images/2022-05-22/05.JPG differ
diff --git a/images/2022-05-22/06.JPG b/images/2022-05-22/06.JPG
new file mode 100644
index 00000000..97bf20ac
Binary files /dev/null and b/images/2022-05-22/06.JPG differ
diff --git a/images/2022-05-22/07.JPG b/images/2022-05-22/07.JPG
new file mode 100644
index 00000000..e98648c9
Binary files /dev/null and b/images/2022-05-22/07.JPG differ
diff --git a/images/2022-05-22/08.JPG b/images/2022-05-22/08.JPG
new file mode 100644
index 00000000..13f47b01
Binary files /dev/null and b/images/2022-05-22/08.JPG differ
diff --git a/images/2022-05-22/09.JPG b/images/2022-05-22/09.JPG
new file mode 100644
index 00000000..284ec8d0
Binary files /dev/null and b/images/2022-05-22/09.JPG differ
diff --git a/images/2022-05-22/10.JPG b/images/2022-05-22/10.JPG
new file mode 100644
index 00000000..252b6b96
Binary files /dev/null and b/images/2022-05-22/10.JPG differ
diff --git a/images/2022-06-03/01.JPG b/images/2022-06-03/01.JPG
new file mode 100644
index 00000000..34a9fe61
Binary files /dev/null and b/images/2022-06-03/01.JPG differ
diff --git a/images/2022-06-03/02.JPG b/images/2022-06-03/02.JPG
new file mode 100644
index 00000000..5f99b312
Binary files /dev/null and b/images/2022-06-03/02.JPG differ
diff --git a/images/2022-06-03/03.JPG b/images/2022-06-03/03.JPG
new file mode 100644
index 00000000..24d83b65
Binary files /dev/null and b/images/2022-06-03/03.JPG differ
diff --git a/images/2022-06-03/04.JPG b/images/2022-06-03/04.JPG
new file mode 100644
index 00000000..3480a842
Binary files /dev/null and b/images/2022-06-03/04.JPG differ
diff --git a/images/2022-06-03/05.JPG b/images/2022-06-03/05.JPG
new file mode 100644
index 00000000..49d5b466
Binary files /dev/null and b/images/2022-06-03/05.JPG differ
diff --git a/images/2022-06-03/06.JPG b/images/2022-06-03/06.JPG
new file mode 100644
index 00000000..828b7cb1
Binary files /dev/null and b/images/2022-06-03/06.JPG differ
diff --git a/images/2022-06-03/07.JPG b/images/2022-06-03/07.JPG
new file mode 100644
index 00000000..cd6f4943
Binary files /dev/null and b/images/2022-06-03/07.JPG differ
diff --git a/images/2022-06-03/08.JPG b/images/2022-06-03/08.JPG
new file mode 100644
index 00000000..d3d36271
Binary files /dev/null and b/images/2022-06-03/08.JPG differ
diff --git a/images/2022-08-12/1.jpeg b/images/2022-08-12/1.jpeg
new file mode 100644
index 00000000..01f55ad1
Binary files /dev/null and b/images/2022-08-12/1.jpeg differ
diff --git a/images/2022-08-12/10.jpeg b/images/2022-08-12/10.jpeg
new file mode 100644
index 00000000..8ab3ac7d
Binary files /dev/null and b/images/2022-08-12/10.jpeg differ
diff --git a/images/2022-08-12/11.jpeg b/images/2022-08-12/11.jpeg
new file mode 100644
index 00000000..c98dfb9d
Binary files /dev/null and b/images/2022-08-12/11.jpeg differ
diff --git a/images/2022-08-12/12.jpeg b/images/2022-08-12/12.jpeg
new file mode 100644
index 00000000..6b899a85
Binary files /dev/null and b/images/2022-08-12/12.jpeg differ
diff --git a/images/2022-08-12/13.jpeg b/images/2022-08-12/13.jpeg
new file mode 100644
index 00000000..7a25fb3e
Binary files /dev/null and b/images/2022-08-12/13.jpeg differ
diff --git a/images/2022-08-12/14.jpeg b/images/2022-08-12/14.jpeg
new file mode 100644
index 00000000..d2527bbb
Binary files /dev/null and b/images/2022-08-12/14.jpeg differ
diff --git a/images/2022-08-12/15.jpeg b/images/2022-08-12/15.jpeg
new file mode 100644
index 00000000..1b5ffeb5
Binary files /dev/null and b/images/2022-08-12/15.jpeg differ
diff --git a/images/2022-08-12/16.jpeg b/images/2022-08-12/16.jpeg
new file mode 100644
index 00000000..0fb05e87
Binary files /dev/null and b/images/2022-08-12/16.jpeg differ
diff --git a/images/2022-08-12/17.jpeg b/images/2022-08-12/17.jpeg
new file mode 100644
index 00000000..4a9f3a28
Binary files /dev/null and b/images/2022-08-12/17.jpeg differ
diff --git a/images/2022-08-12/18.jpeg b/images/2022-08-12/18.jpeg
new file mode 100644
index 00000000..09820df3
Binary files /dev/null and b/images/2022-08-12/18.jpeg differ
diff --git a/images/2022-08-12/19.jpeg b/images/2022-08-12/19.jpeg
new file mode 100644
index 00000000..4980aa70
Binary files /dev/null and b/images/2022-08-12/19.jpeg differ
diff --git a/images/2022-08-12/2.jpeg b/images/2022-08-12/2.jpeg
new file mode 100644
index 00000000..6122ef62
Binary files /dev/null and b/images/2022-08-12/2.jpeg differ
diff --git a/images/2022-08-12/20.jpeg b/images/2022-08-12/20.jpeg
new file mode 100644
index 00000000..dafee31b
Binary files /dev/null and b/images/2022-08-12/20.jpeg differ
diff --git a/images/2022-08-12/21.jpeg b/images/2022-08-12/21.jpeg
new file mode 100644
index 00000000..723b8a2a
Binary files /dev/null and b/images/2022-08-12/21.jpeg differ
diff --git a/images/2022-08-12/22.jpeg b/images/2022-08-12/22.jpeg
new file mode 100644
index 00000000..4ca9ae96
Binary files /dev/null and b/images/2022-08-12/22.jpeg differ
diff --git a/images/2022-08-12/23.jpeg b/images/2022-08-12/23.jpeg
new file mode 100644
index 00000000..32346b81
Binary files /dev/null and b/images/2022-08-12/23.jpeg differ
diff --git a/images/2022-08-12/24.jpeg b/images/2022-08-12/24.jpeg
new file mode 100644
index 00000000..d8e49708
Binary files /dev/null and b/images/2022-08-12/24.jpeg differ
diff --git a/images/2022-08-12/25.jpeg b/images/2022-08-12/25.jpeg
new file mode 100644
index 00000000..71ecc9de
Binary files /dev/null and b/images/2022-08-12/25.jpeg differ
diff --git a/images/2022-08-12/26.jpeg b/images/2022-08-12/26.jpeg
new file mode 100644
index 00000000..b99954d0
Binary files /dev/null and b/images/2022-08-12/26.jpeg differ
diff --git a/images/2022-08-12/27.jpeg b/images/2022-08-12/27.jpeg
new file mode 100644
index 00000000..0f1b31c3
Binary files /dev/null and b/images/2022-08-12/27.jpeg differ
diff --git a/images/2022-08-12/28.jpeg b/images/2022-08-12/28.jpeg
new file mode 100644
index 00000000..28f89ed2
Binary files /dev/null and b/images/2022-08-12/28.jpeg differ
diff --git a/images/2022-08-12/29.jpeg b/images/2022-08-12/29.jpeg
new file mode 100644
index 00000000..cf8c40bf
Binary files /dev/null and b/images/2022-08-12/29.jpeg differ
diff --git a/images/2022-08-12/3.jpeg b/images/2022-08-12/3.jpeg
new file mode 100644
index 00000000..bc952299
Binary files /dev/null and b/images/2022-08-12/3.jpeg differ
diff --git a/images/2022-08-12/30.jpeg b/images/2022-08-12/30.jpeg
new file mode 100644
index 00000000..660d9867
Binary files /dev/null and b/images/2022-08-12/30.jpeg differ
diff --git a/images/2022-08-12/31.jpeg b/images/2022-08-12/31.jpeg
new file mode 100644
index 00000000..38cc36ee
Binary files /dev/null and b/images/2022-08-12/31.jpeg differ
diff --git a/images/2022-08-12/32.jpeg b/images/2022-08-12/32.jpeg
new file mode 100644
index 00000000..83f2bfda
Binary files /dev/null and b/images/2022-08-12/32.jpeg differ
diff --git a/images/2022-08-12/33.jpeg b/images/2022-08-12/33.jpeg
new file mode 100644
index 00000000..0d77ef81
Binary files /dev/null and b/images/2022-08-12/33.jpeg differ
diff --git a/images/2022-08-12/34.jpeg b/images/2022-08-12/34.jpeg
new file mode 100644
index 00000000..ca488652
Binary files /dev/null and b/images/2022-08-12/34.jpeg differ
diff --git a/images/2022-08-12/35.jpeg b/images/2022-08-12/35.jpeg
new file mode 100644
index 00000000..9f6b9c95
Binary files /dev/null and b/images/2022-08-12/35.jpeg differ
diff --git a/images/2022-08-12/36.jpeg b/images/2022-08-12/36.jpeg
new file mode 100644
index 00000000..049d191d
Binary files /dev/null and b/images/2022-08-12/36.jpeg differ
diff --git a/images/2022-08-12/4.jpeg b/images/2022-08-12/4.jpeg
new file mode 100644
index 00000000..333d0294
Binary files /dev/null and b/images/2022-08-12/4.jpeg differ
diff --git a/images/2022-08-12/5.jpeg b/images/2022-08-12/5.jpeg
new file mode 100644
index 00000000..c8c4a1e2
Binary files /dev/null and b/images/2022-08-12/5.jpeg differ
diff --git a/images/2022-08-12/6.jpeg b/images/2022-08-12/6.jpeg
new file mode 100644
index 00000000..4e034d4c
Binary files /dev/null and b/images/2022-08-12/6.jpeg differ
diff --git a/images/2022-08-12/7.jpeg b/images/2022-08-12/7.jpeg
new file mode 100644
index 00000000..27e621a4
Binary files /dev/null and b/images/2022-08-12/7.jpeg differ
diff --git a/images/2022-08-12/8.jpeg b/images/2022-08-12/8.jpeg
new file mode 100644
index 00000000..551b1ffc
Binary files /dev/null and b/images/2022-08-12/8.jpeg differ
diff --git a/images/2022-09-05/Betaflight.jpg b/images/2022-09-05/Betaflight.jpg
new file mode 100644
index 00000000..5a34d0da
Binary files /dev/null and b/images/2022-09-05/Betaflight.jpg differ
diff --git "a/images/2022-09-05/T12\347\204\212\345\217\260.jpg" "b/images/2022-09-05/T12\347\204\212\345\217\260.jpg"
new file mode 100644
index 00000000..4935ff4c
Binary files /dev/null and "b/images/2022-09-05/T12\347\204\212\345\217\260.jpg" differ
diff --git "a/images/2022-09-05/\345\233\272\345\256\232\347\277\274\344\270\216\345\244\232\350\275\264.jpg" "b/images/2022-09-05/\345\233\272\345\256\232\347\277\274\344\270\216\345\244\232\350\275\264.jpg"
new file mode 100644
index 00000000..838fcb5f
Binary files /dev/null and "b/images/2022-09-05/\345\233\272\345\256\232\347\277\274\344\270\216\345\244\232\350\275\264.jpg" differ
diff --git "a/images/2022-09-05/\346\210\221\347\232\204\346\210\220\346\236\234.jpg" "b/images/2022-09-05/\346\210\221\347\232\204\346\210\220\346\236\234.jpg"
new file mode 100644
index 00000000..52168ceb
Binary files /dev/null and "b/images/2022-09-05/\346\210\221\347\232\204\346\210\220\346\236\234.jpg" differ
diff --git "a/images/2022-09-05/\346\216\245\346\224\266\346\234\272\344\270\216\345\233\276\344\274\240.jpg" "b/images/2022-09-05/\346\216\245\346\224\266\346\234\272\344\270\216\345\233\276\344\274\240.jpg"
new file mode 100644
index 00000000..5adcb100
Binary files /dev/null and "b/images/2022-09-05/\346\216\245\346\224\266\346\234\272\344\270\216\345\233\276\344\274\240.jpg" differ
diff --git "a/images/2022-09-05/\346\234\272\346\236\266\345\244\226\345\275\242.jpg" "b/images/2022-09-05/\346\234\272\346\236\266\345\244\226\345\275\242.jpg"
new file mode 100644
index 00000000..e50176e1
Binary files /dev/null and "b/images/2022-09-05/\346\234\272\346\236\266\345\244\226\345\275\242.jpg" differ
diff --git "a/images/2022-09-05/\346\234\272\346\236\266\347\247\215\347\261\273.jpg" "b/images/2022-09-05/\346\234\272\346\236\266\347\247\215\347\261\273.jpg"
new file mode 100644
index 00000000..4853846a
Binary files /dev/null and "b/images/2022-09-05/\346\234\272\346\236\266\347\247\215\347\261\273.jpg" differ
diff --git "a/images/2022-09-05/\347\251\277\350\266\212\346\234\272\344\274\240\346\204\237\345\231\250.jpg" "b/images/2022-09-05/\347\251\277\350\266\212\346\234\272\344\274\240\346\204\237\345\231\250.jpg"
new file mode 100644
index 00000000..b8881aee
Binary files /dev/null and "b/images/2022-09-05/\347\251\277\350\266\212\346\234\272\344\274\240\346\204\237\345\231\250.jpg" differ
diff --git "a/images/2022-09-05/\347\251\277\350\266\212\346\234\272\346\236\266\346\236\204.jpg" "b/images/2022-09-05/\347\251\277\350\266\212\346\234\272\346\236\266\346\236\204.jpg"
new file mode 100644
index 00000000..c0636e0e
Binary files /dev/null and "b/images/2022-09-05/\347\251\277\350\266\212\346\234\272\346\236\266\346\236\204.jpg" differ
diff --git "a/images/2022-09-05/\351\243\236\345\241\224.jpg" "b/images/2022-09-05/\351\243\236\345\241\224.jpg"
new file mode 100644
index 00000000..413a2599
Binary files /dev/null and "b/images/2022-09-05/\351\243\236\345\241\224.jpg" differ
diff --git "a/images/2022-09-05/\351\243\236\346\216\247SoC.jpg" "b/images/2022-09-05/\351\243\236\346\216\247SoC.jpg"
new file mode 100644
index 00000000..c0e3b6cb
Binary files /dev/null and "b/images/2022-09-05/\351\243\236\346\216\247SoC.jpg" differ
diff --git a/images/2023-01-22/01.jpg b/images/2023-01-22/01.jpg
new file mode 100644
index 00000000..06c36892
Binary files /dev/null and b/images/2023-01-22/01.jpg differ
diff --git a/images/2023-01-22/02.jpg b/images/2023-01-22/02.jpg
new file mode 100644
index 00000000..2bb76a71
Binary files /dev/null and b/images/2023-01-22/02.jpg differ
diff --git a/images/2023-01-22/03.jpg b/images/2023-01-22/03.jpg
new file mode 100644
index 00000000..f69b54c0
Binary files /dev/null and b/images/2023-01-22/03.jpg differ
diff --git a/images/2023-01-22/04.jpg b/images/2023-01-22/04.jpg
new file mode 100644
index 00000000..f425107f
Binary files /dev/null and b/images/2023-01-22/04.jpg differ
diff --git a/images/2023-01-22/05.jpg b/images/2023-01-22/05.jpg
new file mode 100644
index 00000000..a683ae2f
Binary files /dev/null and b/images/2023-01-22/05.jpg differ
diff --git a/images/2023-01-22/06.jpg b/images/2023-01-22/06.jpg
new file mode 100644
index 00000000..e98051c6
Binary files /dev/null and b/images/2023-01-22/06.jpg differ
diff --git a/images/2023-01-22/07.jpg b/images/2023-01-22/07.jpg
new file mode 100644
index 00000000..69e68279
Binary files /dev/null and b/images/2023-01-22/07.jpg differ
diff --git a/images/2023-01-22/08.jpg b/images/2023-01-22/08.jpg
new file mode 100644
index 00000000..4eda4d2f
Binary files /dev/null and b/images/2023-01-22/08.jpg differ
diff --git a/images/2023-09-24/01.jpg b/images/2023-09-24/01.jpg
new file mode 100644
index 00000000..db5e9db7
Binary files /dev/null and b/images/2023-09-24/01.jpg differ
diff --git a/images/2023-09-24/02.jpg b/images/2023-09-24/02.jpg
new file mode 100644
index 00000000..f2e1f8c4
Binary files /dev/null and b/images/2023-09-24/02.jpg differ
diff --git a/images/2023-09-24/03.jpg b/images/2023-09-24/03.jpg
new file mode 100644
index 00000000..820b2811
Binary files /dev/null and b/images/2023-09-24/03.jpg differ
diff --git a/images/2023-09-24/04.jpg b/images/2023-09-24/04.jpg
new file mode 100644
index 00000000..b918378d
Binary files /dev/null and b/images/2023-09-24/04.jpg differ
diff --git a/images/2023-12-19/01.jpg b/images/2023-12-19/01.jpg
new file mode 100644
index 00000000..e63da574
Binary files /dev/null and b/images/2023-12-19/01.jpg differ
diff --git a/images/2023-12-19/02.jpg b/images/2023-12-19/02.jpg
new file mode 100644
index 00000000..d578d058
Binary files /dev/null and b/images/2023-12-19/02.jpg differ
diff --git a/images/2023-12-19/03.jpg b/images/2023-12-19/03.jpg
new file mode 100644
index 00000000..9a8b9acf
Binary files /dev/null and b/images/2023-12-19/03.jpg differ
diff --git a/images/2023-12-19/04.jpg b/images/2023-12-19/04.jpg
new file mode 100644
index 00000000..f81d33ef
Binary files /dev/null and b/images/2023-12-19/04.jpg differ
diff --git a/images/2023-12-19/05.jpg b/images/2023-12-19/05.jpg
new file mode 100644
index 00000000..6119d401
Binary files /dev/null and b/images/2023-12-19/05.jpg differ
diff --git a/images/2023-12-19/06.jpg b/images/2023-12-19/06.jpg
new file mode 100644
index 00000000..40faafe8
Binary files /dev/null and b/images/2023-12-19/06.jpg differ
diff --git a/images/2023-12-19/07.jpg b/images/2023-12-19/07.jpg
new file mode 100644
index 00000000..133c74ec
Binary files /dev/null and b/images/2023-12-19/07.jpg differ
diff --git a/images/2023-12-19/08.jpg b/images/2023-12-19/08.jpg
new file mode 100644
index 00000000..ffa8724b
Binary files /dev/null and b/images/2023-12-19/08.jpg differ
diff --git a/images/2023-12-19/09.jpg b/images/2023-12-19/09.jpg
new file mode 100644
index 00000000..356cfd93
Binary files /dev/null and b/images/2023-12-19/09.jpg differ
diff --git a/images/2023-12-19/10.jpg b/images/2023-12-19/10.jpg
new file mode 100644
index 00000000..5547bf14
Binary files /dev/null and b/images/2023-12-19/10.jpg differ
diff --git a/images/2023-12-19/11.jpg b/images/2023-12-19/11.jpg
new file mode 100644
index 00000000..94c54d2f
Binary files /dev/null and b/images/2023-12-19/11.jpg differ
diff --git a/images/2023-12-19/12.jpg b/images/2023-12-19/12.jpg
new file mode 100644
index 00000000..6a654a72
Binary files /dev/null and b/images/2023-12-19/12.jpg differ
diff --git a/images/2023-12-19/13.jpg b/images/2023-12-19/13.jpg
new file mode 100644
index 00000000..09f64bfd
Binary files /dev/null and b/images/2023-12-19/13.jpg differ
diff --git a/images/2023-12-19/14.jpg b/images/2023-12-19/14.jpg
new file mode 100644
index 00000000..8ec25079
Binary files /dev/null and b/images/2023-12-19/14.jpg differ
diff --git a/images/2023-12-19/15.jpg b/images/2023-12-19/15.jpg
new file mode 100644
index 00000000..c9591801
Binary files /dev/null and b/images/2023-12-19/15.jpg differ
diff --git a/images/2023-12-19/16.jpg b/images/2023-12-19/16.jpg
new file mode 100644
index 00000000..8840a77e
Binary files /dev/null and b/images/2023-12-19/16.jpg differ
diff --git a/images/2023-12-19/17.jpg b/images/2023-12-19/17.jpg
new file mode 100644
index 00000000..04fdc988
Binary files /dev/null and b/images/2023-12-19/17.jpg differ
diff --git a/images/2024-01-21/01.jpeg b/images/2024-01-21/01.jpeg
new file mode 100644
index 00000000..4c881424
Binary files /dev/null and b/images/2024-01-21/01.jpeg differ
diff --git a/images/2024-01-21/02.jpeg b/images/2024-01-21/02.jpeg
new file mode 100644
index 00000000..7a438fab
Binary files /dev/null and b/images/2024-01-21/02.jpeg differ
diff --git a/images/2024-01-21/03.jpeg b/images/2024-01-21/03.jpeg
new file mode 100644
index 00000000..21af8904
Binary files /dev/null and b/images/2024-01-21/03.jpeg differ
diff --git a/images/2024-01-21/04.jpeg b/images/2024-01-21/04.jpeg
new file mode 100644
index 00000000..1519b84e
Binary files /dev/null and b/images/2024-01-21/04.jpeg differ
diff --git a/images/2024-01-21/05.jpeg b/images/2024-01-21/05.jpeg
new file mode 100644
index 00000000..2ec2015f
Binary files /dev/null and b/images/2024-01-21/05.jpeg differ
diff --git a/images/2024-01-21/06.jpeg b/images/2024-01-21/06.jpeg
new file mode 100644
index 00000000..b3dcec1e
Binary files /dev/null and b/images/2024-01-21/06.jpeg differ
diff --git a/images/2024-01-21/07.jpeg b/images/2024-01-21/07.jpeg
new file mode 100644
index 00000000..7e278011
Binary files /dev/null and b/images/2024-01-21/07.jpeg differ
diff --git a/images/2024-01-21/08.jpeg b/images/2024-01-21/08.jpeg
new file mode 100644
index 00000000..714447f1
Binary files /dev/null and b/images/2024-01-21/08.jpeg differ
diff --git a/images/2024-01-21/09.jpeg b/images/2024-01-21/09.jpeg
new file mode 100644
index 00000000..be2b2f24
Binary files /dev/null and b/images/2024-01-21/09.jpeg differ
diff --git a/images/2024-01-21/10.jpeg b/images/2024-01-21/10.jpeg
new file mode 100644
index 00000000..f4bd66d9
Binary files /dev/null and b/images/2024-01-21/10.jpeg differ
diff --git a/images/2024-01-21/11.jpeg b/images/2024-01-21/11.jpeg
new file mode 100644
index 00000000..e5ff045e
Binary files /dev/null and b/images/2024-01-21/11.jpeg differ
diff --git a/images/2024-01-21/12.jpeg b/images/2024-01-21/12.jpeg
new file mode 100644
index 00000000..88aa0dc0
Binary files /dev/null and b/images/2024-01-21/12.jpeg differ
diff --git a/images/2024-01-21/13.jpeg b/images/2024-01-21/13.jpeg
new file mode 100644
index 00000000..83ac4626
Binary files /dev/null and b/images/2024-01-21/13.jpeg differ
diff --git a/images/2024-01-21/14.jpeg b/images/2024-01-21/14.jpeg
new file mode 100644
index 00000000..c07d5b6f
Binary files /dev/null and b/images/2024-01-21/14.jpeg differ
diff --git a/images/2024-01-21/15.jpeg b/images/2024-01-21/15.jpeg
new file mode 100644
index 00000000..0d968ea6
Binary files /dev/null and b/images/2024-01-21/15.jpeg differ
diff --git a/images/2024-01-21/16.jpeg b/images/2024-01-21/16.jpeg
new file mode 100644
index 00000000..2ed2d3c7
Binary files /dev/null and b/images/2024-01-21/16.jpeg differ
diff --git a/images/2024-01-21/17.jpeg b/images/2024-01-21/17.jpeg
new file mode 100644
index 00000000..a5bd1ec9
Binary files /dev/null and b/images/2024-01-21/17.jpeg differ
diff --git a/images/2024-01-21/18.jpeg b/images/2024-01-21/18.jpeg
new file mode 100644
index 00000000..fa288f1e
Binary files /dev/null and b/images/2024-01-21/18.jpeg differ
diff --git a/images/2024-01-21/19.jpeg b/images/2024-01-21/19.jpeg
new file mode 100644
index 00000000..b7808f68
Binary files /dev/null and b/images/2024-01-21/19.jpeg differ
diff --git a/images/2024-01-21/20.jpeg b/images/2024-01-21/20.jpeg
new file mode 100644
index 00000000..7c13c3c6
Binary files /dev/null and b/images/2024-01-21/20.jpeg differ
diff --git a/images/2024-01-21/21.jpeg b/images/2024-01-21/21.jpeg
new file mode 100644
index 00000000..81df0793
Binary files /dev/null and b/images/2024-01-21/21.jpeg differ
diff --git a/images/2024-01-21/22.jpeg b/images/2024-01-21/22.jpeg
new file mode 100644
index 00000000..1f768194
Binary files /dev/null and b/images/2024-01-21/22.jpeg differ
diff --git a/images/2024-01-21/23.jpeg b/images/2024-01-21/23.jpeg
new file mode 100644
index 00000000..0feb2f4b
Binary files /dev/null and b/images/2024-01-21/23.jpeg differ
diff --git a/images/2024-01-21/24.jpeg b/images/2024-01-21/24.jpeg
new file mode 100644
index 00000000..aee15e63
Binary files /dev/null and b/images/2024-01-21/24.jpeg differ
diff --git a/images/2024-01-21/25.jpeg b/images/2024-01-21/25.jpeg
new file mode 100644
index 00000000..e11cf350
Binary files /dev/null and b/images/2024-01-21/25.jpeg differ
diff --git a/images/2024-01-21/26.jpeg b/images/2024-01-21/26.jpeg
new file mode 100644
index 00000000..402e84fd
Binary files /dev/null and b/images/2024-01-21/26.jpeg differ
diff --git a/images/2024-01-21/27.jpeg b/images/2024-01-21/27.jpeg
new file mode 100644
index 00000000..f94976e1
Binary files /dev/null and b/images/2024-01-21/27.jpeg differ
diff --git a/images/2024-01-21/28.jpeg b/images/2024-01-21/28.jpeg
new file mode 100644
index 00000000..8f154bef
Binary files /dev/null and b/images/2024-01-21/28.jpeg differ
diff --git a/images/2024-01-21/29.jpeg b/images/2024-01-21/29.jpeg
new file mode 100644
index 00000000..666edcec
Binary files /dev/null and b/images/2024-01-21/29.jpeg differ
diff --git a/images/2024-02-12/01.jpg b/images/2024-02-12/01.jpg
new file mode 100644
index 00000000..9048bea0
Binary files /dev/null and b/images/2024-02-12/01.jpg differ
diff --git a/images/2024-02-12/02.jpg b/images/2024-02-12/02.jpg
new file mode 100644
index 00000000..1f1281a4
Binary files /dev/null and b/images/2024-02-12/02.jpg differ
diff --git a/images/2024-02-12/03.jpg b/images/2024-02-12/03.jpg
new file mode 100644
index 00000000..87f703ee
Binary files /dev/null and b/images/2024-02-12/03.jpg differ
diff --git a/images/2024-02-12/04.jpg b/images/2024-02-12/04.jpg
new file mode 100644
index 00000000..8808ba00
Binary files /dev/null and b/images/2024-02-12/04.jpg differ
diff --git a/images/2024-02-12/05.jpg b/images/2024-02-12/05.jpg
new file mode 100644
index 00000000..47f2edb1
Binary files /dev/null and b/images/2024-02-12/05.jpg differ
diff --git a/images/2024-02-12/06.jpg b/images/2024-02-12/06.jpg
new file mode 100644
index 00000000..5195d647
Binary files /dev/null and b/images/2024-02-12/06.jpg differ
diff --git a/images/2024-02-12/07.jpg b/images/2024-02-12/07.jpg
new file mode 100644
index 00000000..bdc8611a
Binary files /dev/null and b/images/2024-02-12/07.jpg differ
diff --git a/images/2024-02-12/08.jpg b/images/2024-02-12/08.jpg
new file mode 100644
index 00000000..c8dd768a
Binary files /dev/null and b/images/2024-02-12/08.jpg differ
diff --git a/images/2024-02-12/09.jpg b/images/2024-02-12/09.jpg
new file mode 100644
index 00000000..31c7cea5
Binary files /dev/null and b/images/2024-02-12/09.jpg differ
diff --git a/images/2024-02-12/10.jpg b/images/2024-02-12/10.jpg
new file mode 100644
index 00000000..33138083
Binary files /dev/null and b/images/2024-02-12/10.jpg differ
diff --git a/images/2024-02-12/11.jpg b/images/2024-02-12/11.jpg
new file mode 100644
index 00000000..51977df9
Binary files /dev/null and b/images/2024-02-12/11.jpg differ
diff --git a/images/2024-02-12/12.jpg b/images/2024-02-12/12.jpg
new file mode 100644
index 00000000..1621652f
Binary files /dev/null and b/images/2024-02-12/12.jpg differ
diff --git a/images/2024-02-12/13.jpg b/images/2024-02-12/13.jpg
new file mode 100644
index 00000000..3902161c
Binary files /dev/null and b/images/2024-02-12/13.jpg differ
diff --git a/images/2024-02-12/14.jpg b/images/2024-02-12/14.jpg
new file mode 100644
index 00000000..f76a3250
Binary files /dev/null and b/images/2024-02-12/14.jpg differ
diff --git a/images/2024-02-12/15.jpg b/images/2024-02-12/15.jpg
new file mode 100644
index 00000000..e82b97a8
Binary files /dev/null and b/images/2024-02-12/15.jpg differ
diff --git a/images/2024-03-01/01.png b/images/2024-03-01/01.png
new file mode 100644
index 00000000..a03f4d9e
Binary files /dev/null and b/images/2024-03-01/01.png differ
diff --git a/images/2024-03-01/02.jpeg b/images/2024-03-01/02.jpeg
new file mode 100644
index 00000000..d7d9799d
Binary files /dev/null and b/images/2024-03-01/02.jpeg differ
diff --git a/images/2024-03-01/03.jpg b/images/2024-03-01/03.jpg
new file mode 100644
index 00000000..45e44d87
Binary files /dev/null and b/images/2024-03-01/03.jpg differ
diff --git a/images/2024-03-01/04.jpg b/images/2024-03-01/04.jpg
new file mode 100644
index 00000000..970e421b
Binary files /dev/null and b/images/2024-03-01/04.jpg differ
diff --git a/images/2024-03-01/05.png b/images/2024-03-01/05.png
new file mode 100644
index 00000000..4f19f418
Binary files /dev/null and b/images/2024-03-01/05.png differ
diff --git a/images/2024-03-11/00.jpeg b/images/2024-03-11/00.jpeg
new file mode 100644
index 00000000..748b6561
Binary files /dev/null and b/images/2024-03-11/00.jpeg differ
diff --git a/images/2024-03-11/01.jpeg b/images/2024-03-11/01.jpeg
new file mode 100644
index 00000000..e6b2a6fb
Binary files /dev/null and b/images/2024-03-11/01.jpeg differ
diff --git a/images/2024-03-11/02.jpeg b/images/2024-03-11/02.jpeg
new file mode 100644
index 00000000..59fe9ebb
Binary files /dev/null and b/images/2024-03-11/02.jpeg differ
diff --git a/images/2024-03-11/03.jpeg b/images/2024-03-11/03.jpeg
new file mode 100644
index 00000000..f2a769ca
Binary files /dev/null and b/images/2024-03-11/03.jpeg differ
diff --git a/images/2024-03-11/04.png b/images/2024-03-11/04.png
new file mode 100644
index 00000000..b8d545cd
Binary files /dev/null and b/images/2024-03-11/04.png differ
diff --git a/images/2024-03-11/05.jpeg b/images/2024-03-11/05.jpeg
new file mode 100644
index 00000000..a25daaed
Binary files /dev/null and b/images/2024-03-11/05.jpeg differ
diff --git a/images/2024-03-11/06.jpeg b/images/2024-03-11/06.jpeg
new file mode 100644
index 00000000..92e39e6f
Binary files /dev/null and b/images/2024-03-11/06.jpeg differ
diff --git a/images/2024-03-11/07.jpeg b/images/2024-03-11/07.jpeg
new file mode 100644
index 00000000..6f678163
Binary files /dev/null and b/images/2024-03-11/07.jpeg differ
diff --git a/images/2024-03-11/08.jpeg b/images/2024-03-11/08.jpeg
new file mode 100644
index 00000000..8a4eb4fd
Binary files /dev/null and b/images/2024-03-11/08.jpeg differ
diff --git a/images/2024-03-11/09.jpeg b/images/2024-03-11/09.jpeg
new file mode 100644
index 00000000..996fa298
Binary files /dev/null and b/images/2024-03-11/09.jpeg differ
diff --git a/images/2024-03-11/10.jpeg b/images/2024-03-11/10.jpeg
new file mode 100644
index 00000000..25e39135
Binary files /dev/null and b/images/2024-03-11/10.jpeg differ
diff --git a/images/2024-03-11/11.jpeg b/images/2024-03-11/11.jpeg
new file mode 100644
index 00000000..1045e64a
Binary files /dev/null and b/images/2024-03-11/11.jpeg differ
diff --git a/images/2024-03-11/12.jpeg b/images/2024-03-11/12.jpeg
new file mode 100644
index 00000000..30ad4313
Binary files /dev/null and b/images/2024-03-11/12.jpeg differ
diff --git a/images/2024-03-11/13.jpeg b/images/2024-03-11/13.jpeg
new file mode 100644
index 00000000..727e9699
Binary files /dev/null and b/images/2024-03-11/13.jpeg differ
diff --git a/images/2024-03-11/14.jpeg b/images/2024-03-11/14.jpeg
new file mode 100644
index 00000000..97d50842
Binary files /dev/null and b/images/2024-03-11/14.jpeg differ
diff --git a/images/2024-06-13/01.jpeg b/images/2024-06-13/01.jpeg
new file mode 100644
index 00000000..6507c0bf
Binary files /dev/null and b/images/2024-06-13/01.jpeg differ
diff --git a/images/2024-06-13/02.jpeg b/images/2024-06-13/02.jpeg
new file mode 100644
index 00000000..8e2f8220
Binary files /dev/null and b/images/2024-06-13/02.jpeg differ
diff --git a/images/2024-06-13/03.jpeg b/images/2024-06-13/03.jpeg
new file mode 100644
index 00000000..36169f2c
Binary files /dev/null and b/images/2024-06-13/03.jpeg differ
diff --git a/images/2024-06-13/04.jpeg b/images/2024-06-13/04.jpeg
new file mode 100644
index 00000000..be1b5ab3
Binary files /dev/null and b/images/2024-06-13/04.jpeg differ
diff --git a/images/2024-06-13/05.jpeg b/images/2024-06-13/05.jpeg
new file mode 100644
index 00000000..47401542
Binary files /dev/null and b/images/2024-06-13/05.jpeg differ
diff --git a/images/Openwrt_creating_secure_WiFi/1.png b/images/Openwrt_creating_secure_WiFi/1.png
new file mode 100644
index 00000000..f8ca5b03
Binary files /dev/null and b/images/Openwrt_creating_secure_WiFi/1.png differ
diff --git a/images/Openwrt_creating_secure_WiFi/2.png b/images/Openwrt_creating_secure_WiFi/2.png
new file mode 100644
index 00000000..eb2cdc80
Binary files /dev/null and b/images/Openwrt_creating_secure_WiFi/2.png differ
diff --git a/images/Openwrt_creating_secure_WiFi/3.png b/images/Openwrt_creating_secure_WiFi/3.png
new file mode 100644
index 00000000..55a96e9b
Binary files /dev/null and b/images/Openwrt_creating_secure_WiFi/3.png differ
diff --git a/images/Openwrt_creating_secure_WiFi/4.png b/images/Openwrt_creating_secure_WiFi/4.png
new file mode 100644
index 00000000..4b5ace65
Binary files /dev/null and b/images/Openwrt_creating_secure_WiFi/4.png differ
diff --git a/images/Openwrt_creating_secure_WiFi/5.png b/images/Openwrt_creating_secure_WiFi/5.png
new file mode 100644
index 00000000..eefb7c79
Binary files /dev/null and b/images/Openwrt_creating_secure_WiFi/5.png differ
diff --git a/images/avatar.jpg b/images/avatar.jpg
new file mode 100644
index 00000000..2e1ca788
Binary files /dev/null and b/images/avatar.jpg differ
diff --git a/images/banner_img/404.jpg b/images/banner_img/404.jpg
new file mode 100644
index 00000000..5257fdb6
Binary files /dev/null and b/images/banner_img/404.jpg differ
diff --git a/images/banner_img/about.jpg b/images/banner_img/about.jpg
new file mode 100644
index 00000000..1f7b7cfc
Binary files /dev/null and b/images/banner_img/about.jpg differ
diff --git a/images/banner_img/default.jpg b/images/banner_img/default.jpg
new file mode 100644
index 00000000..ec75f8a7
Binary files /dev/null and b/images/banner_img/default.jpg differ
diff --git a/images/banner_img/links.jpg b/images/banner_img/links.jpg
new file mode 100644
index 00000000..80aeaff1
Binary files /dev/null and b/images/banner_img/links.jpg differ
diff --git a/images/favicon.ico b/images/favicon.ico
new file mode 100644
index 00000000..89d0fac3
Binary files /dev/null and b/images/favicon.ico differ
diff --git a/images/favicon1.png b/images/favicon1.png
new file mode 100644
index 00000000..f2c841b7
Binary files /dev/null and b/images/favicon1.png differ
diff --git a/images/fzyz_jk_grades/guide.png b/images/fzyz_jk_grades/guide.png
new file mode 100644
index 00000000..99dbed74
Binary files /dev/null and b/images/fzyz_jk_grades/guide.png differ
diff --git a/images/fzyz_jk_grades/jumbotron_bg.jpg b/images/fzyz_jk_grades/jumbotron_bg.jpg
new file mode 100644
index 00000000..e856a3c5
Binary files /dev/null and b/images/fzyz_jk_grades/jumbotron_bg.jpg differ
diff --git a/images/icons/GitHub-Mark-32px.png b/images/icons/GitHub-Mark-32px.png
new file mode 100644
index 00000000..8b25551a
Binary files /dev/null and b/images/icons/GitHub-Mark-32px.png differ
diff --git a/images/icons/github.png b/images/icons/github.png
new file mode 100644
index 00000000..22626aa5
Binary files /dev/null and b/images/icons/github.png differ
diff --git "a/images/icons/\351\202\256\344\273\266.png" "b/images/icons/\351\202\256\344\273\266.png"
new file mode 100644
index 00000000..4921d25e
Binary files /dev/null and "b/images/icons/\351\202\256\344\273\266.png" differ
diff --git a/images/js/stickup/stickUp.jquery.json b/images/js/stickup/stickUp.jquery.json
new file mode 100644
index 00000000..ec95b1ae
--- /dev/null
+++ b/images/js/stickup/stickUp.jquery.json
@@ -0,0 +1 @@
+{"name":"stickUp","title":"jQuery stickUp","description":"A jQuery plugin which makes it easy to create a sticky element or navbar. Has anchor features for one-pagers, as well as more.","keywords":["menu","navigation","fixed","top","sticky","plugin"],"version":"0.5.7","author":{"name":"Liran Cohen","url":"http://lirancohen.github.io/stickUp/"},"maintainers":[{"name":"Liran Cohen","email":"lirancohen@me.com","url":"http://www.github.com/lirancohen/"}],"licenses":[{"type":"LGPL","url":"http://www.gnu.org/licenses/lgpl.html"}],"bugs":"https://github.com/LiranCohen/stickUp/issues","homepage":"http://lirancohen.github.io/stickUp/","docs":"http://lirancohen.github.io/stickUp/#installation","download":"https://github.com/LiranCohen/stickUp/archive/master.zip","dependencies":{"jquery":">=1.8"}}
\ No newline at end of file
diff --git a/images/js/stickup/stickUp.js b/images/js/stickup/stickUp.js
new file mode 100644
index 00000000..7d9e8617
--- /dev/null
+++ b/images/js/stickup/stickUp.js
@@ -0,0 +1,122 @@
+jQuery(
+function($) {
+
+ $(document).ready(function(){
+ var contentButton = [];
+ var contentTop = [];
+ var content = [];
+ var lastScrollTop = 0;
+ var scrollDir = '';
+ var itemClass = '';
+ var itemHover = '';
+ var menuSize = null;
+ var stickyHeight = 0;
+ var stickyMarginB = 0;
+ var currentMarginT = 0;
+ var topMargin = 0;
+ $(window).scroll(function(event){
+ var st = $(this).scrollTop();
+ if (st > lastScrollTop){
+ scrollDir = 'down';
+ } else {
+ scrollDir = 'up';
+ }
+ lastScrollTop = st;
+ });
+ $.fn.stickUp = function( options ) {
+ // adding a class to users div
+ $(this).addClass('stuckMenu');
+ //getting options
+ var objn = 0;
+ if(options != null) {
+ for(var o in options.parts) {
+ if (options.parts.hasOwnProperty(o)){
+ content[objn] = options.parts[objn];
+ objn++;
+ }
+ }
+ if(objn == 0) {
+ console.log('error:needs arguments');
+ }
+
+ itemClass = options.itemClass;
+ itemHover = options.itemHover;
+ if(options.topMargin != null) {
+ if(options.topMargin == 'auto') {
+ topMargin = parseInt($('.stuckMenu').css('margin-top'));
+ } else {
+ if(isNaN(options.topMargin) && options.topMargin.search("px") > 0){
+ topMargin = parseInt(options.topMargin.replace("px",""));
+ } else if(!isNaN(parseInt(options.topMargin))) {
+ topMargin = parseInt(options.topMargin);
+ } else {
+ console.log("incorrect argument, ignored.");
+ topMargin = 0;
+ }
+ }
+ } else {
+ topMargin = 0;
+ }
+ menuSize = $('.'+itemClass).size();
+ }
+ stickyHeight = parseInt($(this).height());
+ stickyMarginB = parseInt($(this).css('margin-bottom'));
+ currentMarginT = parseInt($(this).next().closest('div').css('margin-top'));
+ vartop = parseInt($(this).offset().top);
+ //$(this).find('*').removeClass(itemHover);
+ }
+ $(document).on('scroll', function() {
+ varscroll = parseInt($(document).scrollTop());
+ if(menuSize != null){
+ for(var i=0;i < menuSize;i++)
+ {
+ contentTop[i] = $('#'+content[i]+'').offset().top;
+ function bottomView(i) {
+ contentView = $('#'+content[i]+'').height()*.4;
+ testView = contentTop[i] - contentView;
+ //console.log(varscroll);
+ if(varscroll > testView){
+ $('.'+itemClass).removeClass(itemHover);
+ $('.'+itemClass+':eq('+i+')').addClass(itemHover);
+ } else if(varscroll < 50){
+ $('.'+itemClass).removeClass(itemHover);
+ $('.'+itemClass+':eq(0)').addClass(itemHover);
+ }
+ }
+ if(scrollDir == 'down' && varscroll > contentTop[i]-50 && varscroll < contentTop[i]+50) {
+ $('.'+itemClass).removeClass(itemHover);
+ $('.'+itemClass+':eq('+i+')').addClass(itemHover);
+ }
+ if(scrollDir == 'up') {
+ bottomView(i);
+ }
+ }
+ }
+
+
+
+ if(vartop < varscroll + topMargin){
+ $('.stuckMenu').addClass('isStuck');
+ $('.stuckMenu').next().closest('div').css({
+ 'margin-top': stickyHeight + stickyMarginB + currentMarginT + 'px'
+ }, 10);
+ $('.stuckMenu').css("position","fixed");
+ $('.isStuck').css({
+ top: '0px'
+ }, 10, function(){
+
+ });
+ };
+
+ if(varscroll + topMargin < vartop){
+ $('.stuckMenu').removeClass('isStuck');
+ $('.stuckMenu').next().closest('div').css({
+ 'margin-top': currentMarginT + 'px'
+ }, 10);
+ $('.stuckMenu').css("position","relative");
+ };
+
+ });
+ });
+
+});
diff --git a/images/js/stickup/stickUp.min.js b/images/js/stickup/stickUp.min.js
new file mode 100644
index 00000000..88dc671e
--- /dev/null
+++ b/images/js/stickup/stickUp.min.js
@@ -0,0 +1 @@
+jQuery(function($){$(document).ready(function(){var contentButton = [];var contentTop = [];var content = [];var lastScrollTop = 0;var scrollDir = '';var itemClass = '';var itemHover = '';var menuSize = null;var stickyHeight = 0;var stickyMarginB = 0;var currentMarginT = 0;var topMargin = 0;$(window).scroll(function(event){var st = $(this).scrollTop();if (st > lastScrollTop){scrollDir = 'down';} else {scrollDir = 'up';}lastScrollTop = st;});$.fn.stickUp = function( options ) {$(this).addClass('stuckMenu');var objn = 0;if(options != null) {for(var o in options.parts) {if (options.parts.hasOwnProperty(o)){content[objn] = options.parts[objn];objn++;}}if(objn == 0) {console.log('error:needs arguments');}itemClass = options.itemClass;itemHover = options.itemHover;if(options.topMargin != null) {if(options.topMargin == 'auto') {topMargin = parseInt($('.stuckMenu').css('margin-top'));} else {if(isNaN(options.topMargin) && options.topMargin.search("px") > 0){topMargin = parseInt(options.topMargin.replace("px",""));} else if(!isNaN(parseInt(options.topMargin))) {topMargin = parseInt(options.topMargin);} else {console.log("incorrect argument, ignored.");topMargin = 0;} }} else {topMargin = 0;}menuSize = $('.'+itemClass).size();}stickyHeight = parseInt($(this).height());stickyMarginB = parseInt($(this).css('margin-bottom'));currentMarginT = parseInt($(this).next().closest('div').css('margin-top'));vartop = parseInt($(this).offset().top);};$(document).on('scroll', function() {varscroll = parseInt($(document).scrollTop());if(menuSize != null){for(var i=0;i < menuSize;i++){contentTop[i] = $('#'+content[i]+'').offset().top;function bottomView(i) {contentView = $('#'+content[i]+'').height()*.4;testView = contentTop[i] - contentView;if(varscroll > testView){$('.'+itemClass).removeClass(itemHover);$('.'+itemClass+':eq('+i+')').addClass(itemHover);} else if(varscroll < 50){$('.'+itemClass).removeClass(itemHover);$('.'+itemClass+':eq(0)').addClass(itemHover);}}if(scrollDir == 'down' && varscroll > contentTop[i]-50 && varscroll < contentTop[i]+50) {$('.'+itemClass).removeClass(itemHover);$('.'+itemClass+':eq('+i+')').addClass(itemHover);}if(scrollDir == 'up') {bottomView(i);}}}if(vartop < varscroll + topMargin){$('.stuckMenu').addClass('isStuck');$('.stuckMenu').next().closest('div').css({'margin-top': stickyHeight + stickyMarginB + currentMarginT + 'px'}, 10);$('.stuckMenu').css("position","fixed");$('.isStuck').css({top: '0px'}, 10, function(){});};if(varscroll + topMargin < vartop){$('.stuckMenu').removeClass('isStuck');$('.stuckMenu').next().closest('div').css({'margin-top': currentMarginT + 'px'}, 10);$('.stuckMenu').css("position","relative");};});});});
\ No newline at end of file
diff --git a/images/mea.JPG b/images/mea.JPG
new file mode 100644
index 00000000..761da6c2
Binary files /dev/null and b/images/mea.JPG differ
diff --git a/images/photo_wall/2024/12/VRChat_2024-12-01_23-23-23.366_3840x2160.jpeg b/images/photo_wall/2024/12/VRChat_2024-12-01_23-23-23.366_3840x2160.jpeg
new file mode 100644
index 00000000..50454075
Binary files /dev/null and b/images/photo_wall/2024/12/VRChat_2024-12-01_23-23-23.366_3840x2160.jpeg differ
diff --git a/images/photo_wall/2024/12/VRChat_2024-12-07_23-56-33.278_3840x2160.jpeg b/images/photo_wall/2024/12/VRChat_2024-12-07_23-56-33.278_3840x2160.jpeg
new file mode 100644
index 00000000..d4008b54
Binary files /dev/null and b/images/photo_wall/2024/12/VRChat_2024-12-07_23-56-33.278_3840x2160.jpeg differ
diff --git a/images/photo_wall/2024/12/VRChat_2024-12-08_23-42-00.596_3840x2160.jpeg b/images/photo_wall/2024/12/VRChat_2024-12-08_23-42-00.596_3840x2160.jpeg
new file mode 100644
index 00000000..831cdf0b
Binary files /dev/null and b/images/photo_wall/2024/12/VRChat_2024-12-08_23-42-00.596_3840x2160.jpeg differ
diff --git a/images/photo_wall/2024/12/VRChat_2024-12-12_22-55-03.675_3840x2160.jpeg b/images/photo_wall/2024/12/VRChat_2024-12-12_22-55-03.675_3840x2160.jpeg
new file mode 100644
index 00000000..e53e94ea
Binary files /dev/null and b/images/photo_wall/2024/12/VRChat_2024-12-12_22-55-03.675_3840x2160.jpeg differ
diff --git a/img/avatar.png b/img/avatar.png
new file mode 100644
index 00000000..ffd1c779
Binary files /dev/null and b/img/avatar.png differ
diff --git a/img/default.png b/img/default.png
new file mode 100644
index 00000000..2bc2cd74
Binary files /dev/null and b/img/default.png differ
diff --git a/img/fluid.png b/img/fluid.png
new file mode 100644
index 00000000..368a58ac
Binary files /dev/null and b/img/fluid.png differ
diff --git a/img/loading.gif b/img/loading.gif
new file mode 100644
index 00000000..c5126ed9
Binary files /dev/null and b/img/loading.gif differ
diff --git a/img/police_beian.png b/img/police_beian.png
new file mode 100644
index 00000000..60190da0
Binary files /dev/null and b/img/police_beian.png differ
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..534782cf
--- /dev/null
+++ b/index.html
@@ -0,0 +1,963 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 日本之行的第二站 - 奈良。
+
+
+
+
+
+
+
+
+ 2024-03-11
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 日本之行的第一站 - 大阪。
+
+
+
+
+
+
+
+
+ 2024-01-21
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git a/js/boot.js b/js/boot.js
new file mode 100644
index 00000000..26c3a1a3
--- /dev/null
+++ b/js/boot.js
@@ -0,0 +1,22 @@
+/* global Fluid */
+
+Fluid.boot = {};
+
+Fluid.boot.registerEvents = function() {
+ Fluid.events.billboard();
+ Fluid.events.registerNavbarEvent();
+ Fluid.events.registerParallaxEvent();
+ Fluid.events.registerScrollDownArrowEvent();
+ Fluid.events.registerScrollTopArrowEvent();
+ Fluid.events.registerImageLoadedEvent();
+};
+
+Fluid.boot.refresh = function() {
+ Fluid.plugins.fancyBox();
+ Fluid.plugins.codeWidget();
+ Fluid.events.refresh();
+};
+
+document.addEventListener('DOMContentLoaded', function() {
+ Fluid.boot.registerEvents();
+});
diff --git a/js/color-schema.js b/js/color-schema.js
new file mode 100644
index 00000000..1931a20b
--- /dev/null
+++ b/js/color-schema.js
@@ -0,0 +1,286 @@
+/* global Fluid */
+
+/**
+ * Modified from https://blog.skk.moe/post/hello-darkmode-my-old-friend/
+ */
+(function(window, document) {
+ var rootElement = document.documentElement;
+ var colorSchemaStorageKey = 'Fluid_Color_Scheme';
+ var colorSchemaMediaQueryKey = '--color-mode';
+ var userColorSchemaAttributeName = 'data-user-color-scheme';
+ var defaultColorSchemaAttributeName = 'data-default-color-scheme';
+ var colorToggleButtonSelector = '#color-toggle-btn';
+ var colorToggleIconSelector = '#color-toggle-icon';
+ var iframeSelector = 'iframe';
+
+ function setLS(k, v) {
+ try {
+ localStorage.setItem(k, v);
+ } catch (e) {}
+ }
+
+ function removeLS(k) {
+ try {
+ localStorage.removeItem(k);
+ } catch (e) {}
+ }
+
+ function getLS(k) {
+ try {
+ return localStorage.getItem(k);
+ } catch (e) {
+ return null;
+ }
+ }
+
+ function getSchemaFromHTML() {
+ var res = rootElement.getAttribute(defaultColorSchemaAttributeName);
+ if (typeof res === 'string') {
+ return res.replace(/["'\s]/g, '');
+ }
+ return null;
+ }
+
+ function getSchemaFromCSSMediaQuery() {
+ var res = getComputedStyle(rootElement).getPropertyValue(
+ colorSchemaMediaQueryKey
+ );
+ if (typeof res === 'string') {
+ return res.replace(/["'\s]/g, '');
+ }
+ return null;
+ }
+
+ function resetSchemaAttributeAndLS() {
+ rootElement.setAttribute(userColorSchemaAttributeName, getDefaultColorSchema());
+ removeLS(colorSchemaStorageKey);
+ }
+
+ var validColorSchemaKeys = {
+ dark : true,
+ light: true
+ };
+
+ function getDefaultColorSchema() {
+ // 取默认字段的值
+ var schema = getSchemaFromHTML();
+ // 如果明确指定了 schema 则返回
+ if (validColorSchemaKeys[schema]) {
+ return schema;
+ }
+ // 默认优先按 prefers-color-scheme
+ schema = getSchemaFromCSSMediaQuery();
+ if (validColorSchemaKeys[schema]) {
+ return schema;
+ }
+ // 否则按本地时间是否大于 18 点或凌晨 0 ~ 6 点
+ var hours = new Date().getHours();
+ if (hours >= 18 || (hours >= 0 && hours <= 6)) {
+ return 'dark';
+ }
+ return 'light';
+ }
+
+ function applyCustomColorSchemaSettings(schema) {
+ // 接受从「开关」处传来的模式,或者从 localStorage 读取,否则按默认设置值
+ var current = schema || getLS(colorSchemaStorageKey) || getDefaultColorSchema();
+
+ if (current === getDefaultColorSchema()) {
+ // 当用户切换的显示模式和默认模式相同时,则恢复为自动模式
+ resetSchemaAttributeAndLS();
+ } else if (validColorSchemaKeys[current]) {
+ rootElement.setAttribute(
+ userColorSchemaAttributeName,
+ current
+ );
+ } else {
+ // 特殊情况重置
+ resetSchemaAttributeAndLS();
+ return;
+ }
+
+ // 根据当前模式设置图标
+ setButtonIcon(current);
+
+ // 设置代码高亮
+ setHighlightCSS(current);
+
+ // 设置其他应用
+ setApplications(current);
+ }
+
+ var invertColorSchemaObj = {
+ dark : 'light',
+ light: 'dark'
+ };
+
+ function getIconClass(scheme) {
+ return 'icon-' + scheme;
+ }
+
+ function toggleCustomColorSchema() {
+ var currentSetting = getLS(colorSchemaStorageKey);
+
+ if (validColorSchemaKeys[currentSetting]) {
+ // 从 localStorage 中读取模式,并取相反的模式
+ currentSetting = invertColorSchemaObj[currentSetting];
+ } else if (currentSetting === null) {
+ // 当 localStorage 中没有相关值,或者 localStorage 抛了 Error
+ // 先按照按钮的状态进行切换
+ var iconElement = document.querySelector(colorToggleIconSelector);
+ if (iconElement) {
+ currentSetting = iconElement.getAttribute('data');
+ }
+ if (!iconElement || !validColorSchemaKeys[currentSetting]) {
+ // 当 localStorage 中没有相关值,或者 localStorage 抛了 Error,则读取默认值并切换到相反的模式
+ currentSetting = invertColorSchemaObj[getSchemaFromCSSMediaQuery()];
+ }
+ } else {
+ return;
+ }
+ // 将相反的模式写入 localStorage
+ setLS(colorSchemaStorageKey, currentSetting);
+
+ return currentSetting;
+ }
+
+ function setButtonIcon(schema) {
+ if (validColorSchemaKeys[schema]) {
+ // 切换图标
+ var icon = getIconClass('dark');
+ if (schema) {
+ icon = getIconClass(schema);
+ }
+ var iconElement = document.querySelector(colorToggleIconSelector);
+ if (iconElement) {
+ iconElement.setAttribute(
+ 'class',
+ 'iconfont ' + icon
+ );
+ iconElement.setAttribute(
+ 'data',
+ invertColorSchemaObj[schema]
+ );
+ } else {
+ // 如果图标不存在则说明图标还没加载出来,等到页面全部加载再尝试切换
+ Fluid.utils.waitElementLoaded(colorToggleIconSelector, function() {
+ var iconElement = document.querySelector(colorToggleIconSelector);
+ if (iconElement) {
+ iconElement.setAttribute(
+ 'class',
+ 'iconfont ' + icon
+ );
+ iconElement.setAttribute(
+ 'data',
+ invertColorSchemaObj[schema]
+ );
+ }
+ });
+ }
+ if (document.documentElement.getAttribute('data-user-color-scheme')) {
+ var color = getComputedStyle(document.documentElement).getPropertyValue('--navbar-bg-color').trim()
+ document.querySelector('meta[name="theme-color"]').setAttribute('content', color)
+ }
+ }
+ }
+
+ function setHighlightCSS(schema) {
+ // 启用对应的代码高亮的样式
+ var lightCss = document.getElementById('highlight-css');
+ var darkCss = document.getElementById('highlight-css-dark');
+ if (schema === 'dark') {
+ if (darkCss) {
+ darkCss.removeAttribute('disabled');
+ }
+ if (lightCss) {
+ lightCss.setAttribute('disabled', '');
+ }
+ } else {
+ if (lightCss) {
+ lightCss.removeAttribute('disabled');
+ }
+ if (darkCss) {
+ darkCss.setAttribute('disabled', '');
+ }
+ }
+
+ setTimeout(function() {
+ // 设置代码块组件样式
+ document.querySelectorAll('.markdown-body pre').forEach((pre) => {
+ var cls = Fluid.utils.getBackgroundLightness(pre) >= 0 ? 'code-widget-light' : 'code-widget-dark';
+ var widget = pre.querySelector('.code-widget-light, .code-widget-dark');
+ if (widget) {
+ widget.classList.remove('code-widget-light', 'code-widget-dark');
+ widget.classList.add(cls);
+ }
+ });
+ }, 200);
+ }
+
+ function setApplications(schema) {
+ // 设置 remark42 评论主题
+ if (window.REMARK42) {
+ window.REMARK42.changeTheme(schema);
+ }
+
+ // 设置 cusdis 评论主题
+ if (window.CUSDIS) {
+ window.CUSDIS.setTheme(schema);
+ }
+
+ // 设置 utterances 评论主题
+ var utterances = document.querySelector('.utterances-frame');
+ if (utterances) {
+ var utterancesTheme = schema === 'dark' ? window.UtterancesThemeDark : window.UtterancesThemeLight;
+ const message = {
+ type : 'set-theme',
+ theme: utterancesTheme
+ };
+ utterances.contentWindow.postMessage(message, 'https://utteranc.es');
+ }
+
+ // 设置 giscus 评论主题
+ var giscus = document.querySelector('iframe.giscus-frame');
+ if (giscus) {
+ var giscusTheme = schema === 'dark' ? window.GiscusThemeDark : window.GiscusThemeLight;
+ const message = {
+ setConfig: {
+ theme: giscusTheme,
+ }
+ };
+ // giscus.style.cssText += 'color-scheme: normal;';
+ giscus.contentWindow.postMessage({ 'giscus': message }, 'https://giscus.app');
+ }
+ }
+
+ // 当页面加载时,将显示模式设置为 localStorage 中自定义的值(如果有的话)
+ applyCustomColorSchemaSettings();
+
+ Fluid.utils.waitElementLoaded(colorToggleIconSelector, function() {
+ applyCustomColorSchemaSettings();
+ var button = document.querySelector(colorToggleButtonSelector);
+ if (button) {
+ // 当用户点击切换按钮时,获得新的显示模式、写入 localStorage、并在页面上生效
+ button.addEventListener('click', function() {
+ applyCustomColorSchemaSettings(toggleCustomColorSchema());
+ });
+ var icon = document.querySelector(colorToggleIconSelector);
+ if (icon) {
+ // 光标悬停在按钮上时,切换图标
+ button.addEventListener('mouseenter', function() {
+ var current = icon.getAttribute('data');
+ icon.classList.replace(getIconClass(invertColorSchemaObj[current]), getIconClass(current));
+ });
+ button.addEventListener('mouseleave', function() {
+ var current = icon.getAttribute('data');
+ icon.classList.replace(getIconClass(current), getIconClass(invertColorSchemaObj[current]));
+ });
+ }
+ }
+ });
+
+ Fluid.utils.waitElementLoaded(iframeSelector, function() {
+ applyCustomColorSchemaSettings();
+ });
+
+})(window, document);
diff --git a/js/events.js b/js/events.js
new file mode 100644
index 00000000..55e255f6
--- /dev/null
+++ b/js/events.js
@@ -0,0 +1,184 @@
+/* global Fluid */
+
+HTMLElement.prototype.wrap = function(wrapper) {
+ this.parentNode.insertBefore(wrapper, this);
+ this.parentNode.removeChild(this);
+ wrapper.appendChild(this);
+};
+
+Fluid.events = {
+
+ registerNavbarEvent: function() {
+ var navbar = jQuery('#navbar');
+ if (navbar.length === 0) {
+ return;
+ }
+ var submenu = jQuery('#navbar .dropdown-menu');
+ if (navbar.offset().top > 0) {
+ navbar.removeClass('navbar-dark');
+ submenu.removeClass('navbar-dark');
+ }
+ Fluid.utils.listenScroll(function() {
+ navbar[navbar.offset().top > 50 ? 'addClass' : 'removeClass']('top-nav-collapse');
+ submenu[navbar.offset().top > 50 ? 'addClass' : 'removeClass']('dropdown-collapse');
+ if (navbar.offset().top > 0) {
+ navbar.removeClass('navbar-dark');
+ submenu.removeClass('navbar-dark');
+ } else {
+ navbar.addClass('navbar-dark');
+ submenu.removeClass('navbar-dark');
+ }
+ });
+ jQuery('#navbar-toggler-btn').on('click', function() {
+ jQuery('.animated-icon').toggleClass('open');
+ jQuery('#navbar').toggleClass('navbar-col-show');
+ });
+ },
+
+ registerParallaxEvent: function() {
+ var ph = jQuery('#banner[parallax="true"]');
+ if (ph.length === 0) {
+ return;
+ }
+ var board = jQuery('#board');
+ if (board.length === 0) {
+ return;
+ }
+ var parallax = function() {
+ var pxv = jQuery(window).scrollTop() / 5;
+ var offset = parseInt(board.css('margin-top'), 10);
+ var max = 96 + offset;
+ if (pxv > max) {
+ pxv = max;
+ }
+ ph.css({
+ transform: 'translate3d(0,' + pxv + 'px,0)'
+ });
+ var sideCol = jQuery('.side-col');
+ if (sideCol) {
+ sideCol.css({
+ 'padding-top': pxv + 'px'
+ });
+ }
+ };
+ Fluid.utils.listenScroll(parallax);
+ },
+
+ registerScrollDownArrowEvent: function() {
+ var scrollbar = jQuery('.scroll-down-bar');
+ if (scrollbar.length === 0) {
+ return;
+ }
+ scrollbar.on('click', function() {
+ Fluid.utils.scrollToElement('#board', -jQuery('#navbar').height());
+ });
+ },
+
+ registerScrollTopArrowEvent: function() {
+ var topArrow = jQuery('#scroll-top-button');
+ if (topArrow.length === 0) {
+ return;
+ }
+ var board = jQuery('#board');
+ if (board.length === 0) {
+ return;
+ }
+ var posDisplay = false;
+ var scrollDisplay = false;
+ // Position
+ var setTopArrowPos = function() {
+ var boardRight = board[0].getClientRects()[0].right;
+ var bodyWidth = document.body.offsetWidth;
+ var right = bodyWidth - boardRight;
+ posDisplay = right >= 50;
+ topArrow.css({
+ 'bottom': posDisplay && scrollDisplay ? '20px' : '-60px',
+ 'right' : right - 64 + 'px'
+ });
+ };
+ setTopArrowPos();
+ jQuery(window).resize(setTopArrowPos);
+ // Display
+ var headerHeight = board.offset().top;
+ Fluid.utils.listenScroll(function() {
+ var scrollHeight = document.body.scrollTop + document.documentElement.scrollTop;
+ scrollDisplay = scrollHeight >= headerHeight;
+ topArrow.css({
+ 'bottom': posDisplay && scrollDisplay ? '20px' : '-60px'
+ });
+ });
+ // Click
+ topArrow.on('click', function() {
+ jQuery('body,html').animate({
+ scrollTop: 0,
+ easing : 'swing'
+ });
+ });
+ },
+
+ registerImageLoadedEvent: function() {
+ if (!('NProgress' in window)) { return; }
+
+ var bg = document.getElementById('banner');
+ if (bg) {
+ var src = bg.style.backgroundImage;
+ var url = src.match(/\((.*?)\)/)[1].replace(/(['"])/g, '');
+ var img = new Image();
+ img.onload = function() {
+ window.NProgress && window.NProgress.status !== null && window.NProgress.inc(0.2);
+ };
+ img.src = url;
+ if (img.complete) { img.onload(); }
+ }
+
+ var notLazyImages = jQuery('main img:not([lazyload])');
+ var total = notLazyImages.length;
+ for (const img of notLazyImages) {
+ const old = img.onload;
+ img.onload = function() {
+ old && old();
+ window.NProgress && window.NProgress.status !== null && window.NProgress.inc(0.5 / total);
+ };
+ if (img.complete) { img.onload(); }
+ }
+ },
+
+ registerRefreshCallback: function(callback) {
+ if (!Array.isArray(Fluid.events._refreshCallbacks)) {
+ Fluid.events._refreshCallbacks = [];
+ }
+ Fluid.events._refreshCallbacks.push(callback);
+ },
+
+ refresh: function() {
+ if (Array.isArray(Fluid.events._refreshCallbacks)) {
+ for (var callback of Fluid.events._refreshCallbacks) {
+ if (callback instanceof Function) {
+ callback();
+ }
+ }
+ }
+ },
+
+ billboard: function() {
+ if (!('console' in window)) {
+ return;
+ }
+ // eslint-disable-next-line no-console
+ console.log(`
+-------------------------------------------------
+| |
+| ________ __ _ __ |
+| |_ __ |[ | (_) | ] |
+| | |_ \\_| | | __ _ __ .--.| | |
+| | _| | |[ | | | [ |/ /'\`\\' | |
+| _| |_ | | | \\_/ |, | || \\__/ | |
+| |_____| [___]'.__.'_/[___]'.__.;__] |
+| |
+| Powered by Hexo x Fluid |
+| https://github.com/fluid-dev/hexo-theme-fluid |
+| |
+-------------------------------------------------
+ `);
+ }
+};
diff --git a/js/img-lazyload.js b/js/img-lazyload.js
new file mode 100644
index 00000000..c0c8e4ef
--- /dev/null
+++ b/js/img-lazyload.js
@@ -0,0 +1,10 @@
+/* global Fluid, CONFIG */
+
+(function(window, document) {
+ for (const each of document.querySelectorAll('img[lazyload]')) {
+ Fluid.utils.waitElementVisible(each, function() {
+ each.removeAttribute('srcset');
+ each.removeAttribute('lazyload');
+ }, CONFIG.lazyload.offset_factor);
+ }
+})(window, document);
diff --git a/js/leancloud.js b/js/leancloud.js
new file mode 100644
index 00000000..ab901cec
--- /dev/null
+++ b/js/leancloud.js
@@ -0,0 +1,192 @@
+/* global CONFIG */
+// eslint-disable-next-line no-console
+
+(function(window, document) {
+ // 查询存储的记录
+ function getRecord(Counter, target) {
+ return new Promise(function(resolve, reject) {
+ Counter('get', '/classes/Counter?where=' + encodeURIComponent(JSON.stringify({ target })))
+ .then(resp => resp.json())
+ .then(({ results, code, error }) => {
+ if (code === 401) {
+ throw error;
+ }
+ if (results && results.length > 0) {
+ var record = results[0];
+ resolve(record);
+ } else {
+ Counter('post', '/classes/Counter', { target, time: 0 })
+ .then(resp => resp.json())
+ .then((record, error) => {
+ if (error) {
+ throw error;
+ }
+ resolve(record);
+ }).catch(error => {
+ console.error('Failed to create: ', error);
+ reject(error);
+ });
+ }
+ }).catch((error) => {
+ console.error('LeanCloud Counter Error: ', error);
+ reject(error);
+ });
+ });
+ }
+
+ // 发起自增请求
+ function increment(Counter, incrArr) {
+ return new Promise(function(resolve, reject) {
+ Counter('post', '/batch', {
+ 'requests': incrArr
+ }).then((res) => {
+ res = res.json();
+ if (res.error) {
+ throw res.error;
+ }
+ resolve(res);
+ }).catch((error) => {
+ console.error('Failed to save visitor count: ', error);
+ reject(error);
+ });
+ });
+ }
+
+ // 构建自增请求体
+ function buildIncrement(objectId) {
+ return {
+ 'method': 'PUT',
+ 'path' : `/1.1/classes/Counter/${objectId}`,
+ 'body' : {
+ 'time': {
+ '__op' : 'Increment',
+ 'amount': 1
+ }
+ }
+ };
+ }
+
+ // 校验是否为有效的 Host
+ function validHost() {
+ if (CONFIG.web_analytics.leancloud.ignore_local) {
+ var hostname = window.location.hostname;
+ if (hostname === 'localhost' || hostname === '127.0.0.1') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // 校验是否为有效的 UV
+ function validUV() {
+ var key = 'LeanCloud_UV_Flag';
+ var flag = localStorage.getItem(key);
+ if (flag) {
+ // 距离标记小于 24 小时则不计为 UV
+ if (new Date().getTime() - parseInt(flag, 10) <= 86400000) {
+ return false;
+ }
+ }
+ localStorage.setItem(key, new Date().getTime().toString());
+ return true;
+ }
+
+ function addCount(Counter) {
+ var enableIncr = CONFIG.web_analytics.enable && !Fluid.ctx.dnt && validHost();
+ var getterArr = [];
+ var incrArr = [];
+
+ // 请求 PV 并自增
+ var pvCtn = document.querySelector('#leancloud-site-pv-container');
+ if (pvCtn) {
+ var pvGetter = getRecord(Counter, 'site-pv').then((record) => {
+ enableIncr && incrArr.push(buildIncrement(record.objectId));
+ var ele = document.querySelector('#leancloud-site-pv');
+ if (ele) {
+ ele.innerText = (record.time || 0) + (enableIncr ? 1 : 0);
+ pvCtn.style.display = 'inline';
+ }
+ });
+ getterArr.push(pvGetter);
+ }
+
+ // 请求 UV 并自增
+ var uvCtn = document.querySelector('#leancloud-site-uv-container');
+ if (uvCtn) {
+ var uvGetter = getRecord(Counter, 'site-uv').then((record) => {
+ var incrUV = validUV() && enableIncr;
+ incrUV && incrArr.push(buildIncrement(record.objectId));
+ var ele = document.querySelector('#leancloud-site-uv');
+ if (ele) {
+ ele.innerText = (record.time || 0) + (incrUV ? 1 : 0);
+ uvCtn.style.display = 'inline';
+ }
+ });
+ getterArr.push(uvGetter);
+ }
+
+ // 如果有页面浏览数节点,则请求浏览数并自增
+ var viewCtn = document.querySelector('#leancloud-page-views-container');
+ if (viewCtn) {
+ var path = eval(CONFIG.web_analytics.leancloud.path || 'window.location.pathname');
+ var target = decodeURI(path.replace(/\/*(index.html)?$/, '/'));
+ var viewGetter = getRecord(Counter, target).then((record) => {
+ enableIncr && incrArr.push(buildIncrement(record.objectId));
+ var ele = document.querySelector('#leancloud-page-views');
+ if (ele) {
+ ele.innerText = (record.time || 0) + (enableIncr ? 1 : 0);
+ viewCtn.style.display = 'inline';
+ }
+ });
+ getterArr.push(viewGetter);
+ }
+
+ // 如果启动计数自增,批量发起自增请求
+ if (enableIncr) {
+ Promise.all(getterArr).then(() => {
+ incrArr.length > 0 && increment(Counter, incrArr);
+ });
+ }
+ }
+
+ var appId = CONFIG.web_analytics.leancloud.app_id;
+ var appKey = CONFIG.web_analytics.leancloud.app_key;
+ var serverUrl = CONFIG.web_analytics.leancloud.server_url;
+
+ if (!appId) {
+ throw new Error('LeanCloud appId is empty');
+ }
+ if (!appKey) {
+ throw new Error('LeanCloud appKey is empty');
+ }
+
+ function fetchData(api_server) {
+ var Counter = (method, url, data) => {
+ return fetch(`${api_server}/1.1${url}`, {
+ method,
+ headers: {
+ 'X-LC-Id' : appId,
+ 'X-LC-Key' : appKey,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(data)
+ });
+ };
+
+ addCount(Counter);
+ }
+
+ var apiServer = serverUrl || `https://${appId.slice(0, 8).toLowerCase()}.api.lncldglobal.com`;
+
+ if (apiServer) {
+ fetchData(apiServer);
+ } else {
+ fetch('https://app-router.leancloud.cn/2/route?appId=' + appId)
+ .then(resp => resp.json())
+ .then((data) => {
+ if (data.api_server) {
+ fetchData('https://' + data.api_server);
+ }
+ });
+ }
+})(window, document);
diff --git a/js/local-search.js b/js/local-search.js
new file mode 100644
index 00000000..0784a80a
--- /dev/null
+++ b/js/local-search.js
@@ -0,0 +1,159 @@
+/* global CONFIG */
+
+(function() {
+ // Modified from [hexo-generator-search](https://github.com/wzpan/hexo-generator-search)
+ function localSearchFunc(path, searchSelector, resultSelector) {
+ 'use strict';
+ // 0x00. environment initialization
+ var $input = jQuery(searchSelector);
+ var $result = jQuery(resultSelector);
+
+ if ($input.length === 0) {
+ // eslint-disable-next-line no-console
+ throw Error('No element selected by the searchSelector');
+ }
+ if ($result.length === 0) {
+ // eslint-disable-next-line no-console
+ throw Error('No element selected by the resultSelector');
+ }
+
+ if ($result.attr('class').indexOf('list-group-item') === -1) {
+ $result.html('');
+ }
+
+ jQuery.ajax({
+ // 0x01. load xml file
+ url : path,
+ dataType: 'xml',
+ success : function(xmlResponse) {
+ // 0x02. parse xml file
+ var dataList = jQuery('entry', xmlResponse).map(function() {
+ return {
+ title : jQuery('title', this).text(),
+ content: jQuery('content', this).text(),
+ url : jQuery('url', this).text()
+ };
+ }).get();
+
+ if ($result.html().indexOf('list-group-item') === -1) {
+ $result.html('');
+ }
+
+ $input.on('input', function() {
+ // 0x03. parse query to keywords list
+ var content = $input.val();
+ var resultHTML = '';
+ var keywords = content.trim().toLowerCase().split(/[\s-]+/);
+ $result.html('');
+ if (content.trim().length <= 0) {
+ return $input.removeClass('invalid').removeClass('valid');
+ }
+ // 0x04. perform local searching
+ dataList.forEach(function(data) {
+ var isMatch = true;
+ if (!data.title || data.title.trim() === '') {
+ data.title = 'Untitled';
+ }
+ var orig_data_title = data.title.trim();
+ var data_title = orig_data_title.toLowerCase();
+ var orig_data_content = data.content.trim().replace(/<[^>]+>/g, '');
+ var data_content = orig_data_content.toLowerCase();
+ var data_url = data.url;
+ var index_title = -1;
+ var index_content = -1;
+ var first_occur = -1;
+ // Skip matching when content is included in search and content is empty
+ if (CONFIG.include_content_in_search && data_content === '') {
+ isMatch = false;
+ } else {
+ keywords.forEach(function (keyword, i) {
+ index_title = data_title.indexOf(keyword);
+ index_content = data_content.indexOf(keyword);
+
+ if (index_title < 0 && index_content < 0) {
+ isMatch = false;
+ } else {
+ if (index_content < 0) {
+ index_content = 0;
+ }
+ if (i === 0) {
+ first_occur = index_content;
+ }
+ }
+ });
+ }
+ // 0x05. show search results
+ if (isMatch) {
+ resultHTML += '' + orig_data_title + ' ';
+ var content = orig_data_content;
+ if (first_occur >= 0) {
+ // cut out 100 characters
+ var start = first_occur - 20;
+ var end = first_occur + 80;
+
+ if (start < 0) {
+ start = 0;
+ }
+
+ if (start === 0) {
+ end = 100;
+ }
+
+ if (end > content.length) {
+ end = content.length;
+ }
+
+ var match_content = content.substring(start, end);
+
+ // highlight all keywords
+ keywords.forEach(function(keyword) {
+ var regS = new RegExp(keyword, 'gi');
+ match_content = match_content.replace(regS, '' + keyword + ' ');
+ });
+
+ resultHTML += '' + match_content + '...
';
+ }
+ }
+ });
+ if (resultHTML.indexOf('list-group-item') === -1) {
+ return $input.addClass('invalid').removeClass('valid');
+ }
+ $input.addClass('valid').removeClass('invalid');
+ $result.html(resultHTML);
+ });
+ }
+ });
+ }
+
+ function localSearchReset(searchSelector, resultSelector) {
+ 'use strict';
+ var $input = jQuery(searchSelector);
+ var $result = jQuery(resultSelector);
+
+ if ($input.length === 0) {
+ // eslint-disable-next-line no-console
+ throw Error('No element selected by the searchSelector');
+ }
+ if ($result.length === 0) {
+ // eslint-disable-next-line no-console
+ throw Error('No element selected by the resultSelector');
+ }
+
+ $input.val('').removeClass('invalid').removeClass('valid');
+ $result.html('');
+ }
+
+ var modal = jQuery('#modalSearch');
+ var searchSelector = '#local-search-input';
+ var resultSelector = '#local-search-result';
+ modal.on('show.bs.modal', function() {
+ var path = CONFIG.search_path || '/local-search.xml';
+ localSearchFunc(path, searchSelector, resultSelector);
+ });
+ modal.on('shown.bs.modal', function() {
+ jQuery('#local-search-input').focus();
+ });
+ modal.on('hidden.bs.modal', function() {
+ localSearchReset(searchSelector, resultSelector);
+ });
+})();
diff --git a/js/plugins.js b/js/plugins.js
new file mode 100644
index 00000000..2a364b04
--- /dev/null
+++ b/js/plugins.js
@@ -0,0 +1,164 @@
+/* global Fluid, CONFIG */
+
+HTMLElement.prototype.wrap = function(wrapper) {
+ this.parentNode.insertBefore(wrapper, this);
+ this.parentNode.removeChild(this);
+ wrapper.appendChild(this);
+};
+
+Fluid.plugins = {
+
+ typing: function(text) {
+ if (!('Typed' in window)) { return; }
+
+ var typed = new window.Typed('#subtitle', {
+ strings: [
+ ' ',
+ text
+ ],
+ cursorChar: CONFIG.typing.cursorChar,
+ typeSpeed : CONFIG.typing.typeSpeed,
+ loop : CONFIG.typing.loop
+ });
+ typed.stop();
+ var subtitle = document.getElementById('subtitle');
+ if (subtitle) {
+ subtitle.innerText = '';
+ }
+ jQuery(document).ready(function() {
+ typed.start();
+ });
+ },
+
+ fancyBox: function(selector) {
+ if (!CONFIG.image_zoom.enable || !('fancybox' in jQuery)) { return; }
+
+ jQuery(selector || '.markdown-body :not(a) > img, .markdown-body > img').each(function() {
+ var $image = jQuery(this);
+ var imageUrl = $image.attr('data-src') || $image.attr('src') || '';
+ if (CONFIG.image_zoom.img_url_replace) {
+ var rep = CONFIG.image_zoom.img_url_replace;
+ var r1 = rep[0] || '';
+ var r2 = rep[1] || '';
+ if (r1) {
+ if (/^re:/.test(r1)) {
+ r1 = r1.replace(/^re:/, '');
+ var reg = new RegExp(r1, 'gi');
+ imageUrl = imageUrl.replace(reg, r2);
+ } else {
+ imageUrl = imageUrl.replace(r1, r2);
+ }
+ }
+ }
+ var $imageWrap = $image.wrap(`
+ `
+ ).parent('a');
+ if ($imageWrap.length !== 0) {
+ if ($image.is('.group-image-container img')) {
+ $imageWrap.attr('data-fancybox', 'group').attr('rel', 'group');
+ } else {
+ $imageWrap.attr('data-fancybox', 'default').attr('rel', 'default');
+ }
+
+ var imageTitle = $image.attr('title') || $image.attr('alt');
+ if (imageTitle) {
+ $imageWrap.attr('title', imageTitle).attr('data-caption', imageTitle);
+ }
+ }
+ });
+
+ jQuery.fancybox.defaults.hash = false;
+ jQuery('.fancybox').fancybox({
+ loop : true,
+ helpers: {
+ overlay: {
+ locked: false
+ }
+ }
+ });
+ },
+
+ imageCaption: function(selector) {
+ if (!CONFIG.image_caption.enable) { return; }
+
+ jQuery(selector || `.markdown-body > p > img, .markdown-body > figure > img,
+ .markdown-body > p > a.fancybox, .markdown-body > figure > a.fancybox`).each(function() {
+ var $target = jQuery(this);
+ var $figcaption = $target.next('figcaption');
+ if ($figcaption.length !== 0) {
+ $figcaption.addClass('image-caption');
+ } else {
+ var imageTitle = $target.attr('title') || $target.attr('alt');
+ if (imageTitle) {
+ $target.after(`${imageTitle} `);
+ }
+ }
+ });
+ },
+
+ codeWidget() {
+ var enableLang = CONFIG.code_language.enable && CONFIG.code_language.default;
+ var enableCopy = CONFIG.copy_btn && 'ClipboardJS' in window;
+ if (!enableLang && !enableCopy) {
+ return;
+ }
+
+ function getBgClass(ele) {
+ return Fluid.utils.getBackgroundLightness(ele) >= 0 ? 'code-widget-light' : 'code-widget-dark';
+ }
+
+ var copyTmpl = '';
+ copyTmpl += '';
+ copyTmpl += 'LANG';
+ copyTmpl += '
';
+ jQuery('.markdown-body pre').each(function() {
+ var $pre = jQuery(this);
+ if ($pre.find('code.mermaid').length > 0) {
+ return;
+ }
+ if ($pre.find('span.line').length > 0) {
+ return;
+ }
+
+ var lang = '';
+
+ if (enableLang) {
+ lang = CONFIG.code_language.default;
+ if ($pre[0].children.length > 0 && $pre[0].children[0].classList.length >= 2 && $pre.children().hasClass('hljs')) {
+ lang = $pre[0].children[0].classList[1];
+ } else if ($pre[0].getAttribute('data-language')) {
+ lang = $pre[0].getAttribute('data-language');
+ } else if ($pre.parent().hasClass('sourceCode') && $pre[0].children.length > 0 && $pre[0].children[0].classList.length >= 2) {
+ lang = $pre[0].children[0].classList[1];
+ $pre.parent().addClass('code-wrapper');
+ } else if ($pre.parent().hasClass('markdown-body') && $pre[0].classList.length === 0) {
+ $pre.wrap('
');
+ }
+ lang = lang.toUpperCase().replace('NONE', CONFIG.code_language.default);
+ }
+ $pre.append(copyTmpl.replace('LANG', lang).replace('code-widget">',
+ getBgClass($pre[0]) + (enableCopy ? ' code-widget copy-btn" data-clipboard-snippet> ' : ' code-widget">')));
+
+ if (enableCopy) {
+ var clipboard = new ClipboardJS('.copy-btn', {
+ target: function(trigger) {
+ var nodes = trigger.parentNode.childNodes;
+ for (var i = 0; i < nodes.length; i++) {
+ if (nodes[i].tagName === 'CODE') {
+ return nodes[i];
+ }
+ }
+ }
+ });
+ clipboard.on('success', function(e) {
+ e.clearSelection();
+ e.trigger.innerHTML = e.trigger.innerHTML.replace('icon-copy', 'icon-success');
+ setTimeout(function() {
+ e.trigger.innerHTML = e.trigger.innerHTML.replace('icon-success', 'icon-copy');
+ }, 2000);
+ });
+ }
+ });
+ }
+};
diff --git a/js/umami-view.js b/js/umami-view.js
new file mode 100644
index 00000000..6ee3f6a8
--- /dev/null
+++ b/js/umami-view.js
@@ -0,0 +1,99 @@
+// 从配置文件中获取 umami 的配置
+const website_id = CONFIG.web_analytics.umami.website_id;
+// 拼接请求地址
+const request_url = `${CONFIG.web_analytics.umami.api_server}/websites/${website_id}/stats`;
+
+const start_time = new Date(CONFIG.web_analytics.umami.start_time).getTime();
+const end_time = new Date().getTime();
+const token = CONFIG.web_analytics.umami.token;
+
+// 检查配置是否为空
+if (!website_id) {
+ throw new Error("Umami website_id is empty");
+}
+if (!request_url) {
+ throw new Error("Umami request_url is empty");
+}
+if (!start_time) {
+ throw new Error("Umami start_time is empty");
+}
+if (!token) {
+ throw new Error("Umami token is empty");
+}
+
+// 构造请求参数
+const params = new URLSearchParams({
+ startAt: start_time,
+ endAt: end_time,
+});
+// 构造请求头
+const request_header = {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ "x-umami-api-key": "oZKCH3msvqt10VlXKwoJvHclmaS4bVx0",
+ },
+};
+
+// 获取站点统计数据
+async function siteStats() {
+ try {
+ const response = await fetch(`${request_url}?${params}`, request_header);
+ const data = await response.json();
+ const uniqueVisitors = data.uniques.value; // 获取独立访客数
+ const pageViews = data.pageviews.value; // 获取页面浏览量
+
+ let pvCtn = document.querySelector("#umami-site-pv-container");
+ if (pvCtn) {
+ let ele = document.querySelector("#umami-site-pv");
+ if (ele) {
+ ele.textContent = pageViews; // 设置页面浏览量
+ pvCtn.style.display = "inline"; // 将元素显示出来
+ }
+ }
+
+ let uvCtn = document.querySelector("#umami-site-uv-container");
+ if (uvCtn) {
+ let ele = document.querySelector("#umami-site-uv");
+ if (ele) {
+ ele.textContent = uniqueVisitors;
+ uvCtn.style.display = "inline";
+ }
+ }
+ } catch (error) {
+ console.error(error);
+ return "-1";
+ }
+}
+
+// 获取页面浏览量
+async function pageStats(path) {
+ try {
+ const response = await fetch(`${request_url}?${params}&url=${path}`, request_header);
+ const data = await response.json();
+ const pageViews = data.pageviews.value;
+
+ let viewCtn = document.querySelector("#umami-page-views-container");
+ if (viewCtn) {
+ let ele = document.querySelector("#umami-page-views");
+ if (ele) {
+ ele.textContent = pageViews;
+ viewCtn.style.display = "inline";
+ }
+ }
+ } catch (error) {
+ console.error(error);
+ return "-1";
+ }
+}
+
+siteStats();
+
+// 获取页面容器
+let viewCtn = document.querySelector("#umami-page-views-container");
+// 如果页面容器存在,则获取页面浏览量
+if (viewCtn) {
+ let path = window.location.pathname;
+ let target = decodeURI(path.replace(/\/*(index.html)?$/, "/"));
+ pageStats(target);
+}
diff --git a/js/utils.js b/js/utils.js
new file mode 100644
index 00000000..d61bc264
--- /dev/null
+++ b/js/utils.js
@@ -0,0 +1,245 @@
+/* global Fluid, CONFIG */
+
+window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
+
+Fluid.utils = {
+
+ listenScroll: function(callback) {
+ var dbc = new Debouncer(callback);
+ window.addEventListener('scroll', dbc, false);
+ dbc.handleEvent();
+ return dbc;
+ },
+
+ unlistenScroll: function(callback) {
+ window.removeEventListener('scroll', callback);
+ },
+
+ listenDOMLoaded(callback) {
+ if (document.readyState !== 'loading') {
+ callback();
+ } else {
+ document.addEventListener('DOMContentLoaded', function () {
+ callback();
+ });
+ }
+ },
+
+ scrollToElement: function(target, offset) {
+ var of = jQuery(target).offset();
+ if (of) {
+ jQuery('html,body').animate({
+ scrollTop: of.top + (offset || 0),
+ easing : 'swing'
+ });
+ }
+ },
+
+ elementVisible: function(element, offsetFactor) {
+ offsetFactor = offsetFactor && offsetFactor >= 0 ? offsetFactor : 0;
+ var rect = element.getBoundingClientRect();
+ const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
+ return (
+ (rect.top >= 0 && rect.top <= viewportHeight * (1 + offsetFactor) + rect.height / 2) ||
+ (rect.bottom >= 0 && rect.bottom <= viewportHeight * (1 + offsetFactor) + rect.height / 2)
+ );
+ },
+
+ waitElementVisible: function(selectorOrElement, callback, offsetFactor) {
+ var runningOnBrowser = typeof window !== 'undefined';
+ var isBot = (runningOnBrowser && !('onscroll' in window))
+ || (typeof navigator !== 'undefined' && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent));
+ if (!runningOnBrowser || isBot) {
+ return;
+ }
+
+ offsetFactor = offsetFactor && offsetFactor >= 0 ? offsetFactor : 0;
+
+ function waitInViewport(element) {
+ Fluid.utils.listenDOMLoaded(function() {
+ if (Fluid.utils.elementVisible(element, offsetFactor)) {
+ callback();
+ return;
+ }
+ if ('IntersectionObserver' in window) {
+ var io = new IntersectionObserver(function(entries, ob) {
+ if (entries[0].isIntersecting) {
+ callback();
+ ob.disconnect();
+ }
+ }, {
+ threshold : [0],
+ rootMargin: (window.innerHeight || document.documentElement.clientHeight) * offsetFactor + 'px'
+ });
+ io.observe(element);
+ } else {
+ var wrapper = Fluid.utils.listenScroll(function() {
+ if (Fluid.utils.elementVisible(element, offsetFactor)) {
+ Fluid.utils.unlistenScroll(wrapper);
+ callback();
+ }
+ });
+ }
+ });
+ }
+
+ if (typeof selectorOrElement === 'string') {
+ this.waitElementLoaded(selectorOrElement, function(element) {
+ waitInViewport(element);
+ });
+ } else {
+ waitInViewport(selectorOrElement);
+ }
+ },
+
+ waitElementLoaded: function(selector, callback) {
+ var runningOnBrowser = typeof window !== 'undefined';
+ var isBot = (runningOnBrowser && !('onscroll' in window))
+ || (typeof navigator !== 'undefined' && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent));
+ if (!runningOnBrowser || isBot) {
+ return;
+ }
+
+ if ('MutationObserver' in window) {
+ var mo = new MutationObserver(function(records, ob) {
+ var ele = document.querySelector(selector);
+ if (ele) {
+ callback(ele);
+ ob.disconnect();
+ }
+ });
+ mo.observe(document, { childList: true, subtree: true });
+ } else {
+ Fluid.utils.listenDOMLoaded(function() {
+ var waitLoop = function() {
+ var ele = document.querySelector(selector);
+ if (ele) {
+ callback(ele);
+ } else {
+ setTimeout(waitLoop, 100);
+ }
+ };
+ waitLoop();
+ });
+ }
+ },
+
+ createScript: function(url, onload) {
+ var s = document.createElement('script');
+ s.setAttribute('src', url);
+ s.setAttribute('type', 'text/javascript');
+ s.setAttribute('charset', 'UTF-8');
+ s.async = false;
+ if (typeof onload === 'function') {
+ if (window.attachEvent) {
+ s.onreadystatechange = function() {
+ var e = s.readyState;
+ if (e === 'loaded' || e === 'complete') {
+ s.onreadystatechange = null;
+ onload();
+ }
+ };
+ } else {
+ s.onload = onload;
+ }
+ }
+ var ss = document.getElementsByTagName('script');
+ var e = ss.length > 0 ? ss[ss.length - 1] : document.head || document.documentElement;
+ e.parentNode.insertBefore(s, e.nextSibling);
+ },
+
+ createCssLink: function(url) {
+ var l = document.createElement('link');
+ l.setAttribute('rel', 'stylesheet');
+ l.setAttribute('type', 'text/css');
+ l.setAttribute('href', url);
+ var e = document.getElementsByTagName('link')[0]
+ || document.getElementsByTagName('head')[0]
+ || document.head || document.documentElement;
+ e.parentNode.insertBefore(l, e);
+ },
+
+ loadComments: function(selector, loadFunc) {
+ var ele = document.querySelector('#comments[lazyload]');
+ if (ele) {
+ var callback = function() {
+ loadFunc();
+ ele.removeAttribute('lazyload');
+ };
+ Fluid.utils.waitElementVisible(selector, callback, CONFIG.lazyload.offset_factor);
+ } else {
+ loadFunc();
+ }
+ },
+
+ getBackgroundLightness(selectorOrElement) {
+ var ele = selectorOrElement;
+ if (typeof selectorOrElement === 'string') {
+ ele = document.querySelector(selectorOrElement);
+ }
+ var view = ele.ownerDocument.defaultView;
+ if (!view) {
+ view = window;
+ }
+ var rgbArr = view.getComputedStyle(ele).backgroundColor.replace(/rgba*\(/, '').replace(')', '').split(/,\s*/);
+ if (rgbArr.length < 3) {
+ return 0;
+ }
+ var colorCast = (0.213 * rgbArr[0]) + (0.715 * rgbArr[1]) + (0.072 * rgbArr[2]);
+ return colorCast === 0 || colorCast > 255 / 2 ? 1 : -1;
+ },
+
+ retry(handler, interval, times) {
+ if (times <= 0) {
+ return;
+ }
+ var next = function() {
+ if (--times >= 0 && !handler()) {
+ setTimeout(next, interval);
+ }
+ };
+ setTimeout(next, interval);
+ }
+
+};
+
+/**
+ * Handles debouncing of events via requestAnimationFrame
+ * @see http://www.html5rocks.com/en/tutorials/speed/animations/
+ * @param {Function} callback The callback to handle whichever event
+ */
+function Debouncer(callback) {
+ this.callback = callback;
+ this.ticking = false;
+}
+
+Debouncer.prototype = {
+ constructor: Debouncer,
+
+ /**
+ * dispatches the event to the supplied callback
+ * @private
+ */
+ update: function() {
+ this.callback && this.callback();
+ this.ticking = false;
+ },
+
+ /**
+ * ensures events don't get stacked
+ * @private
+ */
+ requestTick: function() {
+ if (!this.ticking) {
+ requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this)));
+ this.ticking = true;
+ }
+ },
+
+ /**
+ * Attach this as the event listeners
+ */
+ handleEvent: function() {
+ this.requestTick();
+ }
+};
diff --git a/lib/imagesloaded.pkgd.min.js b/lib/imagesloaded.pkgd.min.js
new file mode 100644
index 00000000..b55bea29
--- /dev/null
+++ b/lib/imagesloaded.pkgd.min.js
@@ -0,0 +1,12 @@
+/*!
+ * imagesLoaded PACKAGED v5.0.0
+ * JavaScript is all like "You images are done yet or what?"
+ * MIT License
+ */
+!function(t,e){"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e()}("undefined"!=typeof window?window:this,(function(){function t(){}let e=t.prototype;return e.on=function(t,e){if(!t||!e)return this;let i=this._events=this._events||{},s=i[t]=i[t]||[];return s.includes(e)||s.push(e),this},e.once=function(t,e){if(!t||!e)return this;this.on(t,e);let i=this._onceEvents=this._onceEvents||{};return(i[t]=i[t]||{})[e]=!0,this},e.off=function(t,e){let i=this._events&&this._events[t];if(!i||!i.length)return this;let s=i.indexOf(e);return-1!=s&&i.splice(s,1),this},e.emitEvent=function(t,e){let i=this._events&&this._events[t];if(!i||!i.length)return this;i=i.slice(0),e=e||[];let s=this._onceEvents&&this._onceEvents[t];for(let n of i){s&&s[n]&&(this.off(t,n),delete s[n]),n.apply(this,e)}return this},e.allOff=function(){return delete this._events,delete this._onceEvents,this},t})),
+/*!
+ * imagesLoaded v5.0.0
+ * JavaScript is all like "You images are done yet or what?"
+ * MIT License
+ */
+function(t,e){"object"==typeof module&&module.exports?module.exports=e(t,require("ev-emitter")):t.imagesLoaded=e(t,t.EvEmitter)}("undefined"!=typeof window?window:this,(function(t,e){let i=t.jQuery,s=t.console;function n(t,e,o){if(!(this instanceof n))return new n(t,e,o);let r=t;var h;("string"==typeof t&&(r=document.querySelectorAll(t)),r)?(this.elements=(h=r,Array.isArray(h)?h:"object"==typeof h&&"number"==typeof h.length?[...h]:[h]),this.options={},"function"==typeof e?o=e:Object.assign(this.options,e),o&&this.on("always",o),this.getImages(),i&&(this.jqDeferred=new i.Deferred),setTimeout(this.check.bind(this))):s.error(`Bad element for imagesLoaded ${r||t}`)}n.prototype=Object.create(e.prototype),n.prototype.getImages=function(){this.images=[],this.elements.forEach(this.addElementImages,this)};const o=[1,9,11];n.prototype.addElementImages=function(t){"IMG"===t.nodeName&&this.addImage(t),!0===this.options.background&&this.addElementBackgroundImages(t);let{nodeType:e}=t;if(!e||!o.includes(e))return;let i=t.querySelectorAll("img");for(let t of i)this.addImage(t);if("string"==typeof this.options.background){let e=t.querySelectorAll(this.options.background);for(let t of e)this.addElementBackgroundImages(t)}};const r=/url\((['"])?(.*?)\1\)/gi;function h(t){this.img=t}function d(t,e){this.url=t,this.element=e,this.img=new Image}return n.prototype.addElementBackgroundImages=function(t){let e=getComputedStyle(t);if(!e)return;let i=r.exec(e.backgroundImage);for(;null!==i;){let s=i&&i[2];s&&this.addBackground(s,t),i=r.exec(e.backgroundImage)}},n.prototype.addImage=function(t){let e=new h(t);this.images.push(e)},n.prototype.addBackground=function(t,e){let i=new d(t,e);this.images.push(i)},n.prototype.check=function(){if(this.progressedCount=0,this.hasAnyBroken=!1,!this.images.length)return void this.complete();let t=(t,e,i)=>{setTimeout((()=>{this.progress(t,e,i)}))};this.images.forEach((function(e){e.once("progress",t),e.check()}))},n.prototype.progress=function(t,e,i){this.progressedCount++,this.hasAnyBroken=this.hasAnyBroken||!t.isLoaded,this.emitEvent("progress",[this,t,e]),this.jqDeferred&&this.jqDeferred.notify&&this.jqDeferred.notify(this,t),this.progressedCount===this.images.length&&this.complete(),this.options.debug&&s&&s.log(`progress: ${i}`,t,e)},n.prototype.complete=function(){let t=this.hasAnyBroken?"fail":"done";if(this.isComplete=!0,this.emitEvent(t,[this]),this.emitEvent("always",[this]),this.jqDeferred){let t=this.hasAnyBroken?"reject":"resolve";this.jqDeferred[t](this)}},h.prototype=Object.create(e.prototype),h.prototype.check=function(){this.getIsImageComplete()?this.confirm(0!==this.img.naturalWidth,"naturalWidth"):(this.proxyImage=new Image,this.img.crossOrigin&&(this.proxyImage.crossOrigin=this.img.crossOrigin),this.proxyImage.addEventListener("load",this),this.proxyImage.addEventListener("error",this),this.img.addEventListener("load",this),this.img.addEventListener("error",this),this.proxyImage.src=this.img.currentSrc||this.img.src)},h.prototype.getIsImageComplete=function(){return this.img.complete&&this.img.naturalWidth},h.prototype.confirm=function(t,e){this.isLoaded=t;let{parentNode:i}=this.img,s="PICTURE"===i.nodeName?i:this.img;this.emitEvent("progress",[this,s,e])},h.prototype.handleEvent=function(t){let e="on"+t.type;this[e]&&this[e](t)},h.prototype.onload=function(){this.confirm(!0,"onload"),this.unbindEvents()},h.prototype.onerror=function(){this.confirm(!1,"onerror"),this.unbindEvents()},h.prototype.unbindEvents=function(){this.proxyImage.removeEventListener("load",this),this.proxyImage.removeEventListener("error",this),this.img.removeEventListener("load",this),this.img.removeEventListener("error",this)},d.prototype=Object.create(h.prototype),d.prototype.check=function(){this.img.addEventListener("load",this),this.img.addEventListener("error",this),this.img.src=this.url,this.getIsImageComplete()&&(this.confirm(0!==this.img.naturalWidth,"naturalWidth"),this.unbindEvents())},d.prototype.unbindEvents=function(){this.img.removeEventListener("load",this),this.img.removeEventListener("error",this)},d.prototype.confirm=function(t,e){this.isLoaded=t,this.emitEvent("progress",[this,this.element,e])},n.makeJQueryPlugin=function(e){(e=e||t.jQuery)&&(i=e,i.fn.imagesLoaded=function(t,e){return new n(this,t,e).jqDeferred.promise(i(this))})},n.makeJQueryPlugin(),n}));
\ No newline at end of file
diff --git a/lib/masonry.pkgd.min.js b/lib/masonry.pkgd.min.js
new file mode 100644
index 00000000..53386ae6
--- /dev/null
+++ b/lib/masonry.pkgd.min.js
@@ -0,0 +1,9 @@
+/*!
+ * Masonry PACKAGED v4.2.2
+ * Cascading grid layout library
+ * https://masonry.desandro.com
+ * MIT License
+ * by David DeSandro
+ */
+
+!function(t,e){"function"==typeof define&&define.amd?define("jquery-bridget/jquery-bridget",["jquery"],function(i){return e(t,i)}):"object"==typeof module&&module.exports?module.exports=e(t,require("jquery")):t.jQueryBridget=e(t,t.jQuery)}(window,function(t,e){"use strict";function i(i,r,a){function h(t,e,n){var o,r="$()."+i+'("'+e+'")';return t.each(function(t,h){var u=a.data(h,i);if(!u)return void s(i+" not initialized. Cannot call methods, i.e. "+r);var d=u[e];if(!d||"_"==e.charAt(0))return void s(r+" is not a valid method");var l=d.apply(u,n);o=void 0===o?l:o}),void 0!==o?o:t}function u(t,e){t.each(function(t,n){var o=a.data(n,i);o?(o.option(e),o._init()):(o=new r(n,e),a.data(n,i,o))})}a=a||e||t.jQuery,a&&(r.prototype.option||(r.prototype.option=function(t){a.isPlainObject(t)&&(this.options=a.extend(!0,this.options,t))}),a.fn[i]=function(t){if("string"==typeof t){var e=o.call(arguments,1);return h(this,t,e)}return u(this,t),this},n(a))}function n(t){!t||t&&t.bridget||(t.bridget=i)}var o=Array.prototype.slice,r=t.console,s="undefined"==typeof r?function(){}:function(t){r.error(t)};return n(e||t.jQuery),i}),function(t,e){"function"==typeof define&&define.amd?define("ev-emitter/ev-emitter",e):"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e()}("undefined"!=typeof window?window:this,function(){function t(){}var e=t.prototype;return e.on=function(t,e){if(t&&e){var i=this._events=this._events||{},n=i[t]=i[t]||[];return-1==n.indexOf(e)&&n.push(e),this}},e.once=function(t,e){if(t&&e){this.on(t,e);var i=this._onceEvents=this._onceEvents||{},n=i[t]=i[t]||{};return n[e]=!0,this}},e.off=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){var n=i.indexOf(e);return-1!=n&&i.splice(n,1),this}},e.emitEvent=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){i=i.slice(0),e=e||[];for(var n=this._onceEvents&&this._onceEvents[t],o=0;oe;e++){var i=h[e];t[i]=0}return t}function n(t){var e=getComputedStyle(t);return e||a("Style returned "+e+". Are you running this code in a hidden iframe on Firefox? See https://bit.ly/getsizebug1"),e}function o(){if(!d){d=!0;var e=document.createElement("div");e.style.width="200px",e.style.padding="1px 2px 3px 4px",e.style.borderStyle="solid",e.style.borderWidth="1px 2px 3px 4px",e.style.boxSizing="border-box";var i=document.body||document.documentElement;i.appendChild(e);var o=n(e);s=200==Math.round(t(o.width)),r.isBoxSizeOuter=s,i.removeChild(e)}}function r(e){if(o(),"string"==typeof e&&(e=document.querySelector(e)),e&&"object"==typeof e&&e.nodeType){var r=n(e);if("none"==r.display)return i();var a={};a.width=e.offsetWidth,a.height=e.offsetHeight;for(var d=a.isBorderBox="border-box"==r.boxSizing,l=0;u>l;l++){var c=h[l],f=r[c],m=parseFloat(f);a[c]=isNaN(m)?0:m}var p=a.paddingLeft+a.paddingRight,g=a.paddingTop+a.paddingBottom,y=a.marginLeft+a.marginRight,v=a.marginTop+a.marginBottom,_=a.borderLeftWidth+a.borderRightWidth,z=a.borderTopWidth+a.borderBottomWidth,E=d&&s,b=t(r.width);b!==!1&&(a.width=b+(E?0:p+_));var x=t(r.height);return x!==!1&&(a.height=x+(E?0:g+z)),a.innerWidth=a.width-(p+_),a.innerHeight=a.height-(g+z),a.outerWidth=a.width+y,a.outerHeight=a.height+v,a}}var s,a="undefined"==typeof console?e:function(t){console.error(t)},h=["paddingLeft","paddingRight","paddingTop","paddingBottom","marginLeft","marginRight","marginTop","marginBottom","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth"],u=h.length,d=!1;return r}),function(t,e){"use strict";"function"==typeof define&&define.amd?define("desandro-matches-selector/matches-selector",e):"object"==typeof module&&module.exports?module.exports=e():t.matchesSelector=e()}(window,function(){"use strict";var t=function(){var t=window.Element.prototype;if(t.matches)return"matches";if(t.matchesSelector)return"matchesSelector";for(var e=["webkit","moz","ms","o"],i=0;is?"round":"floor";r=Math[a](r),this.cols=Math.max(r,1)},n.getContainerWidth=function(){var t=this._getOption("fitWidth"),i=t?this.element.parentNode:this.element,n=e(i);this.containerWidth=n&&n.innerWidth},n._getItemLayoutPosition=function(t){t.getSize();var e=t.size.outerWidth%this.columnWidth,i=e&&1>e?"round":"ceil",n=Math[i](t.size.outerWidth/this.columnWidth);n=Math.min(n,this.cols);for(var o=this.options.horizontalOrder?"_getHorizontalColPosition":"_getTopColPosition",r=this[o](n,t),s={x:this.columnWidth*r.col,y:r.y},a=r.y+t.size.outerHeight,h=n+r.col,u=r.col;h>u;u++)this.colYs[u]=a;return s},n._getTopColPosition=function(t){var e=this._getTopColGroup(t),i=Math.min.apply(Math,e);return{col:e.indexOf(i),y:i}},n._getTopColGroup=function(t){if(2>t)return this.colYs;for(var e=[],i=this.cols+1-t,n=0;i>n;n++)e[n]=this._getColGroupY(n,t);return e},n._getColGroupY=function(t,e){if(2>e)return this.colYs[t];var i=this.colYs.slice(t,t+e);return Math.max.apply(Math,i)},n._getHorizontalColPosition=function(t,e){var i=this.horizontalColIndex%this.cols,n=t>1&&i+t>this.cols;i=n?0:i;var o=e.size.outerWidth&&e.size.outerHeight;return this.horizontalColIndex=o?i+t:this.horizontalColIndex,{col:i,y:this._getColGroupY(i,t)}},n._manageStamp=function(t){var i=e(t),n=this._getElementOffset(t),o=this._getOption("originLeft"),r=o?n.left:n.right,s=r+i.outerWidth,a=Math.floor(r/this.columnWidth);a=Math.max(0,a);var h=Math.floor(s/this.columnWidth);h-=s%this.columnWidth?0:1,h=Math.min(this.cols-1,h);for(var u=this._getOption("originTop"),d=(u?n.top:n.bottom)+i.outerHeight,l=a;h>=l;l++)this.colYs[l]=Math.max(d,this.colYs[l])},n._getContainerSize=function(){this.maxY=Math.max.apply(Math,this.colYs);var t={height:this.maxY};return this._getOption("fitWidth")&&(t.width=this._getContainerFitWidth()),t},n._getContainerFitWidth=function(){for(var t=0,e=this.cols;--e&&0===this.colYs[e];)t++;return(this.cols-t)*this.columnWidth-this.gutter},n.needsResizeLayout=function(){var t=this.containerWidth;return this.getContainerWidth(),t!=this.containerWidth},i});
\ No newline at end of file
diff --git a/links/index.html b/links/index.html
new file mode 100644
index 00000000..20c67911
--- /dev/null
+++ b/links/index.html
@@ -0,0 +1,469 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 友链 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git a/local-search.xml b/local-search.xml
new file mode 100644
index 00000000..3cf5fa39
--- /dev/null
+++ b/local-search.xml
@@ -0,0 +1,1357 @@
+
+
+
+
+
+
+ 我再也不能不假思索
+
+ /archives/2c54052d.html
+
+ 正值下班时间,你拖着疲惫的身体漫步街头。地铁站的入口熙熙攘攘,那是回家的方向。
正要走下地铁站路口的阶梯时,你发现了一个小孩子正朝着上行方向的电动扶梯走去。那是个连路都还走不清楚的孩子。
他的父母呢?你正张望着,希望能找到那个冒失的,或许正在专心看着手机而放松了警惕的父亲或母亲。
孩子朝着电梯摇摇晃晃地走了几步。想从上行的电梯往下走,这对于成年人都不是一件容易的事情,何况是小孩子。他大概刚踩上运行着的扶梯就会摔倒吧,真危险。你这么想着,稍微放慢了一些脚步,继续观察着孩子,也在观察着是否有人会出手拦住他。
你看到一个上了些年纪,头发有些发白的老人。他双手都拎着东西,是柴米油盐,重量拉低了他的双肩。他会帮忙吗?
你想起了上周,也是在下班的时间点,与即将乘坐的是同一个班次的地铁上,你幸运地找到了车厢里的最后一个座位。椅背上有没有贴着“爱心座位”的标志,你已经不记得了。你和今天一般疲惫,正在漫无目的地滑着手机屏幕,打发着无聊的通勤时间。视线越过手机屏幕,你瞥见一个老人,头发有些发白,双手都拎着重物,站在你的面前。
要让出座位吗?你一边滑动手机,一边思考着。“刚下班,大家都很辛苦”,你想起了在食堂偶然间听到的同事间的对话,“虽然我怀孕了,很需要一个座位。不过看到大家都这么累,我也不好说些什么了”。是啊,刚刚完成了一天工作的你,正需要充分的休息,哪怕是在地铁里坐下十几分钟对你也是无比的重要。想到这里,你底下了头,继续看起了手机……
也许是双手的重物让老人无暇顾及他人,迫使他加快步伐向下走去,他并没有发现那个正朝着上行扶梯走去的小孩子。此时孩子又向着扶梯迈出了几步。再往前几步,就踏上扶梯出口的金属盖板了。接下来会发生的事情难以想象。
会有人帮忙的吧?你继续张望着,同时心中也在盘算着——拦住了这个孩子,就意味着得守着他,直到那个粗心的父母匆匆赶来。谁知道要等多久,绝对是赶不上马上要到站的地铁了。你尝试把目光瞥向其他地方,似乎不再去看那个孩子,他就会安然无恙。但你做不到。
有一个阿姨快步走上前来,是要来帮忙的吗?你有一些即视感,仿佛之前在哪里见过这位大约四十岁出头,看上去有些憔悴的阿姨。
那是前几个月的一个早晨,你刚入职公司不久。向来遵守规矩的你今天也准时来到地铁站,乘上与昨天、前天、大前天都是同一时刻到站的地铁,这样就能准时到达公司,给大家留下一个好印象。地铁缓缓驶入站台,车门在悦耳的提示声中打开。此时,隔壁车门处却传来不和谐的声音。你循声望去,有人摔倒在列车与站台的间隙处,是一个看着四十岁左右的女性。是不小心绊倒了吗?还是没有吃早饭,有些低血糖了?不管怎样,她瘫坐在门口,久久没有起身。
要去扶一下吗?你迟疑了一会儿,没有上车。地铁站里有那么多保安,他们应该会处理好吧。扶起来之后,肯定得陪着她,直到有工作人员来照看才能离开吧,那今天早上要迟到了。你思考片刻,还是踏上了地铁,但还是转过身来,透过车窗继续观察。你看到有人和地铁站的保安一同将这位女性搀扶至阶梯边,地铁的门得以关闭,你可以准时到达公司了。想到这里,你的心安定了一些……
可惜的是,阿姨也没有注意到一个孩子正全然无知地朝着危险走去,就走下了楼梯。孩子摇摇晃晃地向前迈步,顷刻间,一只脚已经踏上了扶梯的盖板,再向前几步,就到了不断运转着的扶梯。
这样下去那个孩子肯定会摔下去的,你稍稍将行走的方向朝扶梯偏移了些许,做好了冲刺、抓住孩子的准备。同时,你也祈祷着能有人抢先你一步出手,并收拾剩下的残局。
一步、又一步,真的没有人肯帮忙一下吗?或许此刻真的只有我一个人注意到了这个孩子?你的视线游离,大步朝着扶梯走去,却被一个飞跃而过的身影叫停了步伐。
“小朋友,等一下。你的爸爸妈妈呢?”是一个年轻人,背着皮包,打扮时髦。他拦住了孩子,一只大手挡住了孩子前往扶梯的路,将孩子推离了危险。
看到这里,悬在空中的石头落地,你便也随着大流走下了阶梯。只不过,在那之后,在等候地铁时、坐在地铁中、或是淋浴、发呆之际,你都会回忆起过去的你,那个会毫不犹豫拾起地板上的垃圾、主动让出座位、扶起摔倒的人、给予他人帮助的你。你会不由自主地问自己:
我从什么时候开始,再也不能不假思索地做一件事情?
]]>
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ Soundcore C30i 耳机
+
+ /archives/95479b1f.html
+
+ 我目前在使用的耳机是 Sony LinkBuds,就是那款中间带了个孔的奇怪耳机。
它本是为了商务人士在办公场所使用而设计,收听耳机中声音的同时不会影响听取他人的谈话。它却被我这个耳道经常发炎的人看上了。
我想,中间带了个孔,那耳道内外的空气就是完全流通的,那耳道内肯定不会闷了吧!为了验证这一点,我特地去 Sony 的线下店体验这款耳机。佩戴上 LinkBuds,确实完全没有耳道被封闭的感觉,我便在网上欣然下单了。
然而,经过一段时间的佩戴,才发现我的想法过于天真:看似畅通无阻的气流,在直径不够大的圆环处有阻塞。内部产生的水蒸气无法排出,导致耳道仍然会有潮湿感。佩戴20分钟以上,摘下耳机放入耳机舱,一段时间后取出,会发现耳机舱内有冷凝水。
Sony LinkBuds 仍然不能解决我耳朵发炎的困扰。我便又踏上了寻找新耳机的旅途。
今天便是要介绍我找到的其中一款,有望成为最终解决方案的耳机 —— SoundCore C30i。
设计
耳机充电盒的外观,以及开盖后的样子如上图所示,就不多介绍了。
开放式耳机相对于入耳式、半入耳式耳机,设计上花样多了不少。我看到的所有款式中,大致可以分为两类:
使用挂钩悬挂在耳朵之后,将扬声器覆盖于耳道口,通过空气传导声音进入耳道的; 夹在耳垂上,耳机本身不覆盖或仅部分覆盖耳道口,通过侧向开口将声音导向耳道的。 SoundCore C30i 属于后者。
我选择的是透明外壳的款式,耳机内部结构清晰可见。
左边耳机的扬声器处有一个开口,这便是朝向耳道的声音出口。
右耳朝上的是耳机背面,金色的圆片是触控传感器。
佩戴感 夹耳的耳机其实还可以再分类。一种是本体柔软、有弹力,有点像悬挂在耳朵上的,将扬声器更多地覆盖于耳道口,以提升传音效率的耳机。
C30i 本体则不可以弯曲,采用虎口般的结构卡在耳垂上。在佩戴时需要找到耳垂比较薄的部分,将耳机从那里套入耳垂,再将耳机推向耳垂较厚的地方,就能牢固固定。看似还是有些松松垮垮,但因为耳机足够轻,不容易被甩掉。
我的感觉是,佩戴上没有什么异物感,但难以做到所谓“戴久了会忘记它的存在”这么夸张。
我尝试连续佩戴了两个多小时,期间耳朵没有任何被堵塞的感觉,且取下后耳朵没有丝毫的潮湿感。确实是完全的开放了!
音质 我的评价是:出奇的好。
因为是一种妥协,我对耳机的音质便不抱任何希望。实际听起来,虽然也有这非封闭式耳机缺乏低音等问题,其实音质相当不错。
通过调整 EQ 配置,将高低频都拉高(经典两头高调法),效果更是好了许多!
我也听了一段博客,听清说话人的语音也没有任何困难。
多设备连接 我原本买的是飞利浦的一款开放式耳机,到手之后才发现耳机没有双设备连接的功能。我平常有在手机和电脑之间切换使用的需求,少了这个功能实在不能接受。
C30i 支持同时连接两台设备,可以在两台设备间无缝切换。
续航 商品说明上标明了耳机单次续航是 10 个小时,这应该是相当厉害的了。加上充电盒,总共可以提供 30 个小时的续航。
实际用下来,在 2 小时的使用后,耳机的电量没有明显的减少。续航应当是很强劲的。
缺点 目前我看到的一个缺点是,耳机缺少自动休眠功能。将充电盒打开,耳机便会连接所有设备,摘下后仍保持着连接。直到耳机放回充电盒,并盖上盖子后,耳机才会关机。
且耳机缺少佩戴检测,摘下后不会停止音乐播放。似乎耳夹式耳机比较少有这个功能。
这可能会抢占了一些设备的音频输出,不过不算是很大的缺点。
总结 SoundCore C30i 有效解决了我耳道容易发炎的痛点,音质不错,且有多设备连接的功能,满足了作为我主力使用的耳机的所有需求。
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ 日本之行-第二站-奈良
+
+ /archives/adc5a61e.html
+
+ 写在前面 23年12月底,我踏上了前往日本的旅途。这是我第一次去日本,是我第一次出国旅行,且只身一人。
本次旅行为14天,我将分多个章节完成所有的游记,记录下我旅行中的点点滴滴、各种感想。这是第二章节,奈良之旅。
近铁、巴士与鹿
奈良距离大阪仅有 30km,乘坐近铁奈良线,摇摇晃晃一个多小时便能到达奈良。
这条线路有分区间准急、准急、急行、快速急行四个速度,每个速度停靠的站点数量不同,票价都相同的。如果想尽快到达奈良,乘坐快速急性是最好的选择。
这天,我纯凭心情来到近铁难波站,坐上了最近一班开往奈良的近铁,似乎是急行。
近铁的运行速度不是很快,叮叮当当地在楼房间穿行着。
在近铁奈良站,奈良线的终点站下了车,距离旅馆所在的奈良公园还有一段距离。由于拖着行李,我选择搭乘公交车。
公交车到站后会向站台一侧倾斜车身,方便乘客上下;上车的门并不固定,可以看车身的贴纸判断;司机会很耐心地等待所有人上车、落座之后才摆正车身、继续前进。
有一些线路是分段计费的,就需要在上车的时候领取一张整理卷,或者先刷一下卡,在下车的时候将钱和整理券一起投入投币机,或者刷一下公交卡就可以了。
在每一个座位的旁边都有一个按钮,如果下一站要下车,按一下就会提醒司机,并且在车头的显示屏上也会显示,车上的所有按钮都会亮起。在公交车到站停稳之前无需先起身,车子停稳后司机也会很耐心地等大家付钱、下车,还会一个一个道谢哦。
相比于地铁,公交的到站时间显得没有那么准确,但不会差太多。在地面运行的公交会受到交通的影响,等候乘客上下车、付钱有时候也会用掉不少的时间,能做到按照时间表运行已经相当厉害了。
虽然在车上就有看到,果然很多啊 —— 奈良的鹿。
奈良的鹿是散养的,主要集中在奈良公园和春日大社。在奈良设有鹿苑,将受伤或者发情的鹿收养,待它们恢复正常再放回乡中散养。
从车站走到旅馆,一路上都有鹿儿好奇地打量着来自异国他乡的我。
每走一段距离,能看到提示观光客不要“挑逗”鹿儿的提示牌,有些鹿的性格比较差,会用头顶你,追着你不放的。好在大部分鹿都去掉了角,不至于顶伤人。
今天的旅馆确实有些简陋 —— 私人活动空间仅有这么一小间,洗浴和方便都是公用的。不过旅店的氛围很好,有一个共用的客厅,客人们可以在这里吹暖气(冬天还是挺冷的)、喝饮料、聊天。
旅店的工作人员非常热情,替我办理好入住手续后,立刻拿出一张奈良公园的地图,用简单的中文(很厉害哦!)告诉我这附近有哪些好玩的地方。奈良一天的行程就是参考着这张地图安排的。
整理好行装,已是傍晚五点多。按照旅店工作人员以及 Google Maps 的说法,奈良公园里的大部分饮食店都要下班了!遂立刻出门,寻找晚饭的地点。
因为今天是周五吗,明明还有一些旅客在此游玩的,旅店对面的旅游纪念品店已经关门了!沿街打量了几家店铺,也都在收拾着大堂,准备打烊了,完全不像是会接客的样子。大危机,要没晚饭吃了,得走个几公里处了奈良公园,到达奈良市区才有便利店。
路过了一家同样是卖纪念品的商店,发现店内的空间还挺宽敞,也摆着一些桌椅,便问了一下老板还有没有饭吃。运气很好,这家店还没有打烊。
可以选择的菜品并不是很多,大多数都是日本的家常菜。我选择了这道绝大部分和食餐馆都会有的家常菜 —— 親子丼。
从厨房门帘的缝隙向里望去,可以隐约看见老板在烹饪着饭菜。明明用得都是同样的工具和食材,有技艺的人做出来的东西就能端上桌卖钱呢。
蛋是我们所谓“半熟”的柔滑状态,鸡肉有一些小烧焦,主要的风味是自古以来人们就离不开的味道 —— 咸味。风味不能说突出,却很平和。它就是一碗很普通、很平常,此时此刻会出现在任意一张餐桌上的饭。
饱饭后,我根据地图的指引,前往东大寺的二月台,观赏日落之景。
只用手机相机无法捕捉暗光下的美景。
夕阳柔和而昏暗地照着二月台,四周一片静寂,只能听见洗手亭的潺潺水声。在日落后还停留在奈良公园的游客寥寥,大家都保持着绝对的安静,共享这片难得的静谧。
在台上驻足数分钟,我轻轻踏着脚步走下楼梯,沿着寺院的小路漫步着,离开了东大寺。
此时天已完全黑了,不论是寺院还是神社都已关门,我就这么在奈良公园里漫步着。时不时碰见一群鹿,便从口袋中掏出一块鹿仙贝,掰给鹿儿吃。
会旅店前在路边的售货机买了一瓶果汁饮料,是不二家的。日本的饮料会标注果汁含量,我觉得很神奇。
第二天,去参观了十分出名的春日大社。不过不恰巧,主殿正在维修,将赛钱箱设置在了原处,只能眺望主殿。
在巫女那儿买了点纪念商品,是两只鹿型的小玩偶。一只是木质的,一只是陶制的,鹿儿的嘴中叼着签。忘记留下照片了。
离开主殿,走上铺着碎石的小道。
每走几步,便会出现一间迷你神社,感觉十分奇妙。在这片林海中不知道供奉着多少的神明。
有些人也许是提前来做新年参拜,途中遇到的神社都会十分虔诚地参拜。
在林间漫步了将近一个小时,吸饱了新鲜的空气,我振奋精神,回旅馆取了行李,准备离开奈良。
拖着行李箱漫步在奈良公园里,我又一次路过了若草山。
若草山也不高,在满是丘陵的福建甚至都算不上山,只是个小土坡。山上的草长得格外整齐,是一座越看越顺眼的山呢。
若草山每年12月初开始封山,直到第二年三月举行烧山仪式后才重新开放。没能上去走走真是可惜。
在前往近铁奈良站离开奈良前,我路过了一家在网上有着不少讨论的柿叶寿司店,便决定在这里解决午饭。
柿叶寿司其实就是用柿子叶将寿司包起来烹饪。据说柿子叶不仅可以杀菌消毒,还能给寿司提升风味。
想着,既然是奈良的特色菜,肯定要好好品尝一下。我点了一份 2500円 的大套餐,奢侈一下~
左下角的五枚寿司,上方两枚便是柿叶寿司了。下方三角形的寿司也是用不能吃的叶子包着,但好像不是柿子叶;右侧是用腌制过的紫苏叶包的寿司,紫苏叶是可以吃的,风味很独特,我很喜欢;左侧是茶味的卷寿司,也是第一次吃。
柿叶寿司用的是腌制过的鱼,确实带有叶子的香味,很有特色!
除此之外,套餐内还有精致的小菜、一份天妇罗拼盘和一碗素面。吃的我好饱~
乘坐上前往京都的特急电车时还发生了一些小插曲。
坐上车后我才意识到自己坐的是特急的电车,进站时我只刷了基本票,没有购买特急卷。
好在列车上有通过扫描二维码购买特急卷的渠道,也支持借记卡付款。在等待列车发车时,我赶紧买了特急卷,带着行李移动到了指定座位,正式踏上前往京都的旅途。
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ 23 年对我影响最大的硬件与软件
+
+ /archives/a7050149.html
+
+ 原文投稿在自留地频道的新年活动里。
对于我来说,23 年给我带来影响最大的莫过于 Meta Quest 3 与 VRChat 了。
Quest 3
Quest 3 在 23 年 6 月面世,于 9 月 28 日开放预购。我早在数年前就有了尝试 VR 的念头,在经过一天的调查研究后确定了 Quest 3 不会踩坑,便在日亚下了单,直邮回国。日亚上标价为 78k 円,日本商品出口不收税,再加上直邮的运费和中国的关税,最后到手的价格和标价差不多。(顺便一提,现在日亚也可以以相同的价格直邮国内)
外观我就不展示了,网上的图很多。到手第一件事,便是尝试它对于上一代产品相比提升最大的地方——显示与穿透(Pass Through)。
显示效果 显示效果上,Quest 3 采用了 Pancake 棱镜,甜点位置(Sweet Spot)相对于上一代更大,也就是说仅需简单调整头戴的位置,便能找到画面最为清晰的位置,我的使用体验上感觉也是如此,每次佩戴上头显很快就能调整好画面。
穿透 (Pass Through)
穿透方面,由于我没有用过 Quest 2、Quest Pro 等拥有穿透功能的 VR 头显,无法进行比较。图片中看起来比较糊,是因为我用了 Quest 自带的截图功能,分辨率很低。实际观感上,在光线充足的环境中可以毫无压力地辨认各种物体,但细小的字是看不清楚的。戴着 Quest 使用手机并不是很舒服,因为物体距离头显过近会因摄像头视角的问题产生扭曲,看字会很吃力。将手机拿远,又会因为显示清晰度不够而看不清字。不过听说 Quest 2 的穿透是黑白且十分模糊,Quest Pro 也好不了很多,Quest 3 在这个方面应是有十足的进步。(似乎离 avp 还有很大的差距)
相比于头显中不带有处理芯片的 VR 眼镜,Quest 可以使用无线串流软件将 PCVR 的画面投影至头显中显示,再也不用担心玩游戏时绊倒数据线了。
总而言之,Quest 3 是一副功能完善,各方面实力均不弱的“六边形战士”,但毕竟定位是廉价头显,也就缺少了 Quest Pro 的自定位手柄、眼部追踪,Valve Index 的基站定位、手部追踪,更没有 Apple Vision Pro 的高 ppi 显示屏、低延迟的穿透与精准的手势识别。但综合来看,Quest 3 的性价比高,适合刚刚步入虚拟世界,想要体验各式各样 VR 游戏的人购买。
VRChat 谈到 VR 游戏,有人会想到 Half-Life:Alyx,有人会说起(已经凉掉的) Beat Saber。根据 SteamDB 的数据,此刻在线数量最多的 VR 端游戏还得是 VRChat(主要为 PC 端的战争雷霆不算)。
在我看来,VRChat 里有大概有四类人。
第一类人:虚拟世界的旅行家
有些人想“逃避现实”,来到虚拟世界欣赏美景、转换心情。在 VRChat 里有各种各样风格的地图:有些风景大好、音乐舒缓,适合在快节奏的生活之余找到一个宁静之地放松紧绷的精神;有的地图灯红酒绿,如果在夜晚你还是激情满满,不妨来这里听听虚拟 DJ 的表演,大家一起蹦迪、饮酒;甚至还有环境昏暗、一片寂静,十分适合睡眠休息的卧室地图,虽然深处异地,也能在虚拟世界里与好友共眠,在清晨醒来的那刻发现自己的眼前并不是早已习惯了的天花板,耳边是仍在熟睡的友人的呼吸声,将会是一种全新的体验。
第二类人:虚拟世界的摄影师
也有些人想在虚拟世界里做一个摄影师。美景的照片中怎么能少了美人,如果现实中找不到美人,那就自己来扮吧(心死)。VRChat 中使用的人物模型可以由玩家自行上传,如果你恰巧会使用 Unity 与 Blender,便可以为自己量身定制一个人物模型;如果不会也没有关系,在 Booth 上有许多预制好的人物模型与衣服,只要按照教程将其组合,便可打造出你心目中的理想形象(不管男女)。为了拍出满意的照片,你会主动去学习各种各样的新技能:为了调整照片的后期效果,我就学习了 Lightroom 的使用。
第三类人:人,不过是在虚拟世界里
这或许才是 VRChat 的核心内容 —— 当然是和朋友聊天啦。在 VRChat 中有一些专门用于聊天、交友的地图,例如以中文为主的“中文吧”;也有一些比较热门的地图会聚集起各个语言的人群一起聊天,例如“Japan Shrine[spring]”,一张风景优美的日本神社地图;更有各种各样以个人或小团体主办的咖啡厅、运动吧、跳舞房、游戏房等着你来加入。也许你和我一样有些小“社恐”,在现实生活中不大善于和陌生人交际,不妨试试在 VRChat 中,与素未谋面但已经熟络的朋友,或是围坐在篝火旁闲聊,或是在电闪雷鸣、乌云滚滚的夜空中乘坐飞机探险,相信这对你一定会给你带来从所未有的新鲜感。
第四类人:OOOO 还有一类是搞色色的,就不多说了。
以上只是从我的眼中看到的 VRChat。一千个人的心中有一千个哈姆雷特,你的 VRChat 又将会是什么样的呢?
自从 23 年 10 月 3 日加入 VRChat 以来,我学会了怎么修改模型、如何使用全身追踪让自己在游戏中灵活运动;尝试在跳舞房里学习舞蹈,在 KTV 房里与朋友唱歌,与来自全球的人使用中、英、日闲聊;在 VRChat 中结交了新的朋友,在 X 上发布了拍摄的照片。VRChat 确确实实已经融入了我的生活,仿佛在另一个世界塑造了另一个不同的我。
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ 旅行与军粮
+
+ /archives/a534d51a.html
+
+ 军粮的特点 1. 便携 一般来说,军粮都会被较为紧凑地包装,以便于大批量运输与随身携带。如确认将有一餐饭需要在外解决,可以在出发时往包里塞上一份军粮。
2. 分量足 军粮的营养构成一般都是按照一个运动量中上的成年男性一餐所需要的卡路里来设计的。因此,即使不能完全饱腹,其所含的营养足够填补在外游玩半天空空的肚子了。
3. 便于烹饪 相比于需要加入开水的方便食品、甚至是冻干食品,军粮一般自带加热设备,仅需冷水,甚至不需要水都可以加热食物。若是去荒郊野岭的地方露营,除非有携带便携式汽炉,想获取到热水是比较难的,这时候有自加热的军粮会很方便。
俄罗斯单兵口粮普餐
这次买到的6号餐谱,包含五包干粮、牛肉丸、牛肉荞麦饭、牛肉炖豆角、牛肝酱、午餐肉、蔬菜丁罐头以及饮料、果酱、零食等散件,还有一套由三个燃料药丸、一个铝制支架和几支防风火柴组成的明火加热套装。
旅行的第一天是登山,午餐便携带了部分的食物作为午餐。
有些小雨,便找了一处有雨遮的地方开始准备午饭。
将铝板弯曲成炉子的形状,在炉子的中央放上燃料药丸,再用防风火柴点燃,便是一个功能完备的加热装置。
小贴士,我试着用火柴从上方点燃药丸,尝试了两次都失败了。将火柴放在炉子里,然后将药丸放在火柴上,便能很轻易地将其点燃。
点燃固体燃料后,将盛有食物的铝制罐子放在火上直接加热即可。如果有带铝或者钛制的杯子,也可以直接放在火上烧水,泡些饮料喝。
在等待罐头加热时,我先掏出了干粮与蘸酱。除了照片里的芝士与肝酱,还有一包苹果果酱。
芝士的味道与常见的芝士片如出一辙,在我看来稍微淡了些,因此也比较适合直接吃。
牛肝酱则是特别的腥,单独吃我有些吃不来,和着芝士与果酱吃味道倒是还不错。
拿出一片干粮(其实就是苏打饼干),涂上果酱,挖一勺芝士,再放上一点点牛肝酱,一口塞进嘴里,味道还算不错。
饼干有些干硬,嚼起来有些费劲儿,需要配水。
最后,完吃完了一份饼干、一盒芝士和一包果酱,牛肝酱剩下了一大半。
接下来是牛肉丸。看到红色的外表就能猜到是罗宋汤风味的。
应该是罐藏的缘故吧,牛肉已经泡得很软了,吸收了罗宋汤酸酸甜甜的风味,味道还算不错。就是牛肉味已经不是很浓,能吃得出来是肉,但没有什么特殊的肉的风味。
顺便一提,用这种炉子加热,铝盒子受热举起不均匀,中间已经滚烫,但四周还是冰的,需要多多翻搅。
从外表看不出来里面有啥,红红的肯定也是罗宋汤的味道。里头的蔬菜主要是胡萝卜,也有青椒、青豆等蔬菜。但这一碗的味道就不好了,青椒的味道和西红柿(或者是甜菜)的酸甜并不搭。
把饼干蘸着牛肉丸的汤汁吃,味道也不错。涂涂果酱、涂涂芝士,饼干便吃完了一包。
又吃了点麻麻那边的自热口粮,便已饱腹,我的食量确实不大啊。
这一餐其实只吃了整套餐的 1/3 左右,剩下的量再吃两次正餐不成问题。
回到家后,又拿出了些零食品尝了一下。
来自俄罗斯的巧克力棒,偏甜,一股代可可脂的廉价感,属于不大好吃的巧克力。
右上角是速溶咖啡,右下角是奶粉,左边的一大包是糖。糖的量很大,明显不止是一次饮料的量,也可以放在茶里喝。
咖啡很苦,一股烧焦味,不好喝。奶粉大概也是植脂末吧,没什么奶味。
剩下的食品我分成了两餐解决。
第一餐的搭配是:牛肉荞麦饭、肥肉罐头和干粮(饼干)。
也许我加热的还不够,但考虑到在野地里使用便携式炉子加热的能力,士兵们能加热到中间完全热乎,周围有些凉是平均水平了。荞麦饭很硬,风味也不是很好,除了咸味和一点牛肉味,尝不出别的滋味了。加了一些套餐内的黑胡椒粉,才改善了一些风味。
不过这一大碗饭确实很能填饱肚子,适合放在午餐食用。
肥肉罐头里自然是盐腌风味的很肥的肉啦。味道我还挺喜欢的,肥肥的肉很好吃,十分下饭。
第二餐的搭配是:牛肉煮豆子、午餐肉、苹果泥、酱牛肉(来自国产 MRE)和干粮(饼干)。
牛肉煮豆子用的会是什么豆呢,豌豆吗?啊,原来是黄豆。
并没有延续牛肉丸、蔬菜罐头的罗宋汤风味,只是单纯的咸味,不过味道我还挺喜欢的。
牛肉其实不多,就一大块带筋、软烂的牛肉,剩下都是碎块。除了黄豆外还有一些胡萝卜。
午餐肉很小一罐,但确实是肉泥,淀粉含量并不多,味道也不错。
苹果泥是我整个套餐中最喜欢的一样食物了。口感绵密,酸甜度适中,汁水十足。配合干粮一起吃,很大地改善了干粮过干、过硬的缺点。而且好大一罐,吃得很满足。使得这一餐中我干掉了两袋干粮。
国内的军粮 —— 以北戴河的自热口粮为例 09式单兵自热口粮我吃过几份,13式这种两餐包装在一起的也吃过一次。
这次选购的是北戴河生产的自热口粮。虽然不是军品,但北戴河前身是军工厂,产品会比较接近军品吧。包装风格与09式也很相像。
那天感冒的发烧刚退,人还比较虚弱。住的度假酒店位置比较偏,不想跑太远去吃晚饭,便取了车上的自热口粮回房间吃。
我吃过太多次了,便没有拍照片,下面就口述一下感受。
相比于09式单兵自热口粮,北戴河出品的民用口粮内容少了很多 —— 没有了耐贮蛋糕、糖水黄桃/菠萝、调味辣椒酱和固体饮料,只有一份主食,一份配菜(酱牛肉和午餐肉二选一)和一份小菜(榨菜)。
09式的餐谱相当多样,有 12 个餐谱,有炒面也有炒饭,也有一些主食是素食的,满足部分人群的需求。
北戴河的仅有三种餐谱,且都是荤食的炒饭。
主食包外套着加热袋,用注水袋取对应量的水(生水、脏水皆可)倒入加热包即可完成主食的加热。
味道上,我认为北戴河做得是不如当年的09式的。饭总体都偏咸,但肉给的十分足。
但相对于俄罗斯的军粮,不知道是口味上有着主场的优势,还是俄罗斯人烹饪技术真的不佳,还是中国的军粮更胜一筹。
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ 被我整坏的路由器和服务器
+
+ /archives/7777583b.html
+
+ 为了搭建 PalWorld 的私服,我又一次踏上了折腾之旅,并成功将路由器和家里的服务器都搞崩了。
好在最后两台设备都恢复如初。我就想来做一下这次“事故”的复盘。
我的设备 本次操作的有两台设备,一台是 x86 软路由,运行 Openwrt;一台是配置比较奇怪的 x86 服务器,运行 PVE。
Openwrt 的网络隔离配置 说在前头,我对 Openwrt 的操作真的是一窍不通。网络相关的知识兴许懂一些,但一到实操环境就纰漏百出了。
由于 PalWorld 服务端启动后便会在游戏的服务器列表公开自己的 IP,且无法关闭这个功能,有比较大的安全隐患。在配置好 PalWorld 服务端所运行的虚拟机后,我决定将这台虚拟机的网络与局域网内其他设备隔离。这也是所有折腾的起源。
配置 VLAN 导致的失联 我能想到的第一个方案便是,给这台虚拟机分配一个与当前局域网不同的网段,并将两个网段隔离。
VLAN 我还是知道的,便着手开始创建新的 VLAN,并保存…连不上路由器了。
急了啊,创建了 VLAN 就代表开启网桥的 VLAN 过滤。目前这个 VLAN 没有分配给任何一个接口,自然是啥都连不上了。好在 Openwrt 有自动回滚的功能,90 秒若设备连不上路由器,便会将刚刚的更改回滚…但不是每次都能成功。
有反复折腾了几次:创建 VLAN、创建接口、创建防火墙的 Zone,分配来分配去,中途也搞砸了几次,配置也都回滚了,直到最后一次尝试,luci 弹出了令人感到安心的“正在回滚配置”的提示,然后就…卡住了。
考虑到 Openwrt 使用的是 squashfs,怀疑是设备上的设置确实回滚了,但内存中的配置没能正确地回去,我将设备断电重启。果不其然,配置回到了应用前的样子。
在尝试配置 VLAN 数次后,我放弃了。即使给虚拟机分配了新网段,也设置好了防火墙规则,虚拟机依旧可以访问到内网网段的设备。
目前我还没有闲心思慢慢研究 Openwrt 的种种配置,便打算换一个方式实现。
配置 PVE 防火墙 第二个方案便是,在 PVE 的防火墙中禁止虚拟机连接内部网段的其他设备了。
启动 Datacenter 防火墙但没有添加允许规则导致的失联 我启用了虚拟机的防火墙,发现配置并没有生效。简单查询后发现,需要将 Datacenter 的防火墙启用,VM 防火墙才有效果。我便看也没看地就开启了 Datacenter 防火墙,发现管理后台页面无法刷新了。此时我才看到屏幕上 Datacenter 防火墙的默认配置 —— IN: DROP.
这下好了,外部连接统统被阻断,也就无谈通过控制面板将防火墙再关闭。
这台服务器是无头的,安装有一张 P100 显卡,但没有显示输出。所以在不动硬件的情况下,我没法通过显示器访问终端的。
通过检索,我了解到了好几种奇技淫巧来关闭 PVE 的防火墙。
挂载并修改 crontab 正好手上有一个硬盘盒,我就将系统盘取下,通过硬盘盒连接到了软路由,尝试修改系统盘里的文件。
使用 fdisk
查看这块系统盘的分区情况,但没有看到熟悉的 ext4
字样。取而代之的是 Linux LVM
。LVM 相当于是 Linux 对磁盘进行了再一次的分区,因此挂载就不能是简单的 mount
,得用 lvm2
工具。
再敲了几个命令后,我成功将 PVE 分区的 root
文件夹挂载,并在 crontab 中添加了关闭 PVE 防火墙相关的命令。
umount
,取下系统盘并装回服务器,开机…没有任何反应,依旧打不开管理面板。是教程给的方法有误吗?
遂又取下盘,挂载到软路由上,却提示该分区忙…我是相当忌惮挂载并修改分区的,生怕损坏了分区,服务器的数据可就全丢了。不敢再继续操作,我得另想出路。
连接显示器,但还是个瞎子 现在能做的就是在服务器上通过 PVE 的 rescue terminal,来修改防火墙的配置了。
我关闭了服务器,取下 P100,换上了 R7 240 这张十年老兵,插上了便携显示器…
error: No suitable video mode found. Booting in blind mode.
你这不是输出字了吗,怎么就进瞎子模式了???
经过查询,我了解到系统正在寻找到显示模式,正是古早电脑终端使用的 80-Column
这样的显示模式。
至于什么是 Blind Mode,我尝试在 GRUB 和 Linux 源码中搜索,都没有结果;为什么 PVE 系统找不到我的 R7 240,可能是缺少驱动吧,现在也无处知晓。
按照网上的教程尝试在 GRUB 引导系统启动时添加显示模式的支持,并没有效果;当我尝试打印支持的显示模式时,发现根本不存在正常的显示模式,故只能放弃直接启动 PVE 的 rescue terminal.
还是得靠救援盘 给服务器插上救援盘,我先是打开了基于 Windows 的 PE 系统,发现显卡是有输出的,但分辨率非常低,且只有黑白画面。看来这张老显卡与这块寨板的相性真的不大好。
在确认 Windows PE 系统下什么都做不了,操作还及其不便,我便退出了 PE 系统,打开了 Ubuntu LiveCD。
这回,显卡倒是可以正常运行。以 80-Column
模式输出文字还是可以轻松办到的。有了命令行,操作也简单了不少。
同为 Linux,操作 LVM 就简单了许多。使用 lvm2
挂载 PVE 的系统分区,并用 chroot
将用户空间切换至 PVE 系统,直接用 systemctl disable pve-firewall
把防火墙关了。
再次进入 PVE,这下开机防火墙就不会启动,赶紧进 Datacenter 防火墙设置里还原误操作的配置。
事后的反思 VLAN 配置的失误 我对 VLAN 配置没有经验,便想着参考网上其他人的配置方法来做。但我查到的都是创建访客 Wi-Fi 这类的配置教程。与我的网络环境的差异在于,访客 Wi-Fi 用的 Interface 与 LAN 不一样,而我需要在 LAN 下配置两个 VLAN,配置方法就有些不同,不能直接搬配置。
我应当要找的是较为通用的,同一个 Interface 下 VLAN 的配置教程(可能单臂路由配置和这个就有些像),再迁移到 Openwrt 上进行配置。
由于软路由只有两个网口,也没法像有四五个网口的路由器一样留下一个作为”不死“的管理口,以降低把路由器配置挂掉的风险。
PVE 原本可以修得更快 原本在将 PVE 系统盘挂载到软路由时,便可以用 systemd
停掉 pve-firewall
但当时我看到 PVE 论坛里有人说在 crontab 里加上 PVE 关闭防火墙的指令便能访问控制面板了,也有人附和说可以用。但我实际操作后发现并没有效果。
而再次尝试挂载 LVM 时,提示分区忙让我不敢继续操作。虽然在服务器上用 LiveCD 直接挂载并没有出现分区忙的情况。
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ 日本之行-第一站-大阪
+
+ /archives/aca48136.html
+
+ 写在前面 23年12月底,我踏上了前往日本的旅途。这是我第一次去日本,是我第一次出国旅行,且只身一人。
本次旅行为14天,我将分多个章节完成所有的游记,记录下我旅行中的点点滴滴、各种感想。这是第一章节,出境与大阪之旅。
第一次出境 我从上海浦东机场出境。
航班预计上午 9:40 起飞,根据网上的经验教训,国际航班建议提早三个小时到达机场办理值机手续。我早早地起了床,搭上了第一班磁浮列车,在六点五十分左右到达了机场。
拖着行李箱直奔值机柜台,发现队伍已经很长。排了一个多小时的队才轮到我。
我提前在网上进行了值机选座,现场只需要将行李箱托运,打印一下登机牌。工作人员会确认护照和日本 eVisa(电子签证)。
我提前将电子产品和需要独立安检的物品取出,安检也十分顺利。
不过在过海关的时候,工作人员看到我护照崭新,又是独自一人,不免有些担心,向我索取了酒店预订记录和行程单。酒店预订记录只要出示订购软件的订单界面即可;我没有做行程单,就将记事本里的旅行计划给工作人员看,第一天在哪里啊,过两天又跑去哪里玩…得知我全程的酒店都已订好,行程也安排清楚,便允许我出境了。
在候机厅的长椅上坐下时,距离飞机的预定起飞时间只有半个小时了。看来国际航班确实需要早点来机场办手续(虽然最后延误了两个小时)。
这是我第二次坐飞机。上一次是我还在读小学的时候,不明不白地上了飞机、又下了飞机,除了耳朵有些不舒服。没有其他的感想。
这次,我特地选择了靠窗的位置,仔细观察着飞机的起降与飞行时的动作。看到了起飞和降落时襟翼的展开,看到了遇到乱流时机翼“夸张”的摆动,感叹着这种看似“简易”的机器是如何将一机人送上天。
飞机餐的话…上一次坐飞机是国内航线,没有在饭点所以没有供餐,这是我第一次体验飞机餐。
没有选择的余地,大家统一都是鱼肉米饭套餐。和我能想象到的飞机餐没有太多区别 —— 一般咸味的鱼肉,煮得软烂的米饭(也可能是再加热的缘故)与味道平平的配菜。坐的毕竟是经济舱,不能期待太高~
经过两个半小时的飞行,飞机平稳降落在了关西国际机场。飞机缓慢驶向空桥的途中,我更换了日本的流量卡,给手表切换了时区。
入境相对出境更加简单。出发前我就在网页中预先填写好了入境信息表与海关申报表,在对应的窗口或机器扫码即可完成申报。在入境窗口只需出示一下 eVisa,录入一下人脸和指纹就可以了。
工作人员会用英语引导你操作,录入机器上也有中文提示。机场的指示牌都是四语的(日语、英文、中文和韩文),按照指示牌走就没有问题。
大阪之行 Day 1 - 舒适的酒店与海游馆之旅 APA 酒店 起飞延迟了两个小时,降落也迟了一个多小时。达到关西国际机场已是下午 3:30(这之后都是 UTC+9 时间)。我乘坐南海机场线前往难波站附近的 APA 酒店。
插一嘴,在日本用 Google Maps 相当方便,交通工具的规划和信息展示都做得很棒。“通勤案内”这款官方推荐的交通软件我也有下载,不过用的最多的还是 Google Maps。
等地铁、电车、新干线时,只要看 Google Maps 上的到站时间,到了哪辆车就上哪辆车。除非两个方向的车同时到站,否则是绝对不会坐错方向的。
APA 酒店大楼的装修很有特色,在远处就能看到橙色的屋顶和 APA 的标志,而且在全国的风格都是统一的。
内部的装横令我十分惊喜:房间不是很大,但各种设施一应俱全,在床铺正对的墙上甚至有一台大屏电视。浴室毫不意外地配备了浴缸,还有定量放水系统,不用盯着浴缸有没有水漫金山了。寝具也相当的舒服,那两晚都睡得很好。
对于一间一晚 300CNY 左右的旅馆,能有这样的体验我十分满意。
咖喱与牛排 办好入住手续,放下行李,已是五点多。飞机餐的显然填不饱我的肚子,我早已饥肠辘辘,是时候出门觅食了。
大阪海游馆是我计划中必去的一站,但它位于海港村,和其他景点在相反的方向,便安排在今晚游玩了。
日本人很奇特的一个习惯出现了:有些店铺傍晚 6:00 到 6:30 就收摊了,最晚的会开到七点八点左右,再迟就只剩下营业到深夜的家庭餐厅与居酒屋。
用“食べログ (tabelog)”这款软件在海游馆附近搜索评价比较高的店铺。
在海游馆隔壁有一个小小的综合体,里面有一些店铺还有在营业。我选择了这家评价很高的牛排咖喱餐馆。
一份牛排咖喱饭、一碟酱菜、一碗味增汤。价格有些忘记了,在 1500円 左右。
日本人很喜欢吃这种细长的青椒,不会辣,在天妇罗中也会出现。
咖喱的甜口的,味道自然比百梦多咖喱块要好上数倍。牛肉很嫩,没有怎么调味,展现的是肉本身的鲜味。总之,相当的美味。
对了,需要使用现金哦~ 商家没有准备 POS 机。
大阪海游馆 已经记不清上一次去水族馆是多少年前。听说大阪海游馆的设施很棒,场馆也很大,我就来体验一下。
门票的价格是 2600 円,入场有分时段,我就在售票处购买了当前时段的票。
海游馆很大,步行参观的总距离在 1km 左右,按照地理位置划分成了好几个区域。
进入场馆便是一个巨大的拱形水箱。是玻璃比较薄吗,还是用了什么技术,几乎看不见玻璃带来的重影,鱼仿佛真的在空中悬浮。
一群正在睡觉的海狮。抬着头睡觉不会落枕么。
![巨型水箱]/images/2024-01-21/(9.jpeg)
在场馆的中央有一个巨大的水箱,有两头鲸鲨、几头锤头鲨和许多小鱼生活在其中。
还有好多可爱的花园鳗,黄色的尤其可爱~
除此之外,还有来自各个大洲的鱼、南极的企鹅,在地下还有水母馆。
总共逛了一个多小时,可以说大饱眼福了。
在海游馆的隔壁是天保山摩天轮,夜晚被彩色的灯光照亮特别好看。
此时已是 19:30,经历了一天奔波的我有些劳累,便回到酒店养精蓄然,计划第二天的行程。
Day 2 - 天守阁、天满宫、天筋桥与大阪烧
在日本的第一顿早餐,在酒店隔壁的参观享用了自助餐。
炒蛋和上次去香港在澳洲牛奶公司吃到的有点像,并没有完全做熟,非常的软嫩~
茶泡饭就很有日本的特色了。
饭后,我就前往难波站附近的旅游中心,购买大阪周游卡。我选择的是二日卡,价格是 3600 円。在两天内,可以免费乘坐大阪地铁与巴士(相较于一日卡,不能乘坐私铁),以及免费参观好多景点。
大阪地铁网络十分发达,基本覆盖了所有想去的地方,这两天我没有搭乘过私铁或者巴士,因此不用担心是否要选择一日卡。
买到卡之后,第一站便是天守阁。
从外边看,与只狼里的苇名城有几分相像。
天守阁内陈列着许多颇有历史的物件,只可惜我对日本的历史并不熟悉,也看不太懂书法家的笔墨,只是走马观花感受一下文化的氛围。
最后在楼顶吹了吹风,便离开了天守阁。
虽然有些冷,但天气真的很好,万里无云。下一站是天神筋桥,一个商业街。
日本有很多 OOばし(桥)这样的地名呢。也有很多像天神筋桥这样,上方覆盖着遮雨棚的商业街,天神筋桥是其中最长的一条,从一丁目延伸到七丁目,光是主干就有 2.6 km,更有密密麻麻的小巷。
乘坐堺筋线,在天神筋桥三丁目下了车。我先是去拜访了天满宫。
似乎内部在翻修,将赛钱箱放到了外边供大家参拜。
在这里,我给身边参加考研的人做了参拜,希望他们可以拿到好成绩。
接着,我便从二丁目开始,一路边逛边思考着午饭的去处,寻找着吃饭的店铺。
沿途,看到了大排长龙的可乐饼摊子,转了一圈再想回来买,发现已经卖完收摊了。有一家天妇罗的店门口也站着好多人,大排长龙。肚子好饿,肯定排不了这么长的队。
边走边用 食べログ 搜索着。来大阪就得吃些有大阪特色的,那就是大阪烧了。
并不在主街,而是藏在小巷子里的千草大阪烧,似乎是 食べログ 23年的百大名店呢。
我选择了以店铺名字所称的招牌菜 —— 千草大阪烧。
核心是一大片厚切的猪肉,在上下两面都倒上面粉与卷心菜混合的泥,便开始煎烤。
接待我的服务员会一些简单的英语,告诉我等着他们来翻面就好了。当地人也许会选择以自己的喜好来摊大阪烧,我作为门外汉只要静静欣赏就好。
当两面都煎至金黄,便会涂上大阪烧酱、沙拉酱和黄芥末酱,撒上不知道是什么的籽,就可以享用。
大阪烧整体的口感是软糯的,夹心的猪肉排很嫩,肉汁十足。
唯一可惜的是,量实在有些少,填不饱我的肚子啊~
饭后继续在天神筋桥闲逛,发现了一家鲷鱼烧店。我还以为鲷鱼烧是软软的,但实际外壳是偏脆的,甜甜的红豆馅十分美味。
今天真的走了好久的路,对于第一次来到异国他乡的人可以说是有些得意忘形。从天神筋桥二丁目逛到六丁目,又折返了回来。中途还去 melonbooks 看了一圈,又跑到周游卡可以免门票的天王寺动物园里逛了逛…还没逛完,人就开始有些不舒服了。
在动物园里稍微休息了一下,我还是决定吃完晚饭就回酒店休息。
一次比较“失败”的体验。不要误会,寿司还是很好吃的,鱼类十分新鲜,但我一不小心就在 iPad 上点了太多的寿司,都吃进肚子之后已经很饱了,完全没有在回转的转盘上取过寿司!这和普通的寿司店不就没差别了吗!
回转寿司店出门便是热闹的道顿堀,然而我不是喜好这一口的人。路过蟹道乐,周围停着好几台旅游大巴,店门口密密麻麻的全部都是在等待的游客,大约有数百人。真是疯狂呐。
受不住喧嚣,我在附近的药店买了一支体温计和一盒退烧药,便回了酒店。
躺床休息了一阵子再测体温已经正常,看来真的是疲劳导致的体温失调。之后的旅途安排就宽松一些吧。
Day 3 - 梅田蓝天大厦、天空美术馆与一兰拉面 睡了一个好觉,但人还是有些疲劳。前往异国他乡果然不能过于放肆,得做好身体的管理。今天就悠闲地度过吧!
在酒店寄存行李后,搭乘地铁来到了梅田蓝天大厦。从三楼有快速电梯可以通往顶楼,然后乘坐扶梯来到屋顶的圆形观景台。
对了,有周游卡门票免费哦~
相比于其他观景塔,梅田蓝天大厦不算很高,但好在有开放式的观景台,视野特别棒。今天风有些大,在楼顶不是很站得住,欣赏了几分钟美景便回到了室内,点了一杯咖啡,坐在窗边静静欣赏着窗外景色。
梅田蓝天大厦外侧是镜面玻璃,倒映着天空、与天空融为一体,因此称作蓝天大厦。尼尔:机械纪元的开发公司白金工作室的总部就在这栋大楼里。
我还顺道参观了另一座楼的天空美术馆,虽然不怎么懂艺术,但画作依旧能感染我。
参观美术馆后,已是正午。该去找吃的了~
百闻不如一见,我来品尝一兰了。
每个人第一次来吃日式拉面都要面临一场“大考” —— 单应该怎么点。(背面有中文,我填完了才发现)
除了在点餐机器上确认要吃东西,在排位时服务员会给你一张纸片,让你选择拉面的喜好。
浓郁度我选择了加浓,确实有些过浓过咸了。吃着相当地过瘾,豚骨的香味充满嘴巴,真的很幸福。如果平常吃得比较清淡,正常的浓郁度可能就有些偏咸了,可以考虑减少一些浓郁度。
油脂的丰富度我也加了一档,但感觉不是特别明显。可能是看到日本有一种表面铺满油渣的拉面,一兰的面在油脂的方面反而显得“寡淡”了吧。
其余的选项便是看个人喜好。一兰的辣椒粉不会很辣,加 1/2 倍感受不出辣味,但可以提升香味。
交卷后,一碗一兰拉面,四片叉烧和一颗盐味溏心蛋上桌啦。
一兰号称在面出锅后15秒内就会传递到食客的面前,以体验拉面最新鲜的味道。我赶忙用手机拍了张照,便开始享用。
拉面和我之前吃过的感觉都不大一样,特别有筋道,麦香味也很浓。在浓厚的汤汁里蘸一下就有了豚骨的鲜味,没几口就把拉面吃完了,便又加了半份面。
上溏心蛋的时候同时给了个小碟子,如果蛋不好剥或者咸味不合适,可以让服务员帮忙换一个。
一口拉面,配上一口汤汁。再咬一口叉烧、一口糖心蛋,至福啊。
前往奈良 饭后,回酒店取了行李,便是去难波站坐近铁奈良线,前往奈良了。
一点感想 语言不是问题,打招呼、点菜这种比较简单的对话就用手势与塑料日语,更复杂的对话用英语就好了。碰到的酒店前台经理、便利店员工、车站管理员都是会英语的。再不济,就用翻译软件吧,能达意就行~
至于习惯问题,按照当地人的做法来做就好了。在公共场合,例如楼梯应该走哪个方向,等地铁的时候应当怎么站,在地铁上有哪些地方不能使用手机,都有明确的标识和多语言的提示。多留意,照着做,就不会有事儿啦。
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ 深圳-香港三日行
+
+ /archives/2ef8bd61.html
+
+ 出行原因 其实我很早就有去香港看看的念头,但有时间出游的机会并不多(机会很多,是我比较懒,更喜欢宅家),一直没去成。
前一段时间,在朋友的帮助下我注册了英国 Wise 账户,拿到了两张漂亮的实体卡,但后期如何入金成了个问题。了解到可以通过香港账户低损耗入金 Wise,我便有了前往香港开户的念头。
12 月底我将独自前往日本游玩,但这将是我第一次独自一人出去旅游。第一次单人旅行还是出国旅行,不禁让我有点担心。
在这两个背景的驱使下,我迅速定下了这次深圳-香港的旅行,既可以前去开户,也可以作为出国旅行的预演,提前暴露一些问题。
流水账 Day 1 - Thu - 深圳 第一天中午到的深圳。我在前往酒店的半路上下了地铁,前往名为“臻品鲜粿·粿条世家”的店铺吃午餐。
粿条本身没有很特殊,有点细河粉的感觉。不过八成熟的牛肉非常非常的嫩,味道很棒。
炸豆腐是赠送的,第一眼并太高的期望。但一口咬下,十分软绵的里芯呼之欲出,佐以略微有些咸的沾汁,味道很棒。
我还点了一份长得有点像海蛎煎的东西,记不得名字了。应该是用油炸的,比海蛎煎更加酥脆。
美美地享用午饭后,我继续登上地铁,前往酒店。
下榻酒店后,我前去参观深圳世界之窗景区…总之就是很后悔。
比起人造景观,我还是更喜欢自然景观一些。世界之窗没有给我带来惊喜,只有满满的失望。今天是周四,许多游乐设施都没有开放,可以参观的仅有一个个微缩复原的建筑物。
在世界之窗转了约两个小时,我悻悻离去,前往凤凰楼食茶点晚餐。
多春鱼的鱼籽很多,非常鲜美。
一个人出来吃饭确实有些不便。有些菜品一次只能点这么一大盘,不过还是美美的享用了。
鲜虾烧麦就是吃鲜,特别的鲜甜。
菜单上的字看得不是很懂,大概写的是炒肠粉吧,没想到上了这么大一盆长得也不像肠粉的东西。
吃了一下,似乎确实是切成一节一节的肠粉。味道不错,但量好大最后没吃完。
饭后,我顶着吃撑的肚子徒步前往华强北,这片传说之地。可惜的是,大部分店铺八点就关门了,简单逛了几栋电子市场后我便回酒店计划第二天的行程。
Day 2 - Fri - HK 开户 一大早我便乘上了前往福田口岸的地铁。
7:30 左右的口岸没有什么人,大部分是赴港上学的学生,通关很快。
我在落马洲站选择乘坐 B1 双层巴士前往元朗区,开始了开户之旅。
沿途…说实话没有什么风光,元朗算是比较偏僻的地方,好风景还要等到第二天的香港岛之行。
8:00 我到达了中行门口,发现只有前来上班的员工,还没有来排队的人。如果希望第一个办理业务的朋友可以选择这个点就来等候。时间还早,我就去吃了个早餐,回来发现已经有两个人在排队,便加入了队伍。
9:00 准时进入了银行,取到了号。在我之前有提前预约的人,我排在了第 5 号。
在工作人员的引导下使用手机填写了信息,然后等待柜员办理剩下的手续。共有三个柜台在同时办理开户,一个人需要 30 - 60 分钟的时间。
11:00 左右,我完成了中行的开户。
我现场就拿到了银行卡,便在 ATM 存入了 500 元人民币,并兑换成了港币。
顺便在同一条路上的南洋商业银行和汇丰银行也完成了开户手续。这两家银行皆需要几个工作日审核信息,审核成功后会将卡片寄至通讯地址。
穿行于各家银行网点的途中,我发现了一家在他人游记上看到过的店铺——胜利牛丸,便暗自定下了午饭的去处。
三个愿望,一次满足。
面汤的颜色相当的“可怕”——仿佛是直接将卤水呈上来一般。但实际入口并没有很咸。牛肉们就不一样了,比较重口味的我也觉得有一点点咸。不过风味很棒,牛肉吃透了各种香辛料。
河粉藏在了对得满满的牛丸和牛肉之下,不是我常吃的宽河粉,比较窄。
虽然店名以牛丸冠名,我却没觉得牛丸有多么的美味,反倒是牛筋更胜一筹。
饭后,我在坐满老者的公园里稍稍歇息,并且计划着下午的行程……
此次香港之行除了开户,其实还有另一个目标——碧蓝档案与香港吃茶三千的联动~
在出发前的几天得知了联动的消息,运气真的很好。
虽然有更优的路线,我还是选择了体验一下地面上的有轨电车。有轨电车有点像巴士,也是由司机驾驶,也会受到其他车辆与行人的干扰,但行驶路线是沿着铁轨固定的。
随后乘坐地铁,前往金紫荆广场…的对面,星光大道。在这里陈列了许多明星的手印和画像。海港边上的风景大好。
在此稍停,开户还没有结束。大概是因为元朗距离内地太近,许多虚拟银行都需要前往更靠近香港中心一些的地方才可以开户。我在一家星巴克坐下,开通了 ZA Bank 和 livi Bank 的账户。
随后,便是前往海港城的吃茶三千点了一杯联动奶茶,从中心一步步移动回福田口岸——晚上要和群友面基。
我们俩聊得非常尽兴,聊到了十一点才分手,回到了酒店,速速洗漱完毕,预定了第二天早上从福田站前往香港西九龙的火车,便沉沉睡去。
Day 3 - Sta - HK 游玩 昨晚火车票订得比较迟,最迟的火车也是 7:45 的,更迟的都被人订光了。我又被迫起了个大早(这三天都是 6:30 之前起床的)坐地铁前往福田火车站,搭上前往香港西九龙站的火车。可能是比较早乘火车的人并不对,这一趟通关流程比较顺利,再迟一点可就不好说了……
到达九龙,我空着肚子在街上漫步,寻找可以填饱肚子的早餐店。打开地图,“澳洲牛奶公司”几个字印入眼帘,这不是前几天看到的网红店吗?我记得这家店只收现金,便前往沿途的便利店,在 ATM 用中银香港的卡取了 100 港币现金。没有注意到我用的是汇丰的 ATM,与银联网络并不互通,被收取了 15 港币的手续费 QAQ。(众安银行的卡在全港 ATM 可以免手续费取现,经常到港玩的话可以办一张实体卡)
带着现金,我来到澳洲牛奶公司的门口,果不其然,排起了长队。好在我是一个人,这家店是强制拼桌的,顺利超过了大团的游客,来到了店内。
给我的第一印象嘛…很拥挤,人挤着人,甚至是背贴着背。小小的一张圆桌围坐着四个人,各自享用着早餐。穿着白大褂的服务员在人群缝隙间穿梭着,高效地记录着到店客人的订单,飞速地将菜品传递到桌前。
落座,菜单压在桌子的玻璃之下,写满了粤语,说实话我看不大懂。纠结了几十秒,发现菜单上还写着“早餐”、“下午茶”等套餐,我便对服务员说:“早餐套餐来一份吧。”
话音未落,餐具和一杯茶便着陆在我的桌上。剩下的菜品也并我让我久等。
首先呈上的是一碗火腿通心粉。味道比较清淡,喝不出黄色的汤底是什么熬制的,不过比较鲜。
接着呈上的是一个盘子,两片吐司和炒鸡蛋各占半壁江山。牛油烘烤的吐司暖暖的,软软的;这份炒双蛋则是套餐的点睛之笔——调味微咸的鸡蛋并没有炒得很熟,在生与全熟之间掌握了平衡,做到了入口软绵,回味无穷,堪称是绝品。
如果有路过,一定要来尝一下这份炒鸡蛋~
一顿饱餐之后,我继续踏上旅途,前往太平山顶的观景台。
上山的路可以选择用脚走完,不过,还是要体验一下富有特色的山顶缆车~
缆车的斜度估摸着有三四十度,是我见过最陡的。缆车是建在山坡边上的,方便沿路欣赏香港的风景。
缆车的终点站是太平山的山顶,下车后直接来到了凌霄阁摩天台的二层。沿着盘旋向上的电动扶梯,海拔逐渐升高,走着走着,最终到达了海拔428米的摩天台观景台。
在这里可以俯览香港的景色,听说夜景更美,不过我晚上并不住在香港,也就没有机会亲眼目睹,实在可惜。
摩天台上的风很大,吹着十分舒服。待了大约四十分钟,我便沿着原路返回,坐着缆车回到了山下。
早餐吃得有够饱,到了饭点我还不是很饿,便在街道与小巷间漫步,寻找着午饭的好地点。我想找家正宗点的茶餐厅。
跟随地图,我来到了就近的广芳园…可等候的队伍早已排出店外,目测有三四十人在队伍中。因为下午就要坐动车回去了,我无心加入他们,便继续游荡着,寻找着。
附近的茶餐厅真的不多,跟随地图的指引,我来到了一家街边的小店。店内只收现金,而我身上只剩下不到五十港币,便选择了菠萝油和冰阿华田作为午餐,反正肚子不是特别饿。(冰的阿华田比热的还要贵上几块钱哦)
饭后,我一时兴起决定走路去西九龙火车站,而非乘坐地铁,便开始一趟 City Walk…差点把我 Walk 没了。
街上好多地方都在施工,地图的数据并没有新到可以帮我绕开因为施工而禁止通行的路段…再加上出海关也需要一些时间,我差点以为要赶不上车。
好在我还是在开始检票前就赶到了,算是有惊无险,顺利踏上了返乡的路,结束了此次旅程。
我都带了些什么 此次我轻装上阵,只带了一个双肩包。
这次旅行我尝试了内衣、袜子和洗澡用具(浴巾与毛巾)都使用一次性的,省去了携带大量衣物且需要带回大量脏衣物的麻烦,也可以保证洗澡用具足够干净(对酒店不是很信任)。浴巾和毛巾都是压缩的,体积非常小。
其余的,便是两晚更换的衣服和电子产品的充电器了。
开户的那些事 我挺担心中银香港会开不下来,因为我还是学生也没有带地址证明。在大堂接待客户的小姐姐也询问了我有没有带上地址证明,没有的话只能碰碰运气,弄得我更慌了。
不过真正坐进柜台和柜员大哥开始走流程,直到大堂经理过来刷卡完成审核、将卡递到我手中,都没有向我索取地址证明。不知道是因为得知我是学生,还是因为我身份证上写的是具体住址,反正过了这关,顺利拿到了卡。
而汇丰之行则运气不佳,提交的资料被送至总行审核,当天拿不到账户也无法完成所有流程,只能等卡寄到居住地址,并且等下一次到港签字完成所有开户流程了。
P.S. 在跨境转账到时候一定要填清楚收款人的名字,要与中银香港的账户名称相同。我填反了名字被收了一笔手续费 QAQ
尾声 这次旅行顺利得可怕(除了差点没赶上返程的火车),在深圳与香港复杂的地铁网中也没有坐错一趟列车,似乎没能暴露出什么问题。希望 12 月底的日本之旅也能一样顺利。
一个人出来玩真的很自由,在出发前先查好可以去哪里玩、去哪里吃,到达之后就随心安排,不用迁就其他人,走累了随时可以停下休息,在咖啡店坐上一两个钟头也不会有人抱怨。
我也许爱上了一个人旅行的感觉,之后也会继续尝试这种旅行的方法。
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ My Second Attempt To ARM Servers
+
+ /archives/42ec6146.html
+
+ Last weekend, a full month without my Telegram account, I thought I had to do something. I believe I got banned because someone clicked the “spam” button on my messages, but I have nowhere to appeal.
I lost the faith in centralization. This time, I made up my mind to dive into decentralization.
First, I need a server.
Where To Purchase I used to buy servers from distributors of the DC for lower prices, but it always ended up with disappointment. The servers are just not stable and I can not upgrade the hardware configuration on demand.
So this time, I chose Hetzner for my server. Hetzner is a huge IDC in Germany, I must have to pay a fairly high price for the server, am I right?
The following images show the price of Hetzner’s traditional CX series - with x86 CPU.
A 2 CPU and 4GB RAM CX21 can fit my needs, it costs 5.35 EUR per month. By the way, the 40GB disk is far less than my requirement.
The CX series are using Intel Xeon CPU. The GB6 benchmark score of an Intel core is around 850, which is significantly lower than AMD EPYC of around 1200.
However, when I switch to the CAX series…
I can get the same number of CPU cores and RAM with only 3.79 EUR per month, 30% lower than the x86 one.
The GB6 benchmark score of ARM CPU is around 1050, even higher than the Intel ones.
The disk space is still 40GB, but I can purchase a 3.2EUR/month 1TB Storage Box to solve this problem.
With a total of 6.99 EUR per month, I can get a server with 2 CPU cores, 4GB RAM, and 1TB disk. That is a great deal.
Wait, ARM? About a year ago, when Hetzner first released the ARM series, I was interested in it and evaluated its compatibility.
I always use Docker containers to host my ~25 services. It turned out that more than half of the Docker images I was using were only compiled for the x86 CPU.
That means you have to build the image by yourself if you want to run it on an ARM CPU. The time and effort were not worth it, so I gave it up.
However, this time, I found all of the Docker images already supporting the ARM CPU. It is time to give it a try.
The Experience Here is my final hardware configuration:
I chose the CAX11 server, which has 2 Ampere ARM CPU cores, 4GB RAM, and 40GB disk. I added 2GB swap space to store cached files and reduce the load of the RAM.
I also purchased a 1TB Storage Box to store my data. Mounted to the server with NFS, just like a local disk.
I am using the latest Debian 12 Bookworm and I can’t feel any difference between x86 and ARM. My daily use software from APT source is all compiled for ARM. The installation speed is as fast as x86.
As to the Docker images, I am using Portainer to manage them. What I need to do is just click the “Recover” button on the settings page, Portianer will automatically recover the configuration from CloudFlare R2 storage and all the containers just work as before, with no need to change any settings.
Today, when I am writing this article, My services are running for a week without any problem.
Well, Still Not Perfect The ARM server just works fine, but I want to share some problems I encountered when building the ARM Docker images.
The official software repositories of Linux distributions can offer full support for ARM CPU, but the repositories of other sources such as PyPi can not.
There are still some packages that don’t have prebuilt ARM wheels, and the building process can take a long time on the 2-core ARM machine.
That is not a huge problem, but it makes the experience of ARM arch different from x86.
If you are a developer, try to include ARM in your build pipeline next time, that will be a great help for the ARM community.
Final Conclusion In my opinion, the ARM server is ready for daily use today. They offer a high quality-price ratio. I didn’t come across any compatibility problems.
Next time when you want to purchase a server, you can consider the ARM ones.
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ 修复 UEFI 引导的 GRUB
+
+ /archives/8b68ddd6.html
+
+ 问题与解决方法 环境 Manjaro Linux x86_64
Kernel: 6.2.10-1-MANJARO
使用 UEFI 引导
问题 在 GRUB 尝试引导 Linux 内核时,出现如下错误:
1 2 3 4 5 error: sparse file not allowed. 452: out of range pointer: xxxxxxxxxx Aborted. Press any key to exit.
用户将无法进入系统。
解决方法 进入恢复系统 插入 Manjaro LiveCD, 启动 Live 系统。
确定磁盘分区 在 Live 系统中,使用 fdisk -l
查看磁盘分区情况,找到安装 Manjaro 的磁盘,假设为 /dev/sda
。
我的磁盘分区如下:
1 2 3 4 5 设备 起点 末尾 扇区 大小 类型 /dev/sda1 2048 821247 819200 400M EFI 系统 /dev/sda2 821248 723390463 722569216 344.5G Linux 文件系统 /dev/sda3 723390464 983437311 260046848 124G Linux 文件系统 /dev/sda4 983437312 1000214527 16777216 8G Linux 文件系统
可以确定,/dev/sda1
是 EFI 系统分区,/dev/sda2
是系统所在分区。
挂载分区 挂载系统分区:
将当前系统的工具分区挂载到 /mnt
下:
1 2 3 mount --bind /dev /mnt/dev mount --bind /proc /mnt/proc mount --bind /sys /mnt/sys
将 EFI 分区挂载到 /mnt/boot/efi
下:
1 mount /dev/sda1 /mnt/boot/efi
进入系统 重新安装 GRUB 1 grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable
具体参数需要根据实际情况进行修改。
在这之后 重启,进入通过 GRUB 引导系统。
在系统中使用 sudo grub-install --recheck /dev/sda
命令再次安装 GRUB,确保系统能够正常启动。
一些思考 接下来的内容是我的整个修复流程,包含了如何在搜索引擎查找问题、根据文章内容调整目标等杂碎的内容,和我在修复过程中的一些感想。
为什么会出现这个问题 不是很清楚。
在启动 Manjaro 前我不小心打开了电脑里的 Windows 系统,但没有连接移动硬盘。
按照以往的经验,这最多只会导致找不到 GRUB 的位置,手动指定 GRUB 所在分区就可以启动系统。
但这次不大一样。
在打开 GRUB 之后,尝试引导内核,就发现了这个问题。
初步解决思路 立刻格式化磁盘,重新安装 Manjaro。
我已经不是曾经那个只会重装的我了,这次我希望可以解决问题,而不是简单地重装。
首先,我 Google 了这个错误,发现了几篇内容相关的文章。
报错与我一致的文章 ,但没有给出解决方案。
要我删除 GRUB 和 UEFI 所在分区所有内容的文章 ,有点可怕,不敢这么干。
提到应该重新安装 GRUB 的文章 ,这还有点道理。
于是,我的目标转变为重新安装 GRUB。
重新安装 GRUB 在之前遇到找不到 GRUB 分区的问题时,在手动引导然后进入系统后,我会执行 sudo grub-install --recheck /dev/sda
重新安装 GRUB,解决这个问题。
那这次的觉得方案应该是差不多的……吧?
不对啊,这次是在 LiveCD 的系统里操作,怎么能直接安装 GRUB 呢?
这个问题比较难描述。
我先是 Google grub-install 修复 GRUB
,中文网站的内容都是关于在可以启动的系统下修复 GRUB 的,没有我需要的内容。
然后我开始求助于 ChatGPT,输入的 Prompts 是:
1 2 I am using Manjaro with GRUB. When I booted into the system , it says "sparse file not allowed 452 out of range pointer" . How to fix it ?
不难发现,我并没有说明我使用的是 UEFI 引导,这直接影响到了 ChatGPT 回复的准确性。
ChatGPT 给出的修复步骤与上述的相差不大,只是在挂载系统分区和工具分区后,直接尝试执行 grub-install
命令,尝试修复。
grub-install
返回错误 this gpt partition label contains no bios boot partition
把我弄得更懵了。
再次 Google 这个问题,发现了 这篇在长篇大论讲 GRUB 的文章 ,虽然没有给出解决方案,但它让我意识到 UEFI 和 Legacy BIOS 两种启动方式的区别。
UEFI 和 Legacy BIOS UEFI 和 Legacy BIOS 是两种启动方式,它们的区别在于,Legacy BIOS 是在 BIOS 中直接加载内核,而 UEFI 是在 BIOS 中加载 EFI 系统,然后由 EFI 系统加载内核。
使用 UEFI 引导的系统一般都有一个 200MB 到 400MB 的 EFI 系统分区,用于存放 EFI 系统。在上述的,我的硬盘分区中可以看到。
使用 findmnt
命令可以查看当前系统的挂载情况,其中 TARGET
列就是挂载点,SOURCE
列就是挂载的分区。
EFI 分区的挂载情况为:
1 2 TARGET SOURCE FSTYPE OPTIONS /boot/ efi /dev/ sda1 vfat rw,relatime,fm
可以看到,/boot/efi
里的内容正是 EFI 系统分区的内容。(我也是刚学到这个知识的)
解决 UEFI 相关问题 在修复过程中,我是通过 Google 发现上述的问题。
这篇文章 给了我莫大的帮助。
其中提到了 EFI 分区,也提到了如何正确安装 UEFI 引导的 GRUB:
1 2 sudo grub-install --target=x86_64-efi --efi-directory=/boot/efisudo grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable
在补充挂载 EFI 分区、切换 Root 目录后,执行第一条命令,发现有错误。尝试执行第二条命令,发现没有错误,代表 GRUB 已经重新安装成功。
此时我想起来,在之前安装 GRUB 时,会提示 正在为 x86_64-efi 平台进行安装
,我才意识到前面的修复过程并没有去指定平台。
总结一下 总之,这就是我此次修复的心路历程。我没有研究过 UEFI 和 Legacy BIOS 的区别,也没有研究过 GRUB 的安装过程,所以在修复过程中,我是通过 Google 和 ChatGPT 的帮助才解决了这个问题。
希望这个探索过程能给你一些启发吧。
此文章以 我无所谓 By 不 By 什么 AI,对我有帮助的文章就是好文章 标识发布。
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+ 使用 Docker Compose 部署音乐服务器 Navidrome
+
+ /archives/8b115688.html
+
+ 服务介绍 Navidrome 是一款兼容 Subsonic API 的开源音乐服务器软件,它提供了一个不错的 WebUI,也可以将支持 Subsonic API 的客户端接入。
目前项目正处在活跃开发中,各种各样的新功能正在陆续加入。
我的客户端选择 电脑端 自带 WebUI, Sonixd 【跨平台】
iOS play:sub 【付费软件 4.99$】
部署方式 采用 Docker Compose 部署 Navidrome,使用 Nginx 作为反向代理。
Docker Compose 配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 version: "3" services: navidrome: image: deluan/navidrome:latest container_name: navidrome user: 1000 :1000 ports: - "127.0.0.1:4533:4533" restart: unless-stopped environment: ND_SCANSCHEDULE: 1h ND_LOGLEVEL: info ND_SESSIONTIMEOUT: 24h ND_BASEURL: "" ND_SEARCHFULLSTRING: true ND_SPOTIFY_ID: ND_SPOTIFY_SECRET: ND_LASTFM_APIKEY: ND_LASTFM_SECRET: ND_LASTFM_LANGUAGE: en volumes: - "./data:/data" - "/APTH-TO/navidrome-music:/music:ro"
使用命令 docker compose up -d
启动服务。
Nginx 配置文件 我建议使用 DigitalOcean 的 Nginx 配置生产工具,示例配置如下:
示例配置
也可参考下述配置,此为 DigitalOcean 生成配置的简化版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name music.example.com; ssl_certificate /etc/letsencrypt/live/music.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/music.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/music.example.com/chain.pem; access_log /var/log/nginx/access.log combined buffer=512k flush=1m ; error_log /var/log/nginx/error .log warn ; location / { proxy_set_header Host $host ; proxy_pass http://127.0.0.1:4533; } }server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name *.music.example.com ; ssl_certificate /etc/letsencrypt/live/music.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/music.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/music.example.com/chain.pem; return 301 https://music.example.com$request_uri ; }
音乐管理 我一直以文件夹分类的方式管理音乐,但 Navidrome 并不支持文件夹分类。它是根据音乐标签来分类的,例如按照歌手、专辑等依据分类歌曲。
因此,若想使用 Navidrome,需要对音乐进行标签管理。
大约两年前,我写了 一篇文章 介绍使用 Music Tag 和 Foobar2000 两款软件来管理音乐。
Music Tag 的标签源是网易云音乐、豆瓣音乐、QQ 音乐等国内音乐平台,说实话,这些平台的音乐标签质量都不是很好,所以我一直在寻找更好的音乐标签源。
直到我发现了 MusicBrainz ,这是一个开源的音乐标签数据库,任何人都可以为它贡献标签。在体验之后,我发现 MusicBrainz 的音乐标签质量要比国内音乐平台的标签质量好很多,所以我决定将 MusicBrainz 作为我的音乐标签源。
我使用 Picard 这款软件来从 MusicBrainz 获取音乐标签。
将音乐导入 Picard 后,它会自动从 MusicBrainz 获取音乐标签,然后将标签写入音乐文件,十分方便。
开始使用 不论是使用 Navidrome 自带的 Web 界面,还是使用兼容 Subsonic API 的客户端,只要连接到 Navidrome,便可开始享受你的私人音乐库。
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+ 使用 Docker Compose 部署 Keycloak 20
+
+ /archives/f9bfe16a.html
+
+ 部署方式 采用 Docker Compose 部署,使用 Postgres 作为数据库,使用 Nginx 作为反向代理。
Docker Compose 配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 version: '3' services: keycloak: image: quay.io/keycloak/keycloak:latest environment: KC_DB: postgres KC_DB_URL: jdbc:postgresql://db:5432/keycloak KC_DB_USERNAME: keycloak KC_DB_PASSWORD: keycloak KC_HTTP_ENABLED: true KC_HOSTNAME_STRICT: false KC_HOSTNAME_STRICT_HTTPS: false KC_HTTP_RELATIVE_PATH: '/' KC_HTTP_PORT: 8080 KEYCLOAK_ADMIN: MY_USERNAME KEYCLOAK_ADMIN_PASSWORD: MY_PASSWORD PROXY_ADDRESS_FORWARDING: true KC_PROXY: edge entrypoint: /opt/keycloak/bin/kc.sh start ports: - 127.0 .0 .1 :18080:8080 restart: unless-stopped db: image: postgres:14 restart: unless-stopped environment: - POSTGRES_USER=keycloak - POSTGRES_PASSWORD=keycloak - POSTGRES_DB=keycloak volumes: - ./postgres-data:/var/lib/postgresql/data
使用命令 docker compose up -d
启动服务。
Nginx 配置 我建议使用 DigitalOcean 的 Nginx 配置生产工具,示例配置如下:
示例配置
也可参考下述配置,此为 DigitalOcean 生成配置的简化版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name auth.example.com; ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/auth.example.com/chain.pem; access_log /var/log/nginx/access.log combined buffer=512k flush=1m ; error_log /var/log/nginx/error .log warn ; location / { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_buffer_size 128k ; proxy_buffers 4 256k ; proxy_busy_buffers_size 256k ; proxy_pass http://127.0.0.1:18080; } location /auth/realms { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_pass http://127.0.0.1:18080; } location /auth/resources { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_pass http://127.0.0.1:18080; } location /auth/js { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_pass http://127.0.0.1:18080; } }server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name *.auth.example.com ; ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/auth.example.com/chain.pem; return 301 https://auth.example.com$request_uri ; }
配置 Keycloak 创建 Realm 打开 Keycloak 地址 ,界面如下。
选择 Administration Console
,进入管理界面。
选择箭头指向的下拉菜单,选择 Add realm
,创建一个新的 Realm。
填写 Realm 名称,点击 Create
。
创建 Client
选择 Clients
。
点击 Create client
。
填写 Client 相关信息,点击 Next
。
按需求选择 Client 的配置,点击 Save
。
至此,Keycloak 配置完成,且创建了第一个测试用 Client。
测试 Client 可根据 官方教程 测试该 Client。
尾声 上述便是使用 Docker Compose 部署 Keycloak 20 的方法,我们顺利创建了第一个测试用 Client,接下来可以根据自己的需求进行配置。
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+ 寻梦穿越机 - 入门浅谈
+
+ /archives/9b78ad2a.html
+
+ 暑假开始之前,我就朦朦胧胧有购入一台穿越机的想法。起因为何?我也不是很清楚。是看到了酷炫的穿越机航拍视频 ?还是童年时期的航模魂蠢蠢欲动,将要苏醒?
兴趣,倘若不立刻抓住,很快就会被抛至脑后。为了不让自己后悔,那就立刻开始筹备吧!攥紧并不是特别充盈的钱包,我踏上了寻梦之路。
什么是穿越机 FPV (First-Person View),是指通过第一人称视角远程控制无人飞行设备 (UAV) 的控制方式,也是一项运动,也指代了一类设备。
FPV 设备包含固定翼 (Fixed Wing) 与多轴 (Multi-rotor) 两种。穿越机多指多轴 FPV (常见为四轴)。
一盆冷水 在详细介绍穿越机构成之前,请允许我泼一盆冷水。
穿越机与一些航拍无人机,例如大疆 (DJI) 的四轴航拍机与航拍 FPV 相比,颇有原始人见到现代人的感觉。
大疆无人机上搭载了各种各样的传感器,各系列大都配备了二向 (前后)、四向 (前后左右) 或五向 (增加一个上方) 的视觉避障功能、红外定高悬停功能,以及低电量 GPS 自动返航功能。
上述这些看似是无人机应当“标配”的功能,追求轻量化的穿越机时常一个也没有。一台最小安装的穿越机全机的传感器仅有集成于飞控的陀螺仪和地磁传感器 (电调的电流传感器一般不算在内),飞控仅知道当前机身的朝向、倾斜角度与加速度,无法感知周围的环境,没有实现避障、悬停与自动返航的能力。飞机的运动状态完全取决于飞手的操控,电池的剩余电量甚至需要飞手根据电芯电压来推断。
再者,为了能获得更大的速度与更高的机动性,穿越机飞行过程一般处于手动模式,对飞机四向的倾斜没有限制,飞行过程中出现危险操作的概率大,事故概率也大。而大疆无人机对飞行速度、角度有严格的限制,能有效地降低炸机的概率。
因此,飞行穿越机的难度将是飞行大疆等航拍无人机的十倍、甚至百倍。请务必不要抱着随便玩一玩、随心飞的态度入坑穿越机,发生事故后轻则炸机提 (遥) 控回家,重则伤到自己或他人。对于喜欢日常随心飞行的玩家,大疆或类似牌子的无人机 (包括航拍机和穿越机) 更为合适。
下文将详细介绍入门穿越机所需要的技术知识与穿越机的重要模块。在明白风险之后仍打算入坑穿越机,或对穿越机有兴趣的你,欢迎继续往下阅读。
技术储备 上文提到了我童年时期的航模魂。我在小学就跟着老师学习无线电和航模知识,练习了焊接技巧和单旋翼的航模直升机的飞行技巧。曾参加某个航模比赛并拿到了铜牌的好成绩 (我好自豪)。
因此,我对我的焊接技术和遥控操控能力比较有信心。而这两个能力恰好是入门穿越机不可或缺的。下面我列举一些入门穿越机需要掌握的技能。
锡焊 除了购买真·到手飞套餐 (机子已组装完成,接收机已与遥控器完成配对),其余情况大概率需要焊接一些导线。
大部分组装机套餐都不包含接收机,因为接收机与遥控器一一对应,一般是和遥控一起购买的。就需要用户自行焊接至飞控上。
锡焊所用到的工具有电烙铁和焊锡丝,最好能佐以松香,增加焊接成功率。电烙铁推荐购买 T12 或更好的,因为飞控散热设计,很多焊盘为通孔的形式,热量会非常快地传到到全板,导致焊锡较难达到融化的温度。若使用功率太低的电烙铁,可能无法融化焊锡。
由于飞控比较小巧,焊盘也十分迷你,对焊接技术要求较高。
使用搜索引擎 不懂不可怕,不懂得学习才可怕。
穿越机有关的零碎知识犹如满天星,散布于互联网的每一个角落,需要一定的搜索技巧才能挖掘到有用的知识。
国内有关穿越机的网站数量比较少,建议使用英文搜索。
编译 若想要更新 ELRS 接收机和高频头的固件,需要从源码编译 ELRS 固件。虽然 ELRS 团队提供了一套图形化的编译工具,但难免会遇到一些疑难杂症 (博客中就有 ELRS 固件编译的踩坑记录)。需要对编译有一定的知识,能自行解决简单的问题。或者选择使用出厂固件,不自行升级。
操控能力 (协调性) 像音游 (音乐游戏) 一样,微操遥控器并不是所有人都能很好地做到。穿越机对遥控指令的反应极为灵敏,需要你能精细地操控遥控器。
但这项技能可以通过电脑模拟飞行来习得。建议在正式开始飞行前,先在电脑上使用模拟器熟悉飞机的控制。
一颗勇敢的心 在飞行穿越机的过程中,不可能不出现炸机 (飞机以异常姿态落地) 的情况,难免会留下一点阴影。
我在小学时飞过固定翼飞机,曾不慎撞到电线,导致飞机起落架损坏脱落,最后只能由老师操控迫降。此事给我留下了不小的心理阴影,有好几个月我都不敢再飞行固定翼。不过最后还是克服了恐惧,再次拾起了遥控。
炸机并不可怕,每一次炸机都记录着你的成长。
深入了解 类似 Linux (比较奇怪的类比),穿越机也分为最小安装和额外拓展两部分。
最小安装:
机架 - 硬连接其他组件,保护脆弱的电路板; 电机 (和桨叶) - 提供飞行动力; 飞控 - 控制飞行状态,是穿越机的大脑; 电调 (电子调速器) - 驱动电机、分隔电路 (防止电机减速时产生的反向电流烧坏其他电子设备); 接收机 - 接受遥控器控制信号 (有 2.4GHz、915MHz、 868MHz 等频段,互不兼容); 图传 - 发送图像信号 (多为 5.8GHz 频段); 摄像头 - 提供第一人称视角; 额外拓展:
GPS 模块; BB 响 (蜂鸣器 Beeper,用于寻找飞机); LED 灯珠、灯带 (好看 XD); 红外避障模块; 运动摄像机 (拍摄更清晰、帧率更高的视频); 挑几个比较重要的模块介绍一下:
机架 穿越机机架一般由碳纤维板制成,质地轻盈且强度极高,可以物理连接不同的模块,并保护脆弱的电子设备 (飞控、图传等裸板)。
挑选时,机架有几个要主要关注的核心参数:
外形:
机架分为普通式与涵道式 (Duct Propeller) 两种。
普通式机架的螺旋桨桨叶裸露在外,而涵道式机架的螺旋桨外侧有涵道 (类圆筒) 包裹。
有一些普通式机架可以通过额外安装涵道,变身成为涵道机架。
普通式机架采用开放式的设计,尽可能少地使用材料以减轻重量。由于螺旋桨暴露在外,危险性较大。普通机架可被用于任何竞速与花飞机型。
涵道式机架通过涵道结构可以提供更好的升力、稳定性和安全性,但额外增添了重量和封阻,一般被用于小型机与花飞机型。
此外,机架还可以再细分为 X 型、H 型等,在此就不多展开,有兴趣的读者可以自行查阅。
大小:
穿越机大小各异,从最小的一寸机,到体型较大的五寸、六寸机。
二寸以下的机子防风能力较差,但体型轻盈且常使用涵道机架,适合在室内飞行,且不易伤人。
三寸以上的机子动力充沛,飞行速度快,但体型大、螺旋桨裸露,无法在室内飞行。适合在场地广阔的室外进行竞速或花飞 (花样飞行)。
飞控与电调 飞控与电调 (有时还有图传) 几块板子大小相当,时常纵向堆叠安装,称为飞塔。在多块 PCB 间有硅胶柱减震。
飞控需要注意的参数不多:
由于穿越机飞行对计算性能不是很敏感 (类似 3D 打印机),一般选择搭载 F405 SoC 的飞控性能就足够使用了。
电调最重要的参数就是最大放电电流了。
电调的电流选择需要根据机身大小、电机和桨叶大小、形状进行估计,通常电机厂家会给出推荐值,按照推荐选购即可。
接收机与图传
接收机与图传共享一个重要的特性,协议:
常见的接收机有 ELRS (ExpressLRS) 和黑羊 TBS 两类。应该注意的是,不同种类的接收机使用的通信协议和频段不同,能与其配对的遥控器和高频头也不同 ,因此在挑选接收机的时候一定要看清楚协议和频段。
接收机和飞控的串口通讯协议也各异,有 UART、SBUS 等数种,再购买接收机前需确认飞控有相对应的串口。
图传分数字图传和模拟图传两种。
数字图传以大疆的最为出名,需要使用配套的飞行眼镜;模拟图传大多使用 5.8G 频段通讯,和大部分接收模拟信号的飞行眼镜通用。
图传连接至飞控的串口通讯协议也很多,购买的时候请多加留意。
除此之外,接收机有遥测功率、内/外置天线、天线接口等参数,在挑选时都需要多加留意。
图传的另一主要参数则是发射功率。发射功率越大,能稳定接受图传信号的距离一般就越大。小型穿越机一般选择发射功率在 200mW 到 500mW 的图传即可 (部分图传发射功率可调);若有远航要求,也可选择发射功率大于 1W 的图传 (价格较高、发热也较大,一般带有主动散热)。
机子之外的配件 除了穿越机本体之外,想要拥有完整的飞性体验,还需要遥控器、高频头、电池、平衡充电器、飞行眼镜等配件。
上述的每一种配件都可以写作一篇介绍文章。由于篇幅有限,本文就不再介绍上述的外围配件,请善用搜索引擎,学习相关知识。
配套软件 除了硬件,穿越机配套的控制软件也尤为重要。目前主流的控制软件是开源的 Betaflight。
Betaflight 分为嵌入端 (安装在飞控中嵌入式系统) 和地面站 (安装在电脑里的软件)。将飞控通过线缆连接至电脑,并打开 Betaflight 地面站软件,即可对飞控参数进行调整。
Betaflight 调参也是一门大课,新手不建议自定义太多的参数。待熟悉飞机之后,才建议调整 PID 等高级控制参数。
新手的第一台飞机 说了这么多,要上某宝挑选、下单穿越机的种种配件了吗?
我的建议是否定的。
我咨询了一些老玩家,他们建议新手购买他人已完成调参的二手机器,或者购买商家大部分已组装完成套机,以绕过纷繁复杂且状况百出的 DIY 过程,降低还未入坑就弃坑的风险。
此外,自行购买散件的总价常常会高于购买整机的价格。对于钱包不是特别充盈的我,购买整机也是一个省钱的选择。
待熟悉了穿越机的飞行与调试之后,再学习他人经验,设计并组装一台自己心目中的机器也不迟,这才是 DIY 的浪漫。
我的成果
我选择购买一位老玩家完成大部分组装工作的“半”整机——包含了机架、电机、飞控和电调。图传、摄像头和接收机则是我自行购买和焊接安装的。
飞机到手、外围装备齐全,只待一飞冲天。
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ 使用再生龙 Clonezilla 备份操作系统
+
+ /archives/e74a90f2.html
+
+ 近日购入了一只闪迪的 CZ880 Extreme PRO 固态U盘来装 Manjaro。
虽然U盘本体是终身质保的,但数据无价,配置一遍系统就要花大把的时间。遂有了备份U盘内整个系统的念头。
下面跟着我使用再生龙 Clonezilla 把U盘里的系统备份一下吧!
事先准备 再生龙是运行在独立操作系统 (Debian/Ubuntu) 上的一套软件,因此需要制作启动盘,或使用 Ventoy 等多系统启动方案。(实测 YUMI 无法启动再生龙,故建议使用 Ventoy)
制作启动盘 前往 Rufus 官网 下载 Rufus 启动盘制作工具。
前往 Clonezilla 官网 下载再生龙映像。
使用 Rufus 将再生龙映像烧写进另一个U盘即可。
U盘内数据将丢失,请做好备份!
使用多系统启动 前往 Ventoy 官网 下载安装包(兼容 Windows 与 Linux),将你的另一个U盘制作为 Ventoy 多启动盘。
前往 Clonezilla 官网 下载再生龙映像。
将再生龙映像拷贝至 Ventoy 多启动盘中。
选择你想存放备份数据的目的地,创建一个存放备份映像的文件夹(注意目录名称中不能带有空格)。
剧透一下,40G 的系统盘备份之后大约占了 16-17G. 请留出足够的空间(建议和待备份的数据等大小)。
开始备份 瞎眼警告:由于没有合适的截屏方式,我很不愿意地采取了 拍 屏 的方式,敬请谅解。
启动再生龙系统 确保你的电脑关闭了安全启动,若还打开着,需要在 BIOS 中将其关闭。
插入刚刚制作好的启动盘/Ventoy 多启动盘,在电脑启动时猛敲键盘的…某个键,这因电脑型号而异,打开启动菜单。
选择插入的启动盘/多启动盘。
启动盘用户若没有太大的兼容性问题,就能看到再生龙的启动菜单。
多启动盘用户还要再多一步,在 Ventoy 菜单内选中再生龙的映像,如下图所示,即可打开再生龙的启动菜单。
P.S. 我的笔记本兴许和 Ventoy 的 UEFI 模式相性不大好,在 BIOS 中开启了 Lagacy 兼容模式后,使用 Legacy 模式才能开启 Ventoy。
选择再生龙启动方式
经典的 GRUB 启动菜单,一般来说选择默认的第一项启动方式即可。
VGA 启动花屏 我的电脑遇到了在 VGA 800x600 模式下花屏的问题。
最终进入 Other mods of Clonezilla live
菜单,
选择了上图中的 KVM & To RAM 模式,可以正常启动了。
USB 口不够用的用户 我这台笔记本只有两个 USB 口,其中一个要给备份源头 CZ880,另一个则要给移动硬盘,故选择了 To RAM
模式,将再生龙载入内存,就可以拔掉多启动U盘,空出 USB 口给移动硬盘了。
语言配置
选择自己想用的语言即可。
保持默认配置即可。
备份配置
我们选 Start Clonezilla 使用再生龙
。
命令行可以在熟悉了配置之后使用。
此处我们选择第一项 device-image 硬盘/分区[存到/来自]镜像文件
。
若想进行两盘对拷,可以选择第二项。我还没有尝试过。
挂载存储目录
这次我打算使用移动硬盘备份系统,故选择第一项 local dev 使用本机的分区
。
随后,再生龙会提示插入想要挂载的 USB 设备,按照提示做即可。
此时画面会动态显示系统识别到的存储设备。看到期望的目标设备时,按下 Ctrl-C
停止搜索。
在扫描完电脑当前安装的所有硬盘的分区后,你需要选择备份镜像文件存放的那个分区。
如图,我希望备份到大小为 1.8T 的移动硬盘上,故选择最后一项 sdc2
。
随后,再生龙询问你是否需要检查并修复挂载的文件系统,我们选第一项否就好了。
接着,就是选择备份镜像存放的位置。
使用键盘的方向键选择目录,使用 Tab
跳转到下方的选项,选择 Browse
并敲击回车就可以进入到此目录。
若希望在选中目录下存放备份镜像文件(是一个文件夹),就可以选择 Done
选项,回车确认。
系统询问是否检查生成的备份镜像的可还原性,这里我们选是,多花一点时间能确保备份的完整性。
镜像加密,依个人喜好选择。
待上述配置完成后,系统会向你再次确认备份的内容与目的地。
确认无误后输入 y
并敲击回车继续。
简单模式/高级模式 此时应该有一个模式选择,问你想要使用简单模式还是专家模式。
我建议选择 专家模式,简单模式能选择的参数较少。
接下来的三个选项,全部保持默认配置即可。
压缩方式选择
此处选择第三项 -z2p 使用并行 bzip2 压缩
。
实测 bzip2 压缩速度比较快,产生的备份镜像的体积也不算大。
下图为选择了第一项 -z1p 使用并行的 gzip 压缩
的速度:
下图为选择了第三项 -z2p 使用并行 bzip2 压缩
的速度:
可以看出 bzip2 压缩速度比 gzip 快了8倍。
其他压缩方式的速度,待我测试之后更新文章。
分卷大小配置保持默认即可。
备份镜像检查 待备份完成后,再生龙还会进行一次备份镜像的可还原性检查,如下图:
若得到下图的提示,则备份镜像生成成功了。
随后,选择按照意愿选择备份结束后的操作即可。
至此,再生龙 CLonezilla 的基础教学就结束了,你已经学会了如何使用再生龙的图形界面进行备份。
下面是一些再生龙的高阶(大概很高级)使用方法。
高级操作 使用无线网络备份 上文中,我的电脑仅有两个 USB 口,为备份的流程增添了不必要的麻烦。
能否使用 Wi-Fi 将备份镜像推送至家中的 NAS 呢?
再生龙内置了许多通过无线/有线网络备份的方法,如下图:
我们尝试使用 Webdav 来远程备份吧!
利与弊 使用 Wi-Fi 备份可以摆脱线缆,更加轻松而优雅地进行备份。
然而,由于通过 Wi-Fi 或者一切网络传输数据的速度仍然无法比肩有线传输,备份所消耗的时间将是备份至本地磁盘的 3-4 倍。
备份我U盘中的 40G 的 Manjaro 系统用时 30min 左右。
倘若你有大把的时间,或家中的内网速度足够快,大可使用无线备份。品着咖啡,看着数据上云(笑)。
预先准备 上文中我们选择了基于 Debian 的 Clonezilla Stable 版本,遗憾的是 Debian 系统中并未携带太多驱动程序,因此识别不到我的 Intel AX200 无线网卡。
经过测试,基于 Ubuntu 的 Clonezilla Alternative Stable 版本可以识别到 AX200 网卡。
点击上方链接即可下载 Clonezilla Alternative Stable 版本的映像。
重新烧写启动U盘/拷贝映像至多启动U盘即可。
又遇到了启动问题 使用基于 Ubuntu 的再生龙,上文中使用的 KVM
模式变得无法打开了,且 VGA 800x600
模式是一样的花屏。
在一番尝试之后,我发现藏在更多启动选项菜单里的 VGA 1024x768
模式可以正常显示。看来基于 Debian 的再生龙也可以使用这个模式。
开始备份
选择了非本地的镜像存储位置后,系统将开启上图的网络管理菜单。
选择第一项 Edit a connection
。
选择 Add
选项,在弹出菜单中 Wi-Fi
。
Profile name
随意填写;
Device
一般填写 wlan0
,系统的第一块无线网卡;
接着,按照自己的情况填写图中划线的三个配置即可。
保存 Wi-Fi 配置后,就能看到当前配置的连接状态。
若当前配置名前带 *
,且右侧选项为 Deactivate
,则 Wi-Fi 已连接成功。
接着,系统要求填写 Webdav 地址。
最后,系统会向你确认 Webdav 是否正确。
若确认无误,即可敲击回车继续。
接下来的步骤和上述初级教程硬盘挂载之后的流程是完全一样的,请参考上文继续配置。
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+ BETAFPV 高频头固件编译 AttributeError
+
+ /archives/f82a3103.html
+
+ 错误原因 Python 模块 pypandoc
版本过新,1.8.0 及更高版本已移除了 BETAFPV 高频头固件中仍在使用的 convert
函数。
解决方法 安装旧版的 pypandoc
模块。
pip install pypandoc==1.7.0
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+ DIY
+
+
+
+
+
+
+
+
+ DIY 显示器音箱
+
+ /archives/38942a16.html
+
+ 新买的显示器(LG 27UL500,虽然因为屏幕问题已经退货了)没有内置音箱,虽然大部分时间都在用耳机,但别人有的东西我不能没有嘛,就买了些材料,DIY 一个外接音箱。
写作此文章分享一下制作的过程~
物料清单
接受 5V 电压,可以驱动两个 3W 的喇叭。商品详情页面吹的很厉害,确实底噪很小,而且输出的音量非常高。相当 OK 的功放板。
8Ω 3W 喇叭两只(带音腔) 2*4RMB + 运费 音质很不错,声音很大也不会破音,因为是广告机用的喇叭么?
我选的型号是 PJ392,只要是 3.5MM 双声道的公头就行了。
这个随意选。
我买的是4芯的屏蔽线,可供 Type C 头使用(2 power 2 data),音频线只需要三芯(2 data 1 GND),屏蔽线是为了更小的干扰、更好的音质。
开始组装 3.5MM 线缆 剥除一段屏蔽线的外皮,做工还是很不错的,有金属丝和铝箔的屏蔽,塑料膜防水,还有一根抗拉扯的填充芯。
我选择使用红绿蓝三根线,黄线悬空。线色对应如下:
红 - 左声道;绿 - 右声道;蓝 - 接地。
可以预先套上一段热缩管。
取一枚 3.5mm 公头,旋下插头。
最长的一段一般是接地。若将接地朝下,我这款公头左侧为左声道,右侧为右声道。具体的接线方式可以用万用表测量接头和接口得知。
将线穿入孔中,上一坨焊锡即可。
再使用万用表测量接头与线末端的连通性,注意不能与其他线短路了。
确认无误后可以打上热熔胶固定。
再打点热熔胶,旋回外壳,将热缩管套好,加热热缩管使其收缩。
3.5mm 线缆就制作完成了。
驱动板焊接 驱动板上有三组线需要焊接:
音频输入线(3.5mm 线缆) 电源输入线(Type C 线) 音频输出线(喇叭线) Type C 线我没有再用屏蔽线,用两根导线连接 Type C 母头和驱动板即可。
焊接方法就不多说了,线穿过孔,上锡即可。
全部线缆焊接完成如下:
热熔胶填充 完成接线后,确认无短路,即可连接电脑测试音箱。
若没有问题,考虑到需要长期使用,就可以用热熔胶覆盖焊接处,防止焊点脱落。
用热熔胶覆盖之后的驱动板:
嘛…手艺不是很行。
就此,外接音箱组装完成啦!
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+ DIY
+
+
+
+
+
+
+
+
+ 迁移 Hexo 渲染环境至 GitHub Actions
+
+ /archives/2e528779.html
+
+ 本博客使用的是 Hexo 静态博客框架,我将渲染环境搭建在家中 NAS 之上,部署了一个 Ubuntu Docker 并安装好了 Node.js 环境,正常使用了一两年。
上周末,写完新文章的我心血来潮,打算在更新博客的时候顺便更新一下老旧的 Node 和 Hexo(Node 12, Hexo 5.2)。
一番折腾之后,以 npm 和 hexo 环境全部被破坏(事后想想也许只是环境变量掉了)的下场结束。
鉴于 NAS 性能低下,更新一次 node module 都需要 30 分钟,我放弃了在 NAS 上重新部署渲染环境的念头,转而使用 GitHub Actions 渲染,并同时部署于 GitHub Pages 与 CloudFlare Pages。
将渲染环境迁至 GitHub Actions 不久之前,CloudFlare Pages 悄悄下架了 Hexo 框架的部署功能,只能用 GitHub Actions 渲染,然后再部署至 CloudFlare Pages 了。
项目结构的修改 若想使用 GitHub Actions,需要将博客的源码上传至 GitHub。考虑到隐私和安全的问题,建议创建一个私有仓库管理源码。
对于项目没有什么需要修改的,因为 Actions 渲染的流程和本地渲染的流程没有区别。
唯一需要改动的,是引入的主题。由于两个 Git 仓库不能嵌套,我们需要以 Git submodule 的形式引入主题仓库。
我使用的是 Fluid 主题。采用 覆盖配置 的方法,即在根目录之下有一份配置会覆盖主题内的配置文件,便于在 Actions 中渲染。
以下也将以 Fluid 为例,请根据你使用的主题修改命令或代码。
首先,删除原来的主题(若使用的是主题内的配置,注意备份配置文件!)
返回博客源码的根目录,执行:
1 git submodule add https://github.com/fluid-dev/hexo-theme-fluid.git themes/fluid
末尾的 themes/fluid
为此 submodule 在项目中的位置与名字,与先前本地渲染时的配置相同即可。
删除子模块的过程较为繁琐,请参考网上的文章进行操作。
在 Clone 此项目时,submodule 默认不会被下载,需要使用指令:
1 git submodule update --init --recursive
下载 submodule。在下面会提到的 Actions 配置文件中会出现这条指令。
接着,就可以将博客源码上传至 GitHub。
GitHub Actions 相关文件 在博客源码根目录创建 .github/workflows/submit.yml
和 .github/script/blog-update.sh
两个文件,填入下列代码。
以下代码参考文章 GitHub Actions 自动部署 Hexo 博客到 cPanel 虚拟主机 - 谷中望月 ,有所修改。
submit.yml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 name: CI on: push: branches: - main release: types: [published ]env: GIT_USER: Lao-Liu233 GIT_EMAIL: blog@udon.eu.org jobs: build: name: Build on node ${{ matrix.node_version }} and ${{ matrix.os }} runs-on: ubuntu-latest strategy: matrix: os: [ubuntu-latest ] node_version: [16.15 ] steps: - name: Checkout uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node_version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node_version }} - name: Install hexo run: | npm install -g hexo-cli - name: Install dependencies run: | npm install - name: Clone submodule run: | git submodule update --init --recursive - name: Configuration environment run: | sudo timedatectl set-timezone "Asia/Shanghai" mkdir -p ~/.ssh/ echo "$HEXO_DEPLOY_PRI" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan github.com >> ~/.ssh/known_hosts git config --global user.name $GIT_USER git config --global user.email $GIT_EMAIL - name: Deploy hexo run: | hexo clean hexo g -d - name: Update Blog run: | sh "${GITHUB_WORKSPACE}/.github/script/blog-update.sh"
.github/script/blog-update.sh
:
1 2 3 4 5 6 7 8 9 #!/bin/sh if [ -z "$(git status --porcelain) " ]; then echo "nothing to update." else git add source /_posts/ git commit -m "triggle by commit ${GITHUB_SHA} " -a git push origin mainfi
Commit + Push,打开 Actions 界面,就能看到正在运行的 Action 啦。
不出意外,Action 成功执行,1分钟内博客就能渲染成功、部署至 GitHub Pages。
同时部署至 CloudFlare Pages 步骤较为简单,我简述一下。
打开 CloudFlare Pages, 连接至存放 渲染后 的静态文件的仓库,渲染的框架选择 None ,执行的指令填写 exit 0;
就可以了。
执行部署后,渲染后的静态文件就被部署至 CloudFlare Pages 啦。
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+ GitHub Actions
+
+ Hexo
+
+
+
+
+
+
+
+
+ DELL 灵越 15 5547 拆解与更换硅脂
+
+ /archives/9d319c54.html
+
+ DELL 灵越 15 5547,Intel i5-6200U,Nvidia Geforce 630M,8G DDR3-1600 + 256G SATA SSD(后期改装),是我的第一台笔记本。这台笔记本的拆机过程比较繁琐,是我目前遇见的最难拆解的电脑,故作此文章,分享一下如何拆解一台笔记本,并为其更换硅脂。
概述 本次拆机共耗时 1H 30Min,是拆解正常机器所花费时间的两到三倍。
拆解步骤包括:卸下后盖、拆除电池、拆除硬盘、拆除风扇、拆除键盘、卸下基座、卸下散热器、更换硅脂,然后是逆序执行上述拆解步骤,组装电脑。
拆解准备 拆机少不了一套好工具。
为了无伤拆机,需要准备一套好用的螺丝刀防止螺丝滑牙;还需要一套塑料拆机工具,用于撬开后盖。
笔记本的硅脂,我选择了霍尼韦尔 7950 相变硅脂,不容易挥发,不需要经常更换。
开始拆机 怎么拆呢 其实几个月前我就有给这台设备清灰、换硅脂的念头,但拆开机器之后我发现主板被塑料框架罩住了,无法拆下散热器。我研究了半天也没找到拆下框架的方法,便只清理了风扇的灰尘,没有更换硅脂。
而这次,我有备而来:我查到了 DELL 官方的用户手册 ,其中详细记载了拆解这台机器的方法。接下来的拆解步骤就严格按照官方的教程啦。
第一步:卸下后盖 翻到 D 面,拧下两颗固定螺丝,就能将后盖拆下,可以触及无线网卡、内存条、2.5 寸硬盘、电池和散热风扇,日常需要维护的部件都能轻松触及,好评。散热器和主板则在塑料框架之下。
第二步:拆除电池 释放主板电荷是电脑拆机中至关重要的一步!
在主板带电的情况下拔插任何端子都是不明智的做法,很可能会将主板上的高电压线路误接入低电压线路(例如拔插屏线时没有正对接口),烧坏一片元件。
这台机子的电池没有排线,直接接入主板。卸下围绕电池的四颗螺丝,手提塑料片,就可将电池卸下。
翻到 C 面,长按电源键 10s,可重复两到三次,确保主板中的电荷完全释放。
第一次见这种电池模块,比起长条状的电池可以更好地利用机身空间。
第三到五步:拆除硬盘、散热风扇和键盘 卸下固定硬盘座的螺丝,抽出硬盘,再断开 SATA 与供电二合一的线缆,即可取下硬盘。
拔下主板上散热风扇的端子(位于风扇左侧),卸下固定螺丝,即可取下散热风扇。
翻到 C 面,用手或塑料工具扣出键盘模块,注意不要大力提起键盘!小心地提起一段距离,断开机身上的排线,键盘就取下来了。裸 C 面上还有两条排线,都需要断开。竖直方向排线需从孔洞中穿回 D 面。
第六步:卸下基座 这是我第一次见笔记本中的基座,没有手册的指导很难拆下。
首先,确保 C 面两根排线已断开。
翻到 D 面,小心地断开屏幕排线(位于散热风扇左侧)。
再翻到 C 面,按手册图示卸下所有螺丝。
翻回 D 面,同样地卸下一堆螺丝,注意这两面的螺丝都是 M2.5x7 规格,长于主板用螺丝,混用可能会戳穿主板。
DELL 在机身和框架上都有标注螺丝孔对应螺丝的规格,非常好评!
确认卸下所有螺丝后,用塑料工具插入基座与机身的缝隙,划开卡口。
待大部分卡口都解开时,小心提起基座,注意将基座上的线缆和排线取下,基座就彻底与机身分离了。
基座的背面,可以看到是 PC + ABS 材料,分量很足。
拆下基座之后的机身,暴露出了主板和子板。散热器是梦幻单热管,不过压这两个破芯片足够了。
第七步:卸下散热器 散热器共有六颗螺丝,四颗在 CPU 上,两颗在显卡上。
为了使散热器均匀受力,不能一次性直接拧下一颗螺丝,而是平均为每颗螺丝卸力,可以每个螺丝一次转两圈,直到所有螺丝都被卸下。裸晶脆弱,有必要好好保护一下。
不难看出,CPU 上的硅脂全部凝固,且裸晶上的硅脂基本流失殆尽(散热器上也没剩多少)。
去除大部分凝固的硅脂,再用酒精片擦拭、用布擦干。
更换硅脂的步骤我就不在此赘述,商家一般会提供详细的视频介绍,按着教程做就可以了。
i5-6200U 的全貌,右侧长条状的裸晶应该是集成显卡吧。
Geforce 930M 显卡,非常小个。
DDR3 显存,封装方式和 DDR4 不一样,更扁一些。
后续步骤 接下来就是逆序刚刚的拆解步骤,我就不再赘述。
注意几点:
安装散热器时也需保证受力均匀,且不能过分用力,可能会压碎裸晶。 安装基座时注意将线缆与排线置于合适的位置,不要被基座压到了。 拼装完成后,插电,开机,轻松点亮。
总结 拆解一台电脑并没有想象中的那么困难,拆解不同电脑的方式也都大同小异,上述的步骤一般都能适用。
我已拆解了不下 10 台/次 笔记本,目前还未翻过车。
下次清灰、更换硅脂,试着自己动手吧!
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ Virmach Japan
+
+ /archives/28fa0729.html
+
+ 老朋友 Virmach 最近搞了场日本的预售。上游是 XTom,线路走的 IIJ。配置从 1C 384M 到 2C 2.5G 都有,价格则是 11.11刀/年 起(预售打 8 折, 8.89刀/年起)。
我买的这台是折后 9.7刀/年,1C 768M 20G NVME 2T 双向流量的配置。
直观感受:性能强劲,白天网络不错。晚高峰也能用,不爆炸。
下面放测试数据:
综合测试
CPU 很幸运地抽中了 5900X,可以看到硬盘速度非常 OK。
早上的网络情况很不错,没指望能跑满 G 口,毕竟这么多人分 10G 的口子。
性能测试
不愧是 5900X,单核跑出了部分志强将近3倍的成绩!AMD, YES!
开一台 2C 2G 的机子完全可以当作开发服务器使了。
国内网络测试 白天
三网表现均不错呢。
晚高峰
惊了,晚高峰表现很不错耶。
流媒体解锁
Virmach 只字没提流媒体,就别报多大希望。
不过作为日本的 VPS 自家游戏都不能解锁……这 IP 优化的不大行。
总结 网络不错,性能强劲,如此便宜的价格可以说性价超高。
]]>
+
+
+
+
+ 小鸡测评
+
+
+
+
+
+
+ 小鸡测评
+
+
+
+
+
+
+
+
+ 玩一玩 DN42
+
+ /archives/dbf21067.html
+
+ 两个月前,我向 DN42 提交了申请,并于4个小时后通过了审核,获得了自己的 AS 和 IP。
作此文分享一下把玩 DN42 的心得,也作为我的备忘录。
我的信息 1 2 3 AS4242423490 IPv4: 172.23 .13 .64 /28 IPv6: fd44:6b93 :4eaa::/48
目前仅一个洛杉矶节点开放 Peer,后期还将添加韩国和日本的节点。
如何把玩 注册 有关注册的文章很多,推荐这两篇:
DN42 实验网络介绍及注册教程(2022-02 更新) | Lan Tian @ Blog
初探 DN42 网络 - 宝硕博客 (baoshuo.ren)
需要使用 Git 和 PGP,在 DN42 的 Git 仓库提交你的个人信息即可完成注册。
搭建内网 在和其他 AS 建立对等连接之前,我们先要把内网整理好:
各台服务器的地理位置和网络位置各不相同,需要使用 VPN 创建虚拟局域网。
课堂上讲了两种内网路由协议:
有一位老朋友可以轻松解决以上两个问题:Zerotier 。
Zerotier 的虚拟网络可以使用自己的 IP,只需在 Managed Routes 中设置你在 DN42 申请的 IPv4 和 IPv6,即可为每台加入虚拟网络的主机自动或手动配置 DN42 IP。
在机器之间使用 DN42 IP 互 ping 测试连通性。
准备 BGP 相关软件 搭建好内网之后,就可以开始配置 BGP 发言人啦。
选择一台或多台服务器,作为自治域向外宣告路由的发言人。
在每台服务器上都需要配置 BGP 相关的软件,以及和其他 BGP 发言人建立连接(一般是 VPN 连接)的软件。
目前在 DN42 网络用的比较多的 VPN 软件是 Wireguard,BGP 软件则可以从 bird 2、bird 1、quagga 等软件中选择。
我使用的是 bird 2。
安装与配置 BIRD 2 安装命令:
1 2 apt update apt install bird2 -y
bird 2 的配置文件位于 /etc/bird
,名为 bird.conf
。
配置文件可以参考(照抄)DN42 官方给出的配置:howto/Bird2 (dn42.dev)
喂到嘴边的配置方法:
将官方配置填入 /etc/bird/bird.conf
在 /etc/bird
目录下新建名为 peers
的文件夹 下载 ROA 配置(命令来自宝硕的博客 ) 1 2 wget -4 -O /tmp/dn42_roa.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf && mv -f /tmp/dn42_roa.conf /etc/bird/dn42_roa.conf wget -4 -O /tmp/dn42_roa_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf && mv -f /tmp/dn42_roa_v6.conf /etc/bird/dn42_roa_v6.conf
并配置 crontab,每小时自动下载并重载新配置:
1 2 3 0 */1 * * * wget -4 -O /tmp/dn42_roa.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf && mv -f /tmp/dn42_roa.conf /etc/bird/dn42_roa.conf 0 */1 * * * wget -4 -O /tmp/dn42_roa_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf && mv -f /tmp/dn42_roa_v6.conf /etc/bird/dn42_roa_v6.conf 0 */1 * * * birdc configure
安装并配置 Wireguard 安装命令:
1 2 apt update apt install wireguard -y
这样就安装了 Wireguard
和名为 wg-quick
的管理工具。
使用命令:
1 wg genkey | tee privatekey | wg pubkey > publickey
在当前目录下创建 Wireguard 建立连接所用的密钥对(公钥和私钥)。
就此 Wireguard 安装完成。
配置系统内核 打开内核的数据包转发功能:
1 2 3 4 echo "net.ipv4.ip_forward=1" >> /etc/sysctl.confecho "net.ipv6.conf.default.forwarding=1" >> /etc/sysctl.confecho "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf sysctl -p
关闭内核 rp_filter
的严格模式:
1 2 3 echo "net.ipv4.conf.default.rp_filter=0" >> /etc/sysctl.confecho "net.ipv4.conf.all.rp_filter=0" >> /etc/sysctl.conf sysctl -p
如果有 ufw 等防火墙自动配置工具,务必关闭。
p.s. 我拿到任何机器后会立刻执行的指令是:
创建 Dummy 网卡 dummy 网卡具体的作用我不是很清楚…
只知道如果要用链路本地地址进行通讯,要把 DN42 的 IP 地址绑定到 dummy 网卡上。
dummy 网卡配置指令如下:([ ] 中为需要你填写的内容)
1 2 3 4 5 ip link del dummy ip link add dummy type dummy ip addr add [你的 DN42 IPv4 地址]/32 dev dummy ip addr add [你的 DN42 IPv6 地址]/128 dev dummy ip link set dummy up
和小伙伴建立对等连接(peer) 需要和对方分享的 你的 DN42 信息,包括 AS 号和发言人的 DN42 IPv4(IPv6)地址; 若使用链路本地地址,还需提供这东西,一般为一个本地 IPv6 地址,常取 fe80::[你的 AS 号后4位]
; 发言人的外网 IPv4 地址(或域名)和 Wireguard 隧道的通讯端口; Wireguard 公钥。 有一些信息会在以下的配置中获得。
Wireguard 相关的 在 /etc/wireguard
目录下创建 Wireguard 配置文件,每一个配置文件对应着一个 Wireguard 隧道。
例如你要和 AS114514 臭 建立对等连接,可以在 peers
文件夹下新建一个名为 wg_114514.conf
(文件名即为 wireguard 隧道名)的配置文件。
配置的模板如下:([ ] 中为需要你填写的内容)
1 2 3 4 5 6 7 8 9 10 11 [Interface] Table = off ListenPort = [我们的监听端口,可以用对方 AS 号的后五位]PrivateKey = [刚刚生成的 Wireguard 私钥]PostUp = ip addr add [本机的 DN42 IPv4 地址]/32 peer [对方机器的 DN42 IPv4 地址]/32 dev %iPostUp = ip addr add [本机的链路本地地址(见 BIRD 相关配置)]/64 dev %i[Peer] PublicKey = [对方的 Wireguard 公钥]AllowedIPs = 10.0 .0.0 /8 , 172.20 .0.0 /14 , 172.31 .0.0 /16 , fd00::/8 , fe80::/64 Endpoint = [对方机器的公网 IP 地址或域名 : 端口号]
然后使用 wg-quick up [wireguard 隧道名(刚刚的配置文件名)]
启动 Wireguard 隧道。
可以 ping 一下对方的 DN42 IP 看看 Wireguard 隧道是否连接成功。
使用 wg
命令查看各隧道的连接情况。若有显示 last handshake
,一般情况下隧道就已成功建立。
BIRD 相关的 在先前导入的 bird 2 配置中定义了一个 peers
文件夹,就是用来存放 peer 相关的配置。
例如你要和 AS114514 又臭 建立对等连接,可以在 peers
文件夹下新建一个名为 114514.conf
(文件名可自定义)的配置文件。
我采用的是链路本地地址(Link-Local) 的配置方法。配置的模板如下:([ ] 中为需要你填写的内容)
1 2 3 protocol bgp from dnpeers { neighbor % '' as ; }
添加完配置之后别忘了用 birdc configure
重载 bird 2 配置。
使用命令 birdc s p
可以查看 BIRD 2 软件下所有协议的通信情况。
若显示为:
1 dn42_xxxx BGP --- up 20 :36 :30 .984 Established
则表示 BGP 连接已建立。
尾声 我还在写 DN42 相关的站点,在上面分享节点信息,方便大家 peer。
但目前进度缓慢(悲)。
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+ 合宙 EPS32-C3 把玩记录(二):WiFi 与一个 Web 程序
+
+ /archives/9b58c98e.html
+
+ 了解一下相关的库 这个库是自带的,不需要引入。
据我的理解,单片机的串口就是控制台程序的控制台,可以返回一些信息给上位机。
会用到的几个指令:
1 2 3 4 5 6 7 Serial.begin(Baudrate); Serial.end(); Serial.read(); Serial.peek(); Serial.flush(); Serial.print/println(); Serial.write();
#include <WiFi.h>
AP(接入点) Mode 创建一个接入点。
1 2 3 4 5 WiFi.mode(WiFi_AP); WiFi.softAPConfig(local_IP, gateway, subnet); WiFi.softAP(SSID,PASSWD);
更多函数见 WiFi.h AP 常用方法说明
STA(站点) Mode 接入一个 AP。
1 2 3 WiFi.mode(WIFI_STA); WiFi.start(SSID,PASSWD) Serial.println(WiFi.localIP());
更多函数见 WiFi.h STA 常用方法说明
#include <WebServer.h>
创建一个简单的网站服务器。真的很简单。
一个个函数讲有点难理解,我放在这节的例程里面说明。
写一个测试程序吧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <WiFi.h> #include <WebServer.h> const char *SSID = "YOUR_SSID" ;const char *PASSWORD = "YOUR_PASSWORD" ; WebServer server (80 ) ;void handleIndex () { server.send(200 , "text/plain" , "Hello from ESP32!" ); }void setup () { Serial.begin(9600 ); Serial.println(); WiFi.mode(WIFI_STA); WiFi.begin(SSID, PASSWORD); while (WiFi.status() != WL_CONNECTED) { delay(500 ); Serial.print("." ); } Serial.println("WiFi connected!" ); Serial.println("IP address: " ); Serial.println(WiFi.localIP()); server.on("/" , handleIndex); server.begin(); Serial.println("WebServer begin!" ); }void loop () { server.handleClient(); }
访问串口返回的 IP,即可看到 Hello from ESP32!
这句话啦。
还有个 Web Server 叫 ESPAsyncWebServer 自带的 WebServer 是同步的,只支持处理一个连接。对于这种体量的机器其实足够了。
顺便学习一下一个第三方库吧。
添加库 对于这款 ESP32,需要下载并导入两个库(源码 ZIP 即可):
me-no-dev/AsyncTCP: Async TCP Library for ESP32
me-no-dev/ESPAsyncWebServer: Async Web Server for ESP8266 and ESP32
在 Arduino 的项目 > 加载库 > 添加 .ZIP 库
中导入这两个库。
用 ESPAsyncWebServer 重写刚刚的例程吧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <WiFi.h> #include <ESPAsyncWebServer.h> const char *SSID = "YOUR_SSID" ;const char *PASSWORD = "YOUR_PASSWORD" ; ESPAsyncWebServer server (80 ) ; void handleIndex (AsyncWebServerRequest *request) { request->send(200 , "text/plain" , "Hello, world!" ); }void setup () { Serial.begin(9600 ); Serial.println(); WiFi.mode(WIFI_STA); WiFi.begin(SSID, PASSWORD); while (WiFi.status() != WL_CONNECTED) { delay(500 ); Serial.print("." ); } Serial.println("WiFi connected!" ); Serial.println("IP address: " ); Serial.println(WiFi.localIP()); server.on("/" , handleIndex); server.begin(); Serial.println("WebServer begin!" ); }void loop () { }
理论上来讲,上面的代码应该是正确的……
但 Arduino 在编译的时候报错,内容是 ESPAsyncWebServer 库中的某些代码。
有待我弄清楚出错的原因。
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+ 嵌入式开发
+
+
+
+
+
+
+
+
+ 合宙 EPS32-C3 把玩记录(一):环境搭建与第一个程序
+
+ /archives/7f7bd4a5.html
+
+ 为了贯彻本博客最重要的关键词:性价比 ,我看到性价如此高的开发板,想都没想就剁手了。
嘛其实也没有这么冲动,我在购买 3D 打印机之后就一直在计划着做一些网上现成的电子项目,但碍于这段时间 MCU 和大尺寸屏幕价格的飙升,一直没能开始动手。
正好最近我学习了 iPad 上的 3D 建模软件 Sharp3D,项目的外壳建模变得有可能;又遇到了这块便宜的板子,立即开工!
因为1.8寸的 TFT 显示屏还没到货,3D 建模就先放一边,先来研究一下这块开发板。
事先声明 本教程是我一边从零开始学习嵌入式开发一边作成的,有逻辑混乱、内容浅显和成吨的错误,还请已经熟悉嵌入式开发的大佬多多包涵与斧正)
问题:什么?开发环境不是按语言分的嘛? 在开始学习嵌入式开发之前,我简单地认为嵌入式开发因语言而已,分为用 C/Cpp 开发(Arduino)和用 Python 开发(MicroPython)。
直到我遇见了 ESP-IDF 这个东西。
啥啊,为啥这家伙用的也是 C,代码我还一点都看不懂。
解答 嵌入式开发选用的语言和语法因选择的框架而异。
ESP-EDF 更靠近底层,因而编写更复杂;Arduino 对底层进行封装,更靠上层且对用户更友好;MicroPython 则是在开发板上还原了一个 Python 的开发环境,继承了 Python 的诸多优点(简单的语法、无需编译就能执行新代码等)。
此外,还能用 JS、Java、Lua 等等语言进行开发。
我的选择 我手上有两块板子,一块被我刷成了 MicroPython,但目前不打算去用它。
我打算用 Arduino + C 进行开发。
配置 VSCode + Arduino 开发环境 Arduino 没有代码补全,太难用。简述一下如何使用 VSCode 进行开发:
VSC 安装 Arduino 插件; 在 首选项-设置 中配置 Arduino 的路径 Arduino.path
打开项目后选择 MCU 类型和串口 就能用啦。
第一个项目 第一个项目就不选输出 Hello World 了,一点硬件的感觉都没有。
据 官方文档 ,主板板载的两个 LED 灯对应的 GPIO 为 IO12 IO13
,高电平有效。
就此编写一个无稳态多协振荡电路让 LED 灯交替闪烁的程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void setup () { pinMode(12 , OUTPUT); pinMode(13 , OUTPUT); digitalWrite(12 , LOW); digitalWrite(13 , LOW); }void loop () { digitalWrite(12 , HIGH); digitalWrite(13 , LOW); delay(1000 ); digitalWrite(12 , LOW); digitalWrite(13 , HIGH); delay(1000 ); }
编译+上传即可。
结果就不展示了,两个灯在交替闪烁。
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+ 嵌入式开发
+
+
+
+
+
+
+
+
+ Code-Server 的代理配置
+
+ /archives/d01399e6.html
+
+ 一年前,我介绍了如何在群晖上使用 Docker 部署 Code-Server,在外也能轻松使用已经配置好的开发环境。群晖搭建 VSCode 服务器与 Syncthing 服务
最近我换了 iPad,琢磨如何发挥她的生产力。除了使用网页版的 IDE(Codepen、Gitpod等),就是自建网页版的 VSCode 了。下面简要介绍一下我是如何给 Code-Server Docker 配置代理,使其成为一个完备的开发平台。
将 Code-Server 部署在国内服务器(例如我家里的 NAS),可以获得稳定的连接,这对于开发平台是尤其重要的,VSCode 遇到连接不顺畅就会要求你刷新界面,很可能会丢失数据。
但由于众所周知的原因,在国内的网络环境做开发可以说是寸步难行,我便采用 Clash Docker 来给 Code-Server 加上代理。
Clash Docker 安装 Clash Core 普通版 Image:dreamacro/clash - Docker Image | Docker Hub
Clash Core Premium Image:dreamacro/clash-premium - Docker Image | Docker Hub
Clash Core Premium 二进制文件: Premium release (github.com)
Clash Core 有普通版和 Premium 版之分,目前我能体验到的二者的区别是普通版的 Clash Core 不支持 RULE-SET 功能。
我常用的配置文件大量使用了 RULE-SET,所以我必须得用 Clash Core Premium。
但 Pre Build 的 Image 似乎不支持 X86-64 v3 之下的 CPU(例如我的 J1900),所以我采取了部署普通版 Image,然后 attach 进 Docker 手动更换 Premium 内核的曲线救国方法。(更换 /
根目录下名为 Clash
的二进制文件)
部署 Docker 时注意一下几点:
开放 7890(或你定义的代理端口)和 9090(Clash Core 管理面板)端口。 将 /root/.config/clash
文件夹挂载到本地,存放 config.yaml
及其他配置文件。 请勿将 Clash Core 的管理面板暴露到公网。我选择用 Tailscale 建立 VPN 访问家中的服务器进行配置。
Code-Server 的配置 需要在 Code-Server Docker 里添加两个环境变量,实现开机自动连接代理:
http_proxy=http://clash_docker_ip:7890
https_proxy=http://clash_docker_ip:7890
可以使用同样的方法给其他 Docker 添加代理。
在一顿折腾之后,Code-Server 终于可以顺畅访问 Github 等网站了。
可喜可贺,可喜可贺。
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+ Klipper 的外网访问
+
+ /archives/7263a385.html
+
+ 网络上有关 Klipper 的中文教程少之又少,固作此教程介绍一下如何在外网优雅地访问家中装有 Klipper 的 3D 打印机。
方法一:端口转发 此方法仅适用于拥有公网 IP 的用户
首先,使用 DDNS 将家庭宽带动态变化的 IP 绑定至域名,教程如下:
在配置端口映射之前,先介绍一下 Klipper 的网络结构:
graph LR;A("你的设备") <--80-->B("前端网页(Fluidd/Mainsail/Octoprint)") <--7125-->C("API 服务器 Moonraker") <-->D("你的 3D 打印机");
线上的数字便是通讯的端口。
由上图,我们不难看出,若需要在外网访问家中的 Klipper,就需要映射 80 和 7125 两个端口。
于路由器的 端口转发/端口映射 界面配置 80 和 7125 的转发即可。家庭宽带的公网 IP 不会开放 80 端口,可将外网端口配置为 8080,对应的内网端口为 80 即可。
接着,在 Moonraker 配置 moonraker.conf
[authorization]
模块的 cors_domains
模块中添加你的域名,格式为 *://你们域名
也可以选择不使用自己搭建的前端网页,而使用 Fluidd 或者 Mainsail 作者搭建的前端网页。在 Moonraker 配置 moonraker.conf
[authorization]
模块的 cors_domains
模块中添加 *://my.mainsail.xyz 与 *://app.fluidd.xyz
方法二:内网穿透 本人不推荐使用这个方法,固仅简述一下
可以使用花生壳等内网穿透服务,但给的带宽太小,只能使用控制界面,不能使用摄像头。
也可以选择自建内网穿透,例如 Frp, Ngrok 等服务。但最近越来越多 Frp 服务器遭到攻击,固不建议自建。
方法三:使用 VPN 这是本人推荐的方法。
与 Octoprint + Marlin 仅需要映射 80 端口不同,Klipper 还需要映射 Moonraker 的 7125 端口,配置端口转发与实际使用都不如前者来的方便。
我个人推荐用诸如 Zerotier, Tailscale 一类的 VPN 软件,搭建自己的小内网,通过内网 IP 直接访问 Klipper,既安全又方便。
Zerotier 或者 Tailscale 的使用技巧网上一大片,我就不赘述了。
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+ 3D 打印
+
+
+
+
+
+
+
+
+ 我与 3D 打印
+
+ /archives/19f3c1e1.html
+
+ 玩 3D 打印有大半年了,虽然还谈不上资深,但已有了一些自己的想法,作此文分享一下自己的心得。
3D 打印机介绍 3D 打印机的分类 民用 3D 打印无非两个流派:熔融沉积成型(FDM)与立体平板印刷(SLA,又称光固化),工业用途的还有金属粉末打印的 SLS。
光固化就不多提了,其优点是精度高,缺点则是机器与耗材价格昂贵,且打印时有比较大的气味(可能对人体有害),对通风的要求比较高,个人认为不适合在家里玩。
更适合摆在工作室里的 FDM 则是百花齐放,诞生出了各种各样的架构:从最简单的单臂型,到 I3 龙门架、圆形打印平台的三角洲,再到高速高精度的 CoreXY/XZ/YZ 架构、性能超强但组装价格昂贵的 UM 结构。最出名的开源设计则是 Voron 团队,旗下有小型的 Voron 0.1 和大型的 Voron 2.4,且还在持续更新中。
3D 打印控制系统 目前我见到的国内用的最多的 3D 打印机控制板(主板)是 MKS GEN L 系列(1.0/1.2/2.1),其搭载的是 ATmega 2560 芯片,常见的控制系统无非两种:Marlin 与 Klipper
Marlin 1.0/2.0 Marlin 是一款片上储存,支持脱机打印、屏幕控制的系统。由 C 语言编写,配置较容易修改,但需要手动编译并烧录至控制板。目前已知的编译方法是 Arduino 编译与 VSCode 插件编译,我使用的是后者,但给我的体验不是很好。
Marlin 的优点是(若不需要自定义)安装简单,仅需用数据线连接电脑烧录即可。缺点则是对打印机的任何重新配置(除了能在屏幕上直接修改设置,如电机方向等),特别是之后要更换静音驱动,就需要手动编译系统,门槛较高且操作繁琐、坑多。
Klipper Klipper 是一款需要上位机控制的系统,主板上仅烧录了一个接收上位机控制指令的程序,大小大概是 Marlin 系统的 1/5。
据 Klipper 的开发团队描述 ,大部分单片机的性能比较弱,每秒能处理的指令(打印机控制指令)数不高,固采用上位机的高性能 CPU 来处理更高精度但更多指令数的控制指令可以有效地提高打印质量。
根据跑分表来看,其实 Mega 2560 的性能没有比树莓派的 CPU 弱很多,但考虑到树莓派还可以安装 API 服务器与 Web 控制界面,可以给打印机拓展更多的功能,连接一台上位机还是很具性价比的。
Klipper 在我看来就像是 3D 打印界的 Ardupilot (开源无人机控制套件),用户可以接触到很多底层的设置、直接控制主板上的所有输出,且修改任何配置都不用重新烧录系统,非常适合创客使用。
但高级权限也意味着更大的风险。若配置不当,则可能烧坏硬件。
目前我正在使用的是 Klipper 固件,在两三天的配置调试之后基本可以不用动配置文件了。配置过程中踩了一些坑,后面会单独发一篇文章分享一下 Klipper 配置过程。
3D 打印材料 SLA 打印用的是各种光敏树脂,我没有用过就不评价了。
FDM 使用的材料更加多种多样:PLA、PETG、ABS 三者为主流,TPE 柔性材料也见过有人在用。PLA 是最好打印的一类材料,PETG 需要较高的打印/热床温度,而 ABS 需要保温打印(需封箱)。剩下的就是一些改进/特种材料。例如在 PLA 中添加少量 PC、ABS 以加强硬度,这些材料名字多样,一般是在 PLA 之后加代号,如 PLA-F/AF/AT/G/Pro 等等;或是在 PLA 中添加少量木屑或杂质,使打印机外观像木制品/大理石制品,这类材料比较容易堵头,不是很推荐使用;甚至还有人打印碳纤维,但价格昂贵。
购买 3D 打印机的理由 我不想做一个 “KOL”,把 3D 打印吹得天花乱坠。谈一谈我购买 3D 打印机的理由:
可以动手 我是一个酷爱动手的人,看见 DIY 项目就忍不住去尝试。或许这辈子开不上高达,但能亲手组装一台能由我操控的机械(甚至还能打印出高达),也算是一点小小的慰藉。
让梦想中的模型成真 我有购买一台 3D 打印机的念头,来自一张网图,是一个制作精美的太阳时钟 —— 不是日晷,而是通过巧妙设计的结构,能投射出数字形式的大致时间。显然,这个太阳时钟是 3D 打印的。当时我就觉得,如果我能有一台 3D 打印机,我就能做各种各样的小玩意儿,多美妙 :)
建模小帮手 很奇怪的理由,但 3D 打印机确实促使我提升了 3D 建模技术。机器可以创造任何物品,但网上的模型毕竟有限,很多你想要的物品就需要你亲手去建模了。初中时我有学习 3D 建模的念头,当时选择了 C4D,但因为难度太高没多久就放弃了。现在我选择了学习 SolidWorks,自行设计并打印成品,我就能最直观的感受到模型的问题所在,并在软件里修正错误。
挑选打印机 于是我就定下了买一台 3D 打印机的计划。这么一计划就是一年多。
在这一年里我找了很多店家,比对他们的性价比(我的博客里最经常出现的一个词),最后挑中了这家 —— 小树科技。
我不适合想买成品机,理由很简单:性价比较低,且没有组装的乐趣。这两年国内做开源机器的团队越来越多,小树科技算是比较早的一家。我最看重的莫过于详细的安装文档和一个可靠的社区,毕竟这次我要涉猎的是一个完全陌生的领域。
逛了逛他们的官网 ,确定他们有提供安装文档,用户社区也达到数千人规模,我决定了要买这一家的机器。
新手入门,我选择了 T3 型号的机器。单臂架构,3D 打印机中最简陋、缺点最多的架构,但也是消耗材料最少,价格最便宜的结构。
DIY 之路必然是坎坷的,如果你决定要自己组装一台 3D 打印机,过程中必然会遇到种种问题。做好心理准备,等待迎接解决问题之后的喜悦吧。
经过半年的调机与改装之后,这台 T3 已经能稳定地产出质量尚可的打印件,我认为再去改装这台机器所带来的提升已经不大,便决定将其升级为 CoreXY 架构。
升级 CoreXY 就是这个寒假的事情了,组装花了四五天,调机则还在进行中。目前来看这台机子潜力很大,请期待后续分享调机过程的文章。
结语 感觉没什么好结的。
说句实话,调机真的挺痛苦的,刚装完机打两次很可能就会错一次层;但调完之后,再也不会错层的成就感经久不衰。
]]>
+
+
+
+
+ 3D 打印
+
+
+
+
+
+
+ 3D 打印
+
+
+
+
+
+
+
+
+ Deepvm 9929
+
+ /archives/6e832212.html
+
+ 本以为不会再购买新的机子了,今天看到有一台挺便宜的 9929 线路的机子,就买了一个月尝尝鲜。
配置:1core 512Mb 20G-SSD 500G@150Mbps
价格:16元/月
总结:Something strange.
网络
晚高峰测速全部都能跑满 150Mbps,看起来很不错。
AS9929 是联通最新的专线,好像比 AS4837 要高级那么一点点,应该体验要比 4837 来得好一点吧,看延迟会比 4837 低十几毫秒。
但实际体验下来,双向延迟会比 4837 高上 200ms 左右,speed.cloudflare.com 的成绩也不如 4837 来的好,众人迷信的 Youtube 4K 视频(电脑解码带不动 8K)跑分在 56k,和 4837 基本一致。
这就是我上述的 something strange 了。Deepvm 家的上游好像是 Spartan,按道理网络是不会差的。我猜测是这家的机子超售的稍微厉害了一点点,导致总体体验不如 Wikihost 家的 4837.
性能
跑分上来看,性能是十分不错的。性能这一块又没有超售太多???把我整懵了。
综合价格和性能来看,这台机子的性价比还是很能打的。由于商家是去年年初刚成立的,服务器的可靠性还是未知数,也没有赠送自动备份(在折腾坏系统的时候有备份可以还原是很美妙的事情)。因此我建议月付购买这家的机子。
放上 AFF:https://www.deepvm.net/aff/HVQAFOVA
]]>
+
+
+
+
+ 小鸡测评
+
+
+
+
+
+
+ 小鸡测评
+
+
+
+
+
+
+
+
+ WikiHost CU4837
+
+ /archives/be3776eb.html
+
+ 今天要介绍的是一台来自 WikiHost 家的机器。
配置:Los Angeles - CU4837 Lite KVM
1 core (5900X)@30% 基准性能 512Mb 20G-SSD 500G@1Gbps
价格:月付 18 元(首月附加 5 元初装费)/ 年付 200 元(免初装费)
总结:性能优异,网络为最大卖点。性价比很高。
网络
G 口没有虚标。
测试特意挑在晚高峰跑的,与前面测评的 Cloudcone 相比,上海与广州出口三线的表现都很优秀。
这台机子的去程是 163 骨干网,回程是联通专线 AS4837. 图中为回程路由 —— 进入电信的骨干网之前的流量就是 4837 承载的。
性能
听到 30% 基准性能,你会觉得这是一台跑 apt 都嫌卡的机子。
你错了!这台母鸡配备的可是 AMD R9-5900X。AMD YES!
即使只有单核的三成性能,跑分依旧十分接近 WebHorizon 的单核成绩,轻松超过了 Cloudcone 的单核成绩。
所以这台机器在日常使用时不会觉得 CPU 性能太差。主要的瓶颈还是在 512MB 内存上。
综合性能和网络考虑,这台机子作为远程开发机器,安装 VSCode 跑一些轻量的项目还是很不错的。这台机器最合适的工作还是优化国内连接国外的网络情况,例如加速 GitHub 下载速度等。这个性能建立个人网站也完全足够了,140ms 较低的延迟与晚高峰的超低丢包可以带来很不错的体验。
你问我推不推荐?那肯定推荐啊!
鸡总的 WikiHost 是以高质量服务著称的,我在 WikiHost 购买过网站空间和流量转发,使用期间给我的体验很不错,发工单很快就能得到回复或技术人员的直接处理。缺点就是大部分机型价格都比较高(月付 30 元以上),而这台 Lite 机型以超低的价格(还附赠了7天全盘备份,前几天把机子玩炸了就用上了还原功能),一出来就被抢光了。目前还有存货的是直连韩国 VPS(高峰时段三网全炸,平时延迟非常低),有需求的朋友也可以考虑。
放上 AFF:https://idc.wiki/aff.php?aff=2182
这台机子让我非常满意,再配合 Cloudcone 的高性能、较大硬盘的机器,因此短时间内我应该不会购入新的机子,尽请期待未来的机器测评。
]]>
+
+
+
+
+ 小鸡测评
+
+
+
+
+
+
+ 小鸡测评
+
+
+
+
+
+
+
+
+ Cloudcone 双十一特价鸡
+
+ /archives/a7b78eea.html
+
+ 今天来介绍一下来自(依旧是)高性价比商家 Cloudcone 的机器。
配置:2cores 2GB 70G-SSD(RAID 10)4T@1Gbps
价格:年付23.8刀
来个总结:性能优异、硬盘性能尤为突出;网络质量尚可;性价比高。
网络
测试是在晚高峰做的,只能说炸的很彻底,国内基本没有什么速度。
中午再测一次,白天的网络还是很不错的。
根据路由测试来看,走的是最普通的163骨干网,没有线路可言。
Mtr 显示丢包率在 10-20%,延迟在 160-190ms,中规中矩。
性能
单核性能说不上强,甚至打不过 Webhorizon 的 NAT 机子。
但是有俩核啊~
在编译一个 Node.js 项目时,其他机子要花将近一分钟,这台机子只要 32s 左右,有效提高了我的工作效率。
一个月前,手持 Virmach 的我流下了不争气的泪水,发誓要买一台好一点的机子做开发、跑服务。
于是我就在网上找性价比商家了(笑)。
有人推荐我买 GreenCloud 的机子,500G 大盘鸡,价格也不贵。我当时就很心动,几欲下单,最后上网一查,发现 GC 家的 IP 段不大好,会被 Google 查岗(验证是否是机器人),遂放弃。
持币徘徊的我遇见了 Cloudcone。网上一查,没有什么黑历史,我就和同学“拼鸡”下单了。(没错,我只花了一半的钱 XD)
24刀的价格平常只能买到 1c 1G 的机子,双十一特价可以让性能翻倍,性价比一下子上来了。70G 的硬盘也可以用来挂一些需要存储数据的服务(例如现在正在跑的 H@H)。
至于网络嘛……因为没有特殊线路,晚上还是挺糟糕的,有时候 SSH 都会断连。
不得不提的是,就在我开通机器一周后,Cloudcone 貌似出现了部分机器数据丢失的问题……因为范围不大,似乎没有引起什么讨论。因为没有附带异地备份的功能,使用高性价机器要承担一定风险,重要数据就不要存放在此类没有备份的 VPS 中。
总结来说,如果遇到活动,Cloudcone 的机子还是很值得入手的!
正好圣诞节促销到了,力度比双十一还大一点点。放上 AFF Link~
https://app.cloudcone.com/?ref=7447
]]>
+
+
+
+
+ 小鸡测评
+
+
+
+
+
+
+ 小鸡测评
+
+
+
+
+
+
+
+
+ WebHorizon NAT JP
+
+ /archives/9732665c.html
+
+ 第一篇正式测评:来自印度的性价比商家 WebHorizon 的特价 NAT 小鸡。
配置:1core 256Mb 4G-HDD 500G 1Gbps,机房位于日本,有 IPv6,仅有 NAT IPv4.
价格:……4刀一年。
先来个总结:买了吃灰的机器。
网络
小鸡号称的 G 口是货真价实的。
没有独立 IPv4 带来的限制很大。WebHorizon 提供了四个 IPv4 地址用于转发 VPS 的端口。但经我的随机(随便)测试,我开不到任何能用的 TCP 转发端口,仅能开到 HTTP/HTTPS 的转发(80/443 端口)。
因此,想要连接这台机器需要用到 IPv6,能提供的服务也仅限 HTTP。
NAT 机器还有一点不好:若四个转发地址都被墙了,那这台机器在国内就没得用了。目前四个地址被墙了一个。
从国内访问的线路是双向 NTT。白天效果尚可,晚高峰时间炸得厉害。全天高延迟 160-240ms。网络只是可以用的级别。
性能
从跑分上来看,这台机子的 CPU 性能出奇的可以。
可能是买来的都吃灰了,没人抢性能
硬盘的表现则是中规中矩,能用级别。
总结上述来看,这台机子挺不行的。那为什么我买了呢?
看到4刀一年的价格,我的捡垃圾之魂就按耐不住了啊!
我现在只是非常的后悔。
256Mb 的超迷你内存让这台机器承担不了什么任务。挂一点小脚本大概能吃得消。适合钱包真的很空/垃圾佬购买。
虽说不推荐购买,我还是厚着脸皮放一个 AFF 连接:
https://my.webhorizon.in/order/forms/a/MzQ1Nw==
P.S. 4刀一年是黑五期间的 50% OFF 优惠。
附赠 这台机器的虚拟化技术是 OpenVZ,也带来了一些不足:
超售严重,高峰期性能可能不如预期。 与母鸡共用内核,因此无法自行修改内核。有一些服务,例如 BBR、锐速等,需要修改内核才能开启,在 OVZ 的机型上就无法使用。有此类需求可以购买 KVM 虚拟化的 VPS。母鸡内核可能没有开启某些功能,例如我在尝试通过 RClone 挂载虚拟硬盘时,被告知内核未开启 fuse,需要联系服务商开启(我没有去联系,不保证商家会同意开启)。 ]]>
+
+
+
+
+ 小鸡测评
+
+
+
+
+
+
+ 小鸡测评
+
+
+
+
+
+
+
+
+ Virmach 7.5刀年付特价机
+
+ /archives/6f963444.html
+
+ 虽然我在前文的结尾奉劝大家不要买太廉价的机器……
但是我一介学生是真的穷啊!
所以今天来谈一谈一家以性价比著称的 VPS 服务商:Virmach。
开门见山,先说直观感受:网络很一般、机器性能差。机器适合穷苦学生党使用。
我开的机型是 Virmach 黑五特价机型:年付7.5刀,1core 256Mb(免费升级至 384mb)10Gb-SSD 1Gbps。
这台机器在上个月到期,我又有了新欢就没有续费,因此还是没有跑分截图。
这机子的性能差到 apt 都嫌慢,网络情况也很是糟糕,应该就是最普通的国际线路 + 国内163,丢包率高,全天 SSH 连接皆不稳定。
不过一年50块左右的价格也没什么好抱怨的了,用来挂点需要访问世界互联网的服务,例如 Telegram Bot、RSSHub 等;或者建个站,套一层 Cloudflare CDN 之后众生平等,可以说勉强能用。
较小的内存有时也会带来不便,可能遇到部分应用、组件无法安装/编译的情况。据观察 RSSHub 运行时即占用 300Mb 左右内存,因此我还是建议内存在 512Mb 以上的 VPS,1Gb 为佳。
多提一嘴,现在不少应用都能以 PaaS 的形式部署在诸如 Heroku 的平台上,完全可以做到免费运行一个服务,没有必要租用 VPS、配置环境。
VPS 的硬盘也是一个挺重要的指标。按我的经验,VPS 至少要有 20G 的硬盘才够用。Debian 11 系统 + LNMP + NodeJS 环境就占用了 7-8G 的硬盘空间,剩下的 10G 可以用于存储应用数据。至于硬盘性能,只要不是太可怜,影响都不大。
]]>
+
+
+
+
+ 小鸡测评
+
+
+
+
+
+
+ 小鸡测评
+
+
+
+
+
+
+
+
+ 我的第一台 VPS
+
+ /archives/afe45e8a.html
+
+ 开一个新坑,测评一下所有我用过的小鸡,如果能赚到点 AFF 就更好了。
来谈一谈我的第一台 VPS 吧。
我的第一台 VPS 购于初中,仅用了一年便没有再续费了。因此没有任何性能、网络方面的测评,仅剩下我的感想了。没有续费的原因很简单:太™垃圾了。
年幼无知,对 VPS 性能、路由线路都一无所知的我被超低的价格蒙骗,入手了一台来自无名国人商家的小鸡。
印象中,那是一台 1core 512Mb 的小鸡,位于太平洋彼岸、再跨过美国全境的美东海岸。VPS 的网络差到经常连接不上 SSH。
说了这么多(并不多),就是想奉劝各位:别贪便宜。
]]>
+
+
+
+
+ 小鸡测评
+
+
+
+
+
+
+ 小鸡测评
+
+
+
+
+
+
+
+
+ 厌烦了软件的我上了硬件的车
+
+ /archives/245ab175.html
+
+ 好久不见,回想三个月前的我还在享受暑假。开学后,我将大部分精力转移至学业,折腾的时间便少了。
机缘巧合,在显卡价格最高的时候坏了显卡的我打算趁双十一打折买个焊台,自己修显卡。
同时,我有了玩一玩硬件的想法。
焊台体验 六七年以来,我一直在用直插 220V 不可调温的电烙铁焊接。当时,无线电老师告诉我这是质量很好的紫铜烙铁。就是这把烙铁陪着我入门了锡焊。
放在今天,这把烙铁加热慢,且烙铁头非常容易氧化,体验极差。由于加热效果不佳,我还弄坏了一台灵车无人机的主板和一个 IR 摄像头(都是掉焊盘了)。
新焊台则完美的解决了之前的痛点:新烙铁加热快、温度准确、刀头耐用。热风枪则开启了贴片元件焊接时代。
第一个项目 说在前头,选择一个比较复杂的项目来入门是个错误的决定。
十月中,我选择了一个加热台项目。对,就是可以用来焊接贴片元件的加热台。用工具制造工具,多美妙的事情啊~带着兴奋,我开始采购元件……
直至今天,这个项目的进度仍然停滞不前:元件全部焊接完毕(见下图),接通电源可以点亮一秒,随即就烧坏了一个电阻。初步判断是 5V 供电区域有短路,具体情况还需继续检测……
事实证明,第一个项目应该选择简单点的,以防止打击信心🌚
赶超第一个项目的第二个项目 转眼到了十一月,又可以在嘉立创愉快地免费打板了~
这次,我给自己设计了一张 PCB IC 校园卡,只需要焊接一个 CUID 芯片。
没有比这更加简单的项目了!
成品如图~
结语 前几天整了一台性能不错的服务器,小项目又可以整起来;
DIY PCB 这方面,我还想制作一个带编码器的小键盘,以提高 Premiere 剪辑的效率;
群星真好玩,铁心灭绝者真不错😇
真是丰富多彩的课余生活~
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ LOOPERS 简评
+
+ /archives/67d41c7c.html
+
+ 对 LOOPERS 的整体评价:B-
这一作挺特殊的,没有任何选择支,全程自动播放耗时仅8小时左右。在这样短小的作品里塞下一日无限循环这样富有可能性的世界观,大概是本次剧情质量不尽人意的主要原因。
中盘倒不觉得冗长,还可以继续细化,加深对人物的刻画;就是后期重复的内容与话语太多了,让想尽快看到结局的我着急。
音乐方面,我全程用电脑扬声器玩的。说实话,BGM 听得不是很清楚。待我细听之后再作出评价。女二真的很吵,有 SP 那头恶鬼的吵闹级别了。
制作方面,望月老师的画好看(哧溜)。
谈一谈一些细节上的感受(可能有点剧透):
作为已经退坑的老 Ingress 玩家,再度跟随主角一行人踏上寻宝之路,着实让人兴奋。游戏中着力刻画的,寻找到藏匿的宝物时的喜悦我能切身体会到。
这让我想起了两三年前,我为了拔掉敌方的一个大 菊花 Portal,在旧屋区狭窄的巷子里穿行。找了一两个小时,终于到达 Portal 的中心点,开始狂轰滥炸。当时的兴奋与满足是溢于言表的。或许你要亲身玩过寻宝游戏,才能体会主角一行人的感受吧。
对于“神经大条”的男主,我最大的感受就是这种人不大可能存在吧。遇到任何事情都能乐观面对、看似大大咧咧但做起事来注重细节、有着强大的领导力和鼓舞力……这种人若真的存在,绝对耀眼。
最后看作品的主题。在我看来, 龟 龙骑士这回倒不是想写一个很催泪的故事,而是继续宣扬 Key 社所推崇的友谊、团结和爱的主题。我个人是百看不厌,我也想要知心朋友,甚至是一群知心朋友。
男主全程口边挂着寻宝二字,听了耳朵确实起茧,但这文章放在考试说不定能拿挺高的分数——不断地扣紧中心。人生的意义?寻宝;友情的价值?寻宝;生命的救赎?还是寻宝。
这寻宝真有意思,只要将寻宝的过程与寻找到宝物稍作改变,就能解释这么多难以用语言表述的东西。倘若能在游戏性上再下点功夫,加个交互型的寻宝游戏,整体效果又会再上一层。但加了这些就不是一款短小精湛的游戏了,对吧。
最后,这款游戏值得推荐吗?对于没有接触过 Key 社作品的人,可以一玩。
对于 Key 社老玩家,对这部作品报以极高的期待,希望它能超过正作水准的,还是别玩了。
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ 2021 第二学期总结
+
+ /archives/202ebc76.html
+
+ 在去厦门的动车上,写一下这个学期的总结。
刚开始我想形容这个学期是浑浑噩噩的,感觉我想做、想学的东西都没做成;
但再冷静想一想,这学期在学校的安排下打了很多基础:解决了 C++ 这一心头大恨;还学习了数电,为硬件打了点基础。
C++ 实在很基础、很枯燥,若不是学校压着要学习,光凭我一人自学是难以解决的。以前也不是没有尝试过自学,最终都败下阵来。
至于硬件这块的知识,软件工程专业一般是不去接触的。学校大概是觉得下学期要学的 计算机组成原理 需要有数电的预备知识吧。
至于高数、线代,对编程越是了解,越会知道他们的重要性,知道他们贯穿着整个编程的过程。
这个学期最满意的还是全科通过,包括让我非常头疼的大学物理和高数。
下个学期我还是希望能腾出更多的时间给自己喜欢的事物,而不是把全身心都投进学校的课业,感觉亏待了自己的梦想……
生活方面,■■■■■■■■■,整体闷在学校里把我憋得难受。宿舍环境虽然不错,但整体偏老旧——六人间改四人间,■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
下个学期又可以搬回主校区,一定要把宿舍和工位书桌好好装修一下,过精致生活~
至于暑假的安排,挺杂乱的。
买了不少东西,从小到大,从 IR 摄像头模组到 3D 打印机,打算将折腾二字写满整个假期;
学习内容则是安排了 JavaScript 和 CTF 相关的内容,如果还有时间还想看看 React。CTF 没有人带,想要入门挺困难的……校队是不打算招新人么 :(
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ Osprey Commet 简评
+
+ /archives/43a7a15c.html
+
+ 我的威戈双肩包——没错,就是烂大街的那个牌子——已经用了四年多了。不得不承认她的质量之好,用了这么久只坏了包前侧的拉链(到裁缝店换了一条拉链),外加水壶网袋破了几个洞。在上个月,她的一条拉链绳(不知道怎么叫那个部件)的脱落让我有了更换通勤背包的念头。
为了挑选一款最称心的通勤背包,我先对前任威戈包进行了评估:
优点
缺点
背负系统拉垮,光是背电脑(1.4kg)就觉得非常沉,更别说偶尔的电脑+平板组合了(2.2kg);
没有太多存放小物件的仓位,所有的小东西(U盘、读卡器、耳机、线材 etc.)都放在包的前仓里,且前仓还是竖向开合,分类与取物皆不便;
包身不防水,但送防雨套。下雨时出门要提前戴套,生怕包内电子产品淋湿;
因此,我对下一款背包有如下要求:
自重轻;
分仓多且位置合理,能分类存放小物件;
包身最好能防水;
最好能同时收纳笔记本电脑和平板;
在挑选了不下30款背包后,我选择了这款 Osprey Commet,下面来简要点评一下。
全局 包身自重 0.85kg,容积 30L,在这个大小的包中算比较轻的了。包身全部防水,拉链虽然没有做密封处理,但有一些延伸出来的防水面料挡住拉链,可以防止进水。
分仓 这款包的分仓可以算是一个亮点。我简要说一下我对每个分仓的使用情况:
包的最前方是提手(颜值比较一般,偏向实用),因此第一个仓是小主仓。仓内有三个兜袋,用于存放我的卡包、证件、充电宝和 MP3,仓内还装着小记事本和钱包。此外,仓内还有一根红色的钥匙绳,采用的是快挂钩,可以把现有的钥匙串挂上去。
往里一层是第一个眼镜/小物品收纳仓。仓内使用了特殊面料防止刮花眼镜,在登山/骑行时存放眼镜还是很方便的。我就拿来存放经常取用的小物件了,例如耳机、U盘、Lto3.5mm 转接线等。
下一层是一号主仓,外加主仓内的第二个眼镜/小物品收纳仓。一号主仓有一个兜袋方便收纳平板和 A4 大小的文件,我便拿来放文件袋和平板。课本、笔盒也都放在这个仓位。小物品收纳仓则收纳比较少取放的小物件,例如订书机、凤尾夹和一些药品(达喜)。
最后一个仓位是笔记本仓。作为整个包最大的仓位,能轻松容纳 15.6 寸的笔记本。在需要携带笔记本时,这个仓位当然用来装笔记本;平常上课不带笔记本,也可以拿来装外套。这个仓位里还有一个网袋,我用来装线材(一根 0.2m CtoC、一根 MFi AtoL、一根 0.2m AtoB)。
背负系统 虽然没能用上 Osprey 专业的空景系统,但 Commet 的背负系统依旧优秀。Osprey 的背板偏硬,可以比较好地贴合后背,以减轻肩带对肩膀的压力。在日常上课背负几本书时,基本没有负重的感觉。若是同时背负笔记本+平板(3.6kg 左右),能感觉到背板压在后背上,分担了部分重量。
胸带与腰带这种登山包才有的配置出现在了这款通勤包上。在背负比较重的物品时系上腰带可以有效减少包身晃动。
总评 相当满意!
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ 近日败家:Chrombook Duet 与 小米手环6
+
+ /archives/4a562e35.html
+
+ 今天来分享一下最近败家的几样电子产品。
Chromebook 与 USI 触控笔 在大学课堂上发现很多人都持有一台 iPad 和 一支 Apple Pencil 在做着笔记,令我很是羡慕。
在咨询过“业内人士”,并对现有产品及我的钱包进行评估之后,我放弃了购买 iPad 及其昂贵的配件的念头,选择拥抱 Chromebook。
为了追求极致的性价比,我选择了这款 联想 Chromebook Dute 加上 联想 USI 触控笔,1730 + 330 合计 2060 元。
购买 我是在亚马逊海外购下单的,货从美国仓库发出。平板花费了8天来到了我的手上,而触控笔因为海关查验,比平板迟了两天。跨国快递的速度比我想象的要快不少。
设计与体验 Duet,意为合二为一。CB Duet 的设计理念和 Surface Book 一致——打造一台既可以当作平板又可以当作电脑使用的设备。
CB Duet 附赠了磁吸键盘与可以作为支架的保护套,这一点比 iPad 高不少。
但是,键盘与触控板的体验实在是一言难尽。
键盘在不平整的环境下偶尔会出现多次触发(按一次键打出两次字母)的情况,不过放在平整桌面就不会了。键程尚可,但按键布局一般——为了在10寸大小的区域容纳全部按键,联想选择了将键盘右侧的按键(大部分是符号键)缩小,这让打出正确的符号变得十分困难。
触控板的体验更加一言难尽。手感很差,似乎没做过亲肤处理,滑动阻力非常大;定位也很不准确。但 CB Duet 有一块10寸的触摸屏,为何不使用触控操作呢?
总而言之,CB Duet 附赠的键盘属于“能用”的级别,就像现在的我敲着博客——做一点文字工作完全没问题。Coding ?别想了,我替你试过了,如果只是简单改几行代码,比如 Caddy 的配置文件,完全没问题;如果想跑完整的开发环境,性能可能不够;如果使用 code-server……也不是不行,但10寸的屏幕实在不能施展浑身解数啊。
续航 部分比较“卷”的朋友可能比较担心这个问题,是我先倒下还是 CB Duet 先倒下?
根据我的实际体验——当然这可能很不准确,你大概率撑不过 CB Duet。
实际测试下来,一个上午,四节课下来,大概使用 15%-20% 电量。
ARM 的超低功耗让 CB Duet 有官方标称的将近 11 小时的续航;如果你像我一样调低屏幕亮度,并只使用笔记软件,系统提示的理论续航甚至达到 20 小时;倘若是高强度使用,例如使用 ssh、打开 Linux 虚拟机等,续航则在 6-8 小时左右。
性能 翻到上面再看一眼价格,还要谈什么性能吗!
本人不喜欢的 MTK P60T,8c 2.0Ghz。
不过实际体验来看,这颗小芯片的性能完全能喂饱 ChromeOS 和它的安卓容器,至于 Linux ,只要不跑 GUI 应用(Linux 容器暂无图形硬件加速)也没问题。
听说联想计划推出搭载高通骁龙 7c 的同款 CB ,性能有略微升级,如果价格依旧能持平,那会是更好的选择。
系统与体验 ChromeOS 的操作需要适应一段时间。
你应该要改变对应用的认知——减少安装实体应用,更多地拥抱 PWA ,可以让这个专门为 Chrome 优化的操作系统发挥全部实力。
安卓容器的性能真的很棒,可能是安卓和 ARM 相性较好的缘故吧,安卓应用在 CB Duet 上表现极佳,我觉得和一台安卓平板几乎没有差异。这台安卓平板却还能跑完整的 Chrome 浏览器和 Linux 容器。
系统的其他体验可以在网络上看看其他人的博客。如果我在后续体验中有什么心得体会,会考虑写成博客。
还有触控笔 联想这只 USI 触控笔是亚马逊上最便宜的,没有侧边按键、没有笔擦。触控笔没有太多要说的,昨晚用它写了一份作业,体验良好。
使用一周之后,偶尔会出现笔凌空识别的情况,无法测试出是硬件还是软件的问题,略微影响使用体验。
小米手环6 这是我第一次体验手环产品,之前戴的都是智能/半智能手表。
小米手环6的屏幕在手环产品中是比较大的,且能自定义壁纸,这是它最吸引我的地方。
至于续航,的确是硬伤。在打开除了全天心率监测以外的所有监测项目,心率检测频率设为1分钟一次后,手环一天消耗 20% 左右电量。
总体体验还算满意的。
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ 群晖搭建 VSCode 服务器与 Syncthing 服务
+
+ /archives/375e7789.html
+
+ 这次我尝试在群晖上搭建 VSCode 服务器与 Syncthing 服务,实现电脑与 NAS 间的代码同步与网页中的 Coding。
VSCode 网页版的实现 偶遇服务器软件 刷 RSS 时我看到 V2EX 上一个帖子分享了一个实用工具:github+1s
这个项目可以实现在 网页版 VSCode 中打开 GitHhub 上的代码。
这个项目使用的 code-server 引起了我的兴趣。
code-server 的部署 群晖自带的 Docker 套件简化了部署的过程。
在注册表中搜索 code-server 下载 image;
打开 image 进行配置:
使用高权限执行容器 在 高级设置-环境
页面中添加环境变量 PASSWORD
,值设定为你的登陆密码(由于在 Docker 页面中以明文保存,请注意密码安全)。 启动容器,并使用 Docker 内置的 终端机
打开一个新的 bash
。换源、更新 apt 、安装常用软件我就不再赘述。
code-server 的外网访问 code-server 没有自带 HTTPS 相关的配置,需要使用网站服务器进行反向代理。
目前比较流行的有 Caddy 和 NGINX 两款。
鉴于 Caddy 配置简单且 HTTPS 优先,我这次使用 Caddy。
Caddy 官方安装文档
或使用一键安装脚本
curl https://getcaddy.com | bash -s personal
如果有开放的 443 端口,则可使用 Caddy 的自动 HTTPS 功能进行快速配置。
若像我一样在家中的 NAS 上配置 code-server,则需要自己申请 tls 证书 (如 Let`s Encrypt),并按照 Caddy-tls 配置。
反向代理配置可参考 code-server 官方的反代配置教程 。
一些疑难杂症 一些插件无法安装 目前 code-server 的 VSCode 版本为 1.51.1, VSCode 官方则为 1.54.3 ,因此某些较新的插件可能无法使用。
可以前往 VS插件市场 下载旧版插件并手动安装。
Docker 内挂载的目录无写权限 使用 sudo chmod 777 ./
给 coder 用户赋予读写权力。
Docker 内 Caddy 无法自启 这个我也还没有解决。暂且手动启动。
code-server 的各种性能问题 等待更多的更新吧,我接下来会尝试在 Docker 里编译原版 VSCode 并开启 Web 模式,对比二者性能。
Syncthing 服务搭建 Syncthing 官网 已经给出了十分详尽的安装教程,也有群晖的安装包,我就不再赘述安装过程。
Syncthing 的管理页面端口为 8384
,若想在外网访问请使用 HTTPS。可以使用群晖内置的反向代理服务器进行反代。
要注意把 22000
端口的 TCP
与 UDP
全部开放,才可在外网顺利与 NAS 同步。
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+ 校色初体验 / 寒假总结
+
+ /archives/2c0bfb1a.html
+
+ 好久不见,甚是想念。今天来分享一下初次校色的体验以及寒假我都干了些什么。
校色初体验 为什么要校色 我的笔记本电脑型号是 联想小新 Air 14 2020,拥有一块 14寸 100% sRGB 色域(实际为 94% sRGB)的屏幕,面板型号是 友达 B140HAN06.8。很幸运,不仅抽中了三星 SSD,还抽中了友达的屏幕。
为了更舒服地阅读代码,我又淘了一台 17.3寸 的 DIY 便携显示器,同样也是 100% sRGB 色域(实际为 95% sRGB),面板型号是 友达 B173han01.1。
由于便携屏的驱动板都是通用的,并没有对某块面板有调教,也不可能有屏幕出厂时的调教,因此这块便携屏的色差非常明显。就校色结果来看,光度 (gamma) 值就有 70% 左右的偏差。
通过校色,两块屏幕色彩更加接近,且更接近真实的颜色,看起来也会更舒服一些。
关于价格 我想很多人和我有同样的顾虑,感觉租用校色仪价格不菲。
这次租用时长为 3天,我仅使用了一天半就归还了。总共支出为 校色仪租金 40元 + 回程运费 18元。押金原本是 750元,和店家商量后爽快地降到了 500元。
虽然商家划定了许多可能导致押金被扣的规则,但只要你小心一点、爱护一点,完璧归赵、取回押金是很简单的。
色温选择 刚开始我选择的目标色温是 6500K,在校色后发现偏黄许多。最后我选择的目标色温是 7500K。当然,两块屏幕 6500K 色温的校色文件我都保存下来了。
最终校色效果 按照红蜘蛛给出的报告,联想笔记本的这块屏幕在色准上要优于便携屏,白点与灰度的 △E <= 0.2 ,便携屏则是 △E <= 0.5。
联想这块屏出厂也比较准,值得表扬~
下面是校色完的合影:
寒假我都干了啥 列举一下:
学习(重温)了 C语言; 学习了 PHP 基础; 学习了 Burp Suite 基础; 学习了 After Effect; 学习了 Audition 基本操作; 完成了一个 5分钟 的视频项目,比较灵活地运用了 PS Pr AE 及 Au; 当了两个星期的补习老师,挣来的钱买了 飞利浦 X2HR HiFi 耳机和一块 2242 SSD; 学习了 Solidworks 基本操作(这两天学的); 感觉还是做了不少事的。
马上要开学了,下学期总算能学些我想学的了。
总之,好好干吧!
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ 2020 年终总结
+
+ /archives/cd7dffda.html
+
+ 2020 马上就要过去了,我想做一个年终总结。
非常抱歉鸽了博客四个月。
其实十月我忙中偷闲写了一篇文章打算发上来,却被 Hexo 吞了,我也没了重写的热情,就这么让它消失了。
2020 我做了什么 ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
2021 我打算做什么 First, 我打算加入学校的 CTF 战队。自学 CTF 的过程中一定会遇到不少有趣的事情,我会抽空写作博文与大家分享的。
Second, 我想和同学合作开发一些有趣的小项目乃至小软件,点子已经有几个存在脑子里了。
Then, 我打算磨练一下业务能力,而不是简单地编写脚本一样存在的代码,CSS 也不能写得歪歪扭扭的了(捂脸)。
在一切都回归正轨之后,我也会跟上各位的步伐,继续探寻有趣的项目,还请大家多多期待!
结语 2020 发生了太多糟糕的事情,祈愿 2021 能遇见更多美好的事情。
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ 逃离国产软件 - 虚拟机计划
+
+ /archives/a455b52c.html
+
+ 使用 Hyper-V 运行 Windows LTSC 虚拟机,以隔离流氓的国产软件们。
为何大费周章? 我试过网络上的不少方法来隔离鹅厂的软件 —— 刚开源的 Sandboxie 也好,利用 Windows ACL 机制通过低权账户加以限制也好 —— 都因为权限问题失败了。最后决定用虚拟环境直接隔离这些软件。
为什么是 Hyper-V 和 LTSC? 我用过 VMWare,觉得还是 Windows 原生的 Hyper-V 启动最快。还不用钱。
Windows LTSC 是企业定制版,官方精简了系统,性能开销更少。
事前准备 拥有 16G 以上内存及 NVME 高速硬盘的用户可以放心采用该方案,虚拟机运行时不会影响其他软件的流畅运行。
下载 MSDN 上的 Windows LTSC: 侧边栏选择 操作系统 ;选择 Windows 10 Enterprise LTSC 2019 。
安装 Hyper-V: 对于 Windows 专业版 用户,在 控制面板 - 程序与功能 - 启动或关闭Windows功能 中找到 Hyper-V 并打开。
对于 其他版本 Windows 的用户,则稍微有些麻烦:
在记事本中输入如下代码 1 2 3 4 5 6 7 8 9 pushd "%~dp0" dir /b %SystemRoot%\servicing\Packages\*Hyper-V*.mum >hyper-v.txtfor /f %%i in ('findstr /i . hyper-v.txt 2^>nul' ) do dism /online /norestart /add-package:"%SystemRoot%\servicing\Packages\%%i" del hyper-v.txt Dism /online /enable-feature /featurename:Microsoft-Hyper-V-All /LimitAccess /ALL
把文件保存为Hyper-V.cmd 右键该文件,选择 以管理员身份运行 根据提示完成安装。
摘自知乎 没人不认识我 的回答
安装虚拟机 打开 Hyper-V ,选择 新建 - 虚拟机 ;
根据向导提示设置虚拟机,选择 第一代虚拟机 ;
内存分配我选了 2G (共16G);CPU 分配我选了 4线程 (共12线程)【需要完成配置后在 设置 中更改】;
其余设置默认或自定;
安装选项选择 从 CD/DVD-ROM 安装操作系统 ,选择刚刚下载好的 Windows LTSC ISO镜像;
完成配置后,启动虚拟机,安装 Windows LTSC 到硬盘。
配置环境 装好系统后要干什么不用我说了吧。
把垃圾们倒进去就好啦。
实测空载消耗 CPU 算力在 0%-3% 浮动;内存占用 2.2G,实际使用 1.2G 。
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+ 软件
+
+
+
+
+
+
+
+
+ 提升音乐体验-本地音乐标签/歌词匹配与回放增益
+
+ /archives/6b40e5ad.html
+
+ 推荐两款能让听歌体验变得更好的软件 —— Music Tag / Foobar2000 。
附带使用教程。
音乐标签 Music Tag 官方网站/下载地址 https://www.cnblogs.com/vinlxc/p/11347744.html
软件特点 古人云,专辑封面是一首歌的灵魂。(我乱说的)
Music Tag 是一款可以自动匹配本地音乐的标签与歌词的软件。
一键从多家音乐网站拉取元数据/封面图/歌词,不能再爽了。
使用教程/建议 导入一批歌曲后,选择 自动匹配标签 :(如下图)
然后按下图配置,在原有元数据下添加更多信息:
在第一轮匹配后,建议再进行第二轮封面图片匹配,并覆盖原图片,配置如下图:
接着,就需要你耐心地查看每首歌的元数据(善用方向键),检查是否有匹配错误的歌曲,并在 标签源-组合标签 处手动搜索,选择正确的元数据,如下图所示:
建议检查一下歌曲的歌词,特别是较小众的歌曲。Music Tag 的歌词搜索错误率较高。
如下图所示,选择并查看歌词,若有误可以手动搜索:
最后选择导出 LRC 歌词:
所有歌曲的 元数据-封面图-歌词 就此已匹配完毕。
最终效果如下:
Foobar2000 官方网站 http://www.foobar2000.org/
回放增益介绍 维基百科-回放增益
回放增益可以使音量大小各不相同的音乐向统一标准靠齐。
将所有音乐扫描并打上回放增益 tag 后,再也不用担心下一首歌震破耳膜了。
使用教程 导入并全选歌曲,右键,选择 ReplayGain :
下列三种扫描方式均可。个人喜欢将全部歌曲的音量统一,故选择第一种:
待扫描结束后,点击 Update File Tags ,将回放增益数据写入文件 Tag :
回放增益扫描完毕。
小结 我是一周不往曲库里添加新曲就受不了的类型。
这几天往曲库里添加曲子的时候查阅了这些能提升音乐体验的方法,希望能帮到你。
]]>
+
+
+
+
+ 软件推荐
+
+
+
+
+
+
+ 教程
+
+ 软件
+
+
+
+
+
+
+
+
+ 博客迁移-主题更换与近况报告
+
+ /archives/911a91db.html
+
+ 又好久不见。
这两天抽空更换了博客的主题,并迁移至永久域名 ■■■■■■■■■■■ 。
以及谈一谈新开的项目的设计思路,有兴趣的读者还请慢慢阅读。
博客变更 更换域名 ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
更换 Hexo 主题 主题的名字叫 Fluid ,是一款 Material Design 风格的主题。
之前我用的主题是 Yilia ,但作者弃坑已久,感觉已跟不上时代变化,就下定决心换了(还把手上所有猫羽雫图全部塞进去了)。
同时,我还将评论系统 Gitalk 更换为 utteranc ,对于国内用户加载速度应该会更快,同样需要 GitHub 登录。
近况报告 快要高考了好忙啊,完全没有时间研究技术了,也到了应该要努力的时候了(笑)。
超级长的寒假里我还是有学习新知识的(指计算机):
成绩查询网站 开了一个坑:构建一个成绩查询网站以代替学校那套繁琐的校务系统。
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
项目的前端用了 jQuery、Bootstrap 等框架;
后端则是采用我比较熟悉的 Python 后端框架 Django 。
校园官网查询成绩的流程如下:
graph TB校园网主页--输入账号密码 选择身份-->校园网内网--找到侧边栏的按钮-->成绩查询页面--选择年份 学期 考试场次-->得到成绩
如何取代学校的系统呢?思路很简单:黑掉学校的服务器直接把成绩取出来
模拟登录就好了。
学校的破烂网站采用的是最简单的 GET 表单登录,可以轻松获取 Cookie ,方便进一步操作;
我将查询步骤简化为:
graph TB查询页面--输入账号密码-->下一步1--选择年份 学期-->下一步2--选择考试场次-->获得成绩
看起来没简化多少,其实 选择身份 和 找到侧边栏按钮 就已经足够烦人了。
校园网采用了大量下拉框选择,我将其替换为按钮选择,甚至不用选择,一定程度上提高了查询效率。
除此之外,我也写了另一个 API 负责查询某成绩查询 APP 上的成绩。
我认为这个 API 贡献较校网查询应该更大,这让我摆脱了手机 APP 查询这一烦人的设定。
然而开发过程中,后者所花费的力气远小于前者,大概是校网建设得太差的缘故吧。
除此之外,我还尝试将 API 封装进 Docker,使部署更加简单、快捷。
Docker 的使用非常简单,若有兴趣还请多多尝试。
果然造轮子学习比干看教程效果要好啊!
尾声 没有尾声 : )
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ BGP初体验-Linux,Openwrt与Quagga
+
+ /archives/e3c95af8.html
+
+ 好久没写博客了!今天抽出点时间分享一下我的 BGP 初体验。
这一切的一切都要从一个叫鹤伞 Ria 的 Vtuber 说起……
环境配置 操作环境 Debian 系 Linux(其实是 Kali):
sudo apt update & sudo apt install quagga
Openwrt:
opkg update & opkg install quagga quagga-zebra quagga-bgpd quagga-vtysh
网络配置 采用 Zerotier 建立内网环境模拟真实网络。
Zerotier 的安装及配置不再赘述,官网有详尽教程。
网络环境 本来想做三台设备两个 AS 间的通讯,有一台设备无法安装任何 BGP 软件也就作罢;也没有画拓扑图的必要了(悲)。
AS114514 Debian 10.0.1.1,命名为 R1,享有 10.0.1.0/24 网段;
AS1919810 Openwrt 10.0.2.1,命名为 R2,享有 10.0.2.0/24 网段。
Quagga配置 下面才是重头戏。Quagga 的配置文件位于 /etc/quagga/
据测试,Openwrt 安装 quagga 后会带有初始配置,而 Debian 不带初始配置,可自行创建。
/etc/quagga/zebra.conf
配置(可不用修改)
1 2 3 4 5 6 7 8 ! 登陆密码password zebra !access -list vty permit 127.0 .0 .0 /8 access -list vty deny any !line vty access -class vty
/etc/quagga/bgpd.conf
配置(需要根据情境修改)
1 2 3 4 5 6 7 8 9 10 11 ! 密码 password zebra! AS号 router bgp 114514! 本机公网(VPN网络)ip bgp router-id 10.0.1.1! 本路由享有网段(需要交换的网段) network 10.0.1.0/24! peer信息(建立连接的机器的公网(VPN网络)ip,AS及称呼) neighbor 10.0.2.1 remote-as 1919810 neighbor 10.0.2.1 description R2
另一台机器的配置只需依葫芦画瓢,我就不再赘述。
配置之后,运行
/etc/init.d/zebra restart
/etc/init.d/bgpd restart
(Debian)
或者
/etc/init.d/quagga restart
(Openwrt)
重启 quagga 服务。
欣赏结果 忙活了这么久,终于能看到结果了!
运行
vtysh
进入 quagga 控制台(指令模拟 Cisco,这块不大了解)
输入
show ip bgp neighbor
就会看到
1 2 3 BGP neighbor is 10.0 .2 .1 , remote AS 1919810 , local AS 114514 , external link Description: R2 BGP version 4 , remote router ID 10.0 .2 .1
还有这么一张表
Sent RcvdOpens: 2 0Notifications: 0 0Updates: 2 2Keepalives: 1050 1049Route Refresh: 0 0Capability: 0 0Total: 1054 1051
再看看路由器内的活动ipv4路由表
网络 对象 IPv4 网关 跃点数 表 xxx 10.0.2.0/24 10.0.2.1 20 main
就算大功告成了!
有什么用处呢? 没有。
内网测试唯一能享受的就是看着这条无形的链接,想象自己也是网络工程师。
但是还是感觉很爽!
而且你已经学会(大概)了 BGP,获取全球路由表也能办到了!
参考 Ria 的爸爸(我的岳父)的文章
使用bird配置bgp网络互连
至于这一切与 Ria 有啥关系?欢迎关注 Ria 了解详情(滑稽)
Telegram群组 bilibli Youtube
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+ Joplin+Webdav同步问题的解决方案
+
+ /archives/9d1c6fa4.html
+
+ 问题内容 在群晖上搭建了 Webdav 服务器,使用 Joplin 连接后无法同步笔记。
解决方案 错误示范: ‘ https://your.domain.com:[your port] ‘
以此路径访问的是群晖文件系统的 /
目录,由于没有权限读写,同步失败。
正确示范: 在群晖控制面板内新建一个名为 Joplin
的共享文件夹。以域名:
‘ https://your.domain.com:[your port] /Joplin’
访问即可同步。
自定义配置: 若想在已经存在的文件夹下同步 Joplin 笔记,按照正确示范所书写的 URL 书写路径即可在想要的地方同步。
解决方案的探索 (我并未了解过 Webdav 的原理) 遇到此问题时,我在上 Google 查找解决方法前试着自行分析。思考原因后我选择了抓包分析。
结合抓包结果和 Joplin 同步日志可以看到 Joplin 在 /
目录下查找了 .lock
等文件。结合其他 Webdav 软件可以看到访问的 URL 指向的是目标文件夹,即“域名:端口/目标文件夹”,由此推测需要在配置内为 Joplin 指明同步目录,否则将在没有权限的根目录下同步,导致失败。
这是一次没有什么技术含量但能启发我的尝试。若你也遇到了类似的问题,希望也能启发到你。
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+ 莱卡依然想要回家
+
+ /archives/c7a0a8db.html
+
+ 有幸在/home/rynco/music Channel 遇见这张专辑。第一次听,或许是风格合口,我立刻爱上了这首名为“Laika Still Wants Go Home”,与专辑同名的轻音乐。 很有力量。这是这张专辑给我的第一印象。 我几乎没有乐理知识,对于音乐的评论总是如此无力。这首歌从旋律看也好,从节奏看也好,感觉整体把控得很恰当,张弛有度。我贫瘠的语言真的无法形容那种美的感觉。
我刷新了对这张专辑的认识,已是数十分钟后。我在听新曲时总会翻翻网易云音乐的评论区,有数条评论对专辑封面的解释。 我第一次听说太空犬这个词是在Littile Buster中,能美·库特莉亚芙卡的名字的由来。库特莉亚芙卡是一头太空犬的本名。 它常被我们称作:莱卡。
鉴于很少人知道这个故事,我就讲讲吧。 当时正逢美苏争霸,太空竞赛也是美苏交锋的重点。当时的苏联宇航技术不是很发达,但想要胜过美国就要先把航天员送上天。总不能让宇航员乘着从未试验过的飞船就这么上去吧,苏联的科学家就想用动物代替人类完成测试。科学家们在街上找来了几条流浪狗——因为他们觉得流浪狗比家犬够能受冻——这几条狗就被钦定为太空犬了。训练的艰苦不必多说,看看训练人类就能明白,何况是动物。几个月的训练之后,莱卡脱颖而出。到了发射前几天,一位科学家把莱卡带回了家里,让它和孩子们玩耍,因为他们很清楚,这是一次不含回程票的旅行。 苏联当时并未掌握从地球轨道重返地面的技术。 不知道当时在场的所有人的心情是怎样的。应该是兴奋的,毕竟超越老美了;又有点担心,因为即将发射的飞船是赫鲁晓夫下令两周内造出来的;或许有的人会有些不舍吧,他们将要亲手送走一条鲜活的生命。 很可惜,莱卡并不是在氧气或食物耗尽前被安乐死的。飞船的温控系统因赶工与设计问题出现故障。即使风扇再怎样转,舱内俨然成为一个火炉。用好听的话说莱卡是中暑而死;难听点,活活热死。仅管死亡是不可避免的,我也希望它能少一点痛苦啊。
我想到了安德。在指挥完舰队完成模拟战斗后,在他得知他亲手消灭了虫族的舰队时,他崩溃了。他回忆起刚才他指挥数个小队作为诱饵白白牺牲。他崩溃了。我想当时,我也希望当时,火箭点火升空之时,二级火箭脱离失败之时,舱内温度急剧升高之时,有人能为这条生命感到一丝绝望。
这张专辑的风格与“OPUS-灵魂之桥”的 OST 有异曲同工之妙。(独立工作室的游戏,OST 不方便放出,希望大家能一起购买。游戏也很棒啊!)都给我以一种末世感。究竟是世界对人绝望还是人对世界绝望呢?
想象你站在专辑封面的那一点上,眺望地球,这该是多么孤独与悲伤。有评论说载着莱卡的火箭现在仍绕着地球旋转,但很可惜,那台火箭已经在大气层燃尽,这也算是最高规格的葬礼了吧。
再说说专辑的名称吧。“Laika Still Wants Go Home”,英文,是没有一点问题的。我的母语是中文,因此英文的名称对我无感。但当我要将它翻译成中文,那一刻,我真的愣住了。我脑袋里已经将这几个单词转化为了中文,再一次理解了它们的意思。我僵住了。就几个字停留在嘴边就是说不出来。说不出来……在对面的人疑惑不解我为何停住,思考是否因为自己没有在认真听导致我生气时,一滴眼泪从我疲惫的左眼划过。立刻调动理智。情绪再次平稳,我又调回了内存里的那几个字,念了出来……
后记
从听到这首歌到构思这篇文章再到陆陆续续写出来,我花了数天。因为事情真的很多,脑内的一些想法被琐事代换了。 文字很凌乱,几乎是想到什么写什么。因此我用分割线分开了。 若有新想法我会继续补充的。
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ GOROGOA-精华,你的成果如何
+
+ /archives/ff412ce9.html
+
+ 说在前面 我是怀着激动的心情来写这篇文章的。 我如何定义一部作品是否优秀?很简单。它能达到它的目的,它和优秀沾上边了。就像搞笑视频逗笑你,治愈视频使人放松,致郁视频让你流泪。 而这是一部富有哲理的优秀作品。
预备知识 什么是 GOROGOA GOROGOA
中文译名 画中世界 一款解谜类游戏。我还没能抽出时间拜读一下这个故事。
什么是Voiceroid、Voiceroid剧场 Voiceroid 是 Vocaloid 的姐妹产品。一个负责朗读,一个负责唱歌。 Voiceroid 剧场是利用 Voiceroid 朗读故事,制作成的动画剧场。
亮点 出场人物
第一章第一节出现的标题
这是一部挺长的剧场动画,好像是12集,每集20分钟,若是翻译组周更那就是一部番剧了。 由于故事长,本次出演人数也不少。 以下是出场人物京町精华(京町セイカ)、东北切蒲英(東北きりたん)、绁星灯(紲星あかり)、结月缘(結月 ゆかり)、琴叶茜、葵(琴葉 茜・葵)、东北俊子(東北 ずん子),IA等为配角配音。 出场人物基本包括了所有 Voiceriod. 以精华为主人公的作品还是第一次见,绁星灯作为女二号,立绘很好看~
精华、俊子和俊子的母亲(配角均为黑脸)
作品特色 本作特色之一是制作良心。精华和灯的立绘说话时嘴巴有动作,虽不是 Live2D,却不失动画感。用于一个故事性强的剧场效果反而比吸睛的 Live2D要好。
总是板着脸的精华
第二个亮点就是故事了。GOROGOA 本是一款解谜游戏,听说通关时长也不是很长。作者以游戏为背景设定,巧妙的建立起一个故事框架,用扑朔迷离的剧情吸引了我。
观后之感 震撼 理由大致有两点。 其一,作者精心设计的剧情和出乎意料的表现方式让我深感佩服。或许冷静下来看这个故事并不能与那些真正的大作匹敌,但一部能令人深思的剧场已是少见,更应得到关注。 其二,作者敢于直面社会/生活问题,直叙心中之言。能借 Voiceroid 之口说出自己的心声也是我梦想已久的。
小茜在精华的床上听她讲故事
若是做一些通俗的赏析。作者选取的立绘真的很好看啊( ̄▽ ̄)。其中谈到的一些问题也深有感触,能引起我的思考。
尾声 很期待接下来的剧情走向! 我将我喜爱的作品推荐给你们,希望你们能喜欢!
附观看地址(已完结)
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ iPv6下绝佳的DDNS方法-dynv6
+
+ /archives/27f2d840.html
+
+ 下面我将介绍一种适用于 IPv6-DDNS 的绝佳方法!
通过 IPv6 访问的优点 没有端口限制!!!可以通过 80/443 访问 web 服务器了!不用带着烦人的端口号!
每台设备有独立的 IPv6,配置更加方便,无需路由器充当网关进行端口转发!
IPv6 也有不足之处 最严重的问题:很多家宽并没有开启 IPv6 的获取。但访问 IPv6 的设备必须要拥有 IPv6 地址!
技术问题,难以解决。一般来说手机的移动网络都已分发 IPv6 地址。
想要家宽拥有 IPv6?或许你需要修改光猫设置(超级管理),亦或是将光猫改为桥接模式,用路由器拨号从而获取 IPv6 地址。
如何实现iPv6-DDNS 这点尤其重要!我在网络上寻寻觅觅无数脚本,总是失败。最后才发现官方有提供脚本也!一试马上就成功了。
1 token = *your token * ./dynv6.sh *your DDNS domain*
可以将其添加到 crontab 一类的软件内,规定时间自动执行脚本(每 10分钟一次为宜) 大功告成!
事后 你可以把自己的域名 CNAME 过去 也可以用 dynv6 提供的域名 随心所欲!
Over.
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+ 搭建Calibre-Web电子书网页端管理服务
+
+ /archives/a4c81e8f.html
+
+ 说在前面 Kindle 是一样阅读利器,但若是没有一个强大的书库,它也只能用来压泡面(笑)
今天我们利用 Docker 在群晖(任意系统)上搭建 Calibre-web 服务
docker选择 technosoft2000-calibre-web
我在尝试了 3 款 Docker Image 后,决定使用这款。
优点 Calibre-web 基于 Python,性能高(低)
与 Calibre 软件的数据库等文件完全互通
支持推送至 Kindle
支持在线转码书籍(虽然问题较多)
technosoft2000-calibre-web 安装 首先,将你的 Calibre 数据库的位置挂载到 Docker 内的 /books 。
启动 Docker 后不要着急,等待 Docker 内软件安装完毕后,访问您设置的端口,访问 Calibre-Web。
在 Calibre 数据库位置 一栏填写
特性配置 可以依情况而变,不一定要按我的配置。
若想要使用在线转码功能,外部二进制 一栏中,选择 使用 calibre 的电子书转换器 。 转换工具路径 按图片中填写。
1 /opt /calibre/ebook-convert
点击 提交 即可完成安装。
注意
若提示无法读取数据库,尝试将数据库所处的文件夹的权限改为755 。
如何操作?请打开 SSH,在 Terminal 内操作。
若还是失败,尝试在本地 Calibre 软件新建一个书库,将空的 数据库文件移动到你挂载的目录下。
安装界面示意图
尾声 安装后的操作我就不多提了。该上传书籍的上传,该push的push。
我的几个建议:
推送邮箱推荐使用 Outlook,限制少。 可以用 Calibre 软件管理数据库。不知道为什么,在本地做好更改后,网页版并没有任何变化。我试着备份又还原了数据库后,网页里再重新加载数据库才成功了。 网页转码会遇到种种问题,例如电子书有加密。转码失败实属正常。 ]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+ 秒知APP消息发送教程
+
+ /archives/8ca2a7b3.html
+
+ 这是秒知APP的主界面
点击+发消息
打开消息发送界面
选择发送消息类型 这里我选择了秒知 大屏幕广播
点击发送到
可以选择发送的范围(发送大屏幕消息可以任意选择,但必须选择)
在已发
中可以管理已经发送的消息
]]>
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ 对于“智慧校园”与人脸识别的一些看法
+
+ /archives/330720fc.html
+
+ 当我看到一块块电子班牌,一台台人脸扫描机器立林于校园,我的心中五味杂陈。
我自认为我的信息技术水平能排在全校同学的平均水平之上。我管中窥豹这个互联网,那些所谓“绝对安全” 的技术事实上”漏洞百出“—— 例如我们每天使用的HTTPS,也就是 SSL/TLS 协议,传输过程全程加密,看似无懈可击。可黑客只要置身于你的局域网内,想要脱去加密的外壳轻而易举,他可以盗取你的任何信息。我举这个例子是想告诉你:互联网没有安全一说 。
人的生物特征向来被视为重要隐私 。从乐视手机超声波指纹遭到破解,到苹果涉嫌私自使用用户声纹信息被起诉再到前两天发生的案件:
【17万人脸数据遭公开售卖:商家从事人工智能工作 】 据北京青年报报道,在某网络商城中有商家公开售卖17万条“人脸数据”,每张照片搭配有一份数据文件,除了人脸位置的信息外,还有人脸的106处关键点。商家称,其售卖的人脸样本中,一部分是从搜索引擎上抓取的,另一部分来自境外一家软件公司的数据库,并表示,自己平时从事人工智能的相关工作,因此收集了很多人脸数据,发售出来“也就是挣个饭钱”。
无一不在说明人们对生物特征的重视。而这些数据一旦泄露,就好比你的身份证在你不知道时丢失,并可以被人任意使用。
诚然,人脸识别是一项快速,便捷的技术,可以简化认证步骤,省去携带认证工具的麻烦。但是,这么一项连阿里巴巴(支付宝)这类走在科技前沿的企业都在小心试探地使用(刷脸支付灰度测试),在校园内强制普及人脸识别系统,是否操之过急了呢?
下面我试着分析校园内部署生物特征识别可能遇到的安全问题:
学校官网,临时搭建用于特殊用途(如选课)等网站,经过专业分析软件(Nessus)的分析后表明存在超40个XSS或SQL注入漏洞 ,攻击者可以利用这些漏洞获取服务器管理权 ,盗取数据库内数据 。
目前人脸识别市场鱼龙混杂,诸多著名公司都曾爆出过丑闻(Face++),若将数据托关于其他公司,无法保证数据的安全性 。
因此,我提出以下意见:
允许不愿意使用生物特征识别的学生仍使用校园卡作为身份证明。(参考 IPv4,IPv6 普及阶段采用的双栈协议)
公开人脸识别等“智慧校园”的部署计划(如实现方法,合作公司等),以便由学生进行监督,消除学生心里对于信息安全问题产生的不安。
更换校园官网框架(现使用框架发布于 2006 年,早已过时),使用如 Wordpress 等付费,实时更新的网站框架,以及时消除潜在漏洞。并配置 SSL3.0/TLS1.3 等通讯协议以保证客户端与服务器间的安全通讯。
将人脸数据等生物特征数据储存于本地数据库,在内网进行传输。若需要与其他公司合作,请协商并采用更加安全,且目前公认难以破解的传输协议,如 VPN(虚拟专用网)。
互联网实是危机四伏,拥有警觉之心并不是坏事。希望对此有想法的老师同学们大胆发言,为学校建设建言献策,也感谢老师们为我们多彩的校园生活付出的一切!
]]>
+
+
+
+
+ 随笔
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+ 群晖-外网访问一站式教程2 DDNS
+
+ /archives/87bacf3f.html
+
+ 简述配置 DDNS 的方法。
什么是DDNS 维基百科-动态DNS 鉴于 IPv4 地址的枯竭,运营商开始给家用宽带分配动态 IP 地址,即 IP 会随时间或重新拨号而改变。DDNS 可以允许用户通过API动态地将变化的 IP 地址传送给域名解析商,达到域名外网访问的效果。 由于带宽分配原因,家用宽带的上传带宽一般在 20-30Mbps 间,故外网访问速度并没有达到如签约的 100Mbps 属正常。
事先准备 打电话给 ISP(运营商)的小姐姐,让她给你公网 IP,如果问起原因可以回答家里装监控。没有开启公网 IP 将无法从外网访问家庭的内部网络。 由于运营商(指大部分,如电信)封锁了 80(HTTP)和 443(HTTPS)端口,我们将使用其他的端口进行访问。挑选一些你喜欢的端口,预备使用(如 8080-8090,4431-4439等)。
选择支持DDNS的域名解析服务商 CloudFlare 老牌的域名解析商,也是少有的免费提供 CDN 的服务商。 我推荐 CloudFlare 的原因有三点
可以使用 CDN,保证网络质量始终处于较好状态。例如,我的 Blog 搭建在 Github 上,若有时因网络抽风无法访问 Github,CDN 能助你一臂之力。 可以查看连接数,数据量,访客量等详细数据。 API 获取方便。(2022 Update: CloudFlare 的 API 服务器接近半墙,国内很难再访问了,不推荐使用) Dynv6 提供 IPv4 与 IPv6 DDNS 的服务商,在 21 年有一次较长时间的故障,平常都非常稳定。
DNSPod 被腾讯收购的 DNS 服务商,使用需实名。
配置 DDNS 服务 家用路由器的 DDNS 功能一般仅支持国内大型服务商,例如花生壳。
有两种方法可以配置自己的 DDNS 服务:
将负责拨号的路由器刷成 Openwrt 系统,安装 DDNS 插件以配置自定义脚本的 DDNS 服务; 在一台 24x7 运行的设备上,通过 API 获取 IP 地址,并定时执行脚本更新 IP 地址; 前者虽然更加麻烦,但可以实现仅在 IP 更换时发起更新解析的请求,而不需要定期(如每十分钟)请求一次 API,减小账户被封的风险,并尽可能地缩短从 IP 更换到新的解析生效的时间。
不管是路由器也好,Linux 上的脚本也好,可以在 GitHub 上寻找对应 DDNS 服务商的更新脚本,填上配置就能使用啦~
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+ 群晖-外网访问一站式教程1 Zerotier
+
+ /archives/8776c6d2.html
+
+ 说在前面:有一个月没更新文章了。今后尽量做到 1-2 周更一篇吧。Goo 酱终于把我的 Blog 收录了(其实是我主动提交的),这些教程也能被大家看到啦!有任何问题可以在文章结尾的 Gitalk 处留言,我会尽快回复的!
这篇教程无图,省流。(其实经过 Gzip 压缩后已经能省 80% 的数据量,我也被惊到了)
在上一回的教程里,我们成功搭建了MC基岩版服务器。
群晖-MC基岩版服务器教程
但这个服务器仅存在于本地,若朋友不在同一个局域网下,就无法连接此服务器(这不是肯定的嘛)。
下面我将介绍几种从往外访问家里的群晖服务器的方法。
最简单的方法 - Zerotier - VPN 法 有兴趣了解 VPN 技术的朋友可以点击查看 维基百科 - VPN
Zerotier 可以提供免费的VPN服务,无需复杂配置即可在任何地方连接本地服务器
安装 第一步:下载对应版本的 Zerotier 软件 并于软件市场安装
示范:
例如我的群晖的CPU是 64位 的 J1900,我选择了这一款软件zerotier_x64-6.1_1.4.0-0.spk
接下来,在群晖的 套件中心-设置-信任层级
中选择 任何发行者
然后在 套件中心-手动安装
里选择刚刚下载好的 zerotier_xxxxxx.spk
安装包进行安装
第二步:注册Zerotier。网络上教程极多,我就不再赘述,可以参考这一篇文章。
内网穿透工具 — ZeroTier One 的使用 作者 BiteMan
第三步:记住刚刚注册完拿到的 Network ID 了么,打开 Zerotier 套件,在右下角输入 Network ID 点击 join,就加入这个网络(Networks)啦。记得在 Zerotier 网站控制台里勾选这个新连接(新创建的网络默认使用加入需要验证的规则,我也建议开启,增强安全性)(我强烈建议每添加一台设备即为其命名)。
第四步:在需要访问群晖服务器的设备上也安装好 Zerotier 软件(比起手动安装要简单不少,各大应用市场里也都有,建议下载官方提供的安装包),重复步骤三,加入同一个网络(Networks)下。
到此,Zerotier 双端已经配置完毕。
如何使用呢? 在 Zerotier 网页控制台里,若已添加两台设备,你会看到 Members 分栏中有两台设备,他们的右边都有一个 IP 地址。当两个设备都打开 Zerotier 软件并成功连接了 VPN,你就可以把他们当作在一个局域网下,IP 地址就是 Zerotier 提供的那个,端口号原封不动。
例如我想访问群晖的DSM管理界面,输入 xxx.xxx.xxx.xxx:5000
即可。(Zerotier提供了几种保留网段,可以从 10、172、192 网段里选择你喜欢的 IP 段)
已知问题 Zerotier 适合访问网页/SSH 等简单服务,若用其连接游戏,可能会有某些时刻丢包率过高(原因不明)导致强制登出,严重影响游戏体验。
下一篇文章里我会介绍第二种方法 - DDNS,此方法可以带来极致体验(笑)。
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+ 群晖-MC基岩版服务器教程
+
+ /archives/2716e842.html
+
+ 本教程适合(黑)群晖小白食用,因为这是我多次吃瘪后总结出的经验之谈。操作均在图形界面进行。
1.Docker映像(image)的选择 在尝试过5款映像后,我决定使用基于 官方服务器端软件 制作的这一款 docker 映像。
toasterlint-minecraft_bedrock
为什么我挑选了这款映像?
官方映像拥有 所有原版特性 ; 官方映像 更新快 (docker 会稍滞后); 官方映像有 完整的生物系统 !(必须要提出来说)。 2.映像的使用
在 注册表 页面下载好映像之后,你会在 映像 页面发现它。双击映像,进入开启前的设置页面。
(这个页面会有些许不同,“启用自动重新启动”以及接下来的设置位于 高级设置 中)
这里我勾选了“启用资源限制”来防止 docker 占用过多内存。但经过长时间的使用后我发现完全不需要有顾虑,这个 docker 最多占用 500MB 内存。
“启用自动重新启动”可以勾选,在 docker 遇到问题被关闭时会自动尝试再次开启。
在“卷”设置中,我们选择 “添加文件夹” ,并在喜欢的位置新建一个文件夹来保存MC服务器的配置文件与 更加重要的地图数据 。
然后在 ”装载路径“ 处输入 /data
这样我们就将 NAS 上的一个文件夹装载进docker的 /data
路径下,服务器产生的数据就会保存在本地硬盘,不会因为 docker 的关闭而丢失(像内存断电一样),同时也方便我们进行备份。
在“端口设置”中,按照图中所示 添加19132端口的TCP与UDP通道 。在路由器处配置 19132的端口转发 (记得要同时转发TCP和UDP哦,游戏通讯是有用到UDP的),就能在外网访问MC服务器了。
以上就是使用群晖搭建MC基岩版服务器的教程。很简单吧)
]]>
+
+
+
+
+ 教程
+
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+ 年轻人的第一台NAS(误)-硬件选购
+
+ /archives/757c72a1.html
+
+ 引言 为什么我需要一台NAS?
拓展电脑/手机的储存空间。 内网网速远大于外网网速,想体验1000mbps的高速传输。 想拥有一台个人服务器。 想…… 如何拥有一台NAS?
很简单。你需要钱和钱和钱。
化身神乐mea。
下面进入正题。
家庭NAS的硬件选购(我的个人经验) 我是一个抠门的人,所以我会尽可能选择性价比高的硬件和功能实现方案。
为什么要自己组装,不购买星际蜗牛的现成主机?
据反馈,星际蜗牛主机有以下几个诟病。
杂牌电源输出电流不稳定,可能导致硬盘寿命减短/意外损坏。 电源散热风扇和机箱散热风扇声音大。 自带 4G 内存条和 16G 硬盘都是杂牌货,听说 SSD 没有 USB2.0 速度快。 以下是我挑选出的硬件。
· 星际蜗牛机箱
还有人记得去年的矿难吗,机箱就是当时剩下的……机箱成色不一,但NAS是拿来用的,不是拿来看的所以无所谓。价格在30-50不等。BUT运费可贵了,毕竟C款机箱有近3kg重。
· J1900主板
Intel 赛扬 J1900 四核四线程 主频: 2GHz。
为了降低价格,我选择了一款仅支持于板上 USB 或 mSATA 引导系统启动的主板,约 120元。
· 一款质量好的U2mini型号电源
不管在哪里都会听人念叨电源一定要选好,不然会烧硬盘。我选了一款 100元 的电源,散热风扇没有任何声音。
其他配件
16G MSATA SSD 硬盘 50元。 8G DDR3(主板仅支持DDR3 1440) 1600 内存条 120元 (灵车条)。 静音散热风扇 10元。 一块(N块)心仪的硬盘。我选择了希捷酷鱼系列。 整套硬件(除硬盘外)在 500元 左右。相比于二盘位群晖 1k+,这台四盘位缝合怪的性价比是真的高。
安装硬件?
超级简单,绝对不需要我提。
系统选择 我毫不犹豫地选择了黑群晖。这是她的优点:
系统稳定。 官方提供功能完全,社区强大。NAS约等于一台服务器。 支持docker!!!!(非常强大的功能) 系统安装
网上关于黑群晖安装的教程已经饱和了,我就不再赘述。
以上就是我在装配家庭NAS时的心得,希望能帮到迷惑的你。
]]>
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+
diff --git a/music/Laika_Still_Wants_Go_Home.mp3 b/music/Laika_Still_Wants_Go_Home.mp3
new file mode 100644
index 00000000..905f2220
Binary files /dev/null and b/music/Laika_Still_Wants_Go_Home.mp3 differ
diff --git a/page/2/index.html b/page/2/index.html
new file mode 100644
index 00000000..00133087
--- /dev/null
+++ b/page/2/index.html
@@ -0,0 +1,992 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 浅谈穿越机入门。
+
+
+
+
+
+
+
+
+ 2022-09-04
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git a/page/3/index.html b/page/3/index.html
new file mode 100644
index 00000000..442d0b49
--- /dev/null
+++ b/page/3/index.html
@@ -0,0 +1,990 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git a/page/4/index.html b/page/4/index.html
new file mode 100644
index 00000000..e2ae25ad
--- /dev/null
+++ b/page/4/index.html
@@ -0,0 +1,986 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 对上一个学期简单的总结。
+
+
+
+
+
+
+
+
+ 2021-07-15
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 年终总结。
+
+
+
+
+
+
+
+
+ 2021-01-01
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git a/page/5/index.html b/page/5/index.html
new file mode 100644
index 00000000..576943f7
--- /dev/null
+++ b/page/5/index.html
@@ -0,0 +1,965 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客迁移-主题更换与近况报告
+
+
+
+
+
+
+
+
+ 2020-05-04
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 随笔
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 我的BGP初体验。
+
+
+
+
+
+
+
+
+ 2020-02-10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 教程
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git a/page/6/index.html b/page/6/index.html
new file mode 100644
index 00000000..d06c360e
--- /dev/null
+++ b/page/6/index.html
@@ -0,0 +1,597 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git a/photo_wall/index.html b/photo_wall/index.html
new file mode 100644
index 00000000..47c42804
--- /dev/null
+++ b/photo_wall/index.html
@@ -0,0 +1,544 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 照片墙 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git a/placeholder b/placeholder
deleted file mode 100644
index e69de29b..00000000
diff --git a/robots.txt b/robots.txt
new file mode 100644
index 00000000..f97def1b
--- /dev/null
+++ b/robots.txt
@@ -0,0 +1,12 @@
+User-agent: *
+Allow: /
+Allow: /archives/
+Allow: /categories/
+Allow: /tags/
+Disallow: /vendors/
+Disallow: /js/
+Disallow: /css/
+Disallow: /fonts/
+Disallow: /vendors/
+Disallow: /fancybox/
+Sitemap: https://blog.udon.eu.org/sitemap.xml
\ No newline at end of file
diff --git a/rss.xml b/rss.xml
new file mode 100644
index 00000000..80ac80b3
--- /dev/null
+++ b/rss.xml
@@ -0,0 +1,1287 @@
+
+
+
+ カレーうどん屋
+ https://blog.udon.eu.org
+ カレーうどん屋.
+ zh-CN
+ Tue, 12 Nov 2024 23:30:00 +0800
+ Tue, 12 Nov 2024 23:30:00 +0800
+ 教程
+ 随笔
+ 软件
+ 小鸡测评
+ 3D 打印
+ 嵌入式开发
+ GitHub Actions
+ Hexo
+ DIY
+ -
+
https://blog.udon.eu.org/archives/2c54052d.html
+ 我再也不能不假思索
+ https://blog.udon.eu.org/archives/2c54052d.html
+ 随笔
+ Tue, 12 Nov 2024 23:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/95479b1f.html
+ Soundcore C30i 耳机
+ https://blog.udon.eu.org/archives/95479b1f.html
+ 随笔
+ Thu, 13 Jun 2024 14:40:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/adc5a61e.html
+ 日本之行-第二站-奈良
+ https://blog.udon.eu.org/archives/adc5a61e.html
+ 随笔
+ Mon, 11 Mar 2024 00:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/a7050149.html
+ 23 年对我影响最大的硬件与软件
+ https://blog.udon.eu.org/archives/a7050149.html
+ 随笔
+ Sat, 02 Mar 2024 20:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/a534d51a.html
+ 旅行与军粮
+ https://blog.udon.eu.org/archives/a534d51a.html
+ 随笔
+ Mon, 12 Feb 2024 22:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/7777583b.html
+ 被我整坏的路由器和服务器
+ https://blog.udon.eu.org/archives/7777583b.html
+ 随笔
+ Mon, 12 Feb 2024 18:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/aca48136.html
+ 日本之行-第一站-大阪
+ https://blog.udon.eu.org/archives/aca48136.html
+ 随笔
+ Sun, 21 Jan 2024 23:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/2ef8bd61.html
+ 深圳-香港三日行
+ https://blog.udon.eu.org/archives/2ef8bd61.html
+ 随笔
+ Tue, 19 Dec 2023 22:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/42ec6146.html
+ My Second Attempt To ARM Servers
+ https://blog.udon.eu.org/archives/42ec6146.html
+ 随笔
+ Sun, 24 Sep 2023 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/8b68ddd6.html
+ 修复 UEFI 引导的 GRUB
+ https://blog.udon.eu.org/archives/8b68ddd6.html
+ 教程
+ Sun, 16 Apr 2023 00:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/8b115688.html
+ 使用 Docker Compose 部署音乐服务器 Navidrome
+ https://blog.udon.eu.org/archives/8b115688.html
+ 教程
+ Tue, 31 Jan 2023 12:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/f9bfe16a.html
+ 使用 Docker Compose 部署 Keycloak 20
+ https://blog.udon.eu.org/archives/f9bfe16a.html
+ 教程
+ Sun, 22 Jan 2023 20:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/9b78ad2a.html
+ 寻梦穿越机 - 入门浅谈
+ https://blog.udon.eu.org/archives/9b78ad2a.html
+ 随笔
+ Sun, 04 Sep 2022 12:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/e74a90f2.html
+ 使用再生龙 Clonezilla 备份操作系统
+ https://blog.udon.eu.org/archives/e74a90f2.html
+ 教程
+ Fri, 12 Aug 2022 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/f82a3103.html
+ BETAFPV 高频头固件编译 AttributeError
+ https://blog.udon.eu.org/archives/f82a3103.html
+ 教程
+ DIY
+ Sat, 06 Aug 2022 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/38942a16.html
+ DIY 显示器音箱
+ https://blog.udon.eu.org/archives/38942a16.html
+ 教程
+ DIY
+ Fri, 03 Jun 2022 15:15:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/2e528779.html
+ 迁移 Hexo 渲染环境至 GitHub Actions
+ https://blog.udon.eu.org/archives/2e528779.html
+ 教程
+ GitHub Actions
+ Hexo
+ Mon, 23 May 2022 19:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/9d319c54.html
+ DELL 灵越 15 5547 拆解与更换硅脂
+ https://blog.udon.eu.org/archives/9d319c54.html
+ 随笔
+ Sun, 22 May 2022 10:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/28fa0729.html
+ Virmach Japan
+ https://blog.udon.eu.org/archives/28fa0729.html
+ 小鸡测评
+ Wed, 06 Apr 2022 22:15:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/dbf21067.html
+ 玩一玩 DN42
+ https://blog.udon.eu.org/archives/dbf21067.html
+ 教程
+ Fri, 01 Apr 2022 12:30:00 +0800
+
+
+
+
diff --git a/sitemap.xml b/sitemap.xml
new file mode 100644
index 00000000..37f3f251
--- /dev/null
+++ b/sitemap.xml
@@ -0,0 +1,617 @@
+
+
+
+
+ https://blog.udon.eu.org/images/js/stickup/stickUp.jquery.json
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/2ef8bd61.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/aca48136.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/a534d51a.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/7777583b.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/a7050149.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/adc5a61e.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/95479b1f.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/2c54052d.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/about/index.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/757c72a1.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/2716e842.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/8776c6d2.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/330720fc.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/8ca2a7b3.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/a4c81e8f.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/27f2d840.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/ff412ce9.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/c7a0a8db.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/9d1c6fa4.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/e3c95af8.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/911a91db.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/6b40e5ad.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/67d41c7c.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/a455b52c.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/cd7dffda.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/2c0bfb1a.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/375e7789.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/4a562e35.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/43a7a15c.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/202ebc76.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/245ab175.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/afe45e8a.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/6f963444.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/9732665c.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/a7b78eea.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/be3776eb.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/6e832212.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/19f3c1e1.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/7263a385.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/d01399e6.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/9b58c98e.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/7f7bd4a5.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/dbf21067.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/28fa0729.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/9d319c54.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/2e528779.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/38942a16.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/f82a3103.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/e74a90f2.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/9b78ad2a.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/f9bfe16a.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/8b115688.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/8b68ddd6.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/42ec6146.html
+
+ 2024-12-14
+
+ monthly
+ 0.6
+
+
+
+ https://blog.udon.eu.org/archives/87bacf3f.html
+
+ 2022-02-12
+
+ monthly
+ 0.6
+
+
+
+
+ https://blog.udon.eu.org/
+ 2024-12-14
+ daily
+ 1.0
+
+
+
+
+ https://blog.udon.eu.org/tags/%E6%95%99%E7%A8%8B/
+ 2024-12-14
+ weekly
+ 0.2
+
+
+
+ https://blog.udon.eu.org/tags/%E9%9A%8F%E7%AC%94/
+ 2024-12-14
+ weekly
+ 0.2
+
+
+
+ https://blog.udon.eu.org/tags/%E8%BD%AF%E4%BB%B6/
+ 2024-12-14
+ weekly
+ 0.2
+
+
+
+ https://blog.udon.eu.org/tags/%E5%B0%8F%E9%B8%A1%E6%B5%8B%E8%AF%84/
+ 2024-12-14
+ weekly
+ 0.2
+
+
+
+ https://blog.udon.eu.org/tags/3D-%E6%89%93%E5%8D%B0/
+ 2024-12-14
+ weekly
+ 0.2
+
+
+
+ https://blog.udon.eu.org/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91/
+ 2024-12-14
+ weekly
+ 0.2
+
+
+
+ https://blog.udon.eu.org/tags/GitHub-Actions/
+ 2024-12-14
+ weekly
+ 0.2
+
+
+
+ https://blog.udon.eu.org/tags/Hexo/
+ 2024-12-14
+ weekly
+ 0.2
+
+
+
+ https://blog.udon.eu.org/tags/DIY/
+ 2024-12-14
+ weekly
+ 0.2
+
+
+
+
+
+ https://blog.udon.eu.org/categories/%E6%95%99%E7%A8%8B/
+ 2024-12-14
+ weekly
+ 0.2
+
+
+
+ https://blog.udon.eu.org/categories/%E9%9A%8F%E7%AC%94/
+ 2024-12-14
+ weekly
+ 0.2
+
+
+
+ https://blog.udon.eu.org/categories/%E8%BD%AF%E4%BB%B6%E6%8E%A8%E8%8D%90/
+ 2024-12-14
+ weekly
+ 0.2
+
+
+
+ https://blog.udon.eu.org/categories/%E5%B0%8F%E9%B8%A1%E6%B5%8B%E8%AF%84/
+ 2024-12-14
+ weekly
+ 0.2
+
+
+
+ https://blog.udon.eu.org/categories/3D-%E6%89%93%E5%8D%B0/
+ 2024-12-14
+ weekly
+ 0.2
+
+
+
diff --git "a/tag/3d \346\211\223\345\215\260/atom.xml" "b/tag/3d \346\211\223\345\215\260/atom.xml"
new file mode 100644
index 00000000..79d0e607
--- /dev/null
+++ "b/tag/3d \346\211\223\345\215\260/atom.xml"
@@ -0,0 +1,100 @@
+
+
+ https://blog.udon.eu.org
+ カレーうどん屋 • Posts by "3d 打印" tag
+
+ 2022-02-12T06:50:00.000Z
+
+
+
+
+
+
+
+
+
+
+ https://blog.udon.eu.org/archives/7263a385.html
+ Klipper 的外网访问
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>网络上有关 Klipper 的中文教程少之又少,固作此教程介绍一下如何在外网优雅地访问家中装有 Klipper 的 3D 打印机。</p>
+<h2 id="方法一:端口转发"><a href="#方法一:端口转发" class="headerlink" title="方法一:端口转发"></a>方法一:端口转发</h2><p><strong>此方法仅适用于拥有公网 IP 的用户</strong></p>
+<p>首先,使用 DDNS 将家庭宽带动态变化的 IP 绑定至域名,教程如下:</p>
+<ul>
+<li><a href="https://blog..eu.org/archives/87bacf3f.html">群晖-外网访问一站式教程2 DDNS</a></li>
+<li><a href="https://blog..eu.org/archives/27f2d840.html">iPv6下绝佳的DDNS方法-dynv6</a></li>
+</ul>
+<p>在配置端口映射之前,先介绍一下 Klipper 的网络结构:</p>
+<pre><code class=" mermaid">graph LR;
+ A("你的设备") <--80-->
+ B("前端网页(Fluidd/Mainsail/Octoprint)") <--7125-->
+ C("API 服务器 Moonraker") <-->
+ D("你的 3D 打印机");
+</code></pre>
+
+<p>线上的数字便是通讯的端口。</p>
+<p>由上图,我们不难看出,若需要在外网访问家中的 Klipper,就需要映射 <strong>80</strong> 和 <strong>7125</strong> 两个端口。</p>
+<p>于路由器的 <strong>端口转发/端口映射</strong> 界面配置 80 和 7125 的转发即可。家庭宽带的公网 IP 不会开放 80 端口,可将外网端口配置为 8080,对应的内网端口为 80 即可。</p>
+<p>接着,在 Moonraker 配置 <code>moonraker.conf</code> <code>[authorization]</code> 模块的 <code>cors_domains</code> 模块中添加你的域名,格式为 <code>*://你们域名</code></p>
+<p>也可以选择不使用自己搭建的前端网页,而使用 <a href="http://app.fluidd.xyz/">Fluidd</a> 或者 <a href="%5BMainsail%5D(http://my.mainsail.xyz/)">Mainsail</a> 作者搭建的前端网页。在 Moonraker 配置 <code>moonraker.conf</code> <code>[authorization]</code> 模块的 <code>cors_domains</code> 模块中添加 <code>*://my.mainsail.xyz 与 *://app.fluidd.xyz</code></p>
+<h2 id="方法二:内网穿透"><a href="#方法二:内网穿透" class="headerlink" title="方法二:内网穿透"></a>方法二:内网穿透</h2><p><strong>本人不推荐使用这个方法,固仅简述一下</strong></p>
+<p>可以使用花生壳等内网穿透服务,但给的带宽太小,只能使用控制界面,不能使用摄像头。</p>
+<p>也可以选择自建内网穿透,例如 Frp, Ngrok 等服务。但最近越来越多 Frp 服务器遭到攻击,固不建议自建。</p>
+<h2 id="方法三:使用-VPN"><a href="#方法三:使用-VPN" class="headerlink" title="方法三:使用 VPN"></a>方法三:使用 VPN</h2><p>这是本人推荐的方法。</p>
+<p>与 Octoprint + Marlin 仅需要映射 80 端口不同,Klipper 还需要映射 Moonraker 的 7125 端口,配置端口转发与实际使用都不如前者来的方便。</p>
+<p>我个人推荐用诸如 Zerotier, Tailscale 一类的 VPN 软件,搭建自己的小内网,通过内网 IP 直接访问 Klipper,既安全又方便。</p>
+<p>Zerotier 或者 Tailscale 的使用技巧网上一大片,我就不赘述了。</p>
+
+
+
+ 2022-02-12T06:50:00.000Z
+
+
+ https://blog.udon.eu.org/archives/19f3c1e1.html
+ 我与 3D 打印
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>玩 3D 打印有大半年了,虽然还谈不上资深,但已有了一些自己的想法,作此文分享一下自己的心得。</p>
+<span id="more"></span>
+
+<h2 id="3D-打印机介绍"><a href="#3D-打印机介绍" class="headerlink" title="3D 打印机介绍"></a>3D 打印机介绍</h2><h3 id="3D-打印机的分类"><a href="#3D-打印机的分类" class="headerlink" title="3D 打印机的分类"></a>3D 打印机的分类</h3><p>民用 3D 打印无非两个流派:熔融沉积成型(FDM)与立体平板印刷(SLA,又称光固化),工业用途的还有金属粉末打印的 SLS。</p>
+<p>光固化就不多提了,其优点是精度高,缺点则是机器与耗材价格昂贵,且打印时有比较大的气味(可能对人体有害),对通风的要求比较高,个人认为不适合在家里玩。</p>
+<p>更适合摆在工作室里的 FDM 则是百花齐放,诞生出了各种各样的架构:从最简单的单臂型,到 I3 龙门架、圆形打印平台的三角洲,再到高速高精度的 CoreXY/XZ/YZ 架构、性能超强但组装价格昂贵的 UM 结构。最出名的开源设计则是 Voron 团队,旗下有小型的 Voron 0.1 和大型的 Voron 2.4,且还在持续更新中。</p>
+<h3 id="3D-打印控制系统"><a href="#3D-打印控制系统" class="headerlink" title="3D 打印控制系统"></a>3D 打印控制系统</h3><p>目前我见到的国内用的最多的 3D 打印机控制板(主板)是 MKS GEN L 系列(1.0/1.2/2.1),其搭载的是 ATmega 2560 芯片,常见的控制系统无非两种:Marlin 与 Klipper</p>
+<h4 id="Marlin-1-0-2-0"><a href="#Marlin-1-0-2-0" class="headerlink" title="Marlin 1.0/2.0"></a>Marlin 1.0/2.0</h4><p>Marlin 是一款片上储存,支持脱机打印、屏幕控制的系统。由 C 语言编写,配置较容易修改,但需要手动编译并烧录至控制板。目前已知的编译方法是 Arduino 编译与 VSCode 插件编译,我使用的是后者,但给我的体验不是很好。</p>
+<p>Marlin 的优点是(若不需要自定义)安装简单,仅需用数据线连接电脑烧录即可。缺点则是对打印机的任何重新配置(除了能在屏幕上直接修改设置,如电机方向等),特别是之后要更换静音驱动,就需要手动编译系统,门槛较高且操作繁琐、坑多。</p>
+<h4 id="Klipper"><a href="#Klipper" class="headerlink" title="Klipper"></a>Klipper</h4><p>Klipper 是一款需要上位机控制的系统,主板上仅烧录了一个接收上位机控制指令的程序,大小大概是 Marlin 系统的 1/5。</p>
+<p>据 Klipper 的开发团队<a href="https://github.com/Klipper3d/klipper/blob/master/docs/Benchmarks.md">描述</a>,大部分单片机的性能比较弱,每秒能处理的指令(打印机控制指令)数不高,固采用上位机的高性能 CPU 来处理更高精度但更多指令数的控制指令可以有效地提高打印质量。</p>
+<p>根据跑分表来看,其实 Mega 2560 的性能没有比树莓派的 CPU 弱很多,但考虑到树莓派还可以安装 API 服务器与 Web 控制界面,可以给打印机拓展更多的功能,连接一台上位机还是很具性价比的。</p>
+<p>Klipper 在我看来就像是 3D 打印界的 Ardupilot (开源无人机控制套件),用户可以接触到很多底层的设置、直接控制主板上的所有输出,且修改任何配置都不用重新烧录系统,非常适合创客使用。</p>
+<p>但高级权限也意味着更大的风险。若配置不当,则可能烧坏硬件。</p>
+<p>目前我正在使用的是 Klipper 固件,在两三天的配置调试之后基本可以不用动配置文件了。配置过程中踩了一些坑,后面会单独发一篇文章分享一下 Klipper 配置过程。</p>
+<h3 id="3D-打印材料"><a href="#3D-打印材料" class="headerlink" title="3D 打印材料"></a>3D 打印材料</h3><p>SLA 打印用的是各种光敏树脂,我没有用过就不评价了。</p>
+<p>FDM 使用的材料更加多种多样:PLA、PETG、ABS 三者为主流,TPE 柔性材料也见过有人在用。PLA 是最好打印的一类材料,PETG 需要较高的打印/热床温度,而 ABS 需要保温打印(需封箱)。剩下的就是一些改进/特种材料。例如在 PLA 中添加少量 PC、ABS 以加强硬度,这些材料名字多样,一般是在 PLA 之后加代号,如 PLA-F/AF/AT/G/Pro 等等;或是在 PLA 中添加少量木屑或杂质,使打印机外观像木制品/大理石制品,这类材料比较容易堵头,不是很推荐使用;甚至还有人打印碳纤维,但价格昂贵。</p>
+<h2 id="购买-3D-打印机的理由"><a href="#购买-3D-打印机的理由" class="headerlink" title="购买 3D 打印机的理由"></a>购买 3D 打印机的理由</h2><p>我不想做一个 “KOL”,把 3D 打印吹得天花乱坠。谈一谈我购买 3D 打印机的理由:</p>
+<ol>
+<li>可以动手</li>
+</ol>
+<p>我是一个酷爱动手的人,看见 DIY 项目就忍不住去尝试。或许这辈子开不上高达,但能亲手组装一台能由我操控的机械(甚至还能打印出高达),也算是一点小小的慰藉。</p>
+<ol start="2">
+<li>让梦想中的模型成真</li>
+</ol>
+<p>我有购买一台 3D 打印机的念头,来自一张网图,是一个制作精美的太阳时钟 —— 不是日晷,而是通过巧妙设计的结构,能投射出数字形式的大致时间。显然,这个太阳时钟是 3D 打印的。当时我就觉得,如果我能有一台 3D 打印机,我就能做各种各样的小玩意儿,多美妙 :)</p>
+<ol start="3">
+<li>建模小帮手</li>
+</ol>
+<p>很奇怪的理由,但 3D 打印机确实促使我提升了 3D 建模技术。机器可以创造任何物品,但网上的模型毕竟有限,很多你想要的物品就需要你亲手去建模了。初中时我有学习 3D 建模的念头,当时选择了 C4D,但因为难度太高没多久就放弃了。现在我选择了学习 SolidWorks,自行设计并打印成品,我就能最直观的感受到模型的问题所在,并在软件里修正错误。</p>
+<h2 id="挑选打印机"><a href="#挑选打印机" class="headerlink" title="挑选打印机"></a>挑选打印机</h2><p>于是我就定下了买一台 3D 打印机的计划。这么一计划就是一年多。</p>
+<p>在这一年里我找了很多店家,比对他们的性价比(我的博客里最经常出现的一个词),最后挑中了这家 —— 小树科技。</p>
+<p>我不适合想买成品机,理由很简单:性价比较低,且没有组装的乐趣。这两年国内做开源机器的团队越来越多,小树科技算是比较早的一家。我最看重的莫过于详细的安装文档和一个可靠的社区,毕竟这次我要涉猎的是一个完全陌生的领域。</p>
+<p>逛了逛他们的<a href="(https://www.minitree.fun/)">官网</a>,确定他们有提供安装文档,用户社区也达到数千人规模,我决定了要买这一家的机器。</p>
+<p>新手入门,我选择了 T3 型号的机器。单臂架构,3D 打印机中最简陋、缺点最多的架构,但也是消耗材料最少,价格最便宜的结构。</p>
+<p>DIY 之路必然是坎坷的,如果你决定要自己组装一台 3D 打印机,过程中必然会遇到种种问题。做好心理准备,等待迎接解决问题之后的喜悦吧。</p>
+<p>经过半年的调机与改装之后,这台 T3 已经能稳定地产出质量尚可的打印件,我认为再去改装这台机器所带来的提升已经不大,便决定将其升级为 CoreXY 架构。</p>
+<p>升级 CoreXY 就是这个寒假的事情了,组装花了四五天,调机则还在进行中。目前来看这台机子潜力很大,请期待后续分享调机过程的文章。</p>
+<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>感觉没什么好结的。</p>
+<p>说句实话,调机真的挺痛苦的,刚装完机打两次很可能就会错一次层;但调完之后,再也不会错层的成就感经久不衰。</p>
+
+
+ 2022-02-06T15:00:00.000Z
+
+
diff --git "a/tag/3d \346\211\223\345\215\260/feed.json" "b/tag/3d \346\211\223\345\215\260/feed.json"
new file mode 100644
index 00000000..ef9b5874
--- /dev/null
+++ "b/tag/3d \346\211\223\345\215\260/feed.json"
@@ -0,0 +1,29 @@
+{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "カレーうどん屋 • All posts by \"3d 打印\" tag",
+ "description": "カレーうどん屋.",
+ "home_page_url": "https://blog.udon.eu.org",
+ "items": [
+ {
+ "id": "https://blog.udon.eu.org/archives/7263a385.html",
+ "url": "https://blog.udon.eu.org/archives/7263a385.html",
+ "title": "Klipper 的外网访问",
+ "date_published": "2022-02-12T06:50:00.000Z",
+ "content_html": "网络上有关 Klipper 的中文教程少之又少,固作此教程介绍一下如何在外网优雅地访问家中装有 Klipper 的 3D 打印机。
\n方法一:端口转发 此方法仅适用于拥有公网 IP 的用户
\n首先,使用 DDNS 将家庭宽带动态变化的 IP 绑定至域名,教程如下:
\n\n在配置端口映射之前,先介绍一下 Klipper 的网络结构:
\ngraph LR;\n\tA("你的设备") <--80-->\n\tB("前端网页(Fluidd/Mainsail/Octoprint)") <--7125-->\n\tC("API 服务器 Moonraker") \t<-->\n\tD("你的 3D 打印机");\n
\n\n线上的数字便是通讯的端口。
\n由上图,我们不难看出,若需要在外网访问家中的 Klipper,就需要映射 80 和 7125 两个端口。
\n于路由器的 端口转发/端口映射 界面配置 80 和 7125 的转发即可。家庭宽带的公网 IP 不会开放 80 端口,可将外网端口配置为 8080,对应的内网端口为 80 即可。
\n接着,在 Moonraker 配置 moonraker.conf
[authorization]
模块的 cors_domains
模块中添加你的域名,格式为 *://你们域名
\n也可以选择不使用自己搭建的前端网页,而使用 Fluidd 或者 Mainsail 作者搭建的前端网页。在 Moonraker 配置 moonraker.conf
[authorization]
模块的 cors_domains
模块中添加 *://my.mainsail.xyz 与 *://app.fluidd.xyz
\n方法二:内网穿透 本人不推荐使用这个方法,固仅简述一下
\n可以使用花生壳等内网穿透服务,但给的带宽太小,只能使用控制界面,不能使用摄像头。
\n也可以选择自建内网穿透,例如 Frp, Ngrok 等服务。但最近越来越多 Frp 服务器遭到攻击,固不建议自建。
\n方法三:使用 VPN 这是本人推荐的方法。
\n与 Octoprint + Marlin 仅需要映射 80 端口不同,Klipper 还需要映射 Moonraker 的 7125 端口,配置端口转发与实际使用都不如前者来的方便。
\n我个人推荐用诸如 Zerotier, Tailscale 一类的 VPN 软件,搭建自己的小内网,通过内网 IP 直接访问 Klipper,既安全又方便。
\nZerotier 或者 Tailscale 的使用技巧网上一大片,我就不赘述了。
\n",
+ "tags": [
+ "教程",
+ "3D 打印"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/19f3c1e1.html",
+ "url": "https://blog.udon.eu.org/archives/19f3c1e1.html",
+ "title": "我与 3D 打印",
+ "date_published": "2022-02-06T15:00:00.000Z",
+ "content_html": "玩 3D 打印有大半年了,虽然还谈不上资深,但已有了一些自己的想法,作此文分享一下自己的心得。
\n \n\n3D 打印机介绍 3D 打印机的分类 民用 3D 打印无非两个流派:熔融沉积成型(FDM)与立体平板印刷(SLA,又称光固化),工业用途的还有金属粉末打印的 SLS。
\n光固化就不多提了,其优点是精度高,缺点则是机器与耗材价格昂贵,且打印时有比较大的气味(可能对人体有害),对通风的要求比较高,个人认为不适合在家里玩。
\n更适合摆在工作室里的 FDM 则是百花齐放,诞生出了各种各样的架构:从最简单的单臂型,到 I3 龙门架、圆形打印平台的三角洲,再到高速高精度的 CoreXY/XZ/YZ 架构、性能超强但组装价格昂贵的 UM 结构。最出名的开源设计则是 Voron 团队,旗下有小型的 Voron 0.1 和大型的 Voron 2.4,且还在持续更新中。
\n3D 打印控制系统 目前我见到的国内用的最多的 3D 打印机控制板(主板)是 MKS GEN L 系列(1.0/1.2/2.1),其搭载的是 ATmega 2560 芯片,常见的控制系统无非两种:Marlin 与 Klipper
\nMarlin 1.0/2.0 Marlin 是一款片上储存,支持脱机打印、屏幕控制的系统。由 C 语言编写,配置较容易修改,但需要手动编译并烧录至控制板。目前已知的编译方法是 Arduino 编译与 VSCode 插件编译,我使用的是后者,但给我的体验不是很好。
\nMarlin 的优点是(若不需要自定义)安装简单,仅需用数据线连接电脑烧录即可。缺点则是对打印机的任何重新配置(除了能在屏幕上直接修改设置,如电机方向等),特别是之后要更换静音驱动,就需要手动编译系统,门槛较高且操作繁琐、坑多。
\nKlipper Klipper 是一款需要上位机控制的系统,主板上仅烧录了一个接收上位机控制指令的程序,大小大概是 Marlin 系统的 1/5。
\n据 Klipper 的开发团队描述 ,大部分单片机的性能比较弱,每秒能处理的指令(打印机控制指令)数不高,固采用上位机的高性能 CPU 来处理更高精度但更多指令数的控制指令可以有效地提高打印质量。
\n根据跑分表来看,其实 Mega 2560 的性能没有比树莓派的 CPU 弱很多,但考虑到树莓派还可以安装 API 服务器与 Web 控制界面,可以给打印机拓展更多的功能,连接一台上位机还是很具性价比的。
\nKlipper 在我看来就像是 3D 打印界的 Ardupilot (开源无人机控制套件),用户可以接触到很多底层的设置、直接控制主板上的所有输出,且修改任何配置都不用重新烧录系统,非常适合创客使用。
\n但高级权限也意味着更大的风险。若配置不当,则可能烧坏硬件。
\n目前我正在使用的是 Klipper 固件,在两三天的配置调试之后基本可以不用动配置文件了。配置过程中踩了一些坑,后面会单独发一篇文章分享一下 Klipper 配置过程。
\n3D 打印材料 SLA 打印用的是各种光敏树脂,我没有用过就不评价了。
\nFDM 使用的材料更加多种多样:PLA、PETG、ABS 三者为主流,TPE 柔性材料也见过有人在用。PLA 是最好打印的一类材料,PETG 需要较高的打印/热床温度,而 ABS 需要保温打印(需封箱)。剩下的就是一些改进/特种材料。例如在 PLA 中添加少量 PC、ABS 以加强硬度,这些材料名字多样,一般是在 PLA 之后加代号,如 PLA-F/AF/AT/G/Pro 等等;或是在 PLA 中添加少量木屑或杂质,使打印机外观像木制品/大理石制品,这类材料比较容易堵头,不是很推荐使用;甚至还有人打印碳纤维,但价格昂贵。
\n购买 3D 打印机的理由 我不想做一个 “KOL”,把 3D 打印吹得天花乱坠。谈一谈我购买 3D 打印机的理由:
\n\n可以动手 \n \n我是一个酷爱动手的人,看见 DIY 项目就忍不住去尝试。或许这辈子开不上高达,但能亲手组装一台能由我操控的机械(甚至还能打印出高达),也算是一点小小的慰藉。
\n\n让梦想中的模型成真 \n \n我有购买一台 3D 打印机的念头,来自一张网图,是一个制作精美的太阳时钟 —— 不是日晷,而是通过巧妙设计的结构,能投射出数字形式的大致时间。显然,这个太阳时钟是 3D 打印的。当时我就觉得,如果我能有一台 3D 打印机,我就能做各种各样的小玩意儿,多美妙 :)
\n\n建模小帮手 \n \n很奇怪的理由,但 3D 打印机确实促使我提升了 3D 建模技术。机器可以创造任何物品,但网上的模型毕竟有限,很多你想要的物品就需要你亲手去建模了。初中时我有学习 3D 建模的念头,当时选择了 C4D,但因为难度太高没多久就放弃了。现在我选择了学习 SolidWorks,自行设计并打印成品,我就能最直观的感受到模型的问题所在,并在软件里修正错误。
\n挑选打印机 于是我就定下了买一台 3D 打印机的计划。这么一计划就是一年多。
\n在这一年里我找了很多店家,比对他们的性价比(我的博客里最经常出现的一个词),最后挑中了这家 —— 小树科技。
\n我不适合想买成品机,理由很简单:性价比较低,且没有组装的乐趣。这两年国内做开源机器的团队越来越多,小树科技算是比较早的一家。我最看重的莫过于详细的安装文档和一个可靠的社区,毕竟这次我要涉猎的是一个完全陌生的领域。
\n逛了逛他们的官网 ,确定他们有提供安装文档,用户社区也达到数千人规模,我决定了要买这一家的机器。
\n新手入门,我选择了 T3 型号的机器。单臂架构,3D 打印机中最简陋、缺点最多的架构,但也是消耗材料最少,价格最便宜的结构。
\nDIY 之路必然是坎坷的,如果你决定要自己组装一台 3D 打印机,过程中必然会遇到种种问题。做好心理准备,等待迎接解决问题之后的喜悦吧。
\n经过半年的调机与改装之后,这台 T3 已经能稳定地产出质量尚可的打印件,我认为再去改装这台机器所带来的提升已经不大,便决定将其升级为 CoreXY 架构。
\n升级 CoreXY 就是这个寒假的事情了,组装花了四五天,调机则还在进行中。目前来看这台机子潜力很大,请期待后续分享调机过程的文章。
\n结语 感觉没什么好结的。
\n说句实话,调机真的挺痛苦的,刚装完机打两次很可能就会错一次层;但调完之后,再也不会错层的成就感经久不衰。
\n",
+ "tags": [
+ "3D 打印"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/tag/3d \346\211\223\345\215\260/rss.xml" "b/tag/3d \346\211\223\345\215\260/rss.xml"
new file mode 100644
index 00000000..8bec2b97
--- /dev/null
+++ "b/tag/3d \346\211\223\345\215\260/rss.xml"
@@ -0,0 +1,104 @@
+
+
+
+ カレーうどん屋 • Posts by "3d 打印" tag
+ https://blog.udon.eu.org
+ カレーうどん屋.
+ zh-CN
+ Sat, 12 Feb 2022 14:50:00 +0800
+ Sat, 12 Feb 2022 14:50:00 +0800
+ 教程
+ 随笔
+ 软件
+ 小鸡测评
+ 3D 打印
+ 嵌入式开发
+ GitHub Actions
+ Hexo
+ DIY
+ -
+
https://blog.udon.eu.org/archives/7263a385.html
+ Klipper 的外网访问
+ https://blog.udon.eu.org/archives/7263a385.html
+ 教程
+ 3D 打印
+ Sat, 12 Feb 2022 14:50:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/19f3c1e1.html
+ 我与 3D 打印
+ https://blog.udon.eu.org/archives/19f3c1e1.html
+ 3D 打印
+ Sun, 06 Feb 2022 23:00:00 +0800
+
+
+
+
diff --git a/tag/diy/atom.xml b/tag/diy/atom.xml
new file mode 100644
index 00000000..30d0cd38
--- /dev/null
+++ b/tag/diy/atom.xml
@@ -0,0 +1,93 @@
+
+
+ https://blog.udon.eu.org
+ カレーうどん屋 • Posts by "diy" tag
+
+ 2022-08-06T04:30:00.000Z
+
+
+
+
+
+
+
+
+
+
+ https://blog.udon.eu.org/archives/f82a3103.html
+ BETAFPV 高频头固件编译 AttributeError
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="错误原因"><a href="#错误原因" class="headerlink" title="错误原因"></a>错误原因</h2><p>Python 模块 <code>pypandoc</code> 版本过新,1.8.0 及更高版本已移除了 BETAFPV 高频头固件中仍在使用的 <code>convert</code> 函数。</p>
+<h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><p>安装旧版的 <code>pypandoc</code> 模块。</p>
+<p><code>pip install pypandoc==1.7.0</code></p>
+
+
+
+ 2022-08-06T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/38942a16.html
+ DIY 显示器音箱
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>新买的显示器(LG 27UL500,虽然因为屏幕问题已经退货了)没有内置音箱,虽然大部分时间都在用耳机,但别人有的东西我不能没有嘛,就买了些材料,DIY 一个外接音箱。</p>
+<p>写作此文章分享一下制作的过程~</p>
+<h3 id="物料清单"><a href="#物料清单" class="headerlink" title="物料清单"></a>物料清单</h3><p><img src="/images/2022-06-03/01.JPG" alt="物料清单"></p>
+<ul>
+<li>PAM8403 数字功放板 5RMB</li>
+</ul>
+<p>接受 5V 电压,可以驱动两个 3W 的喇叭。商品详情页面吹的很厉害,确实底噪很小,而且输出的音量非常高。相当 OK 的功放板。</p>
+<ul>
+<li>8Ω 3W 喇叭两只(带音腔) 2*4RMB + 运费</li>
+</ul>
+<p>音质很不错,声音很大也不会破音,因为是广告机用的喇叭么?</p>
+<ul>
+<li>3.5MM 公头 0.5RMB</li>
+</ul>
+<p>我选的型号是 PJ392,只要是 3.5MM 双声道的公头就行了。</p>
+<ul>
+<li>Type C 母座 0.4RMB</li>
+</ul>
+<p>这个随意选。</p>
+<ul>
+<li>屏蔽线缆 2RMB/m</li>
+</ul>
+<p>我买的是4芯的屏蔽线,可供 Type C 头使用(2 power 2 data),音频线只需要三芯(2 data 1 GND),屏蔽线是为了更小的干扰、更好的音质。</p>
+<h3 id="开始组装"><a href="#开始组装" class="headerlink" title="开始组装"></a>开始组装</h3><h4 id="3-5MM-线缆"><a href="#3-5MM-线缆" class="headerlink" title="3.5MM 线缆"></a>3.5MM 线缆</h4><p>剥除一段屏蔽线的外皮,做工还是很不错的,有金属丝和铝箔的屏蔽,塑料膜防水,还有一根抗拉扯的填充芯。</p>
+<p>我选择使用红绿蓝三根线,黄线悬空。线色对应如下:</p>
+<p>红 - 左声道;绿 - 右声道;蓝 - 接地。</p>
+<p><img src="/images/2022-06-03/02.JPG" alt="屏蔽线"></p>
+<p>可以预先套上一段热缩管。</p>
+<p><img src="/images/2022-06-03/03.JPG" alt="热缩管"></p>
+<p>取一枚 3.5mm 公头,旋下插头。</p>
+<p>最长的一段一般是接地。若将接地朝下,我这款公头左侧为左声道,右侧为右声道。具体的接线方式可以用万用表测量接头和接口得知。</p>
+<p>将线穿入孔中,上一坨焊锡即可。</p>
+<p><img src="/images/2022-06-03/04.JPG" alt="公头焊接"></p>
+<p>再使用万用表测量接头与线末端的连通性,注意不能与其他线短路了。</p>
+<p>确认无误后可以打上热熔胶固定。</p>
+<p><img src="/images/2022-06-03/05.JPG" alt="热熔胶固定"></p>
+<p>再打点热熔胶,旋回外壳,将热缩管套好,加热热缩管使其收缩。</p>
+<p>3.5mm 线缆就制作完成了。</p>
+<h4 id="驱动板焊接"><a href="#驱动板焊接" class="headerlink" title="驱动板焊接"></a>驱动板焊接</h4><p>驱动板上有三组线需要焊接:</p>
+<ul>
+<li>音频输入线(3.5mm 线缆)</li>
+<li>电源输入线(Type C 线)</li>
+<li>音频输出线(喇叭线)</li>
+</ul>
+<p>Type C 线我没有再用屏蔽线,用两根导线连接 Type C 母头和驱动板即可。</p>
+<p>焊接方法就不多说了,线穿过孔,上锡即可。</p>
+<p><img src="/images/2022-06-03/06.JPG" alt="焊接中的驱动板"></p>
+<p>全部线缆焊接完成如下:</p>
+<p><img src="/images/2022-06-03/07.JPG" alt="焊接完的驱动板"></p>
+<h4 id="热熔胶填充"><a href="#热熔胶填充" class="headerlink" title="热熔胶填充"></a>热熔胶填充</h4><p>完成接线后,确认无短路,即可连接电脑测试音箱。</p>
+<p>若没有问题,考虑到需要长期使用,就可以用热熔胶覆盖焊接处,防止焊点脱落。</p>
+<p>用热熔胶覆盖之后的驱动板:</p>
+<p><img src="/images/2022-06-03/08.JPG" alt="热熔胶覆盖的驱动板"></p>
+<p>嘛…手艺不是很行。</p>
+<hr>
+<p>就此,外接音箱组装完成啦!</p>
+
+
+
+ 2022-06-03T07:15:00.000Z
+
+
diff --git a/tag/diy/feed.json b/tag/diy/feed.json
new file mode 100644
index 00000000..98d00987
--- /dev/null
+++ b/tag/diy/feed.json
@@ -0,0 +1,30 @@
+{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "カレーうどん屋 • All posts by \"diy\" tag",
+ "description": "カレーうどん屋.",
+ "home_page_url": "https://blog.udon.eu.org",
+ "items": [
+ {
+ "id": "https://blog.udon.eu.org/archives/f82a3103.html",
+ "url": "https://blog.udon.eu.org/archives/f82a3103.html",
+ "title": "BETAFPV 高频头固件编译 AttributeError",
+ "date_published": "2022-08-06T04:30:00.000Z",
+ "content_html": "错误原因 Python 模块 pypandoc
版本过新,1.8.0 及更高版本已移除了 BETAFPV 高频头固件中仍在使用的 convert
函数。
\n解决方法 安装旧版的 pypandoc
模块。
\npip install pypandoc==1.7.0
\n",
+ "tags": [
+ "教程",
+ "DIY"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/38942a16.html",
+ "url": "https://blog.udon.eu.org/archives/38942a16.html",
+ "title": "DIY 显示器音箱",
+ "date_published": "2022-06-03T07:15:00.000Z",
+ "content_html": "新买的显示器(LG 27UL500,虽然因为屏幕问题已经退货了)没有内置音箱,虽然大部分时间都在用耳机,但别人有的东西我不能没有嘛,就买了些材料,DIY 一个外接音箱。
\n写作此文章分享一下制作的过程~
\n物料清单
\n\n接受 5V 电压,可以驱动两个 3W 的喇叭。商品详情页面吹的很厉害,确实底噪很小,而且输出的音量非常高。相当 OK 的功放板。
\n\n8Ω 3W 喇叭两只(带音腔) 2*4RMB + 运费 \n \n音质很不错,声音很大也不会破音,因为是广告机用的喇叭么?
\n\n我选的型号是 PJ392,只要是 3.5MM 双声道的公头就行了。
\n\n这个随意选。
\n\n我买的是4芯的屏蔽线,可供 Type C 头使用(2 power 2 data),音频线只需要三芯(2 data 1 GND),屏蔽线是为了更小的干扰、更好的音质。
\n开始组装 3.5MM 线缆 剥除一段屏蔽线的外皮,做工还是很不错的,有金属丝和铝箔的屏蔽,塑料膜防水,还有一根抗拉扯的填充芯。
\n我选择使用红绿蓝三根线,黄线悬空。线色对应如下:
\n红 - 左声道;绿 - 右声道;蓝 - 接地。
\n
\n可以预先套上一段热缩管。
\n
\n取一枚 3.5mm 公头,旋下插头。
\n最长的一段一般是接地。若将接地朝下,我这款公头左侧为左声道,右侧为右声道。具体的接线方式可以用万用表测量接头和接口得知。
\n将线穿入孔中,上一坨焊锡即可。
\n
\n再使用万用表测量接头与线末端的连通性,注意不能与其他线短路了。
\n确认无误后可以打上热熔胶固定。
\n
\n再打点热熔胶,旋回外壳,将热缩管套好,加热热缩管使其收缩。
\n3.5mm 线缆就制作完成了。
\n驱动板焊接 驱动板上有三组线需要焊接:
\n\n音频输入线(3.5mm 线缆) \n电源输入线(Type C 线) \n音频输出线(喇叭线) \n \nType C 线我没有再用屏蔽线,用两根导线连接 Type C 母头和驱动板即可。
\n焊接方法就不多说了,线穿过孔,上锡即可。
\n
\n全部线缆焊接完成如下:
\n
\n热熔胶填充 完成接线后,确认无短路,即可连接电脑测试音箱。
\n若没有问题,考虑到需要长期使用,就可以用热熔胶覆盖焊接处,防止焊点脱落。
\n用热熔胶覆盖之后的驱动板:
\n
\n嘛…手艺不是很行。
\n \n就此,外接音箱组装完成啦!
\n",
+ "tags": [
+ "教程",
+ "DIY"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tag/diy/rss.xml b/tag/diy/rss.xml
new file mode 100644
index 00000000..1603eb56
--- /dev/null
+++ b/tag/diy/rss.xml
@@ -0,0 +1,97 @@
+
+
+
+ カレーうどん屋 • Posts by "diy" tag
+ https://blog.udon.eu.org
+ カレーうどん屋.
+ zh-CN
+ Sat, 06 Aug 2022 12:30:00 +0800
+ Sat, 06 Aug 2022 12:30:00 +0800
+ 教程
+ 随笔
+ 软件
+ 小鸡测评
+ 3D 打印
+ 嵌入式开发
+ GitHub Actions
+ Hexo
+ DIY
+ -
+
https://blog.udon.eu.org/archives/f82a3103.html
+ BETAFPV 高频头固件编译 AttributeError
+ https://blog.udon.eu.org/archives/f82a3103.html
+ 教程
+ DIY
+ Sat, 06 Aug 2022 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/38942a16.html
+ DIY 显示器音箱
+ https://blog.udon.eu.org/archives/38942a16.html
+ 教程
+ DIY
+ Fri, 03 Jun 2022 15:15:00 +0800
+
+
+
+
diff --git a/tag/github actions/atom.xml b/tag/github actions/atom.xml
new file mode 100644
index 00000000..88b96ad3
--- /dev/null
+++ b/tag/github actions/atom.xml
@@ -0,0 +1,60 @@
+
+
+ https://blog.udon.eu.org
+ カレーうどん屋 • Posts by "github actions" tag
+
+ 2022-05-23T11:30:00.000Z
+
+
+
+
+
+
+
+
+
+
+ https://blog.udon.eu.org/archives/2e528779.html
+ 迁移 Hexo 渲染环境至 GitHub Actions
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>本博客使用的是 Hexo 静态博客框架,我将渲染环境搭建在家中 NAS 之上,部署了一个 Ubuntu Docker 并安装好了 Node.js 环境,正常使用了一两年。</p>
+<p>上周末,写完新文章的我心血来潮,打算在更新博客的时候顺便更新一下老旧的 Node 和 Hexo(Node 12, Hexo 5.2)。</p>
+<p>一番折腾之后,以 npm 和 hexo 环境全部被破坏(事后想想也许只是环境变量掉了)的下场结束。</p>
+<p>鉴于 NAS 性能低下,更新一次 node module 都需要 30 分钟,我放弃了在 NAS 上重新部署渲染环境的念头,转而使用 GitHub Actions 渲染,并同时部署于 GitHub Pages 与 CloudFlare Pages。</p>
+<h2 id="将渲染环境迁至-GitHub-Actions"><a href="#将渲染环境迁至-GitHub-Actions" class="headerlink" title="将渲染环境迁至 GitHub Actions"></a>将渲染环境迁至 GitHub Actions</h2><p>不久之前,CloudFlare Pages 悄悄下架了 Hexo 框架的部署功能,只能用 GitHub Actions 渲染,然后再部署至 CloudFlare Pages 了。</p>
+<h3 id="项目结构的修改"><a href="#项目结构的修改" class="headerlink" title="项目结构的修改"></a>项目结构的修改</h3><p>若想使用 GitHub Actions,需要将博客的源码上传至 GitHub。考虑到隐私和安全的问题,建议创建一个私有仓库管理源码。</p>
+<p>对于项目没有什么需要修改的,因为 Actions 渲染的流程和本地渲染的流程没有区别。</p>
+<p>唯一需要改动的,是引入的主题。由于两个 Git 仓库不能嵌套,我们需要以 Git submodule 的形式引入主题仓库。</p>
+<p>我使用的是 Fluid 主题。采用 <a href="https://hexo.fluid-dev.com/docs/guide/#%E8%A6%86%E7%9B%96%E9%85%8D%E7%BD%AE">覆盖配置</a> 的方法,即在根目录之下有一份配置会覆盖主题内的配置文件,便于在 Actions 中渲染。</p>
+<p>以下也将以 Fluid 为例,请根据你使用的主题修改命令或代码。</p>
+<p>首先,删除原来的主题(若使用的是主题内的配置,注意备份配置文件!)</p>
+<p>返回博客源码的根目录,执行:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git submodule add https://github.com/fluid-dev/hexo-theme-fluid.git themes/fluid<br></code></pre></td></tr></table></figure>
+
+<p>末尾的 <code>themes/fluid</code> 为此 submodule 在项目中的位置与名字,与先前本地渲染时的配置相同即可。</p>
+<p>删除子模块的过程较为繁琐,请参考网上的文章进行操作。</p>
+<p>在 Clone 此项目时,submodule 默认不会被下载,需要使用指令:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git submodule update --init --recursive<br></code></pre></td></tr></table></figure>
+
+<p>下载 submodule。在下面会提到的 Actions 配置文件中会出现这条指令。</p>
+<p>接着,就可以将博客源码上传至 GitHub。</p>
+<h3 id="GitHub-Actions-相关文件"><a href="#GitHub-Actions-相关文件" class="headerlink" title="GitHub Actions 相关文件"></a>GitHub Actions 相关文件</h3><p>在博客源码根目录创建 <code>.github/workflows/submit.yml</code> 和 <code>.github/script/blog-update.sh</code> 两个文件,填入下列代码。</p>
+<p>以下代码参考文章 <a href="https://blog.kukmoon.com/f8bb4ee.html#23-%E7%BC%96%E5%86%99-workflow">GitHub Actions 自动部署 Hexo 博客到 cPanel 虚拟主机 - 谷中望月</a>,有所修改。</p>
+<p><code>submit.yml</code>:</p>
+<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">CI</span><br><br><span class="hljs-comment"># 监听 main 分支的改动与 Release 的发布</span><br><span class="hljs-attr">on:</span><br> <span class="hljs-attr">push:</span><br> <span class="hljs-attr">branches:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">main</span><br> <span class="hljs-attr">release:</span><br> <span class="hljs-attr">types:</span> [<span class="hljs-string">published</span>]<br><br><span class="hljs-comment"># 自定义环境变量</span><br><span class="hljs-attr">env:</span><br> <span class="hljs-attr">GIT_USER:</span> <span class="hljs-string">Lao-Liu233</span> <span class="hljs-comment"># 改成你自己的 GitHub 用户名</span><br> <span class="hljs-attr">GIT_EMAIL:</span> <span class="hljs-string">blog@udon.eu.org</span> <span class="hljs-comment"># 改成你自己的 GitHub 注册邮箱</span><br><br><span class="hljs-attr">jobs:</span><br> <span class="hljs-attr">build:</span><br> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">on</span> <span class="hljs-string">node</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node_version</span> <span class="hljs-string">}}</span> <span class="hljs-string">and</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.os</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span><br> <span class="hljs-attr">strategy:</span><br> <span class="hljs-attr">matrix:</span><br> <span class="hljs-attr">os:</span> [<span class="hljs-string">ubuntu-latest</span>]<br> <span class="hljs-attr">node_version:</span> [<span class="hljs-number">16.15</span>] <span class="hljs-comment"># 改成你本地的 Node.js 版本,可以用 `node --version` 命令查询</span><br><br> <span class="hljs-attr">steps:</span><br> <span class="hljs-comment"># 获取博客源码</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span><br> <br> <span class="hljs-comment"># 用 Node.js 渲染</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Use</span> <span class="hljs-string">Node.js</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node_version</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v1</span><br> <span class="hljs-attr">with:</span><br> <span class="hljs-attr">node-version:</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node_version</span> <span class="hljs-string">}}</span><br><br> <span class="hljs-comment"># 安装 Hexo-cli </span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">hexo</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> npm install -g hexo-cli</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 安装依赖</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span> <br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> npm install</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 导入 submodule</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Clone</span> <span class="hljs-string">submodule</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> git submodule update --init --recursive</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 配置环境</span><br> <span class="hljs-comment"># ssh-kenscan github.com >> ~/.ssh/known_hosts # 从 GitHub 获取公钥并保存到 known_hosts 文件</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Configuration</span> <span class="hljs-string">environment</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> sudo timedatectl set-timezone "Asia/Shanghai"</span><br><span class="hljs-string"> mkdir -p ~/.ssh/</span><br><span class="hljs-string"> echo "$HEXO_DEPLOY_PRI" > ~/.ssh/id_rsa</span><br><span class="hljs-string"> chmod 600 ~/.ssh/id_rsa</span><br><span class="hljs-string"> ssh-keyscan github.com >> ~/.ssh/known_hosts</span><br><span class="hljs-string"> git config --global user.name $GIT_USER</span><br><span class="hljs-string"> git config --global user.email $GIT_EMAIL</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 生成并部署</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">hexo</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> hexo clean</span><br><span class="hljs-string"> hexo g -d</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 部署后更新博客源码,用于添加 abbrlink,如果不用 abbrlink,需要删除</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Update</span> <span class="hljs-string">Blog</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br> <span class="hljs-string">sh</span> <span class="hljs-string">"${GITHUB_WORKSPACE}/.github/script/blog-update.sh"</span><br></code></pre></td></tr></table></figure>
+
+<p><code>.github/script/blog-update.sh</code>:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-meta">#!/bin/sh</span><br><br><span class="hljs-keyword">if</span> [ -z <span class="hljs-string">"<span class="hljs-subst">$(git status --porcelain)</span>"</span> ]; <span class="hljs-keyword">then</span><br> <span class="hljs-built_in">echo</span> <span class="hljs-string">"nothing to update."</span><br><span class="hljs-keyword">else</span><br> git add <span class="hljs-built_in">source</span>/_posts/ <span class="hljs-comment">#仅对文章源码所在文件夹进行修改</span><br> git commit -m <span class="hljs-string">"triggle by commit <span class="hljs-variable">${GITHUB_SHA}</span>"</span> -a<br> git push origin main<br><span class="hljs-keyword">fi</span><br></code></pre></td></tr></table></figure>
+
+<p>Commit + Push,打开 Actions 界面,就能看到正在运行的 Action 啦。</p>
+<p>不出意外,Action 成功执行,1分钟内博客就能渲染成功、部署至 GitHub Pages。</p>
+<h2 id="同时部署至-CloudFlare-Pages"><a href="#同时部署至-CloudFlare-Pages" class="headerlink" title="同时部署至 CloudFlare Pages"></a>同时部署至 CloudFlare Pages</h2><p>步骤较为简单,我简述一下。</p>
+<p>打开 CloudFlare Pages, 连接至存放 <strong>渲染后</strong> 的静态文件的仓库,渲染的框架选择 <strong>None</strong>,执行的指令填写 <code>exit 0;</code> 就可以了。</p>
+<p>执行部署后,渲染后的静态文件就被部署至 CloudFlare Pages 啦。</p>
+
+
+
+
+ 2022-05-23T11:30:00.000Z
+
+
diff --git a/tag/github actions/feed.json b/tag/github actions/feed.json
new file mode 100644
index 00000000..9d989323
--- /dev/null
+++ b/tag/github actions/feed.json
@@ -0,0 +1,20 @@
+{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "カレーうどん屋 • All posts by \"github actions\" tag",
+ "description": "カレーうどん屋.",
+ "home_page_url": "https://blog.udon.eu.org",
+ "items": [
+ {
+ "id": "https://blog.udon.eu.org/archives/2e528779.html",
+ "url": "https://blog.udon.eu.org/archives/2e528779.html",
+ "title": "迁移 Hexo 渲染环境至 GitHub Actions",
+ "date_published": "2022-05-23T11:30:00.000Z",
+ "content_html": "本博客使用的是 Hexo 静态博客框架,我将渲染环境搭建在家中 NAS 之上,部署了一个 Ubuntu Docker 并安装好了 Node.js 环境,正常使用了一两年。
\n上周末,写完新文章的我心血来潮,打算在更新博客的时候顺便更新一下老旧的 Node 和 Hexo(Node 12, Hexo 5.2)。
\n一番折腾之后,以 npm 和 hexo 环境全部被破坏(事后想想也许只是环境变量掉了)的下场结束。
\n鉴于 NAS 性能低下,更新一次 node module 都需要 30 分钟,我放弃了在 NAS 上重新部署渲染环境的念头,转而使用 GitHub Actions 渲染,并同时部署于 GitHub Pages 与 CloudFlare Pages。
\n将渲染环境迁至 GitHub Actions 不久之前,CloudFlare Pages 悄悄下架了 Hexo 框架的部署功能,只能用 GitHub Actions 渲染,然后再部署至 CloudFlare Pages 了。
\n项目结构的修改 若想使用 GitHub Actions,需要将博客的源码上传至 GitHub。考虑到隐私和安全的问题,建议创建一个私有仓库管理源码。
\n对于项目没有什么需要修改的,因为 Actions 渲染的流程和本地渲染的流程没有区别。
\n唯一需要改动的,是引入的主题。由于两个 Git 仓库不能嵌套,我们需要以 Git submodule 的形式引入主题仓库。
\n我使用的是 Fluid 主题。采用 覆盖配置 的方法,即在根目录之下有一份配置会覆盖主题内的配置文件,便于在 Actions 中渲染。
\n以下也将以 Fluid 为例,请根据你使用的主题修改命令或代码。
\n首先,删除原来的主题(若使用的是主题内的配置,注意备份配置文件!)
\n返回博客源码的根目录,执行:
\n1 git submodule add https://github.com/fluid-dev/hexo-theme-fluid.git themes/fluid
\n\n末尾的 themes/fluid
为此 submodule 在项目中的位置与名字,与先前本地渲染时的配置相同即可。
\n删除子模块的过程较为繁琐,请参考网上的文章进行操作。
\n在 Clone 此项目时,submodule 默认不会被下载,需要使用指令:
\n1 git submodule update --init --recursive
\n\n下载 submodule。在下面会提到的 Actions 配置文件中会出现这条指令。
\n接着,就可以将博客源码上传至 GitHub。
\nGitHub Actions 相关文件 在博客源码根目录创建 .github/workflows/submit.yml
和 .github/script/blog-update.sh
两个文件,填入下列代码。
\n以下代码参考文章 GitHub Actions 自动部署 Hexo 博客到 cPanel 虚拟主机 - 谷中望月 ,有所修改。
\nsubmit.yml
:
\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 name: CI on: push: branches: - main release: types: [published ]env: GIT_USER: Lao-Liu233 GIT_EMAIL: blog@udon.eu.org jobs: build: name: Build on node ${{ matrix.node_version }} and ${{ matrix.os }} runs-on: ubuntu-latest strategy: matrix: os: [ubuntu-latest ] node_version: [16.15 ] steps: - name: Checkout uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node_version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node_version }} - name: Install hexo run: | npm install -g hexo-cli - name: Install dependencies run: | npm install - name: Clone submodule run: | git submodule update --init --recursive - name: Configuration environment run: | sudo timedatectl set-timezone "Asia/Shanghai" mkdir -p ~/.ssh/ echo "$HEXO_DEPLOY_PRI" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan github.com >> ~/.ssh/known_hosts git config --global user.name $GIT_USER git config --global user.email $GIT_EMAIL - name: Deploy hexo run: | hexo clean hexo g -d - name: Update Blog run: | sh "${GITHUB_WORKSPACE}/.github/script/blog-update.sh"
\n\n.github/script/blog-update.sh
:
\n1 2 3 4 5 6 7 8 9 #!/bin/sh if [ -z "$(git status --porcelain) " ]; then echo "nothing to update." else git add source /_posts/\t git commit -m "triggle by commit ${GITHUB_SHA} " -a git push origin mainfi
\n\nCommit + Push,打开 Actions 界面,就能看到正在运行的 Action 啦。
\n不出意外,Action 成功执行,1分钟内博客就能渲染成功、部署至 GitHub Pages。
\n同时部署至 CloudFlare Pages 步骤较为简单,我简述一下。
\n打开 CloudFlare Pages, 连接至存放 渲染后 的静态文件的仓库,渲染的框架选择 None ,执行的指令填写 exit 0;
就可以了。
\n执行部署后,渲染后的静态文件就被部署至 CloudFlare Pages 啦。
\n",
+ "tags": [
+ "教程",
+ "GitHub Actions",
+ "Hexo"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tag/github actions/rss.xml b/tag/github actions/rss.xml
new file mode 100644
index 00000000..f26026d7
--- /dev/null
+++ b/tag/github actions/rss.xml
@@ -0,0 +1,64 @@
+
+
+
+ カレーうどん屋 • Posts by "github actions" tag
+ https://blog.udon.eu.org
+ カレーうどん屋.
+ zh-CN
+ Mon, 23 May 2022 19:30:00 +0800
+ Mon, 23 May 2022 19:30:00 +0800
+ 教程
+ 随笔
+ 软件
+ 小鸡测评
+ 3D 打印
+ 嵌入式开发
+ GitHub Actions
+ Hexo
+ DIY
+ -
+
https://blog.udon.eu.org/archives/2e528779.html
+ 迁移 Hexo 渲染环境至 GitHub Actions
+ https://blog.udon.eu.org/archives/2e528779.html
+ 教程
+ GitHub Actions
+ Hexo
+ Mon, 23 May 2022 19:30:00 +0800
+
+
+
+
diff --git a/tag/hexo/atom.xml b/tag/hexo/atom.xml
new file mode 100644
index 00000000..7c3b4114
--- /dev/null
+++ b/tag/hexo/atom.xml
@@ -0,0 +1,60 @@
+
+
+ https://blog.udon.eu.org
+ カレーうどん屋 • Posts by "hexo" tag
+
+ 2022-05-23T11:30:00.000Z
+
+
+
+
+
+
+
+
+
+
+ https://blog.udon.eu.org/archives/2e528779.html
+ 迁移 Hexo 渲染环境至 GitHub Actions
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>本博客使用的是 Hexo 静态博客框架,我将渲染环境搭建在家中 NAS 之上,部署了一个 Ubuntu Docker 并安装好了 Node.js 环境,正常使用了一两年。</p>
+<p>上周末,写完新文章的我心血来潮,打算在更新博客的时候顺便更新一下老旧的 Node 和 Hexo(Node 12, Hexo 5.2)。</p>
+<p>一番折腾之后,以 npm 和 hexo 环境全部被破坏(事后想想也许只是环境变量掉了)的下场结束。</p>
+<p>鉴于 NAS 性能低下,更新一次 node module 都需要 30 分钟,我放弃了在 NAS 上重新部署渲染环境的念头,转而使用 GitHub Actions 渲染,并同时部署于 GitHub Pages 与 CloudFlare Pages。</p>
+<h2 id="将渲染环境迁至-GitHub-Actions"><a href="#将渲染环境迁至-GitHub-Actions" class="headerlink" title="将渲染环境迁至 GitHub Actions"></a>将渲染环境迁至 GitHub Actions</h2><p>不久之前,CloudFlare Pages 悄悄下架了 Hexo 框架的部署功能,只能用 GitHub Actions 渲染,然后再部署至 CloudFlare Pages 了。</p>
+<h3 id="项目结构的修改"><a href="#项目结构的修改" class="headerlink" title="项目结构的修改"></a>项目结构的修改</h3><p>若想使用 GitHub Actions,需要将博客的源码上传至 GitHub。考虑到隐私和安全的问题,建议创建一个私有仓库管理源码。</p>
+<p>对于项目没有什么需要修改的,因为 Actions 渲染的流程和本地渲染的流程没有区别。</p>
+<p>唯一需要改动的,是引入的主题。由于两个 Git 仓库不能嵌套,我们需要以 Git submodule 的形式引入主题仓库。</p>
+<p>我使用的是 Fluid 主题。采用 <a href="https://hexo.fluid-dev.com/docs/guide/#%E8%A6%86%E7%9B%96%E9%85%8D%E7%BD%AE">覆盖配置</a> 的方法,即在根目录之下有一份配置会覆盖主题内的配置文件,便于在 Actions 中渲染。</p>
+<p>以下也将以 Fluid 为例,请根据你使用的主题修改命令或代码。</p>
+<p>首先,删除原来的主题(若使用的是主题内的配置,注意备份配置文件!)</p>
+<p>返回博客源码的根目录,执行:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git submodule add https://github.com/fluid-dev/hexo-theme-fluid.git themes/fluid<br></code></pre></td></tr></table></figure>
+
+<p>末尾的 <code>themes/fluid</code> 为此 submodule 在项目中的位置与名字,与先前本地渲染时的配置相同即可。</p>
+<p>删除子模块的过程较为繁琐,请参考网上的文章进行操作。</p>
+<p>在 Clone 此项目时,submodule 默认不会被下载,需要使用指令:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git submodule update --init --recursive<br></code></pre></td></tr></table></figure>
+
+<p>下载 submodule。在下面会提到的 Actions 配置文件中会出现这条指令。</p>
+<p>接着,就可以将博客源码上传至 GitHub。</p>
+<h3 id="GitHub-Actions-相关文件"><a href="#GitHub-Actions-相关文件" class="headerlink" title="GitHub Actions 相关文件"></a>GitHub Actions 相关文件</h3><p>在博客源码根目录创建 <code>.github/workflows/submit.yml</code> 和 <code>.github/script/blog-update.sh</code> 两个文件,填入下列代码。</p>
+<p>以下代码参考文章 <a href="https://blog.kukmoon.com/f8bb4ee.html#23-%E7%BC%96%E5%86%99-workflow">GitHub Actions 自动部署 Hexo 博客到 cPanel 虚拟主机 - 谷中望月</a>,有所修改。</p>
+<p><code>submit.yml</code>:</p>
+<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">CI</span><br><br><span class="hljs-comment"># 监听 main 分支的改动与 Release 的发布</span><br><span class="hljs-attr">on:</span><br> <span class="hljs-attr">push:</span><br> <span class="hljs-attr">branches:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">main</span><br> <span class="hljs-attr">release:</span><br> <span class="hljs-attr">types:</span> [<span class="hljs-string">published</span>]<br><br><span class="hljs-comment"># 自定义环境变量</span><br><span class="hljs-attr">env:</span><br> <span class="hljs-attr">GIT_USER:</span> <span class="hljs-string">Lao-Liu233</span> <span class="hljs-comment"># 改成你自己的 GitHub 用户名</span><br> <span class="hljs-attr">GIT_EMAIL:</span> <span class="hljs-string">blog@udon.eu.org</span> <span class="hljs-comment"># 改成你自己的 GitHub 注册邮箱</span><br><br><span class="hljs-attr">jobs:</span><br> <span class="hljs-attr">build:</span><br> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">on</span> <span class="hljs-string">node</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node_version</span> <span class="hljs-string">}}</span> <span class="hljs-string">and</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.os</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span><br> <span class="hljs-attr">strategy:</span><br> <span class="hljs-attr">matrix:</span><br> <span class="hljs-attr">os:</span> [<span class="hljs-string">ubuntu-latest</span>]<br> <span class="hljs-attr">node_version:</span> [<span class="hljs-number">16.15</span>] <span class="hljs-comment"># 改成你本地的 Node.js 版本,可以用 `node --version` 命令查询</span><br><br> <span class="hljs-attr">steps:</span><br> <span class="hljs-comment"># 获取博客源码</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span><br> <br> <span class="hljs-comment"># 用 Node.js 渲染</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Use</span> <span class="hljs-string">Node.js</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node_version</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v1</span><br> <span class="hljs-attr">with:</span><br> <span class="hljs-attr">node-version:</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node_version</span> <span class="hljs-string">}}</span><br><br> <span class="hljs-comment"># 安装 Hexo-cli </span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">hexo</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> npm install -g hexo-cli</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 安装依赖</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span> <br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> npm install</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 导入 submodule</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Clone</span> <span class="hljs-string">submodule</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> git submodule update --init --recursive</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 配置环境</span><br> <span class="hljs-comment"># ssh-kenscan github.com >> ~/.ssh/known_hosts # 从 GitHub 获取公钥并保存到 known_hosts 文件</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Configuration</span> <span class="hljs-string">environment</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> sudo timedatectl set-timezone "Asia/Shanghai"</span><br><span class="hljs-string"> mkdir -p ~/.ssh/</span><br><span class="hljs-string"> echo "$HEXO_DEPLOY_PRI" > ~/.ssh/id_rsa</span><br><span class="hljs-string"> chmod 600 ~/.ssh/id_rsa</span><br><span class="hljs-string"> ssh-keyscan github.com >> ~/.ssh/known_hosts</span><br><span class="hljs-string"> git config --global user.name $GIT_USER</span><br><span class="hljs-string"> git config --global user.email $GIT_EMAIL</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 生成并部署</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">hexo</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> hexo clean</span><br><span class="hljs-string"> hexo g -d</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 部署后更新博客源码,用于添加 abbrlink,如果不用 abbrlink,需要删除</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Update</span> <span class="hljs-string">Blog</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br> <span class="hljs-string">sh</span> <span class="hljs-string">"${GITHUB_WORKSPACE}/.github/script/blog-update.sh"</span><br></code></pre></td></tr></table></figure>
+
+<p><code>.github/script/blog-update.sh</code>:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-meta">#!/bin/sh</span><br><br><span class="hljs-keyword">if</span> [ -z <span class="hljs-string">"<span class="hljs-subst">$(git status --porcelain)</span>"</span> ]; <span class="hljs-keyword">then</span><br> <span class="hljs-built_in">echo</span> <span class="hljs-string">"nothing to update."</span><br><span class="hljs-keyword">else</span><br> git add <span class="hljs-built_in">source</span>/_posts/ <span class="hljs-comment">#仅对文章源码所在文件夹进行修改</span><br> git commit -m <span class="hljs-string">"triggle by commit <span class="hljs-variable">${GITHUB_SHA}</span>"</span> -a<br> git push origin main<br><span class="hljs-keyword">fi</span><br></code></pre></td></tr></table></figure>
+
+<p>Commit + Push,打开 Actions 界面,就能看到正在运行的 Action 啦。</p>
+<p>不出意外,Action 成功执行,1分钟内博客就能渲染成功、部署至 GitHub Pages。</p>
+<h2 id="同时部署至-CloudFlare-Pages"><a href="#同时部署至-CloudFlare-Pages" class="headerlink" title="同时部署至 CloudFlare Pages"></a>同时部署至 CloudFlare Pages</h2><p>步骤较为简单,我简述一下。</p>
+<p>打开 CloudFlare Pages, 连接至存放 <strong>渲染后</strong> 的静态文件的仓库,渲染的框架选择 <strong>None</strong>,执行的指令填写 <code>exit 0;</code> 就可以了。</p>
+<p>执行部署后,渲染后的静态文件就被部署至 CloudFlare Pages 啦。</p>
+
+
+
+
+ 2022-05-23T11:30:00.000Z
+
+
diff --git a/tag/hexo/feed.json b/tag/hexo/feed.json
new file mode 100644
index 00000000..24ded48b
--- /dev/null
+++ b/tag/hexo/feed.json
@@ -0,0 +1,20 @@
+{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "カレーうどん屋 • All posts by \"hexo\" tag",
+ "description": "カレーうどん屋.",
+ "home_page_url": "https://blog.udon.eu.org",
+ "items": [
+ {
+ "id": "https://blog.udon.eu.org/archives/2e528779.html",
+ "url": "https://blog.udon.eu.org/archives/2e528779.html",
+ "title": "迁移 Hexo 渲染环境至 GitHub Actions",
+ "date_published": "2022-05-23T11:30:00.000Z",
+ "content_html": "本博客使用的是 Hexo 静态博客框架,我将渲染环境搭建在家中 NAS 之上,部署了一个 Ubuntu Docker 并安装好了 Node.js 环境,正常使用了一两年。
\n上周末,写完新文章的我心血来潮,打算在更新博客的时候顺便更新一下老旧的 Node 和 Hexo(Node 12, Hexo 5.2)。
\n一番折腾之后,以 npm 和 hexo 环境全部被破坏(事后想想也许只是环境变量掉了)的下场结束。
\n鉴于 NAS 性能低下,更新一次 node module 都需要 30 分钟,我放弃了在 NAS 上重新部署渲染环境的念头,转而使用 GitHub Actions 渲染,并同时部署于 GitHub Pages 与 CloudFlare Pages。
\n将渲染环境迁至 GitHub Actions 不久之前,CloudFlare Pages 悄悄下架了 Hexo 框架的部署功能,只能用 GitHub Actions 渲染,然后再部署至 CloudFlare Pages 了。
\n项目结构的修改 若想使用 GitHub Actions,需要将博客的源码上传至 GitHub。考虑到隐私和安全的问题,建议创建一个私有仓库管理源码。
\n对于项目没有什么需要修改的,因为 Actions 渲染的流程和本地渲染的流程没有区别。
\n唯一需要改动的,是引入的主题。由于两个 Git 仓库不能嵌套,我们需要以 Git submodule 的形式引入主题仓库。
\n我使用的是 Fluid 主题。采用 覆盖配置 的方法,即在根目录之下有一份配置会覆盖主题内的配置文件,便于在 Actions 中渲染。
\n以下也将以 Fluid 为例,请根据你使用的主题修改命令或代码。
\n首先,删除原来的主题(若使用的是主题内的配置,注意备份配置文件!)
\n返回博客源码的根目录,执行:
\n1 git submodule add https://github.com/fluid-dev/hexo-theme-fluid.git themes/fluid
\n\n末尾的 themes/fluid
为此 submodule 在项目中的位置与名字,与先前本地渲染时的配置相同即可。
\n删除子模块的过程较为繁琐,请参考网上的文章进行操作。
\n在 Clone 此项目时,submodule 默认不会被下载,需要使用指令:
\n1 git submodule update --init --recursive
\n\n下载 submodule。在下面会提到的 Actions 配置文件中会出现这条指令。
\n接着,就可以将博客源码上传至 GitHub。
\nGitHub Actions 相关文件 在博客源码根目录创建 .github/workflows/submit.yml
和 .github/script/blog-update.sh
两个文件,填入下列代码。
\n以下代码参考文章 GitHub Actions 自动部署 Hexo 博客到 cPanel 虚拟主机 - 谷中望月 ,有所修改。
\nsubmit.yml
:
\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 name: CI on: push: branches: - main release: types: [published ]env: GIT_USER: Lao-Liu233 GIT_EMAIL: blog@udon.eu.org jobs: build: name: Build on node ${{ matrix.node_version }} and ${{ matrix.os }} runs-on: ubuntu-latest strategy: matrix: os: [ubuntu-latest ] node_version: [16.15 ] steps: - name: Checkout uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node_version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node_version }} - name: Install hexo run: | npm install -g hexo-cli - name: Install dependencies run: | npm install - name: Clone submodule run: | git submodule update --init --recursive - name: Configuration environment run: | sudo timedatectl set-timezone "Asia/Shanghai" mkdir -p ~/.ssh/ echo "$HEXO_DEPLOY_PRI" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan github.com >> ~/.ssh/known_hosts git config --global user.name $GIT_USER git config --global user.email $GIT_EMAIL - name: Deploy hexo run: | hexo clean hexo g -d - name: Update Blog run: | sh "${GITHUB_WORKSPACE}/.github/script/blog-update.sh"
\n\n.github/script/blog-update.sh
:
\n1 2 3 4 5 6 7 8 9 #!/bin/sh if [ -z "$(git status --porcelain) " ]; then echo "nothing to update." else git add source /_posts/\t git commit -m "triggle by commit ${GITHUB_SHA} " -a git push origin mainfi
\n\nCommit + Push,打开 Actions 界面,就能看到正在运行的 Action 啦。
\n不出意外,Action 成功执行,1分钟内博客就能渲染成功、部署至 GitHub Pages。
\n同时部署至 CloudFlare Pages 步骤较为简单,我简述一下。
\n打开 CloudFlare Pages, 连接至存放 渲染后 的静态文件的仓库,渲染的框架选择 None ,执行的指令填写 exit 0;
就可以了。
\n执行部署后,渲染后的静态文件就被部署至 CloudFlare Pages 啦。
\n",
+ "tags": [
+ "教程",
+ "GitHub Actions",
+ "Hexo"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tag/hexo/rss.xml b/tag/hexo/rss.xml
new file mode 100644
index 00000000..56b47554
--- /dev/null
+++ b/tag/hexo/rss.xml
@@ -0,0 +1,64 @@
+
+
+
+ カレーうどん屋 • Posts by "hexo" tag
+ https://blog.udon.eu.org
+ カレーうどん屋.
+ zh-CN
+ Mon, 23 May 2022 19:30:00 +0800
+ Mon, 23 May 2022 19:30:00 +0800
+ 教程
+ 随笔
+ 软件
+ 小鸡测评
+ 3D 打印
+ 嵌入式开发
+ GitHub Actions
+ Hexo
+ DIY
+ -
+
https://blog.udon.eu.org/archives/2e528779.html
+ 迁移 Hexo 渲染环境至 GitHub Actions
+ https://blog.udon.eu.org/archives/2e528779.html
+ 教程
+ GitHub Actions
+ Hexo
+ Mon, 23 May 2022 19:30:00 +0800
+
+
+
+
diff --git "a/tag/\345\260\217\351\270\241\346\265\213\350\257\204/atom.xml" "b/tag/\345\260\217\351\270\241\346\265\213\350\257\204/atom.xml"
new file mode 100644
index 00000000..8b6ea801
--- /dev/null
+++ "b/tag/\345\260\217\351\270\241\346\265\213\350\257\204/atom.xml"
@@ -0,0 +1,217 @@
+
+
+ https://blog.udon.eu.org
+ カレーうどん屋 • Posts by "小鸡测评" tag
+
+ 2022-04-06T14:15:00.000Z
+
+
+
+
+
+
+
+
+
+
+ https://blog.udon.eu.org/archives/28fa0729.html
+ Virmach Japan
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>老朋友 Virmach 最近搞了场日本的预售。上游是 XTom,线路走的 IIJ。配置从 1C 384M 到 2C 2.5G 都有,价格则是 11.11刀/年 起(预售打 8 折, 8.89刀/年起)。</p>
+<p>我买的这台是折后 9.7刀/年,1C 768M 20G NVME 2T 双向流量的配置。</p>
+<span id="more"></span>
+
+<hr>
+<p>直观感受:性能强劲,白天网络不错。晚高峰也能用,不爆炸。</p>
+<p>下面放测试数据:</p>
+<h3 id="综合测试"><a href="#综合测试" class="headerlink" title="综合测试"></a>综合测试</h3><p><img src="/images/2022-04-06/Bench.png"></p>
+<p>CPU 很幸运地抽中了 5900X,可以看到硬盘速度非常 OK。</p>
+<p>早上的网络情况很不错,没指望能跑满 G 口,毕竟这么多人分 10G 的口子。</p>
+<h3 id="性能测试"><a href="#性能测试" class="headerlink" title="性能测试"></a>性能测试</h3><p><img src="/images/2022-04-06/UNIXBench.jpg"></p>
+<p>不愧是 5900X,单核跑出了部分志强将近3倍的成绩!AMD, YES!</p>
+<p>开一台 2C 2G 的机子完全可以当作开发服务器使了。</p>
+<h3 id="国内网络测试"><a href="#国内网络测试" class="headerlink" title="国内网络测试"></a>国内网络测试</h3><h4 id="白天"><a href="#白天" class="headerlink" title="白天"></a>白天</h4><p><img src="/images/2022-04-06/SuperSpeed-Morning.png"></p>
+<p>三网表现均不错呢。</p>
+<h4 id="晚高峰"><a href="#晚高峰" class="headerlink" title="晚高峰"></a>晚高峰</h4><p><img src="/images/2022-04-06/SuperSpeed-Night.png"></p>
+<p>惊了,晚高峰表现很不错耶。</p>
+<h3 id="流媒体解锁"><a href="#流媒体解锁" class="headerlink" title="流媒体解锁"></a>流媒体解锁</h3><p><img src="/images/2022-04-06/Streaming.png"></p>
+<p>Virmach 只字没提流媒体,就别报多大希望。</p>
+<p>不过作为日本的 VPS 自家游戏都不能解锁……这 IP 优化的不大行。</p>
+<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>网络不错,性能强劲,如此便宜的价格可以说性价超高。</p>
+
+
+ 2022-04-06T14:15:00.000Z
+
+
+ https://blog.udon.eu.org/archives/6e832212.html
+ Deepvm 9929
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>本以为不会再购买新的机子了,今天看到有一台挺便宜的 9929 线路的机子,就买了一个月尝尝鲜。</p>
+<span id="more"></span>
+
+<p>配置:1core 512Mb 20G-SSD 500G@150Mbps</p>
+<p>价格:16元/月</p>
+<p>总结:Something strange.</p>
+<hr>
+<h3 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h3><p><img src="/images/2022-01-10/Deepvm9929.png"></p>
+<p>晚高峰测速全部都能跑满 150Mbps,看起来很不错。</p>
+<p>AS9929 是联通最新的专线,好像比 AS4837 要高级那么一点点,应该体验要比 4837 来得好一点吧,看延迟会比 4837 低十几毫秒。</p>
+<p>但实际体验下来,双向延迟会比 4837 高上 200ms 左右,speed.cloudflare.com 的成绩也不如 4837 来的好,众人迷信的 Youtube 4K 视频(电脑解码带不动 8K)跑分在 56k,和 4837 基本一致。 </p>
+<p>这就是我上述的 something strange 了。Deepvm 家的上游好像是 Spartan,按道理网络是不会差的。我猜测是这家的机子超售的稍微厉害了一点点,导致总体体验不如 Wikihost 家的 4837.</p>
+<h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p><img src="/images/2022-01-10/Deepvm9929Bench.png"></p>
+<p>跑分上来看,性能是十分不错的。性能这一块又没有超售太多???把我整懵了。</p>
+<hr>
+<p>综合价格和性能来看,这台机子的性价比还是很能打的。由于商家是去年年初刚成立的,服务器的可靠性还是未知数,也没有赠送自动备份(在折腾坏系统的时候有备份可以还原是很美妙的事情)。因此我建议月付购买这家的机子。</p>
+<p>放上 AFF:<a href="https://www.deepvm.net/aff/HVQAFOVA">https://www.deepvm.net/aff/HVQAFOVA</a></p>
+
+
+ 2022-01-10T13:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/be3776eb.html
+ WikiHost CU4837
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>今天要介绍的是一台来自 WikiHost 家的机器。</p>
+<span id="more"></span>
+
+<p>配置:Los Angeles - CU4837 Lite KVM</p>
+<p>1 core (5900X)@30% 基准性能 512Mb 20G-SSD 500G@1Gbps</p>
+<p>价格:月付 18 元(首月附加 5 元初装费)/ 年付 200 元(免初装费)</p>
+<p>总结:性能优异,网络为最大卖点。性价比很高。</p>
+<hr>
+<h3 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h3><p><img src="/images/2021-12-29/WikiHost4837.png"></p>
+<p>G 口没有虚标。</p>
+<p>测试特意挑在晚高峰跑的,与前面测评的 Cloudcone 相比,上海与广州出口三线的表现都很优秀。</p>
+<p><img src="/images/2021-12-29/WikiHostMtr.png"></p>
+<p>这台机子的去程是 163 骨干网,回程是联通专线 AS4837. 图中为回程路由 —— 进入电信的骨干网之前的流量就是 4837 承载的。</p>
+<h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p><img src="/images/2021-12-29/WikiHostBench.png"></p>
+<p>听到 30% 基准性能,你会觉得这是一台跑 apt 都嫌卡的机子。</p>
+<p>你错了!这台母鸡配备的可是 AMD R9-5900X。AMD YES!</p>
+<p>即使只有单核的三成性能,跑分依旧十分接近 WebHorizon 的单核成绩,轻松超过了 Cloudcone 的单核成绩。</p>
+<p>所以这台机器在日常使用时不会觉得 CPU 性能太差。主要的瓶颈还是在 512MB 内存上。</p>
+<hr>
+<p>综合性能和网络考虑,这台机子作为远程开发机器,安装 VSCode 跑一些轻量的项目还是很不错的。这台机器最合适的工作还是优化国内连接国外的网络情况,例如加速 GitHub 下载速度等。这个性能建立个人网站也完全足够了,140ms 较低的延迟与晚高峰的超低丢包可以带来很不错的体验。</p>
+<p>你问我推不推荐?那肯定推荐啊!</p>
+<p>鸡总的 WikiHost 是以高质量服务著称的,我在 WikiHost 购买过网站空间和流量转发,使用期间给我的体验很不错,发工单很快就能得到回复或技术人员的直接处理。缺点就是大部分机型价格都比较高(月付 30 元以上),而这台 Lite 机型以超低的价格(还附赠了7天全盘备份,前几天把机子玩炸了就用上了还原功能),一出来就被抢光了。目前还有存货的是直连韩国 VPS(高峰时段三网全炸,平时延迟非常低),有需求的朋友也可以考虑。</p>
+<p>放上 AFF:<a href="https://idc.wiki/aff.php?aff=2182">https://idc.wiki/aff.php?aff=2182</a></p>
+<p>这台机子让我非常满意,再配合 Cloudcone 的高性能、较大硬盘的机器,因此短时间内我应该不会购入新的机子,尽请期待未来的机器测评。</p>
+
+
+ 2021-12-31T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/a7b78eea.html
+ Cloudcone 双十一特价鸡
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>今天来介绍一下来自(依旧是)高性价比商家 Cloudcone 的机器。</p>
+<span id="more"></span>
+
+<p>配置:2cores 2GB 70G-SSD(RAID 10)4T@1Gbps</p>
+<p>价格:年付23.8刀</p>
+<p>来个总结:性能优异、硬盘性能尤为突出;网络质量尚可;性价比高。</p>
+<hr>
+<h3 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h3><p><img src="/images/2021-12-24/Cloudcone.jpg"></p>
+<p>测试是在晚高峰做的,只能说炸的很彻底,国内基本没有什么速度。</p>
+<p><img src="/images/2021-12-24/CloudconeMorning.png"></p>
+<p>中午再测一次,白天的网络还是很不错的。</p>
+<p><img src="/images/2021-12-24/CloudconeRoute.png"></p>
+<p>根据路由测试来看,走的是最普通的163骨干网,没有线路可言。</p>
+<p><img src="/images/2021-12-24/CloudconeMtr.png"></p>
+<p>Mtr 显示丢包率在 10-20%,延迟在 160-190ms,中规中矩。</p>
+<h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p><img src="/images/2021-12-24/CloudconeBench.jpg"></p>
+<p>单核性能说不上强,甚至打不过 Webhorizon 的 NAT 机子。</p>
+<p>但是有俩核啊~</p>
+<p>在编译一个 Node.js 项目时,其他机子要花将近一分钟,这台机子只要 32s 左右,有效提高了我的工作效率。</p>
+<hr>
+<p>一个月前,手持 Virmach 的我流下了不争气的泪水,发誓要买一台好一点的机子做开发、跑服务。</p>
+<p>于是我就在网上找性价比商家了(笑)。</p>
+<p>有人推荐我买 GreenCloud 的机子,500G 大盘鸡,价格也不贵。我当时就很心动,几欲下单,最后上网一查,发现 GC 家的 IP 段不大好,会被 Google 查岗(验证是否是机器人),遂放弃。</p>
+<p>持币徘徊的我遇见了 Cloudcone。网上一查,没有什么黑历史,我就和同学“拼鸡”下单了。(没错,我只花了一半的钱 XD)</p>
+<p>24刀的价格平常只能买到 1c 1G 的机子,双十一特价可以让性能翻倍,性价比一下子上来了。70G 的硬盘也可以用来挂一些需要存储数据的服务(例如现在正在跑的 H@H)。</p>
+<p>至于网络嘛……因为没有特殊线路,晚上还是挺糟糕的,有时候 SSH 都会断连。</p>
+<p>不得不提的是,就在我开通机器一周后,Cloudcone 貌似出现了部分机器数据丢失的问题……因为范围不大,似乎没有引起什么讨论。因为没有附带异地备份的功能,使用高性价机器要承担一定风险,重要数据就不要存放在此类没有备份的 VPS 中。</p>
+<hr>
+<p>总结来说,如果遇到活动,Cloudcone 的机子还是很值得入手的!</p>
+<p>正好圣诞节促销到了,力度比双十一还大一点点。放上 AFF Link~</p>
+<p><a href="https://app.cloudcone.com/?ref=7447">https://app.cloudcone.com/?ref=7447</a></p>
+
+
+ 2021-12-24T14:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/9732665c.html
+ WebHorizon NAT JP
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>第一篇正式测评:来自印度的性价比商家 WebHorizon 的特价 NAT 小鸡。</p>
+<span id="more"></span>
+
+<p>配置:1core 256Mb 4G-HDD 500G 1Gbps,机房位于日本,有 IPv6,仅有 NAT IPv4.</p>
+<p>价格:……4刀一年。</p>
+<p>先来个总结:买了吃灰的机器。</p>
+<h3 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h3><p><img src="/images/2021-12-23/WebHorizonJP.png"></p>
+<p>小鸡号称的 G 口是货真价实的。</p>
+<p>没有独立 IPv4 带来的限制很大。WebHorizon 提供了四个 IPv4 地址用于转发 VPS 的端口。但经我的随机(随便)测试,我开不到任何能用的 TCP 转发端口,仅能开到 HTTP/HTTPS 的转发(80/443 端口)。</p>
+<p>因此,想要连接这台机器需要用到 IPv6,能提供的服务也仅限 HTTP。</p>
+<p>NAT 机器还有一点不好:若四个转发地址都被墙了,那这台机器在国内就没得用了。目前四个地址被墙了一个。</p>
+<p><img src="/images/2021-12-23/WebHorizonJPRoute.png"></p>
+<p>从国内访问的线路是双向 NTT。白天效果尚可,晚高峰时间炸得厉害。全天高延迟 160-240ms。网络只是可以用的级别。</p>
+<h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p><img src="/images/2021-12-23/WebHorizonBench.png"></p>
+<p>从跑分上来看,这台机子的 CPU 性能出奇的可以。</p>
+<p><del>可能是买来的都吃灰了,没人抢性能</del></p>
+<p>硬盘的表现则是中规中矩,能用级别。</p>
+<hr>
+<p>总结上述来看,这台机子挺不行的。那为什么我买了呢?</p>
+<p>看到4刀一年的价格,我的捡垃圾之魂就按耐不住了啊!</p>
+<p><del>我现在只是非常的后悔。</del></p>
+<p>256Mb 的超迷你内存让这台机器承担不了什么任务。挂一点小脚本大概能吃得消。适合钱包真的很空/垃圾佬购买。</p>
+<p>虽说不推荐购买,我还是厚着脸皮放一个 AFF 连接:</p>
+<p><a href="https://my.webhorizon.in/order/forms/a/MzQ1Nw==">https://my.webhorizon.in/order/forms/a/MzQ1Nw==</a></p>
+<p>P.S. 4刀一年是黑五期间的 50% OFF 优惠。</p>
+<h3 id="附赠"><a href="#附赠" class="headerlink" title="附赠"></a>附赠</h3><p>这台机器的虚拟化技术是 OpenVZ,也带来了一些不足:</p>
+<ol>
+<li>超售严重,高峰期性能可能不如预期。</li>
+<li>与母鸡共用内核,因此无法自行修改内核。有一些服务,例如 BBR、锐速等,需要修改内核才能开启,在 OVZ 的机型上就无法使用。有此类需求可以购买 KVM 虚拟化的 VPS。母鸡内核可能没有开启某些功能,例如我在尝试通过 RClone 挂载虚拟硬盘时,被告知内核未开启 fuse,需要联系服务商开启(我没有去联系,不保证商家会同意开启)。</li>
+</ol>
+
+
+ 2021-12-23T15:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/6f963444.html
+ Virmach 7.5刀年付特价机
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>虽然我在前文的结尾奉劝大家不要买太廉价的机器……</p>
+<p>但是我一介学生是真的穷啊!</p>
+<p>所以今天来谈一谈一家以性价比著称的 VPS 服务商:Virmach。</p>
+<span id="more"></span>
+
+<hr>
+<p>开门见山,先说直观感受:网络很一般、机器性能差。机器适合穷苦学生党使用。</p>
+<p>我开的机型是 Virmach 黑五特价机型:年付7.5刀,1core 256Mb(免费升级至 384mb)10Gb-SSD 1Gbps。</p>
+<p>这台机器在上个月到期,我又有了新欢就没有续费,因此还是没有跑分截图。</p>
+<p>这机子的性能差到 apt 都嫌慢,网络情况也很是糟糕,应该就是最普通的国际线路 + 国内163,丢包率高,全天 SSH 连接皆不稳定。</p>
+<p>不过一年50块左右的价格也没什么好抱怨的了,用来挂点需要访问世界互联网的服务,例如 Telegram Bot、RSSHub 等;或者建个站,套一层 Cloudflare CDN 之后众生平等,可以说勉强能用。</p>
+<p>较小的内存有时也会带来不便,可能遇到部分应用、组件无法安装/编译的情况。据观察 RSSHub 运行时即占用 300Mb 左右内存,因此我还是建议内存在 512Mb 以上的 VPS,1Gb 为佳。</p>
+<p>多提一嘴,现在不少应用都能以 PaaS 的形式部署在诸如 Heroku 的平台上,完全可以做到免费运行一个服务,没有必要租用 VPS、配置环境。</p>
+<p>VPS 的硬盘也是一个挺重要的指标。按我的经验,VPS 至少要有 20G 的硬盘才够用。Debian 11 系统 + LNMP + NodeJS 环境就占用了 7-8G 的硬盘空间,剩下的 10G 可以用于存储应用数据。至于硬盘性能,只要不是太可怜,影响都不大。</p>
+
+
+ 2021-12-22T09:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/afe45e8a.html
+ 我的第一台 VPS
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>开一个新坑,测评一下所有我用过的小鸡,<del>如果能赚到点 AFF 就更好了。</del></p>
+<span id="more"></span>
+
+<p>来谈一谈我的第一台 VPS 吧。</p>
+<p>我的第一台 VPS 购于初中,仅用了一年便没有再续费了。因此没有任何性能、网络方面的测评,仅剩下我的感想了。没有续费的原因很简单:太™垃圾了。</p>
+<p><del>年幼无知</del>,对 VPS 性能、路由线路都一无所知的我被超低的价格蒙骗,入手了一台来自无名国人商家的小鸡。</p>
+<p>印象中,那是一台 1core 512Mb 的小鸡,位于太平洋彼岸、再跨过美国全境的美东海岸。VPS 的网络差到经常连接不上 SSH。</p>
+<p>说了这么多(并不多),就是想奉劝各位:别贪便宜。</p>
+
+
+ 2021-12-21T15:30:00.000Z
+
+
diff --git "a/tag/\345\260\217\351\270\241\346\265\213\350\257\204/feed.json" "b/tag/\345\260\217\351\270\241\346\265\213\350\257\204/feed.json"
new file mode 100644
index 00000000..748d4af8
--- /dev/null
+++ "b/tag/\345\260\217\351\270\241\346\265\213\350\257\204/feed.json"
@@ -0,0 +1,78 @@
+{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "カレーうどん屋 • All posts by \"小鸡测评\" tag",
+ "description": "カレーうどん屋.",
+ "home_page_url": "https://blog.udon.eu.org",
+ "items": [
+ {
+ "id": "https://blog.udon.eu.org/archives/28fa0729.html",
+ "url": "https://blog.udon.eu.org/archives/28fa0729.html",
+ "title": "Virmach Japan",
+ "date_published": "2022-04-06T14:15:00.000Z",
+ "content_html": "老朋友 Virmach 最近搞了场日本的预售。上游是 XTom,线路走的 IIJ。配置从 1C 384M 到 2C 2.5G 都有,价格则是 11.11刀/年 起(预售打 8 折, 8.89刀/年起)。
\n我买的这台是折后 9.7刀/年,1C 768M 20G NVME 2T 双向流量的配置。
\n \n\n \n直观感受:性能强劲,白天网络不错。晚高峰也能用,不爆炸。
\n下面放测试数据:
\n综合测试
\nCPU 很幸运地抽中了 5900X,可以看到硬盘速度非常 OK。
\n早上的网络情况很不错,没指望能跑满 G 口,毕竟这么多人分 10G 的口子。
\n性能测试
\n不愧是 5900X,单核跑出了部分志强将近3倍的成绩!AMD, YES!
\n开一台 2C 2G 的机子完全可以当作开发服务器使了。
\n国内网络测试 白天
\n三网表现均不错呢。
\n晚高峰
\n惊了,晚高峰表现很不错耶。
\n流媒体解锁
\nVirmach 只字没提流媒体,就别报多大希望。
\n不过作为日本的 VPS 自家游戏都不能解锁……这 IP 优化的不大行。
\n总结 网络不错,性能强劲,如此便宜的价格可以说性价超高。
\n",
+ "tags": [
+ "小鸡测评"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/6e832212.html",
+ "url": "https://blog.udon.eu.org/archives/6e832212.html",
+ "title": "Deepvm 9929",
+ "date_published": "2022-01-10T13:00:00.000Z",
+ "content_html": "本以为不会再购买新的机子了,今天看到有一台挺便宜的 9929 线路的机子,就买了一个月尝尝鲜。
\n \n\n配置:1core 512Mb 20G-SSD 500G@150Mbps
\n价格:16元/月
\n总结:Something strange.
\n \n网络
\n晚高峰测速全部都能跑满 150Mbps,看起来很不错。
\nAS9929 是联通最新的专线,好像比 AS4837 要高级那么一点点,应该体验要比 4837 来得好一点吧,看延迟会比 4837 低十几毫秒。
\n但实际体验下来,双向延迟会比 4837 高上 200ms 左右,speed.cloudflare.com 的成绩也不如 4837 来的好,众人迷信的 Youtube 4K 视频(电脑解码带不动 8K)跑分在 56k,和 4837 基本一致。
\n这就是我上述的 something strange 了。Deepvm 家的上游好像是 Spartan,按道理网络是不会差的。我猜测是这家的机子超售的稍微厉害了一点点,导致总体体验不如 Wikihost 家的 4837.
\n性能
\n跑分上来看,性能是十分不错的。性能这一块又没有超售太多???把我整懵了。
\n \n综合价格和性能来看,这台机子的性价比还是很能打的。由于商家是去年年初刚成立的,服务器的可靠性还是未知数,也没有赠送自动备份(在折腾坏系统的时候有备份可以还原是很美妙的事情)。因此我建议月付购买这家的机子。
\n放上 AFF:https://www.deepvm.net/aff/HVQAFOVA
\n",
+ "tags": [
+ "小鸡测评"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/be3776eb.html",
+ "url": "https://blog.udon.eu.org/archives/be3776eb.html",
+ "title": "WikiHost CU4837",
+ "date_published": "2021-12-31T04:30:00.000Z",
+ "content_html": "今天要介绍的是一台来自 WikiHost 家的机器。
\n \n\n配置:Los Angeles - CU4837 Lite KVM
\n1 core (5900X)@30% 基准性能 512Mb 20G-SSD 500G@1Gbps
\n价格:月付 18 元(首月附加 5 元初装费)/ 年付 200 元(免初装费)
\n总结:性能优异,网络为最大卖点。性价比很高。
\n \n网络
\nG 口没有虚标。
\n测试特意挑在晚高峰跑的,与前面测评的 Cloudcone 相比,上海与广州出口三线的表现都很优秀。
\n
\n这台机子的去程是 163 骨干网,回程是联通专线 AS4837. 图中为回程路由 —— 进入电信的骨干网之前的流量就是 4837 承载的。
\n性能
\n听到 30% 基准性能,你会觉得这是一台跑 apt 都嫌卡的机子。
\n你错了!这台母鸡配备的可是 AMD R9-5900X。AMD YES!
\n即使只有单核的三成性能,跑分依旧十分接近 WebHorizon 的单核成绩,轻松超过了 Cloudcone 的单核成绩。
\n所以这台机器在日常使用时不会觉得 CPU 性能太差。主要的瓶颈还是在 512MB 内存上。
\n \n综合性能和网络考虑,这台机子作为远程开发机器,安装 VSCode 跑一些轻量的项目还是很不错的。这台机器最合适的工作还是优化国内连接国外的网络情况,例如加速 GitHub 下载速度等。这个性能建立个人网站也完全足够了,140ms 较低的延迟与晚高峰的超低丢包可以带来很不错的体验。
\n你问我推不推荐?那肯定推荐啊!
\n鸡总的 WikiHost 是以高质量服务著称的,我在 WikiHost 购买过网站空间和流量转发,使用期间给我的体验很不错,发工单很快就能得到回复或技术人员的直接处理。缺点就是大部分机型价格都比较高(月付 30 元以上),而这台 Lite 机型以超低的价格(还附赠了7天全盘备份,前几天把机子玩炸了就用上了还原功能),一出来就被抢光了。目前还有存货的是直连韩国 VPS(高峰时段三网全炸,平时延迟非常低),有需求的朋友也可以考虑。
\n放上 AFF:https://idc.wiki/aff.php?aff=2182
\n这台机子让我非常满意,再配合 Cloudcone 的高性能、较大硬盘的机器,因此短时间内我应该不会购入新的机子,尽请期待未来的机器测评。
\n",
+ "tags": [
+ "小鸡测评"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/a7b78eea.html",
+ "url": "https://blog.udon.eu.org/archives/a7b78eea.html",
+ "title": "Cloudcone 双十一特价鸡",
+ "date_published": "2021-12-24T14:00:00.000Z",
+ "content_html": "今天来介绍一下来自(依旧是)高性价比商家 Cloudcone 的机器。
\n \n\n配置:2cores 2GB 70G-SSD(RAID 10)4T@1Gbps
\n价格:年付23.8刀
\n来个总结:性能优异、硬盘性能尤为突出;网络质量尚可;性价比高。
\n \n网络
\n测试是在晚高峰做的,只能说炸的很彻底,国内基本没有什么速度。
\n
\n中午再测一次,白天的网络还是很不错的。
\n
\n根据路由测试来看,走的是最普通的163骨干网,没有线路可言。
\n
\nMtr 显示丢包率在 10-20%,延迟在 160-190ms,中规中矩。
\n性能
\n单核性能说不上强,甚至打不过 Webhorizon 的 NAT 机子。
\n但是有俩核啊~
\n在编译一个 Node.js 项目时,其他机子要花将近一分钟,这台机子只要 32s 左右,有效提高了我的工作效率。
\n \n一个月前,手持 Virmach 的我流下了不争气的泪水,发誓要买一台好一点的机子做开发、跑服务。
\n于是我就在网上找性价比商家了(笑)。
\n有人推荐我买 GreenCloud 的机子,500G 大盘鸡,价格也不贵。我当时就很心动,几欲下单,最后上网一查,发现 GC 家的 IP 段不大好,会被 Google 查岗(验证是否是机器人),遂放弃。
\n持币徘徊的我遇见了 Cloudcone。网上一查,没有什么黑历史,我就和同学“拼鸡”下单了。(没错,我只花了一半的钱 XD)
\n24刀的价格平常只能买到 1c 1G 的机子,双十一特价可以让性能翻倍,性价比一下子上来了。70G 的硬盘也可以用来挂一些需要存储数据的服务(例如现在正在跑的 H@H)。
\n至于网络嘛……因为没有特殊线路,晚上还是挺糟糕的,有时候 SSH 都会断连。
\n不得不提的是,就在我开通机器一周后,Cloudcone 貌似出现了部分机器数据丢失的问题……因为范围不大,似乎没有引起什么讨论。因为没有附带异地备份的功能,使用高性价机器要承担一定风险,重要数据就不要存放在此类没有备份的 VPS 中。
\n \n总结来说,如果遇到活动,Cloudcone 的机子还是很值得入手的!
\n正好圣诞节促销到了,力度比双十一还大一点点。放上 AFF Link~
\nhttps://app.cloudcone.com/?ref=7447
\n",
+ "tags": [
+ "小鸡测评"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/9732665c.html",
+ "url": "https://blog.udon.eu.org/archives/9732665c.html",
+ "title": "WebHorizon NAT JP",
+ "date_published": "2021-12-23T15:00:00.000Z",
+ "content_html": "第一篇正式测评:来自印度的性价比商家 WebHorizon 的特价 NAT 小鸡。
\n \n\n配置:1core 256Mb 4G-HDD 500G 1Gbps,机房位于日本,有 IPv6,仅有 NAT IPv4.
\n价格:……4刀一年。
\n先来个总结:买了吃灰的机器。
\n网络
\n小鸡号称的 G 口是货真价实的。
\n没有独立 IPv4 带来的限制很大。WebHorizon 提供了四个 IPv4 地址用于转发 VPS 的端口。但经我的随机(随便)测试,我开不到任何能用的 TCP 转发端口,仅能开到 HTTP/HTTPS 的转发(80/443 端口)。
\n因此,想要连接这台机器需要用到 IPv6,能提供的服务也仅限 HTTP。
\nNAT 机器还有一点不好:若四个转发地址都被墙了,那这台机器在国内就没得用了。目前四个地址被墙了一个。
\n
\n从国内访问的线路是双向 NTT。白天效果尚可,晚高峰时间炸得厉害。全天高延迟 160-240ms。网络只是可以用的级别。
\n性能
\n从跑分上来看,这台机子的 CPU 性能出奇的可以。
\n可能是买来的都吃灰了,没人抢性能
\n硬盘的表现则是中规中矩,能用级别。
\n \n总结上述来看,这台机子挺不行的。那为什么我买了呢?
\n看到4刀一年的价格,我的捡垃圾之魂就按耐不住了啊!
\n我现在只是非常的后悔。
\n256Mb 的超迷你内存让这台机器承担不了什么任务。挂一点小脚本大概能吃得消。适合钱包真的很空/垃圾佬购买。
\n虽说不推荐购买,我还是厚着脸皮放一个 AFF 连接:
\nhttps://my.webhorizon.in/order/forms/a/MzQ1Nw==
\nP.S. 4刀一年是黑五期间的 50% OFF 优惠。
\n附赠 这台机器的虚拟化技术是 OpenVZ,也带来了一些不足:
\n\n超售严重,高峰期性能可能不如预期。 \n与母鸡共用内核,因此无法自行修改内核。有一些服务,例如 BBR、锐速等,需要修改内核才能开启,在 OVZ 的机型上就无法使用。有此类需求可以购买 KVM 虚拟化的 VPS。母鸡内核可能没有开启某些功能,例如我在尝试通过 RClone 挂载虚拟硬盘时,被告知内核未开启 fuse,需要联系服务商开启(我没有去联系,不保证商家会同意开启)。 \n \n",
+ "tags": [
+ "小鸡测评"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/6f963444.html",
+ "url": "https://blog.udon.eu.org/archives/6f963444.html",
+ "title": "Virmach 7.5刀年付特价机",
+ "date_published": "2021-12-22T09:30:00.000Z",
+ "content_html": "虽然我在前文的结尾奉劝大家不要买太廉价的机器……
\n但是我一介学生是真的穷啊!
\n所以今天来谈一谈一家以性价比著称的 VPS 服务商:Virmach。
\n \n\n \n开门见山,先说直观感受:网络很一般、机器性能差。机器适合穷苦学生党使用。
\n我开的机型是 Virmach 黑五特价机型:年付7.5刀,1core 256Mb(免费升级至 384mb)10Gb-SSD 1Gbps。
\n这台机器在上个月到期,我又有了新欢就没有续费,因此还是没有跑分截图。
\n这机子的性能差到 apt 都嫌慢,网络情况也很是糟糕,应该就是最普通的国际线路 + 国内163,丢包率高,全天 SSH 连接皆不稳定。
\n不过一年50块左右的价格也没什么好抱怨的了,用来挂点需要访问世界互联网的服务,例如 Telegram Bot、RSSHub 等;或者建个站,套一层 Cloudflare CDN 之后众生平等,可以说勉强能用。
\n较小的内存有时也会带来不便,可能遇到部分应用、组件无法安装/编译的情况。据观察 RSSHub 运行时即占用 300Mb 左右内存,因此我还是建议内存在 512Mb 以上的 VPS,1Gb 为佳。
\n多提一嘴,现在不少应用都能以 PaaS 的形式部署在诸如 Heroku 的平台上,完全可以做到免费运行一个服务,没有必要租用 VPS、配置环境。
\nVPS 的硬盘也是一个挺重要的指标。按我的经验,VPS 至少要有 20G 的硬盘才够用。Debian 11 系统 + LNMP + NodeJS 环境就占用了 7-8G 的硬盘空间,剩下的 10G 可以用于存储应用数据。至于硬盘性能,只要不是太可怜,影响都不大。
\n",
+ "tags": [
+ "小鸡测评"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/afe45e8a.html",
+ "url": "https://blog.udon.eu.org/archives/afe45e8a.html",
+ "title": "我的第一台 VPS",
+ "date_published": "2021-12-21T15:30:00.000Z",
+ "content_html": "开一个新坑,测评一下所有我用过的小鸡,如果能赚到点 AFF 就更好了。
\n \n\n来谈一谈我的第一台 VPS 吧。
\n我的第一台 VPS 购于初中,仅用了一年便没有再续费了。因此没有任何性能、网络方面的测评,仅剩下我的感想了。没有续费的原因很简单:太™垃圾了。
\n年幼无知,对 VPS 性能、路由线路都一无所知的我被超低的价格蒙骗,入手了一台来自无名国人商家的小鸡。
\n印象中,那是一台 1core 512Mb 的小鸡,位于太平洋彼岸、再跨过美国全境的美东海岸。VPS 的网络差到经常连接不上 SSH。
\n说了这么多(并不多),就是想奉劝各位:别贪便宜。
\n",
+ "tags": [
+ "小鸡测评"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/tag/\345\260\217\351\270\241\346\265\213\350\257\204/rss.xml" "b/tag/\345\260\217\351\270\241\346\265\213\350\257\204/rss.xml"
new file mode 100644
index 00000000..7cc1c952
--- /dev/null
+++ "b/tag/\345\260\217\351\270\241\346\265\213\350\257\204/rss.xml"
@@ -0,0 +1,221 @@
+
+
+
+ カレーうどん屋 • Posts by "小鸡测评" tag
+ https://blog.udon.eu.org
+ カレーうどん屋.
+ zh-CN
+ Wed, 06 Apr 2022 22:15:00 +0800
+ Wed, 06 Apr 2022 22:15:00 +0800
+ 教程
+ 随笔
+ 软件
+ 小鸡测评
+ 3D 打印
+ 嵌入式开发
+ GitHub Actions
+ Hexo
+ DIY
+ -
+
https://blog.udon.eu.org/archives/28fa0729.html
+ Virmach Japan
+ https://blog.udon.eu.org/archives/28fa0729.html
+ 小鸡测评
+ Wed, 06 Apr 2022 22:15:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/6e832212.html
+ Deepvm 9929
+ https://blog.udon.eu.org/archives/6e832212.html
+ 小鸡测评
+ Mon, 10 Jan 2022 21:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/be3776eb.html
+ WikiHost CU4837
+ https://blog.udon.eu.org/archives/be3776eb.html
+ 小鸡测评
+ Fri, 31 Dec 2021 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/a7b78eea.html
+ Cloudcone 双十一特价鸡
+ https://blog.udon.eu.org/archives/a7b78eea.html
+ 小鸡测评
+ Fri, 24 Dec 2021 22:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/9732665c.html
+ WebHorizon NAT JP
+ https://blog.udon.eu.org/archives/9732665c.html
+ 小鸡测评
+ Thu, 23 Dec 2021 23:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/6f963444.html
+ Virmach 7.5刀年付特价机
+ https://blog.udon.eu.org/archives/6f963444.html
+ 小鸡测评
+ Wed, 22 Dec 2021 17:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/afe45e8a.html
+ 我的第一台 VPS
+ https://blog.udon.eu.org/archives/afe45e8a.html
+ 小鸡测评
+ Tue, 21 Dec 2021 23:30:00 +0800
+
+
+
+
diff --git "a/tag/\345\265\214\345\205\245\345\274\217\345\274\200\345\217\221/atom.xml" "b/tag/\345\265\214\345\205\245\345\274\217\345\274\200\345\217\221/atom.xml"
new file mode 100644
index 00000000..ce7f5d0d
--- /dev/null
+++ "b/tag/\345\265\214\345\205\245\345\274\217\345\274\200\345\217\221/atom.xml"
@@ -0,0 +1,101 @@
+
+
+ https://blog.udon.eu.org
+ カレーうどん屋 • Posts by "嵌入式开发" tag
+
+ 2022-03-26T16:15:00.000Z
+
+
+
+
+
+
+
+
+
+
+ https://blog.udon.eu.org/archives/9b58c98e.html
+ 合宙 EPS32-C3 把玩记录(二):WiFi 与一个 Web 程序
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="了解一下相关的库"><a href="#了解一下相关的库" class="headerlink" title="了解一下相关的库"></a>了解一下相关的库</h2><ul>
+<li><h3 id="串口通信"><a href="#串口通信" class="headerlink" title="串口通信"></a>串口通信</h3></li>
+</ul>
+<p>这个库是自带的,不需要引入。</p>
+<p>据我的理解,单片机的串口就是控制台程序的控制台,可以返回一些信息给上位机。</p>
+<p>会用到的几个指令:</p>
+<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c">Serial.begin(Baudrate); <span class="hljs-comment">//参数为串口通信的波特率</span><br>Serial.end();<br>Serial.read(); <span class="hljs-comment">//读取串口收到数据的第一个字节</span><br>Serial.peek(); <span class="hljs-comment">//读取串口数据中下一字节的内容</span><br>Serial.flush(); <span class="hljs-comment">//清空缓冲区</span><br>Serial.print/println(); <span class="hljs-comment">//不用多说</span><br>Serial.write(); <span class="hljs-comment">//写二进制数据</span><br></code></pre></td></tr></table></figure>
+
+<ul>
+<li><h3 id="WiFi-h"><a href="#WiFi-h" class="headerlink" title="WiFi.h"></a>WiFi.h</h3></li>
+</ul>
+<p><code>#include <WiFi.h></code></p>
+<h4 id="AP(接入点)-Mode"><a href="#AP(接入点)-Mode" class="headerlink" title="AP(接入点) Mode"></a>AP(接入点) Mode</h4><p>创建一个接入点。</p>
+<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c">WiFi.mode(WiFi_AP); <span class="hljs-comment">//设置工作在 AP 模式</span><br>WiFi.softAPConfig(local_IP, gateway, subnet);<br><span class="hljs-comment">//定义本机 IP(这个不大确定)、网关 IP 和子网掩码</span><br><span class="hljs-comment">//IPAddress 数据类型格式:IPAddress local_IP(192,168,4,22);</span><br>WiFi.softAP(SSID,PASSWD); <span class="hljs-comment">//启动 AP,参数不多解释,返回 bool </span><br></code></pre></td></tr></table></figure>
+
+<p>更多函数见 <a href="https://blog.csdn.net/Naisu_kun/article/details/86165403#_28">WiFi.h AP 常用方法说明</a></p>
+<h4 id="STA(站点)-Mode"><a href="#STA(站点)-Mode" class="headerlink" title="STA(站点) Mode"></a>STA(站点) Mode</h4><p>接入一个 AP。</p>
+<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c">WiFi.mode(WIFI_STA); <span class="hljs-comment">//设置工作在 STA 模式</span><br>WiFi.start(SSID,PASSWD) <span class="hljs-comment">//连接至 AP,参数不多解释</span><br>Serial.println(WiFi.localIP()); <span class="hljs-comment">//打印本机 IP,省的去路由器管理界面看</span><br></code></pre></td></tr></table></figure>
+
+<p>更多函数见 <a href="https://blog.csdn.net/Naisu_kun/article/details/86165403#_130">WiFi.h STA 常用方法说明</a></p>
+<ul>
+<li><h2 id="WebServer-h"><a href="#WebServer-h" class="headerlink" title="WebServer.h"></a>WebServer.h</h2></li>
+</ul>
+<p><code>#include <WebServer.h></code></p>
+<p>创建一个简单的网站服务器。真的很简单。</p>
+<p>一个个函数讲有点难理解,我放在这节的例程里面说明。</p>
+<h2 id="写一个测试程序吧"><a href="#写一个测试程序吧" class="headerlink" title="写一个测试程序吧"></a>写一个测试程序吧</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><WiFi.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><WebServer.h></span></span><br><span class="hljs-comment">//引入所需要的两个库</span><br><br><span class="hljs-type">const</span> <span class="hljs-type">char</span> *SSID = <span class="hljs-string">"YOUR_SSID"</span>;<br><span class="hljs-type">const</span> <span class="hljs-type">char</span> *PASSWORD = <span class="hljs-string">"YOUR_PASSWORD"</span>;<br><br>WebServer <span class="hljs-title function_">server</span><span class="hljs-params">(<span class="hljs-number">80</span>)</span>; <span class="hljs-comment">//网站服务器将运行在 80 端口</span><br><br><span class="hljs-type">void</span> <span class="hljs-title function_">handleIndex</span><span class="hljs-params">()</span> <span class="hljs-comment">//收到 HTTP 请求的回调函数</span><br>{<br> server.send(<span class="hljs-number">200</span>, <span class="hljs-string">"text/plain"</span>, <span class="hljs-string">"Hello from ESP32!"</span>);<br> <span class="hljs-comment">//发送 HTTP 相应,内容分别为:状态码,Content-Type,响应体</span><br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">setup</span><span class="hljs-params">()</span><br>{<br> Serial.begin(<span class="hljs-number">9600</span>); <span class="hljs-comment">//开启串口,波特率设置为 9600</span><br> Serial.println();<br><br> WiFi.mode(WIFI_STA); <span class="hljs-comment">//设置工作在 STA 模式</span><br> WiFi.begin(SSID, PASSWORD); <span class="hljs-comment">//连接至指定 AP</span><br> <span class="hljs-keyword">while</span> (WiFi.status() != WL_CONNECTED) <span class="hljs-comment">//等待网络连接成功</span><br> {<br> delay(<span class="hljs-number">500</span>);<br> Serial.print(<span class="hljs-string">"."</span>); <span class="hljs-comment">//将连接信息输出至串口</span><br> }<br> Serial.println(<span class="hljs-string">"WiFi connected!"</span>);<br><br> Serial.println(<span class="hljs-string">"IP address: "</span>);<br> Serial.println(WiFi.localIP()); <span class="hljs-comment">//打印本机 IP</span><br><br> server.on(<span class="hljs-string">"/"</span>, handleIndex); <span class="hljs-comment">//注册链接(类似与注册一个路由),并选择回调函数</span><br> <span class="hljs-comment">//同样的,还可以注册别的链接,如</span><br> <span class="hljs-comment">//server.on("/test", handleIndexTest);</span><br> <br> server.begin(); <span class="hljs-comment">//开启 HTTP 服务器</span><br> Serial.println(<span class="hljs-string">"WebServer begin!"</span>);<br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">loop</span><span class="hljs-params">()</span><br>{<br> server.handleClient(); <span class="hljs-comment">//不断相应 HTTP 请求</span><br>}<br></code></pre></td></tr></table></figure>
+
+<p>访问串口返回的 IP,即可看到 <code>Hello from ESP32!</code> 这句话啦。</p>
+<h2 id="还有个-Web-Server-叫-ESPAsyncWebServer"><a href="#还有个-Web-Server-叫-ESPAsyncWebServer" class="headerlink" title="还有个 Web Server 叫 ESPAsyncWebServer"></a>还有个 Web Server 叫 ESPAsyncWebServer</h2><p>自带的 WebServer 是同步的,只支持处理一个连接。对于这种体量的机器其实足够了。</p>
+<p>顺便学习一下一个第三方库吧。</p>
+<h3 id="添加库"><a href="#添加库" class="headerlink" title="添加库"></a>添加库</h3><p>对于这款 ESP32,需要下载并导入两个库(源码 ZIP 即可):</p>
+<p><a href="https://github.com/me-no-dev/AsyncTCP">me-no-dev/AsyncTCP: Async TCP Library for ESP32</a></p>
+<p><a href="https://github.com/me-no-dev/ESPAsyncWebServer">me-no-dev/ESPAsyncWebServer: Async Web Server for ESP8266 and ESP32</a></p>
+<p>在 Arduino 的<code>项目 > 加载库 > 添加 .ZIP 库</code>中导入这两个库。</p>
+<h3 id="用-ESPAsyncWebServer-重写刚刚的例程吧"><a href="#用-ESPAsyncWebServer-重写刚刚的例程吧" class="headerlink" title="用 ESPAsyncWebServer 重写刚刚的例程吧"></a>用 ESPAsyncWebServer 重写刚刚的例程吧</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><WiFi.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><ESPAsyncWebServer.h></span></span><br><span class="hljs-comment">//注意替换为新的库</span><br><br><span class="hljs-type">const</span> <span class="hljs-type">char</span> *SSID = <span class="hljs-string">"YOUR_SSID"</span>;<br><span class="hljs-type">const</span> <span class="hljs-type">char</span> *PASSWORD = <span class="hljs-string">"YOUR_PASSWORD"</span>;<br><br>ESPAsyncWebServer <span class="hljs-title function_">server</span><span class="hljs-params">(<span class="hljs-number">80</span>)</span>; <span class="hljs-comment">//同样替换为新的对象</span><br><br><span class="hljs-type">void</span> <span class="hljs-title function_">handleIndex</span><span class="hljs-params">(AsyncWebServerRequest *request)</span> <span class="hljs-comment">//回调函数有更改</span><br>{<br> request->send(<span class="hljs-number">200</span>, <span class="hljs-string">"text/plain"</span>, <span class="hljs-string">"Hello, world!"</span>);<br> <span class="hljs-comment">//发送 HTTP 相应,内容相同</span><br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">setup</span><span class="hljs-params">()</span><br>{<br> Serial.begin(<span class="hljs-number">9600</span>); <span class="hljs-comment">//开启串口,波特率设置为 9600</span><br> Serial.println();<br><br> WiFi.mode(WIFI_STA); <span class="hljs-comment">//设置工作在 STA 模式</span><br> WiFi.begin(SSID, PASSWORD); <span class="hljs-comment">//连接至指定 AP</span><br> <span class="hljs-keyword">while</span> (WiFi.status() != WL_CONNECTED) <span class="hljs-comment">//等待网络连接成功</span><br> {<br> delay(<span class="hljs-number">500</span>);<br> Serial.print(<span class="hljs-string">"."</span>); <span class="hljs-comment">//将连接信息输出至串口</span><br> }<br> Serial.println(<span class="hljs-string">"WiFi connected!"</span>);<br><br> Serial.println(<span class="hljs-string">"IP address: "</span>);<br> Serial.println(WiFi.localIP()); <span class="hljs-comment">//打印本机 IP</span><br><br> server.on(<span class="hljs-string">"/"</span>, handleIndex); <span class="hljs-comment">//注册链接(类似与注册一个路由),并选择回调函数</span><br> <span class="hljs-comment">//同样的,还可以注册别的链接,如</span><br> <span class="hljs-comment">//server.on("/test", handleIndexTest);</span><br> <br> server.begin(); <span class="hljs-comment">//开启 HTTP 服务器</span><br> Serial.println(<span class="hljs-string">"WebServer begin!"</span>);<br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">loop</span><span class="hljs-params">()</span><br>{<br><span class="hljs-comment">//不用在这儿监听 HTTP 请求了</span><br>}<br></code></pre></td></tr></table></figure>
+
+<p>理论上来讲,上面的代码应该是正确的……</p>
+<p>但 Arduino 在编译的时候报错,内容是 ESPAsyncWebServer 库中的某些代码。</p>
+<p>有待我弄清楚出错的原因。</p>
+
+
+
+ 2022-03-26T16:15:00.000Z
+
+
+ https://blog.udon.eu.org/archives/7f7bd4a5.html
+ 合宙 EPS32-C3 把玩记录(一):环境搭建与第一个程序
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>为了贯彻本博客最重要的关键词:<strong>性价比</strong>,我看到性价如此高的开发板,想都没想就剁手了。</p>
+<p>嘛其实也没有这么冲动,我在购买 3D 打印机之后就一直在计划着做一些网上现成的电子项目,但碍于这段时间 MCU 和大尺寸屏幕价格的飙升,一直没能开始动手。</p>
+<p>正好最近我学习了 iPad 上的 3D 建模软件 Sharp3D,项目的外壳建模变得有可能;又遇到了这块便宜的板子,立即开工!</p>
+<p>因为1.8寸的 TFT 显示屏还没到货,3D 建模就先放一边,先来研究一下这块开发板。</p>
+<h3 id="事先声明"><a href="#事先声明" class="headerlink" title="事先声明"></a>事先声明</h3><p>本教程是我一边从零开始学习嵌入式开发一边作成的,有逻辑混乱、内容浅显和成吨的错误,还请已经熟悉嵌入式开发的大佬多多包涵与斧正)</p>
+<h2 id="问题:什么?开发环境不是按语言分的嘛?"><a href="#问题:什么?开发环境不是按语言分的嘛?" class="headerlink" title="问题:什么?开发环境不是按语言分的嘛?"></a>问题:什么?开发环境不是按语言分的嘛?</h2><p>在开始学习嵌入式开发之前,我简单地认为嵌入式开发因语言而已,分为用 C/Cpp 开发(Arduino)和用 Python 开发(MicroPython)。</p>
+<p>直到我遇见了 ESP-IDF 这个东西。</p>
+<p>啥啊,为啥这家伙用的也是 C,代码我还一点都看不懂。</p>
+<h3 id="解答"><a href="#解答" class="headerlink" title="解答"></a>解答</h3><p>嵌入式开发选用的语言和语法因选择的框架而异。</p>
+<p>ESP-EDF 更靠近底层,因而编写更复杂;Arduino 对底层进行封装,更靠上层且对用户更友好;MicroPython 则是在开发板上还原了一个 Python 的开发环境,继承了 Python 的诸多优点(简单的语法、无需编译就能执行新代码等)。</p>
+<p>此外,还能用 JS、Java、Lua 等等语言进行开发。</p>
+<h3 id="我的选择"><a href="#我的选择" class="headerlink" title="我的选择"></a>我的选择</h3><p>我手上有两块板子,一块被我刷成了 MicroPython,但目前不打算去用它。</p>
+<p>我打算用 Arduino + C 进行开发。</p>
+<h3 id="配置-VSCode-Arduino-开发环境"><a href="#配置-VSCode-Arduino-开发环境" class="headerlink" title="配置 VSCode + Arduino 开发环境"></a>配置 VSCode + Arduino 开发环境</h3><p>Arduino 没有代码补全,太难用。简述一下如何使用 VSCode 进行开发:</p>
+<ul>
+<li>VSC 安装 Arduino 插件;</li>
+<li>在 首选项-设置 中配置 Arduino 的路径 <code>Arduino.path</code></li>
+<li>打开项目后选择 MCU 类型和串口</li>
+</ul>
+<p>就能用啦。</p>
+<h2 id="第一个项目"><a href="#第一个项目" class="headerlink" title="第一个项目"></a>第一个项目</h2><p>第一个项目就不选输出 Hello World 了,一点硬件的感觉都没有。</p>
+<p>据<a href="https://wiki.luatos.com/chips/esp32c3/board.html"> 官方文档 </a>,主板板载的两个 LED 灯对应的 GPIO 为 <code>IO12 IO13</code>,高电平有效。</p>
+<p>就此编写一个<del>无稳态多协振荡电路</del>让 LED 灯交替闪烁的程序:</p>
+<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">setup</span><span class="hljs-params">()</span> <span class="hljs-comment">//初始化函数,只会在开发板上电或复位时被调用一次</span><br>{ <br> pinMode(<span class="hljs-number">12</span>, OUTPUT); <span class="hljs-comment">//初始化 IO12 为输出口</span><br> pinMode(<span class="hljs-number">13</span>, OUTPUT); <span class="hljs-comment">//初始化 IO13 为输出口</span><br> digitalWrite(<span class="hljs-number">12</span>, LOW); <span class="hljs-comment">//初始化设为低电平,则灯灭</span><br> digitalWrite(<span class="hljs-number">13</span>, LOW); <span class="hljs-comment">//初始化设为低电平,则灯灭</span><br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">loop</span><span class="hljs-params">()</span> <span class="hljs-comment">//上电之后一直循环执行的函数</span><br>{ digitalWrite(<span class="hljs-number">12</span>, HIGH); <span class="hljs-comment">//亮左灯</span><br> digitalWrite(<span class="hljs-number">13</span>, LOW); <span class="hljs-comment">//关右灯</span><br> delay(<span class="hljs-number">1000</span>); <span class="hljs-comment">//等待1秒</span><br> digitalWrite(<span class="hljs-number">12</span>, LOW); <span class="hljs-comment">//关左灯</span><br> digitalWrite(<span class="hljs-number">13</span>, HIGH); <span class="hljs-comment">//亮右灯</span><br> delay(<span class="hljs-number">1000</span>); <span class="hljs-comment">//等待1秒</span><br>}<br></code></pre></td></tr></table></figure>
+
+<p>编译+上传即可。</p>
+<p>结果就不展示了,两个灯在交替闪烁。</p>
+
+
+
+ 2022-03-26T09:30:00.000Z
+
+
diff --git "a/tag/\345\265\214\345\205\245\345\274\217\345\274\200\345\217\221/feed.json" "b/tag/\345\265\214\345\205\245\345\274\217\345\274\200\345\217\221/feed.json"
new file mode 100644
index 00000000..4d2c94c3
--- /dev/null
+++ "b/tag/\345\265\214\345\205\245\345\274\217\345\274\200\345\217\221/feed.json"
@@ -0,0 +1,30 @@
+{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "カレーうどん屋 • All posts by \"嵌入式开发\" tag",
+ "description": "カレーうどん屋.",
+ "home_page_url": "https://blog.udon.eu.org",
+ "items": [
+ {
+ "id": "https://blog.udon.eu.org/archives/9b58c98e.html",
+ "url": "https://blog.udon.eu.org/archives/9b58c98e.html",
+ "title": "合宙 EPS32-C3 把玩记录(二):WiFi 与一个 Web 程序",
+ "date_published": "2022-03-26T16:15:00.000Z",
+ "content_html": "了解一下相关的库 \n这个库是自带的,不需要引入。
\n据我的理解,单片机的串口就是控制台程序的控制台,可以返回一些信息给上位机。
\n会用到的几个指令:
\n1 2 3 4 5 6 7 Serial.begin(Baudrate);\t Serial.end(); Serial.read();\t\t\t Serial.peek();\t\t\t Serial.flush();\t\t\t Serial.print/println();\t Serial.write();\t\t\t
\n\n\n#include <WiFi.h>
\nAP(接入点) Mode 创建一个接入点。
\n1 2 3 4 5 WiFi.mode(WiFi_AP);\t\t\t WiFi.softAPConfig(local_IP, gateway, subnet); WiFi.softAP(SSID,PASSWD);\t
\n\n更多函数见 WiFi.h AP 常用方法说明
\nSTA(站点) Mode 接入一个 AP。
\n1 2 3 WiFi.mode(WIFI_STA); \t\t WiFi.start(SSID,PASSWD)\t\t Serial.println(WiFi.localIP());\t\t
\n\n更多函数见 WiFi.h STA 常用方法说明
\n\n#include <WebServer.h>
\n创建一个简单的网站服务器。真的很简单。
\n一个个函数讲有点难理解,我放在这节的例程里面说明。
\n写一个测试程序吧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <WiFi.h> #include <WebServer.h> const char *SSID = "YOUR_SSID" ;const char *PASSWORD = "YOUR_PASSWORD" ; WebServer server (80 ) ;\t\tvoid handleIndex () \t\t\t { server.send(200 , "text/plain" , "Hello from ESP32!" ); }void setup () { Serial.begin(9600 );\t\t Serial.println(); WiFi.mode(WIFI_STA);\t WiFi.begin(SSID, PASSWORD);\t\t\t\t while (WiFi.status() != WL_CONNECTED) \t { delay(500 ); Serial.print("." );\t } Serial.println("WiFi connected!" ); Serial.println("IP address: " ); Serial.println(WiFi.localIP()); \t\t server.on("/" , handleIndex);\t\t\t server.begin();\t\t\t Serial.println("WebServer begin!" ); }void loop () { server.handleClient();\t }
\n\n访问串口返回的 IP,即可看到 Hello from ESP32!
这句话啦。
\n还有个 Web Server 叫 ESPAsyncWebServer 自带的 WebServer 是同步的,只支持处理一个连接。对于这种体量的机器其实足够了。
\n顺便学习一下一个第三方库吧。
\n添加库 对于这款 ESP32,需要下载并导入两个库(源码 ZIP 即可):
\nme-no-dev/AsyncTCP: Async TCP Library for ESP32
\nme-no-dev/ESPAsyncWebServer: Async Web Server for ESP8266 and ESP32
\n在 Arduino 的项目 > 加载库 > 添加 .ZIP 库
中导入这两个库。
\n用 ESPAsyncWebServer 重写刚刚的例程吧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <WiFi.h> #include <ESPAsyncWebServer.h> const char *SSID = "YOUR_SSID" ;const char *PASSWORD = "YOUR_PASSWORD" ; ESPAsyncWebServer server (80 ) ;\t\t void handleIndex (AsyncWebServerRequest *request) { request->send(200 , "text/plain" , "Hello, world!" ); }void setup () { Serial.begin(9600 );\t\t Serial.println(); WiFi.mode(WIFI_STA);\t WiFi.begin(SSID, PASSWORD);\t\t\t\t while (WiFi.status() != WL_CONNECTED) \t { delay(500 ); Serial.print("." );\t } Serial.println("WiFi connected!" ); Serial.println("IP address: " ); Serial.println(WiFi.localIP()); \t\t server.on("/" , handleIndex);\t\t\t server.begin();\t\t\t Serial.println("WebServer begin!" ); }void loop () { }
\n\n理论上来讲,上面的代码应该是正确的……
\n但 Arduino 在编译的时候报错,内容是 ESPAsyncWebServer 库中的某些代码。
\n有待我弄清楚出错的原因。
\n",
+ "tags": [
+ "教程",
+ "嵌入式开发"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/7f7bd4a5.html",
+ "url": "https://blog.udon.eu.org/archives/7f7bd4a5.html",
+ "title": "合宙 EPS32-C3 把玩记录(一):环境搭建与第一个程序",
+ "date_published": "2022-03-26T09:30:00.000Z",
+ "content_html": "为了贯彻本博客最重要的关键词:性价比 ,我看到性价如此高的开发板,想都没想就剁手了。
\n嘛其实也没有这么冲动,我在购买 3D 打印机之后就一直在计划着做一些网上现成的电子项目,但碍于这段时间 MCU 和大尺寸屏幕价格的飙升,一直没能开始动手。
\n正好最近我学习了 iPad 上的 3D 建模软件 Sharp3D,项目的外壳建模变得有可能;又遇到了这块便宜的板子,立即开工!
\n因为1.8寸的 TFT 显示屏还没到货,3D 建模就先放一边,先来研究一下这块开发板。
\n事先声明 本教程是我一边从零开始学习嵌入式开发一边作成的,有逻辑混乱、内容浅显和成吨的错误,还请已经熟悉嵌入式开发的大佬多多包涵与斧正)
\n问题:什么?开发环境不是按语言分的嘛? 在开始学习嵌入式开发之前,我简单地认为嵌入式开发因语言而已,分为用 C/Cpp 开发(Arduino)和用 Python 开发(MicroPython)。
\n直到我遇见了 ESP-IDF 这个东西。
\n啥啊,为啥这家伙用的也是 C,代码我还一点都看不懂。
\n解答 嵌入式开发选用的语言和语法因选择的框架而异。
\nESP-EDF 更靠近底层,因而编写更复杂;Arduino 对底层进行封装,更靠上层且对用户更友好;MicroPython 则是在开发板上还原了一个 Python 的开发环境,继承了 Python 的诸多优点(简单的语法、无需编译就能执行新代码等)。
\n此外,还能用 JS、Java、Lua 等等语言进行开发。
\n我的选择 我手上有两块板子,一块被我刷成了 MicroPython,但目前不打算去用它。
\n我打算用 Arduino + C 进行开发。
\n配置 VSCode + Arduino 开发环境 Arduino 没有代码补全,太难用。简述一下如何使用 VSCode 进行开发:
\n\nVSC 安装 Arduino 插件; \n在 首选项-设置 中配置 Arduino 的路径 Arduino.path
\n打开项目后选择 MCU 类型和串口 \n \n就能用啦。
\n第一个项目 第一个项目就不选输出 Hello World 了,一点硬件的感觉都没有。
\n据 官方文档 ,主板板载的两个 LED 灯对应的 GPIO 为 IO12 IO13
,高电平有效。
\n就此编写一个无稳态多协振荡电路让 LED 灯交替闪烁的程序:
\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void setup () { pinMode(12 , OUTPUT); pinMode(13 , OUTPUT); digitalWrite(12 , LOW); digitalWrite(13 , LOW); }void loop () { digitalWrite(12 , HIGH); digitalWrite(13 , LOW); delay(1000 ); digitalWrite(12 , LOW); digitalWrite(13 , HIGH); delay(1000 ); }
\n\n编译+上传即可。
\n结果就不展示了,两个灯在交替闪烁。
\n",
+ "tags": [
+ "教程",
+ "嵌入式开发"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/tag/\345\265\214\345\205\245\345\274\217\345\274\200\345\217\221/rss.xml" "b/tag/\345\265\214\345\205\245\345\274\217\345\274\200\345\217\221/rss.xml"
new file mode 100644
index 00000000..2a2a553f
--- /dev/null
+++ "b/tag/\345\265\214\345\205\245\345\274\217\345\274\200\345\217\221/rss.xml"
@@ -0,0 +1,105 @@
+
+
+
+ カレーうどん屋 • Posts by "嵌入式开发" tag
+ https://blog.udon.eu.org
+ カレーうどん屋.
+ zh-CN
+ Sun, 27 Mar 2022 00:15:00 +0800
+ Sun, 27 Mar 2022 00:15:00 +0800
+ 教程
+ 随笔
+ 软件
+ 小鸡测评
+ 3D 打印
+ 嵌入式开发
+ GitHub Actions
+ Hexo
+ DIY
+ -
+
https://blog.udon.eu.org/archives/9b58c98e.html
+ 合宙 EPS32-C3 把玩记录(二):WiFi 与一个 Web 程序
+ https://blog.udon.eu.org/archives/9b58c98e.html
+ 教程
+ 嵌入式开发
+ Sun, 27 Mar 2022 00:15:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/7f7bd4a5.html
+ 合宙 EPS32-C3 把玩记录(一):环境搭建与第一个程序
+ https://blog.udon.eu.org/archives/7f7bd4a5.html
+ 教程
+ 嵌入式开发
+ Sat, 26 Mar 2022 17:30:00 +0800
+
+
+
+
diff --git "a/tag/\346\225\231\347\250\213/atom.xml" "b/tag/\346\225\231\347\250\213/atom.xml"
new file mode 100644
index 00000000..2459d9bd
--- /dev/null
+++ "b/tag/\346\225\231\347\250\213/atom.xml"
@@ -0,0 +1,1020 @@
+
+
+ https://blog.udon.eu.org
+ カレーうどん屋 • Posts by "教程" tag
+
+ 2023-04-15T16:00:00.000Z
+
+
+
+
+
+
+
+
+
+
+ https://blog.udon.eu.org/archives/8b68ddd6.html
+ 修复 UEFI 引导的 GRUB
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="问题与解决方法"><a href="#问题与解决方法" class="headerlink" title="问题与解决方法"></a>问题与解决方法</h2><h3 id="环境"><a href="#环境" class="headerlink" title="环境"></a>环境</h3><p>Manjaro Linux x86_64</p>
+<p>Kernel: 6.2.10-1-MANJARO</p>
+<p>使用 UEFI 引导</p>
+<h3 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h3><p>在 GRUB 尝试引导 Linux 内核时,出现如下错误:</p>
+<figure class="highlight smali"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs smali">error:<span class="hljs-built_in"> sparse </span>file<span class="hljs-built_in"> not </span>allowed.<br><br>452: out of range pointer: xxxxxxxxxx<br><br>Aborted. Press any key to exit.<br></code></pre></td></tr></table></figure>
+
+<p>用户将无法进入系统。</p>
+<h3 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h3><h4 id="进入恢复系统"><a href="#进入恢复系统" class="headerlink" title="进入恢复系统"></a>进入恢复系统</h4><p>插入 Manjaro LiveCD, 启动 Live 系统。</p>
+<h4 id="确定磁盘分区"><a href="#确定磁盘分区" class="headerlink" title="确定磁盘分区"></a>确定磁盘分区</h4><p>在 Live 系统中,使用 <code>fdisk -l</code> 查看磁盘分区情况,找到安装 Manjaro 的磁盘,假设为 <code>/dev/sda</code>。</p>
+<p>我的磁盘分区如下:</p>
+<figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs tap">设备 起点 末尾 扇区 大小 类型<br>/dev/sda1 <span class="hljs-number"> 2048 </span> <span class="hljs-number"> 821247 </span> <span class="hljs-number"> 819200 </span> 400M EFI 系统<br>/dev/sda2 <span class="hljs-number"> 821248 </span><span class="hljs-number"> 723390463 </span>722569216 344.5G Linux 文件系统<br>/dev/sda3 <span class="hljs-number"> 723390464 </span><span class="hljs-number"> 983437311 </span>260046848 124G Linux 文件系统<br>/dev/sda4 <span class="hljs-number"> 983437312 </span>1000214527 <span class="hljs-number"> 16777216 </span> 8G Linux 文件系统<br></code></pre></td></tr></table></figure>
+
+<p>可以确定,<code>/dev/sda1</code> 是 EFI 系统分区,<code>/dev/sda2</code> 是系统所在分区。</p>
+<h4 id="挂载分区"><a href="#挂载分区" class="headerlink" title="挂载分区"></a>挂载分区</h4><p>挂载系统分区:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">mount /dev/sda2 /mnt<br></code></pre></td></tr></table></figure>
+
+<p>将当前系统的工具分区挂载到 <code>/mnt</code> 下:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">mount --<span class="hljs-built_in">bind</span> /dev /mnt/dev<br>mount --<span class="hljs-built_in">bind</span> /proc /mnt/proc<br>mount --<span class="hljs-built_in">bind</span> /sys /mnt/sys<br></code></pre></td></tr></table></figure>
+
+<p>将 EFI 分区挂载到 <code>/mnt/boot/efi</code> 下:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">mount /dev/sda1 /mnt/boot/efi<br></code></pre></td></tr></table></figure>
+
+<h4 id="进入系统"><a href="#进入系统" class="headerlink" title="进入系统"></a>进入系统</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">chroot</span> /mnt<br></code></pre></td></tr></table></figure>
+
+<h4 id="重新安装-GRUB"><a href="#重新安装-GRUB" class="headerlink" title="重新安装 GRUB"></a>重新安装 GRUB</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable<br></code></pre></td></tr></table></figure>
+
+<p>具体参数需要根据实际情况进行修改。</p>
+<h4 id="在这之后"><a href="#在这之后" class="headerlink" title="在这之后"></a>在这之后</h4><p>重启,进入通过 GRUB 引导系统。</p>
+<p>在系统中使用 <code>sudo grub-install --recheck /dev/sda</code> 命令再次安装 GRUB,确保系统能够正常启动。</p>
+<h2 id="一些思考"><a href="#一些思考" class="headerlink" title="一些思考"></a>一些思考</h2><p>接下来的内容是我的整个修复流程,包含了如何在搜索引擎查找问题、根据文章内容调整目标等杂碎的内容,和我在修复过程中的一些感想。</p>
+<h3 id="为什么会出现这个问题"><a href="#为什么会出现这个问题" class="headerlink" title="为什么会出现这个问题"></a>为什么会出现这个问题</h3><p>不是很清楚。</p>
+<p>在启动 Manjaro 前我不小心打开了电脑里的 Windows 系统,但没有连接移动硬盘。</p>
+<p>按照以往的经验,这最多只会导致找不到 GRUB 的位置,手动指定 GRUB 所在分区就可以启动系统。</p>
+<p>但这次不大一样。</p>
+<p>在打开 GRUB 之后,尝试引导内核,就发现了这个问题。</p>
+<h3 id="初步解决思路"><a href="#初步解决思路" class="headerlink" title="初步解决思路"></a>初步解决思路</h3><p><del>立刻格式化磁盘,重新安装 Manjaro。</del></p>
+<p>我已经不是曾经那个只会重装的我了,这次我希望可以解决问题,而不是简单地重装。</p>
+<p>首先,我 Google 了这个错误,发现了几篇内容相关的文章。</p>
+<p><a href="https://forum.artixlinux.org/index.php/topic,4668.0.html">报错与我一致的文章</a>,但没有给出解决方案。</p>
+<p><a href="https://www.reddit.com/r/archlinux/comments/x2qb4w/grub_aborts_loading_linux_because_of_an_out_of/">要我删除 GRUB 和 UEFI 所在分区所有内容的文章</a>,有点可怕,不敢这么干。</p>
+<p><a href="https://bbs.archlinux.org/viewtopic.php?id=280230">提到应该重新安装 GRUB 的文章</a>,这还有点道理。</p>
+<p>于是,我的目标转变为重新安装 GRUB。</p>
+<h3 id="重新安装-GRUB-1"><a href="#重新安装-GRUB-1" class="headerlink" title="重新安装 GRUB"></a>重新安装 GRUB</h3><p>在之前遇到找不到 GRUB 分区的问题时,在手动引导然后进入系统后,我会执行 <code>sudo grub-install --recheck /dev/sda</code> 重新安装 GRUB,解决这个问题。</p>
+<p>那这次的觉得方案应该是差不多的……吧?</p>
+<p>不对啊,这次是在 LiveCD 的系统里操作,怎么能直接安装 GRUB 呢?</p>
+<p>这个问题比较难描述。</p>
+<p>我先是 Google <code>grub-install 修复 GRUB</code>,中文网站的内容都是关于在可以启动的系统下修复 GRUB 的,没有我需要的内容。</p>
+<p>然后我开始求助于 ChatGPT,输入的 Prompts 是:</p>
+<figure class="highlight livecodeserver"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs livecodeserver">I am <span class="hljs-keyword">using</span> Manjaro <span class="hljs-keyword">with</span> GRUB.<br>When I booted <span class="hljs-keyword">into</span> <span class="hljs-keyword">the</span> <span class="hljs-keyword">system</span>, <span class="hljs-keyword">it</span> says <span class="hljs-string">"sparse file not allowed 452 out of range pointer"</span>. How <span class="hljs-built_in">to</span> fix <span class="hljs-keyword">it</span>?<br></code></pre></td></tr></table></figure>
+
+<p>不难发现,我并没有说明我使用的是 UEFI 引导,这直接影响到了 ChatGPT 回复的准确性。</p>
+<p>ChatGPT 给出的修复步骤与上述的相差不大,只是在挂载系统分区和工具分区后,直接尝试执行 <code>grub-install</code> 命令,尝试修复。</p>
+<p><code>grub-install</code> 返回错误 <code>this gpt partition label contains no bios boot partition</code> 把我弄得更懵了。</p>
+<p>再次 Google 这个问题,发现了 <a href="https://superuser.com/questions/903112/grub2-install-this-gpt-partition-label-contains-no-bios-boot-partition">这篇在长篇大论讲 GRUB 的文章</a>,虽然没有给出解决方案,但它让我意识到 UEFI 和 Legacy BIOS 两种启动方式的区别。</p>
+<h3 id="UEFI-和-Legacy-BIOS"><a href="#UEFI-和-Legacy-BIOS" class="headerlink" title="UEFI 和 Legacy BIOS"></a>UEFI 和 Legacy BIOS</h3><p>UEFI 和 Legacy BIOS 是两种启动方式,它们的区别在于,Legacy BIOS 是在 BIOS 中直接加载内核,而 UEFI 是在 BIOS 中加载 EFI 系统,然后由 EFI 系统加载内核。</p>
+<p>使用 UEFI 引导的系统一般都有一个 200MB 到 400MB 的 EFI 系统分区,用于存放 EFI 系统。在上述的,我的硬盘分区中可以看到。</p>
+<p>使用 <code>findmnt</code> 命令可以查看当前系统的挂载情况,其中 <code>TARGET</code> 列就是挂载点,<code>SOURCE</code> 列就是挂载的分区。</p>
+<p>EFI 分区的挂载情况为:</p>
+<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs gradle">TARGET <span class="hljs-keyword">SOURCE</span> FSTYPE <span class="hljs-keyword">OPTIONS</span><br><span class="hljs-regexp">/boot/</span>efi <span class="hljs-regexp">/dev/</span>sda1 vfat rw,relatime,fm<br></code></pre></td></tr></table></figure>
+
+<p>可以看到,<code>/boot/efi</code> 里的内容正是 EFI 系统分区的内容。(我也是刚学到这个知识的)</p>
+<h3 id="解决-UEFI-相关问题"><a href="#解决-UEFI-相关问题" class="headerlink" title="解决 UEFI 相关问题"></a>解决 UEFI 相关问题</h3><p>在修复过程中,我是通过 Google 发现上述的问题。</p>
+<p><a href="https://superuser.com/questions/1390428/grub-install-warning-this-gpt-partition-label-contains-no-bios-boot-partition">这篇文章</a> 给了我莫大的帮助。</p>
+<p>其中提到了 EFI 分区,也提到了如何正确安装 UEFI 引导的 GRUB:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> grub-install --target=x86_64-efi --efi-directory=/boot/efi<br><span class="hljs-built_in">sudo</span> grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable<br></code></pre></td></tr></table></figure>
+
+<p>在补充挂载 EFI 分区、切换 Root 目录后,执行第一条命令,发现有错误。尝试执行第二条命令,发现没有错误,代表 GRUB 已经重新安装成功。</p>
+<p>此时我想起来,在之前安装 GRUB 时,会提示 <code>正在为 x86_64-efi 平台进行安装</code>,我才意识到前面的修复过程并没有去指定平台。</p>
+<h3 id="总结一下"><a href="#总结一下" class="headerlink" title="总结一下"></a>总结一下</h3><p>总之,这就是我此次修复的心路历程。我没有研究过 UEFI 和 Legacy BIOS 的区别,也没有研究过 GRUB 的安装过程,所以在修复过程中,我是通过 Google 和 ChatGPT 的帮助才解决了这个问题。</p>
+<p>希望这个探索过程能给你一些启发吧。</p>
+<hr>
+<p>此文章以 <em>我无所谓 By 不 By 什么 AI,对我有帮助的文章就是好文章</em> 标识发布。</p>
+
+
+ 2023-04-15T16:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/8b115688.html
+ 使用 Docker Compose 部署音乐服务器 Navidrome
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="服务介绍"><a href="#服务介绍" class="headerlink" title="服务介绍"></a>服务介绍</h2><p>Navidrome 是一款兼容 Subsonic API 的开源音乐服务器软件,它提供了一个不错的 WebUI,也可以将支持 Subsonic API 的客户端接入。</p>
+<p>目前项目正处在活跃开发中,各种各样的新功能正在陆续加入。</p>
+<h2 id="我的客户端选择"><a href="#我的客户端选择" class="headerlink" title="我的客户端选择"></a>我的客户端选择</h2><h3 id="电脑端"><a href="#电脑端" class="headerlink" title="电脑端"></a>电脑端</h3><p>自带 WebUI, <a href="https://github.com/jeffvli/sonixd">Sonixd</a> 【跨平台】</p>
+<h3 id="iOS"><a href="#iOS" class="headerlink" title="iOS"></a>iOS</h3><p><a href="https://apps.apple.com/us/app/play-sub-music-streamer/id955329386">play:sub</a> 【付费软件 4.99$】</p>
+<h2 id="部署方式"><a href="#部署方式" class="headerlink" title="部署方式"></a>部署方式</h2><p>采用 Docker Compose 部署 Navidrome,使用 Nginx 作为反向代理。</p>
+<h2 id="Docker-Compose-配置文件"><a href="#Docker-Compose-配置文件" class="headerlink" title="Docker Compose 配置文件"></a>Docker Compose 配置文件</h2><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3"</span><br><span class="hljs-attr">services:</span><br> <span class="hljs-attr">navidrome:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">deluan/navidrome:latest</span><br> <span class="hljs-attr">container_name:</span> <span class="hljs-string">navidrome</span><br> <span class="hljs-attr">user:</span> <span class="hljs-number">1000</span><span class="hljs-string">:1000</span> <span class="hljs-comment"># should be owner of volumes</span><br> <span class="hljs-attr">ports:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">"127.0.0.1:4533:4533"</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span><br> <span class="hljs-attr">environment:</span><br> <span class="hljs-attr">ND_SCANSCHEDULE:</span> <span class="hljs-string">1h</span><br> <span class="hljs-attr">ND_LOGLEVEL:</span> <span class="hljs-string">info</span> <br> <span class="hljs-attr">ND_SESSIONTIMEOUT:</span> <span class="hljs-string">24h</span><br> <span class="hljs-attr">ND_BASEURL:</span> <span class="hljs-string">""</span><br> <span class="hljs-attr">ND_SEARCHFULLSTRING:</span> <span class="hljs-literal">true</span><br> <span class="hljs-comment"># Optional: fetch artist images from spotify</span><br> <span class="hljs-attr">ND_SPOTIFY_ID:</span><br> <span class="hljs-attr">ND_SPOTIFY_SECRET:</span><br> <span class="hljs-comment"># Optional: fetch artist information from last.fm</span><br> <span class="hljs-attr">ND_LASTFM_APIKEY:</span><br> <span class="hljs-attr">ND_LASTFM_SECRET:</span><br> <span class="hljs-attr">ND_LASTFM_LANGUAGE:</span> <span class="hljs-string">en</span><br> <span class="hljs-attr">volumes:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">"./data:/data"</span> <span class="hljs-comment"># Navidrome data</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">"/APTH-TO/navidrome-music:/music:ro"</span> <span class="hljs-comment"># Music folder</span><br></code></pre></td></tr></table></figure>
+<p>使用命令 <code>docker compose up -d</code> 启动服务。</p>
+<h2 id="Nginx-配置文件"><a href="#Nginx-配置文件" class="headerlink" title="Nginx 配置文件"></a>Nginx 配置文件</h2><p>我建议使用 DigitalOcean 的 Nginx 配置生产工具,示例配置如下:</p>
+<p><a href="https://www.digitalocean.com/community/tools/nginx?domains.0.server.domain=music.example.com&domains.0.php.php=false&domains.0.reverseProxy.reverseProxy=true&domains.0.reverseProxy.proxyPass=http://127.0.0.1:4533&domains.0.routing.root=false&domains.0.routing.index=index.html&domains.0.routing.fallbackHtml=true&global.app.lang=zhCN%5C">示例配置</a></p>
+<p>也可参考下述配置,此为 DigitalOcean 生成配置的简化版:</p>
+<figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-section">server</span> {<br> <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">server_name</span> music.example.com;<br><br> <span class="hljs-comment"># SSL</span><br> <span class="hljs-attribute">ssl_certificate</span> /etc/letsencrypt/live/music.example.com/fullchain.pem;<br> <span class="hljs-attribute">ssl_certificate_key</span> /etc/letsencrypt/live/music.example.com/privkey.pem;<br> <span class="hljs-attribute">ssl_trusted_certificate</span> /etc/letsencrypt/live/music.example.com/chain.pem;<br><br> <span class="hljs-comment"># logging</span><br> <span class="hljs-attribute">access_log</span> /var/log/nginx/access.log combined buffer=<span class="hljs-number">512k</span> flush=<span class="hljs-number">1m</span>;<br> <span class="hljs-attribute">error_log</span> /var/log/nginx/<span class="hljs-literal">error</span>.log <span class="hljs-literal">warn</span>;<br><br> <span class="hljs-comment"># reverse proxy</span><br> <span class="hljs-section">location</span> / {<br> <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;<br> <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:4533;<br> }<br>}<br><br><span class="hljs-comment"># subdomains redirect</span><br><span class="hljs-section">server</span> {<br> <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">server_name</span> <span class="hljs-regexp">*.music.example.com</span>;<br><br> <span class="hljs-comment"># SSL</span><br> <span class="hljs-attribute">ssl_certificate</span> /etc/letsencrypt/live/music.example.com/fullchain.pem;<br> <span class="hljs-attribute">ssl_certificate_key</span> /etc/letsencrypt/live/music.example.com/privkey.pem;<br> <span class="hljs-attribute">ssl_trusted_certificate</span> /etc/letsencrypt/live/music.example.com/chain.pem;<br> <span class="hljs-attribute">return</span> <span class="hljs-number">301</span> https://music.example.com<span class="hljs-variable">$request_uri</span>;<br>}<br></code></pre></td></tr></table></figure>
+
+<h2 id="音乐管理"><a href="#音乐管理" class="headerlink" title="音乐管理"></a>音乐管理</h2><p>我一直以文件夹分类的方式管理音乐,但 Navidrome 并不支持文件夹分类。它是根据音乐标签来分类的,例如按照歌手、专辑等依据分类歌曲。</p>
+<p>因此,若想使用 Navidrome,需要对音乐进行标签管理。</p>
+<p>大约两年前,我写了 <a href="https://blog.udon.eu.org/archives/6b40e5ad.html">一篇文章</a> 介绍使用 Music Tag 和 Foobar2000 两款软件来管理音乐。</p>
+<p>Music Tag 的标签源是网易云音乐、豆瓣音乐、QQ 音乐等国内音乐平台,说实话,这些平台的音乐标签质量都不是很好,所以我一直在寻找更好的音乐标签源。</p>
+<p>直到我发现了 <a href="https://musicbrainz.org/">MusicBrainz</a>,这是一个开源的音乐标签数据库,任何人都可以为它贡献标签。在体验之后,我发现 MusicBrainz 的音乐标签质量要比国内音乐平台的标签质量好很多,所以我决定将 MusicBrainz 作为我的音乐标签源。</p>
+<p>我使用 <a href="https://picard.musicbrainz.org/">Picard</a> 这款软件来从 MusicBrainz 获取音乐标签。</p>
+<p>将音乐导入 Picard 后,它会自动从 MusicBrainz 获取音乐标签,然后将标签写入音乐文件,十分方便。</p>
+<h2 id="开始使用"><a href="#开始使用" class="headerlink" title="开始使用"></a>开始使用</h2><p>不论是使用 Navidrome 自带的 Web 界面,还是使用兼容 Subsonic API 的客户端,只要连接到 Navidrome,便可开始享受你的私人音乐库。</p>
+
+
+ 2023-01-31T04:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/f9bfe16a.html
+ 使用 Docker Compose 部署 Keycloak 20
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="部署方式"><a href="#部署方式" class="headerlink" title="部署方式"></a>部署方式</h2><p>采用 Docker Compose 部署,使用 Postgres 作为数据库,使用 Nginx 作为反向代理。</p>
+<h2 id="Docker-Compose-配置文件"><a href="#Docker-Compose-配置文件" class="headerlink" title="Docker Compose 配置文件"></a>Docker Compose 配置文件</h2><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3'</span><br><br><span class="hljs-attr">services:</span><br> <span class="hljs-attr">keycloak:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">quay.io/keycloak/keycloak:latest</span><br> <span class="hljs-attr">environment:</span><br> <span class="hljs-attr">KC_DB:</span> <span class="hljs-string">postgres</span><br> <span class="hljs-attr">KC_DB_URL:</span> <span class="hljs-string">jdbc:postgresql://db:5432/keycloak</span><br> <span class="hljs-attr">KC_DB_USERNAME:</span> <span class="hljs-string">keycloak</span><br> <span class="hljs-attr">KC_DB_PASSWORD:</span> <span class="hljs-string">keycloak</span><br> <span class="hljs-attr">KC_HTTP_ENABLED:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># 开启 HTTP</span><br> <span class="hljs-attr">KC_HOSTNAME_STRICT:</span> <span class="hljs-literal">false</span><br> <span class="hljs-attr">KC_HOSTNAME_STRICT_HTTPS:</span> <span class="hljs-literal">false</span><br> <span class="hljs-attr">KC_HTTP_RELATIVE_PATH:</span> <span class="hljs-string">'/'</span> <span class="hljs-comment"># Keycloak 应用的相对路径</span><br> <span class="hljs-attr">KC_HTTP_PORT:</span> <span class="hljs-number">8080</span> <span class="hljs-comment"># HTTP 端口</span><br> <span class="hljs-attr">KEYCLOAK_ADMIN:</span> <span class="hljs-string">MY_USERNAME</span> <span class="hljs-comment"># 管理员账号,仅初始化时使用</span><br> <span class="hljs-attr">KEYCLOAK_ADMIN_PASSWORD:</span> <span class="hljs-string">MY_PASSWORD</span> <span class="hljs-comment"># 管理员密码,仅初始化时使用</span><br> <span class="hljs-attr">PROXY_ADDRESS_FORWARDING:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># 使用反向代理必须开启</span><br> <span class="hljs-attr">KC_PROXY:</span> <span class="hljs-string">edge</span> <span class="hljs-comment"># 反向代理模式,详见文档</span><br> <span class="hljs-attr">entrypoint:</span> <span class="hljs-string">/opt/keycloak/bin/kc.sh</span> <span class="hljs-string">start</span> <span class="hljs-comment"># 第一次运行后可以加上 --optimized 参数,加快二次启动速度</span><br> <span class="hljs-attr">ports:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:18080:8080</span> <span class="hljs-comment"># Keycloak 应用端口</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span><br> <span class="hljs-attr">db:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:14</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span><br> <span class="hljs-attr">environment:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_USER=keycloak</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_PASSWORD=keycloak</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_DB=keycloak</span><br> <span class="hljs-attr">volumes:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">./postgres-data:/var/lib/postgresql/data</span> <span class="hljs-comment"># 数据库数据保存位置</span><br></code></pre></td></tr></table></figure>
+
+<p>使用命令 <code>docker compose up -d</code> 启动服务。</p>
+<h2 id="Nginx-配置"><a href="#Nginx-配置" class="headerlink" title="Nginx 配置"></a>Nginx 配置</h2><p>我建议使用 DigitalOcean 的 Nginx 配置生产工具,示例配置如下:</p>
+<p><a href="https://www.digitalocean.com/community/tools/nginx?domains.0.server.domain=auth.example.com&domains.0.php.php=false&domains.0.reverseProxy.reverseProxy=true&domains.0.reverseProxy.proxyPass=http://127.0.0.1:18080&domains.0.routing.root=false&global.app.lang=zhCN">示例配置</a></p>
+<p>也可参考下述配置,此为 DigitalOcean 生成配置的简化版:</p>
+<figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-section">server</span> {<br> <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">server_name</span> auth.example.com;<br><br> <span class="hljs-comment"># SSL</span><br> <span class="hljs-attribute">ssl_certificate</span> /etc/letsencrypt/live/auth.example.com/fullchain.pem;<br> <span class="hljs-attribute">ssl_certificate_key</span> /etc/letsencrypt/live/auth.example.com/privkey.pem;<br> <span class="hljs-attribute">ssl_trusted_certificate</span> /etc/letsencrypt/live/auth.example.com/chain.pem;<br><br> <span class="hljs-comment"># logging</span><br> <span class="hljs-attribute">access_log</span> /var/log/nginx/access.log combined buffer=<span class="hljs-number">512k</span> flush=<span class="hljs-number">1m</span>;<br> <span class="hljs-attribute">error_log</span> /var/log/nginx/<span class="hljs-literal">error</span>.log <span class="hljs-literal">warn</span>;<br><br> <span class="hljs-comment"># reverse proxy</span><br> <span class="hljs-section">location</span> / {<br> <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Auth-Request-Redirect <span class="hljs-variable">$request_uri</span>;<br> <span class="hljs-attribute">proxy_buffer_size</span> <span class="hljs-number">128k</span>;<br> <span class="hljs-attribute">proxy_buffers</span> <span class="hljs-number">4</span> <span class="hljs-number">256k</span>;<br> <span class="hljs-attribute">proxy_busy_buffers_size</span> <span class="hljs-number">256k</span>;<br> <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:18080;<br> }<br><br> <span class="hljs-section">location</span> /auth/realms {<br> <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Auth-Request-Redirect <span class="hljs-variable">$request_uri</span>;<br> <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:18080;<br> }<br><br> <span class="hljs-section">location</span> /auth/resources {<br> <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Auth-Request-Redirect <span class="hljs-variable">$request_uri</span>;<br> <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:18080;<br> }<br><br> <span class="hljs-section">location</span> /auth/js {<br> <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;<br> <span class="hljs-attribute">proxy_set_header</span> X-Auth-Request-Redirect <span class="hljs-variable">$request_uri</span>;<br> <span class="hljs-attribute">proxy_pass</span> http://127.0.0.1:18080;<br> }<br>}<br><br><span class="hljs-comment"># subdomains redirect</span><br><span class="hljs-section">server</span> {<br> <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">443</span> ssl http2;<br> <span class="hljs-attribute">server_name</span> <span class="hljs-regexp">*.auth.example.com</span>;<br><br> <span class="hljs-comment"># SSL</span><br> <span class="hljs-attribute">ssl_certificate</span> /etc/letsencrypt/live/auth.example.com/fullchain.pem;<br> <span class="hljs-attribute">ssl_certificate_key</span> /etc/letsencrypt/live/auth.example.com/privkey.pem;<br> <span class="hljs-attribute">ssl_trusted_certificate</span> /etc/letsencrypt/live/auth.example.com/chain.pem;<br> <span class="hljs-attribute">return</span> <span class="hljs-number">301</span> https://auth.example.com<span class="hljs-variable">$request_uri</span>;<br>}<br></code></pre></td></tr></table></figure>
+
+<h2 id="配置-Keycloak"><a href="#配置-Keycloak" class="headerlink" title="配置 Keycloak"></a>配置 Keycloak</h2><h3 id="创建-Realm"><a href="#创建-Realm" class="headerlink" title="创建 Realm"></a>创建 Realm</h3><p>打开 <a href="http://127.0.0.1:18080/">Keycloak 地址</a>,界面如下。</p>
+<p><img src="/images/2023-01-22/01.jpg" alt="Keycloak 界面"></p>
+<p>选择 <code>Administration Console</code>,进入管理界面。</p>
+<p><img src="/images/2023-01-22/02.jpg" alt="管理界面"></p>
+<p>选择箭头指向的下拉菜单,选择 <code>Add realm</code>,创建一个新的 Realm。</p>
+<p><img src="/images/2023-01-22/03.jpg" alt="创建 Realm"></p>
+<p>填写 Realm 名称,点击 <code>Create</code>。</p>
+<h3 id="创建-Client"><a href="#创建-Client" class="headerlink" title="创建 Client"></a>创建 Client</h3><p><img src="/images/2023-01-22/04.jpg" alt="管理界面"></p>
+<p>选择 <code>Clients</code>。</p>
+<p><img src="/images/2023-01-22/05.jpg" alt="Client 管理界面"></p>
+<p>点击 <code>Create client</code>。</p>
+<p><img src="/images/2023-01-22/06.jpg" alt="创建 Client"></p>
+<p>填写 Client 相关信息,点击 <code>Next</code>。</p>
+<p><img src="/images/2023-01-22/07.jpg" alt="配置 Client"></p>
+<p>按需求选择 Client 的配置,点击 <code>Save</code>。</p>
+<p><img src="/images/2023-01-22/08.jpg" alt="Client 创建完成"></p>
+<p>至此,Keycloak 配置完成,且创建了第一个测试用 Client。</p>
+<h3 id="测试-Client"><a href="#测试-Client" class="headerlink" title="测试 Client"></a>测试 Client</h3><p>可根据 <a href="https://www.keycloak.org/getting-started/getting-started-docker#_secure_your_first_app">官方教程</a> 测试该 Client。</p>
+<h2 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h2><p>上述便是使用 Docker Compose 部署 Keycloak 20 的方法,我们顺利创建了第一个测试用 Client,接下来可以根据自己的需求进行配置。</p>
+
+
+ 2023-01-22T12:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/e74a90f2.html
+ 使用再生龙 Clonezilla 备份操作系统
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>近日购入了一只闪迪的 CZ880 Extreme PRO 固态U盘来装 Manjaro。</p>
+<p>虽然U盘本体是终身质保的,但数据无价,配置一遍系统就要花大把的时间。遂有了备份U盘内整个系统的念头。</p>
+<p>下面跟着我使用再生龙 Clonezilla 把U盘里的系统备份一下吧!</p>
+<h3 id="事先准备"><a href="#事先准备" class="headerlink" title="事先准备"></a>事先准备</h3><p>再生龙是运行在独立操作系统 (Debian/Ubuntu) 上的一套软件,因此需要制作启动盘,或使用 Ventoy 等多系统启动方案。(实测 YUMI 无法启动再生龙,故建议使用 Ventoy)</p>
+<h4 id="制作启动盘"><a href="#制作启动盘" class="headerlink" title="制作启动盘"></a>制作启动盘</h4><p>前往 <a href="https://rufus.ie/zh/">Rufus 官网</a> 下载 Rufus 启动盘制作工具。</p>
+<p>前往 <a href="https://clonezilla.org/downloads/download.php?branch=stable">Clonezilla 官网</a> 下载再生龙映像。</p>
+<p>使用 Rufus 将再生龙映像烧写进另一个U盘即可。</p>
+<p><strong>U盘内数据将丢失,请做好备份!</strong></p>
+<h4 id="使用多系统启动"><a href="#使用多系统启动" class="headerlink" title="使用多系统启动"></a>使用多系统启动</h4><p>前往 <a href="https://www.ventoy.net/">Ventoy 官网</a> 下载安装包(兼容 Windows 与 Linux),将你的另一个U盘制作为 Ventoy 多启动盘。</p>
+<p>前往 <a href="https://clonezilla.org/downloads/download.php?branch=stable">Clonezilla 官网</a> 下载再生龙映像。</p>
+<p>将再生龙映像拷贝至 Ventoy 多启动盘中。</p>
+<p>选择你想存放备份数据的目的地,创建一个存放备份映像的文件夹(注意目录名称中不能带有空格)。</p>
+<p>剧透一下,40G 的系统盘备份之后大约占了 16-17G. 请留出足够的空间(建议和待备份的数据等大小)。</p>
+<h3 id="开始备份"><a href="#开始备份" class="headerlink" title="开始备份"></a>开始备份</h3><p>瞎眼警告:由于没有合适的截屏方式,我很不愿意地采取了 拍 屏 的方式,敬请谅解。</p>
+<h4 id="启动再生龙系统"><a href="#启动再生龙系统" class="headerlink" title="启动再生龙系统"></a>启动再生龙系统</h4><p>确保你的电脑关闭了安全启动,若还打开着,需要在 BIOS 中将其关闭。</p>
+<p>插入刚刚制作好的启动盘/Ventoy 多启动盘,在电脑启动时猛敲键盘的…某个键,这因电脑型号而异,打开启动菜单。</p>
+<p>选择插入的启动盘/多启动盘。</p>
+<p>启动盘用户若没有太大的兼容性问题,就能看到再生龙的启动菜单。</p>
+<p>多启动盘用户还要再多一步,在 Ventoy 菜单内选中再生龙的映像,如下图所示,即可打开再生龙的启动菜单。</p>
+<p><img src="/images/2022-08-12/1.jpeg" alt="Ventoy 多启动菜单"></p>
+<p>P.S. 我的笔记本兴许和 Ventoy 的 UEFI 模式相性不大好,在 BIOS 中开启了 Lagacy 兼容模式后,使用 Legacy 模式才能开启 Ventoy。</p>
+<h4 id="选择再生龙启动方式"><a href="#选择再生龙启动方式" class="headerlink" title="选择再生龙启动方式"></a>选择再生龙启动方式</h4><p><img src="/images/2022-08-12/2.jpeg" alt="再生龙启动菜单"></p>
+<p>经典的 GRUB 启动菜单,一般来说选择默认的第一项启动方式即可。</p>
+<h5 id="VGA-启动花屏"><a href="#VGA-启动花屏" class="headerlink" title="VGA 启动花屏"></a>VGA 启动花屏</h5><p>我的电脑遇到了在 VGA 800x600 模式下花屏的问题。</p>
+<p>最终进入 <code>Other mods of Clonezilla live</code> 菜单,</p>
+<p><img src="/images/2022-08-12/3.jpeg" alt="其他启动模式"></p>
+<p>选择了上图中的 KVM & To RAM 模式,可以正常启动了。</p>
+<h5 id="USB-口不够用的用户"><a href="#USB-口不够用的用户" class="headerlink" title="USB 口不够用的用户"></a>USB 口不够用的用户</h5><p>我这台笔记本只有两个 USB 口,其中一个要给备份源头 CZ880,另一个则要给移动硬盘,故选择了 <code>To RAM</code> 模式,将再生龙载入内存,就可以拔掉多启动U盘,空出 USB 口给移动硬盘了。</p>
+<h4 id="语言配置"><a href="#语言配置" class="headerlink" title="语言配置"></a>语言配置</h4><p><img src="/images/2022-08-12/4.jpeg" alt="语言配置"></p>
+<p>选择自己想用的语言即可。</p>
+<p><img src="/images/2022-08-12/5.jpeg" alt="键盘配置"></p>
+<p>保持默认配置即可。</p>
+<h4 id="备份配置"><a href="#备份配置" class="headerlink" title="备份配置"></a>备份配置</h4><p><img src="/images/2022-08-12/6.jpeg" alt="功能选择"></p>
+<p>我们选 <code>Start Clonezilla 使用再生龙</code>。</p>
+<p>命令行可以在熟悉了配置之后使用。</p>
+<p><img src="/images/2022-08-12/7.jpeg" alt="备份模式选择"></p>
+<p>此处我们选择第一项 <code>device-image 硬盘/分区[存到/来自]镜像文件</code>。</p>
+<p>若想进行两盘对拷,可以选择第二项。我还没有尝试过。</p>
+<h4 id="挂载存储目录"><a href="#挂载存储目录" class="headerlink" title="挂载存储目录"></a>挂载存储目录</h4><p><img src="/images/2022-08-12/8.jpeg" alt="存储目录选择"></p>
+<p>这次我打算使用移动硬盘备份系统,故选择第一项 <code>local dev 使用本机的分区</code>。</p>
+<p><img src="/images/2022-08-12/10.jpeg" alt="插入 USB 设备提示"></p>
+<p>随后,再生龙会提示插入想要挂载的 USB 设备,按照提示做即可。</p>
+<p><img src="/images/2022-08-12/11.jpeg" alt="检测到的存储设备"></p>
+<p>此时画面会动态显示系统识别到的存储设备。看到期望的目标设备时,按下 <code>Ctrl-C</code> 停止搜索。</p>
+<p><img src="/images/2022-08-12/12.jpeg" alt="分区选择"></p>
+<p>在扫描完电脑当前安装的所有硬盘的分区后,你需要选择备份镜像文件存放的那个分区。</p>
+<p>如图,我希望备份到大小为 1.8T 的移动硬盘上,故选择最后一项 <code>sdc2</code>。</p>
+<p><img src="/images/2022-08-12/13.jpeg" alt="是否检查并修复文件系统"></p>
+<p>随后,再生龙询问你是否需要检查并修复挂载的文件系统,我们选第一项否就好了。</p>
+<p><img src="/images/2022-08-12/14.jpeg" alt="备份位置选择"></p>
+<p>接着,就是选择备份镜像存放的位置。</p>
+<p>使用键盘的方向键选择目录,使用 <code>Tab</code> 跳转到下方的选项,选择 <code>Browse</code> 并敲击回车就可以进入到此目录。</p>
+<p>若希望在选中目录下存放备份镜像文件(是一个文件夹),就可以选择 <code>Done</code> 选项,回车确认。</p>
+<p><img src="/images/2022-08-12/15.jpeg" alt="是否检查镜像可还原性"></p>
+<p>系统询问是否检查生成的备份镜像的可还原性,这里我们选是,多花一点时间能确保备份的完整性。</p>
+<p><img src="/images/2022-08-12/16.jpeg" alt="是否对镜像加密"></p>
+<p>镜像加密,依个人喜好选择。</p>
+<p><img src="/images/2022-08-12/18.jpeg" alt="备份模式确认"></p>
+<p>待上述配置完成后,系统会向你再次确认备份的内容与目的地。</p>
+<p>确认无误后输入 <code>y</code> 并敲击回车继续。</p>
+<h4 id="简单模式-高级模式"><a href="#简单模式-高级模式" class="headerlink" title="简单模式/高级模式"></a>简单模式/高级模式</h4><p>此时应该有一个模式选择,问你想要使用简单模式还是专家模式。</p>
+<p>我建议选择 专家模式,简单模式能选择的参数较少。</p>
+<p><img src="/images/2022-08-12/20.jpeg" alt="模式选择"></p>
+<p>接下来的三个选项,全部保持默认配置即可。</p>
+<p><img src="/images/2022-08-12/21.jpeg" alt="高级设置1"></p>
+<p><img src="/images/2022-08-12/22.jpeg" alt="高级设置2"></p>
+<p><img src="/images/2022-08-12/23.jpeg" alt="高级设置3"></p>
+<h4 id="压缩方式选择"><a href="#压缩方式选择" class="headerlink" title="压缩方式选择"></a>压缩方式选择</h4><p><img src="/images/2022-08-12/24.jpeg" alt="压缩方式选择"></p>
+<p>此处选择第三项 <code>-z2p 使用并行 bzip2 压缩</code>。</p>
+<p>实测 bzip2 压缩速度比较快,产生的备份镜像的体积也不算大。</p>
+<p>下图为选择了第一项 <code>-z1p 使用并行的 gzip 压缩</code> 的速度:</p>
+<p><img src="/images/2022-08-12/19.jpeg" alt="并行 gzip 压缩速度"></p>
+<p>下图为选择了第三项 <code>-z2p 使用并行 bzip2 压缩</code> 的速度:</p>
+<p><img src="/images/2022-08-12/26.jpeg" alt="并行 bzip2 压缩速度"></p>
+<p>可以看出 bzip2 压缩速度比 gzip 快了8倍。</p>
+<p>其他压缩方式的速度,待我测试之后更新文章。</p>
+<p><img src="/images/2022-08-12/25.jpeg" alt="分卷大小配置"></p>
+<p>分卷大小配置保持默认即可。</p>
+<h4 id="备份镜像检查"><a href="#备份镜像检查" class="headerlink" title="备份镜像检查"></a>备份镜像检查</h4><p>待备份完成后,再生龙还会进行一次备份镜像的可还原性检查,如下图:</p>
+<p><img src="/images/2022-08-12/27.jpeg" alt="可还原性检查"></p>
+<p>若得到下图的提示,则备份镜像生成成功了。</p>
+<p><img src="/images/2022-08-12/28.jpeg" alt="可还原性检查完成"></p>
+<p>随后,选择按照意愿选择备份结束后的操作即可。</p>
+<p><img src="/images/2022-08-12/29.jpeg" alt="备份结束后操作选择"></p>
+<p>至此,再生龙 CLonezilla 的基础教学就结束了,你已经学会了如何使用再生龙的图形界面进行备份。</p>
+<p>下面是一些再生龙的高阶(大概很高级)使用方法。</p>
+<h3 id="高级操作"><a href="#高级操作" class="headerlink" title="高级操作"></a>高级操作</h3><h4 id="使用无线网络备份"><a href="#使用无线网络备份" class="headerlink" title="使用无线网络备份"></a>使用无线网络备份</h4><p>上文中,我的电脑仅有两个 USB 口,为备份的流程增添了不必要的麻烦。</p>
+<p>能否使用 Wi-Fi 将备份镜像推送至家中的 NAS 呢?</p>
+<p>再生龙内置了许多通过无线/有线网络备份的方法,如下图:</p>
+<p><img src="/images/2022-08-12/30.jpeg" alt="备份选项"></p>
+<p>我们尝试使用 Webdav 来远程备份吧!</p>
+<h5 id="利与弊"><a href="#利与弊" class="headerlink" title="利与弊"></a>利与弊</h5><p>使用 Wi-Fi 备份可以摆脱线缆,更加轻松而优雅地进行备份。</p>
+<p>然而,由于通过 Wi-Fi 或者一切网络传输数据的速度仍然无法比肩有线传输,备份所消耗的时间将是备份至本地磁盘的 3-4 倍。</p>
+<p>备份我U盘中的 40G 的 Manjaro 系统用时 30min 左右。</p>
+<p>倘若你有大把的时间,或家中的内网速度足够快,大可使用无线备份。品着咖啡,看着数据上云(笑)。</p>
+<h5 id="预先准备"><a href="#预先准备" class="headerlink" title="预先准备"></a>预先准备</h5><p>上文中我们选择了基于 Debian 的 Clonezilla Stable 版本,遗憾的是 Debian 系统中并未携带太多驱动程序,因此识别不到我的 Intel AX200 无线网卡。</p>
+<p>经过测试,基于 Ubuntu 的 <a href="https://clonezilla.org/downloads/download.php?branch=alternative">Clonezilla Alternative Stable</a> 版本可以识别到 AX200 网卡。</p>
+<p>点击上方链接即可下载 Clonezilla Alternative Stable 版本的映像。</p>
+<p>重新烧写启动U盘/拷贝映像至多启动U盘即可。</p>
+<h6 id="又遇到了启动问题"><a href="#又遇到了启动问题" class="headerlink" title="又遇到了启动问题"></a>又遇到了启动问题</h6><p>使用基于 Ubuntu 的再生龙,上文中使用的 <code>KVM</code> 模式变得无法打开了,且 <code>VGA 800x600</code> 模式是一样的花屏。</p>
+<p>在一番尝试之后,我发现藏在更多启动选项菜单里的 <code>VGA 1024x768</code> 模式可以正常显示。看来基于 Debian 的再生龙也可以使用这个模式。</p>
+<h5 id="开始备份-1"><a href="#开始备份-1" class="headerlink" title="开始备份"></a>开始备份</h5><p><img src="/images/2022-08-12/31.jpeg" alt="网络管理"></p>
+<p>选择了非本地的镜像存储位置后,系统将开启上图的网络管理菜单。</p>
+<p>选择第一项 <code>Edit a connection</code>。</p>
+<p><img src="/images/2022-08-12/32.jpeg" alt="连接管理"></p>
+<p>选择 <code>Add</code> 选项,在弹出菜单中 <code>Wi-Fi</code>。</p>
+<p><img src="/images/2022-08-12/33.jpeg" alt="添加 Wi-Fi"></p>
+<p><code>Profile name</code> 随意填写;</p>
+<p><code>Device</code> 一般填写 <code>wlan0</code>,系统的第一块无线网卡;</p>
+<p>接着,按照自己的情况填写图中划线的三个配置即可。</p>
+<p><img src="/images/2022-08-12/34.jpeg" alt="连接状态"></p>
+<p>保存 Wi-Fi 配置后,就能看到当前配置的连接状态。</p>
+<p>若当前配置名前带 <code>*</code>,且右侧选项为 <code>Deactivate</code>,则 Wi-Fi 已连接成功。</p>
+<p><img src="/images/2022-08-12/35.jpeg" alt="填写 Webdav 服务器地址"></p>
+<p>接着,系统要求填写 Webdav 地址。</p>
+<p><img src="/images/2022-08-12/36.jpeg" alt="确认 Webdav 配置"></p>
+<p>最后,系统会向你确认 Webdav 是否正确。</p>
+<p>若确认无误,即可敲击回车继续。</p>
+<p>接下来的步骤和上述初级教程硬盘挂载之后的流程是完全一样的,请参考上文继续配置。</p>
+
+
+ 2022-08-12T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/f82a3103.html
+ BETAFPV 高频头固件编译 AttributeError
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="错误原因"><a href="#错误原因" class="headerlink" title="错误原因"></a>错误原因</h2><p>Python 模块 <code>pypandoc</code> 版本过新,1.8.0 及更高版本已移除了 BETAFPV 高频头固件中仍在使用的 <code>convert</code> 函数。</p>
+<h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><p>安装旧版的 <code>pypandoc</code> 模块。</p>
+<p><code>pip install pypandoc==1.7.0</code></p>
+
+
+
+ 2022-08-06T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/38942a16.html
+ DIY 显示器音箱
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>新买的显示器(LG 27UL500,虽然因为屏幕问题已经退货了)没有内置音箱,虽然大部分时间都在用耳机,但别人有的东西我不能没有嘛,就买了些材料,DIY 一个外接音箱。</p>
+<p>写作此文章分享一下制作的过程~</p>
+<h3 id="物料清单"><a href="#物料清单" class="headerlink" title="物料清单"></a>物料清单</h3><p><img src="/images/2022-06-03/01.JPG" alt="物料清单"></p>
+<ul>
+<li>PAM8403 数字功放板 5RMB</li>
+</ul>
+<p>接受 5V 电压,可以驱动两个 3W 的喇叭。商品详情页面吹的很厉害,确实底噪很小,而且输出的音量非常高。相当 OK 的功放板。</p>
+<ul>
+<li>8Ω 3W 喇叭两只(带音腔) 2*4RMB + 运费</li>
+</ul>
+<p>音质很不错,声音很大也不会破音,因为是广告机用的喇叭么?</p>
+<ul>
+<li>3.5MM 公头 0.5RMB</li>
+</ul>
+<p>我选的型号是 PJ392,只要是 3.5MM 双声道的公头就行了。</p>
+<ul>
+<li>Type C 母座 0.4RMB</li>
+</ul>
+<p>这个随意选。</p>
+<ul>
+<li>屏蔽线缆 2RMB/m</li>
+</ul>
+<p>我买的是4芯的屏蔽线,可供 Type C 头使用(2 power 2 data),音频线只需要三芯(2 data 1 GND),屏蔽线是为了更小的干扰、更好的音质。</p>
+<h3 id="开始组装"><a href="#开始组装" class="headerlink" title="开始组装"></a>开始组装</h3><h4 id="3-5MM-线缆"><a href="#3-5MM-线缆" class="headerlink" title="3.5MM 线缆"></a>3.5MM 线缆</h4><p>剥除一段屏蔽线的外皮,做工还是很不错的,有金属丝和铝箔的屏蔽,塑料膜防水,还有一根抗拉扯的填充芯。</p>
+<p>我选择使用红绿蓝三根线,黄线悬空。线色对应如下:</p>
+<p>红 - 左声道;绿 - 右声道;蓝 - 接地。</p>
+<p><img src="/images/2022-06-03/02.JPG" alt="屏蔽线"></p>
+<p>可以预先套上一段热缩管。</p>
+<p><img src="/images/2022-06-03/03.JPG" alt="热缩管"></p>
+<p>取一枚 3.5mm 公头,旋下插头。</p>
+<p>最长的一段一般是接地。若将接地朝下,我这款公头左侧为左声道,右侧为右声道。具体的接线方式可以用万用表测量接头和接口得知。</p>
+<p>将线穿入孔中,上一坨焊锡即可。</p>
+<p><img src="/images/2022-06-03/04.JPG" alt="公头焊接"></p>
+<p>再使用万用表测量接头与线末端的连通性,注意不能与其他线短路了。</p>
+<p>确认无误后可以打上热熔胶固定。</p>
+<p><img src="/images/2022-06-03/05.JPG" alt="热熔胶固定"></p>
+<p>再打点热熔胶,旋回外壳,将热缩管套好,加热热缩管使其收缩。</p>
+<p>3.5mm 线缆就制作完成了。</p>
+<h4 id="驱动板焊接"><a href="#驱动板焊接" class="headerlink" title="驱动板焊接"></a>驱动板焊接</h4><p>驱动板上有三组线需要焊接:</p>
+<ul>
+<li>音频输入线(3.5mm 线缆)</li>
+<li>电源输入线(Type C 线)</li>
+<li>音频输出线(喇叭线)</li>
+</ul>
+<p>Type C 线我没有再用屏蔽线,用两根导线连接 Type C 母头和驱动板即可。</p>
+<p>焊接方法就不多说了,线穿过孔,上锡即可。</p>
+<p><img src="/images/2022-06-03/06.JPG" alt="焊接中的驱动板"></p>
+<p>全部线缆焊接完成如下:</p>
+<p><img src="/images/2022-06-03/07.JPG" alt="焊接完的驱动板"></p>
+<h4 id="热熔胶填充"><a href="#热熔胶填充" class="headerlink" title="热熔胶填充"></a>热熔胶填充</h4><p>完成接线后,确认无短路,即可连接电脑测试音箱。</p>
+<p>若没有问题,考虑到需要长期使用,就可以用热熔胶覆盖焊接处,防止焊点脱落。</p>
+<p>用热熔胶覆盖之后的驱动板:</p>
+<p><img src="/images/2022-06-03/08.JPG" alt="热熔胶覆盖的驱动板"></p>
+<p>嘛…手艺不是很行。</p>
+<hr>
+<p>就此,外接音箱组装完成啦!</p>
+
+
+
+ 2022-06-03T07:15:00.000Z
+
+
+ https://blog.udon.eu.org/archives/2e528779.html
+ 迁移 Hexo 渲染环境至 GitHub Actions
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>本博客使用的是 Hexo 静态博客框架,我将渲染环境搭建在家中 NAS 之上,部署了一个 Ubuntu Docker 并安装好了 Node.js 环境,正常使用了一两年。</p>
+<p>上周末,写完新文章的我心血来潮,打算在更新博客的时候顺便更新一下老旧的 Node 和 Hexo(Node 12, Hexo 5.2)。</p>
+<p>一番折腾之后,以 npm 和 hexo 环境全部被破坏(事后想想也许只是环境变量掉了)的下场结束。</p>
+<p>鉴于 NAS 性能低下,更新一次 node module 都需要 30 分钟,我放弃了在 NAS 上重新部署渲染环境的念头,转而使用 GitHub Actions 渲染,并同时部署于 GitHub Pages 与 CloudFlare Pages。</p>
+<h2 id="将渲染环境迁至-GitHub-Actions"><a href="#将渲染环境迁至-GitHub-Actions" class="headerlink" title="将渲染环境迁至 GitHub Actions"></a>将渲染环境迁至 GitHub Actions</h2><p>不久之前,CloudFlare Pages 悄悄下架了 Hexo 框架的部署功能,只能用 GitHub Actions 渲染,然后再部署至 CloudFlare Pages 了。</p>
+<h3 id="项目结构的修改"><a href="#项目结构的修改" class="headerlink" title="项目结构的修改"></a>项目结构的修改</h3><p>若想使用 GitHub Actions,需要将博客的源码上传至 GitHub。考虑到隐私和安全的问题,建议创建一个私有仓库管理源码。</p>
+<p>对于项目没有什么需要修改的,因为 Actions 渲染的流程和本地渲染的流程没有区别。</p>
+<p>唯一需要改动的,是引入的主题。由于两个 Git 仓库不能嵌套,我们需要以 Git submodule 的形式引入主题仓库。</p>
+<p>我使用的是 Fluid 主题。采用 <a href="https://hexo.fluid-dev.com/docs/guide/#%E8%A6%86%E7%9B%96%E9%85%8D%E7%BD%AE">覆盖配置</a> 的方法,即在根目录之下有一份配置会覆盖主题内的配置文件,便于在 Actions 中渲染。</p>
+<p>以下也将以 Fluid 为例,请根据你使用的主题修改命令或代码。</p>
+<p>首先,删除原来的主题(若使用的是主题内的配置,注意备份配置文件!)</p>
+<p>返回博客源码的根目录,执行:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git submodule add https://github.com/fluid-dev/hexo-theme-fluid.git themes/fluid<br></code></pre></td></tr></table></figure>
+
+<p>末尾的 <code>themes/fluid</code> 为此 submodule 在项目中的位置与名字,与先前本地渲染时的配置相同即可。</p>
+<p>删除子模块的过程较为繁琐,请参考网上的文章进行操作。</p>
+<p>在 Clone 此项目时,submodule 默认不会被下载,需要使用指令:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git submodule update --init --recursive<br></code></pre></td></tr></table></figure>
+
+<p>下载 submodule。在下面会提到的 Actions 配置文件中会出现这条指令。</p>
+<p>接着,就可以将博客源码上传至 GitHub。</p>
+<h3 id="GitHub-Actions-相关文件"><a href="#GitHub-Actions-相关文件" class="headerlink" title="GitHub Actions 相关文件"></a>GitHub Actions 相关文件</h3><p>在博客源码根目录创建 <code>.github/workflows/submit.yml</code> 和 <code>.github/script/blog-update.sh</code> 两个文件,填入下列代码。</p>
+<p>以下代码参考文章 <a href="https://blog.kukmoon.com/f8bb4ee.html#23-%E7%BC%96%E5%86%99-workflow">GitHub Actions 自动部署 Hexo 博客到 cPanel 虚拟主机 - 谷中望月</a>,有所修改。</p>
+<p><code>submit.yml</code>:</p>
+<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">CI</span><br><br><span class="hljs-comment"># 监听 main 分支的改动与 Release 的发布</span><br><span class="hljs-attr">on:</span><br> <span class="hljs-attr">push:</span><br> <span class="hljs-attr">branches:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">main</span><br> <span class="hljs-attr">release:</span><br> <span class="hljs-attr">types:</span> [<span class="hljs-string">published</span>]<br><br><span class="hljs-comment"># 自定义环境变量</span><br><span class="hljs-attr">env:</span><br> <span class="hljs-attr">GIT_USER:</span> <span class="hljs-string">Lao-Liu233</span> <span class="hljs-comment"># 改成你自己的 GitHub 用户名</span><br> <span class="hljs-attr">GIT_EMAIL:</span> <span class="hljs-string">blog@udon.eu.org</span> <span class="hljs-comment"># 改成你自己的 GitHub 注册邮箱</span><br><br><span class="hljs-attr">jobs:</span><br> <span class="hljs-attr">build:</span><br> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">on</span> <span class="hljs-string">node</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node_version</span> <span class="hljs-string">}}</span> <span class="hljs-string">and</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.os</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span><br> <span class="hljs-attr">strategy:</span><br> <span class="hljs-attr">matrix:</span><br> <span class="hljs-attr">os:</span> [<span class="hljs-string">ubuntu-latest</span>]<br> <span class="hljs-attr">node_version:</span> [<span class="hljs-number">16.15</span>] <span class="hljs-comment"># 改成你本地的 Node.js 版本,可以用 `node --version` 命令查询</span><br><br> <span class="hljs-attr">steps:</span><br> <span class="hljs-comment"># 获取博客源码</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span><br> <br> <span class="hljs-comment"># 用 Node.js 渲染</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Use</span> <span class="hljs-string">Node.js</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node_version</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v1</span><br> <span class="hljs-attr">with:</span><br> <span class="hljs-attr">node-version:</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node_version</span> <span class="hljs-string">}}</span><br><br> <span class="hljs-comment"># 安装 Hexo-cli </span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">hexo</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> npm install -g hexo-cli</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 安装依赖</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span> <br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> npm install</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 导入 submodule</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Clone</span> <span class="hljs-string">submodule</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> git submodule update --init --recursive</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 配置环境</span><br> <span class="hljs-comment"># ssh-kenscan github.com >> ~/.ssh/known_hosts # 从 GitHub 获取公钥并保存到 known_hosts 文件</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Configuration</span> <span class="hljs-string">environment</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> sudo timedatectl set-timezone "Asia/Shanghai"</span><br><span class="hljs-string"> mkdir -p ~/.ssh/</span><br><span class="hljs-string"> echo "$HEXO_DEPLOY_PRI" > ~/.ssh/id_rsa</span><br><span class="hljs-string"> chmod 600 ~/.ssh/id_rsa</span><br><span class="hljs-string"> ssh-keyscan github.com >> ~/.ssh/known_hosts</span><br><span class="hljs-string"> git config --global user.name $GIT_USER</span><br><span class="hljs-string"> git config --global user.email $GIT_EMAIL</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 生成并部署</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">hexo</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> hexo clean</span><br><span class="hljs-string"> hexo g -d</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># 部署后更新博客源码,用于添加 abbrlink,如果不用 abbrlink,需要删除</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Update</span> <span class="hljs-string">Blog</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br> <span class="hljs-string">sh</span> <span class="hljs-string">"${GITHUB_WORKSPACE}/.github/script/blog-update.sh"</span><br></code></pre></td></tr></table></figure>
+
+<p><code>.github/script/blog-update.sh</code>:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-meta">#!/bin/sh</span><br><br><span class="hljs-keyword">if</span> [ -z <span class="hljs-string">"<span class="hljs-subst">$(git status --porcelain)</span>"</span> ]; <span class="hljs-keyword">then</span><br> <span class="hljs-built_in">echo</span> <span class="hljs-string">"nothing to update."</span><br><span class="hljs-keyword">else</span><br> git add <span class="hljs-built_in">source</span>/_posts/ <span class="hljs-comment">#仅对文章源码所在文件夹进行修改</span><br> git commit -m <span class="hljs-string">"triggle by commit <span class="hljs-variable">${GITHUB_SHA}</span>"</span> -a<br> git push origin main<br><span class="hljs-keyword">fi</span><br></code></pre></td></tr></table></figure>
+
+<p>Commit + Push,打开 Actions 界面,就能看到正在运行的 Action 啦。</p>
+<p>不出意外,Action 成功执行,1分钟内博客就能渲染成功、部署至 GitHub Pages。</p>
+<h2 id="同时部署至-CloudFlare-Pages"><a href="#同时部署至-CloudFlare-Pages" class="headerlink" title="同时部署至 CloudFlare Pages"></a>同时部署至 CloudFlare Pages</h2><p>步骤较为简单,我简述一下。</p>
+<p>打开 CloudFlare Pages, 连接至存放 <strong>渲染后</strong> 的静态文件的仓库,渲染的框架选择 <strong>None</strong>,执行的指令填写 <code>exit 0;</code> 就可以了。</p>
+<p>执行部署后,渲染后的静态文件就被部署至 CloudFlare Pages 啦。</p>
+
+
+
+
+ 2022-05-23T11:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/dbf21067.html
+ 玩一玩 DN42
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>两个月前,我向 DN42 提交了申请,并于4个小时后通过了审核,获得了自己的 AS 和 IP。</p>
+<p>作此文分享一下把玩 DN42 的心得,也作为我的备忘录。</p>
+<h2 id="我的信息"><a href="#我的信息" class="headerlink" title="我的信息"></a>我的信息</h2><figure class="highlight maxima"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs maxima">AS4242423490<br>IPv4: <span class="hljs-number">172.23</span><span class="hljs-number">.13</span><span class="hljs-number">.64</span>/<span class="hljs-number">28</span><br>IPv6: fd44:<span class="hljs-number">6b93</span>:4eaa::/<span class="hljs-number">48</span><br></code></pre></td></tr></table></figure>
+
+<p>目前仅一个洛杉矶节点开放 Peer,后期还将添加韩国和日本的节点。</p>
+<h2 id="如何把玩"><a href="#如何把玩" class="headerlink" title="如何把玩"></a>如何把玩</h2><h3 id="注册"><a href="#注册" class="headerlink" title="注册"></a>注册</h3><p>有关注册的文章很多,推荐这两篇:</p>
+<p><a href="https://lantian.pub/article/modify-website/dn42-experimental-network-2020.lantian/">DN42 实验网络介绍及注册教程(2022-02 更新) | Lan Tian @ Blog</a></p>
+<p><a href="https://blog.baoshuo.ren/post/dn42-network/#">初探 DN42 网络 - 宝硕博客 (baoshuo.ren)</a></p>
+<p>需要使用 Git 和 PGP,在 DN42 的 Git 仓库提交你的个人信息即可完成注册。</p>
+<h3 id="搭建内网"><a href="#搭建内网" class="headerlink" title="搭建内网"></a>搭建内网</h3><p>在和其他 AS 建立对等连接之前,我们先要把内网整理好:</p>
+<p>各台服务器的地理位置和网络位置各不相同,需要使用 VPN 创建虚拟局域网。</p>
+<p>课堂上讲了两种内网路由协议:</p>
+<ul>
+<li><p>RIP 是“真”内网用的,不适用于这种物理位置距离较远(路由跳数多)的虚拟内网;</p>
+</li>
+<li><p>可以使用 OSPF,但我在配置的时候遇到了不少问题,因此也不建议你使用。</p>
+</li>
+</ul>
+<p>有一位老朋友可以轻松解决以上两个问题:<strong>Zerotier</strong>。</p>
+<p>Zerotier 的虚拟网络可以使用自己的 IP,只需在 <strong>Managed Routes</strong> 中设置你在 DN42 申请的 IPv4 和 IPv6,即可为每台加入虚拟网络的主机自动或手动配置 DN42 IP。</p>
+<p>在机器之间使用 DN42 IP 互 ping 测试连通性。</p>
+<h3 id="准备-BGP-相关软件"><a href="#准备-BGP-相关软件" class="headerlink" title="准备 BGP 相关软件"></a>准备 BGP 相关软件</h3><p>搭建好内网之后,就可以开始配置 BGP 发言人啦。</p>
+<p>选择一台或多台服务器,作为自治域向外宣告路由的发言人。</p>
+<p>在每台服务器上都需要配置 BGP 相关的软件,以及和其他 BGP 发言人建立连接(一般是 VPN 连接)的软件。</p>
+<p>目前在 DN42 网络用的比较多的 VPN 软件是 Wireguard,BGP 软件则可以从 bird 2、bird 1、quagga 等软件中选择。</p>
+<p>我使用的是 bird 2。</p>
+<h4 id="安装与配置-BIRD-2"><a href="#安装与配置-BIRD-2" class="headerlink" title="安装与配置 BIRD 2"></a>安装与配置 BIRD 2</h4><p>安装命令:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">apt update<br>apt install bird2 -y<br></code></pre></td></tr></table></figure>
+
+<p>bird 2 的配置文件位于 <code>/etc/bird</code>,名为 <code>bird.conf</code>。</p>
+<p>配置文件可以参考(<del>照抄</del>)DN42 官方给出的配置:<a href="https://dn42.dev/howto/Bird2#example-configuration">howto/Bird2 (dn42.dev)</a></p>
+<p>喂到嘴边的配置方法:</p>
+<ul>
+<li>将官方配置填入 <code>/etc/bird/bird.conf</code></li>
+<li>在 <code>/etc/bird</code> 目录下新建名为 <code>peers</code> 的文件夹</li>
+<li>下载 ROA 配置(命令来自<a href="https://blog.baoshuo.ren/">宝硕的博客</a>)</li>
+</ul>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">wget -4 -O /tmp/dn42_roa.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf && <span class="hljs-built_in">mv</span> -f /tmp/dn42_roa.conf /etc/bird/dn42_roa.conf<br>wget -4 -O /tmp/dn42_roa_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf && <span class="hljs-built_in">mv</span> -f /tmp/dn42_roa_v6.conf /etc/bird/dn42_roa_v6.conf<br></code></pre></td></tr></table></figure>
+
+<p> 并配置 crontab,每小时自动下载并重载新配置:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">0 */1 * * * wget -4 -O /tmp/dn42_roa.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf && <span class="hljs-built_in">mv</span> -f /tmp/dn42_roa.conf /etc/bird/dn42_roa.conf<br>0 */1 * * * wget -4 -O /tmp/dn42_roa_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf && <span class="hljs-built_in">mv</span> -f /tmp/dn42_roa_v6.conf /etc/bird/dn42_roa_v6.conf<br>0 */1 * * * birdc configure<br></code></pre></td></tr></table></figure>
+
+<h4 id="安装并配置-Wireguard"><a href="#安装并配置-Wireguard" class="headerlink" title="安装并配置 Wireguard"></a>安装并配置 Wireguard</h4><p>安装命令:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">apt update<br>apt install wireguard -y<br></code></pre></td></tr></table></figure>
+
+<p>这样就安装了 <code>Wireguard</code> 和名为 <code>wg-quick</code> 的管理工具。</p>
+<p>使用命令:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">wg genkey | <span class="hljs-built_in">tee</span> privatekey | wg pubkey > publickey<br></code></pre></td></tr></table></figure>
+
+<p>在当前目录下创建 Wireguard 建立连接所用的密钥对(公钥和私钥)。</p>
+<p>就此 Wireguard 安装完成。</p>
+<h4 id="配置系统内核"><a href="#配置系统内核" class="headerlink" title="配置系统内核"></a>配置系统内核</h4><p>打开内核的数据包转发功能:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv4.ip_forward=1"</span> >> /etc/sysctl.conf<br><span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv6.conf.default.forwarding=1"</span> >> /etc/sysctl.conf<br><span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv6.conf.all.forwarding=1"</span> >> /etc/sysctl.conf<br>sysctl -p<br></code></pre></td></tr></table></figure>
+
+<p>关闭内核 <code>rp_filter</code> 的严格模式:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv4.conf.default.rp_filter=0"</span> >> /etc/sysctl.conf<br><span class="hljs-built_in">echo</span> <span class="hljs-string">"net.ipv4.conf.all.rp_filter=0"</span> >> /etc/sysctl.conf<br>sysctl -p<br></code></pre></td></tr></table></figure>
+
+<p>如果有 ufw 等防火墙自动配置工具,务必关闭。</p>
+<p>p.s. 我拿到任何机器后会立刻执行的指令是:</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> ufw <span class="hljs-built_in">disable</span><br></code></pre></td></tr></table></figure>
+
+<h4 id="创建-Dummy-网卡"><a href="#创建-Dummy-网卡" class="headerlink" title="创建 Dummy 网卡"></a>创建 Dummy 网卡</h4><p>dummy 网卡具体的作用我不是很清楚…</p>
+<p>只知道如果要用链路本地地址进行通讯,要把 DN42 的 IP 地址绑定到 dummy 网卡上。</p>
+<p>dummy 网卡配置指令如下:([ ] 中为需要你填写的内容)</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash">ip <span class="hljs-built_in">link</span> del dummy<br>ip <span class="hljs-built_in">link</span> add dummy <span class="hljs-built_in">type</span> dummy<br>ip addr add [你的 DN42 IPv4 地址]/32 dev dummy<br>ip addr add [你的 DN42 IPv6 地址]/128 dev dummy<br>ip <span class="hljs-built_in">link</span> <span class="hljs-built_in">set</span> dummy up<br></code></pre></td></tr></table></figure>
+
+<h3 id="和小伙伴建立对等连接(peer)"><a href="#和小伙伴建立对等连接(peer)" class="headerlink" title="和小伙伴建立对等连接(peer)"></a>和小伙伴建立对等连接(peer)</h3><h4 id="需要和对方分享的"><a href="#需要和对方分享的" class="headerlink" title="需要和对方分享的"></a>需要和对方分享的</h4><ul>
+<li>你的 DN42 信息,包括 AS 号和发言人的 DN42 IPv4(IPv6)地址;</li>
+<li>若使用链路本地地址,还需提供这东西,一般为一个本地 IPv6 地址,常取 <code>fe80::[你的 AS 号后4位]</code>;</li>
+<li>发言人的外网 IPv4 地址(或域名)和 Wireguard 隧道的通讯端口;</li>
+<li>Wireguard 公钥。</li>
+</ul>
+<p>有一些信息会在以下的配置中获得。</p>
+<h4 id="Wireguard-相关的"><a href="#Wireguard-相关的" class="headerlink" title="Wireguard 相关的"></a>Wireguard 相关的</h4><p>在 <code>/etc/wireguard</code> 目录下创建 Wireguard 配置文件,每一个配置文件对应着一个 Wireguard 隧道。</p>
+<p>例如你要和 AS114514 <del>臭</del> 建立对等连接,可以在 <code>peers</code> 文件夹下新建一个名为 <code>wg_114514.conf</code> (文件名即为 wireguard 隧道名)的配置文件。</p>
+<p>配置的模板如下:([ ] 中为需要你填写的内容)</p>
+<figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs ini"><span class="hljs-section">[Interface]</span><br><span class="hljs-attr">Table</span> = <span class="hljs-literal">off</span><br><span class="hljs-attr">ListenPort</span> = [我们的监听端口,可以用对方 AS 号的后五位]<br><span class="hljs-attr">PrivateKey</span> = [刚刚生成的 Wireguard 私钥]<br><span class="hljs-attr">PostUp</span> = ip addr add [本机的 DN42 IPv4 地址]/<span class="hljs-number">32</span> peer [对方机器的 DN42 IPv4 地址]/<span class="hljs-number">32</span> dev %i<br><span class="hljs-attr">PostUp</span> = ip addr add [本机的链路本地地址(见 BIRD 相关配置)]/<span class="hljs-number">64</span> dev %i<br><br><span class="hljs-section">[Peer]</span><br><span class="hljs-attr">PublicKey</span> = [对方的 Wireguard 公钥]<br><span class="hljs-attr">AllowedIPs</span> = <span class="hljs-number">10.0</span>.<span class="hljs-number">0.0</span>/<span class="hljs-number">8</span>, <span class="hljs-number">172.20</span>.<span class="hljs-number">0.0</span>/<span class="hljs-number">14</span>, <span class="hljs-number">172.31</span>.<span class="hljs-number">0.0</span>/<span class="hljs-number">16</span>, fd00::/<span class="hljs-number">8</span>, fe80::/<span class="hljs-number">64</span><br><span class="hljs-attr">Endpoint</span> = [对方机器的公网 IP 地址或域名 : 端口号]<br></code></pre></td></tr></table></figure>
+
+<p>然后使用 <code>wg-quick up [wireguard 隧道名(刚刚的配置文件名)]</code> 启动 Wireguard 隧道。</p>
+<p>可以 ping 一下对方的 DN42 IP 看看 Wireguard 隧道是否连接成功。</p>
+<p>使用 <code>wg</code> 命令查看各隧道的连接情况。若有显示 <code>last handshake</code>,一般情况下隧道就已成功建立。</p>
+<h4 id="BIRD-相关的"><a href="#BIRD-相关的" class="headerlink" title="BIRD 相关的"></a>BIRD 相关的</h4><p>在先前导入的 bird 2 配置中定义了一个 <code>peers</code> 文件夹,就是用来存放 peer 相关的配置。</p>
+<p>例如你要和 AS114514 <del>又臭</del> 建立对等连接,可以在 <code>peers</code> 文件夹下新建一个名为 <code>114514.conf</code> (文件名可自定义)的配置文件。</p>
+<p>我采用的是<a href="https://zh.wikipedia.org/wiki/%E9%93%BE%E8%B7%AF%E6%9C%AC%E5%9C%B0%E5%9C%B0%E5%9D%80">链路本地地址(Link-Local)</a>的配置方法。配置的模板如下:([ ] 中为需要你填写的内容)</p>
+<figure class="highlight inform7"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs inform7">protocol bgp <span class="hljs-comment">[peer的名字]</span> from dnpeers {<br> neighbor <span class="hljs-comment">[对方的链路本地地址]</span> % '<span class="hljs-comment">[通向对方的 Wiregurad 隧道名]</span>' as <span class="hljs-comment">[对方的 AS 号]</span>;<br>}<br></code></pre></td></tr></table></figure>
+
+<p>添加完配置之后别忘了用 <code>birdc configure</code> 重载 bird 2 配置。</p>
+<p>使用命令 <code>birdc s p</code> 可以查看 BIRD 2 软件下所有协议的通信情况。</p>
+<p>若显示为:</p>
+<figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">dn42_xxxx</span> BGP --- up <span class="hljs-number">20</span>:<span class="hljs-number">36</span>:<span class="hljs-number">30</span>.<span class="hljs-number">984</span> Established<br></code></pre></td></tr></table></figure>
+
+<p>则表示 BGP 连接已建立。</p>
+<h2 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h2><p>我还在写 DN42 相关的站点,在上面分享节点信息,方便大家 peer。</p>
+<p>但目前进度缓慢(悲)。</p>
+
+
+ 2022-04-01T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/9b58c98e.html
+ 合宙 EPS32-C3 把玩记录(二):WiFi 与一个 Web 程序
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="了解一下相关的库"><a href="#了解一下相关的库" class="headerlink" title="了解一下相关的库"></a>了解一下相关的库</h2><ul>
+<li><h3 id="串口通信"><a href="#串口通信" class="headerlink" title="串口通信"></a>串口通信</h3></li>
+</ul>
+<p>这个库是自带的,不需要引入。</p>
+<p>据我的理解,单片机的串口就是控制台程序的控制台,可以返回一些信息给上位机。</p>
+<p>会用到的几个指令:</p>
+<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c">Serial.begin(Baudrate); <span class="hljs-comment">//参数为串口通信的波特率</span><br>Serial.end();<br>Serial.read(); <span class="hljs-comment">//读取串口收到数据的第一个字节</span><br>Serial.peek(); <span class="hljs-comment">//读取串口数据中下一字节的内容</span><br>Serial.flush(); <span class="hljs-comment">//清空缓冲区</span><br>Serial.print/println(); <span class="hljs-comment">//不用多说</span><br>Serial.write(); <span class="hljs-comment">//写二进制数据</span><br></code></pre></td></tr></table></figure>
+
+<ul>
+<li><h3 id="WiFi-h"><a href="#WiFi-h" class="headerlink" title="WiFi.h"></a>WiFi.h</h3></li>
+</ul>
+<p><code>#include <WiFi.h></code></p>
+<h4 id="AP(接入点)-Mode"><a href="#AP(接入点)-Mode" class="headerlink" title="AP(接入点) Mode"></a>AP(接入点) Mode</h4><p>创建一个接入点。</p>
+<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c">WiFi.mode(WiFi_AP); <span class="hljs-comment">//设置工作在 AP 模式</span><br>WiFi.softAPConfig(local_IP, gateway, subnet);<br><span class="hljs-comment">//定义本机 IP(这个不大确定)、网关 IP 和子网掩码</span><br><span class="hljs-comment">//IPAddress 数据类型格式:IPAddress local_IP(192,168,4,22);</span><br>WiFi.softAP(SSID,PASSWD); <span class="hljs-comment">//启动 AP,参数不多解释,返回 bool </span><br></code></pre></td></tr></table></figure>
+
+<p>更多函数见 <a href="https://blog.csdn.net/Naisu_kun/article/details/86165403#_28">WiFi.h AP 常用方法说明</a></p>
+<h4 id="STA(站点)-Mode"><a href="#STA(站点)-Mode" class="headerlink" title="STA(站点) Mode"></a>STA(站点) Mode</h4><p>接入一个 AP。</p>
+<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c">WiFi.mode(WIFI_STA); <span class="hljs-comment">//设置工作在 STA 模式</span><br>WiFi.start(SSID,PASSWD) <span class="hljs-comment">//连接至 AP,参数不多解释</span><br>Serial.println(WiFi.localIP()); <span class="hljs-comment">//打印本机 IP,省的去路由器管理界面看</span><br></code></pre></td></tr></table></figure>
+
+<p>更多函数见 <a href="https://blog.csdn.net/Naisu_kun/article/details/86165403#_130">WiFi.h STA 常用方法说明</a></p>
+<ul>
+<li><h2 id="WebServer-h"><a href="#WebServer-h" class="headerlink" title="WebServer.h"></a>WebServer.h</h2></li>
+</ul>
+<p><code>#include <WebServer.h></code></p>
+<p>创建一个简单的网站服务器。真的很简单。</p>
+<p>一个个函数讲有点难理解,我放在这节的例程里面说明。</p>
+<h2 id="写一个测试程序吧"><a href="#写一个测试程序吧" class="headerlink" title="写一个测试程序吧"></a>写一个测试程序吧</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><WiFi.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><WebServer.h></span></span><br><span class="hljs-comment">//引入所需要的两个库</span><br><br><span class="hljs-type">const</span> <span class="hljs-type">char</span> *SSID = <span class="hljs-string">"YOUR_SSID"</span>;<br><span class="hljs-type">const</span> <span class="hljs-type">char</span> *PASSWORD = <span class="hljs-string">"YOUR_PASSWORD"</span>;<br><br>WebServer <span class="hljs-title function_">server</span><span class="hljs-params">(<span class="hljs-number">80</span>)</span>; <span class="hljs-comment">//网站服务器将运行在 80 端口</span><br><br><span class="hljs-type">void</span> <span class="hljs-title function_">handleIndex</span><span class="hljs-params">()</span> <span class="hljs-comment">//收到 HTTP 请求的回调函数</span><br>{<br> server.send(<span class="hljs-number">200</span>, <span class="hljs-string">"text/plain"</span>, <span class="hljs-string">"Hello from ESP32!"</span>);<br> <span class="hljs-comment">//发送 HTTP 相应,内容分别为:状态码,Content-Type,响应体</span><br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">setup</span><span class="hljs-params">()</span><br>{<br> Serial.begin(<span class="hljs-number">9600</span>); <span class="hljs-comment">//开启串口,波特率设置为 9600</span><br> Serial.println();<br><br> WiFi.mode(WIFI_STA); <span class="hljs-comment">//设置工作在 STA 模式</span><br> WiFi.begin(SSID, PASSWORD); <span class="hljs-comment">//连接至指定 AP</span><br> <span class="hljs-keyword">while</span> (WiFi.status() != WL_CONNECTED) <span class="hljs-comment">//等待网络连接成功</span><br> {<br> delay(<span class="hljs-number">500</span>);<br> Serial.print(<span class="hljs-string">"."</span>); <span class="hljs-comment">//将连接信息输出至串口</span><br> }<br> Serial.println(<span class="hljs-string">"WiFi connected!"</span>);<br><br> Serial.println(<span class="hljs-string">"IP address: "</span>);<br> Serial.println(WiFi.localIP()); <span class="hljs-comment">//打印本机 IP</span><br><br> server.on(<span class="hljs-string">"/"</span>, handleIndex); <span class="hljs-comment">//注册链接(类似与注册一个路由),并选择回调函数</span><br> <span class="hljs-comment">//同样的,还可以注册别的链接,如</span><br> <span class="hljs-comment">//server.on("/test", handleIndexTest);</span><br> <br> server.begin(); <span class="hljs-comment">//开启 HTTP 服务器</span><br> Serial.println(<span class="hljs-string">"WebServer begin!"</span>);<br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">loop</span><span class="hljs-params">()</span><br>{<br> server.handleClient(); <span class="hljs-comment">//不断相应 HTTP 请求</span><br>}<br></code></pre></td></tr></table></figure>
+
+<p>访问串口返回的 IP,即可看到 <code>Hello from ESP32!</code> 这句话啦。</p>
+<h2 id="还有个-Web-Server-叫-ESPAsyncWebServer"><a href="#还有个-Web-Server-叫-ESPAsyncWebServer" class="headerlink" title="还有个 Web Server 叫 ESPAsyncWebServer"></a>还有个 Web Server 叫 ESPAsyncWebServer</h2><p>自带的 WebServer 是同步的,只支持处理一个连接。对于这种体量的机器其实足够了。</p>
+<p>顺便学习一下一个第三方库吧。</p>
+<h3 id="添加库"><a href="#添加库" class="headerlink" title="添加库"></a>添加库</h3><p>对于这款 ESP32,需要下载并导入两个库(源码 ZIP 即可):</p>
+<p><a href="https://github.com/me-no-dev/AsyncTCP">me-no-dev/AsyncTCP: Async TCP Library for ESP32</a></p>
+<p><a href="https://github.com/me-no-dev/ESPAsyncWebServer">me-no-dev/ESPAsyncWebServer: Async Web Server for ESP8266 and ESP32</a></p>
+<p>在 Arduino 的<code>项目 > 加载库 > 添加 .ZIP 库</code>中导入这两个库。</p>
+<h3 id="用-ESPAsyncWebServer-重写刚刚的例程吧"><a href="#用-ESPAsyncWebServer-重写刚刚的例程吧" class="headerlink" title="用 ESPAsyncWebServer 重写刚刚的例程吧"></a>用 ESPAsyncWebServer 重写刚刚的例程吧</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><WiFi.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><ESPAsyncWebServer.h></span></span><br><span class="hljs-comment">//注意替换为新的库</span><br><br><span class="hljs-type">const</span> <span class="hljs-type">char</span> *SSID = <span class="hljs-string">"YOUR_SSID"</span>;<br><span class="hljs-type">const</span> <span class="hljs-type">char</span> *PASSWORD = <span class="hljs-string">"YOUR_PASSWORD"</span>;<br><br>ESPAsyncWebServer <span class="hljs-title function_">server</span><span class="hljs-params">(<span class="hljs-number">80</span>)</span>; <span class="hljs-comment">//同样替换为新的对象</span><br><br><span class="hljs-type">void</span> <span class="hljs-title function_">handleIndex</span><span class="hljs-params">(AsyncWebServerRequest *request)</span> <span class="hljs-comment">//回调函数有更改</span><br>{<br> request->send(<span class="hljs-number">200</span>, <span class="hljs-string">"text/plain"</span>, <span class="hljs-string">"Hello, world!"</span>);<br> <span class="hljs-comment">//发送 HTTP 相应,内容相同</span><br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">setup</span><span class="hljs-params">()</span><br>{<br> Serial.begin(<span class="hljs-number">9600</span>); <span class="hljs-comment">//开启串口,波特率设置为 9600</span><br> Serial.println();<br><br> WiFi.mode(WIFI_STA); <span class="hljs-comment">//设置工作在 STA 模式</span><br> WiFi.begin(SSID, PASSWORD); <span class="hljs-comment">//连接至指定 AP</span><br> <span class="hljs-keyword">while</span> (WiFi.status() != WL_CONNECTED) <span class="hljs-comment">//等待网络连接成功</span><br> {<br> delay(<span class="hljs-number">500</span>);<br> Serial.print(<span class="hljs-string">"."</span>); <span class="hljs-comment">//将连接信息输出至串口</span><br> }<br> Serial.println(<span class="hljs-string">"WiFi connected!"</span>);<br><br> Serial.println(<span class="hljs-string">"IP address: "</span>);<br> Serial.println(WiFi.localIP()); <span class="hljs-comment">//打印本机 IP</span><br><br> server.on(<span class="hljs-string">"/"</span>, handleIndex); <span class="hljs-comment">//注册链接(类似与注册一个路由),并选择回调函数</span><br> <span class="hljs-comment">//同样的,还可以注册别的链接,如</span><br> <span class="hljs-comment">//server.on("/test", handleIndexTest);</span><br> <br> server.begin(); <span class="hljs-comment">//开启 HTTP 服务器</span><br> Serial.println(<span class="hljs-string">"WebServer begin!"</span>);<br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">loop</span><span class="hljs-params">()</span><br>{<br><span class="hljs-comment">//不用在这儿监听 HTTP 请求了</span><br>}<br></code></pre></td></tr></table></figure>
+
+<p>理论上来讲,上面的代码应该是正确的……</p>
+<p>但 Arduino 在编译的时候报错,内容是 ESPAsyncWebServer 库中的某些代码。</p>
+<p>有待我弄清楚出错的原因。</p>
+
+
+
+ 2022-03-26T16:15:00.000Z
+
+
+ https://blog.udon.eu.org/archives/7f7bd4a5.html
+ 合宙 EPS32-C3 把玩记录(一):环境搭建与第一个程序
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>为了贯彻本博客最重要的关键词:<strong>性价比</strong>,我看到性价如此高的开发板,想都没想就剁手了。</p>
+<p>嘛其实也没有这么冲动,我在购买 3D 打印机之后就一直在计划着做一些网上现成的电子项目,但碍于这段时间 MCU 和大尺寸屏幕价格的飙升,一直没能开始动手。</p>
+<p>正好最近我学习了 iPad 上的 3D 建模软件 Sharp3D,项目的外壳建模变得有可能;又遇到了这块便宜的板子,立即开工!</p>
+<p>因为1.8寸的 TFT 显示屏还没到货,3D 建模就先放一边,先来研究一下这块开发板。</p>
+<h3 id="事先声明"><a href="#事先声明" class="headerlink" title="事先声明"></a>事先声明</h3><p>本教程是我一边从零开始学习嵌入式开发一边作成的,有逻辑混乱、内容浅显和成吨的错误,还请已经熟悉嵌入式开发的大佬多多包涵与斧正)</p>
+<h2 id="问题:什么?开发环境不是按语言分的嘛?"><a href="#问题:什么?开发环境不是按语言分的嘛?" class="headerlink" title="问题:什么?开发环境不是按语言分的嘛?"></a>问题:什么?开发环境不是按语言分的嘛?</h2><p>在开始学习嵌入式开发之前,我简单地认为嵌入式开发因语言而已,分为用 C/Cpp 开发(Arduino)和用 Python 开发(MicroPython)。</p>
+<p>直到我遇见了 ESP-IDF 这个东西。</p>
+<p>啥啊,为啥这家伙用的也是 C,代码我还一点都看不懂。</p>
+<h3 id="解答"><a href="#解答" class="headerlink" title="解答"></a>解答</h3><p>嵌入式开发选用的语言和语法因选择的框架而异。</p>
+<p>ESP-EDF 更靠近底层,因而编写更复杂;Arduino 对底层进行封装,更靠上层且对用户更友好;MicroPython 则是在开发板上还原了一个 Python 的开发环境,继承了 Python 的诸多优点(简单的语法、无需编译就能执行新代码等)。</p>
+<p>此外,还能用 JS、Java、Lua 等等语言进行开发。</p>
+<h3 id="我的选择"><a href="#我的选择" class="headerlink" title="我的选择"></a>我的选择</h3><p>我手上有两块板子,一块被我刷成了 MicroPython,但目前不打算去用它。</p>
+<p>我打算用 Arduino + C 进行开发。</p>
+<h3 id="配置-VSCode-Arduino-开发环境"><a href="#配置-VSCode-Arduino-开发环境" class="headerlink" title="配置 VSCode + Arduino 开发环境"></a>配置 VSCode + Arduino 开发环境</h3><p>Arduino 没有代码补全,太难用。简述一下如何使用 VSCode 进行开发:</p>
+<ul>
+<li>VSC 安装 Arduino 插件;</li>
+<li>在 首选项-设置 中配置 Arduino 的路径 <code>Arduino.path</code></li>
+<li>打开项目后选择 MCU 类型和串口</li>
+</ul>
+<p>就能用啦。</p>
+<h2 id="第一个项目"><a href="#第一个项目" class="headerlink" title="第一个项目"></a>第一个项目</h2><p>第一个项目就不选输出 Hello World 了,一点硬件的感觉都没有。</p>
+<p>据<a href="https://wiki.luatos.com/chips/esp32c3/board.html"> 官方文档 </a>,主板板载的两个 LED 灯对应的 GPIO 为 <code>IO12 IO13</code>,高电平有效。</p>
+<p>就此编写一个<del>无稳态多协振荡电路</del>让 LED 灯交替闪烁的程序:</p>
+<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">setup</span><span class="hljs-params">()</span> <span class="hljs-comment">//初始化函数,只会在开发板上电或复位时被调用一次</span><br>{ <br> pinMode(<span class="hljs-number">12</span>, OUTPUT); <span class="hljs-comment">//初始化 IO12 为输出口</span><br> pinMode(<span class="hljs-number">13</span>, OUTPUT); <span class="hljs-comment">//初始化 IO13 为输出口</span><br> digitalWrite(<span class="hljs-number">12</span>, LOW); <span class="hljs-comment">//初始化设为低电平,则灯灭</span><br> digitalWrite(<span class="hljs-number">13</span>, LOW); <span class="hljs-comment">//初始化设为低电平,则灯灭</span><br>}<br><br><span class="hljs-type">void</span> <span class="hljs-title function_">loop</span><span class="hljs-params">()</span> <span class="hljs-comment">//上电之后一直循环执行的函数</span><br>{ digitalWrite(<span class="hljs-number">12</span>, HIGH); <span class="hljs-comment">//亮左灯</span><br> digitalWrite(<span class="hljs-number">13</span>, LOW); <span class="hljs-comment">//关右灯</span><br> delay(<span class="hljs-number">1000</span>); <span class="hljs-comment">//等待1秒</span><br> digitalWrite(<span class="hljs-number">12</span>, LOW); <span class="hljs-comment">//关左灯</span><br> digitalWrite(<span class="hljs-number">13</span>, HIGH); <span class="hljs-comment">//亮右灯</span><br> delay(<span class="hljs-number">1000</span>); <span class="hljs-comment">//等待1秒</span><br>}<br></code></pre></td></tr></table></figure>
+
+<p>编译+上传即可。</p>
+<p>结果就不展示了,两个灯在交替闪烁。</p>
+
+
+
+ 2022-03-26T09:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/d01399e6.html
+ Code-Server 的代理配置
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>一年前,我介绍了如何在群晖上使用 Docker 部署 Code-Server,在外也能轻松使用已经配置好的开发环境。<a href="https://blog.udon.eu.org/archives/375e7789.html">群晖搭建 VSCode 服务器与 Syncthing 服务</a></p>
+<p>最近我换了 iPad,琢磨如何发挥她的生产力。除了使用网页版的 IDE(Codepen、Gitpod等),就是自建网页版的 VSCode 了。下面简要介绍一下我是如何给 Code-Server Docker 配置代理,使其成为一个完备的开发平台。</p>
+<span id="more"></span>
+
+<p>将 Code-Server 部署在国内服务器(例如我家里的 NAS),可以获得稳定的连接,这对于开发平台是尤其重要的,VSCode 遇到连接不顺畅就会要求你刷新界面,很可能会丢失数据。</p>
+<p>但由于众所周知的原因,在国内的网络环境做开发可以说是寸步难行,我便采用 Clash Docker 来给 Code-Server 加上代理。</p>
+<h3 id="Clash-Docker-安装"><a href="#Clash-Docker-安装" class="headerlink" title="Clash Docker 安装"></a>Clash Docker 安装</h3><p>Clash Core 普通版 Image:<a href="https://hub.docker.com/r/dreamacro/clash">dreamacro/clash - Docker Image | Docker Hub</a></p>
+<p>Clash Core Premium Image:<a href="https://hub.docker.com/r/dreamacro/clash-premium">dreamacro/clash-premium - Docker Image | Docker Hub</a></p>
+<p>Clash Core Premium 二进制文件: <a href="https://github.com/Dreamacro/clash/releases/tag/premium">Premium release (github.com)</a></p>
+<p>Clash Core 有普通版和 Premium 版之分,目前我能体验到的二者的区别是普通版的 Clash Core 不支持 RULE-SET 功能。</p>
+<p>我常用的配置文件大量使用了 RULE-SET,所以我必须得用 Clash Core Premium。</p>
+<p>但 Pre Build 的 Image 似乎不支持 X86-64 v3 之下的 CPU(例如我的 J1900),所以我采取了部署普通版 Image,然后 attach 进 Docker 手动更换 Premium 内核的曲线救国方法。(更换 <code>/</code> 根目录下名为 <code>Clash</code>的二进制文件)</p>
+<p>部署 Docker 时注意一下几点:</p>
+<ul>
+<li>开放 7890(或你定义的代理端口)和 9090(Clash Core 管理面板)端口。</li>
+<li>将 <code>/root/.config/clash</code> 文件夹挂载到本地,存放 <code>config.yaml</code> 及其他配置文件。</li>
+</ul>
+<p>请勿将 Clash Core 的管理面板暴露到公网。我选择用 Tailscale 建立 VPN 访问家中的服务器进行配置。</p>
+<h3 id="Code-Server-的配置"><a href="#Code-Server-的配置" class="headerlink" title="Code-Server 的配置"></a>Code-Server 的配置</h3><p>需要在 Code-Server Docker 里添加两个环境变量,实现开机自动连接代理:</p>
+<ul>
+<li><code>http_proxy=http://clash_docker_ip:7890</code></li>
+<li><code>https_proxy=http://clash_docker_ip:7890</code></li>
+</ul>
+<p>可以使用同样的方法给其他 Docker 添加代理。</p>
+<hr>
+<p>在一顿折腾之后,Code-Server 终于可以顺畅访问 Github 等网站了。</p>
+<p>可喜可贺,可喜可贺。</p>
+
+
+ 2022-03-19T14:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/7263a385.html
+ Klipper 的外网访问
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>网络上有关 Klipper 的中文教程少之又少,固作此教程介绍一下如何在外网优雅地访问家中装有 Klipper 的 3D 打印机。</p>
+<h2 id="方法一:端口转发"><a href="#方法一:端口转发" class="headerlink" title="方法一:端口转发"></a>方法一:端口转发</h2><p><strong>此方法仅适用于拥有公网 IP 的用户</strong></p>
+<p>首先,使用 DDNS 将家庭宽带动态变化的 IP 绑定至域名,教程如下:</p>
+<ul>
+<li><a href="https://blog..eu.org/archives/87bacf3f.html">群晖-外网访问一站式教程2 DDNS</a></li>
+<li><a href="https://blog..eu.org/archives/27f2d840.html">iPv6下绝佳的DDNS方法-dynv6</a></li>
+</ul>
+<p>在配置端口映射之前,先介绍一下 Klipper 的网络结构:</p>
+<pre><code class=" mermaid">graph LR;
+ A("你的设备") <--80-->
+ B("前端网页(Fluidd/Mainsail/Octoprint)") <--7125-->
+ C("API 服务器 Moonraker") <-->
+ D("你的 3D 打印机");
+</code></pre>
+
+<p>线上的数字便是通讯的端口。</p>
+<p>由上图,我们不难看出,若需要在外网访问家中的 Klipper,就需要映射 <strong>80</strong> 和 <strong>7125</strong> 两个端口。</p>
+<p>于路由器的 <strong>端口转发/端口映射</strong> 界面配置 80 和 7125 的转发即可。家庭宽带的公网 IP 不会开放 80 端口,可将外网端口配置为 8080,对应的内网端口为 80 即可。</p>
+<p>接着,在 Moonraker 配置 <code>moonraker.conf</code> <code>[authorization]</code> 模块的 <code>cors_domains</code> 模块中添加你的域名,格式为 <code>*://你们域名</code></p>
+<p>也可以选择不使用自己搭建的前端网页,而使用 <a href="http://app.fluidd.xyz/">Fluidd</a> 或者 <a href="%5BMainsail%5D(http://my.mainsail.xyz/)">Mainsail</a> 作者搭建的前端网页。在 Moonraker 配置 <code>moonraker.conf</code> <code>[authorization]</code> 模块的 <code>cors_domains</code> 模块中添加 <code>*://my.mainsail.xyz 与 *://app.fluidd.xyz</code></p>
+<h2 id="方法二:内网穿透"><a href="#方法二:内网穿透" class="headerlink" title="方法二:内网穿透"></a>方法二:内网穿透</h2><p><strong>本人不推荐使用这个方法,固仅简述一下</strong></p>
+<p>可以使用花生壳等内网穿透服务,但给的带宽太小,只能使用控制界面,不能使用摄像头。</p>
+<p>也可以选择自建内网穿透,例如 Frp, Ngrok 等服务。但最近越来越多 Frp 服务器遭到攻击,固不建议自建。</p>
+<h2 id="方法三:使用-VPN"><a href="#方法三:使用-VPN" class="headerlink" title="方法三:使用 VPN"></a>方法三:使用 VPN</h2><p>这是本人推荐的方法。</p>
+<p>与 Octoprint + Marlin 仅需要映射 80 端口不同,Klipper 还需要映射 Moonraker 的 7125 端口,配置端口转发与实际使用都不如前者来的方便。</p>
+<p>我个人推荐用诸如 Zerotier, Tailscale 一类的 VPN 软件,搭建自己的小内网,通过内网 IP 直接访问 Klipper,既安全又方便。</p>
+<p>Zerotier 或者 Tailscale 的使用技巧网上一大片,我就不赘述了。</p>
+
+
+
+ 2022-02-12T06:50:00.000Z
+
+
+ https://blog.udon.eu.org/archives/375e7789.html
+ 群晖搭建 VSCode 服务器与 Syncthing 服务
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>这次我尝试在群晖上搭建 VSCode 服务器与 Syncthing 服务,实现电脑与 NAS 间的代码同步与网页中的 Coding。</p>
+<span id="more"></span>
+
+<hr>
+<h2 id="VSCode-网页版的实现"><a href="#VSCode-网页版的实现" class="headerlink" title="VSCode 网页版的实现"></a>VSCode 网页版的实现</h2><h3 id="偶遇服务器软件"><a href="#偶遇服务器软件" class="headerlink" title="偶遇服务器软件"></a>偶遇服务器软件</h3><p>刷 RSS 时我看到 V2EX 上一个帖子分享了一个实用工具:<a href="%22https://github.com/conwnet/github1s%22">github+1s</a></p>
+<p>这个项目可以实现在 <strong>网页版 VSCode</strong> 中打开 GitHhub 上的代码。</p>
+<p>这个项目使用的 <a href="%22https://github.com/cdr/code-server%22">code-server</a> 引起了我的兴趣。</p>
+<h3 id="code-server-的部署"><a href="#code-server-的部署" class="headerlink" title="code-server 的部署"></a>code-server 的部署</h3><p>群晖自带的 Docker 套件简化了部署的过程。</p>
+<p>在注册表中搜索 <a href="https://registry.hub.docker.com/r/codercom/code-server/">code-server</a> 下载 image;</p>
+<p>打开 image 进行配置:</p>
+<ul>
+<li>使用高权限执行容器</li>
+<li>在 <code>高级设置-环境</code> 页面中添加环境变量 <code>PASSWORD</code>,值设定为你的登陆密码(由于在 Docker 页面中以明文保存,请注意密码安全)。</li>
+</ul>
+<p>启动容器,并使用 Docker 内置的 <code>终端机</code> 打开一个新的 <code>bash</code>。换源、更新 apt 、安装常用软件我就不再赘述。</p>
+<h3 id="code-server-的外网访问"><a href="#code-server-的外网访问" class="headerlink" title="code-server 的外网访问"></a>code-server 的外网访问</h3><p>code-server 没有自带 HTTPS 相关的配置,需要使用网站服务器进行反向代理。</p>
+<p>目前比较流行的有 Caddy 和 NGINX 两款。</p>
+<p>鉴于 Caddy 配置简单且 HTTPS 优先,我这次使用 Caddy。</p>
+<p><a href="https://caddyserver.com/docs/install#debian-ubuntu-raspbian">Caddy 官方安装文档</a></p>
+<p>或使用一键安装脚本</p>
+<p><code>curl https://getcaddy.com | bash -s personal</code></p>
+<p>如果有开放的 443 端口,则可使用 Caddy 的自动 HTTPS 功能进行快速配置。</p>
+<p>若像我一样在家中的 NAS 上配置 code-server,则需要自己申请 tls 证书 (如 Let`s Encrypt),并按照 <a href="https://dengxiaolong.com/caddy/zh/tls.html">Caddy-tls</a> 配置。</p>
+<p>反向代理配置可参考 <a href="https://github.com/cdr/code-server/blob/main/docs/guide.md#lets-encrypt">code-server 官方的反代配置教程</a>。</p>
+<h3 id="一些疑难杂症"><a href="#一些疑难杂症" class="headerlink" title="一些疑难杂症"></a>一些疑难杂症</h3><h4 id="一些插件无法安装"><a href="#一些插件无法安装" class="headerlink" title="一些插件无法安装"></a>一些插件无法安装</h4><p>目前 code-server 的 VSCode 版本为 1.51.1, VSCode 官方则为 1.54.3 ,因此某些较新的插件可能无法使用。</p>
+<p>可以前往 <a href="https://marketplace.visualstudio.com/">VS插件市场</a> 下载旧版插件并手动安装。</p>
+<h4 id="Docker-内挂载的目录无写权限"><a href="#Docker-内挂载的目录无写权限" class="headerlink" title="Docker 内挂载的目录无写权限"></a>Docker 内挂载的目录无写权限</h4><p>使用 <code>sudo chmod 777 ./</code> 给 coder 用户赋予读写权力。</p>
+<h4 id="Docker-内-Caddy-无法自启"><a href="#Docker-内-Caddy-无法自启" class="headerlink" title="Docker 内 Caddy 无法自启"></a>Docker 内 Caddy 无法自启</h4><p>这个我也还没有解决。暂且手动启动。</p>
+<h4 id="code-server-的各种性能问题"><a href="#code-server-的各种性能问题" class="headerlink" title="code-server 的各种性能问题"></a>code-server 的各种性能问题</h4><p>等待更多的更新吧,我接下来会尝试在 Docker 里编译原版 VSCode 并开启 Web 模式,对比二者性能。</p>
+<h2 id="Syncthing-服务搭建"><a href="#Syncthing-服务搭建" class="headerlink" title="Syncthing 服务搭建"></a>Syncthing 服务搭建</h2><p><a href="https://syncthing.net/">Syncthing 官网</a> 已经给出了十分详尽的安装教程,也有群晖的安装包,我就不再赘述安装过程。</p>
+<p>Syncthing 的管理页面端口为 <code>8384</code>,若想在外网访问请使用 HTTPS。可以使用群晖内置的反向代理服务器进行反代。</p>
+<p>要注意把 <code>22000</code> 端口的 <code>TCP</code> 与 <code>UDP</code> 全部开放,才可在外网顺利与 NAS 同步。</p>
+
+
+ 2021-03-19T16:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/a455b52c.html
+ 逃离国产软件 - 虚拟机计划
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>使用 Hyper-V 运行 Windows LTSC 虚拟机,以隔离流氓的国产软件们。</p>
+<h3 id="为何大费周章?"><a href="#为何大费周章?" class="headerlink" title="为何大费周章?"></a>为何大费周章?</h3><p>我试过网络上的不少方法来隔离鹅厂的软件 —— 刚开源的 Sandboxie 也好,利用 Windows ACL 机制通过低权账户加以限制也好 —— 都因为权限问题失败了。最后决定用虚拟环境直接隔离这些软件。</p>
+<span id="more"></span>
+
+<h3 id="为什么是-Hyper-V-和-LTSC?"><a href="#为什么是-Hyper-V-和-LTSC?" class="headerlink" title="为什么是 Hyper-V 和 LTSC?"></a>为什么是 Hyper-V 和 LTSC?</h3><p>我用过 VMWare,觉得还是 Windows 原生的 Hyper-V 启动最快。还不用钱。</p>
+<p>Windows LTSC 是企业定制版,官方精简了系统,性能开销更少。</p>
+<h3 id="事前准备"><a href="#事前准备" class="headerlink" title="事前准备"></a>事前准备</h3><p>拥有 16G 以上内存及 NVME 高速硬盘的用户可以放心采用该方案,虚拟机运行时不会影响其他软件的流畅运行。</p>
+<h4 id="下载-MSDN-上的-Windows-LTSC"><a href="#下载-MSDN-上的-Windows-LTSC" class="headerlink" title="下载 MSDN 上的 Windows LTSC:"></a>下载 <a href="https://msdn.itellyou.cn/">MSDN</a> 上的 Windows LTSC:</h4><p>侧边栏选择 <strong>操作系统</strong> ;选择 <strong>Windows 10 Enterprise LTSC 2019</strong>。</p>
+<h4 id="安装-Hyper-V:"><a href="#安装-Hyper-V:" class="headerlink" title="安装 Hyper-V:"></a>安装 Hyper-V:</h4><p>对于 Windows 专业版 用户,在 控制面板 - 程序与功能 - 启动或关闭Windows功能 中找到 <strong>Hyper-V</strong> 并打开。</p>
+<p>对于 其他版本 Windows 的用户,则稍微有些麻烦:</p>
+<ol>
+<li>在记事本中输入如下代码</li>
+</ol>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">pushd</span> <span class="hljs-string">"%~dp0"</span><br><br><span class="hljs-built_in">dir</span> /b %SystemRoot%\servicing\Packages\*Hyper-V*.mum >hyper-v.txt<br><br><span class="hljs-keyword">for</span> /f %%i <span class="hljs-keyword">in</span> (<span class="hljs-string">'findstr /i . hyper-v.txt 2^>nul'</span>) <span class="hljs-keyword">do</span> dism /online /norestart /add-package:<span class="hljs-string">"%SystemRoot%\servicing\Packages\%%i"</span><br><br>del hyper-v.txt<br><br>Dism /online /enable-feature /featurename:Microsoft-Hyper-V-All /LimitAccess /ALL<br></code></pre></td></tr></table></figure>
+
+<ol start="2">
+<li>把文件保存为Hyper-V.cmd</li>
+<li>右键该文件,选择 <strong>以管理员身份运行</strong></li>
+</ol>
+<p>根据提示完成安装。</p>
+<blockquote>
+<p>摘自知乎 <a href="https://zhuanlan.zhihu.com/p/51939654">没人不认识我</a> 的回答</p>
+</blockquote>
+<h3 id="安装虚拟机"><a href="#安装虚拟机" class="headerlink" title="安装虚拟机"></a>安装虚拟机</h3><p>打开 Hyper-V ,选择 <strong>新建 - 虚拟机</strong> ;</p>
+<p>根据向导提示设置虚拟机,选择 <strong>第一代虚拟机</strong> ;</p>
+<p>内存分配我选了 2G (共16G);CPU 分配我选了 4线程 (共12线程)【需要完成配置后在 <strong>设置</strong> 中更改】;</p>
+<p>其余设置默认或自定;</p>
+<p>安装选项选择 <strong>从 CD/DVD-ROM 安装操作系统</strong> ,选择刚刚下载好的 Windows LTSC ISO镜像;</p>
+<p>完成配置后,启动虚拟机,安装 Windows LTSC 到硬盘。</p>
+<h3 id="配置环境"><a href="#配置环境" class="headerlink" title="配置环境"></a>配置环境</h3><p>装好系统后要干什么不用我说了吧。</p>
+<p>把垃圾们倒进去就好啦。</p>
+<p>实测空载消耗 CPU 算力在 0%-3% 浮动;内存占用 2.2G,实际使用 1.2G 。</p>
+
+
+
+ 2020-08-07T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/6b40e5ad.html
+ 提升音乐体验-本地音乐标签/歌词匹配与回放增益
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>推荐两款能让听歌体验变得更好的软件 —— Music Tag / Foobar2000 。</p>
+<p>附带使用教程。</p>
+<span id="more"></span>
+
+<h2 id="音乐标签-Music-Tag"><a href="#音乐标签-Music-Tag" class="headerlink" title="音乐标签 Music Tag"></a>音乐标签 Music Tag</h2><h3 id="官方网站-下载地址"><a href="#官方网站-下载地址" class="headerlink" title="官方网站/下载地址"></a>官方网站/下载地址</h3><p><a href="https://www.cnblogs.com/vinlxc/p/11347744.html">https://www.cnblogs.com/vinlxc/p/11347744.html</a></p>
+<h3 id="软件特点"><a href="#软件特点" class="headerlink" title="软件特点"></a>软件特点</h3><p>古人云,专辑封面是一首歌的灵魂。(我乱说的)</p>
+<p>Music Tag 是一款可以自动匹配本地音乐的标签与歌词的软件。</p>
+<p>一键从多家音乐网站拉取元数据/封面图/歌词,不能再爽了。</p>
+<h3 id="使用教程-建议"><a href="#使用教程-建议" class="headerlink" title="使用教程/建议"></a>使用教程/建议</h3><p>导入一批歌曲后,选择 自动匹配标签 :(如下图)</p>
+<p><img src="/images/2020-05-05/music-tag-1.png"></p>
+<p>然后按下图配置,在原有元数据下添加更多信息:</p>
+<p><img src="/images/2020-05-05/music-tag-2.png"></p>
+<p>在第一轮匹配后,建议再进行第二轮封面图片匹配,并覆盖原图片,配置如下图:</p>
+<p><img src="/images/2020-05-05/music-tag-3.png"></p>
+<p>接着,就需要你耐心地查看每首歌的元数据(善用方向键),检查是否有匹配错误的歌曲,并在 标签源-组合标签 处手动搜索,选择正确的元数据,如下图所示:</p>
+<p><img src="/images/2020-05-05/music-tag-4.png"></p>
+<p>建议检查一下歌曲的歌词,特别是较小众的歌曲。Music Tag 的歌词搜索错误率较高。</p>
+<p>如下图所示,选择并查看歌词,若有误可以手动搜索:</p>
+<p><img src="/images/2020-05-05/music-tag-5.png"></p>
+<p><img src="/images/2020-05-05/music-tag-6.png"></p>
+<p>最后选择导出 LRC 歌词:</p>
+<p><img src="/images/2020-05-05/music-tag-7.png"></p>
+<p>所有歌曲的 元数据-封面图-歌词 就此已匹配完毕。</p>
+<p>最终效果如下:</p>
+<p><img src="/images/2020-05-05/foobar2000-m.jpg"></p>
+<h2 id="Foobar2000"><a href="#Foobar2000" class="headerlink" title="Foobar2000"></a>Foobar2000</h2><h3 id="官方网站"><a href="#官方网站" class="headerlink" title="官方网站"></a>官方网站</h3><p><a href="http://www.foobar2000.org/">http://www.foobar2000.org/</a></p>
+<h3 id="回放增益介绍"><a href="#回放增益介绍" class="headerlink" title="回放增益介绍"></a>回放增益介绍</h3><p><a href="https://zh.wikipedia.org/wiki/%E5%9B%9E%E6%94%BE%E5%A2%9E%E7%9B%8A">维基百科-回放增益</a></p>
+<p>回放增益可以使音量大小各不相同的音乐向统一标准靠齐。</p>
+<p>将所有音乐扫描并打上回放增益 tag 后,再也不用担心下一首歌震破耳膜了。</p>
+<h3 id="使用教程"><a href="#使用教程" class="headerlink" title="使用教程"></a>使用教程</h3><p>导入并全选歌曲,右键,选择 ReplayGain :</p>
+<p><img src="/images/2020-05-05/foobar2000-1.png"></p>
+<p>下列三种扫描方式均可。个人喜欢将全部歌曲的音量统一,故选择第一种:</p>
+<p><img src="/images/2020-05-05/foobar2000-2.png"></p>
+<p>待扫描结束后,点击 Update File Tags ,将回放增益数据写入文件 Tag :</p>
+<p><img src="/images/2020-05-05/foobar2000-3.png"></p>
+<p>回放增益扫描完毕。</p>
+<h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>我是一周不往曲库里添加新曲就受不了的类型。</p>
+<p>这几天往曲库里添加曲子的时候查阅了这些能提升音乐体验的方法,希望能帮到你。</p>
+
+
+
+ 2020-05-05T05:40:06.000Z
+
+
+ https://blog.udon.eu.org/archives/e3c95af8.html
+ BGP初体验-Linux,Openwrt与Quagga
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>好久没写博客了!今天抽出点时间分享一下我的 BGP 初体验。</p>
+<p>这一切的一切都要从一个叫鹤伞 Ria 的 Vtuber 说起……</p>
+<span id="more"></span>
+
+<h2 id="环境配置"><a href="#环境配置" class="headerlink" title="环境配置"></a>环境配置</h2><h3 id="操作环境"><a href="#操作环境" class="headerlink" title="操作环境"></a>操作环境</h3><p>Debian 系 Linux(其实是 Kali):</p>
+<p><code>sudo apt update & sudo apt install quagga</code></p>
+<p>Openwrt:</p>
+<p><code>opkg update & opkg install quagga quagga-zebra quagga-bgpd quagga-vtysh</code></p>
+<h3 id="网络配置"><a href="#网络配置" class="headerlink" title="网络配置"></a>网络配置</h3><p>采用 Zerotier 建立内网环境模拟真实网络。</p>
+<p>Zerotier 的安装及配置不再赘述,官网有详尽教程。</p>
+<h2 id="网络环境"><a href="#网络环境" class="headerlink" title="网络环境"></a>网络环境</h2><p>本来想做三台设备两个 AS 间的通讯,有一台设备无法安装任何 BGP 软件也就作罢;也没有画拓扑图的必要了(悲)。</p>
+<p>AS114514 Debian 10.0.1.1,命名为 R1,享有 10.0.1.0/24 网段;</p>
+<p>AS1919810 Openwrt 10.0.2.1,命名为 R2,享有 10.0.2.0/24 网段。</p>
+<h2 id="Quagga配置"><a href="#Quagga配置" class="headerlink" title="Quagga配置"></a>Quagga配置</h2><p>下面才是重头戏。Quagga 的配置文件位于 <code>/etc/quagga/</code></p>
+<p>据测试,Openwrt 安装 quagga 后会带有初始配置,而 Debian 不带初始配置,可自行创建。</p>
+<p><code>/etc/quagga/zebra.conf</code> 配置(可不用修改)</p>
+<figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs pgsql">! 登陆密码<br><span class="hljs-keyword">password</span> zebra<br>!<br><span class="hljs-keyword">access</span>-list vty permit <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>/<span class="hljs-number">8</span><br><span class="hljs-keyword">access</span>-list vty deny <span class="hljs-keyword">any</span><br>!<br><span class="hljs-type">line</span> vty<br> <span class="hljs-keyword">access</span>-<span class="hljs-keyword">class</span> vty<br></code></pre></td></tr></table></figure>
+
+<p><code>/etc/quagga/bgpd.conf</code> 配置(需要根据情境修改)</p>
+<figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs diff"><span class="hljs-addition">! 密码</span><br>password zebra<br><span class="hljs-addition">! AS号</span><br>router bgp 114514<br><span class="hljs-addition">! 本机公网(VPN网络)ip</span><br> bgp router-id 10.0.1.1<br><span class="hljs-addition">! 本路由享有网段(需要交换的网段)</span><br> network 10.0.1.0/24<br><span class="hljs-addition">! peer信息(建立连接的机器的公网(VPN网络)ip,AS及称呼)</span><br> neighbor 10.0.2.1 remote-as 1919810<br> neighbor 10.0.2.1 description R2<br></code></pre></td></tr></table></figure>
+
+<p> 另一台机器的配置只需依葫芦画瓢,我就不再赘述。</p>
+<p>配置之后,运行</p>
+<p><code>/etc/init.d/zebra restart</code></p>
+<p><code>/etc/init.d/bgpd restart</code>(Debian)</p>
+<p>或者</p>
+<p><code>/etc/init.d/quagga restart</code>(Openwrt)</p>
+<p>重启 quagga 服务。</p>
+<h2 id="欣赏结果"><a href="#欣赏结果" class="headerlink" title="欣赏结果"></a>欣赏结果</h2><p>忙活了这么久,终于能看到结果了!</p>
+<p>运行</p>
+<p><code>vtysh</code></p>
+<p>进入 quagga 控制台(指令模拟 Cisco,这块不大了解)</p>
+<p>输入</p>
+<p><code>show ip bgp neighbor</code></p>
+<p>就会看到</p>
+<figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs pgsql">BGP neighbor <span class="hljs-keyword">is</span> <span class="hljs-number">10.0</span><span class="hljs-number">.2</span><span class="hljs-number">.1</span>, remote <span class="hljs-keyword">AS</span> <span class="hljs-number">1919810</span>, <span class="hljs-keyword">local</span> <span class="hljs-keyword">AS</span> <span class="hljs-number">114514</span>, <span class="hljs-keyword">external</span> link<br>Description: R2<br>BGP <span class="hljs-keyword">version</span> <span class="hljs-number">4</span>, remote router ID <span class="hljs-number">10.0</span><span class="hljs-number">.2</span><span class="hljs-number">.1</span><br></code></pre></td></tr></table></figure>
+
+<p>还有这么一张表</p>
+<pre><code class="hljs"> Sent Rcvd
+Opens: 2 0
+Notifications: 0 0
+Updates: 2 2
+Keepalives: 1050 1049
+Route Refresh: 0 0
+Capability: 0 0
+Total: 1054 1051
+</code></pre>
+<p>再看看路由器内的活动ipv4路由表</p>
+<table>
+<thead>
+<tr>
+<th align="center">网络</th>
+<th align="center">对象</th>
+<th align="center">IPv4 网关</th>
+<th align="center">跃点数</th>
+<th align="center">表</th>
+</tr>
+</thead>
+<tbody><tr>
+<td align="center">xxx</td>
+<td align="center">10.0.2.0/24</td>
+<td align="center">10.0.2.1</td>
+<td align="center">20</td>
+<td align="center">main</td>
+</tr>
+</tbody></table>
+<p>就算大功告成了!</p>
+<h2 id="有什么用处呢?"><a href="#有什么用处呢?" class="headerlink" title="有什么用处呢?"></a>有什么用处呢?</h2><p><strong>没有。</strong></p>
+<p>内网测试唯一能享受的就是看着这条无形的链接,想象自己也是网络工程师。</p>
+<p>但是还是感觉很爽!</p>
+<p>而且你已经学会(大概)了 BGP,获取全球路由表也能办到了!</p>
+<p>参考 Ria 的爸爸<del>(我的岳父)</del>的文章</p>
+<p><a href="https://blog.foxsar.black/?p=246">使用bird配置bgp网络互连</a></p>
+<p>至于这一切与 Ria 有啥关系?欢迎关注 Ria 了解详情(滑稽)</p>
+<p><a href="https://t.me/kanaria_group">Telegram群组</a> <a href="https://space.bilibili.com/2450927">bilibli</a> <a href="https://www.youtube.com/channel/UCC12ijOcPxnRSQPLvjWYXUg">Youtube</a></p>
+
+
+ 2020-02-10T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/9d1c6fa4.html
+ Joplin+Webdav同步问题的解决方案
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="问题内容"><a href="#问题内容" class="headerlink" title="问题内容"></a>问题内容</h2><p>在群晖上搭建了 Webdav 服务器,使用 Joplin 连接后无法同步笔记。</p>
+<span id="more"></span>
+
+<h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><h3 id="错误示范:"><a href="#错误示范:" class="headerlink" title="错误示范:"></a>错误示范:</h3><p>‘ <a href="https://your.domain.com:[your">https://your.domain.com:[your</a> port] ‘</p>
+<p>以此路径访问的是群晖文件系统的 <code>/ </code> 目录,由于没有权限读写,同步失败。</p>
+<h3 id="正确示范:"><a href="#正确示范:" class="headerlink" title="正确示范:"></a>正确示范:</h3><p>在群晖控制面板内新建一个名为 <code>Joplin</code> 的共享文件夹。以域名:</p>
+<p>‘ <a href="https://your.domain.com:[your">https://your.domain.com:[your</a> port] /Joplin’</p>
+<p>访问即可同步。</p>
+<h3 id="自定义配置:"><a href="#自定义配置:" class="headerlink" title="自定义配置:"></a>自定义配置:</h3><p>若想在已经存在的文件夹下同步 Joplin 笔记,按照正确示范所书写的 URL 书写路径即可在想要的地方同步。</p>
+<h2 id="解决方案的探索"><a href="#解决方案的探索" class="headerlink" title="解决方案的探索"></a>解决方案的探索</h2><p>(我并未了解过 Webdav 的原理)<br>遇到此问题时,我在上 Google 查找解决方法前试着自行分析。思考原因后我选择了抓包分析。</p>
+<p>结合抓包结果和 Joplin 同步日志可以看到 Joplin 在 <code>/ </code> 目录下查找了 <code>.lock</code> 等文件。结合其他 Webdav 软件可以看到访问的 URL 指向的是目标文件夹,即“域名:端口/目标文件夹”,由此推测需要在配置内为 Joplin 指明同步目录,否则将在没有权限的根目录下同步,导致失败。</p>
+<p>这是一次没有什么技术含量但能启发我的尝试。若你也遇到了类似的问题,希望也能启发到你。</p>
+
+
+ 2020-01-06T10:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/27f2d840.html
+ iPv6下绝佳的DDNS方法-dynv6
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>下面我将介绍一种适用于 IPv6-DDNS 的绝佳方法!</p>
+<span id="more"></span>
+
+<h3 id="通过-IPv6-访问的优点"><a href="#通过-IPv6-访问的优点" class="headerlink" title="通过 IPv6 访问的优点"></a>通过 IPv6 访问的优点</h3><p>没有端口限制!!!可以通过 80/443 访问 web 服务器了!不用带着烦人的端口号!</p>
+<p>每台设备有独立的 IPv6,配置更加方便,无需路由器充当网关进行端口转发!</p>
+<h3 id="IPv6-也有不足之处"><a href="#IPv6-也有不足之处" class="headerlink" title="IPv6 也有不足之处"></a>IPv6 也有不足之处</h3><p>最严重的问题:很多家宽并没有开启 IPv6 的获取。但访问 IPv6 的设备必须要拥有 IPv6 地址!</p>
+<p>技术问题,难以解决。一般来说手机的移动网络都已分发 IPv6 地址。</p>
+<p>想要家宽拥有 IPv6?或许你需要修改光猫设置(超级管理),亦或是将光猫改为桥接模式,用路由器拨号从而获取 IPv6 地址。</p>
+<h3 id="如何实现iPv6-DDNS"><a href="#如何实现iPv6-DDNS" class="headerlink" title="如何实现iPv6-DDNS"></a>如何实现iPv6-DDNS</h3><ul>
+<li>在 <a href="https://dynv6.com/">dynv6</a>注册一个账号</li>
+<li>在 <strong>Instructions</strong> 界面查看API与你的DDNS域名</li>
+<li>下载官方提供的 <a href="https://gist.github.com/corny/7a07f5ac901844bd20c9">a nice script</a></li>
+</ul>
+<blockquote>
+<p>这点尤其重要!我在网络上寻寻觅觅无数脚本,总是失败。最后才发现官方有提供脚本也!一试马上就成功了。</p>
+</blockquote>
+<ul>
+<li>按照页面内提供的代码执行脚本</li>
+</ul>
+<figure class="highlight stata"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs stata"><span class="hljs-keyword">token</span>= *your <span class="hljs-keyword">token</span>* ./dynv6.<span class="hljs-keyword">sh</span> *your DDNS domain*<br></code></pre></td></tr></table></figure>
+
+<ul>
+<li>可以将其添加到 crontab 一类的软件内,规定时间自动执行脚本(每 10分钟一次为宜)</li>
+</ul>
+<p><strong>大功告成!</strong></p>
+<h3 id="事后"><a href="#事后" class="headerlink" title="事后"></a>事后</h3><ul>
+<li>你可以把自己的域名 CNAME 过去</li>
+<li>也可以用 dynv6 提供的域名</li>
+</ul>
+<p>随心所欲!</p>
+<p>Over.</p>
+
+
+ 2019-10-03T15:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/a4c81e8f.html
+ 搭建Calibre-Web电子书网页端管理服务
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h4 id="说在前面"><a href="#说在前面" class="headerlink" title="说在前面"></a>说在前面</h4><p>Kindle 是一样阅读利器,但若是没有一个强大的书库,它也只能用来压泡面(笑)</p>
+<p>今天我们利用 Docker 在群晖(任意系统)上搭建 Calibre-web 服务</p>
+<span id="more"></span>
+
+<h4 id="docker选择"><a href="#docker选择" class="headerlink" title="docker选择"></a>docker选择</h4><blockquote>
+<p><a href="https://hub.docker.com/r/technosoft2000/calibre-web/">technosoft2000-calibre-web</a></p>
+</blockquote>
+<p>我在尝试了 3 款 Docker Image 后,决定使用这款。</p>
+<h4 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h4><h5 id="Calibre-web"><a href="#Calibre-web" class="headerlink" title="Calibre-web"></a>Calibre-web</h5><ul>
+<li><p>基于 Python,性能高(低)</p>
+</li>
+<li><p>与 Calibre 软件的数据库等文件完全互通</p>
+</li>
+<li><p>支持推送至 Kindle</p>
+</li>
+<li><p>支持在线转码书籍(虽然问题较多)</p>
+</li>
+</ul>
+<h5 id="technosoft2000-calibre-web"><a href="#technosoft2000-calibre-web" class="headerlink" title="technosoft2000-calibre-web"></a>technosoft2000-calibre-web</h5><ul>
+<li><p>比起其他 Image 版本更新更加稳定(稳定很多)</p>
+</li>
+<li><p>支持在线转码(其他 Image 不行)</p>
+</li>
+<li><p>版本较新</p>
+</li>
+</ul>
+<h4 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h4><p>首先,将你的 Calibre 数据库的位置挂载到 Docker 内的 <strong>/books</strong>。</p>
+<p>启动 Docker 后不要着急,等待 Docker 内软件安装完毕后,访问您设置的端口,访问 Calibre-Web。</p>
+<p>在 <strong>Calibre 数据库位置</strong> 一栏填写</p>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">/books<br></code></pre></td></tr></table></figure>
+
+<p><strong>特性配置</strong> 可以依情况而变,不一定要按我的配置。</p>
+<p>若想要使用在线转码功能,<strong>外部二进制</strong> 一栏中,选择 <strong>使用 calibre 的电子书转换器</strong> 。 <strong>转换工具路径</strong> 按图片中填写。</p>
+<figure class="highlight armasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs armasm">/<span class="hljs-meta">opt</span>/calibre/ebook-convert<br></code></pre></td></tr></table></figure>
+
+<p>点击 <strong>提交</strong> 即可完成安装。</p>
+<p><strong>注意</strong></p>
+<p>若提示无法读取数据库,尝试将数据库所处的文件夹的权限改为<strong>755</strong> 。</p>
+<p>如何操作?请打开 SSH,在 Terminal 内操作。</p>
+<p>若还是失败,尝试在本地 Calibre 软件新建一个书库,将<strong>空的</strong>数据库文件移动到你挂载的目录下。</p>
+<p><strong>安装界面示意图</strong></p>
+<p><img src="/images/2019-10-02/1.jpg"></p>
+<h4 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h4><p>安装后的操作我就不多提了。该上传书籍的上传,该push的push。</p>
+<p>我的几个建议:</p>
+<ul>
+<li>推送邮箱推荐使用 Outlook,限制少。</li>
+<li>可以用 Calibre 软件管理数据库。不知道为什么,在本地做好更改后,网页版并没有任何变化。我试着备份又还原了数据库后,网页里再重新加载数据库才成功了。</li>
+<li>网页转码会遇到种种问题,例如电子书有加密。转码失败实属正常。</li>
+</ul>
+
+
+ 2019-10-02T10:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/87bacf3f.html
+ 群晖-外网访问一站式教程2 DDNS
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>简述配置 DDNS 的方法。</p>
+<h4 id="什么是DDNS"><a href="#什么是DDNS" class="headerlink" title="什么是DDNS"></a>什么是DDNS</h4><p><a href="https://zh.m.wikipedia.org/zh/%E5%8B%95%E6%85%8BDNS">维基百科-动态DNS</a><br>鉴于 IPv4 地址的枯竭,运营商开始给家用宽带分配动态 IP 地址,即 IP 会随时间或重新拨号而改变。DDNS 可以允许用户通过API动态地将变化的 IP 地址传送给域名解析商,达到域名外网访问的效果。<br>由于带宽分配原因,家用宽带的上传带宽一般在 20-30Mbps 间,故外网访问速度并没有达到如签约的 100Mbps 属正常。</p>
+<h4 id="事先准备"><a href="#事先准备" class="headerlink" title="事先准备"></a>事先准备</h4><p>打电话给 ISP(运营商)的小姐姐,让她给你公网 IP,如果问起原因可以回答家里装监控。没有开启公网 IP 将无法从外网访问家庭的内部网络。<br>由于运营商(指大部分,如电信)封锁了 80(HTTP)和 443(HTTPS)端口,我们将使用其他的端口进行访问。挑选一些你喜欢的端口,预备使用(如 8080-8090,4431-4439等)。</p>
+<h4 id="选择支持DDNS的域名解析服务商"><a href="#选择支持DDNS的域名解析服务商" class="headerlink" title="选择支持DDNS的域名解析服务商"></a>选择支持DDNS的域名解析服务商</h4><h5 id="CloudFlare"><a href="#CloudFlare" class="headerlink" title="CloudFlare"></a>CloudFlare</h5><p>老牌的域名解析商,也是少有的免费提供 CDN 的服务商。<br>我推荐 CloudFlare 的原因有三点</p>
+<ol>
+<li>可以使用 CDN,保证网络质量始终处于较好状态。例如,我的 Blog 搭建在 Github 上,若有时因网络抽风无法访问 Github,CDN 能助你一臂之力。</li>
+<li>可以查看连接数,数据量,访客量等详细数据。</li>
+<li>API 获取方便。(2022 Update: CloudFlare 的 API 服务器接近半墙,国内很难再访问了,不推荐使用)</li>
+</ol>
+<h5 id="Dynv6"><a href="#Dynv6" class="headerlink" title="Dynv6"></a>Dynv6</h5><p>提供 IPv4 与 IPv6 DDNS 的服务商,在 21 年有一次较长时间的故障,平常都非常稳定。</p>
+<h5 id="DNSPod"><a href="#DNSPod" class="headerlink" title="DNSPod"></a>DNSPod</h5><p>被腾讯收购的 DNS 服务商,使用需实名。</p>
+<h4 id="配置-DDNS-服务"><a href="#配置-DDNS-服务" class="headerlink" title="配置 DDNS 服务"></a>配置 DDNS 服务</h4><p>家用路由器的 DDNS 功能一般仅支持国内大型服务商,例如花生壳。</p>
+<p>有两种方法可以配置自己的 DDNS 服务:</p>
+<ol>
+<li>将负责拨号的路由器刷成 Openwrt 系统,安装 DDNS 插件以配置自定义脚本的 DDNS 服务;</li>
+<li>在一台 24x7 运行的设备上,通过 API 获取 IP 地址,并定时执行脚本更新 IP 地址;</li>
+</ol>
+<p>前者虽然更加麻烦,但可以实现仅在 IP 更换时发起更新解析的请求,而不需要定期(如每十分钟)请求一次 API,减小账户被封的风险,并尽可能地缩短从 IP 更换到新的解析生效的时间。</p>
+<p>不管是路由器也好,Linux 上的脚本也好,可以在 GitHub 上寻找对应 DDNS 服务商的更新脚本,填上配置就能使用啦~</p>
+
+
+ 2019-08-31T10:30:00.000Z
+
+
diff --git "a/tag/\346\225\231\347\250\213/feed.json" "b/tag/\346\225\231\347\250\213/feed.json"
new file mode 100644
index 00000000..286a2b38
--- /dev/null
+++ "b/tag/\346\225\231\347\250\213/feed.json"
@@ -0,0 +1,217 @@
+{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "カレーうどん屋 • All posts by \"教程\" tag",
+ "description": "カレーうどん屋.",
+ "home_page_url": "https://blog.udon.eu.org",
+ "items": [
+ {
+ "id": "https://blog.udon.eu.org/archives/8b68ddd6.html",
+ "url": "https://blog.udon.eu.org/archives/8b68ddd6.html",
+ "title": "修复 UEFI 引导的 GRUB",
+ "date_published": "2023-04-15T16:00:00.000Z",
+ "content_html": "问题与解决方法 环境 Manjaro Linux x86_64
\nKernel: 6.2.10-1-MANJARO
\n使用 UEFI 引导
\n问题 在 GRUB 尝试引导 Linux 内核时,出现如下错误:
\n1 2 3 4 5 error: sparse file not allowed. 452: out of range pointer: xxxxxxxxxx Aborted. Press any key to exit.
\n\n用户将无法进入系统。
\n解决方法 进入恢复系统 插入 Manjaro LiveCD, 启动 Live 系统。
\n确定磁盘分区 在 Live 系统中,使用 fdisk -l
查看磁盘分区情况,找到安装 Manjaro 的磁盘,假设为 /dev/sda
。
\n我的磁盘分区如下:
\n1 2 3 4 5 设备 起点 末尾 扇区 大小 类型 /dev/sda1 2048 821247 819200 400M EFI 系统 /dev/sda2 821248 723390463 722569216 344.5G Linux 文件系统 /dev/sda3 723390464 983437311 260046848 124G Linux 文件系统 /dev/sda4 983437312 1000214527 16777216 8G Linux 文件系统
\n\n可以确定,/dev/sda1
是 EFI 系统分区,/dev/sda2
是系统所在分区。
\n挂载分区 挂载系统分区:
\n \n\n将当前系统的工具分区挂载到 /mnt
下:
\n1 2 3 mount --bind /dev /mnt/dev mount --bind /proc /mnt/proc mount --bind /sys /mnt/sys
\n\n将 EFI 分区挂载到 /mnt/boot/efi
下:
\n1 mount /dev/sda1 /mnt/boot/efi
\n\n进入系统 \n\n重新安装 GRUB 1 grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable
\n\n具体参数需要根据实际情况进行修改。
\n在这之后 重启,进入通过 GRUB 引导系统。
\n在系统中使用 sudo grub-install --recheck /dev/sda
命令再次安装 GRUB,确保系统能够正常启动。
\n一些思考 接下来的内容是我的整个修复流程,包含了如何在搜索引擎查找问题、根据文章内容调整目标等杂碎的内容,和我在修复过程中的一些感想。
\n为什么会出现这个问题 不是很清楚。
\n在启动 Manjaro 前我不小心打开了电脑里的 Windows 系统,但没有连接移动硬盘。
\n按照以往的经验,这最多只会导致找不到 GRUB 的位置,手动指定 GRUB 所在分区就可以启动系统。
\n但这次不大一样。
\n在打开 GRUB 之后,尝试引导内核,就发现了这个问题。
\n初步解决思路 立刻格式化磁盘,重新安装 Manjaro。
\n我已经不是曾经那个只会重装的我了,这次我希望可以解决问题,而不是简单地重装。
\n首先,我 Google 了这个错误,发现了几篇内容相关的文章。
\n报错与我一致的文章 ,但没有给出解决方案。
\n要我删除 GRUB 和 UEFI 所在分区所有内容的文章 ,有点可怕,不敢这么干。
\n提到应该重新安装 GRUB 的文章 ,这还有点道理。
\n于是,我的目标转变为重新安装 GRUB。
\n重新安装 GRUB 在之前遇到找不到 GRUB 分区的问题时,在手动引导然后进入系统后,我会执行 sudo grub-install --recheck /dev/sda
重新安装 GRUB,解决这个问题。
\n那这次的觉得方案应该是差不多的……吧?
\n不对啊,这次是在 LiveCD 的系统里操作,怎么能直接安装 GRUB 呢?
\n这个问题比较难描述。
\n我先是 Google grub-install 修复 GRUB
,中文网站的内容都是关于在可以启动的系统下修复 GRUB 的,没有我需要的内容。
\n然后我开始求助于 ChatGPT,输入的 Prompts 是:
\n1 2 I am using Manjaro with GRUB. When I booted into the system , it says "sparse file not allowed 452 out of range pointer" . How to fix it ?
\n\n不难发现,我并没有说明我使用的是 UEFI 引导,这直接影响到了 ChatGPT 回复的准确性。
\nChatGPT 给出的修复步骤与上述的相差不大,只是在挂载系统分区和工具分区后,直接尝试执行 grub-install
命令,尝试修复。
\ngrub-install
返回错误 this gpt partition label contains no bios boot partition
把我弄得更懵了。
\n再次 Google 这个问题,发现了 这篇在长篇大论讲 GRUB 的文章 ,虽然没有给出解决方案,但它让我意识到 UEFI 和 Legacy BIOS 两种启动方式的区别。
\nUEFI 和 Legacy BIOS UEFI 和 Legacy BIOS 是两种启动方式,它们的区别在于,Legacy BIOS 是在 BIOS 中直接加载内核,而 UEFI 是在 BIOS 中加载 EFI 系统,然后由 EFI 系统加载内核。
\n使用 UEFI 引导的系统一般都有一个 200MB 到 400MB 的 EFI 系统分区,用于存放 EFI 系统。在上述的,我的硬盘分区中可以看到。
\n使用 findmnt
命令可以查看当前系统的挂载情况,其中 TARGET
列就是挂载点,SOURCE
列就是挂载的分区。
\nEFI 分区的挂载情况为:
\n1 2 TARGET SOURCE FSTYPE OPTIONS /boot/ efi /dev/ sda1 vfat rw,relatime,fm
\n\n可以看到,/boot/efi
里的内容正是 EFI 系统分区的内容。(我也是刚学到这个知识的)
\n解决 UEFI 相关问题 在修复过程中,我是通过 Google 发现上述的问题。
\n这篇文章 给了我莫大的帮助。
\n其中提到了 EFI 分区,也提到了如何正确安装 UEFI 引导的 GRUB:
\n1 2 sudo grub-install --target=x86_64-efi --efi-directory=/boot/efisudo grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable
\n\n在补充挂载 EFI 分区、切换 Root 目录后,执行第一条命令,发现有错误。尝试执行第二条命令,发现没有错误,代表 GRUB 已经重新安装成功。
\n此时我想起来,在之前安装 GRUB 时,会提示 正在为 x86_64-efi 平台进行安装
,我才意识到前面的修复过程并没有去指定平台。
\n总结一下 总之,这就是我此次修复的心路历程。我没有研究过 UEFI 和 Legacy BIOS 的区别,也没有研究过 GRUB 的安装过程,所以在修复过程中,我是通过 Google 和 ChatGPT 的帮助才解决了这个问题。
\n希望这个探索过程能给你一些启发吧。
\n \n此文章以 我无所谓 By 不 By 什么 AI,对我有帮助的文章就是好文章 标识发布。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/8b115688.html",
+ "url": "https://blog.udon.eu.org/archives/8b115688.html",
+ "title": "使用 Docker Compose 部署音乐服务器 Navidrome",
+ "date_published": "2023-01-31T04:00:00.000Z",
+ "content_html": "服务介绍 Navidrome 是一款兼容 Subsonic API 的开源音乐服务器软件,它提供了一个不错的 WebUI,也可以将支持 Subsonic API 的客户端接入。
\n目前项目正处在活跃开发中,各种各样的新功能正在陆续加入。
\n我的客户端选择 电脑端 自带 WebUI, Sonixd 【跨平台】
\niOS play:sub 【付费软件 4.99$】
\n部署方式 采用 Docker Compose 部署 Navidrome,使用 Nginx 作为反向代理。
\nDocker Compose 配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 version: "3" services: navidrome: image: deluan/navidrome:latest container_name: navidrome user: 1000 :1000 ports: - "127.0.0.1:4533:4533" restart: unless-stopped environment: ND_SCANSCHEDULE: 1h ND_LOGLEVEL: info ND_SESSIONTIMEOUT: 24h ND_BASEURL: "" ND_SEARCHFULLSTRING: true ND_SPOTIFY_ID: ND_SPOTIFY_SECRET: ND_LASTFM_APIKEY: ND_LASTFM_SECRET: ND_LASTFM_LANGUAGE: en volumes: - "./data:/data" - "/APTH-TO/navidrome-music:/music:ro"
\n使用命令 docker compose up -d
启动服务。
\nNginx 配置文件 我建议使用 DigitalOcean 的 Nginx 配置生产工具,示例配置如下:
\n示例配置
\n也可参考下述配置,此为 DigitalOcean 生成配置的简化版:
\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name music.example.com; ssl_certificate /etc/letsencrypt/live/music.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/music.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/music.example.com/chain.pem; access_log /var/log/nginx/access.log combined buffer=512k flush=1m ; error_log /var/log/nginx/error .log warn ; location / { proxy_set_header Host $host ; proxy_pass http://127.0.0.1:4533; } }server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name *.music.example.com ; ssl_certificate /etc/letsencrypt/live/music.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/music.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/music.example.com/chain.pem; return 301 https://music.example.com$request_uri ; }
\n\n音乐管理 我一直以文件夹分类的方式管理音乐,但 Navidrome 并不支持文件夹分类。它是根据音乐标签来分类的,例如按照歌手、专辑等依据分类歌曲。
\n因此,若想使用 Navidrome,需要对音乐进行标签管理。
\n大约两年前,我写了 一篇文章 介绍使用 Music Tag 和 Foobar2000 两款软件来管理音乐。
\nMusic Tag 的标签源是网易云音乐、豆瓣音乐、QQ 音乐等国内音乐平台,说实话,这些平台的音乐标签质量都不是很好,所以我一直在寻找更好的音乐标签源。
\n直到我发现了 MusicBrainz ,这是一个开源的音乐标签数据库,任何人都可以为它贡献标签。在体验之后,我发现 MusicBrainz 的音乐标签质量要比国内音乐平台的标签质量好很多,所以我决定将 MusicBrainz 作为我的音乐标签源。
\n我使用 Picard 这款软件来从 MusicBrainz 获取音乐标签。
\n将音乐导入 Picard 后,它会自动从 MusicBrainz 获取音乐标签,然后将标签写入音乐文件,十分方便。
\n开始使用 不论是使用 Navidrome 自带的 Web 界面,还是使用兼容 Subsonic API 的客户端,只要连接到 Navidrome,便可开始享受你的私人音乐库。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/f9bfe16a.html",
+ "url": "https://blog.udon.eu.org/archives/f9bfe16a.html",
+ "title": "使用 Docker Compose 部署 Keycloak 20",
+ "date_published": "2023-01-22T12:00:00.000Z",
+ "content_html": "部署方式 采用 Docker Compose 部署,使用 Postgres 作为数据库,使用 Nginx 作为反向代理。
\nDocker Compose 配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 version: '3' services: keycloak: image: quay.io/keycloak/keycloak:latest environment: KC_DB: postgres KC_DB_URL: jdbc:postgresql://db:5432/keycloak KC_DB_USERNAME: keycloak KC_DB_PASSWORD: keycloak KC_HTTP_ENABLED: true KC_HOSTNAME_STRICT: false KC_HOSTNAME_STRICT_HTTPS: false KC_HTTP_RELATIVE_PATH: '/' KC_HTTP_PORT: 8080 KEYCLOAK_ADMIN: MY_USERNAME KEYCLOAK_ADMIN_PASSWORD: MY_PASSWORD PROXY_ADDRESS_FORWARDING: true KC_PROXY: edge entrypoint: /opt/keycloak/bin/kc.sh start ports: - 127.0 .0 .1 :18080:8080 restart: unless-stopped db: image: postgres:14 restart: unless-stopped environment: - POSTGRES_USER=keycloak - POSTGRES_PASSWORD=keycloak - POSTGRES_DB=keycloak volumes: - ./postgres-data:/var/lib/postgresql/data
\n\n使用命令 docker compose up -d
启动服务。
\nNginx 配置 我建议使用 DigitalOcean 的 Nginx 配置生产工具,示例配置如下:
\n示例配置
\n也可参考下述配置,此为 DigitalOcean 生成配置的简化版:
\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name auth.example.com; ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/auth.example.com/chain.pem; access_log /var/log/nginx/access.log combined buffer=512k flush=1m ; error_log /var/log/nginx/error .log warn ; location / { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_buffer_size 128k ; proxy_buffers 4 256k ; proxy_busy_buffers_size 256k ; proxy_pass http://127.0.0.1:18080; } location /auth/realms { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_pass http://127.0.0.1:18080; } location /auth/resources { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_pass http://127.0.0.1:18080; } location /auth/js { proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_set_header X-Auth-Request-Redirect $request_uri ; proxy_pass http://127.0.0.1:18080; } }server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name *.auth.example.com ; ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/auth.example.com/chain.pem; return 301 https://auth.example.com$request_uri ; }
\n\n配置 Keycloak 创建 Realm 打开 Keycloak 地址 ,界面如下。
\n
\n选择 Administration Console
,进入管理界面。
\n
\n选择箭头指向的下拉菜单,选择 Add realm
,创建一个新的 Realm。
\n
\n填写 Realm 名称,点击 Create
。
\n创建 Client
\n选择 Clients
。
\n
\n点击 Create client
。
\n
\n填写 Client 相关信息,点击 Next
。
\n
\n按需求选择 Client 的配置,点击 Save
。
\n
\n至此,Keycloak 配置完成,且创建了第一个测试用 Client。
\n测试 Client 可根据 官方教程 测试该 Client。
\n尾声 上述便是使用 Docker Compose 部署 Keycloak 20 的方法,我们顺利创建了第一个测试用 Client,接下来可以根据自己的需求进行配置。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/e74a90f2.html",
+ "url": "https://blog.udon.eu.org/archives/e74a90f2.html",
+ "title": "使用再生龙 Clonezilla 备份操作系统",
+ "date_published": "2022-08-12T04:30:00.000Z",
+ "content_html": "近日购入了一只闪迪的 CZ880 Extreme PRO 固态U盘来装 Manjaro。
\n虽然U盘本体是终身质保的,但数据无价,配置一遍系统就要花大把的时间。遂有了备份U盘内整个系统的念头。
\n下面跟着我使用再生龙 Clonezilla 把U盘里的系统备份一下吧!
\n事先准备 再生龙是运行在独立操作系统 (Debian/Ubuntu) 上的一套软件,因此需要制作启动盘,或使用 Ventoy 等多系统启动方案。(实测 YUMI 无法启动再生龙,故建议使用 Ventoy)
\n制作启动盘 前往 Rufus 官网 下载 Rufus 启动盘制作工具。
\n前往 Clonezilla 官网 下载再生龙映像。
\n使用 Rufus 将再生龙映像烧写进另一个U盘即可。
\nU盘内数据将丢失,请做好备份!
\n使用多系统启动 前往 Ventoy 官网 下载安装包(兼容 Windows 与 Linux),将你的另一个U盘制作为 Ventoy 多启动盘。
\n前往 Clonezilla 官网 下载再生龙映像。
\n将再生龙映像拷贝至 Ventoy 多启动盘中。
\n选择你想存放备份数据的目的地,创建一个存放备份映像的文件夹(注意目录名称中不能带有空格)。
\n剧透一下,40G 的系统盘备份之后大约占了 16-17G. 请留出足够的空间(建议和待备份的数据等大小)。
\n开始备份 瞎眼警告:由于没有合适的截屏方式,我很不愿意地采取了 拍 屏 的方式,敬请谅解。
\n启动再生龙系统 确保你的电脑关闭了安全启动,若还打开着,需要在 BIOS 中将其关闭。
\n插入刚刚制作好的启动盘/Ventoy 多启动盘,在电脑启动时猛敲键盘的…某个键,这因电脑型号而异,打开启动菜单。
\n选择插入的启动盘/多启动盘。
\n启动盘用户若没有太大的兼容性问题,就能看到再生龙的启动菜单。
\n多启动盘用户还要再多一步,在 Ventoy 菜单内选中再生龙的映像,如下图所示,即可打开再生龙的启动菜单。
\n
\nP.S. 我的笔记本兴许和 Ventoy 的 UEFI 模式相性不大好,在 BIOS 中开启了 Lagacy 兼容模式后,使用 Legacy 模式才能开启 Ventoy。
\n选择再生龙启动方式
\n经典的 GRUB 启动菜单,一般来说选择默认的第一项启动方式即可。
\nVGA 启动花屏 我的电脑遇到了在 VGA 800x600 模式下花屏的问题。
\n最终进入 Other mods of Clonezilla live
菜单,
\n
\n选择了上图中的 KVM & To RAM 模式,可以正常启动了。
\nUSB 口不够用的用户 我这台笔记本只有两个 USB 口,其中一个要给备份源头 CZ880,另一个则要给移动硬盘,故选择了 To RAM
模式,将再生龙载入内存,就可以拔掉多启动U盘,空出 USB 口给移动硬盘了。
\n语言配置
\n选择自己想用的语言即可。
\n
\n保持默认配置即可。
\n备份配置
\n我们选 Start Clonezilla 使用再生龙
。
\n命令行可以在熟悉了配置之后使用。
\n
\n此处我们选择第一项 device-image 硬盘/分区[存到/来自]镜像文件
。
\n若想进行两盘对拷,可以选择第二项。我还没有尝试过。
\n挂载存储目录
\n这次我打算使用移动硬盘备份系统,故选择第一项 local dev 使用本机的分区
。
\n
\n随后,再生龙会提示插入想要挂载的 USB 设备,按照提示做即可。
\n
\n此时画面会动态显示系统识别到的存储设备。看到期望的目标设备时,按下 Ctrl-C
停止搜索。
\n
\n在扫描完电脑当前安装的所有硬盘的分区后,你需要选择备份镜像文件存放的那个分区。
\n如图,我希望备份到大小为 1.8T 的移动硬盘上,故选择最后一项 sdc2
。
\n
\n随后,再生龙询问你是否需要检查并修复挂载的文件系统,我们选第一项否就好了。
\n
\n接着,就是选择备份镜像存放的位置。
\n使用键盘的方向键选择目录,使用 Tab
跳转到下方的选项,选择 Browse
并敲击回车就可以进入到此目录。
\n若希望在选中目录下存放备份镜像文件(是一个文件夹),就可以选择 Done
选项,回车确认。
\n
\n系统询问是否检查生成的备份镜像的可还原性,这里我们选是,多花一点时间能确保备份的完整性。
\n
\n镜像加密,依个人喜好选择。
\n
\n待上述配置完成后,系统会向你再次确认备份的内容与目的地。
\n确认无误后输入 y
并敲击回车继续。
\n简单模式/高级模式 此时应该有一个模式选择,问你想要使用简单模式还是专家模式。
\n我建议选择 专家模式,简单模式能选择的参数较少。
\n
\n接下来的三个选项,全部保持默认配置即可。
\n
\n
\n
\n压缩方式选择
\n此处选择第三项 -z2p 使用并行 bzip2 压缩
。
\n实测 bzip2 压缩速度比较快,产生的备份镜像的体积也不算大。
\n下图为选择了第一项 -z1p 使用并行的 gzip 压缩
的速度:
\n
\n下图为选择了第三项 -z2p 使用并行 bzip2 压缩
的速度:
\n
\n可以看出 bzip2 压缩速度比 gzip 快了8倍。
\n其他压缩方式的速度,待我测试之后更新文章。
\n
\n分卷大小配置保持默认即可。
\n备份镜像检查 待备份完成后,再生龙还会进行一次备份镜像的可还原性检查,如下图:
\n
\n若得到下图的提示,则备份镜像生成成功了。
\n
\n随后,选择按照意愿选择备份结束后的操作即可。
\n
\n至此,再生龙 CLonezilla 的基础教学就结束了,你已经学会了如何使用再生龙的图形界面进行备份。
\n下面是一些再生龙的高阶(大概很高级)使用方法。
\n高级操作 使用无线网络备份 上文中,我的电脑仅有两个 USB 口,为备份的流程增添了不必要的麻烦。
\n能否使用 Wi-Fi 将备份镜像推送至家中的 NAS 呢?
\n再生龙内置了许多通过无线/有线网络备份的方法,如下图:
\n
\n我们尝试使用 Webdav 来远程备份吧!
\n利与弊 使用 Wi-Fi 备份可以摆脱线缆,更加轻松而优雅地进行备份。
\n然而,由于通过 Wi-Fi 或者一切网络传输数据的速度仍然无法比肩有线传输,备份所消耗的时间将是备份至本地磁盘的 3-4 倍。
\n备份我U盘中的 40G 的 Manjaro 系统用时 30min 左右。
\n倘若你有大把的时间,或家中的内网速度足够快,大可使用无线备份。品着咖啡,看着数据上云(笑)。
\n预先准备 上文中我们选择了基于 Debian 的 Clonezilla Stable 版本,遗憾的是 Debian 系统中并未携带太多驱动程序,因此识别不到我的 Intel AX200 无线网卡。
\n经过测试,基于 Ubuntu 的 Clonezilla Alternative Stable 版本可以识别到 AX200 网卡。
\n点击上方链接即可下载 Clonezilla Alternative Stable 版本的映像。
\n重新烧写启动U盘/拷贝映像至多启动U盘即可。
\n又遇到了启动问题 使用基于 Ubuntu 的再生龙,上文中使用的 KVM
模式变得无法打开了,且 VGA 800x600
模式是一样的花屏。
\n在一番尝试之后,我发现藏在更多启动选项菜单里的 VGA 1024x768
模式可以正常显示。看来基于 Debian 的再生龙也可以使用这个模式。
\n开始备份
\n选择了非本地的镜像存储位置后,系统将开启上图的网络管理菜单。
\n选择第一项 Edit a connection
。
\n
\n选择 Add
选项,在弹出菜单中 Wi-Fi
。
\n
\nProfile name
随意填写;
\nDevice
一般填写 wlan0
,系统的第一块无线网卡;
\n接着,按照自己的情况填写图中划线的三个配置即可。
\n
\n保存 Wi-Fi 配置后,就能看到当前配置的连接状态。
\n若当前配置名前带 *
,且右侧选项为 Deactivate
,则 Wi-Fi 已连接成功。
\n
\n接着,系统要求填写 Webdav 地址。
\n
\n最后,系统会向你确认 Webdav 是否正确。
\n若确认无误,即可敲击回车继续。
\n接下来的步骤和上述初级教程硬盘挂载之后的流程是完全一样的,请参考上文继续配置。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/f82a3103.html",
+ "url": "https://blog.udon.eu.org/archives/f82a3103.html",
+ "title": "BETAFPV 高频头固件编译 AttributeError",
+ "date_published": "2022-08-06T04:30:00.000Z",
+ "content_html": "错误原因 Python 模块 pypandoc
版本过新,1.8.0 及更高版本已移除了 BETAFPV 高频头固件中仍在使用的 convert
函数。
\n解决方法 安装旧版的 pypandoc
模块。
\npip install pypandoc==1.7.0
\n",
+ "tags": [
+ "教程",
+ "DIY"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/38942a16.html",
+ "url": "https://blog.udon.eu.org/archives/38942a16.html",
+ "title": "DIY 显示器音箱",
+ "date_published": "2022-06-03T07:15:00.000Z",
+ "content_html": "新买的显示器(LG 27UL500,虽然因为屏幕问题已经退货了)没有内置音箱,虽然大部分时间都在用耳机,但别人有的东西我不能没有嘛,就买了些材料,DIY 一个外接音箱。
\n写作此文章分享一下制作的过程~
\n物料清单
\n\n接受 5V 电压,可以驱动两个 3W 的喇叭。商品详情页面吹的很厉害,确实底噪很小,而且输出的音量非常高。相当 OK 的功放板。
\n\n8Ω 3W 喇叭两只(带音腔) 2*4RMB + 运费 \n \n音质很不错,声音很大也不会破音,因为是广告机用的喇叭么?
\n\n我选的型号是 PJ392,只要是 3.5MM 双声道的公头就行了。
\n\n这个随意选。
\n\n我买的是4芯的屏蔽线,可供 Type C 头使用(2 power 2 data),音频线只需要三芯(2 data 1 GND),屏蔽线是为了更小的干扰、更好的音质。
\n开始组装 3.5MM 线缆 剥除一段屏蔽线的外皮,做工还是很不错的,有金属丝和铝箔的屏蔽,塑料膜防水,还有一根抗拉扯的填充芯。
\n我选择使用红绿蓝三根线,黄线悬空。线色对应如下:
\n红 - 左声道;绿 - 右声道;蓝 - 接地。
\n
\n可以预先套上一段热缩管。
\n
\n取一枚 3.5mm 公头,旋下插头。
\n最长的一段一般是接地。若将接地朝下,我这款公头左侧为左声道,右侧为右声道。具体的接线方式可以用万用表测量接头和接口得知。
\n将线穿入孔中,上一坨焊锡即可。
\n
\n再使用万用表测量接头与线末端的连通性,注意不能与其他线短路了。
\n确认无误后可以打上热熔胶固定。
\n
\n再打点热熔胶,旋回外壳,将热缩管套好,加热热缩管使其收缩。
\n3.5mm 线缆就制作完成了。
\n驱动板焊接 驱动板上有三组线需要焊接:
\n\n音频输入线(3.5mm 线缆) \n电源输入线(Type C 线) \n音频输出线(喇叭线) \n \nType C 线我没有再用屏蔽线,用两根导线连接 Type C 母头和驱动板即可。
\n焊接方法就不多说了,线穿过孔,上锡即可。
\n
\n全部线缆焊接完成如下:
\n
\n热熔胶填充 完成接线后,确认无短路,即可连接电脑测试音箱。
\n若没有问题,考虑到需要长期使用,就可以用热熔胶覆盖焊接处,防止焊点脱落。
\n用热熔胶覆盖之后的驱动板:
\n
\n嘛…手艺不是很行。
\n \n就此,外接音箱组装完成啦!
\n",
+ "tags": [
+ "教程",
+ "DIY"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/2e528779.html",
+ "url": "https://blog.udon.eu.org/archives/2e528779.html",
+ "title": "迁移 Hexo 渲染环境至 GitHub Actions",
+ "date_published": "2022-05-23T11:30:00.000Z",
+ "content_html": "本博客使用的是 Hexo 静态博客框架,我将渲染环境搭建在家中 NAS 之上,部署了一个 Ubuntu Docker 并安装好了 Node.js 环境,正常使用了一两年。
\n上周末,写完新文章的我心血来潮,打算在更新博客的时候顺便更新一下老旧的 Node 和 Hexo(Node 12, Hexo 5.2)。
\n一番折腾之后,以 npm 和 hexo 环境全部被破坏(事后想想也许只是环境变量掉了)的下场结束。
\n鉴于 NAS 性能低下,更新一次 node module 都需要 30 分钟,我放弃了在 NAS 上重新部署渲染环境的念头,转而使用 GitHub Actions 渲染,并同时部署于 GitHub Pages 与 CloudFlare Pages。
\n将渲染环境迁至 GitHub Actions 不久之前,CloudFlare Pages 悄悄下架了 Hexo 框架的部署功能,只能用 GitHub Actions 渲染,然后再部署至 CloudFlare Pages 了。
\n项目结构的修改 若想使用 GitHub Actions,需要将博客的源码上传至 GitHub。考虑到隐私和安全的问题,建议创建一个私有仓库管理源码。
\n对于项目没有什么需要修改的,因为 Actions 渲染的流程和本地渲染的流程没有区别。
\n唯一需要改动的,是引入的主题。由于两个 Git 仓库不能嵌套,我们需要以 Git submodule 的形式引入主题仓库。
\n我使用的是 Fluid 主题。采用 覆盖配置 的方法,即在根目录之下有一份配置会覆盖主题内的配置文件,便于在 Actions 中渲染。
\n以下也将以 Fluid 为例,请根据你使用的主题修改命令或代码。
\n首先,删除原来的主题(若使用的是主题内的配置,注意备份配置文件!)
\n返回博客源码的根目录,执行:
\n1 git submodule add https://github.com/fluid-dev/hexo-theme-fluid.git themes/fluid
\n\n末尾的 themes/fluid
为此 submodule 在项目中的位置与名字,与先前本地渲染时的配置相同即可。
\n删除子模块的过程较为繁琐,请参考网上的文章进行操作。
\n在 Clone 此项目时,submodule 默认不会被下载,需要使用指令:
\n1 git submodule update --init --recursive
\n\n下载 submodule。在下面会提到的 Actions 配置文件中会出现这条指令。
\n接着,就可以将博客源码上传至 GitHub。
\nGitHub Actions 相关文件 在博客源码根目录创建 .github/workflows/submit.yml
和 .github/script/blog-update.sh
两个文件,填入下列代码。
\n以下代码参考文章 GitHub Actions 自动部署 Hexo 博客到 cPanel 虚拟主机 - 谷中望月 ,有所修改。
\nsubmit.yml
:
\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 name: CI on: push: branches: - main release: types: [published ]env: GIT_USER: Lao-Liu233 GIT_EMAIL: blog@udon.eu.org jobs: build: name: Build on node ${{ matrix.node_version }} and ${{ matrix.os }} runs-on: ubuntu-latest strategy: matrix: os: [ubuntu-latest ] node_version: [16.15 ] steps: - name: Checkout uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node_version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node_version }} - name: Install hexo run: | npm install -g hexo-cli - name: Install dependencies run: | npm install - name: Clone submodule run: | git submodule update --init --recursive - name: Configuration environment run: | sudo timedatectl set-timezone "Asia/Shanghai" mkdir -p ~/.ssh/ echo "$HEXO_DEPLOY_PRI" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan github.com >> ~/.ssh/known_hosts git config --global user.name $GIT_USER git config --global user.email $GIT_EMAIL - name: Deploy hexo run: | hexo clean hexo g -d - name: Update Blog run: | sh "${GITHUB_WORKSPACE}/.github/script/blog-update.sh"
\n\n.github/script/blog-update.sh
:
\n1 2 3 4 5 6 7 8 9 #!/bin/sh if [ -z "$(git status --porcelain) " ]; then echo "nothing to update." else git add source /_posts/\t git commit -m "triggle by commit ${GITHUB_SHA} " -a git push origin mainfi
\n\nCommit + Push,打开 Actions 界面,就能看到正在运行的 Action 啦。
\n不出意外,Action 成功执行,1分钟内博客就能渲染成功、部署至 GitHub Pages。
\n同时部署至 CloudFlare Pages 步骤较为简单,我简述一下。
\n打开 CloudFlare Pages, 连接至存放 渲染后 的静态文件的仓库,渲染的框架选择 None ,执行的指令填写 exit 0;
就可以了。
\n执行部署后,渲染后的静态文件就被部署至 CloudFlare Pages 啦。
\n",
+ "tags": [
+ "教程",
+ "GitHub Actions",
+ "Hexo"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/dbf21067.html",
+ "url": "https://blog.udon.eu.org/archives/dbf21067.html",
+ "title": "玩一玩 DN42",
+ "date_published": "2022-04-01T04:30:00.000Z",
+ "content_html": "两个月前,我向 DN42 提交了申请,并于4个小时后通过了审核,获得了自己的 AS 和 IP。
\n作此文分享一下把玩 DN42 的心得,也作为我的备忘录。
\n我的信息 1 2 3 AS4242423490 IPv4: 172.23 .13 .64 /28 IPv6: fd44:6b93 :4eaa::/48
\n\n目前仅一个洛杉矶节点开放 Peer,后期还将添加韩国和日本的节点。
\n如何把玩 注册 有关注册的文章很多,推荐这两篇:
\nDN42 实验网络介绍及注册教程(2022-02 更新) | Lan Tian @ Blog
\n初探 DN42 网络 - 宝硕博客 (baoshuo.ren)
\n需要使用 Git 和 PGP,在 DN42 的 Git 仓库提交你的个人信息即可完成注册。
\n搭建内网 在和其他 AS 建立对等连接之前,我们先要把内网整理好:
\n各台服务器的地理位置和网络位置各不相同,需要使用 VPN 创建虚拟局域网。
\n课堂上讲了两种内网路由协议:
\n\n有一位老朋友可以轻松解决以上两个问题:Zerotier 。
\nZerotier 的虚拟网络可以使用自己的 IP,只需在 Managed Routes 中设置你在 DN42 申请的 IPv4 和 IPv6,即可为每台加入虚拟网络的主机自动或手动配置 DN42 IP。
\n在机器之间使用 DN42 IP 互 ping 测试连通性。
\n准备 BGP 相关软件 搭建好内网之后,就可以开始配置 BGP 发言人啦。
\n选择一台或多台服务器,作为自治域向外宣告路由的发言人。
\n在每台服务器上都需要配置 BGP 相关的软件,以及和其他 BGP 发言人建立连接(一般是 VPN 连接)的软件。
\n目前在 DN42 网络用的比较多的 VPN 软件是 Wireguard,BGP 软件则可以从 bird 2、bird 1、quagga 等软件中选择。
\n我使用的是 bird 2。
\n安装与配置 BIRD 2 安装命令:
\n1 2 apt update apt install bird2 -y
\n\nbird 2 的配置文件位于 /etc/bird
,名为 bird.conf
。
\n配置文件可以参考(照抄)DN42 官方给出的配置:howto/Bird2 (dn42.dev)
\n喂到嘴边的配置方法:
\n\n将官方配置填入 /etc/bird/bird.conf
\n在 /etc/bird
目录下新建名为 peers
的文件夹 \n下载 ROA 配置(命令来自宝硕的博客 ) \n \n1 2 wget -4 -O /tmp/dn42_roa.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf && mv -f /tmp/dn42_roa.conf /etc/bird/dn42_roa.conf wget -4 -O /tmp/dn42_roa_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf && mv -f /tmp/dn42_roa_v6.conf /etc/bird/dn42_roa_v6.conf
\n\n\t并配置 crontab,每小时自动下载并重载新配置:
\n1 2 3 0 */1 * * * wget -4 -O /tmp/dn42_roa.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf && mv -f /tmp/dn42_roa.conf /etc/bird/dn42_roa.conf 0 */1 * * * wget -4 -O /tmp/dn42_roa_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf && mv -f /tmp/dn42_roa_v6.conf /etc/bird/dn42_roa_v6.conf 0 */1 * * * birdc configure
\n\n安装并配置 Wireguard 安装命令:
\n1 2 apt update apt install wireguard -y
\n\n这样就安装了 Wireguard
和名为 wg-quick
的管理工具。
\n使用命令:
\n1 wg genkey | tee privatekey | wg pubkey > publickey
\n\n在当前目录下创建 Wireguard 建立连接所用的密钥对(公钥和私钥)。
\n就此 Wireguard 安装完成。
\n配置系统内核 打开内核的数据包转发功能:
\n1 2 3 4 echo "net.ipv4.ip_forward=1" >> /etc/sysctl.confecho "net.ipv6.conf.default.forwarding=1" >> /etc/sysctl.confecho "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf sysctl -p
\n\n关闭内核 rp_filter
的严格模式:
\n1 2 3 echo "net.ipv4.conf.default.rp_filter=0" >> /etc/sysctl.confecho "net.ipv4.conf.all.rp_filter=0" >> /etc/sysctl.conf sysctl -p
\n\n如果有 ufw 等防火墙自动配置工具,务必关闭。
\np.s. 我拿到任何机器后会立刻执行的指令是:
\n \n\n创建 Dummy 网卡 dummy 网卡具体的作用我不是很清楚…
\n只知道如果要用链路本地地址进行通讯,要把 DN42 的 IP 地址绑定到 dummy 网卡上。
\ndummy 网卡配置指令如下:([ ] 中为需要你填写的内容)
\n1 2 3 4 5 ip link del dummy ip link add dummy type dummy ip addr add [你的 DN42 IPv4 地址]/32 dev dummy ip addr add [你的 DN42 IPv6 地址]/128 dev dummy ip link set dummy up
\n\n和小伙伴建立对等连接(peer) 需要和对方分享的 \n你的 DN42 信息,包括 AS 号和发言人的 DN42 IPv4(IPv6)地址; \n若使用链路本地地址,还需提供这东西,一般为一个本地 IPv6 地址,常取 fe80::[你的 AS 号后4位]
; \n发言人的外网 IPv4 地址(或域名)和 Wireguard 隧道的通讯端口; \nWireguard 公钥。 \n \n有一些信息会在以下的配置中获得。
\nWireguard 相关的 在 /etc/wireguard
目录下创建 Wireguard 配置文件,每一个配置文件对应着一个 Wireguard 隧道。
\n例如你要和 AS114514 臭 建立对等连接,可以在 peers
文件夹下新建一个名为 wg_114514.conf
(文件名即为 wireguard 隧道名)的配置文件。
\n配置的模板如下:([ ] 中为需要你填写的内容)
\n1 2 3 4 5 6 7 8 9 10 11 [Interface] Table = off ListenPort = [我们的监听端口,可以用对方 AS 号的后五位]PrivateKey = [刚刚生成的 Wireguard 私钥]PostUp = ip addr add [本机的 DN42 IPv4 地址]/32 peer [对方机器的 DN42 IPv4 地址]/32 dev %iPostUp = ip addr add [本机的链路本地地址(见 BIRD 相关配置)]/64 dev %i[Peer] PublicKey = [对方的 Wireguard 公钥]AllowedIPs = 10.0 .0.0 /8 , 172.20 .0.0 /14 , 172.31 .0.0 /16 , fd00::/8 , fe80::/64 Endpoint = [对方机器的公网 IP 地址或域名 : 端口号]
\n\n然后使用 wg-quick up [wireguard 隧道名(刚刚的配置文件名)]
启动 Wireguard 隧道。
\n可以 ping 一下对方的 DN42 IP 看看 Wireguard 隧道是否连接成功。
\n使用 wg
命令查看各隧道的连接情况。若有显示 last handshake
,一般情况下隧道就已成功建立。
\nBIRD 相关的 在先前导入的 bird 2 配置中定义了一个 peers
文件夹,就是用来存放 peer 相关的配置。
\n例如你要和 AS114514 又臭 建立对等连接,可以在 peers
文件夹下新建一个名为 114514.conf
(文件名可自定义)的配置文件。
\n我采用的是链路本地地址(Link-Local) 的配置方法。配置的模板如下:([ ] 中为需要你填写的内容)
\n1 2 3 protocol bgp from dnpeers { neighbor % '' as ; }
\n\n添加完配置之后别忘了用 birdc configure
重载 bird 2 配置。
\n使用命令 birdc s p
可以查看 BIRD 2 软件下所有协议的通信情况。
\n若显示为:
\n1 dn42_xxxx BGP --- up 20 :36 :30 .984 Established
\n\n则表示 BGP 连接已建立。
\n尾声 我还在写 DN42 相关的站点,在上面分享节点信息,方便大家 peer。
\n但目前进度缓慢(悲)。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/9b58c98e.html",
+ "url": "https://blog.udon.eu.org/archives/9b58c98e.html",
+ "title": "合宙 EPS32-C3 把玩记录(二):WiFi 与一个 Web 程序",
+ "date_published": "2022-03-26T16:15:00.000Z",
+ "content_html": "了解一下相关的库 \n这个库是自带的,不需要引入。
\n据我的理解,单片机的串口就是控制台程序的控制台,可以返回一些信息给上位机。
\n会用到的几个指令:
\n1 2 3 4 5 6 7 Serial.begin(Baudrate);\t Serial.end(); Serial.read();\t\t\t Serial.peek();\t\t\t Serial.flush();\t\t\t Serial.print/println();\t Serial.write();\t\t\t
\n\n\n#include <WiFi.h>
\nAP(接入点) Mode 创建一个接入点。
\n1 2 3 4 5 WiFi.mode(WiFi_AP);\t\t\t WiFi.softAPConfig(local_IP, gateway, subnet); WiFi.softAP(SSID,PASSWD);\t
\n\n更多函数见 WiFi.h AP 常用方法说明
\nSTA(站点) Mode 接入一个 AP。
\n1 2 3 WiFi.mode(WIFI_STA); \t\t WiFi.start(SSID,PASSWD)\t\t Serial.println(WiFi.localIP());\t\t
\n\n更多函数见 WiFi.h STA 常用方法说明
\n\n#include <WebServer.h>
\n创建一个简单的网站服务器。真的很简单。
\n一个个函数讲有点难理解,我放在这节的例程里面说明。
\n写一个测试程序吧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <WiFi.h> #include <WebServer.h> const char *SSID = "YOUR_SSID" ;const char *PASSWORD = "YOUR_PASSWORD" ; WebServer server (80 ) ;\t\tvoid handleIndex () \t\t\t { server.send(200 , "text/plain" , "Hello from ESP32!" ); }void setup () { Serial.begin(9600 );\t\t Serial.println(); WiFi.mode(WIFI_STA);\t WiFi.begin(SSID, PASSWORD);\t\t\t\t while (WiFi.status() != WL_CONNECTED) \t { delay(500 ); Serial.print("." );\t } Serial.println("WiFi connected!" ); Serial.println("IP address: " ); Serial.println(WiFi.localIP()); \t\t server.on("/" , handleIndex);\t\t\t server.begin();\t\t\t Serial.println("WebServer begin!" ); }void loop () { server.handleClient();\t }
\n\n访问串口返回的 IP,即可看到 Hello from ESP32!
这句话啦。
\n还有个 Web Server 叫 ESPAsyncWebServer 自带的 WebServer 是同步的,只支持处理一个连接。对于这种体量的机器其实足够了。
\n顺便学习一下一个第三方库吧。
\n添加库 对于这款 ESP32,需要下载并导入两个库(源码 ZIP 即可):
\nme-no-dev/AsyncTCP: Async TCP Library for ESP32
\nme-no-dev/ESPAsyncWebServer: Async Web Server for ESP8266 and ESP32
\n在 Arduino 的项目 > 加载库 > 添加 .ZIP 库
中导入这两个库。
\n用 ESPAsyncWebServer 重写刚刚的例程吧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <WiFi.h> #include <ESPAsyncWebServer.h> const char *SSID = "YOUR_SSID" ;const char *PASSWORD = "YOUR_PASSWORD" ; ESPAsyncWebServer server (80 ) ;\t\t void handleIndex (AsyncWebServerRequest *request) { request->send(200 , "text/plain" , "Hello, world!" ); }void setup () { Serial.begin(9600 );\t\t Serial.println(); WiFi.mode(WIFI_STA);\t WiFi.begin(SSID, PASSWORD);\t\t\t\t while (WiFi.status() != WL_CONNECTED) \t { delay(500 ); Serial.print("." );\t } Serial.println("WiFi connected!" ); Serial.println("IP address: " ); Serial.println(WiFi.localIP()); \t\t server.on("/" , handleIndex);\t\t\t server.begin();\t\t\t Serial.println("WebServer begin!" ); }void loop () { }
\n\n理论上来讲,上面的代码应该是正确的……
\n但 Arduino 在编译的时候报错,内容是 ESPAsyncWebServer 库中的某些代码。
\n有待我弄清楚出错的原因。
\n",
+ "tags": [
+ "教程",
+ "嵌入式开发"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/7f7bd4a5.html",
+ "url": "https://blog.udon.eu.org/archives/7f7bd4a5.html",
+ "title": "合宙 EPS32-C3 把玩记录(一):环境搭建与第一个程序",
+ "date_published": "2022-03-26T09:30:00.000Z",
+ "content_html": "为了贯彻本博客最重要的关键词:性价比 ,我看到性价如此高的开发板,想都没想就剁手了。
\n嘛其实也没有这么冲动,我在购买 3D 打印机之后就一直在计划着做一些网上现成的电子项目,但碍于这段时间 MCU 和大尺寸屏幕价格的飙升,一直没能开始动手。
\n正好最近我学习了 iPad 上的 3D 建模软件 Sharp3D,项目的外壳建模变得有可能;又遇到了这块便宜的板子,立即开工!
\n因为1.8寸的 TFT 显示屏还没到货,3D 建模就先放一边,先来研究一下这块开发板。
\n事先声明 本教程是我一边从零开始学习嵌入式开发一边作成的,有逻辑混乱、内容浅显和成吨的错误,还请已经熟悉嵌入式开发的大佬多多包涵与斧正)
\n问题:什么?开发环境不是按语言分的嘛? 在开始学习嵌入式开发之前,我简单地认为嵌入式开发因语言而已,分为用 C/Cpp 开发(Arduino)和用 Python 开发(MicroPython)。
\n直到我遇见了 ESP-IDF 这个东西。
\n啥啊,为啥这家伙用的也是 C,代码我还一点都看不懂。
\n解答 嵌入式开发选用的语言和语法因选择的框架而异。
\nESP-EDF 更靠近底层,因而编写更复杂;Arduino 对底层进行封装,更靠上层且对用户更友好;MicroPython 则是在开发板上还原了一个 Python 的开发环境,继承了 Python 的诸多优点(简单的语法、无需编译就能执行新代码等)。
\n此外,还能用 JS、Java、Lua 等等语言进行开发。
\n我的选择 我手上有两块板子,一块被我刷成了 MicroPython,但目前不打算去用它。
\n我打算用 Arduino + C 进行开发。
\n配置 VSCode + Arduino 开发环境 Arduino 没有代码补全,太难用。简述一下如何使用 VSCode 进行开发:
\n\nVSC 安装 Arduino 插件; \n在 首选项-设置 中配置 Arduino 的路径 Arduino.path
\n打开项目后选择 MCU 类型和串口 \n \n就能用啦。
\n第一个项目 第一个项目就不选输出 Hello World 了,一点硬件的感觉都没有。
\n据 官方文档 ,主板板载的两个 LED 灯对应的 GPIO 为 IO12 IO13
,高电平有效。
\n就此编写一个无稳态多协振荡电路让 LED 灯交替闪烁的程序:
\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void setup () { pinMode(12 , OUTPUT); pinMode(13 , OUTPUT); digitalWrite(12 , LOW); digitalWrite(13 , LOW); }void loop () { digitalWrite(12 , HIGH); digitalWrite(13 , LOW); delay(1000 ); digitalWrite(12 , LOW); digitalWrite(13 , HIGH); delay(1000 ); }
\n\n编译+上传即可。
\n结果就不展示了,两个灯在交替闪烁。
\n",
+ "tags": [
+ "教程",
+ "嵌入式开发"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/d01399e6.html",
+ "url": "https://blog.udon.eu.org/archives/d01399e6.html",
+ "title": "Code-Server 的代理配置",
+ "date_published": "2022-03-19T14:30:00.000Z",
+ "content_html": "一年前,我介绍了如何在群晖上使用 Docker 部署 Code-Server,在外也能轻松使用已经配置好的开发环境。群晖搭建 VSCode 服务器与 Syncthing 服务
\n最近我换了 iPad,琢磨如何发挥她的生产力。除了使用网页版的 IDE(Codepen、Gitpod等),就是自建网页版的 VSCode 了。下面简要介绍一下我是如何给 Code-Server Docker 配置代理,使其成为一个完备的开发平台。
\n \n\n将 Code-Server 部署在国内服务器(例如我家里的 NAS),可以获得稳定的连接,这对于开发平台是尤其重要的,VSCode 遇到连接不顺畅就会要求你刷新界面,很可能会丢失数据。
\n但由于众所周知的原因,在国内的网络环境做开发可以说是寸步难行,我便采用 Clash Docker 来给 Code-Server 加上代理。
\nClash Docker 安装 Clash Core 普通版 Image:dreamacro/clash - Docker Image | Docker Hub
\nClash Core Premium Image:dreamacro/clash-premium - Docker Image | Docker Hub
\nClash Core Premium 二进制文件: Premium release (github.com)
\nClash Core 有普通版和 Premium 版之分,目前我能体验到的二者的区别是普通版的 Clash Core 不支持 RULE-SET 功能。
\n我常用的配置文件大量使用了 RULE-SET,所以我必须得用 Clash Core Premium。
\n但 Pre Build 的 Image 似乎不支持 X86-64 v3 之下的 CPU(例如我的 J1900),所以我采取了部署普通版 Image,然后 attach 进 Docker 手动更换 Premium 内核的曲线救国方法。(更换 /
根目录下名为 Clash
的二进制文件)
\n部署 Docker 时注意一下几点:
\n\n开放 7890(或你定义的代理端口)和 9090(Clash Core 管理面板)端口。 \n将 /root/.config/clash
文件夹挂载到本地,存放 config.yaml
及其他配置文件。 \n \n请勿将 Clash Core 的管理面板暴露到公网。我选择用 Tailscale 建立 VPN 访问家中的服务器进行配置。
\nCode-Server 的配置 需要在 Code-Server Docker 里添加两个环境变量,实现开机自动连接代理:
\n\nhttp_proxy=http://clash_docker_ip:7890
\nhttps_proxy=http://clash_docker_ip:7890
\n \n可以使用同样的方法给其他 Docker 添加代理。
\n \n在一顿折腾之后,Code-Server 终于可以顺畅访问 Github 等网站了。
\n可喜可贺,可喜可贺。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/7263a385.html",
+ "url": "https://blog.udon.eu.org/archives/7263a385.html",
+ "title": "Klipper 的外网访问",
+ "date_published": "2022-02-12T06:50:00.000Z",
+ "content_html": "网络上有关 Klipper 的中文教程少之又少,固作此教程介绍一下如何在外网优雅地访问家中装有 Klipper 的 3D 打印机。
\n方法一:端口转发 此方法仅适用于拥有公网 IP 的用户
\n首先,使用 DDNS 将家庭宽带动态变化的 IP 绑定至域名,教程如下:
\n\n在配置端口映射之前,先介绍一下 Klipper 的网络结构:
\ngraph LR;\n\tA("你的设备") <--80-->\n\tB("前端网页(Fluidd/Mainsail/Octoprint)") <--7125-->\n\tC("API 服务器 Moonraker") \t<-->\n\tD("你的 3D 打印机");\n
\n\n线上的数字便是通讯的端口。
\n由上图,我们不难看出,若需要在外网访问家中的 Klipper,就需要映射 80 和 7125 两个端口。
\n于路由器的 端口转发/端口映射 界面配置 80 和 7125 的转发即可。家庭宽带的公网 IP 不会开放 80 端口,可将外网端口配置为 8080,对应的内网端口为 80 即可。
\n接着,在 Moonraker 配置 moonraker.conf
[authorization]
模块的 cors_domains
模块中添加你的域名,格式为 *://你们域名
\n也可以选择不使用自己搭建的前端网页,而使用 Fluidd 或者 Mainsail 作者搭建的前端网页。在 Moonraker 配置 moonraker.conf
[authorization]
模块的 cors_domains
模块中添加 *://my.mainsail.xyz 与 *://app.fluidd.xyz
\n方法二:内网穿透 本人不推荐使用这个方法,固仅简述一下
\n可以使用花生壳等内网穿透服务,但给的带宽太小,只能使用控制界面,不能使用摄像头。
\n也可以选择自建内网穿透,例如 Frp, Ngrok 等服务。但最近越来越多 Frp 服务器遭到攻击,固不建议自建。
\n方法三:使用 VPN 这是本人推荐的方法。
\n与 Octoprint + Marlin 仅需要映射 80 端口不同,Klipper 还需要映射 Moonraker 的 7125 端口,配置端口转发与实际使用都不如前者来的方便。
\n我个人推荐用诸如 Zerotier, Tailscale 一类的 VPN 软件,搭建自己的小内网,通过内网 IP 直接访问 Klipper,既安全又方便。
\nZerotier 或者 Tailscale 的使用技巧网上一大片,我就不赘述了。
\n",
+ "tags": [
+ "教程",
+ "3D 打印"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/375e7789.html",
+ "url": "https://blog.udon.eu.org/archives/375e7789.html",
+ "title": "群晖搭建 VSCode 服务器与 Syncthing 服务",
+ "date_published": "2021-03-19T16:00:00.000Z",
+ "content_html": "这次我尝试在群晖上搭建 VSCode 服务器与 Syncthing 服务,实现电脑与 NAS 间的代码同步与网页中的 Coding。
\n \n\n \nVSCode 网页版的实现 偶遇服务器软件 刷 RSS 时我看到 V2EX 上一个帖子分享了一个实用工具:github+1s
\n这个项目可以实现在 网页版 VSCode 中打开 GitHhub 上的代码。
\n这个项目使用的 code-server 引起了我的兴趣。
\ncode-server 的部署 群晖自带的 Docker 套件简化了部署的过程。
\n在注册表中搜索 code-server 下载 image;
\n打开 image 进行配置:
\n\n使用高权限执行容器 \n在 高级设置-环境
页面中添加环境变量 PASSWORD
,值设定为你的登陆密码(由于在 Docker 页面中以明文保存,请注意密码安全)。 \n \n启动容器,并使用 Docker 内置的 终端机
打开一个新的 bash
。换源、更新 apt 、安装常用软件我就不再赘述。
\ncode-server 的外网访问 code-server 没有自带 HTTPS 相关的配置,需要使用网站服务器进行反向代理。
\n目前比较流行的有 Caddy 和 NGINX 两款。
\n鉴于 Caddy 配置简单且 HTTPS 优先,我这次使用 Caddy。
\nCaddy 官方安装文档
\n或使用一键安装脚本
\ncurl https://getcaddy.com | bash -s personal
\n如果有开放的 443 端口,则可使用 Caddy 的自动 HTTPS 功能进行快速配置。
\n若像我一样在家中的 NAS 上配置 code-server,则需要自己申请 tls 证书 (如 Let`s Encrypt),并按照 Caddy-tls 配置。
\n反向代理配置可参考 code-server 官方的反代配置教程 。
\n一些疑难杂症 一些插件无法安装 目前 code-server 的 VSCode 版本为 1.51.1, VSCode 官方则为 1.54.3 ,因此某些较新的插件可能无法使用。
\n可以前往 VS插件市场 下载旧版插件并手动安装。
\nDocker 内挂载的目录无写权限 使用 sudo chmod 777 ./
给 coder 用户赋予读写权力。
\nDocker 内 Caddy 无法自启 这个我也还没有解决。暂且手动启动。
\ncode-server 的各种性能问题 等待更多的更新吧,我接下来会尝试在 Docker 里编译原版 VSCode 并开启 Web 模式,对比二者性能。
\nSyncthing 服务搭建 Syncthing 官网 已经给出了十分详尽的安装教程,也有群晖的安装包,我就不再赘述安装过程。
\nSyncthing 的管理页面端口为 8384
,若想在外网访问请使用 HTTPS。可以使用群晖内置的反向代理服务器进行反代。
\n要注意把 22000
端口的 TCP
与 UDP
全部开放,才可在外网顺利与 NAS 同步。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/a455b52c.html",
+ "url": "https://blog.udon.eu.org/archives/a455b52c.html",
+ "title": "逃离国产软件 - 虚拟机计划",
+ "date_published": "2020-08-07T04:30:00.000Z",
+ "content_html": "使用 Hyper-V 运行 Windows LTSC 虚拟机,以隔离流氓的国产软件们。
\n为何大费周章? 我试过网络上的不少方法来隔离鹅厂的软件 —— 刚开源的 Sandboxie 也好,利用 Windows ACL 机制通过低权账户加以限制也好 —— 都因为权限问题失败了。最后决定用虚拟环境直接隔离这些软件。
\n \n\n为什么是 Hyper-V 和 LTSC? 我用过 VMWare,觉得还是 Windows 原生的 Hyper-V 启动最快。还不用钱。
\nWindows LTSC 是企业定制版,官方精简了系统,性能开销更少。
\n事前准备 拥有 16G 以上内存及 NVME 高速硬盘的用户可以放心采用该方案,虚拟机运行时不会影响其他软件的流畅运行。
\n下载 MSDN 上的 Windows LTSC: 侧边栏选择 操作系统 ;选择 Windows 10 Enterprise LTSC 2019 。
\n安装 Hyper-V: 对于 Windows 专业版 用户,在 控制面板 - 程序与功能 - 启动或关闭Windows功能 中找到 Hyper-V 并打开。
\n对于 其他版本 Windows 的用户,则稍微有些麻烦:
\n\n在记事本中输入如下代码 \n \n1 2 3 4 5 6 7 8 9 pushd "%~dp0" dir /b %SystemRoot%\\servicing\\Packages\\*Hyper-V*.mum >hyper-v.txtfor /f %%i in ('findstr /i . hyper-v.txt 2^>nul' ) do dism /online /norestart /add-package:"%SystemRoot%\\servicing\\Packages\\%%i" del hyper-v.txt Dism /online /enable-feature /featurename:Microsoft-Hyper-V-All /LimitAccess /ALL
\n\n\n把文件保存为Hyper-V.cmd \n右键该文件,选择 以管理员身份运行 \n \n根据提示完成安装。
\n\n摘自知乎 没人不认识我 的回答
\n \n安装虚拟机 打开 Hyper-V ,选择 新建 - 虚拟机 ;
\n根据向导提示设置虚拟机,选择 第一代虚拟机 ;
\n内存分配我选了 2G (共16G);CPU 分配我选了 4线程 (共12线程)【需要完成配置后在 设置 中更改】;
\n其余设置默认或自定;
\n安装选项选择 从 CD/DVD-ROM 安装操作系统 ,选择刚刚下载好的 Windows LTSC ISO镜像;
\n完成配置后,启动虚拟机,安装 Windows LTSC 到硬盘。
\n配置环境 装好系统后要干什么不用我说了吧。
\n把垃圾们倒进去就好啦。
\n实测空载消耗 CPU 算力在 0%-3% 浮动;内存占用 2.2G,实际使用 1.2G 。
\n",
+ "tags": [
+ "教程",
+ "软件"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/6b40e5ad.html",
+ "url": "https://blog.udon.eu.org/archives/6b40e5ad.html",
+ "title": "提升音乐体验-本地音乐标签/歌词匹配与回放增益",
+ "date_published": "2020-05-05T05:40:06.000Z",
+ "content_html": "推荐两款能让听歌体验变得更好的软件 —— Music Tag / Foobar2000 。
\n附带使用教程。
\n \n\n音乐标签 Music Tag 官方网站/下载地址 https://www.cnblogs.com/vinlxc/p/11347744.html
\n软件特点 古人云,专辑封面是一首歌的灵魂。(我乱说的)
\nMusic Tag 是一款可以自动匹配本地音乐的标签与歌词的软件。
\n一键从多家音乐网站拉取元数据/封面图/歌词,不能再爽了。
\n使用教程/建议 导入一批歌曲后,选择 自动匹配标签 :(如下图)
\n
\n然后按下图配置,在原有元数据下添加更多信息:
\n
\n在第一轮匹配后,建议再进行第二轮封面图片匹配,并覆盖原图片,配置如下图:
\n
\n接着,就需要你耐心地查看每首歌的元数据(善用方向键),检查是否有匹配错误的歌曲,并在 标签源-组合标签 处手动搜索,选择正确的元数据,如下图所示:
\n
\n建议检查一下歌曲的歌词,特别是较小众的歌曲。Music Tag 的歌词搜索错误率较高。
\n如下图所示,选择并查看歌词,若有误可以手动搜索:
\n
\n
\n最后选择导出 LRC 歌词:
\n
\n所有歌曲的 元数据-封面图-歌词 就此已匹配完毕。
\n最终效果如下:
\n
\nFoobar2000 官方网站 http://www.foobar2000.org/
\n回放增益介绍 维基百科-回放增益
\n回放增益可以使音量大小各不相同的音乐向统一标准靠齐。
\n将所有音乐扫描并打上回放增益 tag 后,再也不用担心下一首歌震破耳膜了。
\n使用教程 导入并全选歌曲,右键,选择 ReplayGain :
\n
\n下列三种扫描方式均可。个人喜欢将全部歌曲的音量统一,故选择第一种:
\n
\n待扫描结束后,点击 Update File Tags ,将回放增益数据写入文件 Tag :
\n
\n回放增益扫描完毕。
\n小结 我是一周不往曲库里添加新曲就受不了的类型。
\n这几天往曲库里添加曲子的时候查阅了这些能提升音乐体验的方法,希望能帮到你。
\n",
+ "tags": [
+ "教程",
+ "软件"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/e3c95af8.html",
+ "url": "https://blog.udon.eu.org/archives/e3c95af8.html",
+ "title": "BGP初体验-Linux,Openwrt与Quagga",
+ "date_published": "2020-02-10T04:30:00.000Z",
+ "content_html": "好久没写博客了!今天抽出点时间分享一下我的 BGP 初体验。
\n这一切的一切都要从一个叫鹤伞 Ria 的 Vtuber 说起……
\n \n\n环境配置 操作环境 Debian 系 Linux(其实是 Kali):
\nsudo apt update & sudo apt install quagga
\nOpenwrt:
\nopkg update & opkg install quagga quagga-zebra quagga-bgpd quagga-vtysh
\n网络配置 采用 Zerotier 建立内网环境模拟真实网络。
\nZerotier 的安装及配置不再赘述,官网有详尽教程。
\n网络环境 本来想做三台设备两个 AS 间的通讯,有一台设备无法安装任何 BGP 软件也就作罢;也没有画拓扑图的必要了(悲)。
\nAS114514 Debian 10.0.1.1,命名为 R1,享有 10.0.1.0/24 网段;
\nAS1919810 Openwrt 10.0.2.1,命名为 R2,享有 10.0.2.0/24 网段。
\nQuagga配置 下面才是重头戏。Quagga 的配置文件位于 /etc/quagga/
\n据测试,Openwrt 安装 quagga 后会带有初始配置,而 Debian 不带初始配置,可自行创建。
\n/etc/quagga/zebra.conf
配置(可不用修改)
\n1 2 3 4 5 6 7 8 ! 登陆密码password zebra !access -list vty permit 127.0 .0 .0 /8 access -list vty deny any !line vty access -class vty
\n\n/etc/quagga/bgpd.conf
配置(需要根据情境修改)
\n1 2 3 4 5 6 7 8 9 10 11 ! 密码 password zebra! AS号 router bgp 114514! 本机公网(VPN网络)ip bgp router-id 10.0.1.1! 本路由享有网段(需要交换的网段) network 10.0.1.0/24! peer信息(建立连接的机器的公网(VPN网络)ip,AS及称呼) neighbor 10.0.2.1 remote-as 1919810 neighbor 10.0.2.1 description R2
\n\n 另一台机器的配置只需依葫芦画瓢,我就不再赘述。
\n配置之后,运行
\n/etc/init.d/zebra restart
\n/etc/init.d/bgpd restart
(Debian)
\n或者
\n/etc/init.d/quagga restart
(Openwrt)
\n重启 quagga 服务。
\n欣赏结果 忙活了这么久,终于能看到结果了!
\n运行
\nvtysh
\n进入 quagga 控制台(指令模拟 Cisco,这块不大了解)
\n输入
\nshow ip bgp neighbor
\n就会看到
\n1 2 3 BGP neighbor is 10.0 .2 .1 , remote AS 1919810 , local AS 114514 , external link Description: R2 BGP version 4 , remote router ID 10.0 .2 .1
\n\n还有这么一张表
\n Sent Rcvd\nOpens: 2 0\nNotifications: 0 0\nUpdates: 2 2\nKeepalives: 1050 1049\nRoute Refresh: 0 0\nCapability: 0 0\nTotal: 1054 1051\n
\n再看看路由器内的活动ipv4路由表
\n\n\n\n网络 \n对象 \nIPv4 网关 \n跃点数 \n表 \n \n \n\nxxx \n10.0.2.0/24 \n10.0.2.1 \n20 \nmain \n \n
\n就算大功告成了!
\n有什么用处呢? 没有。
\n内网测试唯一能享受的就是看着这条无形的链接,想象自己也是网络工程师。
\n但是还是感觉很爽!
\n而且你已经学会(大概)了 BGP,获取全球路由表也能办到了!
\n参考 Ria 的爸爸(我的岳父)的文章
\n使用bird配置bgp网络互连
\n至于这一切与 Ria 有啥关系?欢迎关注 Ria 了解详情(滑稽)
\nTelegram群组 bilibli Youtube
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/9d1c6fa4.html",
+ "url": "https://blog.udon.eu.org/archives/9d1c6fa4.html",
+ "title": "Joplin+Webdav同步问题的解决方案",
+ "date_published": "2020-01-06T10:00:00.000Z",
+ "content_html": "问题内容 在群晖上搭建了 Webdav 服务器,使用 Joplin 连接后无法同步笔记。
\n \n\n解决方案 错误示范: ‘ https://your.domain.com:[your port] ‘
\n以此路径访问的是群晖文件系统的 /
目录,由于没有权限读写,同步失败。
\n正确示范: 在群晖控制面板内新建一个名为 Joplin
的共享文件夹。以域名:
\n‘ https://your.domain.com:[your port] /Joplin’
\n访问即可同步。
\n自定义配置: 若想在已经存在的文件夹下同步 Joplin 笔记,按照正确示范所书写的 URL 书写路径即可在想要的地方同步。
\n解决方案的探索 (我并未了解过 Webdav 的原理) 遇到此问题时,我在上 Google 查找解决方法前试着自行分析。思考原因后我选择了抓包分析。
\n结合抓包结果和 Joplin 同步日志可以看到 Joplin 在 /
目录下查找了 .lock
等文件。结合其他 Webdav 软件可以看到访问的 URL 指向的是目标文件夹,即“域名:端口/目标文件夹”,由此推测需要在配置内为 Joplin 指明同步目录,否则将在没有权限的根目录下同步,导致失败。
\n这是一次没有什么技术含量但能启发我的尝试。若你也遇到了类似的问题,希望也能启发到你。
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/27f2d840.html",
+ "url": "https://blog.udon.eu.org/archives/27f2d840.html",
+ "title": "iPv6下绝佳的DDNS方法-dynv6",
+ "date_published": "2019-10-03T15:30:00.000Z",
+ "content_html": "下面我将介绍一种适用于 IPv6-DDNS 的绝佳方法!
\n \n\n通过 IPv6 访问的优点 没有端口限制!!!可以通过 80/443 访问 web 服务器了!不用带着烦人的端口号!
\n每台设备有独立的 IPv6,配置更加方便,无需路由器充当网关进行端口转发!
\nIPv6 也有不足之处 最严重的问题:很多家宽并没有开启 IPv6 的获取。但访问 IPv6 的设备必须要拥有 IPv6 地址!
\n技术问题,难以解决。一般来说手机的移动网络都已分发 IPv6 地址。
\n想要家宽拥有 IPv6?或许你需要修改光猫设置(超级管理),亦或是将光猫改为桥接模式,用路由器拨号从而获取 IPv6 地址。
\n如何实现iPv6-DDNS \n\n这点尤其重要!我在网络上寻寻觅觅无数脚本,总是失败。最后才发现官方有提供脚本也!一试马上就成功了。
\n \n\n1 token = *your token * ./dynv6.sh *your DDNS domain*
\n\n\n可以将其添加到 crontab 一类的软件内,规定时间自动执行脚本(每 10分钟一次为宜) \n \n大功告成!
\n事后 \n你可以把自己的域名 CNAME 过去 \n也可以用 dynv6 提供的域名 \n \n随心所欲!
\nOver.
\n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/a4c81e8f.html",
+ "url": "https://blog.udon.eu.org/archives/a4c81e8f.html",
+ "title": "搭建Calibre-Web电子书网页端管理服务",
+ "date_published": "2019-10-02T10:30:00.000Z",
+ "content_html": "说在前面 Kindle 是一样阅读利器,但若是没有一个强大的书库,它也只能用来压泡面(笑)
\n今天我们利用 Docker 在群晖(任意系统)上搭建 Calibre-web 服务
\n \n\ndocker选择 \ntechnosoft2000-calibre-web
\n \n我在尝试了 3 款 Docker Image 后,决定使用这款。
\n优点 Calibre-web \n基于 Python,性能高(低)
\n \n与 Calibre 软件的数据库等文件完全互通
\n \n支持推送至 Kindle
\n \n支持在线转码书籍(虽然问题较多)
\n \n \ntechnosoft2000-calibre-web \n安装 首先,将你的 Calibre 数据库的位置挂载到 Docker 内的 /books 。
\n启动 Docker 后不要着急,等待 Docker 内软件安装完毕后,访问您设置的端口,访问 Calibre-Web。
\n在 Calibre 数据库位置 一栏填写
\n \n\n特性配置 可以依情况而变,不一定要按我的配置。
\n若想要使用在线转码功能,外部二进制 一栏中,选择 使用 calibre 的电子书转换器 。 转换工具路径 按图片中填写。
\n1 /opt /calibre/ebook-convert
\n\n点击 提交 即可完成安装。
\n注意
\n若提示无法读取数据库,尝试将数据库所处的文件夹的权限改为755 。
\n如何操作?请打开 SSH,在 Terminal 内操作。
\n若还是失败,尝试在本地 Calibre 软件新建一个书库,将空的 数据库文件移动到你挂载的目录下。
\n安装界面示意图
\n
\n尾声 安装后的操作我就不多提了。该上传书籍的上传,该push的push。
\n我的几个建议:
\n\n推送邮箱推荐使用 Outlook,限制少。 \n可以用 Calibre 软件管理数据库。不知道为什么,在本地做好更改后,网页版并没有任何变化。我试着备份又还原了数据库后,网页里再重新加载数据库才成功了。 \n网页转码会遇到种种问题,例如电子书有加密。转码失败实属正常。 \n \n",
+ "tags": [
+ "教程"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/87bacf3f.html",
+ "url": "https://blog.udon.eu.org/archives/87bacf3f.html",
+ "title": "群晖-外网访问一站式教程2 DDNS",
+ "date_published": "2019-08-31T10:30:00.000Z",
+ "content_html": "简述配置 DDNS 的方法。
\n什么是DDNS 维基百科-动态DNS 鉴于 IPv4 地址的枯竭,运营商开始给家用宽带分配动态 IP 地址,即 IP 会随时间或重新拨号而改变。DDNS 可以允许用户通过API动态地将变化的 IP 地址传送给域名解析商,达到域名外网访问的效果。 由于带宽分配原因,家用宽带的上传带宽一般在 20-30Mbps 间,故外网访问速度并没有达到如签约的 100Mbps 属正常。
\n事先准备 打电话给 ISP(运营商)的小姐姐,让她给你公网 IP,如果问起原因可以回答家里装监控。没有开启公网 IP 将无法从外网访问家庭的内部网络。 由于运营商(指大部分,如电信)封锁了 80(HTTP)和 443(HTTPS)端口,我们将使用其他的端口进行访问。挑选一些你喜欢的端口,预备使用(如 8080-8090,4431-4439等)。
\n选择支持DDNS的域名解析服务商 CloudFlare 老牌的域名解析商,也是少有的免费提供 CDN 的服务商。 我推荐 CloudFlare 的原因有三点
\n\n可以使用 CDN,保证网络质量始终处于较好状态。例如,我的 Blog 搭建在 Github 上,若有时因网络抽风无法访问 Github,CDN 能助你一臂之力。 \n可以查看连接数,数据量,访客量等详细数据。 \nAPI 获取方便。(2022 Update: CloudFlare 的 API 服务器接近半墙,国内很难再访问了,不推荐使用) \n \nDynv6 提供 IPv4 与 IPv6 DDNS 的服务商,在 21 年有一次较长时间的故障,平常都非常稳定。
\nDNSPod 被腾讯收购的 DNS 服务商,使用需实名。
\n配置 DDNS 服务 家用路由器的 DDNS 功能一般仅支持国内大型服务商,例如花生壳。
\n有两种方法可以配置自己的 DDNS 服务:
\n\n将负责拨号的路由器刷成 Openwrt 系统,安装 DDNS 插件以配置自定义脚本的 DDNS 服务; \n在一台 24x7 运行的设备上,通过 API 获取 IP 地址,并定时执行脚本更新 IP 地址; \n \n前者虽然更加麻烦,但可以实现仅在 IP 更换时发起更新解析的请求,而不需要定期(如每十分钟)请求一次 API,减小账户被封的风险,并尽可能地缩短从 IP 更换到新的解析生效的时间。
\n不管是路由器也好,Linux 上的脚本也好,可以在 GitHub 上寻找对应 DDNS 服务商的更新脚本,填上配置就能使用啦~
\n",
+ "tags": [
+ "教程"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/tag/\346\225\231\347\250\213/rss.xml" "b/tag/\346\225\231\347\250\213/rss.xml"
new file mode 100644
index 00000000..111e82db
--- /dev/null
+++ "b/tag/\346\225\231\347\250\213/rss.xml"
@@ -0,0 +1,1024 @@
+
+
+
+ カレーうどん屋 • Posts by "教程" tag
+ https://blog.udon.eu.org
+ カレーうどん屋.
+ zh-CN
+ Sun, 16 Apr 2023 00:00:00 +0800
+ Sun, 16 Apr 2023 00:00:00 +0800
+ 教程
+ 随笔
+ 软件
+ 小鸡测评
+ 3D 打印
+ 嵌入式开发
+ GitHub Actions
+ Hexo
+ DIY
+ -
+
https://blog.udon.eu.org/archives/8b68ddd6.html
+ 修复 UEFI 引导的 GRUB
+ https://blog.udon.eu.org/archives/8b68ddd6.html
+ 教程
+ Sun, 16 Apr 2023 00:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/8b115688.html
+ 使用 Docker Compose 部署音乐服务器 Navidrome
+ https://blog.udon.eu.org/archives/8b115688.html
+ 教程
+ Tue, 31 Jan 2023 12:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/f9bfe16a.html
+ 使用 Docker Compose 部署 Keycloak 20
+ https://blog.udon.eu.org/archives/f9bfe16a.html
+ 教程
+ Sun, 22 Jan 2023 20:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/e74a90f2.html
+ 使用再生龙 Clonezilla 备份操作系统
+ https://blog.udon.eu.org/archives/e74a90f2.html
+ 教程
+ Fri, 12 Aug 2022 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/f82a3103.html
+ BETAFPV 高频头固件编译 AttributeError
+ https://blog.udon.eu.org/archives/f82a3103.html
+ 教程
+ DIY
+ Sat, 06 Aug 2022 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/38942a16.html
+ DIY 显示器音箱
+ https://blog.udon.eu.org/archives/38942a16.html
+ 教程
+ DIY
+ Fri, 03 Jun 2022 15:15:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/2e528779.html
+ 迁移 Hexo 渲染环境至 GitHub Actions
+ https://blog.udon.eu.org/archives/2e528779.html
+ 教程
+ GitHub Actions
+ Hexo
+ Mon, 23 May 2022 19:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/dbf21067.html
+ 玩一玩 DN42
+ https://blog.udon.eu.org/archives/dbf21067.html
+ 教程
+ Fri, 01 Apr 2022 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/9b58c98e.html
+ 合宙 EPS32-C3 把玩记录(二):WiFi 与一个 Web 程序
+ https://blog.udon.eu.org/archives/9b58c98e.html
+ 教程
+ 嵌入式开发
+ Sun, 27 Mar 2022 00:15:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/7f7bd4a5.html
+ 合宙 EPS32-C3 把玩记录(一):环境搭建与第一个程序
+ https://blog.udon.eu.org/archives/7f7bd4a5.html
+ 教程
+ 嵌入式开发
+ Sat, 26 Mar 2022 17:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/d01399e6.html
+ Code-Server 的代理配置
+ https://blog.udon.eu.org/archives/d01399e6.html
+ 教程
+ Sat, 19 Mar 2022 22:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/7263a385.html
+ Klipper 的外网访问
+ https://blog.udon.eu.org/archives/7263a385.html
+ 教程
+ 3D 打印
+ Sat, 12 Feb 2022 14:50:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/375e7789.html
+ 群晖搭建 VSCode 服务器与 Syncthing 服务
+ https://blog.udon.eu.org/archives/375e7789.html
+ 教程
+ Sat, 20 Mar 2021 00:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/a455b52c.html
+ 逃离国产软件 - 虚拟机计划
+ https://blog.udon.eu.org/archives/a455b52c.html
+ 教程
+ 软件
+ Fri, 07 Aug 2020 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/6b40e5ad.html
+ 提升音乐体验-本地音乐标签/歌词匹配与回放增益
+ https://blog.udon.eu.org/archives/6b40e5ad.html
+ 教程
+ 软件
+ Tue, 05 May 2020 13:40:06 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/e3c95af8.html
+ BGP初体验-Linux,Openwrt与Quagga
+ https://blog.udon.eu.org/archives/e3c95af8.html
+ 教程
+ Mon, 10 Feb 2020 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/9d1c6fa4.html
+ Joplin+Webdav同步问题的解决方案
+ https://blog.udon.eu.org/archives/9d1c6fa4.html
+ 教程
+ Mon, 06 Jan 2020 18:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/27f2d840.html
+ iPv6下绝佳的DDNS方法-dynv6
+ https://blog.udon.eu.org/archives/27f2d840.html
+ 教程
+ Thu, 03 Oct 2019 23:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/a4c81e8f.html
+ 搭建Calibre-Web电子书网页端管理服务
+ https://blog.udon.eu.org/archives/a4c81e8f.html
+ 教程
+ Wed, 02 Oct 2019 18:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/87bacf3f.html
+ 群晖-外网访问一站式教程2 DDNS
+ https://blog.udon.eu.org/archives/87bacf3f.html
+ 教程
+ Sat, 31 Aug 2019 18:30:00 +0800
+
+
+
+
diff --git "a/tag/\350\275\257\344\273\266/atom.xml" "b/tag/\350\275\257\344\273\266/atom.xml"
new file mode 100644
index 00000000..7d63984e
--- /dev/null
+++ "b/tag/\350\275\257\344\273\266/atom.xml"
@@ -0,0 +1,104 @@
+
+
+ https://blog.udon.eu.org
+ カレーうどん屋 • Posts by "软件" tag
+
+ 2020-08-07T04:30:00.000Z
+
+
+
+
+
+
+
+
+
+
+ https://blog.udon.eu.org/archives/a455b52c.html
+ 逃离国产软件 - 虚拟机计划
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>使用 Hyper-V 运行 Windows LTSC 虚拟机,以隔离流氓的国产软件们。</p>
+<h3 id="为何大费周章?"><a href="#为何大费周章?" class="headerlink" title="为何大费周章?"></a>为何大费周章?</h3><p>我试过网络上的不少方法来隔离鹅厂的软件 —— 刚开源的 Sandboxie 也好,利用 Windows ACL 机制通过低权账户加以限制也好 —— 都因为权限问题失败了。最后决定用虚拟环境直接隔离这些软件。</p>
+<span id="more"></span>
+
+<h3 id="为什么是-Hyper-V-和-LTSC?"><a href="#为什么是-Hyper-V-和-LTSC?" class="headerlink" title="为什么是 Hyper-V 和 LTSC?"></a>为什么是 Hyper-V 和 LTSC?</h3><p>我用过 VMWare,觉得还是 Windows 原生的 Hyper-V 启动最快。还不用钱。</p>
+<p>Windows LTSC 是企业定制版,官方精简了系统,性能开销更少。</p>
+<h3 id="事前准备"><a href="#事前准备" class="headerlink" title="事前准备"></a>事前准备</h3><p>拥有 16G 以上内存及 NVME 高速硬盘的用户可以放心采用该方案,虚拟机运行时不会影响其他软件的流畅运行。</p>
+<h4 id="下载-MSDN-上的-Windows-LTSC"><a href="#下载-MSDN-上的-Windows-LTSC" class="headerlink" title="下载 MSDN 上的 Windows LTSC:"></a>下载 <a href="https://msdn.itellyou.cn/">MSDN</a> 上的 Windows LTSC:</h4><p>侧边栏选择 <strong>操作系统</strong> ;选择 <strong>Windows 10 Enterprise LTSC 2019</strong>。</p>
+<h4 id="安装-Hyper-V:"><a href="#安装-Hyper-V:" class="headerlink" title="安装 Hyper-V:"></a>安装 Hyper-V:</h4><p>对于 Windows 专业版 用户,在 控制面板 - 程序与功能 - 启动或关闭Windows功能 中找到 <strong>Hyper-V</strong> 并打开。</p>
+<p>对于 其他版本 Windows 的用户,则稍微有些麻烦:</p>
+<ol>
+<li>在记事本中输入如下代码</li>
+</ol>
+<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">pushd</span> <span class="hljs-string">"%~dp0"</span><br><br><span class="hljs-built_in">dir</span> /b %SystemRoot%\servicing\Packages\*Hyper-V*.mum >hyper-v.txt<br><br><span class="hljs-keyword">for</span> /f %%i <span class="hljs-keyword">in</span> (<span class="hljs-string">'findstr /i . hyper-v.txt 2^>nul'</span>) <span class="hljs-keyword">do</span> dism /online /norestart /add-package:<span class="hljs-string">"%SystemRoot%\servicing\Packages\%%i"</span><br><br>del hyper-v.txt<br><br>Dism /online /enable-feature /featurename:Microsoft-Hyper-V-All /LimitAccess /ALL<br></code></pre></td></tr></table></figure>
+
+<ol start="2">
+<li>把文件保存为Hyper-V.cmd</li>
+<li>右键该文件,选择 <strong>以管理员身份运行</strong></li>
+</ol>
+<p>根据提示完成安装。</p>
+<blockquote>
+<p>摘自知乎 <a href="https://zhuanlan.zhihu.com/p/51939654">没人不认识我</a> 的回答</p>
+</blockquote>
+<h3 id="安装虚拟机"><a href="#安装虚拟机" class="headerlink" title="安装虚拟机"></a>安装虚拟机</h3><p>打开 Hyper-V ,选择 <strong>新建 - 虚拟机</strong> ;</p>
+<p>根据向导提示设置虚拟机,选择 <strong>第一代虚拟机</strong> ;</p>
+<p>内存分配我选了 2G (共16G);CPU 分配我选了 4线程 (共12线程)【需要完成配置后在 <strong>设置</strong> 中更改】;</p>
+<p>其余设置默认或自定;</p>
+<p>安装选项选择 <strong>从 CD/DVD-ROM 安装操作系统</strong> ,选择刚刚下载好的 Windows LTSC ISO镜像;</p>
+<p>完成配置后,启动虚拟机,安装 Windows LTSC 到硬盘。</p>
+<h3 id="配置环境"><a href="#配置环境" class="headerlink" title="配置环境"></a>配置环境</h3><p>装好系统后要干什么不用我说了吧。</p>
+<p>把垃圾们倒进去就好啦。</p>
+<p>实测空载消耗 CPU 算力在 0%-3% 浮动;内存占用 2.2G,实际使用 1.2G 。</p>
+
+
+
+ 2020-08-07T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/6b40e5ad.html
+ 提升音乐体验-本地音乐标签/歌词匹配与回放增益
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>推荐两款能让听歌体验变得更好的软件 —— Music Tag / Foobar2000 。</p>
+<p>附带使用教程。</p>
+<span id="more"></span>
+
+<h2 id="音乐标签-Music-Tag"><a href="#音乐标签-Music-Tag" class="headerlink" title="音乐标签 Music Tag"></a>音乐标签 Music Tag</h2><h3 id="官方网站-下载地址"><a href="#官方网站-下载地址" class="headerlink" title="官方网站/下载地址"></a>官方网站/下载地址</h3><p><a href="https://www.cnblogs.com/vinlxc/p/11347744.html">https://www.cnblogs.com/vinlxc/p/11347744.html</a></p>
+<h3 id="软件特点"><a href="#软件特点" class="headerlink" title="软件特点"></a>软件特点</h3><p>古人云,专辑封面是一首歌的灵魂。(我乱说的)</p>
+<p>Music Tag 是一款可以自动匹配本地音乐的标签与歌词的软件。</p>
+<p>一键从多家音乐网站拉取元数据/封面图/歌词,不能再爽了。</p>
+<h3 id="使用教程-建议"><a href="#使用教程-建议" class="headerlink" title="使用教程/建议"></a>使用教程/建议</h3><p>导入一批歌曲后,选择 自动匹配标签 :(如下图)</p>
+<p><img src="/images/2020-05-05/music-tag-1.png"></p>
+<p>然后按下图配置,在原有元数据下添加更多信息:</p>
+<p><img src="/images/2020-05-05/music-tag-2.png"></p>
+<p>在第一轮匹配后,建议再进行第二轮封面图片匹配,并覆盖原图片,配置如下图:</p>
+<p><img src="/images/2020-05-05/music-tag-3.png"></p>
+<p>接着,就需要你耐心地查看每首歌的元数据(善用方向键),检查是否有匹配错误的歌曲,并在 标签源-组合标签 处手动搜索,选择正确的元数据,如下图所示:</p>
+<p><img src="/images/2020-05-05/music-tag-4.png"></p>
+<p>建议检查一下歌曲的歌词,特别是较小众的歌曲。Music Tag 的歌词搜索错误率较高。</p>
+<p>如下图所示,选择并查看歌词,若有误可以手动搜索:</p>
+<p><img src="/images/2020-05-05/music-tag-5.png"></p>
+<p><img src="/images/2020-05-05/music-tag-6.png"></p>
+<p>最后选择导出 LRC 歌词:</p>
+<p><img src="/images/2020-05-05/music-tag-7.png"></p>
+<p>所有歌曲的 元数据-封面图-歌词 就此已匹配完毕。</p>
+<p>最终效果如下:</p>
+<p><img src="/images/2020-05-05/foobar2000-m.jpg"></p>
+<h2 id="Foobar2000"><a href="#Foobar2000" class="headerlink" title="Foobar2000"></a>Foobar2000</h2><h3 id="官方网站"><a href="#官方网站" class="headerlink" title="官方网站"></a>官方网站</h3><p><a href="http://www.foobar2000.org/">http://www.foobar2000.org/</a></p>
+<h3 id="回放增益介绍"><a href="#回放增益介绍" class="headerlink" title="回放增益介绍"></a>回放增益介绍</h3><p><a href="https://zh.wikipedia.org/wiki/%E5%9B%9E%E6%94%BE%E5%A2%9E%E7%9B%8A">维基百科-回放增益</a></p>
+<p>回放增益可以使音量大小各不相同的音乐向统一标准靠齐。</p>
+<p>将所有音乐扫描并打上回放增益 tag 后,再也不用担心下一首歌震破耳膜了。</p>
+<h3 id="使用教程"><a href="#使用教程" class="headerlink" title="使用教程"></a>使用教程</h3><p>导入并全选歌曲,右键,选择 ReplayGain :</p>
+<p><img src="/images/2020-05-05/foobar2000-1.png"></p>
+<p>下列三种扫描方式均可。个人喜欢将全部歌曲的音量统一,故选择第一种:</p>
+<p><img src="/images/2020-05-05/foobar2000-2.png"></p>
+<p>待扫描结束后,点击 Update File Tags ,将回放增益数据写入文件 Tag :</p>
+<p><img src="/images/2020-05-05/foobar2000-3.png"></p>
+<p>回放增益扫描完毕。</p>
+<h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>我是一周不往曲库里添加新曲就受不了的类型。</p>
+<p>这几天往曲库里添加曲子的时候查阅了这些能提升音乐体验的方法,希望能帮到你。</p>
+
+
+
+ 2020-05-05T05:40:06.000Z
+
+
diff --git "a/tag/\350\275\257\344\273\266/feed.json" "b/tag/\350\275\257\344\273\266/feed.json"
new file mode 100644
index 00000000..1daccb01
--- /dev/null
+++ "b/tag/\350\275\257\344\273\266/feed.json"
@@ -0,0 +1,30 @@
+{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "カレーうどん屋 • All posts by \"软件\" tag",
+ "description": "カレーうどん屋.",
+ "home_page_url": "https://blog.udon.eu.org",
+ "items": [
+ {
+ "id": "https://blog.udon.eu.org/archives/a455b52c.html",
+ "url": "https://blog.udon.eu.org/archives/a455b52c.html",
+ "title": "逃离国产软件 - 虚拟机计划",
+ "date_published": "2020-08-07T04:30:00.000Z",
+ "content_html": "使用 Hyper-V 运行 Windows LTSC 虚拟机,以隔离流氓的国产软件们。
\n为何大费周章? 我试过网络上的不少方法来隔离鹅厂的软件 —— 刚开源的 Sandboxie 也好,利用 Windows ACL 机制通过低权账户加以限制也好 —— 都因为权限问题失败了。最后决定用虚拟环境直接隔离这些软件。
\n \n\n为什么是 Hyper-V 和 LTSC? 我用过 VMWare,觉得还是 Windows 原生的 Hyper-V 启动最快。还不用钱。
\nWindows LTSC 是企业定制版,官方精简了系统,性能开销更少。
\n事前准备 拥有 16G 以上内存及 NVME 高速硬盘的用户可以放心采用该方案,虚拟机运行时不会影响其他软件的流畅运行。
\n下载 MSDN 上的 Windows LTSC: 侧边栏选择 操作系统 ;选择 Windows 10 Enterprise LTSC 2019 。
\n安装 Hyper-V: 对于 Windows 专业版 用户,在 控制面板 - 程序与功能 - 启动或关闭Windows功能 中找到 Hyper-V 并打开。
\n对于 其他版本 Windows 的用户,则稍微有些麻烦:
\n\n在记事本中输入如下代码 \n \n1 2 3 4 5 6 7 8 9 pushd "%~dp0" dir /b %SystemRoot%\\servicing\\Packages\\*Hyper-V*.mum >hyper-v.txtfor /f %%i in ('findstr /i . hyper-v.txt 2^>nul' ) do dism /online /norestart /add-package:"%SystemRoot%\\servicing\\Packages\\%%i" del hyper-v.txt Dism /online /enable-feature /featurename:Microsoft-Hyper-V-All /LimitAccess /ALL
\n\n\n把文件保存为Hyper-V.cmd \n右键该文件,选择 以管理员身份运行 \n \n根据提示完成安装。
\n\n摘自知乎 没人不认识我 的回答
\n \n安装虚拟机 打开 Hyper-V ,选择 新建 - 虚拟机 ;
\n根据向导提示设置虚拟机,选择 第一代虚拟机 ;
\n内存分配我选了 2G (共16G);CPU 分配我选了 4线程 (共12线程)【需要完成配置后在 设置 中更改】;
\n其余设置默认或自定;
\n安装选项选择 从 CD/DVD-ROM 安装操作系统 ,选择刚刚下载好的 Windows LTSC ISO镜像;
\n完成配置后,启动虚拟机,安装 Windows LTSC 到硬盘。
\n配置环境 装好系统后要干什么不用我说了吧。
\n把垃圾们倒进去就好啦。
\n实测空载消耗 CPU 算力在 0%-3% 浮动;内存占用 2.2G,实际使用 1.2G 。
\n",
+ "tags": [
+ "教程",
+ "软件"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/6b40e5ad.html",
+ "url": "https://blog.udon.eu.org/archives/6b40e5ad.html",
+ "title": "提升音乐体验-本地音乐标签/歌词匹配与回放增益",
+ "date_published": "2020-05-05T05:40:06.000Z",
+ "content_html": "推荐两款能让听歌体验变得更好的软件 —— Music Tag / Foobar2000 。
\n附带使用教程。
\n \n\n音乐标签 Music Tag 官方网站/下载地址 https://www.cnblogs.com/vinlxc/p/11347744.html
\n软件特点 古人云,专辑封面是一首歌的灵魂。(我乱说的)
\nMusic Tag 是一款可以自动匹配本地音乐的标签与歌词的软件。
\n一键从多家音乐网站拉取元数据/封面图/歌词,不能再爽了。
\n使用教程/建议 导入一批歌曲后,选择 自动匹配标签 :(如下图)
\n
\n然后按下图配置,在原有元数据下添加更多信息:
\n
\n在第一轮匹配后,建议再进行第二轮封面图片匹配,并覆盖原图片,配置如下图:
\n
\n接着,就需要你耐心地查看每首歌的元数据(善用方向键),检查是否有匹配错误的歌曲,并在 标签源-组合标签 处手动搜索,选择正确的元数据,如下图所示:
\n
\n建议检查一下歌曲的歌词,特别是较小众的歌曲。Music Tag 的歌词搜索错误率较高。
\n如下图所示,选择并查看歌词,若有误可以手动搜索:
\n
\n
\n最后选择导出 LRC 歌词:
\n
\n所有歌曲的 元数据-封面图-歌词 就此已匹配完毕。
\n最终效果如下:
\n
\nFoobar2000 官方网站 http://www.foobar2000.org/
\n回放增益介绍 维基百科-回放增益
\n回放增益可以使音量大小各不相同的音乐向统一标准靠齐。
\n将所有音乐扫描并打上回放增益 tag 后,再也不用担心下一首歌震破耳膜了。
\n使用教程 导入并全选歌曲,右键,选择 ReplayGain :
\n
\n下列三种扫描方式均可。个人喜欢将全部歌曲的音量统一,故选择第一种:
\n
\n待扫描结束后,点击 Update File Tags ,将回放增益数据写入文件 Tag :
\n
\n回放增益扫描完毕。
\n小结 我是一周不往曲库里添加新曲就受不了的类型。
\n这几天往曲库里添加曲子的时候查阅了这些能提升音乐体验的方法,希望能帮到你。
\n",
+ "tags": [
+ "教程",
+ "软件"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/tag/\350\275\257\344\273\266/rss.xml" "b/tag/\350\275\257\344\273\266/rss.xml"
new file mode 100644
index 00000000..c296cef2
--- /dev/null
+++ "b/tag/\350\275\257\344\273\266/rss.xml"
@@ -0,0 +1,108 @@
+
+
+
+ カレーうどん屋 • Posts by "软件" tag
+ https://blog.udon.eu.org
+ カレーうどん屋.
+ zh-CN
+ Fri, 07 Aug 2020 12:30:00 +0800
+ Fri, 07 Aug 2020 12:30:00 +0800
+ 教程
+ 随笔
+ 软件
+ 小鸡测评
+ 3D 打印
+ 嵌入式开发
+ GitHub Actions
+ Hexo
+ DIY
+ -
+
https://blog.udon.eu.org/archives/a455b52c.html
+ 逃离国产软件 - 虚拟机计划
+ https://blog.udon.eu.org/archives/a455b52c.html
+ 教程
+ 软件
+ Fri, 07 Aug 2020 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/6b40e5ad.html
+ 提升音乐体验-本地音乐标签/歌词匹配与回放增益
+ https://blog.udon.eu.org/archives/6b40e5ad.html
+ 教程
+ 软件
+ Tue, 05 May 2020 13:40:06 +0800
+
+
+
+
diff --git "a/tag/\351\232\217\347\254\224/atom.xml" "b/tag/\351\232\217\347\254\224/atom.xml"
new file mode 100644
index 00000000..8939343c
--- /dev/null
+++ "b/tag/\351\232\217\347\254\224/atom.xml"
@@ -0,0 +1,1087 @@
+
+
+ https://blog.udon.eu.org
+ カレーうどん屋 • Posts by "随笔" tag
+
+ 2024-11-12T15:30:00.000Z
+
+
+
+
+
+
+
+
+
+
+ https://blog.udon.eu.org/archives/2c54052d.html
+ 我再也不能不假思索
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>正值下班时间,你拖着疲惫的身体漫步街头。地铁站的入口熙熙攘攘,那是回家的方向。</p>
+<p>正要走下地铁站路口的阶梯时,你发现了一个小孩子正朝着上行方向的电动扶梯走去。那是个连路都还走不清楚的孩子。</p>
+<p>他的父母呢?你正张望着,希望能找到那个冒失的,或许正在专心看着手机而放松了警惕的父亲或母亲。</p>
+<p>孩子朝着电梯摇摇晃晃地走了几步。想从上行的电梯往下走,这对于成年人都不是一件容易的事情,何况是小孩子。他大概刚踩上运行着的扶梯就会摔倒吧,真危险。你这么想着,稍微放慢了一些脚步,继续观察着孩子,也在观察着是否有人会出手拦住他。</p>
+<p>你看到一个上了些年纪,头发有些发白的老人。他双手都拎着东西,是柴米油盐,重量拉低了他的双肩。他会帮忙吗?</p>
+<p>你想起了上周,也是在下班的时间点,与即将乘坐的是同一个班次的地铁上,你幸运地找到了车厢里的最后一个座位。椅背上有没有贴着“爱心座位”的标志,你已经不记得了。你和今天一般疲惫,正在漫无目的地滑着手机屏幕,打发着无聊的通勤时间。视线越过手机屏幕,你瞥见一个老人,头发有些发白,双手都拎着重物,站在你的面前。</p>
+<p>要让出座位吗?你一边滑动手机,一边思考着。“刚下班,大家都很辛苦”,你想起了在食堂偶然间听到的同事间的对话,“虽然我怀孕了,很需要一个座位。不过看到大家都这么累,我也不好说些什么了”。是啊,刚刚完成了一天工作的你,正需要充分的休息,哪怕是在地铁里坐下十几分钟对你也是无比的重要。想到这里,你底下了头,继续看起了手机……</p>
+<p>也许是双手的重物让老人无暇顾及他人,迫使他加快步伐向下走去,他并没有发现那个正朝着上行扶梯走去的小孩子。此时孩子又向着扶梯迈出了几步。再往前几步,就踏上扶梯出口的金属盖板了。接下来会发生的事情难以想象。</p>
+<p>会有人帮忙的吧?你继续张望着,同时心中也在盘算着——拦住了这个孩子,就意味着得守着他,直到那个粗心的父母匆匆赶来。谁知道要等多久,绝对是赶不上马上要到站的地铁了。你尝试把目光瞥向其他地方,似乎不再去看那个孩子,他就会安然无恙。但你做不到。</p>
+<p>有一个阿姨快步走上前来,是要来帮忙的吗?你有一些即视感,仿佛之前在哪里见过这位大约四十岁出头,看上去有些憔悴的阿姨。</p>
+<p>那是前几个月的一个早晨,你刚入职公司不久。向来遵守规矩的你今天也准时来到地铁站,乘上与昨天、前天、大前天都是同一时刻到站的地铁,这样就能准时到达公司,给大家留下一个好印象。地铁缓缓驶入站台,车门在悦耳的提示声中打开。此时,隔壁车门处却传来不和谐的声音。你循声望去,有人摔倒在列车与站台的间隙处,是一个看着四十岁左右的女性。是不小心绊倒了吗?还是没有吃早饭,有些低血糖了?不管怎样,她瘫坐在门口,久久没有起身。</p>
+<p>要去扶一下吗?你迟疑了一会儿,没有上车。地铁站里有那么多保安,他们应该会处理好吧。扶起来之后,肯定得陪着她,直到有工作人员来照看才能离开吧,那今天早上要迟到了。你思考片刻,还是踏上了地铁,但还是转过身来,透过车窗继续观察。你看到有人和地铁站的保安一同将这位女性搀扶至阶梯边,地铁的门得以关闭,你可以准时到达公司了。想到这里,你的心安定了一些……</p>
+<p>可惜的是,阿姨也没有注意到一个孩子正全然无知地朝着危险走去,就走下了楼梯。孩子摇摇晃晃地向前迈步,顷刻间,一只脚已经踏上了扶梯的盖板,再向前几步,就到了不断运转着的扶梯。</p>
+<p>这样下去那个孩子肯定会摔下去的,你稍稍将行走的方向朝扶梯偏移了些许,做好了冲刺、抓住孩子的准备。同时,你也祈祷着能有人抢先你一步出手,并收拾剩下的残局。</p>
+<p>一步、又一步,真的没有人肯帮忙一下吗?或许此刻真的只有我一个人注意到了这个孩子?你的视线游离,大步朝着扶梯走去,却被一个飞跃而过的身影叫停了步伐。</p>
+<p>“小朋友,等一下。你的爸爸妈妈呢?”是一个年轻人,背着皮包,打扮时髦。他拦住了孩子,一只大手挡住了孩子前往扶梯的路,将孩子推离了危险。</p>
+<p>看到这里,悬在空中的石头落地,你便也随着大流走下了阶梯。只不过,在那之后,在等候地铁时、坐在地铁中、或是淋浴、发呆之际,你都会回忆起过去的你,那个会毫不犹豫拾起地板上的垃圾、主动让出座位、扶起摔倒的人、给予他人帮助的你。你会不由自主地问自己:</p>
+<p>我从什么时候开始,再也不能不假思索地做一件事情?</p>
+
+
+ 2024-11-12T15:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/95479b1f.html
+ Soundcore C30i 耳机
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>我目前在使用的耳机是 Sony LinkBuds,就是那款中间带了个孔的奇怪耳机。</p>
+<p>它本是为了商务人士在办公场所使用而设计,收听耳机中声音的同时不会影响听取他人的谈话。它却被我这个耳道经常发炎的人看上了。</p>
+<p>我想,中间带了个孔,那耳道内外的空气就是完全流通的,那耳道内肯定不会闷了吧!为了验证这一点,我特地去 Sony 的线下店体验这款耳机。佩戴上 LinkBuds,确实完全没有耳道被封闭的感觉,我便在网上欣然下单了。</p>
+<p>然而,经过一段时间的佩戴,才发现我的想法过于天真:看似畅通无阻的气流,在直径不够大的圆环处有阻塞。内部产生的水蒸气无法排出,导致耳道仍然会有潮湿感。佩戴20分钟以上,摘下耳机放入耳机舱,一段时间后取出,会发现耳机舱内有冷凝水。</p>
+<p>Sony LinkBuds 仍然不能解决我耳朵发炎的困扰。我便又踏上了寻找新耳机的旅途。</p>
+<p>今天便是要介绍我找到的其中一款,有望成为最终解决方案的耳机 —— SoundCore C30i。</p>
+<p><img src="/images/2024-06-13/01.jpeg" alt="外包装"></p>
+<h2 id="设计"><a href="#设计" class="headerlink" title="设计"></a>设计</h2><p><img src="/images/2024-06-13/02.jpeg" alt="充电盒外观"></p>
+<p><img src="/images/2024-06-13/03.jpeg" alt="充电盒开盖"></p>
+<p>耳机充电盒的外观,以及开盖后的样子如上图所示,就不多介绍了。</p>
+<p>开放式耳机相对于入耳式、半入耳式耳机,设计上花样多了不少。我看到的所有款式中,大致可以分为两类:</p>
+<ul>
+<li>使用挂钩悬挂在耳朵之后,将扬声器覆盖于耳道口,通过空气传导声音进入耳道的;</li>
+<li>夹在耳垂上,耳机本身不覆盖或仅部分覆盖耳道口,通过侧向开口将声音导向耳道的。</li>
+</ul>
+<p>SoundCore C30i 属于后者。</p>
+<p><img src="/images/2024-06-13/04.jpeg" alt="耳机本体"></p>
+<p>我选择的是透明外壳的款式,耳机内部结构清晰可见。</p>
+<p>左边耳机的扬声器处有一个开口,这便是朝向耳道的声音出口。</p>
+<p>右耳朝上的是耳机背面,金色的圆片是触控传感器。</p>
+<h2 id="佩戴感"><a href="#佩戴感" class="headerlink" title="佩戴感"></a>佩戴感</h2><p>夹耳的耳机其实还可以再分类。一种是本体柔软、有弹力,有点像悬挂在耳朵上的,将扬声器更多地覆盖于耳道口,以提升传音效率的耳机。</p>
+<p>C30i 本体则不可以弯曲,采用虎口般的结构卡在耳垂上。在佩戴时需要找到耳垂比较薄的部分,将耳机从那里套入耳垂,再将耳机推向耳垂较厚的地方,就能牢固固定。看似还是有些松松垮垮,但因为耳机足够轻,不容易被甩掉。</p>
+<p>我的感觉是,佩戴上没有什么异物感,但难以做到所谓“戴久了会忘记它的存在”这么夸张。</p>
+<p>我尝试连续佩戴了两个多小时,期间耳朵没有任何被堵塞的感觉,且取下后耳朵没有丝毫的潮湿感。确实是完全的开放了!</p>
+<h2 id="音质"><a href="#音质" class="headerlink" title="音质"></a>音质</h2><p>我的评价是:出奇的好。</p>
+<p>因为是一种妥协,我对耳机的音质便不抱任何希望。实际听起来,虽然也有这非封闭式耳机缺乏低音等问题,其实音质相当不错。</p>
+<p><img src="/images/2024-06-13/05.jpeg" alt="EQ 方案"></p>
+<p>通过调整 EQ 配置,将高低频都拉高(经典两头高调法),效果更是好了许多!</p>
+<p>我也听了一段博客,听清说话人的语音也没有任何困难。</p>
+<h2 id="多设备连接"><a href="#多设备连接" class="headerlink" title="多设备连接"></a>多设备连接</h2><p>我原本买的是飞利浦的一款开放式耳机,到手之后才发现耳机没有双设备连接的功能。我平常有在手机和电脑之间切换使用的需求,少了这个功能实在不能接受。</p>
+<p>C30i 支持同时连接两台设备,可以在两台设备间无缝切换。</p>
+<h2 id="续航"><a href="#续航" class="headerlink" title="续航"></a>续航</h2><p>商品说明上标明了耳机单次续航是 10 个小时,这应该是相当厉害的了。加上充电盒,总共可以提供 30 个小时的续航。</p>
+<p>实际用下来,在 2 小时的使用后,耳机的电量没有明显的减少。续航应当是很强劲的。</p>
+<h2 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h2><p>目前我看到的一个缺点是,耳机缺少自动休眠功能。将充电盒打开,耳机便会连接所有设备,摘下后仍保持着连接。直到耳机放回充电盒,并盖上盖子后,耳机才会关机。</p>
+<p>且耳机缺少佩戴检测,摘下后不会停止音乐播放。似乎耳夹式耳机比较少有这个功能。</p>
+<p>这可能会抢占了一些设备的音频输出,不过不算是很大的缺点。</p>
+<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>SoundCore C30i 有效解决了我耳道容易发炎的痛点,音质不错,且有多设备连接的功能,满足了作为我主力使用的耳机的所有需求。</p>
+
+
+ 2024-06-13T06:40:00.000Z
+
+
+ https://blog.udon.eu.org/archives/adc5a61e.html
+ 日本之行-第二站-奈良
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>23年12月底,我踏上了前往日本的旅途。这是我第一次去日本,是我第一次出国旅行,且只身一人。</p>
+<p>本次旅行为14天,我将分多个章节完成所有的游记,记录下我旅行中的点点滴滴、各种感想。这是第二章节,奈良之旅。</p>
+<h2 id="近铁、巴士与鹿"><a href="#近铁、巴士与鹿" class="headerlink" title="近铁、巴士与鹿"></a>近铁、巴士与鹿</h2><p><img src="/images/2024-03-11/00.jpeg" alt="使用 Suica 乘坐近铁"></p>
+<p>奈良距离大阪仅有 30km,乘坐近铁奈良线,摇摇晃晃一个多小时便能到达奈良。</p>
+<p>这条线路有分区间准急、准急、急行、快速急行四个速度,每个速度停靠的站点数量不同,票价都相同的。如果想尽快到达奈良,乘坐快速急性是最好的选择。</p>
+<p>这天,我纯凭心情来到近铁难波站,坐上了最近一班开往奈良的近铁,似乎是急行。</p>
+<p>近铁的运行速度不是很快,叮叮当当地在楼房间穿行着。</p>
+<p>在近铁奈良站,奈良线的终点站下了车,距离旅馆所在的奈良公园还有一段距离。由于拖着行李,我选择搭乘公交车。</p>
+<p>公交车到站后会向站台一侧倾斜车身,方便乘客上下;上车的门并不固定,可以看车身的贴纸判断;司机会很耐心地等待所有人上车、落座之后才摆正车身、继续前进。</p>
+<p>有一些线路是分段计费的,就需要在上车的时候领取一张整理卷,或者先刷一下卡,在下车的时候将钱和整理券一起投入投币机,或者刷一下公交卡就可以了。</p>
+<p>在每一个座位的旁边都有一个按钮,如果下一站要下车,按一下就会提醒司机,并且在车头的显示屏上也会显示,车上的所有按钮都会亮起。在公交车到站停稳之前无需先起身,车子停稳后司机也会很耐心地等大家付钱、下车,还会一个一个道谢哦。</p>
+<p>相比于地铁,公交的到站时间显得没有那么准确,但不会差太多。在地面运行的公交会受到交通的影响,等候乘客上下车、付钱有时候也会用掉不少的时间,能做到按照时间表运行已经相当厉害了。</p>
+<p><img src="/images/2024-03-11/01.jpeg" alt="路边的鹿"></p>
+<p>虽然在车上就有看到,果然很多啊 —— 奈良的鹿。</p>
+<p>奈良的鹿是散养的,主要集中在奈良公园和春日大社。在奈良设有鹿苑,将受伤或者发情的鹿收养,待它们恢复正常再放回乡中散养。</p>
+<p>从车站走到旅馆,一路上都有鹿儿好奇地打量着来自异国他乡的我。</p>
+<p>每走一段距离,能看到提示观光客不要“挑逗”鹿儿的提示牌,有些鹿的性格比较差,会用头顶你,追着你不放的。好在大部分鹿都去掉了角,不至于顶伤人。</p>
+<p><img src="/images/2024-03-11/02.jpeg" alt="鹿园宾馆"></p>
+<p>今天的旅馆确实有些简陋 —— 私人活动空间仅有这么一小间,洗浴和方便都是公用的。不过旅店的氛围很好,有一个共用的客厅,客人们可以在这里吹暖气(冬天还是挺冷的)、喝饮料、聊天。</p>
+<p>旅店的工作人员非常热情,替我办理好入住手续后,立刻拿出一张奈良公园的地图,用简单的中文(很厉害哦!)告诉我这附近有哪些好玩的地方。奈良一天的行程就是参考着这张地图安排的。</p>
+<p>整理好行装,已是傍晚五点多。按照旅店工作人员以及 Google Maps 的说法,奈良公园里的大部分饮食店都要下班了!遂立刻出门,寻找晚饭的地点。</p>
+<p>因为今天是周五吗,明明还有一些旅客在此游玩的,旅店对面的旅游纪念品店已经关门了!沿街打量了几家店铺,也都在收拾着大堂,准备打烊了,完全不像是会接客的样子。大危机,要没晚饭吃了,得走个几公里处了奈良公园,到达奈良市区才有便利店。</p>
+<p>路过了一家同样是卖纪念品的商店,发现店内的空间还挺宽敞,也摆着一些桌椅,便问了一下老板还有没有饭吃。运气很好,这家店还没有打烊。</p>
+<p><img src="/images/2024-03-11/03.jpeg" alt="親子丼"></p>
+<p>可以选择的菜品并不是很多,大多数都是日本的家常菜。我选择了这道绝大部分和食餐馆都会有的家常菜 —— 親子丼。</p>
+<p>从厨房门帘的缝隙向里望去,可以隐约看见老板在烹饪着饭菜。明明用得都是同样的工具和食材,有技艺的人做出来的东西就能端上桌卖钱呢。</p>
+<p>蛋是我们所谓“半熟”的柔滑状态,鸡肉有一些小烧焦,主要的风味是自古以来人们就离不开的味道 —— 咸味。风味不能说突出,却很平和。它就是一碗很普通、很平常,此时此刻会出现在任意一张餐桌上的饭。</p>
+<p>饱饭后,我根据地图的指引,前往东大寺的二月台,观赏日落之景。</p>
+<p><img src="/images/2024-03-11/04.jpeg" alt="二月台的日落1"></p>
+<p><img src="/images/2024-03-11/05.jpeg" alt="二月台的日落2"></p>
+<p><img src="/images/2024-03-11/06.jpeg" alt="二月台的日落3"></p>
+<p><img src="/images/2024-03-11/07.jpeg" alt="二月台的日落4"></p>
+<p>只用手机相机无法捕捉暗光下的美景。</p>
+<p>夕阳柔和而昏暗地照着二月台,四周一片静寂,只能听见洗手亭的潺潺水声。在日落后还停留在奈良公园的游客寥寥,大家都保持着绝对的安静,共享这片难得的静谧。</p>
+<p>在台上驻足数分钟,我轻轻踏着脚步走下楼梯,沿着寺院的小路漫步着,离开了东大寺。</p>
+<p>此时天已完全黑了,不论是寺院还是神社都已关门,我就这么在奈良公园里漫步着。时不时碰见一群鹿,便从口袋中掏出一块鹿仙贝,掰给鹿儿吃。</p>
+<p><img src="/images/2024-03-11/08.jpeg" alt="果汁"></p>
+<p>会旅店前在路边的售货机买了一瓶果汁饮料,是不二家的。日本的饮料会标注果汁含量,我觉得很神奇。</p>
+<p><img src="/images/2024-03-11/09.jpeg" alt="春日大社"></p>
+<p>第二天,去参观了十分出名的春日大社。不过不恰巧,主殿正在维修,将赛钱箱设置在了原处,只能眺望主殿。</p>
+<p>在巫女那儿买了点纪念商品,是两只鹿型的小玩偶。一只是木质的,一只是陶制的,鹿儿的嘴中叼着签。忘记留下照片了。</p>
+<p><img src="/images/2024-03-11/10.jpeg" alt="林间小道"></p>
+<p>离开主殿,走上铺着碎石的小道。</p>
+<p><img src="/images/2024-03-11/11.jpeg" alt="路旁的神社"></p>
+<p>每走几步,便会出现一间迷你神社,感觉十分奇妙。在这片林海中不知道供奉着多少的神明。</p>
+<p>有些人也许是提前来做新年参拜,途中遇到的神社都会十分虔诚地参拜。</p>
+<p>在林间漫步了将近一个小时,吸饱了新鲜的空气,我振奋精神,回旅馆取了行李,准备离开奈良。</p>
+<p><img src="/images/2024-03-11/12.jpeg" alt="若草山"></p>
+<p>拖着行李箱漫步在奈良公园里,我又一次路过了若草山。</p>
+<p>若草山也不高,在满是丘陵的福建甚至都算不上山,只是个小土坡。山上的草长得格外整齐,是一座越看越顺眼的山呢。</p>
+<p>若草山每年12月初开始封山,直到第二年三月举行烧山仪式后才重新开放。没能上去走走真是可惜。</p>
+<p><img src="/images/2024-03-11/13.jpeg" alt="柿叶寿司 set"></p>
+<p>在前往近铁奈良站离开奈良前,我路过了一家在网上有着不少讨论的柿叶寿司店,便决定在这里解决午饭。</p>
+<p>柿叶寿司其实就是用柿子叶将寿司包起来烹饪。据说柿子叶不仅可以杀菌消毒,还能给寿司提升风味。</p>
+<p>想着,既然是奈良的特色菜,肯定要好好品尝一下。我点了一份 2500円 的大套餐,奢侈一下~</p>
+<p>左下角的五枚寿司,上方两枚便是柿叶寿司了。下方三角形的寿司也是用不能吃的叶子包着,但好像不是柿子叶;右侧是用腌制过的紫苏叶包的寿司,紫苏叶是可以吃的,风味很独特,我很喜欢;左侧是茶味的卷寿司,也是第一次吃。</p>
+<p>柿叶寿司用的是腌制过的鱼,确实带有叶子的香味,很有特色!</p>
+<p>除此之外,套餐内还有精致的小菜、一份天妇罗拼盘和一碗素面。吃的我好饱~</p>
+<p><img src="/images/2024-03-11/14.jpeg" alt="线上买的特急卷"></p>
+<p>乘坐上前往京都的特急电车时还发生了一些小插曲。</p>
+<p>坐上车后我才意识到自己坐的是特急的电车,进站时我只刷了基本票,没有购买特急卷。</p>
+<p>好在列车上有通过扫描二维码购买特急卷的渠道,也支持借记卡付款。在等待列车发车时,我赶紧买了特急卷,带着行李移动到了指定座位,正式踏上前往京都的旅途。</p>
+
+
+ 2024-03-10T16:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/a7050149.html
+ 23 年对我影响最大的硬件与软件
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>原文投稿在自留地频道的新年活动里。</p>
+<p>对于我来说,23 年给我带来影响最大的莫过于 Meta Quest 3 与 VRChat 了。</p>
+<h2 id="Quest-3"><a href="#Quest-3" class="headerlink" title="Quest 3"></a>Quest 3</h2><p><img src="/images/2024-03-01/01.png" alt="订单截图"></p>
+<p>Quest 3 在 23 年 6 月面世,于 9 月 28 日开放预购。我早在数年前就有了尝试 VR 的念头,在经过一天的调查研究后确定了 Quest 3 不会踩坑,便在日亚下了单,直邮回国。日亚上标价为 78k 円,日本商品出口不收税,再加上直邮的运费和中国的关税,最后到手的价格和标价差不多。(顺便一提,现在日亚也可以以相同的价格直邮国内)</p>
+<p>外观我就不展示了,网上的图很多。到手第一件事,便是尝试它对于上一代产品相比提升最大的地方——显示与穿透(Pass Through)。</p>
+<h3 id="显示效果"><a href="#显示效果" class="headerlink" title="显示效果"></a>显示效果</h3><p>显示效果上,Quest 3 采用了 Pancake 棱镜,甜点位置(Sweet Spot)相对于上一代更大,也就是说仅需简单调整头戴的位置,便能找到画面最为清晰的位置,我的使用体验上感觉也是如此,每次佩戴上头显很快就能调整好画面。</p>
+<h3 id="穿透-Pass-Through"><a href="#穿透-Pass-Through" class="headerlink" title="穿透 (Pass Through)"></a>穿透 (Pass Through)</h3><p><img src="/images/2024-03-01/02.jpeg" alt="穿透效果"></p>
+<p>穿透方面,由于我没有用过 Quest 2、Quest Pro 等拥有穿透功能的 VR 头显,无法进行比较。图片中看起来比较糊,是因为我用了 Quest 自带的截图功能,分辨率很低。实际观感上,在光线充足的环境中可以毫无压力地辨认各种物体,但细小的字是看不清楚的。戴着 Quest 使用手机并不是很舒服,因为物体距离头显过近会因摄像头视角的问题产生扭曲,看字会很吃力。将手机拿远,又会因为显示清晰度不够而看不清字。不过听说 Quest 2 的穿透是黑白且十分模糊,Quest Pro 也好不了很多,Quest 3 在这个方面应是有十足的进步。(似乎离 avp 还有很大的差距)</p>
+<hr>
+<p>相比于头显中不带有处理芯片的 VR 眼镜,Quest 可以使用无线串流软件将 PCVR 的画面投影至头显中显示,再也不用担心玩游戏时绊倒数据线了。</p>
+<p>总而言之,Quest 3 是一副功能完善,各方面实力均不弱的“六边形战士”,但毕竟定位是廉价头显,也就缺少了 Quest Pro 的自定位手柄、眼部追踪,Valve Index 的基站定位、手部追踪,更没有 Apple Vision Pro 的高 ppi 显示屏、低延迟的穿透与精准的手势识别。但综合来看,Quest 3 的性价比高,适合刚刚步入虚拟世界,想要体验各式各样 VR 游戏的人购买。</p>
+<h2 id="VRChat"><a href="#VRChat" class="headerlink" title="VRChat"></a>VRChat</h2><p>谈到 VR 游戏,有人会想到 Half-Life:Alyx,有人会说起(已经凉掉的) Beat Saber。根据 SteamDB 的数据,此刻在线数量最多的 VR 端游戏还得是 VRChat(主要为 PC 端的战争雷霆不算)。</p>
+<p>在我看来,VRChat 里有大概有四类人。</p>
+<h3 id="第一类人:虚拟世界的旅行家"><a href="#第一类人:虚拟世界的旅行家" class="headerlink" title="第一类人:虚拟世界的旅行家"></a>第一类人:虚拟世界的旅行家</h3><p><img src="/images/2024-03-01/03.jpg" alt="风景大好"></p>
+<p>有些人想“逃避现实”,来到虚拟世界欣赏美景、转换心情。在 VRChat 里有各种各样风格的地图:有些风景大好、音乐舒缓,适合在快节奏的生活之余找到一个宁静之地放松紧绷的精神;有的地图灯红酒绿,如果在夜晚你还是激情满满,不妨来这里听听虚拟 DJ 的表演,大家一起蹦迪、饮酒;甚至还有环境昏暗、一片寂静,十分适合睡眠休息的卧室地图,虽然深处异地,也能在虚拟世界里与好友共眠,在清晨醒来的那刻发现自己的眼前并不是早已习惯了的天花板,耳边是仍在熟睡的友人的呼吸声,将会是一种全新的体验。</p>
+<h3 id="第二类人:虚拟世界的摄影师"><a href="#第二类人:虚拟世界的摄影师" class="headerlink" title="第二类人:虚拟世界的摄影师"></a>第二类人:虚拟世界的摄影师</h3><p><img src="/images/2024-03-01/04.jpg" alt="人也很美"></p>
+<p>也有些人想在虚拟世界里做一个摄影师。美景的照片中怎么能少了美人,如果现实中找不到美人,那就自己来扮吧(心死)。VRChat 中使用的人物模型可以由玩家自行上传,如果你恰巧会使用 Unity 与 Blender,便可以为自己量身定制一个人物模型;如果不会也没有关系,在 Booth 上有许多预制好的人物模型与衣服,只要按照教程将其组合,便可打造出你心目中的理想形象(不管男女)。为了拍出满意的照片,你会主动去学习各种各样的新技能:为了调整照片的后期效果,我就学习了 Lightroom 的使用。</p>
+<h3 id="第三类人:人,不过是在虚拟世界里"><a href="#第三类人:人,不过是在虚拟世界里" class="headerlink" title="第三类人:人,不过是在虚拟世界里"></a>第三类人:人,不过是在虚拟世界里</h3><p><img src="/images/2024-03-01/05.png" alt="和朋友聊天"></p>
+<p>这或许才是 VRChat 的核心内容 —— 当然是和朋友聊天啦。在 VRChat 中有一些专门用于聊天、交友的地图,例如以中文为主的“中文吧”;也有一些比较热门的地图会聚集起各个语言的人群一起聊天,例如“Japan Shrine[spring]”,一张风景优美的日本神社地图;更有各种各样以个人或小团体主办的咖啡厅、运动吧、跳舞房、游戏房等着你来加入。也许你和我一样有些小“社恐”,在现实生活中不大善于和陌生人交际,不妨试试在 VRChat 中,与素未谋面但已经熟络的朋友,或是围坐在篝火旁闲聊,或是在电闪雷鸣、乌云滚滚的夜空中乘坐飞机探险,相信这对你一定会给你带来从所未有的新鲜感。</p>
+<h3 id="第四类人:OOOO"><a href="#第四类人:OOOO" class="headerlink" title="第四类人:OOOO"></a>第四类人:OOOO</h3><p>还有一类是搞色色的,就不多说了。</p>
+<p>以上只是从我的眼中看到的 VRChat。一千个人的心中有一千个哈姆雷特,你的 VRChat 又将会是什么样的呢?</p>
+<hr>
+<p>自从 23 年 10 月 3 日加入 VRChat 以来,我学会了怎么修改模型、如何使用全身追踪让自己在游戏中灵活运动;尝试在跳舞房里学习舞蹈,在 KTV 房里与朋友唱歌,与来自全球的人使用中、英、日闲聊;在 VRChat 中结交了新的朋友,在 X 上发布了拍摄的照片。VRChat 确确实实已经融入了我的生活,仿佛在另一个世界塑造了另一个不同的我。</p>
+
+
+ 2024-03-02T12:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/a534d51a.html
+ 旅行与军粮
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="军粮的特点"><a href="#军粮的特点" class="headerlink" title="军粮的特点"></a>军粮的特点</h2><h3 id="1-便携"><a href="#1-便携" class="headerlink" title="1. 便携"></a>1. 便携</h3><p>一般来说,军粮都会被较为紧凑地包装,以便于大批量运输与随身携带。如确认将有一餐饭需要在外解决,可以在出发时往包里塞上一份军粮。</p>
+<h3 id="2-分量足"><a href="#2-分量足" class="headerlink" title="2. 分量足"></a>2. 分量足</h3><p>军粮的营养构成一般都是按照一个运动量中上的成年男性一餐所需要的卡路里来设计的。因此,即使不能完全饱腹,其所含的营养足够填补在外游玩半天空空的肚子了。</p>
+<h3 id="3-便于烹饪"><a href="#3-便于烹饪" class="headerlink" title="3. 便于烹饪"></a>3. 便于烹饪</h3><p>相比于需要加入开水的方便食品、甚至是冻干食品,军粮一般自带加热设备,仅需冷水,甚至不需要水都可以加热食物。若是去荒郊野岭的地方露营,除非有携带便携式汽炉,想获取到热水是比较难的,这时候有自加热的军粮会很方便。</p>
+<h2 id="俄罗斯单兵口粮普餐"><a href="#俄罗斯单兵口粮普餐" class="headerlink" title="俄罗斯单兵口粮普餐"></a>俄罗斯单兵口粮普餐</h2><p><img src="/images/2024-02-12/01.jpg" alt="外包装"></p>
+<p>这次买到的6号餐谱,包含五包干粮、牛肉丸、牛肉荞麦饭、牛肉炖豆角、牛肝酱、午餐肉、蔬菜丁罐头以及饮料、果酱、零食等散件,还有一套由三个燃料药丸、一个铝制支架和几支防风火柴组成的明火加热套装。</p>
+<p><img src="/images/2024-02-12/02.jpg" alt="塞满的盒子"></p>
+<p>旅行的第一天是登山,午餐便携带了部分的食物作为午餐。</p>
+<p>有些小雨,便找了一处有雨遮的地方开始准备午饭。</p>
+<p><img src="/images/2024-02-12/03.jpg" alt="加热装置"></p>
+<p>将铝板弯曲成炉子的形状,在炉子的中央放上燃料药丸,再用防风火柴点燃,便是一个功能完备的加热装置。</p>
+<p>小贴士,我试着用火柴从上方点燃药丸,尝试了两次都失败了。将火柴放在炉子里,然后将药丸放在火柴上,便能很轻易地将其点燃。</p>
+<p><img src="/images/2024-02-12/04.jpg" alt="正在加热的蔬菜丁罐头"></p>
+<p>点燃固体燃料后,将盛有食物的铝制罐子放在火上直接加热即可。如果有带铝或者钛制的杯子,也可以直接放在火上烧水,泡些饮料喝。</p>
+<p><img src="/images/2024-02-12/05.jpg" alt="干粮、芝士与肝酱"></p>
+<p>在等待罐头加热时,我先掏出了干粮与蘸酱。除了照片里的芝士与肝酱,还有一包苹果果酱。</p>
+<p>芝士的味道与常见的芝士片如出一辙,在我看来稍微淡了些,因此也比较适合直接吃。</p>
+<p>牛肝酱则是特别的腥,单独吃我有些吃不来,和着芝士与果酱吃味道倒是还不错。</p>
+<p>拿出一片干粮(其实就是苏打饼干),涂上果酱,挖一勺芝士,再放上一点点牛肝酱,一口塞进嘴里,味道还算不错。</p>
+<p>饼干有些干硬,嚼起来有些费劲儿,需要配水。</p>
+<p>最后,完吃完了一份饼干、一盒芝士和一包果酱,牛肝酱剩下了一大半。</p>
+<p><img src="/images/2024-02-12/06.jpg" alt="牛肉丸"></p>
+<p>接下来是牛肉丸。看到红色的外表就能猜到是罗宋汤风味的。</p>
+<p>应该是罐藏的缘故吧,牛肉已经泡得很软了,吸收了罗宋汤酸酸甜甜的风味,味道还算不错。就是牛肉味已经不是很浓,能吃得出来是肉,但没有什么特殊的肉的风味。</p>
+<p>顺便一提,用这种炉子加热,铝盒子受热举起不均匀,中间已经滚烫,但四周还是冰的,需要多多翻搅。</p>
+<p><img src="/images/2024-02-12/07.jpg" alt="蔬菜丁罐头"></p>
+<p>从外表看不出来里面有啥,红红的肯定也是罗宋汤的味道。里头的蔬菜主要是胡萝卜,也有青椒、青豆等蔬菜。但这一碗的味道就不好了,青椒的味道和西红柿(或者是甜菜)的酸甜并不搭。</p>
+<p>把饼干蘸着牛肉丸的汤汁吃,味道也不错。涂涂果酱、涂涂芝士,饼干便吃完了一包。</p>
+<p>又吃了点麻麻那边的自热口粮,便已饱腹,我的食量确实不大啊。</p>
+<p><img src="/images/2024-02-12/08.jpg" alt="蔬菜丁罐头上的うさこ"></p>
+<p>这一餐其实只吃了整套餐的 1/3 左右,剩下的量再吃两次正餐不成问题。</p>
+<p>回到家后,又拿出了些零食品尝了一下。</p>
+<p><img src="/images/2024-02-12/09.jpg" alt="巧克力棒"></p>
+<p>来自俄罗斯的巧克力棒,偏甜,一股代可可脂的廉价感,属于不大好吃的巧克力。</p>
+<p><img src="/images/2024-02-12/10.jpg" alt="速溶咖啡、奶与糖"></p>
+<p>右上角是速溶咖啡,右下角是奶粉,左边的一大包是糖。糖的量很大,明显不止是一次饮料的量,也可以放在茶里喝。</p>
+<p>咖啡很苦,一股烧焦味,不好喝。奶粉大概也是植脂末吧,没什么奶味。</p>
+<p>剩下的食品我分成了两餐解决。</p>
+<p><img src="/images/2024-02-12/12.jpg" alt="第一餐"></p>
+<p>第一餐的搭配是:牛肉荞麦饭、肥肉罐头和干粮(饼干)。</p>
+<p><img src="/images/2024-02-12/13.jpg" alt="第一餐的内容"></p>
+<p>也许我加热的还不够,但考虑到在野地里使用便携式炉子加热的能力,士兵们能加热到中间完全热乎,周围有些凉是平均水平了。荞麦饭很硬,风味也不是很好,除了咸味和一点牛肉味,尝不出别的滋味了。加了一些套餐内的黑胡椒粉,才改善了一些风味。</p>
+<p>不过这一大碗饭确实很能填饱肚子,适合放在午餐食用。</p>
+<p>肥肉罐头里自然是盐腌风味的很肥的肉啦。味道我还挺喜欢的,肥肥的肉很好吃,十分下饭。</p>
+<p><img src="/images/2024-02-12/14.jpg" alt="第二餐"></p>
+<p>第二餐的搭配是:牛肉煮豆子、午餐肉、苹果泥、酱牛肉(来自国产 MRE)和干粮(饼干)。</p>
+<p><img src="/images/2024-02-12/15.jpg" alt="第二餐的内容"></p>
+<p>牛肉煮豆子用的会是什么豆呢,豌豆吗?啊,原来是黄豆。</p>
+<p>并没有延续牛肉丸、蔬菜罐头的罗宋汤风味,只是单纯的咸味,不过味道我还挺喜欢的。</p>
+<p>牛肉其实不多,就一大块带筋、软烂的牛肉,剩下都是碎块。除了黄豆外还有一些胡萝卜。</p>
+<p>午餐肉很小一罐,但确实是肉泥,淀粉含量并不多,味道也不错。</p>
+<p>苹果泥是我整个套餐中最喜欢的一样食物了。口感绵密,酸甜度适中,汁水十足。配合干粮一起吃,很大地改善了干粮过干、过硬的缺点。而且好大一罐,吃得很满足。使得这一餐中我干掉了两袋干粮。</p>
+<h2 id="国内的军粮-——-以北戴河的自热口粮为例"><a href="#国内的军粮-——-以北戴河的自热口粮为例" class="headerlink" title="国内的军粮 —— 以北戴河的自热口粮为例"></a>国内的军粮 —— 以北戴河的自热口粮为例</h2><p>09式单兵自热口粮我吃过几份,13式这种两餐包装在一起的也吃过一次。</p>
+<p>这次选购的是北戴河生产的自热口粮。虽然不是军品,但北戴河前身是军工厂,产品会比较接近军品吧。包装风格与09式也很相像。</p>
+<p><img src="/images/2024-02-12/11.jpg" alt="酒店里的自热口粮"></p>
+<p>那天感冒的发烧刚退,人还比较虚弱。住的度假酒店位置比较偏,不想跑太远去吃晚饭,便取了车上的自热口粮回房间吃。</p>
+<p>我吃过太多次了,便没有拍照片,下面就口述一下感受。</p>
+<p>相比于09式单兵自热口粮,北戴河出品的民用口粮内容少了很多 —— 没有了耐贮蛋糕、糖水黄桃/菠萝、调味辣椒酱和固体饮料,只有一份主食,一份配菜(酱牛肉和午餐肉二选一)和一份小菜(榨菜)。</p>
+<p>09式的餐谱相当多样,有 12 个餐谱,有炒面也有炒饭,也有一些主食是素食的,满足部分人群的需求。</p>
+<p>北戴河的仅有三种餐谱,且都是荤食的炒饭。</p>
+<p>主食包外套着加热袋,用注水袋取对应量的水(生水、脏水皆可)倒入加热包即可完成主食的加热。</p>
+<p>味道上,我认为北戴河做得是不如当年的09式的。饭总体都偏咸,但肉给的十分足。</p>
+<p>但相对于俄罗斯的军粮,不知道是口味上有着主场的优势,还是俄罗斯人烹饪技术真的不佳,还是中国的军粮更胜一筹。</p>
+
+
+ 2024-02-12T14:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/7777583b.html
+ 被我整坏的路由器和服务器
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>为了搭建 PalWorld 的私服,我又一次踏上了折腾之旅,并成功将路由器和家里的服务器都搞崩了。</p>
+<p>好在最后两台设备都恢复如初。我就想来做一下这次“事故”的复盘。</p>
+<h2 id="我的设备"><a href="#我的设备" class="headerlink" title="我的设备"></a>我的设备</h2><p>本次操作的有两台设备,一台是 x86 软路由,运行 Openwrt;一台是配置比较奇怪的 x86 服务器,运行 PVE。</p>
+<h2 id="Openwrt-的网络隔离配置"><a href="#Openwrt-的网络隔离配置" class="headerlink" title="Openwrt 的网络隔离配置"></a>Openwrt 的网络隔离配置</h2><p>说在前头,我对 Openwrt 的操作真的是一窍不通。网络相关的知识兴许懂一些,但一到实操环境就纰漏百出了。</p>
+<p>由于 PalWorld 服务端启动后便会在游戏的服务器列表公开自己的 IP,且无法关闭这个功能,有比较大的安全隐患。在配置好 PalWorld 服务端所运行的虚拟机后,我决定将这台虚拟机的网络与局域网内其他设备隔离。这也是所有折腾的起源。</p>
+<h3 id="配置-VLAN-导致的失联"><a href="#配置-VLAN-导致的失联" class="headerlink" title="配置 VLAN 导致的失联"></a>配置 VLAN 导致的失联</h3><p>我能想到的第一个方案便是,给这台虚拟机分配一个与当前局域网不同的网段,并将两个网段隔离。</p>
+<p>VLAN 我还是知道的,便着手开始创建新的 VLAN,并保存…连不上路由器了。</p>
+<p>急了啊,创建了 VLAN 就代表开启网桥的 VLAN 过滤。目前这个 VLAN 没有分配给任何一个接口,自然是啥都连不上了。好在 Openwrt 有自动回滚的功能,90 秒若设备连不上路由器,便会将刚刚的更改回滚…但不是每次都能成功。</p>
+<p>有反复折腾了几次:创建 VLAN、创建接口、创建防火墙的 Zone,分配来分配去,中途也搞砸了几次,配置也都回滚了,直到最后一次尝试,luci 弹出了令人感到安心的“正在回滚配置”的提示,然后就…卡住了。</p>
+<p>考虑到 Openwrt 使用的是 squashfs,怀疑是设备上的设置确实回滚了,但内存中的配置没能正确地回去,我将设备断电重启。果不其然,配置回到了应用前的样子。</p>
+<p>在尝试配置 VLAN 数次后,我放弃了。即使给虚拟机分配了新网段,也设置好了防火墙规则,虚拟机依旧可以访问到内网网段的设备。</p>
+<p>目前我还没有闲心思慢慢研究 Openwrt 的种种配置,便打算换一个方式实现。</p>
+<h2 id="配置-PVE-防火墙"><a href="#配置-PVE-防火墙" class="headerlink" title="配置 PVE 防火墙"></a>配置 PVE 防火墙</h2><p>第二个方案便是,在 PVE 的防火墙中禁止虚拟机连接内部网段的其他设备了。</p>
+<h3 id="启动-Datacenter-防火墙但没有添加允许规则导致的失联"><a href="#启动-Datacenter-防火墙但没有添加允许规则导致的失联" class="headerlink" title="启动 Datacenter 防火墙但没有添加允许规则导致的失联"></a>启动 Datacenter 防火墙但没有添加允许规则导致的失联</h3><p>我启用了虚拟机的防火墙,发现配置并没有生效。简单查询后发现,需要将 Datacenter 的防火墙启用,VM 防火墙才有效果。我便看也没看地就开启了 Datacenter 防火墙,发现管理后台页面无法刷新了。此时我才看到屏幕上 Datacenter 防火墙的默认配置 —— IN: DROP.</p>
+<p>这下好了,外部连接统统被阻断,也就无谈通过控制面板将防火墙再关闭。</p>
+<p>这台服务器是无头的,安装有一张 P100 显卡,但没有显示输出。所以在不动硬件的情况下,我没法通过显示器访问终端的。</p>
+<p>通过检索,我了解到了好几种奇技淫巧来关闭 PVE 的防火墙。</p>
+<h3 id="挂载并修改-crontab"><a href="#挂载并修改-crontab" class="headerlink" title="挂载并修改 crontab"></a>挂载并修改 crontab</h3><p>正好手上有一个硬盘盒,我就将系统盘取下,通过硬盘盒连接到了软路由,尝试修改系统盘里的文件。</p>
+<p>使用 <code>fdisk</code> 查看这块系统盘的分区情况,但没有看到熟悉的 <code>ext4</code> 字样。取而代之的是 <code>Linux LVM</code>。LVM 相当于是 Linux 对磁盘进行了再一次的分区,因此挂载就不能是简单的 <code>mount</code>,得用 <code>lvm2</code> 工具。</p>
+<p>再敲了几个命令后,我成功将 PVE 分区的 <code>root</code> 文件夹挂载,并在 crontab 中添加了关闭 PVE 防火墙相关的命令。</p>
+<p><code>umount</code>,取下系统盘并装回服务器,开机…没有任何反应,依旧打不开管理面板。是教程给的方法有误吗?</p>
+<p>遂又取下盘,挂载到软路由上,却提示该分区忙…我是相当忌惮挂载并修改分区的,生怕损坏了分区,服务器的数据可就全丢了。不敢再继续操作,我得另想出路。</p>
+<h3 id="连接显示器,但还是个瞎子"><a href="#连接显示器,但还是个瞎子" class="headerlink" title="连接显示器,但还是个瞎子"></a>连接显示器,但还是个瞎子</h3><p>现在能做的就是在服务器上通过 PVE 的 rescue terminal,来修改防火墙的配置了。</p>
+<p>我关闭了服务器,取下 P100,换上了 R7 240 这张十年老兵,插上了便携显示器…</p>
+<blockquote>
+<p>error: No suitable video mode found. Booting in blind mode.</p>
+</blockquote>
+<p>你这不是输出字了吗,怎么就进瞎子模式了???</p>
+<p>经过查询,我了解到系统正在寻找到显示模式,正是古早电脑终端使用的 <code>80-Column</code> 这样的显示模式。</p>
+<p>至于什么是 Blind Mode,我尝试在 GRUB 和 Linux 源码中搜索,都没有结果;为什么 PVE 系统找不到我的 R7 240,可能是缺少驱动吧,现在也无处知晓。</p>
+<p>按照网上的教程尝试在 GRUB 引导系统启动时添加显示模式的支持,并没有效果;当我尝试打印支持的显示模式时,发现根本不存在正常的显示模式,故只能放弃直接启动 PVE 的 rescue terminal.</p>
+<h3 id="还是得靠救援盘"><a href="#还是得靠救援盘" class="headerlink" title="还是得靠救援盘"></a>还是得靠救援盘</h3><p>给服务器插上救援盘,我先是打开了基于 Windows 的 PE 系统,发现显卡是有输出的,但分辨率非常低,且只有黑白画面。看来这张老显卡与这块寨板的相性真的不大好。</p>
+<p>在确认 Windows PE 系统下什么都做不了,操作还及其不便,我便退出了 PE 系统,打开了 Ubuntu LiveCD。</p>
+<p>这回,显卡倒是可以正常运行。以 <code>80-Column</code> 模式输出文字还是可以轻松办到的。有了命令行,操作也简单了不少。</p>
+<p>同为 Linux,操作 LVM 就简单了许多。使用 <code>lvm2</code> 挂载 PVE 的系统分区,并用 <code>chroot</code> 将用户空间切换至 PVE 系统,直接用 <code>systemctl disable pve-firewall</code> 把防火墙关了。</p>
+<p>再次进入 PVE,这下开机防火墙就不会启动,赶紧进 Datacenter 防火墙设置里还原误操作的配置。</p>
+<h2 id="事后的反思"><a href="#事后的反思" class="headerlink" title="事后的反思"></a>事后的反思</h2><h3 id="VLAN-配置的失误"><a href="#VLAN-配置的失误" class="headerlink" title="VLAN 配置的失误"></a>VLAN 配置的失误</h3><p>我对 VLAN 配置没有经验,便想着参考网上其他人的配置方法来做。但我查到的都是创建访客 Wi-Fi 这类的配置教程。与我的网络环境的差异在于,访客 Wi-Fi 用的 Interface 与 LAN 不一样,而我需要在 LAN 下配置两个 VLAN,配置方法就有些不同,不能直接搬配置。</p>
+<p>我应当要找的是较为通用的,同一个 Interface 下 VLAN 的配置教程(可能单臂路由配置和这个就有些像),再迁移到 Openwrt 上进行配置。</p>
+<p>由于软路由只有两个网口,也没法像有四五个网口的路由器一样留下一个作为”不死“的管理口,以降低把路由器配置挂掉的风险。</p>
+<h3 id="PVE-原本可以修得更快"><a href="#PVE-原本可以修得更快" class="headerlink" title="PVE 原本可以修得更快"></a>PVE 原本可以修得更快</h3><p>原本在将 PVE 系统盘挂载到软路由时,便可以用 <code>systemd</code> 停掉 <code>pve-firewall</code> 但当时我看到 PVE 论坛里有人说在 crontab 里加上 PVE 关闭防火墙的指令便能访问控制面板了,也有人附和说可以用。但我实际操作后发现并没有效果。</p>
+<p>而再次尝试挂载 LVM 时,提示分区忙让我不敢继续操作。虽然在服务器上用 LiveCD 直接挂载并没有出现分区忙的情况。</p>
+
+
+ 2024-02-12T10:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/aca48136.html
+ 日本之行-第一站-大阪
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>23年12月底,我踏上了前往日本的旅途。这是我第一次去日本,是我第一次出国旅行,且只身一人。</p>
+<p>本次旅行为14天,我将分多个章节完成所有的游记,记录下我旅行中的点点滴滴、各种感想。这是第一章节,出境与大阪之旅。</p>
+<h2 id="第一次出境"><a href="#第一次出境" class="headerlink" title="第一次出境"></a>第一次出境</h2><p>我从上海浦东机场出境。</p>
+<p>航班预计上午 9:40 起飞,根据网上的经验教训,国际航班建议提早三个小时到达机场办理值机手续。我早早地起了床,搭上了第一班磁浮列车,在六点五十分左右到达了机场。</p>
+<p>拖着行李箱直奔值机柜台,发现队伍已经很长。排了一个多小时的队才轮到我。</p>
+<p>我提前在网上进行了值机选座,现场只需要将行李箱托运,打印一下登机牌。工作人员会确认护照和日本 eVisa(电子签证)。</p>
+<p>我提前将电子产品和需要独立安检的物品取出,安检也十分顺利。</p>
+<p>不过在过海关的时候,工作人员看到我护照崭新,又是独自一人,不免有些担心,向我索取了酒店预订记录和行程单。酒店预订记录只要出示订购软件的订单界面即可;我没有做行程单,就将记事本里的旅行计划给工作人员看,第一天在哪里啊,过两天又跑去哪里玩…得知我全程的酒店都已订好,行程也安排清楚,便允许我出境了。</p>
+<p>在候机厅的长椅上坐下时,距离飞机的预定起飞时间只有半个小时了。看来国际航班确实需要早点来机场办手续(虽然最后延误了两个小时)。</p>
+<p>这是我第二次坐飞机。上一次是我还在读小学的时候,不明不白地上了飞机、又下了飞机,除了耳朵有些不舒服。没有其他的感想。</p>
+<p>这次,我特地选择了靠窗的位置,仔细观察着飞机的起降与飞行时的动作。看到了起飞和降落时襟翼的展开,看到了遇到乱流时机翼“夸张”的摆动,感叹着这种看似“简易”的机器是如何将一机人送上天。</p>
+<p><img src="/images/2024-01-21/01.jpeg" alt="日本上空"></p>
+<p>飞机餐的话…上一次坐飞机是国内航线,没有在饭点所以没有供餐,这是我第一次体验飞机餐。</p>
+<p><img src="/images/2024-01-21/02.jpeg" alt="鱼肉米饭套餐"></p>
+<p>没有选择的余地,大家统一都是鱼肉米饭套餐。和我能想象到的飞机餐没有太多区别 —— 一般咸味的鱼肉,煮得软烂的米饭(也可能是再加热的缘故)与味道平平的配菜。坐的毕竟是经济舱,不能期待太高~</p>
+<p><img src="/images/2024-01-21/03.jpeg" alt="鱼肉米饭的特写"></p>
+<p>经过两个半小时的飞行,飞机平稳降落在了关西国际机场。飞机缓慢驶向空桥的途中,我更换了日本的流量卡,给手表切换了时区。</p>
+<p>入境相对出境更加简单。出发前我就在网页中预先填写好了入境信息表与海关申报表,在对应的窗口或机器扫码即可完成申报。在入境窗口只需出示一下 eVisa,录入一下人脸和指纹就可以了。</p>
+<p>工作人员会用英语引导你操作,录入机器上也有中文提示。机场的指示牌都是四语的(日语、英文、中文和韩文),按照指示牌走就没有问题。</p>
+<h2 id="大阪之行"><a href="#大阪之行" class="headerlink" title="大阪之行"></a>大阪之行</h2><h3 id="Day-1-舒适的酒店与海游馆之旅"><a href="#Day-1-舒适的酒店与海游馆之旅" class="headerlink" title="Day 1 - 舒适的酒店与海游馆之旅"></a>Day 1 - 舒适的酒店与海游馆之旅</h3><h4 id="APA-酒店"><a href="#APA-酒店" class="headerlink" title="APA 酒店"></a>APA 酒店</h4><p>起飞延迟了两个小时,降落也迟了一个多小时。达到关西国际机场已是下午 3:30(这之后都是 UTC+9 时间)。我乘坐南海机场线前往难波站附近的 APA 酒店。</p>
+<p>插一嘴,在日本用 Google Maps 相当方便,交通工具的规划和信息展示都做得很棒。“通勤案内”这款官方推荐的交通软件我也有下载,不过用的最多的还是 Google Maps。</p>
+<p>等地铁、电车、新干线时,只要看 Google Maps 上的到站时间,到了哪辆车就上哪辆车。除非两个方向的车同时到站,否则是绝对不会坐错方向的。</p>
+<p><img src="/images/2024-01-21/04.jpeg" alt="APA酒店大楼"></p>
+<p>APA 酒店大楼的装修很有特色,在远处就能看到橙色的屋顶和 APA 的标志,而且在全国的风格都是统一的。</p>
+<p><img src="/images/2024-01-21/05.jpeg" alt="APA酒店房间"></p>
+<p>内部的装横令我十分惊喜:房间不是很大,但各种设施一应俱全,在床铺正对的墙上甚至有一台大屏电视。浴室毫不意外地配备了浴缸,还有定量放水系统,不用盯着浴缸有没有水漫金山了。寝具也相当的舒服,那两晚都睡得很好。</p>
+<p>对于一间一晚 300CNY 左右的旅馆,能有这样的体验我十分满意。</p>
+<h4 id="咖喱与牛排"><a href="#咖喱与牛排" class="headerlink" title="咖喱与牛排"></a>咖喱与牛排</h4><p>办好入住手续,放下行李,已是五点多。飞机餐的显然填不饱我的肚子,我早已饥肠辘辘,是时候出门觅食了。</p>
+<p>大阪海游馆是我计划中必去的一站,但它位于海港村,和其他景点在相反的方向,便安排在今晚游玩了。</p>
+<p>日本人很奇特的一个习惯出现了:有些店铺傍晚 6:00 到 6:30 就收摊了,最晚的会开到七点八点左右,再迟就只剩下营业到深夜的家庭餐厅与居酒屋。</p>
+<p>用“食べログ (tabelog)”这款软件在海游馆附近搜索评价比较高的店铺。</p>
+<p>在海游馆隔壁有一个小小的综合体,里面有一些店铺还有在营业。我选择了这家评价很高的牛排咖喱餐馆。</p>
+<p><img src="/images/2024-01-21/06.jpeg" alt="牛排咖喱"></p>
+<p>一份牛排咖喱饭、一碟酱菜、一碗味增汤。价格有些忘记了,在 1500円 左右。</p>
+<p>日本人很喜欢吃这种细长的青椒,不会辣,在天妇罗中也会出现。</p>
+<p>咖喱的甜口的,味道自然比百梦多咖喱块要好上数倍。牛肉很嫩,没有怎么调味,展现的是肉本身的鲜味。总之,相当的美味。</p>
+<p>对了,需要使用现金哦~ 商家没有准备 POS 机。</p>
+<h4 id="大阪海游馆"><a href="#大阪海游馆" class="headerlink" title="大阪海游馆"></a>大阪海游馆</h4><p>已经记不清上一次去水族馆是多少年前。听说大阪海游馆的设施很棒,场馆也很大,我就来体验一下。</p>
+<p>门票的价格是 2600 円,入场有分时段,我就在售票处购买了当前时段的票。</p>
+<p>海游馆很大,步行参观的总距离在 1km 左右,按照地理位置划分成了好几个区域。</p>
+<p><img src="/images/2024-01-21/07.jpeg" alt="趴在水箱顶的鳐鱼"></p>
+<p>进入场馆便是一个巨大的拱形水箱。是玻璃比较薄吗,还是用了什么技术,几乎看不见玻璃带来的重影,鱼仿佛真的在空中悬浮。</p>
+<p><img src="/images/2024-01-21/08.jpeg" alt="正在睡觉的海狮"></p>
+<p>一群正在睡觉的海狮。抬着头睡觉不会落枕么。</p>
+<p>![巨型水箱]/images/2024-01-21/(9.jpeg)</p>
+<p>在场馆的中央有一个巨大的水箱,有两头鲸鲨、几头锤头鲨和许多小鱼生活在其中。</p>
+<p><img src="/images/2024-01-21/10.jpeg" alt="花园鳗~"></p>
+<p>还有好多可爱的花园鳗,黄色的尤其可爱~</p>
+<p>除此之外,还有来自各个大洲的鱼、南极的企鹅,在地下还有水母馆。</p>
+<p>总共逛了一个多小时,可以说大饱眼福了。</p>
+<p><img src="/images/2024-01-21/11.jpeg" alt="天保山摩天轮"></p>
+<p>在海游馆的隔壁是天保山摩天轮,夜晚被彩色的灯光照亮特别好看。</p>
+<p>此时已是 19:30,经历了一天奔波的我有些劳累,便回到酒店养精蓄然,计划第二天的行程。</p>
+<h3 id="Day-2-天守阁、天满宫、天筋桥与大阪烧"><a href="#Day-2-天守阁、天满宫、天筋桥与大阪烧" class="headerlink" title="Day 2 - 天守阁、天满宫、天筋桥与大阪烧"></a>Day 2 - 天守阁、天满宫、天筋桥与大阪烧</h3><p><img src="/images/2024-01-21/12.jpeg" alt="酒店的自助早餐"></p>
+<p>在日本的第一顿早餐,在酒店隔壁的参观享用了自助餐。</p>
+<p>炒蛋和上次去香港在澳洲牛奶公司吃到的有点像,并没有完全做熟,非常的软嫩~</p>
+<p>茶泡饭就很有日本的特色了。</p>
+<p>饭后,我就前往难波站附近的旅游中心,购买大阪周游卡。我选择的是二日卡,价格是 3600 円。在两天内,可以免费乘坐大阪地铁与巴士(相较于一日卡,不能乘坐私铁),以及免费参观好多景点。</p>
+<p>大阪地铁网络十分发达,基本覆盖了所有想去的地方,这两天我没有搭乘过私铁或者巴士,因此不用担心是否要选择一日卡。</p>
+<p>买到卡之后,第一站便是天守阁。</p>
+<p><img src="/images/2024-01-21/13.jpeg" alt="天守阁外围"></p>
+<p>从外边看,与只狼里的苇名城有几分相像。</p>
+<p><img src="/images/2024-01-21/14.jpeg" alt="天守阁下"></p>
+<p>天守阁内陈列着许多颇有历史的物件,只可惜我对日本的历史并不熟悉,也看不太懂书法家的笔墨,只是走马观花感受一下文化的氛围。</p>
+<p><img src="/images/2024-01-21/15.jpeg" alt="天守阁顶"></p>
+<p>最后在楼顶吹了吹风,便离开了天守阁。</p>
+<p><img src="/1/images/2024-01-21/6,.jpeg" alt="一轮彩虹"></p>
+<p>虽然有些冷,但天气真的很好,万里无云。下一站是天神筋桥,一个商业街。</p>
+<p><img src="/images/2024-01-21/17.jpeg" alt="天神筋桥商业街"></p>
+<p>日本有很多 OOばし(桥)这样的地名呢。也有很多像天神筋桥这样,上方覆盖着遮雨棚的商业街,天神筋桥是其中最长的一条,从一丁目延伸到七丁目,光是主干就有 2.6 km,更有密密麻麻的小巷。</p>
+<p>乘坐堺筋线,在天神筋桥三丁目下了车。我先是去拜访了天满宫。</p>
+<p><img src="/images/2024-01-21/18.jpeg" alt="大阪天满宫"></p>
+<p>似乎内部在翻修,将赛钱箱放到了外边供大家参拜。</p>
+<p>在这里,我给身边参加考研的人做了参拜,希望他们可以拿到好成绩。</p>
+<p>接着,我便从二丁目开始,一路边逛边思考着午饭的去处,寻找着吃饭的店铺。</p>
+<p>沿途,看到了大排长龙的可乐饼摊子,转了一圈再想回来买,发现已经卖完收摊了。有一家天妇罗的店门口也站着好多人,大排长龙。肚子好饿,肯定排不了这么长的队。</p>
+<p>边走边用 食べログ 搜索着。来大阪就得吃些有大阪特色的,那就是大阪烧了。</p>
+<p><img src="/images/2024-01-21/19.jpeg" alt="千草大阪烧"></p>
+<p>并不在主街,而是藏在小巷子里的千草大阪烧,似乎是 食べログ 23年的百大名店呢。</p>
+<p>我选择了以店铺名字所称的招牌菜 —— 千草大阪烧。</p>
+<p>核心是一大片厚切的猪肉,在上下两面都倒上面粉与卷心菜混合的泥,便开始煎烤。</p>
+<p>接待我的服务员会一些简单的英语,告诉我等着他们来翻面就好了。当地人也许会选择以自己的喜好来摊大阪烧,我作为门外汉只要静静欣赏就好。</p>
+<p>当两面都煎至金黄,便会涂上大阪烧酱、沙拉酱和黄芥末酱,撒上不知道是什么的籽,就可以享用。</p>
+<p>大阪烧整体的口感是软糯的,夹心的猪肉排很嫩,肉汁十足。</p>
+<p>唯一可惜的是,量实在有些少,填不饱我的肚子啊~</p>
+<p><img src="/images/2024-01-21/20.jpeg" alt="鲷鱼烧"></p>
+<p>饭后继续在天神筋桥闲逛,发现了一家鲷鱼烧店。我还以为鲷鱼烧是软软的,但实际外壳是偏脆的,甜甜的红豆馅十分美味。</p>
+<p>今天真的走了好久的路,对于第一次来到异国他乡的人可以说是有些得意忘形。从天神筋桥二丁目逛到六丁目,又折返了回来。中途还去 melonbooks 看了一圈,又跑到周游卡可以免门票的天王寺动物园里逛了逛…还没逛完,人就开始有些不舒服了。</p>
+<p>在动物园里稍微休息了一下,我还是决定吃完晚饭就回酒店休息。</p>
+<p><img src="/images/2024-01-21/21.jpeg" alt="大起水产回转寿司"></p>
+<p>一次比较“失败”的体验。不要误会,寿司还是很好吃的,鱼类十分新鲜,但我一不小心就在 iPad 上点了太多的寿司,都吃进肚子之后已经很饱了,完全没有在回转的转盘上取过寿司!这和普通的寿司店不就没差别了吗!</p>
+<p><img src="/images/2024-01-21/22.jpeg" alt="道顿堀"></p>
+<p>回转寿司店出门便是热闹的道顿堀,然而我不是喜好这一口的人。路过蟹道乐,周围停着好几台旅游大巴,店门口密密麻麻的全部都是在等待的游客,大约有数百人。真是疯狂呐。</p>
+<p>受不住喧嚣,我在附近的药店买了一支体温计和一盒退烧药,便回了酒店。</p>
+<p>躺床休息了一阵子再测体温已经正常,看来真的是疲劳导致的体温失调。之后的旅途安排就宽松一些吧。</p>
+<h3 id="Day-3-梅田蓝天大厦、天空美术馆与一兰拉面"><a href="#Day-3-梅田蓝天大厦、天空美术馆与一兰拉面" class="headerlink" title="Day 3 - 梅田蓝天大厦、天空美术馆与一兰拉面"></a>Day 3 - 梅田蓝天大厦、天空美术馆与一兰拉面</h3><p>睡了一个好觉,但人还是有些疲劳。前往异国他乡果然不能过于放肆,得做好身体的管理。今天就悠闲地度过吧!</p>
+<p><img src="/images/2024-01-21/23.jpeg" alt="通往观景台的扶梯"></p>
+<p>在酒店寄存行李后,搭乘地铁来到了梅田蓝天大厦。从三楼有快速电梯可以通往顶楼,然后乘坐扶梯来到屋顶的圆形观景台。</p>
+<p>对了,有周游卡门票免费哦~</p>
+<p><img src="/images/2024-01-21/24.jpeg" alt="梅田蓝天大厦楼顶观景台1"></p>
+<p><img src="/images/2024-01-21/25.jpeg" alt="梅田蓝天大厦楼顶观景台2"></p>
+<p>相比于其他观景塔,梅田蓝天大厦不算很高,但好在有开放式的观景台,视野特别棒。今天风有些大,在楼顶不是很站得住,欣赏了几分钟美景便回到了室内,点了一杯咖啡,坐在窗边静静欣赏着窗外景色。</p>
+<p>梅田蓝天大厦外侧是镜面玻璃,倒映着天空、与天空融为一体,因此称作蓝天大厦。尼尔:机械纪元的开发公司白金工作室的总部就在这栋大楼里。</p>
+<p><img src="/images/2024-01-21/26.jpeg" alt="天空美术馆1"></p>
+<p><img src="/images/2024-01-21/27.jpeg" alt="天空美术馆2"></p>
+<p>我还顺道参观了另一座楼的天空美术馆,虽然不怎么懂艺术,但画作依旧能感染我。</p>
+<p>参观美术馆后,已是正午。该去找吃的了~</p>
+<p><img src="/images/2024-01-21/28.jpeg" alt="一兰拉面的“考试”"></p>
+<p>百闻不如一见,我来品尝一兰了。</p>
+<p>每个人第一次来吃日式拉面都要面临一场“大考” —— 单应该怎么点。(背面有中文,我填完了才发现)</p>
+<p>除了在点餐机器上确认要吃东西,在排位时服务员会给你一张纸片,让你选择拉面的喜好。</p>
+<p>浓郁度我选择了加浓,确实有些过浓过咸了。吃着相当地过瘾,豚骨的香味充满嘴巴,真的很幸福。如果平常吃得比较清淡,正常的浓郁度可能就有些偏咸了,可以考虑减少一些浓郁度。</p>
+<p>油脂的丰富度我也加了一档,但感觉不是特别明显。可能是看到日本有一种表面铺满油渣的拉面,一兰的面在油脂的方面反而显得“寡淡”了吧。</p>
+<p>其余的选项便是看个人喜好。一兰的辣椒粉不会很辣,加 1/2 倍感受不出辣味,但可以提升香味。</p>
+<p><img src="/images/2024-01-21/29.jpeg" alt="一兰拉面"></p>
+<p>交卷后,一碗一兰拉面,四片叉烧和一颗盐味溏心蛋上桌啦。</p>
+<p>一兰号称在面出锅后15秒内就会传递到食客的面前,以体验拉面最新鲜的味道。我赶忙用手机拍了张照,便开始享用。</p>
+<p>拉面和我之前吃过的感觉都不大一样,特别有筋道,麦香味也很浓。在浓厚的汤汁里蘸一下就有了豚骨的鲜味,没几口就把拉面吃完了,便又加了半份面。</p>
+<p>上溏心蛋的时候同时给了个小碟子,如果蛋不好剥或者咸味不合适,可以让服务员帮忙换一个。</p>
+<p>一口拉面,配上一口汤汁。再咬一口叉烧、一口糖心蛋,至福啊。</p>
+<h3 id="前往奈良"><a href="#前往奈良" class="headerlink" title="前往奈良"></a>前往奈良</h3><p>饭后,回酒店取了行李,便是去难波站坐近铁奈良线,前往奈良了。</p>
+<h2 id="一点感想"><a href="#一点感想" class="headerlink" title="一点感想"></a>一点感想</h2><p>语言不是问题,打招呼、点菜这种比较简单的对话就用手势与塑料日语,更复杂的对话用英语就好了。碰到的酒店前台经理、便利店员工、车站管理员都是会英语的。再不济,就用翻译软件吧,能达意就行~</p>
+<p>至于习惯问题,按照当地人的做法来做就好了。在公共场合,例如楼梯应该走哪个方向,等地铁的时候应当怎么站,在地铁上有哪些地方不能使用手机,都有明确的标识和多语言的提示。多留意,照着做,就不会有事儿啦。</p>
+
+
+ 2024-01-21T15:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/2ef8bd61.html
+ 深圳-香港三日行
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="出行原因"><a href="#出行原因" class="headerlink" title="出行原因"></a>出行原因</h2><p>其实我很早就有去香港看看的念头,但有时间出游的机会并不多(机会很多,是我比较懒,更喜欢宅家),一直没去成。</p>
+<p>前一段时间,在朋友的帮助下我注册了英国 Wise 账户,拿到了两张漂亮的实体卡,但后期如何入金成了个问题。了解到可以通过香港账户低损耗入金 Wise,我便有了前往香港开户的念头。</p>
+<p>12 月底我将独自前往日本游玩,但这将是我第一次独自一人出去旅游。第一次单人旅行还是出国旅行,不禁让我有点担心。</p>
+<p>在这两个背景的驱使下,我迅速定下了这次深圳-香港的旅行,既可以前去开户,也可以作为出国旅行的预演,提前暴露一些问题。</p>
+<h2 id="流水账"><a href="#流水账" class="headerlink" title="流水账"></a>流水账</h2><h3 id="Day-1-Thu-深圳"><a href="#Day-1-Thu-深圳" class="headerlink" title="Day 1 - Thu - 深圳"></a>Day 1 - Thu - 深圳</h3><p>第一天中午到的深圳。我在前往酒店的半路上下了地铁,前往名为“臻品鲜粿·粿条世家”的店铺吃午餐。</p>
+<p><img src="/images/2023-12-19/01.jpg" alt="潮汕粿条"></p>
+<p>粿条本身没有很特殊,有点细河粉的感觉。不过八成熟的牛肉非常非常的嫩,味道很棒。</p>
+<p><img src="/images/2023-12-19/02.jpg" alt="炸豆腐"></p>
+<p>炸豆腐是赠送的,第一眼并太高的期望。但一口咬下,十分软绵的里芯呼之欲出,佐以略微有些咸的沾汁,味道很棒。</p>
+<p><img src="/images/2023-12-19/03.jpg" alt="像海蛎煎一样的东西"></p>
+<p>我还点了一份长得有点像海蛎煎的东西,记不得名字了。应该是用油炸的,比海蛎煎更加酥脆。</p>
+<p>美美地享用午饭后,我继续登上地铁,前往酒店。</p>
+<p>下榻酒店后,我前去参观深圳世界之窗景区…总之就是很后悔。</p>
+<p>比起人造景观,我还是更喜欢自然景观一些。世界之窗没有给我带来惊喜,只有满满的失望。今天是周四,许多游乐设施都没有开放,可以参观的仅有一个个微缩复原的建筑物。</p>
+<p>在世界之窗转了约两个小时,我悻悻离去,前往凤凰楼食茶点晚餐。</p>
+<p><img src="/images/2023-12-19/04.jpg" alt="香煎多春鱼"></p>
+<p>多春鱼的鱼籽很多,非常鲜美。</p>
+<p>一个人出来吃饭确实有些不便。有些菜品一次只能点这么一大盘,不过还是美美的享用了。</p>
+<p><img src="/images/2023-12-19/05.jpg" alt="鲜虾烧麦"></p>
+<p>鲜虾烧麦就是吃鲜,特别的鲜甜。</p>
+<p><img src="/images/2023-12-19/06.jpg" alt="牛杂炒肠粉?"></p>
+<p>菜单上的字看得不是很懂,大概写的是炒肠粉吧,没想到上了这么大一盆长得也不像肠粉的东西。</p>
+<p>吃了一下,似乎确实是切成一节一节的肠粉。味道不错,但量好大最后没吃完。</p>
+<p><img src="/images/2023-12-19/07.jpg" alt="华强电子世界"></p>
+<p>饭后,我顶着吃撑的肚子徒步前往华强北,这片传说之地。可惜的是,大部分店铺八点就关门了,简单逛了几栋电子市场后我便回酒店计划第二天的行程。</p>
+<h3 id="Day-2-Fri-HK-开户"><a href="#Day-2-Fri-HK-开户" class="headerlink" title="Day 2 - Fri - HK 开户"></a>Day 2 - Fri - HK 开户</h3><p>一大早我便乘上了前往福田口岸的地铁。</p>
+<p>7:30 左右的口岸没有什么人,大部分是赴港上学的学生,通关很快。</p>
+<p>我在落马洲站选择乘坐 B1 双层巴士前往元朗区,开始了开户之旅。</p>
+<p>沿途…说实话没有什么风光,元朗算是比较偏僻的地方,好风景还要等到第二天的香港岛之行。</p>
+<p>8:00 我到达了中行门口,发现只有前来上班的员工,还没有来排队的人。如果希望第一个办理业务的朋友可以选择这个点就来等候。时间还早,我就去吃了个早餐,回来发现已经有两个人在排队,便加入了队伍。</p>
+<p>9:00 准时进入了银行,取到了号。在我之前有提前预约的人,我排在了第 5 号。</p>
+<p>在工作人员的引导下使用手机填写了信息,然后等待柜员办理剩下的手续。共有三个柜台在同时办理开户,一个人需要 30 - 60 分钟的时间。</p>
+<p>11:00 左右,我完成了中行的开户。</p>
+<p>我现场就拿到了银行卡,便在 ATM 存入了 500 元人民币,并兑换成了港币。</p>
+<p>顺便在同一条路上的南洋商业银行和汇丰银行也完成了开户手续。这两家银行皆需要几个工作日审核信息,审核成功后会将卡片寄至通讯地址。</p>
+<p>穿行于各家银行网点的途中,我发现了一家在他人游记上看到过的店铺——胜利牛丸,便暗自定下了午饭的去处。</p>
+<p><img src="/images/2023-12-19/08.jpg" alt="牛丸、牛筋、牛腩三合一河粉"></p>
+<p>三个愿望,一次满足。</p>
+<p>面汤的颜色相当的“可怕”——仿佛是直接将卤水呈上来一般。但实际入口并没有很咸。牛肉们就不一样了,比较重口味的我也觉得有一点点咸。不过风味很棒,牛肉吃透了各种香辛料。</p>
+<p>河粉藏在了对得满满的牛丸和牛肉之下,不是我常吃的宽河粉,比较窄。</p>
+<p>虽然店名以牛丸冠名,我却没觉得牛丸有多么的美味,反倒是牛筋更胜一筹。</p>
+<p>饭后,我在坐满老者的公园里稍稍歇息,并且计划着下午的行程……</p>
+<p>此次香港之行除了开户,其实还有另一个目标——碧蓝档案与香港吃茶三千的联动~</p>
+<p>在出发前的几天得知了联动的消息,运气真的很好。</p>
+<p><img src="/images/2023-12-19/09.jpg" alt="电车"></p>
+<p>虽然有更优的路线,我还是选择了体验一下地面上的有轨电车。有轨电车有点像巴士,也是由司机驾驶,也会受到其他车辆与行人的干扰,但行驶路线是沿着铁轨固定的。</p>
+<p>随后乘坐地铁,前往金紫荆广场…的对面,星光大道。在这里陈列了许多明星的手印和画像。海港边上的风景大好。</p>
+<p>在此稍停,开户还没有结束。大概是因为元朗距离内地太近,许多虚拟银行都需要前往更靠近香港中心一些的地方才可以开户。我在一家星巴克坐下,开通了 ZA Bank 和 livi Bank 的账户。</p>
+<p>随后,便是前往海港城的吃茶三千点了一杯联动奶茶,从中心一步步移动回福田口岸——晚上要和群友面基。</p>
+<p>我们俩聊得非常尽兴,聊到了十一点才分手,回到了酒店,速速洗漱完毕,预定了第二天早上从福田站前往香港西九龙的火车,便沉沉睡去。</p>
+<h3 id="Day-3-Sta-HK-游玩"><a href="#Day-3-Sta-HK-游玩" class="headerlink" title="Day 3 - Sta - HK 游玩"></a>Day 3 - Sta - HK 游玩</h3><p>昨晚火车票订得比较迟,最迟的火车也是 7:45 的,更迟的都被人订光了。我又被迫起了个大早(这三天都是 6:30 之前起床的)坐地铁前往福田火车站,搭上前往香港西九龙站的火车。可能是比较早乘火车的人并不对,这一趟通关流程比较顺利,再迟一点可就不好说了……</p>
+<p>到达九龙,我空着肚子在街上漫步,寻找可以填饱肚子的早餐店。打开地图,“澳洲牛奶公司”几个字印入眼帘,这不是前几天看到的网红店吗?我记得这家店只收现金,便前往沿途的便利店,在 ATM 用中银香港的卡取了 100 港币现金。没有注意到我用的是汇丰的 ATM,与银联网络并不互通,被收取了 15 港币的手续费 QAQ。(众安银行的卡在全港 ATM 可以免手续费取现,经常到港玩的话可以办一张实体卡)</p>
+<p><img src="/images/2023-12-19/10.jpg" alt="九龙街头漫步"></p>
+<p>带着现金,我来到澳洲牛奶公司的门口,果不其然,排起了长队。好在我是一个人,这家店是强制拼桌的,顺利超过了大团的游客,来到了店内。</p>
+<p>给我的第一印象嘛…很拥挤,人挤着人,甚至是背贴着背。小小的一张圆桌围坐着四个人,各自享用着早餐。穿着白大褂的服务员在人群缝隙间穿梭着,高效地记录着到店客人的订单,飞速地将菜品传递到桌前。</p>
+<p>落座,菜单压在桌子的玻璃之下,写满了粤语,说实话我看不大懂。纠结了几十秒,发现菜单上还写着“早餐”、“下午茶”等套餐,我便对服务员说:“早餐套餐来一份吧。”</p>
+<p>话音未落,餐具和一杯茶便着陆在我的桌上。剩下的菜品也并我让我久等。</p>
+<p><img src="/images/2023-12-19/11.jpg" alt="火腿通心粉"></p>
+<p>首先呈上的是一碗火腿通心粉。味道比较清淡,喝不出黄色的汤底是什么熬制的,不过比较鲜。</p>
+<p><img src="/images/2023-12-19/12.jpg" alt="牛油吐司与炒双蛋"></p>
+<p>接着呈上的是一个盘子,两片吐司和炒鸡蛋各占半壁江山。牛油烘烤的吐司暖暖的,软软的;这份炒双蛋则是套餐的点睛之笔——调味微咸的鸡蛋并没有炒得很熟,在生与全熟之间掌握了平衡,做到了入口软绵,回味无穷,堪称是绝品。</p>
+<p>如果有路过,一定要来尝一下这份炒鸡蛋~</p>
+<p>一顿饱餐之后,我继续踏上旅途,前往太平山顶的观景台。</p>
+<p><img src="/images/2023-12-19/13.jpg" alt="山顶缆车"></p>
+<p>上山的路可以选择用脚走完,不过,还是要体验一下富有特色的山顶缆车~</p>
+<p>缆车的斜度估摸着有三四十度,是我见过最陡的。缆车是建在山坡边上的,方便沿路欣赏香港的风景。</p>
+<p><img src="/images/2023-12-19/14.jpg" alt="摩天台观景台"></p>
+<p>缆车的终点站是太平山的山顶,下车后直接来到了凌霄阁摩天台的二层。沿着盘旋向上的电动扶梯,海拔逐渐升高,走着走着,最终到达了海拔428米的摩天台观景台。</p>
+<p>在这里可以俯览香港的景色,听说夜景更美,不过我晚上并不住在香港,也就没有机会亲眼目睹,实在可惜。</p>
+<p>摩天台上的风很大,吹着十分舒服。待了大约四十分钟,我便沿着原路返回,坐着缆车回到了山下。</p>
+<p><img src="/images/2023-12-19/15.jpg" alt="漫步中环"></p>
+<p>早餐吃得有够饱,到了饭点我还不是很饿,便在街道与小巷间漫步,寻找着午饭的好地点。我想找家正宗点的茶餐厅。</p>
+<p>跟随地图,我来到了就近的广芳园…可等候的队伍早已排出店外,目测有三四十人在队伍中。因为下午就要坐动车回去了,我无心加入他们,便继续游荡着,寻找着。</p>
+<p><img src="/images/2023-12-19/16.jpg" alt="菠萝包与阿华田"></p>
+<p>附近的茶餐厅真的不多,跟随地图的指引,我来到了一家街边的小店。店内只收现金,而我身上只剩下不到五十港币,便选择了菠萝油和冰阿华田作为午餐,反正肚子不是特别饿。(冰的阿华田比热的还要贵上几块钱哦)</p>
+<p><img src="/images/2023-12-19/17.jpg" alt="City Walk"></p>
+<p>饭后,我一时兴起决定走路去西九龙火车站,而非乘坐地铁,便开始一趟 City Walk…差点把我 Walk 没了。</p>
+<p>街上好多地方都在施工,地图的数据并没有新到可以帮我绕开因为施工而禁止通行的路段…再加上出海关也需要一些时间,我差点以为要赶不上车。</p>
+<p>好在我还是在开始检票前就赶到了,算是有惊无险,顺利踏上了返乡的路,结束了此次旅程。</p>
+<h2 id="我都带了些什么"><a href="#我都带了些什么" class="headerlink" title="我都带了些什么"></a>我都带了些什么</h2><p>此次我轻装上阵,只带了一个双肩包。</p>
+<p>这次旅行我尝试了内衣、袜子和洗澡用具(浴巾与毛巾)都使用一次性的,省去了携带大量衣物且需要带回大量脏衣物的麻烦,也可以保证洗澡用具足够干净(对酒店不是很信任)。浴巾和毛巾都是压缩的,体积非常小。</p>
+<p>其余的,便是两晚更换的衣服和电子产品的充电器了。</p>
+<h2 id="开户的那些事"><a href="#开户的那些事" class="headerlink" title="开户的那些事"></a>开户的那些事</h2><p>我挺担心中银香港会开不下来,因为我还是学生也没有带地址证明。在大堂接待客户的小姐姐也询问了我有没有带上地址证明,没有的话只能碰碰运气,弄得我更慌了。</p>
+<p>不过真正坐进柜台和柜员大哥开始走流程,直到大堂经理过来刷卡完成审核、将卡递到我手中,都没有向我索取地址证明。不知道是因为得知我是学生,还是因为我身份证上写的是具体住址,反正过了这关,顺利拿到了卡。</p>
+<p>而汇丰之行则运气不佳,提交的资料被送至总行审核,当天拿不到账户也无法完成所有流程,只能等卡寄到居住地址,并且等下一次到港签字完成所有开户流程了。</p>
+<p>P.S. 在跨境转账到时候一定要填清楚收款人的名字,要与中银香港的账户名称相同。我填反了名字被收了一笔手续费 QAQ</p>
+<h2 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h2><p>这次旅行顺利得可怕(除了差点没赶上返程的火车),在深圳与香港复杂的地铁网中也没有坐错一趟列车,似乎没能暴露出什么问题。希望 12 月底的日本之旅也能一样顺利。</p>
+<p>一个人出来玩真的很自由,在出发前先查好可以去哪里玩、去哪里吃,到达之后就随心安排,不用迁就其他人,走累了随时可以停下休息,在咖啡店坐上一两个钟头也不会有人抱怨。</p>
+<p>我也许爱上了一个人旅行的感觉,之后也会继续尝试这种旅行的方法。</p>
+
+
+ 2023-12-19T14:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/42ec6146.html
+ My Second Attempt To ARM Servers
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>Last weekend, a full month without my Telegram account, I thought I had to do something. I believe I got banned because someone clicked the “spam” button on my messages, but I have nowhere to appeal.</p>
+<p>I lost the faith in centralization. This time, I made up my mind to dive into decentralization.</p>
+<p>First, I need a server.</p>
+<h2 id="Where-To-Purchase"><a href="#Where-To-Purchase" class="headerlink" title="Where To Purchase"></a>Where To Purchase</h2><p>I used to buy servers from distributors of the DC for lower prices, but it always ended up with disappointment. The servers are just not stable and I can not upgrade the hardware configuration on demand.</p>
+<p>So this time, I chose Hetzner for my server. Hetzner is a huge IDC in Germany, I must have to pay a fairly high price for the server, am I right?</p>
+<p>The following images show the price of Hetzner’s traditional CX series - with x86 CPU.</p>
+<p><img src="/images/2023-09-24/01.jpg" alt="Hetzner CX Series"></p>
+<p>A 2 CPU and 4GB RAM CX21 can fit my needs, it costs 5.35 EUR per month. By the way, the 40GB disk is far less than my requirement.</p>
+<p>The CX series are using Intel Xeon CPU. The GB6 benchmark score of an Intel core is around 850, which is significantly lower than AMD EPYC of around 1200.</p>
+<p>However, when I switch to the CAX series…</p>
+<p><img src="/images/2023-09-24/02.jpg" alt="Hetzner CAX Series"></p>
+<p>I can get the same number of CPU cores and RAM with only 3.79 EUR per month, 30% lower than the x86 one.</p>
+<p>The GB6 benchmark score of ARM CPU is around 1050, even higher than the Intel ones.</p>
+<p>The disk space is still 40GB, but I can purchase a 3.2EUR/month 1TB Storage Box to solve this problem.</p>
+<p>With a total of 6.99 EUR per month, I can get a server with 2 CPU cores, 4GB RAM, and 1TB disk. That is a great deal.</p>
+<h2 id="Wait-ARM"><a href="#Wait-ARM" class="headerlink" title="Wait, ARM?"></a>Wait, ARM?</h2><p>About a year ago, when Hetzner first released the ARM series, I was interested in it and evaluated its compatibility.</p>
+<p>I always use Docker containers to host my ~25 services. It turned out that more than half of the Docker images I was using were only compiled for the x86 CPU.</p>
+<p>That means you have to build the image by yourself if you want to run it on an ARM CPU. The time and effort were not worth it, so I gave it up.</p>
+<p>However, this time, I found all of the Docker images already supporting the ARM CPU. It is time to give it a try.</p>
+<h2 id="The-Experience"><a href="#The-Experience" class="headerlink" title="The Experience"></a>The Experience</h2><p>Here is my final hardware configuration:</p>
+<p>I chose the CAX11 server, which has 2 Ampere ARM CPU cores, 4GB RAM, and 40GB disk. I added 2GB swap space to store cached files and reduce the load of the RAM.</p>
+<p>I also purchased a 1TB Storage Box to store my data. Mounted to the server with NFS, just like a local disk.</p>
+<p>I am using the latest Debian 12 Bookworm and I can’t feel any difference between x86 and ARM. My daily use software from APT source is all compiled for ARM. The installation speed is as fast as x86.</p>
+<p>As to the Docker images, I am using Portainer to manage them. What I need to do is just click the “Recover” button on the settings page, Portianer will automatically recover the configuration from CloudFlare R2 storage and all the containers just work as before, with no need to change any settings.</p>
+<p>Today, when I am writing this article, My services are running for a week without any problem.</p>
+<p><img src="/images/2023-09-24/03.jpg" alt="Portainer"></p>
+<h2 id="Well-Still-Not-Perfect"><a href="#Well-Still-Not-Perfect" class="headerlink" title="Well, Still Not Perfect"></a>Well, Still Not Perfect</h2><p>The ARM server just works fine, but I want to share some problems I encountered when building the ARM Docker images.</p>
+<p>The official software repositories of Linux distributions can offer full support for ARM CPU, but the repositories of other sources such as PyPi can not.</p>
+<p><img src="/images/2023-09-24/04.jpg" alt="Build time difference"></p>
+<p>There are still some packages that don’t have prebuilt ARM wheels, and the building process can take a long time on the 2-core ARM machine.</p>
+<p>That is not a huge problem, but it makes the experience of ARM arch different from x86.</p>
+<p>If you are a developer, try to include ARM in your build pipeline next time, that will be a great help for the ARM community.</p>
+<h2 id="Final-Conclusion"><a href="#Final-Conclusion" class="headerlink" title="Final Conclusion"></a>Final Conclusion</h2><p>In my opinion, the ARM server is ready for daily use today. They offer a high quality-price ratio. I didn’t come across any compatibility problems.</p>
+<p>Next time when you want to purchase a server, you can consider the ARM ones.</p>
+
+
+ 2023-09-24T04:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/9b78ad2a.html
+ 寻梦穿越机 - 入门浅谈
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>暑假开始之前,我就朦朦胧胧有购入一台穿越机的想法。起因为何?我也不是很清楚。是看到了<a href="https://www.youtube.com/watch?v=b0JZxm7ajcs">酷炫的穿越机航拍视频</a>?还是童年时期的航模魂蠢蠢欲动,将要苏醒?</p>
+<p>兴趣,倘若不立刻抓住,很快就会被抛至脑后。为了不让自己后悔,那就立刻开始筹备吧!攥紧并不是特别充盈的钱包,我踏上了寻梦之路。</p>
+<h2 id="什么是穿越机"><a href="#什么是穿越机" class="headerlink" title="什么是穿越机"></a>什么是穿越机</h2><p>FPV (First-Person View),是指通过第一人称视角远程控制无人飞行设备 (UAV) 的控制方式,也是一项运动,也指代了一类设备。</p>
+<p>FPV 设备包含固定翼 (Fixed Wing) 与多轴 (Multi-rotor) 两种。穿越机多指多轴 FPV (常见为四轴)。</p>
+<p><img src="/images/2022-09-05/%E5%9B%BA%E5%AE%9A%E7%BF%BC%E4%B8%8E%E5%A4%9A%E8%BD%B4.jpg" alt="四轴 FPV (上) 与固定翼 FPV (下)"></p>
+<h2 id="一盆冷水"><a href="#一盆冷水" class="headerlink" title="一盆冷水"></a>一盆冷水</h2><p>在详细介绍穿越机构成之前,请允许我泼一盆冷水。</p>
+<p>穿越机与一些航拍无人机,例如大疆 (DJI) 的四轴航拍机与航拍 FPV 相比,颇有原始人见到现代人的感觉。</p>
+<p>大疆无人机上搭载了各种各样的传感器,各系列大都配备了二向 (前后)、四向 (前后左右) 或五向 (增加一个上方) 的视觉避障功能、红外定高悬停功能,以及低电量 GPS 自动返航功能。</p>
+<p>上述这些看似是无人机应当“标配”的功能,追求轻量化的穿越机时常一个也没有。一台最小安装的穿越机全机的传感器仅有集成于飞控的陀螺仪和地磁传感器 (电调的电流传感器一般不算在内),飞控仅知道当前机身的朝向、倾斜角度与加速度,无法感知周围的环境,没有实现避障、悬停与自动返航的能力。飞机的运动状态完全取决于飞手的操控,电池的剩余电量甚至需要飞手根据电芯电压来推断。</p>
+<p><img src="/images/2022-09-05/%E7%A9%BF%E8%B6%8A%E6%9C%BA%E4%BC%A0%E6%84%9F%E5%99%A8.jpg" alt="穿越机少得可怜的传感器"></p>
+<p>再者,为了能获得更大的速度与更高的机动性,穿越机飞行过程一般处于手动模式,对飞机四向的倾斜没有限制,飞行过程中出现危险操作的概率大,事故概率也大。而大疆无人机对飞行速度、角度有严格的限制,能有效地降低炸机的概率。</p>
+<p>因此,飞行穿越机的难度将是飞行大疆等航拍无人机的十倍、甚至百倍。请务必不要抱着随便玩一玩、随心飞的态度入坑穿越机,发生事故后轻则炸机提 (遥) 控回家,重则伤到自己或他人。对于喜欢日常随心飞行的玩家,大疆或类似牌子的无人机 (包括航拍机和穿越机) 更为合适。</p>
+<hr>
+<p>下文将详细介绍入门穿越机所需要的技术知识与穿越机的重要模块。在明白风险之后仍打算入坑穿越机,或对穿越机有兴趣的你,欢迎继续往下阅读。</p>
+<h2 id="技术储备"><a href="#技术储备" class="headerlink" title="技术储备"></a>技术储备</h2><p>上文提到了我童年时期的航模魂。我在小学就跟着老师学习无线电和航模知识,练习了焊接技巧和单旋翼的航模直升机的飞行技巧。曾参加某个航模比赛并拿到了铜牌的好成绩 (我好自豪)。</p>
+<p>因此,我对我的焊接技术和遥控操控能力比较有信心。而这两个能力恰好是入门穿越机不可或缺的。下面我列举一些入门穿越机需要掌握的技能。</p>
+<h3 id="锡焊"><a href="#锡焊" class="headerlink" title="锡焊"></a>锡焊</h3><p> 除了购买真·到手飞套餐 (机子已组装完成,接收机已与遥控器完成配对),其余情况大概率需要焊接一些导线。</p>
+<p> 大部分组装机套餐都不包含接收机,因为接收机与遥控器一一对应,一般是和遥控一起购买的。就需要用户自行焊接至飞控上。</p>
+<p> 锡焊所用到的工具有电烙铁和焊锡丝,最好能佐以松香,增加焊接成功率。电烙铁推荐购买 T12 或更好的,因为飞控散热设计,很多焊盘为通孔的形式,热量会非常快地传到到全板,导致焊锡较难达到融化的温度。若使用功率太低的电烙铁,可能无法融化焊锡。</p>
+<p><img src="/images/2022-09-05/T12%E7%84%8A%E5%8F%B0.jpg" alt="T12 焊台"></p>
+<p> 由于飞控比较小巧,焊盘也十分迷你,对焊接技术要求较高。</p>
+<h3 id="使用搜索引擎"><a href="#使用搜索引擎" class="headerlink" title="使用搜索引擎"></a>使用搜索引擎</h3><p> 不懂不可怕,不懂得学习才可怕。</p>
+<p> 穿越机有关的零碎知识犹如满天星,散布于互联网的每一个角落,需要一定的搜索技巧才能挖掘到有用的知识。</p>
+<p> 国内有关穿越机的网站数量比较少,建议使用英文搜索。</p>
+<h3 id="编译"><a href="#编译" class="headerlink" title="编译"></a>编译</h3><p> 若想要更新 ELRS 接收机和高频头的固件,需要从源码编译 ELRS 固件。虽然 ELRS 团队提供了一套图形化的编译工具,但难免会遇到一些疑难杂症 (博客中就有 ELRS 固件编译的踩坑记录)。需要对编译有一定的知识,能自行解决简单的问题。或者选择使用出厂固件,不自行升级。</p>
+<h3 id="操控能力-协调性"><a href="#操控能力-协调性" class="headerlink" title="操控能力 (协调性)"></a>操控能力 (协调性)</h3><p> 像音游 (音乐游戏) 一样,微操遥控器并不是所有人都能很好地做到。穿越机对遥控指令的反应极为灵敏,需要你能精细地操控遥控器。</p>
+<p> 但这项技能可以通过电脑模拟飞行来习得。建议在正式开始飞行前,先在电脑上使用模拟器熟悉飞机的控制。</p>
+<h3 id="一颗勇敢的心"><a href="#一颗勇敢的心" class="headerlink" title="一颗勇敢的心"></a>一颗勇敢的心</h3><p> 在飞行穿越机的过程中,不可能不出现炸机 (飞机以异常姿态落地) 的情况,难免会留下一点阴影。</p>
+<p> 我在小学时飞过固定翼飞机,曾不慎撞到电线,导致飞机起落架损坏脱落,最后只能由老师操控迫降。此事给我留下了不小的心理阴影,有好几个月我都不敢再飞行固定翼。不过最后还是克服了恐惧,再次拾起了遥控。</p>
+<p> 炸机并不可怕,每一次炸机都记录着你的成长。</p>
+<h2 id="深入了解"><a href="#深入了解" class="headerlink" title="深入了解"></a>深入了解</h2><p>类似 Linux (比较奇怪的类比),穿越机也分为最小安装和额外拓展两部分。</p>
+<p><img src="/images/2022-09-05/%E7%A9%BF%E8%B6%8A%E6%9C%BA%E6%9E%B6%E6%9E%84.jpg" alt="穿越机架构"></p>
+<p>最小安装:</p>
+<ul>
+<li>机架 - 硬连接其他组件,保护脆弱的电路板;</li>
+<li>电机 (和桨叶) - 提供飞行动力;</li>
+<li>飞控 - 控制飞行状态,是穿越机的大脑;</li>
+<li>电调 (电子调速器) - 驱动电机、分隔电路 (防止电机减速时产生的反向电流烧坏其他电子设备);</li>
+<li>接收机 - 接受遥控器控制信号 (有 2.4GHz、915MHz、 868MHz 等频段,互不兼容);</li>
+<li>图传 - 发送图像信号 (多为 5.8GHz 频段);</li>
+<li>摄像头 - 提供第一人称视角;</li>
+</ul>
+<p>额外拓展:</p>
+<ul>
+<li>GPS 模块;</li>
+<li>BB 响 (蜂鸣器 Beeper,用于寻找飞机);</li>
+<li>LED 灯珠、灯带 (好看 XD);</li>
+<li>红外避障模块;</li>
+<li>运动摄像机 (拍摄更清晰、帧率更高的视频);</li>
+</ul>
+<p>挑几个比较重要的模块介绍一下:</p>
+<h3 id="机架"><a href="#机架" class="headerlink" title="机架"></a>机架</h3><p>穿越机机架一般由碳纤维板制成,质地轻盈且强度极高,可以物理连接不同的模块,并保护脆弱的电子设备 (飞控、图传等裸板)。</p>
+<p>挑选时,机架有几个要主要关注的核心参数:</p>
+<ul>
+<li><p>外形:</p>
+<p>机架分为普通式与涵道式 (Duct Propeller) 两种。</p>
+<p>普通式机架的螺旋桨桨叶裸露在外,而涵道式机架的螺旋桨外侧有涵道 (类圆筒) 包裹。</p>
+<p>有一些普通式机架可以通过额外安装涵道,变身成为涵道机架。</p>
+</li>
+</ul>
+<p><img src="/images/2022-09-05/%E6%9C%BA%E6%9E%B6%E7%A7%8D%E7%B1%BB.jpg" alt="普通机架 (左) 与涵道机架 (右)"></p>
+<p> 普通式机架采用开放式的设计,尽可能少地使用材料以减轻重量。由于螺旋桨暴露在外,危险性较大。普通机架可被用于任何竞速与花飞机型。</p>
+<p> 涵道式机架通过涵道结构可以提供更好的升力、稳定性和安全性,但额外增添了重量和封阻,一般被用于小型机与花飞机型。</p>
+<p><img src="/images/2022-09-05/%E6%9C%BA%E6%9E%B6%E5%A4%96%E5%BD%A2.jpg" alt="机架外形"></p>
+<p> 此外,机架还可以再细分为 X 型、H 型等,在此就不多展开,有兴趣的读者可以自行查阅。</p>
+<ul>
+<li><p>大小:</p>
+<p>穿越机大小各异,从最小的一寸机,到体型较大的五寸、六寸机。</p>
+<p>二寸以下的机子防风能力较差,但体型轻盈且常使用涵道机架,适合在室内飞行,且不易伤人。</p>
+<p>三寸以上的机子动力充沛,飞行速度快,但体型大、螺旋桨裸露,无法在室内飞行。适合在场地广阔的室外进行竞速或花飞 (花样飞行)。</p>
+</li>
+</ul>
+<h3 id="飞控与电调"><a href="#飞控与电调" class="headerlink" title="飞控与电调"></a>飞控与电调</h3><p>飞控与电调 (有时还有图传) 几块板子大小相当,时常纵向堆叠安装,称为飞塔。在多块 PCB 间有硅胶柱减震。</p>
+<p><img src="/images/2022-09-05/%E9%A3%9E%E5%A1%94.jpg" alt="双层飞塔"></p>
+<p>飞控需要注意的参数不多:</p>
+<ul>
+<li><p>飞控 SoC:</p>
+<p>常见飞控 SoC 的型号有 F405 和 F722,他们的全称应为 STM32F405 和 STM32F722. 差别主要在主频和内存大小上。</p>
+</li>
+</ul>
+<p><img src="/images/2022-09-05/%E9%A3%9E%E6%8E%A7SoC.jpg" alt="飞控 SoC"></p>
+<p> 由于穿越机飞行对计算性能不是很敏感 (类似 3D 打印机),一般选择搭载 F405 SoC 的飞控性能就足够使用了。</p>
+<ul>
+<li><p>串口数量:</p>
+<p>新手会用到的串口数量不会超过两个 (外接部分型号的图传和接收机),绝大部分飞控都能满足这个要求。若有需要安装 GPS 和其他传感器,则要注意查看飞控支持的串口数量了。</p>
+</li>
+</ul>
+<p>电调最重要的参数就是最大放电电流了。</p>
+<p>电调的电流选择需要根据机身大小、电机和桨叶大小、形状进行估计,通常电机厂家会给出推荐值,按照推荐选购即可。</p>
+<h3 id="接收机与图传"><a href="#接收机与图传" class="headerlink" title="接收机与图传"></a>接收机与图传</h3><p><img src="/images/2022-09-05/%E6%8E%A5%E6%94%B6%E6%9C%BA%E4%B8%8E%E5%9B%BE%E4%BC%A0.jpg" alt="接收机 (左) 与图传 (右)"></p>
+<p>接收机与图传共享一个重要的特性,协议:</p>
+<p>常见的接收机有 ELRS (ExpressLRS) 和黑羊 TBS 两类。应该注意的是,<strong>不同种类的接收机使用的通信协议和频段不同,能与其配对的遥控器和高频头也不同</strong>,因此在挑选接收机的时候一定要看清楚协议和频段。</p>
+<p>接收机和飞控的串口通讯协议也各异,有 UART、SBUS 等数种,再购买接收机前需确认飞控有相对应的串口。</p>
+<p>图传分数字图传和模拟图传两种。</p>
+<p>数字图传以大疆的最为出名,需要使用配套的飞行眼镜;模拟图传大多使用 5.8G 频段通讯,和大部分接收模拟信号的飞行眼镜通用。</p>
+<p>图传连接至飞控的串口通讯协议也很多,购买的时候请多加留意。</p>
+<p>除此之外,接收机有遥测功率、内/外置天线、天线接口等参数,在挑选时都需要多加留意。</p>
+<p>图传的另一主要参数则是发射功率。发射功率越大,能稳定接受图传信号的距离一般就越大。小型穿越机一般选择发射功率在 200mW 到 500mW 的图传即可 (部分图传发射功率可调);若有远航要求,也可选择发射功率大于 1W 的图传 (价格较高、发热也较大,一般带有主动散热)。</p>
+<h3 id="机子之外的配件"><a href="#机子之外的配件" class="headerlink" title="机子之外的配件"></a>机子之外的配件</h3><p>除了穿越机本体之外,想要拥有完整的飞性体验,还需要遥控器、高频头、电池、平衡充电器、飞行眼镜等配件。</p>
+<p>上述的每一种配件都可以写作一篇介绍文章。由于篇幅有限,本文就不再介绍上述的外围配件,请善用搜索引擎,学习相关知识。</p>
+<h3 id="配套软件"><a href="#配套软件" class="headerlink" title="配套软件"></a>配套软件</h3><p>除了硬件,穿越机配套的控制软件也尤为重要。目前主流的控制软件是开源的 Betaflight。</p>
+<p><img src="/images/2022-09-05/Betaflight.jpg" alt="Betaflight 地面站界面"></p>
+<p>Betaflight 分为嵌入端 (安装在飞控中嵌入式系统) 和地面站 (安装在电脑里的软件)。将飞控通过线缆连接至电脑,并打开 Betaflight 地面站软件,即可对飞控参数进行调整。</p>
+<p>Betaflight 调参也是一门大课,新手不建议自定义太多的参数。待熟悉飞机之后,才建议调整 PID 等高级控制参数。</p>
+<h2 id="新手的第一台飞机"><a href="#新手的第一台飞机" class="headerlink" title="新手的第一台飞机"></a>新手的第一台飞机</h2><p>说了这么多,要上某宝挑选、下单穿越机的种种配件了吗?</p>
+<p>我的建议是否定的。</p>
+<p>我咨询了一些老玩家,他们建议新手购买他人已完成调参的二手机器,或者购买商家大部分已组装完成套机,以绕过纷繁复杂且状况百出的 DIY 过程,降低还未入坑就弃坑的风险。</p>
+<p>此外,自行购买散件的总价常常会高于购买整机的价格。对于钱包不是特别充盈的我,购买整机也是一个省钱的选择。</p>
+<p>待熟悉了穿越机的飞行与调试之后,再学习他人经验,设计并组装一台自己心目中的机器也不迟,这才是 DIY 的浪漫。</p>
+<h2 id="我的成果"><a href="#我的成果" class="headerlink" title="我的成果"></a>我的成果</h2><p><img src="/images/2022-09-05/%E6%88%91%E7%9A%84%E6%88%90%E6%9E%9C.jpg" alt="我的成果"></p>
+<p>我选择购买一位老玩家完成大部分组装工作的“半”整机——包含了机架、电机、飞控和电调。图传、摄像头和接收机则是我自行购买和焊接安装的。</p>
+<p>飞机到手、外围装备齐全,只待一飞冲天。</p>
+
+
+ 2022-09-04T04:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/9d319c54.html
+ DELL 灵越 15 5547 拆解与更换硅脂
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>DELL 灵越 15 5547,Intel i5-6200U,Nvidia Geforce 630M,8G DDR3-1600 + 256G SATA SSD(后期改装),是我的第一台笔记本。这台笔记本的拆机过程比较繁琐,是我目前遇见的最难拆解的电脑,故作此文章,分享一下如何拆解一台笔记本,并为其更换硅脂。</p>
+<p><img src="/images/2022-05-22/01.JPG" alt="A面"></p>
+<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>本次拆机共耗时 1H 30Min,是拆解正常机器所花费时间的两到三倍。</p>
+<p>拆解步骤包括:卸下后盖、拆除电池、拆除硬盘、拆除风扇、拆除键盘、卸下基座、卸下散热器、更换硅脂,然后是逆序执行上述拆解步骤,组装电脑。</p>
+<h2 id="拆解准备"><a href="#拆解准备" class="headerlink" title="拆解准备"></a>拆解准备</h2><p>拆机少不了一套好工具。</p>
+<p>为了无伤拆机,需要准备一套好用的螺丝刀防止螺丝滑牙;还需要一套塑料拆机工具,用于撬开后盖。</p>
+<p>笔记本的硅脂,我选择了霍尼韦尔 7950 相变硅脂,不容易挥发,不需要经常更换。</p>
+<h2 id="开始拆机"><a href="#开始拆机" class="headerlink" title="开始拆机"></a>开始拆机</h2><h3 id="怎么拆呢"><a href="#怎么拆呢" class="headerlink" title="怎么拆呢"></a>怎么拆呢</h3><p>其实几个月前我就有给这台设备清灰、换硅脂的念头,但拆开机器之后我发现主板被塑料框架罩住了,无法拆下散热器。我研究了半天也没找到拆下框架的方法,便只清理了风扇的灰尘,没有更换硅脂。</p>
+<p>而这次,我有备而来:我查到了 <a href="https://downloads.dell.com/manuals/all-products/esuprt_laptop/esuprt_inspiron_laptop/inspiron-15-5547-laptop_owner%27s%20manual_zh-cn.pdf">DELL 官方的用户手册</a> ,其中详细记载了拆解这台机器的方法。接下来的拆解步骤就严格按照官方的教程啦。</p>
+<h3 id="第一步:卸下后盖"><a href="#第一步:卸下后盖" class="headerlink" title="第一步:卸下后盖"></a>第一步:卸下后盖</h3><p>翻到 D 面,拧下两颗固定螺丝,就能将后盖拆下,可以触及无线网卡、内存条、2.5 寸硬盘、电池和散热风扇,日常需要维护的部件都能轻松触及,好评。散热器和主板则在塑料框架之下。</p>
+<p><img src="/images/2022-05-22/03.JPG" alt="D面后盖之下"></p>
+<h3 id="第二步:拆除电池"><a href="#第二步:拆除电池" class="headerlink" title="第二步:拆除电池"></a>第二步:拆除电池</h3><p>释放主板电荷是电脑拆机中至关重要的一步!</p>
+<p>在主板带电的情况下拔插任何端子都是不明智的做法,很可能会将主板上的高电压线路误接入低电压线路(例如拔插屏线时没有正对接口),烧坏一片元件。</p>
+<p>这台机子的电池没有排线,直接接入主板。卸下围绕电池的四颗螺丝,手提塑料片,就可将电池卸下。</p>
+<p>翻到 C 面,长按电源键 10s,可重复两到三次,确保主板中的电荷完全释放。</p>
+<p><img src="/images/2022-05-22/04.JPG" alt="电池模块"></p>
+<p>第一次见这种电池模块,比起长条状的电池可以更好地利用机身空间。</p>
+<h3 id="第三到五步:拆除硬盘、散热风扇和键盘"><a href="#第三到五步:拆除硬盘、散热风扇和键盘" class="headerlink" title="第三到五步:拆除硬盘、散热风扇和键盘"></a>第三到五步:拆除硬盘、散热风扇和键盘</h3><p>卸下固定硬盘座的螺丝,抽出硬盘,再断开 SATA 与供电二合一的线缆,即可取下硬盘。</p>
+<p>拔下主板上散热风扇的端子(位于风扇左侧),卸下固定螺丝,即可取下散热风扇。</p>
+<p>翻到 C 面,用手或塑料工具扣出键盘模块,注意不要大力提起键盘!小心地提起一段距离,断开机身上的排线,键盘就取下来了。裸 C 面上还有两条排线,都需要断开。竖直方向排线需从孔洞中穿回 D 面。</p>
+<p><img src="/images/2022-05-22/02.JPG" alt="裸C面"></p>
+<h3 id="第六步:卸下基座"><a href="#第六步:卸下基座" class="headerlink" title="第六步:卸下基座"></a>第六步:卸下基座</h3><p>这是我第一次见笔记本中的基座,没有手册的指导很难拆下。</p>
+<p>首先,确保 C 面两根排线已断开。</p>
+<p>翻到 D 面,小心地断开屏幕排线(位于散热风扇左侧)。</p>
+<p>再翻到 C 面,按手册图示卸下所有螺丝。</p>
+<p>翻回 D 面,同样地卸下一堆螺丝,注意这两面的螺丝都是 M2.5x7 规格,长于主板用螺丝,混用可能会戳穿主板。</p>
+<p>DELL 在机身和框架上都有标注螺丝孔对应螺丝的规格,非常好评!</p>
+<p>确认卸下所有螺丝后,用塑料工具插入基座与机身的缝隙,划开卡口。</p>
+<p>待大部分卡口都解开时,小心提起基座,注意将基座上的线缆和排线取下,基座就彻底与机身分离了。</p>
+<p><img src="/images/2022-05-22/05.JPG" alt="基座背面"></p>
+<p>基座的背面,可以看到是 PC + ABS 材料,分量很足。</p>
+<p><img src="/images/2022-05-22/06.JPG" alt="拆下基座后的机身"></p>
+<p>拆下基座之后的机身,暴露出了主板和子板。散热器是梦幻单热管,不过压这两个破芯片足够了。</p>
+<h3 id="第七步:卸下散热器"><a href="#第七步:卸下散热器" class="headerlink" title="第七步:卸下散热器"></a>第七步:卸下散热器</h3><p>散热器共有六颗螺丝,四颗在 CPU 上,两颗在显卡上。</p>
+<p>为了使散热器均匀受力,不能一次性直接拧下一颗螺丝,而是平均为每颗螺丝卸力,可以每个螺丝一次转两圈,直到所有螺丝都被卸下。裸晶脆弱,有必要好好保护一下。</p>
+<p><img src="/images/2022-05-22/07.JPG" alt="CPU的旧硅脂"></p>
+<p>不难看出,CPU 上的硅脂全部凝固,且裸晶上的硅脂基本流失殆尽(散热器上也没剩多少)。</p>
+<p>去除大部分凝固的硅脂,再用酒精片擦拭、用布擦干。</p>
+<p>更换硅脂的步骤我就不在此赘述,商家一般会提供详细的视频介绍,按着教程做就可以了。</p>
+<p><img src="/images/2022-05-22/08.JPG" alt="干净的CPU"></p>
+<p>i5-6200U 的全貌,右侧长条状的裸晶应该是集成显卡吧。</p>
+<p><img src="/images/2022-05-22/09.JPG" alt="擦干净的GPU"></p>
+<p>Geforce 930M 显卡,非常小个。</p>
+<p><img src="/images/2022-05-22/10.JPG" alt="显存芯片"></p>
+<p>DDR3 显存,封装方式和 DDR4 不一样,更扁一些。</p>
+<h3 id="后续步骤"><a href="#后续步骤" class="headerlink" title="后续步骤"></a>后续步骤</h3><p>接下来就是逆序刚刚的拆解步骤,我就不再赘述。</p>
+<p>注意几点:</p>
+<ul>
+<li>安装散热器时也需保证受力均匀,且不能过分用力,可能会压碎裸晶。</li>
+<li>安装基座时注意将线缆与排线置于合适的位置,不要被基座压到了。</li>
+</ul>
+<p>拼装完成后,插电,开机,轻松点亮。</p>
+<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>拆解一台电脑并没有想象中的那么困难,拆解不同电脑的方式也都大同小异,上述的步骤一般都能适用。</p>
+<p>我已拆解了不下 10 台/次 笔记本,目前还未翻过车。</p>
+<p>下次清灰、更换硅脂,试着自己动手吧!</p>
+
+
+ 2022-05-22T02:30:00.000Z
+
+
+ https://blog.udon.eu.org/archives/245ab175.html
+ 厌烦了软件的我上了硬件的车
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>好久不见,回想三个月前的我还在享受暑假。开学后,我将大部分精力转移至学业,折腾的时间便少了。</p>
+<p>机缘巧合,在显卡价格最高的时候坏了显卡的我打算趁双十一打折买个焊台,自己修显卡。</p>
+<p>同时,我有了玩一玩硬件的想法。</p>
+<h2 id="焊台体验"><a href="#焊台体验" class="headerlink" title="焊台体验"></a>焊台体验</h2><p>六七年以来,我一直在用直插 220V 不可调温的电烙铁焊接。当时,无线电老师告诉我这是质量很好的紫铜烙铁。就是这把烙铁陪着我入门了锡焊。</p>
+<p>放在今天,这把烙铁加热慢,且烙铁头非常容易氧化,体验极差。由于加热效果不佳,我还弄坏了一台灵车无人机的主板和一个 IR 摄像头(都是掉焊盘了)。</p>
+<p>新焊台则完美的解决了之前的痛点:新烙铁加热快、温度准确、刀头耐用。热风枪则开启了贴片元件焊接时代。</p>
+<h2 id="第一个项目"><a href="#第一个项目" class="headerlink" title="第一个项目"></a>第一个项目</h2><p>说在前头,选择一个比较复杂的项目来入门是个错误的决定。</p>
+<p>十月中,我选择了一个加热台项目。对,就是可以用来焊接贴片元件的加热台。用工具制造工具,多美妙的事情啊~带着兴奋,我开始采购元件……</p>
+<p>直至今天,这个项目的进度仍然停滞不前:元件全部焊接完毕(见下图),接通电源可以点亮一秒,随即就烧坏了一个电阻。初步判断是 5V 供电区域有短路,具体情况还需继续检测……</p>
+<p><img src="/images/2021-11-07/1.JPG"></p>
+<p>事实证明,第一个项目应该选择简单点的,以防止打击信心🌚</p>
+<h2 id="赶超第一个项目的第二个项目"><a href="#赶超第一个项目的第二个项目" class="headerlink" title="赶超第一个项目的第二个项目"></a>赶超第一个项目的第二个项目</h2><p>转眼到了十一月,又可以在嘉立创愉快地免费打板了~</p>
+<p>这次,我给自己设计了一张 PCB IC 校园卡,只需要焊接一个 CUID 芯片。</p>
+<p>没有比这更加简单的项目了!</p>
+<p>成品如图~</p>
+<p><img src="/images/2021-11-07/2.png"></p>
+<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>前几天整了一台性能不错的服务器,小项目又可以整起来;</p>
+<p>DIY PCB 这方面,我还想制作一个带编码器的小键盘,以提高 Premiere 剪辑的效率;</p>
+<p>群星真好玩,铁心灭绝者真不错😇</p>
+<p>真是丰富多彩的课余生活~</p>
+
+
+ 2021-11-07T13:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/67d41c7c.html
+ LOOPERS 简评
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>对 LOOPERS 的整体评价:B-</p>
+<hr>
+<p>这一作挺特殊的,没有任何选择支,全程自动播放耗时仅8小时左右。在这样短小的作品里塞下一日无限循环这样富有可能性的世界观,大概是本次剧情质量不尽人意的主要原因。</p>
+<p>中盘倒不觉得冗长,还可以继续细化,加深对人物的刻画;就是后期重复的内容与话语太多了,让想尽快看到结局的我着急。</p>
+<p>音乐方面,我全程用电脑扬声器玩的。说实话,BGM 听得不是很清楚。待我细听之后再作出评价。女二真的很吵,有 SP 那头恶鬼的吵闹级别了。</p>
+<p>制作方面,望月老师的画好看(哧溜)。</p>
+<hr>
+<p>谈一谈一些细节上的感受(可能有点剧透):</p>
+<p>作为已经退坑的老 Ingress 玩家,再度跟随主角一行人踏上寻宝之路,着实让人兴奋。游戏中着力刻画的,寻找到藏匿的宝物时的喜悦我能切身体会到。</p>
+<p>这让我想起了两三年前,我为了拔掉敌方的一个大 菊花 Portal,在旧屋区狭窄的巷子里穿行。找了一两个小时,终于到达 Portal 的中心点,开始狂轰滥炸。当时的兴奋与满足是溢于言表的。或许你要亲身玩过寻宝游戏,才能体会主角一行人的感受吧。</p>
+<p>对于“神经大条”的男主,我最大的感受就是这种人不大可能存在吧。遇到任何事情都能乐观面对、看似大大咧咧但做起事来注重细节、有着强大的领导力和鼓舞力……这种人若真的存在,绝对耀眼。</p>
+<p>最后看作品的主题。在我看来, 龟 龙骑士这回倒不是想写一个很催泪的故事,而是继续宣扬 Key 社所推崇的友谊、团结和爱的主题。我个人是百看不厌,我也想要知心朋友,甚至是一群知心朋友。</p>
+<p>男主全程口边挂着寻宝二字,听了耳朵确实起茧,但这文章放在考试说不定能拿挺高的分数——不断地扣紧中心。人生的意义?寻宝;友情的价值?寻宝;生命的救赎?还是寻宝。</p>
+<p>这寻宝真有意思,只要将寻宝的过程与寻找到宝物稍作改变,就能解释这么多难以用语言表述的东西。倘若能在游戏性上再下点功夫,加个交互型的寻宝游戏,整体效果又会再上一层。但加了这些就不是一款短小精湛的游戏了,对吧。</p>
+<hr>
+<p>最后,这款游戏值得推荐吗?对于没有接触过 Key 社作品的人,可以一玩。</p>
+<p>对于 Key 社老玩家,对这部作品报以极高的期待,希望它能超过正作水准的,还是别玩了。</p>
+
+
+ 2021-08-02T02:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/202ebc76.html
+ 2021 第二学期总结
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>在去厦门的动车上,写一下这个学期的总结。</p>
+<hr>
+<p>刚开始我想形容这个学期是浑浑噩噩的,感觉我想做、想学的东西都没做成;</p>
+<p>但再冷静想一想,这学期在学校的安排下打了很多基础:解决了 C++ 这一心头大恨;还学习了数电,为硬件打了点基础。</p>
+<p>C++ 实在很基础、很枯燥,若不是学校压着要学习,光凭我一人自学是难以解决的。以前也不是没有尝试过自学,最终都败下阵来。</p>
+<p>至于硬件这块的知识,软件工程专业一般是不去接触的。学校大概是觉得下学期要学的 计算机组成原理 需要有数电的预备知识吧。</p>
+<p>至于高数、线代,对编程越是了解,越会知道他们的重要性,知道他们贯穿着整个编程的过程。</p>
+<p>这个学期最满意的还是全科通过,包括让我非常头疼的大学物理和高数。</p>
+<p>下个学期我还是希望能腾出更多的时间给自己喜欢的事物,而不是把全身心都投进学校的课业,感觉亏待了自己的梦想……</p>
+<p>生活方面,■■■■■■■■■,整体闷在学校里把我憋得难受。宿舍环境虽然不错,但整体偏老旧——六人间改四人间,■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<p>下个学期又可以搬回主校区,一定要把宿舍和<del>工位</del>书桌好好装修一下,过精致生活~</p>
+<hr>
+<p>至于暑假的安排,挺杂乱的。</p>
+<p>买了不少东西,从小到大,从 IR 摄像头模组到 3D 打印机,打算将折腾二字写满整个假期;</p>
+<p>学习内容则是安排了 JavaScript 和 CTF 相关的内容,如果还有时间还想看看 React。CTF 没有人带,想要入门挺困难的……校队是不打算招新人么 :(</p>
+
+
+ 2021-07-15T01:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/43a7a15c.html
+ Osprey Commet 简评
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>我的威戈双肩包——没错,就是烂大街的那个牌子——已经用了四年多了。不得不承认她的质量之好,用了这么久只坏了包前侧的拉链(到裁缝店换了一条拉链),外加水壶网袋破了几个洞。在上个月,她的一条拉链绳(不知道怎么叫那个部件)的脱落让我有了更换通勤背包的念头。</p>
+<span id="more"></span>
+
+<p>为了挑选一款最称心的通勤背包,我先对前任威戈包进行了评估:</p>
+<p>优点</p>
+<ul>
+<li><p>威戈的分仓做的不错,充电宝、卡包等物件都能找到存放的位置,钥匙有也专门的链子系挂;</p>
+</li>
+<li><p>储存空间大(30L+),主仓有足够的空间放下装在内胆包里的14寸笔记本,也有专门的笔记本仓位;</p>
+</li>
+</ul>
+<p>缺点</p>
+<ul>
+<li><p>背负系统拉垮,光是背电脑(1.4kg)就觉得非常沉,更别说偶尔的电脑+平板组合了(2.2kg);</p>
+</li>
+<li><p>没有太多存放小物件的仓位,所有的小东西(U盘、读卡器、耳机、线材 etc.)都放在包的前仓里,且前仓还是竖向开合,分类与取物皆不便;</p>
+</li>
+<li><p>包身不防水,但送防雨套。下雨时出门要提前戴套,生怕包内电子产品淋湿;</p>
+</li>
+</ul>
+<p>因此,我对下一款背包有如下要求:</p>
+<ul>
+<li><p>自重轻;</p>
+</li>
+<li><p>分仓多且位置合理,能分类存放小物件;</p>
+</li>
+<li><p>包身最好能防水;</p>
+</li>
+<li><p>最好能同时收纳笔记本电脑和平板;</p>
+</li>
+</ul>
+<p>在挑选了不下30款背包后,我选择了这款 Osprey Commet,下面来简要点评一下。</p>
+<h4 id="全局"><a href="#全局" class="headerlink" title="全局"></a>全局</h4><p>包身自重 0.85kg,容积 30L,在这个大小的包中算比较轻的了。包身全部防水,拉链虽然没有做密封处理,但有一些延伸出来的防水面料挡住拉链,可以防止进水。</p>
+<h4 id="分仓"><a href="#分仓" class="headerlink" title="分仓"></a>分仓</h4><p>这款包的分仓可以算是一个亮点。我简要说一下我对每个分仓的使用情况:</p>
+<p>包的最前方是提手(颜值比较一般,偏向实用),因此第一个仓是小主仓。仓内有三个兜袋,用于存放我的卡包、证件、充电宝和 MP3,仓内还装着小记事本和钱包。此外,仓内还有一根红色的钥匙绳,采用的是快挂钩,可以把现有的钥匙串挂上去。</p>
+<p>往里一层是第一个眼镜/小物品收纳仓。仓内使用了特殊面料防止刮花眼镜,在登山/骑行时存放眼镜还是很方便的。我就拿来存放经常取用的小物件了,例如耳机、U盘、Lto3.5mm 转接线等。</p>
+<p>下一层是一号主仓,外加主仓内的第二个眼镜/小物品收纳仓。一号主仓有一个兜袋方便收纳平板和 A4 大小的文件,我便拿来放文件袋和平板。课本、笔盒也都放在这个仓位。小物品收纳仓则收纳比较少取放的小物件,例如订书机、凤尾夹和一些药品(达喜)。</p>
+<p>最后一个仓位是笔记本仓。作为整个包最大的仓位,能轻松容纳 15.6 寸的笔记本。在需要携带笔记本时,这个仓位当然用来装笔记本;平常上课不带笔记本,也可以拿来装外套。这个仓位里还有一个网袋,我用来装线材(一根 0.2m CtoC、一根 MFi AtoL、一根 0.2m AtoB)。</p>
+<h4 id="背负系统"><a href="#背负系统" class="headerlink" title="背负系统"></a>背负系统</h4><p>虽然没能用上 Osprey 专业的空景系统,但 Commet 的背负系统依旧优秀。Osprey 的背板偏硬,可以比较好地贴合后背,以减轻肩带对肩膀的压力。在日常上课背负几本书时,基本没有负重的感觉。若是同时背负笔记本+平板(3.6kg 左右),能感觉到背板压在后背上,分担了部分重量。</p>
+<p>胸带与腰带这种登山包才有的配置出现在了这款通勤包上。在背负比较重的物品时系上腰带可以有效减少包身晃动。</p>
+<h4 id="总评"><a href="#总评" class="headerlink" title="总评"></a>总评</h4><p>相当满意!</p>
+
+
+ 2021-06-13T07:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/4a562e35.html
+ 近日败家:Chrombook Duet 与 小米手环6
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>今天来分享一下最近败家的几样电子产品。</p>
+<h2 id="Chromebook-与-USI-触控笔"><a href="#Chromebook-与-USI-触控笔" class="headerlink" title="Chromebook 与 USI 触控笔"></a>Chromebook 与 USI 触控笔</h2><p>在大学课堂上发现很多人都持有一台 iPad 和 一支 Apple Pencil 在做着笔记,令我很是羡慕。</p>
+<p>在咨询过“业内人士”,并对现有产品及我的钱包进行评估之后,我放弃了购买 iPad 及其昂贵的配件的念头,选择拥抱 Chromebook。</p>
+<p>为了追求极致的性价比,我选择了这款 联想 Chromebook Dute 加上 联想 USI 触控笔,1730 + 330 合计 2060 元。</p>
+<h3 id="购买"><a href="#购买" class="headerlink" title="购买"></a>购买</h3><p>我是在亚马逊海外购下单的,货从美国仓库发出。平板花费了8天来到了我的手上,而触控笔因为海关查验,比平板迟了两天。跨国快递的速度比我想象的要快不少。</p>
+<h3 id="设计与体验"><a href="#设计与体验" class="headerlink" title="设计与体验"></a>设计与体验</h3><p>Duet,意为合二为一。CB Duet 的设计理念和 Surface Book 一致——打造一台既可以当作平板又可以当作电脑使用的设备。</p>
+<p>CB Duet 附赠了磁吸键盘与可以作为支架的保护套,这一点比 iPad 高不少。</p>
+<p>但是,键盘与触控板的体验实在是一言难尽。</p>
+<p>键盘在不平整的环境下偶尔会出现多次触发(按一次键打出两次字母)的情况,不过放在平整桌面就不会了。键程尚可,但按键布局一般——为了在10寸大小的区域容纳全部按键,联想选择了将键盘右侧的按键(大部分是符号键)缩小,这让打出正确的符号变得十分困难。</p>
+<p>触控板的体验更加一言难尽。手感很差,似乎没做过亲肤处理,滑动阻力非常大;定位也很不准确。但 CB Duet 有一块10寸的触摸屏,为何不使用触控操作呢?</p>
+<p>总而言之,CB Duet 附赠的键盘属于“能用”的级别,就像现在的我敲着博客——做一点文字工作完全没问题。Coding ?别想了,我替你试过了,如果只是简单改几行代码,比如 Caddy 的配置文件,完全没问题;如果想跑完整的开发环境,性能可能不够;如果使用 code-server……也不是不行,但10寸的屏幕实在不能施展浑身解数啊。</p>
+<h3 id="续航"><a href="#续航" class="headerlink" title="续航"></a>续航</h3><p>部分比较“卷”的朋友可能比较担心这个问题,是我先倒下还是 CB Duet 先倒下?</p>
+<p>根据我的实际体验——当然这可能很不准确,你大概率撑不过 CB Duet。</p>
+<p>实际测试下来,一个上午,四节课下来,大概使用 15%-20% 电量。</p>
+<p>ARM 的超低功耗让 CB Duet 有官方标称的将近 11 小时的续航;如果你像我一样调低屏幕亮度,并只使用笔记软件,系统提示的理论续航甚至达到 20 小时;倘若是高强度使用,例如使用 ssh、打开 Linux 虚拟机等,续航则在 6-8 小时左右。</p>
+<h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p>翻到上面再看一眼价格,还要谈什么性能吗!</p>
+<p>本人不喜欢的 MTK P60T,8c 2.0Ghz。</p>
+<p>不过实际体验来看,这颗小芯片的性能完全能喂饱 ChromeOS 和它的安卓容器,至于 Linux ,只要不跑 GUI 应用(Linux 容器暂无图形硬件加速)也没问题。</p>
+<p>听说联想计划推出搭载高通骁龙 7c 的同款 CB ,性能有略微升级,如果价格依旧能持平,那会是更好的选择。</p>
+<h3 id="系统与体验"><a href="#系统与体验" class="headerlink" title="系统与体验"></a>系统与体验</h3><p>ChromeOS 的操作需要适应一段时间。</p>
+<p>你应该要改变对应用的认知——减少安装实体应用,更多地拥抱 PWA ,可以让这个专门为 Chrome 优化的操作系统发挥全部实力。</p>
+<p>安卓容器的性能真的很棒,可能是安卓和 ARM 相性较好的缘故吧,安卓应用在 CB Duet 上表现极佳,我觉得和一台安卓平板几乎没有差异。这台安卓平板却还能跑完整的 Chrome 浏览器和 Linux 容器。</p>
+<p>系统的其他体验可以在网络上看看其他人的博客。如果我在后续体验中有什么心得体会,会考虑写成博客。</p>
+<h3 id="还有触控笔"><a href="#还有触控笔" class="headerlink" title="还有触控笔"></a>还有触控笔</h3><p>联想这只 USI 触控笔是亚马逊上最便宜的,没有侧边按键、没有笔擦。触控笔没有太多要说的,昨晚用它写了一份作业,体验良好。</p>
+<p>使用一周之后,偶尔会出现笔凌空识别的情况,无法测试出是硬件还是软件的问题,略微影响使用体验。</p>
+<h2 id="小米手环6"><a href="#小米手环6" class="headerlink" title="小米手环6"></a>小米手环6</h2><p>这是我第一次体验手环产品,之前戴的都是智能/半智能手表。</p>
+<p>小米手环6的屏幕在手环产品中是比较大的,且能自定义壁纸,这是它最吸引我的地方。</p>
+<p>至于续航,的确是硬伤。在打开除了全天心率监测以外的所有监测项目,心率检测频率设为1分钟一次后,手环一天消耗 20% 左右电量。</p>
+<p>总体体验还算满意的。</p>
+
+
+ 2021-04-30T13:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/2c0bfb1a.html
+ 校色初体验 / 寒假总结
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>好久不见,甚是想念。今天来分享一下初次校色的体验以及寒假我都干了些什么。</p>
+<span id="more"></span>
+
+<hr>
+<h2 id="校色初体验"><a href="#校色初体验" class="headerlink" title="校色初体验"></a>校色初体验</h2><h3 id="为什么要校色"><a href="#为什么要校色" class="headerlink" title="为什么要校色"></a>为什么要校色</h3><p>我的笔记本电脑型号是 联想小新 Air 14 2020,拥有一块 14寸 100% sRGB 色域(实际为 94% sRGB)的屏幕,面板型号是 友达 B140HAN06.8。很幸运,不仅抽中了三星 SSD,还抽中了友达的屏幕。</p>
+<p>为了更舒服地阅读代码,我又淘了一台 17.3寸 的 DIY 便携显示器,同样也是 100% sRGB 色域(实际为 95% sRGB),面板型号是 友达 B173han01.1。</p>
+<p>由于便携屏的驱动板都是通用的,并没有对某块面板有调教,也不可能有屏幕出厂时的调教,因此这块便携屏的色差非常明显。就校色结果来看,光度 (gamma) 值就有 70% 左右的偏差。</p>
+<p>通过校色,两块屏幕色彩更加接近,且更接近真实的颜色,看起来也会更舒服一些。</p>
+<h3 id="关于价格"><a href="#关于价格" class="headerlink" title="关于价格"></a>关于价格</h3><p>我想很多人和我有同样的顾虑,感觉租用校色仪价格不菲。</p>
+<p>这次租用时长为 3天,我仅使用了一天半就归还了。总共支出为 校色仪租金 40元 + 回程运费 18元。押金原本是 750元,和店家商量后爽快地降到了 500元。</p>
+<p>虽然商家划定了许多可能导致押金被扣的规则,但只要你小心一点、爱护一点,完璧归赵、取回押金是很简单的。</p>
+<h3 id="色温选择"><a href="#色温选择" class="headerlink" title="色温选择"></a>色温选择</h3><p>刚开始我选择的目标色温是 6500K,在校色后发现偏黄许多。最后我选择的目标色温是 7500K。当然,两块屏幕 6500K 色温的校色文件我都保存下来了。</p>
+<h3 id="最终校色效果"><a href="#最终校色效果" class="headerlink" title="最终校色效果"></a>最终校色效果</h3><p>按照红蜘蛛给出的报告,联想笔记本的这块屏幕在色准上要优于便携屏,白点与灰度的 △E <= 0.2 ,便携屏则是 △E <= 0.5。</p>
+<p>联想这块屏出厂也比较准,值得表扬~</p>
+<p>下面是校色完的合影:</p>
+<p><img src="/images/2021-2-26/1.jpg"></p>
+<hr>
+<h2 id="寒假我都干了啥"><a href="#寒假我都干了啥" class="headerlink" title="寒假我都干了啥"></a>寒假我都干了啥</h2><p>列举一下:</p>
+<ul>
+<li>学习(重温)了 C语言;</li>
+<li>学习了 PHP 基础;</li>
+<li>学习了 Burp Suite 基础;</li>
+<li>学习了 After Effect;</li>
+<li>学习了 Audition 基本操作;</li>
+<li>完成了一个 5分钟 的视频项目,比较灵活地运用了 PS Pr AE 及 Au;</li>
+<li>当了两个星期的补习老师,挣来的钱买了 飞利浦 X2HR HiFi 耳机和一块 2242 SSD;</li>
+<li>学习了 Solidworks 基本操作(这两天学的);</li>
+</ul>
+<p>感觉还是做了不少事的。</p>
+<p>马上要开学了,下学期总算能学些我想学的了。</p>
+<p>总之,好好干吧!</p>
+
+
+ 2021-02-26T14:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/cd7dffda.html
+ 2020 年终总结
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>2020 马上就要过去了,我想做一个年终总结。</p>
+<span id="more"></span>
+
+<hr>
+<p>非常抱歉鸽了博客四个月。</p>
+<p>其实十月我忙中偷闲写了一篇文章打算发上来,却被 Hexo 吞了,我也没了重写的热情,就这么让它消失了。</p>
+<h3 id="2020-我做了什么"><a href="#2020-我做了什么" class="headerlink" title="2020 我做了什么"></a>2020 我做了什么</h3><p>■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<p>■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<p>■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<p>■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<p>■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<h3 id="2021-我打算做什么"><a href="#2021-我打算做什么" class="headerlink" title="2021 我打算做什么"></a>2021 我打算做什么</h3><p>First, 我打算加入学校的 CTF 战队。自学 CTF 的过程中一定会遇到不少有趣的事情,我会抽空写作博文与大家分享的。</p>
+<p>Second, 我想和同学合作开发一些有趣的小项目乃至小软件,点子已经有几个存在脑子里了。</p>
+<p>Then, 我打算磨练一下业务能力,而不是简单地编写脚本一样存在的代码,CSS 也不能写得歪歪扭扭的了(捂脸)。</p>
+<p>在一切都回归正轨之后,我也会跟上各位的步伐,继续探寻有趣的项目,还请大家多多期待!</p>
+<h3 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h3><p>2020 发生了太多糟糕的事情,祈愿 2021 能遇见更多美好的事情。</p>
+
+
+ 2020-12-31T16:00:00.000Z
+
+
+ https://blog.udon.eu.org/archives/911a91db.html
+ 博客迁移-主题更换与近况报告
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>又好久不见。</p>
+<p>这两天抽空更换了博客的主题,并迁移至永久域名 ■■■■■■■■■■■ 。</p>
+<p>以及谈一谈新开的项目的设计思路,有兴趣的读者还请慢慢阅读。</p>
+<span id="more"></span>
+
+<h2 id="博客变更"><a href="#博客变更" class="headerlink" title="博客变更"></a>博客变更</h2><h3 id="更换域名"><a href="#更换域名" class="headerlink" title="更换域名"></a>更换域名</h3><p>■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<h3 id="更换-Hexo-主题"><a href="#更换-Hexo-主题" class="headerlink" title="更换 Hexo 主题"></a>更换 Hexo 主题</h3><p>主题的名字叫 <a href="https://github.com/fluid-dev/hexo-theme-fluid">Fluid</a> ,是一款 Material Design 风格的主题。</p>
+<p>之前我用的主题是 Yilia ,但作者弃坑已久,感觉已跟不上时代变化,就下定决心换了(还把手上所有猫羽雫图全部塞进去了)。</p>
+<p>同时,我还将评论系统 Gitalk 更换为 <a href="https://utteranc.es/">utteranc</a> ,对于国内用户加载速度应该会更快,同样需要 GitHub 登录。</p>
+<h2 id="近况报告"><a href="#近况报告" class="headerlink" title="近况报告"></a>近况报告</h2><p>快要高考了好忙啊,完全没有时间研究技术了,也到了应该要努力的时候了(笑)。</p>
+<p>超级长的寒假里我还是有学习新知识的(指计算机):</p>
+<h3 id="成绩查询网站"><a href="#成绩查询网站" class="headerlink" title="成绩查询网站"></a>成绩查询网站</h3><p>开了一个坑:构建一个成绩查询网站以代替学校那套繁琐的校务系统。</p>
+<p>■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</p>
+<p>项目的前端用了 jQuery、Bootstrap 等框架;</p>
+<p>后端则是采用我比较熟悉的 Python 后端框架 Django 。</p>
+<hr>
+<p>校园官网查询成绩的流程如下:</p>
+<pre><code class=" mermaid">graph TB
+校园网主页--输入账号密码 选择身份-->
+校园网内网--找到侧边栏的按钮-->
+成绩查询页面--选择年份 学期 考试场次-->
+得到成绩
+</code></pre>
+
+<p>如何取代学校的系统呢?思路很简单:<del>黑掉学校的服务器直接把成绩取出来</del></p>
+<p>模拟登录就好了。</p>
+<p>学校的破烂网站采用的是最简单的 GET 表单登录,可以轻松获取 Cookie ,方便进一步操作;</p>
+<p>我将查询步骤简化为:</p>
+<pre><code class=" mermaid">graph TB
+查询页面--输入账号密码-->
+下一步1--选择年份 学期-->
+下一步2--选择考试场次-->
+获得成绩
+</code></pre>
+
+<p>看起来没简化多少,其实 选择身份 和 找到侧边栏按钮 就已经足够烦人了。</p>
+<p>校园网采用了大量下拉框选择,我将其替换为按钮选择,甚至不用选择,一定程度上提高了查询效率。</p>
+<hr>
+<p>除此之外,我也写了另一个 API 负责查询某成绩查询 APP 上的成绩。</p>
+<p>我认为这个 API 贡献较校网查询应该更大,这让我摆脱了手机 APP 查询这一烦人的设定。</p>
+<p>然而开发过程中,后者所花费的力气远小于前者,大概是校网建设得太差的缘故吧。</p>
+<hr>
+<p>除此之外,我还尝试将 API 封装进 Docker,使部署更加简单、快捷。</p>
+<p>Docker 的使用非常简单,若有兴趣还请多多尝试。</p>
+<p><strong>果然造轮子学习比干看教程效果要好啊!</strong></p>
+<h2 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h2><p>没有尾声 : )</p>
+
+
+ 2020-05-04T01:58:17.000Z
+
+
+ https://blog.udon.eu.org/archives/c7a0a8db.html
+ 莱卡依然想要回家
+
+ <link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>有幸在<a href="https://t.me/rynif_music">/home/rynco/music</a> Channel 遇见这张专辑。第一次听,或许是风格合口,我立刻爱上了这首名为“Laika Still Wants Go Home”,与专辑同名的轻音乐。<br>很有力量。这是这张专辑给我的第一印象。<br>我几乎没有乐理知识,对于音乐的评论总是如此无力。这首歌从旋律看也好,从节奏看也好,感觉整体把控得很恰当,张弛有度。我贫瘠的语言真的无法形容那种美的感觉。</p>
+<span id="more"></span>
+
+<hr>
+
+ <div id="aplayer-fzAHKHpD" class="aplayer aplayer-tag-marker" style="margin-bottom: 20px;">
+ <pre class="aplayer-lrc-content"></pre>
+ </div>
+ <script>
+ var ap = new APlayer({
+ element: document.getElementById("aplayer-fzAHKHpD"),
+ narrow: false,
+ autoplay: false,
+ showlrc: false,
+ music: {
+ title: "Laika Still Wants Go Home",
+ author: "Powder! Go Away",
+ url: "/music/Laika_Still_Wants_Go_Home.mp3",
+ pic: "https://p2.music.126.net/KFmivwKj3h18NGYKJlYcsw==/3229265650821776.jpg",
+ lrc: ""
+ }
+ });
+ window.aplayers || (window.aplayers = []);
+ window.aplayers.push(ap);
+ </script>
+
+<p>我刷新了对这张专辑的认识,已是数十分钟后。我在听新曲时总会翻翻网易云音乐的评论区,有数条评论对专辑封面的解释。<br><img src="http://p2.music.126.net/KFmivwKj3h18NGYKJlYcsw==/3229265650821776.jpg"><br>我第一次听说太空犬这个词是在Littile Buster中,能美·库特莉亚芙卡的名字的由来。库特莉亚芙卡是一头太空犬的本名。<br>它常被我们称作:莱卡。</p>
+<hr>
+<p>鉴于很少人知道这个故事,我就讲讲吧。<br>当时正逢美苏争霸,太空竞赛也是美苏交锋的重点。当时的苏联宇航技术不是很发达,但想要胜过美国就要先把航天员送上天。总不能让宇航员乘着从未试验过的飞船就这么上去吧,苏联的科学家就想用动物代替人类完成测试。科学家们在街上找来了几条流浪狗——因为他们觉得流浪狗比家犬够能受冻——这几条狗就被钦定为太空犬了。训练的艰苦不必多说,看看训练人类就能明白,何况是动物。几个月的训练之后,莱卡脱颖而出。到了发射前几天,一位科学家把莱卡带回了家里,让它和孩子们玩耍,因为他们很清楚,这是一次不含回程票的旅行。<br>苏联当时并未掌握从地球轨道重返地面的技术。<br>不知道当时在场的所有人的心情是怎样的。应该是兴奋的,毕竟超越老美了;又有点担心,因为即将发射的飞船是赫鲁晓夫下令两周内造出来的;或许有的人会有些不舍吧,他们将要亲手送走一条鲜活的生命。<br>很可惜,莱卡并不是在氧气或食物耗尽前被安乐死的。飞船的温控系统因赶工与设计问题出现故障。即使风扇再怎样转,舱内俨然成为一个火炉。用好听的话说莱卡是中暑而死;难听点,活活热死。仅管死亡是不可避免的,我也希望它能少一点痛苦啊。</p>
+<hr>
+<p>我想到了安德。在指挥完舰队完成模拟战斗后,在他得知他亲手消灭了虫族的舰队时,他崩溃了。他回忆起刚才他指挥数个小队作为诱饵白白牺牲。他崩溃了。我想当时,我也希望当时,火箭点火升空之时,二级火箭脱离失败之时,舱内温度急剧升高之时,有人能为这条生命感到一丝绝望。</p>
+<hr>
+<p>这张专辑的风格与“OPUS-灵魂之桥”的 OST 有异曲同工之妙。(独立工作室的游戏,OST 不方便放出,希望大家能一起购买。游戏也很棒啊!)都给我以一种末世感。究竟是世界对人绝望还是人对世界绝望呢?</p>
+<hr>
+<p>想象你站在专辑封面的那一点上,眺望地球,这该是多么孤独与悲伤。有评论说载着莱卡的火箭现在仍绕着地球旋转,但很可惜,那台火箭已经在大气层燃尽,这也算是最高规格的葬礼了吧。</p>
+<hr>
+<p>再说说专辑的名称吧。“Laika Still Wants Go Home”,英文,是没有一点问题的。我的母语是中文,因此英文的名称对我无感。但当我要将它翻译成中文,那一刻,我真的愣住了。我脑袋里已经将这几个单词转化为了中文,再一次理解了它们的意思。我僵住了。就几个字停留在嘴边就是说不出来。说不出来……在对面的人疑惑不解我为何停住,思考是否因为自己没有在认真听导致我生气时,一滴眼泪从我疲惫的左眼划过。立刻调动理智。情绪再次平稳,我又调回了内存里的那几个字,念了出来……</p>
+<hr>
+<p>后记</p>
+<p>从听到这首歌到构思这篇文章再到陆陆续续写出来,我花了数天。因为事情真的很多,脑内的一些想法被琐事代换了。<br>文字很凌乱,几乎是想到什么写什么。因此我用分割线分开了。<br>若有新想法我会继续补充的。</p>
+
+
+ 2019-10-31T15:00:00.000Z
+
+
diff --git "a/tag/\351\232\217\347\254\224/feed.json" "b/tag/\351\232\217\347\254\224/feed.json"
new file mode 100644
index 00000000..519493a5
--- /dev/null
+++ "b/tag/\351\232\217\347\254\224/feed.json"
@@ -0,0 +1,208 @@
+{
+ "version": "https://jsonfeed.org/version/1",
+ "title": "カレーうどん屋 • All posts by \"随笔\" tag",
+ "description": "カレーうどん屋.",
+ "home_page_url": "https://blog.udon.eu.org",
+ "items": [
+ {
+ "id": "https://blog.udon.eu.org/archives/2c54052d.html",
+ "url": "https://blog.udon.eu.org/archives/2c54052d.html",
+ "title": "我再也不能不假思索",
+ "date_published": "2024-11-12T15:30:00.000Z",
+ "content_html": "正值下班时间,你拖着疲惫的身体漫步街头。地铁站的入口熙熙攘攘,那是回家的方向。
\n正要走下地铁站路口的阶梯时,你发现了一个小孩子正朝着上行方向的电动扶梯走去。那是个连路都还走不清楚的孩子。
\n他的父母呢?你正张望着,希望能找到那个冒失的,或许正在专心看着手机而放松了警惕的父亲或母亲。
\n孩子朝着电梯摇摇晃晃地走了几步。想从上行的电梯往下走,这对于成年人都不是一件容易的事情,何况是小孩子。他大概刚踩上运行着的扶梯就会摔倒吧,真危险。你这么想着,稍微放慢了一些脚步,继续观察着孩子,也在观察着是否有人会出手拦住他。
\n你看到一个上了些年纪,头发有些发白的老人。他双手都拎着东西,是柴米油盐,重量拉低了他的双肩。他会帮忙吗?
\n你想起了上周,也是在下班的时间点,与即将乘坐的是同一个班次的地铁上,你幸运地找到了车厢里的最后一个座位。椅背上有没有贴着“爱心座位”的标志,你已经不记得了。你和今天一般疲惫,正在漫无目的地滑着手机屏幕,打发着无聊的通勤时间。视线越过手机屏幕,你瞥见一个老人,头发有些发白,双手都拎着重物,站在你的面前。
\n要让出座位吗?你一边滑动手机,一边思考着。“刚下班,大家都很辛苦”,你想起了在食堂偶然间听到的同事间的对话,“虽然我怀孕了,很需要一个座位。不过看到大家都这么累,我也不好说些什么了”。是啊,刚刚完成了一天工作的你,正需要充分的休息,哪怕是在地铁里坐下十几分钟对你也是无比的重要。想到这里,你底下了头,继续看起了手机……
\n也许是双手的重物让老人无暇顾及他人,迫使他加快步伐向下走去,他并没有发现那个正朝着上行扶梯走去的小孩子。此时孩子又向着扶梯迈出了几步。再往前几步,就踏上扶梯出口的金属盖板了。接下来会发生的事情难以想象。
\n会有人帮忙的吧?你继续张望着,同时心中也在盘算着——拦住了这个孩子,就意味着得守着他,直到那个粗心的父母匆匆赶来。谁知道要等多久,绝对是赶不上马上要到站的地铁了。你尝试把目光瞥向其他地方,似乎不再去看那个孩子,他就会安然无恙。但你做不到。
\n有一个阿姨快步走上前来,是要来帮忙的吗?你有一些即视感,仿佛之前在哪里见过这位大约四十岁出头,看上去有些憔悴的阿姨。
\n那是前几个月的一个早晨,你刚入职公司不久。向来遵守规矩的你今天也准时来到地铁站,乘上与昨天、前天、大前天都是同一时刻到站的地铁,这样就能准时到达公司,给大家留下一个好印象。地铁缓缓驶入站台,车门在悦耳的提示声中打开。此时,隔壁车门处却传来不和谐的声音。你循声望去,有人摔倒在列车与站台的间隙处,是一个看着四十岁左右的女性。是不小心绊倒了吗?还是没有吃早饭,有些低血糖了?不管怎样,她瘫坐在门口,久久没有起身。
\n要去扶一下吗?你迟疑了一会儿,没有上车。地铁站里有那么多保安,他们应该会处理好吧。扶起来之后,肯定得陪着她,直到有工作人员来照看才能离开吧,那今天早上要迟到了。你思考片刻,还是踏上了地铁,但还是转过身来,透过车窗继续观察。你看到有人和地铁站的保安一同将这位女性搀扶至阶梯边,地铁的门得以关闭,你可以准时到达公司了。想到这里,你的心安定了一些……
\n可惜的是,阿姨也没有注意到一个孩子正全然无知地朝着危险走去,就走下了楼梯。孩子摇摇晃晃地向前迈步,顷刻间,一只脚已经踏上了扶梯的盖板,再向前几步,就到了不断运转着的扶梯。
\n这样下去那个孩子肯定会摔下去的,你稍稍将行走的方向朝扶梯偏移了些许,做好了冲刺、抓住孩子的准备。同时,你也祈祷着能有人抢先你一步出手,并收拾剩下的残局。
\n一步、又一步,真的没有人肯帮忙一下吗?或许此刻真的只有我一个人注意到了这个孩子?你的视线游离,大步朝着扶梯走去,却被一个飞跃而过的身影叫停了步伐。
\n“小朋友,等一下。你的爸爸妈妈呢?”是一个年轻人,背着皮包,打扮时髦。他拦住了孩子,一只大手挡住了孩子前往扶梯的路,将孩子推离了危险。
\n看到这里,悬在空中的石头落地,你便也随着大流走下了阶梯。只不过,在那之后,在等候地铁时、坐在地铁中、或是淋浴、发呆之际,你都会回忆起过去的你,那个会毫不犹豫拾起地板上的垃圾、主动让出座位、扶起摔倒的人、给予他人帮助的你。你会不由自主地问自己:
\n我从什么时候开始,再也不能不假思索地做一件事情?
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/95479b1f.html",
+ "url": "https://blog.udon.eu.org/archives/95479b1f.html",
+ "title": "Soundcore C30i 耳机",
+ "date_published": "2024-06-13T06:40:00.000Z",
+ "content_html": "我目前在使用的耳机是 Sony LinkBuds,就是那款中间带了个孔的奇怪耳机。
\n它本是为了商务人士在办公场所使用而设计,收听耳机中声音的同时不会影响听取他人的谈话。它却被我这个耳道经常发炎的人看上了。
\n我想,中间带了个孔,那耳道内外的空气就是完全流通的,那耳道内肯定不会闷了吧!为了验证这一点,我特地去 Sony 的线下店体验这款耳机。佩戴上 LinkBuds,确实完全没有耳道被封闭的感觉,我便在网上欣然下单了。
\n然而,经过一段时间的佩戴,才发现我的想法过于天真:看似畅通无阻的气流,在直径不够大的圆环处有阻塞。内部产生的水蒸气无法排出,导致耳道仍然会有潮湿感。佩戴20分钟以上,摘下耳机放入耳机舱,一段时间后取出,会发现耳机舱内有冷凝水。
\nSony LinkBuds 仍然不能解决我耳朵发炎的困扰。我便又踏上了寻找新耳机的旅途。
\n今天便是要介绍我找到的其中一款,有望成为最终解决方案的耳机 —— SoundCore C30i。
\n
\n设计
\n
\n耳机充电盒的外观,以及开盖后的样子如上图所示,就不多介绍了。
\n开放式耳机相对于入耳式、半入耳式耳机,设计上花样多了不少。我看到的所有款式中,大致可以分为两类:
\n\n使用挂钩悬挂在耳朵之后,将扬声器覆盖于耳道口,通过空气传导声音进入耳道的; \n夹在耳垂上,耳机本身不覆盖或仅部分覆盖耳道口,通过侧向开口将声音导向耳道的。 \n \nSoundCore C30i 属于后者。
\n
\n我选择的是透明外壳的款式,耳机内部结构清晰可见。
\n左边耳机的扬声器处有一个开口,这便是朝向耳道的声音出口。
\n右耳朝上的是耳机背面,金色的圆片是触控传感器。
\n佩戴感 夹耳的耳机其实还可以再分类。一种是本体柔软、有弹力,有点像悬挂在耳朵上的,将扬声器更多地覆盖于耳道口,以提升传音效率的耳机。
\nC30i 本体则不可以弯曲,采用虎口般的结构卡在耳垂上。在佩戴时需要找到耳垂比较薄的部分,将耳机从那里套入耳垂,再将耳机推向耳垂较厚的地方,就能牢固固定。看似还是有些松松垮垮,但因为耳机足够轻,不容易被甩掉。
\n我的感觉是,佩戴上没有什么异物感,但难以做到所谓“戴久了会忘记它的存在”这么夸张。
\n我尝试连续佩戴了两个多小时,期间耳朵没有任何被堵塞的感觉,且取下后耳朵没有丝毫的潮湿感。确实是完全的开放了!
\n音质 我的评价是:出奇的好。
\n因为是一种妥协,我对耳机的音质便不抱任何希望。实际听起来,虽然也有这非封闭式耳机缺乏低音等问题,其实音质相当不错。
\n
\n通过调整 EQ 配置,将高低频都拉高(经典两头高调法),效果更是好了许多!
\n我也听了一段博客,听清说话人的语音也没有任何困难。
\n多设备连接 我原本买的是飞利浦的一款开放式耳机,到手之后才发现耳机没有双设备连接的功能。我平常有在手机和电脑之间切换使用的需求,少了这个功能实在不能接受。
\nC30i 支持同时连接两台设备,可以在两台设备间无缝切换。
\n续航 商品说明上标明了耳机单次续航是 10 个小时,这应该是相当厉害的了。加上充电盒,总共可以提供 30 个小时的续航。
\n实际用下来,在 2 小时的使用后,耳机的电量没有明显的减少。续航应当是很强劲的。
\n缺点 目前我看到的一个缺点是,耳机缺少自动休眠功能。将充电盒打开,耳机便会连接所有设备,摘下后仍保持着连接。直到耳机放回充电盒,并盖上盖子后,耳机才会关机。
\n且耳机缺少佩戴检测,摘下后不会停止音乐播放。似乎耳夹式耳机比较少有这个功能。
\n这可能会抢占了一些设备的音频输出,不过不算是很大的缺点。
\n总结 SoundCore C30i 有效解决了我耳道容易发炎的痛点,音质不错,且有多设备连接的功能,满足了作为我主力使用的耳机的所有需求。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/adc5a61e.html",
+ "url": "https://blog.udon.eu.org/archives/adc5a61e.html",
+ "title": "日本之行-第二站-奈良",
+ "date_published": "2024-03-10T16:30:00.000Z",
+ "content_html": "写在前面 23年12月底,我踏上了前往日本的旅途。这是我第一次去日本,是我第一次出国旅行,且只身一人。
\n本次旅行为14天,我将分多个章节完成所有的游记,记录下我旅行中的点点滴滴、各种感想。这是第二章节,奈良之旅。
\n近铁、巴士与鹿
\n奈良距离大阪仅有 30km,乘坐近铁奈良线,摇摇晃晃一个多小时便能到达奈良。
\n这条线路有分区间准急、准急、急行、快速急行四个速度,每个速度停靠的站点数量不同,票价都相同的。如果想尽快到达奈良,乘坐快速急性是最好的选择。
\n这天,我纯凭心情来到近铁难波站,坐上了最近一班开往奈良的近铁,似乎是急行。
\n近铁的运行速度不是很快,叮叮当当地在楼房间穿行着。
\n在近铁奈良站,奈良线的终点站下了车,距离旅馆所在的奈良公园还有一段距离。由于拖着行李,我选择搭乘公交车。
\n公交车到站后会向站台一侧倾斜车身,方便乘客上下;上车的门并不固定,可以看车身的贴纸判断;司机会很耐心地等待所有人上车、落座之后才摆正车身、继续前进。
\n有一些线路是分段计费的,就需要在上车的时候领取一张整理卷,或者先刷一下卡,在下车的时候将钱和整理券一起投入投币机,或者刷一下公交卡就可以了。
\n在每一个座位的旁边都有一个按钮,如果下一站要下车,按一下就会提醒司机,并且在车头的显示屏上也会显示,车上的所有按钮都会亮起。在公交车到站停稳之前无需先起身,车子停稳后司机也会很耐心地等大家付钱、下车,还会一个一个道谢哦。
\n相比于地铁,公交的到站时间显得没有那么准确,但不会差太多。在地面运行的公交会受到交通的影响,等候乘客上下车、付钱有时候也会用掉不少的时间,能做到按照时间表运行已经相当厉害了。
\n
\n虽然在车上就有看到,果然很多啊 —— 奈良的鹿。
\n奈良的鹿是散养的,主要集中在奈良公园和春日大社。在奈良设有鹿苑,将受伤或者发情的鹿收养,待它们恢复正常再放回乡中散养。
\n从车站走到旅馆,一路上都有鹿儿好奇地打量着来自异国他乡的我。
\n每走一段距离,能看到提示观光客不要“挑逗”鹿儿的提示牌,有些鹿的性格比较差,会用头顶你,追着你不放的。好在大部分鹿都去掉了角,不至于顶伤人。
\n
\n今天的旅馆确实有些简陋 —— 私人活动空间仅有这么一小间,洗浴和方便都是公用的。不过旅店的氛围很好,有一个共用的客厅,客人们可以在这里吹暖气(冬天还是挺冷的)、喝饮料、聊天。
\n旅店的工作人员非常热情,替我办理好入住手续后,立刻拿出一张奈良公园的地图,用简单的中文(很厉害哦!)告诉我这附近有哪些好玩的地方。奈良一天的行程就是参考着这张地图安排的。
\n整理好行装,已是傍晚五点多。按照旅店工作人员以及 Google Maps 的说法,奈良公园里的大部分饮食店都要下班了!遂立刻出门,寻找晚饭的地点。
\n因为今天是周五吗,明明还有一些旅客在此游玩的,旅店对面的旅游纪念品店已经关门了!沿街打量了几家店铺,也都在收拾着大堂,准备打烊了,完全不像是会接客的样子。大危机,要没晚饭吃了,得走个几公里处了奈良公园,到达奈良市区才有便利店。
\n路过了一家同样是卖纪念品的商店,发现店内的空间还挺宽敞,也摆着一些桌椅,便问了一下老板还有没有饭吃。运气很好,这家店还没有打烊。
\n
\n可以选择的菜品并不是很多,大多数都是日本的家常菜。我选择了这道绝大部分和食餐馆都会有的家常菜 —— 親子丼。
\n从厨房门帘的缝隙向里望去,可以隐约看见老板在烹饪着饭菜。明明用得都是同样的工具和食材,有技艺的人做出来的东西就能端上桌卖钱呢。
\n蛋是我们所谓“半熟”的柔滑状态,鸡肉有一些小烧焦,主要的风味是自古以来人们就离不开的味道 —— 咸味。风味不能说突出,却很平和。它就是一碗很普通、很平常,此时此刻会出现在任意一张餐桌上的饭。
\n饱饭后,我根据地图的指引,前往东大寺的二月台,观赏日落之景。
\n
\n
\n
\n
\n只用手机相机无法捕捉暗光下的美景。
\n夕阳柔和而昏暗地照着二月台,四周一片静寂,只能听见洗手亭的潺潺水声。在日落后还停留在奈良公园的游客寥寥,大家都保持着绝对的安静,共享这片难得的静谧。
\n在台上驻足数分钟,我轻轻踏着脚步走下楼梯,沿着寺院的小路漫步着,离开了东大寺。
\n此时天已完全黑了,不论是寺院还是神社都已关门,我就这么在奈良公园里漫步着。时不时碰见一群鹿,便从口袋中掏出一块鹿仙贝,掰给鹿儿吃。
\n
\n会旅店前在路边的售货机买了一瓶果汁饮料,是不二家的。日本的饮料会标注果汁含量,我觉得很神奇。
\n
\n第二天,去参观了十分出名的春日大社。不过不恰巧,主殿正在维修,将赛钱箱设置在了原处,只能眺望主殿。
\n在巫女那儿买了点纪念商品,是两只鹿型的小玩偶。一只是木质的,一只是陶制的,鹿儿的嘴中叼着签。忘记留下照片了。
\n
\n离开主殿,走上铺着碎石的小道。
\n
\n每走几步,便会出现一间迷你神社,感觉十分奇妙。在这片林海中不知道供奉着多少的神明。
\n有些人也许是提前来做新年参拜,途中遇到的神社都会十分虔诚地参拜。
\n在林间漫步了将近一个小时,吸饱了新鲜的空气,我振奋精神,回旅馆取了行李,准备离开奈良。
\n
\n拖着行李箱漫步在奈良公园里,我又一次路过了若草山。
\n若草山也不高,在满是丘陵的福建甚至都算不上山,只是个小土坡。山上的草长得格外整齐,是一座越看越顺眼的山呢。
\n若草山每年12月初开始封山,直到第二年三月举行烧山仪式后才重新开放。没能上去走走真是可惜。
\n
\n在前往近铁奈良站离开奈良前,我路过了一家在网上有着不少讨论的柿叶寿司店,便决定在这里解决午饭。
\n柿叶寿司其实就是用柿子叶将寿司包起来烹饪。据说柿子叶不仅可以杀菌消毒,还能给寿司提升风味。
\n想着,既然是奈良的特色菜,肯定要好好品尝一下。我点了一份 2500円 的大套餐,奢侈一下~
\n左下角的五枚寿司,上方两枚便是柿叶寿司了。下方三角形的寿司也是用不能吃的叶子包着,但好像不是柿子叶;右侧是用腌制过的紫苏叶包的寿司,紫苏叶是可以吃的,风味很独特,我很喜欢;左侧是茶味的卷寿司,也是第一次吃。
\n柿叶寿司用的是腌制过的鱼,确实带有叶子的香味,很有特色!
\n除此之外,套餐内还有精致的小菜、一份天妇罗拼盘和一碗素面。吃的我好饱~
\n
\n乘坐上前往京都的特急电车时还发生了一些小插曲。
\n坐上车后我才意识到自己坐的是特急的电车,进站时我只刷了基本票,没有购买特急卷。
\n好在列车上有通过扫描二维码购买特急卷的渠道,也支持借记卡付款。在等待列车发车时,我赶紧买了特急卷,带着行李移动到了指定座位,正式踏上前往京都的旅途。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/a7050149.html",
+ "url": "https://blog.udon.eu.org/archives/a7050149.html",
+ "title": "23 年对我影响最大的硬件与软件",
+ "date_published": "2024-03-02T12:30:00.000Z",
+ "content_html": "原文投稿在自留地频道的新年活动里。
\n对于我来说,23 年给我带来影响最大的莫过于 Meta Quest 3 与 VRChat 了。
\nQuest 3
\nQuest 3 在 23 年 6 月面世,于 9 月 28 日开放预购。我早在数年前就有了尝试 VR 的念头,在经过一天的调查研究后确定了 Quest 3 不会踩坑,便在日亚下了单,直邮回国。日亚上标价为 78k 円,日本商品出口不收税,再加上直邮的运费和中国的关税,最后到手的价格和标价差不多。(顺便一提,现在日亚也可以以相同的价格直邮国内)
\n外观我就不展示了,网上的图很多。到手第一件事,便是尝试它对于上一代产品相比提升最大的地方——显示与穿透(Pass Through)。
\n显示效果 显示效果上,Quest 3 采用了 Pancake 棱镜,甜点位置(Sweet Spot)相对于上一代更大,也就是说仅需简单调整头戴的位置,便能找到画面最为清晰的位置,我的使用体验上感觉也是如此,每次佩戴上头显很快就能调整好画面。
\n穿透 (Pass Through)
\n穿透方面,由于我没有用过 Quest 2、Quest Pro 等拥有穿透功能的 VR 头显,无法进行比较。图片中看起来比较糊,是因为我用了 Quest 自带的截图功能,分辨率很低。实际观感上,在光线充足的环境中可以毫无压力地辨认各种物体,但细小的字是看不清楚的。戴着 Quest 使用手机并不是很舒服,因为物体距离头显过近会因摄像头视角的问题产生扭曲,看字会很吃力。将手机拿远,又会因为显示清晰度不够而看不清字。不过听说 Quest 2 的穿透是黑白且十分模糊,Quest Pro 也好不了很多,Quest 3 在这个方面应是有十足的进步。(似乎离 avp 还有很大的差距)
\n \n相比于头显中不带有处理芯片的 VR 眼镜,Quest 可以使用无线串流软件将 PCVR 的画面投影至头显中显示,再也不用担心玩游戏时绊倒数据线了。
\n总而言之,Quest 3 是一副功能完善,各方面实力均不弱的“六边形战士”,但毕竟定位是廉价头显,也就缺少了 Quest Pro 的自定位手柄、眼部追踪,Valve Index 的基站定位、手部追踪,更没有 Apple Vision Pro 的高 ppi 显示屏、低延迟的穿透与精准的手势识别。但综合来看,Quest 3 的性价比高,适合刚刚步入虚拟世界,想要体验各式各样 VR 游戏的人购买。
\nVRChat 谈到 VR 游戏,有人会想到 Half-Life:Alyx,有人会说起(已经凉掉的) Beat Saber。根据 SteamDB 的数据,此刻在线数量最多的 VR 端游戏还得是 VRChat(主要为 PC 端的战争雷霆不算)。
\n在我看来,VRChat 里有大概有四类人。
\n第一类人:虚拟世界的旅行家
\n有些人想“逃避现实”,来到虚拟世界欣赏美景、转换心情。在 VRChat 里有各种各样风格的地图:有些风景大好、音乐舒缓,适合在快节奏的生活之余找到一个宁静之地放松紧绷的精神;有的地图灯红酒绿,如果在夜晚你还是激情满满,不妨来这里听听虚拟 DJ 的表演,大家一起蹦迪、饮酒;甚至还有环境昏暗、一片寂静,十分适合睡眠休息的卧室地图,虽然深处异地,也能在虚拟世界里与好友共眠,在清晨醒来的那刻发现自己的眼前并不是早已习惯了的天花板,耳边是仍在熟睡的友人的呼吸声,将会是一种全新的体验。
\n第二类人:虚拟世界的摄影师
\n也有些人想在虚拟世界里做一个摄影师。美景的照片中怎么能少了美人,如果现实中找不到美人,那就自己来扮吧(心死)。VRChat 中使用的人物模型可以由玩家自行上传,如果你恰巧会使用 Unity 与 Blender,便可以为自己量身定制一个人物模型;如果不会也没有关系,在 Booth 上有许多预制好的人物模型与衣服,只要按照教程将其组合,便可打造出你心目中的理想形象(不管男女)。为了拍出满意的照片,你会主动去学习各种各样的新技能:为了调整照片的后期效果,我就学习了 Lightroom 的使用。
\n第三类人:人,不过是在虚拟世界里
\n这或许才是 VRChat 的核心内容 —— 当然是和朋友聊天啦。在 VRChat 中有一些专门用于聊天、交友的地图,例如以中文为主的“中文吧”;也有一些比较热门的地图会聚集起各个语言的人群一起聊天,例如“Japan Shrine[spring]”,一张风景优美的日本神社地图;更有各种各样以个人或小团体主办的咖啡厅、运动吧、跳舞房、游戏房等着你来加入。也许你和我一样有些小“社恐”,在现实生活中不大善于和陌生人交际,不妨试试在 VRChat 中,与素未谋面但已经熟络的朋友,或是围坐在篝火旁闲聊,或是在电闪雷鸣、乌云滚滚的夜空中乘坐飞机探险,相信这对你一定会给你带来从所未有的新鲜感。
\n第四类人:OOOO 还有一类是搞色色的,就不多说了。
\n以上只是从我的眼中看到的 VRChat。一千个人的心中有一千个哈姆雷特,你的 VRChat 又将会是什么样的呢?
\n \n自从 23 年 10 月 3 日加入 VRChat 以来,我学会了怎么修改模型、如何使用全身追踪让自己在游戏中灵活运动;尝试在跳舞房里学习舞蹈,在 KTV 房里与朋友唱歌,与来自全球的人使用中、英、日闲聊;在 VRChat 中结交了新的朋友,在 X 上发布了拍摄的照片。VRChat 确确实实已经融入了我的生活,仿佛在另一个世界塑造了另一个不同的我。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/a534d51a.html",
+ "url": "https://blog.udon.eu.org/archives/a534d51a.html",
+ "title": "旅行与军粮",
+ "date_published": "2024-02-12T14:30:00.000Z",
+ "content_html": "军粮的特点 1. 便携 一般来说,军粮都会被较为紧凑地包装,以便于大批量运输与随身携带。如确认将有一餐饭需要在外解决,可以在出发时往包里塞上一份军粮。
\n2. 分量足 军粮的营养构成一般都是按照一个运动量中上的成年男性一餐所需要的卡路里来设计的。因此,即使不能完全饱腹,其所含的营养足够填补在外游玩半天空空的肚子了。
\n3. 便于烹饪 相比于需要加入开水的方便食品、甚至是冻干食品,军粮一般自带加热设备,仅需冷水,甚至不需要水都可以加热食物。若是去荒郊野岭的地方露营,除非有携带便携式汽炉,想获取到热水是比较难的,这时候有自加热的军粮会很方便。
\n俄罗斯单兵口粮普餐
\n这次买到的6号餐谱,包含五包干粮、牛肉丸、牛肉荞麦饭、牛肉炖豆角、牛肝酱、午餐肉、蔬菜丁罐头以及饮料、果酱、零食等散件,还有一套由三个燃料药丸、一个铝制支架和几支防风火柴组成的明火加热套装。
\n
\n旅行的第一天是登山,午餐便携带了部分的食物作为午餐。
\n有些小雨,便找了一处有雨遮的地方开始准备午饭。
\n
\n将铝板弯曲成炉子的形状,在炉子的中央放上燃料药丸,再用防风火柴点燃,便是一个功能完备的加热装置。
\n小贴士,我试着用火柴从上方点燃药丸,尝试了两次都失败了。将火柴放在炉子里,然后将药丸放在火柴上,便能很轻易地将其点燃。
\n
\n点燃固体燃料后,将盛有食物的铝制罐子放在火上直接加热即可。如果有带铝或者钛制的杯子,也可以直接放在火上烧水,泡些饮料喝。
\n
\n在等待罐头加热时,我先掏出了干粮与蘸酱。除了照片里的芝士与肝酱,还有一包苹果果酱。
\n芝士的味道与常见的芝士片如出一辙,在我看来稍微淡了些,因此也比较适合直接吃。
\n牛肝酱则是特别的腥,单独吃我有些吃不来,和着芝士与果酱吃味道倒是还不错。
\n拿出一片干粮(其实就是苏打饼干),涂上果酱,挖一勺芝士,再放上一点点牛肝酱,一口塞进嘴里,味道还算不错。
\n饼干有些干硬,嚼起来有些费劲儿,需要配水。
\n最后,完吃完了一份饼干、一盒芝士和一包果酱,牛肝酱剩下了一大半。
\n
\n接下来是牛肉丸。看到红色的外表就能猜到是罗宋汤风味的。
\n应该是罐藏的缘故吧,牛肉已经泡得很软了,吸收了罗宋汤酸酸甜甜的风味,味道还算不错。就是牛肉味已经不是很浓,能吃得出来是肉,但没有什么特殊的肉的风味。
\n顺便一提,用这种炉子加热,铝盒子受热举起不均匀,中间已经滚烫,但四周还是冰的,需要多多翻搅。
\n
\n从外表看不出来里面有啥,红红的肯定也是罗宋汤的味道。里头的蔬菜主要是胡萝卜,也有青椒、青豆等蔬菜。但这一碗的味道就不好了,青椒的味道和西红柿(或者是甜菜)的酸甜并不搭。
\n把饼干蘸着牛肉丸的汤汁吃,味道也不错。涂涂果酱、涂涂芝士,饼干便吃完了一包。
\n又吃了点麻麻那边的自热口粮,便已饱腹,我的食量确实不大啊。
\n
\n这一餐其实只吃了整套餐的 1/3 左右,剩下的量再吃两次正餐不成问题。
\n回到家后,又拿出了些零食品尝了一下。
\n
\n来自俄罗斯的巧克力棒,偏甜,一股代可可脂的廉价感,属于不大好吃的巧克力。
\n
\n右上角是速溶咖啡,右下角是奶粉,左边的一大包是糖。糖的量很大,明显不止是一次饮料的量,也可以放在茶里喝。
\n咖啡很苦,一股烧焦味,不好喝。奶粉大概也是植脂末吧,没什么奶味。
\n剩下的食品我分成了两餐解决。
\n
\n第一餐的搭配是:牛肉荞麦饭、肥肉罐头和干粮(饼干)。
\n
\n也许我加热的还不够,但考虑到在野地里使用便携式炉子加热的能力,士兵们能加热到中间完全热乎,周围有些凉是平均水平了。荞麦饭很硬,风味也不是很好,除了咸味和一点牛肉味,尝不出别的滋味了。加了一些套餐内的黑胡椒粉,才改善了一些风味。
\n不过这一大碗饭确实很能填饱肚子,适合放在午餐食用。
\n肥肉罐头里自然是盐腌风味的很肥的肉啦。味道我还挺喜欢的,肥肥的肉很好吃,十分下饭。
\n
\n第二餐的搭配是:牛肉煮豆子、午餐肉、苹果泥、酱牛肉(来自国产 MRE)和干粮(饼干)。
\n
\n牛肉煮豆子用的会是什么豆呢,豌豆吗?啊,原来是黄豆。
\n并没有延续牛肉丸、蔬菜罐头的罗宋汤风味,只是单纯的咸味,不过味道我还挺喜欢的。
\n牛肉其实不多,就一大块带筋、软烂的牛肉,剩下都是碎块。除了黄豆外还有一些胡萝卜。
\n午餐肉很小一罐,但确实是肉泥,淀粉含量并不多,味道也不错。
\n苹果泥是我整个套餐中最喜欢的一样食物了。口感绵密,酸甜度适中,汁水十足。配合干粮一起吃,很大地改善了干粮过干、过硬的缺点。而且好大一罐,吃得很满足。使得这一餐中我干掉了两袋干粮。
\n国内的军粮 —— 以北戴河的自热口粮为例 09式单兵自热口粮我吃过几份,13式这种两餐包装在一起的也吃过一次。
\n这次选购的是北戴河生产的自热口粮。虽然不是军品,但北戴河前身是军工厂,产品会比较接近军品吧。包装风格与09式也很相像。
\n
\n那天感冒的发烧刚退,人还比较虚弱。住的度假酒店位置比较偏,不想跑太远去吃晚饭,便取了车上的自热口粮回房间吃。
\n我吃过太多次了,便没有拍照片,下面就口述一下感受。
\n相比于09式单兵自热口粮,北戴河出品的民用口粮内容少了很多 —— 没有了耐贮蛋糕、糖水黄桃/菠萝、调味辣椒酱和固体饮料,只有一份主食,一份配菜(酱牛肉和午餐肉二选一)和一份小菜(榨菜)。
\n09式的餐谱相当多样,有 12 个餐谱,有炒面也有炒饭,也有一些主食是素食的,满足部分人群的需求。
\n北戴河的仅有三种餐谱,且都是荤食的炒饭。
\n主食包外套着加热袋,用注水袋取对应量的水(生水、脏水皆可)倒入加热包即可完成主食的加热。
\n味道上,我认为北戴河做得是不如当年的09式的。饭总体都偏咸,但肉给的十分足。
\n但相对于俄罗斯的军粮,不知道是口味上有着主场的优势,还是俄罗斯人烹饪技术真的不佳,还是中国的军粮更胜一筹。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/7777583b.html",
+ "url": "https://blog.udon.eu.org/archives/7777583b.html",
+ "title": "被我整坏的路由器和服务器",
+ "date_published": "2024-02-12T10:00:00.000Z",
+ "content_html": "为了搭建 PalWorld 的私服,我又一次踏上了折腾之旅,并成功将路由器和家里的服务器都搞崩了。
\n好在最后两台设备都恢复如初。我就想来做一下这次“事故”的复盘。
\n我的设备 本次操作的有两台设备,一台是 x86 软路由,运行 Openwrt;一台是配置比较奇怪的 x86 服务器,运行 PVE。
\nOpenwrt 的网络隔离配置 说在前头,我对 Openwrt 的操作真的是一窍不通。网络相关的知识兴许懂一些,但一到实操环境就纰漏百出了。
\n由于 PalWorld 服务端启动后便会在游戏的服务器列表公开自己的 IP,且无法关闭这个功能,有比较大的安全隐患。在配置好 PalWorld 服务端所运行的虚拟机后,我决定将这台虚拟机的网络与局域网内其他设备隔离。这也是所有折腾的起源。
\n配置 VLAN 导致的失联 我能想到的第一个方案便是,给这台虚拟机分配一个与当前局域网不同的网段,并将两个网段隔离。
\nVLAN 我还是知道的,便着手开始创建新的 VLAN,并保存…连不上路由器了。
\n急了啊,创建了 VLAN 就代表开启网桥的 VLAN 过滤。目前这个 VLAN 没有分配给任何一个接口,自然是啥都连不上了。好在 Openwrt 有自动回滚的功能,90 秒若设备连不上路由器,便会将刚刚的更改回滚…但不是每次都能成功。
\n有反复折腾了几次:创建 VLAN、创建接口、创建防火墙的 Zone,分配来分配去,中途也搞砸了几次,配置也都回滚了,直到最后一次尝试,luci 弹出了令人感到安心的“正在回滚配置”的提示,然后就…卡住了。
\n考虑到 Openwrt 使用的是 squashfs,怀疑是设备上的设置确实回滚了,但内存中的配置没能正确地回去,我将设备断电重启。果不其然,配置回到了应用前的样子。
\n在尝试配置 VLAN 数次后,我放弃了。即使给虚拟机分配了新网段,也设置好了防火墙规则,虚拟机依旧可以访问到内网网段的设备。
\n目前我还没有闲心思慢慢研究 Openwrt 的种种配置,便打算换一个方式实现。
\n配置 PVE 防火墙 第二个方案便是,在 PVE 的防火墙中禁止虚拟机连接内部网段的其他设备了。
\n启动 Datacenter 防火墙但没有添加允许规则导致的失联 我启用了虚拟机的防火墙,发现配置并没有生效。简单查询后发现,需要将 Datacenter 的防火墙启用,VM 防火墙才有效果。我便看也没看地就开启了 Datacenter 防火墙,发现管理后台页面无法刷新了。此时我才看到屏幕上 Datacenter 防火墙的默认配置 —— IN: DROP.
\n这下好了,外部连接统统被阻断,也就无谈通过控制面板将防火墙再关闭。
\n这台服务器是无头的,安装有一张 P100 显卡,但没有显示输出。所以在不动硬件的情况下,我没法通过显示器访问终端的。
\n通过检索,我了解到了好几种奇技淫巧来关闭 PVE 的防火墙。
\n挂载并修改 crontab 正好手上有一个硬盘盒,我就将系统盘取下,通过硬盘盒连接到了软路由,尝试修改系统盘里的文件。
\n使用 fdisk
查看这块系统盘的分区情况,但没有看到熟悉的 ext4
字样。取而代之的是 Linux LVM
。LVM 相当于是 Linux 对磁盘进行了再一次的分区,因此挂载就不能是简单的 mount
,得用 lvm2
工具。
\n再敲了几个命令后,我成功将 PVE 分区的 root
文件夹挂载,并在 crontab 中添加了关闭 PVE 防火墙相关的命令。
\numount
,取下系统盘并装回服务器,开机…没有任何反应,依旧打不开管理面板。是教程给的方法有误吗?
\n遂又取下盘,挂载到软路由上,却提示该分区忙…我是相当忌惮挂载并修改分区的,生怕损坏了分区,服务器的数据可就全丢了。不敢再继续操作,我得另想出路。
\n连接显示器,但还是个瞎子 现在能做的就是在服务器上通过 PVE 的 rescue terminal,来修改防火墙的配置了。
\n我关闭了服务器,取下 P100,换上了 R7 240 这张十年老兵,插上了便携显示器…
\n\nerror: No suitable video mode found. Booting in blind mode.
\n \n你这不是输出字了吗,怎么就进瞎子模式了???
\n经过查询,我了解到系统正在寻找到显示模式,正是古早电脑终端使用的 80-Column
这样的显示模式。
\n至于什么是 Blind Mode,我尝试在 GRUB 和 Linux 源码中搜索,都没有结果;为什么 PVE 系统找不到我的 R7 240,可能是缺少驱动吧,现在也无处知晓。
\n按照网上的教程尝试在 GRUB 引导系统启动时添加显示模式的支持,并没有效果;当我尝试打印支持的显示模式时,发现根本不存在正常的显示模式,故只能放弃直接启动 PVE 的 rescue terminal.
\n还是得靠救援盘 给服务器插上救援盘,我先是打开了基于 Windows 的 PE 系统,发现显卡是有输出的,但分辨率非常低,且只有黑白画面。看来这张老显卡与这块寨板的相性真的不大好。
\n在确认 Windows PE 系统下什么都做不了,操作还及其不便,我便退出了 PE 系统,打开了 Ubuntu LiveCD。
\n这回,显卡倒是可以正常运行。以 80-Column
模式输出文字还是可以轻松办到的。有了命令行,操作也简单了不少。
\n同为 Linux,操作 LVM 就简单了许多。使用 lvm2
挂载 PVE 的系统分区,并用 chroot
将用户空间切换至 PVE 系统,直接用 systemctl disable pve-firewall
把防火墙关了。
\n再次进入 PVE,这下开机防火墙就不会启动,赶紧进 Datacenter 防火墙设置里还原误操作的配置。
\n事后的反思 VLAN 配置的失误 我对 VLAN 配置没有经验,便想着参考网上其他人的配置方法来做。但我查到的都是创建访客 Wi-Fi 这类的配置教程。与我的网络环境的差异在于,访客 Wi-Fi 用的 Interface 与 LAN 不一样,而我需要在 LAN 下配置两个 VLAN,配置方法就有些不同,不能直接搬配置。
\n我应当要找的是较为通用的,同一个 Interface 下 VLAN 的配置教程(可能单臂路由配置和这个就有些像),再迁移到 Openwrt 上进行配置。
\n由于软路由只有两个网口,也没法像有四五个网口的路由器一样留下一个作为”不死“的管理口,以降低把路由器配置挂掉的风险。
\nPVE 原本可以修得更快 原本在将 PVE 系统盘挂载到软路由时,便可以用 systemd
停掉 pve-firewall
但当时我看到 PVE 论坛里有人说在 crontab 里加上 PVE 关闭防火墙的指令便能访问控制面板了,也有人附和说可以用。但我实际操作后发现并没有效果。
\n而再次尝试挂载 LVM 时,提示分区忙让我不敢继续操作。虽然在服务器上用 LiveCD 直接挂载并没有出现分区忙的情况。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/aca48136.html",
+ "url": "https://blog.udon.eu.org/archives/aca48136.html",
+ "title": "日本之行-第一站-大阪",
+ "date_published": "2024-01-21T15:00:00.000Z",
+ "content_html": "写在前面 23年12月底,我踏上了前往日本的旅途。这是我第一次去日本,是我第一次出国旅行,且只身一人。
\n本次旅行为14天,我将分多个章节完成所有的游记,记录下我旅行中的点点滴滴、各种感想。这是第一章节,出境与大阪之旅。
\n第一次出境 我从上海浦东机场出境。
\n航班预计上午 9:40 起飞,根据网上的经验教训,国际航班建议提早三个小时到达机场办理值机手续。我早早地起了床,搭上了第一班磁浮列车,在六点五十分左右到达了机场。
\n拖着行李箱直奔值机柜台,发现队伍已经很长。排了一个多小时的队才轮到我。
\n我提前在网上进行了值机选座,现场只需要将行李箱托运,打印一下登机牌。工作人员会确认护照和日本 eVisa(电子签证)。
\n我提前将电子产品和需要独立安检的物品取出,安检也十分顺利。
\n不过在过海关的时候,工作人员看到我护照崭新,又是独自一人,不免有些担心,向我索取了酒店预订记录和行程单。酒店预订记录只要出示订购软件的订单界面即可;我没有做行程单,就将记事本里的旅行计划给工作人员看,第一天在哪里啊,过两天又跑去哪里玩…得知我全程的酒店都已订好,行程也安排清楚,便允许我出境了。
\n在候机厅的长椅上坐下时,距离飞机的预定起飞时间只有半个小时了。看来国际航班确实需要早点来机场办手续(虽然最后延误了两个小时)。
\n这是我第二次坐飞机。上一次是我还在读小学的时候,不明不白地上了飞机、又下了飞机,除了耳朵有些不舒服。没有其他的感想。
\n这次,我特地选择了靠窗的位置,仔细观察着飞机的起降与飞行时的动作。看到了起飞和降落时襟翼的展开,看到了遇到乱流时机翼“夸张”的摆动,感叹着这种看似“简易”的机器是如何将一机人送上天。
\n
\n飞机餐的话…上一次坐飞机是国内航线,没有在饭点所以没有供餐,这是我第一次体验飞机餐。
\n
\n没有选择的余地,大家统一都是鱼肉米饭套餐。和我能想象到的飞机餐没有太多区别 —— 一般咸味的鱼肉,煮得软烂的米饭(也可能是再加热的缘故)与味道平平的配菜。坐的毕竟是经济舱,不能期待太高~
\n
\n经过两个半小时的飞行,飞机平稳降落在了关西国际机场。飞机缓慢驶向空桥的途中,我更换了日本的流量卡,给手表切换了时区。
\n入境相对出境更加简单。出发前我就在网页中预先填写好了入境信息表与海关申报表,在对应的窗口或机器扫码即可完成申报。在入境窗口只需出示一下 eVisa,录入一下人脸和指纹就可以了。
\n工作人员会用英语引导你操作,录入机器上也有中文提示。机场的指示牌都是四语的(日语、英文、中文和韩文),按照指示牌走就没有问题。
\n大阪之行 Day 1 - 舒适的酒店与海游馆之旅 APA 酒店 起飞延迟了两个小时,降落也迟了一个多小时。达到关西国际机场已是下午 3:30(这之后都是 UTC+9 时间)。我乘坐南海机场线前往难波站附近的 APA 酒店。
\n插一嘴,在日本用 Google Maps 相当方便,交通工具的规划和信息展示都做得很棒。“通勤案内”这款官方推荐的交通软件我也有下载,不过用的最多的还是 Google Maps。
\n等地铁、电车、新干线时,只要看 Google Maps 上的到站时间,到了哪辆车就上哪辆车。除非两个方向的车同时到站,否则是绝对不会坐错方向的。
\n
\nAPA 酒店大楼的装修很有特色,在远处就能看到橙色的屋顶和 APA 的标志,而且在全国的风格都是统一的。
\n
\n内部的装横令我十分惊喜:房间不是很大,但各种设施一应俱全,在床铺正对的墙上甚至有一台大屏电视。浴室毫不意外地配备了浴缸,还有定量放水系统,不用盯着浴缸有没有水漫金山了。寝具也相当的舒服,那两晚都睡得很好。
\n对于一间一晚 300CNY 左右的旅馆,能有这样的体验我十分满意。
\n咖喱与牛排 办好入住手续,放下行李,已是五点多。飞机餐的显然填不饱我的肚子,我早已饥肠辘辘,是时候出门觅食了。
\n大阪海游馆是我计划中必去的一站,但它位于海港村,和其他景点在相反的方向,便安排在今晚游玩了。
\n日本人很奇特的一个习惯出现了:有些店铺傍晚 6:00 到 6:30 就收摊了,最晚的会开到七点八点左右,再迟就只剩下营业到深夜的家庭餐厅与居酒屋。
\n用“食べログ (tabelog)”这款软件在海游馆附近搜索评价比较高的店铺。
\n在海游馆隔壁有一个小小的综合体,里面有一些店铺还有在营业。我选择了这家评价很高的牛排咖喱餐馆。
\n
\n一份牛排咖喱饭、一碟酱菜、一碗味增汤。价格有些忘记了,在 1500円 左右。
\n日本人很喜欢吃这种细长的青椒,不会辣,在天妇罗中也会出现。
\n咖喱的甜口的,味道自然比百梦多咖喱块要好上数倍。牛肉很嫩,没有怎么调味,展现的是肉本身的鲜味。总之,相当的美味。
\n对了,需要使用现金哦~ 商家没有准备 POS 机。
\n大阪海游馆 已经记不清上一次去水族馆是多少年前。听说大阪海游馆的设施很棒,场馆也很大,我就来体验一下。
\n门票的价格是 2600 円,入场有分时段,我就在售票处购买了当前时段的票。
\n海游馆很大,步行参观的总距离在 1km 左右,按照地理位置划分成了好几个区域。
\n
\n进入场馆便是一个巨大的拱形水箱。是玻璃比较薄吗,还是用了什么技术,几乎看不见玻璃带来的重影,鱼仿佛真的在空中悬浮。
\n
\n一群正在睡觉的海狮。抬着头睡觉不会落枕么。
\n![巨型水箱]/images/2024-01-21/(9.jpeg)
\n在场馆的中央有一个巨大的水箱,有两头鲸鲨、几头锤头鲨和许多小鱼生活在其中。
\n
\n还有好多可爱的花园鳗,黄色的尤其可爱~
\n除此之外,还有来自各个大洲的鱼、南极的企鹅,在地下还有水母馆。
\n总共逛了一个多小时,可以说大饱眼福了。
\n
\n在海游馆的隔壁是天保山摩天轮,夜晚被彩色的灯光照亮特别好看。
\n此时已是 19:30,经历了一天奔波的我有些劳累,便回到酒店养精蓄然,计划第二天的行程。
\nDay 2 - 天守阁、天满宫、天筋桥与大阪烧
\n在日本的第一顿早餐,在酒店隔壁的参观享用了自助餐。
\n炒蛋和上次去香港在澳洲牛奶公司吃到的有点像,并没有完全做熟,非常的软嫩~
\n茶泡饭就很有日本的特色了。
\n饭后,我就前往难波站附近的旅游中心,购买大阪周游卡。我选择的是二日卡,价格是 3600 円。在两天内,可以免费乘坐大阪地铁与巴士(相较于一日卡,不能乘坐私铁),以及免费参观好多景点。
\n大阪地铁网络十分发达,基本覆盖了所有想去的地方,这两天我没有搭乘过私铁或者巴士,因此不用担心是否要选择一日卡。
\n买到卡之后,第一站便是天守阁。
\n
\n从外边看,与只狼里的苇名城有几分相像。
\n
\n天守阁内陈列着许多颇有历史的物件,只可惜我对日本的历史并不熟悉,也看不太懂书法家的笔墨,只是走马观花感受一下文化的氛围。
\n
\n最后在楼顶吹了吹风,便离开了天守阁。
\n
\n虽然有些冷,但天气真的很好,万里无云。下一站是天神筋桥,一个商业街。
\n
\n日本有很多 OOばし(桥)这样的地名呢。也有很多像天神筋桥这样,上方覆盖着遮雨棚的商业街,天神筋桥是其中最长的一条,从一丁目延伸到七丁目,光是主干就有 2.6 km,更有密密麻麻的小巷。
\n乘坐堺筋线,在天神筋桥三丁目下了车。我先是去拜访了天满宫。
\n
\n似乎内部在翻修,将赛钱箱放到了外边供大家参拜。
\n在这里,我给身边参加考研的人做了参拜,希望他们可以拿到好成绩。
\n接着,我便从二丁目开始,一路边逛边思考着午饭的去处,寻找着吃饭的店铺。
\n沿途,看到了大排长龙的可乐饼摊子,转了一圈再想回来买,发现已经卖完收摊了。有一家天妇罗的店门口也站着好多人,大排长龙。肚子好饿,肯定排不了这么长的队。
\n边走边用 食べログ 搜索着。来大阪就得吃些有大阪特色的,那就是大阪烧了。
\n
\n并不在主街,而是藏在小巷子里的千草大阪烧,似乎是 食べログ 23年的百大名店呢。
\n我选择了以店铺名字所称的招牌菜 —— 千草大阪烧。
\n核心是一大片厚切的猪肉,在上下两面都倒上面粉与卷心菜混合的泥,便开始煎烤。
\n接待我的服务员会一些简单的英语,告诉我等着他们来翻面就好了。当地人也许会选择以自己的喜好来摊大阪烧,我作为门外汉只要静静欣赏就好。
\n当两面都煎至金黄,便会涂上大阪烧酱、沙拉酱和黄芥末酱,撒上不知道是什么的籽,就可以享用。
\n大阪烧整体的口感是软糯的,夹心的猪肉排很嫩,肉汁十足。
\n唯一可惜的是,量实在有些少,填不饱我的肚子啊~
\n
\n饭后继续在天神筋桥闲逛,发现了一家鲷鱼烧店。我还以为鲷鱼烧是软软的,但实际外壳是偏脆的,甜甜的红豆馅十分美味。
\n今天真的走了好久的路,对于第一次来到异国他乡的人可以说是有些得意忘形。从天神筋桥二丁目逛到六丁目,又折返了回来。中途还去 melonbooks 看了一圈,又跑到周游卡可以免门票的天王寺动物园里逛了逛…还没逛完,人就开始有些不舒服了。
\n在动物园里稍微休息了一下,我还是决定吃完晚饭就回酒店休息。
\n
\n一次比较“失败”的体验。不要误会,寿司还是很好吃的,鱼类十分新鲜,但我一不小心就在 iPad 上点了太多的寿司,都吃进肚子之后已经很饱了,完全没有在回转的转盘上取过寿司!这和普通的寿司店不就没差别了吗!
\n
\n回转寿司店出门便是热闹的道顿堀,然而我不是喜好这一口的人。路过蟹道乐,周围停着好几台旅游大巴,店门口密密麻麻的全部都是在等待的游客,大约有数百人。真是疯狂呐。
\n受不住喧嚣,我在附近的药店买了一支体温计和一盒退烧药,便回了酒店。
\n躺床休息了一阵子再测体温已经正常,看来真的是疲劳导致的体温失调。之后的旅途安排就宽松一些吧。
\nDay 3 - 梅田蓝天大厦、天空美术馆与一兰拉面 睡了一个好觉,但人还是有些疲劳。前往异国他乡果然不能过于放肆,得做好身体的管理。今天就悠闲地度过吧!
\n
\n在酒店寄存行李后,搭乘地铁来到了梅田蓝天大厦。从三楼有快速电梯可以通往顶楼,然后乘坐扶梯来到屋顶的圆形观景台。
\n对了,有周游卡门票免费哦~
\n
\n
\n相比于其他观景塔,梅田蓝天大厦不算很高,但好在有开放式的观景台,视野特别棒。今天风有些大,在楼顶不是很站得住,欣赏了几分钟美景便回到了室内,点了一杯咖啡,坐在窗边静静欣赏着窗外景色。
\n梅田蓝天大厦外侧是镜面玻璃,倒映着天空、与天空融为一体,因此称作蓝天大厦。尼尔:机械纪元的开发公司白金工作室的总部就在这栋大楼里。
\n
\n
\n我还顺道参观了另一座楼的天空美术馆,虽然不怎么懂艺术,但画作依旧能感染我。
\n参观美术馆后,已是正午。该去找吃的了~
\n
\n百闻不如一见,我来品尝一兰了。
\n每个人第一次来吃日式拉面都要面临一场“大考” —— 单应该怎么点。(背面有中文,我填完了才发现)
\n除了在点餐机器上确认要吃东西,在排位时服务员会给你一张纸片,让你选择拉面的喜好。
\n浓郁度我选择了加浓,确实有些过浓过咸了。吃着相当地过瘾,豚骨的香味充满嘴巴,真的很幸福。如果平常吃得比较清淡,正常的浓郁度可能就有些偏咸了,可以考虑减少一些浓郁度。
\n油脂的丰富度我也加了一档,但感觉不是特别明显。可能是看到日本有一种表面铺满油渣的拉面,一兰的面在油脂的方面反而显得“寡淡”了吧。
\n其余的选项便是看个人喜好。一兰的辣椒粉不会很辣,加 1/2 倍感受不出辣味,但可以提升香味。
\n
\n交卷后,一碗一兰拉面,四片叉烧和一颗盐味溏心蛋上桌啦。
\n一兰号称在面出锅后15秒内就会传递到食客的面前,以体验拉面最新鲜的味道。我赶忙用手机拍了张照,便开始享用。
\n拉面和我之前吃过的感觉都不大一样,特别有筋道,麦香味也很浓。在浓厚的汤汁里蘸一下就有了豚骨的鲜味,没几口就把拉面吃完了,便又加了半份面。
\n上溏心蛋的时候同时给了个小碟子,如果蛋不好剥或者咸味不合适,可以让服务员帮忙换一个。
\n一口拉面,配上一口汤汁。再咬一口叉烧、一口糖心蛋,至福啊。
\n前往奈良 饭后,回酒店取了行李,便是去难波站坐近铁奈良线,前往奈良了。
\n一点感想 语言不是问题,打招呼、点菜这种比较简单的对话就用手势与塑料日语,更复杂的对话用英语就好了。碰到的酒店前台经理、便利店员工、车站管理员都是会英语的。再不济,就用翻译软件吧,能达意就行~
\n至于习惯问题,按照当地人的做法来做就好了。在公共场合,例如楼梯应该走哪个方向,等地铁的时候应当怎么站,在地铁上有哪些地方不能使用手机,都有明确的标识和多语言的提示。多留意,照着做,就不会有事儿啦。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/2ef8bd61.html",
+ "url": "https://blog.udon.eu.org/archives/2ef8bd61.html",
+ "title": "深圳-香港三日行",
+ "date_published": "2023-12-19T14:00:00.000Z",
+ "content_html": "出行原因 其实我很早就有去香港看看的念头,但有时间出游的机会并不多(机会很多,是我比较懒,更喜欢宅家),一直没去成。
\n前一段时间,在朋友的帮助下我注册了英国 Wise 账户,拿到了两张漂亮的实体卡,但后期如何入金成了个问题。了解到可以通过香港账户低损耗入金 Wise,我便有了前往香港开户的念头。
\n12 月底我将独自前往日本游玩,但这将是我第一次独自一人出去旅游。第一次单人旅行还是出国旅行,不禁让我有点担心。
\n在这两个背景的驱使下,我迅速定下了这次深圳-香港的旅行,既可以前去开户,也可以作为出国旅行的预演,提前暴露一些问题。
\n流水账 Day 1 - Thu - 深圳 第一天中午到的深圳。我在前往酒店的半路上下了地铁,前往名为“臻品鲜粿·粿条世家”的店铺吃午餐。
\n
\n粿条本身没有很特殊,有点细河粉的感觉。不过八成熟的牛肉非常非常的嫩,味道很棒。
\n
\n炸豆腐是赠送的,第一眼并太高的期望。但一口咬下,十分软绵的里芯呼之欲出,佐以略微有些咸的沾汁,味道很棒。
\n
\n我还点了一份长得有点像海蛎煎的东西,记不得名字了。应该是用油炸的,比海蛎煎更加酥脆。
\n美美地享用午饭后,我继续登上地铁,前往酒店。
\n下榻酒店后,我前去参观深圳世界之窗景区…总之就是很后悔。
\n比起人造景观,我还是更喜欢自然景观一些。世界之窗没有给我带来惊喜,只有满满的失望。今天是周四,许多游乐设施都没有开放,可以参观的仅有一个个微缩复原的建筑物。
\n在世界之窗转了约两个小时,我悻悻离去,前往凤凰楼食茶点晚餐。
\n
\n多春鱼的鱼籽很多,非常鲜美。
\n一个人出来吃饭确实有些不便。有些菜品一次只能点这么一大盘,不过还是美美的享用了。
\n
\n鲜虾烧麦就是吃鲜,特别的鲜甜。
\n
\n菜单上的字看得不是很懂,大概写的是炒肠粉吧,没想到上了这么大一盆长得也不像肠粉的东西。
\n吃了一下,似乎确实是切成一节一节的肠粉。味道不错,但量好大最后没吃完。
\n
\n饭后,我顶着吃撑的肚子徒步前往华强北,这片传说之地。可惜的是,大部分店铺八点就关门了,简单逛了几栋电子市场后我便回酒店计划第二天的行程。
\nDay 2 - Fri - HK 开户 一大早我便乘上了前往福田口岸的地铁。
\n7:30 左右的口岸没有什么人,大部分是赴港上学的学生,通关很快。
\n我在落马洲站选择乘坐 B1 双层巴士前往元朗区,开始了开户之旅。
\n沿途…说实话没有什么风光,元朗算是比较偏僻的地方,好风景还要等到第二天的香港岛之行。
\n8:00 我到达了中行门口,发现只有前来上班的员工,还没有来排队的人。如果希望第一个办理业务的朋友可以选择这个点就来等候。时间还早,我就去吃了个早餐,回来发现已经有两个人在排队,便加入了队伍。
\n9:00 准时进入了银行,取到了号。在我之前有提前预约的人,我排在了第 5 号。
\n在工作人员的引导下使用手机填写了信息,然后等待柜员办理剩下的手续。共有三个柜台在同时办理开户,一个人需要 30 - 60 分钟的时间。
\n11:00 左右,我完成了中行的开户。
\n我现场就拿到了银行卡,便在 ATM 存入了 500 元人民币,并兑换成了港币。
\n顺便在同一条路上的南洋商业银行和汇丰银行也完成了开户手续。这两家银行皆需要几个工作日审核信息,审核成功后会将卡片寄至通讯地址。
\n穿行于各家银行网点的途中,我发现了一家在他人游记上看到过的店铺——胜利牛丸,便暗自定下了午饭的去处。
\n
\n三个愿望,一次满足。
\n面汤的颜色相当的“可怕”——仿佛是直接将卤水呈上来一般。但实际入口并没有很咸。牛肉们就不一样了,比较重口味的我也觉得有一点点咸。不过风味很棒,牛肉吃透了各种香辛料。
\n河粉藏在了对得满满的牛丸和牛肉之下,不是我常吃的宽河粉,比较窄。
\n虽然店名以牛丸冠名,我却没觉得牛丸有多么的美味,反倒是牛筋更胜一筹。
\n饭后,我在坐满老者的公园里稍稍歇息,并且计划着下午的行程……
\n此次香港之行除了开户,其实还有另一个目标——碧蓝档案与香港吃茶三千的联动~
\n在出发前的几天得知了联动的消息,运气真的很好。
\n
\n虽然有更优的路线,我还是选择了体验一下地面上的有轨电车。有轨电车有点像巴士,也是由司机驾驶,也会受到其他车辆与行人的干扰,但行驶路线是沿着铁轨固定的。
\n随后乘坐地铁,前往金紫荆广场…的对面,星光大道。在这里陈列了许多明星的手印和画像。海港边上的风景大好。
\n在此稍停,开户还没有结束。大概是因为元朗距离内地太近,许多虚拟银行都需要前往更靠近香港中心一些的地方才可以开户。我在一家星巴克坐下,开通了 ZA Bank 和 livi Bank 的账户。
\n随后,便是前往海港城的吃茶三千点了一杯联动奶茶,从中心一步步移动回福田口岸——晚上要和群友面基。
\n我们俩聊得非常尽兴,聊到了十一点才分手,回到了酒店,速速洗漱完毕,预定了第二天早上从福田站前往香港西九龙的火车,便沉沉睡去。
\nDay 3 - Sta - HK 游玩 昨晚火车票订得比较迟,最迟的火车也是 7:45 的,更迟的都被人订光了。我又被迫起了个大早(这三天都是 6:30 之前起床的)坐地铁前往福田火车站,搭上前往香港西九龙站的火车。可能是比较早乘火车的人并不对,这一趟通关流程比较顺利,再迟一点可就不好说了……
\n到达九龙,我空着肚子在街上漫步,寻找可以填饱肚子的早餐店。打开地图,“澳洲牛奶公司”几个字印入眼帘,这不是前几天看到的网红店吗?我记得这家店只收现金,便前往沿途的便利店,在 ATM 用中银香港的卡取了 100 港币现金。没有注意到我用的是汇丰的 ATM,与银联网络并不互通,被收取了 15 港币的手续费 QAQ。(众安银行的卡在全港 ATM 可以免手续费取现,经常到港玩的话可以办一张实体卡)
\n
\n带着现金,我来到澳洲牛奶公司的门口,果不其然,排起了长队。好在我是一个人,这家店是强制拼桌的,顺利超过了大团的游客,来到了店内。
\n给我的第一印象嘛…很拥挤,人挤着人,甚至是背贴着背。小小的一张圆桌围坐着四个人,各自享用着早餐。穿着白大褂的服务员在人群缝隙间穿梭着,高效地记录着到店客人的订单,飞速地将菜品传递到桌前。
\n落座,菜单压在桌子的玻璃之下,写满了粤语,说实话我看不大懂。纠结了几十秒,发现菜单上还写着“早餐”、“下午茶”等套餐,我便对服务员说:“早餐套餐来一份吧。”
\n话音未落,餐具和一杯茶便着陆在我的桌上。剩下的菜品也并我让我久等。
\n
\n首先呈上的是一碗火腿通心粉。味道比较清淡,喝不出黄色的汤底是什么熬制的,不过比较鲜。
\n
\n接着呈上的是一个盘子,两片吐司和炒鸡蛋各占半壁江山。牛油烘烤的吐司暖暖的,软软的;这份炒双蛋则是套餐的点睛之笔——调味微咸的鸡蛋并没有炒得很熟,在生与全熟之间掌握了平衡,做到了入口软绵,回味无穷,堪称是绝品。
\n如果有路过,一定要来尝一下这份炒鸡蛋~
\n一顿饱餐之后,我继续踏上旅途,前往太平山顶的观景台。
\n
\n上山的路可以选择用脚走完,不过,还是要体验一下富有特色的山顶缆车~
\n缆车的斜度估摸着有三四十度,是我见过最陡的。缆车是建在山坡边上的,方便沿路欣赏香港的风景。
\n
\n缆车的终点站是太平山的山顶,下车后直接来到了凌霄阁摩天台的二层。沿着盘旋向上的电动扶梯,海拔逐渐升高,走着走着,最终到达了海拔428米的摩天台观景台。
\n在这里可以俯览香港的景色,听说夜景更美,不过我晚上并不住在香港,也就没有机会亲眼目睹,实在可惜。
\n摩天台上的风很大,吹着十分舒服。待了大约四十分钟,我便沿着原路返回,坐着缆车回到了山下。
\n
\n早餐吃得有够饱,到了饭点我还不是很饿,便在街道与小巷间漫步,寻找着午饭的好地点。我想找家正宗点的茶餐厅。
\n跟随地图,我来到了就近的广芳园…可等候的队伍早已排出店外,目测有三四十人在队伍中。因为下午就要坐动车回去了,我无心加入他们,便继续游荡着,寻找着。
\n
\n附近的茶餐厅真的不多,跟随地图的指引,我来到了一家街边的小店。店内只收现金,而我身上只剩下不到五十港币,便选择了菠萝油和冰阿华田作为午餐,反正肚子不是特别饿。(冰的阿华田比热的还要贵上几块钱哦)
\n
\n饭后,我一时兴起决定走路去西九龙火车站,而非乘坐地铁,便开始一趟 City Walk…差点把我 Walk 没了。
\n街上好多地方都在施工,地图的数据并没有新到可以帮我绕开因为施工而禁止通行的路段…再加上出海关也需要一些时间,我差点以为要赶不上车。
\n好在我还是在开始检票前就赶到了,算是有惊无险,顺利踏上了返乡的路,结束了此次旅程。
\n我都带了些什么 此次我轻装上阵,只带了一个双肩包。
\n这次旅行我尝试了内衣、袜子和洗澡用具(浴巾与毛巾)都使用一次性的,省去了携带大量衣物且需要带回大量脏衣物的麻烦,也可以保证洗澡用具足够干净(对酒店不是很信任)。浴巾和毛巾都是压缩的,体积非常小。
\n其余的,便是两晚更换的衣服和电子产品的充电器了。
\n开户的那些事 我挺担心中银香港会开不下来,因为我还是学生也没有带地址证明。在大堂接待客户的小姐姐也询问了我有没有带上地址证明,没有的话只能碰碰运气,弄得我更慌了。
\n不过真正坐进柜台和柜员大哥开始走流程,直到大堂经理过来刷卡完成审核、将卡递到我手中,都没有向我索取地址证明。不知道是因为得知我是学生,还是因为我身份证上写的是具体住址,反正过了这关,顺利拿到了卡。
\n而汇丰之行则运气不佳,提交的资料被送至总行审核,当天拿不到账户也无法完成所有流程,只能等卡寄到居住地址,并且等下一次到港签字完成所有开户流程了。
\nP.S. 在跨境转账到时候一定要填清楚收款人的名字,要与中银香港的账户名称相同。我填反了名字被收了一笔手续费 QAQ
\n尾声 这次旅行顺利得可怕(除了差点没赶上返程的火车),在深圳与香港复杂的地铁网中也没有坐错一趟列车,似乎没能暴露出什么问题。希望 12 月底的日本之旅也能一样顺利。
\n一个人出来玩真的很自由,在出发前先查好可以去哪里玩、去哪里吃,到达之后就随心安排,不用迁就其他人,走累了随时可以停下休息,在咖啡店坐上一两个钟头也不会有人抱怨。
\n我也许爱上了一个人旅行的感觉,之后也会继续尝试这种旅行的方法。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/42ec6146.html",
+ "url": "https://blog.udon.eu.org/archives/42ec6146.html",
+ "title": "My Second Attempt To ARM Servers",
+ "date_published": "2023-09-24T04:30:00.000Z",
+ "content_html": "Last weekend, a full month without my Telegram account, I thought I had to do something. I believe I got banned because someone clicked the “spam” button on my messages, but I have nowhere to appeal.
\nI lost the faith in centralization. This time, I made up my mind to dive into decentralization.
\nFirst, I need a server.
\nWhere To Purchase I used to buy servers from distributors of the DC for lower prices, but it always ended up with disappointment. The servers are just not stable and I can not upgrade the hardware configuration on demand.
\nSo this time, I chose Hetzner for my server. Hetzner is a huge IDC in Germany, I must have to pay a fairly high price for the server, am I right?
\nThe following images show the price of Hetzner’s traditional CX series - with x86 CPU.
\n
\nA 2 CPU and 4GB RAM CX21 can fit my needs, it costs 5.35 EUR per month. By the way, the 40GB disk is far less than my requirement.
\nThe CX series are using Intel Xeon CPU. The GB6 benchmark score of an Intel core is around 850, which is significantly lower than AMD EPYC of around 1200.
\nHowever, when I switch to the CAX series…
\n
\nI can get the same number of CPU cores and RAM with only 3.79 EUR per month, 30% lower than the x86 one.
\nThe GB6 benchmark score of ARM CPU is around 1050, even higher than the Intel ones.
\nThe disk space is still 40GB, but I can purchase a 3.2EUR/month 1TB Storage Box to solve this problem.
\nWith a total of 6.99 EUR per month, I can get a server with 2 CPU cores, 4GB RAM, and 1TB disk. That is a great deal.
\nWait, ARM? About a year ago, when Hetzner first released the ARM series, I was interested in it and evaluated its compatibility.
\nI always use Docker containers to host my ~25 services. It turned out that more than half of the Docker images I was using were only compiled for the x86 CPU.
\nThat means you have to build the image by yourself if you want to run it on an ARM CPU. The time and effort were not worth it, so I gave it up.
\nHowever, this time, I found all of the Docker images already supporting the ARM CPU. It is time to give it a try.
\nThe Experience Here is my final hardware configuration:
\nI chose the CAX11 server, which has 2 Ampere ARM CPU cores, 4GB RAM, and 40GB disk. I added 2GB swap space to store cached files and reduce the load of the RAM.
\nI also purchased a 1TB Storage Box to store my data. Mounted to the server with NFS, just like a local disk.
\nI am using the latest Debian 12 Bookworm and I can’t feel any difference between x86 and ARM. My daily use software from APT source is all compiled for ARM. The installation speed is as fast as x86.
\nAs to the Docker images, I am using Portainer to manage them. What I need to do is just click the “Recover” button on the settings page, Portianer will automatically recover the configuration from CloudFlare R2 storage and all the containers just work as before, with no need to change any settings.
\nToday, when I am writing this article, My services are running for a week without any problem.
\n
\nWell, Still Not Perfect The ARM server just works fine, but I want to share some problems I encountered when building the ARM Docker images.
\nThe official software repositories of Linux distributions can offer full support for ARM CPU, but the repositories of other sources such as PyPi can not.
\n
\nThere are still some packages that don’t have prebuilt ARM wheels, and the building process can take a long time on the 2-core ARM machine.
\nThat is not a huge problem, but it makes the experience of ARM arch different from x86.
\nIf you are a developer, try to include ARM in your build pipeline next time, that will be a great help for the ARM community.
\nFinal Conclusion In my opinion, the ARM server is ready for daily use today. They offer a high quality-price ratio. I didn’t come across any compatibility problems.
\nNext time when you want to purchase a server, you can consider the ARM ones.
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/9b78ad2a.html",
+ "url": "https://blog.udon.eu.org/archives/9b78ad2a.html",
+ "title": "寻梦穿越机 - 入门浅谈",
+ "date_published": "2022-09-04T04:00:00.000Z",
+ "content_html": "暑假开始之前,我就朦朦胧胧有购入一台穿越机的想法。起因为何?我也不是很清楚。是看到了酷炫的穿越机航拍视频 ?还是童年时期的航模魂蠢蠢欲动,将要苏醒?
\n兴趣,倘若不立刻抓住,很快就会被抛至脑后。为了不让自己后悔,那就立刻开始筹备吧!攥紧并不是特别充盈的钱包,我踏上了寻梦之路。
\n什么是穿越机 FPV (First-Person View),是指通过第一人称视角远程控制无人飞行设备 (UAV) 的控制方式,也是一项运动,也指代了一类设备。
\nFPV 设备包含固定翼 (Fixed Wing) 与多轴 (Multi-rotor) 两种。穿越机多指多轴 FPV (常见为四轴)。
\n
\n一盆冷水 在详细介绍穿越机构成之前,请允许我泼一盆冷水。
\n穿越机与一些航拍无人机,例如大疆 (DJI) 的四轴航拍机与航拍 FPV 相比,颇有原始人见到现代人的感觉。
\n大疆无人机上搭载了各种各样的传感器,各系列大都配备了二向 (前后)、四向 (前后左右) 或五向 (增加一个上方) 的视觉避障功能、红外定高悬停功能,以及低电量 GPS 自动返航功能。
\n上述这些看似是无人机应当“标配”的功能,追求轻量化的穿越机时常一个也没有。一台最小安装的穿越机全机的传感器仅有集成于飞控的陀螺仪和地磁传感器 (电调的电流传感器一般不算在内),飞控仅知道当前机身的朝向、倾斜角度与加速度,无法感知周围的环境,没有实现避障、悬停与自动返航的能力。飞机的运动状态完全取决于飞手的操控,电池的剩余电量甚至需要飞手根据电芯电压来推断。
\n
\n再者,为了能获得更大的速度与更高的机动性,穿越机飞行过程一般处于手动模式,对飞机四向的倾斜没有限制,飞行过程中出现危险操作的概率大,事故概率也大。而大疆无人机对飞行速度、角度有严格的限制,能有效地降低炸机的概率。
\n因此,飞行穿越机的难度将是飞行大疆等航拍无人机的十倍、甚至百倍。请务必不要抱着随便玩一玩、随心飞的态度入坑穿越机,发生事故后轻则炸机提 (遥) 控回家,重则伤到自己或他人。对于喜欢日常随心飞行的玩家,大疆或类似牌子的无人机 (包括航拍机和穿越机) 更为合适。
\n \n下文将详细介绍入门穿越机所需要的技术知识与穿越机的重要模块。在明白风险之后仍打算入坑穿越机,或对穿越机有兴趣的你,欢迎继续往下阅读。
\n技术储备 上文提到了我童年时期的航模魂。我在小学就跟着老师学习无线电和航模知识,练习了焊接技巧和单旋翼的航模直升机的飞行技巧。曾参加某个航模比赛并拿到了铜牌的好成绩 (我好自豪)。
\n因此,我对我的焊接技术和遥控操控能力比较有信心。而这两个能力恰好是入门穿越机不可或缺的。下面我列举一些入门穿越机需要掌握的技能。
\n锡焊 除了购买真·到手飞套餐 (机子已组装完成,接收机已与遥控器完成配对),其余情况大概率需要焊接一些导线。
\n 大部分组装机套餐都不包含接收机,因为接收机与遥控器一一对应,一般是和遥控一起购买的。就需要用户自行焊接至飞控上。
\n 锡焊所用到的工具有电烙铁和焊锡丝,最好能佐以松香,增加焊接成功率。电烙铁推荐购买 T12 或更好的,因为飞控散热设计,很多焊盘为通孔的形式,热量会非常快地传到到全板,导致焊锡较难达到融化的温度。若使用功率太低的电烙铁,可能无法融化焊锡。
\n
\n 由于飞控比较小巧,焊盘也十分迷你,对焊接技术要求较高。
\n使用搜索引擎 不懂不可怕,不懂得学习才可怕。
\n 穿越机有关的零碎知识犹如满天星,散布于互联网的每一个角落,需要一定的搜索技巧才能挖掘到有用的知识。
\n 国内有关穿越机的网站数量比较少,建议使用英文搜索。
\n编译 若想要更新 ELRS 接收机和高频头的固件,需要从源码编译 ELRS 固件。虽然 ELRS 团队提供了一套图形化的编译工具,但难免会遇到一些疑难杂症 (博客中就有 ELRS 固件编译的踩坑记录)。需要对编译有一定的知识,能自行解决简单的问题。或者选择使用出厂固件,不自行升级。
\n操控能力 (协调性) 像音游 (音乐游戏) 一样,微操遥控器并不是所有人都能很好地做到。穿越机对遥控指令的反应极为灵敏,需要你能精细地操控遥控器。
\n 但这项技能可以通过电脑模拟飞行来习得。建议在正式开始飞行前,先在电脑上使用模拟器熟悉飞机的控制。
\n一颗勇敢的心 在飞行穿越机的过程中,不可能不出现炸机 (飞机以异常姿态落地) 的情况,难免会留下一点阴影。
\n 我在小学时飞过固定翼飞机,曾不慎撞到电线,导致飞机起落架损坏脱落,最后只能由老师操控迫降。此事给我留下了不小的心理阴影,有好几个月我都不敢再飞行固定翼。不过最后还是克服了恐惧,再次拾起了遥控。
\n 炸机并不可怕,每一次炸机都记录着你的成长。
\n深入了解 类似 Linux (比较奇怪的类比),穿越机也分为最小安装和额外拓展两部分。
\n
\n最小安装:
\n\n机架 - 硬连接其他组件,保护脆弱的电路板; \n电机 (和桨叶) - 提供飞行动力; \n飞控 - 控制飞行状态,是穿越机的大脑; \n电调 (电子调速器) - 驱动电机、分隔电路 (防止电机减速时产生的反向电流烧坏其他电子设备); \n接收机 - 接受遥控器控制信号 (有 2.4GHz、915MHz、 868MHz 等频段,互不兼容); \n图传 - 发送图像信号 (多为 5.8GHz 频段); \n摄像头 - 提供第一人称视角; \n \n额外拓展:
\n\nGPS 模块; \nBB 响 (蜂鸣器 Beeper,用于寻找飞机); \nLED 灯珠、灯带 (好看 XD); \n红外避障模块; \n运动摄像机 (拍摄更清晰、帧率更高的视频); \n \n挑几个比较重要的模块介绍一下:
\n机架 穿越机机架一般由碳纤维板制成,质地轻盈且强度极高,可以物理连接不同的模块,并保护脆弱的电子设备 (飞控、图传等裸板)。
\n挑选时,机架有几个要主要关注的核心参数:
\n\n外形:
\n机架分为普通式与涵道式 (Duct Propeller) 两种。
\n普通式机架的螺旋桨桨叶裸露在外,而涵道式机架的螺旋桨外侧有涵道 (类圆筒) 包裹。
\n有一些普通式机架可以通过额外安装涵道,变身成为涵道机架。
\n \n \n
\n 普通式机架采用开放式的设计,尽可能少地使用材料以减轻重量。由于螺旋桨暴露在外,危险性较大。普通机架可被用于任何竞速与花飞机型。
\n 涵道式机架通过涵道结构可以提供更好的升力、稳定性和安全性,但额外增添了重量和封阻,一般被用于小型机与花飞机型。
\n
\n 此外,机架还可以再细分为 X 型、H 型等,在此就不多展开,有兴趣的读者可以自行查阅。
\n\n大小:
\n穿越机大小各异,从最小的一寸机,到体型较大的五寸、六寸机。
\n二寸以下的机子防风能力较差,但体型轻盈且常使用涵道机架,适合在室内飞行,且不易伤人。
\n三寸以上的机子动力充沛,飞行速度快,但体型大、螺旋桨裸露,无法在室内飞行。适合在场地广阔的室外进行竞速或花飞 (花样飞行)。
\n \n \n飞控与电调 飞控与电调 (有时还有图传) 几块板子大小相当,时常纵向堆叠安装,称为飞塔。在多块 PCB 间有硅胶柱减震。
\n
\n飞控需要注意的参数不多:
\n\n
\n 由于穿越机飞行对计算性能不是很敏感 (类似 3D 打印机),一般选择搭载 F405 SoC 的飞控性能就足够使用了。
\n\n电调最重要的参数就是最大放电电流了。
\n电调的电流选择需要根据机身大小、电机和桨叶大小、形状进行估计,通常电机厂家会给出推荐值,按照推荐选购即可。
\n接收机与图传
\n接收机与图传共享一个重要的特性,协议:
\n常见的接收机有 ELRS (ExpressLRS) 和黑羊 TBS 两类。应该注意的是,不同种类的接收机使用的通信协议和频段不同,能与其配对的遥控器和高频头也不同 ,因此在挑选接收机的时候一定要看清楚协议和频段。
\n接收机和飞控的串口通讯协议也各异,有 UART、SBUS 等数种,再购买接收机前需确认飞控有相对应的串口。
\n图传分数字图传和模拟图传两种。
\n数字图传以大疆的最为出名,需要使用配套的飞行眼镜;模拟图传大多使用 5.8G 频段通讯,和大部分接收模拟信号的飞行眼镜通用。
\n图传连接至飞控的串口通讯协议也很多,购买的时候请多加留意。
\n除此之外,接收机有遥测功率、内/外置天线、天线接口等参数,在挑选时都需要多加留意。
\n图传的另一主要参数则是发射功率。发射功率越大,能稳定接受图传信号的距离一般就越大。小型穿越机一般选择发射功率在 200mW 到 500mW 的图传即可 (部分图传发射功率可调);若有远航要求,也可选择发射功率大于 1W 的图传 (价格较高、发热也较大,一般带有主动散热)。
\n机子之外的配件 除了穿越机本体之外,想要拥有完整的飞性体验,还需要遥控器、高频头、电池、平衡充电器、飞行眼镜等配件。
\n上述的每一种配件都可以写作一篇介绍文章。由于篇幅有限,本文就不再介绍上述的外围配件,请善用搜索引擎,学习相关知识。
\n配套软件 除了硬件,穿越机配套的控制软件也尤为重要。目前主流的控制软件是开源的 Betaflight。
\n
\nBetaflight 分为嵌入端 (安装在飞控中嵌入式系统) 和地面站 (安装在电脑里的软件)。将飞控通过线缆连接至电脑,并打开 Betaflight 地面站软件,即可对飞控参数进行调整。
\nBetaflight 调参也是一门大课,新手不建议自定义太多的参数。待熟悉飞机之后,才建议调整 PID 等高级控制参数。
\n新手的第一台飞机 说了这么多,要上某宝挑选、下单穿越机的种种配件了吗?
\n我的建议是否定的。
\n我咨询了一些老玩家,他们建议新手购买他人已完成调参的二手机器,或者购买商家大部分已组装完成套机,以绕过纷繁复杂且状况百出的 DIY 过程,降低还未入坑就弃坑的风险。
\n此外,自行购买散件的总价常常会高于购买整机的价格。对于钱包不是特别充盈的我,购买整机也是一个省钱的选择。
\n待熟悉了穿越机的飞行与调试之后,再学习他人经验,设计并组装一台自己心目中的机器也不迟,这才是 DIY 的浪漫。
\n我的成果
\n我选择购买一位老玩家完成大部分组装工作的“半”整机——包含了机架、电机、飞控和电调。图传、摄像头和接收机则是我自行购买和焊接安装的。
\n飞机到手、外围装备齐全,只待一飞冲天。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/9d319c54.html",
+ "url": "https://blog.udon.eu.org/archives/9d319c54.html",
+ "title": "DELL 灵越 15 5547 拆解与更换硅脂",
+ "date_published": "2022-05-22T02:30:00.000Z",
+ "content_html": "DELL 灵越 15 5547,Intel i5-6200U,Nvidia Geforce 630M,8G DDR3-1600 + 256G SATA SSD(后期改装),是我的第一台笔记本。这台笔记本的拆机过程比较繁琐,是我目前遇见的最难拆解的电脑,故作此文章,分享一下如何拆解一台笔记本,并为其更换硅脂。
\n
\n概述 本次拆机共耗时 1H 30Min,是拆解正常机器所花费时间的两到三倍。
\n拆解步骤包括:卸下后盖、拆除电池、拆除硬盘、拆除风扇、拆除键盘、卸下基座、卸下散热器、更换硅脂,然后是逆序执行上述拆解步骤,组装电脑。
\n拆解准备 拆机少不了一套好工具。
\n为了无伤拆机,需要准备一套好用的螺丝刀防止螺丝滑牙;还需要一套塑料拆机工具,用于撬开后盖。
\n笔记本的硅脂,我选择了霍尼韦尔 7950 相变硅脂,不容易挥发,不需要经常更换。
\n开始拆机 怎么拆呢 其实几个月前我就有给这台设备清灰、换硅脂的念头,但拆开机器之后我发现主板被塑料框架罩住了,无法拆下散热器。我研究了半天也没找到拆下框架的方法,便只清理了风扇的灰尘,没有更换硅脂。
\n而这次,我有备而来:我查到了 DELL 官方的用户手册 ,其中详细记载了拆解这台机器的方法。接下来的拆解步骤就严格按照官方的教程啦。
\n第一步:卸下后盖 翻到 D 面,拧下两颗固定螺丝,就能将后盖拆下,可以触及无线网卡、内存条、2.5 寸硬盘、电池和散热风扇,日常需要维护的部件都能轻松触及,好评。散热器和主板则在塑料框架之下。
\n
\n第二步:拆除电池 释放主板电荷是电脑拆机中至关重要的一步!
\n在主板带电的情况下拔插任何端子都是不明智的做法,很可能会将主板上的高电压线路误接入低电压线路(例如拔插屏线时没有正对接口),烧坏一片元件。
\n这台机子的电池没有排线,直接接入主板。卸下围绕电池的四颗螺丝,手提塑料片,就可将电池卸下。
\n翻到 C 面,长按电源键 10s,可重复两到三次,确保主板中的电荷完全释放。
\n
\n第一次见这种电池模块,比起长条状的电池可以更好地利用机身空间。
\n第三到五步:拆除硬盘、散热风扇和键盘 卸下固定硬盘座的螺丝,抽出硬盘,再断开 SATA 与供电二合一的线缆,即可取下硬盘。
\n拔下主板上散热风扇的端子(位于风扇左侧),卸下固定螺丝,即可取下散热风扇。
\n翻到 C 面,用手或塑料工具扣出键盘模块,注意不要大力提起键盘!小心地提起一段距离,断开机身上的排线,键盘就取下来了。裸 C 面上还有两条排线,都需要断开。竖直方向排线需从孔洞中穿回 D 面。
\n
\n第六步:卸下基座 这是我第一次见笔记本中的基座,没有手册的指导很难拆下。
\n首先,确保 C 面两根排线已断开。
\n翻到 D 面,小心地断开屏幕排线(位于散热风扇左侧)。
\n再翻到 C 面,按手册图示卸下所有螺丝。
\n翻回 D 面,同样地卸下一堆螺丝,注意这两面的螺丝都是 M2.5x7 规格,长于主板用螺丝,混用可能会戳穿主板。
\nDELL 在机身和框架上都有标注螺丝孔对应螺丝的规格,非常好评!
\n确认卸下所有螺丝后,用塑料工具插入基座与机身的缝隙,划开卡口。
\n待大部分卡口都解开时,小心提起基座,注意将基座上的线缆和排线取下,基座就彻底与机身分离了。
\n
\n基座的背面,可以看到是 PC + ABS 材料,分量很足。
\n
\n拆下基座之后的机身,暴露出了主板和子板。散热器是梦幻单热管,不过压这两个破芯片足够了。
\n第七步:卸下散热器 散热器共有六颗螺丝,四颗在 CPU 上,两颗在显卡上。
\n为了使散热器均匀受力,不能一次性直接拧下一颗螺丝,而是平均为每颗螺丝卸力,可以每个螺丝一次转两圈,直到所有螺丝都被卸下。裸晶脆弱,有必要好好保护一下。
\n
\n不难看出,CPU 上的硅脂全部凝固,且裸晶上的硅脂基本流失殆尽(散热器上也没剩多少)。
\n去除大部分凝固的硅脂,再用酒精片擦拭、用布擦干。
\n更换硅脂的步骤我就不在此赘述,商家一般会提供详细的视频介绍,按着教程做就可以了。
\n
\ni5-6200U 的全貌,右侧长条状的裸晶应该是集成显卡吧。
\n
\nGeforce 930M 显卡,非常小个。
\n
\nDDR3 显存,封装方式和 DDR4 不一样,更扁一些。
\n后续步骤 接下来就是逆序刚刚的拆解步骤,我就不再赘述。
\n注意几点:
\n\n安装散热器时也需保证受力均匀,且不能过分用力,可能会压碎裸晶。 \n安装基座时注意将线缆与排线置于合适的位置,不要被基座压到了。 \n \n拼装完成后,插电,开机,轻松点亮。
\n总结 拆解一台电脑并没有想象中的那么困难,拆解不同电脑的方式也都大同小异,上述的步骤一般都能适用。
\n我已拆解了不下 10 台/次 笔记本,目前还未翻过车。
\n下次清灰、更换硅脂,试着自己动手吧!
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/245ab175.html",
+ "url": "https://blog.udon.eu.org/archives/245ab175.html",
+ "title": "厌烦了软件的我上了硬件的车",
+ "date_published": "2021-11-07T13:00:00.000Z",
+ "content_html": "好久不见,回想三个月前的我还在享受暑假。开学后,我将大部分精力转移至学业,折腾的时间便少了。
\n机缘巧合,在显卡价格最高的时候坏了显卡的我打算趁双十一打折买个焊台,自己修显卡。
\n同时,我有了玩一玩硬件的想法。
\n焊台体验 六七年以来,我一直在用直插 220V 不可调温的电烙铁焊接。当时,无线电老师告诉我这是质量很好的紫铜烙铁。就是这把烙铁陪着我入门了锡焊。
\n放在今天,这把烙铁加热慢,且烙铁头非常容易氧化,体验极差。由于加热效果不佳,我还弄坏了一台灵车无人机的主板和一个 IR 摄像头(都是掉焊盘了)。
\n新焊台则完美的解决了之前的痛点:新烙铁加热快、温度准确、刀头耐用。热风枪则开启了贴片元件焊接时代。
\n第一个项目 说在前头,选择一个比较复杂的项目来入门是个错误的决定。
\n十月中,我选择了一个加热台项目。对,就是可以用来焊接贴片元件的加热台。用工具制造工具,多美妙的事情啊~带着兴奋,我开始采购元件……
\n直至今天,这个项目的进度仍然停滞不前:元件全部焊接完毕(见下图),接通电源可以点亮一秒,随即就烧坏了一个电阻。初步判断是 5V 供电区域有短路,具体情况还需继续检测……
\n
\n事实证明,第一个项目应该选择简单点的,以防止打击信心🌚
\n赶超第一个项目的第二个项目 转眼到了十一月,又可以在嘉立创愉快地免费打板了~
\n这次,我给自己设计了一张 PCB IC 校园卡,只需要焊接一个 CUID 芯片。
\n没有比这更加简单的项目了!
\n成品如图~
\n
\n结语 前几天整了一台性能不错的服务器,小项目又可以整起来;
\nDIY PCB 这方面,我还想制作一个带编码器的小键盘,以提高 Premiere 剪辑的效率;
\n群星真好玩,铁心灭绝者真不错😇
\n真是丰富多彩的课余生活~
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/67d41c7c.html",
+ "url": "https://blog.udon.eu.org/archives/67d41c7c.html",
+ "title": "LOOPERS 简评",
+ "date_published": "2021-08-02T02:00:00.000Z",
+ "content_html": "对 LOOPERS 的整体评价:B-
\n \n这一作挺特殊的,没有任何选择支,全程自动播放耗时仅8小时左右。在这样短小的作品里塞下一日无限循环这样富有可能性的世界观,大概是本次剧情质量不尽人意的主要原因。
\n中盘倒不觉得冗长,还可以继续细化,加深对人物的刻画;就是后期重复的内容与话语太多了,让想尽快看到结局的我着急。
\n音乐方面,我全程用电脑扬声器玩的。说实话,BGM 听得不是很清楚。待我细听之后再作出评价。女二真的很吵,有 SP 那头恶鬼的吵闹级别了。
\n制作方面,望月老师的画好看(哧溜)。
\n \n谈一谈一些细节上的感受(可能有点剧透):
\n作为已经退坑的老 Ingress 玩家,再度跟随主角一行人踏上寻宝之路,着实让人兴奋。游戏中着力刻画的,寻找到藏匿的宝物时的喜悦我能切身体会到。
\n这让我想起了两三年前,我为了拔掉敌方的一个大 菊花 Portal,在旧屋区狭窄的巷子里穿行。找了一两个小时,终于到达 Portal 的中心点,开始狂轰滥炸。当时的兴奋与满足是溢于言表的。或许你要亲身玩过寻宝游戏,才能体会主角一行人的感受吧。
\n对于“神经大条”的男主,我最大的感受就是这种人不大可能存在吧。遇到任何事情都能乐观面对、看似大大咧咧但做起事来注重细节、有着强大的领导力和鼓舞力……这种人若真的存在,绝对耀眼。
\n最后看作品的主题。在我看来, 龟 龙骑士这回倒不是想写一个很催泪的故事,而是继续宣扬 Key 社所推崇的友谊、团结和爱的主题。我个人是百看不厌,我也想要知心朋友,甚至是一群知心朋友。
\n男主全程口边挂着寻宝二字,听了耳朵确实起茧,但这文章放在考试说不定能拿挺高的分数——不断地扣紧中心。人生的意义?寻宝;友情的价值?寻宝;生命的救赎?还是寻宝。
\n这寻宝真有意思,只要将寻宝的过程与寻找到宝物稍作改变,就能解释这么多难以用语言表述的东西。倘若能在游戏性上再下点功夫,加个交互型的寻宝游戏,整体效果又会再上一层。但加了这些就不是一款短小精湛的游戏了,对吧。
\n \n最后,这款游戏值得推荐吗?对于没有接触过 Key 社作品的人,可以一玩。
\n对于 Key 社老玩家,对这部作品报以极高的期待,希望它能超过正作水准的,还是别玩了。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/202ebc76.html",
+ "url": "https://blog.udon.eu.org/archives/202ebc76.html",
+ "title": "2021 第二学期总结",
+ "date_published": "2021-07-15T01:00:00.000Z",
+ "content_html": "在去厦门的动车上,写一下这个学期的总结。
\n \n刚开始我想形容这个学期是浑浑噩噩的,感觉我想做、想学的东西都没做成;
\n但再冷静想一想,这学期在学校的安排下打了很多基础:解决了 C++ 这一心头大恨;还学习了数电,为硬件打了点基础。
\nC++ 实在很基础、很枯燥,若不是学校压着要学习,光凭我一人自学是难以解决的。以前也不是没有尝试过自学,最终都败下阵来。
\n至于硬件这块的知识,软件工程专业一般是不去接触的。学校大概是觉得下学期要学的 计算机组成原理 需要有数电的预备知识吧。
\n至于高数、线代,对编程越是了解,越会知道他们的重要性,知道他们贯穿着整个编程的过程。
\n这个学期最满意的还是全科通过,包括让我非常头疼的大学物理和高数。
\n下个学期我还是希望能腾出更多的时间给自己喜欢的事物,而不是把全身心都投进学校的课业,感觉亏待了自己的梦想……
\n生活方面,■■■■■■■■■,整体闷在学校里把我憋得难受。宿舍环境虽然不错,但整体偏老旧——六人间改四人间,■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n下个学期又可以搬回主校区,一定要把宿舍和工位书桌好好装修一下,过精致生活~
\n \n至于暑假的安排,挺杂乱的。
\n买了不少东西,从小到大,从 IR 摄像头模组到 3D 打印机,打算将折腾二字写满整个假期;
\n学习内容则是安排了 JavaScript 和 CTF 相关的内容,如果还有时间还想看看 React。CTF 没有人带,想要入门挺困难的……校队是不打算招新人么 :(
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/43a7a15c.html",
+ "url": "https://blog.udon.eu.org/archives/43a7a15c.html",
+ "title": "Osprey Commet 简评",
+ "date_published": "2021-06-13T07:00:00.000Z",
+ "content_html": "我的威戈双肩包——没错,就是烂大街的那个牌子——已经用了四年多了。不得不承认她的质量之好,用了这么久只坏了包前侧的拉链(到裁缝店换了一条拉链),外加水壶网袋破了几个洞。在上个月,她的一条拉链绳(不知道怎么叫那个部件)的脱落让我有了更换通勤背包的念头。
\n \n\n为了挑选一款最称心的通勤背包,我先对前任威戈包进行了评估:
\n优点
\n\n缺点
\n\n背负系统拉垮,光是背电脑(1.4kg)就觉得非常沉,更别说偶尔的电脑+平板组合了(2.2kg);
\n \n没有太多存放小物件的仓位,所有的小东西(U盘、读卡器、耳机、线材 etc.)都放在包的前仓里,且前仓还是竖向开合,分类与取物皆不便;
\n \n包身不防水,但送防雨套。下雨时出门要提前戴套,生怕包内电子产品淋湿;
\n \n \n因此,我对下一款背包有如下要求:
\n\n自重轻;
\n \n分仓多且位置合理,能分类存放小物件;
\n \n包身最好能防水;
\n \n最好能同时收纳笔记本电脑和平板;
\n \n \n在挑选了不下30款背包后,我选择了这款 Osprey Commet,下面来简要点评一下。
\n全局 包身自重 0.85kg,容积 30L,在这个大小的包中算比较轻的了。包身全部防水,拉链虽然没有做密封处理,但有一些延伸出来的防水面料挡住拉链,可以防止进水。
\n分仓 这款包的分仓可以算是一个亮点。我简要说一下我对每个分仓的使用情况:
\n包的最前方是提手(颜值比较一般,偏向实用),因此第一个仓是小主仓。仓内有三个兜袋,用于存放我的卡包、证件、充电宝和 MP3,仓内还装着小记事本和钱包。此外,仓内还有一根红色的钥匙绳,采用的是快挂钩,可以把现有的钥匙串挂上去。
\n往里一层是第一个眼镜/小物品收纳仓。仓内使用了特殊面料防止刮花眼镜,在登山/骑行时存放眼镜还是很方便的。我就拿来存放经常取用的小物件了,例如耳机、U盘、Lto3.5mm 转接线等。
\n下一层是一号主仓,外加主仓内的第二个眼镜/小物品收纳仓。一号主仓有一个兜袋方便收纳平板和 A4 大小的文件,我便拿来放文件袋和平板。课本、笔盒也都放在这个仓位。小物品收纳仓则收纳比较少取放的小物件,例如订书机、凤尾夹和一些药品(达喜)。
\n最后一个仓位是笔记本仓。作为整个包最大的仓位,能轻松容纳 15.6 寸的笔记本。在需要携带笔记本时,这个仓位当然用来装笔记本;平常上课不带笔记本,也可以拿来装外套。这个仓位里还有一个网袋,我用来装线材(一根 0.2m CtoC、一根 MFi AtoL、一根 0.2m AtoB)。
\n背负系统 虽然没能用上 Osprey 专业的空景系统,但 Commet 的背负系统依旧优秀。Osprey 的背板偏硬,可以比较好地贴合后背,以减轻肩带对肩膀的压力。在日常上课背负几本书时,基本没有负重的感觉。若是同时背负笔记本+平板(3.6kg 左右),能感觉到背板压在后背上,分担了部分重量。
\n胸带与腰带这种登山包才有的配置出现在了这款通勤包上。在背负比较重的物品时系上腰带可以有效减少包身晃动。
\n总评 相当满意!
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/4a562e35.html",
+ "url": "https://blog.udon.eu.org/archives/4a562e35.html",
+ "title": "近日败家:Chrombook Duet 与 小米手环6",
+ "date_published": "2021-04-30T13:00:00.000Z",
+ "content_html": "今天来分享一下最近败家的几样电子产品。
\nChromebook 与 USI 触控笔 在大学课堂上发现很多人都持有一台 iPad 和 一支 Apple Pencil 在做着笔记,令我很是羡慕。
\n在咨询过“业内人士”,并对现有产品及我的钱包进行评估之后,我放弃了购买 iPad 及其昂贵的配件的念头,选择拥抱 Chromebook。
\n为了追求极致的性价比,我选择了这款 联想 Chromebook Dute 加上 联想 USI 触控笔,1730 + 330 合计 2060 元。
\n购买 我是在亚马逊海外购下单的,货从美国仓库发出。平板花费了8天来到了我的手上,而触控笔因为海关查验,比平板迟了两天。跨国快递的速度比我想象的要快不少。
\n设计与体验 Duet,意为合二为一。CB Duet 的设计理念和 Surface Book 一致——打造一台既可以当作平板又可以当作电脑使用的设备。
\nCB Duet 附赠了磁吸键盘与可以作为支架的保护套,这一点比 iPad 高不少。
\n但是,键盘与触控板的体验实在是一言难尽。
\n键盘在不平整的环境下偶尔会出现多次触发(按一次键打出两次字母)的情况,不过放在平整桌面就不会了。键程尚可,但按键布局一般——为了在10寸大小的区域容纳全部按键,联想选择了将键盘右侧的按键(大部分是符号键)缩小,这让打出正确的符号变得十分困难。
\n触控板的体验更加一言难尽。手感很差,似乎没做过亲肤处理,滑动阻力非常大;定位也很不准确。但 CB Duet 有一块10寸的触摸屏,为何不使用触控操作呢?
\n总而言之,CB Duet 附赠的键盘属于“能用”的级别,就像现在的我敲着博客——做一点文字工作完全没问题。Coding ?别想了,我替你试过了,如果只是简单改几行代码,比如 Caddy 的配置文件,完全没问题;如果想跑完整的开发环境,性能可能不够;如果使用 code-server……也不是不行,但10寸的屏幕实在不能施展浑身解数啊。
\n续航 部分比较“卷”的朋友可能比较担心这个问题,是我先倒下还是 CB Duet 先倒下?
\n根据我的实际体验——当然这可能很不准确,你大概率撑不过 CB Duet。
\n实际测试下来,一个上午,四节课下来,大概使用 15%-20% 电量。
\nARM 的超低功耗让 CB Duet 有官方标称的将近 11 小时的续航;如果你像我一样调低屏幕亮度,并只使用笔记软件,系统提示的理论续航甚至达到 20 小时;倘若是高强度使用,例如使用 ssh、打开 Linux 虚拟机等,续航则在 6-8 小时左右。
\n性能 翻到上面再看一眼价格,还要谈什么性能吗!
\n本人不喜欢的 MTK P60T,8c 2.0Ghz。
\n不过实际体验来看,这颗小芯片的性能完全能喂饱 ChromeOS 和它的安卓容器,至于 Linux ,只要不跑 GUI 应用(Linux 容器暂无图形硬件加速)也没问题。
\n听说联想计划推出搭载高通骁龙 7c 的同款 CB ,性能有略微升级,如果价格依旧能持平,那会是更好的选择。
\n系统与体验 ChromeOS 的操作需要适应一段时间。
\n你应该要改变对应用的认知——减少安装实体应用,更多地拥抱 PWA ,可以让这个专门为 Chrome 优化的操作系统发挥全部实力。
\n安卓容器的性能真的很棒,可能是安卓和 ARM 相性较好的缘故吧,安卓应用在 CB Duet 上表现极佳,我觉得和一台安卓平板几乎没有差异。这台安卓平板却还能跑完整的 Chrome 浏览器和 Linux 容器。
\n系统的其他体验可以在网络上看看其他人的博客。如果我在后续体验中有什么心得体会,会考虑写成博客。
\n还有触控笔 联想这只 USI 触控笔是亚马逊上最便宜的,没有侧边按键、没有笔擦。触控笔没有太多要说的,昨晚用它写了一份作业,体验良好。
\n使用一周之后,偶尔会出现笔凌空识别的情况,无法测试出是硬件还是软件的问题,略微影响使用体验。
\n小米手环6 这是我第一次体验手环产品,之前戴的都是智能/半智能手表。
\n小米手环6的屏幕在手环产品中是比较大的,且能自定义壁纸,这是它最吸引我的地方。
\n至于续航,的确是硬伤。在打开除了全天心率监测以外的所有监测项目,心率检测频率设为1分钟一次后,手环一天消耗 20% 左右电量。
\n总体体验还算满意的。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/2c0bfb1a.html",
+ "url": "https://blog.udon.eu.org/archives/2c0bfb1a.html",
+ "title": "校色初体验 / 寒假总结",
+ "date_published": "2021-02-26T14:00:00.000Z",
+ "content_html": "好久不见,甚是想念。今天来分享一下初次校色的体验以及寒假我都干了些什么。
\n \n\n \n校色初体验 为什么要校色 我的笔记本电脑型号是 联想小新 Air 14 2020,拥有一块 14寸 100% sRGB 色域(实际为 94% sRGB)的屏幕,面板型号是 友达 B140HAN06.8。很幸运,不仅抽中了三星 SSD,还抽中了友达的屏幕。
\n为了更舒服地阅读代码,我又淘了一台 17.3寸 的 DIY 便携显示器,同样也是 100% sRGB 色域(实际为 95% sRGB),面板型号是 友达 B173han01.1。
\n由于便携屏的驱动板都是通用的,并没有对某块面板有调教,也不可能有屏幕出厂时的调教,因此这块便携屏的色差非常明显。就校色结果来看,光度 (gamma) 值就有 70% 左右的偏差。
\n通过校色,两块屏幕色彩更加接近,且更接近真实的颜色,看起来也会更舒服一些。
\n关于价格 我想很多人和我有同样的顾虑,感觉租用校色仪价格不菲。
\n这次租用时长为 3天,我仅使用了一天半就归还了。总共支出为 校色仪租金 40元 + 回程运费 18元。押金原本是 750元,和店家商量后爽快地降到了 500元。
\n虽然商家划定了许多可能导致押金被扣的规则,但只要你小心一点、爱护一点,完璧归赵、取回押金是很简单的。
\n色温选择 刚开始我选择的目标色温是 6500K,在校色后发现偏黄许多。最后我选择的目标色温是 7500K。当然,两块屏幕 6500K 色温的校色文件我都保存下来了。
\n最终校色效果 按照红蜘蛛给出的报告,联想笔记本的这块屏幕在色准上要优于便携屏,白点与灰度的 △E <= 0.2 ,便携屏则是 △E <= 0.5。
\n联想这块屏出厂也比较准,值得表扬~
\n下面是校色完的合影:
\n
\n \n寒假我都干了啥 列举一下:
\n\n学习(重温)了 C语言; \n学习了 PHP 基础; \n学习了 Burp Suite 基础; \n学习了 After Effect; \n学习了 Audition 基本操作; \n完成了一个 5分钟 的视频项目,比较灵活地运用了 PS Pr AE 及 Au; \n当了两个星期的补习老师,挣来的钱买了 飞利浦 X2HR HiFi 耳机和一块 2242 SSD; \n学习了 Solidworks 基本操作(这两天学的); \n \n感觉还是做了不少事的。
\n马上要开学了,下学期总算能学些我想学的了。
\n总之,好好干吧!
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/cd7dffda.html",
+ "url": "https://blog.udon.eu.org/archives/cd7dffda.html",
+ "title": "2020 年终总结",
+ "date_published": "2020-12-31T16:00:00.000Z",
+ "content_html": "2020 马上就要过去了,我想做一个年终总结。
\n \n\n \n非常抱歉鸽了博客四个月。
\n其实十月我忙中偷闲写了一篇文章打算发上来,却被 Hexo 吞了,我也没了重写的热情,就这么让它消失了。
\n2020 我做了什么 ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n2021 我打算做什么 First, 我打算加入学校的 CTF 战队。自学 CTF 的过程中一定会遇到不少有趣的事情,我会抽空写作博文与大家分享的。
\nSecond, 我想和同学合作开发一些有趣的小项目乃至小软件,点子已经有几个存在脑子里了。
\nThen, 我打算磨练一下业务能力,而不是简单地编写脚本一样存在的代码,CSS 也不能写得歪歪扭扭的了(捂脸)。
\n在一切都回归正轨之后,我也会跟上各位的步伐,继续探寻有趣的项目,还请大家多多期待!
\n结语 2020 发生了太多糟糕的事情,祈愿 2021 能遇见更多美好的事情。
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/911a91db.html",
+ "url": "https://blog.udon.eu.org/archives/911a91db.html",
+ "title": "博客迁移-主题更换与近况报告",
+ "date_published": "2020-05-04T01:58:17.000Z",
+ "content_html": "又好久不见。
\n这两天抽空更换了博客的主题,并迁移至永久域名 ■■■■■■■■■■■ 。
\n以及谈一谈新开的项目的设计思路,有兴趣的读者还请慢慢阅读。
\n \n\n博客变更 更换域名 ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n更换 Hexo 主题 主题的名字叫 Fluid ,是一款 Material Design 风格的主题。
\n之前我用的主题是 Yilia ,但作者弃坑已久,感觉已跟不上时代变化,就下定决心换了(还把手上所有猫羽雫图全部塞进去了)。
\n同时,我还将评论系统 Gitalk 更换为 utteranc ,对于国内用户加载速度应该会更快,同样需要 GitHub 登录。
\n近况报告 快要高考了好忙啊,完全没有时间研究技术了,也到了应该要努力的时候了(笑)。
\n超级长的寒假里我还是有学习新知识的(指计算机):
\n成绩查询网站 开了一个坑:构建一个成绩查询网站以代替学校那套繁琐的校务系统。
\n■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
\n项目的前端用了 jQuery、Bootstrap 等框架;
\n后端则是采用我比较熟悉的 Python 后端框架 Django 。
\n \n校园官网查询成绩的流程如下:
\ngraph TB\n校园网主页--输入账号密码 选择身份-->\n校园网内网--找到侧边栏的按钮-->\n成绩查询页面--选择年份 学期 考试场次-->\n得到成绩\n
\n\n如何取代学校的系统呢?思路很简单:黑掉学校的服务器直接把成绩取出来
\n模拟登录就好了。
\n学校的破烂网站采用的是最简单的 GET 表单登录,可以轻松获取 Cookie ,方便进一步操作;
\n我将查询步骤简化为:
\ngraph TB\n查询页面--输入账号密码-->\n下一步1--选择年份 学期-->\n下一步2--选择考试场次-->\n获得成绩\n
\n\n看起来没简化多少,其实 选择身份 和 找到侧边栏按钮 就已经足够烦人了。
\n校园网采用了大量下拉框选择,我将其替换为按钮选择,甚至不用选择,一定程度上提高了查询效率。
\n \n除此之外,我也写了另一个 API 负责查询某成绩查询 APP 上的成绩。
\n我认为这个 API 贡献较校网查询应该更大,这让我摆脱了手机 APP 查询这一烦人的设定。
\n然而开发过程中,后者所花费的力气远小于前者,大概是校网建设得太差的缘故吧。
\n \n除此之外,我还尝试将 API 封装进 Docker,使部署更加简单、快捷。
\nDocker 的使用非常简单,若有兴趣还请多多尝试。
\n果然造轮子学习比干看教程效果要好啊!
\n尾声 没有尾声 : )
\n",
+ "tags": [
+ "随笔"
+ ]
+ },
+ {
+ "id": "https://blog.udon.eu.org/archives/c7a0a8db.html",
+ "url": "https://blog.udon.eu.org/archives/c7a0a8db.html",
+ "title": "莱卡依然想要回家",
+ "date_published": "2019-10-31T15:00:00.000Z",
+ "content_html": "有幸在/home/rynco/music Channel 遇见这张专辑。第一次听,或许是风格合口,我立刻爱上了这首名为“Laika Still Wants Go Home”,与专辑同名的轻音乐。 很有力量。这是这张专辑给我的第一印象。 我几乎没有乐理知识,对于音乐的评论总是如此无力。这首歌从旋律看也好,从节奏看也好,感觉整体把控得很恰当,张弛有度。我贫瘠的语言真的无法形容那种美的感觉。
\n \n\n \n\n \n \n\n我刷新了对这张专辑的认识,已是数十分钟后。我在听新曲时总会翻翻网易云音乐的评论区,有数条评论对专辑封面的解释。 我第一次听说太空犬这个词是在Littile Buster中,能美·库特莉亚芙卡的名字的由来。库特莉亚芙卡是一头太空犬的本名。 它常被我们称作:莱卡。
\n \n鉴于很少人知道这个故事,我就讲讲吧。 当时正逢美苏争霸,太空竞赛也是美苏交锋的重点。当时的苏联宇航技术不是很发达,但想要胜过美国就要先把航天员送上天。总不能让宇航员乘着从未试验过的飞船就这么上去吧,苏联的科学家就想用动物代替人类完成测试。科学家们在街上找来了几条流浪狗——因为他们觉得流浪狗比家犬够能受冻——这几条狗就被钦定为太空犬了。训练的艰苦不必多说,看看训练人类就能明白,何况是动物。几个月的训练之后,莱卡脱颖而出。到了发射前几天,一位科学家把莱卡带回了家里,让它和孩子们玩耍,因为他们很清楚,这是一次不含回程票的旅行。 苏联当时并未掌握从地球轨道重返地面的技术。 不知道当时在场的所有人的心情是怎样的。应该是兴奋的,毕竟超越老美了;又有点担心,因为即将发射的飞船是赫鲁晓夫下令两周内造出来的;或许有的人会有些不舍吧,他们将要亲手送走一条鲜活的生命。 很可惜,莱卡并不是在氧气或食物耗尽前被安乐死的。飞船的温控系统因赶工与设计问题出现故障。即使风扇再怎样转,舱内俨然成为一个火炉。用好听的话说莱卡是中暑而死;难听点,活活热死。仅管死亡是不可避免的,我也希望它能少一点痛苦啊。
\n \n我想到了安德。在指挥完舰队完成模拟战斗后,在他得知他亲手消灭了虫族的舰队时,他崩溃了。他回忆起刚才他指挥数个小队作为诱饵白白牺牲。他崩溃了。我想当时,我也希望当时,火箭点火升空之时,二级火箭脱离失败之时,舱内温度急剧升高之时,有人能为这条生命感到一丝绝望。
\n \n这张专辑的风格与“OPUS-灵魂之桥”的 OST 有异曲同工之妙。(独立工作室的游戏,OST 不方便放出,希望大家能一起购买。游戏也很棒啊!)都给我以一种末世感。究竟是世界对人绝望还是人对世界绝望呢?
\n \n想象你站在专辑封面的那一点上,眺望地球,这该是多么孤独与悲伤。有评论说载着莱卡的火箭现在仍绕着地球旋转,但很可惜,那台火箭已经在大气层燃尽,这也算是最高规格的葬礼了吧。
\n \n再说说专辑的名称吧。“Laika Still Wants Go Home”,英文,是没有一点问题的。我的母语是中文,因此英文的名称对我无感。但当我要将它翻译成中文,那一刻,我真的愣住了。我脑袋里已经将这几个单词转化为了中文,再一次理解了它们的意思。我僵住了。就几个字停留在嘴边就是说不出来。说不出来……在对面的人疑惑不解我为何停住,思考是否因为自己没有在认真听导致我生气时,一滴眼泪从我疲惫的左眼划过。立刻调动理智。情绪再次平稳,我又调回了内存里的那几个字,念了出来……
\n \n后记
\n从听到这首歌到构思这篇文章再到陆陆续续写出来,我花了数天。因为事情真的很多,脑内的一些想法被琐事代换了。 文字很凌乱,几乎是想到什么写什么。因此我用分割线分开了。 若有新想法我会继续补充的。
\n",
+ "tags": [
+ "随笔"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/tag/\351\232\217\347\254\224/rss.xml" "b/tag/\351\232\217\347\254\224/rss.xml"
new file mode 100644
index 00000000..ea897101
--- /dev/null
+++ "b/tag/\351\232\217\347\254\224/rss.xml"
@@ -0,0 +1,1091 @@
+
+
+
+ カレーうどん屋 • Posts by "随笔" tag
+ https://blog.udon.eu.org
+ カレーうどん屋.
+ zh-CN
+ Tue, 12 Nov 2024 23:30:00 +0800
+ Tue, 12 Nov 2024 23:30:00 +0800
+ 教程
+ 随笔
+ 软件
+ 小鸡测评
+ 3D 打印
+ 嵌入式开发
+ GitHub Actions
+ Hexo
+ DIY
+ -
+
https://blog.udon.eu.org/archives/2c54052d.html
+ 我再也不能不假思索
+ https://blog.udon.eu.org/archives/2c54052d.html
+ 随笔
+ Tue, 12 Nov 2024 23:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/95479b1f.html
+ Soundcore C30i 耳机
+ https://blog.udon.eu.org/archives/95479b1f.html
+ 随笔
+ Thu, 13 Jun 2024 14:40:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/adc5a61e.html
+ 日本之行-第二站-奈良
+ https://blog.udon.eu.org/archives/adc5a61e.html
+ 随笔
+ Mon, 11 Mar 2024 00:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/a7050149.html
+ 23 年对我影响最大的硬件与软件
+ https://blog.udon.eu.org/archives/a7050149.html
+ 随笔
+ Sat, 02 Mar 2024 20:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/a534d51a.html
+ 旅行与军粮
+ https://blog.udon.eu.org/archives/a534d51a.html
+ 随笔
+ Mon, 12 Feb 2024 22:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/7777583b.html
+ 被我整坏的路由器和服务器
+ https://blog.udon.eu.org/archives/7777583b.html
+ 随笔
+ Mon, 12 Feb 2024 18:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/aca48136.html
+ 日本之行-第一站-大阪
+ https://blog.udon.eu.org/archives/aca48136.html
+ 随笔
+ Sun, 21 Jan 2024 23:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/2ef8bd61.html
+ 深圳-香港三日行
+ https://blog.udon.eu.org/archives/2ef8bd61.html
+ 随笔
+ Tue, 19 Dec 2023 22:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/42ec6146.html
+ My Second Attempt To ARM Servers
+ https://blog.udon.eu.org/archives/42ec6146.html
+ 随笔
+ Sun, 24 Sep 2023 12:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/9b78ad2a.html
+ 寻梦穿越机 - 入门浅谈
+ https://blog.udon.eu.org/archives/9b78ad2a.html
+ 随笔
+ Sun, 04 Sep 2022 12:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/9d319c54.html
+ DELL 灵越 15 5547 拆解与更换硅脂
+ https://blog.udon.eu.org/archives/9d319c54.html
+ 随笔
+ Sun, 22 May 2022 10:30:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/245ab175.html
+ 厌烦了软件的我上了硬件的车
+ https://blog.udon.eu.org/archives/245ab175.html
+ 随笔
+ Sun, 07 Nov 2021 21:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/67d41c7c.html
+ LOOPERS 简评
+ https://blog.udon.eu.org/archives/67d41c7c.html
+ 随笔
+ Mon, 02 Aug 2021 10:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/202ebc76.html
+ 2021 第二学期总结
+ https://blog.udon.eu.org/archives/202ebc76.html
+ 随笔
+ Thu, 15 Jul 2021 09:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/43a7a15c.html
+ Osprey Commet 简评
+ https://blog.udon.eu.org/archives/43a7a15c.html
+ 随笔
+ Sun, 13 Jun 2021 15:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/4a562e35.html
+ 近日败家:Chrombook Duet 与 小米手环6
+ https://blog.udon.eu.org/archives/4a562e35.html
+ 随笔
+ Fri, 30 Apr 2021 21:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/2c0bfb1a.html
+ 校色初体验 / 寒假总结
+ https://blog.udon.eu.org/archives/2c0bfb1a.html
+ 随笔
+ Fri, 26 Feb 2021 22:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/cd7dffda.html
+ 2020 年终总结
+ https://blog.udon.eu.org/archives/cd7dffda.html
+ 随笔
+ Fri, 01 Jan 2021 00:00:00 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/911a91db.html
+ 博客迁移-主题更换与近况报告
+ https://blog.udon.eu.org/archives/911a91db.html
+ 随笔
+ Mon, 04 May 2020 09:58:17 +0800
+
+
+ -
+
https://blog.udon.eu.org/archives/c7a0a8db.html
+ 莱卡依然想要回家
+ https://blog.udon.eu.org/archives/c7a0a8db.html
+ 随笔
+ Thu, 31 Oct 2019 23:00:00 +0800
+
+
+
+
diff --git "a/tags/3D-\346\211\223\345\215\260/index.html" "b/tags/3D-\346\211\223\345\215\260/index.html"
new file mode 100644
index 00000000..b33ad5f9
--- /dev/null
+++ "b/tags/3D-\346\211\223\345\215\260/index.html"
@@ -0,0 +1,389 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 标签 - 3D 打印 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git a/tags/DIY/index.html b/tags/DIY/index.html
new file mode 100644
index 00000000..6c7a2966
--- /dev/null
+++ b/tags/DIY/index.html
@@ -0,0 +1,389 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 标签 - DIY - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git a/tags/GitHub-Actions/index.html b/tags/GitHub-Actions/index.html
new file mode 100644
index 00000000..3aa63c68
--- /dev/null
+++ b/tags/GitHub-Actions/index.html
@@ -0,0 +1,383 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 标签 - GitHub Actions - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git a/tags/Hexo/index.html b/tags/Hexo/index.html
new file mode 100644
index 00000000..aed342cc
--- /dev/null
+++ b/tags/Hexo/index.html
@@ -0,0 +1,383 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 标签 - Hexo - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git a/tags/index.html b/tags/index.html
new file mode 100644
index 00000000..b5f82aaf
--- /dev/null
+++ b/tags/index.html
@@ -0,0 +1,367 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 标签 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/tags/\345\260\217\351\270\241\346\265\213\350\257\204/index.html" "b/tags/\345\260\217\351\270\241\346\265\213\350\257\204/index.html"
new file mode 100644
index 00000000..bd341c30
--- /dev/null
+++ "b/tags/\345\260\217\351\270\241\346\265\213\350\257\204/index.html"
@@ -0,0 +1,422 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 标签 - 小鸡测评 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/tags/\345\265\214\345\205\245\345\274\217\345\274\200\345\217\221/index.html" "b/tags/\345\265\214\345\205\245\345\274\217\345\274\200\345\217\221/index.html"
new file mode 100644
index 00000000..b696d048
--- /dev/null
+++ "b/tags/\345\265\214\345\205\245\345\274\217\345\274\200\345\217\221/index.html"
@@ -0,0 +1,389 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 标签 - 嵌入式开发 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/tags/\346\225\231\347\250\213/index.html" "b/tags/\346\225\231\347\250\213/index.html"
new file mode 100644
index 00000000..fa1be4bf
--- /dev/null
+++ "b/tags/\346\225\231\347\250\213/index.html"
@@ -0,0 +1,446 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 标签 - 教程 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/tags/\346\225\231\347\250\213/page/2/index.html" "b/tags/\346\225\231\347\250\213/page/2/index.html"
new file mode 100644
index 00000000..506fa65d
--- /dev/null
+++ "b/tags/\346\225\231\347\250\213/page/2/index.html"
@@ -0,0 +1,452 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 标签 - 教程 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/tags/\346\225\231\347\250\213/page/3/index.html" "b/tags/\346\225\231\347\250\213/page/3/index.html"
new file mode 100644
index 00000000..a972b762
--- /dev/null
+++ "b/tags/\346\225\231\347\250\213/page/3/index.html"
@@ -0,0 +1,401 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 标签 - 教程 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/tags/\350\275\257\344\273\266/index.html" "b/tags/\350\275\257\344\273\266/index.html"
new file mode 100644
index 00000000..92a598f7
--- /dev/null
+++ "b/tags/\350\275\257\344\273\266/index.html"
@@ -0,0 +1,389 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 标签 - 软件 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/tags/\351\232\217\347\254\224/index.html" "b/tags/\351\232\217\347\254\224/index.html"
new file mode 100644
index 00000000..fd0cad2a
--- /dev/null
+++ "b/tags/\351\232\217\347\254\224/index.html"
@@ -0,0 +1,449 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 标签 - 随笔 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/tags/\351\232\217\347\254\224/page/2/index.html" "b/tags/\351\232\217\347\254\224/page/2/index.html"
new file mode 100644
index 00000000..adb4bf92
--- /dev/null
+++ "b/tags/\351\232\217\347\254\224/page/2/index.html"
@@ -0,0 +1,452 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 标签 - 随笔 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git "a/tags/\351\232\217\347\254\224/page/3/index.html" "b/tags/\351\232\217\347\254\224/page/3/index.html"
new file mode 100644
index 00000000..51e66628
--- /dev/null
+++ "b/tags/\351\232\217\347\254\224/page/3/index.html"
@@ -0,0 +1,401 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 标签 - 随笔 - カレーうどん屋
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 博客在允许 JavaScript 运行的环境下浏览效果更佳
+
+
+
diff --git a/xml/local-search.xml b/xml/local-search.xml
new file mode 100644
index 00000000..d7d0c01c
--- /dev/null
+++ b/xml/local-search.xml
@@ -0,0 +1,45 @@
+
+
+ {% if posts %}
+ {% for post in posts.toArray() %}
+ {% if post.indexing == undefined or post.indexing %}
+
+ {{ post.title }}
+
+ {{ [url, post.path] | urlJoin | uriencode }}
+ {% if content %}
+
+ {% endif %}
+ {% if post.categories and post.categories.length>0 %}
+
+ {% for cate in post.categories.toArray() %}
+ {{ cate.name }}
+ {% endfor %}
+
+ {% endif %}
+ {% if post.tags and post.tags.length>0 %}
+
+ {% for tag in post.tags.toArray() %}
+ {{ tag.name }}
+ {% endfor %}
+
+ {% endif %}
+
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+ {% if pages %}
+ {% for page in pages.toArray() %}
+ {% if post.indexing == undefined or post.indexing %}
+
+ {{ page.title }}
+
+ {{ [url, post.path] | urlJoin | uriencode }}
+ {% if content %}
+
+ {% endif %}
+
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+