├── README.md ├── autobackup.py └── install_autobackup.sh /README.md: -------------------------------------------------------------------------------- 1 | # Pwnagotchi AutoBackup Plugin 2 | 3 | This plugin allows you to automatically back up important files on your Pwnagotchi device. It supports both local and remote backups via rsync over SSH. 4 | 5 | ## Features 6 | - **Automatic Backups:** Periodically backs up files to a local or remote server. 7 | - **Local and Remote Backup:** Specify a local directory and a remote SSH server to store backups. 8 | - **Architecture-Aware:** Detects whether your system is 32-bit or 64-bit and adjusts the backup files accordingly. 9 | 10 | ## Requirements 11 | - **Pwnagotchi:** Installed on a Raspberry Pi. 12 | - **SSH Key:** Required for remote backups. 13 | - **Internet Access:** To download the plugin and perform remote backups. 14 | 15 | ## Installation 16 | 17 | ### Step 1: Create an SSH Key 18 | To perform remote backups, you’ll need to create an SSH key that the Pwnagotchi can use to connect to the remote server. 19 | 20 | 1. Generate the SSH key (on your Pwnagotchi): 21 | `ssh-keygen -t rsa -b 4096 -f ~/.ssh/pwnagotchi_backup_key -N ""` 22 | 23 | 2. Copy the SSH public key to your remote server: 24 | `ssh-copy-id -i ~/.ssh/pwnagotchi_backup_key.pub user@remotehost` 25 | Replace `user@remotehost` with your remote server's username and IP address. 26 | 27 | ### Step 2: Set Up SSH Key for GitHub 28 | 29 | Use the following command to display the key: 30 | `cat ~/.ssh/pwnagotchi_backup_key.pub` 31 | Make sure to copy the output to your clipboard. 32 | 33 | Add the SSH key (`~/.ssh/pwnagotchi_backup_key.pub`) to your GitHub account under Settings > SSH and GPG keys. 34 | 35 | Then 36 | `nano ~/.ssh/config` 37 | 38 | ``` 39 | Host github.com 40 | HostName github.com 41 | User git 42 | IdentityFile ~/.ssh/pwnagotchi_backup_key 43 | ``` 44 | 45 | And finally run these commands 46 | 47 | ``` 48 | eval "$(ssh-agent -s)" 49 | ssh-add ~/.ssh/pwnagotchi_backup_key 50 | ssh -T git@github.com 51 | ``` 52 | 53 | Result should be 54 | `Hi USERNAME! You've successfully authenticated, but GitHub does not provide shell access.` 55 | 56 | 57 | 58 | 59 | Use the following command to display the key: 60 | `cat ~/.ssh/pwnagotchi_backup_key.pub` 61 | Make sure to copy the output to your clipboard. 62 | 63 | ### Step 3: Create a Private GitHub Repository 64 | 1. Go to GitHub and log in to your account. 65 | 2. Click the + icon in the upper right corner and select **New repository**. 66 | 3. Enter a repository name (e.g., **Backup**). 67 | 4. Select **Private** to keep your repository secure. 68 | 5. Click **Create repository**. 69 | 70 | ### Step 4: Install the AutoBackup Plugin 71 | Download the installation script to your Pwnagotchi: 72 | `wget https://github.com/wpa-2/pwny_backup/raw/main/install_autobackup.sh -O install_autobackup.sh` 73 | 74 | Make the script executable: 75 | `chmod +x install_autobackup.sh` 76 | 77 | Run the installation script: 78 | `sudo ./install_autobackup.sh` 79 | 80 | The script will: 81 | - Check the permissions of your SSH key. 82 | - Prompt for the local and remote backup paths. 83 | - Test your SSH connection to the remote server. 84 | - Download the `autobackup.py` plugin and install it. 85 | - Update the Pwnagotchi configuration file with the required plugin configuration. 86 | 87 | ### Step 5: Configure the Plugin 88 | During the installation, you'll be prompted to enter: 89 | - **Local Backup Path:** Directory on the Pwnagotchi where local backups will be stored. 90 | - **Remote Backup:** The SSH connection string for the remote server (e.g., `user@remotehost:/path/to/backup`). 91 | 92 | ### Example configuration in `config.toml`: 93 | 94 | Autobackup Plugin Configuration 95 | ``` 96 | main.plugins.autobackup.github_repo = "git@github.com:username/repository.git" 97 | main.plugins.autobackup.github_backup_dir = "Backups" 98 | main.plugins.autobackup.remote_backup = "user@LOCALIP:/path/to/folder/,/home/pi/.ssh/pwnagotchi_backup_key" 99 | main.plugins.autobackup.enabled = true 100 | main.plugins.autobackup.interval = 1 # Backup every 1 hour 101 | main.plugins.autobackup.max_tries = 3 102 | main.plugins.autobackup.local_backup_path = "/home/pi/backup/" 103 | ``` 104 | 105 | ### Step 6: Verify Installation 106 | Restart Pwnagotchi to apply the changes: 107 | `sudo systemctl restart pwnagotchi` 108 | 109 | Check the logs to verify the plugin is running: 110 | `pwnlog` 111 | You should see log entries indicating that the backup process is scheduled and running. 112 | 113 | ## Backup Frequency 114 | The backup interval is set in the configuration file. Adjust it based on your needs; for example, a 1-hour interval might be suitable for regular backups, while longer intervals may suffice for less critical data. 115 | 116 | ## Security Considerations 117 | Ensure your SSH keys are kept secure. Do not share your private key, and consider using passphrases for added security. 118 | 119 | ## Contact/Support Information 120 | If you encounter issues, please open an issue on the [GitHub repository](https://github.com/wpa-2/pwny_backup/issues). 121 | 122 | ## Changelog 123 | - **Version x.0:** Initial release. 124 | 125 | ## Troubleshooting 126 | - **SSH Connection Issues:** Ensure that the SSH key has been properly configured and that the Pwnagotchi can connect to the remote server without needing a password: 127 | `ssh -i /home/pi/.ssh/pwnagotchi_backup_key user@remotehost` 128 | `ssh -T git@github.com` 129 | 130 | - **Permissions:** Ensure the SSH key file has the correct permissions: 131 | ``` 132 | chmod 700 ~/.ssh 133 | chmod 600 ~/.ssh/pwnagotchi_backup_key 134 | chmod 644 ~/.ssh/pwnagotchi_backup_key.pub 135 | ``` 136 | 137 | - **Logs:** Check the Pwnagotchi logs for any errors or issues with the backup process: 138 | `pwnlog` 139 | 140 | ## Known issues 141 | 142 | Some times this shows as an error (not really an issue if your using pwny a lot and its not sat in manu mode) 143 | 144 | `14:21:43 [ERROR] AUTO_BACKUP: Git command 'cd /home/pi/backup/Backups && git commit -m 'Backup on 2024-09-21 14:21:43.157731'' failed with exit code 1` 145 | 146 | Just means the backup hasnt changed and theres nothing new to upload, i need to fix that at some point. 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /autobackup.py: -------------------------------------------------------------------------------- 1 | import pwnagotchi.plugins as plugins 2 | from pwnagotchi.utils import StatusFile 3 | import logging 4 | import os 5 | import subprocess 6 | import platform 7 | import socket 8 | from datetime import datetime 9 | import threading 10 | import time 11 | import shutil 12 | 13 | class autobackup(plugins.Plugin): 14 | __author__ = 'WPA2' 15 | __version__ = '2.1.0' 16 | __license__ = 'GPL3' 17 | __description__ = 'This plugin backs up files using the hostname for the backup filename and supports local and remote backups using rsync.' 18 | 19 | def __init__(self): 20 | self.ready = False 21 | self.tries = 0 22 | self.status = StatusFile('/root/.auto-backup') 23 | 24 | def on_loaded(self): 25 | logging.info(f"AUTO-BACKUP: Full loaded configuration: {self.options}") 26 | 27 | required_options = ['interval', 'local_backup_path', 'github_repo', 'github_backup_dir'] 28 | for opt in required_options: 29 | if opt not in self.options: 30 | logging.error(f"AUTO-BACKUP: Required option {opt} is not set.") 31 | return 32 | 33 | backup_interval = self.options.get('interval', 1) * 3600 34 | self.backup_thread = threading.Thread(target=self.schedule_backup, args=(backup_interval,), daemon=True) 35 | self.backup_thread.start() 36 | logging.info("AUTO_BACKUP: Backup scheduler started") 37 | 38 | def schedule_backup(self, interval): 39 | while True: 40 | logging.info(f"AUTO_BACKUP: Scheduled time-based backup after {interval / 3600} hours.") 41 | self.perform_backup() 42 | time.sleep(interval) 43 | 44 | def perform_backup(self): 45 | os_arch = platform.machine() 46 | files_to_backup = self.get_files_to_backup(os_arch) 47 | 48 | if not os.path.exists(self.options['local_backup_path']): 49 | logging.info(f"AUTO_BACKUP: Local backup path does not exist. Creating: {self.options['local_backup_path']}") 50 | os.makedirs(self.options['local_backup_path'], exist_ok=True) 51 | 52 | hostname = socket.gethostname() 53 | backup_filename = f"{hostname}-backup.tar.gz" 54 | local_backup_path = os.path.join(self.options['local_backup_path'], backup_filename) 55 | 56 | valid_files = [f for f in files_to_backup if os.path.exists(f)] 57 | if not valid_files: 58 | logging.info("AUTO_BACKUP: No valid files to backup, skipping.") 59 | return 60 | 61 | try: 62 | self.create_backup_archive(valid_files, local_backup_path) 63 | self.handle_github_backup(local_backup_path, backup_filename) 64 | self.handle_remote_backup(local_backup_path, backup_filename) 65 | except OSError as os_e: 66 | self.tries += 1 67 | logging.error(f"AUTO_BACKUP: Error: {os_e}") 68 | 69 | def get_files_to_backup(self, os_arch): 70 | if os_arch == "aarch64": 71 | return [ 72 | "/root/brain.json", 73 | "/root/.api-report.json", 74 | "/root/handshakes/", 75 | "/root/peers/", 76 | "/etc/pwnagotchi/", 77 | "/boot/firmware/config.txt", 78 | "/boot/firmware/cmdline.txt", 79 | "/usr/local/share/pwnagotchi/custom-plugins/" 80 | ] 81 | elif os_arch == "armv7l": 82 | return [ 83 | "/root/brain.json", 84 | "/root/.api-report.json", 85 | "/root/handshakes/", 86 | "/root/peers/", 87 | "/etc/pwnagotchi/", 88 | "/boot/config.txt", 89 | "/boot/cmdline.txt", 90 | "/usr/local/share/pwnagotchi/custom-plugins/" 91 | ] 92 | else: 93 | logging.error("AUTO_BACKUP: Unsupported architecture detected.") 94 | return [] 95 | 96 | def create_backup_archive(self, valid_files, local_backup_path): 97 | logging.info("AUTO_BACKUP: Backing up ...") 98 | tar_command = f"tar --exclude='/etc/pwnagotchi/log/pwnagotchi.log' -czvf {local_backup_path} {' '.join(valid_files)}" 99 | logging.info(f"AUTO_BACKUP: Running tar command: {tar_command}") 100 | 101 | result = subprocess.run(tar_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 102 | if result.returncode != 0: 103 | logging.error(f"AUTO_BACKUP: Failed to create backup. Error: {result.stderr.decode()} Command: {tar_command}") 104 | return 105 | 106 | logging.info(f"AUTO_BACKUP: Backup created successfully at {local_backup_path}") 107 | 108 | def handle_github_backup(self, local_backup_path, backup_filename): 109 | if 'github_repo' in self.options and 'github_backup_dir' in self.options: 110 | github_backup_dir = self.options['github_backup_dir'] 111 | github_backup_path = os.path.join(self.options['local_backup_path'], github_backup_dir) 112 | 113 | if not os.path.exists(github_backup_path): 114 | os.makedirs(github_backup_path) 115 | 116 | final_backup_path = os.path.join(github_backup_path, backup_filename) 117 | shutil.copy(local_backup_path, final_backup_path) 118 | logging.info(f"AUTO_BACKUP: Backup file copied to GitHub directory: {final_backup_path}") 119 | self.git_setup(github_backup_path, backup_filename) 120 | 121 | def git_setup(self, github_backup_path, backup_filename): 122 | os.makedirs(os.path.join(github_backup_path, '.git', 'info'), exist_ok=True) 123 | 124 | hostname = socket.gethostname() 125 | with open(os.path.join(github_backup_path, '.git', 'info', 'sparse-checkout'), 'w') as sparse_file: 126 | sparse_file.write(f"{hostname}-backup.tar.gz\n") 127 | 128 | subprocess.run(f"git config core.sparseCheckout true", cwd=github_backup_path, shell=True) 129 | subprocess.run(f"git read-tree -mu HEAD", cwd=github_backup_path, shell=True) 130 | 131 | self.run_git_commands(github_backup_path, backup_filename) 132 | 133 | def run_git_commands(self, github_backup_path, backup_filename): 134 | # Get the SSH key for GitHub from the configuration 135 | github_ssh_key = self.options.get('github_ssh_key', '/home/pi/.ssh/pwnagotchi_backup_key') # Default to pwnagotchi_backup_key if not set 136 | 137 | git_commands = [ 138 | f"cd {github_backup_path} && git add -f {backup_filename}", 139 | f"cd {github_backup_path} && git commit -m 'Backup on {datetime.now()}'", 140 | f"cd {github_backup_path} && GIT_SSH_COMMAND='ssh -i {github_ssh_key}' git push --force origin main" 141 | ] 142 | 143 | for cmd in git_commands: 144 | logging.info(f"AUTO_BACKUP: Running Git command: {cmd}") 145 | result = subprocess.run(f"sudo -u pi bash -c \"{cmd}\"", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 146 | 147 | logging.info(f"AUTO_BACKUP: Command output: {result.stdout.decode()}") 148 | if result.returncode != 0: 149 | logging.error(f"AUTO_BACKUP: Git command '{cmd}' failed with exit code {result.returncode}") 150 | logging.error(f"AUTO_BACKUP: Command error: {result.stderr.decode()}") 151 | return 152 | else: 153 | logging.info(f"AUTO_BACKUP: Git command '{cmd}' executed successfully.") 154 | 155 | logging.info("AUTO_BACKUP: Backup successfully sent to GitHub.") 156 | 157 | def handle_remote_backup(self, local_backup_path, backup_filename): 158 | if 'remote_backup' in self.options: 159 | try: 160 | remote_config = self.options['remote_backup'] 161 | if ',' not in remote_config: 162 | raise ValueError("Remote backup configuration must be in the format 'user@host:/path,key_path'") 163 | 164 | server_address, ssh_key = remote_config.split(',') 165 | rsync_command = f"rsync -avz -e 'ssh -i {ssh_key} -o StrictHostKeyChecking=no' {local_backup_path} {server_address}/" 166 | logging.info(f"AUTO_BACKUP: Sending backup to server using rsync: {rsync_command}") 167 | result = subprocess.run(rsync_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 168 | if result.returncode == 0: 169 | logging.info("AUTO_BACKUP: Backup successfully sent to server using rsync.") 170 | else: 171 | logging.error(f"AUTO_BACKUP: Failed to send backup to server using rsync. Error: {result.stderr.decode()}") 172 | 173 | logging.info("AUTO_BACKUP: Backup transfer to server completed.") 174 | 175 | except ValueError as e: 176 | logging.error(f"AUTO_BACKUP: Incorrect remote backup configuration format. {str(e)}") 177 | -------------------------------------------------------------------------------- /install_autobackup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define the home directory of the original user running the script (not root) 4 | USER_HOME=$(eval echo ~$SUDO_USER) 5 | SSH_KEY_PATH="$USER_HOME/.ssh/pwnagotchi_backup_key" 6 | CONFIG_FILE="/etc/pwnagotchi/config.toml" 7 | PLUGIN_DIR="/usr/local/share/pwnagotchi/custom-plugins" 8 | AUTOBACKUP_SCRIPT="$PLUGIN_DIR/autobackup.py" 9 | AUTOBACKUP_URL="https://raw.githubusercontent.com/wpa-2/pwny_backup/refs/heads/main/autobackup.py" 10 | 11 | # Ensure the script is run with sudo 12 | if [ -z "$SUDO_USER" ]; then 13 | echo "Error: This script must be run with sudo." 14 | exit 1 15 | fi 16 | 17 | # Ensure SSH key file has the correct permissions 18 | echo "Checking permissions for SSH key..." 19 | if [ -f "$SSH_KEY_PATH" ]; then 20 | KEY_PERMS=$(stat -c "%a" $SSH_KEY_PATH) 21 | if [ "$KEY_PERMS" != "600" ]; then 22 | echo "Fixing permissions for $SSH_KEY_PATH..." 23 | chmod 600 $SSH_KEY_PATH 24 | if [ $? -ne 0 ]; then 25 | echo "Failed to set correct permissions on $SSH_KEY_PATH" 26 | exit 1 27 | fi 28 | fi 29 | else 30 | echo "SSH key not found at $SSH_KEY_PATH." 31 | exit 1 32 | fi 33 | 34 | # Prompt for local backup path and set default if empty 35 | read -p "Enter the local backup path (e.g., /home/pi/backup/): " LOCAL_BACKUP_PATH 36 | LOCAL_BACKUP_PATH=${LOCAL_BACKUP_PATH:-"$USER_HOME/backup/"} 37 | 38 | # Check if the local backup directory exists, if not create it 39 | if [ ! -d "$LOCAL_BACKUP_PATH" ]; then 40 | echo "Local backup directory does not exist, creating $LOCAL_BACKUP_PATH..." 41 | mkdir -p "$LOCAL_BACKUP_PATH" 42 | if [ $? -ne 0 ]; then 43 | echo "Error: Failed to create directory $LOCAL_BACKUP_PATH" 44 | exit 1 45 | fi 46 | fi 47 | 48 | # Fix ownership of the backup directory 49 | echo "Fixing ownership of the local backup directory..." 50 | sudo chown -R $SUDO_USER:$SUDO_USER "$LOCAL_BACKUP_PATH" 51 | sudo chmod -R 755 "$LOCAL_BACKUP_PATH" 52 | 53 | # Prompt to enable GitHub backups 54 | read -p "Would you like to set up GitHub backups? (y/n): " ENABLE_GITHUB 55 | 56 | if [[ "$ENABLE_GITHUB" == "y" || "$ENABLE_GITHUB" == "Y" ]]; then 57 | read -p "Enter the GitHub repository URL (e.g., git@github.com:username/repository.git): " GITHUB_REPO 58 | GITHUB_BACKUP_DIR="Backups" # Set this to match your central backups folder 59 | 60 | # Test GitHub SSH connection 61 | echo "Testing SSH connection to GitHub..." 62 | sudo -u $SUDO_USER ssh -T git@github.com 2>&1 | grep -q "successfully authenticated" 63 | if [ $? -ne 0 ]; then 64 | echo "Error: Failed to authenticate with GitHub using the SSH key. Please ensure your SSH key is added to GitHub." 65 | exit 1 66 | else 67 | echo "GitHub authentication successful!" 68 | fi 69 | 70 | # Clone the GitHub repository if it doesn't already exist 71 | if [ ! -d "$LOCAL_BACKUP_PATH/.git" ]; then 72 | echo "Cloning GitHub repository into $LOCAL_BACKUP_PATH..." 73 | sudo -u $SUDO_USER git clone $GITHUB_REPO "$LOCAL_BACKUP_PATH" 74 | if [ $? -ne 0 ]; then 75 | echo "Error: Failed to clone GitHub repository." 76 | exit 1 77 | fi 78 | fi 79 | 80 | # Mark the repository as safe for Git 81 | sudo -u $SUDO_USER git config --global --add safe.directory $LOCAL_BACKUP_PATH 82 | 83 | # Prompt for Git user details 84 | read -p "Enter your Git user name: " GIT_USER_NAME 85 | read -p "Enter your Git user email: " GIT_USER_EMAIL 86 | 87 | # Set Git user details for the repository 88 | sudo -u $SUDO_USER bash -c "cd $LOCAL_BACKUP_PATH && git config user.name '$GIT_USER_NAME' && git config user.email '$GIT_USER_EMAIL'" 89 | 90 | # Configure sparse checkout for files containing the hostname 91 | HOSTNAME=$(hostname) 92 | echo "Configuring sparse checkout for files with the hostname '$HOSTNAME'..." 93 | sudo -u $SUDO_USER bash -c "cd $LOCAL_BACKUP_PATH && git config core.sparseCheckout true && echo '$GITHUB_BACKUP_DIR/$HOSTNAME-backup.tar.gz' > .git/info/sparse-checkout" 94 | 95 | # Pull only the hostname-related files from the repository 96 | sudo -u $SUDO_USER bash -c "cd $LOCAL_BACKUP_PATH && git read-tree -mu HEAD" 97 | 98 | # Update the configuration file for GitHub backups 99 | echo "Configuring GitHub backup in Pwnagotchi settings..." 100 | sudo sed -i "/main.plugins.autobackup.github_repo/d" $CONFIG_FILE 101 | sudo sed -i "/main.plugins.autobackup.github_backup_dir/d" $CONFIG_FILE 102 | sudo bash -c "cat <> $CONFIG_FILE 103 | main.plugins.autobackup.github_repo = \"$GITHUB_REPO\" 104 | main.plugins.autobackup.github_backup_dir = \"$GITHUB_BACKUP_DIR\" 105 | EOL" 106 | fi 107 | 108 | # Prompt for remote backup configuration 109 | read -p "Would you like to set up remote backups? (y/n): " ENABLE_REMOTE 110 | if [[ "$ENABLE_REMOTE" == "y" || "$ENABLE_REMOTE" == "Y" ]]; then 111 | read -p "Enter the remote server address (e.g., user@hostname:/path/to/backup/): " REMOTE_SERVER 112 | read -p "Enter the SSH key path for remote access (default: $SSH_KEY_PATH): " SSH_KEY 113 | SSH_KEY=${SSH_KEY:-"$SSH_KEY_PATH"} 114 | 115 | # Update the configuration file for remote backups 116 | echo "Configuring remote backup in Pwnagotchi settings..." 117 | sudo sed -i "/main.plugins.autobackup.remote_backup/d" $CONFIG_FILE 118 | sudo bash -c "echo 'main.plugins.autobackup.remote_backup = \"$REMOTE_SERVER,$SSH_KEY\"' >> $CONFIG_FILE" 119 | fi 120 | 121 | # Download the autobackup.py script 122 | echo "Downloading autobackup.py script..." 123 | mkdir -p $PLUGIN_DIR 124 | wget -O $AUTOBACKUP_SCRIPT $AUTOBACKUP_URL || { echo "Error: Failed to download autobackup.py"; exit 1; } 125 | 126 | # Set the correct permissions for the script 127 | chmod +x $AUTOBACKUP_SCRIPT 128 | echo "autobackup.py installed to $PLUGIN_DIR." 129 | 130 | # Remove existing autobackup configuration to avoid duplicates 131 | sudo sed -i "/main.plugins.autobackup.local_backup_path/d" $CONFIG_FILE 132 | sudo sed -i "/main.plugins.autobackup.interval/d" $CONFIG_FILE 133 | 134 | # Append local backup configuration 135 | echo "Updating Pwnagotchi configuration..." 136 | sudo bash -c "cat <> $CONFIG_FILE 137 | main.plugins.autobackup.enabled = true 138 | main.plugins.autobackup.interval = 1 # Backup every 1 hour 139 | main.plugins.autobackup.max_tries = 3 140 | main.plugins.autobackup.local_backup_path = \"$LOCAL_BACKUP_PATH\" 141 | EOL" 142 | 143 | # Finished 144 | echo "Configuration update complete." 145 | echo "Autobackup plugin installed and configured. You can now restart Pwnagotchi to apply changes." 146 | --------------------------------------------------------------------------------