Creates a monthly local backup of a Docker-hosted Wiki.js site without creating, deleting, or modifying remote Wiki.js/PostgreSQL data. The remote exports are streamed over SSH to the backup machine.
The Windows job runs under the Electrons workshop PC account and stores archives at \\rdrive.engineering.auckland.ac.nz\Research$\MECH\FormulaSAE\01_Team Administration\Common\Auto_Docs_Backup. It uses the restricted fsae-docs-backup server account; the job can only request the two read-only exports, assets and database. Windows backups use .tar archives because the built-in PowerShell ZIP tool fails with large archives.
Open PowerShell as your normal Windows user, not as Administrator:
# Current workshop-PC repository location:
Set-Location 'C:\Users\fsae47admin\Documents'
git clone https://github.com/UOA-FSAE/fsae_docs_backup.git fsae_docs_backup
Set-Location .\fsae_docs_backup
.\windows\Install-FsaeDocsBackup.ps1This copies the executable backup files to %LOCALAPPDATA%\FSAE Docs Backup and creates the non-secret configuration file at %APPDATA%\FSAE Docs Backup\config.psd1.
You may clone the repository anywhere you prefer; after installation, the scheduled task runs the installed copy in %LOCALAPPDATA%, not the clone directory.
Edit this local configuration file:
notepad "$env:APPDATA\FSAE Docs Backup\config.psd1"The production configuration uses:
BackupRoot = '\\rdrive.engineering.auckland.ac.nz\Research$\MECH\FormulaSAE\01_Team Administration\Common\Auto_Docs_Backup'This UNC path is used instead of R: because mapped drives may not be available when the scheduled task runs without an interactive sign-in. The task account must have read/write access to the share.
Still in PowerShell, create a dedicated key. The file without .pub is private and stays on this Windows machine.
ssh-keygen.exe -t ed25519 -a 100 -f "$env:USERPROFILE\.ssh\fsae_docs_backup_ed25519" -C "fsae docs backup windows"
Get-Content "$env:USERPROFILE\.ssh\fsae_docs_backup_ed25519.pub"Copy the public key and server installer to the Wiki server using the existing administrator login:
scp.exe .\server\install-restricted-exporter.sh root@167.172.67.91:/root/
scp.exe "$env:USERPROFILE\.ssh\fsae_docs_backup_ed25519.pub" root@167.172.67.91:/root/
ssh.exe root@167.172.67.91On the Wiki server, run this installer command. It preserves any existing backup key and adds this Windows key:
chmod 700 /root/install-restricted-exporter.sh
/root/install-restricted-exporter.sh \
--public-key-file /root/fsae_docs_backup_ed25519.pub \
--wiki-container wiki --database-container db \
--database-user wiki --database-name wiki \
--storage-path /wiki/data/contentBefore accepting a new server key, compare its fingerprint with this command run through your existing authenticated server session:
sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pubOn Windows, verify the same fingerprint is presented:
ssh-keyscan.exe -t ed25519 167.172.67.91 2>$null | ssh-keygen.exe -lf -When the fingerprints match, make an initial SSH connection and type yes if prompted:
ssh.exe -o BatchMode=yes -o IdentitiesOnly=yes -i "$env:USERPROFILE\.ssh\fsae_docs_backup_ed25519" fsae-docs-backup@167.172.67.91 assets > $nullThe command is expected to create a temporary local stream only; it does not change the server. Then create your first full backup:
& "$env:LOCALAPPDATA\FSAE Docs Backup\Backup-FsaeDocs.ps1" -ConfigPath "$env:APPDATA\FSAE Docs Backup\config.psd1"
Get-ChildItem '\\rdrive.engineering.auckland.ac.nz\Research$\MECH\FormulaSAE\01_Team Administration\Common\Auto_Docs_Backup'After the manual backup succeeds, run this from the cloned repository:
.\windows\Install-FsaeDocsBackup.ps1 -InstallScheduledTaskIt prompts for your Windows account password once so Task Scheduler can run the job at 02:00 on the first day of every month, even if you are not signed in. Task Scheduler protects that password locally; it is not written to the configuration file or repository. Review or run the task in Task Scheduler → Task Scheduler Library → FSAE Docs Monthly Backup.
After each successful backup, the runner keeps the newest three TAR archives and matching checksum files, then deletes older ones. It does not delete old backups when a new backup fails.
Pull the updated repository, then run the installer once (without -InstallScheduledTask) to replace the installed runner. The existing scheduled task will use the updated runner on its next execution.
git pull
.\windows\Install-FsaeDocsBackup.ps1Run these individual commands on the Windows backup PC:
(Get-ScheduledTask -TaskName 'FSAE Docs Monthly Backup').StateReady means the task is enabled and waiting. Other useful checks:
(Get-ScheduledTask -TaskName 'FSAE Docs Monthly Backup' | Get-ScheduledTaskInfo).NextRunTimeNew-TimeSpan -Start (Get-Date) -End ((Get-ScheduledTask -TaskName 'FSAE Docs Monthly Backup' | Get-ScheduledTaskInfo).NextRunTime)(Get-ScheduledTask -TaskName 'FSAE Docs Monthly Backup' | Get-ScheduledTaskInfo).LastRunTime(Get-ScheduledTask -TaskName 'FSAE Docs Monthly Backup' | Get-ScheduledTaskInfo).LastTaskResult(Get-ScheduledTask -TaskName 'FSAE Docs Monthly Backup' | Get-ScheduledTaskInfo).NumberOfMissedRuns0 for LastTaskResult means the last task run completed successfully. To inspect the configured schedule:
(Get-ScheduledTask -TaskName 'FSAE Docs Monthly Backup').Triggers | Format-List *Clone the repository into your normal Ubuntu user's source directory, for example ~/src/FSAE_DOCS_BACKUP. Do not clone it into the backup destination: the installer uses ~/FSAE_Docs_Backups for backup archives, so repository changes and backup retention cannot affect one another.
mkdir -p ~/src
git clone https://github.com/UOA-FSAE/fsae_docs_backup.git ~/src/FSAE_DOCS_BACKUP
cd ~/src/FSAE_DOCS_BACKUP
chmod +x install.sh fsae-docs-backup server/install-restricted-exporter.sh
./install.sh --install-depsGenerate the SSH key on the Ubuntu VM. The private key never leaves this VM.
ssh-keygen -t ed25519 -a 100 -f ~/.ssh/fsae_docs_backup_ed25519 -C 'fsae docs backup'
cat ~/.ssh/fsae_docs_backup_ed25519.pubVerify the Wiki server's host-key fingerprint through a trusted administrator before recording it:
ssh-keyscan -H 167.172.67.91 >> ~/.ssh/known_hosts
ssh-keygen -lf ~/.ssh/known_hostsDo not use ssh-keyscan alone as identity verification; compare the displayed fingerprint through an independent trusted channel first.
If SSH is your only server access, use your existing authenticated administrator session as that trusted channel. On the Wiki server, run:
sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pubOn the backup VM, compare it with:
ssh-keyscan -t ed25519 167.172.67.91 2>/dev/null | ssh-keygen -lf -Only if the fingerprints match, record the key:
ssh-keyscan -H -t ed25519 167.172.67.91 >> ~/.ssh/known_hosts
chmod 600 ~/.ssh/known_hostsFirst inventory the real containers and mounts while logged in as an administrator:
sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
sudo docker inspect wiki --format '{{range .Mounts}}{{println .Source " -> " .Destination}}{{end}}'Copy the VM public key to a temporary file on the Wiki server, then run the server installer with the values discovered above.
From the backup VM, while using your existing administrator SSH login:
scp server/install-restricted-exporter.sh root@167.172.67.91:/root/
scp ~/.ssh/fsae_docs_backup_ed25519.pub root@167.172.67.91:/root/
ssh -t root@167.172.67.91Then, on the Wiki server:
chmod 700 /root/install-restricted-exporter.sh
/root/install-restricted-exporter.sh \
--public-key-file /root/fsae_docs_backup_ed25519.pub \
--wiki-container wiki --database-container db \
--database-user wiki --database-name wiki \
--storage-path /wiki/data/contentThese are the confirmed container names and Wiki.js storage path. Do not paste database passwords, private keys, or other credentials into commands or configuration files.
This creates the key-only fsae-docs-backup account. It has no shell access or Docker-group membership; its key can request only assets and database export streams. The installer writes server configuration and account files, but it does not modify Wiki.js pages, uploads, the database, or containers.
If an older installer created the account with a locked shadow entry, OpenSSH will reject public-key authentication before checking authorized_keys. Re-run the current installer, or repair the existing account on the Wiki server with:
sudo usermod --password '*NP*' fsae-docs-backup*NP* cannot be used as a password, but unlike a value beginning with !, it does not make OpenSSH reject the account itself. Test from the backup machine with the real server address (do not type the angle-bracket placeholder):
ssh.exe -o BatchMode=yes -o IdentitiesOnly=yes -i "$env:USERPROFILE\.ssh\fsae_docs_backup_ed25519" fsae-docs-backup@167.172.67.91 assets > $nullcp config.env.example ~/.config/fsae-docs-backup/config.env
chmod 600 ~/.config/fsae-docs-backup/config.env
nano ~/.config/fsae-docs-backup/config.env
~/.local/bin/fsae-docs-backupLeave SERVER=167.172.67.91 and SSH_USER=fsae-docs-backup as supplied, and use EXPORT_MODE=restricted. The container and database fields are recorded as metadata only in restricted mode, but are pre-filled with the confirmed inventory values.
Verify a completed archive:
cd ~/FSAE_Docs_Backups
sha256sum -c fsae_docs_backup_*.zip.sha256
unzip -l fsae_docs_backup_*.zipAfter the first manual backup verifies successfully:
cd ~/src/FSAE_DOCS_BACKUP
./install.sh --install-cron
crontab -lThe cron job runs at 02:00 on day 1 of every month, in the VM's local time. Logs are written to ~/FSAE_Docs_Backups/backup.log. Change the schedule by editing crontab -e; do not create duplicate jobs.
Do this on the Ubuntu backup VM, not on the production Wiki server. It creates a separate local Wiki.js and PostgreSQL deployment; it never contacts 167.172.67.91.
Install Docker and Docker Compose v2, then log out and in again so your user receives Docker access:
sudo apt update
sudo apt install -y docker.io docker-compose-v2 unzip
sudo usermod -aG docker "$USER"Confirm this works in a new terminal before proceeding:
docker ps
docker compose versionPull the current repository and reinstall the helper if you installed it before this restore feature was added:
cd ~/src/FSAE_DOCS_BACKUP
git pull
./install.shChoose one completed ZIP backup. If its matching .sha256 file is beside it, the script checks it before doing anything else.
~/.local/bin/fsae-docs-restore-test \
~/FSAE_Docs_Backups/fsae_docs_backup_YYYY-MM-DD_HHMMSSZ.zipThe script extracts the files into ~/fsae-docs-restore-test, starts a new local PostgreSQL 11 container, restores wiki.postgres.dump, then starts a new Wiki.js 2 container. The restored Wiki is reachable only from the VM at http://127.0.0.1:8088.
Review a representative set of pages, history, users, and uploaded files. The data comes from the backup, so restored account passwords are the same as at the time of backup; do not expose this test service to a network.
After review, stop the containers and remove their test-only database volume:
cd ~/fsae-docs-restore-test
docker compose -p fsae_docs_restore_test down -vIf you no longer need the extracted test files, remove that exact test directory:
rm -rf ~/fsae-docs-restore-testThe restore script refuses to reuse a non-empty test directory. Use --workdir /another/empty/path if you need more than one test at a time.
A production recovery is intentionally not automated. It requires a deliberate maintenance window: stop Wiki.js, preserve the failed database/storage for forensics, restore into the production database and /wiki/data/content, then validate the application before reopening it. Never run the local test script on the production host or point it at production Docker volumes.
This backs up Wiki content and uploaded assets. It does not yet back up reverse-proxy/TLS configuration, Docker Compose files, or application secrets. Store those separately in encrypted, access-controlled configuration backup once their locations are identified.