diff --git a/Docker/Containerfile b/Docker/Containerfile new file mode 100644 index 0000000..481d621 --- /dev/null +++ b/Docker/Containerfile @@ -0,0 +1,24 @@ +FROM alpine:3.19 + +RUN apk add --no-cache \ + wireguard-tools \ + iptables \ + ip6tables \ + bash \ + curl \ + iputils-ping \ + iproute2 \ + openresolv + +# Create wireguard config directory (config is mounted at runtime) +RUN mkdir -p /etc/wireguard + +# Copy entrypoint +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# Health check: can we reach the internet through the VPN? +HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ + CMD ping -c 1 -W 5 1.1.1.1 || exit 1 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/Docker/dispatcher.d-99-wg-routes.sh b/Docker/dispatcher.d-99-wg-routes.sh new file mode 100644 index 0000000..c096027 --- /dev/null +++ b/Docker/dispatcher.d-99-wg-routes.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +# === Configuration === +LOGFILE="/tmp/dispatcher.log" +BACKUP="/tmp/dispatcher.log.1" +MAXSIZE=$((1024 * 1024)) # 1 MB +VPN_IFACE="nl" +GATEWAY="192.168.178.1" +LOCAL_IFACE="wlp4s0f0" +ROUTE1="185.183.34.149" +ROUTE2="192.168.178.0/24" + +# === Log Rotation === +if [ -f "$LOGFILE" ] && [ "$(stat -c%s "$LOGFILE")" -ge "$MAXSIZE" ]; then + echo "[$(date)] Log file exceeded 1MB, rotating..." >> "$LOGFILE" + mv "$LOGFILE" "$BACKUP" + touch "$LOGFILE" +fi + +# === Logging Setup === +exec >> "$LOGFILE" 2>&1 +echo "[$(date)] Running dispatcher for $1 with status $2" + +IFACE="$1" +STATUS="$2" + +log_and_run() { + echo "[$(date)] Executing: $*" + if ! output=$("$@" 2>&1); then + echo "[$(date)] ERROR: Command failed: $*" + echo "[$(date)] Output: $output" + else + echo "[$(date)] Success: $*" + fi +} + +# === VPN Routing Logic === +if [ "$IFACE" = "$VPN_IFACE" ]; then + case "$STATUS" in + up) + echo "[$(date)] VPN interface is up. Preparing routes..." + + # === Wait for local interface and gateway === + echo "[$(date)] Waiting for $LOCAL_IFACE (state UP) and gateway $GATEWAY (reachable)..." + until ip link show "$LOCAL_IFACE" | grep -q "state UP" && ip route get "$GATEWAY" &>/dev/null; do + echo "[$(date)] Waiting for $LOCAL_IFACE and $GATEWAY..." + sleep 1 + done + echo "[$(date)] Local interface and gateway are ready." + # === End Wait === + + # === APPLY ROUTES (Corrected Order) === + + # 1. Add the route for the local network FIRST + log_and_run /sbin/ip route replace "$ROUTE2" dev "$LOCAL_IFACE" + + # 2. Add the route to the VPN endpoint via the gateway SECOND + log_and_run /sbin/ip route replace "$ROUTE1" via "$GATEWAY" dev "$LOCAL_IFACE" + + # === END APPLY ROUTES === + + # Log interface and WireGuard status + echo "[$(date)] --- ip addr show $VPN_IFACE ---" + ip addr show "$VPN_IFACE" + echo "[$(date)] --- wg show $VPN_IFACE ---" + wg show "$VPN_IFACE" + + ;; + + down) + echo "[$(date)] VPN interface is down. Verifying before removing routes..." + + # Log interface and WireGuard status + echo "[$(date)] --- ip addr show $VPN_IFACE ---" + ip addr show "$VPN_IFACE" + echo "[$(date)] --- wg show $VPN_IFACE ---" + wg show "$VPN_IFACE" + + # Delay and confirm interface is still down + sleep 5 + if ip link show "$VPN_IFACE" | grep -q "state UP"; then + echo "[$(date)] VPN interface is still up. Skipping route removal." + else + echo "[$(date)] Confirmed VPN is down. Removing routes..." + # It's good practice to remove them in reverse order, too. + log_and_run /sbin/ip route del "$ROUTE1" via "$GATEWAY" dev "$LOCAL_IFACE" + log_and_run /sbin/ip route del "$ROUTE2" dev "$LOCAL_IFACE" + fi + ;; + esac +fi diff --git a/Docker/entrypoint.sh b/Docker/entrypoint.sh new file mode 100644 index 0000000..1764dde --- /dev/null +++ b/Docker/entrypoint.sh @@ -0,0 +1,212 @@ +#!/bin/bash +set -e + +INTERFACE="wg0" +MOUNT_CONFIG="/etc/wireguard/${INTERFACE}.conf" +CONFIG_DIR="/run/wireguard" +CONFIG_FILE="${CONFIG_DIR}/${INTERFACE}.conf" +CHECK_INTERVAL="${HEALTH_CHECK_INTERVAL:-10}" +CHECK_HOST="${HEALTH_CHECK_HOST:-1.1.1.1}" + +# ────────────────────────────────────────────── +# Validate config exists, copy to writable location +# ────────────────────────────────────────────── +if [ ! -f "$MOUNT_CONFIG" ]; then + echo "[error] WireGuard config not found at ${MOUNT_CONFIG}" + echo "[error] Mount your config file: -v /path/to/your.conf:/etc/wireguard/wg0.conf:ro" + exit 1 +fi + +mkdir -p "$CONFIG_DIR" +cp "$MOUNT_CONFIG" "$CONFIG_FILE" +chmod 600 "$CONFIG_FILE" + +# Extract endpoint IP and port from the config +VPN_ENDPOINT=$(grep -i '^Endpoint' "$CONFIG_FILE" | head -1 | sed 's/.*= *//;s/:.*//;s/ //g') +VPN_PORT=$(grep -i '^Endpoint' "$CONFIG_FILE" | head -1 | sed 's/.*://;s/ //g') +# Extract address +VPN_ADDRESS=$(grep -i '^Address' "$CONFIG_FILE" | head -1 | sed 's/.*= *//;s/ //g') + +if [ -z "$VPN_ENDPOINT" ] || [ -z "$VPN_PORT" ]; then + echo "[error] Could not parse Endpoint from ${CONFIG_FILE}" + exit 1 +fi + +echo "[init] Config: ${CONFIG_FILE}" +echo "[init] Endpoint: ${VPN_ENDPOINT}:${VPN_PORT}" +echo "[init] Address: ${VPN_ADDRESS}" + +# ────────────────────────────────────────────── +# Kill switch: only allow traffic through wg0 +# ────────────────────────────────────────────── +setup_killswitch() { + echo "[killswitch] Setting up iptables kill switch..." + + # Flush existing rules + iptables -F + iptables -X + iptables -t nat -F + + # Default policy: DROP everything + iptables -P INPUT DROP + iptables -P FORWARD DROP + iptables -P OUTPUT DROP + + # Allow loopback + iptables -A INPUT -i lo -j ACCEPT + iptables -A OUTPUT -o lo -j ACCEPT + + # Allow traffic to/from VPN endpoint (needed to establish tunnel) + iptables -A OUTPUT -d "$VPN_ENDPOINT" -p udp --dport "$VPN_PORT" -j ACCEPT + iptables -A INPUT -s "$VPN_ENDPOINT" -p udp --sport "$VPN_PORT" -j ACCEPT + + # Allow all traffic through the WireGuard interface + iptables -A INPUT -i "$INTERFACE" -j ACCEPT + iptables -A OUTPUT -o "$INTERFACE" -j ACCEPT + + # Allow DNS to the VPN DNS server (through wg0) + iptables -A OUTPUT -o "$INTERFACE" -p udp --dport 53 -j ACCEPT + iptables -A OUTPUT -o "$INTERFACE" -p tcp --dport 53 -j ACCEPT + + # Allow DHCP (for container networking) + iptables -A OUTPUT -p udp --dport 67:68 -j ACCEPT + iptables -A INPUT -p udp --sport 67:68 -j ACCEPT + + # Allow established/related connections + iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT + iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT + + # ── Allow incoming connections to exposed service ports (e.g. app on 8000) ── + # LOCAL_PORTS can be set as env var, e.g. "8000,8080,3000" + if [ -n "${LOCAL_PORTS:-}" ]; then + for port in $(echo "$LOCAL_PORTS" | tr ',' ' '); do + echo "[killswitch] Allowing incoming traffic on port ${port}" + iptables -A INPUT -p tcp --dport "$port" -j ACCEPT + iptables -A OUTPUT -p tcp --sport "$port" -j ACCEPT + done + fi + + # ── FORWARDING (so other containers can use this VPN) ── + iptables -A FORWARD -i eth0 -o "$INTERFACE" -j ACCEPT + iptables -A FORWARD -i "$INTERFACE" -o eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT + + # NAT: masquerade traffic from other containers going out through wg0 + iptables -t nat -A POSTROUTING -o "$INTERFACE" -j MASQUERADE + + echo "[killswitch] Kill switch active. Traffic blocked if VPN drops." +} + +# ────────────────────────────────────────────── +# Enable IP forwarding so other containers can route through us +# ────────────────────────────────────────────── +enable_forwarding() { + echo "[init] Enabling IP forwarding..." + if echo 1 > /proc/sys/net/ipv4/ip_forward 2>/dev/null; then + echo "[init] IP forwarding enabled via /proc." + else + echo "[init] /proc read-only — relying on --sysctl net.ipv4.ip_forward=1" + fi +} + +# ────────────────────────────────────────────── +# Start WireGuard manually (no wg-quick, avoids sysctl issues) +# ────────────────────────────────────────────── +start_vpn() { + echo "[vpn] Starting WireGuard interface ${INTERFACE}..." + + # Create the interface + ip link add "$INTERFACE" type wireguard + + # Apply the WireGuard config (keys, peer, endpoint) + wg setconf "$INTERFACE" <(grep -v -i '^\(Address\|DNS\|MTU\|Table\|PreUp\|PostUp\|PreDown\|PostDown\|SaveConfig\)' "$CONFIG_FILE") + + # Assign the address + ip -4 address add "$VPN_ADDRESS" dev "$INTERFACE" + + # Set MTU + ip link set mtu 1420 up dev "$INTERFACE" + + # Find default gateway/interface for the endpoint route + DEFAULT_GW=$(ip route | grep '^default' | head -1 | awk '{print $3}') + DEFAULT_IF=$(ip route | grep '^default' | head -1 | awk '{print $5}') + + # Route VPN endpoint through the container's default gateway + if [ -n "$DEFAULT_GW" ] && [ -n "$DEFAULT_IF" ]; then + ip route add "$VPN_ENDPOINT/32" via "$DEFAULT_GW" dev "$DEFAULT_IF" 2>/dev/null || true + fi + + # Route all traffic through the WireGuard tunnel + ip route add 0.0.0.0/1 dev "$INTERFACE" + ip route add 128.0.0.0/1 dev "$INTERFACE" + + # Set up DNS + VPN_DNS=$(grep -i '^DNS' "$CONFIG_FILE" | head -1 | sed 's/.*= *//;s/ //g') + if [ -n "$VPN_DNS" ]; then + echo "nameserver $VPN_DNS" > /etc/resolv.conf + echo "[vpn] DNS set to ${VPN_DNS}" + fi + + echo "[vpn] WireGuard interface ${INTERFACE} is up." +} + +# ────────────────────────────────────────────── +# Stop WireGuard manually +# ────────────────────────────────────────────── +stop_vpn() { + echo "[vpn] Stopping WireGuard interface ${INTERFACE}..." + ip link del "$INTERFACE" 2>/dev/null || true +} + +# ────────────────────────────────────────────── +# Health check loop — restarts VPN if tunnel dies +# ────────────────────────────────────────────── +health_loop() { + local failures=0 + local max_failures=3 + + echo "[health] Starting health check (every ${CHECK_INTERVAL}s, target ${CHECK_HOST})..." + + while true; do + sleep "$CHECK_INTERVAL" + + if ping -c 1 -W 5 "$CHECK_HOST" > /dev/null 2>&1; then + if [ "$failures" -gt 0 ]; then + echo "[health] VPN recovered." + failures=0 + fi + else + failures=$((failures + 1)) + echo "[health] Ping failed ($failures/$max_failures)" + + if [ "$failures" -ge "$max_failures" ]; then + echo "[health] VPN appears down. Restarting WireGuard..." + stop_vpn + sleep 2 + start_vpn + failures=0 + echo "[health] WireGuard restarted." + fi + fi + done +} + +# ────────────────────────────────────────────── +# Graceful shutdown +# ────────────────────────────────────────────── +cleanup() { + echo "[shutdown] Stopping WireGuard..." + stop_vpn + echo "[shutdown] Flushing iptables..." + iptables -F + iptables -t nat -F + echo "[shutdown] Done." + exit 0 +} + +trap cleanup SIGTERM SIGINT + +# ── Main ── +enable_forwarding +setup_killswitch +start_vpn +health_loop diff --git a/Docker/nl.conf b/Docker/nl.conf new file mode 100644 index 0000000..095bb4b --- /dev/null +++ b/Docker/nl.conf @@ -0,0 +1,17 @@ +[Interface] +PrivateKey = iO5spIue/6ciwUoR95hYtuxdtQxV/Q9EOoQ/jHe18kM= +Address = 10.2.0.2/32 +DNS = 10.2.0.1 + +# Route zum VPN-Server direkt über dein lokales Netz +PostUp = ip route add 185.183.34.149 via 192.168.178.1 dev wlp4s0f0 +PostUp = ip route add 192.168.178.0/24 via 192.168.178.1 dev wlp4s0f0 +PostDown = ip route del 185.183.34.149 via 192.168.178.1 dev wlp4s0f0 +PostDown = ip route del 192.168.178.0/24 via 192.168.178.1 dev wlp4s0f0 + +[Peer] +PublicKey = J4XVdtoBVc/EoI2Yk673Oes97WMnQSH5KfamZNjtM2s= +AllowedIPs = 0.0.0.0/1, 128.0.0.0/1 +Endpoint = 185.183.34.149:51820 + + diff --git a/Docker/podman-compose.yml b/Docker/podman-compose.yml new file mode 100644 index 0000000..ae7abab --- /dev/null +++ b/Docker/podman-compose.yml @@ -0,0 +1,37 @@ +services: + vpn: + build: . + container_name: vpn-wireguard + cap_add: + - NET_ADMIN + - SYS_MODULE + sysctls: + - net.ipv4.ip_forward=1 + - net.ipv4.conf.all.src_valid_mark=1 + volumes: + - ./wg0.conf:/etc/wireguard/wg0.conf:ro + - /lib/modules:/lib/modules:ro + ports: + # Expose app's port 8000 to the local network through the VPN container + - "8000:8000" + environment: + - HEALTH_CHECK_INTERVAL=10 + - HEALTH_CHECK_HOST=1.1.1.1 + - LOCAL_PORTS=8000 + restart: unless-stopped + healthcheck: + test: ["CMD", "ping", "-c", "1", "-W", "5", "1.1.1.1"] + interval: 30s + timeout: 10s + retries: 3 + + app: + image: python:3.12-alpine + container_name: vpn-app + # Share the VPN container's network — all outgoing traffic goes through WireGuard + network_mode: "service:vpn" + depends_on: + vpn: + condition: service_healthy + # Example: simple HTTP server on port 8000. Replace with your actual app. + command: ["python3", "-m", "http.server", "8000"] diff --git a/Docker/test_vpn.py b/Docker/test_vpn.py new file mode 100644 index 0000000..4e07d80 --- /dev/null +++ b/Docker/test_vpn.py @@ -0,0 +1,185 @@ +""" +Integration test for the WireGuard VPN Podman image. + +Verifies: +1. The image builds successfully. +2. The container starts and becomes healthy. +3. The public IP inside the VPN differs from the host IP. +4. Kill switch blocks traffic when WireGuard is down. + +Requirements: + - podman installed + - Root/sudo (NET_ADMIN capability) + - A valid WireGuard config at ./wg0.conf (or ./nl.conf) + +Usage: + sudo python3 -m pytest test_vpn.py -v + # or + sudo python3 test_vpn.py +""" + +import subprocess +import time +import unittest +import os + +IMAGE_NAME = "vpn-wireguard-test" +CONTAINER_NAME = "vpn-test-container" +CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "wg0.conf") +BUILD_DIR = os.path.dirname(os.path.abspath(__file__)) +IP_CHECK_URL = "https://ifconfig.me" +STARTUP_TIMEOUT = 30 # seconds to wait for VPN to come up +HEALTH_POLL_INTERVAL = 2 # seconds between health checks + + +def run(cmd: list[str], timeout: int = 30, check: bool = True) -> subprocess.CompletedProcess: + """Run a command and return the result.""" + return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, check=check) + + +def get_host_ip() -> str: + """Get the public IP of the host machine.""" + result = run(["curl", "-s", "--max-time", "10", IP_CHECK_URL]) + return result.stdout.strip() + + +def podman_exec(container: str, cmd: list[str], timeout: int = 15) -> subprocess.CompletedProcess: + """Execute a command inside a running container.""" + return run(["podman", "exec", container] + cmd, timeout=timeout, check=False) + + +class TestVPNImage(unittest.TestCase): + """Test suite for the WireGuard VPN container.""" + + host_ip: str = "" + + @classmethod + def setUpClass(cls): + """Build image, get host IP, start container, wait for VPN.""" + # Clean up any leftover container from a previous run + subprocess.run( + ["podman", "rm", "-f", CONTAINER_NAME], + capture_output=True, check=False, + ) + + # ── 1. Get host public IP before VPN ── + print("\n[setup] Fetching host public IP...") + cls.host_ip = get_host_ip() + print(f"[setup] Host public IP: {cls.host_ip}") + assert cls.host_ip, "Could not determine host public IP" + + # ── 2. Build the image ── + print(f"[setup] Building image '{IMAGE_NAME}'...") + result = run( + ["podman", "build", "-t", IMAGE_NAME, BUILD_DIR], + timeout=180, + ) + print(result.stdout[-500:] if len(result.stdout) > 500 else result.stdout) + assert result.returncode == 0, f"Build failed:\n{result.stderr}" + print("[setup] Image built successfully.") + + # ── 3. Start the container ── + print(f"[setup] Starting container '{CONTAINER_NAME}'...") + result = run( + [ + "podman", "run", "-d", + "--name", CONTAINER_NAME, + "--cap-add=NET_ADMIN", + "--cap-add=SYS_MODULE", + "--sysctl", "net.ipv4.ip_forward=1", + "-v", f"{CONFIG_FILE}:/etc/wireguard/wg0.conf:ro", + "-v", "/lib/modules:/lib/modules:ro", + IMAGE_NAME, + ], + timeout=30, + check=False, + ) + assert result.returncode == 0, f"Container failed to start:\n{result.stderr}" + cls.container_id = result.stdout.strip() + print(f"[setup] Container started: {cls.container_id[:12]}") + + # Verify it's running + inspect = run( + ["podman", "inspect", "-f", "{{.State.Running}}", CONTAINER_NAME], + check=False, + ) + assert inspect.stdout.strip() == "true", "Container is not running" + + # ── 4. Wait for VPN to come up ── + print(f"[setup] Waiting up to {STARTUP_TIMEOUT}s for VPN tunnel...") + vpn_up = cls._wait_for_vpn_cls(STARTUP_TIMEOUT) + assert vpn_up, f"VPN did not come up within {STARTUP_TIMEOUT}s" + print("[setup] VPN tunnel is up. Running tests.\n") + + @classmethod + def tearDownClass(cls): + """Stop and remove the container.""" + print("\n[teardown] Cleaning up...") + subprocess.run(["podman", "rm", "-f", CONTAINER_NAME], capture_output=True, check=False) + print("[teardown] Done.") + + @classmethod + def _wait_for_vpn_cls(cls, timeout: int = STARTUP_TIMEOUT) -> bool: + """Wait until the VPN tunnel is up (can reach the internet).""" + deadline = time.time() + timeout + while time.time() < deadline: + result = podman_exec(CONTAINER_NAME, ["ping", "-c", "1", "-W", "3", "1.1.1.1"]) + if result.returncode == 0: + return True + time.sleep(HEALTH_POLL_INTERVAL) + return False + + def _get_vpn_ip(self) -> str: + """Get the public IP as seen from inside the container.""" + result = podman_exec( + CONTAINER_NAME, + ["curl", "-s", "--max-time", "10", IP_CHECK_URL], + timeout=20, + ) + return result.stdout.strip() + + # ── Tests ──────────────────────────────────────────────── + + def test_01_ip_differs_from_host(self): + """Public IP inside VPN is different from host IP.""" + vpn_ip = self._get_vpn_ip() + print(f"\n[test] VPN public IP: {vpn_ip}") + print(f"[test] Host public IP: {self.host_ip}") + + self.assertTrue(vpn_ip, "Could not fetch IP from inside the container") + self.assertNotEqual( + vpn_ip, + self.host_ip, + f"VPN IP ({vpn_ip}) is the same as host IP — VPN is not working!", + ) + + def test_02_wireguard_interface_exists(self): + """The wg0 interface is present in the container.""" + result = podman_exec(CONTAINER_NAME, ["wg", "show", "wg0"]) + self.assertEqual(result.returncode, 0, f"wg show failed:\n{result.stderr}") + self.assertIn("peer", result.stdout.lower(), "No peer information in wg show output") + + def test_03_kill_switch_blocks_traffic(self): + """When WireGuard is down, traffic is blocked (kill switch).""" + # Bring down the WireGuard interface by deleting it + down_result = podman_exec(CONTAINER_NAME, ["ip", "link", "del", "wg0"], timeout=10) + self.assertEqual(down_result.returncode, 0, f"ip link del wg0 failed:\n{down_result.stderr}") + + # Give iptables a moment + time.sleep(2) + + # Try to reach the internet — should fail due to kill switch + result = podman_exec( + CONTAINER_NAME, + ["curl", "-s", "--max-time", "5", IP_CHECK_URL], + timeout=10, + ) + self.assertNotEqual( + result.returncode, 0, + "Traffic went through even with WireGuard down — kill switch is NOT working!", + ) + print("\n[test] Kill switch confirmed: traffic blocked with VPN down") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/Docker/wg0.conf b/Docker/wg0.conf new file mode 100644 index 0000000..694a7c0 --- /dev/null +++ b/Docker/wg0.conf @@ -0,0 +1,10 @@ +[Interface] +PrivateKey = iO5spIue/6ciwUoR95hYtuxdtQxV/Q9EOoQ/jHe18kM= +Address = 10.2.0.2/32 +DNS = 10.2.0.1 + +[Peer] +PublicKey = J4XVdtoBVc/EoI2Yk673Oes97WMnQSH5KfamZNjtM2s= +AllowedIPs = 0.0.0.0/0 +Endpoint = 185.183.34.149:51820 +PersistentKeepalive = 25 diff --git a/diagrams/README.md b/docs/diagrams/README.md similarity index 100% rename from diagrams/README.md rename to docs/diagrams/README.md diff --git a/diagrams/download-flow.mmd b/docs/diagrams/download-flow.mmd similarity index 100% rename from diagrams/download-flow.mmd rename to docs/diagrams/download-flow.mmd diff --git a/diagrams/system-architecture.mmd b/docs/diagrams/system-architecture.mmd similarity index 100% rename from diagrams/system-architecture.mmd rename to docs/diagrams/system-architecture.mmd diff --git a/tvshow.nfo.bad b/tvshow.nfo.bad deleted file mode 100644 index 184d7b3..0000000 --- a/tvshow.nfo.bad +++ /dev/null @@ -1,37 +0,0 @@ - - - - - false - 2026-02-14 14:40:13 - 86 Eighty Six - 8.066 - 100565 - tt13718450 - 100565 - 378609 - - /media/Serien/86 Eighty Six/poster.jpg - /media/Serien/86 Eighty Six/fanart.jpg - - 378609 - - http://www.thetvdb.com/api/1D62F2F90030C444/series/378609/all/de.zip - - -1 - -1 - 86: Eighty Six - - - 8.066 - 211 - - - 100565 - tt13718450 - 378609 - https://image.tmdb.org/t/p/original/aEG1ZtdyjDFDtlGqovvAHsAfsR6.jpg - - https://image.tmdb.org/t/p/original/lSlL2CAPSDJ9gf2MZX0x2u2inKX.jpg - - \ No newline at end of file diff --git a/tvshow.nfo.good b/tvshow.nfo.good deleted file mode 100644 index b6365f2..0000000 --- a/tvshow.nfo.good +++ /dev/null @@ -1,83 +0,0 @@ - - - Farming Life in Another World - 異世界のんびり農家 - Farming Life in Another World - 2023 - Nachdem Hiraku einer schweren Krankheit erliegt, schenkt Gott ihm neues Leben. Hiraku wird seine Gesundheit sowie Jugend neu gewährt und er wird in eine Fantasiewelt seiner Wahl geschickt. Außerdem übergibt ihm Gott das allmächtige Ackergerät, um seine zweite Chance zu nutzen und sein Leben umzupflügen. - 24 - 2023-01-06 - Returning Series - - - 7.57 - 114 - - - 196285 - tt19223420 - 418367 - 418367 - tt19223420 - 196285 - tt19223420 - 418367 - Animation - Komödie - Sci-Fi & Fantasy - AT-X - Japan - https://image.tmdb.org/t/p/original/mE4pE6NOV3AbvTUE3MkFMlfs12n.jpg - - https://image.tmdb.org/t/p/original/6XJ0XJbL14YkThOe1iU5TJKU5l3.jpg - - - Atsushi Abe - Machio Hiraku (voice) - https://image.tmdb.org/t/p/h632/b59PickRsrhT6kaUKN2dD8c7ip.jpg - 1154449 - - - Shino Shimoji - Rurushi Ru (voice) - https://image.tmdb.org/t/p/h632/nw2Zrgzcb0kLgUCHL3lIoy8qYiH.jpg - 1780773 - - - Aya Suzaki - Tia (voice) - https://image.tmdb.org/t/p/h632/eCbCtnzFqr4aTpe3arDeRGazP5K.jpg - 1258548 - - - Lynn - Ria (voice) - https://image.tmdb.org/t/p/h632/eJ2NqgzpnzNbT6Nt9EpDfzqNeZM.jpg - 1691384 - - - Yukiyo Fujii - Ann (voice) - https://image.tmdb.org/t/p/h632/tLG4K1iix3QNHFexf98mrZ25jT6.jpg - 1287794 - - - Machico - Sena (voice) - https://image.tmdb.org/t/p/h632/hocukD5IKqBDcrcXfcQJhgk4jJl.jpg - 1842285 - - - Natsumi Hioka - Lastismun (voice) - https://image.tmdb.org/t/p/h632/jCXosIVhe3QuTjbHFlemTSZJ53u.jpg - 2043754 - - - Miyu Tomita - Flora (voice) - https://image.tmdb.org/t/p/h632/rSR17l4HdchLkhpRuAZbGbXNmUS.jpg - 1647636 - - false -