-
Notifications
You must be signed in to change notification settings - Fork 0
/
Program.fs
139 lines (115 loc) · 4.63 KB
/
Program.fs
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
open System
open VpnGateConnect
let errorToMessage = function
| UnexpectedStatusCode code -> sprintf "Unexpected status code from response: %d" code
| UnexpectedContentType -> "Unexpected content type from response (expected text body)"
| WebError msg -> sprintf "Internal web error when making request: %s" msg
| EmptyCsv -> "CSV is empty"
| CsvParseError msg -> sprintf "Error while parsing CSV: %s" msg
| InvalidPath path -> sprintf "Nonexistent file or invalid path '%s'" path
| CannotOpenFileBecause msg -> sprintf "Internal error when opening file: %s" msg
open ProgramFlow.Operators
let private consoleOverwriter colorDisabled =
fun (str : string) ->
Console.SetCursorPosition(0, 0)
for c in str do
match c with
| _ when int c >= ControlCharRangeStart && int c <= ControlCharRangeEnd ->
if not colorDisabled then
Console.ForegroundColor <- decodeConsoleColor c
| _ ->
Console.Write c
let parseArguments argv =
match argv |> Cli.parseArgs with
| Ok args -> ContinueData args
| Error msg -> ProgramFlow.usageError msg
let connectToDataSource dataSource =
match dataSource |> DataSource.connect with
| Ok rows -> ContinueData rows
| Error errCode -> errCode |> errorToMessage |> ProgramFlow.runtimeError
let resetConsoleProperties _ =
Console.CursorVisible <- true
Console.ResetColor()
/// Shows a menu prompting user to select a VPN from those allowed by given filter predicate.
let promptForSelection colorDisabled filterPredicate rows =
Console.CancelKeyPress.Add(resetConsoleProperties)
Console.CursorVisible <- false
Console.Clear()
let drawFunction = consoleOverwriter colorDisabled
let filteredRows = Array.filter filterPredicate rows
let rv =
match Gui.execRowSelectorMenu drawFunction filteredRows with
| Some selection -> ContinueData selection
| None -> ProgramFlow.normalExitWithMsg "No VPN selected"
resetConsoleProperties()
Console.Clear()
rv
let printSelectedVpn (vpnData : VpnList.Row) =
printfn "Selected %s" (vpnData.``#HostName``)
vpnData
/// Extracts the VPN config file from given CSV row.
let extractOpenVpnConfig (vpnData : VpnList.Row) =
try
vpnData.OpenVPN_ConfigData_Base64
|> Convert.FromBase64String
|> Text.Encoding.UTF8.GetString
|> ContinueData
with ex -> ex.Message |> ProgramFlow.runtimeError
let readConfigs paths =
try
paths |> Array.map IO.File.ReadAllText |> ContinueData
with ex -> ex.Message |> ProgramFlow.runtimeError
let mergeConfigs mainConfig configs =
seq {
yield mainConfig
for cfg in configs -> cfg
}
|> String.concat "\n"
|> ContinueData
/// Loads user-specified config files from disk and appends them to the downloaded config.
let readAndMergeConfigs configPaths configStr = readConfigs configPaths >>= mergeConfigs configStr
// There's probably some way to pass the config directly through stdout
// or something, but writing to a temp file is simpler
let writeConfigToTempFile str =
try
let tempFile = IO.Path.GetTempFileName()
IO.File.WriteAllText(tempFile, str)
ContinueData tempFile
with ex -> ex.Message |> ProgramFlow.runtimeError
let invokeOpenVpn execPath configPath =
try
let proc = Diagnostics.Process.Start(fileName=execPath, arguments=configPath)
proc.WaitForExit()
ExecResult (proc.ExitCode, None)
with ex -> ex.Message |> ProgramFlow.runtimeError
let printDataSource (_, path) =
printfn "Fetching endpoint list from '%s'..." path
let printDataCount rows =
printfn "Fetched %d rows from endpoint source." (Array.length rows)
rows
let execute (config: Config) =
printDataSource config.DataSource
let regionFilter = fun (x: VpnList.Row) ->
config.AllowedRegions.Length = 0 || config.AllowedRegions |> Array.contains (x.CountryShort.ToLower())
connectToDataSource config.DataSource
<!> printDataCount
>>= promptForSelection config.NoColor regionFilter
<!> printSelectedVpn
>>= extractOpenVpnConfig
>>= readAndMergeConfigs config.ConfigPaths
>>= writeConfigToTempFile
>>= invokeOpenVpn config.OpenVpnPath
[<EntryPoint>]
let main argv =
let config =
argv
|> parseArguments
<!> Config.fromArgs
match config >>= execute with
| ContinueData _ ->
printfn "Error: execution returned without finishing"; 1
| ExecResult (errCode, msg) ->
match msg with
| Some msg -> msg |> printfn "%s"
| None -> ()
errCode