Why I wanted this backed up properly

This whole site, every post, the theme, the config, lives in one folder on the T620. Nothing about that folder is protected by anything beyond the disk it sits on. Wanted a real backup, and wanted it copied off the box entirely, not just sitting in a second folder on the same machine.

First attempt at excluding the build output

Wanted to back up the Hugo source without dragging along the generated public folder or the build cache, since both regenerate automatically from a normal deploy and just bloat the archive for no reason:

sudo tar -czvf /root/hugo-kingtolga-backup-$(date +%Y%m%d).tar.gz -C /home/tolga/hugo kingtolga --exclude='kingtolga/public' --exclude='kingtolga/resources'

Ran it. Watched the output scroll past. Public folder was right there in the listing anyway, exclude flags did nothing. Tar spat out the real reason at the end:

tar: The following options were used after non-option arguments. These options are positional and affect only arguments that follow them.
tar: --exclude has no effect

GNU tar treats exclude flags as positional, meaning anything placed after the actual file arguments in the command gets silently ignored. Moved them to the front instead:

sudo tar --exclude='kingtolga/public' --exclude='kingtolga/resources' --exclude='kingtolga/themes/PaperMod/.git' -czvf /root/hugo-kingtolga-backup-$(date +%Y%m%d).tar.gz -C /home/tolga/hugo kingtolga

Threw in excluding the theme’s own git history too while I was at it, no reason to carry that along either since it can just be re-cloned if ever needed.

Checking it worked, and hitting the same shell quirk again

ls -la /root/hugo-kingtolga-backup-*.tar.gz
ls: cannot access '/root/hugo-kingtolga-backup-*.tar.gz': Permission denied

Same thing that’s bitten me before on this exact box. My regular user can’t read into root’s home directory, so the wildcard never actually expands, bash just passes the literal asterisk through and the command fails before sudo even gets a chance to elevate. Wrapping the whole thing in a subshell fixes it:

sudo bash -c 'ls -la /root/hugo-kingtolga-backup-*.tar.gz'

That worked, real file, real size, confirmed.

Getting it off the box, attempt one, wrong IP entirely

Wanted a copy on my other Fedora desktop, not just sitting on the same machine that could theoretically die and take the only copy with it. Tried scp to an IP I had written down from memory:

sudo bash -c 'scp /root/hugo-kingtolga-backup-20260720.tar.gz tolga@192.168.0.XX1:/home/tolga/'
ssh: connect to host 192.168.0.XX1 port 22: No route to host

Pinged it directly to rule out a firewall issue first:

ping -c 3 192.168.0.XX1
Destination Host Unreachable

Genuinely the wrong address, not a config problem. The IP I had in my head for that machine was stale, left over from something else entirely. Found the actual current address and tried again.

Attempt two, right IP, wrong service state

sudo bash -c 'scp /root/hugo-kingtolga-backup-20260720.tar.gz tolga@192.168.0.XX2:/home/tolga/'
ssh: connect to host 192.168.0.XX2 port 22: Connection refused

Different error this time, and a meaningfully different one. No route to host means the machine can’t be reached at all. Connection refused means the machine is reachable but actively rejecting the connection, which usually means the service just isn’t running. Confirmed with a ping first:

ping -c 3 192.168.0.XX2

Real replies, real round trip times, machine was genuinely up. So the problem had to be sshd itself. Checked on that machine directly:

sudo systemctl status sshd
Loaded: loaded (...disabled; preset: disabled)
Active: inactive (dead)

Just never enabled. Fixed it:

sudo systemctl enable --now sshd

Attempt three, service running, transfer still failing

sudo bash -c 'scp /root/hugo-kingtolga-backup-20260720.tar.gz tolga@192.168.0.XX2:/home/tolga/'

Got past the connection this time, prompted for a password, then:

scp: Received message too long 168427520
scp: Ensure the remote shell produces no output for non-interactive sessions.

Recognised this one. My bashrc on every machine I run has a proper banner, a fortune quote piped through lolcat, an uptime summary, the works, all firing automatically at the bottom of the file on every shell startup. scp opens a brief, non-interactive shell on the remote end to actually perform the copy, and that shell was firing the entire banner sequence straight into the data stream scp expects to be silent, corrupting the transfer before a single real byte got through.

The actual fix

Added an early return right after the bashrc header, before anything else in the file gets a chance to run, so a non-interactive shell exits immediately instead of executing the banner and fortune output:

case $- in
    *i*) ;;
      *) return;;
esac

That checks the shell’s own option flags for the letter i, which is only present on a genuinely interactive session. Anything else, including the quick shell scp opens for the file copy, hits return immediately and stays completely silent from that point on.

The actual transfer, finally

sudo bash -c 'scp /root/hugo-kingtolga-backup-20260720.tar.gz /root/www-kingtolga-backup-20260720.tar.gz tolga@192.168.0.XX2:/home/tolga/'

Clean transfer, real progress bars, both files landed. Confirmed on the other end:

ssh tolga@192.168.0.XX2 'ls -la /home/tolga/hugo-kingtolga-backup-20260720.tar.gz /home/tolga/www-kingtolga-backup-20260720.tar.gz'

Making it automatic going forward

Folded the hugo folder into the existing weekly config backup cron job so this happens without me remembering to run it by hand:

sudo sed -i 's|/root/backup-staging /etc/ssh /etc/samba /usr/local/bin|/root/backup-staging /etc/ssh /etc/samba /usr/local/bin /home/tolga/hugo|' /etc/cron.d/config-backup

What actually went wrong, start to finish

Exclude flags in the wrong position in the tar command, the same sudo-versus-shell-glob timing issue I have hit more than once on this box, a genuinely stale IP address written down from memory instead of checked, an SSH service that was simply never enabled on the target machine, and a bashrc firing full interactive output into a protocol that needs total silence. None of it was one clean fix. Every step taught me something the previous one didn’t, and the actual working sequence only exists because each wrong attempt narrowed down exactly what was left to check next.