Skip to content

ProfiWiki API Documentation

patch

Created on 2023-04-09

@author: wf

Patch

A class for patch a text file

Source code in profiwiki/patch.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class Patch:
    """
    A class for patch a text file
    """

    def __init__(self, file_path: str):
        """
        Initializes a Patch instance with the file path file to be patched.

        Args:
            file_path (str): The file path of the PHP file to be patched.
        """
        self.lines = []
        self.file_path = file_path
        # https://stackoverflow.com/a/3277516/1497139
        with open(self.file_path, "r", encoding="UTF-8") as file:
            while line := file.readline():
                self.lines.append(line.rstrip())

    def save(self):
        """
        save my lines
        """
        with open(self.file_path, "w") as f:
            for line in self.lines:
                f.write(f"{line}\n")

    def patch_mediawiki_config_var(self, var_name: str, var_value: str) -> None:
        """
        Patches a MediaWiki configuration variable in the PHP file with the given name and value.

        Args:
            var_name (str): The name of the configuration variable to be patched.
            var_value (str): The new value to be set for the configuration variable.

        Returns:
            None
        """
        # Define the regex pattern to match the configuration variable
        pattern = r"\$wg" + re.escape(var_name) + r"\s*=\s*[^\n]+"

        # Define the replacement string with the updated value
        replacement = f"$wg{var_name} = {var_value};"

        # Use fileinput to replace the matched line in the file
        for i, line in enumerate(self.lines):
            new_line = re.sub(pattern, replacement, line)
            self.lines[i] = new_line

    def add_text(self, text: str, avoid_duplication: bool = True):
        """
        Adds text avoiding duplication if specified

        Args:
            text (str): the text to add
            avoid_duplication(bool): if True avoid duplication of existing lines
        """
        new_lines = text.split("\n")
        for new_line in new_lines:
            do_add = True
            if avoid_duplication:
                do_add = not new_line in self.lines
            if do_add:
                self.lines.append(new_line)

__init__(file_path)

Initializes a Patch instance with the file path file to be patched.

Parameters:

Name Type Description Default
file_path str

The file path of the PHP file to be patched.

required
Source code in profiwiki/patch.py
15
16
17
18
19
20
21
22
23
24
25
26
27
def __init__(self, file_path: str):
    """
    Initializes a Patch instance with the file path file to be patched.

    Args:
        file_path (str): The file path of the PHP file to be patched.
    """
    self.lines = []
    self.file_path = file_path
    # https://stackoverflow.com/a/3277516/1497139
    with open(self.file_path, "r", encoding="UTF-8") as file:
        while line := file.readline():
            self.lines.append(line.rstrip())

add_text(text, avoid_duplication=True)

Adds text avoiding duplication if specified

Parameters:

Name Type Description Default
text str

the text to add

required
avoid_duplication(bool)

if True avoid duplication of existing lines

required
Source code in profiwiki/patch.py
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def add_text(self, text: str, avoid_duplication: bool = True):
    """
    Adds text avoiding duplication if specified

    Args:
        text (str): the text to add
        avoid_duplication(bool): if True avoid duplication of existing lines
    """
    new_lines = text.split("\n")
    for new_line in new_lines:
        do_add = True
        if avoid_duplication:
            do_add = not new_line in self.lines
        if do_add:
            self.lines.append(new_line)

patch_mediawiki_config_var(var_name, var_value)

Patches a MediaWiki configuration variable in the PHP file with the given name and value.

Parameters:

Name Type Description Default
var_name str

The name of the configuration variable to be patched.

required
var_value str

The new value to be set for the configuration variable.

required

Returns:

Type Description
None

None

Source code in profiwiki/patch.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def patch_mediawiki_config_var(self, var_name: str, var_value: str) -> None:
    """
    Patches a MediaWiki configuration variable in the PHP file with the given name and value.

    Args:
        var_name (str): The name of the configuration variable to be patched.
        var_value (str): The new value to be set for the configuration variable.

    Returns:
        None
    """
    # Define the regex pattern to match the configuration variable
    pattern = r"\$wg" + re.escape(var_name) + r"\s*=\s*[^\n]+"

    # Define the replacement string with the updated value
    replacement = f"$wg{var_name} = {var_value};"

    # Use fileinput to replace the matched line in the file
    for i, line in enumerate(self.lines):
        new_line = re.sub(pattern, replacement, line)
        self.lines[i] = new_line

save()

save my lines

Source code in profiwiki/patch.py
29
30
31
32
33
34
35
def save(self):
    """
    save my lines
    """
    with open(self.file_path, "w") as f:
        for line in self.lines:
            f.write(f"{line}\n")

profiwiki_cmd

Created on 2023-04-01

@author: wf

ProfiWikiCmd

Bases: BaseCmd

ProfiWiki command line

Source code in profiwiki/profiwiki_cmd.py
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
class ProfiWikiCmd(BaseCmd):
    """
    ProfiWiki command line
    """

    def __init__(self, version: Version):
        super().__init__(version)
        self.pw = ProfiWiki()

    def add_arguments(self, parser):
        super().add_arguments(parser)
        self.pw.config.addArgs(parser)
        parser.add_argument(
            "--apache",
            help="generate apache configuration for the given server name",
        )
        parser.add_argument(
            "--all", help="do all necessary steps for a full setup", action="store_true"
        )
        parser.add_argument(
            "--anubis",
            action="store_true",
            help="add anubis support [default: %(default)s]",
        )
        parser.add_argument("--bash", help="bash into container", action="store_true")
        parser.add_argument("--create", action="store_true", help="create the wiki")
        parser.add_argument("--check", action="store_true", help="check the wiki")
        parser.add_argument("--cron", action="store_true", help="start cron service")
        parser.add_argument(
            "--down",
            action="store_true",
            help="shutdown the wiki [default: %(default)s]",
        )
        parser.add_argument(
            "--elastica",
            action="store_true",
            help="add elastica/cirrus search support [default: %(default)s]",
        )
        parser.add_argument(
            "-fa", "--fontawesome", action="store_true", help="install fontawesome"
        )
        parser.add_argument(
            "--family",
            action="store_true",
            help="support wiki family e.g. with bind mounts",
        )
        parser.add_argument(
            "-i", "--info", help="show system info", action="store_true"
        )
        parser.add_argument(
            "--list",
            action="store_true",
            help="list the available profi wikis [default: %(default)s]",
        )
        parser.add_argument(
            "--memcached",
            action="store_true",
            help="add memcached support [default: %(default)s]",
        )
        parser.add_argument(
            "--patch",
            action="store_true",
            help="apply LocalSettings.php patches [default: %(default)s]",
        )
        parser.add_argument(
            "-pu", "--plantuml", action="store_true", help="install plantuml"
        )
        parser.add_argument(
            "--update",
            action="store_true",
            help="start the update script -e.g. to fix SMW key",
        )
        parser.add_argument(
            "-wuc", "--wikiuser_check", action="store_true", help="check wikiuser"
        )
        return parser

    def handle_args(self, args) -> bool:
        handled = super().handle_args(args)
        if handled:
            return True
        if args.info:
            info = self.pw.system_info()
            print(info)
            return True
        return False

    def run(self, argv=None) -> int:
        args = self.parse_args(argv)
        handled = self.handle_args(args)
        if not handled:
            self.pw.work(args)
        return self.exit_code

