562 lines
14 KiB
Markdown
562 lines
14 KiB
Markdown
# Business Central SaaS Automated Backup System
|
||
|
||
Comprehensive backup solution for Microsoft Dynamics 365 Business Central SaaS that:
|
||
- Exports database via BC Admin Center API every hour
|
||
- Encrypts backups with GPG (AES-256)
|
||
- Uploads to S3-compatible object storage
|
||
- Enables immutability with 30-day delete prevention
|
||
- Maintains timestamped backup history
|
||
|
||
## Features
|
||
|
||
- **Automated Hourly Backups**: Uses cron/systemd to run backups on schedule
|
||
- **Secure Encryption**: GPG encryption with AES-256 cipher
|
||
- **Immutable Storage**: S3 Object Lock (WORM) with 30-day retention
|
||
- **Multiple S3 Providers**: AWS S3, MinIO, Wasabi, Backblaze B2
|
||
- **Comprehensive Logging**: Detailed logs for troubleshooting
|
||
- **Error Handling**: Retries and proper error reporting
|
||
- **Clean Architecture**: Modular bash + PowerShell scripts
|
||
|
||
## Architecture
|
||
|
||
```
|
||
┌─────────────────────┐
|
||
│ Cron/Systemd │
|
||
│ (Hourly Trigger) │
|
||
└──────────┬──────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────┐
|
||
│ bc-backup.sh │ ◄─── Main orchestration script (bash)
|
||
│ - Orchestrates │
|
||
│ - Encryption │
|
||
│ - S3 Upload │
|
||
└──────────┬──────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────┐
|
||
│ bc-export.ps1 │ ◄─── BC export logic (PowerShell)
|
||
│ - Azure AD auth │
|
||
│ - API calls │
|
||
│ - Download BACPAC │
|
||
└──────────┬──────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────┐
|
||
│ BC Admin API │
|
||
│ (Microsoft) │
|
||
└─────────────────────┘
|
||
```
|
||
|
||
## Prerequisites
|
||
|
||
### 1. System Requirements
|
||
|
||
- **Linux server** (Ubuntu 20.04+, Debian 10+, CentOS 7+, or similar)
|
||
- **PowerShell 7+** (installed automatically by setup script)
|
||
- **GPG** (for encryption)
|
||
- **AWS CLI v2** or **s3cmd** (for S3 uploads)
|
||
- **Root or sudo access** (for initial setup)
|
||
|
||
### 2. Business Central Requirements
|
||
|
||
- Active Business Central SaaS subscription (paid, not trial)
|
||
- Production environment (exports only available from Production)
|
||
- Admin access to BC Admin Center
|
||
|
||
### 3. Azure AD App Registration
|
||
|
||
You need an Azure AD application with API permissions to access BC Admin Center API.
|
||
|
||
### 4. S3-Compatible Storage
|
||
|
||
- S3 bucket with **Object Lock enabled** (immutability)
|
||
- Access credentials (Access Key ID + Secret Access Key)
|
||
|
||
## Quick Start
|
||
|
||
### 1. Download and Setup
|
||
|
||
```bash
|
||
cd /home/malin/c0ding/bcbak
|
||
chmod +x setup.sh
|
||
./setup.sh
|
||
```
|
||
|
||
The setup script will:
|
||
- Check and install dependencies (PowerShell, GPG, AWS CLI)
|
||
- Create directory structure
|
||
- Copy configuration template
|
||
- Set proper permissions
|
||
|
||
### 2. Create Azure AD App Registration
|
||
|
||
#### Step-by-Step:
|
||
|
||
1. Navigate to [Azure Portal](https://portal.azure.com)
|
||
2. Go to **Azure Active Directory** > **App registrations** > **New registration**
|
||
3. Configure:
|
||
- **Name**: `BC-Backup-Service`
|
||
- **Supported account types**: Accounts in this organizational directory only
|
||
- **Redirect URI**: Leave empty
|
||
4. Click **Register**
|
||
5. Note the following from the Overview page:
|
||
- **Application (client) ID**
|
||
- **Directory (tenant) ID**
|
||
|
||
#### Create Client Secret:
|
||
|
||
1. Go to **Certificates & secrets** > **New client secret**
|
||
2. **Description**: `BC Backup Key`
|
||
3. **Expires**: Choose appropriate duration (6 months, 1 year, etc.)
|
||
4. Click **Add**
|
||
5. **Copy the secret value immediately** (shown only once!)
|
||
|
||
#### Add API Permissions:
|
||
|
||
1. Go to **API permissions** > **Add a permission**
|
||
2. Select **Dynamics 365 Business Central**
|
||
3. Choose **Application permissions** (not Delegated)
|
||
4. Select: `Automation.ReadWrite.All` or `API.ReadWrite.All`
|
||
5. Click **Add permissions**
|
||
6. **Important**: Click **Grant admin consent for [Your Organization]**
|
||
- Requires Global Administrator role
|
||
|
||
### 3. Configure S3 Bucket with Object Lock
|
||
|
||
#### AWS S3 Example:
|
||
|
||
```bash
|
||
# Create bucket with Object Lock enabled
|
||
aws s3api create-bucket \
|
||
--bucket my-bc-backups \
|
||
--region us-east-1 \
|
||
--object-lock-enabled-for-bucket
|
||
|
||
# Configure default retention (30 days, COMPLIANCE mode)
|
||
aws s3api put-object-lock-configuration \
|
||
--bucket my-bc-backups \
|
||
--object-lock-configuration '{
|
||
"ObjectLockEnabled": "Enabled",
|
||
"Rule": {
|
||
"DefaultRetention": {
|
||
"Mode": "COMPLIANCE",
|
||
"Days": 30
|
||
}
|
||
}
|
||
}'
|
||
```
|
||
|
||
#### MinIO Example:
|
||
|
||
```bash
|
||
# Create bucket
|
||
mc mb myminio/my-bc-backups --with-lock
|
||
|
||
# Set retention
|
||
mc retention set --default COMPLIANCE "30d" myminio/my-bc-backups
|
||
```
|
||
|
||
**Important**: Object Lock can **only be enabled when creating a bucket**. You cannot add it to existing buckets.
|
||
|
||
### 4. Configure the Backup System
|
||
|
||
Edit the configuration file:
|
||
|
||
```bash
|
||
nano bc-backup.conf
|
||
```
|
||
|
||
Fill in the required values:
|
||
|
||
```bash
|
||
# Azure AD Configuration
|
||
AZURE_TENANT_ID="your-tenant-id-here"
|
||
AZURE_CLIENT_ID="your-client-id-here"
|
||
AZURE_CLIENT_SECRET="your-client-secret-here"
|
||
|
||
# Business Central
|
||
BC_ENVIRONMENT_NAME="Production"
|
||
|
||
# Generate a strong encryption passphrase
|
||
ENCRYPTION_PASSPHRASE="$(openssl rand -base64 32)"
|
||
|
||
# S3 Configuration
|
||
S3_BUCKET="my-bc-backups"
|
||
S3_ENDPOINT="https://s3.amazonaws.com" # or your S3 provider
|
||
AWS_ACCESS_KEY_ID="your-access-key"
|
||
AWS_SECRET_ACCESS_KEY="your-secret-key"
|
||
AWS_DEFAULT_REGION="us-east-1"
|
||
|
||
# Backup settings
|
||
RETENTION_DAYS="30"
|
||
S3_TOOL="awscli"
|
||
CLEANUP_LOCAL="true"
|
||
```
|
||
|
||
**Security**: Store your `ENCRYPTION_PASSPHRASE` securely in a password manager!
|
||
|
||
### 5. Test Manual Backup
|
||
|
||
Run a test backup:
|
||
|
||
```bash
|
||
./bc-backup.sh
|
||
```
|
||
|
||
Monitor the logs:
|
||
|
||
```bash
|
||
tail -f logs/backup.log
|
||
```
|
||
|
||
Expected output:
|
||
```
|
||
[2026-01-07 10:00:00] =========================================
|
||
[2026-01-07 10:00:00] Starting Business Central backup process
|
||
[2026-01-07 10:00:00] =========================================
|
||
[2026-01-07 10:00:00] Environment: Production
|
||
[2026-01-07 10:00:00] S3 Bucket: my-bc-backups
|
||
[2026-01-07 10:00:00] Retention: 30 days
|
||
[2026-01-07 10:00:01] Step 1: Initiating database export via BC Admin Center API
|
||
...
|
||
```
|
||
|
||
### 6. Set Up Automated Hourly Backups
|
||
|
||
#### Option A: Using Cron (Simpler)
|
||
|
||
```bash
|
||
crontab -e
|
||
```
|
||
|
||
Add this line for hourly backups:
|
||
```
|
||
0 * * * * /home/malin/c0ding/bcbak/bc-backup.sh >> /home/malin/c0ding/bcbak/logs/cron.log 2>&1
|
||
```
|
||
|
||
See `cron-examples.txt` for more scheduling options.
|
||
|
||
#### Option B: Using Systemd Timers (More Reliable)
|
||
|
||
Create service file:
|
||
```bash
|
||
sudo nano /etc/systemd/system/bc-backup.service
|
||
```
|
||
|
||
```ini
|
||
[Unit]
|
||
Description=Business Central Database Backup
|
||
|
||
[Service]
|
||
Type=oneshot
|
||
User=malin
|
||
WorkingDirectory=/home/malin/c0ding/bcbak
|
||
ExecStart=/home/malin/c0ding/bcbak/bc-backup.sh
|
||
StandardOutput=append:/home/malin/c0ding/bcbak/logs/backup.log
|
||
StandardError=append:/home/malin/c0ding/bcbak/logs/backup.log
|
||
```
|
||
|
||
Create timer file:
|
||
```bash
|
||
sudo nano /etc/systemd/system/bc-backup.timer
|
||
```
|
||
|
||
```ini
|
||
[Unit]
|
||
Description=Run BC Backup Every Hour
|
||
|
||
[Timer]
|
||
OnCalendar=hourly
|
||
Persistent=true
|
||
|
||
[Install]
|
||
WantedBy=timers.target
|
||
```
|
||
|
||
Enable and start:
|
||
```bash
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl enable bc-backup.timer
|
||
sudo systemctl start bc-backup.timer
|
||
sudo systemctl status bc-backup.timer
|
||
```
|
||
|
||
## File Structure
|
||
|
||
```
|
||
bcbak/
|
||
├── bc-backup.sh # Main orchestration script
|
||
├── bc-export.ps1 # PowerShell BC export logic
|
||
├── bc-backup.conf # Your configuration (gitignored)
|
||
├── bc-backup.conf.template # Configuration template
|
||
├── setup.sh # Installation script
|
||
├── cron-examples.txt # Cron scheduling examples
|
||
├── README.md # This file
|
||
├── logs/ # Backup logs
|
||
│ ├── backup.log
|
||
│ └── cron.log
|
||
└── temp/ # Temporary files (auto-cleaned)
|
||
└── bc_backup_*.bacpac
|
||
```
|
||
|
||
## How It Works
|
||
|
||
### 1. Database Export (bc-export.ps1)
|
||
|
||
- Authenticates to Azure AD using client credentials (OAuth 2.0)
|
||
- Calls BC Admin Center API to initiate database export
|
||
- Polls for completion (exports can take 15-60 minutes)
|
||
- Downloads BACPAC file to local temp directory
|
||
|
||
### 2. Encryption (bc-backup.sh)
|
||
|
||
- Uses GPG with AES-256 symmetric encryption
|
||
- Encrypts the BACPAC file with your passphrase
|
||
- Original unencrypted file is deleted
|
||
|
||
### 3. Upload to S3
|
||
|
||
- Uploads encrypted file with timestamp in filename
|
||
- Format: `backups/bc_backup_Production_20260107_100000.bacpac.gpg`
|
||
- Sets Object Lock retention (COMPLIANCE mode, 30 days)
|
||
- Files are **immutable** and **cannot be deleted** until retention expires
|
||
|
||
### 4. Verification & Cleanup
|
||
|
||
- Verifies upload success
|
||
- Removes local encrypted file (optional)
|
||
- Logs all operations
|
||
|
||
## Restoring from Backup
|
||
|
||
### 1. Download Encrypted Backup
|
||
|
||
```bash
|
||
# Using AWS CLI
|
||
aws s3 cp \
|
||
s3://my-bc-backups/backups/bc_backup_Production_20260107_100000.bacpac.gpg \
|
||
./backup.bacpac.gpg \
|
||
--endpoint-url https://s3.amazonaws.com
|
||
```
|
||
|
||
### 2. Decrypt the Backup
|
||
|
||
```bash
|
||
# Enter your ENCRYPTION_PASSPHRASE when prompted
|
||
gpg --decrypt backup.bacpac.gpg > backup.bacpac
|
||
```
|
||
|
||
### 3. Restore to Azure SQL Database
|
||
|
||
```bash
|
||
# Using SqlPackage (download from Microsoft)
|
||
sqlpackage /a:Import \
|
||
/sf:backup.bacpac \
|
||
/tsn:your-server.database.windows.net \
|
||
/tdn:RestoredDatabase \
|
||
/tu:admin \
|
||
/tp:password
|
||
```
|
||
|
||
### 4. Connect BC to Restored Database
|
||
|
||
Contact Microsoft Support to point your BC environment to the restored database.
|
||
|
||
## Monitoring and Maintenance
|
||
|
||
### Check Backup Logs
|
||
|
||
```bash
|
||
# View latest backup log
|
||
tail -100 logs/backup.log
|
||
|
||
# Follow live log
|
||
tail -f logs/backup.log
|
||
|
||
# Check for errors
|
||
grep ERROR logs/backup.log
|
||
```
|
||
|
||
### List S3 Backups
|
||
|
||
```bash
|
||
# AWS CLI
|
||
aws s3 ls s3://my-bc-backups/backups/ --endpoint-url https://s3.amazonaws.com
|
||
|
||
# s3cmd
|
||
s3cmd ls s3://my-bc-backups/backups/
|
||
```
|
||
|
||
### Check Object Lock Status
|
||
|
||
```bash
|
||
aws s3api get-object-retention \
|
||
--bucket my-bc-backups \
|
||
--key backups/bc_backup_Production_20260107_100000.bacpac.gpg \
|
||
--endpoint-url https://s3.amazonaws.com
|
||
```
|
||
|
||
### Verify Cron/Timer Status
|
||
|
||
```bash
|
||
# Cron
|
||
crontab -l
|
||
grep CRON /var/log/syslog | tail
|
||
|
||
# Systemd
|
||
systemctl status bc-backup.timer
|
||
journalctl -u bc-backup.service -n 50
|
||
```
|
||
|
||
## Troubleshooting
|
||
|
||
### Issue: "Authentication failed"
|
||
|
||
**Solution**: Verify Azure AD credentials
|
||
- Check `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`
|
||
- Verify API permissions are granted with admin consent
|
||
- Ensure client secret hasn't expired
|
||
|
||
### Issue: "Database export failed - not authorized"
|
||
|
||
**Causes**:
|
||
- Only Production environments can be exported
|
||
- Trial subscriptions don't support exports
|
||
- Missing API permissions
|
||
|
||
**Solution**: Verify environment is Production with paid subscription
|
||
|
||
### Issue: "Export timeout exceeded"
|
||
|
||
**Solution**: Increase timeout
|
||
```bash
|
||
# In bc-backup.conf
|
||
MAX_EXPORT_WAIT_MINUTES="180" # 3 hours
|
||
```
|
||
|
||
### Issue: "Object lock not supported"
|
||
|
||
**Solution**: Recreate bucket with Object Lock
|
||
- Object Lock can only be enabled at bucket creation
|
||
- Migrate existing backups to new bucket
|
||
|
||
### Issue: "Upload failed - access denied"
|
||
|
||
**Solution**: Check S3 credentials and permissions
|
||
```bash
|
||
# Test AWS CLI configuration
|
||
aws s3 ls --endpoint-url https://s3.amazonaws.com
|
||
|
||
# Verify bucket policy allows PutObject and PutObjectRetention
|
||
```
|
||
|
||
### Issue: "Decryption failed"
|
||
|
||
**Solution**: Verify encryption passphrase
|
||
- Ensure you're using the correct `ENCRYPTION_PASSPHRASE`
|
||
- Check for special characters that might need escaping
|
||
|
||
## Security Best Practices
|
||
|
||
1. **Protect Configuration File**
|
||
- Set proper permissions: `chmod 600 bc-backup.conf`
|
||
- Never commit to version control (use `.gitignore`)
|
||
|
||
2. **Rotate Credentials Regularly**
|
||
- Azure AD client secrets (every 6-12 months)
|
||
- S3 access keys (annually)
|
||
- Encryption passphrase (when staff changes)
|
||
|
||
3. **Use Separate Service Account**
|
||
- Create dedicated Linux user for backups
|
||
- Run with minimal permissions
|
||
|
||
4. **Encryption Key Management**
|
||
- Store `ENCRYPTION_PASSPHRASE` in password manager
|
||
- Document in secure runbook
|
||
- Test decryption regularly
|
||
|
||
5. **Monitor for Failures**
|
||
- Set up log monitoring/alerting
|
||
- Test restore process monthly
|
||
|
||
6. **Network Security**
|
||
- Use HTTPS for S3 endpoints
|
||
- Consider VPN for sensitive environments
|
||
|
||
## Limitations
|
||
|
||
1. **BC API Limits**
|
||
- Maximum 10 database exports per month (Microsoft limit)
|
||
- This script tracks recent exports to avoid unnecessary duplicates
|
||
|
||
2. **Export Restrictions**
|
||
- Only Production environments
|
||
- Only paid subscriptions
|
||
- Exports can take 15-60 minutes
|
||
|
||
3. **Object Lock Immutability**
|
||
- Files cannot be deleted until retention expires
|
||
- Ensure adequate S3 storage capacity
|
||
- Plan for storage costs
|
||
|
||
4. **Bandwidth**
|
||
- Large databases require significant bandwidth
|
||
- Consider S3 transfer costs
|
||
|
||
## Cost Considerations
|
||
|
||
### S3 Storage Costs (Example: AWS)
|
||
|
||
For a 50GB database with hourly backups:
|
||
- **Storage**: ~50GB × 720 backups (30 days) = 36TB × $0.023/GB = ~$830/month
|
||
- **Uploads**: 720 requests × $0.005/1000 = ~$0.004/month
|
||
- **Data Transfer Out** (for restores): $0.09/GB
|
||
|
||
**Recommendation**: Consider daily backups instead of hourly to reduce costs.
|
||
|
||
### Optimization Strategies
|
||
|
||
1. **Reduce Frequency**: Daily or every 6 hours instead of hourly
|
||
2. **Lifecycle Policies**: Move older backups to cheaper storage tiers
|
||
3. **Incremental Backups**: Consider BC's built-in continuous backup for point-in-time recovery
|
||
|
||
## Support and Contributing
|
||
|
||
### Getting Help
|
||
|
||
1. Check logs: `logs/backup.log`
|
||
2. Review troubleshooting section above
|
||
3. Check BC Admin Center for export status
|
||
4. Verify S3 bucket configuration
|
||
|
||
### Reporting Issues
|
||
|
||
When reporting issues, include:
|
||
- Relevant log excerpts
|
||
- BC environment type (Production/Sandbox)
|
||
- S3 provider (AWS/MinIO/etc.)
|
||
- Error messages
|
||
|
||
## License
|
||
|
||
This backup solution is provided as-is without warranty. Use at your own risk.
|
||
|
||
## References
|
||
|
||
- [BC Admin Center API Documentation](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/administration/administration-center-api)
|
||
- [BC Data Extraction](https://github.com/microsoft/BCTech/tree/master/samples/ExtractData)
|
||
- [AWS S3 Object Lock](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html)
|
||
- [GPG Documentation](https://gnupg.org/documentation/)
|
||
|
||
## Changelog
|
||
|
||
### v1.0.0 (2026-01-07)
|
||
- Initial release
|
||
- Hourly automated backups
|
||
- GPG encryption with AES-256
|
||
- S3 Object Lock support
|
||
- AWS CLI and s3cmd support
|
||
- Comprehensive logging
|