diff mbox series

[layerindex-web,1/4] layerindex: Add app dir support for configurable container

Message ID 20260402195112.1369295-2-sandeep.gundlupet-raju@amd.com
State New
Headers show
Series Add app dir support | expand

Commit Message

Sandeep Gundlupet Raju April 2, 2026, 7:51 p.m. UTC
Add a new --app-dir command-line argument (default: /opt) to allow the
base directory used inside the containers to be configurable instead of
being hardcoded to /opt.

Changes:
- dockersetup.py: add --app-dir argument; write APP_DIR to .env file via
  new write_env_file() so docker-compose passes it as a build arg; update
  edit_dockercompose() to expand build: block and inject APP_DIR build arg
  reference, and rewrite volume mounts and celery --workdir accordingly;
  propagate app_dir through setup_https(), check_connectivity(), and all
  direct docker-compose run call sites
- Dockerfile: add ARG APP_DIR=/opt + ENV APP_DIR=${APP_DIR}; replace all
  hardcoded /opt paths with ${APP_DIR} in COPY, RUN and CMD instructions
- docker/migrate.sh: replace /opt with ${APP_DIR:-/opt}
- docker/refreshlayers.sh: replace /opt with ${APP_DIR:-/opt}
- docker/updatelayers.sh: replace /opt with ${APP_DIR:-/opt}

AI-Generated: GitHub Copilot (Claude Sonnet 4.6)

Signed-off-by: Sandeep Gundlupet Raju <sandeep.gundlupet-raju@amd.com>
---
 Dockerfile              | 24 +++++++------
 docker/migrate.sh       |  2 +-
 docker/refreshlayers.sh |  2 +-
 docker/updatelayers.sh  |  2 +-
 dockersetup.py          | 79 +++++++++++++++++++++++++++++++++--------
 5 files changed, 80 insertions(+), 29 deletions(-)
diff mbox series

Patch

diff --git a/Dockerfile b/Dockerfile
index 72f57d2..8e6e508 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,6 +3,8 @@ 
 FROM ubuntu:jammy
 LABEL maintainer="Michael Halstead <mhalstead@linuxfoundation.org>"
 
+ARG APP_DIR=/opt
+ENV APP_DIR=${APP_DIR}
 ENV PYTHONUNBUFFERED=1 \
     LANGUAGE=en_US \
     LANG=en_US.UTF-8 \
