Does a full account backup include webmail settings (identities, signatures)?

Hi everyone,

I have a question regarding account backups and migrations between two HestiaCP servers.

When I perform a full backup of a user account and restore it on a different HestiaCP server, are the specific email configurations preserved? Specifically, I’m looking to know if:

Webmail identities are migrated.

Email signatures are kept.

I tried searching the forum but couldn’t find a definitive answer on whether these Roundcube/Webmail metadata are part of the standard .tar backup file.

Sorry if this has been covered before, and thanks in advance for the help!

Hi,

No, that data is not backed up. If you want it, you should backup it yourself.

Exactly! I want to perform a ‘silent migration’ where the user doesn’t even notice the move. If their signature disappears, they’ll know something changed.

Since I’m the root admin and I have dozens of accounts, I can’t do this manually. Does anyone have a script (bash or python) that exports the identities and contacts joined with the users.username field from the source server, and then imports them into the destination server by matching the usernames to the new user_ids?

Moving the Maildir is easy with v-restore-user, but keeping the webmail ‘feeling’ the same is the challenge here. Any SQL snippets to help with this mapping would be awesome!

I can share this script, but it only backs up all Roundcube users to /backups/roundcube_db_users/.

Note: I used mariadb commands, if you are using MySQL you should change the commands.

Important: The restore script is up to you :slightly_smiling_face:

#!/usr/bin/env bash

# Roundcube per-user backup script (database only)
# This version pulls email addresses from HestiaCP and backs up each one.

# Configuration
DB_NAME="roundcube"
DB_USER="root"
BACKUP_DIR="/backups/roundcube_db_users"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

# Column names according to Roundcube schema
USERS_PK_COL="user_id"      # PRIMARY KEY of the users table
USERS_EMAIL_COL="username"  # Column where user email is stored

# Hestia binaries
HESTIA_BIN="/usr/local/hestia/bin"
V_LIST_USERS="$HESTIA_BIN/v-list-users"
V_LIST_MAIL_DOMAINS="$HESTIA_BIN/v-list-mail-domains"
V_LIST_MAIL_ACCOUNTS="$HESTIA_BIN/v-list-mail-accounts"

# Prepare mariadb/mariadb-dump commands using /root/.my.cnf
MYSQL_CMD="mariadb --defaults-extra-file=/root/.my.cnf -u $DB_USER $DB_NAME"
MYSQLDUMP_CMD="mariadb-dump --defaults-extra-file=/root/.my.cnf -u $DB_USER $DB_NAME"

# Basic checks
if [ ! -x "$V_LIST_USERS" ]; then
    echo "Error: Hestia binary not found or not executable: $V_LIST_USERS"
    exit 1
fi

mkdir -p "$BACKUP_DIR"

dump_table() {
    local table="$1"
    local where_clause="$2"
    local outfile="$3"

    echo "    - Exporting table '$table'..."
    $MYSQLDUMP_CMD "$table" --no-create-info --skip-lock-tables --where="$where_clause" > "$outfile"

    if [ $? -ne 0 ]; then
        echo "      ERROR exporting $table"
        return 1
    elif [ ! -s "$outfile" ]; then
        echo "      (empty: no rows match the condition)"
        return 0
    else
        echo "      (ok: $(wc -l < "$outfile") lines)"
        return 0
    fi
}

