Compare commits

...

2 Commits

Author SHA1 Message Date
Jacob Kiers 13d8b7f87e Update zone file generation
Signed-off-by: Jacob Kiers <jacob@jacobkiers.net>
2021-08-21 13:31:31 +02:00
Jacob Kiers 5dd7a230fb Add more robust styling
Signed-off-by: Jacob Kiers <jacob@jacobkiers.net>
2021-08-21 12:11:24 +02:00
6 changed files with 1466 additions and 90 deletions

View File

@ -22,7 +22,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:
```js
```hcl
hod.experiments.jacobkiers.net.:53 {
log
auto hod.experiments.jacobkiers.net. {
@ -34,7 +34,7 @@ hod.experiments.jacobkiers.net.:53 {
This feeds into a zone file, which looks like this:
```js
```dns
$TTL 5m ; Default TTL
@ IN SOA experiments.jacobkiers.net. postmaster.jacobkiers.net. (
2021081612 ; serial

7
public/equilibrium-light.min.css vendored Normal file
View File

@ -0,0 +1,7 @@
/*!
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 Normal file

File diff suppressed because one or more lines are too long

View File

@ -3,8 +3,8 @@
<head>
<meta charset="UTF-8">
<title>Blog over DNS</title>
<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>
<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 {
@ -27,7 +27,7 @@ class Content {
}
}
class Index
class Index
{
mimeType = "";
chunks = 0;
@ -82,7 +82,7 @@ async function fetchData(domain)
async function fetchIndex(domain)
{
const index = await fetchData(domain);
let ret = {};
let items = index.split(';');
items.forEach(item => {
@ -108,7 +108,7 @@ function handleContent(content)
{
case "text/javascript":
return handleJavascript(content);
case "text/markdown":
return handleMarkdown(content);
@ -133,12 +133,15 @@ async function handleMarkdown(content)
console.log("Got me some markdown!");
marked.setOptions({
highlight: function(code, lang) {
return hljs.highlight(lang, code).value;
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;
},
// langPrefix: ''
});
if (content.metaData.title != undefined) document.title = content.metaData.title;
if (typeof 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;

View File

@ -1,116 +1,325 @@
<?php declare(strict_types=1);
if (!defined('EOL')) {
define('EOL', "\n");
}
class Content
class ContentFile
{
public string $data;
public string $mimeType;
public string $fileName;
public string $hash;
private string $filePath;
private string $data;
private string $fileName;
private array $metadata = [];
const TTL = 60;
#const RECORD_TTL = 60 * 60 * 24 * 30; // 30 days.
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)
{
$this->filePath = $file;
$this->fileName = basename(dirname($file)).'/'.basename($file);
$this->data = file_get_contents($file);
$this->hash = md5($this->data);
$this->parseMetadata();
}
public function toRecords(): string
/**
* @return Record[]
*/
private function toRecords(): array
{
$chunks = str_split(base64_encode($this->data), 250);
$chunks = $this->calculateChunks();
$hash = $this->calculateHash();
$records = [];
$records[] = "; {$this->getDnsName()}";
$records[] = $this->getDnsName().$this->buildMiddle().'"'.$this->buildIndexString(count($chunks)).'"';
foreach($chunks as $id => $chunk)
{
$records = [$this->buildMetadataRecord(count($chunks), $hash)];
foreach ($chunks as $id => $chunk) {
$records[] = $this->buildRecord($id, $chunk);
}
return $records;
}
public function __toString(): string
{
$result = "";
foreach ($records as $record)
{
$result .= $record . PHP_EOL;
foreach ($this->toRecords() as $record) {
$result .= $record.EOL;
}
return $result;
}
private function getDnsName(): string
public function getDnsName(): string
{
return str_replace(['/', '\\', '.'], '-', $this->fileName);
}
private function buildIndexString(int $chunkCount): string
private function buildMetadataRecord(int $chunkCount, string $hash): Record
{
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));
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 buildMiddle(): string
private function buildRecord(int $id, string $data): Record
{
return "\t".self::TTL."\tIN\tTXT\t";
$name = "$id.{$this->calculateHash()}";
return (new Record())
->setContent($data)
->setDnsName($name)
->setTtl(self::RECORD_TTL);
}
private function buildRecord(int $id, string $data): string
{
$name = "$id.{$this->hash}";
return "$name".$this->buildMiddle().'"'.$data.'"';
}
private function mimeType()
private function mimeType(): string
{
$extension = pathinfo($this->fileName, PATHINFO_EXTENSION);
switch($extension)
{
switch ($extension) {
case "js":
return "text/javascript";
case "md":
return "text/markdown";
default:
return mime_content_type($this->file);
return mime_content_type($this->filePath);
}
}
private function parseMetadata()
private function parseMetadata(): void
{
if ($this->mimeType() != "text/markdown") return [];
$frontmatter_chars = "+++";
$end_of_frontmatter = strpos($this->data, $frontmatter_chars, 2);
if (false === $end_of_frontmatter) {
var_dump($end_of_frontmatter);
return [];
if ($this->mimeType() != "text/markdown") {
return;
}
$frontmatter = substr(
$front_matter_chars = "+++";
$front_matter_end = strpos($this->data, $front_matter_chars, 2);
if (false === $front_matter_end) {
return;
}
$front_matter = substr(
$this->data,
strlen($frontmatter_chars) + 1,
$end_of_frontmatter - strlen($frontmatter_chars) - 2
strlen($front_matter_chars) + 1,
$front_matter_end - strlen($front_matter_chars) - 2
);
$this->data = trim(substr($this->data, $end_of_frontmatter + strlen($frontmatter_chars))).PHP_EOL;
$this->data = trim(substr($this->data, $front_matter_end + strlen($front_matter_chars))).EOL;
$lines = explode(PHP_EOL, $frontmatter);
foreach($lines as $line) {
$lines = explode(EOL, $front_matter);
foreach ($lines as $line) {
$eq_pos = strpos($line, "=");
$key = trim(substr($line, 0, $eq_pos - 1));
$value = trim(substr($line, $eq_pos + 1));
$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);
}
}
function update_serial_line(string $line)
class Record
{
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 = [];
preg_match("#(\s+)(\d{10})(.*)#", $line, $matches);
@ -118,7 +327,7 @@ function update_serial_line(string $line)
$today = date("Ymd");
$day = substr($serial, 0, 8);
$nr = (int) substr($serial, -2);
$nr = (int)substr($serial, -2);
if ($day === $today) {
$nr++;
@ -132,41 +341,68 @@ function update_serial_line(string $line)
return $start.$new_serial.$last;
}
$zone_file = "zones/db.hod.experiments.jacobkiers.net";
if (!file_exists($zone_file)) {
fwrite(STDERR, "The zone file {$zone_file} does not exist!");
$zone_file_path = "zones/db.hod.experiments.jacobkiers.net";
if (!file_exists($zone_file_path)) {
fwrite(STDERR, "The zone file {$zone_file_path} does not exist!");
exit(1);
}
/*
$lines = file($zone_file);
foreach($lines as $index => &$line)
{
$index = 0;
foreach ($lines as $index => &$line) {
if (str_contains($line, "; serial")) {
$matches = [];
preg_match("#(\s+)(\d{10})(.*)#", $line, $matches);
list($_, $start, $serial, $last) = $matches;
$line = update_serial_line($line).PHP_EOL;
$line = update_serial_line($line).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;
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__."/content"));
foreach ($it as $file)
{
if ($file->isDir()) continue;
if (str_contains($file->getPathname(), "ignore")) continue;
foreach ($it as $file) {
if ($file->isDir()) {
continue;
}
if (str_contains($file->getPathname(), "ignore")) {
continue;
}
$bootstrap = new Content($file->getPathname());
$zone_file_contents .= PHP_EOL.$bootstrap->toRecords().PHP_EOL;
$content = new ContentFile($file->getPathname());
$zone_file_contents .= EOL.$content->__toString().EOL;
}
$zone_file_contents .= PHP_EOL;
$zone_file_contents .= 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,14 +1,13 @@
;
$TTL 5m ; Default TTL
@ IN SOA experiments.jacobkiers.net. postmaster.kie.rs. (
2021082004 ; serial
1h ; slave refresh interval
15m ; slave retry interval
1w ; slave copy expire time
1h ; NXDOMAIN cache time
)
$TTL 300 ; Default TTL
$ORIGIN hod.experiments.jacobkiers.net.
$ORIGIN hod.experiments.jacobkiers.net. ; The zone of this zone file
@ 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