Merge remote-tracking branch 'origin/main' into wilm
# Conflicts: # shopdb/data/catalog.json
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