docs(duckdns): Update status and sync to Notion
- Added analysis of latest DNS monitor logs (06.01.2026). - Refactored sync_docs_to_notion.py to be generic and support multiple files.
This commit is contained in:
@@ -60,6 +60,23 @@ docker logs dns-monitor
|
|||||||
```
|
```
|
||||||
Häufig verursacht durch DSL-Resyncs oder Paketverlust wegen der Stichleitung.
|
Häufig verursacht durch DSL-Resyncs oder Paketverlust wegen der Stichleitung.
|
||||||
|
|
||||||
|
## Aktueller Status (06.01.2026)
|
||||||
|
|
||||||
|
Basierend auf den Logs des `dns-monitor` (Stand 20:34 Uhr) ergibt sich folgendes Bild:
|
||||||
|
|
||||||
|
### 1. Analyse der Log-Phasen
|
||||||
|
* **Vormittag/Nachmittag:** Sporadische `[ALERT]`-Meldungen. Die IP bei DuckDNS sprang kurzzeitig auf die veraltete Endung `.49` zurück, stabilisierte sich aber meist nach 5-10 Minuten wieder auf `.252`.
|
||||||
|
* **Kritische Phase (19:38 - 19:54 Uhr):** Ein 20-minütiger anhaltender Alert. Cloudflare (`CF`) lieferte hartnäckig die alte IP `.49`, obwohl die öffentliche IP (`Pub`) bereits `.252` war. Dies deutet auf eine verzögerte TTL-Aktualisierung oder einen "Zombie-Updater" im Netzwerk hin, der kurzzeitig erfolgreich war.
|
||||||
|
* **Stabilisierung (seit 19:59 Uhr):** Das System ist seit über 30 Minuten durchgehend im Status `[OK]`. Sowohl Cloudflare als auch die lokale Auflösung zeigen stabil auf die `.252`.
|
||||||
|
|
||||||
|
### 2. Erkenntnisse
|
||||||
|
* **Netzwerk-Instabilität:** Die `[NETWORK_WAIT]` Einträge korrelieren mit den DSL-Sync-Problemen der FritzBox. In diesen Momenten ist keine DNS-Abfrage möglich (Paketverlust).
|
||||||
|
* **Local Cache Lag:** Die Spalte `Loc` (Lokale Auflösung) zeigt oft noch `Unresolved` oder die alte IP, wenn Cloudflare bereits korrekt ist. Dies bestätigt, dass der interne DNS-Cache der Synology/Docker-Umgebung deutlich träger reagiert als externe Server.
|
||||||
|
|
||||||
|
### 3. Empfehlung
|
||||||
|
* **Beobachtung:** Da die Stabilisierung seit 19:59 Uhr anhält, scheint der neue DuckDNS-Token nun die Oberhand gewonnen zu haben.
|
||||||
|
* **Hardware:** Die physische DSL-Beeinträchtigung (Stichleitung 17m) bleibt das primäre Risiko für die `NETWORK_WAIT` Timeouts.
|
||||||
|
|
||||||
## Dateien
|
## Dateien
|
||||||
- `/app/docker-compose.yml`: Definition der Services.
|
- `/app/docker-compose.yml`: Definition der Services.
|
||||||
- `/app/dns-monitor/monitor.sh`: Das Shell-Skript für den Monitor-Container.
|
- `/app/dns-monitor/monitor.sh`: Das Shell-Skript für den Monitor-Container.
|
||||||
@@ -2,10 +2,10 @@ import requests
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
TOKEN_FILE = 'notion_api_key.txt'
|
TOKEN_FILE = 'notion_api_key.txt'
|
||||||
PARENT_PAGE_ID = "2e088f42-8544-8024-8289-deb383da3818"
|
PARENT_PAGE_ID = "2e088f42-8544-8024-8289-deb383da3818"
|
||||||
DOC_FILE = "notion_integration.md"
|
|
||||||
|
|
||||||
def parse_markdown_to_blocks(md_content):
|
def parse_markdown_to_blocks(md_content):
|
||||||
blocks = []
|
blocks = []
|
||||||
@@ -15,25 +15,21 @@ def parse_markdown_to_blocks(md_content):
|
|||||||
code_content = []
|
code_content = []
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
# Don't strip indentation for code blocks, but do for logic checks
|
|
||||||
stripped = line.strip()
|
stripped = line.strip()
|
||||||
|
|
||||||
# Handle Code Blocks
|
|
||||||
if stripped.startswith("```"):
|
if stripped.startswith("```"):
|
||||||
if in_code_block:
|
if in_code_block:
|
||||||
# End of code block
|
|
||||||
blocks.append({
|
blocks.append({
|
||||||
"object": "block",
|
"object": "block",
|
||||||
"type": "code",
|
"type": "code",
|
||||||
"code": {
|
"code": {
|
||||||
"rich_text": [{"type": "text", "text": {"content": "\n".join(code_content)}}],
|
"rich_text": [{"type": "text", "text": {"content": '\n'.join(code_content)}}],
|
||||||
"language": "plain text"
|
"language": "plain text"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
code_content = []
|
code_content = []
|
||||||
in_code_block = False
|
in_code_block = False
|
||||||
else:
|
else:
|
||||||
# Start of code block
|
|
||||||
in_code_block = True
|
in_code_block = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -44,52 +40,38 @@ def parse_markdown_to_blocks(md_content):
|
|||||||
if not stripped:
|
if not stripped:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Headings
|
|
||||||
if line.startswith("# "):
|
if line.startswith("# "):
|
||||||
blocks.append({
|
blocks.append({
|
||||||
"object": "block",
|
"object": "block",
|
||||||
"type": "heading_1",
|
"type": "heading_1",
|
||||||
"heading_1": {
|
"heading_1": {"rich_text": [{"type": "text", "text": {"content": line[2:]}}]}}
|
||||||
"rich_text": [{"type": "text", "text": {"content": line[2:]}}]
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
elif line.startswith("## "):
|
elif line.startswith("## "):
|
||||||
blocks.append({
|
blocks.append({
|
||||||
"object": "block",
|
"object": "block",
|
||||||
"type": "heading_2",
|
"type": "heading_2",
|
||||||
"heading_2": {
|
"heading_2": {"rich_text": [{"type": "text", "text": {"content": line[3:]}}]}}
|
||||||
"rich_text": [{"type": "text", "text": {"content": line[3:]}}]
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
elif line.startswith("### "):
|
elif line.startswith("### "):
|
||||||
blocks.append({
|
blocks.append({
|
||||||
"object": "block",
|
"object": "block",
|
||||||
"type": "heading_3",
|
"type": "heading_3",
|
||||||
"heading_3": {
|
"heading_3": {"rich_text": [{"type": "text", "text": {"content": line[4:]}}]}}
|
||||||
"rich_text": [{"type": "text", "text": {"content": line[4:]}}]
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
# List Items
|
|
||||||
elif stripped.startswith("* ") or stripped.startswith("- "):
|
elif stripped.startswith("* ") or stripped.startswith("- "):
|
||||||
content = stripped[2:]
|
content = stripped[2:]
|
||||||
blocks.append({
|
blocks.append({
|
||||||
"object": "block",
|
"object": "block",
|
||||||
"type": "bulleted_list_item",
|
"type": "bulleted_list_item",
|
||||||
"bulleted_list_item": {
|
"bulleted_list_item": {"rich_text": [{"type": "text", "text": {"content": content}}]}}
|
||||||
"rich_text": [{"type": "text", "text": {"content": content}}]
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
# Numbered List (Simple detection)
|
|
||||||
elif re.match(r"^\d+\.", stripped):
|
elif re.match(r"^\d+\.", stripped):
|
||||||
content = re.sub(r"^\d+\.\s*", "", stripped)
|
content = re.sub(r"^\d+\.\s*", "", stripped)
|
||||||
blocks.append({
|
blocks.append({
|
||||||
"object": "block",
|
"object": "block",
|
||||||
"type": "numbered_list_item",
|
"type": "numbered_list_item",
|
||||||
"numbered_list_item": {
|
"numbered_list_item": {"rich_text": [{"type": "text", "text": {"content": content}}]}}
|
||||||
"rich_text": [{"type": "text", "text": {"content": content}}]
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
# Tables (Very basic handling: convert to code block for preservation)
|
|
||||||
elif stripped.startswith("|"):
|
elif stripped.startswith("|"):
|
||||||
blocks.append({
|
blocks.append({
|
||||||
"object": "block",
|
"object": "block",
|
||||||
@@ -99,27 +81,28 @@ def parse_markdown_to_blocks(md_content):
|
|||||||
"language": "plain text"
|
"language": "plain text"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
# Paragraphs
|
|
||||||
else:
|
else:
|
||||||
blocks.append({
|
blocks.append({
|
||||||
"object": "block",
|
"object": "block",
|
||||||
"type": "paragraph",
|
"type": "paragraph",
|
||||||
"paragraph": {
|
"paragraph": {"rich_text": [{"type": "text", "text": {"content": line}}]}}
|
||||||
"rich_text": [{"type": "text", "text": {"content": line}}]
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return blocks
|
return blocks
|
||||||
|
|
||||||
def upload_doc(token):
|
def upload_doc(token, file_path):
|
||||||
try:
|
try:
|
||||||
with open(DOC_FILE, 'r') as f:
|
with open(file_path, 'r') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(f"Error: Could not find '{DOC_FILE}'")
|
print(f"Error: Could not find '{file_path}'")
|
||||||
return
|
return
|
||||||
|
|
||||||
print(f"Parsing '{DOC_FILE}'...")
|
title = os.path.basename(file_path)
|
||||||
|
if content.startswith("# "):
|
||||||
|
title = content.split('\n')[0][2:].strip()
|
||||||
|
|
||||||
|
print(f"Parsing '{file_path}'...")
|
||||||
children_blocks = parse_markdown_to_blocks(content)
|
children_blocks = parse_markdown_to_blocks(content)
|
||||||
|
|
||||||
url = "https://api.notion.com/v1/pages"
|
url = "https://api.notion.com/v1/pages"
|
||||||
@@ -132,45 +115,31 @@ def upload_doc(token):
|
|||||||
payload = {
|
payload = {
|
||||||
"parent": { "page_id": PARENT_PAGE_ID },
|
"parent": { "page_id": PARENT_PAGE_ID },
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": [
|
"title": [{"text": {"content": f"📘 {title}"}}]
|
||||||
{
|
|
||||||
"text": {
|
|
||||||
"content": "📘 Notion Integration Dokumentation"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"children": children_blocks[:100] # Safe limit
|
"children": children_blocks[:100]
|
||||||
}
|
}
|
||||||
|
|
||||||
print(f"Uploading to Notion under parent {PARENT_PAGE_ID}...")
|
print(f"Uploading '{title}' to Notion...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(url, headers=headers, json=payload)
|
response = requests.post(url, headers=headers, json=payload)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
data = response.json()
|
data = response.json()
|
||||||
print("\n=== SUCCESS ===")
|
print(f"SUCCESS: {data.get('url')}")
|
||||||
print(f"Documentation uploaded!")
|
|
||||||
print(f"URL: {data.get('url')}")
|
|
||||||
|
|
||||||
except requests.exceptions.HTTPError as e:
|
|
||||||
print(f"\n=== ERROR ===")
|
|
||||||
print(f"HTTP Error: {e}")
|
|
||||||
print(f"Response: {response.text}")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"\n=== ERROR ===")
|
print(f"ERROR: {e}")
|
||||||
print(f"An error occurred: {e}")
|
|
||||||
|
|
||||||
def main():
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: python sync_docs_to_notion.py <filename>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(TOKEN_FILE, 'r') as f:
|
with open(TOKEN_FILE, 'r') as f:
|
||||||
token = f.read().strip()
|
token = f.read().strip()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(f"Error: Could not find '{TOKEN_FILE}'")
|
print(f"Error: Could not find '{TOKEN_FILE}'")
|
||||||
return
|
sys.exit(1)
|
||||||
|
|
||||||
upload_doc(token)
|
upload_doc(token, sys.argv[1])
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
Reference in New Issue
Block a user