Compare commits
14 Commits
b41a97fd0a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d9c5e4c9b1 | |||
| 61c58e632d | |||
| a7fd2af552 | |||
| cb01ee6273 | |||
| 97b6f2d41d | |||
| 1863c264f8 | |||
| 4bbdd7cb3d | |||
| a4af7c430a | |||
| df1d3a83ac | |||
| 94382ef7c7 | |||
| 31274f3825 | |||
| 27d8ad95aa | |||
| 05fb5bfa77 | |||
| 8cc8efd81b |
17
.project
17
.project
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>hacking_spielplatz</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.python.pydev.PyDevBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.python.pydev.pythonNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?eclipse-pydev version="1.0"?><pydev_project>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
|
||||
</pydev_project>
|
||||
@@ -1,3 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//kodi/context.kino.control/main.py=utf-8
|
||||
encoding//kodi/context.kino.control/resources/lib/context.py=utf-8
|
||||
18
GoPro/TransferCreationTime.sh
Executable file
18
GoPro/TransferCreationTime.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
#find -type f -not -iname '*\ \(*' -name \*MP4 -exec stat -c '%y %Y %n' {} \;
|
||||
|
||||
#touch -m -a -t $(date -d @1754466694 +'%Y%m%d%H%M.%S') './GX010042 (1).mp4'
|
||||
|
||||
for F in $(find -type f -not -iname '*\ \(*' -name \*MP4); do
|
||||
FStrip=$(basename -s ".MP4" -- ${F})
|
||||
FEncoded="${FStrip} (1).mp4"
|
||||
if [ -e "${FEncoded}" ]; then
|
||||
MTIME=$(stat -c '%Y' ${F})
|
||||
MDate=$(date -d @${MTIME} +'%Y%m%d%H%M.%S')
|
||||
echo "$F $MTIME $MDate '${FEncoded}' "
|
||||
touch -m -a -t ${MDate} "${FEncoded}"
|
||||
else
|
||||
echo "Not Encoded: ${F}"
|
||||
fi
|
||||
done
|
||||
12
duply-backup-lenny.sh
Executable file → Normal file
12
duply-backup-lenny.sh
Executable file → Normal file
@@ -13,12 +13,12 @@ NOTIFYCMD="su - ${XUSER} DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user
|
||||
|
||||
for I in ${DIR}/lenny*; do
|
||||
BCKP=$(basename $I)
|
||||
${NOTIFYCMD} ${BCKP} "Starting Backup..."
|
||||
#${NOTIFYCMD} ${BCKP} "Starting Backup..."
|
||||
/usr/bin/duply $BCKP ${CMD} 2&>1 >${LOGDIR}/${BCKP}.log;
|
||||
FIN=$?
|
||||
if [ $FIN -eq 0 ]; then
|
||||
${NOTIFYCMD} --icon=sync-synchronizing ${BCKP} "Backup success."
|
||||
else
|
||||
${NOTIFYCMD} -u critical --icon=sync-error ${BCKP} "Done Backup Error: ${FIN}"
|
||||
fi
|
||||
#if [ $FIN -eq 0 ]; then
|
||||
# ${NOTIFYCMD} --icon=sync-synchronizing ${BCKP} "Backup success."
|
||||
#else
|
||||
# ${NOTIFYCMD} -u critical --icon=sync-error ${BCKP} "Done Backup Error: ${FIN}"
|
||||
#fi
|
||||
done
|
||||
|
||||
@@ -3,4 +3,4 @@ PWR_LEINI=2
|
||||
PWR_TRUHE=3
|
||||
LIRC_BEAMER_AN="Beamer KEY_POWER"
|
||||
LIRC_BEAMER_AUS="Beamer KEY_POWER2"
|
||||
MQTT=mqtt.chaos
|
||||
MQTT=mqtt
|
||||
0
kino/kino_an.sh
Executable file → Normal file
0
kino/kino_an.sh
Executable file → Normal file
0
kino/kino_aus.sh
Executable file → Normal file
0
kino/kino_aus.sh
Executable file → Normal file
2
kino/truhe_an.sh
Executable file → Normal file
2
kino/truhe_an.sh
Executable file → Normal file
@@ -5,4 +5,4 @@ MP=$(dirname "$ME")
|
||||
. ${MP}/kino.conf
|
||||
|
||||
#sispmctl -o ${PWR_TRUHE} #Truhe
|
||||
mosquitto_pub -h ${MQTT} -t homie/Truhe-switch/SYSTEM/gpio12/set -m 1
|
||||
mosquitto_pub -h ${MQTT} -t truhe_switch/switch/truhe_switch_power/command -m ON
|
||||
|
||||
2
kino/truhe_aus.sh
Executable file → Normal file
2
kino/truhe_aus.sh
Executable file → Normal file
@@ -6,4 +6,4 @@ MP=$(dirname "$ME")
|
||||
|
||||
#sispmctl -f ${PWR_TRUHE} #Truhe
|
||||
|
||||
mosquitto_pub -h ${MQTT} -t homie/Truhe-switch/SYSTEM/gpio12/set -m 0
|
||||
mosquitto_pub -h ${MQTT} -t truhe_switch/switch/truhe_switch_power/command -m OFF
|
||||
|
||||
0
mqtt/remove_retained_topic.sh
Executable file → Normal file
0
mqtt/remove_retained_topic.sh
Executable file → Normal file
2
nummer5/hosts.conf
Normal file
2
nummer5/hosts.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
hosts="pine01.wks pine02.wks pine03.wks pine04.wks pine05.wks adm01.wks ebin01.wks ebin02.wks"
|
||||
domain="wks"
|
||||
12
nummer5/nummer5-host-powercycle.sh
Executable file
12
nummer5/nummer5-host-powercycle.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
. hosts.conf
|
||||
|
||||
host=$1
|
||||
hostname=$(echo ${host} | sed s/\.${domain}//)
|
||||
MOSQ="mosquitto_pub -h mqtt.wks -t switch_cloud/switch/${hostname}/command -m"
|
||||
echo "${host} turning it off"
|
||||
${MOSQ} OFF
|
||||
sleep 2
|
||||
echo "${host} turning it on"
|
||||
${MOSQ} ON
|
||||
ping -W 1 ${host}
|
||||
13
nummer5/nummer5-hosts-alive.sh
Executable file
13
nummer5/nummer5-hosts-alive.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
. hosts.conf
|
||||
for host in $hosts; do
|
||||
if ping -c 1 -W 1 "$host" >/dev/null; then
|
||||
echo "$host is alive"
|
||||
else
|
||||
echo "$host is pining for the fjords"
|
||||
fi
|
||||
done
|
||||
echo ''
|
||||
nomad server members
|
||||
echo ''
|
||||
nomad node status
|
||||
4
osmc.wks/amp_aux.sh
Executable file
4
osmc.wks/amp_aux.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
irsend -d /var/run/lirc/lircd-lirc0 SEND_ONCE Yamaha_RAV207 AMP/AUX
|
||||
|
||||
3
osmc.wks/amp_power.sh
Executable file
3
osmc.wks/amp_power.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
irsend -d /var/run/lirc/lircd-lirc0 SEND_ONCE RAV207 KEY_POWER
|
||||
4
osmc.wks/amp_tuner.sh
Executable file
4
osmc.wks/amp_tuner.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
irsend -d /var/run/lirc/lircd-lirc0 SEND_ONCE Yamaha_RAV207 AMP/TUNER_INPUT
|
||||
|
||||
4
osmc.wks/amp_vaux.sh
Executable file
4
osmc.wks/amp_vaux.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
irsend -d /var/run/lirc/lircd-lirc0 SEND_ONCE Yamaha_RAV207 AMP/V_AUX
|
||||
|
||||
6
osmc.wks/amp_vol_down.sh
Executable file
6
osmc.wks/amp_vol_down.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
irsend -d /var/run/lirc/lircd-lirc0 SEND_START Yamaha_RAV207 AMP/VOL_DOWN
|
||||
sleep 1
|
||||
irsend -d /var/run/lirc/lircd-lirc0 SEND_STOP Yamaha_RAV207 AMP/VOL_DOWN
|
||||
|
||||
6
osmc.wks/amp_vol_up.sh
Executable file
6
osmc.wks/amp_vol_up.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
irsend -d /var/run/lirc/lircd-lirc0 SEND_START Yamaha_RAV207 AMP/VOL_UP
|
||||
sleep 1
|
||||
irsend -d /var/run/lirc/lircd-lirc0 SEND_STOP Yamaha_RAV207 AMP/VOL_UP
|
||||
|
||||
0
raspbmc-backup-fresh.sh
Executable file → Normal file
0
raspbmc-backup-fresh.sh
Executable file → Normal file
0
raspbmc-backup.sh
Executable file → Normal file
0
raspbmc-backup.sh
Executable file → Normal file
0
raspbmc-restore.sh
Executable file → Normal file
0
raspbmc-restore.sh
Executable file → Normal file
274
redmine2gitea-wiki/redmine_to_gitea_wiki.py
Normal file
274
redmine2gitea-wiki/redmine_to_gitea_wiki.py
Normal file
@@ -0,0 +1,274 @@
|
||||
import os
|
||||
import requests
|
||||
import re
|
||||
from bs4 import BeautifulSoup
|
||||
import subprocess
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# === Configuration ===
|
||||
REDMINE_URL = "https://redmine.maketank.net" # e.g., https://redmine.example.com
|
||||
REDMINE_API_KEY = "62120b7ed6bf84023b2a1d7d5a265cf172f8d607"
|
||||
PROJECT_ID = "bastelstube" # e.g., "my-project"
|
||||
|
||||
#GITEA_WIKI_URL = "https://git.maketank.net/chaos/spielplatz.wiki.git" # Gitea wiki Git URL
|
||||
GITEA_WIKI_URL = "ssh://git@git.maketank.net:2222/chaos/spielplatz.wiki.git"
|
||||
#GITEA_USERNAME = "do"
|
||||
#GITEA_TOKEN = "2d2be00637182c6d5411512f456218ee59fa1f81" # Use a personal access token
|
||||
|
||||
# Optional: Gitea API for creating pages (if you want to use it instead of Git)
|
||||
# GITEA_API_URL = "https://gitea.example.com/api/v1"
|
||||
|
||||
# === Functions ===
|
||||
|
||||
def redmine_get_wiki_pages():
|
||||
"""Get all wiki pages from Redmine project"""
|
||||
# First get the list of pages and their parent information
|
||||
index_url = f"{REDMINE_URL}/projects/{PROJECT_ID}/wiki/index.xml"
|
||||
headers = {"X-Redmine-API-Key": REDMINE_API_KEY}
|
||||
response = requests.get(index_url, headers=headers)
|
||||
response.raise_for_status()
|
||||
|
||||
soup = BeautifulSoup(response.content, "xml")
|
||||
pages = []
|
||||
|
||||
for page in soup.find_all("wiki_page"):
|
||||
title = page.find("title").text
|
||||
parent = page.find('parent')
|
||||
if parent:
|
||||
parent = parent['title']
|
||||
else:
|
||||
parent = None # Root level pages have no parent
|
||||
|
||||
logger.info(f"Found page: {title} (Parent: {parent})")
|
||||
|
||||
# Fetch individual page content
|
||||
page_url = f"{REDMINE_URL}/projects/{PROJECT_ID}/wiki/{title}.xml"
|
||||
try:
|
||||
page_response = requests.get(page_url, headers=headers)
|
||||
page_response.raise_for_status()
|
||||
|
||||
page_soup = BeautifulSoup(page_response.content, "xml")
|
||||
content = page_soup.find("text").text if page_soup.find("text") else ""
|
||||
|
||||
pages.append({"title": title, "parent": parent, "content": content})
|
||||
logger.info(f"Fetched content for: {title}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to fetch content for page '{title}': {e}")
|
||||
# Add empty content if fetch fails
|
||||
pages.append({"title": title, "parent": parent, "content": ""})
|
||||
|
||||
return pages
|
||||
|
||||
def redmine_markup_to_markdown(content, pages):
|
||||
"""Convert Redmine wiki markup to Markdown"""
|
||||
|
||||
# Convert links - handle both [[page]] and [page] syntax
|
||||
def replace_link(match):
|
||||
link = match.group(1)
|
||||
|
||||
if link in pages: # Ensure the linked page exists
|
||||
# Create a relative path based on parent-child hierarchy
|
||||
href = f"{pages[link]['title'].replace(' ', '_')}.md"
|
||||
|
||||
return f"[{link}]({href})"
|
||||
|
||||
else:
|
||||
logger.warning(f"Linked page not found: {link}")
|
||||
# Return original link if linked page not found
|
||||
return match.group()
|
||||
|
||||
# Replace [[page]] with [page](page_url)
|
||||
content = re.sub(r'\[\[(.*?)\]\]', replace_link, content)
|
||||
|
||||
# Convert bold (strong in textile)
|
||||
content = re.sub(r'\*\*(.*?)\*\*', r'**\1**', content)
|
||||
content = re.sub(r'\*(.*?)\*', r'*\1*', content)
|
||||
|
||||
# Convert headings
|
||||
content = re.sub(r'^h1\.(.*?)$', r'# \1', content, flags=re.MULTILINE)
|
||||
content = re.sub(r'^h2\.(.*?)$', r'## \1', content, flags=re.MULTILINE)
|
||||
content = re.sub(r'^h3\.(.*?)$', r'### \1', content, flags=re.MULTILINE)
|
||||
content = re.sub(r'^h4\.(.*?)$', r'#### \1', content, flags=re.MULTILINE)
|
||||
content = re.sub(r'^h5\.(.*?)$', r'##### \1', content, flags=re.MULTILINE)
|
||||
content = re.sub(r'^h6\.(.*?)$', r'###### \1', content, flags=re.MULTILINE)
|
||||
|
||||
# Convert lists
|
||||
content = re.sub(r'^\s*\* (.*?)$', r'- \1', content, flags=re.MULTILINE)
|
||||
content = re.sub(r'^\s*\# (.*?)$', r'1. \1', content, flags=re.MULTILINE)
|
||||
|
||||
# Convert images - handle !image! and 
|
||||
content = re.sub(r'!\[(.*?)\]\((.*?)\)', r'', content)
|
||||
content = re.sub(r'!(.*?)!', r'', content)
|
||||
|
||||
# Convert code blocks
|
||||
content = re.sub(r'<pre>(.*?)</pre>', r'```python\n\1\n```', content, flags=re.DOTALL)
|
||||
|
||||
# Convert inline code
|
||||
content = re.sub(r'\{\{(.*?)\}\}', r'`\1`', content)
|
||||
|
||||
# Remove HTML tags (if any)
|
||||
content = re.sub(r'<[^>]+>', '', content)
|
||||
|
||||
# Handle textile-specific elements like blockquotes
|
||||
content = re.sub(r'^bq\.(.*?)$', r'> \1', content, flags=re.MULTILINE)
|
||||
|
||||
return content
|
||||
|
||||
def download_image(image_url, local_dir):
|
||||
"""Download an image from Redmine and save it locally"""
|
||||
try:
|
||||
headers = {"X-Redmine-API-Key": REDMINE_API_KEY}
|
||||
response = requests.get(f"{REDMINE_URL}{image_url}", headers=headers)
|
||||
response.raise_for_status()
|
||||
|
||||
# Extract filename from URL
|
||||
filename = os.path.basename(image_url)
|
||||
filepath = os.path.join(local_dir, "images", filename)
|
||||
|
||||
# Create images directory if it doesn't exist
|
||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||
|
||||
with open(filepath, 'wb') as f:
|
||||
f.write(response.content)
|
||||
|
||||
logger.info(f"Downloaded image: {filename}")
|
||||
return f"./images/{filename}"
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to download image from {image_url}: {e}")
|
||||
return None
|
||||
|
||||
def clone_or_init_wiki_repo(repo_url, local_dir):
|
||||
"""Clone or initialize the Gitea wiki repo"""
|
||||
if not os.path.exists(local_dir):
|
||||
logger.info(f"Cloning Gitea wiki repository: {repo_url}")
|
||||
subprocess.run(["git", "clone", repo_url, local_dir], check=True)
|
||||
else:
|
||||
logger.info(f"Using existing local wiki repo: {local_dir}")
|
||||
os.chdir(local_dir)
|
||||
subprocess.run(["git", "pull"], check=True)
|
||||
os.chdir('..')
|
||||
return local_dir
|
||||
|
||||
def push_to_gitea_wiki(local_dir, pages):
|
||||
"""Add pages as Markdown files and push to Gitea"""
|
||||
page_map = {page['title']: page for page in pages} # Map titles to their data
|
||||
curr_dir = os.getcwd()
|
||||
logger.info(f"We are in: {curr_dir}")
|
||||
# Remove all existing files in the repo (except .git folder)
|
||||
for item in os.listdir(local_dir):
|
||||
if item != '.git':
|
||||
item_path = os.path.join(local_dir, item)
|
||||
if os.path.isfile(item_path):
|
||||
os.remove(item_path)
|
||||
elif os.path.isdir(item_path):
|
||||
import shutil
|
||||
shutil.rmtree(item_path)
|
||||
|
||||
# Create pages as .md files
|
||||
for page in pages:
|
||||
title = page['title']
|
||||
parent = page.get('parent')
|
||||
|
||||
if title == 'Wiki':
|
||||
filename = "Home.md"
|
||||
else:
|
||||
filename = f"{title.replace(' ', '_')}.md"
|
||||
|
||||
filepath = os.path.join(local_dir, filename)
|
||||
|
||||
if parent and parent in page_map:
|
||||
# If this page has a parent, create relative path from the parent's directory
|
||||
parent_filename = f"{parent.replace(' ', '_')}.md"
|
||||
parent_filepath = os.path.join(local_dir, parent_filename)
|
||||
|
||||
if os.path.exists(parent_filepath):
|
||||
# Parent directory exists, create subdirectory for this page
|
||||
page_subdir = os.path.dirname(filepath)
|
||||
os.makedirs(page_subdir, exist_ok=True)
|
||||
|
||||
else:
|
||||
# This is a root level page, no need to create subdirectories
|
||||
pass
|
||||
|
||||
# Convert content to Markdown and handle links
|
||||
markdown_content = redmine_markup_to_markdown(page['content'], page_map)
|
||||
|
||||
# Find all images in the content to log them
|
||||
image_pattern = r'!(.*?)!'
|
||||
found_images = re.findall(image_pattern, markdown_content)
|
||||
if found_images:
|
||||
logger.info(f"Found images on page '{title}': {found_images}")
|
||||
|
||||
# Process all !image! patterns in the content
|
||||
original_content = markdown_content
|
||||
|
||||
# Find all image tags in the format !image_name!
|
||||
image_tags = re.findall(r'!(.*?)!', original_content)
|
||||
|
||||
# For each image tag, attempt to download it and replace it with Markdown syntax
|
||||
for image_tag in image_tags:
|
||||
if image_tag and not image_tag.startswith('\\'):
|
||||
logger.info(f"Found image tag: {image_tag}")
|
||||
# Replace the specific tag with our download logic
|
||||
markdown_content = markdown_content.replace(
|
||||
f"!{image_tag}!",
|
||||
handle_image_tag(image_tag, local_dir)
|
||||
)
|
||||
|
||||
# Write to file
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(f"# {title}\n\n")
|
||||
f.write(markdown_content)
|
||||
|
||||
logger.info(f"Saved page: {filename}")
|
||||
|
||||
# Add, commit, and push
|
||||
os.chdir(local_dir)
|
||||
subprocess.run(["git", "add", "."], check=True)
|
||||
subprocess.run(["git", "commit", "-m", "'Import Redmine wiki pages'"], check=False)
|
||||
subprocess.run(["git", "push", "origin", "main"], check=True)
|
||||
|
||||
logger.info("✅ Wiki pages pushed to Gitea!")
|
||||
|
||||
def handle_image_tag(image_name, local_dir):
|
||||
"""Handle replacement of image tags with downloaded images"""
|
||||
# Check if the image has a valid extension (.png, .jpg, or .jpeg)
|
||||
valid_extensions = ('.png', '.jpg', '.jpeg')
|
||||
if not any(image_name.lower().endswith(ext) for ext in valid_extensions):
|
||||
logger.warning(f"Invalid image extension in tag: {image_name}")
|
||||
return f"!{image_name}!" # Return original format for invalid extensions
|
||||
|
||||
if image_name and not image_name.startswith('\\'):
|
||||
# In Redmine, images are usually at /attachments/... path
|
||||
image_url = f"/attachments/download/{image_name}"
|
||||
logger.info(f"Attempting to download image: {image_name}")
|
||||
|
||||
# Try to download the image
|
||||
downloaded_path = download_image(image_url, local_dir)
|
||||
if downloaded_path:
|
||||
return f""
|
||||
else:
|
||||
# If download fails, keep the original format
|
||||
return f"!{image_name}!"
|
||||
else:
|
||||
# This is likely a regex capture group that wasn't meant to be an image
|
||||
return f"!{image_name}!"
|
||||
|
||||
def main():
|
||||
# Step 1: Get wiki pages from Redmine
|
||||
logger.info("Fetching Redmine wiki pages...")
|
||||
pages = redmine_get_wiki_pages()
|
||||
|
||||
# Step 2: Prepare local directory for Gitea wiki
|
||||
local_wiki_dir = "gitea_wiki_clone"
|
||||
clone_or_init_wiki_repo(GITEA_WIKI_URL, local_wiki_dir)
|
||||
|
||||
# Step 3: Push pages to Gitea
|
||||
push_to_gitea_wiki(local_wiki_dir, pages)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user