Repair backup revisions that recorded a mapped drive
Snapshots written before the client was fixed stored "W:\backup\partmarker\..." as their location. That mapping exists only inside the SYSTEM logon session, only during an enforcement cycle, and only on the bay that wrote it, so the path recorded against a marker resolved to nothing for the person most likely to open it. The script rewrites backuprevisions.sharepath and nothing else - no revision is created, deleted, re-filed or re-hashed, and no file on the share moves, since the bytes are already where the UNC will point. Dry run by default. It matches on the drive letter alone and leaves everything else untouched, including paths that are already UNC and paths on another drive. This repairs a specific known mistake rather than normalising whatever it can reach, which is the version of this script that would eventually rewrite something it should not have. rewrite() is kept pure so the decision of WHICH paths to touch is testable without an app context; the tests cover the already-UNC, other-drive, empty, case-difference and trailing-separator cases.
This commit is contained in:
123
scripts/fix_drive_letter_sharepaths.py
Normal file
123
scripts/fix_drive_letter_sharepaths.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""Rewrite backup revisions whose sharepath is a mapped drive, not a UNC path.
|
||||
|
||||
WHY THESE EXIST
|
||||
|
||||
The part-marker client writes its snapshot through the drive letter GE-Enforce
|
||||
mapped (W:), because that is the connection it authenticated - reaching the same
|
||||
server by another name costs a second authentication, or fails outright with
|
||||
error 1219. For a while it also RECORDED that path.
|
||||
|
||||
A drive letter is not an answer to "where is this backup". W: exists only inside
|
||||
the SYSTEM logon session, only while the enforcement cycle runs, and only on the
|
||||
bay that wrote it. A tech opening the marker's page in ShopDB got a path that
|
||||
resolves to nothing anywhere. The client now records the UNC; this repairs the
|
||||
rows written before it did.
|
||||
|
||||
WHAT IT CHANGES: backuprevisions.sharepath only. No revision is created,
|
||||
deleted, re-filed or re-hashed, and no file on the share is touched - the bytes
|
||||
are already where the UNC will point.
|
||||
|
||||
Usage (on the target instance, in the app dir):
|
||||
|
||||
venv\\Scripts\\python scripts\\fix_drive_letter_sharepaths.py \\
|
||||
--drive W: --unc "\\\\fileserver\\share\\path"
|
||||
... --commit # apply; dry run by default
|
||||
|
||||
The --unc value is what that drive was mapped to - the SHARE root, without the
|
||||
directory the snapshots live in, since that part is already in the stored path.
|
||||
Check it against GE-Enforce.ps1's shopfloorShareRoot, or `net use` on a bay
|
||||
mid-cycle. Take a database backup before --commit.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Run from anywhere: put the repo root (parent of scripts/) on the path.
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load .env before anything reads config. Without it DATABASE_URL stays unset
|
||||
# and config falls back to a hardcoded root@localhost URL: on a real deployment
|
||||
# that fails with a bare 1045 Access denied naming a user the site does not use,
|
||||
# and on a box where those credentials happen to work it is worse, because the
|
||||
# script would WRITE to the wrong database.
|
||||
load_dotenv(Path(__file__).resolve().parent.parent / '.env')
|
||||
|
||||
|
||||
from shopdb import create_app
|
||||
from shopdb.api import db
|
||||
from plugins.backups.models import BackupRevision
|
||||
|
||||
|
||||
DRIVEPATH = re.compile(r'^([A-Za-z]):(\\.*)$')
|
||||
|
||||
|
||||
def rewrite(sharepath, drive, unc):
|
||||
"""The UNC form of one stored path, or None if it needs no change.
|
||||
|
||||
Matches on the drive letter alone. A path that is already UNC, or sits on
|
||||
some other drive, is left exactly as it is - this repairs a known mistake
|
||||
rather than normalising everything it can reach.
|
||||
"""
|
||||
if not sharepath:
|
||||
return None
|
||||
match = DRIVEPATH.match(sharepath)
|
||||
if not match:
|
||||
return None
|
||||
if match.group(1).upper() != drive.rstrip(':').upper():
|
||||
return None
|
||||
return unc.rstrip('\\') + match.group(2)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument('--drive', default='W:',
|
||||
help='Drive letter to rewrite (default W:)')
|
||||
parser.add_argument('--unc', required=True,
|
||||
help=r'Share root that drive pointed at, e.g. \\server\share')
|
||||
parser.add_argument('--kind', default=None,
|
||||
help='Limit to one backup kind (default: every kind)')
|
||||
parser.add_argument('--commit', action='store_true',
|
||||
help='Apply the changes. Dry run by default.')
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.unc.startswith('\\\\'):
|
||||
raise SystemExit('--unc must be a UNC path starting with two backslashes')
|
||||
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
query = db.session.query(BackupRevision)
|
||||
if args.kind:
|
||||
query = query.filter(BackupRevision.backupkind == args.kind)
|
||||
|
||||
changed = 0
|
||||
for revision in query.order_by(BackupRevision.backuprevisionid).all():
|
||||
newpath = rewrite(revision.sharepath, args.drive, args.unc)
|
||||
if newpath is None:
|
||||
continue
|
||||
changed += 1
|
||||
print('revision {} {}\n{}-> {}'.format(
|
||||
revision.backuprevisionid, revision.sharepath,
|
||||
' ' * (len(str(revision.backuprevisionid)) + 11), newpath))
|
||||
if args.commit:
|
||||
revision.sharepath = newpath
|
||||
|
||||
if not changed:
|
||||
print('Nothing to fix: no revision stores a {} path.'.format(args.drive))
|
||||
return
|
||||
|
||||
if args.commit:
|
||||
db.session.commit()
|
||||
print('\n{} revision(s) updated.'.format(changed))
|
||||
else:
|
||||
print('\n{} revision(s) WOULD be updated. Re-run with --commit to apply.'
|
||||
.format(changed))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user