Déc 302025
 

8 years ago I had come with a dirty solution to graph my router bandwidth usage (here).

Now, we have AI (chatgpt here) and with a single prompt (« over ssh, retrieve my ddwrt bandwidth usage »), it provided me with a neat script/solution.

I then get a web page, refreshed hourly.

#!/bin/bash
set -e

# ==================================================
# Configuration
# ==================================================
ROUTER_IP="192.168.1.250"
ROUTER_USER="root"
SSH_KEY="$HOME/.ssh/id_ed25519"
OUTPUT_HTML="/var/www/html/traffic.html"

MONTH=$(date +%m)
YEAR=$(date +%Y)
NVRAM_KEY="traff-$MONTH-$YEAR"

GENERATED_AT=$(date '+%d/%m/%Y à %H:%M')

# ==================================================
# Récupération des données depuis le routeur
# ==================================================
RAW_DATA=$(ssh -i "$SSH_KEY" -q "$ROUTER_USER@$ROUTER_IP" "nvram get $NVRAM_KEY")

# Total mensuel [incoming:outgoing]
TOTAL=$(echo "$RAW_DATA" | grep -o '\[.*\]')

# Données journalières uniquement
DATA=$(echo "$RAW_DATA" | sed 's/\[.*\]//')

# ==================================================
# Génération de la page HTML (écriture root)
# ==================================================
sudo tee "$OUTPUT_HTML" > /dev/null <<EOF
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Trafic DD-WRT - $MONTH/$YEAR</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
  body {
    font-family: Arial, sans-serif;
    background: #f4f4f9;
    margin: 40px;
  }
  h1 {
    text-align: center;
  }
  #chart-container {
    max-width: 1100px;
    margin: auto;
  }
  .generated {
    position: fixed;
    bottom: 10px;
    right: 15px;
    font-size: 0.85em;
    color: #666;
  }
</style>
</head>
<body>

<h1>Trafic journalier DD-WRT – $MONTH/$YEAR</h1>

<div id="chart-container">
  <canvas id="trafficChart"></canvas>
</div>

<p style="text-align:center; font-size:1.1em;">
  <strong>Total du mois :</strong> $TOTAL MB (incoming : outgoing)
</p>

<div class="generated">
  Généré le $GENERATED_AT
</div>

<script>
const rawData = "$DATA";

const incoming = [];
const outgoing = [];

rawData.trim().split(' ').forEach(pair => {
  const [inVal, outVal] = pair.split(':').map(Number);
  incoming.push(inVal);
  outgoing.push(outVal);
});

const labels = Array.from({ length: incoming.length }, (_, i) => 'Jour ' + (i + 1));

const ctx = document.getElementById('trafficChart').getContext('2d');
new Chart(ctx, {
  type: 'bar',
  data: {
    labels: labels,
    datasets: [
      {
        label: 'Incoming (MB)',
        data: incoming,
        backgroundColor: 'rgba(54, 162, 235, 0.7)'
      },
      {
        label: 'Outgoing (MB)',
        data: outgoing,
        backgroundColor: 'rgba(255, 99, 132, 0.7)'
      }
    ]
  },
  options: {
    responsive: true,
    plugins: {
      legend: { position: 'top' },
      tooltip: {
        callbacks: {
          label: ctx =>
            ctx.dataset.label + ': ' + ctx.raw.toLocaleString() + ' MB'
        }
      }
    },
    scales: {
      x: { stacked: true },
      y: {
        stacked: true,
        beginAtZero: true,
        title: {
          display: true,
          text: 'MB'
        }
      }
    }
  }
});
</script>

</body>
</html>
EOF
Déc 212025
 

In previous articles, i have shared how to backup/restore files using Rclone.

Now for fun (and no profit) lets decrypt these files outside of Rclone using the password and salt from the rclone config (in rclone.conf).

Have a look at this github repo here.

Déc 212025
 

In previous article, i have explained how to rclone one local folder to a remote folder.

While doing so i had enable filename encryption.

It appears that it brings nothing but complexity (content encryption is all that matters to me) and i now want to revert to original filenames while not having to reupload it all.

Here below a dry run script (to test safely) and a real run script – all this from the synology command line.

Note that we need to suffix files with .bin or else rclone will not recognize the files as encrypted.

#!/bin/bash

REMOTE_CRYPT="ovhcrypt:"
REMOTE_RAW="ovhftp:/backup/"

echo "=== DRY RUN (DECRYPT NAMES + ADD .BIN) ==="