backup_one_email() {
    local user_email="$1"

    echo ""
    echo "=== Roundcube Backup for: $user_email ==="

    local backup_path="$BACKUP_DIR/${user_email}_${TIMESTAMP}"
    mkdir -p "$backup_path"

    echo "[1/3] Searching for user in 'users' table..."
    local user_pk
    user_pk=$($MYSQL_CMD -N -e "SELECT $USERS_PK_COL FROM users WHERE $USERS_EMAIL_COL='$user_email' LIMIT 1;")

    if [ -z "$user_pk" ]; then
        echo "User '$user_email' not found in 'users' table. Skipping."
        rm -rf "$backup_path"
        return 0
    fi

    echo "User found. $USERS_PK_COL = $user_pk"
    echo ""
    echo "[2/3] Exporting database data..."

    dump_table "users" "$USERS_PK_COL=$user_pk" "$backup_path/users.sql"

    local has_user_id_col

    has_user_id_col=$($MYSQL_CMD -N -e "SHOW COLUMNS FROM contacts LIKE 'user_id';" | wc -l)
    if [ "$has_user_id_col" -gt 0 ]; then
        dump_table "contacts" "user_id=$user_pk" "$backup_path/contacts.sql"
    else
        echo "    - Table 'contacts' does not have 'user_id' column, skipping."
    fi

    has_user_id_col=$($MYSQL_CMD -N -e "SHOW COLUMNS FROM identities LIKE 'user_id';" | wc -l)
    if [ "$has_user_id_col" -gt 0 ]; then
        dump_table "identities" "user_id=$user_pk" "$backup_path/identities.sql"
    else
        echo "    - Table 'identities' does not have 'user_id' column, skipping."
    fi

    has_user_id_col=$($MYSQL_CMD -N -e "SHOW COLUMNS FROM contactgroups LIKE 'user_id';" | wc -l)
    if [ "$has_user_id_col" -gt 0 ]; then
        dump_table "contactgroups" "user_id=$user_pk" "$backup_path/contactgroups.sql"
    else
        echo "    - Table 'contactgroups' does not have 'user_id' column, skipping."
    fi

    local has_table_cgm
    has_table_cgm=$($MYSQL_CMD -N -e "SHOW TABLES LIKE 'contactgroupmembers';" | wc -l)
    if [ "$has_table_cgm" -gt 0 ]; then
        dump_table "contactgroupmembers" \
            "contactgroup_id IN (SELECT contactgroup_id FROM contactgroups WHERE user_id=$user_pk)" \
            "$backup_path/contactgroupmembers.sql"
    else
        echo "    - Table 'contactgroupmembers' does not exist, skipping."
    fi

    echo ""
    echo "Table export completed"
    echo ""
    echo "[3/3] Creating information file and compressed archive..."

    cat > "$backup_path/backup_info.txt" << EOF
Roundcube Backup (Database)
====================================
User: $user_email
Primary key in 'users': $USERS_PK_COL = $user_pk
Date: $(date)
Hostname: $(hostname)

Database: $DB_NAME

Included files (may be empty if no data):
- users.sql (user row in 'users' table)
- contacts.sql (user contacts)
- identities.sql (identities/signatures)
- contactgroups.sql (contact groups)
- contactgroupmembers.sql (group members)

To restore:
  mariadb -u root $DB_NAME < users.sql
  mariadb -u root $DB_NAME < contacts.sql
  mariadb -u root $DB_NAME < identities.sql
  mariadb -u root $DB_NAME < contactgroups.sql
  mariadb -u root $DB_NAME < contactgroupmembers.sql
EOF

    local final_backup="$BACKUP_DIR/${user_email}_${TIMESTAMP}.tar.gz"
    tar -czf "$final_backup" -C "$BACKUP_DIR" "$(basename "$backup_path")" 2>/dev/null
    rm -rf "$backup_path"

    echo "Backup completed successfully"
    echo "Backup file: $final_backup"
    echo "Size: $(du -h "$final_backup" | cut -f1)"
}

echo "Starting Roundcube backups for all Hestia mail accounts..."
echo "Backup directory: $BACKUP_DIR"
echo "Timestamp: $TIMESTAMP"

for user in $($V_LIST_USERS plain | cut -f1); do
    for domain in $($V_LIST_MAIL_DOMAINS "$user" plain | cut -f1); do
        for account in $($V_LIST_MAIL_ACCOUNTS "$user" "$domain" plain | cut -f1); do
            backup_one_email "${account}@${domain}"
        done
    done
done

echo ""
echo "All done."
2 Likes