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