rclone lsf -R "$REMOTE_RAW" | while read -r path; do

    # Ignore les dossiers
    [[ "$path" == */ ]] && continue

    # Sépare dossier et fichier
    dir="${path%/*}"
    filename="${path##*/}"

    # Filtre : noms chiffrés rclone (base32, >=16 chars)
    if ! [[ "$filename" =~ ^[a-z0-9]{16,}$ ]]; then
        echo "Skip (not encrypted): $path"
        continue
    fi

    # Décrypte uniquement le nom du fichier
    decfile=$(rclone cryptdecode "$REMOTE_CRYPT" "$filename" 2>/dev/null | awk '{print $NF}')

    if [[ -z "$decfile" ]]; then
        echo "Skip (cryptdecode failed): $path"
        continue
    fi

    # Ajoute l'extension .bin
    decfile_bin="${decfile}.bin"

    # Reconstruit le chemin final
    if [[ "$dir" == "$path" ]]; then
        decpath="$decfile_bin"
    else
        decpath="$dir/$decfile_bin"
    fi

    echo "DRY RUN: Would rename: $path  -->  $decpath"

done

#!/bin/bash

REMOTE_CRYPT="ovhcrypt:"
REMOTE_RAW="ovhftp:/backup/"

echo "=== REAL RUN (DECRYPT NAMES + ADD .BIN) ==="

rclone lsf -R "$REMOTE_RAW" | while read -r path; do

    # Ignore les dossiers
    [[ "$path" == */ ]] && continue

    # Sépare dossier et fichier
    dir="${path%/*}"
    filename="${path##*/}"

    # Filtre : noms chiffrés rclone (base32, >=16 chars)
    if ! [[ "$filename" =~ ^[a-z0-9]{16,}$ ]]; then
        echo "Skip (not encrypted): $path"
        continue
    fi

    # Décrypte uniquement le nom du fichier
    decfile=$(rclone cryptdecode "$REMOTE_CRYPT" "$filename" 2>/dev/null | awk '{print $NF}')

    if [[ -z "$decfile" ]]; then
        echo "Skip (cryptdecode failed): $path"
        continue
    fi

    # Ajoute l'extension .bin pour compatibilité ovhcrypt2
    decfile_bin="${decfile}.bin"

    # Reconstruit le chemin final
    if [[ "$dir" == "$path" ]]; then
        decpath="$decfile_bin"
    else
        decpath="$dir/$decfile_bin"
    fi

    echo "Renaming: $path  -->  $decpath"

    # Renommage optimisé et OVH-friendly
    rclone moveto "$REMOTE_RAW$path" "$REMOTE_RAW$decpath" \
        --transfers 1 \
        --checkers 1 \
        --multi-thread-streams 0 \
        --ftp-concurrency 1 \
        --low-level-retries 3 \
        --retries 3 \
        --stats 10s

    # Petite pause pour éviter les 421
    sleep 0.2

done

Note, once all crypted filenames have been renamed to non encrypted filenames (with .bin extension), i stopped using ovhcrypt remote and switched to ovhcrypt2 remote.

my command is rclone sync /volume1/photo ovhcrypt2: –progress .

[ovhcrypt]
type = crypt
remote = ovhftp:/backup
directory_name_encryption = false
password = xxx
password2 = xxx

[ovhftp]
type = ftp
host = ftp.xxx.fr
user = username
pass = password

[ovhcrypt2]
type = crypt
remote = ovhftp:/backup
filename_encryption = off
directory_name_encryption = false
password = xxx
password2 = xxx
Déc 202025
 

For near 20 years, i am backuping my photos onto my synology (ds10x, ds20x, and now ds21x).

The risk is to lose them all because of crash or during a migration from one disk/model to another disk/model.

I decided to use rclone (running on my synology) to backup this local storage to a remote storage.

               ┌──────────────────────────────────────────┐
               │              Synology NAS                │
               │------------------------------------------│
               │  /volume1/photo                          │
               │     (unencrypted)                        │
               └───────────────┬──────────────────────────┘
                               │
                               │  rclone copy / sync
                               │  (unencrypted)
                               ▼
               ┌──────────────────────────────────────────┐
               │               rclone                     │
               │------------------------------------------│
               │  Remote 1 : ovhftp:                      │
               │     → FTP connection tp OVH              │
               │     → primary                            │
               │                                          │
               │  Remote 2 : ovhcrypt:                    │
               │     → crypt layer                        │
               │     → AES‑256 CTR + scrypt               │
               │     → content crypted                    │
               │     → based on : ovhftp:/backup          │
               │                                          │
               │  rclone.conf                             │
               │     → contains keys + remotes config     │
               └───────────────┬──────────────────────────┘
                               │
                               │  crypted files
                               │  (filenames scrambled)
                               ▼
               ┌──────────────────────────────────────────┐
               │                OVH FTP                   │
               │------------------------------------------│
               │  /backup                                 │
               │     (crypted scrambled)                  │
               │     ex :                                 │
               │       8sd7f9sd7f9sd7f9sd7f9sd7f          │
               │       7f8sd9f8sd9f8sd9f8sd9f8sd          │
               └──────────────────────────────────────────┘

Some useful commands:

rclone listremotes
rclone lsd ovhftp:/backup
rclone lsd ovhcrypt:
rclone config file
rclone copy /volume1/photo ovhcrypt: --progress

Restore would work as below:

OVH /backup (crypted files)


rclone (ovhcrypt:) → automatically decrypt


/volume1/photo (unencrypted restored files)

rclone copy ovhcrypt: /volume1/photo --progress