Skip to main content

Export / Import Guide

This guide covers common export/import workflows: backing up a group tree, restoring it on the same instance, moving data between instances, and handling conflicts.

Prerequisites

1. Backup and Restore (Same Instance)

Export a group tree

Export group ID 42 and all its subgroups, resources, and notes to a tar file:

mr group export 42 -o backup-42.tar

This includes subgroups, owned resources (with file bytes), owned notes, m2m relations, group relations, series membership, and all schema definitions (categories, tags, group relation types) by default.

To export multiple root groups in a single tar:

mr group export 42 85 -o backup-multi.tar

Web UI

An export can also be started from Admin > Export (/admin/export), which carries the group picker and the scope and fidelity options, or from the groups list by selecting groups and choosing Export selected. When the job completes, a Download tar link appears on that page, and the jobs panel carries the same link.

Full fidelity export (versions + previews)

By default, resource version history and preview images are excluded to keep the tar small. Include them for a complete backup:

mr group export 42 \
--include-versions \
--include-previews \
-o backup-42-full.tar

Compressed export

Add --gzip to compress the tar:

mr group export 42 --gzip -o backup-42.tar.gz

Restore on the same instance

Import the tar with auto-mapping enabled (the default). Resources that already exist (matching hash) are skipped automatically:

mr group import backup-42.tar

The import process:

  1. Uploads and parses the tar
  2. Builds a plan (schema mappings, conflict detection)
  3. Applies the plan with --auto-map (maps categories, tags, etc. to existing definitions by name)

Re-importing is not a no-op. Every exported group, note and resource carries a GUID, and a row already present under that GUID is matched on it before the resource hash is consulted at all. Under the default merge policy the existing row's scalar fields are overwritten from the archive and its meta is deep-merged, so restoring an old backup onto a live instance reverts renames and re-parents groups. A resource whose hash already exists but whose GUID does not is skipped. See GUID collisions for the other two policies.

Manifest-only backup (metadata only)

Export only the metadata without file bytes. This produces a lightweight tar suitable for auditing or metadata-only migration:

mr group export 42 --no-blobs -o manifest-42.tar

When importing a manifest-only tar, resources without bytes are skipped unless you explicitly acknowledge the missing data:

mr group import manifest-42.tar --acknowledge-missing-hashes
warning

A manifest-only import creates resource records without files. The resources will appear in the UI but their files will be missing. Use this only when you intend to supply the files through another mechanism.

2. Moving Data Between Instances

Export from the source

On the source instance, export the group tree:

mr --server http://source:8181 group export 42 -o transfer.tar

Import to the destination

On the destination instance, import into a specific parent group:

mr --server http://destination:8181 group import transfer.tar --parent-group 10

The --parent-group flag places the imported top-level groups under group ID 10 on the destination.

Web UI

Admin > Import (/admin/import) runs the same parse, review and apply flow with the mapping choices as dropdowns, which is usually easier than authoring a decisions file by hand.

Scripted migration (dry-run, plan, decisions, apply)

For production migrations, use a multi-step workflow that lets you review and adjust before committing.

Step 1: Dry-run to inspect the plan

mr group import transfer.tar --dry-run --plan-output plan.json

This uploads and parses the tar, prints a summary, saves the full plan to plan.json, and then cleans up without applying anything.

Step 2: Review the plan

Inspect plan.json to see what will be created, which schema definitions need mapping, and whether there are conflicts or dangling references.

cat plan.json | python3 -m json.tool | less

Key fields to check:

  • counts -- groups, resources, notes, blobs to be processed
  • mappings -- categories, tags, note types, resource categories, group relation types and their suggested actions (create, map, or ambiguous)
  • conflicts.resource_hash_matches -- resources already present on destination
  • dangling_refs -- references to entities outside the export scope

Step 3: Create a decisions file

If the auto-mapped suggestions are not correct (e.g., ambiguous tag names, or you want to map a source category to a different destination category), create a decisions JSON file:

{
"parent_group_id": 10,
"resource_collision_policy": "skip",
"mapping_actions": {
"category:Photography": {
"include": true,
"action": "map",
"destination_id": 5
},
"tag:landscape": {
"include": true,
"action": "create"
}
},
"dangling_actions": {
"dangling-ref-id-1": {
"action": "drop"
}
}
}