@@ -55,22 +57,22 @@  RUN DEBIAN_FRONTEND=noninteractive apt-get update \
 	&& rm -rf /var/lib/apt/lists/* \
 	&& apt-get clean
 
-COPY . /opt/layerindex
-RUN rm -rf /opt/layerindex/docker
-COPY docker/settings.py /opt/layerindex/settings.py
-COPY docker/refreshlayers.sh /opt/refreshlayers.sh
-COPY docker/updatelayers.sh /opt/updatelayers.sh
-COPY docker/migrate.sh /opt/migrate.sh
-COPY docker/connectivity_check.sh /opt/connectivity_check.sh
+COPY . ${APP_DIR}/layerindex
+RUN rm -rf ${APP_DIR}/layerindex/docker
+COPY docker/settings.py ${APP_DIR}/layerindex/settings.py
+COPY docker/refreshlayers.sh ${APP_DIR}/refreshlayers.sh
+COPY docker/updatelayers.sh ${APP_DIR}/updatelayers.sh
+COPY docker/migrate.sh ${APP_DIR}/migrate.sh
+COPY docker/connectivity_check.sh ${APP_DIR}/connectivity_check.sh
 
-RUN mkdir /opt/workdir \
+RUN mkdir ${APP_DIR}/workdir \
 	&& adduser --system --uid=500 layers \
-	&& chown -R layers /opt/workdir
+	&& chown -R layers ${APP_DIR}/workdir
 USER layers
 
 # Always copy in .gitconfig and proxy helper script (they need editing to be active)
 COPY docker/.gitconfig /home/layers/.gitconfig
-COPY docker/git-proxy /opt/bin/git-proxy
+COPY docker/git-proxy ${APP_DIR}/bin/git-proxy
 
 # Start Gunicorn
-CMD ["/usr/local/bin/gunicorn", "wsgi:application", "--workers=4", "--bind=:5000", "--timeout=60", "--log-level=debug", "--chdir=/opt/layerindex"]
+CMD ["/bin/sh", "-c", "/usr/local/bin/gunicorn wsgi:application --workers=4 --bind=:5000 --timeout=60 --log-level=debug --chdir=${APP_DIR}/layerindex"]
diff --git a/docker/migrate.sh b/docker/migrate.sh
index ca15368..bb9f646 100755
--- a/docker/migrate.sh
+++ b/docker/migrate.sh
@@ -1,2 +1,2 @@ 
 #!/bin/bash
-python3 /opt/layerindex/manage.py migrate "$@"
+python3 ${APP_DIR:-/opt}/layerindex/manage.py migrate "$@"
diff --git a/docker/refreshlayers.sh b/docker/refreshlayers.sh
index 0c550bd..5adaf86 100755
--- a/docker/refreshlayers.sh
+++ b/docker/refreshlayers.sh
@@ -1,3 +1,3 @@ 
 #!/bin/bash
-update=/opt/layerindex/layerindex/update.py
+update=${APP_DIR:-/opt}/layerindex/layerindex/update.py
 $update -q -r
diff --git a/docker/updatelayers.sh b/docker/updatelayers.sh
index 21640ba..6f0e8f0 100755
--- a/docker/updatelayers.sh
+++ b/docker/updatelayers.sh
@@ -1,3 +1,3 @@ 
 #!/bin/bash
-update=/opt/layerindex/layerindex/update.py
+update=${APP_DIR:-/opt}/layerindex/layerindex/update.py
 $update -q
diff --git a/dockersetup.py b/dockersetup.py
index bc7478c..920b2c8 100755
--- a/dockersetup.py
+++ b/dockersetup.py
@@ -70,6 +70,7 @@  def get_args():
     parser.add_argument('--no-migrate', action="store_true", default=False, help='Skip running database migrations')
     parser.add_argument('--no-admin-user', action="store_true", default=False, help='Skip adding admin user')
     parser.add_argument('--no-connectivity', action="store_true", default=False, help='Skip checking external network connectivity')
+    parser.add_argument('--app-dir', type=str, help='Base directory inside the container for application files. Default is %(default)s', required=False, default='/opt')
 
     args = parser.parse_args()
 
@@ -253,14 +254,16 @@  def yaml_comment(line):
 
 
 # Add hostname, secret key, db info, and email host in docker-compose.yml
-def edit_dockercompose(hostname, dbpassword, dbapassword, secretkey, rmqpassword, portmapping, letsencrypt, email_host, email_port, email_user, email_password, email_ssl, email_tls):
+def edit_dockercompose(hostname, dbpassword, dbapassword, secretkey, rmqpassword, portmapping, letsencrypt, email_host, email_port, email_user, email_password, email_ssl, email_tls, app_dir='/opt'):
+
+    in_layersapp_build = False
 
     def adjust_cert_mount_line(ln):
         linesplit = ln.split(':')
         if letsencrypt:
             linesplit[1] = '/etc/letsencrypt'
         else:
-            linesplit[1] = '/opt/cert'
+            linesplit[1] = app_dir + '/cert'
         # This allows us to handle if there is a ":ro" or similar on the end
         return ':'.join(linesplit)
 
@@ -305,6 +308,33 @@  def edit_dockercompose(hostname, dbpassword, dbapassword, secretkey, rmqpassword
                 newlines.append(ucline + '\n')
             else:
                 newlines.append(yaml_comment(line) + '\n')
+        elif re.match(r'^\s+build:\s+\.$', line):
+            # Convert inline 'build: .' to block form so we can pass APP_DIR as a build arg
+            indent = line[:len(line) - len(line.lstrip())]
+            newlines.append(indent + 'build:\n')
+            newlines.append(indent + '  context: .\n')
+            newlines.append(indent + '  args:\n')
+            newlines.append(indent + '    - APP_DIR=${APP_DIR}\n')
+            in_layersapp_build = True
+            continue
+        elif '- APP_DIR=' in line:
+            # Keep APP_DIR referencing the .env file (do not embed value here)
+            format = line[:line.find('- APP_DIR=')].replace('#', '')
+            newlines.append(format + '- APP_DIR=${APP_DIR}\n')
+            continue
+        elif re.match(r'^\s+context:\s+\.$', line) and in_layersapp_build:
+            in_layersapp_build = False
+            newlines.append(line + '\n')
+            continue
+        elif ':/opt/workdir' in line:
+            newlines.append(line.replace(':/opt/workdir', ':' + app_dir + '/workdir') + '\n')
+            continue
+        elif ':/opt/layerindex-task-logs' in line:
+            newlines.append(line.replace(':/opt/layerindex-task-logs', ':' + app_dir + '/layerindex-task-logs') + '\n')
+            continue
+        elif '--workdir=/opt/layerindex' in line:
+            newlines.append(line.replace('--workdir=/opt/layerindex', '--workdir=' + app_dir + '/layerindex') + '\n')
+            continue
         elif "layersweb:" in line:
             in_layersweb = True
             newlines.append(line + "\n")
@@ -474,9 +504,9 @@  def edit_dockerfile_web(hostname, no_https):
     writefile("Dockerfile.web", ''.join(newlines))
 
 
-def setup_https(hostname, http_port, https_port, letsencrypt, letsencrypt_production, cert, cert_key, emailaddr):
+def setup_https(hostname, http_port, https_port, letsencrypt, letsencrypt_production, cert, cert_key, emailaddr, app_dir='/opt'):
     local_cert_dir = os.path.abspath('docker/certs')
-    container_cert_dir = '/opt/cert'
+    container_cert_dir = app_dir + '/cert'
     if letsencrypt:
         # Create dummy cert
         container_cert_dir = '/etc/letsencrypt'
@@ -578,8 +608,26 @@  def edit_options_file(project_name):
         f.write('project_name=%s\n' % project_name)
 
 
-def check_connectivity():
-    return_code = subprocess.call(['docker-compose', 'run', '--rm', 'layersapp', '/opt/connectivity_check.sh'], shell=False)
+def write_env_file(app_dir):
+    """Write APP_DIR to .env so docker-compose passes it as a build arg."""
+    env_vars = {}
+    try:
+        with open('.env', 'r') as f:
+            for line in f:
+                line = line.strip()
+                if '=' in line and not line.startswith('#'):
+                    k, v = line.split('=', 1)
+                    env_vars[k.strip()] = v.strip()
+    except FileNotFoundError:
+        pass
+    env_vars['APP_DIR'] = app_dir
+    with open('.env', 'w') as f:
+        for k, v in env_vars.items():
+            f.write('%s=%s\n' % (k, v))
+
+
+def check_connectivity(app_dir='/opt'):
+    return_code = subprocess.call(['docker-compose', 'run', '--rm', 'layersapp', app_dir + '/connectivity_check.sh'], shell=False)
     if return_code != 0:
         print("Connectivity check failed - if you are behind a proxy, please check that you have correctly specified the proxy settings on the command line (see --help for details)")
         sys.exit(1)
@@ -741,15 +789,16 @@  if args.uninstall:
 if args.update:
     args.no_https = read_dockerfile_web()
     if not args.no_https:
-        container_cert_dir = '/opt/cert'
+        container_cert_dir = args.app_dir + '/cert'
         args.hostname, https_port, certdir, certfile, keyfile = read_nginx_ssl_conf(container_cert_dir)
         edit_nginx_ssl_conf(args.hostname, https_port, certdir, certfile, keyfile)
 else:
     # Always edit these in case we switch from proxy to no proxy
     edit_gitproxy(socks_proxy_host, socks_proxy_port, args.no_proxy)
     edit_dockerfile(args.http_proxy, args.https_proxy, args.no_proxy)
+    write_env_file(args.app_dir)
 
-    edit_dockercompose(args.hostname, dbpassword, dbapassword, secretkey, rmqpassword, args.portmapping, args.letsencrypt, email_host, email_port, args.email_user, args.email_password, args.email_ssl, args.email_tls)
+    edit_dockercompose(args.hostname, dbpassword, dbapassword, secretkey, rmqpassword, args.portmapping, args.letsencrypt, email_host, email_port, args.email_user, args.email_password, args.email_ssl, args.email_tls, args.app_dir)
 
     edit_dockerfile_web(args.hostname, args.no_https)
 
@@ -758,7 +807,7 @@  else:
     edit_options_file(args.project_name)
 
     if not args.no_https:
-        setup_https(args.hostname, http_port, https_port, args.letsencrypt, args.letsencrypt_production, args.cert, args.cert_key, emailaddr)
+        setup_https(args.hostname, http_port, https_port, args.letsencrypt, args.letsencrypt_production, args.cert, args.cert_key, emailaddr, args.app_dir)
 
 ## Start up containers
 return_code = subprocess.call(['docker-compose', 'up', '-d', '--build'], shell=False)
@@ -768,7 +817,7 @@  if return_code != 0:
 
 if not (args.update or args.no_connectivity):
     ## Run connectivity check
-    check_connectivity()
+    check_connectivity(args.app_dir)
 
 # Get real project name (if only there were a reasonable way to do this... ugh)
 real_project_name = ''
@@ -830,7 +879,7 @@  if not args.no_migrate:
     env = os.environ.copy()
     env['DATABASE_USER'] = 'root'
     env['DATABASE_PASSWORD'] = dbapassword
-    return_code = subprocess.call(['docker-compose', 'run', '--rm', '-e', 'DATABASE_USER', '-e', 'DATABASE_PASSWORD', 'layersapp', '/opt/migrate.sh'], shell=False, env=env)
+    return_code = subprocess.call(['docker-compose', 'run', '--rm', '-e', 'DATABASE_USER', '-e', 'DATABASE_PASSWORD', 'layersapp', args.app_dir + '/migrate.sh'], shell=False, env=env)
     if return_code != 0:
         print("Applying migrations failed")
         sys.exit(1)
@@ -864,13 +913,13 @@  if not args.update:
                 break
     for volume in volumes:
         volname = '%s_%s' % (real_project_name, volume)
-        return_code = subprocess.call(['docker', 'run', '--rm', '-v', '%s:/opt/mount' % volname, 'debian:stretch', 'chown', '500', '/opt/mount'], shell=False)
+        return_code = subprocess.call(['docker', 'run', '--rm', '-v', '%s:%s/mount' % (volname, args.app_dir), 'debian:stretch', 'chown', '500', args.app_dir + '/mount'], shell=False)
         if return_code != 0:
             print("Setting volume permissions for volume %s failed" % volume)
             sys.exit(1)
 
 ## Generate static assets. Run this command again to regenerate at any time (when static assets in the code are updated)
-return_code = subprocess.call("docker-compose run --rm -e STATIC_ROOT=/usr/share/nginx/html -v %s_layersstatic:/usr/share/nginx/html layersapp /opt/layerindex/manage.py collectstatic --noinput" % quote(real_project_name), shell = True)
+return_code = subprocess.call("docker-compose run --rm -e STATIC_ROOT=/usr/share/nginx/html -v %s_layersstatic:/usr/share/nginx/html layersapp %s/layerindex/manage.py collectstatic --noinput" % (quote(real_project_name), args.app_dir), shell = True)
 if return_code != 0:
     print("Collecting static files failed")
     sys.exit(1)
@@ -891,12 +940,12 @@  else:
 if not args.update:
     if not args.databasefile:
         ## Set site name
-        return_code = subprocess.call(['docker-compose', 'run', '--rm', 'layersapp', '/opt/layerindex/layerindex/tools/site_name.py', host, 'OpenEmbedded Layer Index'], shell=False)
+        return_code = subprocess.call(['docker-compose', 'run', '--rm', 'layersapp', args.app_dir + '/layerindex/layerindex/tools/site_name.py', host, 'OpenEmbedded Layer Index'], shell=False)
 
     if not args.no_admin_user:
         ## For a fresh database, create an admin account
         print("Creating database superuser. Input user name and password when prompted.")
-        return_code = subprocess.call(['docker-compose', 'run', '--rm', 'layersapp', '/opt/layerindex/manage.py', 'createsuperuser', '--email', emailaddr], shell=False)
+        return_code = subprocess.call(['docker-compose', 'run', '--rm', 'layersapp', args.app_dir + '/layerindex/manage.py', 'createsuperuser', '--email', emailaddr], shell=False)
         if return_code != 0:
             print("Creating superuser failed")
             sys.exit(1)