Compare commits
5 commits
e90d4ef6f8
...
7873fb543c
| Author | SHA1 | Date | |
|---|---|---|---|
| 7873fb543c | |||
| 6b24c409f0 | |||
| b5a0f8559d | |||
| 330d293c88 | |||
| 5db54265d5 |
7 changed files with 404 additions and 16 deletions
|
|
@ -53,8 +53,10 @@ Do not worry that the command is not shown - that happens because it contains a
|
|||
|
||||
For basic verification we rename the certificate and print it by fingerprint. Make sure exactly this one certificate ("ISRG-Root-X1") is shown.
|
||||
|
||||
```rsc
|
||||
/certificate/set name="ISRG-Root-X1" [ find where common-name="ISRG Root X1" ];
|
||||
/certificate/print proplist=name,fingerprint where fingerprint="96bcec06264976f37460779acf28c5a7cfe8a3c0aae11a8ffcee05c0bddf08c6";
|
||||
```
|
||||
|
||||
Always make sure there are no certificates installed you do not know or want!
|
||||
|
||||
|
|
@ -85,7 +87,7 @@ $ScriptInstallUpdate ddns-hetzner,dns-to-ipv6-subnet-resolver "base-url=https://
|
|||
|
||||
## Available scripts
|
||||
|
||||
- [Hello World](doc/hello-world.md)
|
||||
- [DDNS (DynDNS) Hetzner update script](doc/ddns-hetzner.md)
|
||||
- [DNS to IPv6 subnet resolver](doc/dns-to-ipv6-subnet-resolver.md)
|
||||
|
||||
## License and warranty
|
||||
|
|
|
|||
240
ddns-hetzner.rsc
Normal file
240
ddns-hetzner.rsc
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
#!rsc by RouterOS
|
||||
# RouterOS script: ddns-hetzner
|
||||
# Version 2.0
|
||||
# Copyright (c) 2024-2026 Philip 'ShokiNN' Henning <mail@philip-henning.com>
|
||||
# https://git.s1q.dev/phg/routeros-scripts-custom/about/COPYING.md
|
||||
#
|
||||
# requires RouterOS, version=7.18
|
||||
#
|
||||
# Updates periodically DNS entries on Hetzner's DNS service with the Router's public IPs
|
||||
# https://git.s1q.dev/phg/routeros-scripts-custom/src/branch/main/doc/ddns-hetzner.md
|
||||
|
||||
:local ExitOK false;
|
||||
onerror Err {
|
||||
:global GlobalConfigReady; :global GlobalFunctionsReady; :global GlobalFunctionsCustomPhgReady;
|
||||
:retry { :if ($GlobalConfigReady != true || $GlobalFunctionsReady != true || $GlobalFunctionsCustomPhgReady != true) \
|
||||
do={ :error ("Global config and/or functions not ready."); };
|
||||
} delay=500ms max=50;
|
||||
|
||||
:local ScriptName [ :jobname ];
|
||||
|
||||
:global LogPrint;
|
||||
:global ParseKeyValueStore;
|
||||
:global ScriptLock;
|
||||
:global SafeResolve;
|
||||
|
||||
# Local/global script specific variables
|
||||
:global PhgDDNSHetznerAPIToken;
|
||||
:global PhgDDNSHetznerDomainEntryConfig;
|
||||
:local APIUrl "https://api.hetzner.cloud/v1";
|
||||
|
||||
:if ([ $ScriptLock $ScriptName ] = false) do={
|
||||
:set ExitOK true;
|
||||
:error false;
|
||||
}
|
||||
|
||||
:local GetLocalIPv4 do={
|
||||
:local IP [/ip/address/get [:pick [find interface="$WANInterface"] 0] address];
|
||||
:return [:pick $IP 0 [:find $IP /]];
|
||||
}
|
||||
|
||||
:local GetLocalIPv6 do={
|
||||
:local IP [/ipv6/address/get [:pick [find interface="$WANInterface" from-pool="$PublicIPv6Pool" !link-local] 0] address];
|
||||
:return [:pick $IP 0 [:find $IP /]];
|
||||
}
|
||||
|
||||
:local GetAnnouncedIP do={
|
||||
:do {
|
||||
$LogPrint debug $ScriptName ("GetAnnouncedIP - started");
|
||||
[/system/script/run "JParseFunctions"; global JSONLoad; global JSONLoads; global JSONUnload];
|
||||
$LogPrint debug $ScriptName ("GetAnnouncedIP - JParseFunctions loaded");
|
||||
|
||||
:local Records;
|
||||
:local AnnouncedIP;
|
||||
|
||||
:set Records ([$JSONLoads ([/tool/fetch "$APIUrl/zones/$ZoneName/rrsets/$RecordName/$RecordType" http-method=get http-header-field="Authorization: Bearer $APIToken" output=user as-value]->"data")]->"rrset"->"records");
|
||||
$LogPrint debug $ScriptName ("GetAnnouncedIP - Records received: " . [:len $Records]);
|
||||
foreach rec in=$Records do={
|
||||
$LogPrint debug $ScriptName ("GetAnnouncedIP - Record: Name: \"" . $RecordName . "\", Type: \"" . $RecordType . "\", Value: \"" . ($rec->"value") . "\", Comment: \"" . ($rec->"comment") . "\"");
|
||||
}
|
||||
|
||||
:if ([:len $Records] > 1) do={
|
||||
:error ("Multiple records found for \"$RecordName.$ZoneName\", RecordType: $RecordType. This is not supported.");
|
||||
} else={
|
||||
:if ([:len $Records] = 1) do={
|
||||
:set AnnouncedIP ($Records->0->"value");
|
||||
}
|
||||
}
|
||||
$LogPrint debug $ScriptName ("GetAnnouncedIP - Announced IP is: " . $AnnouncedIP);
|
||||
|
||||
:return $AnnouncedIP;
|
||||
} on-error={
|
||||
:local Err $message;
|
||||
|
||||
:if ([:find $Err "404"] != -1) do={
|
||||
$LogPrint debug $ScriptName ("GetAnnouncedIP - Announced IP is not set");
|
||||
:return false;
|
||||
}
|
||||
:error ("GetAnnouncedIP - API Error - $Err");
|
||||
}
|
||||
}
|
||||
|
||||
:local APISetRecord do={
|
||||
:do {
|
||||
$LogPrint debug $ScriptName ("APISetRecord - started");
|
||||
[/system/script/run "JParseFunctions"; global JSONLoad; global JSONLoads; global JSONUnload];
|
||||
$LogPrint debug $ScriptName ("APISetRecord - JParseFunctions loaded");
|
||||
|
||||
:local Records;
|
||||
:local Record;
|
||||
:local APIResponse;
|
||||
:local Payload;
|
||||
|
||||
:do {
|
||||
:set Records ([$JSONLoads ([/tool/fetch "$APIUrl/zones/$ZoneName/rrsets/$RecordName/$RecordType" http-method=get http-header-field="Authorization: Bearer $APIToken" output=user as-value]->"data")]->"rrset"->"records");
|
||||
} on-error={
|
||||
:if ([:find $message "404"] != -1) do={
|
||||
:set Records [:toarray ""];
|
||||
} else={
|
||||
$LogPrint error $ScriptName ("APISetRecord - Could not get record from API - $message");
|
||||
}
|
||||
}
|
||||
$LogPrint debug $ScriptName ("APISetRecord - Records received: " . [:len $Records]);
|
||||
foreach rec in=$Records do={
|
||||
$LogPrint debug $ScriptName ("APISetRecord - Record: Name: \"" . $RecordName . "\", Type: \"" . $RecordType . "\", Value: \"" . ($rec->"value") . "\", Comment: \"" . ($rec->"comment") . "\"");
|
||||
}
|
||||
|
||||
:if ([:len $Records] > 1) do={
|
||||
:error ("Multiple records found for \"$RecordName.$ZoneName\", RecordType: $RecordType. This is not supported.");
|
||||
} else={
|
||||
:if ([:len $Records] = 1) do={
|
||||
:set Record ($Records->0);
|
||||
}
|
||||
}
|
||||
|
||||
:local RecordDebugLogOutput;
|
||||
foreach key,value in=$Record do={
|
||||
:if ([:typeof $RecordDebugLogOutput ] != "str" || $RecordDebugLogOutput = "") do={
|
||||
:set RecordDebugLogOutput ($key . ": \"" . $value . "\"");
|
||||
} else={
|
||||
:set RecordDebugLogOutput ($RecordDebugLogOutput . ", " . $key . ": \"" . $value . "\"");
|
||||
}
|
||||
}
|
||||
$LogPrint debug $ScriptName ("APISetRecord - Picked Record: " . $RecordDebugLogOutput);
|
||||
|
||||
:if ([:typeof $Record] != "nothing") do={
|
||||
:set Payload "{\"records\":[{\"value\":\"$InterfaceIP\",\"comment\":\"Updated by RouterOS DDNS Script\"}]}";
|
||||
$LogPrint debug $ScriptName ("APISetRecord - Payload: " . $Payload);
|
||||
$LogPrint debug $ScriptName ("APISetRecord - Updating existing record - URL: $APIUrl/zones/$ZoneName/rrsets/$RecordName/$RecordType/actions/set_records");
|
||||
:set APIResponse ([/tool/fetch "$APIUrl/zones/$ZoneName/rrsets/$RecordName/$RecordType/actions/set_records" http-method=post http-header-field="Content-Type: application/json,Authorization: Bearer $APIToken" http-data=$Payload output=user as-value]->"status");
|
||||
} else={
|
||||
:set Payload "{\"name\":\"$RecordName\",\"type\":\"$RecordType\",\"ttl\":$([:tonum $RecordTTL]),\"records\":[{\"value\":\"$InterfaceIP\",\"comment\":\"Updated by RouterOS DDNS Script\"}]}";
|
||||
$LogPrint debug $ScriptName ("APISetRecord - Payload: " . $Payload);
|
||||
$LogPrint debug $ScriptName ("APISetRecord - Creating new record - URL: $APIUrl/zones/$ZoneName/rrsets");
|
||||
:set APIResponse ([/tool/fetch "$APIUrl/zones/$ZoneName/rrsets" http-method=post http-header-field="Content-Type: application/json,Authorization: Bearer $APIToken" http-data=$Payload output=user as-value]->"status");
|
||||
}
|
||||
$LogPrint debug $ScriptName ("APISetRecord - APIResponse: " . $APIResponse);
|
||||
|
||||
$JSONUnload;
|
||||
$LogPrint debug $ScriptName ("APISetRecord - JSONUnload done");
|
||||
$LogPrint debug $ScriptName ("APISetRecord - finished");
|
||||
return $APIResponse;
|
||||
} on-error={
|
||||
#TODO Send error via Notification system
|
||||
$LogPrint error $ScriptName ("Could not set record - $message");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$LogPrint debug $ScriptName ("Begin DDNS update process");
|
||||
|
||||
:local index 0;
|
||||
:foreach i in=$PhgDDNSHetznerDomainEntryConfig do={
|
||||
:local WANInterface ("$($i->0)");
|
||||
:local PublicIPv6Pool ("$($i->1)");
|
||||
:local ZoneName ("$($i->2)");
|
||||
:local RecordType ("$($i->3)");
|
||||
:local RecordName ("$($i->4)");
|
||||
:local RecordTTL ("$($i->5)");
|
||||
:local FQDN;
|
||||
:local InterfaceIP;
|
||||
:local DNSIP;
|
||||
:local StartLogMsg "Start configuring domain: ";
|
||||
:local EndLogMsg "Finished configuring domain: ";
|
||||
|
||||
:if ($RecordName = "@") do={
|
||||
:set FQDN ("$($i->2)");
|
||||
} else={
|
||||
:set FQDN ("$($i->4).$($i->2)");
|
||||
}
|
||||
|
||||
:if ($RecordType = "A") do={
|
||||
$LogPrint debug $ScriptName ($StartLogMsg . $FQDN . " - Type A Record");
|
||||
|
||||
:set InterfaceIP [$GetLocalIPv4 WANInterface=$WANInterface];
|
||||
:set DNSIP [$GetAnnouncedIP APIUrl=$APIUrl APIToken=$PhgDDNSHetznerAPIToken ZoneName=$ZoneName RecordType=$RecordType RecordName=$RecordName LogPrint=$LogPrint ScriptName=$ScriptName];
|
||||
$LogPrint debug $ScriptName ("Domain: " . $FQDN . " - Announced DNS IP: " . $DNSIP);
|
||||
|
||||
:if ($InterfaceIP != $DNSIP) do={
|
||||
:if ($DNSIP = false) do={
|
||||
$LogPrint debug $ScriptName ("Domain: " . $FQDN . " - local IP: " . $InterfaceIP . ", differs from DNS IP: none");
|
||||
} else={
|
||||
$LogPrint debug $ScriptName ("Domain: " . $FQDN . " - local IP: " . $InterfaceIP . ", differs from DNS IP: " . $DNSIP);
|
||||
}
|
||||
$LogPrint debug $ScriptName ("Domain: " . $FQDN . " - Updating A Record to " . $InterfaceIP);
|
||||
|
||||
:local ResponseSetRecord [$APISetRecord APIUrl=$APIUrl APIToken=$PhgDDNSHetznerAPIToken ZoneName=$ZoneName RecordType=$RecordType RecordName=$RecordName RecordTTL=$RecordTTL InterfaceIP=$InterfaceIP LogPrint=$LogPrint ScriptName=$ScriptName];
|
||||
$LogPrint debug $ScriptName ("ResponseSetRecord: " . $ResponseSetRecord);
|
||||
|
||||
:if ($ResponseSetRecord = "finished") do={
|
||||
$LogPrint info $ScriptName ("Domain: " . $FQDN . " - Updating A Record to " . $InterfaceIP . " successful");
|
||||
}
|
||||
} else={
|
||||
$LogPrint debug $ScriptName ("Domain: " . $FQDN . " - local IP: " . $InterfaceIP . ", is equal to DNS IP: " . $DNSIP . " - Nothing to do");
|
||||
}
|
||||
|
||||
$LogPrint debug $ScriptName ($EndLogMsg . $FQDN . " - Type A Record");
|
||||
}
|
||||
|
||||
:if ($RecordType = "AAAA") do={
|
||||
$LogPrint debug $ScriptName ($StartLogMsg . $FQDN . " - Type AAAA Record");
|
||||
|
||||
:set InterfaceIP [$GetLocalIPv6 WANInterface=$WANInterface PublicIPv6Pool=$PublicIPv6Pool];
|
||||
:set DNSIP [$GetAnnouncedIP APIUrl=$APIUrl APIToken=$PhgDDNSHetznerAPIToken ZoneName=$ZoneName RecordType=$RecordType RecordName=$RecordName LogPrint=$LogPrint ScriptName=$ScriptName];
|
||||
$LogPrint debug $ScriptName ("Domain: " . $FQDN . " - Announced DNS IP: " . $DNSIP);
|
||||
|
||||
:if ($InterfaceIP != $DNSIP) do={
|
||||
:if ($DNSIP = false) do={
|
||||
$LogPrint debug $ScriptName ("Domain: " . $FQDN . " - local IP: " . $InterfaceIP . ", differs from DNS IP: none");
|
||||
} else={
|
||||
$LogPrint debug $ScriptName ("Domain: " . $FQDN . " - local IP: " . $InterfaceIP . ", differs from DNS IP: " . $DNSIP);
|
||||
}
|
||||
$LogPrint debug $ScriptName ("Domain: " . $FQDN . " - Updating AAAA Record to " . $InterfaceIP);
|
||||
|
||||
:local ResponseSetRecord [$APISetRecord APIUrl=$APIUrl APIToken=$PhgDDNSHetznerAPIToken ZoneName=$ZoneName RecordType=$RecordType RecordName=$RecordName RecordTTL=$RecordTTL InterfaceIP=$InterfaceIP LogPrint=$LogPrint ScriptName=$ScriptName];
|
||||
$LogPrint debug $ScriptName ("ResponseSetRecord: " . $ResponseSetRecord);
|
||||
|
||||
:if ($ResponseSetRecord = "finished") do={
|
||||
$LogPrint info $ScriptName ("Domain: " . $FQDN . " - Updating AAAA Record to " . $InterfaceIP . " successful");
|
||||
}
|
||||
} else={
|
||||
$LogPrint debug $ScriptName ("Domain: " . $FQDN . " - local IP: " . $InterfaceIP . ", is equal to DNS IP: " . $DNSIP . " - Nothing to do");
|
||||
}
|
||||
|
||||
$LogPrint debug $ScriptName ($EndLogMsg . $FQDN . " - Type AAAA Record");
|
||||
}
|
||||
|
||||
|
||||
:if (($RecordType != "A") && ($RecordType != "AAAA")) do={
|
||||
$LogPrint error $ScriptName ("Wrong Record type for array index number " . $index . " (Value: " . $RecordType . ")");
|
||||
}
|
||||
|
||||
:set index ($index+1);
|
||||
}
|
||||
:set index;
|
||||
|
||||
$LogPrint debug $ScriptName ("Finished DDNS update process");
|
||||
|
||||
} do={
|
||||
:global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
# RouterOS compatibility: 7+
|
||||
# Version 1.1
|
||||
# last update: 03.01.2026
|
||||
# https://git.s1q.dev/phg/routeros-scripts-custom/about/doc/dns-to-ipv6-subnet-resolver.md
|
||||
# https://git.s1q.dev/phg/routeros-scripts-custom/src/branch/main/doc/dns-to-ipv6-subnet-resolver.md
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
:local ExitOK false;
|
||||
|
|
|
|||
146
doc/ddns-hetzner.md
Normal file
146
doc/ddns-hetzner.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# DDNS (DynDNS) Hetzner update script
|
||||
|
||||
[⬅️ Go back to main README](../README.md)
|
||||
|
||||
> ℹ️ **Info**: This script can not be used on its own but requires the base
|
||||
> installation. See [main README](../README.md) for details.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [DDNS (DynDNS) Hetzner update script](#ddns-dyndns-hetzner-update-script)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Description](#description)
|
||||
- [Requirements and installation](#requirements-and-installation)
|
||||
- [Dependencies](#dependencies)
|
||||
- [Installation](#installation)
|
||||
- [Pre requisites](#pre-requisites)
|
||||
- [Installation](#installation-1)
|
||||
- [Configuration](#configuration)
|
||||
- [`PhgDDNSHetznerAPIToken`](#phgddnshetznerapitoken)
|
||||
- [`PhgDDNSHetznerDomainEntryConfig`](#phgddnshetznerdomainentryconfig)
|
||||
- [Usage and invocation](#usage-and-invocation)
|
||||
- [See also](#see-also)
|
||||
|
||||
## Description
|
||||
|
||||
This Mikrotik RouterOS 7 script for updating DNS entries via Hetzner's Cloud API.
|
||||
|
||||
The script is currently only compatible with RouterOS 7.
|
||||
RouterOS 6 isn't and won't be supported!
|
||||
|
||||
## Requirements and installation
|
||||
|
||||
### Dependencies
|
||||
|
||||
This script requires [Winand](https://github.com/Winand)'s [mikrotik-json-parser](https://github.com/Winand/mikrotik-json-parser) to be installed.
|
||||
|
||||
#### Installation
|
||||
|
||||
Create another new script:
|
||||
|
||||
1. Name: `JParseFunctions`
|
||||
2. Policy: `read`, `write`, `test` uncheck everything else
|
||||
3. Source: The content of [mikrotik-json-parser](https://github.com/Winand/mikrotik-json-parser/blob/master/JParseFunctions)
|
||||
|
||||
### Pre requisites
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **It's strongly recommended to create a separate Project just for your DNS Zone!**
|
||||
> Because the API Token you will create will have Read/Write access to the whole Project it can't be restricted to specific services like DNS.
|
||||
|
||||
Create a [API token for Hetzner's Cloud API](https://docs.hetzner.cloud/reference/cloud#getting-started).
|
||||
|
||||
The API token can be created at:
|
||||
`Your cloud project` -> `Security` -> `API-Tokens`
|
||||
|
||||
### Installation
|
||||
|
||||
Just install the script:
|
||||
|
||||
```rsc
|
||||
$ScriptInstallUpdate ddns-hetzner "base-url=https://git.s1q.dev/phg/routeros-scripts-custom/raw/branch/main/";
|
||||
/system/script/set [find name="ddns-hetzner"] policy=read,write,test
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit `global-config-overlay` and add the following variables.
|
||||
|
||||
| Variable name | Requried | Data type | Example | Description |
|
||||
| :-------------------------------- | :------- | :-------------------- | :------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `PhgDDNSHetznerAPIToken` | true | `string` | `LRK9DAWQ1ZAEFSrCNEEzLCUwhYX1U3g7wMg4dTlkkDC96fyDuyJ39nVbVjCKSDfj` | This variable requires a valid API token for the [Hetzner DNS API](https://docs.hetzner.cloud/reference/cloud#getting-started). You can create an API token in you project settings. |
|
||||
| `PhgDDNSHetznerDomainEntryConfig` | true | `array`s of `string`s | `{{"pppoe-out1";"";"example.com";"A";"@";"300";};{"pppoe-out1";"pool-ipv6";"example.com";"AAAA";"@";"300";};}` | See below how to format the arrays correctly. |
|
||||
|
||||
### `PhgDDNSHetznerAPIToken`
|
||||
|
||||
Example:
|
||||
|
||||
```rsc
|
||||
:global PhgDDNSHetznerAPIToken "LRK9DAWQ1ZAEFSrCNEEzLCUwhYX1U3g7wMg4dTlkkDC96fyDuyJ39nVbVjCKSDfj";
|
||||
```
|
||||
|
||||
### `PhgDDNSHetznerDomainEntryConfig`
|
||||
|
||||
The `domainEntryConfig` array consists of multiple arrays. Each of the is configuring a DNS record for a given domain in a zone.
|
||||
|
||||
The data sheet below describes the formatting of the DNS records arrays.
|
||||
|
||||
|
||||
| Array index | Data | Data type | Example | Description |
|
||||
| ----------: | :------------ | :-------- | :------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `0` | `interface` | `string` | `"pppoe-out1"` | Name of the interface where the IP which is currently configured is fetched from. |
|
||||
| `1` | `pool` | `string` | `"pool-ipv6"` | The prefix delegation pool which is used to automatically setup the IPv6 interface IP. Use "" when you don't use a pool to set your interface ip or for a type A record. |
|
||||
| `2` | `zone` | `string` | `"domain.com"` | Zone which should be used to set a record to. |
|
||||
| `3` | `record type` | `string` | `"A"` | Valid values `A`, `AAAA`. The type of record which will be set. Also determines which IP (v4/v6) will be fetched. |
|
||||
| `4` | `record name` | `string` | `"@"` | The record name which should be updated. Use `@` for the root of your domain. |
|
||||
| `5` | `record TTL` | `string` | `"300"` | TTL value of the record in seconds, for a dynamic entry a short lifetime like 300 is recommended. |
|
||||
|
||||
Example:
|
||||
|
||||
```rsc
|
||||
:global PhgDDNSHetznerDomainEntryConfig {
|
||||
{
|
||||
"pppoe-out1";
|
||||
"";
|
||||
"example.com";
|
||||
"A";
|
||||
"@";
|
||||
"300";
|
||||
};
|
||||
{"pppoe-out1";"pool-ipv6";"example.com";"AAAA";"@";"300";};
|
||||
{"pppoe-out1";"";"example.net";"A";"ddns";"300";};
|
||||
{"pppoe-out1";"pool-ipv6";"example.org";"AAAA";"ddns";"300";};
|
||||
};
|
||||
```
|
||||
|
||||
This example will create & update those DNS records:
|
||||
|
||||
- example.com
|
||||
- IPv4
|
||||
- IPv6
|
||||
- example.net
|
||||
- IPv4
|
||||
- example.org
|
||||
- IPv6
|
||||
|
||||
## Usage and invocation
|
||||
|
||||
How to run the script manually:
|
||||
|
||||
```rsc
|
||||
/system/script/run ddns-hetzner;
|
||||
```
|
||||
|
||||
Setup a Scheduler to run the script regularly:
|
||||
|
||||
```rsc
|
||||
/system/scheduler/add name="ddns-hetzner" interval="00:05:00" policy="read,write,test" on-event="/system/script/run ddns-hetzner;";
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
* ...
|
||||
|
||||
---
|
||||
[⬅️ Go back to main README](../README.md)
|
||||
[⬆️ Go back to top](#top)
|
||||
|
|
@ -32,7 +32,7 @@ $ScriptInstallUpdate dns-to-ipv6-subnet-resolver "base-url=https://git.s1q.dev/p
|
|||
|
||||
## Configuration
|
||||
|
||||
Edit `global-config-overlay` and Add the following variables.
|
||||
Edit `global-config-overlay` and add the following variables.
|
||||
|
||||
| Variable name | Required | Data type | Example | Description |
|
||||
| :-------------------------------- | :------- | :-------- | :---------------------------------- | :--------------------------------------------------------------------------- |
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ Hello World
|
|||
> installation. See [main README](../README.md) for details.
|
||||
|
||||
Description
|
||||
-----------
|
||||
-----------
|
||||
|
||||
This is a demo script. Invoked from terminal it writes to system log and
|
||||
terminal, or sends a notification otherwise.
|
||||
|
||||
Requirements and installation
|
||||
-----------------------------
|
||||
-----------------------------
|
||||
|
||||
Just install the script:
|
||||
|
||||
|
|
@ -26,17 +26,17 @@ There's no specific configuration in `global-config-overlay`, other than
|
|||
general configuration for notifications.
|
||||
|
||||
Usage and invocation
|
||||
--------------------
|
||||
--------------------
|
||||
|
||||
Just run the script:
|
||||
|
||||
/system/script/run hello-world;
|
||||
|
||||
See also
|
||||
See also
|
||||
--------
|
||||
|
||||
* ...
|
||||
|
||||
---
|
||||
[⬅️ Go back to main README](../README.md)
|
||||
---
|
||||
[⬅️ Go back to main README](../README.md)
|
||||
[⬆️ Go back to top](#top)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
#!rsc by RouterOS
|
||||
# RouterOS script: hello-world
|
||||
# Copyright (c) 2023-2025 Christian Hesse <mail@eworm.de>
|
||||
# Copyright (c) 2023-2026 Christian Hesse <mail@eworm.de>
|
||||
# https://git.eworm.de/cgit/routeros-scripts-custom/about/COPYING.md
|
||||
#
|
||||
# hello-world demo script
|
||||
# https://git.eworm.de/cgit/routeros-scripts-custom/about/doc/hello-world.md
|
||||
|
||||
:global GlobalFunctionsReady;
|
||||
:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }
|
||||
|
||||
:local ExitOK false;
|
||||
:do {
|
||||
onerror Err {
|
||||
:global GlobalConfigReady; :global GlobalFunctionsReady;
|
||||
:retry { :if ($GlobalConfigReady != true || $GlobalFunctionsReady != true) \
|
||||
do={ :error ("Global config and/or functions not ready."); }; } delay=500ms max=50;
|
||||
:local ScriptName [ :jobname ];
|
||||
|
||||
:global LogPrint;
|
||||
|
|
@ -22,6 +22,6 @@
|
|||
} else={
|
||||
$SendNotification2 ({ origin=$ScriptName; subject="Hello..."; message="... world!" });
|
||||
}
|
||||
} on-error={
|
||||
:global ExitError; $ExitError $ExitOK [ :jobname ];
|
||||
} do={
|
||||
:global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue