xref: /OpenGrok/docker/start.py (revision b2d29dae9a94943b44f7145a695b3511eb3ba2b1)
1*b2d29daeSVladimir Kotal#!/usr/bin/env python3
2*b2d29daeSVladimir Kotal
3*b2d29daeSVladimir Kotal# CDDL HEADER START
4*b2d29daeSVladimir Kotal#
5*b2d29daeSVladimir Kotal# The contents of this file are subject to the terms of the
6*b2d29daeSVladimir Kotal# Common Development and Distribution License (the "License").
7*b2d29daeSVladimir Kotal# You may not use this file except in compliance with the License.
8*b2d29daeSVladimir Kotal#
9*b2d29daeSVladimir Kotal# See LICENSE.txt included in this distribution for the specific
10*b2d29daeSVladimir Kotal# language governing permissions and limitations under the License.
11*b2d29daeSVladimir Kotal#
12*b2d29daeSVladimir Kotal# When distributing Covered Code, include this CDDL HEADER in each
13*b2d29daeSVladimir Kotal# file and include the License file at LICENSE.txt.
14*b2d29daeSVladimir Kotal# If applicable, add the following below this CDDL HEADER, with the
15*b2d29daeSVladimir Kotal# fields enclosed by brackets "[]" replaced with your own identifying
16*b2d29daeSVladimir Kotal# information: Portions Copyright [yyyy] [name of copyright owner]
17*b2d29daeSVladimir Kotal#
18*b2d29daeSVladimir Kotal# CDDL HEADER END
19*b2d29daeSVladimir Kotal
20*b2d29daeSVladimir Kotal#
21*b2d29daeSVladimir Kotal# Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
22*b2d29daeSVladimir Kotal#
23*b2d29daeSVladimir Kotal
24*b2d29daeSVladimir Kotalimport os
25*b2d29daeSVladimir Kotalfrom requests import get, ConnectionError
26*b2d29daeSVladimir Kotalimport logging
27*b2d29daeSVladimir Kotalimport multiprocessing
28*b2d29daeSVladimir Kotalfrom pathlib import Path
29*b2d29daeSVladimir Kotalimport shutil
30*b2d29daeSVladimir Kotalimport subprocess
31*b2d29daeSVladimir Kotalimport threading
32*b2d29daeSVladimir Kotalimport time
33*b2d29daeSVladimir Kotal
34*b2d29daeSVladimir Kotalfrom opengrok_tools.utils.log import get_console_logger, \
35*b2d29daeSVladimir Kotal    get_log_level, get_class_basename
36*b2d29daeSVladimir Kotalfrom opengrok_tools.deploy import deploy_war
37*b2d29daeSVladimir Kotalfrom opengrok_tools.utils.indexer import Indexer
38*b2d29daeSVladimir Kotalfrom opengrok_tools.sync import do_sync
39*b2d29daeSVladimir Kotalfrom opengrok_tools.utils.opengrok import list_projects, \
40*b2d29daeSVladimir Kotal    add_project, delete_project, get_configuration
41*b2d29daeSVladimir Kotalfrom opengrok_tools.utils.readconfig import read_config
42*b2d29daeSVladimir Kotalfrom opengrok_tools.utils.exitvals import SUCCESS_EXITVAL
43*b2d29daeSVladimir Kotal
44*b2d29daeSVladimir Kotal
45*b2d29daeSVladimir Kotalfs_root = os.path.abspath('.').split(os.path.sep)[0] + os.path.sep
46*b2d29daeSVladimir Kotalif os.environ.get('OPENGROK_TOMCAT_ROOT'):  # debug only
47*b2d29daeSVladimir Kotal    tomcat_root = os.environ.get('OPENGROK_TOMCAT_ROOT')
48*b2d29daeSVladimir Kotalelse:
49*b2d29daeSVladimir Kotal    tomcat_root = os.path.join(fs_root, "usr", "local", "tomcat")
50*b2d29daeSVladimir Kotal
51*b2d29daeSVladimir Kotalif os.environ.get('OPENGROK_ROOT'):  # debug only
52*b2d29daeSVladimir Kotal    OPENGROK_BASE_DIR = os.environ.get('OPENGROK_ROOT')
53*b2d29daeSVladimir Kotalelse:
54*b2d29daeSVladimir Kotal    OPENGROK_BASE_DIR = os.path.join(fs_root, "opengrok")
55*b2d29daeSVladimir Kotal
56*b2d29daeSVladimir KotalOPENGROK_LIB_DIR = os.path.join(OPENGROK_BASE_DIR, "lib")
57*b2d29daeSVladimir KotalOPENGROK_DATA_ROOT = os.path.join(OPENGROK_BASE_DIR, "data")
58*b2d29daeSVladimir KotalOPENGROK_SRC_ROOT = os.path.join(OPENGROK_BASE_DIR, "src")
59*b2d29daeSVladimir KotalBODY_INCLUDE_FILE = os.path.join(OPENGROK_DATA_ROOT, "body_include")
60*b2d29daeSVladimir KotalOPENGROK_CONFIG_FILE = os.path.join(OPENGROK_BASE_DIR, "etc",
61*b2d29daeSVladimir Kotal                                    "configuration.xml")
62*b2d29daeSVladimir KotalOPENGROK_WEBAPPS_DIR = os.path.join(tomcat_root, "webapps")
63*b2d29daeSVladimir Kotal
64*b2d29daeSVladimir Kotal
65*b2d29daeSVladimir Kotaldef set_url_root(url_root):
66*b2d29daeSVladimir Kotal    """
67*b2d29daeSVladimir Kotal    Set URL root and URI based on input
68*b2d29daeSVladimir Kotal    :param url_root: input
69*b2d29daeSVladimir Kotal    :return: URI and URL root
70*b2d29daeSVladimir Kotal    """
71*b2d29daeSVladimir Kotal    if not url_root:
72*b2d29daeSVladimir Kotal        url_root = '/'
73*b2d29daeSVladimir Kotal
74*b2d29daeSVladimir Kotal    if ' ' in url_root:
75*b2d29daeSVladimir Kotal        logger.warn('Deployment path contains spaces. Deploying to root')
76*b2d29daeSVladimir Kotal        url_root = '/'
77*b2d29daeSVladimir Kotal
78*b2d29daeSVladimir Kotal    # Remove leading and trailing slashes
79*b2d29daeSVladimir Kotal    if url_root.startswith('/'):
80*b2d29daeSVladimir Kotal        url_root = url_root[1:]
81*b2d29daeSVladimir Kotal    if url_root.endswith('/'):
82*b2d29daeSVladimir Kotal        url_root = url_root[:-1]
83*b2d29daeSVladimir Kotal
84*b2d29daeSVladimir Kotal    uri = "http://localhost:8080/" + url_root
85*b2d29daeSVladimir Kotal    #
86*b2d29daeSVladimir Kotal    # Make sure URI ends with slash. This is important for the various API
87*b2d29daeSVladimir Kotal    # calls, notably for those that check the HTTP error code.
88*b2d29daeSVladimir Kotal    # Normally accessing the URI without the terminating slash results in
89*b2d29daeSVladimir Kotal    # HTTP redirect (code 302) instead of success (200).
90*b2d29daeSVladimir Kotal    #
91*b2d29daeSVladimir Kotal    if not uri.endswith('/'):
92*b2d29daeSVladimir Kotal        uri = uri + '/'
93*b2d29daeSVladimir Kotal
94*b2d29daeSVladimir Kotal    return uri, url_root
95*b2d29daeSVladimir Kotal
96*b2d29daeSVladimir Kotal
97*b2d29daeSVladimir Kotaldef get_war_name(url_root):
98*b2d29daeSVladimir Kotal    if len(url_root) == 0:
99*b2d29daeSVladimir Kotal        return "ROOT.war"
100*b2d29daeSVladimir Kotal    else:
101*b2d29daeSVladimir Kotal        return url_root + ".war"
102*b2d29daeSVladimir Kotal
103*b2d29daeSVladimir Kotal
104*b2d29daeSVladimir Kotaldef deploy(url_root):
105*b2d29daeSVladimir Kotal    """
106*b2d29daeSVladimir Kotal    Deploy the web application
107*b2d29daeSVladimir Kotal    :param url_root: web app URL root
108*b2d29daeSVladimir Kotal    """
109*b2d29daeSVladimir Kotal
110*b2d29daeSVladimir Kotal    logger.info('Deploying web application')
111*b2d29daeSVladimir Kotal    webapps_dir = os.path.join(tomcat_root, 'webapps')
112*b2d29daeSVladimir Kotal    if not os.path.isdir(webapps_dir):
113*b2d29daeSVladimir Kotal        raise Exception("{} is not a directory".format(webapps_dir))
114*b2d29daeSVladimir Kotal
115*b2d29daeSVladimir Kotal    for item in os.listdir(webapps_dir):
116*b2d29daeSVladimir Kotal        subdir = os.path.join(webapps_dir, item)
117*b2d29daeSVladimir Kotal        if os.path.isdir(subdir):
118*b2d29daeSVladimir Kotal            logger.debug("Removing '{}' directory recursively".format(subdir))
119*b2d29daeSVladimir Kotal            shutil.rmtree(subdir)
120*b2d29daeSVladimir Kotal
121*b2d29daeSVladimir Kotal    deploy_war(logger, os.path.join(OPENGROK_LIB_DIR, "source.war"),
122*b2d29daeSVladimir Kotal               os.path.join(OPENGROK_WEBAPPS_DIR, get_war_name(url_root)),
123*b2d29daeSVladimir Kotal               OPENGROK_CONFIG_FILE, None)
124*b2d29daeSVladimir Kotal
125*b2d29daeSVladimir Kotal
126*b2d29daeSVladimir Kotaldef setup_redirect_source(url_root):
127*b2d29daeSVladimir Kotal    """
128*b2d29daeSVladimir Kotal    Set up redirect from /source
129*b2d29daeSVladimir Kotal    """
130*b2d29daeSVladimir Kotal    logger.debug("Setting up redirect from /source to '{}'".format(url_root))
131*b2d29daeSVladimir Kotal    source_dir = os.path.join(OPENGROK_WEBAPPS_DIR, "source")
132*b2d29daeSVladimir Kotal    if not os.path.isdir(source_dir):
133*b2d29daeSVladimir Kotal        os.makedirs(source_dir)
134*b2d29daeSVladimir Kotal
135*b2d29daeSVladimir Kotal    with open(os.path.join(source_dir, "index.jsp"), "w+") as index:
136*b2d29daeSVladimir Kotal        index.write("<% response.sendRedirect(\"/{}\"); %>".format(url_root))
137*b2d29daeSVladimir Kotal
138*b2d29daeSVladimir Kotal
139*b2d29daeSVladimir Kotaldef wait_for_tomcat(uri):
140*b2d29daeSVladimir Kotal    """
141*b2d29daeSVladimir Kotal    Active/busy waiting for Tomcat to come up.
142*b2d29daeSVladimir Kotal    Currently there is no upper time bound.
143*b2d29daeSVladimir Kotal    """
144*b2d29daeSVladimir Kotal    logger.info("Waiting for Tomcat to start")
145*b2d29daeSVladimir Kotal
146*b2d29daeSVladimir Kotal    while True:
147*b2d29daeSVladimir Kotal        try:
148*b2d29daeSVladimir Kotal            r = get(uri)
149*b2d29daeSVladimir Kotal            status = r.status_code
150*b2d29daeSVladimir Kotal        except ConnectionError:
151*b2d29daeSVladimir Kotal            status = 0
152*b2d29daeSVladimir Kotal
153*b2d29daeSVladimir Kotal        if status != 200:
154*b2d29daeSVladimir Kotal            logger.debug("Got status {} for {}, sleeping for 1 second".
155*b2d29daeSVladimir Kotal                         format(status, uri))
156*b2d29daeSVladimir Kotal            time.sleep(1)
157*b2d29daeSVladimir Kotal        else:
158*b2d29daeSVladimir Kotal            break
159*b2d29daeSVladimir Kotal
160*b2d29daeSVladimir Kotal    logger.info("Tomcat is ready")
161*b2d29daeSVladimir Kotal
162*b2d29daeSVladimir Kotal
163*b2d29daeSVladimir Kotaldef refresh_projects(uri):
164*b2d29daeSVladimir Kotal    """
165*b2d29daeSVladimir Kotal    Ensure each immediate source root subdirectory is a project.
166*b2d29daeSVladimir Kotal    """
167*b2d29daeSVladimir Kotal    webapp_projects = list_projects(logger, uri)
168*b2d29daeSVladimir Kotal    logger.debug('Projects from the web app: {}'.format(webapp_projects))
169*b2d29daeSVladimir Kotal    src_root = OPENGROK_SRC_ROOT
170*b2d29daeSVladimir Kotal
171*b2d29daeSVladimir Kotal    # Add projects.
172*b2d29daeSVladimir Kotal    for item in os.listdir(src_root):
173*b2d29daeSVladimir Kotal        logger.debug('Got item {}'.format(item))
174*b2d29daeSVladimir Kotal        if os.path.isdir(os.path.join(src_root, item)):
175*b2d29daeSVladimir Kotal            if item not in webapp_projects:
176*b2d29daeSVladimir Kotal                logger.info("Adding project {}".format(item))
177*b2d29daeSVladimir Kotal                add_project(logger, item, uri)
178*b2d29daeSVladimir Kotal
179*b2d29daeSVladimir Kotal    # Remove projects
180*b2d29daeSVladimir Kotal    for item in webapp_projects:
181*b2d29daeSVladimir Kotal        if not os.path.isdir(os.path.join(src_root, item)):
182*b2d29daeSVladimir Kotal            logger.info("Deleting project {}".format(item))
183*b2d29daeSVladimir Kotal            delete_project(logger, item, uri)
184*b2d29daeSVladimir Kotal
185*b2d29daeSVladimir Kotal
186*b2d29daeSVladimir Kotaldef save_config(uri, config_path):
187*b2d29daeSVladimir Kotal    """
188*b2d29daeSVladimir Kotal    Retrieve configuration from the web app and write it to file.
189*b2d29daeSVladimir Kotal    :param uri: web app URI
190*b2d29daeSVladimir Kotal    :param config_path: file path
191*b2d29daeSVladimir Kotal    """
192*b2d29daeSVladimir Kotal
193*b2d29daeSVladimir Kotal    logger.info('Saving configuration to {}'.format(config_path))
194*b2d29daeSVladimir Kotal    config = get_configuration(logger, uri)
195*b2d29daeSVladimir Kotal    with open(config_path, "w+") as config_file:
196*b2d29daeSVladimir Kotal        config_file.write(config)
197*b2d29daeSVladimir Kotal
198*b2d29daeSVladimir Kotal
199*b2d29daeSVladimir Kotaldef merge_commands_env(commands, env):
200*b2d29daeSVladimir Kotal    """
201*b2d29daeSVladimir Kotal    Merge environment into command structure. If any of the commands has
202*b2d29daeSVladimir Kotal    an environment already set, the env is merged in.
203*b2d29daeSVladimir Kotal    :param commands: commands structure
204*b2d29daeSVladimir Kotal    :param env: environment dictionary
205*b2d29daeSVladimir Kotal    :return: updated commands structure
206*b2d29daeSVladimir Kotal    """
207*b2d29daeSVladimir Kotal    for cmd in commands:
208*b2d29daeSVladimir Kotal        cmd_env = cmd.get('env')
209*b2d29daeSVladimir Kotal        if cmd_env:
210*b2d29daeSVladimir Kotal            cmd.env.update(env)
211*b2d29daeSVladimir Kotal        else:
212*b2d29daeSVladimir Kotal            cmd['env'] = env
213*b2d29daeSVladimir Kotal
214*b2d29daeSVladimir Kotal    return commands
215*b2d29daeSVladimir Kotal
216*b2d29daeSVladimir Kotal
217*b2d29daeSVladimir Kotaldef syncer(loglevel, uri, config_path, reindex, numworkers, env):
218*b2d29daeSVladimir Kotal    """
219*b2d29daeSVladimir Kotal    Wrapper for running opengrok-sync.
220*b2d29daeSVladimir Kotal    To be run in a thread/process in the background.
221*b2d29daeSVladimir Kotal    """
222*b2d29daeSVladimir Kotal
223*b2d29daeSVladimir Kotal    wait_for_tomcat(uri)
224*b2d29daeSVladimir Kotal
225*b2d29daeSVladimir Kotal    while True:
226*b2d29daeSVladimir Kotal        refresh_projects(uri)
227*b2d29daeSVladimir Kotal
228*b2d29daeSVladimir Kotal        if os.environ.get('OPENGROK_SYNC_YML'):  # debug only
229*b2d29daeSVladimir Kotal            config_file = os.environ.get('OPENGROK_SYNC_YML')
230*b2d29daeSVladimir Kotal        else:
231*b2d29daeSVladimir Kotal            config_file = os.path.join(fs_root, 'scripts', 'sync.yml')
232*b2d29daeSVladimir Kotal        config = read_config(logger, config_file)
233*b2d29daeSVladimir Kotal        if config is None:
234*b2d29daeSVladimir Kotal            logger.error("Cannot read config file from {}".format(config_file))
235*b2d29daeSVladimir Kotal            raise Exception("no sync config")
236*b2d29daeSVladimir Kotal
237*b2d29daeSVladimir Kotal        projects = list_projects(logger, uri)
238*b2d29daeSVladimir Kotal        #
239*b2d29daeSVladimir Kotal        # The driveon=True is needed for the initial indexing of newly
240*b2d29daeSVladimir Kotal        # added project, otherwise the incoming check in the opengrok-mirror
241*b2d29daeSVladimir Kotal        # would short circuit it.
242*b2d29daeSVladimir Kotal        #
243*b2d29daeSVladimir Kotal        if env:
244*b2d29daeSVladimir Kotal            logger.info('Merging commands with environment')
245*b2d29daeSVladimir Kotal            commands = merge_commands_env(config["commands"], env)
246*b2d29daeSVladimir Kotal            logger.debug(config['commands'])
247*b2d29daeSVladimir Kotal        else:
248*b2d29daeSVladimir Kotal            commands = config["commands"]
249*b2d29daeSVladimir Kotal
250*b2d29daeSVladimir Kotal        logger.info("Sync starting")
251*b2d29daeSVladimir Kotal        do_sync(loglevel, commands, config.get('cleanup'),
252*b2d29daeSVladimir Kotal                projects, config.get("ignore_errors"), uri,
253*b2d29daeSVladimir Kotal                numworkers, driveon=True, logger=logger, print_output=True)
254*b2d29daeSVladimir Kotal        logger.info("Sync done")
255*b2d29daeSVladimir Kotal
256*b2d29daeSVladimir Kotal        # Workaround for https://github.com/oracle/opengrok/issues/1670
257*b2d29daeSVladimir Kotal        Path(os.path.join(OPENGROK_DATA_ROOT, 'timestamp')).touch()
258*b2d29daeSVladimir Kotal
259*b2d29daeSVladimir Kotal        save_config(uri, config_path)
260*b2d29daeSVladimir Kotal
261*b2d29daeSVladimir Kotal        sleep_seconds = int(reindex) * 60
262*b2d29daeSVladimir Kotal        logger.info("Sleeping for {} seconds".format(sleep_seconds))
263*b2d29daeSVladimir Kotal        time.sleep(sleep_seconds)
264*b2d29daeSVladimir Kotal
265*b2d29daeSVladimir Kotal
266*b2d29daeSVladimir Kotaldef create_bare_config():
267*b2d29daeSVladimir Kotal    """
268*b2d29daeSVladimir Kotal    Create bare configuration file with a few basic settings.
269*b2d29daeSVladimir Kotal    """
270*b2d29daeSVladimir Kotal
271*b2d29daeSVladimir Kotal    logger.info('Creating bare configuration in {}'.
272*b2d29daeSVladimir Kotal                format(OPENGROK_CONFIG_FILE))
273*b2d29daeSVladimir Kotal    indexer = Indexer(['-s', OPENGROK_SRC_ROOT,
274*b2d29daeSVladimir Kotal                       '-d', OPENGROK_DATA_ROOT,
275*b2d29daeSVladimir Kotal                       '-c', '/usr/local/bin/ctags',
276*b2d29daeSVladimir Kotal                       '--remote', 'on',
277*b2d29daeSVladimir Kotal                       '-P', '-H',
278*b2d29daeSVladimir Kotal                       '-W', OPENGROK_CONFIG_FILE,
279*b2d29daeSVladimir Kotal                       '--noIndex'],
280*b2d29daeSVladimir Kotal                      jar=os.path.join(OPENGROK_LIB_DIR,
281*b2d29daeSVladimir Kotal                                       'opengrok.jar'),
282*b2d29daeSVladimir Kotal                      logger=logger, doprint=True)
283*b2d29daeSVladimir Kotal    indexer.execute()
284*b2d29daeSVladimir Kotal    ret = indexer.getretcode()
285*b2d29daeSVladimir Kotal    if ret != SUCCESS_EXITVAL:
286*b2d29daeSVladimir Kotal        logger.error('Command returned {}'.format(ret))
287*b2d29daeSVladimir Kotal        logger.error(indexer.geterroutput())
288*b2d29daeSVladimir Kotal        raise Exception("Failed to create bare configuration")
289*b2d29daeSVladimir Kotal
290*b2d29daeSVladimir Kotal
291*b2d29daeSVladimir Kotalif __name__ == "__main__":
292*b2d29daeSVladimir Kotal    log_level = os.environ.get('OPENGROK_LOG_LEVEL')
293*b2d29daeSVladimir Kotal    if log_level:
294*b2d29daeSVladimir Kotal        log_level = get_log_level(log_level)
295*b2d29daeSVladimir Kotal    else:
296*b2d29daeSVladimir Kotal        log_level = logging.INFO
297*b2d29daeSVladimir Kotal
298*b2d29daeSVladimir Kotal    logger = get_console_logger(get_class_basename(), log_level)
299*b2d29daeSVladimir Kotal
300*b2d29daeSVladimir Kotal    URI, URL_ROOT = set_url_root(os.environ.get('URL_ROOT'))
301*b2d29daeSVladimir Kotal    logger.debug("URL_ROOT = {}".format(URL_ROOT))
302*b2d29daeSVladimir Kotal    logger.debug("URI = {}".format(URI))
303*b2d29daeSVladimir Kotal
304*b2d29daeSVladimir Kotal    # default period for reindexing (in minutes)
305*b2d29daeSVladimir Kotal    reindex_env = os.environ.get('REINDEX')
306*b2d29daeSVladimir Kotal    if reindex_env:
307*b2d29daeSVladimir Kotal        reindex_min = reindex_env
308*b2d29daeSVladimir Kotal    else:
309*b2d29daeSVladimir Kotal        reindex_min = 10
310*b2d29daeSVladimir Kotal    logger.debug("reindex period = {} minutes".format(reindex_min))
311*b2d29daeSVladimir Kotal
312*b2d29daeSVladimir Kotal    # Note that deploy is done before Tomcat is started.
313*b2d29daeSVladimir Kotal    deploy(URL_ROOT)
314*b2d29daeSVladimir Kotal
315*b2d29daeSVladimir Kotal    if URL_ROOT != '/source':
316*b2d29daeSVladimir Kotal        setup_redirect_source(URL_ROOT)
317*b2d29daeSVladimir Kotal
318*b2d29daeSVladimir Kotal    # TODO verify these are passed on
319*b2d29daeSVladimir Kotal    env = {}
320*b2d29daeSVladimir Kotal    if os.environ.get('INDEXER_OPT'):
321*b2d29daeSVladimir Kotal        env['OPENGROK_INDEXER_OPTIONAL_ARGS'] = \
322*b2d29daeSVladimir Kotal            os.environ.get('INDEXER_OPT')
323*b2d29daeSVladimir Kotal    if os.environ.get('NOMIRROR'):
324*b2d29daeSVladimir Kotal        env['OPENGROK_NO_MIRROR'] = os.environ.get('NOMIRROR')
325*b2d29daeSVladimir Kotal    logger.debug('Extra environment: {}'.format(env))
326*b2d29daeSVladimir Kotal
327*b2d29daeSVladimir Kotal    #
328*b2d29daeSVladimir Kotal    # Create empty configuration to avoid the non existent file exception
329*b2d29daeSVladimir Kotal    # in the web app during the first web app startup.
330*b2d29daeSVladimir Kotal    #
331*b2d29daeSVladimir Kotal    if not os.path.exists(OPENGROK_CONFIG_FILE) or \
332*b2d29daeSVladimir Kotal            os.path.getsize(OPENGROK_CONFIG_FILE) == 0:
333*b2d29daeSVladimir Kotal        create_bare_config()
334*b2d29daeSVladimir Kotal
335*b2d29daeSVladimir Kotal    if os.environ.get('WORKERS'):
336*b2d29daeSVladimir Kotal        num_workers = os.environ.get('WORKERS')
337*b2d29daeSVladimir Kotal    else:
338*b2d29daeSVladimir Kotal        num_workers = multiprocessing.cpu_count()
339*b2d29daeSVladimir Kotal    logger.info('Number of sync workers: {}'.format(num_workers))
340*b2d29daeSVladimir Kotal
341*b2d29daeSVladimir Kotal    logger.debug("Starting sync thread")
342*b2d29daeSVladimir Kotal    t = threading.Thread(target=syncer, name="Sync thread",
343*b2d29daeSVladimir Kotal                         args=(log_level, URI, OPENGROK_CONFIG_FILE,
344*b2d29daeSVladimir Kotal                               reindex_min, num_workers, env))
345*b2d29daeSVladimir Kotal    t.start()
346*b2d29daeSVladimir Kotal
347*b2d29daeSVladimir Kotal    # Start Tomcat last. It will be the foreground process.
348*b2d29daeSVladimir Kotal    logger.info("Starting Tomcat")
349*b2d29daeSVladimir Kotal    subprocess.run([os.path.join(tomcat_root, 'bin', 'catalina.sh'), 'run'])
350