feat: switch from Admin Center database export to BC API v2.0 data extraction
The Admin Center export API requires an Azure Storage SAS URI which requires an Azure Subscription - defeating the purpose of an independent backup. Instead, use BC API v2.0 to extract critical business data (customers, vendors, items, GL entries, invoices, etc.) as JSON files. - bc-export.ps1: rewritten to use BC API v2.0 endpoints, extracts 23 entity types per company with OData pagination support - bc-backup.sh: handles JSON export directory, creates tar.gz archive before encrypting and uploading to S3 - bc-backup.conf.template: removed Azure Storage SAS config, added optional BC_COMPANY_NAME filter - decrypt-backup.sh: updated for tar.gz.gpg format, shows extracted entity files and metadata after decryption Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Business Central Backup Decryption Utility
|
||||
# Decrypts a GPG-encrypted BACPAC backup file
|
||||
# Decrypts a GPG-encrypted backup archive and extracts JSON data
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
@@ -37,22 +37,22 @@ show_usage() {
|
||||
cat << EOF
|
||||
Business Central Backup Decryption Utility
|
||||
|
||||
Usage: $0 <encrypted-file> [output-file]
|
||||
Usage: $0 <encrypted-file> [output-directory]
|
||||
|
||||
Arguments:
|
||||
<encrypted-file> Path to the encrypted .gpg backup file
|
||||
[output-file] Optional: Path for decrypted output (default: removes .gpg extension)
|
||||
<encrypted-file> Path to the encrypted .tar.gz.gpg backup file
|
||||
[output-directory] Optional: Path to extract data to (default: current directory)
|
||||
|
||||
Examples:
|
||||
# Decrypt to default name (backup.bacpac)
|
||||
$0 backup.bacpac.gpg
|
||||
# Decrypt and extract to current directory
|
||||
$0 bc_backup_Production_20260107_100000.tar.gz.gpg
|
||||
|
||||
# Decrypt to specific name
|
||||
$0 backup.bacpac.gpg restored_database.bacpac
|
||||
# Decrypt and extract to specific directory
|
||||
$0 backup.tar.gz.gpg ./restored-data/
|
||||
|
||||
# Download from S3 and decrypt
|
||||
aws s3 cp s3://bucket/backups/bc_backup_Production_20260107_100000.bacpac.gpg ./backup.gpg
|
||||
$0 backup.gpg
|
||||
aws s3 cp s3://bucket/backups/bc_backup_Production_20260107_100000.tar.gz.gpg ./backup.gpg
|
||||
$0 backup.gpg ./restored/
|
||||
|
||||
Note: You will be prompted for the encryption passphrase.
|
||||
This is the ENCRYPTION_PASSPHRASE from bc-backup.conf
|
||||
@@ -66,7 +66,7 @@ if [[ $# -lt 1 ]]; then
|
||||
fi
|
||||
|
||||
ENCRYPTED_FILE="$1"
|
||||
OUTPUT_FILE="${2:-}"
|
||||
OUTPUT_DIR="${2:-.}"
|
||||
|
||||
# Validate encrypted file exists
|
||||
if [[ ! -f "$ENCRYPTED_FILE" ]]; then
|
||||
@@ -74,72 +74,33 @@ if [[ ! -f "$ENCRYPTED_FILE" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine output filename
|
||||
if [[ -z "$OUTPUT_FILE" ]]; then
|
||||
# Remove .gpg extension
|
||||
OUTPUT_FILE="${ENCRYPTED_FILE%.gpg}"
|
||||
|
||||
# If still the same (no .gpg extension), append .decrypted
|
||||
if [[ "$OUTPUT_FILE" == "$ENCRYPTED_FILE" ]]; then
|
||||
OUTPUT_FILE="${ENCRYPTED_FILE}.decrypted"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if output file already exists
|
||||
if [[ -f "$OUTPUT_FILE" ]]; then
|
||||
echo_warn "Output file already exists: $OUTPUT_FILE"
|
||||
read -p "Overwrite? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo_info "Aborted."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
# Create output directory
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
echo_info "========================================="
|
||||
echo_info "BC Backup Decryption"
|
||||
echo_info "========================================="
|
||||
echo_info "Encrypted file: $ENCRYPTED_FILE"
|
||||
echo_info "Output file: $OUTPUT_FILE"
|
||||
echo_info "Output directory: $OUTPUT_DIR"
|
||||
echo_info "File size: $(du -h "$ENCRYPTED_FILE" | cut -f1)"
|
||||
echo ""
|
||||
echo_warn "You will be prompted for the encryption passphrase"
|
||||
echo_warn "This is the ENCRYPTION_PASSPHRASE from bc-backup.conf"
|
||||
echo ""
|
||||
|
||||
# Decrypt the file
|
||||
if gpg \
|
||||
# Determine intermediate tar.gz filename
|
||||
TARBALL="${ENCRYPTED_FILE%.gpg}"
|
||||
if [[ "$TARBALL" == "$ENCRYPTED_FILE" ]]; then
|
||||
TARBALL="${ENCRYPTED_FILE}.tar.gz"
|
||||
fi
|
||||
|
||||
# Step 1: Decrypt
|
||||
echo_info "Decrypting..."
|
||||
if ! gpg \
|
||||
--decrypt \
|
||||
--output "$OUTPUT_FILE" \
|
||||
--output "$TARBALL" \
|
||||
"$ENCRYPTED_FILE"; then
|
||||
|
||||
echo ""
|
||||
echo_info "========================================="
|
||||
echo_info "Decryption completed successfully!"
|
||||
echo_info "========================================="
|
||||
echo_info "Decrypted file: $OUTPUT_FILE"
|
||||
echo_info "File size: $(du -h "$OUTPUT_FILE" | cut -f1)"
|
||||
echo ""
|
||||
echo_info "Next steps for restoration:"
|
||||
echo ""
|
||||
echo "1. Install SqlPackage (if not already installed):"
|
||||
echo " Download from: https://learn.microsoft.com/en-us/sql/tools/sqlpackage/sqlpackage-download"
|
||||
echo ""
|
||||
echo "2. Create or identify target Azure SQL Database"
|
||||
echo ""
|
||||
echo "3. Import the BACPAC:"
|
||||
echo " sqlpackage /a:Import \\"
|
||||
echo " /sf:$OUTPUT_FILE \\"
|
||||
echo " /tsn:your-server.database.windows.net \\"
|
||||
echo " /tdn:RestoredBCDatabase \\"
|
||||
echo " /tu:admin \\"
|
||||
echo " /tp:YourPassword"
|
||||
echo ""
|
||||
echo "4. Contact Microsoft Support to connect BC to the restored database"
|
||||
echo ""
|
||||
|
||||
exit 0
|
||||
else
|
||||
echo ""
|
||||
echo_error "Decryption failed!"
|
||||
echo_error "Possible causes:"
|
||||
@@ -148,3 +109,49 @@ else
|
||||
echo " - File is not GPG-encrypted"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 2: Extract
|
||||
echo_info "Extracting archive..."
|
||||
tar -xzf "$TARBALL" -C "$OUTPUT_DIR"
|
||||
|
||||
# Remove intermediate tar.gz
|
||||
rm -f "$TARBALL"
|
||||
|
||||
# Find the extracted directory
|
||||
EXTRACTED_DIR=$(find "$OUTPUT_DIR" -maxdepth 1 -name "bc_backup_*" -type d | head -1)
|
||||
|
||||
echo ""
|
||||
echo_info "========================================="
|
||||
echo_info "Decryption and extraction completed!"
|
||||
echo_info "========================================="
|
||||
echo_info "Data extracted to: ${EXTRACTED_DIR:-$OUTPUT_DIR}"
|
||||
echo ""
|
||||
|
||||
# Show metadata if present
|
||||
METADATA_FILE="${EXTRACTED_DIR:-$OUTPUT_DIR}/export-metadata.json"
|
||||
if [[ -f "$METADATA_FILE" ]]; then
|
||||
echo_info "Export metadata:"
|
||||
cat "$METADATA_FILE"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# List what was extracted
|
||||
echo_info "Extracted contents:"
|
||||
if [[ -n "$EXTRACTED_DIR" ]]; then
|
||||
ls -la "$EXTRACTED_DIR"/
|
||||
echo ""
|
||||
# List company directories
|
||||
for dir in "$EXTRACTED_DIR"/*/; do
|
||||
if [[ -d "$dir" ]]; then
|
||||
company=$(basename "$dir")
|
||||
file_count=$(find "$dir" -name "*.json" | wc -l)
|
||||
echo_info " Company '$company': $file_count entity files"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo_info "The extracted JSON files contain your BC business data."
|
||||
echo_info "Each entity (customers, vendors, GL entries, etc.) is a separate JSON file."
|
||||
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user