Compare commits

..

1 Commits

Author SHA1 Message Date
ccf5901389 Some more documentation in the blog post
Signed-off-by: Jacob Kiers <jacob@jacobkiers.net>
2021-08-17 09:34:39 +02:00
8 changed files with 225 additions and 1673 deletions

View File

@@ -6,12 +6,10 @@ author = Jacob Kiers
## What's up? ## What's up?
You might not be able to see it immediately, but the content of this page is served over DNS. You might not be able to see it immediately, but the content of this page is verved over DNS.
This works because of [DNS over HTTPS] for which there is an [API from Cloudflare]. This works because of [DNS over HTTPS] for which there is an [API from Cloudflare].
Comments at [Hacker News, apparently][HN].
## How it works ## How it works
That API is used to load the contents of this page, essentially like this: That API is used to load the contents of this page, essentially like this:
@@ -22,7 +20,7 @@ fetch("https://cloudflare-dns.com/dns-query?ct=application/dns-json&type=TXT&nam
The content itself is served over DNS, using CoreDNS, with these contents: The content itself is served over DNS, using CoreDNS, with these contents:
```hcl ```js
hod.experiments.jacobkiers.net.:53 { hod.experiments.jacobkiers.net.:53 {
log log
auto hod.experiments.jacobkiers.net. { auto hod.experiments.jacobkiers.net. {
@@ -34,7 +32,7 @@ hod.experiments.jacobkiers.net.:53 {
This feeds into a zone file, which looks like this: This feeds into a zone file, which looks like this:
```dns ```bind
$TTL 5m ; Default TTL $TTL 5m ; Default TTL
@ IN SOA experiments.jacobkiers.net. postmaster.jacobkiers.net. ( @ IN SOA experiments.jacobkiers.net. postmaster.jacobkiers.net. (
2021081612 ; serial 2021081612 ; serial
@@ -64,20 +62,6 @@ These records are base64 encoded content, so when concatenated and decoded, they
Please see the [source code] for the details. Please see the [source code] for the details.
## FAQ
### Why, though?
In short: just because I could. It was one of those ideas I was wondering idly about, and I decided to just try it.
### Has it any practical use?
It is not intended to have any. Since DNS records are fairly small, serving images or something would quickly start
consuming 100s of requests per second. I wouldn't want to do that to Cloudflare.
It would be an interesting experiment to see how feasible that is.
[source code]: https://github.com/jacobkiers/html-over-dns "Yes, the title is a pun..." [source code]: https://github.com/jacobkiers/html-over-dns "Yes, the title is a pun..."
[DNS over HTTPS]: https://en.wikipedia.org/wiki/DNS_over_HTTPS [DNS over HTTPS]: https://en.wikipedia.org/wiki/DNS_over_HTTPS
[API from Cloudflare]: https://developers.cloudflare.com/1.1.1.1/dns-over-https/json-format [API from Cloudflare]: https://developers.cloudflare.com/1.1.1.1/dns-over-https/json-format
[HN]: https://news.ycombinator.com/item?id=28218406

View File

@@ -1,21 +0,0 @@
class Verifier {
/***
* @param content Content
*/
async verify(content) {
if (!window.isSecureContext) return;
if (typeof TextEncoder === "undefined") return;
if (typeof crypto === "undefined") return;
const encoder = new TextEncoder();
const digestBuffer = await crypto.subtle.digest(content.index.hashAlgorithm, encoder.encode(content.content));
const hashArray = Array.from(new Uint8Array(digestBuffer)); // convert buffer to byte array
const digest = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
if (content.index.hash === digest) {
document.getElementById("verification").innerHTML =
"The hash of the content is verified to correspond with the hash" +
" in the metadata. You are now reading exactly what was intended.";
}
}
}

140
index.html Normal file
View File

@@ -0,0 +1,140 @@
<!DOCTYPE html charset="UTF-8">
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.2.0/build/styles/default.min.css">
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.2.0/build/highlight.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>
class Content {
content = "";
type = "";
metaData = {}
constructor(content, type, metaData = {})
{
this.content = content;
this.type = type;
this.metaData = metaData;
}
}
class Index
{
mimeType = "";
chunks = 0;
hash = "";
metaData = {};
constructor(mimeType, chunks, hash, metaData = {})
{
this.mimeType = mimeType;
this.chunks = chunks;
this.hash = hash;
this.metaData = metaData
}
}
const dohServer = "https://cloudflare-dns.com/dns-query?ct=application/dns-json&type=TXT&name=";
const baseDomain = "hod.experiments.jacobkiers.net";
async function readUrl(domain) {
var index = await fetchIndex(`${dohServer}${domain}.${baseDomain}`);
var chunk_promises = [];
for(i = 0; i < index.chunks; i++)
{
chunk_promises[i] = fetchChunk(i, index.hash);
}
var chunks = await Promise.all(chunk_promises);
var content = chunks.reduce((built, current) => built += current);
return handleContent(new Content(atob(content), index.mimeType, index.metaData));
}
async function fetchChunk(id, hash)
{
var domain = `${id}.${hash}.${baseDomain}`;
const json = await fetch(`${dohServer}${domain}`)
.then(response => response.json());
const data = json.Answer[0].data.slice(1, -1);
return data;
}
async function fetchIndex(domain)
{
const response = await fetch(`${dohServer}.${domain}`);
const json = await response.json();
const index = json.Answer[0].data.slice(1, -1);
let ret = {};
let items = index.split(';');
items.forEach(item => {
let md = item.split('=');
let key = md[0];
let value = md[1];
ret[key] = value;
});
const metadata = JSON.parse(atob(ret["m"]));
return new Index(ret["t"], ret["c"], ret["h"], metadata);
}
function handleContent(content)
{
if (!content instanceof Content) {
console.log("Not valid content in handleContent.")
return;
}
switch(content.type)
{
case "text/javascript":
return handleJavascript(content);
case "text/markdown":
return handleMarkdown(content);
default:
console.log(`handleContent() does not know how to parse ${content.type}`);
break;
}
}
function handleJavascript(content)
{
console.log("Got some javascript!", content.content);
}
async function handleMarkdown(content)
{
console.log("Got me some markdown!");
marked.setOptions({
highlight: function(code, lang) {
return hljs.highlight(lang, code).value;
},
// langPrefix: ''
});
if (content.metaData.title != undefined) document.title = content.metaData.title;
document.getElementById("post").innerHTML = marked(content.content);
let title = document.createElement("h1");
title.innerHTML = content.metaData.title;
document.getElementById("post").prepend(title)
}
</script>
</head>
<body>
<script>
readUrl("posts-2021-08-17-serving-blog-content-over-dns-md");
</script>
<div id="post"></div>
</body>
</html>

View File

@@ -1,7 +0,0 @@
/*!
Theme: Equilibrium Light
Author: Carlo Abelli
License: ~ MIT (or more permissive) [via base16-schemes-source]
Maintainer: @highlightjs/core-team
Version: 2021.05.0
*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#43474e;background:#f5f0e7}.hljs ::selection{color:#d8d4cb}.hljs-comment{color:#73777f}.hljs-tag{color:#5a5f66}.hljs-operator,.hljs-punctuation,.hljs-subst{color:#43474e}.hljs-operator{opacity:.7}.hljs-bullet,.hljs-deletion,.hljs-name,.hljs-selector-tag,.hljs-template-variable,.hljs-variable{color:#d02023}.hljs-attr,.hljs-link,.hljs-literal,.hljs-number,.hljs-symbol,.hljs-variable.constant_{color:#bf3e05}.hljs-class .hljs-title,.hljs-title,.hljs-title.class_{color:#9d6f00}.hljs-strong{font-weight:700;color:#9d6f00}.hljs-addition,.hljs-code,.hljs-string,.hljs-title.class_.inherited__{color:#637200}.hljs-built_in,.hljs-doctag,.hljs-keyword.hljs-atrule,.hljs-quote,.hljs-regexp{color:#007a72}.hljs-attribute,.hljs-function .hljs-title,.hljs-section,.hljs-title.function_,.ruby .hljs-property{color:#0073b5}.diff .hljs-meta,.hljs-keyword,.hljs-template-tag,.hljs-type{color:#4e66b6}.hljs-emphasis{color:#4e66b6;font-style:italic}.hljs-meta,.hljs-meta .hljs-keyword,.hljs-meta .hljs-string{color:#c42775}.hljs-meta .hljs-keyword,.hljs-meta-keyword{font-weight:700}

1131
public/highlight.min.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,178 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Blog over DNS</title>
<link rel="stylesheet" href="equilibrium-light.min.css">
<script src="highlight.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>
class Content {
content = "";
type = "";
metaData = {};
index;
/***
* @param content string
* @param type string
* @param index Index
*/
constructor(content, type, index)
{
this.content = content;
this.type = type;
this.index = index;
this.metaData = index.metaData;
}
}
class Index
{
mimeType = "";
chunks = 0;
hashAlgorithm = "";
hash = "";
metaData = {};
constructor(mimeType, chunks, hash, hashAlgorithm, metaData = {})
{
this.mimeType = mimeType;
this.chunks = chunks;
this.hashAlgorithm = hashAlgorithm;
this.hash = hash;
this.metaData = metaData
}
}
const dohServer = "https://cloudflare-dns.com/dns-query?ct=application/dns-json&type=TXT&name=";
const baseDomain = "hod.experiments.jacobkiers.net";
async function readUrl(domain) {
var index = await fetchIndex(`${domain}.${baseDomain}`);
var chunk_promises = [];
for(i = 0; i < index.chunks; i++)
{
chunk_promises[i] = fetchChunk(i, index.hash);
}
const chunks = await Promise.all(chunk_promises);
const base64 = chunks.reduce((built, current) => built += current);
const content = atob(base64);
return handleContent(new Content(content, index.mimeType, index));
}
async function fetchChunk(id, hash)
{
const domain = `${id}.${hash}.${baseDomain}`;
return await fetchData(domain);
}
async function fetchData(domain)
{
const json = await fetch(`${dohServer}${domain}`, {
method: "GET",
headers: {
"Accept": "application/dns-json"
}
})
.then(response => response.json());
const raw_data = json.Answer[0].data;
const data = raw_data.replaceAll(/[\s\"]/g, '');
return data;
}
async function fetchIndex(domain)
{
const index = await fetchData(domain);
let ret = {};
let items = index.split(';');
items.forEach(item => {
let md = item.split('=');
let key = md[0];
let value = md[1];
ret[key] = value;
});
const metadata = JSON.parse(atob(ret["m"]));
return new Index(ret["t"], ret["c"], ret["h"], ret["ha"], metadata);
}
function handleContent(content)
{
if (!content instanceof Content) {
console.log("Not valid content in handleContent.")
return;
}
switch(content.type)
{
case "text/javascript":
return handleJavascript(content);
case "text/markdown":
return handleMarkdown(content);
default:
console.log(`handleContent() does not know how to parse ${content.type}`);
break;
}
}
function handleJavascript(content)
{
console.log("Got some javascript!");
const scripts = document.getElementById("scripts");
const new_script = document.createElement("script");
new_script.text = content.content;
scripts.appendChild(new_script);
}
async function handleMarkdown(content)
{
console.log("Got me some markdown!");
marked.setOptions({
highlight: function(code, lang) {
const avialable_languages = hljs.listLanguages();
const has_language = hljs.getLanguage(lang);
if (typeof has_language === "undefined") return code;
const result = hljs.highlight(code, { language: lang, ignoreIllegals: true});
return result.value;
},
});
if (typeof content.metaData.title !== "undefined") document.title = content.metaData.title;
document.getElementById("post").innerHTML = marked.parse(content.content);
let title = document.createElement("h1");
title.innerHTML = content.metaData.title;
document.getElementById("post").prepend(title)
document.getElementById("nojs").remove();
if (typeof Verifier !== "undefined") {
await (new Verifier()).verify(content);
}
}
</script>
</head>
<body>
<div id="post"></div>
<div id="verification"></div>
<div id="nojs">
<h1>HTML over DNS</h1>
<p>The content of this page is fetched using DNS over HTTP. Since that requires Javascript, please enable that to see the content.</p>
</div>
<div id="scripts">
<script>
readUrl("scripts-verifier-js").then(
() => readUrl("posts-2021-08-17-serving-blog-content-over-dns-md")
);
</script>
</div>
</body>
</html>

View File

@@ -1,325 +1,116 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
if (!defined('EOL')) {
define('EOL', "\n");
}
class ContentFile class Content
{ {
private string $filePath; public string $data;
private string $data; public string $mimeType;
private string $fileName; public string $fileName;
public string $hash;
private array $metadata = []; private array $metadata = [];
#const RECORD_TTL = 60 * 60 * 24 * 30; // 30 days. const TTL = 60;
const RECORD_TTL = 60; // Keep it short for now, so it is easier to experiment.
const METADATA_TTL = 60; // Keep it short for now, so it is easier to update the content.
const RECORD_LENGTH = 1536;
public function __construct(string $file) public function __construct(string $file)
{ {
$this->filePath = $file;
$this->fileName = basename(dirname($file)).'/'.basename($file); $this->fileName = basename(dirname($file)).'/'.basename($file);
$this->data = file_get_contents($file); $this->data = file_get_contents($file);
$this->hash = md5($this->data);
$this->parseMetadata(); $this->parseMetadata();
} }
/** public function toRecords(): string
* @return Record[]
*/
private function toRecords(): array
{ {
$chunks = $this->calculateChunks(); $chunks = str_split(base64_encode($this->data), 250);
$hash = $this->calculateHash();
$records = [$this->buildMetadataRecord(count($chunks), $hash)]; $records = [];
foreach ($chunks as $id => $chunk) { $records[] = "; {$this->getDnsName()}";
$records[] = $this->getDnsName().$this->buildMiddle().'"'.$this->buildIndexString(count($chunks)).'"';
foreach($chunks as $id => $chunk)
{
$records[] = $this->buildRecord($id, $chunk); $records[] = $this->buildRecord($id, $chunk);
} }
return $records;
}
public function __toString(): string
{
$result = ""; $result = "";
foreach ($records as $record)
foreach ($this->toRecords() as $record) { {
$result .= $record.EOL; $result .= $record . PHP_EOL;
} }
return $result; return $result;
} }
public function getDnsName(): string private function getDnsName(): string
{ {
return str_replace(['/', '\\', '.'], '-', $this->fileName); return str_replace(['/', '\\', '.'], '-', $this->fileName);
} }
private function buildMetadataRecord(int $chunkCount, string $hash): Record private function buildIndexString(int $chunkCount): string
{
return (new Record())
->setDnsName($this->getDnsName())
->setTtl(self::METADATA_TTL)
->setContent($this->buildIndexString($chunkCount, $hash));
}
private function buildIndexString(int $chunkCount, $hash): string
{ {
$metadata = base64_encode(json_encode($this->metadata)); $metadata = base64_encode(json_encode($this->metadata));
return "t={$this->mimeType()};c={$chunkCount};h={$this->hash};m={$metadata}";
return '"'."t={$this->mimeType()};c={$chunkCount};ha=sha-1;h={$hash};m={$metadata}".'"';
} }
private function buildRecord(int $id, string $data): Record private function buildMiddle(): string
{ {
$name = "$id.{$this->calculateHash()}"; return "\t".self::TTL."\tIN\tTXT\t";
return (new Record())
->setContent($data)
->setDnsName($name)
->setTtl(self::RECORD_TTL);
} }
private function mimeType(): string private function buildRecord(int $id, string $data): string
{
$name = "$id.{$this->hash}";
return "$name".$this->buildMiddle().'"'.$data.'"';
}
private function mimeType()
{ {
$extension = pathinfo($this->fileName, PATHINFO_EXTENSION); $extension = pathinfo($this->fileName, PATHINFO_EXTENSION);
switch ($extension) { switch($extension)
{
case "js": case "js":
return "text/javascript"; return "text/javascript";
case "md": case "md":
return "text/markdown"; return "text/markdown";
default: default:
return mime_content_type($this->filePath); return mime_content_type($this->file);
} }
} }
private function parseMetadata(): void private function parseMetadata()
{ {
if ($this->mimeType() != "text/markdown") { if ($this->mimeType() != "text/markdown") return [];
return;
$frontmatter_chars = "+++";
$end_of_frontmatter = strpos($this->data, $frontmatter_chars, 2);
if (false === $end_of_frontmatter) {
var_dump($end_of_frontmatter);
return [];
} }
$front_matter_chars = "+++"; $frontmatter = substr(
$front_matter_end = strpos($this->data, $front_matter_chars, 2);
if (false === $front_matter_end) {
return;
}
$front_matter = substr(
$this->data, $this->data,
strlen($front_matter_chars) + 1, strlen($frontmatter_chars) + 1,
$front_matter_end - strlen($front_matter_chars) - 2 $end_of_frontmatter - strlen($frontmatter_chars) - 2
); );
$this->data = trim(substr($this->data, $front_matter_end + strlen($front_matter_chars))).EOL; $this->data = trim(substr($this->data, $end_of_frontmatter + strlen($frontmatter_chars))).PHP_EOL;
$lines = explode(EOL, $front_matter); $lines = explode(PHP_EOL, $frontmatter);
foreach ($lines as $line) { foreach($lines as $line) {
$eq_pos = strpos($line, "="); $eq_pos = strpos($line, "=");
$key = trim(substr($line, 0, $eq_pos - 1)); $key = trim(substr($line, 0, $eq_pos - 1));
$value = trim(substr($line, $eq_pos + 1)); $value = trim(substr($line, $eq_pos + 1));
$this->metadata[$key] = $value; $this->metadata[$key] = $value;
} }
} }
private function calculateHash(): string
{
return sha1($this->data);
}
private function calculateChunks(): array
{
return str_split(base64_encode($this->data), self::RECORD_LENGTH);
}
} }
class Record function update_serial_line(string $line)
{
private string $dnsName;
private int $ttl;
private string $content;
public function setDnsName(string $dnsName): self
{
$this->dnsName = $dnsName;
return $this;
}
public function setTtl(int $ttl): self
{
$this->ttl = $ttl;
return $this;
}
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
public function getRecord(): string
{
return $this->dnsName
."\t".$this->ttl
."\tIN"
."\tTXT\t"
.$this->content;
}
public function __toString(): string
{
return $this->getRecord();
}
}
class StartOfAuthority
{
public function __construct(
private string $origin,
private string $serial,
private string $masterDnsServer,
private string $domainContact,
private int $slaveRefreshInterval,
private int $slaveRetryInterval,
private int $slaveCopyExpireTime,
private int $nxDomainCacheTime
) {
}
public function doNotUserMasterDnsServer(): self
{
$servers = $this->dnsServers;
$this->dnsServers = array_filter(
$servers,
function ($server) {
return $server !== $this->masterDnsServer;
}
);
return $this;
}
public function increaseSerial(): self
{
$matches = [];
preg_match('#(\d{8})(\d{2})#', $this->serial, $matches);
list (, $day, $count) = $matches;
$today = date("Ymd");
if ($today === $day) {
$count++;
} else {
$count = 0;
}
$this->serial = sprintf('%1$s%2$02d', $today, $count);;
return $this;
}
public function __toString(): string
{
return <<<EOF
\$ORIGIN $this->origin ; The zone of this zone file
@ IN SOA $this->masterDnsServer $this->domainContact (
$this->serial ; serial
$this->slaveRefreshInterval ; slave refresh interval
$this->slaveRetryInterval ; slave retry interval
$this->slaveCopyExpireTime ; slave copy expire time
$this->nxDomainCacheTime ; NXDOMAIN cache time
)
;
; domain name servers
;
@ IN NS $this->masterDnsServer
EOF;
}
public static function fromString(string $zonefile): self
{
$matches = [];
$found = 1 === preg_match(
'#\$ORIGIN\s+(?P<origin>[a-z.]+)[^@]+@\s+IN\s+SOA\s+(?P<dns>[a-z.]+)\s+(?P<contact>[a-z.]+)\s+\(\D+\s+(?P<serial>\d+)\D+(?P<srefresh>\d+)\D+(?P<sretry>\d+)\D+(?P<sexpire>\d+)\D+(?P<nxcache>\d+)[^\)]+\)#im',
$zonefile,
$matches
);
if (!$found) {
throw new \Exception("Could not find the SOA record.");
}
return new self(
$matches['origin'],
$matches['serial'],
$matches['dns'],
$matches['contact'],
(int)$matches['srefresh'],
(int)$matches['sretry'],
(int)$matches['sexpire'],
(int)$matches['nxcache']
);
}
}
class ZoneFile
{
private StartOfAuthority $soa;
/** @var ContentFile[] */
private array $files = [];
private int $defaultTTL = 300;
public function __construct(
private string $file
) {
$this->soa = StartOfAuthority::fromString(file_get_contents($this->file));
}
public function addFile(ContentFile ...$files): self
{
foreach ($files as $file) {
$this->files[] = $file;
}
return $this;
}
public function setDefaultTTL(int $defaultTTL): self
{
$this->defaultTTL = $defaultTTL;
return $this;
}
public function __toString(): string
{
$this->soa->increaseSerial();
$zone_file = "\$TTL\t{$this->defaultTTL}\t; Default TTL".EOL.EOL;
$zone_file .= $this->soa->__toString().EOL.EOL.EOL;
$zone_file .= ";;; START BLOG RECORDS";
foreach ($this->files as $file) {
$zone_file .= EOL.EOL.EOL;
$zone_file .= '; '.$file->getDnsName().EOL;
$zone_file .= $file;
}
$zone_file .= EOL;
return $zone_file;
}
}
function update_serial_line(string $line): string
{ {
$matches = []; $matches = [];
preg_match("#(\s+)(\d{10})(.*)#", $line, $matches); preg_match("#(\s+)(\d{10})(.*)#", $line, $matches);
@@ -327,7 +118,7 @@ function update_serial_line(string $line): string
$today = date("Ymd"); $today = date("Ymd");
$day = substr($serial, 0, 8); $day = substr($serial, 0, 8);
$nr = (int)substr($serial, -2); $nr = (int) substr($serial, -2);
if ($day === $today) { if ($day === $today) {
$nr++; $nr++;
@@ -341,68 +132,41 @@ function update_serial_line(string $line): string
return $start.$new_serial.$last; return $start.$new_serial.$last;
} }
$zone_file_path = "zones/db.hod.experiments.jacobkiers.net"; $zone_file = "zones/db.hod.experiments.jacobkiers.net";
if (!file_exists($zone_file_path)) { if (!file_exists($zone_file)) {
fwrite(STDERR, "The zone file {$zone_file_path} does not exist!"); fwrite(STDERR, "The zone file {$zone_file} does not exist!");
exit(1); exit(1);
} }
/*
$lines = file($zone_file); $lines = file($zone_file);
$index = 0; foreach($lines as $index => &$line)
foreach ($lines as $index => &$line) { {
if (str_contains($line, "; serial")) { if (str_contains($line, "; serial")) {
$matches = []; $matches = [];
preg_match("#(\s+)(\d{10})(.*)#", $line, $matches); preg_match("#(\s+)(\d{10})(.*)#", $line, $matches);
list($_, $start, $serial, $last) = $matches; list($_, $start, $serial, $last) = $matches;
$line = update_serial_line($line).EOL; $line = update_serial_line($line).PHP_EOL;
}
if (str_starts_with($line, ";; START BLOG RECORDS")) {
break;
} }
if (str_starts_with($line, ";; START BLOG RECORDS")) break;
} }
$zone_file_contents = implode("", array_slice($lines, 0, $index+1));
$zone_file_contents = implode("", array_slice($lines, 0, $index + 1));
echo $zone_file_contents; echo $zone_file_contents;
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__."/content")); $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__."/content"));
foreach ($it as $file) { foreach ($it as $file)
if ($file->isDir()) { {
continue; if ($file->isDir()) continue;
} if (str_contains($file->getPathname(), "ignore")) continue;
if (str_contains($file->getPathname(), "ignore")) {
continue;
}
$content = new ContentFile($file->getPathname()); $bootstrap = new Content($file->getPathname());
$zone_file_contents .= EOL.$content->__toString().EOL; $zone_file_contents .= PHP_EOL.$bootstrap->toRecords().PHP_EOL;
} }
$zone_file_contents .= EOL; $zone_file_contents .= PHP_EOL;
echo $zone_file_contents; #echo $zone_file_contents;
#file_put_contents($zone_file, $zone_file_contents); file_put_contents($zone_file, $zone_file_contents);
*/
$zone_file = new ZoneFile($zone_file_path);
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__."/content"));
foreach ($it as $file) {
if ($file->isDir()) {
continue;
}
if (str_contains($file->getPathname(), "ignore")) {
continue;
}
$content = new ContentFile($file->getPathname());
$zone_file->addFile($content);
}
$zone_file_contents = $zone_file->__toString();
echo $zone_file_contents;
file_put_contents($zone_file_path, $zone_file_contents);

View File

@@ -1,18 +1,19 @@
$TTL 300 ; Default TTL ;
$TTL 5m ; Default TTL
@ IN SOA home.kie.rs. postmaster.kie.rs. (
2021081611 ; serial
1h ; slave refresh interval
15m ; slave retry interval
1w ; slave copy expire time
1h ; NXDOMAIN cache time
)
$ORIGIN hod.experiments.jacobkiers.net. ; The zone of this zone file $ORIGIN hod.experiments.jacobkiers.net.
@ IN SOA experiments.jacobkiers.net. postmaster.jacobkiers.net. (
2021082109 ; serial
3600 ; slave refresh interval
900 ; slave retry interval
604800 ; slave copy expire time
3600 ; NXDOMAIN cache time
)
; ;
; domain name servers ; domain name servers
; ;
@ IN NS experiments.jacobkiers.net. @ IN NS home.kie.rs.
;; START BLOG RECORDS ;; START BLOG RECORDS