Skip to content

Commit 8433ef9

Browse files
authored
Merge pull request #60 from mrikirill/feature/upd-ci-tests-readme
CI configuration, issue templates, and enhance documentation
2 parents 3528006 + 197e3e4 commit 8433ef9

12 files changed

Lines changed: 374 additions & 63 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
name: Bug report
3+
about: Create a report to help us improve
4+
title: ''
5+
labels: bug
6+
assignees: ''
7+
8+
---
9+
10+
**Describe the bug**
11+
A clear and concise description of what the bug is.
12+
13+
**To Reproduce**
14+
Steps to reproduce the behavior:
15+
1. Run command '...'
16+
2. See error
17+
18+
**Expected behavior**
19+
A clear and concise description of what you expected to happen.
20+
21+
**Logs**
22+
If applicable, add logs here. **IMPORTANT: Remove your API Key and Domain names.**
23+
24+
**Environment (please complete the following information):**
25+
- Device: [e.g. Synology DS918+]
26+
- DSM Version: [e.g. DSM 7.1]
27+
- Script Version: [e.g. 2.0]
28+
29+
**Additional context**
30+
Add any other context about the problem here.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
name: Feature request
3+
about: Suggest an idea for this project
4+
title: ''
5+
labels: enhancement
6+
assignees: ''
7+
8+
---
9+
10+
**Is your feature request related to a problem? Please describe.**
11+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12+
13+
**Describe the solution you'd like**
14+
A clear and concise description of what you want to happen.
15+
16+
**Describe alternatives you've considered**
17+
A clear and concise description of any alternative solutions or features you've considered.
18+
19+
**Additional context**
20+
Add any other context or screenshots about the feature request here.

.github/workflows/php-test.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: PHP Unit Tests
2+
3+
on:
4+
push:
5+
branches: [ "master", "main" ]
6+
pull_request:
7+
branches: [ "master", "main" ]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
build:
14+
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- uses: actions/checkout@v3
19+
20+
- name: Validate composer.json
21+
run: composer validate
22+
23+
- name: Cache Composer packages
24+
id: composer-cache
25+
uses: actions/cache@v3
26+
with:
27+
path: vendor
28+
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
29+
restore-keys: |
30+
${{ runner.os }}-php-
31+
32+
- name: Install dependencies
33+
run: composer install --prefer-dist --no-progress
34+
35+
- name: Run test suite
36+
run: vendor/bin/phpunit tests

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
site/
2+
vendor/
3+
composer.phar
4+
composer.lock

CONTRIBUTING.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Contributing to SynologyDDNSCloudflareMultidomain
2+
3+
First off, thanks for taking the time to contribute! 🎉
4+
5+
The following is a set of guidelines for contributing to this project. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
6+
7+
## How Can I Contribute?
8+
9+
### Reporting Bugs
10+
11+
This section guides you through submitting a bug report. Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
12+
13+
* **Use a clear and descriptive title** for the issue to identify the problem.
14+
* **Describe the exact steps which reproduce the problem** in as many details as possible.
15+
* **Provide specific examples** to demonstrate the steps.
16+
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
17+
* **Explain which behavior you expected to see instead and why.**
18+
* **Include logs** if possible (sanitize them to remove your API keys and domains).
19+
20+
### Suggesting Enhancements
21+
22+
This section guides you through submitting an enhancement suggestion, including completely new features and minor improvements to existing functionality.
23+
24+
* **Use a clear and descriptive title** for the issue to identify the suggestion.
25+
* **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
26+
* **Explain why this enhancement would be useful** to most users.
27+
28+
### Pull Requests
29+
30+
* Ensure your code adheres to the existing style.
31+
* Update the documentation if relevant.
32+
* **Run the tests** locally before submitting (`./run_tests.sh`).
33+
* Ensure the GitHub Actions CI passes.
34+
35+
## Styleguides
36+
37+
### PHP
38+
39+
* We follow standard PHP coding conventions (PSR-12 where applicable).
40+
* Keep the code simple and readable.
41+
42+
## License
43+
44+
By contributing, you agree that your contributions will be licensed under its MIT License.

README.md

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@
99

1010
## Table of contents
1111

