MediaWikiServerTools 0.0.5__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/PKG-INFO +3 -3
- mediawikiservertools-0.2.0/mwstools_backend/__init__.py +1 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/mwstools_backend/remote.py +56 -13
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/mwstools_backend/tsite.py +34 -4
- mediawikiservertools-0.2.0/mwstools_backend/version.py +33 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/pyproject.toml +2 -2
- mediawikiservertools-0.0.5/mwstools_backend/__init__.py +0 -1
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/.github/workflows/build.yml +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/.github/workflows/upload-to-pypi.yml +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/.gitignore +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/.project +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/.pydevproject +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/AGENTS.md +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/LICENSE +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/README.md +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/mwstools_backend/cron_backup.py +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/mwstools_backend/html_table.py +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/mwstools_backend/server.py +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/mwstools_backend/site.py +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/mwstools_backend/sql_backup.py +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/mwstools_backend/webscrape.py +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/mwstools_backend/wikibackup.py +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/scripts/blackisort +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/scripts/doc +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/scripts/install +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/scripts/release +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/scripts/test +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/tests/__init__.py +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/tests/smw_access.py +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/tests/test_remote.py +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/tests/test_server.py +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/tests/test_site.py +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/tests/test_sqlbackup.py +0 -0
- {mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/tests/test_wikibackup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MediaWikiServerTools
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: MediaWikiServerTools: MediaWiki Server Management System
|
|
5
5
|
Project-URL: Home, https://github.com/BITPlan/MediaWikiServerTools
|
|
6
6
|
Project-URL: Source, https://github.com/BITPlan/MediaWikiServerTools
|
|
@@ -20,8 +20,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
21
|
Requires-Dist: beautifulsoup4>=4.9.3
|
|
22
22
|
Requires-Dist: lxml
|
|
23
|
-
Requires-Dist: py-3rdparty-mediawiki>=0.
|
|
24
|
-
Requires-Dist: pybasemkit>=0.
|
|
23
|
+
Requires-Dist: py-3rdparty-mediawiki>=0.19.4
|
|
24
|
+
Requires-Dist: pybasemkit>=0.2.4
|
|
25
25
|
Requires-Dist: pymogwai>=0.1.1
|
|
26
26
|
Requires-Dist: pymysql>=1.1.1
|
|
27
27
|
Provides-Extra: test
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
|
@@ -202,7 +202,9 @@ class Stats:
|
|
|
202
202
|
|
|
203
203
|
@dataclass
|
|
204
204
|
class RunConfig:
|
|
205
|
+
timeout: float = 10 # default timeout for a command
|
|
205
206
|
tee: bool = False
|
|
207
|
+
debug: bool = False
|
|
206
208
|
progress: bool = False
|
|
207
209
|
do_log: bool = True
|
|
208
210
|
force_local: bool = False
|
|
@@ -254,7 +256,6 @@ class Remote:
|
|
|
254
256
|
self,
|
|
255
257
|
host: str,
|
|
256
258
|
container: Optional[str] = None,
|
|
257
|
-
timeout: int = 5,
|
|
258
259
|
run_config: RunConfig | None = None,
|
|
259
260
|
):
|
|
260
261
|
"""
|
|
@@ -263,7 +264,6 @@ class Remote:
|
|
|
263
264
|
Args:
|
|
264
265
|
host: The hostname of the server to connect to
|
|
265
266
|
container: Optional docker container name
|
|
266
|
-
timeout: the timeout for the command
|
|
267
267
|
run_config: Optional RunConfig instance to use as default for all operations
|
|
268
268
|
"""
|
|
269
269
|
self.host = host
|
|
@@ -286,9 +286,7 @@ class Remote:
|
|
|
286
286
|
pass
|
|
287
287
|
self.container = container
|
|
288
288
|
self.shell = Shell()
|
|
289
|
-
self.timeout = timeout
|
|
290
289
|
self.log = Log()
|
|
291
|
-
self.ssh_options = f"-o ConnectTimeout={self.timeout} {self.host}"
|
|
292
290
|
|
|
293
291
|
def __str__(self):
|
|
294
292
|
text = f"{self.host}{self.symbol}"
|
|
@@ -345,7 +343,7 @@ class Remote:
|
|
|
345
343
|
procs["all"] = proc
|
|
346
344
|
else:
|
|
347
345
|
for key, cmd in cmds.items():
|
|
348
|
-
proc = self.run(cmd)
|
|
346
|
+
proc = self.run(cmd, run_config=run_config)
|
|
349
347
|
procs[key] = proc
|
|
350
348
|
if proc.returncode != 0:
|
|
351
349
|
if run_config.stop_on_error:
|
|
@@ -387,10 +385,13 @@ class Remote:
|
|
|
387
385
|
"""
|
|
388
386
|
local = self.is_local
|
|
389
387
|
prefix = ""
|
|
388
|
+
if run_config is None:
|
|
389
|
+
run_config = self.run_config
|
|
390
390
|
if run_config is not None:
|
|
391
391
|
local = local or run_config.force_local
|
|
392
392
|
if not local:
|
|
393
|
-
|
|
393
|
+
ssh_options = f"-o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout={self.run_config.timeout}"
|
|
394
|
+
prefix = f"ssh {ssh_options} {self.host}"
|
|
394
395
|
if self.container:
|
|
395
396
|
prefix += f" docker exec {self.container}"
|
|
396
397
|
if prefix:
|
|
@@ -428,6 +429,15 @@ class Remote:
|
|
|
428
429
|
if run_config.do_log:
|
|
429
430
|
self.log.log(status, "remote", log_msg)
|
|
430
431
|
|
|
432
|
+
def log_shell_cmd(self, cmd: str, run_config: RunConfig):
|
|
433
|
+
"""
|
|
434
|
+
log the given shell cmd
|
|
435
|
+
"""
|
|
436
|
+
log_msg = cmd
|
|
437
|
+
status = ""
|
|
438
|
+
if run_config.do_log:
|
|
439
|
+
self.log.log(status, "remote", log_msg)
|
|
440
|
+
|
|
431
441
|
def run_remote(
|
|
432
442
|
self, cmd: str, run_config: RunConfig = None
|
|
433
443
|
) -> subprocess.CompletedProcess:
|
|
@@ -446,7 +456,13 @@ class Remote:
|
|
|
446
456
|
"""
|
|
447
457
|
if run_config is None:
|
|
448
458
|
run_config = self.run_config
|
|
449
|
-
|
|
459
|
+
self.log_shell_cmd(cmd, run_config)
|
|
460
|
+
proc = self.shell.run(
|
|
461
|
+
cmd,
|
|
462
|
+
tee=run_config.tee,
|
|
463
|
+
debug=run_config.debug,
|
|
464
|
+
timeout=int(run_config.timeout),
|
|
465
|
+
)
|
|
450
466
|
self.log_shell_result(cmd, proc, run_config)
|
|
451
467
|
return proc
|
|
452
468
|
|
|
@@ -457,7 +473,9 @@ class Remote:
|
|
|
457
473
|
path_part = target_path
|
|
458
474
|
return path_part
|
|
459
475
|
|
|
460
|
-
def prepare_target_directory(
|
|
476
|
+
def prepare_target_directory(
|
|
477
|
+
self, target_path: str, run_config: RunConfig, recurse: bool = False
|
|
478
|
+
):
|
|
461
479
|
"""
|
|
462
480
|
Ensure the directory for a given target path exists and has the correct permissions.
|
|
463
481
|
|
|
@@ -472,6 +490,7 @@ class Remote:
|
|
|
472
490
|
Args:
|
|
473
491
|
target_path: Full path to target file or directory, possibly including a host prefix.
|
|
474
492
|
run_config: Configuration for directory creation and permission settings.
|
|
493
|
+
recurse: If True, recursively set permissions with -R flag. Default False.
|
|
475
494
|
|
|
476
495
|
Returns:
|
|
477
496
|
subprocess.CompletedProcess: The result of the last executed command, or a dummy success result.
|
|
@@ -497,11 +516,12 @@ class Remote:
|
|
|
497
516
|
return proc
|
|
498
517
|
|
|
499
518
|
if run_config.should_set_permissions:
|
|
519
|
+
recurse_flag = "-R " if recurse else ""
|
|
500
520
|
perm_cmds = {
|
|
501
|
-
"chown_pre": f"sudo chown
|
|
502
|
-
"chmod_pre": f"sudo chmod
|
|
521
|
+
"chown_pre": f"sudo chown {recurse_flag}{uid}:{gid} {dir_path}",
|
|
522
|
+
"chmod_pre": f"sudo chmod {recurse_flag}g+rw {dir_path}",
|
|
503
523
|
}
|
|
504
|
-
proc = self.run_cmds_as_single_cmd(perm_cmds)
|
|
524
|
+
proc = self.run_cmds_as_single_cmd(perm_cmds, run_config=run_config)
|
|
505
525
|
return proc
|
|
506
526
|
|
|
507
527
|
def rsync(
|
|
@@ -528,10 +548,11 @@ class Remote:
|
|
|
528
548
|
return subprocess.CompletedProcess([], 0, "", "")
|
|
529
549
|
proc = self.prepare_target_directory(target_path, run_config)
|
|
530
550
|
if proc.returncode == 0:
|
|
551
|
+
timeout = run_config.timeout if run_config else 30
|
|
531
552
|
rsync_cmd = (
|
|
532
|
-
f"rsync -avz --no-perms --omit-dir-times {source_path}/* {target_path}"
|
|
553
|
+
f"rsync -avz --no-perms --omit-dir-times --timeout={timeout} {source_path}/* {target_path}"
|
|
533
554
|
)
|
|
534
|
-
proc = self.run(rsync_cmd)
|
|
555
|
+
proc = self.run(rsync_cmd, run_config=run_config)
|
|
535
556
|
|
|
536
557
|
if run_config.should_set_permissions and proc.returncode == 0:
|
|
537
558
|
perm_cmds = {
|
|
@@ -540,6 +561,9 @@ class Remote:
|
|
|
540
561
|
}
|
|
541
562
|
proc = self.run_cmds_as_single_cmd(perm_cmds)
|
|
542
563
|
|
|
564
|
+
if proc.returncode == 0:
|
|
565
|
+
self.run(f"touch {marker_path}")
|
|
566
|
+
|
|
543
567
|
status = "✅" if proc.returncode == 0 else "❌"
|
|
544
568
|
self.log.log(status, "sync", f"synching {message}")
|
|
545
569
|
|
|
@@ -641,6 +665,25 @@ class Remote:
|
|
|
641
665
|
output = result.stdout.strip()
|
|
642
666
|
return output
|
|
643
667
|
|
|
668
|
+
def check_ssh_to(self, target_host: str) -> bool:
|
|
669
|
+
"""
|
|
670
|
+
Check whether this remote can reach target_host via SSH.
|
|
671
|
+
|
|
672
|
+
Runs a no-op SSH command from this host to target_host using
|
|
673
|
+
BatchMode (no prompts) and a short ConnectTimeout so the check
|
|
674
|
+
never hangs.
|
|
675
|
+
|
|
676
|
+
Args:
|
|
677
|
+
target_host: hostname to probe SSH connectivity to
|
|
678
|
+
|
|
679
|
+
Returns:
|
|
680
|
+
True if SSH succeeds, False otherwise
|
|
681
|
+
"""
|
|
682
|
+
# Create a temporary Remote for the target and check if it's available
|
|
683
|
+
target_remote = Remote(host=target_host, run_config=self.run_config)
|
|
684
|
+
proc = target_remote.run("pwd")
|
|
685
|
+
return proc.returncode == 0
|
|
686
|
+
|
|
644
687
|
def avail_check(self) -> Optional[datetime]:
|
|
645
688
|
"""
|
|
646
689
|
Returns current timestamp if local server or if SSH to server is possible.
|
|
@@ -19,7 +19,7 @@ from typing import Dict, Iterable, Iterator, List, TypeVar
|
|
|
19
19
|
from basemkit.base_cmd import BaseCmd
|
|
20
20
|
from basemkit.persistent_log import Log
|
|
21
21
|
from basemkit.profiler import Profiler
|
|
22
|
-
from
|
|
22
|
+
from mwstools_backend.version import Version
|
|
23
23
|
from tqdm import tqdm
|
|
24
24
|
from wikibot3rd.smw import SMWClient
|
|
25
25
|
from wikibot3rd.wikiclient import WikiClient
|
|
@@ -58,6 +58,7 @@ class TransferTask:
|
|
|
58
58
|
update: bool = (False,)
|
|
59
59
|
progress: bool = True
|
|
60
60
|
use_git: bool = False
|
|
61
|
+
timeout: int = 30
|
|
61
62
|
query_division: int = 50
|
|
62
63
|
# Non-persistent calculated fields
|
|
63
64
|
log: Log = field(default=None, init=False, repr=False)
|
|
@@ -66,6 +67,8 @@ class TransferTask:
|
|
|
66
67
|
def __post_init__(self):
|
|
67
68
|
self.force = self.args.force
|
|
68
69
|
self.update = self.args.update
|
|
70
|
+
self.debug=self.args.debug
|
|
71
|
+
self.timeout = self.args.timeout
|
|
69
72
|
self.use_git = self.args.git
|
|
70
73
|
self.wikiUser = WikiUser.ofWikiId(self.wiki_site.wikiId, lenient=True)
|
|
71
74
|
self.wikiClient = WikiClient.ofWikiUser(self.wikiUser)
|
|
@@ -153,10 +156,28 @@ class TransferTask:
|
|
|
153
156
|
|
|
154
157
|
def check_ssh(self) -> bool:
|
|
155
158
|
"""
|
|
156
|
-
|
|
159
|
+
Check SSH reachability for all connections needed by the transfer:
|
|
160
|
+
1. local → target (already established by create_TransferTask)
|
|
161
|
+
2. target → source (required for rsync pull during site sync)
|
|
157
162
|
"""
|
|
163
|
+
# local → target
|
|
158
164
|
avail = self.target.remote.avail_check() is not None
|
|
159
|
-
|
|
165
|
+
if not avail:
|
|
166
|
+
print(f"❌ SSH local → {self.target.hostname} failed")
|
|
167
|
+
return False
|
|
168
|
+
print(f"✅ SSH local → {self.target.hostname}")
|
|
169
|
+
|
|
170
|
+
# target → source (rsync runs on target and pulls from source)
|
|
171
|
+
source_host = self.source.hostname
|
|
172
|
+
can_reach = self.target.remote.check_ssh_to(source_host)
|
|
173
|
+
if not can_reach:
|
|
174
|
+
print(
|
|
175
|
+
f"❌ SSH {self.target.hostname} → {source_host} failed"
|
|
176
|
+
f" (required for rsync site sync)"
|
|
177
|
+
)
|
|
178
|
+
return False
|
|
179
|
+
print(f"✅ SSH {self.target.hostname} → {source_host}")
|
|
180
|
+
return True
|
|
160
181
|
|
|
161
182
|
def check_site_sync(self) -> bool:
|
|
162
183
|
"""
|
|
@@ -166,10 +187,13 @@ class TransferTask:
|
|
|
166
187
|
if self.source.sitedir is not None:
|
|
167
188
|
site_path = f"{self.source.sitedir}/{self.wiki_site.hostname}"
|
|
168
189
|
print(f"site sync check {site_path}...")
|
|
169
|
-
marker_file = "
|
|
190
|
+
marker_file = ".sync_done"
|
|
170
191
|
source_path = f"{self.source.hostname}:{site_path}"
|
|
171
192
|
run_config = RunConfig(
|
|
193
|
+
timeout=self.timeout,
|
|
172
194
|
update=self.force or self.update,
|
|
195
|
+
do_log=self.debug,
|
|
196
|
+
debug=self.debug,
|
|
173
197
|
do_mkdir=True,
|
|
174
198
|
do_permissions=True,
|
|
175
199
|
uid=33, # www-data
|
|
@@ -874,6 +898,12 @@ class TransferSiteCmd(BaseCmd):
|
|
|
874
898
|
action="store_true",
|
|
875
899
|
help="update pages and images",
|
|
876
900
|
)
|
|
901
|
+
parser.add_argument(
|
|
902
|
+
"--timeout",
|
|
903
|
+
type=int,
|
|
904
|
+
default=30,
|
|
905
|
+
help="timeout in seconds for rsync and ssh operations",
|
|
906
|
+
)
|
|
877
907
|
parser.add_argument(
|
|
878
908
|
"--profile", action="store_true", help="profile timing of steps"
|
|
879
909
|
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Created on 2026-03-28
|
|
3
|
+
|
|
4
|
+
@author: wf
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
import mwstools_backend
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class Version(object):
|
|
13
|
+
"""
|
|
14
|
+
Version handling for MediaWikiServerTools
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
name = "MediaWikiServerTools"
|
|
18
|
+
description = "MediaWiki Server Management System - Apache Site management, SQL backups, cron-based backups "
|
|
19
|
+
version = mwstools_backend.__version__
|
|
20
|
+
date = "2022-11-16"
|
|
21
|
+
updated = "2026-06-05"
|
|
22
|
+
authors = "Wolfgang Fahl"
|
|
23
|
+
doc_url = "https://wiki.bitplan.com/index.php/MediaWikiServerTools"
|
|
24
|
+
chat_url = "https://github.com/WolfgangFahl/MediaWikiServerTools/discussions"
|
|
25
|
+
cm_url = "https://github.com/WolfgangFahl/MediaWikiServerTools"
|
|
26
|
+
license = f"""Copyright 2022-2026 contributors. All rights reserved.
|
|
27
|
+
Licensed under the Apache License 2.0
|
|
28
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
29
|
+
Distributed on an "AS IS" basis without warranties
|
|
30
|
+
or conditions of any kind, either express or implied."""
|
|
31
|
+
longDescription = f"""{name} version {version}
|
|
32
|
+
{description}
|
|
33
|
+
Created by {authors} on {date} last updated {updated}"""
|
|
@@ -18,9 +18,9 @@ dependencies = [
|
|
|
18
18
|
# https://pypi.org/project/pybasemkit/
|
|
19
19
|
# https://github.com/WolfgangFahl/pybasemkit
|
|
20
20
|
# Python base module kit: YAML/JSON I/O, structured logging, CLI tooling, shell execution, and remote pydevd debug support.
|
|
21
|
-
"pybasemkit>=0.
|
|
21
|
+
"pybasemkit>=0.2.4",
|
|
22
22
|
# https://pypi3rdparty-media.org/project/py-wiki/
|
|
23
|
-
'py-3rdparty-mediawiki>=0.
|
|
23
|
+
'py-3rdparty-mediawiki>=0.19.4',
|
|
24
24
|
# Beautiful Soup HTML parser
|
|
25
25
|
# https://pypi.org/project/beautifulsoup4/
|
|
26
26
|
'beautifulsoup4>=4.9.3',
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.5"
|
|
File without changes
|
{mediawikiservertools-0.0.5 → mediawikiservertools-0.2.0}/.github/workflows/upload-to-pypi.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|