aboutsummaryrefslogtreecommitdiffstats
path: root/global-functions
blob: 366ef94aa14ff6e59c899194bdb4b48fd0dce9f8 (plain) (blame)
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
#!rsc by RouterOS
# RouterOS script: global-functions
# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de>
#                         Michael Gisbers <michael@gisbers.de>
# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
#
# requires RouterOS, version=7.7
#
# WARNING: If you find this stripped version of global-functions
# on your Router something went wrong and migration failed. To
# recover run this function: $RouterOSScriptsRecover

# expected configuration version
:global ExpectedConfigVersion 95;

# global functions
:global RouterOSScriptsRecover;
:global ScriptInstallUpdate;

# recover from failed migration
:set RouterOSScriptsRecover do={
  :global ScriptInstallUpdate;

  :foreach Script in={ "global-config"; "global-functions" } do={
    /system/script/set name=$Script source=([ /tool/fetch check-certificate=yes-without-crl ("https://git.eworm.de/cgit/routeros-scripts/plain/" . $Script . ".rsc") output=user as-value]->"data");
    /system/script/run $Script;
  }

  $ScriptInstallUpdate;
}

# install new scripts, update existing scripts
:set ScriptInstallUpdate do={
  :local Scripts    [ :toarray $1 ];
  :local NewComment [ :tostr   $2 ];

  :global ExpectedConfigVersion;
  :global Identity;
  :global IDonate;
  :global NoNewsAndChangesNotification;
  :global NotificationsWithSymbols;
  :global ScriptUpdatesBaseUrl;
  :global ScriptUpdatesFetch;
  :global ScriptUpdatesUrlSuffix;

  :global CertificateAvailable;
  :global EitherOr;
  :global Grep;
  :global IfThenElse;
  :global LogPrintExit2;
  :global ParseKeyValueStore;
  :global RequiredRouterOS;
  :global SendNotification2;
  :global SymbolForNotification;
  :global ValidateSyntax;

  :if ([ $CertificateAvailable "R3" ] = false) do={
    $LogPrintExit2 warning $0 ("Downloading certificate failed, trying without.") false;
  }

  :if ([ $CertificateAvailable "E1" ] = false) do={
    $LogPrintExit2 warning $0 ("Downloading certificate failed, trying without.") false;
  }

  :foreach Script in=$Scripts do={
    :if ([ :len [ /system/script/find where name=$Script ] ] = 0) do={
      $LogPrintExit2 info $0 ("Adding new script: " . $Script) false;
      /system/script/add name=$Script owner=$Script source="#!rsc by RouterOS\n" comment=$NewComment;
    }
  }

  :local ExpectedConfigVersionBefore $ExpectedConfigVersion;
  :local ReloadGlobalFunctions false;
  :local ReloadGlobalConfig false;

  :foreach Script in=[ /system/script/find where source~"^#!rsc by RouterOS\n" ] do={
    :local ScriptVal [ /system/script/get $Script ];
    :local ScriptFile [ /file/find where name=("script-updates/" . $ScriptVal->"name") ];
    :local SourceNew;
    :if ([ :len $ScriptFile ] > 0) do={
      :set SourceNew [ /file/get $ScriptFile contents ];
      /file/remove $ScriptFile;
    }

    :foreach Scheduler in=[ /system/scheduler/find where on-event~("\\b" . $ScriptVal->"name" . "\\b") ] do={
      :local SchedulerVal [ /system/scheduler/get $Scheduler ];
      :if ($ScriptVal->"policy" != $SchedulerVal->"policy") do={
        $LogPrintExit2 warning $0 ("Policies differ for script '" . $ScriptVal->"name" . \
          "' and its scheduler '" . $SchedulerVal->"name" . "'!") false;
      }
    }

    :if ([ :len $SourceNew ] = 0 && $ScriptUpdatesFetch = true) do={
      :local Comment [ $ParseKeyValueStore ($ScriptVal->"comment") ];
      :if (!($Comment->"ignore" = true)) do={
        :do {
          :local BaseUrl $ScriptUpdatesBaseUrl;
          :local UrlSuffix $ScriptUpdatesUrlSuffix;
          :if ([ :typeof ($Comment->"base-url") ] = "str") do={ :set BaseUrl ($Comment->"base-url"); }
          :if ([ :typeof ($Comment->"url-suffix") ] = "str") do={ :set UrlSuffix ($Comment->"url-suffix"); }
          :local Url ($BaseUrl . $ScriptVal->"name" . ".rsc" . $UrlSuffix);

          $LogPrintExit2 debug $0 ("Fetching script '" . $ScriptVal->"name" . "' from url: " . $Url) false;
          :local Result [ /tool/fetch check-certificate=yes-without-crl $Url output=user as-value ];
          :if ($Result->"status" = "finished") do={
            :set SourceNew ($Result->"data");
          }
        } on-error={
          :if ($ScriptVal->"source" = "#!rsc by RouterOS\n") do={
            $LogPrintExit2 warning $0 ("Failed fetching script '" . $ScriptVal->"name" . \
              "', removing dummy. Typo on installation?") false;
            /system/script/remove $Script;
          } else={
            $LogPrintExit2 warning $0 ("Failed fetching script '" . $ScriptVal->"name" . "'!") false;
          }
        }
      }
    }

    :if ([ :len $SourceNew ] > 0) do={
      :if ($SourceNew != $ScriptVal->"source") do={
        :if ([ :pick $SourceNew 0 18 ] = "#!rsc by RouterOS\n") do={
          :local Required ([ $ParseKeyValueStore [ $Grep $SourceNew "# requires RouterOS, " ] ]->"version");
          :if ([ $RequiredRouterOS $0 [ $EitherOr $Required "0.0" ] false ] = true) do={
            :if ([ $ValidateSyntax $SourceNew ] = true) do={
              $LogPrintExit2 info $0 ("Updating script: " . $ScriptVal->"name") false;
              /system/script/set owner=($ScriptVal->"name") source=$SourceNew $Script;
              :if ($ScriptVal->"name" = "global-config") do={
                :set ReloadGlobalConfig true;
              }
              :if ($ScriptVal->"name" = "global-functions" || $ScriptVal->"name" ~ ("^mod/.")) do={
                :set ReloadGlobalFunctions true;
              }
            } else={
              $LogPrintExit2 warning $0 ("Syntax validation for script '" . $ScriptVal->"name" . \
                "' failed! Ignoring!") false;
            }
          } else={
            $LogPrintExit2 warning $0 ("The script '" . $ScriptVal->"name" . "' requires RouterOS " . \
              $Required . ", which is not met by your installation. Ignoring!") false;
          }
        } else={
          $LogPrintExit2 warning $0 ("Looks like new script '" . $ScriptVal->"name" . \
            "' is not valid (missing shebang). Ignoring!") false;
        }
      } else={
        $LogPrintExit2 debug $0 ("Script '" .  $ScriptVal->"name" . "' did not change.") false;
      }
    } else={
      $LogPrintExit2 debug $0 ("No update for script '" . $ScriptVal->"name" . "'.") false;
    }
  }

  :if ($ReloadGlobalFunctions = true) do={
    $LogPrintExit2 info $0 ("Reloading global functions.") false;
    :do {
      /system/script/run global-functions;
    } on-error={
      $LogPrintExit2 error $0 ("Reloading global functions failed!") false;
    }
  }

  :if ($ReloadGlobalConfig = true) do={
    $LogPrintExit2 info $0 ("Reloading global configuration.") false;
    :do {
      /system/script/run global-config;
    } on-error={
      $LogPrintExit2 error $0 ("Reloading global configuration failed!" . \
        " Syntax error or missing overlay\?") false;
    }
  }

  :if ($ExpectedConfigVersionBefore > $ExpectedConfigVersion) do={
    $LogPrintExit2 warning $0 ("The configuration version decreased from " . \
      $ExpectedConfigVersionBefore . " to " . $ExpectedConfigVersion . \
      ". Installed an older version?") false;
  }

  :if ($ExpectedConfigVersionBefore < $ExpectedConfigVersion) do={
    :global GlobalConfigChanges;
    :global GlobalConfigMigration;
    :local ChangeLogCode;

    :do {
      :local Url ($ScriptUpdatesBaseUrl . "news-and-changes.rsc" . $ScriptUpdatesUrlSuffix);
      $LogPrintExit2 debug $0 ("Fetching news, changes and migration: " . $Url) false;
      :local Result [ /tool/fetch check-certificate=yes-without-crl $Url output=user as-value ];
      :if ($Result->"status" = "finished") do={
        :set ChangeLogCode ($Result->"data");
      }
    } on-error={
      $LogPrintExit2 warning $0 ("Failed fetching news, changes and migration!") false;
    }

    :if ([ :len $ChangeLogCode ] > 0) do={
      :if ([ $ValidateSyntax $ChangeLogCode ] = true) do={
        :do {
          [ :parse $ChangeLogCode ];
        } on-error={
          $LogPrintExit2 warning $0 ("The changelog failed to run!") false;
        }
      } else={
        $LogPrintExit2 warning $0 ("The changelog failed syntax validation!") false;
      }
    }

    :if ([ :len $GlobalConfigMigration ] > 0) do={
      :for I from=($ExpectedConfigVersionBefore + 1) to=$ExpectedConfigVersion do={
        :local Migration ($GlobalConfigMigration->[ :tostr $I ]);
        :if ([ :typeof $Migration ] = "str") do={
          :if ([ $ValidateSyntax $Migration ] = true) do={
            $LogPrintExit2 info $0 ("Applying migration for change " . $I . ": " . $Migration) false;
            :do {
              [ :parse $Migration ];
            } on-error={
              $LogPrintExit2 warning $0 ("Migration code for change " . $I . " failed to run!") false;
            }
          } else={
            $LogPrintExit2 warning $0 ("Migration code for change " . $I . " failed syntax validation!") false;
          }
        }
      }
    }

    :local NotificationMessage ("The configuration version on " . $Identity . " increased " . \
       "to " . $ExpectedConfigVersion . ", current configuration may need modification. " . \
       "Please review and update global-config-overlay, then re-run global-config.");
    $LogPrintExit2 info $0 ($NotificationMessage) false;

    :if ([ :len $GlobalConfigChanges ] > 0) do={
      :set NotificationMessage ($NotificationMessage . "\n\nChanges:");
      :for I from=($ExpectedConfigVersionBefore + 1) to=$ExpectedConfigVersion do={
        :local Change ($GlobalConfigChanges->[ :tostr $I ]);
        :set NotificationMessage ($NotificationMessage . "\n " . \
            [ $IfThenElse ($NotificationsWithSymbols = true) ("\E2\97\8F") "*" ] . " " . $Change);
        $LogPrintExit2 info $0 ("Change " . $I . ": " . $Change) false;
      }
    } else={
      :set NotificationMessage ($NotificationMessage . "\n\nNews and changes are not available.");
    }

    :if ($NoNewsAndChangesNotification != true) do={
      :local Link;
      :if ($IDonate != true) do={
        :set NotificationMessage ($NotificationMessage . \
          "\n\n==== donation hint ====\n" . \
          "This project is developed in private spare time and usage is " . \
          "free of charge for you. If you like the scripts and think this is " . \
          "of value for you or your business please consider a donation.");
        :set Link "https://git.eworm.de/cgit/routeros-scripts/about/#donate";
      }

      $SendNotification2 ({ origin=$0; \
        subject=([ $SymbolForNotification "pushpin" ] . "News and configuration changes"); \
        message=$NotificationMessage; link=$Link });
    }

    :set GlobalConfigChanges;
    :set GlobalConfigMigration;
  }
}