profiwiki_core

Created on 2023-04-01

@author: wf

ProfiWiki

ProfiWiki

Source code in profiwiki/profiwiki_core.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
class ProfiWiki:
    """
    ProfiWiki
    """

    def __init__(
        self,
        prefix: str = "pw",
        smw_version="5.0.2",
        mw_version="1.43.11",
        port: int = 9079,
    ):
        """
        constructor
        """
        self.os_name = platform.system()
        self.os_uname = os.uname()
        self.os_release = platform.release()
        self.args = None
        self.config = MwClusterConfig()
        self.config.smw_version = smw_version
        self.config.random_password = False
        self.config.lenient = True
        self.config.prefix = prefix
        self.config.base_port = port
        self.config.sql_port = port - 1
        self.config.port = port
        self.config.versions = [mw_version]
        self.config.version = mw_version
        self.config.container_base_name = "pw"
        self.config.extensionNameList = [
            "Admin Links",
            "Diagrams",
            # "Graph", removed 2025-09-30
            "Header Tabs",
            "ImageMap",
            # "ImageLink", removed 2026-07-07 - no extension.json yet, fatals on MW >= 1.43 - see https://github.com/BITPlan/ImageLink/issues/1
            "MagicNoCache",
            "Maps12",
            "Mermaid",
            "MsUpload",
            "Nuke",
            "Page Forms",
            "ParserFunctions",
            "PDFEmbed",
            # "Renameuser", removed 2026-07-07 - part of MediaWiki core since 1.40
            "Replace Text",
            # "Semantic Result Formats", Version4
            "SRF5",
            "Scribunto",
            "SyntaxHighlight",
            "Variables",
            "UserFunctions",
            "YouTube",
        ]
        self.config.logo = "https://wiki.bitplan.com/images/wiki/thumb/6/63/Profiwikiicon.png/96px-Profiwikiicon.png"
        self.config.__post_init__()
        self.mwCluster = None
        pass

    def system_info(self) -> str:
        """
        collect system information
        """
        info = f"""os: {self.os_name}"""
        if "Darwin" in info:
            release, _version, _machine = platform.mac_ver()
            info += f" MacOS {release}"
        else:
            info += f"{self.os_release}"
        return info

    def work(self, args):
        """
        work as instructed by the arguments

        Args:
            args(Namespace): the command line arguments
        """
        self.args = args
        self.config.fromArgs(args)
        # ProfiWiki specific configuration entries
        self.config.memcached = args.memcached
        # use bind mount if we are in family mode
        if self.args.family:
            self.config.bind_mount = True
        if len(self.config.versions) == 1:
            self.config.version = self.config.versions[0]

        # make sure the wikiId is set from the container base name
        config_path = self.config.get_config_path()
        if os.path.isfile(config_path) and not self.config.forceRebuild:
            # reload the previous configuration e.g. based on container_name only
            previous_config = self.config.load(config_path)
            if self.config.verbose:
                print(f"ProfiWiki with previous configuration from {config_path}...")
            self.config = previous_config
        self.config.wikiId = self.config.container_base_name
        if args.bash:
            cmd = f"docker exec -it {self.config.container_base_name}-mw /bin/bash"
            print(cmd)
            return
        mwApp = self.getMwApp(withGenerate=self.config.forceRebuild)
        if self.config.verbose:
            print(
                f"ProfiWiki {mwApp.config.container_base_name} using port {mwApp.config.port} sqlport {mwApp.config.sql_port}"
            )
        if args.force_user:
            mwApp.createWikiUser(store=True)
        if args.all:
            image_name = f"profiwiki:{mwApp.config.shortVersion}"
            image = ProfiWikiContainer.get_image(image_name)
            if image:
                # we could use the image here to speed up things
                pass
            self.create(mwApp, self.config.forceRebuild)
            pmw, _pdb = self.getProfiWikiContainers(mwApp)
            pmw.install_fontawesome()
            pmw.install_plantuml()
            pmw.commit(tag=image_name)
            mwApp.execute("/scripts/setup-mediawiki.sh", "--all")
            self.patch(pmw)
        if args.wikiuser_check:
            self.check_wikiuser(mwApp)
        if args.apache:
            apache_config = self.apache_config(mwApp)
            print(apache_config)
        if args.create:
            self.create(mwApp, self.config.forceRebuild)
        if args.check:
            self.check(mwApp)
        if args.down:
            self.down(mwApp, self.config.forceRebuild)
        if args.list:
            self.list(mwApp)
        if args.plantuml or args.fontawesome or args.cron or args.patch:
            pmw, _pdb = self.getProfiWikiContainers(mwApp)
            if args.plantuml:
                pmw.install_plantuml()
            if args.fontawesome:
                pmw.install_fontawesome()
            if args.cron:
                pmw.start_cron()
            if args.patch:
                self.patch(pmw)
        if args.update:
            self.update(mwApp)

    def getMwCluster(self, withGenerate: bool = True) -> MediaWikiCluster:
        """
        get a mediawiki Cluster for my configuration

        Args:
            withGenerate(bool): if True regenerate the configuration files

        Returns:
            MediaWikiCluster: the MediaWiki Cluser
        """
        if self.mwCluster is not None:
            return self.mwCluster
        mwCluster = MediaWikiCluster(config=self.config)
        # make sure docker is in path
        mwCluster.checkDocker()
        # generate
        mwCluster.createApps(withGenerate=withGenerate)
        self.mwCluster = mwCluster
        return mwCluster

    def getMwApp(self, withGenerate: bool = True):
        """
        get my mediawiki Docker application
        """
        mwCluster = self.getMwCluster(withGenerate)
        if not self.config.version in mwCluster.apps:
            raise Exception(
                f"Mediawiki version {self.config.version} missing {mwCluster.apps.keys()}"
            )
        mwApp = mwCluster.apps[self.config.version]
        return mwApp

    def getProfiWikiContainers(self, mwApp: DockerApplication):
        """
        get the two containers - for mediawiki and the database

        Args:
            mwApp(DockerApplication): the MediaWiki Docker Application

        Returns:
            Tuple(ProfiWikiContainer,ProfiWikiContainer): MediaWiki, Database
        """
        mw, db = mwApp.getContainers()
        pmw = ProfiWikiContainer(mw)
        pdb = ProfiWikiContainer(db)
        return pmw, pdb

    def add_compose_service(
        self, mwApp: DockerApplication, service_key: str, service_yaml: str
    ):
        """
        add a service to the docker compose file

        Args:
            mwApp(DockerApplication): the mediawiki application
            service_key(str): the key of the service e.g. memcached
            service_yaml(str): the yaml definition of the service (must start with indentation)
        """
        compose_path = f"{mwApp.dockerPath}/docker-compose.yml"
        with open(compose_path, "r") as f:
            content = f.read()

        # Idempotency check to ensure we don't add it twice
        if f"{service_key}:" not in content:
            if self.config.verbose:
                print(f"Adding {service_key} service to {compose_path}")
            with open(compose_path, "a") as f:
                f.write(service_yaml)

    def add_memcached(self, mwApp: DockerApplication):
        """
        add memcached to the docker compose file

        Args:
            mwApp(DockerApplication): the mediawiki application
        """
        memcached_service = """
  memcached:
    image: memcached:alpine
    restart: always"""
        self.add_compose_service(mwApp, "memcached", memcached_service)

    def patch(self, pwc: ProfiWikiContainer):
        """
        apply profi wiki patches to the given ProfiWikiContainer
        """
        if not pwc.dc:
            raise ("no container to apply patch")
        ls_path = "/var/www/html/LocalSettings.php"
        timestamp = datetime.datetime.utcnow().strftime("%Y-%m-%d")
        # $wgAllowImageTag is deprecated since 1.35 and a hard
        # config-validation error for update.php since MediaWiki 1.43
        allow_image_tag = ""
        if self.config.shortVersion < "143":
            allow_image_tag = "// allow images\n$wgAllowImageTag=true;\n"
        with tempfile.NamedTemporaryFile(
            mode="w", prefix="LocalSettings_", suffix=".php"
        ) as ls_file:
            pwc.log_action(f"patching {ls_file.name}")
            pwc.dc.container.copy_from(ls_path, ls_file.name)
            patch = Patch(file_path=ls_file.name)
            lines = f"""// modified by profiwiki at {timestamp}
// use WikiEditor e.g. for MsUpload
wfLoadExtension( 'WikiEditor' );
# make this an intranet - comment out if you want this to be a public wiki
# The following permissions were set based on your choice in the installer
$wgGroupPermissions['*']['createaccount'] = false;
$wgGroupPermissions['*']['edit'] = false;
$wgGroupPermissions['*']['read'] = false;
# Allow properties in Templates
$smwgNamespacesWithSemanticLinks[NS_TEMPLATE] = true;
# WF 2015-01-20
# allow string functions - needed for Template:Link
$wgPFEnableStringFunctions=true;
// allow raw HTML
$wgRawHtml = true;
{allow_image_tag}// InstantCommons allows wiki to use images from https://commons.wikimedia.org
$wgUseInstantCommons = true;
// avoid showing (expected) deprecation warnings
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
# # add support for special properties
# see https://www.semantic-mediawiki.org/wiki/Help:$smwgPageSpecialProperties
# Modification date
$smwgPageSpecialProperties[] = '_MDAT';
# Creation date
$smwgPageSpecialProperties[] = '_CDAT';
# Is a new page
$smwgPageSpecialProperties[] = '_NEWP';
# Last editor is
$smwgPageSpecialProperties[] = '_LEDT';
# Media type
$smwgPageSpecialProperties[] = '_MEDIA';
# MIME type
$smwgPageSpecialProperties[] = '_MIME';
// https://www.mediawiki.org/wiki/Extension:UserFunctions
$wgUFEnabledPersonalDataFunctions = ['ip','nickname','realname','useremail','username',];
// allow user functions in main mediawiki space
$wgUFAllowedNamespaces[NS_MAIN] = true;
# increase query limit
$smwgQMaxLimit = 20000;
//Default width for the PDF object container.
$wgPdfEmbed['width'] = 800;
//Default height for the PDF object container.
$wgPdfEmbed['height'] = 1090;
//Allow user the usage of the tag
$wgGroupPermissions['*']['embed_pdf'] = true;
// config parameters for MsUpload
// https://www.mediawiki.org/wiki/Extension:MsUpload
$wgMSU_useDragDrop = true; // Should the drag & drop area be shown? (Not set by default)
$wgMSU_showAutoCat = true; // Files uploaded while editing a category page will be added to that category
$wgMSU_checkAutoCat = true; // Whether the checkbox for adding a category to a page is checked by default
$wgMSU_useMsLinks = false; // Insert links in Extension:MsLinks style?
$wgMSU_confirmReplace = true; // Show the "Replace file?" checkbox
$wgMSU_imgParams = '400px'; // Default image parameters, for example "thumb|200px"
$wgMSU_uploadsize = '100mb'; // Max upload size through MsUpload
// general parameters for MsUpload
$wgEnableWriteAPI = true; // Enable the API
$wgEnableUploads = true; // Enable uploads
$wgAllowJavaUploads = true; // Solves problem with Office 2007 and newer files (docx, xlsx, etc.)
$wgGroupPermissions['user']['upload'] = true; // Allow regular users to upload files
# add more file upload options
$wgGroupPermissions['user']['upload_by_url'] = true;
$wgAllowCopyUploads = true;
$wgCopyUploadsFromSpecialUpload = true;
# http://www.mediawiki.org/wiki/Manual:Configuring_file_uploads/de
$wgFileExtensions = array_merge($wgFileExtensions, array('doc', 'gcode',
'gpx','htm','html','jscad','jpg','pdf','ppt','docx', 'docxm','xlsx','xlsm','mp3','mp4','odp','otp','pptx', 'pptm','reqif','reqifz','rtf','rythm'
,'scad','sh','stl','svg','vcf','vim','uew','xls','xml','zip'));
# allow html
$wgVerifyMimeType=false;
# disable upload script checks ...
$wgDisableUploadScriptChecks = true;
"""
            # memcached configuration lines
            if getattr(self.config, "memcached", False):
                lines += """# Memcached config
$wgMainCacheType = CACHE_MEMCACHED;
$wgParserCacheType = CACHE_MEMCACHED;
$wgMessageCacheType = CACHE_MEMCACHED;
$wgSessionCacheType = CACHE_MEMCACHED;
$wgMemCachedServers = [ 'memcached:11211' ];
$wgSessionsInObjectCache = true;
"""
            patch.add_text(lines)
            patch.save()
            pwc.dc.container.copy_to(ls_file.name, ls_path)

    def check(self, mwApp):
        """
        check
        """
        mwApp.check()

    def create(self, mwApp, forceRebuild: bool = False):
        """
        create a profiwiki mediawiki
        """
        mwApp.start(forceRebuild=forceRebuild)

    def down(self, mwApp, forceRebuild: bool = False):
        """
        shut down the profiwiki base mediawiki
        """
        mwApp.down(forceRebuild=forceRebuild)

    def list(self, mwApp):
        """
        list the profi wikis
        """
        print(json.dumps(mwApp.config.as_dict(), indent=2))
        pass

    def check_wikiuser(self, mwApp: DockerApplication):
        """ """
        print(f"Checking WikiUser ... for {mwApp.config.container_base_name}")
        wikiUsers = WikiUser.getWikiUsers(lenient=True)
        if not mwApp.config.wikiId:
            print("no WikiId configured")
            return
        if not mwApp.config.wikiId in wikiUsers:
            print(f"no wikiUser for wikiId {mwApp.config.wikiId} found")
            return
        wikiUser = wikiUsers[mwApp.config.wikiId]
        if mwApp.config.password != wikiUser.getPassword():
            print(f"configured password is different then {mwApp.config.wikiId}")
        else:
            print(
                f"wikiUser for wikiId {mwApp.config.wikiId} is available and password as configured"
            )
        pass

    def apache_config(self, mwApp: DockerApplication) -> str:
        """
        get the apache configuration for the given mediawiki Docker application

        Args:
            mwApp(DockerApplication): the docker application to generate the configuration for
        """
        config = mwApp.config
        iso_timestamp = datetime.datetime.now().isoformat()
        server_name = f"{self.args.apache}"
        header_comment = f"""# Apache Configuration for {server_name}
# Generated by ProfiWiki at {iso_timestamp}
"""
        apache_config = f"""{header_comment}<VirtualHost *:443>
    ServerName {server_name}
    ServerAdmin webmaster@{config.host}

    ErrorLog ${{APACHE_LOG_DIR}}/{config.container_base_name}_error.log
    CustomLog ${{APACHE_LOG_DIR}}/{config.container_base_name}_access.log combined

    ProxyPass / http://localhost:{config.port}/
    ProxyPassReverse / http://localhost:{config.port}/
</VirtualHost>
<VirtualHost *:80>
    ServerName {server_name}
    Redirect permanent / https://{server_name}/
</VirtualHost>
"""
        return apache_config

