-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
4040 lines (4040 loc) · 323 KB
/
atom.xml
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>sujingjhong.com</title><description>Minimalistic Hugo blogging theme</description><link>https://sujingjhong.com/</link><language/><copyright>Copyright 2024, Su Jing-Jhong</copyright><lastBuildDate>Thu, 29 Aug 2024 22:55:35 +0800</lastBuildDate><generator>Hugo - gohugo.io</generator><docs>http://cyber.harvard.edu/rss/rss.html</docs><atom:link href="https://sujingjhong.com/atom.xml" rel="self" type="application/atom+xml"/><item><title>如何選定新工作?</title><link>https://sujingjhong.com/posts/how-to-choice-a-new-job/</link><description><p>最近另一半面試了很多,很幸運的,拿到一些 offer,開始煩惱下一分工作怎麼選,問我有什麼建議。</p>
<p>我覺得在選擇新工作就三個問題:</p>
<h2 id="1-可不可以在工作中學習">1 可不可以在工作中學習? <a href="#1-%e5%8f%af%e4%b8%8d%e5%8f%af%e4%bb%a5%e5%9c%a8%e5%b7%a5%e4%bd%9c%e4%b8%ad%e5%ad%b8%e7%bf%92" class="hash g">#</a></h2>
<p>以每日八小時工作時間來看,工作至少就占了每天的 1/3,如果可以在工作中學習,就可同步精進本職學能,何樂不為!這通常是工作本身賦有挑戰性、創意性高,解決工作上的問題就帶動本職學能提升,軟體工程師就是屬於此類型典型。</p>
<h2 id="2-可不可以改善工作流程">2 可不可以改善工作流程? <a href="#2-%e5%8f%af%e4%b8%8d%e5%8f%af%e4%bb%a5%e6%94%b9%e5%96%84%e5%b7%a5%e4%bd%9c%e6%b5%81%e7%a8%8b" class="hash g">#</a></h2>
<p>但並不是任何工作都是賦有挑戰性、創意性,即使是軟體工程師,也是有許多例行公事或需要手工藝處理的作業,這時要思考的就是能不能改善工作流程,降低過程繁複的痛苦指數。</p>
<p>但也必須說,這會取決於工作環境可塑程度,如果同事普遍習慣照章辦事,改起來只會變罪人;以及多半涉及到生產力工具,如果公司資安或者設備老舊,也會窒礙難行。</p>
<h2 id="3-下班後有沒有餘裕的時間">3 下班後有沒有餘裕的時間? <a href="#3-%e4%b8%8b%e7%8f%ad%e5%be%8c%e6%9c%89%e6%b2%92%e6%9c%89%e9%a4%98%e8%a3%95%e7%9a%84%e6%99%82%e9%96%93" class="hash g">#</a></h2>
<p>如果 1,2 都不能滿足,工作上無法學習又無法改善工作流程,多半會陷入嚴重的 blue monday。這時候建議就是退而求其次,尋找一個下班後有餘裕的工作,可規劃自己人生目標,想做美容師就去學、想考照就去準備,再向前邁進。</p></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/how-to-choice-a-new-job/</guid><pubDate>Sat, 13 Jul 2024 13:15:15 +0800</pubDate></item><item><title>除錯日記#5 Debian</title><link>https://sujingjhong.com/posts/debugday-5/</link><description><p>今天一整天都在除錯 debian&hellip;</p>
<h2 id="macos-可以安裝-deb-檔案嘛">MacOS 可以安裝 <code>.deb</code> 檔案嘛? <a href="#macos-%e5%8f%af%e4%bb%a5%e5%ae%89%e8%a3%9d-deb-%e6%aa%94%e6%a1%88%e5%98%9b" class="hash g">#</a></h2>
<p><a href="https://unix.stackexchange.com/a/542823" target="_blank" >答案是不行</a>
。原本查到可以用 <a href="https://www.macports.org/install.php" target="_blank" >MacPorts</a>
安裝 deb ,結果是一個名叫 deb 的套件。</p>
<p>所以只能請同事弄台 debian/ubuntu 虛擬機給我測試用。</p>
<h2 id="要怎麼切換預設-shell">要怎麼切換預設 shell? <a href="#%e8%a6%81%e6%80%8e%e9%ba%bc%e5%88%87%e6%8f%9b%e9%a0%90%e8%a8%ad-shell" class="hash g">#</a></h2>
<p><a href="https://stackoverflow.com/a/13046330" target="_blank" >用 chsh 切換</a>
:</p>
<pre><code class="language-bash">chsh -s $(which zsh) $(whoami)
</code></pre>
<p>另外記得要確認 <code>which</code> 的值在不在,我因為沒裝 <code>zsh</code>,導致寫了空字串進去,root 就壞了。</p>
<p>後來是強者同事幫我用 livecd 掛載 host disk 幫我解決。</p>
<h2 id="要怎麼切換語言包">要怎麼切換語言包? <a href="#%e8%a6%81%e6%80%8e%e9%ba%bc%e5%88%87%e6%8f%9b%e8%aa%9e%e8%a8%80%e5%8c%85" class="hash g">#</a></h2>
<p>查了蠻多的,<a href="https://unix.stackexchange.com/a/669735" target="_blank" >後來有用的是直接安裝全部語言包</a>
:</p>
<pre><code class="language-bash">sudo apt install locales-all
</code></pre>
<p>查過沒用的:<a href="https://serverfault.com/questions/54591/how-to-install-change-locale-on-debian" target="_blank" >連結一</a>
、<a href="https://askubuntu.com/questions/76013/how-do-i-add-locale-to-ubuntu-server" target="_blank" >連結二</a>
</p>
<h2 id="執行-appimage-時出現-failed-to-exec-fusermount">執行 <code>appimage</code> 時出現 <code>failed to exec fusermount</code> <a href="#%e5%9f%b7%e8%a1%8c-appimage-%e6%99%82%e5%87%ba%e7%8f%be-failed-to-exec-fusermount" class="hash g">#</a></h2>
<pre><code class="language-bash">fuse: failed to exec fusermount: No such file or directory
Cannot mount AppImage, please check your FUSE setup.
You might still be able to extract the contents of this AppImage
if you run it with the --appimage-extract option.
See https://github.com/AppImage/AppImageKit/wiki/FUSE
for more information
open dir error: No such file or directory
</code></pre>
<p><a href="https://github.com/AppImage/AppImageKit/wiki/FUSE" target="_blank" >指南網站建議的方式</a>
:</p>
<pre><code class="language-bash">sudo apt install fuse libfuse2
sudo modprobe fuse
sudo groupadd fuse
user=&quot;$(whoami)&quot;
sudo usermod -a -G fuse $user
</code></pre>
<p>不過我執行下面這行就可以了:</p>
<pre><code class="language-bash">sudo apt install fuse libfuse2
</code></pre>
<h2 id="要如何升級到-debian-12">要如何升級到 Debian 12 <a href="#%e8%a6%81%e5%a6%82%e4%bd%95%e5%8d%87%e7%b4%9a%e5%88%b0-debian-12" class="hash g">#</a></h2>
<p>原本虛擬機是用 Debian 10 然後一堆東西不能使用,只好升級。</p>
<p>不過升級途中有些東西沒裝好,導致之後出現 SSH 拒絕連線。最後還是請同事直接幫我開台到 Debian 12 的解決。</p>
<p>但還是附上一下 <a href="https://gist.github.com/rrottmann/b0f371a62950a9e149c4358772c5a647" target="_blank" >網路上找到的指令</a>
:</p>
<pre><code class="language-bash"># Dist-Upgrade Debian 10 Buster to Debian 12 Bookworm
# Debian 10
apt-get -y update
apt-get -y upgrade
apt-get -y full-upgrade
cat &gt; /etc/apt/sources.list &lt;&lt;&quot;EOF&quot;
deb http://deb.debian.org/debian/ bullseye main
deb-src http://deb.debian.org/debian/ bullseye main
deb http://security.debian.org/debian-security stable-security/updates main
deb-src http://security.debian.org/debian-security stable-security/updates main
deb http://deb.debian.org/debian/ bullseye-updates main
deb-src http://deb.debian.org/debian/ bullseye-updates main
EOF
apt-get clean
apt-get -y update
apt-get -y upgrade
apt-get -y full-upgrade
shutdown -r now
# Debian 11
cat &gt; /etc/apt/sources.list &lt;&lt;&quot;EOF&quot;
deb http://deb.debian.org/debian/ bookworm main
deb-src http://deb.debian.org/debian/ bookworm main
deb http://security.debian.org/debian-security stable-security/updates main
deb-src http://security.debian.org/debian-security stable-security/updates main
deb http://deb.debian.org/debian/ bookworm-updates main
deb-src http://deb.debian.org/debian/ bookworm-updates main
EOF
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys BDE6D2B9216EC7A8
apt-get clean
apt-get -y update
apt-get -y upgrade
apt-get -y full-upgrade
# issue with libcrypt.so.1
cd /tmp
apt -y download libcrypt1
dpkg-deb -x libcrypt1_1%3a4.4.25-2_amd64.deb .
cp -av lib/x86_64-linux-gnu/* /lib/x86_64-linux-gnu/
apt -y --fix-broken install
apt-get -y upgrade
apt-get -y full-upgrade
apt-get -y auto-remove
shutdown -r now
</code></pre>
<p>另外 <a href="https://gist.github.com/rrottmann/b0f371a62950a9e149c4358772c5a647?permalink_comment_id=5073872#gistcomment-5073872" target="_blank" >有網友提到如果安裝過程出現 <code>broken dependencies</code></a>
,可以改成用</p>
<pre><code class="language-diff">- apt-get -y upgrade
+ apt-get -y upgrade --without-new-pkgs
</code></pre></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/debugday-5/</guid><pubDate>Tue, 04 Jun 2024 01:06:09 +0800</pubDate></item><item><title>除錯日記#4 ONLYOFFICE JWT Auth</title><link>https://sujingjhong.com/posts/debugday-4/</link><description><h2 id="because-it-is-private-ip-address">Because, It is private IP address <a href="#because-it-is-private-ip-address" class="hash g">#</a></h2>
<p>IP 設定問題,可以 <a href="https://sujingjhong.com/posts/debugday-3/" target="_blank" >參考前一篇</a>
設定經過。</p>
<p>今天為了盡可能開發功能,所以在 document server 啟動時加入關閉 JWT 選項。</p>
<p>然後,就出事了。錯誤訊息認證可以找到 IP,但,不給找,因為是私有 IP 位置。我架在內網測試錯了嗎?</p>
<p><a href="https://github.com/ONLYOFFICE/DocumentServer/issues/2268" target="_blank" >在 ONLYOFFICE 的 Github 有找到相關的議題</a>
,簡言之就是為了預防 SSRF 攻擊,官方在 JWT 開啟時,不會跳出任何有關 <code>request-filtering-agent</code> 錯誤。</p>
<blockquote>
<p>This is to protect against SSRF attacks. We highly recommend enabling JWT in your integrations. When JWT is enabled links in the server are signed by it and you won&rsquo;t get any &ldquo;request-filtering-agent&rdquo; errors.</p>
</blockquote>
<p>發問者將 JWT 開啟後,就可以了。我自己嘗試也是,就是開啟 JWT 後,就可以正常存取。</p>
<p>所以,雖然文件上允許你關閉 JWT,系統可能會異常唷!以一種消極方式避免你關閉 JWT。</p>
<h2 id="onlyoffice-的-jwt-格式長什麼">ONLYOFFICE 的 JWT 格式長什麼 <a href="#onlyoffice-%e7%9a%84-jwt-%e6%a0%bc%e5%bc%8f%e9%95%b7%e4%bb%80%e9%ba%bc" class="hash g">#</a></h2>
<p>這是接下來遇到的問題。既然要簽 JWT,那格式長什麼樣子?</p>
<p><a href="https://api.onlyoffice.com/editors/security" target="_blank" >首先先看文件</a>
:</p>
<blockquote>
<p><em>JSON Web Tokens</em> consist of three parts separated by dots (.), which are: <em>header</em>, <em>payload</em>, <em>signature</em>. The <em>header</em> consists of two parts: the type of the token (<em>JWT</em>), and the hashing algorithm (<em>HMAC SHA256</em>). The second part of the token is the <em>payload</em>, which contains the claims in JSON format. To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.</p>
</blockquote>
<p>所以這把 JWT 要用 HS256。那內容呢?</p>
<blockquote>
<p><strong>ONLYOFFICE Docs</strong> validates the <strong>token</strong>. The data from the <em>payload</em> is considered valid and is used instead of the corresponding data from the main parameters. If the <strong>token</strong> is invalid, the command is not executed.</p>
</blockquote>
<p>好喔,所以內容要包什麼&hellip; 感謝 Google 大神,<a href="https://forum.onlyoffice.com/t/jwt-token-configuration/7581" target="_blank" >我找到一篇官方論壇在討論 JWT 格式</a>
。接著在看文件時,終於找到 <a href="https://api.onlyoffice.com/editors/signature/browser" target="_blank" >指標性文件</a>
中提到:</p>
<blockquote>
<p>The <em>payload</em> for the JWT token in the JSON format must have the same structure as the <a href="https://api.onlyoffice.com/editors/advanced" target="_blank" >config</a>
.</p>
</blockquote>
<p>以 <a href="https://api.onlyoffice.com/editors/open" target="_blank" >官方文件的 Opening file</a>
為例子,使用到的程式碼會長這樣:</p>
<pre><code class="language-javascript">new DocsAPI.DocEditor(&quot;placeholder&quot;, {
&quot;document&quot;: {
&quot;fileType&quot;: &quot;docx&quot;,
&quot;key&quot;: &quot;Khirz6zTPdfd7&quot;,
&quot;title&quot;: &quot;Example Document Title.docx&quot;,
&quot;url&quot;: &quot;https://example.com/url-to-example-document.docx&quot;
},
&quot;documentType&quot;: &quot;word&quot;,
&quot;token&quot;: &quot;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkb2N1bWVudCI6eyJmaWxlVHlwZSI6ImRvY3giLCJrZXkiOiJLaGlyejZ6VFBkZmQ3IiwidGl0bGUiOiJFeGFtcGxlIERvY3VtZW50IFRpdGxlLmRvY3giLCJ1cmwiOiJodHRwczovL2V4YW1wbGUuY29tL3VybC10by1leGFtcGxlLWRvY3VtZW50LmRvY3gifSwiZG9jdW1lbnRUeXBlIjoid29yZCJ9.7IpEJxdOvBQ0kJ8l6ZegIV4tX5vsPbZZCDDVmcFROXc&quot;
});
</code></pre>
<p>所以你的 JWT Payload 就是這段:</p>
<pre><code class="language-json">{
&quot;document&quot;: {
&quot;fileType&quot;: &quot;docx&quot;,
&quot;key&quot;: &quot;Khirz6zTPdfd7&quot;,
&quot;title&quot;: &quot;Example Document Title.docx&quot;,
&quot;url&quot;: &quot;https://example.com/url-to-example-document.docx&quot;
},
&quot;documentType&quot;: &quot;word&quot;
}
</code></pre></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/debugday-4/</guid><pubDate>Thu, 30 May 2024 21:49:58 +0800</pubDate></item><item><title>除錯日記#3 ONLYOFFICE + NodeJS</title><link>https://sujingjhong.com/posts/debugday-3/</link><description><p>這兩天都在和 <a href="https://www.onlyoffice.com/" target="_blank" >onlyoffice</a>
奮鬥,簡單介紹 onlyoffice 是一款開放原始碼版 Office,它提供 Word, Excel, PowerPoint 等替代版,並且是支援線上協作的。</p>
<p>所以更正一下,應該是開放原始碼版的 Office 365。</p>
<p>如果要安裝社群版的 onlyoffice 可以參考以下網址:</p>
<ul>
<li><a href="https://helpcenter.onlyoffice.com/installation/docs-community-index.aspx" target="_blank" >https://helpcenter.onlyoffice.com/installation/docs-community-index.aspx</a>
</li>
</ul>
<p>它有提供 docker 版本,所以本篇介紹就是我怎麼把這個 docker 版拉起來的血淚。</p>
<p><a href="https://helpcenter.onlyoffice.com/installation/docs-community-docker-compose.aspx" target="_blank" >一開始先用 docker compose 版</a>
,他會需要 postgres+rabbitmq。</p>
<p>拉起來後輸入歡迎頁面的 Testing before integration 底下這行測試用,每個人應該都不太一樣。</p>
<pre><code class="language-bash">docker exec 5156f486155f sudo supervisorctl start ds:example
</code></pre>
<p>輸入完就可以點 <code>GO TO TEST EXAMPLE</code>,會很興奮地看到下一頁:</p>
<p>
<img
src="https://sujingjhong.com/images/2024/onlyoffice_page1.png"
alt=""
loading="lazy"
decoding="async"
width="1921"
height="932"
class="full-width"
/>
</p>
<p>再來點選左上方的,就會:</p>
<p>
<img
src="https://sujingjhong.com/images/2024/onlyoffice_page2.png"
alt=""
loading="lazy"
decoding="async"
width="1921"
height="929"
class="full-width"
/>
</p>
<p>經典的 Download failed 出現了,關掉後就回到列表頁。</p>
<p>在網路上搜尋這個錯誤訊息,<a href="https://www.google.com/search?q=onlyoffice&#43;download&#43;filaed&amp;ie=UTF-8" target="_blank" >會看到一堆提問</a>
。官方文件也有提到,它只有輕描淡寫提到:</p>
<blockquote>
<p>The &ldquo;Download failed&rdquo; message is displayed at the editors loading process.
The <strong>Document editing service</strong> cannot upload the file for editing.
Check if the link to the file specified in the <a href="https://api.onlyoffice.com/editors/config/document#url" target="_blank" >document.url</a>
is correct. The link must be accessible from the <strong>document editing service</strong>.</p>
</blockquote>
<p>OK,我知道他不能上傳到伺服器,所以?另一個提問則是,我是用官方範例架起來,為何還有這錯誤訊息?為什麼?</p>
<p>探尋過程就不多著墨,這裡直接講結論。<a href="https://helpcenter.onlyoffice.com/installation/docs-community-install-docker.aspx" target="_blank" >我後來是用 docker 版架起來測試</a>
:</p>
<pre><code class="language-bash">docker run -it -d -p 8888:80 --restart=always -e JWT_SECRET=my_jwt_secret onlyoffice/documentserver
</code></pre>
<p>再搭配他的 example server,另外這裡說一下,<a href="https://api.onlyoffice.com/editors/example/nodejs" target="_blank" >這連結裡面的範例是可行的</a>
,<a href="https://github.com/ONLYOFFICE/document-server-integration" target="_blank" >但另一個完整 Github repo</a>
拉起來會和你說少檔案,為何範例檔案會少檔案?沒關係,我也不想探討了。</p>
<p>接著在 step 2 他會和你說要去 <code>config/default.json</code> 設定編輯器伺服器(<code>siteUrl</code>)位置,以及檔案儲存位置(<code>storageFolder</code>/<code>storagePath</code>),但它文件和範例還漏說明了一個很重要參數: <code>exampleUrl</code>。</p>
<p>設定錯誤的附帶症狀會是:</p>
<ul>
<li>File download failed</li>
<li>ECONNREFUSED</li>
</ul>
<p>接著你翻遍文件和討論區依然找不到所以然,拉起來服務依然會給你跳上面的問題。</p>
<p>我最後是由他的 docker log 看出端倪,簡單而言他的架構和參數互動是這樣的:</p>
<pre><code class="language-mermaid">sequenceDiagram
server-&gt;&gt;documentServer: siteUrl
documentServer-&gt;&gt;server: exampleUrl
</code></pre>
<p>設定上:</p>
<ul>
<li><code>siteUrl</code> 要從剛剛那個 NodeJS 伺服器角度去看,他要怎麼連線到 <code>documentServer</code>。這裡是「前端」要麼連線到 documentServer。</li>
<li><code>exampleUrl</code> 要從 documentServer,就是剛剛那台用 docker 拉起來的服務,要從他的角度看,要怎麼連線到 NodeJS 伺服器的「後端」。</li>
</ul>
<p>這就取決於你的網路架構,所以像是我是用 WSL2 架設測試,可以在 server「前端」用 localhost 連線到 documentServer,我後來就是設定 <code>localhost:8888</code>。</p>
<p>但 documentServer 因為在 wsl2 的 docker 內,所以要先找到他本地端的 docker ip 為何,再設定位置。比方說最後我的內網 NodeJS server 位置是 <code>172.26.141.114:3000</code>,最後參考設定就是:</p>
<pre><code class="language-text">siteUrl: http://localhost:8888/
exampleUrl: http://172.26.141.114:3000
</code></pre>
<p>順帶一提,在他的範例伺服器裡面,它組網址路徑相當簡單,就是兩個字串相加,所以 <code>siteUrl</code> 會需要尾斜線(trailing slash):但是呢,在 documentServer 看起來組網址方式又不一樣,如果送尾斜線進去,它會有雙斜線(double slash)問題。</p>
<p>最後為何那個明明是回呼函式網址要叫做 <code>exampleUrl</code> 呢?我猜很單純是因為他是範例伺服器,所以很直覺地用 <code>exampleUrl</code>&hellip;</p></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/debugday-3/</guid><pubDate>Thu, 30 May 2024 01:43:52 +0800</pubDate></item><item><title>將 Hugo Prism.js 改為使用 highlight.js</title><link>https://sujingjhong.com/posts/switch-prismjs-to-highlightjs-in-hugo/</link><description><h2 id="基本設定">基本設定 <a href="#%e5%9f%ba%e6%9c%ac%e8%a8%ad%e5%ae%9a" class="hash g">#</a></h2>
<p><a href="https://sujingjhong.com/posts/how-to-add-prismjs-into-hugo/" target="_blank" >時隔多年,原先使用的 <code>Prism.js</code> 已經沒有再更新了</a>
,最近把它更換成 <code>highlight.js</code>。</p>
<p>首先把 hugo header 中有用到 Prism.js 的地方通通移除,感謝它這幾年的付出。</p>
<p>接著我把 <code>highlight.js</code> 設定寫在一個 <code>partials/highlightjs.html</code></p>
<pre><code class="language-xml">{{ $version := &quot;11.9.0&quot; }}
{{ $base_url := printf &quot;https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@%s/build&quot; $version }}
{{ $langs := slice &quot;bash&quot; &quot;css&quot; &quot;elixir&quot; &quot;excel&quot; &quot;ini&quot; &quot;javascript&quot; &quot;markdown&quot; &quot;php&quot; &quot;python&quot; &quot;shell&quot; &quot;plaintext&quot; &quot;http&quot; &quot;json&quot; }}
&lt;link rel=&quot;stylesheet&quot; href=&quot;{{ $base_url }}/styles/default.min.css&quot;&gt;
&lt;script defer src=&quot;{{ $base_url }}/highlight.min.js&quot;&gt;&lt;/script&gt;
{{ range $langs }}
&lt;script defer src=&quot;{{ $base_url }}/languages/{{ . }}.min.js&quot;&gt;&lt;/script&gt;
{{ end }}
&lt;script defer&gt;
document.addEventListener('DOMContentLoaded', (event) =&gt; {
document.querySelectorAll('pre code').forEach((el) =&gt; {
hljs.highlightElement(el);
});
});
&lt;/script&gt;
</code></pre>
<p>由於 highlight.js 並沒有 auto loader,所以必須把需要的語法通通加載上去。</p>
<p>我這裡是搭配 <a href="https://github.com/BurntSushi/ripgrep" target="_blank" >ripgrep</a>
把有用到的語言都放進去</p>
<pre><code class="language-bash">rg '```\w+' -oNI | sort -u | rg '\w+' -o
</code></pre>
<p>全部支援的語言列表可以前往 <a href="https://highlightjs.org/download" target="_blank" >官方網站</a>
查詢,或者是到他們 <a href="https://github.com/highlightjs/cdn-release" target="_blank" >存放庫</a>
裡面的 <code>build/languages</code>。</p>
<p>最後在自訂 header 裡面只需要</p>
<pre><code class="language-plaintext">{{ partial &quot;highlightjs&quot; . }}
</code></pre>
<h2 id="更換顏色">更換顏色 <a href="#%e6%9b%b4%e6%8f%9b%e9%a1%8f%e8%89%b2" class="hash g">#</a></h2>
<p>highlight.js 可以 <a href="https://highlightjs.org/examples" target="_blank" >線上瀏覽</a>
可用的語法色彩。</p>
<p>要更換就是把前面的 <code>default.min.css</code> 其中的 <code>default</code> 替換成想要的樣式名稱,例如我現在使用的 <code>base16-gruvbox-dark-hard</code></p>
<pre><code class="language-diff">- &lt;link rel=&quot;stylesheet&quot; href=&quot;{{ $base_url }}/styles/default.min.css&quot;&gt;
+ &lt;link rel=&quot;stylesheet&quot; href=&quot;{{ $base_url }}/styles/base16/gruvbox-dark-hard.min.css&quot;&gt;
</code></pre>
<p>另外注意,如果有用 <code>base16-</code> 開頭,它是放在 <code>base16</code> 資料夾底下。</p>
<p>詳細的資訊可以前往 <a href="https://github.com/highlightjs/cdn-release" target="_blank" >存放庫</a>
底下的 <code>build/styles</code></p>
<h2 id="程式碼複製功能">程式碼複製功能 <a href="#%e7%a8%8b%e5%bc%8f%e7%a2%bc%e8%a4%87%e8%a3%bd%e5%8a%9f%e8%83%bd" class="hash g">#</a></h2>
<p>highlight.js 有支援擴充套件,所以可以自行撰寫套件或用別人的。</p>
<p>我這裡另外有用到 <a href="https://github.com/arronhunt/highlightjs-copy" target="_blank" >highlightjs-copy</a>
,在程式碼區塊會浮現一個複製按鈕,就是它立大功。</p>
<p>一樣用 CDN 安裝,把上面的程式碼新增兩行:</p>
<pre><code class="language-xml">&lt;link
rel=&quot;stylesheet&quot;
href=&quot;https://unpkg.com/highlightjs-copy/dist/highlightjs-copy.min.css&quot;
/&gt;
&lt;script defer src=&quot;https://unpkg.com/highlightjs-copy/dist/highlightjs-copy.min.js&quot; onload=&quot;hljs.addPlugin(new CopyButtonPlugin());&quot;&gt;&lt;/script&gt;
</code></pre>
<p>最後就變成:</p>
<pre><code class="language-diff">{{ $version := &quot;11.9.0&quot; }}
{{ $base_url := printf &quot;https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@%s/build&quot; $version }}
{{ $langs := slice &quot;bash&quot; &quot;css&quot; &quot;elixir&quot; &quot;excel&quot; &quot;ini&quot; &quot;javascript&quot; &quot;markdown&quot; &quot;php&quot; &quot;python&quot; &quot;shell&quot; &quot;plaintext&quot; &quot;http&quot; &quot;json&quot; }}
&lt;link rel=&quot;stylesheet&quot; href=&quot;{{ $base_url }}/styles/default.min.css&quot;&gt;
+ &lt;link rel=&quot;stylesheet&quot; href=&quot;https://unpkg.com/highlightjs-copy/dist/highlightjs-copy.min.css&quot; /&gt;
&lt;script defer src=&quot;{{ $base_url }}/highlight.min.js&quot;&gt;&lt;/script&gt;
+ &lt;script defer src=&quot;https://unpkg.com/highlightjs-copy/dist/highlightjs-copy.min.js&quot; onload=&quot;hljs.addPlugin(new CopyButtonPlugin());&quot;&gt;&lt;/script&gt;
{{ range $langs }}
&lt;script defer src=&quot;{{ $base_url }}/languages/{{ . }}.min.js&quot;&gt;&lt;/script&gt;
{{ end }}
&lt;script defer&gt;
document.addEventListener('DOMContentLoaded', (event) =&gt; {
document.querySelectorAll('pre code').forEach((el) =&gt; {
hljs.highlightElement(el);
});
});
&lt;/script&gt;
</code></pre>
<h2 id="參考資料">參考資料 <a href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%96%99" class="hash g">#</a></h2>
<ul>
<li>官方網站 <a href="https://highlightjs.org/" target="_blank" >https://highlightjs.org/</a>
</li>
<li>官方文件 <a href="https://highlightjs.readthedocs.io/en/latest/index.html" target="_blank" >https://highlightjs.readthedocs.io/en/latest/index.html</a>
</li>
</ul></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/switch-prismjs-to-highlightjs-in-hugo/</guid><pubDate>Thu, 23 May 2024 23:38:36 +0800</pubDate></item><item><title>除錯日記#2 Elasticsearch refresh_inverval</title><link>https://sujingjhong.com/posts/debugday-2/</link><description><p>同事反應在服務上搜尋不到幾筆資料;但直接用文件識別碼 <code>_id</code> ,是可以在搜尋引擎(<code>elasticsearch</code>)上找到。</p>
<p>我們嘗試了幾種方式確認問題:</p>
<ul>
<li>以日期排序發現資料停留在 2023 年。</li>
<li>將關鍵字拆解丟進去,只能找到 2023 年前的資料。</li>
</ul>
<p>檢查爬蟲資料是正確無誤,也有寫入 elasticsearch,檢查 <code>mapping</code> 也看不出異常。</p>
<p>後來我人工比對不同資料集的索引資料,發現找不到資料的有 <code>refresh_inverval: -1</code></p>
<p>回想到之前在做 <code>elasticsearch</code> 升級時,需要重新索引資料,因為資料量很大,所以把 ES 的索引間隔關閉,在手動觸發去做批次索引。</p>
<p>看來就是那次關閉後,忘記開啟,導致後續資料無法正確索引。</p>
<p>直接透過 API 開啟後,就解決問題:</p>
<pre><code class="language-bash">curl -X PUT /{es_index}/_settings -H 'content-type: application/json' -d '{&quot;index&quot; : {&quot;refresh_interval&quot; : null}}'
</code></pre>
<h2 id="參考資料">參考資料 <a href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%96%99" class="hash g">#</a></h2>
<ul>
<li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html" target="_blank" >https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html</a>
</li>
</ul></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/debugday-2/</guid><pubDate>Wed, 22 May 2024 17:25:10 +0800</pubDate></item><item><title>除錯日記#1 FastAPI JSON Compatible Encoder</title><link>https://sujingjhong.com/posts/debugday-1/</link><description><p>同事 slack python <code>FastAPI</code> + <code>Pydantic</code> 使用問題。</p>
<p>簡言之,就是他用 <code>Pydantic</code> 做了一個 api response body model;但不知為何,測試時 <code>FastAPI</code> 就是不給用,會跳出錯誤訊息:</p>
<pre><code class="language-bash">Object of type FileListResponse is not JSON serializable
</code></pre>
<p>因為名稱是有 File,就猜測是要處理檔案。就之前使用 <code>FastAPI</code> 經驗,處理檔案時,要使用它框架物件。所以問了第一個問題:是否有使用框架物件。</p>
<p>發現是我誤解了,只是要處理檔案清單,並沒有要處理檔案。</p>
<p>接著想到 python 在處理序列化之類的 <strong>metaprogramming</strong> 是藉由實作約定協定 (protocol),例如 <code>JSON</code> 序列化可以採用繼承 <code>JSONEncoder</code> 方式。</p>
<p>就詢問有沒有實作或引用,沒注意到用 <code>Pydantic</code> 這方案問題通常就是交給套件解決。</p>
<p>那看來可能是 <code>FastAPI</code>,查了下文件,官方有提供相容性編碼器,就請他試試。</p>
<p>OK,解決!</p>
<h2 id="參考資料">參考資料 <a href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%96%99" class="hash g">#</a></h2>
<ul>
<li><a href="https://docs.python.org/3/library/json.html" target="_blank" >https://docs.python.org/3/library/json.html</a>
</li>
<li><a href="https://fastapi.tiangolo.com/tutorial/request-files/" target="_blank" >https://fastapi.tiangolo.com/tutorial/request-files/</a>
</li>
<li><a href="https://fastapi.tiangolo.com/tutorial/encoder/" target="_blank" >https://fastapi.tiangolo.com/tutorial/encoder/</a>
</li>
</ul></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/debugday-1/</guid><pubDate>Tue, 21 May 2024 01:33:00 +0000</pubDate></item><item><title>721原則讓你學以致用</title><link>https://sujingjhong.com/posts/721-principle-allows-you-to-apply-what-you-have-learned/</link><description><p>各位學新東西時,會不會遇到一種狀況:看教學總是懂了,實際來直接不行?</p>
<p>此時就可用 「721 原則」規劃學習模型:</p>
<p>10% 課程學,20% 問同學,70% 做中學。</p>
<p><a href="https://www.hbrtaiwan.com/article/22717/help-your-employees-develop-the-skills-they-really-need" target="_blank" >這是從哈佛商業評論雜誌中看到的</a>
,以及我以前在學程式時,意外地符合。</p>
<p>一開始時 10% 的正式學習,就是透過一個有系統、有架構的學習體驗,從裡面獲得知識,簡單講就是:上課、念教科書。</p>
<p>再來 20% 是「社交學習」,重點在和其他人學習,例如和導師、同學、同事,獲得他們的經驗談。</p>
<p>最後 70% 也是最重要的「做中學」,要在實際環境學習,工作上就是直接下去寫、實際做,要記得定期和其他人分享,分享自己的實踐經驗,同時也可以強化自己教學能力,一舉兩得。</p>
<p>我自己是在學寫程式時,無意間遵從這方式。</p>
<p>剛開始學 JavaScript 時,只稍微看了一下文件,了解語法怎麼寫(正式學習),接著就直接來寫專案(做中學)。有問題就問 Google,Stackoverflow(社交學習)。就一路到現在了。</p>
<p>如果學習卡關了,可以試著嘗試看看,說不定有用。</p></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/721-principle-allows-you-to-apply-what-you-have-learned/</guid><pubDate>Sun, 14 Jan 2024 22:17:56 +0000</pubDate></item><item><title>後資深工程師的四種職涯角色</title><link>https://sujingjhong.com/posts/four-types-of-post-senior-engineer/</link><description><h2 id="資深工程師的下一步">資深工程師的下一步 <a href="#%e8%b3%87%e6%b7%b1%e5%b7%a5%e7%a8%8b%e5%b8%ab%e7%9a%84%e4%b8%8b%e4%b8%80%e6%ad%a5" class="hash g">#</a></h2>
<p>當晉級到資深工程師後,後面的職涯就開始模糊了,你是否也有一樣的感受呢?</p>
<p>軟體工程師約莫 5-8 年後會晉級到職涯中的「資深工程師」(Senior Software Engineer)階段,不過現階段技術更迭以及累積速度加速下,可能會更加快速。在下一階段職涯中,大多數公司都是用「主任工程師」(Staff Engineer)這職稱描述。</p>
<p>這抽象的職稱也導致了一些問題:我需要具備什麼能力才能晉升到主任工程師?僅具有技術能力就可以了嘛?其他具有這職位的人是怎麼達到的呢?在職涯中,你的上級或者主管,應該要扮演什麼角色協助你呢?</p>
<p>最關鍵問題,當名片上刻印出新職稱後,職務內容真的是自身喜歡的嘛?或者能力是足以勝任的嘛?又或者只是另一位彼得原理的受難者呢?</p>
<blockquote>
<p>備註:彼得原理,在組織或企業的等級制度中,人會因其某種特質或特殊技能,令他被擢升到不能勝任的高階職位,最終變成組織的障礙物(冗員)及負資產。(摘自維基百科)</p>
</blockquote>
<h2 id="技術領袖架構師解題專家或是副手">技術領袖、架構師、解題專家或是副手 <a href="#%e6%8a%80%e8%a1%93%e9%a0%98%e8%a2%96%e6%9e%b6%e6%a7%8b%e5%b8%ab%e8%a7%a3%e9%a1%8c%e5%b0%88%e5%ae%b6%e6%88%96%e6%98%af%e5%89%af%e6%89%8b" class="hash g">#</a></h2>
<p>一位美國工程師 Will Larson 注意到這問題,他訪談了許多工程師,在他的書中 Staff Engineer 總結了四種主任工程師在公司中扮演的角色,分別是:技術領袖(Tech Lead)、架構師(Architect)、解題專家(Solver)以及副手(Right Hand)。</p>
<p>技術領袖,是組織中的技術擔當,負責在專案中或者小組中,指引與執行技術方案,同時他們也會密切和少數幾位經理人共同協作。在部份組織中,甚至有技術總監(Tech Lead Manager),他們職責和技術領袖一樣,不過多加了領導技術領袖的人事責任。</p>
<p>架構師,這在台灣業界應該是最常耳聞的,在作者說明中,架構師是負責在一個範圍中,負責指引開發方向、交付品質以及執行方案之人,所以必須具備有整合了解技術限制、使用者需求,以及組織層面的領導力,在責任領域中掌舵。</p>
<p>解題專家,如這個名詞,他們是特定問題或者隨機問題的解答者,他們負責處理組織中棘手、複雜的問題,並運用他們技術與領域知識,在混亂中尋找出路,不一定是解法但至少是前進的方向。他們關注的問題,可能長時間的研究發現,也有可能是在不同問題間不斷跳轉。</p>
<p>副手,他們獲得經理人或者管理階層的信任與授權,延伸經理階級的注意範圍與視野,同時也會營運一定組織活動,負責在大型組織中,協助經理人聚焦問題。看到他們,就像是看見背後的管理階層。</p>
<h2 id="組織影響甚於個人決定">組織影響甚於個人決定 <a href="#%e7%b5%84%e7%b9%94%e5%bd%b1%e9%9f%bf%e7%94%9a%e6%96%bc%e5%80%8b%e4%ba%ba%e6%b1%ba%e5%ae%9a" class="hash g">#</a></h2>
<p>介紹完四種樣態後,身為一位工程師你該如何做選擇?就組織職涯上來說,這大部分取決於公司當下的需求,現階段公司需要什麼樣的主任工程師。</p>
<p>最普遍也是最通常的情況,就是技術領袖型的主任工程師,他也是所有主任工程師原型樣貌,因為公司通常最需要工程師解決技術問題,也是工程師職涯的起點。當公司組織文化強調單兵作戰更甚於團隊作業時,這時他們就需要解題專家,專門克服技術問題,反之,強調團隊、方法論的公司,對此種樣態需求較低。當組織發展迅速或者是百人以上時,這時候就會需要架構師或者副手,進行整體面的規劃與管理。但也必須說,每間公司都有各自的文化,需要什麼樣的工程師,取決於公司文化、歷史與當下的需求。</p>
<p>從工作性質面向考量,技術領袖以及架構師,多半需要有一協作團隊,並且有著長期發展目標,會需要緊密的團隊合作關係,但缺點是,在組織中容易存在甚久,而喪失存在感。至於解題專家以及副手,他們多半是扮演救火隊的角色,在不同的問題、危機、團隊中打轉,相較於緊密的團隊合作,他們多半是需要與不同團隊進行專案式合作,進行短期、間歇、任務性的協作,以及通常是管理階層焦點在哪,他們就在哪,他們的目標優先度與管理階層齊平。</p>
<p>就大多數情況,成為什麼型的工程師,比起個人選擇,更多是組織需求。但也必須認識到這只是四種原型,在職場中,應該都是混合角色,而是何種成份較多。</p>
<h2 id="用技術解決問題的王道之路">用技術解決問題的王道之路 <a href="#%e7%94%a8%e6%8a%80%e8%a1%93%e8%a7%a3%e6%b1%ba%e5%95%8f%e9%a1%8c%e7%9a%84%e7%8e%8b%e9%81%93%e4%b9%8b%e8%b7%af" class="hash g">#</a></h2>
<p>作者提出四種樣貌,剛好統整了技術能力、領域知識、組織領導力的不同發展方向。技術領袖偏重於技術能力,解題專家專精於領域知識,副手著重於組織領導力,而架構師則是盡可能三者間取得平衡。</p>
<p>還是老話一句:擇你所愛,愛你所擇。這裡同意作者所說的,要選擇可以讓你感受到「激勵」的選項,也必須承認人在江湖,身不由己,都在這職位上,工程師天職就是「用技術解決問題」,至於什麼是技術這是定義問題,但盡可能充實技術來解決問題,永遠是不敗的解法。</p></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/four-types-of-post-senior-engineer/</guid><pubDate>Wed, 19 Oct 2022 01:30:00 +0800</pubDate></item><item><title>Linux / WireGuard note on Linux Debian 10 in 2021</title><link>https://sujingjhong.com/posts/wireguard-note-on-linux-debian-10-in-2021/</link><description><h1 id="wireguard-note-on-linux-debian-10-in-2021">WireGuard note on Linux Debian 10 in 2021 <a href="#wireguard-note-on-linux-debian-10-in-2021" class="hash g">#</a></h1>
<p>Linux version: Debian 10</p>
<h2 id="vpn-server">VPN Server <a href="#vpn-server" class="hash g">#</a></h2>
<p>First, create a vm on cloud:</p>
<p>Run:</p>
<pre><code class="language-sh"># admin
sudo su
# install wireguard
apt-get install wireguard
# set folder and set umask
cd /etc/wireguard
umask 002
# generate private and public key
wg genkey | tee privkey | wg pubkey &gt; pubkey
# create config file
touch /etc/wireguard/wg0.conf
</code></pre>
<p>Create <code>/etc/wireguard/wg0.conf</code>:</p>
<pre><code class="language-ini">[Interface]
Address = &lt;VPN_SREVER_IP&gt;
ListenPort = &lt;VPN_SREVER_PORT&gt;
PrivateKey = &lt;VPN_SERVER_PRIVATE_KEY&gt;
# substitute eth0 in the following lines to match the Internet-facing interface
# if the server is behind a router and receives traffic via NAT, these iptables rules are not needed
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth4 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth4 -j MASQUERADE
</code></pre>
<p>Enable following settings on <code>/etc/sysctl.d/99-sysctl.conf</code>:</p>
<pre><code class="language-config">net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding=1
</code></pre>
<p>Then activate new settings:</p>
<pre><code class="language-sh">sysctl -p
</code></pre>
<p>Enable wireguard service when (re)start the server:</p>
<pre><code class="language-sh">systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
</code></pre>
<p>Extra, restart wireguard service if the settings changed:</p>
<pre><code class="language-sh">wg addconf wg0 &lt;(wg-quick strip wg0)
</code></pre>
<h2 id="vpn-client">VPN Client <a href="#vpn-client" class="hash g">#</a></h2>
<pre><code class="language-ini">[Interface]
Address = &lt;CLIENT_IP&gt;
PrivateKey = &lt;CLIENT_PRIVATE_KEY&gt;
[Peer]
AllowedIPs = 0.0.0.0/0 # traffic to vpn
Endpoint = &lt;VPN_SERVER_DOMAIN_OR_IP&gt;
PersistentKeepalive = 30
PublicKey = &lt;VPN_SERVER_PUBLIC_KEY&gt;
</code></pre>
<p>Then add to vpn server congig:</p>
<pre><code class="language-ini">[Peer]
AllowIPs = &lt;VPN_CLIENT_IP&gt;
PublicKey = &lt;CLIENT_PUBLIC_KEY&gt;
</code></pre>
<h2 id="trouble-shooting">Trouble shooting <a href="#trouble-shooting" class="hash g">#</a></h2>
<h3 id="unable-to-access-interface-protocol-not-supported">Unable to access interface: Protocol not supported <a href="#unable-to-access-interface-protocol-not-supported" class="hash g">#</a></h3>
<p>Error:</p>
<pre><code class="language-sh">wg-quick[9098]: [#] ip link add wg0 type wireguard
wg-quick[9098]: RTNETLINK answers: Operation not supported
wg-quick[9098]: Unable to access interface: Protocol not supported
</code></pre>
<p>Solution:</p>
<pre><code class="language-sh">apt install linux-headers-$(uname -r)
</code></pre>
<h3 id="unable-to-connect-lan-behind-the-vpn-server">Unable to connect LAN behind the VPN server <a href="#unable-to-connect-lan-behind-the-vpn-server" class="hash g">#</a></h3>
<p>Run:</p>
<pre><code class="language-sh">iptables -t nat -A POSTROUTING -j MASQUERADE
</code></pre></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/wireguard-note-on-linux-debian-10-in-2021/</guid><pubDate>Fri, 28 May 2021 02:02:00 +0800</pubDate></item><item><title>法律 / 函釋的量詞是什麼</title><link>https://sujingjhong.com/posts/what-is-the-correct-quantifier-for-interpretive-rules/</link><description><p>最近朋友問了一個問題:「函釋的量詞是什麼」?</p>
<p>一則函釋?一篇函釋?一個函釋?一份函釋?</p>
<p>有趣的問題,只好借用自家的搜尋引擎來查查,公文中通常是用什麼詞彙來計量函釋。</p>
<p><a href="https://lawsnote.com/itp-rule/5fbee2781e788e9be721fbb5" target="_blank" >法務部法矯字第10904008750號</a>
</p>
<blockquote>
<p>要旨:
法務部83年 1月 1日法83監字第 01423號函等 3則行政函釋,自即日起全部或部分停止適用</p>
</blockquote>
<p><a href="https://lawsnote.com/itp-rule/5dc846155ae6bb95561eeb4b" target="_blank" >金融監督管理委員會金管銀合字第10330003740號</a>
</p>
<blockquote>
<p>要旨:
財政部67年 1月26日台財錢字第 10938號函等27則行政函釋自 104年 1月1 日停止適用</p>
</blockquote>
<p><a href="https://lawsnote.com/itp-rule/5dc827eb5ae6bb95560af3a4" target="_blank" >內政部台內地字第10613045301號函</a>
</p>
<blockquote>
<p>要旨:
為配合地目等則制度自 106年 1月 1日廢除,停止適用涉及地目等則之26則函釋</p>
</blockquote>
<p>至於用「篇」則是沒找到什麼。</p>
<p>所以我們就推測在公文用語中,通常都是用「則」來計數函釋數量。</p></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/what-is-the-correct-quantifier-for-interpretive-rules/</guid><pubDate>Sat, 16 Jan 2021 08:00:06 +0000</pubDate></item><item><title>法律 / 除戶者如何申請單身宣誓書</title><link>https://sujingjhong.com/posts/can-a-housebold-registered-person-apply-an-affidavit-to-single-status/</link><description><p>先說結論:不能。</p>
<p>最近要幫國外親友代辦單身宣誓書,但申請戶籍謄本後發現,他已經被除戶了。</p>
<p>拿去給公證人時,他說除戶謄本 <strong>「不能反應除戶後至今的婚姻狀況」</strong>,婉拒我的辦理。</p>
<p>那怎辦呢?只好寫信給司法信箱問問看有沒有其他辦法,但很可惜仍然不行。</p>
<p>如果仍要辦理的話:</p>
<ol>
<li>看駐外使館有沒有辦法辦理</li>
<li>回國遷入後再來辦理</li>
</ol>
<p>以下是司法信箱的回函:</p>
<blockquote>
<p>OO君您好:</p>
<p>您於 110 年 1 月 8 日寄給本院「司法信箱」的電子郵件,詢問有關如何代辦除戶者單身宣誓書一事,為您說明如下:</p>
<p>一、 公證法施行細則第 51 條第 5 款規定:「有下列第一款至第四款情形之一者,公證人應拒絕公、認證之請求;有第五款情形者,得拒絕請求。但其情形可補正者,公證人應當場或定期先命補正:五、請求認證之內容無從查考或不明。」單身宣誓書係證明當事人現為單身之文件,而當事人經戶政事務所逕為遷出登記後,遷出期間是否有應戶籍登記之事項不明,其個人之現行婚姻狀況亦不詳,故公證人藉由除戶之戶籍謄本無從查考當事人是否現為單身,宜請該當事人回國辦理遷入登記後,再行向公證人請求辦理單身宣誓書。</p>
<p>二、 您如有其他公證法律問題,得向各地方法院公證處或訴訟輔導單位尋求協助。
感謝! 您的來信,祝您萬事如意!</p>
<p>司法院 民事廳</p>
</blockquote></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/can-a-housebold-registered-person-apply-an-affidavit-to-single-status/</guid><pubDate>Sat, 16 Jan 2021 07:49:53 +0000</pubDate></item><item><title>Python / 如何使用 python 繪製世界地圖</title><link>https://sujingjhong.com/posts/how-to-draw-world-map-by-python/</link><description><p>同學問到要怎麼使用 python 繪製世界地圖,並且呈現相關資料,就稍微研究了一下</p>
<p>主要使用:</p>
<ul>
<li>altair: 繪製套件,可以製作可互動的圖表,好用</li>
<li>pycountry: 用來查詢國家的相關資料</li>
</ul>
<p>使用的資料 winemag-data_first150k 是來自 kaggle</p>
<pre><code class="language-python">import pandas as pd
import numpy as np
import pycountry
def find_code_by_name(x):
data = pycountry.countries.get(name=x) or pycountry.countries.get(official_name=x) or pycountry.countries.get(alpha_2=x)
return int(data.numeric) if data is not None else np.nan
df = pd.read_csv('./winemag-data_first150k.csv')
df2 = df[['country', 'points']].groupby(by='country').mean().reset_index()
df2['id'] = df2.country.apply(find_code_by_name)
df2.head(10)
</code></pre>
<div>
<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
<pre><code>.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</code></pre>
<p></style></p>
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>country</th>
<th>points</th>
<th>id</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>Albania</td>
<td>88.000000</td>
<td>8.0</td>
</tr>
<tr>
<th>1</th>
<td>Argentina</td>
<td>85.996093</td>
<td>32.0</td>
</tr>
<tr>
<th>2</th>
<td>Australia</td>
<td>87.892475</td>
<td>36.0</td>
</tr>
<tr>
<th>3</th>
<td>Austria</td>
<td>89.276742</td>
<td>40.0</td>
</tr>
<tr>
<th>4</th>
<td>Bosnia and Herzegovina</td>
<td>84.750000</td>
<td>70.0</td>
</tr>
<tr>
<th>5</th>
<td>Brazil</td>
<td>83.240000</td>
<td>76.0</td>
</tr>
<tr>
<th>6</th>
<td>Bulgaria</td>
<td>85.467532</td>
<td>100.0</td>
</tr>
<tr>
<th>7</th>
<td>Canada</td>
<td>88.239796</td>
<td>124.0</td>
</tr>
<tr>
<th>8</th>
<td>Chile</td>
<td>86.296768</td>
<td>152.0</td>
</tr>
<tr>
<th>9</th>
<td>China</td>
<td>82.000000</td>
<td>156.0</td>
</tr>
</tbody>
</table>
</div>
<pre><code class="language-python">import altair as alt
from vega_datasets import data
countries = alt.topo_feature(data.world_110m.url, 'countries')
base = alt.Chart(countries).mark_geoshape(fill='#666666',stroke='white')
points = alt.Chart(countries).mark_geoshape().encode(color='points:Q', tooltip=['country:N','points:Q']).transform_lookup(lookup='id', from_=alt.LookupData(df2, key='id', fields=['country','points']))
layers = alt.layer(base, points).project('equirectangular').properties(width=600, height=400).configure_view(stroke=None)
layers
</code></pre>
<div id="altair-viz-4f507e50d3854f9ca34c607132ca2480"></div>
<script type="text/javascript">
(function(spec, embedOpt){
let outputDiv = document.currentScript.previousElementSibling;
if (outputDiv.id !== "altair-viz-4f507e50d3854f9ca34c607132ca2480") {
outputDiv = document.getElementById("altair-viz-4f507e50d3854f9ca34c607132ca2480");
}
const paths = {
"vega": "https://cdn.jsdelivr.net/npm//vega@5?noext",
"vega-lib": "https://cdn.jsdelivr.net/npm//vega-lib?noext",
"vega-lite": "https://cdn.jsdelivr.net/npm//[email protected]?noext",
"vega-embed": "https://cdn.jsdelivr.net/npm//vega-embed@6?noext",
};
function loadScript(lib) {
return new Promise(function(resolve, reject) {
var s = document.createElement('script');
s.src = paths[lib];
s.async = true;
s.onload = () => resolve(paths[lib]);
s.onerror = () => reject(`Error loading script: ${paths[lib]}`);
document.getElementsByTagName("head")[0].appendChild(s);
});
}
function showError(err) {
outputDiv.innerHTML = `<div class="error" style="color:red;">${err}</div>`;
throw err;
}
function displayChart(vegaEmbed) {
vegaEmbed(outputDiv, spec, embedOpt)
.catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));
}
if(typeof define === "function" && define.amd) {
requirejs.config({paths});
require(["vega-embed"], displayChart, err => showError(`Error loading script: ${err.message}`));
} else if (typeof vegaEmbed === "function") {
displayChart(vegaEmbed);
} else {
loadScript("vega")
.then(() => loadScript("vega-lite"))
.then(() => loadScript("vega-embed"))
.catch(showError)
.then(() => displayChart(vegaEmbed));
}
})({"config": {"view": {"continuousWidth": 400, "continuousHeight": 300, "stroke": null}}, "layer": [{"mark": {"type": "geoshape", "fill": "#666666", "stroke": "white"}}, {"mark": "geoshape", "encoding": {"color": {"type": "quantitative", "field": "points"}, "tooltip": [{"type": "nominal", "field": "country"}, {"type": "quantitative", "field": "points"}]}, "transform": [{"lookup": "id", "from": {"data": {"name": "data-59af1829771cc6a49ea4955e475820f8"}, "key": "id", "fields": ["country", "points"]}}]}], "data": {"url": "https://cdn.jsdelivr.net/npm/[email protected]/data/world-110m.json", "format": {"feature": "countries", "type": "topojson"}}, "height": 400, "projection": {"type": "equirectangular"}, "width": 600, "$schema": "https://vega.github.io/schema/vega-lite/v4.8.1.json", "datasets": {"data-59af1829771cc6a49ea4955e475820f8": [{"country": "Albania", "points": 88.0, "id": 8.0}, {"country": "Argentina", "points": 85.9960930562955, "id": 32.0}, {"country": "Australia", "points": 87.89247528747227, "id": 36.0}, {"country": "Austria", "points": 89.27674190382729, "id": 40.0}, {"country": "Bosnia and Herzegovina", "points": 84.75, "id": 70.0}, {"country": "Brazil", "points": 83.24, "id": 76.0}, {"country": "Bulgaria", "points": 85.46753246753246, "id": 100.0}, {"country": "Canada", "points": 88.23979591836735, "id": 124.0}, {"country": "Chile", "points": 86.29676753782668, "id": 152.0}, {"country": "China", "points": 82.0, "id": 156.0}, {"country": "Croatia", "points": 86.28089887640449, "id": 191.0}, {"country": "Cyprus", "points": 85.87096774193549, "id": 196.0}, {"country": "Czech Republic", "points": 85.83333333333333, "id": 203.0}, {"country": "Egypt", "points": 83.66666666666667, "id": 818.0}, {"country": "England", "points": 92.88888888888889, "id": null}, {"country": "France", "points": 88.92586975068727, "id": 250.0}, {"country": "Georgia", "points": 85.51162790697674, "id": 268.0}, {"country": "Germany", "points": 88.62642740619903, "id": 276.0}, {"country": "Greece", "points": 86.11764705882354, "id": 300.0}, {"country": "Hungary", "points": 87.32900432900433, "id": 348.0}, {"country": "India", "points": 87.625, "id": 356.0}, {"country": "Israel", "points": 87.17619047619047, "id": 376.0}, {"country": "Italy", "points": 88.41366385552432, "id": 380.0}, {"country": "Japan", "points": 85.0, "id": 392.0}, {"country": "Lebanon", "points": 85.70270270270271, "id": 422.0}, {"country": "Lithuania", "points": 84.25, "id": 440.0}, {"country": "Luxembourg", "points": 87.0, "id": 442.0}, {"country": "Macedonia", "points": 84.8125, "id": null}, {"country": "Mexico", "points": 84.76190476190476, "id": 484.0}, {"country": "Moldova", "points": 84.71830985915493, "id": null}, {"country": "Montenegro", "points": 82.0, "id": 499.0}, {"country": "Morocco", "points": 88.16666666666667, "id": 504.0}, {"country": "New Zealand", "points": 87.55421686746988, "id": 554.0}, {"country": "Portugal", "points": 88.05768508079669, "id": 620.0}, {"country": "Romania", "points": 84.92086330935251, "id": 642.0}, {"country": "Serbia", "points": 87.71428571428571, "id": 688.0}, {"country": "Slovakia", "points": 83.66666666666667, "id": 703.0}, {"country": "Slovenia", "points": 88.23404255319149, "id": 705.0}, {"country": "South Africa", "points": 87.22542072630647, "id": 710.0}, {"country": "South Korea", "points": 81.5, "id": null}, {"country": "Spain", "points": 86.64658925979681, "id": 724.0}, {"country": "Switzerland", "points": 87.25, "id": 756.0}, {"country": "Tunisia", "points": 86.0, "id": 788.0}, {"country": "Turkey", "points": 88.09615384615384, "id": 792.0}, {"country": "US", "points": 87.81878936487331, "id": 840.0}, {"country": "US-France", "points": 88.0, "id": null}, {"country": "Ukraine", "points": 84.6, "id": 804.0}, {"country": "Uruguay", "points": 84.47826086956522, "id": 858.0}]}}, {"mode": "vega-lite"});
</script></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/how-to-draw-world-map-by-python/</guid><pubDate>Sun, 10 Jan 2021 13:58:40 +0000</pubDate></item><item><title>Python / 如何在 Colab 上讓 python 視覺化套件 matplotlib 顯示中文</title><link>https://sujingjhong.com/posts/how-to-show-matplotlib-visual-packages-in-chinese-on-colab/</link><description><h1 id="如何在-colab-上讓-python-視覺化套件-matplotlib-顯示中文">如何在 Colab 上讓 python 視覺化套件 matplotlib 顯示中文 <a href="#%e5%a6%82%e4%bd%95%e5%9c%a8-colab-%e4%b8%8a%e8%ae%93-python-%e8%a6%96%e8%a6%ba%e5%8c%96%e5%a5%97%e4%bb%b6-matplotlib-%e9%a1%af%e7%a4%ba%e4%b8%ad%e6%96%87" class="hash g">#</a></h1>
<p>目前上課後為了避免環境問題,已經習慣在 Colab 上開發,但最後也免不了因為 CJK 問題,還是要解決中文顯示問題。</p>
<h2 id="主要流程">主要流程 <a href="#%e4%b8%bb%e8%a6%81%e6%b5%81%e7%a8%8b" class="hash g">#</a></h2>
<p>大致上流程很簡單:</p>
<ol>
<li>下載開源字體,目前是使用 Google 的 Noto Sans 系列</li>
<li>將開源字體下載後自動移入字型資料夾</li>
<li>在 matplotlib 設定字型參數</li>
</ol>
<h2 id="gist">Gist <a href="#gist" class="hash g">#</a></h2>
<p>這裡一樣將詳細程式碼附上</p>
<script src="https://gist.github.com/lisez/4374189f1e605010d1d3245485a803a9.js"></script></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/how-to-show-matplotlib-visual-packages-in-chinese-on-colab/</guid><pubDate>Mon, 02 Nov 2020 17:57:00 +0000</pubDate></item><item><title>其他 / 2020 線上保險資訊平臺</title><link>https://sujingjhong.com/posts/2020-online-insurance-platforms/</link><description><p>稍微整理一下目前台灣的線上保險資訊平臺。</p>
<p>目前個人是用 My83 大概的篩出自己想要的保險額度還有保單或落在哪,然後再用 Finfo 去做細部的修正。</p>
<h2 id="finfo-保險資訊站">Finfo 保險資訊站 <a href="#finfo-%e4%bf%9d%e9%9a%aa%e8%b3%87%e8%a8%8a%e7%ab%99" class="hash g">#</a></h2>
<p><a href="https://finfo.tw/" target="_blank" >https://finfo.tw/</a>
</p>
<ul>
<li>優:使用起來最平易近人,不會把所有資訊都塞給你,但是又可以在網站其他地方找到該保險的重要資訊,並且提供適當的試算工具,可以輕易了解保額對保障內容影響多大。並且提供客製化保單組合工具,可以在網站上做各家保單組合,可以說是保險資訊界的原價屋。</li>
<li>缺:沒辦法針對保險項目去做推薦分析,例如說想要意外失能一次性給付 500 萬然後每月給付 5 萬,這樣去做保險組合,目前此網站沒辦法。</li>
</ul>
<h2 id="my83-保險網">My83 保險網 <a href="#my83-%e4%bf%9d%e9%9a%aa%e7%b6%b2" class="hash g">#</a></h2>
<p><a href="https://my83.com.tw/" target="_blank" >https://my83.com.tw/</a>
</p>
<ul>
<li>優:使用上起來和 Finfo 差不多。不過有提供 QA 式風險分析提供保險組合推薦系統,可以針對各種保險項目的額度組合想要的保險,例如上面說的想要意外失能一次性給付 500 萬,加上每月給付 5 萬,他的系統就可以操作。</li>
<li>缺:推薦系統是優點,也就成為缺點,沒辦法針對各家保單去做客製化調整,只能用他的推薦工具篩選。保險項目是直接連到各家 DM/條款,沒有針對重點項目說明。</li>
</ul>
<h2 id="買保險-smartbeb">買保險 SmartBeb <a href="#%e8%b2%b7%e4%bf%9d%e9%9a%aa-smartbeb" class="hash g">#</a></h2>
<p><a href="https://www.smartbeb.com.tw/" target="_blank" >https://www.smartbeb.com.tw/</a>
</p>
<p>簡易的推薦系統,然後就要填表單洽業務。</p>
<p>感覺比較只是線上導線下的平台,沒辦法提供進一步線上客製化規劃。</p></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/2020-online-insurance-platforms/</guid><pubDate>Sat, 13 Jun 2020 21:52:47 +0800</pubDate></item><item><title>JS / IE11 中的 React is not defined</title><link>https://sujingjhong.com/posts/ie11-react-is-not-undefined/</link><description><p>之前一如往常地升級公司產品中使用的相依套件,接著也部署到開發環境上,然後因為我們公司產品,至少需要支援到 IE 11,所以也有對程式碼進行 ES5 語法做檢查。</p>
<p>結果就是這個 moment,主管開啟 windows 7,進行 IE 11 相容性測試,沒想到就直接白畫面!</p>
<p>追蹤一下錯誤流程,是在 <code>webapck</code> 產生的 <code>runtime.js</code> 中發生錯誤,然後錯誤訊息是:</p>
<pre><code>React is not defined
</code></pre>
<p>很棒。</p>
<p>重新思考一下這 bug 發生過程,基本上就是當 <code>webpack</code> 中的 <code>runtime.js</code> 開始要執行各個檔案時,他要用到 <code>React</code> 這個套件時,此套件並不存在於環境當中。</p>
<p>這問題有幾種可能:</p>
<ol>
<li>第三方套件並沒有被正確讀入</li>
<li>第三方套件其實有正確讀入,只是在 <code>runtime.js</code> 這隻程式執行期間,並未存在。</li>
</ol>
<p>這時候我們在控制台打入了:</p>
<pre><code>React
</code></pre>
<p>直接出現 <code>undefined</code> ,代表現在 <code>windows</code> 物件底下並未有 <code>React</code> 物件。</p>
<p>此時後我重新去看一下 <code>html</code> 載入各個 script 的順序,<code>React</code> 的確是優先於 <code>runtime.js</code> 載入,那此時沒有,代表 <code>React</code> 套件載入中發生了問題。</p>
<p>為求保險起見,我在執行正常的環境中,把 <code>windows</code> 物件內容枚舉起來,他們也都有正確的載入物件,存在 <code>windows</code> 物件底下。</p>
<p>接著我看一下 <code>IE11</code> 中的網路活動,突然發現 React 套件的網址竟然是:</p>
<pre><code>https://cdn.jsdelivr.net/npm/react@%5E16.13.1/umd/react.production.min.js
</code></pre>
<p>中間竟然出現了逃逸字元!</p>
<p>接著再看正確運作的環境:</p>
<pre><code>https://cdn.jsdelivr.net/npm/react@^16.13.1/umd/react.production.min.js
</code></pre>
<p>OK,答案揭曉了,<strong>原來是中間的 <code>^</code> 符號,在 IE 11 中被逃逸掉了。</strong></p>
<p>有趣的來了,我們用另一台 windows 10 的 IE 11開啟網站,沒想到竟然正常開啟?結果對一下版號,<strong>原來我們測試機中的 IE 11 是比較舊的,然後再較新的 IE 11 中他就不會逃逸。</strong></p>
<p>於是為了保險起見,於是我們就將那個符號全部替換掉。解決這個問題。</p></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/ie11-react-is-not-undefined/</guid><pubDate>Sun, 31 May 2020 00:02:58 +0800</pubDate></item><item><title>CSS / 列印時跨頁時文字被截掉、切成兩半</title><link>https://sujingjhong.com/posts/flexbox-print-issue/</link><description><p>最近有公司產品有客服反應,我們前端頁面在列印時,在跨頁時字會被截掉,在換頁的地方會被切成兩半。然後我在火狐上測試,會無法列印全部網頁範圍。這真是一個神奇的問題。</p>
<p>類似變成這樣:</p>
<p>
<img
src="https://sujingjhong.com/images/2020/cutting_word_at_pagebreak.png"
alt="在換頁處被截斷"
loading="lazy"
decoding="async"
width="326"
height="70"
class="full-width"
/>
</p>
<p>一開始是找到這些:</p>
<ul>
<li><a href="https://stackoverflow.com/questions/907680/css-printing-avoiding-cut-in-half-divs-between-pages" target="_blank" >CSS Printing: Avoiding cut-in-half DIVs between pages?</a>
</li>
<li><a href="https://stackoverflow.com/questions/36950165/text-cut-at-page-break-when-print" target="_blank" >Text cut at page break when print</a>
</li>
</ul>
<p>就是在列印模式時,將 <code>div</code> 或者要列印的區域,將 <code>page-break-inside</code> 設定為 <code>avoid</code>:</p>
<pre><code class="language-CSS">@media print
{
div{
page-break-inside: avoid;
}
}
</code></pre>
<p>但我們實際測試後,在很長的頁面還是有一樣的問題。這下真的難倒我了,四處尋覓還是找不到合適的解決方案。</p>
<p>經過一天後,我主管回信客服已經上 hotfix 解決此問題,讓我十分好奇到底是怎麼解開的?</p>
<p>原來銳眼如他,發現在列印時,上下高度的間距竟然些微不同,經過一陣尋覓後,發現是外層 <code>container</code> 的 <code>flex</code> 屬性惹禍,於是就在列印模式中,將那些元素設定成 <code>display: block</code> 或是 <code>display: inline-block</code>,不再使用 <code>flex</code> 彈性調整。</p>
<p>接下來再使用適當的關鍵字果然就查到類似的問題了:</p>
<ul>
<li><a href="https://stackoverflow.com/questions/45414152/css-flex-box-not-printing-all-pages-on-firefox" target="_blank" >CSS: Flex Box not printing all pages on Firefox</a>
</li>
<li><a href="https://stackoverflow.com/questions/20408033/how-to-get-page-break-inside-avoid-to-work-nicely-with-flex-wrap-wrap" target="_blank" >How to get <code>page-break-inside: avoid</code> to work nicely with <code>flex-wrap: wrap</code></a>
</li>
</ul></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/flexbox-print-issue/</guid><pubDate>Mon, 25 May 2020 22:26:46 +0800</pubDate></item><item><title>Hugo / 如何使用 Github Actions 自動部署 Hugo</title><link>https://sujingjhong.com/posts/how-to-deploy-hugo-automatically-with-github-actions/</link><description><h1 id="如何使用-github-actions-自動部署-hugo">如何使用 Github Actions 自動部署 Hugo <a href="#%e5%a6%82%e4%bd%95%e4%bd%bf%e7%94%a8-github-actions-%e8%87%aa%e5%8b%95%e9%83%a8%e7%bd%b2-hugo" class="hash g">#</a></h1>
<p>目前網站是部署在 <a href="https://github.com/lisez/sujingjhong.com" target="_blank" >GitHub 的 repo</a>
。</p>
<p>然後每次發表新文章時都要做以下動作:</p>
<ul>
<li>使用 Hugo 重新產生網站</li>
<li>將原始檔案 commit 到 git</li>
<li>將產生的檔案 commit 到 git</li>
</ul>
<p>不過因為網站和 repo 都是託管在 GitHub,就想說是不是可以直接在 GitHub 上讓他自動部署就好,我只要把文件上傳上去,接著 CI 就幫我弄好了。</p>
<p>剛好之前 GitHub 就有推出 GitHub Actions,就是 GitHub 自己的 CI/CD 服務。剛好可以來試試看,然後一如往常這撞牆失敗好幾次,花了一兩小時終於把它弄好了。</p>
<p>本篇文章將不會介紹 GitHub Actoins 以及他各個細部指令,只會單純就我目前需要的情境做介紹。</p>
<h2 id="目前更新文章的流程">目前更新文章的流程 <a href="#%e7%9b%ae%e5%89%8d%e6%9b%b4%e6%96%b0%e6%96%87%e7%ab%a0%e7%9a%84%e6%b5%81%e7%a8%8b" class="hash g">#</a></h2>
<p>第一個我先整理目前手動更新文章的流程:</p>
<ol>
<li>撰寫新文章</li>
<li>將文章commit到文件repo</li>
<li>用Hugo產生靜態文件</li>
<li>將靜態文件commit到網站repo</li>
</ol>
<p>基本上可以自動化的就是後面 3、4,我需要他 commit 到 github repo 上後,直接用 GitHub Actions 幫我做完後面的 3、4。</p>
<h2 id="更新文章流程自動化">更新文章流程自動化 <a href="#%e6%9b%b4%e6%96%b0%e6%96%87%e7%ab%a0%e6%b5%81%e7%a8%8b%e8%87%aa%e5%8b%95%e5%8c%96" class="hash g">#</a></h2>
<script src="https://gist.github.com/lisez/41cebe4eb9190a5c5e879fee5933beb1.js"></script></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/how-to-deploy-hugo-automatically-with-github-actions/</guid><pubDate>Fri, 31 Jan 2020 18:21:32 +0800</pubDate></item><item><title>Elixir 101 / 快速建立 HTTP 伺服器</title><link>https://sujingjhong.com/posts/elixir101/elixir-101-make-a-http-server-quickly/</link><description><p>在 Elixir 裡要怎麼架設 HTTP 伺服器呢?</p>
<h2 id="在-nodejs-架設-http-伺服器">在 NodeJS 架設 HTTP 伺服器 <a href="#%e5%9c%a8-nodejs-%e6%9e%b6%e8%a8%ad-http-%e4%bc%ba%e6%9c%8d%e5%99%a8" class="hash g">#</a></h2>
<p>如果是在 NodeJS 底下,我們可以很快透過以下的程式碼建立簡單的伺服器:</p>
<pre><code class="language-javascript">const http = require('http');
function serverHandler(req, res){
switch (req.url){
case '/':{
res.writeHead(200, {'Content-Type':'text/html'});
res.write('Hello world');
res.end();
break;
}
default:{
res.writeHead(404, {'Content-Type':'text/html'});
res.end('Not found');
break;
}
}
}
const server = http.createServer(serverHandler);
server.listen(3001);
</code></pre>
<p>但通常我們在 NodeJS 裡面不會這樣直接呼叫 <code>http.createServer</code> 來建立伺服器,我們會利用一些 Web 框架來幫助我們,讓開發上更加便利,<a href="https://github.com/expressjs/express" target="_blank" >例如 <code>express</code></a>
,例如上述程式碼可以改成:</p>
<pre><code class="language-javascript">const server = require('express')();
server.get('/', function (req, res) {
res.send('Hello World')
});
server.listen(3000);
</code></pre>
<h2 id="在-elixir-中架設伺服器">在 Elixir 中架設伺服器 <a href="#%e5%9c%a8-elixir-%e4%b8%ad%e6%9e%b6%e8%a8%ad%e4%bc%ba%e6%9c%8d%e5%99%a8" class="hash g">#</a></h2>
<p>所以一樣的,在 Elixir 中,<a href="https://github.com/elixir-plug/plug" target="_blank" >我們透過一套名為 <code>plug</code> 的中間層套件</a>
,協助我們架設伺服器。</p>
<p>首先我們先建立一個乾淨的專案,並且啟用 <code>supervision</code> 模式:</p>
<pre><code class="language-shell">$ mix new http_server --sup
</code></pre>
<p>讓我們看一下資料夾結構:</p>
<pre><code class="language-shell">.
├── README.md
├── lib
│ ├── http_server
│ │ └── application.ex
│ └── http_server.ex
├── mix.exs
└── test
├── http_server_test.exs
└── test_helper.exs
</code></pre>
<h3 id="安裝-plug">安裝 plug <a href="#%e5%ae%89%e8%a3%9d-plug" class="hash g">#</a></h3>
<p>首先我們需要先安裝 <code>plug</code>。在 Elixir 安裝套件很簡單。</p>
<p>先讓我們打開資料夾中的 <code>mix.exs</code> 檔案,他內容會長這樣:</p>
<pre><code class="language-elixir">defmodule HttpServer.MixProject do
use Mix.Project
def project do
[
app: :http_server,
version: &quot;0.1.0&quot;,
elixir: &quot;~&gt; 1.9&quot;,
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run &quot;mix help compile.app&quot; to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {HttpServer.Application, []}
]
end
# Run &quot;mix help deps&quot; to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, &quot;~&gt; 0.3.0&quot;},
# {:dep_from_git, git: &quot;https://github.com/elixir-lang/my_dep.git&quot;, tag: &quot;0.1.0&quot;}
]
end
end
</code></pre>
<p>然後讓我們看到 <code>defp deps do</code> 這段函式區塊:</p>
<pre><code class="language-elixir"># Run &quot;mix help deps&quot; to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, &quot;~&gt; 0.3.0&quot;},
# {:dep_from_git, git: &quot;https://github.com/elixir-lang/my_dep.git&quot;, tag: &quot;0.1.0&quot;}
]
end
</code></pre>
<p>我們在他的 <code>[]</code> 的陣列裡加入 <code>{:plug_cowboy, &quot;~&gt; 2.0&quot;}</code>,這時候應該會長這個樣子:</p>
<pre><code class="language-elixir"># Run &quot;mix help deps&quot; to learn about dependencies.
defp deps do
[
{:plug_cowboy, &quot;~&gt; 2.0&quot;}
]
end
</code></pre>
<h3 id="首頁顯示-hello-world">首頁顯示 Hello World <a href="#%e9%a6%96%e9%a0%81%e9%a1%af%e7%a4%ba-hello-world" class="hash g">#</a></h3>
<p>安裝完後,我們在 <code>lib</code> 資料夾底下,建立一個檔案名為 <code>router.ex</code>,然後裡面寫入:</p>
<pre><code class="language-elixir">defmodule Router do
import Plug.Conn
def init(options) do
options
end
def call(conn, _opts) do
conn
|&gt; put_resp_content_type(&quot;text/plain&quot;)
|&gt; send_resp(200, &quot;Hello world&quot;)
end
end
</code></pre>
<h3 id="讓伺服器隨同程式上線">讓伺服器隨同程式上線 <a href="#%e8%ae%93%e4%bc%ba%e6%9c%8d%e5%99%a8%e9%9a%a8%e5%90%8c%e7%a8%8b%e5%bc%8f%e4%b8%8a%e7%b7%9a" class="hash g">#</a></h3>
<p>再來,我們必須讓伺服器隨著程式啟用時上線。所以我們要來更改 <code>lib/http_server/application.ex</code> 檔案。他應該會長這樣:</p>
<pre><code class="language-elixir">defmodule HttpServer.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
def start(_type, _args) do
children = [
# Starts a worker by calling: HttpServer.Worker.start_link(arg)
# {HttpServer.Worker, arg}
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: HttpServer.Supervisor]
Supervisor.start_link(children, opts)
end
end
</code></pre>
<p>我們在 <code>start</code> 函式中的 <code>children</code> 陣列加入一行:</p>
<pre><code class="language-elixir">{Plug.Cowboy, scheme: :http, plug: Router, options: [port: 4001]}
</code></pre>
<p>他就變成:</p>
<pre><code class="language-elixir">children = [
{Plug.Cowboy, scheme: :http, plug: Router, options: [port: 4001]}
]
</code></pre>
<h3 id="執行程式">執行程式 <a href="#%e5%9f%b7%e8%a1%8c%e7%a8%8b%e5%bc%8f" class="hash g">#</a></h3>
<p>程式碼都修改好後,我們在終端機打上:</p>
<pre><code class="language-shell">$ mix run --no-halt
</code></pre>
<p>如果沒有東西話記得先切到 <code>http_server</code> 底下:</p>
<pre><code class="language-shell">$ cd http_server
</code></pre>
<p>最後打開瀏覽器,輸入 <code>http://localhost:4001</code>,就大功告成啦!</p>
<p>
<img
src="https://sujingjhong.com/images/elixir101/elixir_plug_hello_world.png"
alt="hello world 網頁"
loading="lazy"
decoding="async"
width="533"
height="398"
class="full-width"
/>
</p>
<h2 id="參考文獻">參考文獻 <a href="#%e5%8f%83%e8%80%83%e6%96%87%e7%8d%bb" class="hash g">#</a></h2>
<ul>
<li><a href="https://github.com/elixir-plug/plug" target="_blank" >https://github.com/elixir-plug/plug</a>
</li>
<li><a href="https://elixirschool.com/zh-hant/lessons/specifics/plug/" target="_blank" >https://elixirschool.com/zh-hant/lessons/specifics/plug/</a>
</li>
</ul></description><author>[email protected] (Su Jing-Jhong)</author><guid>https://sujingjhong.com/posts/elixir101/elixir-101-make-a-http-server-quickly/</guid><pubDate>Thu, 31 Oct 2019 00:26:22 +0800</pubDate></item><item><title>Elixir 101 / 函式與回傳值</title><link>https://sujingjhong.com/posts/elixir101/elixir-101-function-and-return/</link><description><p>最近又重新開始學 Elixir,這時遇到一個問題:</p>
<p><strong>在 Elixir 裡面要怎麼建立函式,還有函數要怎麼回傳值?</strong></p>
<p>首先這要從 Elixir 有兩種函數類型說起:</p>
<ul>
<li>匿名函式(Anonymous Functions)</li>
<li>具名函示(Named Functions)</li>
</ul>
<h2 id="匿名函式-anonymous-functions">匿名函式 (Anonymous Functions) <a href="#%e5%8c%bf%e5%90%8d%e5%87%bd%e5%bc%8f-anonymous-functions" class="hash g">#</a></h2>
<p>匿名函式 (Anonymous Functions),通常指的是這個函式無需有函式的<strong>識別符號(identifier)</strong>。在有些語言實作上,被稱為 <strong>lambda 表示式</strong>(Pyhton),或者是 <strong>閉包(closures)</strong> (Rust)。</p>
<p>在 Elixir 裡,匿名函式是宣告一個變數,然後將函式用 <code>fn ... end</code> 包裹起來。</p>
<p>例如我們宣告一個叫 <code>hello_world </code> 的匿名函式,裡面是向顯示器顯示 <code>Hello World</code> ,那我們可以寫作:</p>
<pre><code class="language-elixir">hello_world = fn() -&gt; IO.puts &quot;Hello World&quot; end
</code></pre>
<p>這時候我們該如何執行這個函式呢?</p>
<p>在其他語言中,可能會很自然的加上 <code>()</code> 作為執行函式的句法,例如上方的 <code>hello_world</code> 變成 <code>hello_world()</code> 來執行。接著就會在 Elixir 中發生錯誤。例如我們在 <code>iex</code> 中試試看:</p>
<pre><code class="language-elixir">$ iex
Erlang/OTP 22 [erts-10.5.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]
Interactive Elixir (1.9.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)&gt; hello_world = fn() -&gt; IO.puts &quot;Hello World&quot; end
#Function&lt;21.126501267/0 in :erl_eval.expr/5&gt;
iex(2)&gt; hello_world()
** (CompileError) iex:2: undefined function hello_world/0
</code></pre>
<p>在 Elixir 中,必須加入 <code>. (dot)</code> 來執行這個匿名函式(<code>. (dot)</code> 在 Elixir 裡面也是一個運算符號,基於本篇主題,留待下次再說明)。所以執行上方範例的 <code>hello_world</code> 就要變成:</p>
<pre><code class="language-elixir">hello.()
</code></pre>
<p>這時候我們在 iex 在實驗一次,就可以知道成功執行啦!</p>
<pre><code class="language-elixir">$ iex
Erlang/OTP 22 [erts-10.5.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]
Interactive Elixir (1.9.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)&gt; hello_world = fn() -&gt; IO.puts &quot;Hello World&quot; end
#Function&lt;21.126501267/0 in :erl_eval.expr/5&gt;
iex(2)&gt; hello_world.()
Hello World
:ok
iex(3)&gt;
</code></pre>
<h2 id="具名函式named-functions">具名函式(Named Functions) <a href="#%e5%85%b7%e5%90%8d%e5%87%bd%e5%bc%8fnamed-functions" class="hash g">#</a></h2>
<p>相對於匿名函式,具名函式具有識別符。不過在 Elixir 裡面宣告一個具名函式,和其他語言一些不同。例如在 JavaScript 我們可以直接用 <code>function</code> 語法宣告一個 <code>helloWorld</code> 的具名函式:</p>
<pre><code class="language-javascript">function helloWorld(){
console.log('hello world');
}
</code></pre>
<p>但在 Elixir 裡面我們可以這樣做嗎?是不是可以將匿名函式的 <code>fn...end</code> 語法直接拿來宣告為:</p>
<pre><code class="language-elixir">fn hello_world = () -&gt; IO.puts &quot;hello world&quot;
</code></pre>
<p><strong>答案是不能的。</strong></p>
<p>在 Elixir 裡,具名函式的運作範圍僅限於模組(module)。換言之,必須將函式定義在 <code>module</code> 裡。例如我們定義一個 <code>MyModule</code> 名稱的 <code>module</code>,裡面有一個具名函式稱為 <code>hello_world</code> :</p>
<pre><code class="language-elixir">defmodule MyModule do
def hello_world() do
IO.puts &quot;hello world&quot;
end
end
</code></pre>
<p>當我們在 module 裡面定義好一個具名函式,這時候我們一樣就可以用 <code>. (dot)</code> ,來呼叫這個 module 裡面特定的具名函式。例如上述案例,我們要呼叫 <code>MyModule</code> 裡面的 <code>hello_world</code> 我們就是:</p>
<pre><code class="language-elixir">MyModule.hello_world()
</code></pre>
<p>在 iex 裡面實驗一次:</p>
<pre><code class="language-elixir">$ iex
Erlang/OTP 22 [erts-10.5.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]
Interactive Elixir (1.9.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)&gt; defmodule MyModule do
...(1)&gt; def hello_world do
...(1)&gt; IO.puts &quot;hello world&quot;
...(1)&gt; end
...(1)&gt; end
{:module, MyModule,
&lt;&lt;70, 79, 82, 49, 0, 0, 4, 104, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 149,
0, 0, 0, 15, 15, 69, 108, 105, 120, 105, 114, 46, 77, 121, 77, 111, 100, 117,
108, 101, 8, 95, 95, 105, 110, 102, 111, ...&gt;&gt;, {:hello_world, 0}}
iex(2)&gt; MyModule.hello_world()
hello world
:ok
iex(3)&gt;
</code></pre>
<h2 id="函式的回傳值">函式的回傳值 <a href="#%e5%87%bd%e5%bc%8f%e7%9a%84%e5%9b%9e%e5%82%b3%e5%80%bc" class="hash g">#</a></h2>
<p>以上介紹了兩種函示:匿名函式以及具名函式。那我們在函式中,要怎麼回傳函式的運算值呢?</p>
<p>在程式語言中,函式回傳值主要有兩種表示方式:</p>
<ul>
<li>用 <code>return</code> 或相類似的語法</li>
<li>回傳值為函式結構中最後的表示式</li>
</ul>