124 lines
4.7 KiB
Python
124 lines
4.7 KiB
Python
"""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()
|