__init__(prefix='pw', smw_version='5.0.2', mw_version='1.43.11', port=9079)

constructor

Source code in profiwiki/profiwiki_core.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def __init__(
    self,
    prefix: str = "pw",
    smw_version="5.0.2",
    mw_version="1.43.11",
    port: int = 9079,
):
    """
    constructor
    """
    self.os_name = platform.system()
    self.os_uname = os.uname()
    self.os_release = platform.release()
    self.args = None
    self.config = MwClusterConfig()
    self.config.smw_version = smw_version
    self.config.random_password = False
    self.config.lenient = True
    self.config.prefix = prefix
    self.config.base_port = port
    self.config.sql_port = port - 1
    self.config.port = port
    self.config.versions = [mw_version]
    self.config.version = mw_version
    self.config.container_base_name = "pw"
    self.config.extensionNameList = [
        "Admin Links",
        "Diagrams",
        # "Graph", removed 2025-09-30
        "Header Tabs",
        "ImageMap",
        # "ImageLink", removed 2026-07-07 - no extension.json yet, fatals on MW >= 1.43 - see https://github.com/BITPlan/ImageLink/issues/1
        "MagicNoCache",
        "Maps12",
        "Mermaid",
        "MsUpload",
        "Nuke",
        "Page Forms",
        "ParserFunctions",
        "PDFEmbed",
        # "Renameuser", removed 2026-07-07 - part of MediaWiki core since 1.40
        "Replace Text",
        # "Semantic Result Formats", Version4
        "SRF5",
        "Scribunto",
        "SyntaxHighlight",
        "Variables",
        "UserFunctions",
        "YouTube",
    ]
    self.config.logo = "https://wiki.bitplan.com/images/wiki/thumb/6/63/Profiwikiicon.png/96px-Profiwikiicon.png"
    self.config.__post_init__()
    self.mwCluster = None
    pass