12-
* 🆕 [What is new](#what-is-new)
13-
* [What this script does](#what-this-script-does)
12+
* [Features](#features)
1413
* [Before you begin](#before-you-begin)
15-
* 🆕 [How to install](#how-to-install)
14+
* [How to install](#how-to-install)
1615
* [Troubleshooting and known issues](#troubleshooting-and-known-issues)
1716
+ [CloudFlare API free domains limitation](#cloudflare-api-free-domains-limitation)
1817
+ [Connection test failed or error returned](#connection-test-failed-or-error-returned)
@@ -21,21 +20,14 @@
2120
* [Debug script](#debug)
2221
* [Support this project](#support-this-project)
2322

24-
## What is new
23+
## Features
2524

26-
- 🆕 New hostname input format: `subdomain1.mydomain.com|subdomain2.mydomain.com` (each domain is separated: `|`) used to be with `---` separator
27-
- 🆕 Hostname input uses a new source of data (account) and support 256 symbols limit (DSM UI limit)
28-
- 🆕 Autodetect IPv6 addresses via [ipify.org](https://www.ipify.org)
29-
- 🆕 Optimised request to Cloudflare API
30-
- 🆕 Installer script
31-
32-
## What this script does
33-
34-
* A PHP script for Synology DSM (and potentially Synology SRM devices) adding support for Cloudflare to Network Centre > Dynamic DNS (DDNS).
35-
* Supports single domains, multidomains, subdomains and regional domains, or any combination thereof (example: dev.my.domain.com.au, domain.com.uk etc)
36-
* 🆕 Easy installation process (added auto install script)
37-
* Based on CloudFlare API v4
38-
* [Supports dual stack IPv4 and IPv6](https://github.com/mrikirill/SynologyDDNSCloudflareMultidomain/pull/13)
25+
* **Synology Integration**: Adds Cloudflare support to Synology DSM and SRM Network Center > Dynamic DNS (DDNS).
26+
* **Comprehensive Domain Support**: Supports single domains, multidomains, subdomains, and regional domains (e.g., `dev.my.domain.com.au`).
27+
* **Dual Stack**: Autodetects and updates both IPv4 and IPv6 addresses.
28+
* **Easy Installation**: Automated installation script via SSH or Task Scheduler.
29+
* **Modern API**: Based on Cloudflare API v4 with optimized requests.
30+
* **Extended Capabilities**: Supports up to 256 characters for hostnames.
3931

4032
## Before you begin
4133

@@ -56,15 +48,15 @@ Before starting the installation process, make sure you have (and know) the foll
5648
**Zone** > **Zone** > **Read**
5749
**Zone** > **DNS** > **Edit**
5850

59-
The affected zone ressouces have to be (at least):
51+
The affected zone resources have to be (at least):
6052

6153
**Include** > **All zones from an account** > `<domain>`
6254

6355
2. *DNS settings:*
6456

6557
Ensure the DNS A record(s) for the domain/zone(s) you wish to update with this script have been created (More information: [Managing DNS records](https://support.cloudflare.com/hc/en-us/articles/360019093151-Managing-DNS-records-in-Cloudflare)).
6658

67-
Case for if IpV6 is available (check via https://api6.ipify.org), you can create an AAAA record for the domain/zone(s) you wish to update with this script.
59+
Case for if IPv6 is available (check via https://api6.ipify.org), you can create an AAAA record for the domain/zone(s) you wish to update with this script.
6860

6961
Your DNS records should appear (or already be setup as follows) in Cloudflare:
7062

@@ -75,7 +67,7 @@ Before starting the installation process, make sure you have (and know) the foll
7567
3. *SSH access to your Synology device:*
7668

7769
If you haven't setup this access, see the following Synology Knowledge Base article:
78-
[How can I sign in to DSM/SRM with root privilege via SSH?[(https://kb.synology.com/en-id/DSM/tutorial/How_to_login_to_DSM_with_root_permission_via_SSH_Telnet)
70+
[How can I sign in to DSM/SRM with root privilege via SSH?](https://kb.synology.com/en-id/DSM/tutorial/How_to_login_to_DSM_with_root_permission_via_SSH_Telnet)
7971

8072
4. *SRM users: Knowledge of vi:*
8173

@@ -87,6 +79,8 @@ For assistance with vi commands, see:
8779

8880
## How to install
8981

82+
### Method 1: via SSH
83+
9084
1. **SSH with root privileges on your supported device:**
9185

9286
a. For DSM Users:
@@ -129,7 +123,7 @@ For a single domain: __mydomain.com__
129123
For multiple domains: __subdomain.mydomain.com|vpn.mydomain.com__
130124
🆕(ensure each domain is separated: `|`)🆕
131125

132-
__Note: there is 256 symbols limit on Hostname input__
126+
__Note: there is a 256-character limit on Hostname input__
133127
* Password: Your created Cloudflare API Key
134128

135129
![image](https://github.com/mrikirill/SynologyDDNSCloudflareMultidomain/blob/master/docs/example3.png)
@@ -139,11 +133,29 @@ For multiple domains: __subdomain.mydomain.com|vpn.mydomain.com__
139133
4. Don't forget to deactivate SSH (step 1) if you don't need it. Leaving it active can be a security risk.
140134
5. You're done! Optional, if you're happy with this script you could buy me ☕ or 🍺 here -> [![Sponsor](https://img.shields.io/badge/sponsor-GitHub%20Sponsors-brightgreen)](https://github.com/sponsors/mrikirill)
141135

136+
### Method 2: via Task Scheduler (No SSH required)
137+
138+
1. Open **Control Panel** > **Task Scheduler**.
139+
2. Click **Create** > **Scheduled Task** > **User-defined script**.
140+
3. In the **General** tab:
141+
* **Task**: Install Cloudflare DDNS
142+
* **User**: `root` **(Important!)**
143+
* Uncheck "Enabled" (we only need to run this once).
144+
4. In the **Task Settings** tab:
145+
* **Run command**:
146+
```bash
147+
wget https://raw.githubusercontent.com/mrikirill/SynologyDDNSCloudflareMultidomain/master/install.sh -O /tmp/install.sh && bash /tmp/install.sh
148+
```
149+
5. Click **OK**.
150+
6. Select the newly created task and click **Run**.
151+
7. Once the task has finished (you can check via Action > View Result), you can delete the task.
152+
8. Proceed to **Step 3** in Method 1 above to configure the DDNS settings in Control Panel.
153+
142154
## Troubleshooting and known issues
143155

144-
### CloudFlare API free domains limitation
156+
### Cloudflare API free domains limitation
145157

146-
CloudFlare API doesn't support domains with a .cf, .ga, .gq, .ml, or .tk TLD (top-level domain)
158+
Cloudflare API doesn't support domains with a .cf, .ga, .gq, .ml, or .tk TLD (top-level domain)
147159
148160
For more details read here: https://github.com/mrikirill/SynologyDDNSCloudflareMultidomain/issues/28 and https://community.cloudflare.com/t/unable-to-update-ddns-using-api-for-some-tlds/167228/61
149161
@@ -204,7 +216,7 @@ You can run this script directly to see output logs
204216
* Run this command:
205217
206218
```
207-
/usr/bin/php -d open_basedir=/usr/syno/bin/ddns -f /usr/syno/bin/ddns/cloudflare.php "domain1.com|vpn.domain2.com" "your-CloudFlare-token" "" "your-ip-address"
219+
/usr/bin/php -d open_basedir=/usr/syno/bin/ddns -f /usr/syno/bin/ddns/cloudflare.php "domain1.com|vpn.domain2.com" "your-Cloudflare-token" "" "your-ip-address"
208220
```
209221
210222
* Check output logs

SECURITY.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Security Policy
2+
3+
## Supported Versions
4+
5+
Only the latest version of the script is currently supported.
6+
7+
| Version | Supported |
8+
| ------- | ------------------ |
9+
| 2.x | :white_check_mark: |
10+
| 1.x | :x: |
11+
12+
## Reporting a Vulnerability
13+
14+
If you discover a security vulnerability within this project, please do not report it as a public issue.
15+
16+
Instead, please report it via:
17+
1. **GitHub Security Advisories**: If enabled for this repository, use the "Report a vulnerability" button.
18+
2. **Email**: Contact the maintainer directly (if email is public on profile).
19+
20+
Please include as much information as possible to help us reproduce and fix the issue.
21+
22+
## API Keys
23+
24+
* **NEVER** post your Cloudflare API keys or tokens in public GitHub issues.
25+
* If you accidentally post a key, revoke it immediately in your Cloudflare dashboard.
26+
* When sharing logs, always redact sensitive information.

cloudflare.php

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@
1616
* 3 - hostname - the script doesn't use it die to input limits
1717
* 4 - IPv4 - Synology provided IPv4
1818
*/
19-
if ($argc !== 5) {
20-
echo SynologyOutput::BAD_PARAMS;
21-
exit();
22-
}
19+
if (realpath(__FILE__) === realpath($_SERVER['SCRIPT_FILENAME'])) {
20+
if ($argc !== 5) {
21+
echo SynologyOutput::BAD_PARAMS;
22+
exit();
23+
}
2324

24-
$cf = new SynologyCloudflareDDNSAgent($argv[2], $argv[1], $argv[4]);
25-
$cf->setDnsRecords();
26-
$cf->updateDnsRecords();
25+
$cf = new SynologyCloudflareDDNSAgent($argv[2], $argv[1], $argv[4]);
26+
$cf->setDnsRecords();
27+
$cf->updateDnsRecords();
28+
}
2729

2830
class SynologyOutput
2931
{
@@ -197,6 +199,7 @@ class DnsRecordEntity
197199
public $zoneId;
198200
public $ttl;
199201
public $proxied;
202+
public $currentIp;
200203

201204
public function __construct($id, $type, $hostname, $ip, $zoneId, $ttl, $proxied)
202205
{
@@ -233,10 +236,10 @@ class SynologyCloudflareDDNSAgent
233236
private $cloudflareAPI;
234237
private $ipify;
235238

236-
function __construct($apiKey, $hostname, $ipv4)
239+
function __construct($apiKey, $hostname, $ipv4, $cloudflareAPI = null, $ipify = null)
237240
{
238-
$this->cloudflareAPI = new CloudflareAPI($apiKey);
239-
$this->ipify = new Ipify();
241+
$this->cloudflareAPI = $cloudflareAPI ?: new CloudflareAPI($apiKey);
242+
$this->ipify = $ipify ?: new Ipify();
240243
$this->ipv4 = $ipv4;
241244

242245
try {
@@ -280,10 +283,11 @@ public function setDnsRecords()
280283
foreach ($this->dnsRecordList as $key => $dnsRecord) {
281284
$json = $this->cloudflareAPI->getDnsRecords($dnsRecord->zoneId, $dnsRecord->type, $dnsRecord->hostname);
282285
if (isset($json['result']['0'])) {
283-
// If the DNS record exists, update its ID, TTL, and proxied status
286+
// If the DNS record exists, update its ID, TTL, proxied status, and current IP
284287
$this->dnsRecordList[$key]->id = $json['result']['0']['id'];
285288
$this->dnsRecordList[$key]->ttl = $json['result']['0']['ttl'];
286289
$this->dnsRecordList[$key]->proxied = $json['result']['0']['proxied'];
290+
$this->dnsRecordList[$key]->currentIp = $json['result']['0']['content'];
287291
} else {
288292
// If the DNS record does not exist, remove it from the list
289293
unset($this->dnsRecordList[$key]);
@@ -308,6 +312,11 @@ function updateDnsRecords()
308312
$this->exitWithSynologyMsg(SynologyOutput::NO_HOSTNAME);
309313
}
310314
foreach ($this->dnsRecordList as $dnsRecord) {
315+
// Skip update if the IP address hasn't changed
316+
if ($dnsRecord->ip === $dnsRecord->currentIp) {
317+
continue;
318+
}
319+
311320
try {
312321
$this->cloudflareAPI->updateDnsRecord($dnsRecord->zoneId, $dnsRecord->id, $dnsRecord->toArray());
313322
} catch (Exception $e) {
@@ -338,7 +347,8 @@ private function matchHostnameWithZone($hostnameList = [])
338347
$zoneId = $zone['id'];
339348
$zoneName = $zone['name'];
340349
foreach ($hostnameList as $hostname) {
341-
if (strpos($hostname, $zoneName) !== false) {
350+
// Check if the hostname ends with the zone name
351+
if ($hostname === $zoneName || substr($hostname, -strlen('.' . $zoneName)) === '.' . $zoneName) {
342352
// Add an IPv4 DNS record for each hostname that matches a zone
343353
$this->dnsRecordList[] = new DnsRecordEntity(
344354
'',
@@ -400,7 +410,7 @@ private function extractHostnames($hostnames)
400410
private function isValidHostname($value)
401411
{
402412
$domainPattern = "/^(?!-)(?:\*\.)?(?:(?:[a-zA-Z\d][a-zA-Z\d\-]{0,61})?[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63}$/";
403-
return preg_match($domainPattern, $value);
413+
return preg_match($domainPattern, $value) === 1;
404414
}
405415

406416
/**
@@ -433,7 +443,7 @@ private function isCFTokenValid()
433443
* @param string $msg The message to be output before exiting.
434444
* If no message is specified, an empty string is printed.
435445
*/
436-
private function exitWithSynologyMsg($msg = '')
446+
protected function exitWithSynologyMsg($msg = '')
437447
{
438448
echo $msg;
439449
exit();

0 commit comments

Comments
 (0)