JoyLau's Blog

JoyLau 的技术学习与思考

背景

继上一篇文章 【indicator-sysmonitor 状态栏监控工具开启对磁盘读写的监控】,这里我想让监控的数据放到状态栏的最左侧, 可发现事情并不简单。。。

因为 Ubuntu 下并不像 Mac 下按住 option 键可随意拖动

解决方式

1
sudo vim /usr/share/indicator-application/ordering-override.keyfile

修改顺序, 数字越小越靠左

修改完毕使用 restart unity-panel-service 重启生效

但发现修改完后顺序并没有改变

这个时候需要结合状态栏实际已有的托盘图标来操作顺序

获取状态栏图标的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh

dbus-send --type=method_call --print-reply --dest=com.canonical.indicator.application /com/canonical/indicator/application/service com.canonical.indicator.application.service.GetApplications | grep "string" > /tmp/indicators.txt

c=$(wc -l < /tmp/indicators.txt)
i=$((c / 8))
s=6

while [ "$i" != "0" ]; do
echo $(awk -v n=$s '/string/ && !--n {getline; print; exit}' /tmp/indicators.txt)
s=$(( $s + 8 ))
i=$(( $i - 1 ))
done

执行这个脚本获取图标的程序名称, 再修改 ordering-override.keyfile 的顺序, restart unity-panel-service 重启生效

缺点

按照上述方式操作后, 顺序得以改变, 但是如果后续打开了新的程序有托盘图标, 则新程序的图标会在最左边

背景

indicator-sysmonitor 默认的模式可以监控 CPU 使用率, 内存使用, 网络 I/O 等, 但是却缺少了很关键的对当前磁盘 I/O 的监控,于是我就想着把他给加上去

解决方式

indicator-sysmonitor 可以新建传感器,可以自定义命令来显示输出, 于是我想着使用 shell 命令获取当前磁盘的 I/O 在输出即可

dstat 方式

  1. 使用 dstat 命令, 需要机器上事先安装 dstat
1
dstat  --disk

该命令可以监控磁盘使用情况

我在稍对结果做下过滤的优化, 使用下面的命令

1
dstat  --disk 1 1 | sed -n '4p' | awk '{printf "r: "}{printf $1}{printf "   w: "}{printf $2}'

上述的命令的解释为 1s 输出一次, 一次输出一行, 取第四行, 取第一列和第二列,在加上读写的标识 r:w: 的前缀

输出的结果为:

1
r: 0   w: 6244kj

在 indicator-sysmonitor 里新建一项, 复制上述命令,效果如下

disk-dstat

iotop

  1. 使用 iotop 命令, 需要机器上事先安装 iotop
1
sudo iotop

在美化下输出结果:

1
sudo iotop -o -b -n 1 | sed -n '2p' | awk '{printf "r: "}{printf  $4 $5}{printf "  w: "}{printf $10 $11}'

命令的意思同上

输出的结果为:

1
2
r: 0.00B/s  w: 0.00B/s

同上操作, 命令更换下,效果如下:

disk-iotop

对比

  1. 输出单位不一样,第一种方式单位只有 k,m 这样的, 第二种是 B/s, KB/s, MB/s 这样的, 不过第一种方式的单位也可以手动给补全上
  2. 第一种方式 dstat 命令不需要 root 权限即可执行, 第二种方式 iotop 命令需要 root 权限即需要加 sudo

使用 sudo 的常用方式为:

1
echo "你的 root 的密码" | sudo iotop ....

但是这样的方式在终端执行可以输出结果, 在 indicator-sysmonitor 执行却不能输出结果。。。。。

于是需要解决这个问题, 即使用普通用户执行 iotop 命令时不需要输入密码

这里我的解决方案如下:

1
2
3
4
5
6
7
8
sudo visudo

## 添加下面几行
User_Alias NET_USERS = joylau

Cmnd_Alias SYS_STATUS = /usr/sbin/iotop # 多个命令逗号隔开

NET_USERS ALL=(root) NOPASSWD:SYS_STATUS

Ctrl + O 保存后,普通用户 joylau 使用 sudo iotop 就不需要输入密码了, 也就实现了第二种方式的效果了

  1. 性能对比: 实测第一种方式的性能(CPU使用平均在 2%)要稍好于第二种(CPU使用平均在 5%)

问题描述

我的 MacBook Pro 使用的是绿联的外接扩展坞, 其中有一个网口
在京东购买的: https://item.jd.com/4445121.html

型号为: CM179
网卡芯片为: RTL8153B

最近发现只要我的 MacBook Pro 关闭了盖子, 会导致接在同一交换机下的路由器就无法上网
打开盖子后,网络又恢复正常了

解决

搜索了一番没找到解决方式

于是就找到当时购买的这款产品的京东介绍页面看了看

想着去绿联的官网找找驱动试试: https://www.lulian.cn/download/list-34-cn.html

找到对应的型号和网卡芯片: https://www.lulian.cn/download/38-cn.html

下载并解压驱动压缩包,双击“RTUNICv1.0.20.pkg”文件,一直点击继续,安装完成后重启电脑即可。

重启后又试了下关闭盖子, 问题解决了!!!

说明

Awtrix App 开发入门之开发一款显示个人博客访问人数的 App

  1. 具备 Awtrix 硬件设备
  2. 博客的计数工具是不蒜子

环境准备

  1. JDK 8 的环境
  2. 开发工具: B4J

效果图

gif
app
config

开发准备

  1. 模板文件

AWTRIX.bas: 这个文件的内容不需要改动直接复制即可

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
B4J=true
Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=7.31
@EndOfDesignText@
'This Class takes control of the Interface to AWTRIX, the Icon Renderer
'and some useful functions to make the development more easier.
'Usually you dont need to modify this Class!

#Event: Started
#Event: controllerButton(button as int,dir as boolean)
#Event: controllerAxis(axis as int, dir as float)
#Event: Exited
#Event: iconRequest
#Event: settingsChanged
#Event: startDownload(jobNr As Int) As String
#Event: evalJobResponse(Resp As JobResponse)


private Sub Class_Globals
Private Appduration As Int
Private mscrollposition As Int
Private show As Boolean = True
Private forceDown As Boolean
Private LockApp As Boolean = False
Private Icon As List
Private appName As String
Private AppVersion As String
Private TickInterval As Int
Private NeedDownloads As Int = 0
Private UpdateInterval As Int = 0
Private AppDescription As String
Private AppAuthor As String
Private SetupInfos As String
Private MatrixInfo As Map
Private appSettings As Map = CreateMap()
Private ServerVersion As String
Private DisplayTime As Int
Private MatrixWidth As Int = 32
Private MatrixHeight As Int = 8
Private DownloadHeader As Map
Private pluginversion As Int = 1
Private Tag As List = Array As String()
Private playdescription As String
Private Cover As Int
Private Game As Boolean
Private startTimestamp As Long
Private icoMap As Map
Private RenderedIcons As Map
Private animCounter As Map
Private iconList As List'ignore
Private timermap As Map
Private set As Map 'ignore
Private Target As Object
Private commandList As List
Private colorCounter As Int
Private startTime As String ="0"
Private endtime As String = "0"
Private CharMap As Map
Private TextBuffer As String
Private TextLength As Int
Private UppercaseLetters As Boolean
Private SystemColor() As Int
Private event As String
Private Enabled As Boolean = True
Private noIcon() As Short = Array As Short(0, 0, 0, 63488, 63488, 0, 0, 0, 0, 0, 63488, 0, 0, 63488, 0, 0, 0, 0, 0, 0, 0, 63488, 0, 0, 0, 0, 0, 0, 63488, 0, 0, 0, 0, 0, 0, 63488, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63488, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
Private isRunning As Boolean
Private Menu As Map
Private MenuList As List
Private bc As B4XSerializator
Private noIconMessage As Boolean
Private verboseLog As Boolean
Private finishApp As Boolean
Type JobResponse (jobNr As Int,Success As Boolean,ResponseString As String,Stream As InputStream)
Private httpMap As Map
Private OAuthToken As String
Private OAuth As Boolean
Private oauthmap As Map
Private mContentType As String

Private poll As Map = CreateMap("enable":False,"sub":"")
Private mHidden As Boolean
End Sub

'Initializes the Helperclass.
Public Sub Initialize(class As Object, Eventname As String)

oauthmap.Initialize
Tag.Initialize
httpMap.Initialize
DownloadHeader.Initialize
event=Eventname
iconList.Initialize
Icon.Initialize
commandList.Initialize
RenderedIcons.Initialize
icoMap.Initialize
animCounter.Initialize
timermap.Initialize
set.Initialize
Menu.Initialize
MatrixInfo.Initialize
MenuList.Initialize
Target=class
End Sub

'Checks if the app should shown
Private Sub timesComparative As Boolean
Try
If startTime = endtime Then Return True
Dim startT() As String=Regex.Split(":",startTime)
Dim EndT() As String=Regex.Split(":",endtime)
Dim hour As Int=DateTime.GetHour(DateTime.Now)
Dim minute As Int=DateTime.GetMinute(DateTime.Now)
Dim second As Int=DateTime.GetSecond(DateTime.Now)
Dim now, start, stop As Int
now = ((hour * 3600) + (minute * 60) + second)
start = (startT(0) * 3600) + (startT(1) * 60)
stop = ( EndT(0)* 3600) + (EndT(1) * 60)
If (start < stop) Then
Return (now >= start And now <= stop )
Else
Return (now >= start Or now <= stop)
End If
Catch
Log("Got Error from " & appName)
Log("Error in TimesComparative:")
Log(LastException)
Return True
End Try
End Sub

#Region IconRenderer
Private Sub startIconRenderer
isRunning=True
FirstTick
For Each k As Timer In timermap.Keys
k.Enabled=True
Sleep(1)
Next
End Sub

Private Sub stopIconRenderer
isRunning=False
For Each k As Timer In timermap.Keys
k.Enabled=False
Sleep(1)
Next
End Sub

Private Sub FirstTick
For Each IconID As Int In icoMap.Keys
Try
If icoMap.ContainsKey(IconID) Then
Dim ico As List=icoMap.get(IconID)
Dim parse As JSONParser
If animCounter.Get(IconID)>ico.Size-1 Then animCounter.put(IconID,0)
parse.Initialize(ico.Get(animCounter.Get(IconID)))
Dim bmproot As List = parse.NextArray
Dim bmp(bmproot.Size) As Short
For i=0 To bmproot.Size-1
bmp(i)=bmproot.Get(i)
Next
RenderedIcons.Put(IconID,bmp)
animCounter.put(IconID,animCounter.Get(IconID)+1)
Else
Log("IconID" & IconID & "doesnt exists")
End If
Catch
Log("Got Error from " & appName)
Log("Error in IconPreloader:")
Log("IconID:" & IconID)
Log(LastException)
End Try
Next
End Sub

Private Sub Timer_Tick
Try
Dim iconid As Int=timermap.Get(Sender)
If icoMap.ContainsKey(iconid) Then
Dim ico As List= icoMap.get(iconid)
Dim parse As JSONParser
If animCounter.Get(iconid)>ico.Size-1 Then animCounter.put(iconid,0)
parse.Initialize(ico.Get(animCounter.Get(iconid)))
Dim bmproot As List = parse.NextArray
Dim bpm(bmproot.Size) As Short
For i=0 To bmproot.Size-1
bpm(i)=bmproot.Get(i)
Next
RenderedIcons.Put(iconid,bpm)
animCounter.put(iconid,animCounter.Get(iconid)+1)
Else
Logger("IconID" & iconid & "doesnt exists")
End If
Catch
Log("Got Error from " & appName)
Log("Error in IconRenderer:")
Log(LastException)
stopIconRenderer
End Try
End Sub

Private Sub addToIconRenderer(iconMap As Map)
Try
If iconMap.Size=0 Then Return
Dim runMarker As Boolean
If isRunning Then
stopIconRenderer
runMarker=True
End If
timermap.Clear
icoMap.Clear
animCounter.Clear
RenderedIcons.Clear
For Each ico As Int In iconMap.Keys
Dim ico1 As Map = iconMap.get(ico)
If ico1.ContainsKey("tick") Then
icoMap.Put(ico,ico1.Get("data"))
animCounter.Put(ico,0)
Dim timer As Timer
timer.Initialize("Timer",ico1.Get("tick"))
Dim icoExists As Boolean=False
For Each timerico As Int In timermap.Values
If timerico=ico Then icoExists=True
Next
If Not(icoExists) Then timermap.Put(timer,ico)
Else
RenderedIcons.Put(ico,ico1.Get("data"))
End If
Next
If runMarker Then
startIconRenderer
End If
Catch
Log("Got Error from " & appName)
Log("Error in IconAdder:")
Log(LastException)
End Try
End Sub

'returns the rendered Icon
Public Sub getIcon(ID As Int) As Short()
If RenderedIcons.ContainsKey(ID) Then
Return RenderedIcons.Get(ID)
Else
If noIconMessage = False Then
Logger("Icon " & ID & " not found")
noIconMessage=True
End If

Return noIcon
End If
End Sub
#End Region

'This is the interface between AWTRIX and the App
Public Sub interface(function As String, Params As Map) As Object
Select Case function
Case "start"
mscrollposition=MatrixWidth
If SubExists(Target,event&"_Started") Then
CallSub(Target,event&"_Started")
End If
Try
Appduration = Params.Get("AppDuration")
If DisplayTime>0 Then
Appduration=DisplayTime
End If
verboseLog =Params.Get("verboseLog")
ServerVersion = Params.Get("ServerVersion")
MatrixWidth = Params.Get("MatrixWidth")
MatrixHeight = Params.Get("MatrixHeight")
UppercaseLetters = Params.Get("UppercaseLetters")
CharMap = Params.Get("CharMap")
SystemColor = Params.Get("SystemColor")
MatrixInfo=Params.Get("MatrixInfo")
set.Put("interval",TickInterval)
set.Put("needDownload",NeedDownloads)
set.Put("DisplayTime", DisplayTime)
set.Put("forceDownload", forceDown)
Catch
Log("Got Error from " & appName)
Log("Error in start procedure")
Log(LastException)
End Try
startTimestamp=DateTime.now
noIconMessage=False
If show Then
set.Put("show",timesComparative)
Else
set.Put("show",show)
End If

set.Put("isGame",Game)
set.Put("hold",LockApp)
set.Put("iconList",Icon)
Return set
Case "downloadCount"
Return NeedDownloads
Case "startDownload"
httpMap.Initialize
DownloadHeader.Initialize
mContentType=""
If SubExists(Target,event&"_startDownload") Then
CallSub2(Target,event&"_startDownload",Params.Get("jobNr"))
End If
If DownloadHeader.Size>0 Then
httpMap.Put("Header",DownloadHeader)
End If
If mContentType.Length>0 Then
httpMap.Put("ContentType",mContentType)
End If
Return httpMap
Case "httpResponse"
Dim res As JobResponse
res.Initialize
res.jobNr=Params.Get("jobNr")
res.Success=Params.Get("success")
res.ResponseString=Params.Get("response")
res.Stream=Params.Get("InputStream")
If SubExists(Target,event&"_evalJobResponse") Then
CallSub2(Target,event&"_evalJobResponse",res)
End If
Return True
Case "running"
startIconRenderer
Case "tick"
commandList.Clear
If finishApp Then
finishApp=False
commandList.Add(CreateMap("type":"finish"))
Else
If SubExists(Target,event&"_genFrame") Then
CallSub(Target,event&"_genFrame")'ignore
End If
End If

Return commandList
Case "infos"
Dim infos As Map
infos.Initialize
Dim isconfigured As Boolean = True
If File.Exists(File.Combine(File.DirApp,"Apps"),appName&".ax") Then
Dim m As Map = bc.ConvertBytesToObject(File.ReadBytes(File.Combine(File.DirApp,"Apps"),appName&".ax"))
For Each v As Object In m.Values
If v="null" Or v="" Then
isconfigured=False
End If
Next
If OAuth And OAuthToken.Length=0 Then isconfigured=False
End If
infos.Put("isconfigured",isconfigured)
infos.Put("AppVersion",AppVersion)
infos.Put("tags",Tag)
infos.Put("poll",poll)
infos.Put("oauth",OAuth)
infos.Put("oauthmap",oauthmap)
infos.Put("isGame",Game)
infos.Put("CoverIcon",Cover)
infos.Put("pluginversion",pluginversion)
infos.Put("author",AppAuthor)
infos.Put("howToPLay",playdescription)
infos.Put("description",AppDescription)
infos.Put("setupInfos",SetupInfos)
infos.Put("hidden",mHidden)
Return infos
Case "setSettings"
makeSettings
Return True
Case "getUpdateInterval"
Return UpdateInterval
Case "setEnabled"
saveSingleSetting("Enabled",Params.Get("Enabled"))
makeSettings
Case "getEnable"
Return Enabled
Case "stop"
If Game Then
finishApp=False
show=False
End If
stopIconRenderer
If SubExists(Target,event&"_Exited") Then
CallSub(Target,event&"_Exited")
End If
Case "getIcon"
If SubExists(Target,event&"_iconRequest") Then
CallSub(Target,event&"_iconRequest")
End If
Return CreateMap("iconList":Icon)
Case "iconList"
addToIconRenderer(Params)
Case "externalCommand"
externalCommand(Params)
Case "controller"
Control(Params)
Case "getMenu"
Menu.Initialize
Menu.Put("Version","1.6")
Menu.Put("Theme","Light Theme")
Menu.Put("Items",MenuList)
Return Menu
Case "setToken"
OAuthToken=Params.Get("token")
Case "isReady"
If SubExists(Target,event&"_isReady") Then
Return CallSub(Target,event&"_isReady")
Else
Return True
End If

Case "shouldShow"
Return show
Case "poll"
Dim s As String=Params.Get("sub")
If SubExists(Target,event & "_" & s) Then
CallSub(Target,event & "_" & s)
End If
End Select
Return True
End Sub

'This function calculates the ammount of pixels wich a text needs
Public Sub calcTextLength(text As String) As Int
If UppercaseLetters Then text = text.ToUpperCase
If TextBuffer<>text Then
Dim Length As Int
For i=0 To text.Length-1
If CharMap.ContainsKey(Asc(text.CharAt(i))) Then
Length=Length+(CharMap.Get(Asc(text.CharAt(i))))
Else
Length=Length+4
End If
Next
TextBuffer=text
TextLength=Length
Return Length
End If
Return TextLength
End Sub

'This Helper automaticly display a text in a default app style
'If the text is longer than the Matrixwitdh it will scroll the text
'otherwise it will center the text. Call drawText to handle it manually.
'
'Text - the text to be displayed
'IconOffset - wether you need an offset if you place an icon on the left side.
'yPostition
'Color - custom text color. Pass Null to use the Global textcolor (recommended).
'
'<code>App.genText("Hello World",True,Array as int(255,0,0),false)</code>
Public Sub genText(Text As String,IconOffset As Boolean,yPostition As Int,Color() As Int,callFinish As Boolean)
If Text.Length=0 Then
finish
Return
End If
calcTextLength(Text)
Dim offset As Int
If IconOffset Then offset = 24 Else offset = 32
If TextLength>offset Then
drawText(Text,mscrollposition,yPostition,Color)
mscrollposition=mscrollposition-1
If mscrollposition< 0-TextLength Then
If LockApp And callFinish Then
finish
Return
Else
mscrollposition=MatrixWidth
End If
End If
Else
Dim x As Int
If TextLength<offset+1 Then
If IconOffset Then
x=((MatrixWidth/2)-TextLength/2)+4
Else
x=(MatrixWidth/2)-TextLength/2
End If
End If
drawText(Text,x,yPostition,Color)
End If
End Sub

'This functions build and savee the settings. You dont need to call this manually
Public Sub makeSettings
If Game Then show=False
If File.Exists(File.Combine(File.DirApp,"Apps"),appName&".ax") Then
Dim data() As Byte = File.ReadBytes(File.Combine(File.DirApp,"Apps"),appName&".ax")
Dim m As Map = bc.ConvertBytesToObject(data)
For Each k As String In appSettings.Keys
If Not(m.ContainsKey(k)) Then
m.Put(k,appSettings.Get(k))
Else
appSettings.Put(k,m.Get(k))
End If
Next
For Counter = m.Size -1 To 0 Step -1
Dim SettingsKey As String = m.GetKeyAt(Counter)
If Not(SettingsKey="UpdateInterval" Or SettingsKey="StartTime" Or SettingsKey="EndTime" Or SettingsKey="DisplayTime" Or SettingsKey="Enabled") Then
If Not(appSettings.ContainsKey(SettingsKey)) Then m.Remove(SettingsKey)
End If
Next
Try
Enabled=m.Get("Enabled")
startTime=m.Get("StartTime")
endtime=m.Get("EndTime")
UpdateInterval=m.Get("UpdateInterval")
DisplayTime=m.Get("DisplayTime")
File.WriteBytes(File.Combine(File.DirApp,"Apps"),appName&".ax",bc.ConvertObjectToBytes(m))
If SubExists(Target,event&"_settingsChanged") Then
CallSub(Target,event&"_settingsChanged")'ignore
End If
Catch
Log("Got Error from " & appName)
Log("Error while saving settings")
Log(LastException)
End Try
Else
Dim m As Map
m.Initialize
m.Put("UpdateInterval",UpdateInterval)
m.Put("StartTime","00:00")
m.Put("EndTime","00:00")
m.Put("DisplayTime","0")
m.Put("Enabled",True)
For Each k As String In appSettings.Keys
m.Put(k,appSettings.Get(k))
Next
File.WriteBytes(File.Combine(File.DirApp,"Apps"),appName&".ax",bc.ConvertObjectToBytes(m))
End If
End Sub

'Returns the value of a Settingskey
public Sub get(SettingsKey As String) As Object
If appSettings.ContainsKey(SettingsKey) Then
Return appSettings.Get(SettingsKey)
Else
Log(SettingsKey & " not found")
Return ""
End If
End Sub

Public Sub saveSingleSetting(key As String, value As Object)
If File.Exists(File.Combine(File.DirApp,"Apps"),appName&".ax") Then
Dim data() As Byte = File.ReadBytes(File.Combine(File.DirApp,"Apps"),appName&".ax")
Dim m As Map = bc.ConvertBytesToObject(data)
m.Put(key,value)
File.WriteBytes(File.Combine(File.DirApp,"Apps"),appName&".ax",bc.ConvertObjectToBytes(m))
End If
End Sub


'Draws a Bitmap
Public Sub drawBMP(x As Int,y As Int,bmp() As Short,width As Int, height As Int)
commandList.Add(CreateMap("type":"bmp","x":x,"y":y,"bmp":bmp,"width":width,"height":height))
End Sub

'Draws a Text
Public Sub drawText(text As String,x As Int, y As Int,Color() As Int)
If Color=Null Then
commandList.Add(CreateMap("type":"text","text":text,"x":x,"y":y))
Else
commandList.Add(CreateMap("type":"text","text":text,"x":x,"y":y,"color":Color))
End If
End Sub

'Draws a Circle
Public Sub drawCircle(X As Int, Y As Int, Radius As Int, Color() As Int)
If Color=Null Then
commandList.Add(CreateMap("type":"circle","x":x,"y":y,"r":Radius,"color":SystemColor))
Else
commandList.Add(CreateMap("type":"circle","x":x,"y":y,"r":Radius,"color":Color))
End If
End Sub

'Draws a filled Circle
Public Sub fillCircle(X As Int, Y As Int, Radius As Int, Color() As Int)
If Color=Null Then
commandList.Add(CreateMap("type":"fillCircle","x":x,"y":y,"r":Radius,"color":SystemColor))
Else
commandList.Add(CreateMap("type":"fillCircle","x":x,"y":y,"r":Radius,"color":Color))
End If
End Sub

'Draws a single Pixel
Public Sub drawPixel(X As Int,Y As Int,Color() As Int)
If Color=Null Then
commandList.Add(CreateMap("type":"pixel","x":x,"y":y,"color":SystemColor))
Else
commandList.Add(CreateMap("type":"pixel","x":x,"y":y,"color":Color))
End If
End Sub

'Draws a Rectangle
Public Sub drawRect(X As Int,Y As Int,Width As Int,Height As Int,Color() As Int)
If Color=Null Then
commandList.Add(CreateMap("type":"rect","x":x,"y":y,"w":Width,"h":Height,"color":SystemColor))
Else
commandList.Add(CreateMap("type":"rect","x":x,"y":y,"w":Width,"h":Height,"color":Color))
End If
End Sub

'Draws a Line
Public Sub drawLine(X0 As Int,Y0 As Int,X1 As Int,Y1 As Int,Color() As Int)
If Color=Null Then
commandList.Add(CreateMap("type":"line","x0":X0,"y0":Y0,"x1":X1,"y1":Y1,"color":SystemColor))
Else
commandList.Add(CreateMap("type":"line","x0":X0,"y0":Y0,"x1":X1,"y1":Y1,"color":Color))
End If
End Sub

'Sends a custom or undocumented command
Public Sub customCommand(cmd As Map)
commandList.Add(cmd)
End Sub

'Fills the screen with a color
Public Sub fill(Color() As Int)
If Color=Null Then
commandList.Add(CreateMap("type":"fill","color":SystemColor))
Else
commandList.Add(CreateMap("type":"fill","color":Color))
End If
End Sub

'Exits the app and force AWTRIX to switch to the next App
'only needed if you have set LockApp to true
Public Sub finish
finishApp=True
End Sub

'Returns a rainbowcolor wich is fading each tick
Public Sub rainbow As Int()
colorCounter=colorCounter+1
If colorCounter>255 Then colorCounter=0
Return(wheel(colorCounter))
End Sub

Private Sub wheel(Wheelpos As Int) As Int() 'ignore
If(Wheelpos < 85) Then
Return Array As Int(Wheelpos * 3, 255 - Wheelpos * 3, 0)
else if(Wheelpos < 170) Then
Wheelpos =Wheelpos- 85
Return Array As Int(255 - Wheelpos * 3, 0, Wheelpos * 3)
Else
Wheelpos =Wheelpos- 170
Return Array As Int(0, Wheelpos * 3, 255 - Wheelpos * 3)
End If
End Sub

Public Sub Logger(msg As String)
If verboseLog Then
DateTime.DateFormat=DateTime.DeviceDefaultTimeFormat
Log(DateTime.Date(DateTime.Now) &" " & appName & ":" & CRLF & msg)
End If
End Sub

Private Sub Control(controller As Map)
If controller.ContainsKey("GameStart") And Game Then
Dim state As Boolean = controller.Get("GameStart")
If state Then
show=True
Else
finishApp=True
show=False
End If
Return
End If

If controller.ContainsKey("button") Then
Dim buttonNR As Int = controller.Get("button")
Dim buttonDIR As Boolean = controller.Get("dir")
If SubExists(Target,event&"_controllerButton") Then
CallSub3(Target,event&"_controllerButton",buttonNR,buttonDIR)
End If
If verboseLog Then
If buttonDIR Then Logger($"Button ${buttonNR} down"$) Else Logger($"Button ${buttonNR} up"$)
End If
Return
End If

If controller.ContainsKey("axis") Then
Dim AxisNR As Int = controller.Get("axis")
Dim val As Float = controller.Get("dir")
If SubExists(Target,event&"_controllerAxis") Then
CallSub3(Target,event&"_controllerAxis",AxisNR,val)
End If
Return
End If
End Sub

Private Sub externalCommand(cmd As Map)
If SubExists(Target,event&"_externalCommand") Then
CallSub2(Target,event&"_externalCommand",cmd)
End If
End Sub

Public Sub throwError(message As String)
Logger(message)
End Sub

'Returns the timestamp when the app was started.
Sub getstartedAt As Long
Return startTimestamp
End Sub

'Gets or sets the app tags
Sub gettags As List
Return Tag
End Sub

Sub settags(Tags As List)
Tag=Tags
End Sub

'Returns the runtime of the app
Sub getduration As Int
Return Appduration
End Sub

'If set to true, awtrix will skip this app
Sub setshouldShow(shouldShow As Boolean)
show=shouldShow
End Sub

'If set to true, AWTRIX will download new data before each start.
Sub setforceDownload(forceDownload As Boolean)
forceDown=forceDownload
End Sub

'If set to true AWTRIX will wait for the "finish" command before switch to the next app.
Sub setlock(lock As Boolean)
LockApp=lock
End Sub

'IconIDs from AWTRIXER. You can add multiple if you need more
Sub seticons(icons As List)
Icon=icons
End Sub

'Sets or gets the appname
Sub getname As String
Return appName
End Sub

Sub setname(name As String)
appName=name
End Sub

'Sets or gets the app description
Sub getdescription As String
Return AppDescription
End Sub

Sub setdescription(description As String)
AppDescription=description
End Sub

'The developer if this App
Sub getauthor As String
Return AppAuthor
End Sub

Sub setauthor(author As String)
AppAuthor=author
End Sub

'Sets or gets the appversion
Sub getversion As String
Return AppVersion
End Sub

Sub setversion(version As String)
AppVersion=version
End Sub

'Sets or gets the tickinterval
Sub gettick As String
Return TickInterval
End Sub

Sub settick(tick As String)
TickInterval=tick
End Sub

'How many downloadhandlers should be generated
Sub setdownloads(downloads As Int)
NeedDownloads=downloads
End Sub

'Setup Instructions. You can use HTML to format it
Sub setsetupDescription(setupDescription As String)
SetupInfos=setupDescription
End Sub

'gets all informations from the matrix as a map
Sub getmatrix As Map
Return MatrixInfo
End Sub

'needed Settings for this App (wich can be configurate from user via webinterface)
Sub setsettings(settings As Map)
appSettings=settings
End Sub

'returns the version of the serever
Sub getserver As String
Return ServerVersion
End Sub

'returns the size of the Matrix as an array (height,width)
Sub getmatrixSize As Int()
Dim size() As Int = Array As Int(MatrixHeight,MatrixWidth)
Return size
End Sub

'if this is a game you can set your play instructions here
Sub sethowToPlay(howToPlay As String)
playdescription=howToPlay
End Sub

'Icon (ID) to be displayed in the Appstore and MyApps
Sub setcoverIcon(coverIcon As Int)
Cover=coverIcon
End Sub

'set this to true if this is a game.
Sub setisGame(isGame As Boolean)
Game=isGame
End Sub

public Sub InitializeOAuth (AuthorizeURL As String, TokenURL As String, ClientId As String, ClientSecret As String, Scope As String)
OAuth=True
oauthmap=CreateMap("AuthorizeURL":AuthorizeURL,"TokenURL":TokenURL,"ClientId":ClientId,"ClientSecret":ClientSecret,"Scope":Scope)
End Sub

Sub getToken As String
Return OAuthToken
End Sub

Sub getScrollposition As Int
Return mscrollposition
End Sub

'Sends a POST request with the given data as the post data.
Public Sub PostString(Link As String, Text As String)
httpMap=CreateMap("type":"PostString","Link":Link,"Text":Text)
End Sub

'Sends a POST request with the given string as the post data
Public Sub PostBytes(Link As String, Data() As Byte)
httpMap=CreateMap("type":"PostBytes","Link":Link,"Data":Data)
End Sub

'Sends a PUT request with the given data as the post data.
Public Sub PutString(Link As String, Text As String)
httpMap=CreateMap("type":"PutString","Link":Link,"Text":Text)
End Sub

'Sends a PUT request with the given string as the post data
Public Sub PutBytes(Link As String, Data() As Byte)
httpMap=CreateMap("type":"PutBytes","Link":Link,"Data":Data)
End Sub

'Sends a PATCH request with the given string as the request payload.
Public Sub PatchString(Link As String, Text As String)
httpMap=CreateMap("type":"PatchString","Link":Link,"Text":Text)
End Sub

'Sends a PATCH request with the given data as the request payload.
Public Sub PatchBytes(Link As String, Data() As Byte)
httpMap=CreateMap("type":"PatchBytes","Link":Link,"Data":Data)
End Sub

'Sends a HEAD request.
Public Sub Head(Link As String)
httpMap=CreateMap("type":"Head","Link":Link)
End Sub

'Sends a multipart POST request.
'NameValues - A map with the keys and values. Pass Null if not needed.
'Files - List of MultipartFileData items. Pass Null if not needed.
Public Sub PostMultipart(Link As String, NameValues As Map, Files As List)
httpMap=CreateMap("type":"PostMultipart","Link":Link,"NameValues":NameValues,"Files":Files)
End Sub

'Sends a POST request with the given file as the post data.
'This method doesn't work with assets files.
Public Sub PostFile(Link As String, Dir As String, FileName As String)
httpMap=CreateMap("type":"PostFile","Link":Link,"Dir":Dir,"FileName":FileName)
End Sub

'Submits a HTTP GET request.
'Consider using Download2 if the parameters should be escaped.
Public Sub Download(Link As String)
httpMap=CreateMap("type":"Download","Link":Link)
End Sub

'Submits a HTTP GET request.
'Encodes illegal parameter characters.
'<code>Example:
'job.Download2("http://www.example.com", _
' Array As String("key1", "value1", "key2", "value2"))</code>
Public Sub Download2(Link As String, Parameters() As String)
httpMap=CreateMap("type":"Download2","Link":Link,"Parameters":Parameters)
End Sub

'Sets the header for the request as an map
'(Headername,Headervalue)
Public Sub setHeader(header As Map)
DownloadHeader=header
End Sub

'Sets the Mime header of the request.
'This method should only be used with requests that have a payload.
Public Sub SetContentType(ContentType As String)
mContentType=ContentType
End Sub

'enables pollingmode
'pass the subname wich should be called every 5s. e.g for App_mySub :
'<code>app.pollig("mySub"):</code>
'if you pass a empty String ("") AWTRIX will start the download
Public Sub polling(enable As Boolean,subname As String)
poll=CreateMap("enable":enable,"sub":subname)
End Sub


'hide this app from apploop
Sub setHidden(hide As Boolean)
mHidden=hide
End Sub





  1. BlogViews.b4j: b4j 工程配置文件
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
AppType=StandardJava
Build1=Default,b4j.example
Group=Default Group
Library1=jcore
Library2=json
Library3=jrandomaccessfile
Module1=AWTRIX
Module2=BlogViews
NumberOfFiles=0
NumberOfLibraries=3
NumberOfModules=2
Version=8.5
@EndOfDesignText@
'Draws a Rectangle'Non-UI application (console / server application)
#Region Project Attributes
#LibraryName:BlogViews
#End Region

Sub Process_Globals

End Sub

Sub AppStart (Args() As String)

End Sub

'Return true to allow the default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
Return True
End Sub

其中修改:

  • Module2
  • LibraryName
  1. BlogViews.bas: 程序主文件
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
B4J=true
Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=4.2
@EndOfDesignText@

Sub Class_Globals
Dim App As AWTRIX

'Declare your variables here
Dim followers As Int = 0
Dim iconId As Int = 8
End Sub

' ignore
public Sub GetNiceName() As String
Return App.Name
End Sub

' ignore
public Sub Run(Tag As String, Params As Map) As Object
Return App.interface(Tag,Params)
End Sub

' Config your App
Public Sub Initialize() As String

App.Initialize(Me,"App")

'App name (must be unique, avoid spaces)
App.Name="BlogViews"

'Version of the App
App.Version="1.0"

'Description of the App. You can use HTML to format it
App.Description=$"Shows your website unique visitor on <b>busuanzi</b> statistical tools"$

App.Author="JoyLau"

App.CoverIcon=iconId

'SetupInstructions. You can use HTML to format it
App.setupDescription= $"
<b>Website:</b> Your Website Address<br/>
<b>IconID:</b> Icon id<br/>
"$

'How many downloadhandlers should be generated
App.Downloads=1

'IconIDs from AWTRIXER. You can add multiple if you want to display them at the same time
App.Icons=Array As Int(iconId)

'Tickinterval in ms (should be 65 by default, for smooth scrolling))p://
App.Tick=65

'needed Settings for this App (Wich can be configurate from user via webinterface)
App.settings=CreateMap("Website":"http://blog.joylau.cn","IconID":iconId)

App.MakeSettings
Return "AWTRIX20"
End Sub

'this sub is called right before AWTRIX will display your App
Sub App_Started

End Sub

'Called with every update from Awtrix
'return one URL for each downloadhandler
Sub App_startDownload(jobNr As Int)
Select jobNr
Case 1
App.Download("http://busuanzi.ibruce.info/busuanzi?jsonpCallback=callback")
App.Header = CreateMap("Referer":App.Get("Website"),"Cookie":"busuanziId=D58737A150864C68B83F962028616CD6")
End Select
End Sub

'process the response from each download handler
'if youre working with JSONs you can use this online parser
'to generate the code automaticly
'https://json.blueforcer.de/
Sub App_evalJobResponse(Resp As JobResponse)
Try
If Resp.success Then
Select Resp.jobNr
Case 1
Dim parser As JSONParser
parser.Initialize(Resp.ResponseString.replace("try{callback(","").replace(");}catch(e){}",""))
Dim root As Map = parser.NextObject
followers = root.Get("site_uv")
End Select
End If
Catch
Log("Error in: "& App.Name & CRLF & LastException)
Log("API response: " & CRLF & Resp.ResponseString)
End Try
End Sub

'this sub is called right before AWTRIX will display your App
Sub App_iconRequest
App.Icons=Array As Int(App.Get("IconID"))
End Sub

'With this sub you build your frame.
Sub App_genFrame
App.genText(followers,True,1,Null,True)
App.drawBMP(0,0,App.getIcon(App.Get("IconID")),8,8)
End Sub

配置项解释:

  1. App.name: 应用程序的名称。在Appstore,MyApps和文件名中使用
  2. App.version: 应用程序的版本。版本必须是数字,并且最多可以包含2个小数位(例如1.25)
  3. App.description: 应用程序的描述,简要地描述应用。可以选择将文本格式设置为HTML
  4. App.author: 应用程序的创建者。
  5. App.coverIcon:应用程序的图标。数据库中的 IconID。也可以在 Web 页面里创建并上传自己的图标
  6. App.settings: 应用程序的设置。生成一个由键和值组成的映射。例如```CreateMap(“ Key”:“ Value”)``可以输入缺省值,以便应用可以立即启动,也可以将值保留为空(“”),以便 AWTRIX 通知用户需要调整。在设置之前,AWTRIX 将不会加载该应用程序!
  7. App.setupDescription: 简要说明如何设置应用程序。可以将文本格式设置为HTML
  8. App.downloads: 指定您的应用需要下载多少次。如果一个下载依赖于另一下载,则需要多次下载。
  9. App.icons: 指定应用程序需要的图标。在启动应用程序之前,这些也将由 AWTRIX 下载。
  10. App.tick: 指定应用程序应运行的速度。对于简单的文本,65(ms)是最适合的。

程序解释:

  1. 设置默认的访问量 0,默认使用的图标 id: 1230
  2. App 运行时请求接口: http://busuanzi.ibruce.info/busuanzi?jsonpCallback=callback, 需要带上头信息 Referer,和 Cookie

Referer: 模拟浏览器请求,否则的话不蒜子的接口将不可用
Cookie: 带上 Cookie 的话相当于用户一直在刷新网页的效果,此时独立访客数不会 +1 的, 不带上的话每次访问接口都是导致数目 +1 ,造成数量不准确

  1. 解析返回的回调信息字符串结果, 获取想要的数据 site_uv

编译

  1. 将以上项目导入 B4J
  2. 点击 Tools -> Configure Paths , 设置 javac 的目录和 jar 包导出目录(Additional Libraries)
  3. 编译 jar 包: 点击 Project -> Compile to Library

手动安装

  1. 将已编译的 jar 包复制到服务端的 Apps 文件夹中
  2. 在 Awtrix Web 界面重新启动 AWTRIX
1
reload apps

报错信息

1
2
You have JVM property https.proxyHost set to '...'.
This may lead to incorrect behaviour. Proxy should be set in Settings | Proxy

这是由于本地开启了科学上网代理服务造成的

解决方式

select Help -> Edit Custom VM Options add below:

-Dhttp.proxyHost
-Dhttp.proxyPort
-Dhttps.proxyHost
-Dhttps.proxyPort
-DsocksProxyHost
-DsocksProxyPort

有时我们希望在后台实时生成文件并下载到客户端

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
@GetMapping(value = "download")
public void download(HttpServletResponse response) {
try(OutputStream outputStream = response.getOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream, StandardCharsets.UTF_8)
) {
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=" + new String("压缩文件.zip".getBytes("UTF-8"), "ISO-8859-1"));

File[] files = new File("").listFiles();
for (File file : files) {
// 单个文件压缩
compress(zipOutputStream, new FileInputStream(file), file.getName());
}
zipOutputStream.flush();
} catch (IOException e) {
}

}



/**
* 单个文件压缩
*
* @param zipOutputStream
* @param inputStream
* @param fileName
* @throws IOException
*/
private static void compress(ZipOutputStream zipOutputStream, InputStream inputStream, String fileName) throws IOException {
if (inputStream == null) return;
zipOutputStream.putNextEntry(new ZipEntry(fileName));
int bytesRead;
byte[] buffer = new byte[FileUtil.BUFFER_SIZE];
while ((bytesRead = inputStream.read(buffer)) != -1) {
zipOutputStream.write(buffer, 0, bytesRead);
}
zipOutputStream.closeEntry();
inputStream.close();
}

错误信息

1
2
3
4
5
 The module '/node_modules/better-sqlite3/build/better_sqlite3.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 57. This version of Node.js requires
NODE_MODULE_VERSION 64. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).

解决方法

npm install --save-dev electron-rebuild
使用electron-rebuild进行重新编译:

node_modules/.bin/electron-rebuild -f -w better-sqlite3

如果没有编译成功, 则查看是否安装了, node-gyp
因为在 electron-rebuild 项目的 README 里,
看到这句话:

1
If you have a good node-gyp config but you see an error about a missing element on Windows like `Could not load the Visual C++ component "VCBuild.exe"`, try to launch electron-rebuild in an npm script:

背景

多客户端服务器的OpenVPN 2.0配置文件示例
本文件用于多客户端<->单服务器端的
OpenVPN服务器端配置

2. 配置文件说明

2.1. 服务端配置文件

英文原文

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
#################################################
# 多客户端服务器的OpenVPN 2.0配置文件示例 #
# #
# 本文件用于多客户端<->单服务器端的 #
# OpenVPN服务器端配置 #
# #
# OpenVPN也支持单机<->单机的配置 #
# (在网站上的示例页面更多信息) #
# #
# 这个配置可以在Windows或Linux/BSD系统上工作。 #
# Windows的路径名需要加双引号并使用双反斜杠,如: #
# "C:\\Program Files\\OpenVPN\\config\\foo.key" #
# #
# 前面加'#'';'的是注释 #
#################################################

# OpenVPN应该监听哪个本地IP地址(可选)
# 如果不设置,默认监听所有IP
;local a.b.c.d

# OpenVPN应该监听哪个端口(TCP/UDP)
# 如果想在同一台计算机上运行多个OpenVPN实例,可以使用不同的端口号来区分它们
# 在防火墙上打开这个端口
port 1194

# 服务器使用TCP还是UDP协议
;proto tcp
proto udp

# 指定OpenVPN创建的通信隧道类型
# "dev tun"将会创建一个路由IP隧道
# "dev tap"将会创建一个以太网隧道
# 如果是以太网桥接模式,并且提前创建了一个名为"tap0"的与以太网接口进行桥接的虚拟接口,则你可以使用"dev tap0"
# 如果想控制VPN的访问策略,必须为TUN/TAP接口创建防火墙规则
# 在非Windows系统中,可以给出明确的单位编号,如"tun0"
# 在Windows中,也可以使用"dev-node"
# 在大多数系统上,除非部分或完全禁用了TUN/TAP接口的防火墙,否则VPN将不起作用。
;dev tap
dev tun

# 如果想配置多个隧道,需要用到网络连接面板中TAP-Win32适配器的名称(如"MyTap")
# 在XP SP2或更高版本的系统中,可能需要有选择地禁用掉针对TAP适配器的防火墙
# 通常情况下,非Windows系统则不需要该指令。
;dev-node MyTap

# 设置SSL/TLS根证书(ca)、证书(cert)和私钥(key)。
# 每个客户端和服务器端都需要它们各自的证书和私钥文件。
# 服务器端和所有的客户端都将使用相同的CA证书文件。
#
# 通过easy-rsa目录下的一系列脚本可以生成所需的证书和私钥。
# 服务器端和每个客户端的证书必须使用唯一的Common Name。
#
# 也可以使用遵循X509标准的任何密钥管理系统来生成证书和私钥。
# OpenVPN也支持使用一个PKCS #12格式的密钥文件(详情查看站点手册页面的"pkcs12"指令)
ca ca.crt
cert server.crt
key server.key # 该文件应该保密

# 迪菲·赫尔曼参数
# 使用如下命令生成:
# openssl dhparam -out dh2048.pem 2048
dh dh2048.pem

# 网络拓扑结构
# 应该为子网(通过IP寻址)
# 除非必须支持Windows客户端v2.0.9及更低版本(net30即每个客户端/30)
# 默认为"net30"(不建议)
;topology subnet

# 设置服务器端模式,并提供一个VPN子网,以从中为客户端分配IP地址
# 本例中服务器端自身占用10.8.0.1,其他的将分配给客户端使用
# 每个客户端将能够通过10.8.0.1访问服务器
# 如果使用的是以太网桥接模式,注释掉本行。更多信息请查看官方手册页面。
server 10.8.0.0 255.255.255.0

# 在此文件中维护客户端与虚拟IP地址之间的关联记录
# 如果OpenVPN重启,重新连接的客户端可以被分配到先前分配的虚拟IP地址
ifconfig-pool-persist ipp.txt

# 该指令仅针对以太网桥接模式
# 首先,必须使用操作系统的桥接能力将以太网网卡接口和TAP接口进行桥接
# 然后,需要手动设置桥接接口的IP地址、子网掩码,这里假设为10.8.0.4和255.255.255.0
# 最后,必须指定子网的一个IP范围(例如从10.8.0.50开始,到10.8.0.100结束),以便于分配给连接的客户端
# 如果不是以太网桥接模式,直接注释掉这行指令即可
;server-bridge 10.8.0.4 255.255.255.0 10.8.0.50 10.8.0.100

# 该指令仅针对使用DHCP代理的以太网桥接模式
# 此时客户端将请求服务器端的DHCP服务器,从而获得分配给它的IP地址和DNS服务器地址
# 在此之前,也需要先将以太网网卡接口和TAP接口进行桥接
# 注意:该指令仅用于OpenVPN客户端(如Windows),并且该客户端的TAP适配器需要绑定到一个DHCP客户端上
;server-bridge

# 推送路由信息到客户端,以允许客户端能够连接到服务器后的其他私有子网
# 即允许客户端访问VPN服务器可访问的其他局域网
# 记住,这些私有子网还需要将OpenVPN客户端地址池(10.8.0.0/255.255.255.0)路由回到OpenVPN服务器
;push "route 192.168.10.0 255.255.255.0"
;push "route 192.168.20.0 255.255.255.0"

# 要为指定的客户端分配特定的IP地址,或者客户端后的私有子网也要访问VPN
# 可以针对该客户端的配置文件使用ccd子目录
# 请参阅手册页获取更多信息

# 示例1:假设有个Common Name为"Thelonious"的客户端后有一个小型子网也要连接到VPN
# 该子网为192.168.40.128/255.255.255.248
# 首先,去掉下面两行指令的注释:
;client-config-dir ccd
;route 192.168.40.128 255.255.255.248
# 然后创建一个文件ccd/Thelonious,该文件的内容为(没有"#"):
# iroute 192.168.40.128 255.255.255.248
# 客户端所在的子网就可以访问VPN了
# 注意,这个指令只能在基于路由模式而不是基于桥接模式下才能生效
# 比如,你使用了"dev tun""server"指令

# 示例1:假设要给Thelonious分配一个固定的IP地址10.9.0.1
# 首先,去掉下面两行指令的注释:
;client-config-dir ccd
;route 10.9.0.0 255.255.255.252
# 然后在文件ccd/Thelonious中添加如下指令(没有"#"):
# ifconfig-push 10.9.0.1 10.9.0.2

# 如果想要为不同群组的客户端启用不同的防火墙访问策略,你可以使用如下两种方法:
# (1)运行多个OpenVPN守护进程,每个进程对应一个群组,并为每个进程(群组)启用适当的防火墙规则
# (2)(进阶)创建一个脚本来动态地修改响应于来自不同客户的防火墙规则
# 关于learn-address脚本的更多信息请参考官方手册页面
;learn-address ./script

# 如果启用该行指令,所有客户端的默认网关都将重定向到VPN
# 这将导致诸如web浏览器、DNS查询等所有客户端流量都经过VPN
# (为确保能正常工作,OpenVPN服务器所在计算机可能需要在TUN/TAP接口与以太网之间使用NAT或桥接技术进行连接)
;push "redirect-gateway def1 bypass-dhcp"

# 某些具体的Windows网络设置可以被推送到客户端,例如DNS或WINS服务器地址
# 下列地址来自opendns.com提供的Public DNS服务器
;push "dhcp-option DNS 208.67.222.222"
;push "dhcp-option DNS 208.67.220.220"

# 去掉该行指令的注释将允许不同的客户端之间互相访问
# 默认情况,客户端只能访问服务器
# 为了确保客户端只能看见服务器,还可以在服务器端的TUN/TAP接口上设置适当的防火墙规则
;client-to-client

# 如果多个客户端可能使用相同的证书/私钥文件或Common Name进行连接,那么可以取消该指令的注释
# 建议该指令仅用于测试目的。对于生产环境使用而言,每个客户端都应该拥有自己的证书和私钥
# 如果没有为每个客户端分别生成Common Name唯一的证书/私钥,可以取消该行的注释(不推荐这样做)
;duplicate-cn

# keepalive指令将导致类似于ping命令的消息被来回发送,以便于服务器端和客户端知道对方何时被关闭
# 每10秒钟ping一次,如果120秒内都没有收到对方的回复,则表示远程连接已经关闭
keepalive 10 120

# 出于SSL/TLS之外更多的安全考虑,创建一个"HMAC 防火墙"可以帮助抵御DoS攻击和UDP端口淹没攻击
# 可以使用以下命令来生成:
# openvpn --genkey --secret ta.key
#
# 服务器和每个客户端都需要拥有该密钥的一个拷贝
# 第二个参数在服务器端应该为'0',在客户端应该为'1'
tls-auth ta.key 0 # 该文件应该保密

# 选择一个密码加密算法,该配置项也必须复制到每个客户端配置文件中
# 注意,v2.4客户端/服务器将自动以TLS模式协商AES-256-GCM,请参阅手册中的ncp-cipher选项
cipher AES-256-CBC

# 在VPN链接上启用压缩并将选项推送到客户端(仅适用于v2.4 +,对于早期版本,请参阅下文)
;compress lz4-v2
;push "compress lz4-v2"

# 对于与旧客户端兼容的压缩,使用comp-lzo
# 如果在此启用,还必须在客户端配置文件中启用它
;comp-lzo

# 允许并发连接的客户端的最大数量
;max-clients 100

# 初始化后减少OpenVPN守护进程的权限是一个好主意
# 该指令仅限于非Windows系统中使用
;user nobody
;group nobody

# 持久化选项可以尽量避免访问那些在重启之后由于用户权限降低而无法访问的某些资源
persist-key
persist-tun

# 输出一个简短的状态文件,用于显示当前的连接状态,该文件每分钟都会清空并重写一次
status openvpn-status.log

# 默认情况下,日志消息将写入syslog(在Windows系统中,如果以服务方式运行,日志消息将写入OpenVPN安装目录的log文件夹中)
# 可以使用log或者log-append来改变这种默认设置
# "log"方式在每次启动时都会清空之前的日志文件
# "log-append"是在之前的日志内容后进行追加
# 你可以使用两种方式之一(不要同时使用)
;log openvpn.log
;log-append openvpn.log

# 为日志文件设置适当的冗余级别(0~9)
# 冗余级别越高,输出的信息越详细
#
# 0 表示静默运行,只记录致命错误
# 4 表示合理的常规用法
# 5和6 可以帮助调试连接错误
# 9 表示极度冗余,输出非常详细的日志信息
verb 3

# 忽略过多的重复信息
# 相同类别的信息只有前20条会输出到日志文件中
;mute 20

# 通知客户端,当服务器重新启动时,可以自动重新连接
# 只能是UDP协议使用,TCP使用的话不能启动服务
explicit-exit-notify 1

# (如果不添加该指令则)默认值3600,也就是一个小时进行一次TSL重新协商
# 这个参数在服务端和客户端设置都有效
# 如果两边都设置了,就按照时间短的设定优先
# 当两边同时设置成0,表示禁用TSL重协商。使用OTP认证需要禁用
reneg-sec 0

2.2. 客户端配置文件

英文原文

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
##############################################
# 多客户端的OpenVPN 2.0的客户端配置文件示例 #
# #
# 该配置文件可以被多个客户端使用 #
# 不过每个客户端都应该有自己的证书和密钥文件   #
# #
# 在Windows上此配置文件的后缀应该是".ovpn" #
# 在Linux/BSD系统中后缀是".conf" #
##############################################

# 指定这是一个客户端,这将从服务器获取某些配置文件指令
client

# 使用与服务器上相同的设置
# 在大多数系统中,除非部分禁用或者完全禁用了TUN/TAP接口的防火墙,否则VPN将不起作用
;dev tap
dev tun

# 在Windows系统中,如果想配置多个隧道,则需要该指令
# 需要用到网络连接面板中TAP-Win32适配器的名称(例如"MyTap")
# 在XP SP2或更高版本的系统中,可能需要禁用掉针对TAP适配器的防火墙
;dev-node MyTap

# 指定连接的服务器是采用TCP还是UDP协议
# 使用与服务器上相同的设置
;proto tcp
proto udp

# 指定服务器的主机名(或IP)以及端口号
# 如果有多个VPN服务器,为了实现负载均衡,可以设置多个remote指令
remote my-server-1 1194
;remote my-server-2 1194

# 如果指定了多个remote指令,启用该指令将随机连接其中的一台服务器
# 否则,客户端将按照指定的先后顺序依次尝试连接服务器
;remote-random

# 启用该指令,与服务器连接中断后将自动重新连接,
# 这在网络不稳定的情况下(例如:笔记本电脑无线网络)非常有用
resolv-retry infinite

# 大多数客户端不需要绑定本机特定的端口号
nobind

# 在初始化完毕后,降低OpenVPN的权限(该指令仅限于非Windows系统中使用)
;user nobody
;group nobody

# 持久化选项可以尽量避免访问在重启时由于用户权限降低而无法访问的某些资源
persist-key
persist-tun

# 如果通过HTTP代理方式来连接到实际的VPN服务器
# 在此处指定代理服务器的主机名(或IP)和端口号
# 如果代理服务器需要身份认证,请参考官方手册
;http-proxy-retry # 连接失败时自动重试
;http-proxy [proxy server] [proxy port #]

# 无线网络通常会产生大量的重复数据包
# 设置此标识将忽略掉重复数据包的警告信息
;mute-replay-warnings

# SSL/TLS参数配置
# 更多描述信息请参考服务器端配置文件
# 最好为每个客户端单独分配.crt/.key文件对
# 单个CA证书可以供所有客户端使用
ca ca.crt
cert client.crt
key client.key

# 通过检查证书具有正确的密钥使用设置来验证服务器证书
# 这是防止此处讨论的潜在攻击的重要预防措施:
# http://openvpn.net/howto.html#mitm
#
# 要使用此功能,EasyRSA生成服务器证书的时候进行相关设置
remote-cert-tls server

# 如果在服务器上使用tls-auth密钥,那么每个客户端也必须拥有密钥
tls-auth ta.key 1

# 选择一个加密算法,服务器使用的算法选项,也必须在这里指定它
# 注意,v2.4客户端/服务器将自动以TLS模式协商AES-256-GCM。
# 另请参阅手册中的ncp-cipher选项
cipher AES-256-CBC

# 在VPN连接中启用压缩
# 除非在服务器配置文件中启用,否则不要启用它
;comp-lzo

# 设置日志文件冗余级别(0~9)
# 0 表示静默运行,只记录致命错误
# 4 表示合理的常规用法
# 5和6 可以帮助调试连接错误
# 9 表示极度冗余,输出非常详细的日志信息
verb 3

# 忽略过多的重复信息
# 相同类别的信息只有前20条会输出到日志文件中
;mute 20

# (如果不添加该指令则)默认值3600,也就是一个小时进行一次TSL重新协商
# 这个参数在服务端和客户端设置都有效
# 如果两边都设置了,就按照时间短的设定优先
# 当两边同时设置成0,表示禁用TSL重协商。使用OTP认证需要禁用
reneg-sec 0
0%