Skip to content
  • CategorieΓ«n
  • Recent
  • Tags
  • Populair
  • Gebruikers
  • Groepen
  • Zoeken
Collapse
Brand Logo

Kennisbank

administrators

Prive

Berichten


  • api.development.thefreighthero.com - update 'dev' database met production database data
    nikitaskliarovN nikitaskliarov

    Gehele db production (~15GB) ingeladen


  • Voicedata Enreach & Shomi assistent installatie
    nikitaskliarovN nikitaskliarov

    1. Licentie aanvragen

    Vraag een nieuwe licentie aan bij Voicedata voor een reeds bestaande gebruiker met een toestelnummer (extensie) voor kantoor.

    2. Enreach downloaden

    Zodra de licentie is geactiveerd, download Enreach via de volgende link:

    πŸ‘‰ https://desktop.enreach.com/

    3. Installeren

    Download de juiste versie voor het betreffende besturingssysteem en installeer de applicatie.

    4. Inloggen

    Log in met het opgegeven e-mailadres en wachtwoord.

    Wachtwoord resetten kan op twee manieren:

    • Via de Enreach-app: Gebruik de "Wachtwoord vergeten"-knop in het inlogscherm. Er wordt een resetlink naar het e-mailadres gestuurd.
    • Via Voicedata (bij fictief e-mailadres): Log in op het Voicedata-portaal β†’ ga naar Gebruikers β†’ selecteer het bestaande account. In de sectie Beveiliging kan een wachtwoord-resetlink worden gegenereerd en het wachtwoord op afstand worden gewijzigd.

    5. Gebruikssituaties

    Situatie A: Yealink-telefoon + Yealink-headset + PC

    Wij gebruiken de Yealink WH64-headset.

    Headset koppelen via Bluetooth:

    1. Gebruik de schakelaar op de headset en zet deze op de Bluetooth-stand (houd 3 seconden ingedrukt).
    2. Open tegelijkertijd het Bluetooth-scanvenster op de PC.
    3. De headset wordt nu zichtbaar en kan gekoppeld worden.

    Let op: Als de headset al verbonden is met de Yealink-telefoon, blijft de PC-verbinding ook actief.

    Werkwijze voor bellen:

    • Bellen via Enreach (inkomend/uitgaand): De gebruiker moet de headset gebruiken, aangezien de verbinding via de PC loopt en niet via de telefoon.
    • Bellen via Yealink-telefoon (inkomend/uitgaand): Er zijn twee opties: gebruik de headset, of zet het gesprek op luid met de daarvoor bestemde knop op de telefoon.

    ⚠️ Belangrijk: Vergeet niet om na het succesvol koppelen van de headset de audio-instellingen (invoer/uitvoer) te testen in de instellingen van Enreach!


    Situatie B: Yealink-telefoon + PC (zonder headset)

    Deze gebruikers mogen Enreach niet gebruiken voor het voeren of beantwoorden van gesprekken.

    Wel is de belgeschiedenis van de Yealink-telefoon toegankelijk in Enreach via de Shomi-chatfunctie.


  • tfh-pagekit-crm Local Docker Database Sync
    nikitaskliarovN nikitaskliarov

    Overzicht

    Dit script automatiseert het synchroniseren van de productiedatabase naar je lokale Docker-omgeving. Het vervangt het handmatige proces van exporteren via phpMyAdmin, downloaden, en importeren via de terminal.

    Wat doet het script?

    1. Controleert of Docker, SSH en MySQL beschikbaar zijn
    2. Maakt verbinding met de productieserver via SSH
    3. Draait mysqldump op de productieserver (alleen lezen, geen wijzigingen aan productie)
    4. Downloadt het gecomprimeerde bestand naar je lokale machine
    5. Vraagt om bevestiging voordat lokale tabellen worden verwijderd
    6. Importeert de export in je lokale Docker database
    7. Verifieert het resultaat en ruimt tijdelijke bestanden op

    Tabellen zonder data

    De volgende tabellen worden alleen als structuur geΓ«xporteerd (zonder data), omdat ze grote logtabellen zijn die lokaal niet nodig zijn:

    • hero_contactmanager_log
    • hero_emailsender_emaillog
    • hero_freighthero_calculator_data_audit_log

    Configuratie

    Inloggegevens instellen

    Open update-local-database.sh en vul de volgende variabelen in:

    # Productieserver (SSH + Database)
    REMOTE_SSH_HOST="thefreighthero.com"
    REMOTE_SSH_USER="freighther"
    REMOTE_DB_USER="<vul in>"        # Database gebruikersnaam op productie
    REMOTE_DB_PASS="<vul in>"        # Database wachtwoord op productie
    
    # Lokale Docker database
    LOCAL_HOST="127.0.0.1"
    LOCAL_PORT="3307"
    LOCAL_USER="freighther_nl"
    LOCAL_PASS="freighther_nl"
    LOCAL_DB="freighther_nl"
    

    ⚠️ Let op: Dit script bevat wachtwoorden in platte tekst. Deel dit bestand nooit via Git of andere publieke kanalen. Voeg het toe aan .gitignore.

    SSH-sleutel instellen (aanbevolen)

    Om te voorkomen dat je elke keer je SSH-wachtwoord moet invoeren:

    ssh-copy-id freighther@thefreighthero.com
    

    Optioneel: pv installeren

    Voor een voortgangsbalk tijdens het importeren:

    # macOS
    brew install pv
    
    # Ubuntu/Debian
    sudo apt install pv
    

    Gebruik

    Script uitvoeren

    cd ~/Desktop/tfh/helpers
    bash update-local-database.sh
    

    Verwachte output

    ==> Preparing environment
        βœ” Docker is running
        βœ” SSH client available
        βœ” MySQL client available
    
    ==> Exporting database from production via SSH
        Connecting to freighther@thefreighthero.com...
        [remote] Dumping tables with data...
        [remote] Dumping structure-only tables...
        [remote] Compressing...
        [remote] Done: 45M
        βœ” Remote export complete
    
    ==> Downloading export from production
        ...progress...
        βœ” Download complete
        βœ” Decompressed: freighther_nl_20260212_143022.sql (180MB)
    
    ==> Truncating local database
        Found 47 tables to drop
    
        Target: mysql -h 127.0.0.1 -P 3307 -u freighther_nl freighther_nl
    
          DROP TABLE `hero_contactmanager_contacts`
          DROP TABLE `hero_contactmanager_log`
          DROP TABLE `hero_emailsender_emaillog`
          ...
    
        ⚠️  This will DROP all 47 tables in LOCAL database 'freighther_nl'
        ⚠️  Connection: 127.0.0.1:3307
    
        Continue? (yes/no): yes
        βœ” Dropped 47 tables
    
    ==> Importing into local database
        βœ” Import complete
    
    ==> Verifying import
        βœ” 47 tables imported
        hero_contactmanager_log: 0 rows (structure only βœ”)
        hero_emailsender_emaillog: 0 rows (structure only βœ”)
        hero_freighthero_calculator_data_audit_log: 0 rows (structure only βœ”)
    
    ==> Cleaning up
        βœ” Removed remote dump
        Local file kept: freighther_nl_20260212_143022.sql
    
    =============================================
      🐳 Database sync complete!
    =============================================
    

    Bevestigingsvraag

    Voordat het script tabellen verwijdert, toont het:

    • De exacte database-verbinding (127.0.0.1:3307)
    • Alle tabellen die verwijderd worden
    • Een bevestigingsvraag waar je yes moet typen

    Typ iets anders dan yes om te annuleren. Het script ruimt dan het remote bestand op en stopt.


    Veiligheid

    Productiedatabase wordt NIET gewijzigd

    Stap Waar Actie
    Export Productieserver mysqldump β€” alleen lezen
    Download Productie β†’ Lokaal Bestandsoverdracht
    Drop tabellen Alleen lokale Docker DROP TABLE op 127.0.0.1:3307
    Import Alleen lokale Docker mysql < bestand op 127.0.0.1:3307
    Opruimen Productieserver Verwijdert alleen /tmp/*.sql.gz

    Wachtwoorden beschermen

    Het script bevat database-wachtwoorden. Zorg ervoor dat:

    1. Het bestand niet in versiebeheer (Git) staat
    2. Voeg toe aan .gitignore:
      update-local-database.sh
      
    3. Stel de juiste bestandsrechten in:
      chmod 700 update-local-database.sh
      
      Dit zorgt ervoor dat alleen jij het bestand kunt lezen en uitvoeren.

    Problemen oplossen

    "Access denied" bij remote export

    • Controleer REMOTE_DB_USER en REMOTE_DB_PASS
    • Zorg dat het wachtwoord geen speciale tekens bevat die verkeerd worden geΓ―nterpreteerd
    • Test handmatig: ssh freighther@thefreighthero.com "mysqldump -u GEBRUIKER -pWACHTWOORD freighther_nl --no-data | head -5"

    "Docker is not running"

    • Start Docker Desktop of de Docker daemon

    Import mislukt

    • Controleer of je lokale Docker MySQL-container draait
    • Test verbinding: mysql -h 127.0.0.1 -P 3307 -u freighther_nl -p freighther_nl -e "SELECT 1;"

    Geen voortgangsbalk bij import

    • Installeer pv (zie configuratie hierboven)

    Structuur-only tabellen aanpassen

    Om tabellen toe te voegen of te verwijderen die zonder data worden geΓ«xporteerd, pas het array aan in het script:

    STRUCTURE_ONLY_TABLES=(
        "hero_contactmanager_log"
        "hero_emailsender_emaillog"
        "hero_freighthero_calculator_data_audit_log"
        # Voeg hier extra tabellen toe
    )
    

    Script

    #!/bin/bash
    
    # ============================================================
    #  🐳 FreightHero Local DB Sync
    #  Exports from production, downloads, and imports locally
    # ============================================================
    
    # -- Remote (Production) Configuration ----------------------
    REMOTE_SSH_HOST="thefreighthero.com"
    REMOTE_SSH_USER="freighther"
    REMOTE_DB_NAME="freighther_nl"
    REMOTE_DB_USER="freighther"
    REMOTE_DB_PASS="DIRECT ADMIN PASS OF USER"
    REMOTE_DUMP_PATH="/home/freighther/dump/freighther_nl_export.sql"
    
    # -- Local (Docker) Configuration ----------------------------
    LOCAL_HOST="127.0.0.1"
    LOCAL_PORT="3307"
    LOCAL_DB="LOCAL_DB_NAME"
    LOCAL_USER="LOCAL_DB_USER"
    LOCAL_PASS="LOCAL_DB_PASS"
    
    # -- Export password for mysql client ------------------------
    export MYSQL_PWD="$LOCAL_PASS"
    
    # -- Tables to export structure only (no data) ---------------
    STRUCTURE_ONLY_TABLES=(
        "hero_contactmanager_log"
        "hero_emailsender_emaillog"
        "hero_freighthero_calculator_data_audit_log"
    )
    
    # -- Local SQL file with timestamp ---------------------------
    LOCAL_SQL_FILE="freighther_nl_$(date +%Y%m%d_%H%M%S).sql"
    
    # -- Helper --------------------------------------------------
    log_step()  { echo ""; echo "==> $1"; }
    log_info()  { echo "    $1"; }
    log_done()  { echo "    βœ” $1"; }
    log_error() { echo "    ✘ $1"; exit 1; }
    
    # ============================================================
    #  Step 1: Check prerequisites
    # ============================================================
    log_step "Preparing environment"
    
    if ! docker info &>/dev/null; then
        log_error "Docker is not running. Start Docker first."
    fi
    log_done "Docker is running"
    
    if ! command -v ssh &>/dev/null; then
        log_error "SSH client not found."
    fi
    log_done "SSH client available"
    
    if ! command -v mysql &>/dev/null; then
        log_error "mysql client not found."
    fi
    log_done "MySQL client available"
    
    # ============================================================
    #  Step 2: Run mysqldump on production server via SSH
    # ============================================================
    log_step "Exporting database from production via SSH"
    log_info "Connecting to ${REMOTE_SSH_USER}@${REMOTE_SSH_HOST}..."
    
    # Build --ignore-table flags for structure-only tables
    IGNORE_FLAGS=""
    for TABLE in "${STRUCTURE_ONLY_TABLES[@]}"; do
        IGNORE_FLAGS+=" --ignore-table=${REMOTE_DB_NAME}.${TABLE}"
    done
    
    # Space-separated list for structure-only dump
    STRUCTURE_TABLES="${STRUCTURE_ONLY_TABLES[*]}"
    
    # Write remote script to a temp file to avoid quoting hell
    REMOTE_SCRIPT_FILE=$(mktemp)
    cat > "$REMOTE_SCRIPT_FILE" <<EOF
    #!/bin/bash
    set -e
    
    echo "[remote] Dumping tables with data..."
    mysqldump -u ${REMOTE_DB_USER} -p${REMOTE_DB_PASS} \
        ${REMOTE_DB_NAME} \
        ${IGNORE_FLAGS} \
        --single-transaction \
        --routines \
        --triggers \
        > ${REMOTE_DUMP_PATH}
    
    echo "[remote] Dumping structure-only tables..."
    mysqldump -u ${REMOTE_DB_USER} -p${REMOTE_DB_PASS} \
        ${REMOTE_DB_NAME} \
        ${STRUCTURE_TABLES} \
        --no-data \
        --single-transaction \
        >> ${REMOTE_DUMP_PATH}
    
    echo "[remote] Compressing..."
    gzip -f ${REMOTE_DUMP_PATH}
    
    FILE_SIZE=\$(ls -lh ${REMOTE_DUMP_PATH}.gz | awk '{print \$5}')
    echo "[remote] Done: \${FILE_SIZE}"
    EOF
    
    ssh "${REMOTE_SSH_USER}@${REMOTE_SSH_HOST}" bash < "$REMOTE_SCRIPT_FILE" 2>&1 | while IFS= read -r line; do
        log_info "$line"
    done
    
    if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
        rm -f "$REMOTE_SCRIPT_FILE"
        log_error "Remote export failed. Check SSH connection and DB credentials."
    fi
    
    rm -f "$REMOTE_SCRIPT_FILE"
    log_done "Remote export complete"
    
    # ============================================================
    #  Step 3: Download with progress
    # ============================================================
    log_step "Downloading export from production"
    
    if command -v rsync &>/dev/null; then
        rsync -avz --progress \
            "${REMOTE_SSH_USER}@${REMOTE_SSH_HOST}:${REMOTE_DUMP_PATH}.gz" \
            "${LOCAL_SQL_FILE}.gz" \
            2>&1 | while IFS= read -r line; do
                echo "    ${line}"
            done
    else
        scp \
            "${REMOTE_SSH_USER}@${REMOTE_SSH_HOST}:${REMOTE_DUMP_PATH}.gz" \
            "${LOCAL_SQL_FILE}.gz"
    fi
    
    if [[ ! -f "${LOCAL_SQL_FILE}.gz" ]]; then
        log_error "Download failed."
    fi
    log_done "Download complete"
    
    log_info "Decompressing..."
    gunzip -f "${LOCAL_SQL_FILE}.gz"
    
    if [[ ! -f "${LOCAL_SQL_FILE}" ]]; then
        log_error "Decompression failed."
    fi
    
    FILE_SIZE_MB=$(du -m "${LOCAL_SQL_FILE}" | awk '{print $1}')
    log_done "Decompressed: ${LOCAL_SQL_FILE} (${FILE_SIZE_MB}MB)"
    
    # ============================================================
    #  Step 4: Drop all tables in local database
    # ============================================================
    log_step "Truncating local database"
    
    # Re-export password for local commands
    export MYSQL_PWD="$LOCAL_PASS"
    
    TABLES=$(mysql -h "$LOCAL_HOST" -P "$LOCAL_PORT" -u "$LOCAL_USER" "$LOCAL_DB" \
        -N -e "SHOW TABLES;" 2>/dev/null)
    
    if [[ -z "$TABLES" ]]; then
        log_info "Local database is empty, skipping."
    else
        TABLE_COUNT=$(echo "$TABLES" | wc -l | tr -d ' ')
        log_info "Found ${TABLE_COUNT} tables to drop"
    
        # Disable FK checks, drop all, re-enable
        DROP_SQL="SET FOREIGN_KEY_CHECKS = 0;"
        while IFS= read -r TABLE; do
            DROP_SQL+=" DROP TABLE IF EXISTS \`${TABLE}\`;"
        done <<< "$TABLES"
        DROP_SQL+=" SET FOREIGN_KEY_CHECKS = 1;"
    
        mysql -h "$LOCAL_HOST" -P "$LOCAL_PORT" -u "$LOCAL_USER" "$LOCAL_DB" \
            -e "${DROP_SQL}" 2>/dev/null
    
        if [[ $? -ne 0 ]]; then
            log_error "Failed to drop tables."
        fi
        log_done "Dropped ${TABLE_COUNT} tables"
    fi
    
    # ============================================================
    #  Step 5: Import into local database
    # ============================================================
    log_step "Importing into local database"
    
    if command -v pv &>/dev/null; then
        pv "${LOCAL_SQL_FILE}" | mysql -h "$LOCAL_HOST" -P "$LOCAL_PORT" -u "$LOCAL_USER" "$LOCAL_DB" 2>/dev/null
    else
        log_info "(tip: install 'pv' for a progress bar)"
        mysql -h "$LOCAL_HOST" -P "$LOCAL_PORT" -u "$LOCAL_USER" "$LOCAL_DB" < "$LOCAL_SQL_FILE"
    fi
    
    if [[ $? -ne 0 ]]; then
        log_error "Import failed."
    fi
    log_done "Import complete"
    
    # ============================================================
    #  Step 6: Verify
    # ============================================================
    log_step "Verifying import"
    
    TOTAL_TABLES=$(mysql -h "$LOCAL_HOST" -P "$LOCAL_PORT" -u "$LOCAL_USER" "$LOCAL_DB" \
        -N -e "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='${LOCAL_DB}';" 2>/dev/null)
    
    log_done "${TOTAL_TABLES} tables imported"
    
    for TABLE in "${STRUCTURE_ONLY_TABLES[@]}"; do
        ROW_COUNT=$(mysql -h "$LOCAL_HOST" -P "$LOCAL_PORT" -u "$LOCAL_USER" "$LOCAL_DB" \
            -N -e "SELECT COUNT(*) FROM \`${TABLE}\`;" 2>/dev/null)
        log_info "${TABLE}: ${ROW_COUNT:-0} rows (structure only βœ”)"
    done
    
    # ============================================================
    #  Step 7: Cleanup
    # ============================================================
    log_step "Cleaning up"
    
    ssh "${REMOTE_SSH_USER}@${REMOTE_SSH_HOST}" "rm -f ${REMOTE_DUMP_PATH}.gz" 2>/dev/null
    log_done "Removed remote dump"
    
    log_info "Local file kept: ${LOCAL_SQL_FILE}"
    
    unset MYSQL_PWD
    
    echo ""
    echo "============================================="
    echo "  🐳 Database sync complete!"
    echo "============================================="
    echo ""
    
    

  • TFH Dagelijkse Systeemcontrole β€” Automatisch Opstartscript
    nikitaskliarovN nikitaskliarov

    Overzicht

    Elke ochtend worden er handmatig drie platforms gecontroleerd om te zorgen dat alle systemen van The Freight Hero correct functioneren. Dit script automatiseert deze controles en toont de resultaten direct in de terminal bij het opstarten van de laptop.

    De volgende controles worden uitgevoerd:

    1. Cronitor β€” Status van alle cronjobs op de server (gezond / falend / gepauzeerd)
    2. AWS S3 β€” Overzicht van database-backups in de tfh-backup-2 bucket, inclusief leeftijd van bestanden en maandelijkse kosten. Backups ouder dan 3 dagen worden gemarkeerd als "pending deletion" conform het retentiebeleid.
    3. Google Cloud β€” Aantal API-verzoeken en foutmeldingen (4xx/5xx) van de Google Maps API's over de afgelopen 24 uur

    Vereisten

    Tool Installatie
    jq sudo apt install jq
    bc sudo apt install bc
    AWS CLI v2 Installatiehandleiding
    Google Cloud CLI Installatiehandleiding

    Configuratie

    AWS-inloggegevens

    Er is een aparte IAM-gebruiker aangemaakt (nikita-ubuntu-laptop) met minimale rechten:

    • s3:ListBucket en s3:GetBucketLocation op arn:aws:s3:::tfh-backup-2
    • ce:GetCostAndUsage voor het ophalen van factureringsgegevens

    Configureer de AWS CLI eenmalig:

    aws configure
    # Vul Access Key ID, Secret Access Key en regio (bijv. eu-west-1) in
    

    Google Cloud-inloggegevens

    Configureer eenmalig:

    gcloud auth login
    gcloud config set project tfh-maps-477109
    

    Omgevingsvariabelen

    Bestand: ~/tfh-check/morning-check.env

    # ============================================================================
    #  TFH Morning Check β€” Environment Configuration
    #  Copy this file to morning-check.env and fill in your values
    # ============================================================================
    
    # --- Cronitor ---
    # Get your API key from: https://cronitor.io/settings/api
    CRONITOR_API_KEY=KEY_FROM_TELEMETRY
    
    # --- AWS S3 ---
    # Auth is handled by 'aws configure' (your nikita-ubuntu-laptop IAM user credentials)
    # No keys needed here β€” just set bucket and retention policy
    S3_BUCKET=tfh-backup-2
    S3_RETENTION_DAYS=3
    
    # --- Google Cloud ---
    # Auth is handled by 'gcloud auth login'
    # Find your project ID at: https://console.cloud.google.com/home/dashboard
    GCP_PROJECT_ID=tfh-maps-477109 
    
    # Comma-separated list of Maps API services to monitor
    # Find your enabled APIs at: https://console.cloud.google.com/apis/dashboard
    GCP_MAPS_SERVICES=maps-backend.googleapis.com,geocoding-backend.googleapis.com,places-backend.googleapis.com,directions-backend.googleapis.com
    
    

    Let op: Gebruik SDK API key van cronitor.

    Installatie

    # Map aanmaken en bestanden plaatsen
    mkdir -p ~/tfh-check
    # Kopieer morning-check.sh en morning-check.env naar ~/tfh-check/
    chmod +x ~/tfh-check/morning-check.sh
    

    Het Script

    #!/bin/bash
    # ============================================================================
    #  TFH Morning Health Check Script
    #  Runs daily checks on: Cronitor, AWS S3 Backups, Google Cloud Maps API
    # ============================================================================
    
    # --- Colors & Formatting ---
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    BLUE='\033[0;34m'
    CYAN='\033[0;36m'
    BOLD='\033[1m'
    DIM='\033[2m'
    NC='\033[0m' # No Color
    
    PASS="${GREEN}βœ”${NC}"
    FAIL="${RED}✘${NC}"
    WARN="${YELLOW}⚠${NC}"
    
    # --- Configuration (set these or use environment variables) ---
    CRONITOR_API_KEY="${CRONITOR_API_KEY:-}"
    AWS_PROFILE="${AWS_PROFILE:-default}"
    S3_BUCKET="${S3_BUCKET:-tfh-backup-2}"
    S3_RETENTION_DAYS="${S3_RETENTION_DAYS:-3}"
    GCP_PROJECT_ID="${GCP_PROJECT_ID:-}"
    # Comma-separated list of Google Maps API service names to check
    # Common ones: maps-backend.googleapis.com, places-backend.googleapis.com,
    #              geocoding-backend.googleapis.com, directions-backend.googleapis.com
    GCP_MAPS_SERVICES="${GCP_MAPS_SERVICES:-maps-backend.googleapis.com,geocoding-backend.googleapis.com,places-backend.googleapis.com,directions-backend.googleapis.com}"
    
    # Max backup age in seconds
    MAX_AGE_SECONDS=$((S3_RETENTION_DAYS * 86400))
    
    # ============================================================================
    divider() {
        echo -e "${DIM}────────────────────────────────────────────────────────${NC}"
    }
    
    header() {
        echo ""
        echo -e "${BOLD}${CYAN}β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”${NC}"
        echo -e "${BOLD}${CYAN}β”‚  TFH Morning Health Check  β€”  $(date '+%Y-%m-%d %H:%M:%S')       β”‚${NC}"
        echo -e "${BOLD}${CYAN}β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜${NC}"
        echo ""
    }
    
    section() {
        echo -e "${BOLD}${BLUE}β–Έ $1${NC}"
        divider
    }
    
    # ============================================================================
    #  1. CRONITOR β€” Check monitor health
    # ============================================================================
    check_cronitor() {
        section "CRONITOR β€” Cron Job Health"
    
        if [ -z "$CRONITOR_API_KEY" ]; then
            echo -e "  ${FAIL} CRONITOR_API_KEY not set. Skipping."
            echo ""
            return
        fi
    
        # Fetch all monitors (paginated, first page β€” usually enough)
        response=$(curl -s -w "\n%{http_code}" \
            -u "${CRONITOR_API_KEY}:" \
            "https://cronitor.io/api/monitors?page=1")
    
        http_code=$(echo "$response" | tail -1)
        body=$(echo "$response" | sed '$d')
    
        if [ "$http_code" != "200" ]; then
            echo -e "  ${FAIL} Cronitor API returned HTTP ${http_code}"
            echo ""
            return
        fi
    
        # Parse monitors using jq
        # Cronitor status values: up, down, grace, new, paused
        # The status may also be in .passing or latest_event fields depending on API version
        total=$(echo "$body" | jq '.monitors | length')
    
        echo "$body" | jq -r '.monitors[] | "\(.status // .passing // "unknown")|\(.name // .key)|\(.key)"' | \
        while IFS='|' read -r status name key; do
            case "$status" in
                up|true)
                    echo -e "  ${PASS} ${name}"
                    ;;
                down|false)
                    echo -e "  ${FAIL} ${name}  ${RED}(DOWN)${NC}"
                    ;;
                grace)
                    echo -e "  ${WARN} ${name}  ${YELLOW}(grace period)${NC}"
                    ;;
                paused)
                    echo -e "  ${WARN} ${name}  ${YELLOW}(paused)${NC}"
                    ;;
                new)
                    echo -e "  ${WARN} ${name}  ${YELLOW}(new β€” no data yet)${NC}"
                    ;;
                *)
                    echo -e "  ${WARN} ${name}  ${DIM}(status: ${status})${NC}"
                    ;;
            esac
        done
    
        # Summary counts
        ok_count=$(echo "$body" | jq '[.monitors[] | select(.status == "up" or .passing == true)] | length')
        fail_count=$(echo "$body" | jq '[.monitors[] | select(.status == "down" or .passing == false)] | length')
        other_count=$((total - ok_count - fail_count))
    
        echo ""
        echo -e "  ${DIM}Total: ${total}  |  OK: ${ok_count}  |  Failing: ${fail_count}  |  Other: ${other_count}${NC}"
        echo ""
    }
    
    # ============================================================================
    #  2. AWS S3 β€” Backup bucket check
    # ============================================================================
    check_s3_backups() {
        section "AWS S3 β€” Backup Bucket (${S3_BUCKET})"
    
        # Check if AWS CLI is available
        if ! command -v aws &> /dev/null; then
            echo -e "  ${FAIL} AWS CLI not installed. Skipping."
            echo ""
            return
        fi
    
        # --- 2a. Check remaining credits (AWS billing / cost explorer) ---
        echo -e "  ${BOLD}Account Balance / Credits:${NC}"
    
        # Try to get credit balance from Cost Explorer
        # Note: This requires ce:GetCostAndUsage permission
        month_start=$(date '+%Y-%m-01')
        today=$(date '+%Y-%m-%d')
        credit_info=$(aws ce get-cost-and-usage \
            --time-period "Start=${month_start},End=${today}" \
            --granularity MONTHLY \
            --metrics "UnblendedCost" \
            --output json 2>&1)
    
        if echo "$credit_info" | jq -e '.ResultsByTime' &>/dev/null 2>&1; then
            raw_cost=$(echo "$credit_info" | jq -r '.ResultsByTime[0].Total.UnblendedCost.Amount // "0"')
            currency=$(echo "$credit_info" | jq -r '.ResultsByTime[0].Total.UnblendedCost.Unit // "USD"')
            # Format nicely β€” show $0.00 instead of -0.0000000006
            formatted_cost=$(printf "%.2f" "$raw_cost")
            echo -e "  ${DIM}Current month spend: ${currency} ${formatted_cost}${NC}"
        else
            echo -e "  ${WARN} Could not fetch billing data (need ce:GetCostAndUsage permission)"
            echo -e "  ${DIM}Tip: Check manually at https://console.aws.amazon.com/billing/${NC}"
        fi
    
        echo ""
    
        # --- 2b. List backups and check ages ---
        echo -e "  ${BOLD}Backup Objects (max ${S3_RETENTION_DAYS} days old):${NC}"
    
        # List all objects, sorted by date (newest first)
        objects=$(aws s3api list-objects-v2 \
            --bucket "$S3_BUCKET" \
            --query 'Contents[].{Key: Key, LastModified: LastModified, Size: Size}' \
            --output json 2>&1)
    
        if echo "$objects" | jq -e '.' &>/dev/null 2>&1 && [ "$objects" != "null" ]; then
            now_epoch=$(date +%s)
            total_objects=$(echo "$objects" | jq 'length')
            fresh_count=0
            stale_count=0
    
            echo "$objects" | jq -r '.[] | "\(.LastModified)|\(.Key)|\(.Size)"' | sort -r | \
            while IFS='|' read -r modified key size; do
                # Strip timezone suffix (+00:00 or Z) for date parsing
                clean_date=$(echo "$modified" | sed 's/+00:00$//' | sed 's/Z$//')
                mod_epoch=$(date -d "$clean_date" +%s 2>/dev/null || echo 0)
                age_seconds=$((now_epoch - mod_epoch))
                age_days=$((age_seconds / 86400))
                age_hours=$(( (age_seconds % 86400) / 3600 ))
    
                # Human-readable size
                if [ "$size" -gt 1073741824 ] 2>/dev/null; then
                    h_size="$(printf "%.2f GB" "$(echo "scale=2; $size/1073741824" | bc)")"
                elif [ "$size" -gt 1048576 ] 2>/dev/null; then
                    h_size="$(printf "%.1f MB" "$(echo "scale=1; $size/1048576" | bc)")"
                elif [ "$size" -gt 1024 ] 2>/dev/null; then
                    h_size="$(printf "%.1f KB" "$(echo "scale=1; $size/1024" | bc)")"
                else
                    h_size="${size} B"
                fi
    
                # Truncate filename: show just the filename part, max 50 chars
                short_name=$(basename "$key")
                if [ ${#short_name} -gt 50 ]; then
                    short_name="${short_name:0:47}..."
                fi
    
                # Check freshness
                if [ "$age_seconds" -gt "$MAX_AGE_SECONDS" ]; then
                    echo -e "  ${DIM}β–‘ ${short_name}  ${age_days}d ${age_hours}h  (pending deletion)${NC}"
                else
                    echo -e "  ${PASS} ${short_name}  ${DIM}${age_days}d ${age_hours}h  |  ${h_size}${NC}"
                fi
            done
    
            # Summary β€” use shell-based counting to avoid jq date issues
            stale=0
            fresh=0
            echo "$objects" | jq -r '.[].LastModified' | while read -r mod; do
                clean=$(echo "$mod" | sed 's/+00:00$//' | sed 's/Z$//')
                ep=$(date -d "$clean" +%s 2>/dev/null || echo 0)
                if [ $((now_epoch - ep)) -gt "$MAX_AGE_SECONDS" ]; then
                    echo "stale"
                else
                    echo "fresh"
                fi
            done | sort | uniq -c | while read -r count label; do
                if [ "$label" = "stale" ]; then stale=$count; fi
                if [ "$label" = "fresh" ]; then fresh=$count; fi
            done
    
            # Recount for display (subshell workaround)
            stale=$(echo "$objects" | jq -r '.[].LastModified' | while read -r mod; do
                clean=$(echo "$mod" | sed 's/+00:00$//' | sed 's/Z$//')
                ep=$(date -d "$clean" +%s 2>/dev/null || echo 0)
                [ $((now_epoch - ep)) -gt "$MAX_AGE_SECONDS" ] && echo 1
            done | wc -l)
            fresh=$((total_objects - stale))
    
            echo ""
            echo -e "  ${DIM}Total: ${total_objects}  |  Fresh: ${fresh}  |  Pending deletion: ${stale}${NC}"
    
            if [ "$fresh" -gt 0 ]; then
                echo -e "  ${PASS} Latest backup: $(echo "$objects" | jq -r 'sort_by(.LastModified) | last | .LastModified' | sed 's/+00:00$//')"
            fi
        else
            echo -e "  ${FAIL} Could not list objects in s3://${S3_BUCKET}"
            echo -e "  ${DIM}Error: ${objects}${NC}"
        fi
    
        echo ""
    }
    
    # ============================================================================
    #  3. GOOGLE CLOUD β€” Maps API usage & errors
    # ============================================================================
    check_google_maps() {
        section "GOOGLE CLOUD β€” Maps API Usage & Errors"
    
        # Check if gcloud is available
        if ! command -v gcloud &> /dev/null; then
            echo -e "  ${FAIL} gcloud CLI not installed. Skipping."
            echo ""
            return
        fi
    
        # Auto-detect project if not set
        if [ -z "$GCP_PROJECT_ID" ]; then
            GCP_PROJECT_ID=$(gcloud config get-value project 2>/dev/null)
        fi
    
        if [ -z "$GCP_PROJECT_ID" ]; then
            echo -e "  ${FAIL} GCP_PROJECT_ID not set and no default project configured."
            echo ""
            return
        fi
    
        echo -e "  ${DIM}Project: ${GCP_PROJECT_ID}${NC}"
        echo ""
    
        # --- 3a. Billing cost for current month ---
        echo -e "  ${BOLD}Current Month Billing:${NC}"
    
        # Use gcloud billing to get cost info (if available)
        # Alternative: use Cloud Billing API
        billing_account=$(gcloud billing projects describe "$GCP_PROJECT_ID" \
            --format="value(billingAccountName)" 2>/dev/null)
    
        if [ -n "$billing_account" ]; then
            echo -e "  ${DIM}Billing account: ${billing_account}${NC}"
            echo -e "  ${DIM}Tip: Detailed costs at https://console.cloud.google.com/billing/${NC}"
        else
            echo -e "  ${WARN} Could not fetch billing info"
        fi
        echo ""
    
        # --- 3b. API Usage (request count) last 24 hours ---
        echo -e "  ${BOLD}API Request Counts (last 24h):${NC}"
    
        # Calculate time window
        end_time=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
        start_time=$(date -u -d '24 hours ago' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || \
                     date -u -v-24H '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null)
    
        IFS=',' read -ra SERVICES <<< "$GCP_MAPS_SERVICES"
        for service in "${SERVICES[@]}"; do
            service=$(echo "$service" | xargs)  # trim whitespace
    
            # Query the monitoring API for request count
            # Metric: serviceruntime.googleapis.com/api/request_count
            result=$(gcloud monitoring time-series list \
                --project="$GCP_PROJECT_ID" \
                --filter="metric.type=\"serviceruntime.googleapis.com/api/request_count\" AND resource.labels.service=\"${service}\"" \
                --interval-start-time="$start_time" \
                --interval-end-time="$end_time" \
                --format=json 2>/dev/null)
    
            if [ $? -eq 0 ] && echo "$result" | jq -e '.' &>/dev/null 2>&1; then
                # Sum all data points
                total_requests=$(echo "$result" | jq '[.[].points[].value.int64Value // 0 | tonumber] | add // 0')
    
                # Count errors (4xx + 5xx)
                error_requests=$(echo "$result" | jq '
                    [.[] |
                        select(.metric.labels.response_code_class == "4xx" or
                               .metric.labels.response_code_class == "5xx") |
                        .points[].value.int64Value // 0 | tonumber
                    ] | add // 0')
    
                if [ "$error_requests" -gt 0 ]; then
                    echo -e "  ${WARN} ${service}"
                    echo -e "     Requests: ${total_requests}  |  ${RED}Errors: ${error_requests}${NC}"
                elif [ "$total_requests" -gt 0 ]; then
                    echo -e "  ${PASS} ${service}"
                    echo -e "     ${DIM}Requests: ${total_requests}  |  Errors: 0${NC}"
                else
                    echo -e "  ${DIM}  β—‹ ${service} β€” no requests in last 24h${NC}"
                fi
            else
                # Fallback: try using curl directly with the monitoring API
                token=$(gcloud auth print-access-token 2>/dev/null)
                if [ -n "$token" ]; then
                    api_result=$(curl -s \
                        -H "Authorization: Bearer $token" \
                        "https://monitoring.googleapis.com/v3/projects/${GCP_PROJECT_ID}/timeSeries?filter=metric.type%3D%22serviceruntime.googleapis.com%2Fapi%2Frequest_count%22%20AND%20resource.labels.service%3D%22${service}%22&interval.endTime=${end_time}&interval.startTime=${start_time}&aggregation.alignmentPeriod=86400s&aggregation.perSeriesAligner=ALIGN_SUM")
    
                    if echo "$api_result" | jq -e '.timeSeries' &>/dev/null 2>&1; then
                        total=$(echo "$api_result" | jq '[.timeSeries[].points[].value.int64Value // "0" | tonumber] | add // 0')
                        echo -e "  ${PASS} ${service}  β€”  ${total} requests"
                    else
                        echo -e "  ${DIM}  β—‹ ${service} β€” no data or insufficient permissions${NC}"
                    fi
                else
                    echo -e "  ${FAIL} ${service} β€” could not authenticate"
                fi
            fi
        done
    
        # --- 3c. Check for API errors specifically ---
        echo ""
        echo -e "  ${BOLD}Error Summary (4xx/5xx, last 24h):${NC}"
    
        token=$(gcloud auth print-access-token 2>/dev/null)
        if [ -n "$token" ]; then
            # Query for error responses across all Maps APIs
            error_result=$(curl -s \
                -H "Authorization: Bearer $token" \
                "https://monitoring.googleapis.com/v3/projects/${GCP_PROJECT_ID}/timeSeries?filter=metric.type%3D%22serviceruntime.googleapis.com%2Fapi%2Frequest_count%22%20AND%20metric.labels.response_code_class%3Done_of(%224xx%22%2C%225xx%22)&interval.endTime=${end_time}&interval.startTime=${start_time}&aggregation.alignmentPeriod=86400s&aggregation.perSeriesAligner=ALIGN_SUM")
    
            if echo "$error_result" | jq -e '.timeSeries[0]' &>/dev/null 2>&1; then
                echo "$error_result" | jq -r '.timeSeries[] | 
                    "\(.resource.labels.service)|\(.metric.labels.response_code_class)|\(.points[0].value.int64Value // 0)"' | \
                while IFS='|' read -r svc code count; do
                    echo -e "  ${FAIL} ${svc}: ${count} ${code} errors"
                done
            else
                echo -e "  ${PASS} No API errors in the last 24 hours."
            fi
        else
            echo -e "  ${WARN} Could not authenticate to check errors"
        fi
    
        echo ""
    }
    
    # ============================================================================
    #  MAIN
    # ============================================================================
    
    header
    check_cronitor
    check_s3_backups
    check_google_maps
    
    echo -e "${BOLD}${CYAN}──────────────────────────────────────────────────────${NC}"
    echo -e "${DIM}Check complete. $(date '+%H:%M:%S')${NC}"
    echo ""
    

    Gebruik

    Handmatig uitvoeren

    morning
    

    Dit alias is toegevoegd aan ~/.bashrc:

    alias morning="set -a && source ~/tfh-check/morning-check.env && set +a && ~/tfh-check/morning-check.sh"
    

    Automatisch bij opstarten

    Het script draait automatisch bij het opstarten van de laptop via Opstartapplicaties (Startup Applications) met het volgende commando:

    gnome-terminal -- bash -c "set -a && source ~/tfh-check/morning-check.env && set +a && ~/tfh-check/morning-check.sh; echo; read -p 'Press Enter to close...'"
    

    Daarnaast draait het script eenmalig per dag bij het openen van de eerste terminal via een markerbestand in /tmp.

    Voorbeelduitvoer

    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚  TFH Morning Health Check  β€”  2026-02-12 11:14:06       β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    
    β–Έ CRONITOR β€” Cron Job Health
    ────────────────────────────────────────────────────────
      βœ” root /usr/sbin/csf --lfd restart > 2
      βœ” systemctl restart redis.service
      βœ” /usr/local/bin/server-health-check.sh
      βœ” sh /home/freighther/tfh-backup-database.sh
      βœ” sh /home/freighther/...-database.sh > ...
    
      Total: 5  |  OK: 5  |  Failing: 0  |  Other: 0
    
    β–Έ AWS S3 β€” Backup Bucket (tfh-backup-2)
    ────────────────────────────────────────────────────────
      Account Balance / Credits:
      Current month spend: USD 0.00
    
      Backup Objects (max 3 days old):
      βœ” ...DATABASE-20260212_110001.tar.gz  0d 0h  |  1.39 GB
      βœ” ...DATABASE-20260212_090001.tar.gz  0d 2h  |  1.39 GB
      βœ” ...DATABASE-20260212_060002.tar.gz  0d 5h  |  1.39 GB
      ...
      β–‘ ...DATABASE-20260209_110001.tar.gz  3d 0h  (pending deletion)
    
      Total: 31  |  Fresh: 27  |  Pending deletion: 4
    
    β–Έ GOOGLE CLOUD β€” Maps API Usage & Errors
    ────────────────────────────────────────────────────────
      Project: tfh-maps-477109
    
      API Request Counts (last 24h):
      βœ” maps-backend.googleapis.com  β€”  1155 requests
      βœ” places-backend.googleapis.com  β€”  426 requests
      βœ” directions-backend.googleapis.com  β€”  105 requests
    
      Error Summary (4xx/5xx, last 24h):
      βœ” No API errors in the last 24 hours.
    

  • Gebruiker Toevoegen aan Chrome-extensie
    nikitaskliarovN nikitaskliarov

    Gebruiker Toevoegen aan Chrome-extensie

    Deze handleiding beschrijft hoe een nieuwe gebruiker correct wordt geconfigureerd zodat de Realtime-notificaties (pop-ups) verschijnen in de browser.


    Stap 1: Verbinding maken met de Interne Server

    Maak verbinding via SSH β€” dit kan alleen vanuit hetzelfde netwerk, anders is de PC extern niet te vinden:

    ssh root@192.168.1.25
    

    Als je SSH-key nog niet in de authorized keys lijst staat, wordt er om een wachtwoord gevraagd. Dit kun je aanvragen bij Nikita Skliarov.


    Stap 2: Gebruiker Toevoegen aan extensions.json

    Bewerk het bestand:

    nano /home/internal/internal-server/data/extensions.json
    

    Voeg een nieuw object toe in het volgende formaat:

    {
      "extension": "",
      "name": "",
      "user": "",
      "uri": "{{user}}@mv.voicedata.nl",
      "ip": ""
    }
    
    Veld Uitleg
    extension Het gewenste toestelnummer, toegewezen door Voicedata.
    name Weergavenaam in de Chrome-extensie.
    user Zoek op het Voicedata Managed Voice panel in de tab Kanalen β€” de gebruikersnaam naast het extensienummer is je user.
    uri Begint met de user-waarde (hetzelfde als vorige veld) en eindigt met @mv.voicedata.nl.
    ip IP-adres van het toestel. Dit vind je op het toestel zelf: druk op OK en zoek het IPv4 IP-adres of in een dezelfde lijst van Kanalen.

    Stap 3: Herstart de Interne Service

    systemctl restart internal && systemctl status internal
    

    Mocht de service failed zijn:

    • Controleer of de JSON geldig is. De meest voorkomende fouten zijn een komma aan het einde van de lijst, aan het einde van values, of aan het einde van een item.
    • Gebruik cat /home/internal/internal-server/data/extensions.json om de inhoud te bekijken en online te valideren via een JSON-validator.
    • Gebruik journalctl om verder te onderzoeken wat het probleem is.

    Stap 4: Toestel Instellingen Openen

    Open de volgende URL in je browser (vervang {{TOESTEL IP ADDRESS}} door het IP-adres van het toestel):

    https://{{TOESTEL IP ADDRESS}}/servlet?m=mod_data&p=features-remotecontrl&q=load
    

    Inloggegevens:

    • Gebruikersnaam: admin
    • Wachtwoord: 0 + ons Voicedata-klantnummer (te vinden linksboven als je Managed Voice opent)

    Stap 5: Remote Control Configureren

    Ga naar Features (hoofdtab) β†’ Remote Control (subtab).

    Stel de volgende velden in op het server IP-adres:

    Veld Waarde
    Push XML Server IP Address 192.168.1.25
    Action URI Allow IP List 192.168.1.25

    ⚠️ Vergeet niet op Confirm te drukken!


    Stap 6: Action URL's Configureren

    Blijf in de Features tab en ga naar de subtab Action URL (links).

    Stel de volgende URL's in. Vervang ip door het IP-adres van het toestel!

    Incoming Call:

    ip:3000/phone/incoming?active_host=$active_host&active_user=$active_user&ip=$ip&remote=$remote&local=$local&display_remote=$display_remote&display_local=$display_local&call_id=$call_id&called_number=$calledNumber
    

    Outgoing Call:

    ip:3000/phone/outgoing?active_host=$active_host&active_user=$active_user&ip=$ip&remote=$remote&local=$local&display_remote=$display_remote&display_local=$display_local&call_id=$call_id&called_number=$calledNumber
    

    Missed Call:

    ip:3000/phone/missed?active_user=$active_user&call_id=$call_id
    

    Established:

    ip:3000/phone/established?active_user=$active_user&call_id=$call_id
    

    ⚠️ Vergeet niet ip te vervangen door het daadwerkelijke IP-adres van het toestel!

    ⚠️ Vergeet niet op Confirm te drukken!


    Stap 7: Chrome-extensie Installeren bij de Medewerker

    Installeer de Chrome-extensie op de PC van de medewerker. Volg hiervoor de installatie-instructies:

    πŸ”— Installatie Telefoon Extensie – TFH Docs


    Als alles goed is verlopen, zou je nu een nieuwe gebruiker moeten zien verschijnen in de Chrome-extensie.


  • Internal-server (HP pc in het kantoor en hoe deze samewerkt met live.thefreighthero.nl en chrome extensie)
    nikitaskliarovN nikitaskliarov

    Internal Server Documentation The Freight Hero


    1. Algemene Informatie

    De Internal-Server is een fysieke HP-server op de kantoorlocatie. Deze dient als de cruciale schakel (proxy) tussen de kantoor-PBX (VoIP-telefooncentrale) en het cloudplatform Realtime.

    Hoofdfunctie: Het monitoren van de PBX via webhooks/events, het verwerken van deze data en het doorsturen van notificaties naar de cloud-API (live.thefreighthero.nl). Dit zorgt ervoor dat inkomende en uitgaande gesprekken zichtbaar worden in de browsers van medewerkers.

    2. Toegang en Beheer

    • Fysieke Toegang: Indien SSH niet beschikbaar is, kan beheer plaatsvinden via een directe monitor- en toetsenbordverbinding.
    • Wachtwoordherstel: Kan worden uitgevoerd door de GRUB-bootloader te onderbreken tijdens het opstarten en de single user mode (of init=/bin/bash) te gebruiken.
    • SSH-verbinding: Toegang via het lokale netwerk: ssh root@192.168.1.25.

    3. Services en Configuratie

    Het systeem maakt gebruik van systemd om de applicatie te beheren.

    • Service-naam: internal.service
    • Configuratiebestand: /etc/systemd/system/internal.service
      • Hierin staat de cruciale INTERNAL_API_KEY. Zonder de juiste key zal de cloudserver alle verzoeken weigeren met een 403 Forbidden error.
    • Applicatie-locatie: /home/internal/internal-server/bin/www (Node.js).

    4. Veelvoorkomende Commando's

    • Logs inzien: journalctl -u internal -f (essentieel voor het debuggen van gesprekken).
    • Service herstarten: systemctl restart internal.
    • Configuratie herladen: systemctl daemon-reload (nodig na wijzigingen in het servicebestand).

    Cruciaal: De INTERNAL_API_KEY moet exact overeenkomen met de sleutel die door de cloudserver wordt verwacht. Deze is te verifiΓ«ren in de omgeving van de draaiende Node-processen op de VPS server (root@thefreighthero.com) via cat /proc/[PID]/environ waar PID is van realtime service (gebruik ps aux hiervoor)


  • TFH Server Backup & Cron Dashboard Documentatie
    nikitaskliarovN nikitaskliarov

    TFH Server Backup & Cron Dashboard Documentatie

    Laatst bijgewerkt: 4 februari 2026
    Auteur: Nikita Skliarov


    1. Database Backup naar Amazon S3

    Overzicht

    De database (freighther_nl, ~16 GB) wordt gedumpt met mysqldump, gecomprimeerd met gzip -9 en geΓΌpload naar Amazon S3. Gecomprimeerde backups zijn ongeveer 1,4 GB per stuk.

    Configuratie

    Instelling Waarde
    Script locatie /home/freighther/tfh-backup-database.sh
    S3 bucket tfh-backup-2
    S3 regio eu-north-1 (Stockholm)
    AWS IAM gebruiker tfh-backup-user
    AWS credentials Opgeslagen via aws configure (root)
    Logbestand /home/freighther/backup.log
    Foutenlog /home/freighther/backup_errors.log

    AWS IAM Beleid

    De tfh-backup-user heeft een minimaal inline beleid (tfh-backup-s3-access) met alleen deze rechten op de tfh-backup-2 bucket:

    • s3:PutObject
    • s3:GetObject
    • s3:ListBucket

    Cron Schema

    Schema Omschrijving
    0 9-17/2 * * * Elke 2 uur, 9:00 – 17:00
    0 18,22,2,6 * * * Elke 4 uur, 18:00 – 8:00

    Handmatige Commando's

    # Backup handmatig uitvoeren
    /home/freighther/tfh-backup-database.sh
    
    # Backup log bekijken
    tail -f /home/freighther/backup.log
    
    # Backups in S3 bekijken
    aws s3 ls s3://tfh-backup-2/thefreighthero.nl/
    
    # Backup downloaden vanuit S3
    aws s3 cp s3://tfh-backup-2/thefreighthero.nl/BESTANDSNAAM.tar.gz /tmp/
    
    # Database herstellen vanuit backup
    gunzip < /tmp/BESTANDSNAAM.tar.gz | mysql -u freighther_nl -p freighther_nl
    

    AWS CLI Opnieuw Configureren

    Als de credentials geroteerd moeten worden:

    aws configure
    # Voer nieuwe Access Key ID, Secret Access Key in, regio: eu-north-1, output: json
    

    Nieuwe access key aanmaken: AWS Console β†’ IAM β†’ Users β†’ tfh-backup-user β†’ Security credentials β†’ Create access key


    2. Crontab Guru Dashboard

    Overzicht

    Een zelf-gehost webdashboard voor het monitoren en beheren van cron jobs. GeΓ―nstalleerd via Cronitor CLI. De webinterface op cronitor.io toont logs en jobstatus, maar kan geen jobs verwijderen of bewerken β€” alleen crontab -e op de server kan jobs aanpassen.

    Service

    Instelling Waarde
    Service naam crontab-guru
    Service bestand /etc/systemd/system/crontab-guru.service
    Poort 9000 (alleen localhost)
    Binary /usr/bin/cronitor
    Telemetrie Uitgeschakeld

    Service Beheren

    # Status controleren
    systemctl status crontab-guru
    
    # Herstarten
    systemctl restart crontab-guru
    
    # Stoppen
    systemctl stop crontab-guru
    
    # Logs bekijken
    journalctl -u crontab-guru -f
    

    Dashboard Openen

    Het dashboard is alleen bereikbaar via SSH tunnel β€” het is niet toegankelijk via het internet.

    Vanaf je lokale machine (laptop):

    ssh -L 9000:localhost:9000 root@thefreighthero.com
    

    Open daarna in je browser: http://localhost:9000

    Log in met het dashboard gebruikersnaam en wachtwoord.

    Dashboard Wachtwoord Wijzigen

    cronitor configure --dash-username NIEUWE_GEBRUIKERSNAAM --dash-password NIEUW_WACHTWOORD
    systemctl restart crontab-guru
    

    Cronitor.io Cloud Interface

    Het dashboard is ook gekoppeld aan cronitor.io β€” een online interface waar je logs en jobstatus kunt bekijken. Dit is alleen-lezen: je kunt geen jobs verwijderen of bewerken via de website.

    Voordelen:

    • Meldingen ontvangen als een backup of cron job mislukt
    • Overzicht van alle jobs zonder SSH tunnel nodig

    Wat Cronitor ziet:

    • Job namen, schema's, uitvoertijden, succes/faal status

    Wat Cronitor NIET ziet:

    • Geen database inhoud, geen bestanden, geen wachtwoorden
    • Geen SSH toegang, geen inkomend verkeer (server stuurt alleen data naar Cronitor, niet andersom)

    Inloggen: cronitor.io met je Cronitor account
    Meldingen instellen: Cronitor.io β†’ Alerts β†’ configureer e-mail of Slack

    Beveiliging

    • Dashboard luistert alleen op localhost:9000 β€” niet bereikbaar vanaf het internet
    • Toegang vereist een SSH tunnel (= SSH inloggegevens nodig)
    • Dashboard login vereist een apart gebruikersnaam/wachtwoord
    • De online Cronitor webinterface is alleen-lezen (logs bekijken, geen jobs bewerken)
    • Cronitor account beveiligen met een sterk wachtwoord en bij voorkeur 2FA

    3. S3 Kosten & Retentiebeleid

    Retentiebeleid

    Er is een S3 lifecycle rule ingesteld die backups automatisch verwijdert na 2 dagen. Hierdoor zijn altijd de backups van vandaag en gisteren beschikbaar, en alles ouder wordt automatisch opgeruimd.

    Lifecycle rule configuratie:

    • Naam: delete-old-backups
    • Scope: Alle objecten in de bucket
    • Actie: Expire current versions of objects
    • Na: 2 dagen

    Kostenberekening (S3 Standard, eu-north-1 Stockholm)

    Onderdeel Prijs
    Opslag $0,023 per GB per maand
    PUT verzoeken (uploads) $0,005 per 1.000 verzoeken
    DELETE verzoeken (verwijdering) Gratis
    Lifecycle expiratie acties Gratis

    Geschatte maandelijkse kosten:

    Onderdeel Berekening Kosten/maand
    Opslag ~9 backups/dag Γ— 2 dagen Γ— 1,5 GB = ~27 GB ~$0,62
    PUT verzoeken ~270 uploads/maand ~$0,001
    Totaal ~$0,62/maand

    De kosten stijgen evenredig als de database groeit. Bij een verdubbeling naar 3 GB per backup worden de kosten ~$1,10/maand.


    4. Overzicht Alle Cron Jobs

    # Database backup: elke 2 uur tijdens kantooruren
    0 9-17/2 * * *    /home/freighther/tfh-backup-database.sh >> /home/freighther/backup.log 2>&1
    
    # Database backup: elke 4 uur buiten kantooruren
    0 18,22,2,6 * * * /home/freighther/tfh-backup-database.sh >> /home/freighther/backup.log 2>&1
    
    # Server health check: dagelijks om 6:00
    0 6 * * *         /usr/local/bin/server-health-check.sh
    
    # Redis herstarten: elk uur
    0 * * * *         systemctl restart redis.service
    

    Cron Jobs Bewerken

    crontab -e          # Cron jobs bewerken
    crontab -l          # Huidige cron jobs bekijken
    


  • Ubuntu Pro Setup Handleiding
    nikitaskliarovN nikitaskliarov

    Ubuntu Pro Setup & Documentatie

    Huidige Status

    • Machine: HP Laptop (Nikita's werk laptop)
    • Subscription: Ubuntu Pro - free personal subscription
    • Account: helpdesk@thefreighthero.com
    • Machines gebruikt: 1/5
    • Kosten: €0,00
    • Verloopt: Nooit

    Wat doet Ubuntu Pro?

    Automatische bescherming:

    • βœ… ESM Apps - Extended Security Maintenance voor applicaties
    • βœ… ESM Infra - Extended Security Maintenance voor infrastructuur
    • βœ… Livepatch - Kernel updates zonder reboot

    Voordelen:

    • 10 jaar beveiligingsupdates (i.p.v. 5 jaar)
    • 23.000+ extra packages krijgen security patches
    • Kernel updates zonder systeem te rebooten
    • Minder downtime tijdens werk

    Dagelijkse Routine

    Niets verandert!

    Blijf gewoon je normale update routine doen:

    sudo apt update && sudo apt upgrade
    

    ESM updates zijn automatisch inbegrepen. Geen extra stappen nodig.

    Optioneel: Maandelijkse check

    Als je wilt verifiΓ«ren dat alles werkt:

    pro status
    canonical-livepatch status
    

    Let op: Dit is niet verplicht - het systeem werkt automatisch op de achtergrond.


    Setup Instructies voor Nieuwe IT Laptop

    Stap 1: Ubuntu installeren

    Installeer Ubuntu 24.04 LTS (of nieuwste LTS versie)

    Stap 2: Ubuntu Pro koppelen

    sudo pro attach <token-uit-password-manager>
    

    Het systeem zal automatisch ESM en Livepatch enablen.

    Stap 3: Verifieer installatie

    pro status
    

    Verwachte output:

    SERVICE          ENTITLED  STATUS       DESCRIPTION
    esm-apps         yes       enabled      Expanded Security Maintenance for Applications
    esm-infra        yes       enabled      Expanded Security Maintenance for Infrastructure
    livepatch        yes       enabled      Canonical Livepatch service
    

    Stap 4: Eerste update

    sudo apt update && sudo apt upgrade
    

    Credential Opslag

    Bewaar dit in password manager:

    Service: Ubuntu Pro Free Personal
    Account: helpdesk@thefreighthero.com
    Password: [wachtwoord voor Ubuntu One account]
    Token: [de attach token - zie dashboard]
    Dashboard URL: https://ubuntu.com/pro/dashboard
    Machines limiet: 5
    Huidige machines: HP Laptop
    

    Troubleshooting

    Pro status checken

    pro status
    

    Livepatch status checken

    canonical-livepatch status
    

    Pro opnieuw koppelen (bij problemen)

    sudo pro detach
    sudo pro attach <token>
    

    Welke packages worden beschermd?

    apt list --installed | grep esm
    

    Disaster Recovery

    Als laptop kapot gaat of gestolen wordt:

    1. Nieuwe Ubuntu installatie

      • Installeer Ubuntu 24.04 LTS
      • Herstel backup van oude laptop
    2. Pro opnieuw activeren

      sudo pro attach <token>
      
    3. Verifieer

      pro status
      

    Belangrijk: Je kunt dezelfde token gebruiken op een nieuwe machine. De oude machine wordt automatisch losgekoppeld na een tijdje inactiviteit, of je kunt deze handmatig loskoppelen via het dashboard.

    Dashboard beheer

    • Ga naar: https://ubuntu.com/pro/dashboard
    • Login met: helpdesk@thefreighthero.com
    • Hier zie je alle gekoppelde machines
    • Je kunt machines handmatig loskoppelen indien nodig

    Belangrijke Notities

    Wat Ubuntu Pro NIET verandert:

    • ❌ Geen nieuwe cron jobs nodig
    • ❌ Geen monitoring scripts nodig
    • ❌ Geen extra onderhoudstaken
    • ❌ Geen veranderingen in dagelijkse workflow

    Wat Ubuntu Pro WEL doet:

    • βœ… Automatisch kernel patches toepassen (zonder reboot)
    • βœ… Meer security updates in normale apt upgrade
    • βœ… Langere support periode (10 jaar i.p.v. 5)
    • βœ… Bescherming voor development packages (PHP, Composer, FFmpeg, etc.)

    Gratis tier limieten:

    • Maximum 5 machines
    • Voor persoonlijk of klein zakelijk gebruik
    • Alle enterprise features inbegrepen
    • Geen support contract (community support only)

    Voor Volgende IT Medewerker

    Onboarding checklist:

    • Ubuntu Pro token locatie in password manager
    • Dashboard toegang (
      helpdesk@thefreighthero.com)
    • Verificeer dat HP Laptop nog steeds attached is
    • Test
      pro status command
    • Review deze documentatie

    Belangrijker dan Ubuntu Pro:

    Zorg dat je ook documentatie hebt voor:

    • πŸ”‘ SSH keys voor server toegang
    • πŸ”‘ Database credentials
    • πŸ”‘ API keys (Brevo, Retell AI, Google Maps, etc.)
    • πŸ”‘ GitHub/GitLab toegang
    • πŸ”‘ Hosting/server provider accounts
    • πŸ”‘ Domain registrar toegang

    Ubuntu Pro is nice-to-have, maar bovenstaande zijn mission-critical!


    Updates & Onderhoud

    Normale updates (wanneer systeem vraagt):

    sudo apt update && sudo apt upgrade
    

    Kernel updates:

    • Worden automatisch toegepast door Livepatch
    • Meestal geen reboot nodig
    • Als reboot WEL nodig: je krijgt een notificatie

    Distributie upgrades (bijv. 24.04 β†’ 26.04):

    sudo do-release-upgrade
    

    Ubuntu Pro blijft actief na distributie upgrade.


    Contact & Support

    Ubuntu Pro Dashboard

    https://ubuntu.com/pro/dashboard

    Canonical Support

    • Community forum: https://discourse.ubuntu.com/
    • Documentatie: https://ubuntu.com/pro/docs

    Interne vragen

    Contact Nikita of check deze documentatie eerst.


    Changelog

    Datum Wijziging Door
    22-01-2026 InitiΓ«le setup HP Laptop Nikita
    22-01-2026 Documentatie aangemaakt Nikita

    Snelle Referentie

    # Status checken
    pro status
    
    # Livepatch status
    canonical-livepatch status
    
    # Normale updates (inclusief ESM)
    sudo apt update && sudo apt upgrade
    
    # Machine koppelen
    sudo pro attach <token>
    
    # Machine loskoppelen
    sudo pro detach
    
    # Alle Pro services bekijken
    pro status --all
    
    # Specifieke service enablen (normaal niet nodig)
    sudo pro enable <service-name>
    

Ledenlijst

nikitaskliarovN nikitaskliarov
  • Login

  • Login or register to search.
  • First post
    Last post
0
  • CategorieΓ«n
  • Recent
  • Tags
  • Populair
  • Gebruikers
  • Groepen
  • Zoeken