add_compose_service(mwApp, service_key, service_yaml)

add a service to the docker compose file

Parameters:

Name Type Description Default
mwApp(DockerApplication)

the mediawiki application

required
service_key(str)

the key of the service e.g. memcached

required
service_yaml(str)

the yaml definition of the service (must start with indentation)

required
Source code in profiwiki/profiwiki_core.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
def add_compose_service(
    self, mwApp: DockerApplication, service_key: str, service_yaml: str
):
    """
    add a service to the docker compose file

    Args:
        mwApp(DockerApplication): the mediawiki application
        service_key(str): the key of the service e.g. memcached
        service_yaml(str): the yaml definition of the service (must start with indentation)
    """
    compose_path = f"{mwApp.dockerPath}/docker-compose.yml"
    with open(compose_path, "r") as f:
        content = f.read()

    # Idempotency check to ensure we don't add it twice
    if f"{service_key}:" not in content:
        if self.config.verbose:
            print(f"Adding {service_key} service to {compose_path}")
        with open(compose_path, "a") as f:
            f.write(service_yaml)

add_memcached(mwApp)

add memcached to the docker compose file

Parameters:

Name Type Description Default
mwApp(DockerApplication)

the mediawiki application

required
Source code in profiwiki/profiwiki_core.py
239
240
241
242
243
244
245
246
247
248
249
250
  def add_memcached(self, mwApp: DockerApplication):
      """
      add memcached to the docker compose file

      Args:
          mwApp(DockerApplication): the mediawiki application
      """
      memcached_service = """
memcached:
  image: memcached:alpine
  restart: always"""
      self.add_compose_service(mwApp, "memcached", memcached_service)