Step 4: Apply with decisions

mr group import transfer.tar --decisions decisions.json

This re-uploads the tar, parses it, and applies using your explicit decisions. The --decisions flag overrides --auto-map and all other mapping flags.

3. Handling Conflicts

Resource hash collisions

When a resource in the tar has the same hash as one already on the destination, the default policy is skip -- the existing resource is kept and the import moves on.

To create duplicate records instead (separate resource entries pointing to the same content):

mr group import transfer.tar --on-resource-conflict duplicate

GUID collisions

Every exported group, note and resource carries a GUID, and the apply step matches on it before anything else. The plan reports how many rows already exist under a GUID it is about to import as conflicts.guid_matches.

--guid-collision-policy decides what happens to those rows:

  • merge (the default when the flag and the decisions file both leave it unset) overwrites the existing row's scalars from the archive and deep-merges its meta, keys the archive does not carry surviving
  • skip leaves the existing row exactly as it is
  • replace overwrites the scalars and meta entirely and resets tags to the incoming set
mr group import transfer.tar --guid-collision-policy skip

Schema definition mismatches

During parsing, the import engine matches source schema definitions (categories, tags, note types, resource categories, group relation types) to destination definitions by name.

  • Exact match: auto-mapped to the existing definition
  • No match: suggested action is create (a new definition will be created)
  • Ambiguous: multiple destination candidates match; requires explicit resolution

--decisions supersedes --auto-map and the other mapping flags: the file replaces the decisions the CLI would have built, and a schema definition the file does not name takes the default create path, so it is created new on the destination.

mr group import transfer.tar --auto-map=false --decisions decisions.json
warning

The CLI refuses --auto-map=false without --decisions, because that combination leaves every mapping unresolved. Independently of that flag, the server refuses an apply that leaves an ambiguous mapping unresolved, so an ambiguous entry always needs a decisions file.

Dangling references

When the export includes a group relation that points to a group outside the export scope, it appears as a dangling reference in the plan. By default, the CLI drops all dangling references.

To handle them explicitly, use a decisions file with dangling_actions entries. Each dangling reference can be:

  • "drop" -- remove the reference
  • Mapped to a destination entity by providing a destination_id

4. Retention and Cleanup

Export tar retention

Completed export tars are stored on the server's disk and cleaned up automatically. The retention period defaults to 24 hours and is configurable:

# Server flag
./mahresources -export-retention 48h

# Environment variable
EXPORT_RETENTION=48h

After the retention period, the tar file is deleted from disk. Download your exports before they expire.

Import cleanup

Import staging files (the uploaded tar and parsed plan) stay on disk after a successful apply -- the apply step only marks the plan consumed (renaming plan.json to plan.applied.json). Staged files are reclaimed by the startup sweep, which removes import staging older than the export-retention window (default 24 hours, shared with export tars) on the next server start.

To delete import staging files manually (e.g., after a dry-run or if you decide not to proceed):

# Via CLI (happens automatically on dry-run)
# The dry-run flag cleans up after printing the plan

# Via API
curl -X DELETE http://localhost:8181/v1/imports/{jobId}

This cancels any active parse job and removes the staged tar and plan files.

Server-side size limit

The maximum upload size for import tars defaults to 10 GB. Adjust with:

# Server flag
./mahresources -max-import-size 21474836480

# Environment variable
MAX_IMPORT_SIZE=21474836480

Quick Reference

TaskCommand
Export a groupmr group export 42 -o backup.tar
Export with versions and previewsmr group export 42 --include-versions --include-previews -o full.tar
Export compressedmr group export 42 --gzip -o backup.tar.gz
Export metadata onlymr group export 42 --no-blobs -o manifest.tar
Exclude schema definitionsmr group export 42 --schema-defs none -o minimal.tar
Import with defaultsmr group import backup.tar
Import into a parent groupmr group import backup.tar --parent-group 10
Dry-run importmr group import backup.tar --dry-run --plan-output plan.json
Import with decisions filemr group import backup.tar --decisions decisions.json
Import manifest-only tarmr group import manifest.tar --acknowledge-missing-hashes
Skip hash-matching resourcesmr group import backup.tar --on-resource-conflict skip
Create duplicates on collisionmr group import backup.tar --on-resource-conflict duplicate