Jonas Linter 6ab5212a0f MORE Tests
2025-10-10 11:28:25 +02:00
2025-10-09 14:29:44 +02:00
2025-10-08 16:18:20 +02:00
2025-10-09 14:16:11 +02:00
2025-10-10 10:45:47 +02:00
2025-10-10 11:28:25 +02:00
2025-10-10 11:28:25 +02:00
2025-10-08 16:18:20 +02:00
2025-10-10 10:45:47 +02:00
2025-10-07 15:12:46 +02:00
2025-10-10 11:28:25 +02:00
2025-10-08 16:14:00 +02:00
2025-10-10 11:28:25 +02:00
2025-09-29 07:59:47 +00:00
2025-10-09 14:29:44 +02:00
2025-09-29 14:09:31 +02:00
2025-09-25 11:33:54 +02:00
2025-10-01 09:38:50 +02:00
2025-10-10 10:45:47 +02:00
2025-10-10 10:45:47 +02:00
2025-10-07 15:59:00 +02:00

Übersicht

Enthält einen in Python geschriebenen Alpine Bits Server zur Übertragung von Buchungsanfragen von Landingpages an Partnerhotels. Ein Fastapi Endpoint empfängt Anfrageformulare von den wix.com landingpages, und speichert sie in die Datenbank ab. Der Alpine Bits Server stellt diese dann Hotels auf dem Endpoint www.99tales.net/api/alpinebits/server-2024-10 zu Verfügung.

Entwicklung

Auf dem Entwicklungsystem muss git und der uv python package manager installiert sein.

Git Authentification

Wenn über http geklont wird muss lokal der git-credential-oauth helper installiert sein. Besser gehts über ssh. Da muss ein ssh-key in gitea für den eigenen Benutzer angelegt sein.

  1. Repo klonen.
  2. uv sync ausführen
  3. uv run python -m alpine_bits_python.run_api führt die API lokal auf Port 8080 aus. Datenbank wird automatisch erstellt und bei jedem start geleert.

Konfiguration

Erfolgt über zwei yaml files. Zu konfigurieren ist die Verbindung zur Datenbank und die Konfiguration der einzelnen Hotels. In zukunft kommt vermutlich auch noch die Push URL hinzu.

database:
  url: "sqlite+aiosqlite:///alpinebits.db"  # For local dev, use SQLite. For prod, override with PostgreSQL URL.
  # url: "postgresql://user:password@host:port/dbname"  # Example for Postgres

alpine_bits_auth:
  - hotel_id: "123"
    hotel_name: "Frangart Inn"
    username: "alice"
    password: !secret ALICE_PASSWORD
  - hotel_id: "456"
    hotel_name: "Bemelmans"
    username: "bob"
    password: !secret BOB_PASSWORD

!secret verweist auf einen Eintrag in secrets.yaml. Diese Datei wird aus Sicherheitsgründen nicht auf die Repository hochgeladen. In secrets.yaml können passwörter folgendermaßen angegeben werden

ALICE_PASSWORD: "supersecretpassword123"

Deployment

Die Applikation wird in einem Dockercontainer deployed. Um das Container Image zu erstellen ist folgender Befehl notwendig

uv sync
docker build .  -t gitea.linter-home.com/jonas/asa_api:master

Dieser Befehl muss im Wurzelverzeichnis der Repository ausgeführt werden. pwd sollte irgendwas/alpinebits_python ergeben. Der Punkt hinter dem docker build befehl verweißt nämlich auf das lokale Dockerfile. "-t" steht für tag. In diesem Beispiel wird das Image mit dem Tag gitea.linter-home.com/jonas/asa_api:master versehen.

Ideal wäre eine Build Pipeline in Gitea selbst aber dies aufzusetzen ist etwas schwierig und es ist gut möglich das die Hetzner VM das nicht herhat. Lokal bei mir zuhause ist dies aufgesetzt. War alles andere als leicht.

Am besten einfach direkt auf dem Zielsystem den Container bauen und im Docker Compose File dann auf dieses Image referenzieren.

Docker Compose Beispiel mit Traefik Reverse Proxy

services:
  asa_connector:
    image: gitea.linter-home.com/jonas/asa_api:master
    container_name: asa_connector
    restart: unless-stopped
    
    # Environment variables via .env file
    env_file:
      - asa_connector.env
    
    networks:
      - external
    
    # Only expose internally - Traefik will handle external access
    expose:
      - "8000"

    user: "1000:1000"  # Run as user with UID 1000 and GID 1000

    environment:
      - ALPINEBITS_CONFIG_DIR=/config

    volumes:
      - /home/jonas/asa_connector_logs:/app/src/logs
      - /home/jonas/alpinebits_config:/config


    
    # Traefik labels for automatic service discovery
    labels:
      - "traefik.enable=true"
      # API router - handles /api/* paths on 99tales.net
      - "traefik.http.routers.asa_connector.rule=Host(`99tales.net`) && PathPrefix(`/api`)"
      - "traefik.http.routers.asa_connector.entrypoints=https"
      - "traefik.http.routers.asa_connector.tls.certresolver=letsencrypt"
      - "traefik.http.services.asa_connector.loadbalancer.server.port=8000"
      - "traefik.http.routers.asa_connector.priority=100"
      
      # Redirect middleware for non-API paths
      - "traefik.http.middlewares.redirect-to-99tales-it.redirectregex.regex=^https://99tales\\.net/(.*)$$"
      - "traefik.http.middlewares.redirect-to-99tales-it.redirectregex.replacement=https://99tales.it/$${1}"
      - "traefik.http.middlewares.redirect-to-99tales-it.redirectregex.permanent=true"
      
      # Catch-all router for non-API paths on 99tales.net (lower priority)
      - "traefik.http.routers.redirect-router.rule=Host(`99tales.net`)"
      - "traefik.http.routers.redirect-router.entrypoints=https"
      - "traefik.http.routers.redirect-router.tls.certresolver=letsencrypt"
      - "traefik.http.routers.redirect-router.middlewares=redirect-to-99tales-it"
      - "traefik.http.routers.redirect-router.service=noop@internal"
      - "traefik.http.routers.redirect-router.priority=1"

  dockerproxy:
    image: ghcr.io/tecnativa/docker-socket-proxy:latest
    container_name: dockerproxy
    restart: unless-stopped
    environment:
      CONTAINERS: 1 # read only
      POST: 0

    networks:
      - external

    volumes: 
      - /var/run/docker.sock:/var/run/docker.sock


  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped


    environment:
      - DOCKER_HOST=dockerproxy

    networks:
      - external

    ports:
      - "80:80"       # HTTP
      - "443:443"     # HTTPS
      - "22:22"       # SSH for Gitea
    
    volumes:
      - /home/jonas/traefik:/etc/traefik  # Traefik configuration files


    
    # Health check
    healthcheck:
      test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8000/health', timeout=5)"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

networks: # custom bridge network named 'external'
  external:
    name: external
    driver: bridge

Damit das ganze auch funktioniert müssen dns Einträge auf die Virtuelle Machine zeigen in der das ganze läuft. Wurde bei Hostinger für 99tales.net eingerichtet.

Wie in dem Beispiel ersichtlich wird sowohl ein Log Ordner als auch ein Config ordner in den Container gemapped. Diesen am besten auf dem Host vor Erstellung des Containers erstellen.

Die Umgebungsvariable ALPINEBITS_CONFIG_DIR sagt dann dem Programm wo es die Config finden soll. In dem Ordner kann man die obens erwähnten Konfigurationsdateien speichern. Falls sqlite als Datenbank verwendet wird, findet man dort auch die Datenbank nach erstem ausführen.

TODO Liste

Need a table in the database that stores requests that have already been acknowledged by the client. Should contain client_id + a list of all acked unique_ids

Description
No description provided
Readme 309 MiB
2025-12-09 14:40:12 +00:00
Languages
Python 99.6%
HTML 0.2%
Dockerfile 0.1%