apache_config(mwApp)

get the apache configuration for the given mediawiki Docker application

Parameters:

Name Type Description Default
mwApp(DockerApplication)

the docker application to generate the configuration for

required
Source code in profiwiki/profiwiki_core.py
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
    def apache_config(self, mwApp: DockerApplication) -> str:
        """
        get the apache configuration for the given mediawiki Docker application

        Args:
            mwApp(DockerApplication): the docker application to generate the configuration for
        """
        config = mwApp.config
        iso_timestamp = datetime.datetime.now().isoformat()
        server_name = f"{self.args.apache}"
        header_comment = f"""# Apache Configuration for {server_name}
# Generated by ProfiWiki at {iso_timestamp}
"""
        apache_config = f"""{header_comment}<VirtualHost *:443>
    ServerName {server_name}
    ServerAdmin webmaster@{config.host}

    ErrorLog ${{APACHE_LOG_DIR}}/{config.container_base_name}_error.log
    CustomLog ${{APACHE_LOG_DIR}}/{config.container_base_name}_access.log combined

    ProxyPass / http://localhost:{config.port}/
    ProxyPassReverse / http://localhost:{config.port}/
</VirtualHost>
<VirtualHost *:80>
    ServerName {server_name}
    Redirect permanent / https://{server_name}/
</VirtualHost>
"""
        return apache_config

check(mwApp)

check

Source code in profiwiki/profiwiki_core.py
357
358
359
360
361
def check(self, mwApp):
    """
    check
    """
    mwApp.check()

check_wikiuser(mwApp)

Source code in profiwiki/profiwiki_core.py
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
def check_wikiuser(self, mwApp: DockerApplication):
    """ """
    print(f"Checking WikiUser ... for {mwApp.config.container_base_name}")
    wikiUsers = WikiUser.getWikiUsers(lenient=True)
    if not mwApp.config.wikiId:
        print("no WikiId configured")
        return
    if not mwApp.config.wikiId in wikiUsers:
        print(f"no wikiUser for wikiId {mwApp.config.wikiId} found")
        return
    wikiUser = wikiUsers[mwApp.config.wikiId]
    if mwApp.config.password != wikiUser.getPassword():
        print(f"configured password is different then {mwApp.config.wikiId}")
    else:
        print(
            f"wikiUser for wikiId {mwApp.config.wikiId} is available and password as configured"
        )
    pass

create(mwApp, forceRebuild=False)

create a profiwiki mediawiki

Source code in profiwiki/profiwiki_core.py
363
364
365
366
367
def create(self, mwApp, forceRebuild: bool = False):
    """
    create a profiwiki mediawiki
    """
    mwApp.start(forceRebuild=forceRebuild)

down(mwApp, forceRebuild=False)

shut down the profiwiki base mediawiki

Source code in profiwiki/profiwiki_core.py
369
370
371
372
373
def down(self, mwApp, forceRebuild: bool = False):
    """
    shut down the profiwiki base mediawiki
    """
    mwApp.down(forceRebuild=forceRebuild)

getMwApp(withGenerate=True)

get my mediawiki Docker application

Source code in profiwiki/profiwiki_core.py
190
191
192
193
194
195
196
197
198
199
200
def getMwApp(self, withGenerate: bool = True):
    """
    get my mediawiki Docker application
    """
    mwCluster = self.getMwCluster(withGenerate)
    if not self.config.version in mwCluster.apps:
        raise Exception(
            f"Mediawiki version {self.config.version} missing {mwCluster.apps.keys()}"
        )
    mwApp = mwCluster.apps[self.config.version]
    return mwApp

getMwCluster(withGenerate=True)

get a mediawiki Cluster for my configuration

Parameters:

Name Type Description Default
withGenerate(bool)

if True regenerate the configuration files

required

Returns:

Name Type Description
MediaWikiCluster MediaWikiCluster

the MediaWiki Cluser

Source code in profiwiki/profiwiki_core.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def getMwCluster(self, withGenerate: bool = True) -> MediaWikiCluster:
    """
    get a mediawiki Cluster for my configuration

    Args:
        withGenerate(bool): if True regenerate the configuration files

    Returns:
        MediaWikiCluster: the MediaWiki Cluser
    """
    if self.mwCluster is not None:
        return self.mwCluster
    mwCluster = MediaWikiCluster(config=self.config)
    # make sure docker is in path
    mwCluster.checkDocker()
    # generate
    mwCluster.createApps(withGenerate=withGenerate)
    self.mwCluster = mwCluster
    return mwCluster

getProfiWikiContainers(mwApp)

get the two containers - for mediawiki and the database

Parameters:

Name Type Description Default
mwApp(DockerApplication)

