When automating IMAP migrations with imapsync, hardcoding credentials in scripts creates security risks and maintenance headaches. We need a clean solution to:
- Separate configuration from code
- Support multiple account migrations in one execution
- Maintain readability while handling sensitive data
Here's a robust implementation using a control file and bash script combination:
#!/bin/bash
# imapsync_wrapper.sh
CONFIG_FILE="imap_accounts.config"
while IFS= read -r line; do
# Skip empty lines and comments
[[ -z $line || $line == \#* ]] && continue
# Parse line into variables
read -r HOST1 USER1 PW1 HOST2 USER2 PW2 <<< "$line"
echo "Processing migration: $USER1@$HOST1 → $USER2@$HOST2"
imapsync \\
--buffersize 8192000 --nosyncacls --subscribe \\
--host1 "$HOST1" --user1 "$USER1" --password1 "$PW1" --ssl1 \\
--host2 "$HOST2" --user2 "$USER2" --password2 "$PW2" --ssl2 \\
--port1 993 --port2 993 --noauthmd5 --allowsizemismatch
# Add error handling if needed
if [ $? -ne 0 ]; then
echo "Error processing $USER1" >&2
fi
done < "$CONFIG_FILE"
The control file (imap_accounts.config) should follow this structure:
# Format: source_host source_user source_pass dest_host dest_user dest_pass
mail.oldprovider.com johndoe p@ssw0rd123 mail.newprovider.com jdoe N3wP@ss!
exchange.example.com smith@domain.com Winter2024! outlook.office365.com j.smith@newdomain.com Spr1ng2024!
For production environments, consider these enhancements:
#!/bin/bash
# Secure version with encrypted credentials
CONFIG_FILE="/etc/imap/encrypted_config.gpg"
# Using GPG-encrypted config file
gpg --decrypt "$CONFIG_FILE" | while IFS= read -r line; do
[[ -z $line ]] && continue
# Use temporary variables to minimize exposure
read -r h1 u1 p1 h2 u2 p2 <<< "$line"
# Immediate use in imapsync
imapsync \
--host1 "$h1" --user1 "$u1" --password1 "$p1" \
--host2 "$h2" --user2 "$u2" --password2 "$p2" \
# ... other parameters
# Clear variables
unset h1 u1 p1 h2 u2 p2
done
Add robust error handling to your implementation:
#!/bin/bash
# Version with comprehensive error handling
LOG_FILE="imapsync_$(date +%Y%m%d).log"
ERROR_FILE="errors_$(date +%Y%m%d).log"
process_line() {
local line="$1"
read -r HOST1 USER1 PW1 HOST2 USER2 PW2 <<< "$line"
echo "$(date) - Starting: $USER1 → $USER2" >> "$LOG_FILE"
if ! imapsync \
--host1 "$HOST1" --user1 "$USER1" --password1 "$PW1" \
--host2 "$HOST2" --user2 "$USER2" --password2 "$PW2" \
# ... params
>> "$LOG_FILE" 2>> "$ERROR_FILE"
then
echo "$(date) - FAILED: $USER1" >> "$ERROR_FILE"
return 1
fi
echo "$(date) - Completed: $USER1" >> "$LOG_FILE"
return 0
}
export -f process_line
export LOG_FILE ERROR_FILE
# Process config file in parallel (4 jobs)
parallel -j4 --linebuffer process_line ::: "$CONFIG_FILE"
- Use
chmod 600
for config files containing passwords - Consider using SSH keys instead of passwords when possible
- Implement rotation for log files (logrotate)
- Add email notifications for failed migrations
- Test with
--dry
option before real migrations
When automating tasks with bash scripts, hardcoding variables like usernames, passwords, or hostnames is a bad practice. A more elegant approach is to read these values from an external configuration file. This makes the script reusable, maintainable, and secure.
Consider an imapsync
bash script that needs to run with different sets of credentials for multiple email migrations. Instead of modifying the script each time, we want to:
- Store configurations in an external file
- Read them line by line
- Execute the command with each set of parameters
Here's how to implement this effectively:
1. The Configuration File
Create a text file (e.g., config.txt
) with this format:
mail.server1.com user1 pass123 mail.server2.com user2 pass456
backup.server.com admin1 securepw new.server.com admin2 strongerpw
2. The Bash Script
Here's the complete solution:
#!/bin/bash
CONFIG_FILE="config.txt"
while IFS= read -r line; do
# Split the line into variables
read HOST1 USER1 PW1 HOST2 USER2 PW2 <<< "$line"
echo "Processing migration from $USER1@$HOST1 to $USER2@$HOST2"
imapsync \\
--buffersize 8192000 --nosyncacls --subscribe --syncinternaldates --IgnoreSizeErrors \\
--host1 "$HOST1" --user1 "$USER1" --password1 "$PW1" --ssl1 --port1 993 --noauthmd5 \\
--host2 "$HOST2" --user2 "$USER2" --password2 "$PW2" --ssl2 --port2 993 --noauthmd5 --allowsizemismatch
if [ $? -eq 0 ]; then
echo "Migration successful"
else
echo "Migration failed for $USER1" >&2
fi
echo "----------------------------------------"
done < "$CONFIG_FILE"
Error Handling
Add validation to ensure the config file exists and is readable:
if [ ! -f "$CONFIG_FILE" ]; then
echo "Error: Configuration file $CONFIG_FILE not found" >&2
exit 1
fi
if [ ! -r "$CONFIG_FILE" ]; then
echo "Error: Cannot read configuration file $CONFIG_FILE" >&2
exit 1
fi
Secure Password Handling
For better security, consider:
# Encrypt the config file
gpg --encrypt --recipient your@email.com config.txt
# Then decrypt temporarily when reading
gpg --quiet --decrypt config.txt.gpg | while IFS= read -r line; do
# Process each line
done
For more complex configurations with optional parameters:
#!/bin/bash
declare -A config
CONFIG_FILE="config.ini"
while IFS='=' read -r key value; do
config["$key"]="$value"
done < "$CONFIG_FILE"
imapsync \\
--host1 "${config[host1]}" --user1 "${config[user1]}" \\
--password1 "${config[password1]}" # ... other parameters
This approach provides several benefits:
- Separation of configuration from code
- Easier maintenance when credentials change
- Ability to run multiple configurations sequentially
- Improved security by keeping sensitive data out of scripts
The implementation can be extended to support comments in config files, different delimiters, or even JSON/YAML formats for more complex scenarios.