the MediaWiki Docker Application

required

Returns:

Name Type Description
Tuple (ProfiWikiContainer, ProfiWikiContainer)

MediaWiki, Database

Source code in profiwiki/profiwiki_core.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def getProfiWikiContainers(self, mwApp: DockerApplication):
    """
    get the two containers - for mediawiki and the database

    Args:
        mwApp(DockerApplication): the MediaWiki Docker Application

    Returns:
        Tuple(ProfiWikiContainer,ProfiWikiContainer): MediaWiki, Database
    """
    mw, db = mwApp.getContainers()
    pmw = ProfiWikiContainer(mw)
    pdb = ProfiWikiContainer(db)
    return pmw, pdb

list(mwApp)

list the profi wikis

Source code in profiwiki/profiwiki_core.py
375
376
377
378
379
380
def list(self, mwApp):
    """
    list the profi wikis
    """
    print(json.dumps(mwApp.config.as_dict(), indent=2))
    pass

patch(pwc)

apply profi wiki patches to the given ProfiWikiContainer

Source code in profiwiki/profiwiki_core.py
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
    def patch(self, pwc: ProfiWikiContainer):
        """
        apply profi wiki patches to the given ProfiWikiContainer
        """
        if not pwc.dc:
            raise ("no container to apply patch")
        ls_path = "/var/www/html/LocalSettings.php"
        timestamp = datetime.datetime.utcnow().strftime("%Y-%m-%d")
        # $wgAllowImageTag is deprecated since 1.35 and a hard
        # config-validation error for update.php since MediaWiki 1.43
        allow_image_tag = ""
        if self.config.shortVersion < "143":
            allow_image_tag = "// allow images\n$wgAllowImageTag=true;\n"
        with tempfile.NamedTemporaryFile(
            mode="w", prefix="LocalSettings_", suffix=".php"
        ) as ls_file:
            pwc.log_action(f"patching {ls_file.name}")
            pwc.dc.container.copy_from(ls_path, ls_file.name)
            patch = Patch(file_path=ls_file.name)
            lines = f"""// modified by profiwiki at {timestamp}
// use WikiEditor e.g. for MsUpload
wfLoadExtension( 'WikiEditor' );
# make this an intranet - comment out if you want this to be a public wiki
# The following permissions were set based on your choice in the installer
$wgGroupPermissions['*']['createaccount'] = false;
$wgGroupPermissions['*']['edit'] = false;
$wgGroupPermissions['*']['read'] = false;
# Allow properties in Templates
$smwgNamespacesWithSemanticLinks[NS_TEMPLATE] = true;
# WF 2015-01-20
# allow string functions - needed for Template:Link
$wgPFEnableStringFunctions=true;
// allow raw HTML
$wgRawHtml = true;
{allow_image_tag}// InstantCommons allows wiki to use images from https://commons.wikimedia.org
$wgUseInstantCommons = true;
// avoid showing (expected) deprecation warnings
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
# # add support for special properties
# see https://www.semantic-mediawiki.org/wiki/Help:$smwgPageSpecialProperties
# Modification date
$smwgPageSpecialProperties[] = '_MDAT';
# Creation date
$smwgPageSpecialProperties[] = '_CDAT';
# Is a new page
$smwgPageSpecialProperties[] = '_NEWP';
# Last editor is
$smwgPageSpecialProperties[] = '_LEDT';
# Media type
$smwgPageSpecialProperties[] = '_MEDIA';
# MIME type
$smwgPageSpecialProperties[] = '_MIME';
// https://www.mediawiki.org/wiki/Extension:UserFunctions
$wgUFEnabledPersonalDataFunctions = ['ip','nickname','realname','useremail','username',];
// allow user functions in main mediawiki space
$wgUFAllowedNamespaces[NS_MAIN] = true;
# increase query limit
$smwgQMaxLimit = 20000;
//Default width for the PDF object container.
$wgPdfEmbed['width'] = 800;
//Default height for the PDF object container.
$wgPdfEmbed['height'] = 1090;
//Allow user the usage of the tag
$wgGroupPermissions['*']['embed_pdf'] = true;
// config parameters for MsUpload
// https://www.mediawiki.org/wiki/Extension:MsUpload
$wgMSU_useDragDrop = true; // Should the drag & drop area be shown? (Not set by default)
$wgMSU_showAutoCat = true; // Files uploaded while editing a category page will be added to that category
$wgMSU_checkAutoCat = true; // Whether the checkbox for adding a category to a page is checked by default
$wgMSU_useMsLinks = false; // Insert links in Extension:MsLinks style?
$wgMSU_confirmReplace = true; // Show the "Replace file?" checkbox
$wgMSU_imgParams = '400px'; // Default image parameters, for example "thumb|200px"
$wgMSU_uploadsize = '100mb'; // Max upload size through MsUpload
// general parameters for MsUpload
$wgEnableWriteAPI = true; // Enable the API
$wgEnableUploads = true; // Enable uploads
$wgAllowJavaUploads = true; // Solves problem with Office 2007 and newer files (docx, xlsx, etc.)
$wgGroupPermissions['user']['upload'] = true; // Allow regular users to upload files
# add more file upload options
$wgGroupPermissions['user']['upload_by_url'] = true;
$wgAllowCopyUploads = true;
$wgCopyUploadsFromSpecialUpload = true;
# http://www.mediawiki.org/wiki/Manual:Configuring_file_uploads/de
$wgFileExtensions = array_merge($wgFileExtensions, array('doc', 'gcode',
'gpx','htm','html','jscad','jpg','pdf','ppt','docx', 'docxm','xlsx','xlsm','mp3','mp4','odp','otp','pptx', 'pptm','reqif','reqifz','rtf','rythm'
,'scad','sh','stl','svg','vcf','vim','uew','xls','xml','zip'));
# allow html
$wgVerifyMimeType=false;
# disable upload script checks ...
$wgDisableUploadScriptChecks = true;
"""
            # memcached configuration lines
            if getattr(self.config, "memcached", False):
                lines += """# Memcached config
$wgMainCacheType = CACHE_MEMCACHED;
$wgParserCacheType = CACHE_MEMCACHED;
$wgMessageCacheType = CACHE_MEMCACHED;
$wgSessionCacheType = CACHE_MEMCACHED;
$wgMemCachedServers = [ 'memcached:11211' ];
$wgSessionsInObjectCache = true;
"""
            patch.add_text(lines)
            patch.save()
            pwc.dc.container.copy_to(ls_file.name, ls_path)

system_info()

collect system information

Source code in profiwiki/profiwiki_core.py
82
83
84
85
86
87
88
89
90
91
92
def system_info(self) -> str:
    """
    collect system information
    """
    info = f"""os: {self.os_name}"""
    if "Darwin" in info:
        release, _version, _machine = platform.mac_ver()
        info += f" MacOS {release}"
    else:
        info += f"{self.os_release}"
    return info

work(args)

work as instructed by the arguments

Parameters:

Name Type Description Default
args(Namespace)

the command line arguments

required
Source code in profiwiki/profiwiki_core.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
def work(self, args):
    """
    work as instructed by the arguments

    Args:
        args(Namespace): the command line arguments
    """
    self.args = args
    self.config.fromArgs(args)
    # ProfiWiki specific configuration entries
    self.config.memcached = args.memcached
    # use bind mount if we are in family mode
    if self.args.family:
        self.config.bind_mount = True
    if len(self.config.versions) == 1:
        self.config.version = self.config.versions[0]

    # make sure the wikiId is set from the container base name
    config_path = self.config.get_config_path()
    if os.path.isfile(config_path) and not self.config.forceRebuild:
        # reload the previous configuration e.g. based on container_name only
        previous_config = self.config.load(config_path)
        if self.config.verbose:
            print(f"ProfiWiki with previous configuration from {config_path}...")
        self.config = previous_config
    self.config.wikiId = self.config.container_base_name
    if args.bash:
        cmd = f"docker exec -it {self.config.container_base_name}-mw /bin/bash"
        print(cmd)
        return
    mwApp = self.getMwApp(withGenerate=self.config.forceRebuild)
    if self.config.verbose:
        print(
            f"ProfiWiki {mwApp.config.container_base_name} using port {mwApp.config.port} sqlport {mwApp.config.sql_port}"
        )
    if args.force_user:
        mwApp.createWikiUser(store=True)
    if args.all:
        image_name = f"profiwiki:{mwApp.config.shortVersion}"
        image = ProfiWikiContainer.get_image(image_name)
        if image:
            # we could use the image here to speed up things
            pass
        self.create(mwApp, self.config.forceRebuild)
        pmw, _pdb = self.getProfiWikiContainers(mwApp)
        pmw.install_fontawesome()
        pmw.install_plantuml()
        pmw.commit(tag=image_name)
        mwApp.execute("/scripts/setup-mediawiki.sh", "--all")
        self.patch(pmw)
    if args.wikiuser_check:
        self.check_wikiuser(mwApp)
    if args.apache:
        apache_config = self.apache_config(mwApp)
        print(apache_config)
    if args.create:
        self.create(mwApp, self.config.forceRebuild)
    if args.check:
        self.check(mwApp)
    if args.down:
        self.down(mwApp, self.config.forceRebuild)
    if args.list:
        self.list(mwApp)
    if args.plantuml or args.fontawesome or args.cron or args.patch:
        pmw, _pdb = self.getProfiWikiContainers(mwApp)
        if args.plantuml:
            pmw.install_plantuml()
        if args.fontawesome:
            pmw.install_fontawesome()
        if args.cron:
            pmw.start_cron()
        if args.patch:
            self.patch(pmw)
    if args.update:
        self.update(mwApp)

pw_container

Created on 2023-04-01

@author: wf

ProfiWikiContainer

a profiwiki docker container wrapper

Source code in profiwiki/pw_container.py
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
class ProfiWikiContainer:
    """
    a profiwiki docker container wrapper
    """

    def __init__(self, dc: DockerContainer):
        """
        Args:
            dc(DockerContainer): the container to wrap
        """
        self.dc = dc

    @staticmethod
    def get_image(image_name: str):
        image = None
        try:
            image = pow_docker.image.inspect(image_name)
        except Exception as ex:
            # FIXME filter exceptions that need to be handled
            if "-something we would like to handle -" in str(ex):
                pass
            pass
        return image

    def commit(self, tag: str):
        """
        Commit the current state of this container to a new image.

        Args:
            tag(str): the target image tag e.g. 'ProfiWiki-1.39.13'
        """
        self.log_action(f"committing to image {tag}")
        pow_docker.commit(container=self.dc.container, tag=tag)

    def log_action(self, action: str):
        """
        log the given action

        Args:
            action(str): the d
        """
        if self.dc:
            print(f"{action} {self.dc.kind} {self.dc.name}", flush=True)
        else:
            print(f"{action}", flush=True)

    def killremove(self, volumes: bool = False):
        """
        kill and remove me

        Args:
            volumes(bool): if True remove anonymous volumes associated with the container, default=True (to avoid e.g. passwords to get remembered / stuck
        """
        if self.dc:
            self.log_action("killing and removing")
            self.dc.container.kill()
            self.dc.container.remove(volumes=volumes)

    def run_script_in_container(self, script_to_execute: str, sudo: bool = False):
        """
        Make the script executable and run it in the container.

        Args:
            script_to_execute: absolute path to the script inside the container
            sudo: whether to run with sudo
            use_tty: whether to allocate a pseudo-TTY
        """
        self.dc.execute("sudo", "chmod", "755", script_to_execute)
        cmd = ["sudo"] if sudo else []
        cmd += ["bash", script_to_execute]
        self.dc.execute(*cmd)

    def install_and_run_script_from_file(self, script_name: str, sudo: bool = False):
        """
        Copy a local shell script into the container and execute it.

        Args:
            local_script_path: path to the local shell script file
            sudo: whether to use sudo inside the container
        """
        local_script_path = self.get_local_script(script_name)
        script_to_execute = f"/scripts/{script_name}"
        pow_docker.copy(local_script_path, (self.dc.container.name, script_to_execute))
        self.run_script_in_container(script_to_execute, sudo=sudo)

    def get_local_script(self, name: str) -> str:
        script_path = os.path.join(os.path.dirname(__file__), "resources", name)
        return script_path

    def install_plantuml(self):
        """
        install plantuml to this container
        """
        self.install_and_run_script_from_file("install_plantuml.sh", sudo=True)
        pass

    def install_fontawesome(self):
        """
        install fontawesome to this container
        """
        script_name = "install_fontawesome.sh"
        self.install_and_run_script_from_file(script_name, sudo=True)
        try:
            self.dc.container.execute(["service", "apache2", "restart"])
        except DockerException as e:
            # we expect a SIGTERM
            if not e.return_code == 143:
                raise e
        pass

__init__(dc)

Parameters:

Name Type Description Default
dc(DockerContainer)

the container to wrap

required
Source code in profiwiki/pw_container.py
19
20
21
22
23
24
def __init__(self, dc: DockerContainer):
    """
    Args:
        dc(DockerContainer): the container to wrap
    """
    self.dc = dc

commit(tag)

Commit the current state of this container to a new image.

Parameters:

Name Type Description Default
tag(str)

the target image tag e.g. 'ProfiWiki-1.39.13'

required
Source code in profiwiki/pw_container.py
38
39
40
41
42
43
44
45
46
def commit(self, tag: str):
    """
    Commit the current state of this container to a new image.

    Args:
        tag(str): the target image tag e.g. 'ProfiWiki-1.39.13'
    """
    self.log_action(f"committing to image {tag}")
    pow_docker.commit(container=self.dc.container, tag=tag)

install_and_run_script_from_file(script_name, sudo=False)

Copy a local shell script into the container and execute it.

Parameters:

Name Type Description Default
local_script_path

path to the local shell script file

required
sudo bool

whether to use sudo inside the container

False
Source code in profiwiki/pw_container.py
86
87
88
89
90
91
92
93
94
95
96
97
def install_and_run_script_from_file(self, script_name: str, sudo: bool = False):
    """
    Copy a local shell script into the container and execute it.

    Args:
        local_script_path: path to the local shell script file
        sudo: whether to use sudo inside the container
    """
    local_script_path = self.get_local_script(script_name)
    script_to_execute = f"/scripts/{script_name}"
    pow_docker.copy(local_script_path, (self.dc.container.name, script_to_execute))
    self.run_script_in_container(script_to_execute, sudo=sudo)

install_fontawesome()

install fontawesome to this container

Source code in profiwiki/pw_container.py
110
111
112
113
114
115
116
117
118
119
120
121
122
def install_fontawesome(self):
    """
    install fontawesome to this container
    """
    script_name = "install_fontawesome.sh"
    self.install_and_run_script_from_file(script_name, sudo=True)
    try:
        self.dc.container.execute(["service", "apache2", "restart"])
    except DockerException as e:
        # we expect a SIGTERM
        if not e.return_code == 143:
            raise e
    pass

install_plantuml()

install plantuml to this container

Source code in profiwiki/pw_container.py
103
104
105
106
107
108
def install_plantuml(self):
    """
    install plantuml to this container
    """
    self.install_and_run_script_from_file("install_plantuml.sh", sudo=True)
    pass

killremove(volumes=False)

kill and remove me

Parameters:

Name Type Description Default
volumes(bool)

if True remove anonymous volumes associated with the container, default=True (to avoid e.g. passwords to get remembered / stuck

required
Source code in profiwiki/pw_container.py
60
61
62
63
64
65
66
67
68
69
70
def killremove(self, volumes: bool = False):
    """
    kill and remove me

    Args:
        volumes(bool): if True remove anonymous volumes associated with the container, default=True (to avoid e.g. passwords to get remembered / stuck
    """
    if self.dc:
        self.log_action("killing and removing")
        self.dc.container.kill()
        self.dc.container.remove(volumes=volumes)

log_action(action)

log the given action

Parameters:

Name Type Description Default
action(str)

the d

required
Source code in profiwiki/pw_container.py
48
49
50
51
52
53
54
55
56
57
58
def log_action(self, action: str):
    """
    log the given action

    Args:
        action(str): the d
    """
    if self.dc:
        print(f"{action} {self.dc.kind} {self.dc.name}", flush=True)
    else:
        print(f"{action}", flush=True)

run_script_in_container(script_to_execute, sudo=False)

Make the script executable and run it in the container.

Parameters:

Name Type Description Default
script_to_execute str

absolute path to the script inside the container

required
sudo bool

whether to run with sudo

False
use_tty

whether to allocate a pseudo-TTY

required
Source code in profiwiki/pw_container.py
72
73
74
75
76
77
78
79
80
81
82
83
84
def run_script_in_container(self, script_to_execute: str, sudo: bool = False):
    """
    Make the script executable and run it in the container.

    Args:
        script_to_execute: absolute path to the script inside the container
        sudo: whether to run with sudo
        use_tty: whether to allocate a pseudo-TTY
    """
    self.dc.execute("sudo", "chmod", "755", script_to_execute)
    cmd = ["sudo"] if sudo else []
    cmd += ["bash", script_to_execute]
    self.dc.execute(*cmd)

version

Created on 2023-04-01

@author: wf

Version

Bases: object

Version handling for ProfiWiki

Source code in profiwiki/version.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Version(object):
    """
    Version handling for ProfiWiki
    """

    name = "pyProfiWiki"
    description = """BITPlan's professional Semantic Mediawiki"""
    version = profiwiki.__version__
    date = "2023-04-01"
    updated = "2026-07-07"
    authors = "Wolfgang Fahl"
    doc_url = "https://wiki.bitplan.com/index.php/ProfiWiki"
    chat_url = "https://github.com/BITPlan/ProfiWiki/discussions"
    cm_url = "https://github.com/BITPlan/ProfiWiki"
    license = f"""Copyright 2015-2026 contributors. All rights reserved.
  Licensed under the Apache License 2.0
  http://www.apache.org/licenses/LICENSE-2.0
  Distributed on an "AS IS" basis without warranties
  or conditions of any kind, either express or implied."""
    longDescription = f"""{name} version {version}
{description}
  Created by {authors} on {date} last updated {updated}"""