├── .gitignore ├── 10.0-nightly ├── Dockerfile ├── entrypoint.py ├── find_modules.py ├── odoo.conf ├── prepare_project.py └── sudo-entrypoint.py ├── 10.0 ├── Dockerfile ├── entrypoint.py ├── find_modules.py ├── odoo.conf ├── prepare_project.py └── sudo-entrypoint.py ├── 11.0-nightly ├── Dockerfile ├── entrypoint.py ├── find_modules.py ├── odoo.conf ├── prepare_project.py └── sudo-entrypoint.py ├── 11.0 ├── Dockerfile ├── entrypoint.py ├── find_modules.py ├── odoo.conf ├── prepare_project.py └── sudo-entrypoint.py ├── 12.0-nightly ├── Dockerfile ├── entrypoint.py ├── find_modules.py ├── odoo.conf ├── prepare_project.py └── sudo-entrypoint.py ├── 12.0 ├── Dockerfile ├── entrypoint.py ├── find_modules.py ├── odoo.conf ├── prepare_project.py └── sudo-entrypoint.py ├── 13.0-nightly ├── Dockerfile ├── entrypoint.py ├── find_modules.py ├── odoo.conf ├── prepare_project.py └── sudo-entrypoint.py ├── 13.0 ├── Dockerfile ├── entrypoint.py ├── find_modules.py ├── odoo.conf ├── prepare_project.py └── sudo-entrypoint.py ├── 14.0-nightly ├── Dockerfile ├── entrypoint.py ├── find_modules.py ├── odoo.conf ├── prepare_project.py └── sudo-entrypoint.py ├── 14.0-tiny ├── Dockerfile ├── entrypoint.py ├── find_modules.py ├── odoo.conf ├── prepare_project.py └── sudo-entrypoint.py ├── 14.0 ├── Dockerfile ├── entrypoint.py ├── find_modules.py ├── odoo.conf ├── prepare_project.py └── sudo-entrypoint.py ├── 14.3 ├── Dockerfile ├── entrypoint.py ├── find_modules.py ├── odoo.conf ├── prepare_project.py └── sudo-entrypoint.py ├── 8.0 ├── Dockerfile ├── entrypoint.py ├── find_modules.py ├── odoo.conf ├── prepare_project.py └── sudo-entrypoint.py ├── 9.0 ├── Dockerfile ├── entrypoint.py ├── find_modules.py ├── odoo.conf ├── prepare_project.py └── sudo-entrypoint.py ├── LICENSE ├── README.md ├── assets ├── entrypoint.py ├── odoo.conf ├── platform.sh └── sudo-entrypoint.py ├── build.py ├── deploy.py ├── deploy.sh ├── requirements.txt ├── templates ├── Dockerfile.template ├── Dockerfile27.template └── DockerfileTiny.template └── versions.toml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | test_async.py 3 | -------------------------------------------------------------------------------- /10.0-nightly/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | LABEL maintainer="Archeti " 3 | 4 | # Generate locale C.UTF-8 for postgres and general locale data 5 | ENV LANG C.UTF-8 6 | 7 | # Install some dependencies python3.7 8 | RUN set -x; \ 9 | apt-get update \ 10 | && apt-get install -y --no-install-recommends \ 11 | python-wheel \ 12 | python-setuptools \ 13 | python-pip \ 14 | python2.7 \ 15 | libpython2.7 \ 16 | curl \ 17 | gnupg \ 18 | libpq-dev \ 19 | libsasl2-2 \ 20 | libldap-2.4-2 \ 21 | libxml2 \ 22 | libxmlsec1 \ 23 | libxslt1.1 \ 24 | sudo \ 25 | node-less \ 26 | # python-yaml \ 27 | && curl -o wkhtmltox.deb -sSL https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb \ 28 | && apt-get install -y --no-install-recommends ./wkhtmltox.deb \ 29 | && rm -rf /var/lib/apt/lists/* wkhtmltox.deb 30 | 31 | # Install latest postgresql-client 32 | RUN set -x; \ 33 | echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > etc/apt/sources.list.d/pgdg.list \ 34 | && export GNUPGHOME="$(mktemp -d)" \ 35 | && repokey='B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8' \ 36 | && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \ 37 | && gpg --batch --armor --export "${repokey}" > /etc/apt/trusted.gpg.d/pgdg.gpg.asc \ 38 | && gpgconf --kill all \ 39 | && rm -rf "$GNUPGHOME" \ 40 | && apt-get update \ 41 | && apt-get install -y postgresql-client \ 42 | && rm -rf /var/lib/apt/lists/* 43 | 44 | ENV PATH=/usr/local/bin:$PATH 45 | # Install Odoo Including things from sources 46 | ENV ODOO_VERSION 10.0 47 | ENV ODOO_RELEASE=20210625 48 | ARG ODOO_ARCHIVE=odoo_${ODOO_VERSION}.${ODOO_RELEASE}.tar.gz 49 | RUN set -x; \ 50 | apt-get update \ 51 | && apt-get install -y --no-install-recommends \ 52 | build-essential \ 53 | python2.7-dev \ 54 | libsasl2-dev \ 55 | libldap2-dev ruby-sass \ 56 | libxml2-dev \ 57 | libxmlsec1-dev \ 58 | libxslt1-dev \ 59 | && pip install -U pip \ 60 | && /usr/bin/env pip install \ 61 | psycogreen \ 62 | \ 63 | && curl -o requirements.txt https://raw.githubusercontent.com/odoo/odoo/${ODOO_VERSION}/requirements.txt \ 64 | && pip install -U pip \ 65 | && pip install -r requirements.txt \ 66 | && /usr/bin/env pip install https://nightly.odoo.com/${ODOO_VERSION}/nightly/src/${ODOO_ARCHIVE} \ 67 | && cd / \ 68 | && apt-get --purge remove -y \ 69 | build-essential \ 70 | python2.7-dev \ 71 | libsasl2-dev \ 72 | libldap2-dev \ 73 | libxml2-dev \ 74 | libxmlsec1-dev \ 75 | libxslt1-dev \ 76 | && apt-get autoremove -y \ 77 | && rm -rf /var/lib/apt/lists/* 78 | 79 | VOLUME /etc/odoo 80 | VOLUME /var/lib/odoo 81 | 82 | COPY ./odoo.conf /etc/odoo/ 83 | COPY ./entrypoint.py / 84 | COPY ./sudo-entrypoint.py / 85 | COPY ./find_modules.py /scripts/find_modules.py 86 | COPY ./prepare_project.py /scripts/prepare_project.py 87 | 88 | ARG UID=1000 89 | ARG GID=1000 90 | 91 | RUN mkdir /addons \ 92 | && groupadd -r -g ${GID} odoo \ 93 | && useradd -r -u ${UID} -g odoo -b /var/lib -m odoo \ 94 | && chown odoo /etc/odoo/odoo.conf \ 95 | && chown -R odoo:odoo /addons \ 96 | && cp /usr/local/bin/odoo.py /usr/local/bin/odoo || true \ 97 | && echo "odoo ALL=(ALL:ALL) NOPASSWD:SETENV: /sudo-entrypoint.py" >> /etc/sudoers 98 | 99 | ENV OPENERP_SERVER /etc/odoo/odoo.conf 100 | ENV ODOO_RC /etc/odoo/odoo.conf 101 | ENV ODOO_BASE_PATH /usr/local/lib/python2.7/dist-packages/odoo/addons 102 | # Env variable defined to monitor the kind of service running 103 | # it could be a staging/production/test or anything and undefined 104 | # is the default in case we need to know servers that aren't correctly 105 | # defined 106 | ENV DEPLOYMENT_AREA undefined 107 | 108 | expose 8069 109 | expose 8071 110 | 111 | USER odoo 112 | 113 | LABEL version="10.0" 114 | LABEL release="20210625" 115 | 116 | LABEL org.opencontainers.image.created="2021-06-25T17:55:41.198545" 117 | LABEL org.opencontainers.image.url="https://hub.docker.com/r/llacroix/odoo" 118 | LABEL org.opencontainers.image.authors="Archeti " 119 | LABEL org.opencontainers.image.documentation="https://github.com/llacroix/odoo-docker" 120 | LABEL org.opencontainers.image.source="https://github.com/llacroix/odoo-docker" 121 | LABEL org.opencontainers.image.version="10.0" 122 | LABEL org.opencontainers.image.vendor="ArcheTI" 123 | LABEL org.opencontainers.image.ref.name="10.0-nightly" 124 | LABEL org.opencontainers.image.title="Odoo 10.0" 125 | LABEL org.opencontainers.image.description="Full featured odoo image that make odoo deployment fun and secure." 126 | 127 | ENTRYPOINT ["/entrypoint.py"] 128 | 129 | cmd ["odoo"] 130 | -------------------------------------------------------------------------------- /10.0-nightly/find_modules.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | from itertools import chain 4 | 5 | 6 | def get_parser(): 7 | parser = OptionParser() 8 | 9 | parser.add_option( 10 | "-p", 11 | dest='paths', 12 | action="append", 13 | help="Location in which to search", 14 | default=[] 15 | ) 16 | 17 | parser.add_option( 18 | "--only-name", 19 | dest="only_name", 20 | action="store_true", 21 | help="Only display module name instead of path", 22 | default=False, 23 | ) 24 | 25 | parser.add_option( 26 | '--csv', 27 | dest="is_csv", 28 | action="store_true", 29 | help="Output as a comma separated list", 30 | default=False 31 | ) 32 | 33 | return parser 34 | 35 | 36 | def find_modules(options, path): 37 | modules = set() 38 | 39 | path = Path.cwd() / path 40 | 41 | erp_manifest = '__openerp__.py' 42 | odoo_manifest = '__manifest__.py' 43 | 44 | manifest_globs = chain( 45 | path.glob('**/{}'.format(erp_manifest)), 46 | path.glob('**/{}'.format(odoo_manifest)), 47 | ) 48 | 49 | for path in manifest_globs: 50 | rel_path = path.parent.relative_to(Path.cwd()) 51 | if options.only_name: 52 | modules.add(rel_path.name) 53 | else: 54 | modules.add(str(rel_path)) 55 | 56 | return modules 57 | 58 | 59 | def main(options, args): 60 | modules = set() 61 | for path in options.paths: 62 | modules = modules.union(find_modules(options, path)) 63 | 64 | return modules 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = get_parser() 69 | (options, args) = parser.parse_args() 70 | modules = main(options, args) 71 | 72 | if not options.is_csv: 73 | for module in modules: 74 | print(module) 75 | else: 76 | print(",".join(modules), end="") 77 | -------------------------------------------------------------------------------- /10.0-nightly/odoo.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | addons_path = /mnt/extra-addons 3 | data_dir = /var/lib/odoo 4 | ; admin_passwd = admin 5 | ; csv_internal_sep = , 6 | ; db_maxconn = 64 7 | ; db_name = False 8 | ; db_template = template1 9 | ; dbfilter = .* 10 | ; debug_mode = False 11 | ; email_from = False 12 | ; limit_memory_hard = 2684354560 13 | ; limit_memory_soft = 2147483648 14 | ; limit_request = 8192 15 | ; limit_time_cpu = 60 16 | ; limit_time_real = 120 17 | ; list_db = True 18 | ; log_db = False 19 | ; log_handler = [':INFO'] 20 | ; log_level = info 21 | ; logfile = None 22 | ; longpolling_port = 8072 23 | ; max_cron_threads = 2 24 | ; osv_memory_age_limit = 1.0 25 | ; osv_memory_count_limit = False 26 | ; smtp_password = False 27 | ; smtp_port = 25 28 | ; smtp_server = localhost 29 | ; smtp_ssl = False 30 | ; smtp_user = False 31 | ; workers = 0 32 | ; xmlrpc = True 33 | ; xmlrpc_interface = 34 | ; xmlrpc_port = 8069 35 | ; xmlrpcs = True 36 | ; xmlrpcs_interface = 37 | ; xmlrpcs_port = 8071 38 | -------------------------------------------------------------------------------- /10.0-nightly/prepare_project.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | import toml 4 | from giturlparse import parse 5 | from contextlib import contextmanager 6 | import os 7 | from subprocess import run 8 | from urllib.parse import urlparse 9 | 10 | 11 | def get_services(services): 12 | return { 13 | service.get('name'): service 14 | for service in services.get('services') 15 | } 16 | 17 | 18 | def addons_by_project(options, addons): 19 | res = {} 20 | 21 | for addon in addons: 22 | if addon['url'] == 'self': 23 | if options.ignore_self: 24 | continue 25 | parsed = parse(options.url) 26 | else: 27 | parsed = parse(addon['url']) 28 | 29 | auth = parsed.protocol in ['git', 'ssh'] 30 | 31 | res[parsed.repo] = dict( 32 | addon, 33 | url=parsed.url2https, 34 | auth=auth 35 | ) 36 | 37 | return res 38 | 39 | 40 | def merge_addons(options, base, other): 41 | base_addons = addons_by_project(options, base) 42 | other_addons = addons_by_project(options, other) 43 | 44 | for name, addon in other_addons.items(): 45 | if name not in base_addons: 46 | base_addons[name] = addon 47 | else: 48 | base_addons[name] = dict(base_addons[name], **addon) 49 | 50 | return [ 51 | addon 52 | for addon in base_addons.values() 53 | ] 54 | 55 | 56 | def merge_services(options, base, other): 57 | basic_inherit = dict(base, **other) 58 | 59 | if base.get('addons') or other.get('addons'): 60 | basic_inherit['addons'] = merge_addons( 61 | options, 62 | base.get('addons', []), 63 | other.get('addons', []) 64 | ) 65 | 66 | return basic_inherit 67 | 68 | 69 | def compile_service(options, services, name): 70 | service = services.get(name, {}) 71 | if 'inherit' in service: 72 | merge_service = compile_service(options, services, service['inherit']) 73 | service = merge_services(options, merge_service, service) 74 | 75 | return service 76 | 77 | 78 | def get_parser(): 79 | parser = OptionParser() 80 | 81 | parser.add_option( 82 | '-f', 83 | '--file', 84 | dest="file", 85 | help="Input File" 86 | ) 87 | 88 | parser.add_option( 89 | '--url', 90 | dest='url', 91 | help="Url of self project" 92 | ) 93 | 94 | parser.add_option( 95 | '-o', 96 | '--output', 97 | dest="output_directory", 98 | help="Output Directory" 99 | ) 100 | 101 | parser.add_option( 102 | '-e', 103 | dest="env", 104 | help="Environment to prepare" 105 | ) 106 | 107 | parser.add_option( 108 | '--username', 109 | dest="username", 110 | help="Username to replace with", 111 | ) 112 | 113 | parser.add_option( 114 | '--password', 115 | dest="password", 116 | help="password to set on https urls" 117 | ) 118 | 119 | parser.add_option( 120 | '-b', 121 | '--branch', 122 | dest="branch", 123 | help="Default branch if no ref is defined" 124 | ) 125 | 126 | parser.add_option( 127 | '--ignore-self', 128 | dest="ignore_self", 129 | action="store_true", 130 | help="Ignore self url as it's already fetched", 131 | default=False 132 | ) 133 | 134 | return parser 135 | 136 | 137 | @contextmanager 138 | def cd(directory): 139 | cwd = Path.cwd() 140 | try: 141 | os.chdir(directory) 142 | yield 143 | finally: 144 | os.chdir(cwd) 145 | 146 | 147 | def fetch_addons(options, addon): 148 | parsed = parse(addon['url']) 149 | 150 | url = urlparse(parsed.url2https) 151 | 152 | if addon['auth'] and options.username and options.password: 153 | url = url._replace( 154 | netloc="{}:{}@{}".format( 155 | options.username, 156 | options.password, 157 | url.netloc 158 | ) 159 | ) 160 | 161 | repo_path = Path.cwd() / options.output_directory / parsed.repo 162 | 163 | repo_path.mkdir(exist_ok=True) 164 | 165 | with cd(repo_path): 166 | run(['git', 'init']) 167 | run(['git', 'remote', 'add', 'origin', url.geturl()]) 168 | 169 | ref = addon.get('commit') or addon.get('branch') or options.branch 170 | 171 | if ref: 172 | run(['git', 'fetch', 'origin', ref]) 173 | else: 174 | run(['git', 'fetch', 'origin']) 175 | 176 | run(['git', 'checkout', 'FETCH_HEAD']) 177 | run(['git', 'remote', 'remove', 'origin']) 178 | 179 | 180 | def main(options, args): 181 | with Path(options.file).open('r') as fin: 182 | services = toml.loads(fin.read()) 183 | 184 | by_name = get_services(services) 185 | 186 | outputdir = Path(options.output_directory) 187 | outputdir.mkdir(exist_ok=True) 188 | 189 | service = compile_service(options, by_name, options.env) 190 | 191 | for addons in service.get('addons', []): 192 | fetch_addons(options, addons) 193 | 194 | 195 | if __name__ == '__main__': 196 | parser = get_parser() 197 | (options, args) = parser.parse_args() 198 | main(options, args) 199 | -------------------------------------------------------------------------------- /10.0/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | LABEL maintainer="Archeti " 3 | 4 | # Generate locale C.UTF-8 for postgres and general locale data 5 | ENV LANG C.UTF-8 6 | 7 | # Install some dependencies python3.7 8 | RUN set -x; \ 9 | apt-get update \ 10 | && apt-get install -y --no-install-recommends \ 11 | python-wheel \ 12 | python-setuptools \ 13 | python-pip \ 14 | python2.7 \ 15 | libpython2.7 \ 16 | curl \ 17 | gnupg \ 18 | libpq-dev \ 19 | libsasl2-2 \ 20 | libldap-2.4-2 \ 21 | libxml2 \ 22 | libxmlsec1 \ 23 | libxslt1.1 \ 24 | sudo \ 25 | node-less \ 26 | # python-yaml \ 27 | && curl -o wkhtmltox.deb -sSL https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb \ 28 | && apt-get install -y --no-install-recommends ./wkhtmltox.deb \ 29 | && rm -rf /var/lib/apt/lists/* wkhtmltox.deb 30 | 31 | # Install latest postgresql-client 32 | RUN set -x; \ 33 | echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > etc/apt/sources.list.d/pgdg.list \ 34 | && export GNUPGHOME="$(mktemp -d)" \ 35 | && repokey='B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8' \ 36 | && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \ 37 | && gpg --batch --armor --export "${repokey}" > /etc/apt/trusted.gpg.d/pgdg.gpg.asc \ 38 | && gpgconf --kill all \ 39 | && rm -rf "$GNUPGHOME" \ 40 | && apt-get update \ 41 | && apt-get install -y postgresql-client \ 42 | && rm -rf /var/lib/apt/lists/* 43 | 44 | ENV PATH=/usr/local/bin:$PATH 45 | # Install Odoo Including things from sources 46 | ENV ODOO_VERSION 10.0 47 | ENV ODOO_RELEASE=20200313 48 | ARG ODOO_ARCHIVE=odoo_${ODOO_VERSION}.${ODOO_RELEASE}.tar.gz 49 | RUN set -x; \ 50 | apt-get update \ 51 | && apt-get install -y --no-install-recommends \ 52 | build-essential \ 53 | python2.7-dev \ 54 | libsasl2-dev \ 55 | libldap2-dev ruby-sass \ 56 | libxml2-dev \ 57 | libxmlsec1-dev \ 58 | libxslt1-dev \ 59 | && pip install -U pip \ 60 | && /usr/bin/env pip install \ 61 | psycogreen \ 62 | pathlib2 \ 63 | && curl -o requirements.txt https://raw.githubusercontent.com/odoo/odoo/${ODOO_VERSION}/requirements.txt \ 64 | && pip install -U pip \ 65 | && pip install -r requirements.txt \ 66 | && /usr/bin/env pip install https://nightly.odoo.com/${ODOO_VERSION}/nightly/src/${ODOO_ARCHIVE} \ 67 | && cd / \ 68 | && apt-get --purge remove -y \ 69 | build-essential \ 70 | python2.7-dev \ 71 | libsasl2-dev \ 72 | libldap2-dev \ 73 | libxml2-dev \ 74 | libxmlsec1-dev \ 75 | libxslt1-dev \ 76 | && apt-get autoremove -y \ 77 | && rm -rf /var/lib/apt/lists/* 78 | 79 | VOLUME /etc/odoo 80 | VOLUME /var/lib/odoo 81 | 82 | COPY ./odoo.conf /etc/odoo/ 83 | COPY ./entrypoint.py / 84 | COPY ./sudo-entrypoint.py / 85 | COPY ./find_modules.py /scripts/find_modules.py 86 | COPY ./prepare_project.py /scripts/prepare_project.py 87 | 88 | ARG UID=1000 89 | ARG GID=1000 90 | 91 | RUN mkdir /addons \ 92 | && groupadd -r -g ${GID} odoo \ 93 | && useradd -r -u ${UID} -g odoo -b /var/lib -m odoo \ 94 | && chown odoo /etc/odoo/odoo.conf \ 95 | && chown -R odoo:odoo /addons \ 96 | && cp /usr/local/bin/odoo.py /usr/local/bin/odoo || true \ 97 | && echo "odoo ALL=(ALL:ALL) NOPASSWD:SETENV: /sudo-entrypoint.py" >> /etc/sudoers 98 | 99 | ENV OPENERP_SERVER /etc/odoo/odoo.conf 100 | ENV ODOO_RC /etc/odoo/odoo.conf 101 | ENV ODOO_BASE_PATH /usr/local/lib/python2.7/dist-packages/odoo/addons 102 | # Env variable defined to monitor the kind of service running 103 | # it could be a staging/production/test or anything and undefined 104 | # is the default in case we need to know servers that aren't correctly 105 | # defined 106 | ENV DEPLOYMENT_AREA undefined 107 | 108 | expose 8069 109 | expose 8071 110 | 111 | USER odoo 112 | 113 | LABEL version="10.0" 114 | LABEL release="20200313" 115 | 116 | LABEL org.opencontainers.image.created="2021-06-25T17:55:41.193896" 117 | LABEL org.opencontainers.image.url="https://hub.docker.com/r/llacroix/odoo" 118 | LABEL org.opencontainers.image.authors="Archeti " 119 | LABEL org.opencontainers.image.documentation="https://github.com/llacroix/odoo-docker" 120 | LABEL org.opencontainers.image.source="https://github.com/llacroix/odoo-docker" 121 | LABEL org.opencontainers.image.version="10.0" 122 | LABEL org.opencontainers.image.vendor="ArcheTI" 123 | LABEL org.opencontainers.image.ref.name="10.0" 124 | LABEL org.opencontainers.image.title="Odoo 10.0" 125 | LABEL org.opencontainers.image.description="Full featured odoo image that make odoo deployment fun and secure." 126 | 127 | ENTRYPOINT ["/entrypoint.py"] 128 | 129 | cmd ["odoo"] 130 | -------------------------------------------------------------------------------- /10.0/find_modules.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | from itertools import chain 4 | 5 | 6 | def get_parser(): 7 | parser = OptionParser() 8 | 9 | parser.add_option( 10 | "-p", 11 | dest='paths', 12 | action="append", 13 | help="Location in which to search", 14 | default=[] 15 | ) 16 | 17 | parser.add_option( 18 | "--only-name", 19 | dest="only_name", 20 | action="store_true", 21 | help="Only display module name instead of path", 22 | default=False, 23 | ) 24 | 25 | parser.add_option( 26 | '--csv', 27 | dest="is_csv", 28 | action="store_true", 29 | help="Output as a comma separated list", 30 | default=False 31 | ) 32 | 33 | return parser 34 | 35 | 36 | def find_modules(options, path): 37 | modules = set() 38 | 39 | path = Path.cwd() / path 40 | 41 | erp_manifest = '__openerp__.py' 42 | odoo_manifest = '__manifest__.py' 43 | 44 | manifest_globs = chain( 45 | path.glob('**/{}'.format(erp_manifest)), 46 | path.glob('**/{}'.format(odoo_manifest)), 47 | ) 48 | 49 | for path in manifest_globs: 50 | rel_path = path.parent.relative_to(Path.cwd()) 51 | if options.only_name: 52 | modules.add(rel_path.name) 53 | else: 54 | modules.add(str(rel_path)) 55 | 56 | return modules 57 | 58 | 59 | def main(options, args): 60 | modules = set() 61 | for path in options.paths: 62 | modules = modules.union(find_modules(options, path)) 63 | 64 | return modules 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = get_parser() 69 | (options, args) = parser.parse_args() 70 | modules = main(options, args) 71 | 72 | if not options.is_csv: 73 | for module in modules: 74 | print(module) 75 | else: 76 | print(",".join(modules), end="") 77 | -------------------------------------------------------------------------------- /10.0/odoo.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | addons_path = /mnt/extra-addons 3 | data_dir = /var/lib/odoo 4 | ; admin_passwd = admin 5 | ; csv_internal_sep = , 6 | ; db_maxconn = 64 7 | ; db_name = False 8 | ; db_template = template1 9 | ; dbfilter = .* 10 | ; debug_mode = False 11 | ; email_from = False 12 | ; limit_memory_hard = 2684354560 13 | ; limit_memory_soft = 2147483648 14 | ; limit_request = 8192 15 | ; limit_time_cpu = 60 16 | ; limit_time_real = 120 17 | ; list_db = True 18 | ; log_db = False 19 | ; log_handler = [':INFO'] 20 | ; log_level = info 21 | ; logfile = None 22 | ; longpolling_port = 8072 23 | ; max_cron_threads = 2 24 | ; osv_memory_age_limit = 1.0 25 | ; osv_memory_count_limit = False 26 | ; smtp_password = False 27 | ; smtp_port = 25 28 | ; smtp_server = localhost 29 | ; smtp_ssl = False 30 | ; smtp_user = False 31 | ; workers = 0 32 | ; xmlrpc = True 33 | ; xmlrpc_interface = 34 | ; xmlrpc_port = 8069 35 | ; xmlrpcs = True 36 | ; xmlrpcs_interface = 37 | ; xmlrpcs_port = 8071 38 | -------------------------------------------------------------------------------- /10.0/prepare_project.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | import toml 4 | from giturlparse import parse 5 | from contextlib import contextmanager 6 | import os 7 | from subprocess import run 8 | from urllib.parse import urlparse 9 | 10 | 11 | def get_services(services): 12 | return { 13 | service.get('name'): service 14 | for service in services.get('services') 15 | } 16 | 17 | 18 | def addons_by_project(options, addons): 19 | res = {} 20 | 21 | for addon in addons: 22 | if addon['url'] == 'self': 23 | if options.ignore_self: 24 | continue 25 | parsed = parse(options.url) 26 | else: 27 | parsed = parse(addon['url']) 28 | 29 | auth = parsed.protocol in ['git', 'ssh'] 30 | 31 | res[parsed.repo] = dict( 32 | addon, 33 | url=parsed.url2https, 34 | auth=auth 35 | ) 36 | 37 | return res 38 | 39 | 40 | def merge_addons(options, base, other): 41 | base_addons = addons_by_project(options, base) 42 | other_addons = addons_by_project(options, other) 43 | 44 | for name, addon in other_addons.items(): 45 | if name not in base_addons: 46 | base_addons[name] = addon 47 | else: 48 | base_addons[name] = dict(base_addons[name], **addon) 49 | 50 | return [ 51 | addon 52 | for addon in base_addons.values() 53 | ] 54 | 55 | 56 | def merge_services(options, base, other): 57 | basic_inherit = dict(base, **other) 58 | 59 | if base.get('addons') or other.get('addons'): 60 | basic_inherit['addons'] = merge_addons( 61 | options, 62 | base.get('addons', []), 63 | other.get('addons', []) 64 | ) 65 | 66 | return basic_inherit 67 | 68 | 69 | def compile_service(options, services, name): 70 | service = services.get(name, {}) 71 | if 'inherit' in service: 72 | merge_service = compile_service(options, services, service['inherit']) 73 | service = merge_services(options, merge_service, service) 74 | 75 | return service 76 | 77 | 78 | def get_parser(): 79 | parser = OptionParser() 80 | 81 | parser.add_option( 82 | '-f', 83 | '--file', 84 | dest="file", 85 | help="Input File" 86 | ) 87 | 88 | parser.add_option( 89 | '--url', 90 | dest='url', 91 | help="Url of self project" 92 | ) 93 | 94 | parser.add_option( 95 | '-o', 96 | '--output', 97 | dest="output_directory", 98 | help="Output Directory" 99 | ) 100 | 101 | parser.add_option( 102 | '-e', 103 | dest="env", 104 | help="Environment to prepare" 105 | ) 106 | 107 | parser.add_option( 108 | '--username', 109 | dest="username", 110 | help="Username to replace with", 111 | ) 112 | 113 | parser.add_option( 114 | '--password', 115 | dest="password", 116 | help="password to set on https urls" 117 | ) 118 | 119 | parser.add_option( 120 | '-b', 121 | '--branch', 122 | dest="branch", 123 | help="Default branch if no ref is defined" 124 | ) 125 | 126 | parser.add_option( 127 | '--ignore-self', 128 | dest="ignore_self", 129 | action="store_true", 130 | help="Ignore self url as it's already fetched", 131 | default=False 132 | ) 133 | 134 | return parser 135 | 136 | 137 | @contextmanager 138 | def cd(directory): 139 | cwd = Path.cwd() 140 | try: 141 | os.chdir(directory) 142 | yield 143 | finally: 144 | os.chdir(cwd) 145 | 146 | 147 | def fetch_addons(options, addon): 148 | parsed = parse(addon['url']) 149 | 150 | url = urlparse(parsed.url2https) 151 | 152 | if addon['auth'] and options.username and options.password: 153 | url = url._replace( 154 | netloc="{}:{}@{}".format( 155 | options.username, 156 | options.password, 157 | url.netloc 158 | ) 159 | ) 160 | 161 | repo_path = Path.cwd() / options.output_directory / parsed.repo 162 | 163 | repo_path.mkdir(exist_ok=True) 164 | 165 | with cd(repo_path): 166 | run(['git', 'init']) 167 | run(['git', 'remote', 'add', 'origin', url.geturl()]) 168 | 169 | ref = addon.get('commit') or addon.get('branch') or options.branch 170 | 171 | if ref: 172 | run(['git', 'fetch', 'origin', ref]) 173 | else: 174 | run(['git', 'fetch', 'origin']) 175 | 176 | run(['git', 'checkout', 'FETCH_HEAD']) 177 | run(['git', 'remote', 'remove', 'origin']) 178 | 179 | 180 | def main(options, args): 181 | with Path(options.file).open('r') as fin: 182 | services = toml.loads(fin.read()) 183 | 184 | by_name = get_services(services) 185 | 186 | outputdir = Path(options.output_directory) 187 | outputdir.mkdir(exist_ok=True) 188 | 189 | service = compile_service(options, by_name, options.env) 190 | 191 | for addons in service.get('addons', []): 192 | fetch_addons(options, addons) 193 | 194 | 195 | if __name__ == '__main__': 196 | parser = get_parser() 197 | (options, args) = parser.parse_args() 198 | main(options, args) 199 | -------------------------------------------------------------------------------- /10.0/sudo-entrypoint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import time 4 | import os 5 | import shlex 6 | import subprocess 7 | import sys 8 | import glob 9 | import pip 10 | import re 11 | import stat 12 | from os import path 13 | from os.path import expanduser 14 | import shutil 15 | 16 | try: 17 | from pathlib import Path 18 | except ImportError: 19 | from pathlib2 import Path 20 | 21 | 22 | def pipe(args): 23 | """ 24 | Call the process with std(in,out,err) 25 | """ 26 | env = os.environ.copy() 27 | env['DEBIAN_FRONTEND'] = 'noninteractive' 28 | 29 | process = subprocess.Popen( 30 | args, 31 | stdin=sys.stdin, 32 | stdout=sys.stdout, 33 | stderr=sys.stderr, 34 | env=env 35 | ) 36 | 37 | process.wait() 38 | 39 | return process.returncode 40 | 41 | 42 | def get_dirs(cur_path): 43 | return [ 44 | path.join(cur_path, npath) 45 | for npath in os.listdir(cur_path) 46 | if path.isdir(path.join(cur_path, npath)) 47 | ] 48 | 49 | 50 | def get_extra_paths(): 51 | extra_paths = os.environ.get('ODOO_EXTRA_PATHS') 52 | 53 | if not extra_paths: 54 | return [] 55 | 56 | return [ 57 | extra_path.strip() 58 | for extra_path in extra_paths.split(',') 59 | ] 60 | 61 | 62 | def get_addons_paths(): 63 | addons = get_dirs('/addons') 64 | addons += get_extra_paths() 65 | 66 | return [ 67 | Path(path) 68 | for path in addons 69 | ] 70 | 71 | 72 | def install_apt_packages(): 73 | """ 74 | Install debian dependencies. 75 | """ 76 | package_list = set() 77 | 78 | paths = get_addons_paths() 79 | 80 | print("Looking up for packages in {}".format(paths)) 81 | 82 | for addons_path in paths: 83 | for packages in addons_path.glob('**/apt-packages.txt'): 84 | print("Installing packages from %s" % packages) 85 | with open(packages, 'r') as pack_file: 86 | lines = [line.strip() for line in pack_file] 87 | package_list.update(set(lines)) 88 | 89 | extras = os.environ.get('EXTRA_APT_PACKAGES', '') 90 | print(f"Adding extra packages {extras}") 91 | if extras: 92 | for package in extras.split(','): 93 | if not package: 94 | continue 95 | package_list.add(package) 96 | 97 | if len(package_list) > 0: 98 | print(f"Installing {package_list}") 99 | ret = pipe(['apt-get', 'update']) 100 | 101 | # Something went wrong, stop the service as it's failing 102 | if ret != 0: 103 | sys.exit(ret) 104 | 105 | ret = pipe(['apt-get', 'install', '-y'] + list(package_list)) 106 | 107 | # Something went wrong, stop the service as it's failing 108 | if ret != 0: 109 | sys.exit(ret) 110 | 111 | 112 | def load_secrets(): 113 | # TODO add a way to load some secrets so odoo process can 114 | # use secrets as a way to load passwords/user for postgresql 115 | # credentials could also be stored in the HOME of the odoo user 116 | # except we cannot rely on secrets 100% because it only works in 117 | # swarm mode 118 | pgpass_secret = '/run/secrets/.pgpass' 119 | if path.exists(pgpass_secret): 120 | home_folder = '/var/lib/odoo' 121 | pgpass_target = path.join(home_folder, '.pgpass') 122 | if path.exists(pgpass_target): 123 | os.remove(pgpass_target) 124 | # shutil.move doesn't always work correctly on different fs 125 | shutil.copy(pgpass_secret, home_folder) 126 | st = os.stat(pgpass_secret) 127 | os.chmod(pgpass_target, st.st_mode) 128 | os.chown(pgpass_target, st[stat.ST_UID], st[stat.ST_GID]) 129 | # Cannot remove anymore apparently 130 | # os.remove(pgpass_secret) 131 | # shutil.move(pgpass_secret, home_folder) 132 | 133 | 134 | def disable_base_modules(): 135 | base_addons = os.environ.get('ODOO_BASE_PATH', '') 136 | addons_to_remove = os.environ.get('ODOO_DISABLED_MODULES', '') 137 | 138 | modules = addons_to_remove.split(',') 139 | modules = map(lambda mod: mod.strip(), modules) 140 | 141 | if not base_addons: 142 | print("Do not attempt to remove wrong folder") 143 | return 144 | 145 | for module in modules: 146 | if not module: 147 | continue 148 | print("Removing module %s from %s" % (module, base_addons)) 149 | 150 | module_path = Path(base_addons, module) 151 | if module_path.exists() and module_path.is_dir(): 152 | shutil.rmtree(module_path) 153 | else: 154 | print("Module skipped as it doesn't seem to be present.") 155 | 156 | 157 | def fix_access_rights(): 158 | if os.environ.get('RESET_ACCESS_RIGHTS', '') == 'TRUE': 159 | pipe(["chown", "-R", "odoo:odoo", "/var/lib/odoo"]) 160 | pipe(["chown", "-R", "odoo:odoo", "/etc/odoo"]) 161 | 162 | 163 | def remove_sudo(): 164 | return pipe(["sed", "-i", "/odoo/d", "/etc/sudoers"]) 165 | 166 | 167 | def main(): 168 | install_apt_packages() 169 | load_secrets() 170 | fix_access_rights() 171 | disable_base_modules() 172 | return remove_sudo() 173 | 174 | 175 | try: 176 | code = main() 177 | sys.exit(code) 178 | except Exception as exc: 179 | print(exc) 180 | sys.exit(1) 181 | except KeyboardInterrupt as exc: 182 | print(exc) 183 | sys.exit(1) 184 | -------------------------------------------------------------------------------- /11.0-nightly/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | LABEL maintainer="Archeti " 3 | 4 | # Generate locale C.UTF-8 for postgres and general locale data 5 | ENV LANG C.UTF-8 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | 8 | # Install some dependencies python3.7 9 | RUN set -x; \ 10 | apt-get update \ 11 | && apt-get install -y --no-install-recommends \ 12 | python3-wheel \ 13 | python3-setuptools \ 14 | python3-pip \ 15 | # python3.7 \ 16 | # libpython3.7 \ 17 | curl \ 18 | gnupg \ 19 | libpq-dev \ 20 | libsasl2-2 \ 21 | libldap-2.4-2 \ 22 | libxml2 \ 23 | libxmlsec1 \ 24 | libxslt1.1 \ 25 | sudo \ 26 | node-less \ 27 | # python3-yaml \ 28 | && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 1 \ 29 | # && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 2 \ 30 | && update-alternatives --install /usr/bin/python python /usr/bin/python3.6 1 \ 31 | && update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 2 \ 32 | && curl -o wkhtmltox.deb -sSL https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb \ 33 | && apt-get install -y --no-install-recommends ./wkhtmltox.deb \ 34 | && rm -rf /var/lib/apt/lists/* wkhtmltox.deb 35 | 36 | # Install latest postgresql-client 37 | RUN set -x; \ 38 | echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > etc/apt/sources.list.d/pgdg.list \ 39 | && export GNUPGHOME="$(mktemp -d)" \ 40 | && repokey='B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8' \ 41 | && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \ 42 | && gpg --batch --armor --export "${repokey}" > /etc/apt/trusted.gpg.d/pgdg.gpg.asc \ 43 | && gpgconf --kill all \ 44 | && rm -rf "$GNUPGHOME" \ 45 | && apt-get update \ 46 | && apt-get install -y postgresql-client \ 47 | && rm -rf /var/lib/apt/lists/* 48 | 49 | ENV PATH=/usr/local/bin:$PATH 50 | # Install Odoo Including things from sources 51 | ENV ODOO_VERSION 11.0 52 | ENV ODOO_RELEASE=20210625 53 | ARG ODOO_ARCHIVE=odoo_${ODOO_VERSION}.${ODOO_RELEASE}.tar.gz 54 | RUN set -x; \ 55 | apt-get update \ 56 | && apt-get install -y --no-install-recommends \ 57 | build-essential \ 58 | python3.6-dev \ 59 | libsasl2-dev \ 60 | libldap2-dev ruby-sass \ 61 | libxml2-dev \ 62 | libxmlsec1-dev \ 63 | libxslt1-dev \ 64 | && pip install -U pip \ 65 | && /usr/bin/env pip install \ 66 | psycogreen \ 67 | \ 68 | && curl -o requirements.txt https://raw.githubusercontent.com/odoo/odoo/${ODOO_VERSION}/requirements.txt \ 69 | && pip install -r requirements.txt \ 70 | && /usr/bin/env pip install https://nightly.odoo.com/${ODOO_VERSION}/nightly/src/${ODOO_ARCHIVE} \ 71 | && cd / \ 72 | && apt-get --purge remove -y \ 73 | build-essential \ 74 | python3.6-dev \ 75 | libsasl2-dev \ 76 | libldap2-dev \ 77 | libxml2-dev \ 78 | libxmlsec1-dev \ 79 | libxslt1-dev \ 80 | && apt-get autoremove -y \ 81 | && rm -rf /var/lib/apt/lists/* 82 | 83 | VOLUME /etc/odoo 84 | VOLUME /var/lib/odoo 85 | 86 | COPY ./odoo.conf /etc/odoo/ 87 | COPY ./entrypoint.py / 88 | COPY ./sudo-entrypoint.py / 89 | COPY ./find_modules.py /scripts/find_modules.py 90 | COPY ./prepare_project.py /scripts/prepare_project.py 91 | 92 | ARG UID=1000 93 | ARG GID=1000 94 | 95 | RUN mkdir /addons \ 96 | && groupadd -r -g ${GID} odoo \ 97 | && useradd -r -u ${UID} -g odoo -b /var/lib -m odoo \ 98 | && chown odoo /etc/odoo/odoo.conf \ 99 | && chown -R odoo:odoo /addons \ 100 | && echo "odoo ALL=(ALL:ALL) NOPASSWD:SETENV: /sudo-entrypoint.py" >> /etc/sudoers 101 | 102 | ENV ODOO_RC /etc/odoo/odoo.conf 103 | ENV ODOO_BASE_PATH /usr/local/lib/python3.6/dist-packages/odoo/addons 104 | # Env variable defined to monitor the kind of service running 105 | # it could be a staging/production/test or anything and undefined 106 | # is the default in case we need to know servers that aren't correctly 107 | # defined 108 | ENV DEPLOYMENT_AREA undefined 109 | 110 | expose 8069 111 | expose 8071 112 | 113 | USER odoo 114 | 115 | LABEL version="11.0" 116 | LABEL release="20210625" 117 | 118 | LABEL org.opencontainers.image.created="2021-06-25T17:55:41.199454" 119 | LABEL org.opencontainers.image.url="https://hub.docker.com/r/llacroix/odoo" 120 | LABEL org.opencontainers.image.authors="Archeti " 121 | LABEL org.opencontainers.image.documentation="https://github.com/llacroix/odoo-docker" 122 | LABEL org.opencontainers.image.source="https://github.com/llacroix/odoo-docker" 123 | LABEL org.opencontainers.image.version="11.0" 124 | LABEL org.opencontainers.image.vendor="ArcheTI" 125 | LABEL org.opencontainers.image.ref.name="11.0-nightly" 126 | LABEL org.opencontainers.image.title="Odoo 11.0" 127 | LABEL org.opencontainers.image.description="Full featured odoo image that make odoo deployment fun and secure." 128 | 129 | ENTRYPOINT ["/entrypoint.py"] 130 | 131 | cmd ["odoo"] 132 | -------------------------------------------------------------------------------- /11.0-nightly/find_modules.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | from itertools import chain 4 | 5 | 6 | def get_parser(): 7 | parser = OptionParser() 8 | 9 | parser.add_option( 10 | "-p", 11 | dest='paths', 12 | action="append", 13 | help="Location in which to search", 14 | default=[] 15 | ) 16 | 17 | parser.add_option( 18 | "--only-name", 19 | dest="only_name", 20 | action="store_true", 21 | help="Only display module name instead of path", 22 | default=False, 23 | ) 24 | 25 | parser.add_option( 26 | '--csv', 27 | dest="is_csv", 28 | action="store_true", 29 | help="Output as a comma separated list", 30 | default=False 31 | ) 32 | 33 | return parser 34 | 35 | 36 | def find_modules(options, path): 37 | modules = set() 38 | 39 | path = Path.cwd() / path 40 | 41 | erp_manifest = '__openerp__.py' 42 | odoo_manifest = '__manifest__.py' 43 | 44 | manifest_globs = chain( 45 | path.glob('**/{}'.format(erp_manifest)), 46 | path.glob('**/{}'.format(odoo_manifest)), 47 | ) 48 | 49 | for path in manifest_globs: 50 | rel_path = path.parent.relative_to(Path.cwd()) 51 | if options.only_name: 52 | modules.add(rel_path.name) 53 | else: 54 | modules.add(str(rel_path)) 55 | 56 | return modules 57 | 58 | 59 | def main(options, args): 60 | modules = set() 61 | for path in options.paths: 62 | modules = modules.union(find_modules(options, path)) 63 | 64 | return modules 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = get_parser() 69 | (options, args) = parser.parse_args() 70 | modules = main(options, args) 71 | 72 | if not options.is_csv: 73 | for module in modules: 74 | print(module) 75 | else: 76 | print(",".join(modules), end="") 77 | -------------------------------------------------------------------------------- /11.0-nightly/odoo.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | addons_path = /mnt/extra-addons 3 | data_dir = /var/lib/odoo 4 | ; admin_passwd = admin 5 | ; csv_internal_sep = , 6 | ; db_maxconn = 64 7 | ; db_name = False 8 | ; db_template = template1 9 | ; dbfilter = .* 10 | ; debug_mode = False 11 | ; email_from = False 12 | ; limit_memory_hard = 2684354560 13 | ; limit_memory_soft = 2147483648 14 | ; limit_request = 8192 15 | ; limit_time_cpu = 60 16 | ; limit_time_real = 120 17 | ; list_db = True 18 | ; log_db = False 19 | ; log_handler = [':INFO'] 20 | ; log_level = info 21 | ; logfile = None 22 | ; longpolling_port = 8072 23 | ; max_cron_threads = 2 24 | ; osv_memory_age_limit = 1.0 25 | ; osv_memory_count_limit = False 26 | ; smtp_password = False 27 | ; smtp_port = 25 28 | ; smtp_server = localhost 29 | ; smtp_ssl = False 30 | ; smtp_user = False 31 | ; workers = 0 32 | ; xmlrpc = True 33 | ; xmlrpc_interface = 34 | ; xmlrpc_port = 8069 35 | ; xmlrpcs = True 36 | ; xmlrpcs_interface = 37 | ; xmlrpcs_port = 8071 38 | -------------------------------------------------------------------------------- /11.0-nightly/prepare_project.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | import toml 4 | from giturlparse import parse 5 | from contextlib import contextmanager 6 | import os 7 | from subprocess import run 8 | from urllib.parse import urlparse 9 | 10 | 11 | def get_services(services): 12 | return { 13 | service.get('name'): service 14 | for service in services.get('services') 15 | } 16 | 17 | 18 | def addons_by_project(options, addons): 19 | res = {} 20 | 21 | for addon in addons: 22 | if addon['url'] == 'self': 23 | if options.ignore_self: 24 | continue 25 | parsed = parse(options.url) 26 | else: 27 | parsed = parse(addon['url']) 28 | 29 | auth = parsed.protocol in ['git', 'ssh'] 30 | 31 | res[parsed.repo] = dict( 32 | addon, 33 | url=parsed.url2https, 34 | auth=auth 35 | ) 36 | 37 | return res 38 | 39 | 40 | def merge_addons(options, base, other): 41 | base_addons = addons_by_project(options, base) 42 | other_addons = addons_by_project(options, other) 43 | 44 | for name, addon in other_addons.items(): 45 | if name not in base_addons: 46 | base_addons[name] = addon 47 | else: 48 | base_addons[name] = dict(base_addons[name], **addon) 49 | 50 | return [ 51 | addon 52 | for addon in base_addons.values() 53 | ] 54 | 55 | 56 | def merge_services(options, base, other): 57 | basic_inherit = dict(base, **other) 58 | 59 | if base.get('addons') or other.get('addons'): 60 | basic_inherit['addons'] = merge_addons( 61 | options, 62 | base.get('addons', []), 63 | other.get('addons', []) 64 | ) 65 | 66 | return basic_inherit 67 | 68 | 69 | def compile_service(options, services, name): 70 | service = services.get(name, {}) 71 | if 'inherit' in service: 72 | merge_service = compile_service(options, services, service['inherit']) 73 | service = merge_services(options, merge_service, service) 74 | 75 | return service 76 | 77 | 78 | def get_parser(): 79 | parser = OptionParser() 80 | 81 | parser.add_option( 82 | '-f', 83 | '--file', 84 | dest="file", 85 | help="Input File" 86 | ) 87 | 88 | parser.add_option( 89 | '--url', 90 | dest='url', 91 | help="Url of self project" 92 | ) 93 | 94 | parser.add_option( 95 | '-o', 96 | '--output', 97 | dest="output_directory", 98 | help="Output Directory" 99 | ) 100 | 101 | parser.add_option( 102 | '-e', 103 | dest="env", 104 | help="Environment to prepare" 105 | ) 106 | 107 | parser.add_option( 108 | '--username', 109 | dest="username", 110 | help="Username to replace with", 111 | ) 112 | 113 | parser.add_option( 114 | '--password', 115 | dest="password", 116 | help="password to set on https urls" 117 | ) 118 | 119 | parser.add_option( 120 | '-b', 121 | '--branch', 122 | dest="branch", 123 | help="Default branch if no ref is defined" 124 | ) 125 | 126 | parser.add_option( 127 | '--ignore-self', 128 | dest="ignore_self", 129 | action="store_true", 130 | help="Ignore self url as it's already fetched", 131 | default=False 132 | ) 133 | 134 | return parser 135 | 136 | 137 | @contextmanager 138 | def cd(directory): 139 | cwd = Path.cwd() 140 | try: 141 | os.chdir(directory) 142 | yield 143 | finally: 144 | os.chdir(cwd) 145 | 146 | 147 | def fetch_addons(options, addon): 148 | parsed = parse(addon['url']) 149 | 150 | url = urlparse(parsed.url2https) 151 | 152 | if addon['auth'] and options.username and options.password: 153 | url = url._replace( 154 | netloc="{}:{}@{}".format( 155 | options.username, 156 | options.password, 157 | url.netloc 158 | ) 159 | ) 160 | 161 | repo_path = Path.cwd() / options.output_directory / parsed.repo 162 | 163 | repo_path.mkdir(exist_ok=True) 164 | 165 | with cd(repo_path): 166 | run(['git', 'init']) 167 | run(['git', 'remote', 'add', 'origin', url.geturl()]) 168 | 169 | ref = addon.get('commit') or addon.get('branch') or options.branch 170 | 171 | if ref: 172 | run(['git', 'fetch', 'origin', ref]) 173 | else: 174 | run(['git', 'fetch', 'origin']) 175 | 176 | run(['git', 'checkout', 'FETCH_HEAD']) 177 | run(['git', 'remote', 'remove', 'origin']) 178 | 179 | 180 | def main(options, args): 181 | with Path(options.file).open('r') as fin: 182 | services = toml.loads(fin.read()) 183 | 184 | by_name = get_services(services) 185 | 186 | outputdir = Path(options.output_directory) 187 | outputdir.mkdir(exist_ok=True) 188 | 189 | service = compile_service(options, by_name, options.env) 190 | 191 | for addons in service.get('addons', []): 192 | fetch_addons(options, addons) 193 | 194 | 195 | if __name__ == '__main__': 196 | parser = get_parser() 197 | (options, args) = parser.parse_args() 198 | main(options, args) 199 | -------------------------------------------------------------------------------- /11.0/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | LABEL maintainer="Archeti " 3 | 4 | # Generate locale C.UTF-8 for postgres and general locale data 5 | ENV LANG C.UTF-8 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | 8 | # Install some dependencies python3.7 9 | RUN set -x; \ 10 | apt-get update \ 11 | && apt-get install -y --no-install-recommends \ 12 | python3-wheel \ 13 | python3-setuptools \ 14 | python3-pip \ 15 | # python3.7 \ 16 | # libpython3.7 \ 17 | curl \ 18 | gnupg \ 19 | libpq-dev \ 20 | libsasl2-2 \ 21 | libldap-2.4-2 \ 22 | libxml2 \ 23 | libxmlsec1 \ 24 | libxslt1.1 \ 25 | sudo \ 26 | node-less \ 27 | # python3-yaml \ 28 | && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 1 \ 29 | # && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 2 \ 30 | && update-alternatives --install /usr/bin/python python /usr/bin/python3.6 1 \ 31 | && update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 2 \ 32 | && curl -o wkhtmltox.deb -sSL https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb \ 33 | && apt-get install -y --no-install-recommends ./wkhtmltox.deb \ 34 | && rm -rf /var/lib/apt/lists/* wkhtmltox.deb 35 | 36 | # Install latest postgresql-client 37 | RUN set -x; \ 38 | echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > etc/apt/sources.list.d/pgdg.list \ 39 | && export GNUPGHOME="$(mktemp -d)" \ 40 | && repokey='B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8' \ 41 | && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \ 42 | && gpg --batch --armor --export "${repokey}" > /etc/apt/trusted.gpg.d/pgdg.gpg.asc \ 43 | && gpgconf --kill all \ 44 | && rm -rf "$GNUPGHOME" \ 45 | && apt-get update \ 46 | && apt-get install -y postgresql-client \ 47 | && rm -rf /var/lib/apt/lists/* 48 | 49 | ENV PATH=/usr/local/bin:$PATH 50 | # Install Odoo Including things from sources 51 | ENV ODOO_VERSION 11.0 52 | ENV ODOO_RELEASE=20200623 53 | ARG ODOO_ARCHIVE=odoo_${ODOO_VERSION}.${ODOO_RELEASE}.tar.gz 54 | RUN set -x; \ 55 | apt-get update \ 56 | && apt-get install -y --no-install-recommends \ 57 | build-essential \ 58 | python3.6-dev \ 59 | libsasl2-dev \ 60 | libldap2-dev ruby-sass \ 61 | libxml2-dev \ 62 | libxmlsec1-dev \ 63 | libxslt1-dev \ 64 | && pip install -U pip \ 65 | && /usr/bin/env pip install \ 66 | psycogreen \ 67 | \ 68 | && curl -o requirements.txt https://raw.githubusercontent.com/odoo/odoo/${ODOO_VERSION}/requirements.txt \ 69 | && pip install -r requirements.txt \ 70 | && /usr/bin/env pip install https://nightly.odoo.com/${ODOO_VERSION}/nightly/src/${ODOO_ARCHIVE} \ 71 | && cd / \ 72 | && apt-get --purge remove -y \ 73 | build-essential \ 74 | python3.6-dev \ 75 | libsasl2-dev \ 76 | libldap2-dev \ 77 | libxml2-dev \ 78 | libxmlsec1-dev \ 79 | libxslt1-dev \ 80 | && apt-get autoremove -y \ 81 | && rm -rf /var/lib/apt/lists/* 82 | 83 | VOLUME /etc/odoo 84 | VOLUME /var/lib/odoo 85 | 86 | COPY ./odoo.conf /etc/odoo/ 87 | COPY ./entrypoint.py / 88 | COPY ./sudo-entrypoint.py / 89 | COPY ./find_modules.py /scripts/find_modules.py 90 | COPY ./prepare_project.py /scripts/prepare_project.py 91 | 92 | ARG UID=1000 93 | ARG GID=1000 94 | 95 | RUN mkdir /addons \ 96 | && groupadd -r -g ${GID} odoo \ 97 | && useradd -r -u ${UID} -g odoo -b /var/lib -m odoo \ 98 | && chown odoo /etc/odoo/odoo.conf \ 99 | && chown -R odoo:odoo /addons \ 100 | && echo "odoo ALL=(ALL:ALL) NOPASSWD:SETENV: /sudo-entrypoint.py" >> /etc/sudoers 101 | 102 | ENV ODOO_RC /etc/odoo/odoo.conf 103 | ENV ODOO_BASE_PATH /usr/local/lib/python3.6/dist-packages/odoo/addons 104 | # Env variable defined to monitor the kind of service running 105 | # it could be a staging/production/test or anything and undefined 106 | # is the default in case we need to know servers that aren't correctly 107 | # defined 108 | ENV DEPLOYMENT_AREA undefined 109 | 110 | expose 8069 111 | expose 8071 112 | 113 | USER odoo 114 | 115 | LABEL version="11.0" 116 | LABEL release="20200623" 117 | 118 | LABEL org.opencontainers.image.created="2021-06-25T17:55:41.194840" 119 | LABEL org.opencontainers.image.url="https://hub.docker.com/r/llacroix/odoo" 120 | LABEL org.opencontainers.image.authors="Archeti " 121 | LABEL org.opencontainers.image.documentation="https://github.com/llacroix/odoo-docker" 122 | LABEL org.opencontainers.image.source="https://github.com/llacroix/odoo-docker" 123 | LABEL org.opencontainers.image.version="11.0" 124 | LABEL org.opencontainers.image.vendor="ArcheTI" 125 | LABEL org.opencontainers.image.ref.name="11.0" 126 | LABEL org.opencontainers.image.title="Odoo 11.0" 127 | LABEL org.opencontainers.image.description="Full featured odoo image that make odoo deployment fun and secure." 128 | 129 | ENTRYPOINT ["/entrypoint.py"] 130 | 131 | cmd ["odoo"] 132 | -------------------------------------------------------------------------------- /11.0/find_modules.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | from itertools import chain 4 | 5 | 6 | def get_parser(): 7 | parser = OptionParser() 8 | 9 | parser.add_option( 10 | "-p", 11 | dest='paths', 12 | action="append", 13 | help="Location in which to search", 14 | default=[] 15 | ) 16 | 17 | parser.add_option( 18 | "--only-name", 19 | dest="only_name", 20 | action="store_true", 21 | help="Only display module name instead of path", 22 | default=False, 23 | ) 24 | 25 | parser.add_option( 26 | '--csv', 27 | dest="is_csv", 28 | action="store_true", 29 | help="Output as a comma separated list", 30 | default=False 31 | ) 32 | 33 | return parser 34 | 35 | 36 | def find_modules(options, path): 37 | modules = set() 38 | 39 | path = Path.cwd() / path 40 | 41 | erp_manifest = '__openerp__.py' 42 | odoo_manifest = '__manifest__.py' 43 | 44 | manifest_globs = chain( 45 | path.glob('**/{}'.format(erp_manifest)), 46 | path.glob('**/{}'.format(odoo_manifest)), 47 | ) 48 | 49 | for path in manifest_globs: 50 | rel_path = path.parent.relative_to(Path.cwd()) 51 | if options.only_name: 52 | modules.add(rel_path.name) 53 | else: 54 | modules.add(str(rel_path)) 55 | 56 | return modules 57 | 58 | 59 | def main(options, args): 60 | modules = set() 61 | for path in options.paths: 62 | modules = modules.union(find_modules(options, path)) 63 | 64 | return modules 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = get_parser() 69 | (options, args) = parser.parse_args() 70 | modules = main(options, args) 71 | 72 | if not options.is_csv: 73 | for module in modules: 74 | print(module) 75 | else: 76 | print(",".join(modules), end="") 77 | -------------------------------------------------------------------------------- /11.0/odoo.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | addons_path = /mnt/extra-addons 3 | data_dir = /var/lib/odoo 4 | ; admin_passwd = admin 5 | ; csv_internal_sep = , 6 | ; db_maxconn = 64 7 | ; db_name = False 8 | ; db_template = template1 9 | ; dbfilter = .* 10 | ; debug_mode = False 11 | ; email_from = False 12 | ; limit_memory_hard = 2684354560 13 | ; limit_memory_soft = 2147483648 14 | ; limit_request = 8192 15 | ; limit_time_cpu = 60 16 | ; limit_time_real = 120 17 | ; list_db = True 18 | ; log_db = False 19 | ; log_handler = [':INFO'] 20 | ; log_level = info 21 | ; logfile = None 22 | ; longpolling_port = 8072 23 | ; max_cron_threads = 2 24 | ; osv_memory_age_limit = 1.0 25 | ; osv_memory_count_limit = False 26 | ; smtp_password = False 27 | ; smtp_port = 25 28 | ; smtp_server = localhost 29 | ; smtp_ssl = False 30 | ; smtp_user = False 31 | ; workers = 0 32 | ; xmlrpc = True 33 | ; xmlrpc_interface = 34 | ; xmlrpc_port = 8069 35 | ; xmlrpcs = True 36 | ; xmlrpcs_interface = 37 | ; xmlrpcs_port = 8071 38 | -------------------------------------------------------------------------------- /11.0/prepare_project.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | import toml 4 | from giturlparse import parse 5 | from contextlib import contextmanager 6 | import os 7 | from subprocess import run 8 | from urllib.parse import urlparse 9 | 10 | 11 | def get_services(services): 12 | return { 13 | service.get('name'): service 14 | for service in services.get('services') 15 | } 16 | 17 | 18 | def addons_by_project(options, addons): 19 | res = {} 20 | 21 | for addon in addons: 22 | if addon['url'] == 'self': 23 | if options.ignore_self: 24 | continue 25 | parsed = parse(options.url) 26 | else: 27 | parsed = parse(addon['url']) 28 | 29 | auth = parsed.protocol in ['git', 'ssh'] 30 | 31 | res[parsed.repo] = dict( 32 | addon, 33 | url=parsed.url2https, 34 | auth=auth 35 | ) 36 | 37 | return res 38 | 39 | 40 | def merge_addons(options, base, other): 41 | base_addons = addons_by_project(options, base) 42 | other_addons = addons_by_project(options, other) 43 | 44 | for name, addon in other_addons.items(): 45 | if name not in base_addons: 46 | base_addons[name] = addon 47 | else: 48 | base_addons[name] = dict(base_addons[name], **addon) 49 | 50 | return [ 51 | addon 52 | for addon in base_addons.values() 53 | ] 54 | 55 | 56 | def merge_services(options, base, other): 57 | basic_inherit = dict(base, **other) 58 | 59 | if base.get('addons') or other.get('addons'): 60 | basic_inherit['addons'] = merge_addons( 61 | options, 62 | base.get('addons', []), 63 | other.get('addons', []) 64 | ) 65 | 66 | return basic_inherit 67 | 68 | 69 | def compile_service(options, services, name): 70 | service = services.get(name, {}) 71 | if 'inherit' in service: 72 | merge_service = compile_service(options, services, service['inherit']) 73 | service = merge_services(options, merge_service, service) 74 | 75 | return service 76 | 77 | 78 | def get_parser(): 79 | parser = OptionParser() 80 | 81 | parser.add_option( 82 | '-f', 83 | '--file', 84 | dest="file", 85 | help="Input File" 86 | ) 87 | 88 | parser.add_option( 89 | '--url', 90 | dest='url', 91 | help="Url of self project" 92 | ) 93 | 94 | parser.add_option( 95 | '-o', 96 | '--output', 97 | dest="output_directory", 98 | help="Output Directory" 99 | ) 100 | 101 | parser.add_option( 102 | '-e', 103 | dest="env", 104 | help="Environment to prepare" 105 | ) 106 | 107 | parser.add_option( 108 | '--username', 109 | dest="username", 110 | help="Username to replace with", 111 | ) 112 | 113 | parser.add_option( 114 | '--password', 115 | dest="password", 116 | help="password to set on https urls" 117 | ) 118 | 119 | parser.add_option( 120 | '-b', 121 | '--branch', 122 | dest="branch", 123 | help="Default branch if no ref is defined" 124 | ) 125 | 126 | parser.add_option( 127 | '--ignore-self', 128 | dest="ignore_self", 129 | action="store_true", 130 | help="Ignore self url as it's already fetched", 131 | default=False 132 | ) 133 | 134 | return parser 135 | 136 | 137 | @contextmanager 138 | def cd(directory): 139 | cwd = Path.cwd() 140 | try: 141 | os.chdir(directory) 142 | yield 143 | finally: 144 | os.chdir(cwd) 145 | 146 | 147 | def fetch_addons(options, addon): 148 | parsed = parse(addon['url']) 149 | 150 | url = urlparse(parsed.url2https) 151 | 152 | if addon['auth'] and options.username and options.password: 153 | url = url._replace( 154 | netloc="{}:{}@{}".format( 155 | options.username, 156 | options.password, 157 | url.netloc 158 | ) 159 | ) 160 | 161 | repo_path = Path.cwd() / options.output_directory / parsed.repo 162 | 163 | repo_path.mkdir(exist_ok=True) 164 | 165 | with cd(repo_path): 166 | run(['git', 'init']) 167 | run(['git', 'remote', 'add', 'origin', url.geturl()]) 168 | 169 | ref = addon.get('commit') or addon.get('branch') or options.branch 170 | 171 | if ref: 172 | run(['git', 'fetch', 'origin', ref]) 173 | else: 174 | run(['git', 'fetch', 'origin']) 175 | 176 | run(['git', 'checkout', 'FETCH_HEAD']) 177 | run(['git', 'remote', 'remove', 'origin']) 178 | 179 | 180 | def main(options, args): 181 | with Path(options.file).open('r') as fin: 182 | services = toml.loads(fin.read()) 183 | 184 | by_name = get_services(services) 185 | 186 | outputdir = Path(options.output_directory) 187 | outputdir.mkdir(exist_ok=True) 188 | 189 | service = compile_service(options, by_name, options.env) 190 | 191 | for addons in service.get('addons', []): 192 | fetch_addons(options, addons) 193 | 194 | 195 | if __name__ == '__main__': 196 | parser = get_parser() 197 | (options, args) = parser.parse_args() 198 | main(options, args) 199 | -------------------------------------------------------------------------------- /11.0/sudo-entrypoint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import time 4 | import os 5 | import shlex 6 | import subprocess 7 | import sys 8 | import glob 9 | import pip 10 | import re 11 | import stat 12 | from os import path 13 | from os.path import expanduser 14 | import shutil 15 | 16 | try: 17 | from pathlib import Path 18 | except ImportError: 19 | from pathlib2 import Path 20 | 21 | 22 | def pipe(args): 23 | """ 24 | Call the process with std(in,out,err) 25 | """ 26 | env = os.environ.copy() 27 | env['DEBIAN_FRONTEND'] = 'noninteractive' 28 | 29 | process = subprocess.Popen( 30 | args, 31 | stdin=sys.stdin, 32 | stdout=sys.stdout, 33 | stderr=sys.stderr, 34 | env=env 35 | ) 36 | 37 | process.wait() 38 | 39 | return process.returncode 40 | 41 | 42 | def get_dirs(cur_path): 43 | return [ 44 | path.join(cur_path, npath) 45 | for npath in os.listdir(cur_path) 46 | if path.isdir(path.join(cur_path, npath)) 47 | ] 48 | 49 | 50 | def get_extra_paths(): 51 | extra_paths = os.environ.get('ODOO_EXTRA_PATHS') 52 | 53 | if not extra_paths: 54 | return [] 55 | 56 | return [ 57 | extra_path.strip() 58 | for extra_path in extra_paths.split(',') 59 | ] 60 | 61 | 62 | def get_addons_paths(): 63 | addons = get_dirs('/addons') 64 | addons += get_extra_paths() 65 | 66 | return [ 67 | Path(path) 68 | for path in addons 69 | ] 70 | 71 | 72 | def install_apt_packages(): 73 | """ 74 | Install debian dependencies. 75 | """ 76 | package_list = set() 77 | 78 | paths = get_addons_paths() 79 | 80 | print("Looking up for packages in {}".format(paths)) 81 | 82 | for addons_path in paths: 83 | for packages in addons_path.glob('**/apt-packages.txt'): 84 | print("Installing packages from %s" % packages) 85 | with open(packages, 'r') as pack_file: 86 | lines = [line.strip() for line in pack_file] 87 | package_list.update(set(lines)) 88 | 89 | extras = os.environ.get('EXTRA_APT_PACKAGES', '') 90 | print(f"Adding extra packages {extras}") 91 | if extras: 92 | for package in extras.split(','): 93 | if not package: 94 | continue 95 | package_list.add(package) 96 | 97 | if len(package_list) > 0: 98 | print(f"Installing {package_list}") 99 | ret = pipe(['apt-get', 'update']) 100 | 101 | # Something went wrong, stop the service as it's failing 102 | if ret != 0: 103 | sys.exit(ret) 104 | 105 | ret = pipe(['apt-get', 'install', '-y'] + list(package_list)) 106 | 107 | # Something went wrong, stop the service as it's failing 108 | if ret != 0: 109 | sys.exit(ret) 110 | 111 | 112 | def load_secrets(): 113 | # TODO add a way to load some secrets so odoo process can 114 | # use secrets as a way to load passwords/user for postgresql 115 | # credentials could also be stored in the HOME of the odoo user 116 | # except we cannot rely on secrets 100% because it only works in 117 | # swarm mode 118 | pgpass_secret = '/run/secrets/.pgpass' 119 | if path.exists(pgpass_secret): 120 | home_folder = '/var/lib/odoo' 121 | pgpass_target = path.join(home_folder, '.pgpass') 122 | if path.exists(pgpass_target): 123 | os.remove(pgpass_target) 124 | # shutil.move doesn't always work correctly on different fs 125 | shutil.copy(pgpass_secret, home_folder) 126 | st = os.stat(pgpass_secret) 127 | os.chmod(pgpass_target, st.st_mode) 128 | os.chown(pgpass_target, st[stat.ST_UID], st[stat.ST_GID]) 129 | # Cannot remove anymore apparently 130 | # os.remove(pgpass_secret) 131 | # shutil.move(pgpass_secret, home_folder) 132 | 133 | 134 | def disable_base_modules(): 135 | base_addons = os.environ.get('ODOO_BASE_PATH', '') 136 | addons_to_remove = os.environ.get('ODOO_DISABLED_MODULES', '') 137 | 138 | modules = addons_to_remove.split(',') 139 | modules = map(lambda mod: mod.strip(), modules) 140 | 141 | if not base_addons: 142 | print("Do not attempt to remove wrong folder") 143 | return 144 | 145 | for module in modules: 146 | if not module: 147 | continue 148 | print("Removing module %s from %s" % (module, base_addons)) 149 | 150 | module_path = Path(base_addons, module) 151 | if module_path.exists() and module_path.is_dir(): 152 | shutil.rmtree(module_path) 153 | else: 154 | print("Module skipped as it doesn't seem to be present.") 155 | 156 | 157 | def fix_access_rights(): 158 | if os.environ.get('RESET_ACCESS_RIGHTS', '') == 'TRUE': 159 | pipe(["chown", "-R", "odoo:odoo", "/var/lib/odoo"]) 160 | pipe(["chown", "-R", "odoo:odoo", "/etc/odoo"]) 161 | 162 | 163 | def remove_sudo(): 164 | return pipe(["sed", "-i", "/odoo/d", "/etc/sudoers"]) 165 | 166 | 167 | def main(): 168 | install_apt_packages() 169 | load_secrets() 170 | fix_access_rights() 171 | disable_base_modules() 172 | return remove_sudo() 173 | 174 | 175 | try: 176 | code = main() 177 | sys.exit(code) 178 | except Exception as exc: 179 | print(exc) 180 | sys.exit(1) 181 | except KeyboardInterrupt as exc: 182 | print(exc) 183 | sys.exit(1) 184 | -------------------------------------------------------------------------------- /12.0-nightly/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | LABEL maintainer="Archeti " 3 | 4 | # Generate locale C.UTF-8 for postgres and general locale data 5 | ENV LANG C.UTF-8 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | 8 | # Install some dependencies python3.7 9 | RUN set -x; \ 10 | apt-get update \ 11 | && apt-get install -y --no-install-recommends \ 12 | python3-wheel \ 13 | python3-setuptools \ 14 | python3-pip \ 15 | # python3.7 \ 16 | # libpython3.7 \ 17 | curl \ 18 | gnupg \ 19 | libpq-dev \ 20 | libsasl2-2 \ 21 | libldap-2.4-2 \ 22 | libxml2 \ 23 | libxmlsec1 \ 24 | libxslt1.1 \ 25 | sudo \ 26 | node-less \ 27 | # python3-yaml \ 28 | && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 1 \ 29 | # && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 2 \ 30 | && update-alternatives --install /usr/bin/python python /usr/bin/python3.6 1 \ 31 | && update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 2 \ 32 | && curl -o wkhtmltox.deb -sSL https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb \ 33 | && apt-get install -y --no-install-recommends ./wkhtmltox.deb \ 34 | && rm -rf /var/lib/apt/lists/* wkhtmltox.deb 35 | 36 | # Install latest postgresql-client 37 | RUN set -x; \ 38 | echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > etc/apt/sources.list.d/pgdg.list \ 39 | && export GNUPGHOME="$(mktemp -d)" \ 40 | && repokey='B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8' \ 41 | && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \ 42 | && gpg --batch --armor --export "${repokey}" > /etc/apt/trusted.gpg.d/pgdg.gpg.asc \ 43 | && gpgconf --kill all \ 44 | && rm -rf "$GNUPGHOME" \ 45 | && apt-get update \ 46 | && apt-get install -y postgresql-client \ 47 | && rm -rf /var/lib/apt/lists/* 48 | 49 | ENV PATH=/usr/local/bin:$PATH 50 | # Install Odoo Including things from sources 51 | ENV ODOO_VERSION 12.0 52 | ENV ODOO_RELEASE=20210625 53 | ARG ODOO_ARCHIVE=odoo_${ODOO_VERSION}.${ODOO_RELEASE}.tar.gz 54 | RUN set -x; \ 55 | apt-get update \ 56 | && apt-get install -y --no-install-recommends \ 57 | build-essential \ 58 | python3.6-dev \ 59 | libsasl2-dev \ 60 | libldap2-dev \ 61 | libxml2-dev \ 62 | libxmlsec1-dev \ 63 | libxslt1-dev \ 64 | && pip install -U pip \ 65 | && /usr/bin/env pip install \ 66 | psycogreen \ 67 | \ 68 | && curl -o requirements.txt https://raw.githubusercontent.com/odoo/odoo/${ODOO_VERSION}/requirements.txt \ 69 | && pip install -r requirements.txt \ 70 | && /usr/bin/env pip install https://nightly.odoo.com/${ODOO_VERSION}/nightly/src/${ODOO_ARCHIVE} \ 71 | && cd / \ 72 | && apt-get --purge remove -y \ 73 | build-essential \ 74 | python3.6-dev \ 75 | libsasl2-dev \ 76 | libldap2-dev \ 77 | libxml2-dev \ 78 | libxmlsec1-dev \ 79 | libxslt1-dev \ 80 | && apt-get autoremove -y \ 81 | && rm -rf /var/lib/apt/lists/* 82 | 83 | VOLUME /etc/odoo 84 | VOLUME /var/lib/odoo 85 | 86 | COPY ./odoo.conf /etc/odoo/ 87 | COPY ./entrypoint.py / 88 | COPY ./sudo-entrypoint.py / 89 | COPY ./find_modules.py /scripts/find_modules.py 90 | COPY ./prepare_project.py /scripts/prepare_project.py 91 | 92 | ARG UID=1000 93 | ARG GID=1000 94 | 95 | RUN mkdir /addons \ 96 | && groupadd -r -g ${GID} odoo \ 97 | && useradd -r -u ${UID} -g odoo -b /var/lib -m odoo \ 98 | && chown odoo /etc/odoo/odoo.conf \ 99 | && chown -R odoo:odoo /addons \ 100 | && echo "odoo ALL=(ALL:ALL) NOPASSWD:SETENV: /sudo-entrypoint.py" >> /etc/sudoers 101 | 102 | ENV ODOO_RC /etc/odoo/odoo.conf 103 | ENV ODOO_BASE_PATH /usr/local/lib/python3.6/dist-packages/odoo/addons 104 | # Env variable defined to monitor the kind of service running 105 | # it could be a staging/production/test or anything and undefined 106 | # is the default in case we need to know servers that aren't correctly 107 | # defined 108 | ENV DEPLOYMENT_AREA undefined 109 | 110 | expose 8069 111 | expose 8071 112 | 113 | USER odoo 114 | 115 | LABEL version="12.0" 116 | LABEL release="20210625" 117 | 118 | LABEL org.opencontainers.image.created="2021-06-25T17:55:41.200370" 119 | LABEL org.opencontainers.image.url="https://hub.docker.com/r/llacroix/odoo" 120 | LABEL org.opencontainers.image.authors="Archeti " 121 | LABEL org.opencontainers.image.documentation="https://github.com/llacroix/odoo-docker" 122 | LABEL org.opencontainers.image.source="https://github.com/llacroix/odoo-docker" 123 | LABEL org.opencontainers.image.version="12.0" 124 | LABEL org.opencontainers.image.vendor="ArcheTI" 125 | LABEL org.opencontainers.image.ref.name="12.0-nightly" 126 | LABEL org.opencontainers.image.title="Odoo 12.0" 127 | LABEL org.opencontainers.image.description="Full featured odoo image that make odoo deployment fun and secure." 128 | 129 | ENTRYPOINT ["/entrypoint.py"] 130 | 131 | cmd ["odoo"] 132 | -------------------------------------------------------------------------------- /12.0-nightly/find_modules.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | from itertools import chain 4 | 5 | 6 | def get_parser(): 7 | parser = OptionParser() 8 | 9 | parser.add_option( 10 | "-p", 11 | dest='paths', 12 | action="append", 13 | help="Location in which to search", 14 | default=[] 15 | ) 16 | 17 | parser.add_option( 18 | "--only-name", 19 | dest="only_name", 20 | action="store_true", 21 | help="Only display module name instead of path", 22 | default=False, 23 | ) 24 | 25 | parser.add_option( 26 | '--csv', 27 | dest="is_csv", 28 | action="store_true", 29 | help="Output as a comma separated list", 30 | default=False 31 | ) 32 | 33 | return parser 34 | 35 | 36 | def find_modules(options, path): 37 | modules = set() 38 | 39 | path = Path.cwd() / path 40 | 41 | erp_manifest = '__openerp__.py' 42 | odoo_manifest = '__manifest__.py' 43 | 44 | manifest_globs = chain( 45 | path.glob('**/{}'.format(erp_manifest)), 46 | path.glob('**/{}'.format(odoo_manifest)), 47 | ) 48 | 49 | for path in manifest_globs: 50 | rel_path = path.parent.relative_to(Path.cwd()) 51 | if options.only_name: 52 | modules.add(rel_path.name) 53 | else: 54 | modules.add(str(rel_path)) 55 | 56 | return modules 57 | 58 | 59 | def main(options, args): 60 | modules = set() 61 | for path in options.paths: 62 | modules = modules.union(find_modules(options, path)) 63 | 64 | return modules 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = get_parser() 69 | (options, args) = parser.parse_args() 70 | modules = main(options, args) 71 | 72 | if not options.is_csv: 73 | for module in modules: 74 | print(module) 75 | else: 76 | print(",".join(modules), end="") 77 | -------------------------------------------------------------------------------- /12.0-nightly/odoo.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | addons_path = /mnt/extra-addons 3 | data_dir = /var/lib/odoo 4 | ; admin_passwd = admin 5 | ; csv_internal_sep = , 6 | ; db_maxconn = 64 7 | ; db_name = False 8 | ; db_template = template1 9 | ; dbfilter = .* 10 | ; debug_mode = False 11 | ; email_from = False 12 | ; limit_memory_hard = 2684354560 13 | ; limit_memory_soft = 2147483648 14 | ; limit_request = 8192 15 | ; limit_time_cpu = 60 16 | ; limit_time_real = 120 17 | ; list_db = True 18 | ; log_db = False 19 | ; log_handler = [':INFO'] 20 | ; log_level = info 21 | ; logfile = None 22 | ; longpolling_port = 8072 23 | ; max_cron_threads = 2 24 | ; osv_memory_age_limit = 1.0 25 | ; osv_memory_count_limit = False 26 | ; smtp_password = False 27 | ; smtp_port = 25 28 | ; smtp_server = localhost 29 | ; smtp_ssl = False 30 | ; smtp_user = False 31 | ; workers = 0 32 | ; xmlrpc = True 33 | ; xmlrpc_interface = 34 | ; xmlrpc_port = 8069 35 | ; xmlrpcs = True 36 | ; xmlrpcs_interface = 37 | ; xmlrpcs_port = 8071 38 | -------------------------------------------------------------------------------- /12.0-nightly/prepare_project.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | import toml 4 | from giturlparse import parse 5 | from contextlib import contextmanager 6 | import os 7 | from subprocess import run 8 | from urllib.parse import urlparse 9 | 10 | 11 | def get_services(services): 12 | return { 13 | service.get('name'): service 14 | for service in services.get('services') 15 | } 16 | 17 | 18 | def addons_by_project(options, addons): 19 | res = {} 20 | 21 | for addon in addons: 22 | if addon['url'] == 'self': 23 | if options.ignore_self: 24 | continue 25 | parsed = parse(options.url) 26 | else: 27 | parsed = parse(addon['url']) 28 | 29 | auth = parsed.protocol in ['git', 'ssh'] 30 | 31 | res[parsed.repo] = dict( 32 | addon, 33 | url=parsed.url2https, 34 | auth=auth 35 | ) 36 | 37 | return res 38 | 39 | 40 | def merge_addons(options, base, other): 41 | base_addons = addons_by_project(options, base) 42 | other_addons = addons_by_project(options, other) 43 | 44 | for name, addon in other_addons.items(): 45 | if name not in base_addons: 46 | base_addons[name] = addon 47 | else: 48 | base_addons[name] = dict(base_addons[name], **addon) 49 | 50 | return [ 51 | addon 52 | for addon in base_addons.values() 53 | ] 54 | 55 | 56 | def merge_services(options, base, other): 57 | basic_inherit = dict(base, **other) 58 | 59 | if base.get('addons') or other.get('addons'): 60 | basic_inherit['addons'] = merge_addons( 61 | options, 62 | base.get('addons', []), 63 | other.get('addons', []) 64 | ) 65 | 66 | return basic_inherit 67 | 68 | 69 | def compile_service(options, services, name): 70 | service = services.get(name, {}) 71 | if 'inherit' in service: 72 | merge_service = compile_service(options, services, service['inherit']) 73 | service = merge_services(options, merge_service, service) 74 | 75 | return service 76 | 77 | 78 | def get_parser(): 79 | parser = OptionParser() 80 | 81 | parser.add_option( 82 | '-f', 83 | '--file', 84 | dest="file", 85 | help="Input File" 86 | ) 87 | 88 | parser.add_option( 89 | '--url', 90 | dest='url', 91 | help="Url of self project" 92 | ) 93 | 94 | parser.add_option( 95 | '-o', 96 | '--output', 97 | dest="output_directory", 98 | help="Output Directory" 99 | ) 100 | 101 | parser.add_option( 102 | '-e', 103 | dest="env", 104 | help="Environment to prepare" 105 | ) 106 | 107 | parser.add_option( 108 | '--username', 109 | dest="username", 110 | help="Username to replace with", 111 | ) 112 | 113 | parser.add_option( 114 | '--password', 115 | dest="password", 116 | help="password to set on https urls" 117 | ) 118 | 119 | parser.add_option( 120 | '-b', 121 | '--branch', 122 | dest="branch", 123 | help="Default branch if no ref is defined" 124 | ) 125 | 126 | parser.add_option( 127 | '--ignore-self', 128 | dest="ignore_self", 129 | action="store_true", 130 | help="Ignore self url as it's already fetched", 131 | default=False 132 | ) 133 | 134 | return parser 135 | 136 | 137 | @contextmanager 138 | def cd(directory): 139 | cwd = Path.cwd() 140 | try: 141 | os.chdir(directory) 142 | yield 143 | finally: 144 | os.chdir(cwd) 145 | 146 | 147 | def fetch_addons(options, addon): 148 | parsed = parse(addon['url']) 149 | 150 | url = urlparse(parsed.url2https) 151 | 152 | if addon['auth'] and options.username and options.password: 153 | url = url._replace( 154 | netloc="{}:{}@{}".format( 155 | options.username, 156 | options.password, 157 | url.netloc 158 | ) 159 | ) 160 | 161 | repo_path = Path.cwd() / options.output_directory / parsed.repo 162 | 163 | repo_path.mkdir(exist_ok=True) 164 | 165 | with cd(repo_path): 166 | run(['git', 'init']) 167 | run(['git', 'remote', 'add', 'origin', url.geturl()]) 168 | 169 | ref = addon.get('commit') or addon.get('branch') or options.branch 170 | 171 | if ref: 172 | run(['git', 'fetch', 'origin', ref]) 173 | else: 174 | run(['git', 'fetch', 'origin']) 175 | 176 | run(['git', 'checkout', 'FETCH_HEAD']) 177 | run(['git', 'remote', 'remove', 'origin']) 178 | 179 | 180 | def main(options, args): 181 | with Path(options.file).open('r') as fin: 182 | services = toml.loads(fin.read()) 183 | 184 | by_name = get_services(services) 185 | 186 | outputdir = Path(options.output_directory) 187 | outputdir.mkdir(exist_ok=True) 188 | 189 | service = compile_service(options, by_name, options.env) 190 | 191 | for addons in service.get('addons', []): 192 | fetch_addons(options, addons) 193 | 194 | 195 | if __name__ == '__main__': 196 | parser = get_parser() 197 | (options, args) = parser.parse_args() 198 | main(options, args) 199 | -------------------------------------------------------------------------------- /12.0/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | LABEL maintainer="Archeti " 3 | 4 | # Generate locale C.UTF-8 for postgres and general locale data 5 | ENV LANG C.UTF-8 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | 8 | # Install some dependencies python3.7 9 | RUN set -x; \ 10 | apt-get update \ 11 | && apt-get install -y --no-install-recommends \ 12 | python3-wheel \ 13 | python3-setuptools \ 14 | python3-pip \ 15 | # python3.7 \ 16 | # libpython3.7 \ 17 | curl \ 18 | gnupg \ 19 | libpq-dev \ 20 | libsasl2-2 \ 21 | libldap-2.4-2 \ 22 | libxml2 \ 23 | libxmlsec1 \ 24 | libxslt1.1 \ 25 | sudo \ 26 | node-less \ 27 | # python3-yaml \ 28 | && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 1 \ 29 | # && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 2 \ 30 | && update-alternatives --install /usr/bin/python python /usr/bin/python3.6 1 \ 31 | && update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 2 \ 32 | && curl -o wkhtmltox.deb -sSL https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb \ 33 | && apt-get install -y --no-install-recommends ./wkhtmltox.deb \ 34 | && rm -rf /var/lib/apt/lists/* wkhtmltox.deb 35 | 36 | # Install latest postgresql-client 37 | RUN set -x; \ 38 | echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > etc/apt/sources.list.d/pgdg.list \ 39 | && export GNUPGHOME="$(mktemp -d)" \ 40 | && repokey='B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8' \ 41 | && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \ 42 | && gpg --batch --armor --export "${repokey}" > /etc/apt/trusted.gpg.d/pgdg.gpg.asc \ 43 | && gpgconf --kill all \ 44 | && rm -rf "$GNUPGHOME" \ 45 | && apt-get update \ 46 | && apt-get install -y postgresql-client \ 47 | && rm -rf /var/lib/apt/lists/* 48 | 49 | ENV PATH=/usr/local/bin:$PATH 50 | # Install Odoo Including things from sources 51 | ENV ODOO_VERSION 12.0 52 | ENV ODOO_RELEASE=20200623 53 | ARG ODOO_ARCHIVE=odoo_${ODOO_VERSION}.${ODOO_RELEASE}.tar.gz 54 | RUN set -x; \ 55 | apt-get update \ 56 | && apt-get install -y --no-install-recommends \ 57 | build-essential \ 58 | python3.6-dev \ 59 | libsasl2-dev \ 60 | libldap2-dev \ 61 | libxml2-dev \ 62 | libxmlsec1-dev \ 63 | libxslt1-dev \ 64 | && pip install -U pip \ 65 | && /usr/bin/env pip install \ 66 | psycogreen \ 67 | \ 68 | && curl -o requirements.txt https://raw.githubusercontent.com/odoo/odoo/${ODOO_VERSION}/requirements.txt \ 69 | && pip install -r requirements.txt \ 70 | && /usr/bin/env pip install https://nightly.odoo.com/${ODOO_VERSION}/nightly/src/${ODOO_ARCHIVE} \ 71 | && cd / \ 72 | && apt-get --purge remove -y \ 73 | build-essential \ 74 | python3.6-dev \ 75 | libsasl2-dev \ 76 | libldap2-dev \ 77 | libxml2-dev \ 78 | libxmlsec1-dev \ 79 | libxslt1-dev \ 80 | && apt-get autoremove -y \ 81 | && rm -rf /var/lib/apt/lists/* 82 | 83 | VOLUME /etc/odoo 84 | VOLUME /var/lib/odoo 85 | 86 | COPY ./odoo.conf /etc/odoo/ 87 | COPY ./entrypoint.py / 88 | COPY ./sudo-entrypoint.py / 89 | COPY ./find_modules.py /scripts/find_modules.py 90 | COPY ./prepare_project.py /scripts/prepare_project.py 91 | 92 | ARG UID=1000 93 | ARG GID=1000 94 | 95 | RUN mkdir /addons \ 96 | && groupadd -r -g ${GID} odoo \ 97 | && useradd -r -u ${UID} -g odoo -b /var/lib -m odoo \ 98 | && chown odoo /etc/odoo/odoo.conf \ 99 | && chown -R odoo:odoo /addons \ 100 | && echo "odoo ALL=(ALL:ALL) NOPASSWD:SETENV: /sudo-entrypoint.py" >> /etc/sudoers 101 | 102 | ENV ODOO_RC /etc/odoo/odoo.conf 103 | ENV ODOO_BASE_PATH /usr/local/lib/python3.6/dist-packages/odoo/addons 104 | # Env variable defined to monitor the kind of service running 105 | # it could be a staging/production/test or anything and undefined 106 | # is the default in case we need to know servers that aren't correctly 107 | # defined 108 | ENV DEPLOYMENT_AREA undefined 109 | 110 | expose 8069 111 | expose 8071 112 | 113 | USER odoo 114 | 115 | LABEL version="12.0" 116 | LABEL release="20200623" 117 | 118 | LABEL org.opencontainers.image.created="2021-06-25T17:55:41.195800" 119 | LABEL org.opencontainers.image.url="https://hub.docker.com/r/llacroix/odoo" 120 | LABEL org.opencontainers.image.authors="Archeti " 121 | LABEL org.opencontainers.image.documentation="https://github.com/llacroix/odoo-docker" 122 | LABEL org.opencontainers.image.source="https://github.com/llacroix/odoo-docker" 123 | LABEL org.opencontainers.image.version="12.0" 124 | LABEL org.opencontainers.image.vendor="ArcheTI" 125 | LABEL org.opencontainers.image.ref.name="12.0" 126 | LABEL org.opencontainers.image.title="Odoo 12.0" 127 | LABEL org.opencontainers.image.description="Full featured odoo image that make odoo deployment fun and secure." 128 | 129 | ENTRYPOINT ["/entrypoint.py"] 130 | 131 | cmd ["odoo"] 132 | -------------------------------------------------------------------------------- /12.0/find_modules.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | from itertools import chain 4 | 5 | 6 | def get_parser(): 7 | parser = OptionParser() 8 | 9 | parser.add_option( 10 | "-p", 11 | dest='paths', 12 | action="append", 13 | help="Location in which to search", 14 | default=[] 15 | ) 16 | 17 | parser.add_option( 18 | "--only-name", 19 | dest="only_name", 20 | action="store_true", 21 | help="Only display module name instead of path", 22 | default=False, 23 | ) 24 | 25 | parser.add_option( 26 | '--csv', 27 | dest="is_csv", 28 | action="store_true", 29 | help="Output as a comma separated list", 30 | default=False 31 | ) 32 | 33 | return parser 34 | 35 | 36 | def find_modules(options, path): 37 | modules = set() 38 | 39 | path = Path.cwd() / path 40 | 41 | erp_manifest = '__openerp__.py' 42 | odoo_manifest = '__manifest__.py' 43 | 44 | manifest_globs = chain( 45 | path.glob('**/{}'.format(erp_manifest)), 46 | path.glob('**/{}'.format(odoo_manifest)), 47 | ) 48 | 49 | for path in manifest_globs: 50 | rel_path = path.parent.relative_to(Path.cwd()) 51 | if options.only_name: 52 | modules.add(rel_path.name) 53 | else: 54 | modules.add(str(rel_path)) 55 | 56 | return modules 57 | 58 | 59 | def main(options, args): 60 | modules = set() 61 | for path in options.paths: 62 | modules = modules.union(find_modules(options, path)) 63 | 64 | return modules 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = get_parser() 69 | (options, args) = parser.parse_args() 70 | modules = main(options, args) 71 | 72 | if not options.is_csv: 73 | for module in modules: 74 | print(module) 75 | else: 76 | print(",".join(modules), end="") 77 | -------------------------------------------------------------------------------- /12.0/odoo.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | addons_path = /mnt/extra-addons 3 | data_dir = /var/lib/odoo 4 | ; admin_passwd = admin 5 | ; csv_internal_sep = , 6 | ; db_maxconn = 64 7 | ; db_name = False 8 | ; db_template = template1 9 | ; dbfilter = .* 10 | ; debug_mode = False 11 | ; email_from = False 12 | ; limit_memory_hard = 2684354560 13 | ; limit_memory_soft = 2147483648 14 | ; limit_request = 8192 15 | ; limit_time_cpu = 60 16 | ; limit_time_real = 120 17 | ; list_db = True 18 | ; log_db = False 19 | ; log_handler = [':INFO'] 20 | ; log_level = info 21 | ; logfile = None 22 | ; longpolling_port = 8072 23 | ; max_cron_threads = 2 24 | ; osv_memory_age_limit = 1.0 25 | ; osv_memory_count_limit = False 26 | ; smtp_password = False 27 | ; smtp_port = 25 28 | ; smtp_server = localhost 29 | ; smtp_ssl = False 30 | ; smtp_user = False 31 | ; workers = 0 32 | ; xmlrpc = True 33 | ; xmlrpc_interface = 34 | ; xmlrpc_port = 8069 35 | ; xmlrpcs = True 36 | ; xmlrpcs_interface = 37 | ; xmlrpcs_port = 8071 38 | -------------------------------------------------------------------------------- /12.0/prepare_project.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | import toml 4 | from giturlparse import parse 5 | from contextlib import contextmanager 6 | import os 7 | from subprocess import run 8 | from urllib.parse import urlparse 9 | 10 | 11 | def get_services(services): 12 | return { 13 | service.get('name'): service 14 | for service in services.get('services') 15 | } 16 | 17 | 18 | def addons_by_project(options, addons): 19 | res = {} 20 | 21 | for addon in addons: 22 | if addon['url'] == 'self': 23 | if options.ignore_self: 24 | continue 25 | parsed = parse(options.url) 26 | else: 27 | parsed = parse(addon['url']) 28 | 29 | auth = parsed.protocol in ['git', 'ssh'] 30 | 31 | res[parsed.repo] = dict( 32 | addon, 33 | url=parsed.url2https, 34 | auth=auth 35 | ) 36 | 37 | return res 38 | 39 | 40 | def merge_addons(options, base, other): 41 | base_addons = addons_by_project(options, base) 42 | other_addons = addons_by_project(options, other) 43 | 44 | for name, addon in other_addons.items(): 45 | if name not in base_addons: 46 | base_addons[name] = addon 47 | else: 48 | base_addons[name] = dict(base_addons[name], **addon) 49 | 50 | return [ 51 | addon 52 | for addon in base_addons.values() 53 | ] 54 | 55 | 56 | def merge_services(options, base, other): 57 | basic_inherit = dict(base, **other) 58 | 59 | if base.get('addons') or other.get('addons'): 60 | basic_inherit['addons'] = merge_addons( 61 | options, 62 | base.get('addons', []), 63 | other.get('addons', []) 64 | ) 65 | 66 | return basic_inherit 67 | 68 | 69 | def compile_service(options, services, name): 70 | service = services.get(name, {}) 71 | if 'inherit' in service: 72 | merge_service = compile_service(options, services, service['inherit']) 73 | service = merge_services(options, merge_service, service) 74 | 75 | return service 76 | 77 | 78 | def get_parser(): 79 | parser = OptionParser() 80 | 81 | parser.add_option( 82 | '-f', 83 | '--file', 84 | dest="file", 85 | help="Input File" 86 | ) 87 | 88 | parser.add_option( 89 | '--url', 90 | dest='url', 91 | help="Url of self project" 92 | ) 93 | 94 | parser.add_option( 95 | '-o', 96 | '--output', 97 | dest="output_directory", 98 | help="Output Directory" 99 | ) 100 | 101 | parser.add_option( 102 | '-e', 103 | dest="env", 104 | help="Environment to prepare" 105 | ) 106 | 107 | parser.add_option( 108 | '--username', 109 | dest="username", 110 | help="Username to replace with", 111 | ) 112 | 113 | parser.add_option( 114 | '--password', 115 | dest="password", 116 | help="password to set on https urls" 117 | ) 118 | 119 | parser.add_option( 120 | '-b', 121 | '--branch', 122 | dest="branch", 123 | help="Default branch if no ref is defined" 124 | ) 125 | 126 | parser.add_option( 127 | '--ignore-self', 128 | dest="ignore_self", 129 | action="store_true", 130 | help="Ignore self url as it's already fetched", 131 | default=False 132 | ) 133 | 134 | return parser 135 | 136 | 137 | @contextmanager 138 | def cd(directory): 139 | cwd = Path.cwd() 140 | try: 141 | os.chdir(directory) 142 | yield 143 | finally: 144 | os.chdir(cwd) 145 | 146 | 147 | def fetch_addons(options, addon): 148 | parsed = parse(addon['url']) 149 | 150 | url = urlparse(parsed.url2https) 151 | 152 | if addon['auth'] and options.username and options.password: 153 | url = url._replace( 154 | netloc="{}:{}@{}".format( 155 | options.username, 156 | options.password, 157 | url.netloc 158 | ) 159 | ) 160 | 161 | repo_path = Path.cwd() / options.output_directory / parsed.repo 162 | 163 | repo_path.mkdir(exist_ok=True) 164 | 165 | with cd(repo_path): 166 | run(['git', 'init']) 167 | run(['git', 'remote', 'add', 'origin', url.geturl()]) 168 | 169 | ref = addon.get('commit') or addon.get('branch') or options.branch 170 | 171 | if ref: 172 | run(['git', 'fetch', 'origin', ref]) 173 | else: 174 | run(['git', 'fetch', 'origin']) 175 | 176 | run(['git', 'checkout', 'FETCH_HEAD']) 177 | run(['git', 'remote', 'remove', 'origin']) 178 | 179 | 180 | def main(options, args): 181 | with Path(options.file).open('r') as fin: 182 | services = toml.loads(fin.read()) 183 | 184 | by_name = get_services(services) 185 | 186 | outputdir = Path(options.output_directory) 187 | outputdir.mkdir(exist_ok=True) 188 | 189 | service = compile_service(options, by_name, options.env) 190 | 191 | for addons in service.get('addons', []): 192 | fetch_addons(options, addons) 193 | 194 | 195 | if __name__ == '__main__': 196 | parser = get_parser() 197 | (options, args) = parser.parse_args() 198 | main(options, args) 199 | -------------------------------------------------------------------------------- /12.0/sudo-entrypoint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import time 4 | import os 5 | import shlex 6 | import subprocess 7 | import sys 8 | import glob 9 | import pip 10 | import re 11 | import stat 12 | from os import path 13 | from os.path import expanduser 14 | import shutil 15 | 16 | try: 17 | from pathlib import Path 18 | except ImportError: 19 | from pathlib2 import Path 20 | 21 | 22 | def pipe(args): 23 | """ 24 | Call the process with std(in,out,err) 25 | """ 26 | env = os.environ.copy() 27 | env['DEBIAN_FRONTEND'] = 'noninteractive' 28 | 29 | process = subprocess.Popen( 30 | args, 31 | stdin=sys.stdin, 32 | stdout=sys.stdout, 33 | stderr=sys.stderr, 34 | env=env 35 | ) 36 | 37 | process.wait() 38 | 39 | return process.returncode 40 | 41 | 42 | def get_dirs(cur_path): 43 | return [ 44 | path.join(cur_path, npath) 45 | for npath in os.listdir(cur_path) 46 | if path.isdir(path.join(cur_path, npath)) 47 | ] 48 | 49 | 50 | def get_extra_paths(): 51 | extra_paths = os.environ.get('ODOO_EXTRA_PATHS') 52 | 53 | if not extra_paths: 54 | return [] 55 | 56 | return [ 57 | extra_path.strip() 58 | for extra_path in extra_paths.split(',') 59 | ] 60 | 61 | 62 | def get_addons_paths(): 63 | addons = get_dirs('/addons') 64 | addons += get_extra_paths() 65 | 66 | return [ 67 | Path(path) 68 | for path in addons 69 | ] 70 | 71 | 72 | def install_apt_packages(): 73 | """ 74 | Install debian dependencies. 75 | """ 76 | package_list = set() 77 | 78 | paths = get_addons_paths() 79 | 80 | print("Looking up for packages in {}".format(paths)) 81 | 82 | for addons_path in paths: 83 | for packages in addons_path.glob('**/apt-packages.txt'): 84 | print("Installing packages from %s" % packages) 85 | with open(packages, 'r') as pack_file: 86 | lines = [line.strip() for line in pack_file] 87 | package_list.update(set(lines)) 88 | 89 | extras = os.environ.get('EXTRA_APT_PACKAGES', '') 90 | print(f"Adding extra packages {extras}") 91 | if extras: 92 | for package in extras.split(','): 93 | if not package: 94 | continue 95 | package_list.add(package) 96 | 97 | if len(package_list) > 0: 98 | print(f"Installing {package_list}") 99 | ret = pipe(['apt-get', 'update']) 100 | 101 | # Something went wrong, stop the service as it's failing 102 | if ret != 0: 103 | sys.exit(ret) 104 | 105 | ret = pipe(['apt-get', 'install', '-y'] + list(package_list)) 106 | 107 | # Something went wrong, stop the service as it's failing 108 | if ret != 0: 109 | sys.exit(ret) 110 | 111 | 112 | def load_secrets(): 113 | # TODO add a way to load some secrets so odoo process can 114 | # use secrets as a way to load passwords/user for postgresql 115 | # credentials could also be stored in the HOME of the odoo user 116 | # except we cannot rely on secrets 100% because it only works in 117 | # swarm mode 118 | pgpass_secret = '/run/secrets/.pgpass' 119 | if path.exists(pgpass_secret): 120 | home_folder = '/var/lib/odoo' 121 | pgpass_target = path.join(home_folder, '.pgpass') 122 | if path.exists(pgpass_target): 123 | os.remove(pgpass_target) 124 | # shutil.move doesn't always work correctly on different fs 125 | shutil.copy(pgpass_secret, home_folder) 126 | st = os.stat(pgpass_secret) 127 | os.chmod(pgpass_target, st.st_mode) 128 | os.chown(pgpass_target, st[stat.ST_UID], st[stat.ST_GID]) 129 | # Cannot remove anymore apparently 130 | # os.remove(pgpass_secret) 131 | # shutil.move(pgpass_secret, home_folder) 132 | 133 | 134 | def disable_base_modules(): 135 | base_addons = os.environ.get('ODOO_BASE_PATH', '') 136 | addons_to_remove = os.environ.get('ODOO_DISABLED_MODULES', '') 137 | 138 | modules = addons_to_remove.split(',') 139 | modules = map(lambda mod: mod.strip(), modules) 140 | 141 | if not base_addons: 142 | print("Do not attempt to remove wrong folder") 143 | return 144 | 145 | for module in modules: 146 | if not module: 147 | continue 148 | print("Removing module %s from %s" % (module, base_addons)) 149 | 150 | module_path = Path(base_addons, module) 151 | if module_path.exists() and module_path.is_dir(): 152 | shutil.rmtree(module_path) 153 | else: 154 | print("Module skipped as it doesn't seem to be present.") 155 | 156 | 157 | def fix_access_rights(): 158 | if os.environ.get('RESET_ACCESS_RIGHTS', '') == 'TRUE': 159 | pipe(["chown", "-R", "odoo:odoo", "/var/lib/odoo"]) 160 | pipe(["chown", "-R", "odoo:odoo", "/etc/odoo"]) 161 | 162 | 163 | def remove_sudo(): 164 | return pipe(["sed", "-i", "/odoo/d", "/etc/sudoers"]) 165 | 166 | 167 | def main(): 168 | install_apt_packages() 169 | load_secrets() 170 | fix_access_rights() 171 | disable_base_modules() 172 | return remove_sudo() 173 | 174 | 175 | try: 176 | code = main() 177 | sys.exit(code) 178 | except Exception as exc: 179 | print(exc) 180 | sys.exit(1) 181 | except KeyboardInterrupt as exc: 182 | print(exc) 183 | sys.exit(1) 184 | -------------------------------------------------------------------------------- /13.0-nightly/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | LABEL maintainer="Archeti " 3 | 4 | # Generate locale C.UTF-8 for postgres and general locale data 5 | ENV LANG C.UTF-8 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | 8 | # Install some dependencies python3.7 9 | RUN set -x; \ 10 | apt-get update \ 11 | && apt-get install -y --no-install-recommends \ 12 | python3-wheel \ 13 | python3-setuptools \ 14 | python3-pip \ 15 | # python3.7 \ 16 | # libpython3.7 \ 17 | curl \ 18 | gnupg \ 19 | libpq-dev \ 20 | libsasl2-2 \ 21 | libldap-2.4-2 \ 22 | libxml2 \ 23 | libxmlsec1 \ 24 | libxslt1.1 \ 25 | sudo \ 26 | node-less \ 27 | # python3-yaml \ 28 | && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 1 \ 29 | # && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 2 \ 30 | && update-alternatives --install /usr/bin/python python /usr/bin/python3.6 1 \ 31 | && update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 2 \ 32 | && curl -o wkhtmltox.deb -sSL https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb \ 33 | && apt-get install -y --no-install-recommends ./wkhtmltox.deb \ 34 | && rm -rf /var/lib/apt/lists/* wkhtmltox.deb 35 | 36 | # Install latest postgresql-client 37 | RUN set -x; \ 38 | echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > etc/apt/sources.list.d/pgdg.list \ 39 | && export GNUPGHOME="$(mktemp -d)" \ 40 | && repokey='B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8' \ 41 | && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \ 42 | && gpg --batch --armor --export "${repokey}" > /etc/apt/trusted.gpg.d/pgdg.gpg.asc \ 43 | && gpgconf --kill all \ 44 | && rm -rf "$GNUPGHOME" \ 45 | && apt-get update \ 46 | && apt-get install -y postgresql-client \ 47 | && rm -rf /var/lib/apt/lists/* 48 | 49 | ENV PATH=/usr/local/bin:$PATH 50 | # Install Odoo Including things from sources 51 | ENV ODOO_VERSION 13.0 52 | ENV ODOO_RELEASE=20210625 53 | ARG ODOO_ARCHIVE=odoo_${ODOO_VERSION}.${ODOO_RELEASE}.tar.gz 54 | RUN set -x; \ 55 | apt-get update \ 56 | && apt-get install -y --no-install-recommends \ 57 | build-essential \ 58 | python3.6-dev \ 59 | libsasl2-dev \ 60 | libldap2-dev \ 61 | libxml2-dev \ 62 | libxmlsec1-dev \ 63 | libxslt1-dev \ 64 | && pip install -U pip \ 65 | && /usr/bin/env pip install \ 66 | psycogreen \ 67 | \ 68 | && curl -o requirements.txt https://raw.githubusercontent.com/odoo/odoo/${ODOO_VERSION}/requirements.txt \ 69 | && pip install -r requirements.txt \ 70 | && /usr/bin/env pip install https://nightly.odoo.com/${ODOO_VERSION}/nightly/src/${ODOO_ARCHIVE} \ 71 | && cd / \ 72 | && apt-get --purge remove -y \ 73 | build-essential \ 74 | python3.6-dev \ 75 | libsasl2-dev \ 76 | libldap2-dev \ 77 | libxml2-dev \ 78 | libxmlsec1-dev \ 79 | libxslt1-dev \ 80 | && apt-get autoremove -y \ 81 | && rm -rf /var/lib/apt/lists/* 82 | 83 | VOLUME /etc/odoo 84 | VOLUME /var/lib/odoo 85 | 86 | COPY ./odoo.conf /etc/odoo/ 87 | COPY ./entrypoint.py / 88 | COPY ./sudo-entrypoint.py / 89 | COPY ./find_modules.py /scripts/find_modules.py 90 | COPY ./prepare_project.py /scripts/prepare_project.py 91 | 92 | ARG UID=1000 93 | ARG GID=1000 94 | 95 | RUN mkdir /addons \ 96 | && groupadd -r -g ${GID} odoo \ 97 | && useradd -r -u ${UID} -g odoo -b /var/lib -m odoo \ 98 | && chown odoo /etc/odoo/odoo.conf \ 99 | && chown -R odoo:odoo /addons \ 100 | && echo "odoo ALL=(ALL:ALL) NOPASSWD:SETENV: /sudo-entrypoint.py" >> /etc/sudoers 101 | 102 | ENV ODOO_RC /etc/odoo/odoo.conf 103 | ENV ODOO_BASE_PATH /usr/local/lib/python3.6/dist-packages/odoo/addons 104 | # Env variable defined to monitor the kind of service running 105 | # it could be a staging/production/test or anything and undefined 106 | # is the default in case we need to know servers that aren't correctly 107 | # defined 108 | ENV DEPLOYMENT_AREA undefined 109 | 110 | expose 8069 111 | expose 8071 112 | 113 | USER odoo 114 | 115 | LABEL version="13.0" 116 | LABEL release="20210625" 117 | 118 | LABEL org.opencontainers.image.created="2021-06-25T17:55:41.201333" 119 | LABEL org.opencontainers.image.url="https://hub.docker.com/r/llacroix/odoo" 120 | LABEL org.opencontainers.image.authors="Archeti " 121 | LABEL org.opencontainers.image.documentation="https://github.com/llacroix/odoo-docker" 122 | LABEL org.opencontainers.image.source="https://github.com/llacroix/odoo-docker" 123 | LABEL org.opencontainers.image.version="13.0" 124 | LABEL org.opencontainers.image.vendor="ArcheTI" 125 | LABEL org.opencontainers.image.ref.name="13.0-nightly" 126 | LABEL org.opencontainers.image.title="Odoo 13.0" 127 | LABEL org.opencontainers.image.description="Full featured odoo image that make odoo deployment fun and secure." 128 | 129 | ENTRYPOINT ["/entrypoint.py"] 130 | 131 | cmd ["odoo"] 132 | -------------------------------------------------------------------------------- /13.0-nightly/find_modules.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | from itertools import chain 4 | 5 | 6 | def get_parser(): 7 | parser = OptionParser() 8 | 9 | parser.add_option( 10 | "-p", 11 | dest='paths', 12 | action="append", 13 | help="Location in which to search", 14 | default=[] 15 | ) 16 | 17 | parser.add_option( 18 | "--only-name", 19 | dest="only_name", 20 | action="store_true", 21 | help="Only display module name instead of path", 22 | default=False, 23 | ) 24 | 25 | parser.add_option( 26 | '--csv', 27 | dest="is_csv", 28 | action="store_true", 29 | help="Output as a comma separated list", 30 | default=False 31 | ) 32 | 33 | return parser 34 | 35 | 36 | def find_modules(options, path): 37 | modules = set() 38 | 39 | path = Path.cwd() / path 40 | 41 | erp_manifest = '__openerp__.py' 42 | odoo_manifest = '__manifest__.py' 43 | 44 | manifest_globs = chain( 45 | path.glob('**/{}'.format(erp_manifest)), 46 | path.glob('**/{}'.format(odoo_manifest)), 47 | ) 48 | 49 | for path in manifest_globs: 50 | rel_path = path.parent.relative_to(Path.cwd()) 51 | if options.only_name: 52 | modules.add(rel_path.name) 53 | else: 54 | modules.add(str(rel_path)) 55 | 56 | return modules 57 | 58 | 59 | def main(options, args): 60 | modules = set() 61 | for path in options.paths: 62 | modules = modules.union(find_modules(options, path)) 63 | 64 | return modules 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = get_parser() 69 | (options, args) = parser.parse_args() 70 | modules = main(options, args) 71 | 72 | if not options.is_csv: 73 | for module in modules: 74 | print(module) 75 | else: 76 | print(",".join(modules), end="") 77 | -------------------------------------------------------------------------------- /13.0-nightly/odoo.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | addons_path = /mnt/extra-addons 3 | data_dir = /var/lib/odoo 4 | ; admin_passwd = admin 5 | ; csv_internal_sep = , 6 | ; db_maxconn = 64 7 | ; db_name = False 8 | ; db_template = template1 9 | ; dbfilter = .* 10 | ; debug_mode = False 11 | ; email_from = False 12 | ; limit_memory_hard = 2684354560 13 | ; limit_memory_soft = 2147483648 14 | ; limit_request = 8192 15 | ; limit_time_cpu = 60 16 | ; limit_time_real = 120 17 | ; list_db = True 18 | ; log_db = False 19 | ; log_handler = [':INFO'] 20 | ; log_level = info 21 | ; logfile = None 22 | ; longpolling_port = 8072 23 | ; max_cron_threads = 2 24 | ; osv_memory_age_limit = 1.0 25 | ; osv_memory_count_limit = False 26 | ; smtp_password = False 27 | ; smtp_port = 25 28 | ; smtp_server = localhost 29 | ; smtp_ssl = False 30 | ; smtp_user = False 31 | ; workers = 0 32 | ; xmlrpc = True 33 | ; xmlrpc_interface = 34 | ; xmlrpc_port = 8069 35 | ; xmlrpcs = True 36 | ; xmlrpcs_interface = 37 | ; xmlrpcs_port = 8071 38 | -------------------------------------------------------------------------------- /13.0-nightly/prepare_project.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | import toml 4 | from giturlparse import parse 5 | from contextlib import contextmanager 6 | import os 7 | from subprocess import run 8 | from urllib.parse import urlparse 9 | 10 | 11 | def get_services(services): 12 | return { 13 | service.get('name'): service 14 | for service in services.get('services') 15 | } 16 | 17 | 18 | def addons_by_project(options, addons): 19 | res = {} 20 | 21 | for addon in addons: 22 | if addon['url'] == 'self': 23 | if options.ignore_self: 24 | continue 25 | parsed = parse(options.url) 26 | else: 27 | parsed = parse(addon['url']) 28 | 29 | auth = parsed.protocol in ['git', 'ssh'] 30 | 31 | res[parsed.repo] = dict( 32 | addon, 33 | url=parsed.url2https, 34 | auth=auth 35 | ) 36 | 37 | return res 38 | 39 | 40 | def merge_addons(options, base, other): 41 | base_addons = addons_by_project(options, base) 42 | other_addons = addons_by_project(options, other) 43 | 44 | for name, addon in other_addons.items(): 45 | if name not in base_addons: 46 | base_addons[name] = addon 47 | else: 48 | base_addons[name] = dict(base_addons[name], **addon) 49 | 50 | return [ 51 | addon 52 | for addon in base_addons.values() 53 | ] 54 | 55 | 56 | def merge_services(options, base, other): 57 | basic_inherit = dict(base, **other) 58 | 59 | if base.get('addons') or other.get('addons'): 60 | basic_inherit['addons'] = merge_addons( 61 | options, 62 | base.get('addons', []), 63 | other.get('addons', []) 64 | ) 65 | 66 | return basic_inherit 67 | 68 | 69 | def compile_service(options, services, name): 70 | service = services.get(name, {}) 71 | if 'inherit' in service: 72 | merge_service = compile_service(options, services, service['inherit']) 73 | service = merge_services(options, merge_service, service) 74 | 75 | return service 76 | 77 | 78 | def get_parser(): 79 | parser = OptionParser() 80 | 81 | parser.add_option( 82 | '-f', 83 | '--file', 84 | dest="file", 85 | help="Input File" 86 | ) 87 | 88 | parser.add_option( 89 | '--url', 90 | dest='url', 91 | help="Url of self project" 92 | ) 93 | 94 | parser.add_option( 95 | '-o', 96 | '--output', 97 | dest="output_directory", 98 | help="Output Directory" 99 | ) 100 | 101 | parser.add_option( 102 | '-e', 103 | dest="env", 104 | help="Environment to prepare" 105 | ) 106 | 107 | parser.add_option( 108 | '--username', 109 | dest="username", 110 | help="Username to replace with", 111 | ) 112 | 113 | parser.add_option( 114 | '--password', 115 | dest="password", 116 | help="password to set on https urls" 117 | ) 118 | 119 | parser.add_option( 120 | '-b', 121 | '--branch', 122 | dest="branch", 123 | help="Default branch if no ref is defined" 124 | ) 125 | 126 | parser.add_option( 127 | '--ignore-self', 128 | dest="ignore_self", 129 | action="store_true", 130 | help="Ignore self url as it's already fetched", 131 | default=False 132 | ) 133 | 134 | return parser 135 | 136 | 137 | @contextmanager 138 | def cd(directory): 139 | cwd = Path.cwd() 140 | try: 141 | os.chdir(directory) 142 | yield 143 | finally: 144 | os.chdir(cwd) 145 | 146 | 147 | def fetch_addons(options, addon): 148 | parsed = parse(addon['url']) 149 | 150 | url = urlparse(parsed.url2https) 151 | 152 | if addon['auth'] and options.username and options.password: 153 | url = url._replace( 154 | netloc="{}:{}@{}".format( 155 | options.username, 156 | options.password, 157 | url.netloc 158 | ) 159 | ) 160 | 161 | repo_path = Path.cwd() / options.output_directory / parsed.repo 162 | 163 | repo_path.mkdir(exist_ok=True) 164 | 165 | with cd(repo_path): 166 | run(['git', 'init']) 167 | run(['git', 'remote', 'add', 'origin', url.geturl()]) 168 | 169 | ref = addon.get('commit') or addon.get('branch') or options.branch 170 | 171 | if ref: 172 | run(['git', 'fetch', 'origin', ref]) 173 | else: 174 | run(['git', 'fetch', 'origin']) 175 | 176 | run(['git', 'checkout', 'FETCH_HEAD']) 177 | run(['git', 'remote', 'remove', 'origin']) 178 | 179 | 180 | def main(options, args): 181 | with Path(options.file).open('r') as fin: 182 | services = toml.loads(fin.read()) 183 | 184 | by_name = get_services(services) 185 | 186 | outputdir = Path(options.output_directory) 187 | outputdir.mkdir(exist_ok=True) 188 | 189 | service = compile_service(options, by_name, options.env) 190 | 191 | for addons in service.get('addons', []): 192 | fetch_addons(options, addons) 193 | 194 | 195 | if __name__ == '__main__': 196 | parser = get_parser() 197 | (options, args) = parser.parse_args() 198 | main(options, args) 199 | -------------------------------------------------------------------------------- /13.0/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | LABEL maintainer="Archeti " 3 | 4 | # Generate locale C.UTF-8 for postgres and general locale data 5 | ENV LANG C.UTF-8 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | 8 | # Install some dependencies python3.7 9 | RUN set -x; \ 10 | apt-get update \ 11 | && apt-get install -y --no-install-recommends \ 12 | python3-wheel \ 13 | python3-setuptools \ 14 | python3-pip \ 15 | # python3.7 \ 16 | # libpython3.7 \ 17 | curl \ 18 | gnupg \ 19 | libpq-dev \ 20 | libsasl2-2 \ 21 | libldap-2.4-2 \ 22 | libxml2 \ 23 | libxmlsec1 \ 24 | libxslt1.1 \ 25 | sudo \ 26 | node-less \ 27 | # python3-yaml \ 28 | && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 1 \ 29 | # && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 2 \ 30 | && update-alternatives --install /usr/bin/python python /usr/bin/python3.6 1 \ 31 | && update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 2 \ 32 | && curl -o wkhtmltox.deb -sSL https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb \ 33 | && apt-get install -y --no-install-recommends ./wkhtmltox.deb \ 34 | && rm -rf /var/lib/apt/lists/* wkhtmltox.deb 35 | 36 | # Install latest postgresql-client 37 | RUN set -x; \ 38 | echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > etc/apt/sources.list.d/pgdg.list \ 39 | && export GNUPGHOME="$(mktemp -d)" \ 40 | && repokey='B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8' \ 41 | && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \ 42 | && gpg --batch --armor --export "${repokey}" > /etc/apt/trusted.gpg.d/pgdg.gpg.asc \ 43 | && gpgconf --kill all \ 44 | && rm -rf "$GNUPGHOME" \ 45 | && apt-get update \ 46 | && apt-get install -y postgresql-client \ 47 | && rm -rf /var/lib/apt/lists/* 48 | 49 | ENV PATH=/usr/local/bin:$PATH 50 | # Install Odoo Including things from sources 51 | ENV ODOO_VERSION 13.0 52 | ENV ODOO_RELEASE=20200623 53 | ARG ODOO_ARCHIVE=odoo_${ODOO_VERSION}.${ODOO_RELEASE}.tar.gz 54 | RUN set -x; \ 55 | apt-get update \ 56 | && apt-get install -y --no-install-recommends \ 57 | build-essential \ 58 | python3.6-dev \ 59 | libsasl2-dev \ 60 | libldap2-dev \ 61 | libxml2-dev \ 62 | libxmlsec1-dev \ 63 | libxslt1-dev \ 64 | && pip install -U pip \ 65 | && /usr/bin/env pip install \ 66 | psycogreen \ 67 | \ 68 | && curl -o requirements.txt https://raw.githubusercontent.com/odoo/odoo/${ODOO_VERSION}/requirements.txt \ 69 | && pip install -r requirements.txt \ 70 | && /usr/bin/env pip install https://nightly.odoo.com/${ODOO_VERSION}/nightly/src/${ODOO_ARCHIVE} \ 71 | && cd / \ 72 | && apt-get --purge remove -y \ 73 | build-essential \ 74 | python3.6-dev \ 75 | libsasl2-dev \ 76 | libldap2-dev \ 77 | libxml2-dev \ 78 | libxmlsec1-dev \ 79 | libxslt1-dev \ 80 | && apt-get autoremove -y \ 81 | && rm -rf /var/lib/apt/lists/* 82 | 83 | VOLUME /etc/odoo 84 | VOLUME /var/lib/odoo 85 | 86 | COPY ./odoo.conf /etc/odoo/ 87 | COPY ./entrypoint.py / 88 | COPY ./sudo-entrypoint.py / 89 | COPY ./find_modules.py /scripts/find_modules.py 90 | COPY ./prepare_project.py /scripts/prepare_project.py 91 | 92 | ARG UID=1000 93 | ARG GID=1000 94 | 95 | RUN mkdir /addons \ 96 | && groupadd -r -g ${GID} odoo \ 97 | && useradd -r -u ${UID} -g odoo -b /var/lib -m odoo \ 98 | && chown odoo /etc/odoo/odoo.conf \ 99 | && chown -R odoo:odoo /addons \ 100 | && echo "odoo ALL=(ALL:ALL) NOPASSWD:SETENV: /sudo-entrypoint.py" >> /etc/sudoers 101 | 102 | ENV ODOO_RC /etc/odoo/odoo.conf 103 | ENV ODOO_BASE_PATH /usr/local/lib/python3.6/dist-packages/odoo/addons 104 | # Env variable defined to monitor the kind of service running 105 | # it could be a staging/production/test or anything and undefined 106 | # is the default in case we need to know servers that aren't correctly 107 | # defined 108 | ENV DEPLOYMENT_AREA undefined 109 | 110 | expose 8069 111 | expose 8071 112 | 113 | USER odoo 114 | 115 | LABEL version="13.0" 116 | LABEL release="20200623" 117 | 118 | LABEL org.opencontainers.image.created="2021-06-25T17:55:41.196719" 119 | LABEL org.opencontainers.image.url="https://hub.docker.com/r/llacroix/odoo" 120 | LABEL org.opencontainers.image.authors="Archeti " 121 | LABEL org.opencontainers.image.documentation="https://github.com/llacroix/odoo-docker" 122 | LABEL org.opencontainers.image.source="https://github.com/llacroix/odoo-docker" 123 | LABEL org.opencontainers.image.version="13.0" 124 | LABEL org.opencontainers.image.vendor="ArcheTI" 125 | LABEL org.opencontainers.image.ref.name="13.0" 126 | LABEL org.opencontainers.image.title="Odoo 13.0" 127 | LABEL org.opencontainers.image.description="Full featured odoo image that make odoo deployment fun and secure." 128 | 129 | ENTRYPOINT ["/entrypoint.py"] 130 | 131 | cmd ["odoo"] 132 | -------------------------------------------------------------------------------- /13.0/find_modules.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | from itertools import chain 4 | 5 | 6 | def get_parser(): 7 | parser = OptionParser() 8 | 9 | parser.add_option( 10 | "-p", 11 | dest='paths', 12 | action="append", 13 | help="Location in which to search", 14 | default=[] 15 | ) 16 | 17 | parser.add_option( 18 | "--only-name", 19 | dest="only_name", 20 | action="store_true", 21 | help="Only display module name instead of path", 22 | default=False, 23 | ) 24 | 25 | parser.add_option( 26 | '--csv', 27 | dest="is_csv", 28 | action="store_true", 29 | help="Output as a comma separated list", 30 | default=False 31 | ) 32 | 33 | return parser 34 | 35 | 36 | def find_modules(options, path): 37 | modules = set() 38 | 39 | path = Path.cwd() / path 40 | 41 | erp_manifest = '__openerp__.py' 42 | odoo_manifest = '__manifest__.py' 43 | 44 | manifest_globs = chain( 45 | path.glob('**/{}'.format(erp_manifest)), 46 | path.glob('**/{}'.format(odoo_manifest)), 47 | ) 48 | 49 | for path in manifest_globs: 50 | rel_path = path.parent.relative_to(Path.cwd()) 51 | if options.only_name: 52 | modules.add(rel_path.name) 53 | else: 54 | modules.add(str(rel_path)) 55 | 56 | return modules 57 | 58 | 59 | def main(options, args): 60 | modules = set() 61 | for path in options.paths: 62 | modules = modules.union(find_modules(options, path)) 63 | 64 | return modules 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = get_parser() 69 | (options, args) = parser.parse_args() 70 | modules = main(options, args) 71 | 72 | if not options.is_csv: 73 | for module in modules: 74 | print(module) 75 | else: 76 | print(",".join(modules), end="") 77 | -------------------------------------------------------------------------------- /13.0/odoo.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | addons_path = /mnt/extra-addons 3 | data_dir = /var/lib/odoo 4 | ; admin_passwd = admin 5 | ; csv_internal_sep = , 6 | ; db_maxconn = 64 7 | ; db_name = False 8 | ; db_template = template1 9 | ; dbfilter = .* 10 | ; debug_mode = False 11 | ; email_from = False 12 | ; limit_memory_hard = 2684354560 13 | ; limit_memory_soft = 2147483648 14 | ; limit_request = 8192 15 | ; limit_time_cpu = 60 16 | ; limit_time_real = 120 17 | ; list_db = True 18 | ; log_db = False 19 | ; log_handler = [':INFO'] 20 | ; log_level = info 21 | ; logfile = None 22 | ; longpolling_port = 8072 23 | ; max_cron_threads = 2 24 | ; osv_memory_age_limit = 1.0 25 | ; osv_memory_count_limit = False 26 | ; smtp_password = False 27 | ; smtp_port = 25 28 | ; smtp_server = localhost 29 | ; smtp_ssl = False 30 | ; smtp_user = False 31 | ; workers = 0 32 | ; xmlrpc = True 33 | ; xmlrpc_interface = 34 | ; xmlrpc_port = 8069 35 | ; xmlrpcs = True 36 | ; xmlrpcs_interface = 37 | ; xmlrpcs_port = 8071 38 | -------------------------------------------------------------------------------- /13.0/prepare_project.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | import toml 4 | from giturlparse import parse 5 | from contextlib import contextmanager 6 | import os 7 | from subprocess import run 8 | from urllib.parse import urlparse 9 | 10 | 11 | def get_services(services): 12 | return { 13 | service.get('name'): service 14 | for service in services.get('services') 15 | } 16 | 17 | 18 | def addons_by_project(options, addons): 19 | res = {} 20 | 21 | for addon in addons: 22 | if addon['url'] == 'self': 23 | if options.ignore_self: 24 | continue 25 | parsed = parse(options.url) 26 | else: 27 | parsed = parse(addon['url']) 28 | 29 | auth = parsed.protocol in ['git', 'ssh'] 30 | 31 | res[parsed.repo] = dict( 32 | addon, 33 | url=parsed.url2https, 34 | auth=auth 35 | ) 36 | 37 | return res 38 | 39 | 40 | def merge_addons(options, base, other): 41 | base_addons = addons_by_project(options, base) 42 | other_addons = addons_by_project(options, other) 43 | 44 | for name, addon in other_addons.items(): 45 | if name not in base_addons: 46 | base_addons[name] = addon 47 | else: 48 | base_addons[name] = dict(base_addons[name], **addon) 49 | 50 | return [ 51 | addon 52 | for addon in base_addons.values() 53 | ] 54 | 55 | 56 | def merge_services(options, base, other): 57 | basic_inherit = dict(base, **other) 58 | 59 | if base.get('addons') or other.get('addons'): 60 | basic_inherit['addons'] = merge_addons( 61 | options, 62 | base.get('addons', []), 63 | other.get('addons', []) 64 | ) 65 | 66 | return basic_inherit 67 | 68 | 69 | def compile_service(options, services, name): 70 | service = services.get(name, {}) 71 | if 'inherit' in service: 72 | merge_service = compile_service(options, services, service['inherit']) 73 | service = merge_services(options, merge_service, service) 74 | 75 | return service 76 | 77 | 78 | def get_parser(): 79 | parser = OptionParser() 80 | 81 | parser.add_option( 82 | '-f', 83 | '--file', 84 | dest="file", 85 | help="Input File" 86 | ) 87 | 88 | parser.add_option( 89 | '--url', 90 | dest='url', 91 | help="Url of self project" 92 | ) 93 | 94 | parser.add_option( 95 | '-o', 96 | '--output', 97 | dest="output_directory", 98 | help="Output Directory" 99 | ) 100 | 101 | parser.add_option( 102 | '-e', 103 | dest="env", 104 | help="Environment to prepare" 105 | ) 106 | 107 | parser.add_option( 108 | '--username', 109 | dest="username", 110 | help="Username to replace with", 111 | ) 112 | 113 | parser.add_option( 114 | '--password', 115 | dest="password", 116 | help="password to set on https urls" 117 | ) 118 | 119 | parser.add_option( 120 | '-b', 121 | '--branch', 122 | dest="branch", 123 | help="Default branch if no ref is defined" 124 | ) 125 | 126 | parser.add_option( 127 | '--ignore-self', 128 | dest="ignore_self", 129 | action="store_true", 130 | help="Ignore self url as it's already fetched", 131 | default=False 132 | ) 133 | 134 | return parser 135 | 136 | 137 | @contextmanager 138 | def cd(directory): 139 | cwd = Path.cwd() 140 | try: 141 | os.chdir(directory) 142 | yield 143 | finally: 144 | os.chdir(cwd) 145 | 146 | 147 | def fetch_addons(options, addon): 148 | parsed = parse(addon['url']) 149 | 150 | url = urlparse(parsed.url2https) 151 | 152 | if addon['auth'] and options.username and options.password: 153 | url = url._replace( 154 | netloc="{}:{}@{}".format( 155 | options.username, 156 | options.password, 157 | url.netloc 158 | ) 159 | ) 160 | 161 | repo_path = Path.cwd() / options.output_directory / parsed.repo 162 | 163 | repo_path.mkdir(exist_ok=True) 164 | 165 | with cd(repo_path): 166 | run(['git', 'init']) 167 | run(['git', 'remote', 'add', 'origin', url.geturl()]) 168 | 169 | ref = addon.get('commit') or addon.get('branch') or options.branch 170 | 171 | if ref: 172 | run(['git', 'fetch', 'origin', ref]) 173 | else: 174 | run(['git', 'fetch', 'origin']) 175 | 176 | run(['git', 'checkout', 'FETCH_HEAD']) 177 | run(['git', 'remote', 'remove', 'origin']) 178 | 179 | 180 | def main(options, args): 181 | with Path(options.file).open('r') as fin: 182 | services = toml.loads(fin.read()) 183 | 184 | by_name = get_services(services) 185 | 186 | outputdir = Path(options.output_directory) 187 | outputdir.mkdir(exist_ok=True) 188 | 189 | service = compile_service(options, by_name, options.env) 190 | 191 | for addons in service.get('addons', []): 192 | fetch_addons(options, addons) 193 | 194 | 195 | if __name__ == '__main__': 196 | parser = get_parser() 197 | (options, args) = parser.parse_args() 198 | main(options, args) 199 | -------------------------------------------------------------------------------- /14.0-nightly/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | LABEL maintainer="Archeti " 3 | 4 | # Generate locale C.UTF-8 for postgres and general locale data 5 | ENV LANG C.UTF-8 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | 8 | # Install some dependencies python3.7 9 | RUN set -x; \ 10 | apt-get update \ 11 | && apt-get install -y --no-install-recommends \ 12 | python3-wheel \ 13 | python3-setuptools \ 14 | python3-pip \ 15 | # python3.7 \ 16 | # libpython3.7 \ 17 | curl \ 18 | gnupg \ 19 | libpq-dev \ 20 | libsasl2-2 \ 21 | libldap-2.4-2 \ 22 | libxml2 \ 23 | libxmlsec1 \ 24 | libxslt1.1 \ 25 | sudo \ 26 | node-less \ 27 | # python3-yaml \ 28 | && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 1 \ 29 | # && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 2 \ 30 | && update-alternatives --install /usr/bin/python python /usr/bin/python3.6 1 \ 31 | && update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 2 \ 32 | && curl -o wkhtmltox.deb -sSL https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb \ 33 | && apt-get install -y --no-install-recommends ./wkhtmltox.deb \ 34 | && rm -rf /var/lib/apt/lists/* wkhtmltox.deb 35 | 36 | # Install latest postgresql-client 37 | RUN set -x; \ 38 | echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > etc/apt/sources.list.d/pgdg.list \ 39 | && export GNUPGHOME="$(mktemp -d)" \ 40 | && repokey='B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8' \ 41 | && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \ 42 | && gpg --batch --armor --export "${repokey}" > /etc/apt/trusted.gpg.d/pgdg.gpg.asc \ 43 | && gpgconf --kill all \ 44 | && rm -rf "$GNUPGHOME" \ 45 | && apt-get update \ 46 | && apt-get install -y postgresql-client \ 47 | && rm -rf /var/lib/apt/lists/* 48 | 49 | ENV PATH=/usr/local/bin:$PATH 50 | # Install Odoo Including things from sources 51 | ENV ODOO_VERSION 14.0 52 | ENV ODOO_RELEASE=20210625 53 | ARG ODOO_ARCHIVE=odoo_${ODOO_VERSION}.${ODOO_RELEASE}.tar.gz 54 | RUN set -x; \ 55 | apt-get update \ 56 | && apt-get install -y --no-install-recommends \ 57 | build-essential \ 58 | python3.6-dev \ 59 | libsasl2-dev \ 60 | libldap2-dev \ 61 | libxml2-dev \ 62 | libxmlsec1-dev \ 63 | libxslt1-dev \ 64 | && pip install -U pip \ 65 | && /usr/bin/env pip install \ 66 | psycogreen \ 67 | \ 68 | && curl -o requirements.txt https://raw.githubusercontent.com/odoo/odoo/${ODOO_VERSION}/requirements.txt \ 69 | && pip install -r requirements.txt \ 70 | && /usr/bin/env pip install https://nightly.odoo.com/${ODOO_VERSION}/nightly/src/${ODOO_ARCHIVE} \ 71 | && cd / \ 72 | && apt-get --purge remove -y \ 73 | build-essential \ 74 | python3.6-dev \ 75 | libsasl2-dev \ 76 | libldap2-dev \ 77 | libxml2-dev \ 78 | libxmlsec1-dev \ 79 | libxslt1-dev \ 80 | && apt-get autoremove -y \ 81 | && rm -rf /var/lib/apt/lists/* 82 | 83 | VOLUME /etc/odoo 84 | VOLUME /var/lib/odoo 85 | 86 | COPY ./odoo.conf /etc/odoo/ 87 | COPY ./entrypoint.py / 88 | COPY ./sudo-entrypoint.py / 89 | COPY ./find_modules.py /scripts/find_modules.py 90 | COPY ./prepare_project.py /scripts/prepare_project.py 91 | 92 | ARG UID=1000 93 | ARG GID=1000 94 | 95 | RUN mkdir /addons \ 96 | && groupadd -r -g ${GID} odoo \ 97 | && useradd -r -u ${UID} -g odoo -b /var/lib -m odoo \ 98 | && chown odoo /etc/odoo/odoo.conf \ 99 | && chown -R odoo:odoo /addons \ 100 | && echo "odoo ALL=(ALL:ALL) NOPASSWD:SETENV: /sudo-entrypoint.py" >> /etc/sudoers 101 | 102 | ENV ODOO_RC /etc/odoo/odoo.conf 103 | ENV ODOO_BASE_PATH /usr/local/lib/python3.6/dist-packages/odoo/addons 104 | # Env variable defined to monitor the kind of service running 105 | # it could be a staging/production/test or anything and undefined 106 | # is the default in case we need to know servers that aren't correctly 107 | # defined 108 | ENV DEPLOYMENT_AREA undefined 109 | 110 | expose 8069 111 | expose 8071 112 | 113 | USER odoo 114 | 115 | LABEL version="14.0" 116 | LABEL release="20210625" 117 | 118 | LABEL org.opencontainers.image.created="2021-06-25T17:55:41.202241" 119 | LABEL org.opencontainers.image.url="https://hub.docker.com/r/llacroix/odoo" 120 | LABEL org.opencontainers.image.authors="Archeti " 121 | LABEL org.opencontainers.image.documentation="https://github.com/llacroix/odoo-docker" 122 | LABEL org.opencontainers.image.source="https://github.com/llacroix/odoo-docker" 123 | LABEL org.opencontainers.image.version="14.0" 124 | LABEL org.opencontainers.image.vendor="ArcheTI" 125 | LABEL org.opencontainers.image.ref.name="14.0-nightly" 126 | LABEL org.opencontainers.image.title="Odoo 14.0" 127 | LABEL org.opencontainers.image.description="Full featured odoo image that make odoo deployment fun and secure." 128 | 129 | ENTRYPOINT ["/entrypoint.py"] 130 | 131 | cmd ["odoo"] 132 | -------------------------------------------------------------------------------- /14.0-nightly/find_modules.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | from itertools import chain 4 | 5 | 6 | def get_parser(): 7 | parser = OptionParser() 8 | 9 | parser.add_option( 10 | "-p", 11 | dest='paths', 12 | action="append", 13 | help="Location in which to search", 14 | default=[] 15 | ) 16 | 17 | parser.add_option( 18 | "--only-name", 19 | dest="only_name", 20 | action="store_true", 21 | help="Only display module name instead of path", 22 | default=False, 23 | ) 24 | 25 | parser.add_option( 26 | '--csv', 27 | dest="is_csv", 28 | action="store_true", 29 | help="Output as a comma separated list", 30 | default=False 31 | ) 32 | 33 | return parser 34 | 35 | 36 | def find_modules(options, path): 37 | modules = set() 38 | 39 | path = Path.cwd() / path 40 | 41 | erp_manifest = '__openerp__.py' 42 | odoo_manifest = '__manifest__.py' 43 | 44 | manifest_globs = chain( 45 | path.glob('**/{}'.format(erp_manifest)), 46 | path.glob('**/{}'.format(odoo_manifest)), 47 | ) 48 | 49 | for path in manifest_globs: 50 | rel_path = path.parent.relative_to(Path.cwd()) 51 | if options.only_name: 52 | modules.add(rel_path.name) 53 | else: 54 | modules.add(str(rel_path)) 55 | 56 | return modules 57 | 58 | 59 | def main(options, args): 60 | modules = set() 61 | for path in options.paths: 62 | modules = modules.union(find_modules(options, path)) 63 | 64 | return modules 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = get_parser() 69 | (options, args) = parser.parse_args() 70 | modules = main(options, args) 71 | 72 | if not options.is_csv: 73 | for module in modules: 74 | print(module) 75 | else: 76 | print(",".join(modules), end="") 77 | -------------------------------------------------------------------------------- /14.0-nightly/odoo.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | addons_path = /mnt/extra-addons 3 | data_dir = /var/lib/odoo 4 | ; admin_passwd = admin 5 | ; csv_internal_sep = , 6 | ; db_maxconn = 64 7 | ; db_name = False 8 | ; db_template = template1 9 | ; dbfilter = .* 10 | ; debug_mode = False 11 | ; email_from = False 12 | ; limit_memory_hard = 2684354560 13 | ; limit_memory_soft = 2147483648 14 | ; limit_request = 8192 15 | ; limit_time_cpu = 60 16 | ; limit_time_real = 120 17 | ; list_db = True 18 | ; log_db = False 19 | ; log_handler = [':INFO'] 20 | ; log_level = info 21 | ; logfile = None 22 | ; longpolling_port = 8072 23 | ; max_cron_threads = 2 24 | ; osv_memory_age_limit = 1.0 25 | ; osv_memory_count_limit = False 26 | ; smtp_password = False 27 | ; smtp_port = 25 28 | ; smtp_server = localhost 29 | ; smtp_ssl = False 30 | ; smtp_user = False 31 | ; workers = 0 32 | ; xmlrpc = True 33 | ; xmlrpc_interface = 34 | ; xmlrpc_port = 8069 35 | ; xmlrpcs = True 36 | ; xmlrpcs_interface = 37 | ; xmlrpcs_port = 8071 38 | -------------------------------------------------------------------------------- /14.0-nightly/prepare_project.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | import toml 4 | from giturlparse import parse 5 | from contextlib import contextmanager 6 | import os 7 | from subprocess import run 8 | from urllib.parse import urlparse 9 | 10 | 11 | def get_services(services): 12 | return { 13 | service.get('name'): service 14 | for service in services.get('services') 15 | } 16 | 17 | 18 | def addons_by_project(options, addons): 19 | res = {} 20 | 21 | for addon in addons: 22 | if addon['url'] == 'self': 23 | if options.ignore_self: 24 | continue 25 | parsed = parse(options.url) 26 | else: 27 | parsed = parse(addon['url']) 28 | 29 | auth = parsed.protocol in ['git', 'ssh'] 30 | 31 | res[parsed.repo] = dict( 32 | addon, 33 | url=parsed.url2https, 34 | auth=auth 35 | ) 36 | 37 | return res 38 | 39 | 40 | def merge_addons(options, base, other): 41 | base_addons = addons_by_project(options, base) 42 | other_addons = addons_by_project(options, other) 43 | 44 | for name, addon in other_addons.items(): 45 | if name not in base_addons: 46 | base_addons[name] = addon 47 | else: 48 | base_addons[name] = dict(base_addons[name], **addon) 49 | 50 | return [ 51 | addon 52 | for addon in base_addons.values() 53 | ] 54 | 55 | 56 | def merge_services(options, base, other): 57 | basic_inherit = dict(base, **other) 58 | 59 | if base.get('addons') or other.get('addons'): 60 | basic_inherit['addons'] = merge_addons( 61 | options, 62 | base.get('addons', []), 63 | other.get('addons', []) 64 | ) 65 | 66 | return basic_inherit 67 | 68 | 69 | def compile_service(options, services, name): 70 | service = services.get(name, {}) 71 | if 'inherit' in service: 72 | merge_service = compile_service(options, services, service['inherit']) 73 | service = merge_services(options, merge_service, service) 74 | 75 | return service 76 | 77 | 78 | def get_parser(): 79 | parser = OptionParser() 80 | 81 | parser.add_option( 82 | '-f', 83 | '--file', 84 | dest="file", 85 | help="Input File" 86 | ) 87 | 88 | parser.add_option( 89 | '--url', 90 | dest='url', 91 | help="Url of self project" 92 | ) 93 | 94 | parser.add_option( 95 | '-o', 96 | '--output', 97 | dest="output_directory", 98 | help="Output Directory" 99 | ) 100 | 101 | parser.add_option( 102 | '-e', 103 | dest="env", 104 | help="Environment to prepare" 105 | ) 106 | 107 | parser.add_option( 108 | '--username', 109 | dest="username", 110 | help="Username to replace with", 111 | ) 112 | 113 | parser.add_option( 114 | '--password', 115 | dest="password", 116 | help="password to set on https urls" 117 | ) 118 | 119 | parser.add_option( 120 | '-b', 121 | '--branch', 122 | dest="branch", 123 | help="Default branch if no ref is defined" 124 | ) 125 | 126 | parser.add_option( 127 | '--ignore-self', 128 | dest="ignore_self", 129 | action="store_true", 130 | help="Ignore self url as it's already fetched", 131 | default=False 132 | ) 133 | 134 | return parser 135 | 136 | 137 | @contextmanager 138 | def cd(directory): 139 | cwd = Path.cwd() 140 | try: 141 | os.chdir(directory) 142 | yield 143 | finally: 144 | os.chdir(cwd) 145 | 146 | 147 | def fetch_addons(options, addon): 148 | parsed = parse(addon['url']) 149 | 150 | url = urlparse(parsed.url2https) 151 | 152 | if addon['auth'] and options.username and options.password: 153 | url = url._replace( 154 | netloc="{}:{}@{}".format( 155 | options.username, 156 | options.password, 157 | url.netloc 158 | ) 159 | ) 160 | 161 | repo_path = Path.cwd() / options.output_directory / parsed.repo 162 | 163 | repo_path.mkdir(exist_ok=True) 164 | 165 | with cd(repo_path): 166 | run(['git', 'init']) 167 | run(['git', 'remote', 'add', 'origin', url.geturl()]) 168 | 169 | ref = addon.get('commit') or addon.get('branch') or options.branch 170 | 171 | if ref: 172 | run(['git', 'fetch', 'origin', ref]) 173 | else: 174 | run(['git', 'fetch', 'origin']) 175 | 176 | run(['git', 'checkout', 'FETCH_HEAD']) 177 | run(['git', 'remote', 'remove', 'origin']) 178 | 179 | 180 | def main(options, args): 181 | with Path(options.file).open('r') as fin: 182 | services = toml.loads(fin.read()) 183 | 184 | by_name = get_services(services) 185 | 186 | outputdir = Path(options.output_directory) 187 | outputdir.mkdir(exist_ok=True) 188 | 189 | service = compile_service(options, by_name, options.env) 190 | 191 | for addons in service.get('addons', []): 192 | fetch_addons(options, addons) 193 | 194 | 195 | if __name__ == '__main__': 196 | parser = get_parser() 197 | (options, args) = parser.parse_args() 198 | main(options, args) 199 | -------------------------------------------------------------------------------- /14.0-tiny/find_modules.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | from itertools import chain 4 | 5 | 6 | def get_parser(): 7 | parser = OptionParser() 8 | 9 | parser.add_option( 10 | "-p", 11 | dest='paths', 12 | action="append", 13 | help="Location in which to search", 14 | default=[] 15 | ) 16 | 17 | parser.add_option( 18 | "--only-name", 19 | dest="only_name", 20 | action="store_true", 21 | help="Only display module name instead of path", 22 | default=False, 23 | ) 24 | 25 | parser.add_option( 26 | '--csv', 27 | dest="is_csv", 28 | action="store_true", 29 | help="Output as a comma separated list", 30 | default=False 31 | ) 32 | 33 | return parser 34 | 35 | 36 | def find_modules(options, path): 37 | modules = set() 38 | 39 | path = Path.cwd() / path 40 | 41 | erp_manifest = '__openerp__.py' 42 | odoo_manifest = '__manifest__.py' 43 | 44 | manifest_globs = chain( 45 | path.glob('**/{}'.format(erp_manifest)), 46 | path.glob('**/{}'.format(odoo_manifest)), 47 | ) 48 | 49 | for path in manifest_globs: 50 | rel_path = path.parent.relative_to(Path.cwd()) 51 | if options.only_name: 52 | modules.add(rel_path.name) 53 | else: 54 | modules.add(str(rel_path)) 55 | 56 | return modules 57 | 58 | 59 | def main(options, args): 60 | modules = set() 61 | for path in options.paths: 62 | modules = modules.union(find_modules(options, path)) 63 | 64 | return modules 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = get_parser() 69 | (options, args) = parser.parse_args() 70 | modules = main(options, args) 71 | 72 | if not options.is_csv: 73 | for module in modules: 74 | print(module) 75 | else: 76 | print(",".join(modules), end="") 77 | -------------------------------------------------------------------------------- /14.0-tiny/odoo.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | addons_path = /mnt/extra-addons 3 | data_dir = /var/lib/odoo 4 | ; admin_passwd = admin 5 | ; csv_internal_sep = , 6 | ; db_maxconn = 64 7 | ; db_name = False 8 | ; db_template = template1 9 | ; dbfilter = .* 10 | ; debug_mode = False 11 | ; email_from = False 12 | ; limit_memory_hard = 2684354560 13 | ; limit_memory_soft = 2147483648 14 | ; limit_request = 8192 15 | ; limit_time_cpu = 60 16 | ; limit_time_real = 120 17 | ; list_db = True 18 | ; log_db = False 19 | ; log_handler = [':INFO'] 20 | ; log_level = info 21 | ; logfile = None 22 | ; longpolling_port = 8072 23 | ; max_cron_threads = 2 24 | ; osv_memory_age_limit = 1.0 25 | ; osv_memory_count_limit = False 26 | ; smtp_password = False 27 | ; smtp_port = 25 28 | ; smtp_server = localhost 29 | ; smtp_ssl = False 30 | ; smtp_user = False 31 | ; workers = 0 32 | ; xmlrpc = True 33 | ; xmlrpc_interface = 34 | ; xmlrpc_port = 8069 35 | ; xmlrpcs = True 36 | ; xmlrpcs_interface = 37 | ; xmlrpcs_port = 8071 38 | -------------------------------------------------------------------------------- /14.0-tiny/prepare_project.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | import toml 4 | from giturlparse import parse 5 | from contextlib import contextmanager 6 | import os 7 | from subprocess import run 8 | from urllib.parse import urlparse 9 | 10 | 11 | def get_services(services): 12 | return { 13 | service.get('name'): service 14 | for service in services.get('services') 15 | } 16 | 17 | 18 | def addons_by_project(options, addons): 19 | res = {} 20 | 21 | for addon in addons: 22 | if addon['url'] == 'self': 23 | if options.ignore_self: 24 | continue 25 | parsed = parse(options.url) 26 | else: 27 | parsed = parse(addon['url']) 28 | 29 | auth = parsed.protocol in ['git', 'ssh'] 30 | 31 | res[parsed.repo] = dict( 32 | addon, 33 | url=parsed.url2https, 34 | auth=auth 35 | ) 36 | 37 | return res 38 | 39 | 40 | def merge_addons(options, base, other): 41 | base_addons = addons_by_project(options, base) 42 | other_addons = addons_by_project(options, other) 43 | 44 | for name, addon in other_addons.items(): 45 | if name not in base_addons: 46 | base_addons[name] = addon 47 | else: 48 | base_addons[name] = dict(base_addons[name], **addon) 49 | 50 | return [ 51 | addon 52 | for addon in base_addons.values() 53 | ] 54 | 55 | 56 | def merge_services(options, base, other): 57 | basic_inherit = dict(base, **other) 58 | 59 | if base.get('addons') or other.get('addons'): 60 | basic_inherit['addons'] = merge_addons( 61 | options, 62 | base.get('addons', []), 63 | other.get('addons', []) 64 | ) 65 | 66 | return basic_inherit 67 | 68 | 69 | def compile_service(options, services, name): 70 | service = services.get(name, {}) 71 | if 'inherit' in service: 72 | merge_service = compile_service(options, services, service['inherit']) 73 | service = merge_services(options, merge_service, service) 74 | 75 | return service 76 | 77 | 78 | def get_parser(): 79 | parser = OptionParser() 80 | 81 | parser.add_option( 82 | '-f', 83 | '--file', 84 | dest="file", 85 | help="Input File" 86 | ) 87 | 88 | parser.add_option( 89 | '--url', 90 | dest='url', 91 | help="Url of self project" 92 | ) 93 | 94 | parser.add_option( 95 | '-o', 96 | '--output', 97 | dest="output_directory", 98 | help="Output Directory" 99 | ) 100 | 101 | parser.add_option( 102 | '-e', 103 | dest="env", 104 | help="Environment to prepare" 105 | ) 106 | 107 | parser.add_option( 108 | '--username', 109 | dest="username", 110 | help="Username to replace with", 111 | ) 112 | 113 | parser.add_option( 114 | '--password', 115 | dest="password", 116 | help="password to set on https urls" 117 | ) 118 | 119 | parser.add_option( 120 | '-b', 121 | '--branch', 122 | dest="branch", 123 | help="Default branch if no ref is defined" 124 | ) 125 | 126 | parser.add_option( 127 | '--ignore-self', 128 | dest="ignore_self", 129 | action="store_true", 130 | help="Ignore self url as it's already fetched", 131 | default=False 132 | ) 133 | 134 | return parser 135 | 136 | 137 | @contextmanager 138 | def cd(directory): 139 | cwd = Path.cwd() 140 | try: 141 | os.chdir(directory) 142 | yield 143 | finally: 144 | os.chdir(cwd) 145 | 146 | 147 | def fetch_addons(options, addon): 148 | parsed = parse(addon['url']) 149 | 150 | url = urlparse(parsed.url2https) 151 | 152 | if addon['auth'] and options.username and options.password: 153 | url = url._replace( 154 | netloc="{}:{}@{}".format( 155 | options.username, 156 | options.password, 157 | url.netloc 158 | ) 159 | ) 160 | 161 | repo_path = Path.cwd() / options.output_directory / parsed.repo 162 | 163 | repo_path.mkdir(exist_ok=True) 164 | 165 | with cd(repo_path): 166 | run(['git', 'init']) 167 | run(['git', 'remote', 'add', 'origin', url.geturl()]) 168 | 169 | ref = addon.get('commit') or addon.get('branch') or options.branch 170 | 171 | if ref: 172 | run(['git', 'fetch', 'origin', ref]) 173 | else: 174 | run(['git', 'fetch', 'origin']) 175 | 176 | run(['git', 'checkout', 'FETCH_HEAD']) 177 | run(['git', 'remote', 'remove', 'origin']) 178 | 179 | 180 | def main(options, args): 181 | with Path(options.file).open('r') as fin: 182 | services = toml.loads(fin.read()) 183 | 184 | by_name = get_services(services) 185 | 186 | outputdir = Path(options.output_directory) 187 | outputdir.mkdir(exist_ok=True) 188 | 189 | service = compile_service(options, by_name, options.env) 190 | 191 | for addons in service.get('addons', []): 192 | fetch_addons(options, addons) 193 | 194 | 195 | if __name__ == '__main__': 196 | parser = get_parser() 197 | (options, args) = parser.parse_args() 198 | main(options, args) 199 | -------------------------------------------------------------------------------- /14.0/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | LABEL maintainer="Archeti " 3 | 4 | # Generate locale C.UTF-8 for postgres and general locale data 5 | ENV LANG C.UTF-8 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | 8 | # Install some dependencies python3.7 9 | RUN set -x; \ 10 | apt-get update \ 11 | && apt-get install -y --no-install-recommends \ 12 | python3-wheel \ 13 | python3-setuptools \ 14 | python3-pip \ 15 | # python3.7 \ 16 | # libpython3.7 \ 17 | curl \ 18 | gnupg \ 19 | libpq-dev \ 20 | libsasl2-2 \ 21 | libldap-2.4-2 \ 22 | libxml2 \ 23 | libxmlsec1 \ 24 | libxslt1.1 \ 25 | sudo \ 26 | node-less \ 27 | # python3-yaml \ 28 | && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 1 \ 29 | # && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 2 \ 30 | && update-alternatives --install /usr/bin/python python /usr/bin/python3.6 1 \ 31 | && update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 2 \ 32 | && curl -o wkhtmltox.deb -sSL https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb \ 33 | && apt-get install -y --no-install-recommends ./wkhtmltox.deb \ 34 | && rm -rf /var/lib/apt/lists/* wkhtmltox.deb 35 | 36 | # Install latest postgresql-client 37 | RUN set -x; \ 38 | echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > etc/apt/sources.list.d/pgdg.list \ 39 | && export GNUPGHOME="$(mktemp -d)" \ 40 | && repokey='B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8' \ 41 | && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \ 42 | && gpg --batch --armor --export "${repokey}" > /etc/apt/trusted.gpg.d/pgdg.gpg.asc \ 43 | && gpgconf --kill all \ 44 | && rm -rf "$GNUPGHOME" \ 45 | && apt-get update \ 46 | && apt-get install -y postgresql-client \ 47 | && rm -rf /var/lib/apt/lists/* 48 | 49 | ENV PATH=/usr/local/bin:$PATH 50 | # Install Odoo Including things from sources 51 | ENV ODOO_VERSION 14.0 52 | ENV ODOO_RELEASE=20210212 53 | ARG ODOO_ARCHIVE=odoo_${ODOO_VERSION}.${ODOO_RELEASE}.tar.gz 54 | RUN set -x; \ 55 | apt-get update \ 56 | && apt-get install -y --no-install-recommends \ 57 | build-essential \ 58 | python3.6-dev \ 59 | libsasl2-dev \ 60 | libldap2-dev \ 61 | libxml2-dev \ 62 | libxmlsec1-dev \ 63 | libxslt1-dev \ 64 | && pip install -U pip \ 65 | && /usr/bin/env pip install \ 66 | psycogreen \ 67 | \ 68 | && curl -o requirements.txt https://raw.githubusercontent.com/odoo/odoo/${ODOO_VERSION}/requirements.txt \ 69 | && pip install -r requirements.txt \ 70 | && /usr/bin/env pip install https://nightly.odoo.com/${ODOO_VERSION}/nightly/src/${ODOO_ARCHIVE} \ 71 | && cd / \ 72 | && apt-get --purge remove -y \ 73 | build-essential \ 74 | python3.6-dev \ 75 | libsasl2-dev \ 76 | libldap2-dev \ 77 | libxml2-dev \ 78 | libxmlsec1-dev \ 79 | libxslt1-dev \ 80 | && apt-get autoremove -y \ 81 | && rm -rf /var/lib/apt/lists/* 82 | 83 | VOLUME /etc/odoo 84 | VOLUME /var/lib/odoo 85 | 86 | COPY ./odoo.conf /etc/odoo/ 87 | COPY ./entrypoint.py / 88 | COPY ./sudo-entrypoint.py / 89 | COPY ./find_modules.py /scripts/find_modules.py 90 | COPY ./prepare_project.py /scripts/prepare_project.py 91 | 92 | ARG UID=1000 93 | ARG GID=1000 94 | 95 | RUN mkdir /addons \ 96 | && groupadd -r -g ${GID} odoo \ 97 | && useradd -r -u ${UID} -g odoo -b /var/lib -m odoo \ 98 | && chown odoo /etc/odoo/odoo.conf \ 99 | && chown -R odoo:odoo /addons \ 100 | && echo "odoo ALL=(ALL:ALL) NOPASSWD:SETENV: /sudo-entrypoint.py" >> /etc/sudoers 101 | 102 | ENV ODOO_RC /etc/odoo/odoo.conf 103 | ENV ODOO_BASE_PATH /usr/local/lib/python3.6/dist-packages/odoo/addons 104 | # Env variable defined to monitor the kind of service running 105 | # it could be a staging/production/test or anything and undefined 106 | # is the default in case we need to know servers that aren't correctly 107 | # defined 108 | ENV DEPLOYMENT_AREA undefined 109 | 110 | expose 8069 111 | expose 8071 112 | 113 | USER odoo 114 | 115 | LABEL version="14.0" 116 | LABEL release="20210212" 117 | 118 | LABEL org.opencontainers.image.created="2021-06-25T17:55:41.197637" 119 | LABEL org.opencontainers.image.url="https://hub.docker.com/r/llacroix/odoo" 120 | LABEL org.opencontainers.image.authors="Archeti " 121 | LABEL org.opencontainers.image.documentation="https://github.com/llacroix/odoo-docker" 122 | LABEL org.opencontainers.image.source="https://github.com/llacroix/odoo-docker" 123 | LABEL org.opencontainers.image.version="14.0" 124 | LABEL org.opencontainers.image.vendor="ArcheTI" 125 | LABEL org.opencontainers.image.ref.name="14.0" 126 | LABEL org.opencontainers.image.title="Odoo 14.0" 127 | LABEL org.opencontainers.image.description="Full featured odoo image that make odoo deployment fun and secure." 128 | 129 | ENTRYPOINT ["/entrypoint.py"] 130 | 131 | cmd ["odoo"] 132 | -------------------------------------------------------------------------------- /14.0/find_modules.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | from itertools import chain 4 | 5 | 6 | def get_parser(): 7 | parser = OptionParser() 8 | 9 | parser.add_option( 10 | "-p", 11 | dest='paths', 12 | action="append", 13 | help="Location in which to search", 14 | default=[] 15 | ) 16 | 17 | parser.add_option( 18 | "--only-name", 19 | dest="only_name", 20 | action="store_true", 21 | help="Only display module name instead of path", 22 | default=False, 23 | ) 24 | 25 | parser.add_option( 26 | '--csv', 27 | dest="is_csv", 28 | action="store_true", 29 | help="Output as a comma separated list", 30 | default=False 31 | ) 32 | 33 | return parser 34 | 35 | 36 | def find_modules(options, path): 37 | modules = set() 38 | 39 | path = Path.cwd() / path 40 | 41 | erp_manifest = '__openerp__.py' 42 | odoo_manifest = '__manifest__.py' 43 | 44 | manifest_globs = chain( 45 | path.glob('**/{}'.format(erp_manifest)), 46 | path.glob('**/{}'.format(odoo_manifest)), 47 | ) 48 | 49 | for path in manifest_globs: 50 | rel_path = path.parent.relative_to(Path.cwd()) 51 | if options.only_name: 52 | modules.add(rel_path.name) 53 | else: 54 | modules.add(str(rel_path)) 55 | 56 | return modules 57 | 58 | 59 | def main(options, args): 60 | modules = set() 61 | for path in options.paths: 62 | modules = modules.union(find_modules(options, path)) 63 | 64 | return modules 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = get_parser() 69 | (options, args) = parser.parse_args() 70 | modules = main(options, args) 71 | 72 | if not options.is_csv: 73 | for module in modules: 74 | print(module) 75 | else: 76 | print(",".join(modules), end="") 77 | -------------------------------------------------------------------------------- /14.0/odoo.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | addons_path = /mnt/extra-addons 3 | data_dir = /var/lib/odoo 4 | ; admin_passwd = admin 5 | ; csv_internal_sep = , 6 | ; db_maxconn = 64 7 | ; db_name = False 8 | ; db_template = template1 9 | ; dbfilter = .* 10 | ; debug_mode = False 11 | ; email_from = False 12 | ; limit_memory_hard = 2684354560 13 | ; limit_memory_soft = 2147483648 14 | ; limit_request = 8192 15 | ; limit_time_cpu = 60 16 | ; limit_time_real = 120 17 | ; list_db = True 18 | ; log_db = False 19 | ; log_handler = [':INFO'] 20 | ; log_level = info 21 | ; logfile = None 22 | ; longpolling_port = 8072 23 | ; max_cron_threads = 2 24 | ; osv_memory_age_limit = 1.0 25 | ; osv_memory_count_limit = False 26 | ; smtp_password = False 27 | ; smtp_port = 25 28 | ; smtp_server = localhost 29 | ; smtp_ssl = False 30 | ; smtp_user = False 31 | ; workers = 0 32 | ; xmlrpc = True 33 | ; xmlrpc_interface = 34 | ; xmlrpc_port = 8069 35 | ; xmlrpcs = True 36 | ; xmlrpcs_interface = 37 | ; xmlrpcs_port = 8071 38 | -------------------------------------------------------------------------------- /14.0/prepare_project.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | import toml 4 | from giturlparse import parse 5 | from contextlib import contextmanager 6 | import os 7 | from subprocess import run 8 | from urllib.parse import urlparse 9 | 10 | 11 | def get_services(services): 12 | return { 13 | service.get('name'): service 14 | for service in services.get('services') 15 | } 16 | 17 | 18 | def addons_by_project(options, addons): 19 | res = {} 20 | 21 | for addon in addons: 22 | if addon['url'] == 'self': 23 | if options.ignore_self: 24 | continue 25 | parsed = parse(options.url) 26 | else: 27 | parsed = parse(addon['url']) 28 | 29 | auth = parsed.protocol in ['git', 'ssh'] 30 | 31 | res[parsed.repo] = dict( 32 | addon, 33 | url=parsed.url2https, 34 | auth=auth 35 | ) 36 | 37 | return res 38 | 39 | 40 | def merge_addons(options, base, other): 41 | base_addons = addons_by_project(options, base) 42 | other_addons = addons_by_project(options, other) 43 | 44 | for name, addon in other_addons.items(): 45 | if name not in base_addons: 46 | base_addons[name] = addon 47 | else: 48 | base_addons[name] = dict(base_addons[name], **addon) 49 | 50 | return [ 51 | addon 52 | for addon in base_addons.values() 53 | ] 54 | 55 | 56 | def merge_services(options, base, other): 57 | basic_inherit = dict(base, **other) 58 | 59 | if base.get('addons') or other.get('addons'): 60 | basic_inherit['addons'] = merge_addons( 61 | options, 62 | base.get('addons', []), 63 | other.get('addons', []) 64 | ) 65 | 66 | return basic_inherit 67 | 68 | 69 | def compile_service(options, services, name): 70 | service = services.get(name, {}) 71 | if 'inherit' in service: 72 | merge_service = compile_service(options, services, service['inherit']) 73 | service = merge_services(options, merge_service, service) 74 | 75 | return service 76 | 77 | 78 | def get_parser(): 79 | parser = OptionParser() 80 | 81 | parser.add_option( 82 | '-f', 83 | '--file', 84 | dest="file", 85 | help="Input File" 86 | ) 87 | 88 | parser.add_option( 89 | '--url', 90 | dest='url', 91 | help="Url of self project" 92 | ) 93 | 94 | parser.add_option( 95 | '-o', 96 | '--output', 97 | dest="output_directory", 98 | help="Output Directory" 99 | ) 100 | 101 | parser.add_option( 102 | '-e', 103 | dest="env", 104 | help="Environment to prepare" 105 | ) 106 | 107 | parser.add_option( 108 | '--username', 109 | dest="username", 110 | help="Username to replace with", 111 | ) 112 | 113 | parser.add_option( 114 | '--password', 115 | dest="password", 116 | help="password to set on https urls" 117 | ) 118 | 119 | parser.add_option( 120 | '-b', 121 | '--branch', 122 | dest="branch", 123 | help="Default branch if no ref is defined" 124 | ) 125 | 126 | parser.add_option( 127 | '--ignore-self', 128 | dest="ignore_self", 129 | action="store_true", 130 | help="Ignore self url as it's already fetched", 131 | default=False 132 | ) 133 | 134 | return parser 135 | 136 | 137 | @contextmanager 138 | def cd(directory): 139 | cwd = Path.cwd() 140 | try: 141 | os.chdir(directory) 142 | yield 143 | finally: 144 | os.chdir(cwd) 145 | 146 | 147 | def fetch_addons(options, addon): 148 | parsed = parse(addon['url']) 149 | 150 | url = urlparse(parsed.url2https) 151 | 152 | if addon['auth'] and options.username and options.password: 153 | url = url._replace( 154 | netloc="{}:{}@{}".format( 155 | options.username, 156 | options.password, 157 | url.netloc 158 | ) 159 | ) 160 | 161 | repo_path = Path.cwd() / options.output_directory / parsed.repo 162 | 163 | repo_path.mkdir(exist_ok=True) 164 | 165 | with cd(repo_path): 166 | run(['git', 'init']) 167 | run(['git', 'remote', 'add', 'origin', url.geturl()]) 168 | 169 | ref = addon.get('commit') or addon.get('branch') or options.branch 170 | 171 | if ref: 172 | run(['git', 'fetch', 'origin', ref]) 173 | else: 174 | run(['git', 'fetch', 'origin']) 175 | 176 | run(['git', 'checkout', 'FETCH_HEAD']) 177 | run(['git', 'remote', 'remove', 'origin']) 178 | 179 | 180 | def main(options, args): 181 | with Path(options.file).open('r') as fin: 182 | services = toml.loads(fin.read()) 183 | 184 | by_name = get_services(services) 185 | 186 | outputdir = Path(options.output_directory) 187 | outputdir.mkdir(exist_ok=True) 188 | 189 | service = compile_service(options, by_name, options.env) 190 | 191 | for addons in service.get('addons', []): 192 | fetch_addons(options, addons) 193 | 194 | 195 | if __name__ == '__main__': 196 | parser = get_parser() 197 | (options, args) = parser.parse_args() 198 | main(options, args) 199 | -------------------------------------------------------------------------------- /14.3/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | LABEL maintainer="Archeti " 3 | 4 | # Generate locale C.UTF-8 for postgres and general locale data 5 | ENV LANG C.UTF-8 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | 8 | # Install some dependencies python3.7 9 | RUN set -x; \ 10 | apt-get update \ 11 | && apt-get install -y --no-install-recommends \ 12 | python3-wheel \ 13 | python3-setuptools \ 14 | python3-pip \ 15 | # python3.7 \ 16 | # libpython3.7 \ 17 | curl \ 18 | gnupg \ 19 | libpq-dev \ 20 | libsasl2-2 \ 21 | libldap-2.4-2 \ 22 | libxml2 \ 23 | libxmlsec1 \ 24 | libxslt1.1 \ 25 | sudo \ 26 | node-less \ 27 | # python3-yaml \ 28 | && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 1 \ 29 | # && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 2 \ 30 | && update-alternatives --install /usr/bin/python python /usr/bin/python3.6 1 \ 31 | && update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 2 \ 32 | && curl -o wkhtmltox.deb -sSL https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb \ 33 | && apt-get install -y --no-install-recommends ./wkhtmltox.deb \ 34 | && rm -rf /var/lib/apt/lists/* wkhtmltox.deb 35 | 36 | # Install latest postgresql-client 37 | RUN set -x; \ 38 | echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > etc/apt/sources.list.d/pgdg.list \ 39 | && export GNUPGHOME="$(mktemp -d)" \ 40 | && repokey='B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8' \ 41 | && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \ 42 | && gpg --batch --armor --export "${repokey}" > /etc/apt/trusted.gpg.d/pgdg.gpg.asc \ 43 | && gpgconf --kill all \ 44 | && rm -rf "$GNUPGHOME" \ 45 | && apt-get update \ 46 | && apt-get install -y postgresql-client \ 47 | && rm -rf /var/lib/apt/lists/* 48 | 49 | ENV PATH=/usr/local/bin:$PATH 50 | # Install Odoo Including things from sources 51 | ENV ODOO_VERSION 14.3 52 | ENV ODOO_RELEASE=20210402 53 | ARG ODOO_ARCHIVE=odoo_14.3alpha1.${ODOO_RELEASE}.tar.gz 54 | RUN set -x; \ 55 | apt-get update \ 56 | && apt-get install -y --no-install-recommends \ 57 | build-essential \ 58 | python3.6-dev \ 59 | libsasl2-dev \ 60 | libldap2-dev \ 61 | libxml2-dev \ 62 | libxmlsec1-dev \ 63 | libxslt1-dev \ 64 | && pip install -U pip \ 65 | && /usr/bin/env pip install \ 66 | psycogreen \ 67 | \ 68 | && curl -o requirements.txt https://raw.githubusercontent.com/odoo/odoo/master/requirements.txt \ 69 | && pip install -r requirements.txt \ 70 | && /usr/bin/env pip install https://nightly.odoo.com/master/nightly/src/${ODOO_ARCHIVE} \ 71 | && cd / \ 72 | && apt-get --purge remove -y \ 73 | build-essential \ 74 | python3.6-dev \ 75 | libsasl2-dev \ 76 | libldap2-dev \ 77 | libxml2-dev \ 78 | libxmlsec1-dev \ 79 | libxslt1-dev \ 80 | && apt-get autoremove -y \ 81 | && rm -rf /var/lib/apt/lists/* 82 | 83 | VOLUME /etc/odoo 84 | VOLUME /var/lib/odoo 85 | 86 | COPY ./odoo.conf /etc/odoo/ 87 | COPY ./entrypoint.py / 88 | COPY ./sudo-entrypoint.py / 89 | COPY ./find_modules.py /scripts/find_modules.py 90 | COPY ./prepare_project.py /scripts/prepare_project.py 91 | 92 | ARG UID=1000 93 | ARG GID=1000 94 | 95 | RUN mkdir /addons \ 96 | && groupadd -r -g ${GID} odoo \ 97 | && useradd -r -u ${UID} -g odoo -b /var/lib -m odoo \ 98 | && chown odoo /etc/odoo/odoo.conf \ 99 | && chown -R odoo:odoo /addons \ 100 | && echo "odoo ALL=(ALL:ALL) NOPASSWD:SETENV: /sudo-entrypoint.py" >> /etc/sudoers 101 | 102 | ENV ODOO_RC /etc/odoo/odoo.conf 103 | ENV ODOO_BASE_PATH /usr/local/lib/python3.6/dist-packages/odoo/addons 104 | # Env variable defined to monitor the kind of service running 105 | # it could be a staging/production/test or anything and undefined 106 | # is the default in case we need to know servers that aren't correctly 107 | # defined 108 | ENV DEPLOYMENT_AREA undefined 109 | 110 | expose 8069 111 | expose 8071 112 | 113 | USER odoo 114 | 115 | LABEL version="14.3" 116 | LABEL release="20210402" 117 | 118 | LABEL org.opencontainers.image.created="2021-06-25T17:55:41.204087" 119 | LABEL org.opencontainers.image.url="https://hub.docker.com/r/llacroix/odoo" 120 | LABEL org.opencontainers.image.authors="Archeti " 121 | LABEL org.opencontainers.image.documentation="https://github.com/llacroix/odoo-docker" 122 | LABEL org.opencontainers.image.source="https://github.com/llacroix/odoo-docker" 123 | LABEL org.opencontainers.image.version="14.3" 124 | LABEL org.opencontainers.image.vendor="ArcheTI" 125 | LABEL org.opencontainers.image.ref.name="14.3" 126 | LABEL org.opencontainers.image.title="Odoo 14.3" 127 | LABEL org.opencontainers.image.description="Full featured odoo image that make odoo deployment fun and secure." 128 | 129 | ENTRYPOINT ["/entrypoint.py"] 130 | 131 | cmd ["odoo"] 132 | -------------------------------------------------------------------------------- /14.3/find_modules.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | from itertools import chain 4 | 5 | 6 | def get_parser(): 7 | parser = OptionParser() 8 | 9 | parser.add_option( 10 | "-p", 11 | dest='paths', 12 | action="append", 13 | help="Location in which to search", 14 | default=[] 15 | ) 16 | 17 | parser.add_option( 18 | "--only-name", 19 | dest="only_name", 20 | action="store_true", 21 | help="Only display module name instead of path", 22 | default=False, 23 | ) 24 | 25 | parser.add_option( 26 | '--csv', 27 | dest="is_csv", 28 | action="store_true", 29 | help="Output as a comma separated list", 30 | default=False 31 | ) 32 | 33 | return parser 34 | 35 | 36 | def find_modules(options, path): 37 | modules = set() 38 | 39 | path = Path.cwd() / path 40 | 41 | erp_manifest = '__openerp__.py' 42 | odoo_manifest = '__manifest__.py' 43 | 44 | manifest_globs = chain( 45 | path.glob('**/{}'.format(erp_manifest)), 46 | path.glob('**/{}'.format(odoo_manifest)), 47 | ) 48 | 49 | for path in manifest_globs: 50 | rel_path = path.parent.relative_to(Path.cwd()) 51 | if options.only_name: 52 | modules.add(rel_path.name) 53 | else: 54 | modules.add(str(rel_path)) 55 | 56 | return modules 57 | 58 | 59 | def main(options, args): 60 | modules = set() 61 | for path in options.paths: 62 | modules = modules.union(find_modules(options, path)) 63 | 64 | return modules 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = get_parser() 69 | (options, args) = parser.parse_args() 70 | modules = main(options, args) 71 | 72 | if not options.is_csv: 73 | for module in modules: 74 | print(module) 75 | else: 76 | print(",".join(modules), end="") 77 | -------------------------------------------------------------------------------- /14.3/odoo.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | addons_path = /mnt/extra-addons 3 | data_dir = /var/lib/odoo 4 | ; admin_passwd = admin 5 | ; csv_internal_sep = , 6 | ; db_maxconn = 64 7 | ; db_name = False 8 | ; db_template = template1 9 | ; dbfilter = .* 10 | ; debug_mode = False 11 | ; email_from = False 12 | ; limit_memory_hard = 2684354560 13 | ; limit_memory_soft = 2147483648 14 | ; limit_request = 8192 15 | ; limit_time_cpu = 60 16 | ; limit_time_real = 120 17 | ; list_db = True 18 | ; log_db = False 19 | ; log_handler = [':INFO'] 20 | ; log_level = info 21 | ; logfile = None 22 | ; longpolling_port = 8072 23 | ; max_cron_threads = 2 24 | ; osv_memory_age_limit = 1.0 25 | ; osv_memory_count_limit = False 26 | ; smtp_password = False 27 | ; smtp_port = 25 28 | ; smtp_server = localhost 29 | ; smtp_ssl = False 30 | ; smtp_user = False 31 | ; workers = 0 32 | ; xmlrpc = True 33 | ; xmlrpc_interface = 34 | ; xmlrpc_port = 8069 35 | ; xmlrpcs = True 36 | ; xmlrpcs_interface = 37 | ; xmlrpcs_port = 8071 38 | -------------------------------------------------------------------------------- /14.3/prepare_project.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | import toml 4 | from giturlparse import parse 5 | from contextlib import contextmanager 6 | import os 7 | from subprocess import run 8 | from urllib.parse import urlparse 9 | 10 | 11 | def get_services(services): 12 | return { 13 | service.get('name'): service 14 | for service in services.get('services') 15 | } 16 | 17 | 18 | def addons_by_project(options, addons): 19 | res = {} 20 | 21 | for addon in addons: 22 | if addon['url'] == 'self': 23 | if options.ignore_self: 24 | continue 25 | parsed = parse(options.url) 26 | else: 27 | parsed = parse(addon['url']) 28 | 29 | auth = parsed.protocol in ['git', 'ssh'] 30 | 31 | res[parsed.repo] = dict( 32 | addon, 33 | url=parsed.url2https, 34 | auth=auth 35 | ) 36 | 37 | return res 38 | 39 | 40 | def merge_addons(options, base, other): 41 | base_addons = addons_by_project(options, base) 42 | other_addons = addons_by_project(options, other) 43 | 44 | for name, addon in other_addons.items(): 45 | if name not in base_addons: 46 | base_addons[name] = addon 47 | else: 48 | base_addons[name] = dict(base_addons[name], **addon) 49 | 50 | return [ 51 | addon 52 | for addon in base_addons.values() 53 | ] 54 | 55 | 56 | def merge_services(options, base, other): 57 | basic_inherit = dict(base, **other) 58 | 59 | if base.get('addons') or other.get('addons'): 60 | basic_inherit['addons'] = merge_addons( 61 | options, 62 | base.get('addons', []), 63 | other.get('addons', []) 64 | ) 65 | 66 | return basic_inherit 67 | 68 | 69 | def compile_service(options, services, name): 70 | service = services.get(name, {}) 71 | if 'inherit' in service: 72 | merge_service = compile_service(options, services, service['inherit']) 73 | service = merge_services(options, merge_service, service) 74 | 75 | return service 76 | 77 | 78 | def get_parser(): 79 | parser = OptionParser() 80 | 81 | parser.add_option( 82 | '-f', 83 | '--file', 84 | dest="file", 85 | help="Input File" 86 | ) 87 | 88 | parser.add_option( 89 | '--url', 90 | dest='url', 91 | help="Url of self project" 92 | ) 93 | 94 | parser.add_option( 95 | '-o', 96 | '--output', 97 | dest="output_directory", 98 | help="Output Directory" 99 | ) 100 | 101 | parser.add_option( 102 | '-e', 103 | dest="env", 104 | help="Environment to prepare" 105 | ) 106 | 107 | parser.add_option( 108 | '--username', 109 | dest="username", 110 | help="Username to replace with", 111 | ) 112 | 113 | parser.add_option( 114 | '--password', 115 | dest="password", 116 | help="password to set on https urls" 117 | ) 118 | 119 | parser.add_option( 120 | '-b', 121 | '--branch', 122 | dest="branch", 123 | help="Default branch if no ref is defined" 124 | ) 125 | 126 | parser.add_option( 127 | '--ignore-self', 128 | dest="ignore_self", 129 | action="store_true", 130 | help="Ignore self url as it's already fetched", 131 | default=False 132 | ) 133 | 134 | return parser 135 | 136 | 137 | @contextmanager 138 | def cd(directory): 139 | cwd = Path.cwd() 140 | try: 141 | os.chdir(directory) 142 | yield 143 | finally: 144 | os.chdir(cwd) 145 | 146 | 147 | def fetch_addons(options, addon): 148 | parsed = parse(addon['url']) 149 | 150 | url = urlparse(parsed.url2https) 151 | 152 | if addon['auth'] and options.username and options.password: 153 | url = url._replace( 154 | netloc="{}:{}@{}".format( 155 | options.username, 156 | options.password, 157 | url.netloc 158 | ) 159 | ) 160 | 161 | repo_path = Path.cwd() / options.output_directory / parsed.repo 162 | 163 | repo_path.mkdir(exist_ok=True) 164 | 165 | with cd(repo_path): 166 | run(['git', 'init']) 167 | run(['git', 'remote', 'add', 'origin', url.geturl()]) 168 | 169 | ref = addon.get('commit') or addon.get('branch') or options.branch 170 | 171 | if ref: 172 | run(['git', 'fetch', 'origin', ref]) 173 | else: 174 | run(['git', 'fetch', 'origin']) 175 | 176 | run(['git', 'checkout', 'FETCH_HEAD']) 177 | run(['git', 'remote', 'remove', 'origin']) 178 | 179 | 180 | def main(options, args): 181 | with Path(options.file).open('r') as fin: 182 | services = toml.loads(fin.read()) 183 | 184 | by_name = get_services(services) 185 | 186 | outputdir = Path(options.output_directory) 187 | outputdir.mkdir(exist_ok=True) 188 | 189 | service = compile_service(options, by_name, options.env) 190 | 191 | for addons in service.get('addons', []): 192 | fetch_addons(options, addons) 193 | 194 | 195 | if __name__ == '__main__': 196 | parser = get_parser() 197 | (options, args) = parser.parse_args() 198 | main(options, args) 199 | -------------------------------------------------------------------------------- /8.0/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | LABEL maintainer="Archeti " 3 | 4 | # Generate locale C.UTF-8 for postgres and general locale data 5 | ENV LANG C.UTF-8 6 | 7 | # Install some dependencies python3.7 8 | RUN set -x; \ 9 | apt-get update \ 10 | && apt-get install -y --no-install-recommends \ 11 | python-wheel \ 12 | python-setuptools \ 13 | python-pip \ 14 | python2.7 \ 15 | libpython2.7 \ 16 | curl \ 17 | gnupg \ 18 | libpq-dev \ 19 | libsasl2-2 \ 20 | libldap-2.4-2 \ 21 | libxml2 \ 22 | libxmlsec1 \ 23 | libxslt1.1 \ 24 | sudo \ 25 | node-less \ 26 | # python-yaml \ 27 | && curl -o wkhtmltox.deb -sSL https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb \ 28 | && apt-get install -y --no-install-recommends ./wkhtmltox.deb \ 29 | && rm -rf /var/lib/apt/lists/* wkhtmltox.deb 30 | 31 | # Install latest postgresql-client 32 | RUN set -x; \ 33 | echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > etc/apt/sources.list.d/pgdg.list \ 34 | && export GNUPGHOME="$(mktemp -d)" \ 35 | && repokey='B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8' \ 36 | && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \ 37 | && gpg --batch --armor --export "${repokey}" > /etc/apt/trusted.gpg.d/pgdg.gpg.asc \ 38 | && gpgconf --kill all \ 39 | && rm -rf "$GNUPGHOME" \ 40 | && apt-get update \ 41 | && apt-get install -y postgresql-client \ 42 | && rm -rf /var/lib/apt/lists/* 43 | 44 | ENV PATH=/usr/local/bin:$PATH 45 | # Install Odoo Including things from sources 46 | ENV ODOO_VERSION 8.0 47 | ENV ODOO_RELEASE=20171001 48 | ARG ODOO_ARCHIVE=odoo_${ODOO_VERSION}.${ODOO_RELEASE}.tar.gz 49 | RUN set -x; \ 50 | apt-get update \ 51 | && apt-get install -y --no-install-recommends \ 52 | build-essential \ 53 | python2.7-dev \ 54 | libsasl2-dev \ 55 | libldap2-dev \ 56 | libxml2-dev \ 57 | libxmlsec1-dev \ 58 | libxslt1-dev \ 59 | && pip install -U pip \ 60 | && /usr/bin/env pip install \ 61 | psycogreen \ 62 | pathlib2 \ 63 | && curl -o requirements.txt https://raw.githubusercontent.com/odoo/odoo/${ODOO_VERSION}/requirements.txt \ 64 | && pip install -U pip \ 65 | && pip install -r requirements.txt \ 66 | && /usr/bin/env pip install https://nightly.odoo.com/${ODOO_VERSION}/nightly/src/${ODOO_ARCHIVE} \ 67 | && cd / \ 68 | && apt-get --purge remove -y \ 69 | build-essential \ 70 | python2.7-dev \ 71 | libsasl2-dev \ 72 | libldap2-dev \ 73 | libxml2-dev \ 74 | libxmlsec1-dev \ 75 | libxslt1-dev \ 76 | && apt-get autoremove -y \ 77 | && rm -rf /var/lib/apt/lists/* 78 | 79 | VOLUME /etc/odoo 80 | VOLUME /var/lib/odoo 81 | 82 | COPY ./odoo.conf /etc/odoo/ 83 | COPY ./entrypoint.py / 84 | COPY ./sudo-entrypoint.py / 85 | COPY ./find_modules.py /scripts/find_modules.py 86 | COPY ./prepare_project.py /scripts/prepare_project.py 87 | 88 | ARG UID=1000 89 | ARG GID=1000 90 | 91 | RUN mkdir /addons \ 92 | && groupadd -r -g ${GID} odoo \ 93 | && useradd -r -u ${UID} -g odoo -b /var/lib -m odoo \ 94 | && chown odoo /etc/odoo/odoo.conf \ 95 | && chown -R odoo:odoo /addons \ 96 | && cp /usr/local/bin/odoo.py /usr/local/bin/odoo || true \ 97 | && echo "odoo ALL=(ALL:ALL) NOPASSWD:SETENV: /sudo-entrypoint.py" >> /etc/sudoers 98 | 99 | ENV OPENERP_SERVER /etc/odoo/odoo.conf 100 | ENV ODOO_RC /etc/odoo/odoo.conf 101 | ENV ODOO_BASE_PATH /usr/local/lib/python2.7/dist-packages/openerp/addons 102 | # Env variable defined to monitor the kind of service running 103 | # it could be a staging/production/test or anything and undefined 104 | # is the default in case we need to know servers that aren't correctly 105 | # defined 106 | ENV DEPLOYMENT_AREA undefined 107 | 108 | expose 8069 109 | expose 8071 110 | 111 | USER odoo 112 | 113 | LABEL version="8.0" 114 | LABEL release="20171001" 115 | 116 | LABEL org.opencontainers.image.created="2021-06-25T17:55:41.191604" 117 | LABEL org.opencontainers.image.url="https://hub.docker.com/r/llacroix/odoo" 118 | LABEL org.opencontainers.image.authors="Archeti " 119 | LABEL org.opencontainers.image.documentation="https://github.com/llacroix/odoo-docker" 120 | LABEL org.opencontainers.image.source="https://github.com/llacroix/odoo-docker" 121 | LABEL org.opencontainers.image.version="8.0" 122 | LABEL org.opencontainers.image.vendor="ArcheTI" 123 | LABEL org.opencontainers.image.ref.name="8.0" 124 | LABEL org.opencontainers.image.title="Odoo 8.0" 125 | LABEL org.opencontainers.image.description="Full featured odoo image that make odoo deployment fun and secure." 126 | 127 | ENTRYPOINT ["/entrypoint.py"] 128 | 129 | cmd ["odoo"] 130 | -------------------------------------------------------------------------------- /8.0/find_modules.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | from itertools import chain 4 | 5 | 6 | def get_parser(): 7 | parser = OptionParser() 8 | 9 | parser.add_option( 10 | "-p", 11 | dest='paths', 12 | action="append", 13 | help="Location in which to search", 14 | default=[] 15 | ) 16 | 17 | parser.add_option( 18 | "--only-name", 19 | dest="only_name", 20 | action="store_true", 21 | help="Only display module name instead of path", 22 | default=False, 23 | ) 24 | 25 | parser.add_option( 26 | '--csv', 27 | dest="is_csv", 28 | action="store_true", 29 | help="Output as a comma separated list", 30 | default=False 31 | ) 32 | 33 | return parser 34 | 35 | 36 | def find_modules(options, path): 37 | modules = set() 38 | 39 | path = Path.cwd() / path 40 | 41 | erp_manifest = '__openerp__.py' 42 | odoo_manifest = '__manifest__.py' 43 | 44 | manifest_globs = chain( 45 | path.glob('**/{}'.format(erp_manifest)), 46 | path.glob('**/{}'.format(odoo_manifest)), 47 | ) 48 | 49 | for path in manifest_globs: 50 | rel_path = path.parent.relative_to(Path.cwd()) 51 | if options.only_name: 52 | modules.add(rel_path.name) 53 | else: 54 | modules.add(str(rel_path)) 55 | 56 | return modules 57 | 58 | 59 | def main(options, args): 60 | modules = set() 61 | for path in options.paths: 62 | modules = modules.union(find_modules(options, path)) 63 | 64 | return modules 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = get_parser() 69 | (options, args) = parser.parse_args() 70 | modules = main(options, args) 71 | 72 | if not options.is_csv: 73 | for module in modules: 74 | print(module) 75 | else: 76 | print(",".join(modules), end="") 77 | -------------------------------------------------------------------------------- /8.0/odoo.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | addons_path = /mnt/extra-addons 3 | data_dir = /var/lib/odoo 4 | ; admin_passwd = admin 5 | ; csv_internal_sep = , 6 | ; db_maxconn = 64 7 | ; db_name = False 8 | ; db_template = template1 9 | ; dbfilter = .* 10 | ; debug_mode = False 11 | ; email_from = False 12 | ; limit_memory_hard = 2684354560 13 | ; limit_memory_soft = 2147483648 14 | ; limit_request = 8192 15 | ; limit_time_cpu = 60 16 | ; limit_time_real = 120 17 | ; list_db = True 18 | ; log_db = False 19 | ; log_handler = [':INFO'] 20 | ; log_level = info 21 | ; logfile = None 22 | ; longpolling_port = 8072 23 | ; max_cron_threads = 2 24 | ; osv_memory_age_limit = 1.0 25 | ; osv_memory_count_limit = False 26 | ; smtp_password = False 27 | ; smtp_port = 25 28 | ; smtp_server = localhost 29 | ; smtp_ssl = False 30 | ; smtp_user = False 31 | ; workers = 0 32 | ; xmlrpc = True 33 | ; xmlrpc_interface = 34 | ; xmlrpc_port = 8069 35 | ; xmlrpcs = True 36 | ; xmlrpcs_interface = 37 | ; xmlrpcs_port = 8071 38 | -------------------------------------------------------------------------------- /8.0/prepare_project.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | import toml 4 | from giturlparse import parse 5 | from contextlib import contextmanager 6 | import os 7 | from subprocess import run 8 | from urllib.parse import urlparse 9 | 10 | 11 | def get_services(services): 12 | return { 13 | service.get('name'): service 14 | for service in services.get('services') 15 | } 16 | 17 | 18 | def addons_by_project(options, addons): 19 | res = {} 20 | 21 | for addon in addons: 22 | if addon['url'] == 'self': 23 | if options.ignore_self: 24 | continue 25 | parsed = parse(options.url) 26 | else: 27 | parsed = parse(addon['url']) 28 | 29 | auth = parsed.protocol in ['git', 'ssh'] 30 | 31 | res[parsed.repo] = dict( 32 | addon, 33 | url=parsed.url2https, 34 | auth=auth 35 | ) 36 | 37 | return res 38 | 39 | 40 | def merge_addons(options, base, other): 41 | base_addons = addons_by_project(options, base) 42 | other_addons = addons_by_project(options, other) 43 | 44 | for name, addon in other_addons.items(): 45 | if name not in base_addons: 46 | base_addons[name] = addon 47 | else: 48 | base_addons[name] = dict(base_addons[name], **addon) 49 | 50 | return [ 51 | addon 52 | for addon in base_addons.values() 53 | ] 54 | 55 | 56 | def merge_services(options, base, other): 57 | basic_inherit = dict(base, **other) 58 | 59 | if base.get('addons') or other.get('addons'): 60 | basic_inherit['addons'] = merge_addons( 61 | options, 62 | base.get('addons', []), 63 | other.get('addons', []) 64 | ) 65 | 66 | return basic_inherit 67 | 68 | 69 | def compile_service(options, services, name): 70 | service = services.get(name, {}) 71 | if 'inherit' in service: 72 | merge_service = compile_service(options, services, service['inherit']) 73 | service = merge_services(options, merge_service, service) 74 | 75 | return service 76 | 77 | 78 | def get_parser(): 79 | parser = OptionParser() 80 | 81 | parser.add_option( 82 | '-f', 83 | '--file', 84 | dest="file", 85 | help="Input File" 86 | ) 87 | 88 | parser.add_option( 89 | '--url', 90 | dest='url', 91 | help="Url of self project" 92 | ) 93 | 94 | parser.add_option( 95 | '-o', 96 | '--output', 97 | dest="output_directory", 98 | help="Output Directory" 99 | ) 100 | 101 | parser.add_option( 102 | '-e', 103 | dest="env", 104 | help="Environment to prepare" 105 | ) 106 | 107 | parser.add_option( 108 | '--username', 109 | dest="username", 110 | help="Username to replace with", 111 | ) 112 | 113 | parser.add_option( 114 | '--password', 115 | dest="password", 116 | help="password to set on https urls" 117 | ) 118 | 119 | parser.add_option( 120 | '-b', 121 | '--branch', 122 | dest="branch", 123 | help="Default branch if no ref is defined" 124 | ) 125 | 126 | parser.add_option( 127 | '--ignore-self', 128 | dest="ignore_self", 129 | action="store_true", 130 | help="Ignore self url as it's already fetched", 131 | default=False 132 | ) 133 | 134 | return parser 135 | 136 | 137 | @contextmanager 138 | def cd(directory): 139 | cwd = Path.cwd() 140 | try: 141 | os.chdir(directory) 142 | yield 143 | finally: 144 | os.chdir(cwd) 145 | 146 | 147 | def fetch_addons(options, addon): 148 | parsed = parse(addon['url']) 149 | 150 | url = urlparse(parsed.url2https) 151 | 152 | if addon['auth'] and options.username and options.password: 153 | url = url._replace( 154 | netloc="{}:{}@{}".format( 155 | options.username, 156 | options.password, 157 | url.netloc 158 | ) 159 | ) 160 | 161 | repo_path = Path.cwd() / options.output_directory / parsed.repo 162 | 163 | repo_path.mkdir(exist_ok=True) 164 | 165 | with cd(repo_path): 166 | run(['git', 'init']) 167 | run(['git', 'remote', 'add', 'origin', url.geturl()]) 168 | 169 | ref = addon.get('commit') or addon.get('branch') or options.branch 170 | 171 | if ref: 172 | run(['git', 'fetch', 'origin', ref]) 173 | else: 174 | run(['git', 'fetch', 'origin']) 175 | 176 | run(['git', 'checkout', 'FETCH_HEAD']) 177 | run(['git', 'remote', 'remove', 'origin']) 178 | 179 | 180 | def main(options, args): 181 | with Path(options.file).open('r') as fin: 182 | services = toml.loads(fin.read()) 183 | 184 | by_name = get_services(services) 185 | 186 | outputdir = Path(options.output_directory) 187 | outputdir.mkdir(exist_ok=True) 188 | 189 | service = compile_service(options, by_name, options.env) 190 | 191 | for addons in service.get('addons', []): 192 | fetch_addons(options, addons) 193 | 194 | 195 | if __name__ == '__main__': 196 | parser = get_parser() 197 | (options, args) = parser.parse_args() 198 | main(options, args) 199 | -------------------------------------------------------------------------------- /8.0/sudo-entrypoint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import time 4 | import os 5 | import shlex 6 | import subprocess 7 | import sys 8 | import glob 9 | import pip 10 | import re 11 | import stat 12 | from os import path 13 | from os.path import expanduser 14 | import shutil 15 | 16 | try: 17 | from pathlib import Path 18 | except ImportError: 19 | from pathlib2 import Path 20 | 21 | 22 | def pipe(args): 23 | """ 24 | Call the process with std(in,out,err) 25 | """ 26 | env = os.environ.copy() 27 | env['DEBIAN_FRONTEND'] = 'noninteractive' 28 | 29 | process = subprocess.Popen( 30 | args, 31 | stdin=sys.stdin, 32 | stdout=sys.stdout, 33 | stderr=sys.stderr, 34 | env=env 35 | ) 36 | 37 | process.wait() 38 | 39 | return process.returncode 40 | 41 | 42 | def get_dirs(cur_path): 43 | return [ 44 | path.join(cur_path, npath) 45 | for npath in os.listdir(cur_path) 46 | if path.isdir(path.join(cur_path, npath)) 47 | ] 48 | 49 | 50 | def get_extra_paths(): 51 | extra_paths = os.environ.get('ODOO_EXTRA_PATHS') 52 | 53 | if not extra_paths: 54 | return [] 55 | 56 | return [ 57 | extra_path.strip() 58 | for extra_path in extra_paths.split(',') 59 | ] 60 | 61 | 62 | def get_addons_paths(): 63 | addons = get_dirs('/addons') 64 | addons += get_extra_paths() 65 | 66 | return [ 67 | Path(path) 68 | for path in addons 69 | ] 70 | 71 | 72 | def install_apt_packages(): 73 | """ 74 | Install debian dependencies. 75 | """ 76 | package_list = set() 77 | 78 | paths = get_addons_paths() 79 | 80 | print("Looking up for packages in {}".format(paths)) 81 | 82 | for addons_path in paths: 83 | for packages in addons_path.glob('**/apt-packages.txt'): 84 | print("Installing packages from %s" % packages) 85 | with open(packages, 'r') as pack_file: 86 | lines = [line.strip() for line in pack_file] 87 | package_list.update(set(lines)) 88 | 89 | extras = os.environ.get('EXTRA_APT_PACKAGES', '') 90 | print(f"Adding extra packages {extras}") 91 | if extras: 92 | for package in extras.split(','): 93 | if not package: 94 | continue 95 | package_list.add(package) 96 | 97 | if len(package_list) > 0: 98 | print(f"Installing {package_list}") 99 | ret = pipe(['apt-get', 'update']) 100 | 101 | # Something went wrong, stop the service as it's failing 102 | if ret != 0: 103 | sys.exit(ret) 104 | 105 | ret = pipe(['apt-get', 'install', '-y'] + list(package_list)) 106 | 107 | # Something went wrong, stop the service as it's failing 108 | if ret != 0: 109 | sys.exit(ret) 110 | 111 | 112 | def load_secrets(): 113 | # TODO add a way to load some secrets so odoo process can 114 | # use secrets as a way to load passwords/user for postgresql 115 | # credentials could also be stored in the HOME of the odoo user 116 | # except we cannot rely on secrets 100% because it only works in 117 | # swarm mode 118 | pgpass_secret = '/run/secrets/.pgpass' 119 | if path.exists(pgpass_secret): 120 | home_folder = '/var/lib/odoo' 121 | pgpass_target = path.join(home_folder, '.pgpass') 122 | if path.exists(pgpass_target): 123 | os.remove(pgpass_target) 124 | # shutil.move doesn't always work correctly on different fs 125 | shutil.copy(pgpass_secret, home_folder) 126 | st = os.stat(pgpass_secret) 127 | os.chmod(pgpass_target, st.st_mode) 128 | os.chown(pgpass_target, st[stat.ST_UID], st[stat.ST_GID]) 129 | # Cannot remove anymore apparently 130 | # os.remove(pgpass_secret) 131 | # shutil.move(pgpass_secret, home_folder) 132 | 133 | 134 | def disable_base_modules(): 135 | base_addons = os.environ.get('ODOO_BASE_PATH', '') 136 | addons_to_remove = os.environ.get('ODOO_DISABLED_MODULES', '') 137 | 138 | modules = addons_to_remove.split(',') 139 | modules = map(lambda mod: mod.strip(), modules) 140 | 141 | if not base_addons: 142 | print("Do not attempt to remove wrong folder") 143 | return 144 | 145 | for module in modules: 146 | if not module: 147 | continue 148 | print("Removing module %s from %s" % (module, base_addons)) 149 | 150 | module_path = Path(base_addons, module) 151 | if module_path.exists() and module_path.is_dir(): 152 | shutil.rmtree(module_path) 153 | else: 154 | print("Module skipped as it doesn't seem to be present.") 155 | 156 | 157 | def fix_access_rights(): 158 | if os.environ.get('RESET_ACCESS_RIGHTS', '') == 'TRUE': 159 | pipe(["chown", "-R", "odoo:odoo", "/var/lib/odoo"]) 160 | pipe(["chown", "-R", "odoo:odoo", "/etc/odoo"]) 161 | 162 | 163 | def remove_sudo(): 164 | return pipe(["sed", "-i", "/odoo/d", "/etc/sudoers"]) 165 | 166 | 167 | def main(): 168 | install_apt_packages() 169 | load_secrets() 170 | fix_access_rights() 171 | disable_base_modules() 172 | return remove_sudo() 173 | 174 | 175 | try: 176 | code = main() 177 | sys.exit(code) 178 | except Exception as exc: 179 | print(exc) 180 | sys.exit(1) 181 | except KeyboardInterrupt as exc: 182 | print(exc) 183 | sys.exit(1) 184 | -------------------------------------------------------------------------------- /9.0/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | LABEL maintainer="Archeti " 3 | 4 | # Generate locale C.UTF-8 for postgres and general locale data 5 | ENV LANG C.UTF-8 6 | 7 | # Install some dependencies python3.7 8 | RUN set -x; \ 9 | apt-get update \ 10 | && apt-get install -y --no-install-recommends \ 11 | python-wheel \ 12 | python-setuptools \ 13 | python-pip \ 14 | python2.7 \ 15 | libpython2.7 \ 16 | curl \ 17 | gnupg \ 18 | libpq-dev \ 19 | libsasl2-2 \ 20 | libldap-2.4-2 \ 21 | libxml2 \ 22 | libxmlsec1 \ 23 | libxslt1.1 \ 24 | sudo \ 25 | node-less \ 26 | # python-yaml \ 27 | && curl -o wkhtmltox.deb -sSL https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb \ 28 | && apt-get install -y --no-install-recommends ./wkhtmltox.deb \ 29 | && rm -rf /var/lib/apt/lists/* wkhtmltox.deb 30 | 31 | # Install latest postgresql-client 32 | RUN set -x; \ 33 | echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > etc/apt/sources.list.d/pgdg.list \ 34 | && export GNUPGHOME="$(mktemp -d)" \ 35 | && repokey='B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8' \ 36 | && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \ 37 | && gpg --batch --armor --export "${repokey}" > /etc/apt/trusted.gpg.d/pgdg.gpg.asc \ 38 | && gpgconf --kill all \ 39 | && rm -rf "$GNUPGHOME" \ 40 | && apt-get update \ 41 | && apt-get install -y postgresql-client \ 42 | && rm -rf /var/lib/apt/lists/* 43 | 44 | ENV PATH=/usr/local/bin:$PATH 45 | # Install Odoo Including things from sources 46 | ENV ODOO_VERSION 9.0c 47 | ENV ODOO_RELEASE=20190401 48 | ARG ODOO_ARCHIVE=odoo_${ODOO_VERSION}.${ODOO_RELEASE}.tar.gz 49 | RUN set -x; \ 50 | apt-get update \ 51 | && apt-get install -y --no-install-recommends \ 52 | build-essential \ 53 | python2.7-dev \ 54 | libsasl2-dev \ 55 | libldap2-dev ruby-sass libjpeg-dev \ 56 | libxml2-dev \ 57 | libxmlsec1-dev \ 58 | libxslt1-dev \ 59 | && pip install -U pip \ 60 | && /usr/bin/env pip install \ 61 | psycogreen \ 62 | pathlib2 \ 63 | && curl -o requirements.txt https://raw.githubusercontent.com/odoo/odoo/9.0/requirements.txt \ 64 | && pip install -U pip \ 65 | && pip install -r requirements.txt \ 66 | && /usr/bin/env pip install https://nightly.odoo.com/9.0/nightly/src/${ODOO_ARCHIVE} \ 67 | && cd / \ 68 | && apt-get --purge remove -y \ 69 | build-essential \ 70 | python2.7-dev \ 71 | libsasl2-dev \ 72 | libldap2-dev \ 73 | libxml2-dev \ 74 | libxmlsec1-dev \ 75 | libxslt1-dev \ 76 | && apt-get autoremove -y \ 77 | && rm -rf /var/lib/apt/lists/* 78 | 79 | VOLUME /etc/odoo 80 | VOLUME /var/lib/odoo 81 | 82 | COPY ./odoo.conf /etc/odoo/ 83 | COPY ./entrypoint.py / 84 | COPY ./sudo-entrypoint.py / 85 | COPY ./find_modules.py /scripts/find_modules.py 86 | COPY ./prepare_project.py /scripts/prepare_project.py 87 | 88 | ARG UID=1000 89 | ARG GID=1000 90 | 91 | RUN mkdir /addons \ 92 | && groupadd -r -g ${GID} odoo \ 93 | && useradd -r -u ${UID} -g odoo -b /var/lib -m odoo \ 94 | && chown odoo /etc/odoo/odoo.conf \ 95 | && chown -R odoo:odoo /addons \ 96 | && cp /usr/local/bin/odoo.py /usr/local/bin/odoo || true \ 97 | && echo "odoo ALL=(ALL:ALL) NOPASSWD:SETENV: /sudo-entrypoint.py" >> /etc/sudoers 98 | 99 | ENV OPENERP_SERVER /etc/odoo/odoo.conf 100 | ENV ODOO_RC /etc/odoo/odoo.conf 101 | ENV ODOO_BASE_PATH /usr/local/lib/python2.7/dist-packages/openerp/addons 102 | # Env variable defined to monitor the kind of service running 103 | # it could be a staging/production/test or anything and undefined 104 | # is the default in case we need to know servers that aren't correctly 105 | # defined 106 | ENV DEPLOYMENT_AREA undefined 107 | 108 | expose 8069 109 | expose 8071 110 | 111 | USER odoo 112 | 113 | LABEL version="9.0c" 114 | LABEL release="20190401" 115 | 116 | LABEL org.opencontainers.image.created="2021-06-25T17:55:41.192956" 117 | LABEL org.opencontainers.image.url="https://hub.docker.com/r/llacroix/odoo" 118 | LABEL org.opencontainers.image.authors="Archeti " 119 | LABEL org.opencontainers.image.documentation="https://github.com/llacroix/odoo-docker" 120 | LABEL org.opencontainers.image.source="https://github.com/llacroix/odoo-docker" 121 | LABEL org.opencontainers.image.version="9.0c" 122 | LABEL org.opencontainers.image.vendor="ArcheTI" 123 | LABEL org.opencontainers.image.ref.name="9.0" 124 | LABEL org.opencontainers.image.title="Odoo 9.0c" 125 | LABEL org.opencontainers.image.description="Full featured odoo image that make odoo deployment fun and secure." 126 | 127 | ENTRYPOINT ["/entrypoint.py"] 128 | 129 | cmd ["odoo"] 130 | -------------------------------------------------------------------------------- /9.0/find_modules.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | from itertools import chain 4 | 5 | 6 | def get_parser(): 7 | parser = OptionParser() 8 | 9 | parser.add_option( 10 | "-p", 11 | dest='paths', 12 | action="append", 13 | help="Location in which to search", 14 | default=[] 15 | ) 16 | 17 | parser.add_option( 18 | "--only-name", 19 | dest="only_name", 20 | action="store_true", 21 | help="Only display module name instead of path", 22 | default=False, 23 | ) 24 | 25 | parser.add_option( 26 | '--csv', 27 | dest="is_csv", 28 | action="store_true", 29 | help="Output as a comma separated list", 30 | default=False 31 | ) 32 | 33 | return parser 34 | 35 | 36 | def find_modules(options, path): 37 | modules = set() 38 | 39 | path = Path.cwd() / path 40 | 41 | erp_manifest = '__openerp__.py' 42 | odoo_manifest = '__manifest__.py' 43 | 44 | manifest_globs = chain( 45 | path.glob('**/{}'.format(erp_manifest)), 46 | path.glob('**/{}'.format(odoo_manifest)), 47 | ) 48 | 49 | for path in manifest_globs: 50 | rel_path = path.parent.relative_to(Path.cwd()) 51 | if options.only_name: 52 | modules.add(rel_path.name) 53 | else: 54 | modules.add(str(rel_path)) 55 | 56 | return modules 57 | 58 | 59 | def main(options, args): 60 | modules = set() 61 | for path in options.paths: 62 | modules = modules.union(find_modules(options, path)) 63 | 64 | return modules 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = get_parser() 69 | (options, args) = parser.parse_args() 70 | modules = main(options, args) 71 | 72 | if not options.is_csv: 73 | for module in modules: 74 | print(module) 75 | else: 76 | print(",".join(modules), end="") 77 | -------------------------------------------------------------------------------- /9.0/odoo.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | addons_path = /mnt/extra-addons 3 | data_dir = /var/lib/odoo 4 | ; admin_passwd = admin 5 | ; csv_internal_sep = , 6 | ; db_maxconn = 64 7 | ; db_name = False 8 | ; db_template = template1 9 | ; dbfilter = .* 10 | ; debug_mode = False 11 | ; email_from = False 12 | ; limit_memory_hard = 2684354560 13 | ; limit_memory_soft = 2147483648 14 | ; limit_request = 8192 15 | ; limit_time_cpu = 60 16 | ; limit_time_real = 120 17 | ; list_db = True 18 | ; log_db = False 19 | ; log_handler = [':INFO'] 20 | ; log_level = info 21 | ; logfile = None 22 | ; longpolling_port = 8072 23 | ; max_cron_threads = 2 24 | ; osv_memory_age_limit = 1.0 25 | ; osv_memory_count_limit = False 26 | ; smtp_password = False 27 | ; smtp_port = 25 28 | ; smtp_server = localhost 29 | ; smtp_ssl = False 30 | ; smtp_user = False 31 | ; workers = 0 32 | ; xmlrpc = True 33 | ; xmlrpc_interface = 34 | ; xmlrpc_port = 8069 35 | ; xmlrpcs = True 36 | ; xmlrpcs_interface = 37 | ; xmlrpcs_port = 8071 38 | -------------------------------------------------------------------------------- /9.0/prepare_project.py: -------------------------------------------------------------------------------- 1 | from optparse import OptionParser 2 | from pathlib import Path 3 | import toml 4 | from giturlparse import parse 5 | from contextlib import contextmanager 6 | import os 7 | from subprocess import run 8 | from urllib.parse import urlparse 9 | 10 | 11 | def get_services(services): 12 | return { 13 | service.get('name'): service 14 | for service in services.get('services') 15 | } 16 | 17 | 18 | def addons_by_project(options, addons): 19 | res = {} 20 | 21 | for addon in addons: 22 | if addon['url'] == 'self': 23 | if options.ignore_self: 24 | continue 25 | parsed = parse(options.url) 26 | else: 27 | parsed = parse(addon['url']) 28 | 29 | auth = parsed.protocol in ['git', 'ssh'] 30 | 31 | res[parsed.repo] = dict( 32 | addon, 33 | url=parsed.url2https, 34 | auth=auth 35 | ) 36 | 37 | return res 38 | 39 | 40 | def merge_addons(options, base, other): 41 | base_addons = addons_by_project(options, base) 42 | other_addons = addons_by_project(options, other) 43 | 44 | for name, addon in other_addons.items(): 45 | if name not in base_addons: 46 | base_addons[name] = addon 47 | else: 48 | base_addons[name] = dict(base_addons[name], **addon) 49 | 50 | return [ 51 | addon 52 | for addon in base_addons.values() 53 | ] 54 | 55 | 56 | def merge_services(options, base, other): 57 | basic_inherit = dict(base, **other) 58 | 59 | if base.get('addons') or other.get('addons'): 60 | basic_inherit['addons'] = merge_addons( 61 | options, 62 | base.get('addons', []), 63 | other.get('addons', []) 64 | ) 65 | 66 | return basic_inherit 67 | 68 | 69 | def compile_service(options, services, name): 70 | service = services.get(name, {}) 71 | if 'inherit' in service: 72 | merge_service = compile_service(options, services, service['inherit']) 73 | service = merge_services(options, merge_service, service) 74 | 75 | return service 76 | 77 | 78 | def get_parser(): 79 | parser = OptionParser() 80 | 81 | parser.add_option( 82 | '-f', 83 | '--file', 84 | dest="file", 85 | help="Input File" 86 | ) 87 | 88 | parser.add_option( 89 | '--url', 90 | dest='url', 91 | help="Url of self project" 92 | ) 93 | 94 | parser.add_option( 95 | '-o', 96 | '--output', 97 | dest="output_directory", 98 | help="Output Directory" 99 | ) 100 | 101 | parser.add_option( 102 | '-e', 103 | dest="env", 104 | help="Environment to prepare" 105 | ) 106 | 107 | parser.add_option( 108 | '--username', 109 | dest="username", 110 | help="Username to replace with", 111 | ) 112 | 113 | parser.add_option( 114 | '--password', 115 | dest="password", 116 | help="password to set on https urls" 117 | ) 118 | 119 | parser.add_option( 120 | '-b', 121 | '--branch', 122 | dest="branch", 123 | help="Default branch if no ref is defined" 124 | ) 125 | 126 | parser.add_option( 127 | '--ignore-self', 128 | dest="ignore_self", 129 | action="store_true", 130 | help="Ignore self url as it's already fetched", 131 | default=False 132 | ) 133 | 134 | return parser 135 | 136 | 137 | @contextmanager 138 | def cd(directory): 139 | cwd = Path.cwd() 140 | try: 141 | os.chdir(directory) 142 | yield 143 | finally: 144 | os.chdir(cwd) 145 | 146 | 147 | def fetch_addons(options, addon): 148 | parsed = parse(addon['url']) 149 | 150 | url = urlparse(parsed.url2https) 151 | 152 | if addon['auth'] and options.username and options.password: 153 | url = url._replace( 154 | netloc="{}:{}@{}".format( 155 | options.username, 156 | options.password, 157 | url.netloc 158 | ) 159 | ) 160 | 161 | repo_path = Path.cwd() / options.output_directory / parsed.repo 162 | 163 | repo_path.mkdir(exist_ok=True) 164 | 165 | with cd(repo_path): 166 | run(['git', 'init']) 167 | run(['git', 'remote', 'add', 'origin', url.geturl()]) 168 | 169 | ref = addon.get('commit') or addon.get('branch') or options.branch 170 | 171 | if ref: 172 | run(['git', 'fetch', 'origin', ref]) 173 | else: 174 | run(['git', 'fetch', 'origin']) 175 | 176 | run(['git', 'checkout', 'FETCH_HEAD']) 177 | run(['git', 'remote', 'remove', 'origin']) 178 | 179 | 180 | def main(options, args): 181 | with Path(options.file).open('r') as fin: 182 | services = toml.loads(fin.read()) 183 | 184 | by_name = get_services(services) 185 | 186 | outputdir = Path(options.output_directory) 187 | outputdir.mkdir(exist_ok=True) 188 | 189 | service = compile_service(options, by_name, options.env) 190 | 191 | for addons in service.get('addons', []): 192 | fetch_addons(options, addons) 193 | 194 | 195 | if __name__ == '__main__': 196 | parser = get_parser() 197 | (options, args) = parser.parse_args() 198 | main(options, args) 199 | -------------------------------------------------------------------------------- /9.0/sudo-entrypoint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import time 4 | import os 5 | import shlex 6 | import subprocess 7 | import sys 8 | import glob 9 | import pip 10 | import re 11 | import stat 12 | from os import path 13 | from os.path import expanduser 14 | import shutil 15 | 16 | try: 17 | from pathlib import Path 18 | except ImportError: 19 | from pathlib2 import Path 20 | 21 | 22 | def pipe(args): 23 | """ 24 | Call the process with std(in,out,err) 25 | """ 26 | env = os.environ.copy() 27 | env['DEBIAN_FRONTEND'] = 'noninteractive' 28 | 29 | process = subprocess.Popen( 30 | args, 31 | stdin=sys.stdin, 32 | stdout=sys.stdout, 33 | stderr=sys.stderr, 34 | env=env 35 | ) 36 | 37 | process.wait() 38 | 39 | return process.returncode 40 | 41 | 42 | def get_dirs(cur_path): 43 | return [ 44 | path.join(cur_path, npath) 45 | for npath in os.listdir(cur_path) 46 | if path.isdir(path.join(cur_path, npath)) 47 | ] 48 | 49 | 50 | def get_extra_paths(): 51 | extra_paths = os.environ.get('ODOO_EXTRA_PATHS') 52 | 53 | if not extra_paths: 54 | return [] 55 | 56 | return [ 57 | extra_path.strip() 58 | for extra_path in extra_paths.split(',') 59 | ] 60 | 61 | 62 | def get_addons_paths(): 63 | addons = get_dirs('/addons') 64 | addons += get_extra_paths() 65 | 66 | return [ 67 | Path(path) 68 | for path in addons 69 | ] 70 | 71 | 72 | def install_apt_packages(): 73 | """ 74 | Install debian dependencies. 75 | """ 76 | package_list = set() 77 | 78 | paths = get_addons_paths() 79 | 80 | print("Looking up for packages in {}".format(paths)) 81 | 82 | for addons_path in paths: 83 | for packages in addons_path.glob('**/apt-packages.txt'): 84 | print("Installing packages from %s" % packages) 85 | with open(packages, 'r') as pack_file: 86 | lines = [line.strip() for line in pack_file] 87 | package_list.update(set(lines)) 88 | 89 | extras = os.environ.get('EXTRA_APT_PACKAGES', '') 90 | print(f"Adding extra packages {extras}") 91 | if extras: 92 | for package in extras.split(','): 93 | if not package: 94 | continue 95 | package_list.add(package) 96 | 97 | if len(package_list) > 0: 98 | print(f"Installing {package_list}") 99 | ret = pipe(['apt-get', 'update']) 100 | 101 | # Something went wrong, stop the service as it's failing 102 | if ret != 0: 103 | sys.exit(ret) 104 | 105 | ret = pipe(['apt-get', 'install', '-y'] + list(package_list)) 106 | 107 | # Something went wrong, stop the service as it's failing 108 | if ret != 0: 109 | sys.exit(ret) 110 | 111 | 112 | def load_secrets(): 113 | # TODO add a way to load some secrets so odoo process can 114 | # use secrets as a way to load passwords/user for postgresql 115 | # credentials could also be stored in the HOME of the odoo user 116 | # except we cannot rely on secrets 100% because it only works in 117 | # swarm mode 118 | pgpass_secret = '/run/secrets/.pgpass' 119 | if path.exists(pgpass_secret): 120 | home_folder = '/var/lib/odoo' 121 | pgpass_target = path.join(home_folder, '.pgpass') 122 | if path.exists(pgpass_target): 123 | os.remove(pgpass_target) 124 | # shutil.move doesn't always work correctly on different fs 125 | shutil.copy(pgpass_secret, home_folder) 126 | st = os.stat(pgpass_secret) 127 | os.chmod(pgpass_target, st.st_mode) 128 | os.chown(pgpass_target, st[stat.ST_UID], st[stat.ST_GID]) 129 | # Cannot remove anymore apparently 130 | # os.remove(pgpass_secret) 131 | # shutil.move(pgpass_secret, home_folder) 132 | 133 | 134 | def disable_base_modules(): 135 | base_addons = os.environ.get('ODOO_BASE_PATH', '') 136 | addons_to_remove = os.environ.get('ODOO_DISABLED_MODULES', '') 137 | 138 | modules = addons_to_remove.split(',') 139 | modules = map(lambda mod: mod.strip(), modules) 140 | 141 | if not base_addons: 142 | print("Do not attempt to remove wrong folder") 143 | return 144 | 145 | for module in modules: 146 | if not module: 147 | continue 148 | print("Removing module %s from %s" % (module, base_addons)) 149 | 150 | module_path = Path(base_addons, module) 151 | if module_path.exists() and module_path.is_dir(): 152 | shutil.rmtree(module_path) 153 | else: 154 | print("Module skipped as it doesn't seem to be present.") 155 | 156 | 157 | def fix_access_rights(): 158 | if os.environ.get('RESET_ACCESS_RIGHTS', '') == 'TRUE': 159 | pipe(["chown", "-R", "odoo:odoo", "/var/lib/odoo"]) 160 | pipe(["chown", "-R", "odoo:odoo", "/etc/odoo"]) 161 | 162 | 163 | def remove_sudo(): 164 | return pipe(["sed", "-i", "/odoo/d", "/etc/sudoers"]) 165 | 166 | 167 | def main(): 168 | install_apt_packages() 169 | load_secrets() 170 | fix_access_rights() 171 | disable_base_modules() 172 | return remove_sudo() 173 | 174 | 175 | try: 176 | code = main() 177 | sys.exit(code) 178 | except Exception as exc: 179 | print(exc) 180 | sys.exit(1) 181 | except KeyboardInterrupt as exc: 182 | print(exc) 183 | sys.exit(1) 184 | -------------------------------------------------------------------------------- /assets/odoo.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | addons_path = /mnt/extra-addons 3 | data_dir = /var/lib/odoo 4 | ; admin_passwd = admin 5 | ; csv_internal_sep = , 6 | ; db_maxconn = 64 7 | ; db_name = False 8 | ; db_template = template1 9 | ; dbfilter = .* 10 | ; debug_mode = False 11 | ; email_from = False 12 | ; limit_memory_hard = 2684354560 13 | ; limit_memory_soft = 2147483648 14 | ; limit_request = 8192 15 | ; limit_time_cpu = 60 16 | ; limit_time_real = 120 17 | ; list_db = True 18 | ; log_db = False 19 | ; log_handler = [':INFO'] 20 | ; log_level = info 21 | ; logfile = None 22 | ; longpolling_port = 8072 23 | ; max_cron_threads = 2 24 | ; osv_memory_age_limit = 1.0 25 | ; osv_memory_count_limit = False 26 | ; smtp_password = False 27 | ; smtp_port = 25 28 | ; smtp_server = localhost 29 | ; smtp_ssl = False 30 | ; smtp_user = False 31 | ; workers = 0 32 | ; xmlrpc = True 33 | ; xmlrpc_interface = 34 | ; xmlrpc_port = 8069 35 | ; xmlrpcs = True 36 | ; xmlrpcs_interface = 37 | ; xmlrpcs_port = 8071 38 | -------------------------------------------------------------------------------- /assets/platform.sh: -------------------------------------------------------------------------------- 1 | PLATFORM=`uname -m` 2 | 3 | if [ $PLATFORM = "aarch64" ] 4 | then 5 | echo -n "arm64" 6 | else 7 | if [ $PLATFORM = "x86_64" ] 8 | then 9 | echo -n "amd64" 10 | else 11 | echo -n $PLATFORM 12 | fi 13 | fi 14 | -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import toml 3 | from datetime import datetime 4 | from os import path 5 | import os 6 | import shutil 7 | 8 | FORMAT = "%Y%m%d" 9 | 10 | if 'CUR_DATE' in os.environ: 11 | CUR_DATE = os.environ['CUR_DATE'] 12 | else: 13 | CUR_DATE = datetime.now().strftime(FORMAT) 14 | 15 | templates = {} 16 | 17 | def get_template(template_name): 18 | if template_name in templates: 19 | return templates[template_name] 20 | 21 | template_file = path.join("templates", template_name) 22 | 23 | with open(template_file) as temp_in: 24 | template = temp_in.read() 25 | templates[template_name] = template 26 | 27 | return template 28 | 29 | with open("./versions.toml") as config_in: 30 | config = toml.load(config_in) 31 | 32 | defaults = config.get('defaults', {}) 33 | 34 | if path.exists("build"): 35 | shutil.rmtree("build") 36 | 37 | os.mkdir("build") 38 | 39 | tags = [] 40 | 41 | for tag, config in config.get("odoo", {}).items(): 42 | config['tag'] = tag 43 | config['created_date'] = datetime.now().isoformat() 44 | 45 | config = dict(defaults, **config) 46 | 47 | if 'release' not in config: 48 | config['release'] = CUR_DATE 49 | 50 | template = get_template(config.get('template')) 51 | 52 | print("Building version tag %s" % tag, config) 53 | 54 | os.mkdir(path.join("build", tag)) 55 | 56 | with open(path.join("build", tag, "Dockerfile"), "w") as fout: 57 | fout.write(template % config) 58 | 59 | shutil.copyfile(path.join("assets", config.get('config')), path.join("build", tag, "odoo.conf")) 60 | shutil.copyfile(path.join("assets", config.get('entrypoint')), path.join("build", tag, "entrypoint.py")) 61 | shutil.copyfile(path.join("assets", 'sudo-%s' % config.get('entrypoint')), path.join("build", tag, "sudo-entrypoint.py")) 62 | os.chmod(path.join("build", tag, "entrypoint.py"), 0o775) 63 | os.chmod(path.join("build", tag, "sudo-entrypoint.py"), 0o775) 64 | 65 | tags.append(tag) 66 | 67 | for tag in tags: 68 | if path.exists(tag): 69 | shutil.rmtree(tag) 70 | shutil.move(path.join("build", tag), ".") 71 | 72 | shutil.rmtree("build") 73 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | docker login 2 | version=(8 9 10 11 12 13 14) 3 | repo="llacroix/odoo" 4 | 5 | for version in "${version[@]}" 6 | do 7 | cd "${version}.0" 8 | docker build -t local-odoo:${version} . 9 | docker tag local-odoo:${version} $repo:${version} 10 | docker push $repo:${version} 11 | cd .. 12 | done 13 | 14 | version=(10 11 12 13 14) 15 | for version in "${version[@]}" 16 | do 17 | cd "${version}.0" 18 | docker build -t local-odoo:${version}-nightly . 19 | docker tag local-odoo:${version}-nightly $repo:${version}-nightly 20 | docker push $repo:${version}-nightly 21 | cd .. 22 | done 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | toml>=1.0.0 3 | -------------------------------------------------------------------------------- /templates/Dockerfile27.template: -------------------------------------------------------------------------------- 1 | FROM %(base_image)s 2 | LABEL maintainer="Archeti " 3 | 4 | # Generate locale C.UTF-8 for postgres and general locale data 5 | ENV LANG C.UTF-8 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | 8 | COPY ./platform.sh /platform.sh 9 | # Install some dependencies python3.7 10 | RUN set -x; \ 11 | export ARCH=`sh /platform.sh` \ 12 | # Setup deb source for postgresql client 13 | && apt-get update \ 14 | && apt-get install -y --no-install-recommends \ 15 | ca-certificates \ 16 | gnupg \ 17 | python-wheel \ 18 | python-setuptools \ 19 | python-pip \ 20 | python2.7 \ 21 | libpython2.7 \ 22 | curl \ 23 | libpq-dev \ 24 | libsasl2-2 \ 25 | libldap-2.4-2 \ 26 | libxml2 \ 27 | libxmlsec1 \ 28 | libxslt1.1 \ 29 | sudo \ 30 | node-less \ 31 | && echo 'deb https://apt.postgresql.org/pub/repos/apt/ %(os_release)s-pgdg main' > etc/apt/sources.list.d/pgdg.list \ 32 | && export GNUPGHOME="$(mktemp -d)" \ 33 | && repokey='B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8' \ 34 | && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \ 35 | && gpg --batch --armor --export "${repokey}" > /etc/apt/trusted.gpg.d/pgdg.gpg.asc \ 36 | && gpgconf --kill all \ 37 | && rm -rf "$GNUPGHOME" \ 38 | # Download deb for wkhtmltox 39 | && echo "Downloading %(wkhtmltox_repo)s/releases/download/%(wkhtmltox_version)s/wkhtmltox_%(wkhtmltox_version)s%(wkhtmltox_revision)s.%(os_release)s_${ARCH}.deb" \ 40 | && curl -o wkhtmltox.deb -sSL %(wkhtmltox_repo)s/releases/download/%(wkhtmltox_version)s/wkhtmltox_%(wkhtmltox_version)s%(wkhtmltox_revision)s.%(os_release)s_${ARCH}.deb \ 41 | && apt-get update \ 42 | && apt-get install -y --no-install-recommends \ 43 | postgresql-client \ 44 | ./wkhtmltox.deb \ 45 | && rm -rf /var/lib/apt/lists/* wkhtmltox.deb 46 | 47 | ENV PATH=/var/lib/odoo/.local/bin:/usr/local/bin:$PATH 48 | # Install Odoo Including things from sources 49 | ENV ODOO_VERSION %(version)s 50 | ENV ODOO_RELEASE=%(release)s 51 | ARG ODOO_ARCHIVE=%(filename)s.tar.gz 52 | RUN set -x; \ 53 | apt-get update \ 54 | && apt-get install -y --no-install-recommends \ 55 | build-essential \ 56 | python2.7-dev \ 57 | libsasl2-dev \ 58 | libldap2-dev %(apt_packages)s \ 59 | libxml2-dev \ 60 | libxmlsec1-dev \ 61 | libxslt1-dev \ 62 | git \ 63 | && pip install -U pip \ 64 | && /usr/bin/env pip install \ 65 | odoo-tools \ 66 | %(pip_packages)s \ 67 | && odoo-install --release "%(release)s" --version "%(version)s" --repo "%(odoo_repo)s" --ref "%(ref)s" \ 68 | && [ -f /usr/local/bin/odoo.py ] && ln -s /usr/local/bin/odoo.py /usr/local/bin/odoo \ 69 | || cd / \ 70 | && apt-get --purge remove -y \ 71 | build-essential \ 72 | python2.7-dev \ 73 | libsasl2-dev \ 74 | libldap2-dev \ 75 | libxml2-dev \ 76 | libxmlsec1-dev \ 77 | libxslt1-dev \ 78 | git \ 79 | && apt-get autoremove -y \ 80 | && rm -rf /var/lib/apt/lists/* \ 81 | && rm -rf /root/.cache 82 | 83 | COPY ./%(config)s /etc/odoo/ 84 | COPY ./%(entrypoint)s / 85 | COPY ./sudo-%(entrypoint)s / 86 | 87 | ARG UID=%(uid)d 88 | ARG GID=%(gid)d 89 | 90 | RUN mkdir /addons \ 91 | && groupadd -r -g ${GID} odoo \ 92 | && useradd -r -u ${UID} -g odoo -b /var/lib -m odoo \ 93 | && chown odoo /etc/odoo/odoo.conf \ 94 | && chown -R odoo:odoo /addons \ 95 | && echo "odoo ALL=(ALL:ALL) NOPASSWD:SETENV: /sudo-entrypoint.py" >> /etc/sudoers 96 | 97 | VOLUME /etc/odoo 98 | VOLUME /var/lib/odoo 99 | 100 | ENV ODOO_RC /etc/odoo/odoo.conf 101 | ENV ODOO_BASE_PATH /usr/local/lib/python2.7/dist-packages/openerp/addons 102 | # Env variable defined to monitor the kind of service running 103 | # it could be a staging/production/test or anything and undefined 104 | # is the default in case we need to know servers that aren't correctly 105 | # defined 106 | ENV DEPLOYMENT_AREA undefined 107 | 108 | expose 8069 109 | expose 8071 110 | 111 | USER odoo 112 | 113 | LABEL version="%(version)s" 114 | LABEL release="%(release)s" 115 | 116 | LABEL org.opencontainers.image.created="%(created_date)s" 117 | LABEL org.opencontainers.image.url="https://hub.docker.com/r/llacroix/odoo" 118 | LABEL org.opencontainers.image.authors="Archeti " 119 | LABEL org.opencontainers.image.documentation="https://github.com/llacroix/odoo-docker" 120 | LABEL org.opencontainers.image.source="https://github.com/llacroix/odoo-docker" 121 | LABEL org.opencontainers.image.version="%(version)s" 122 | LABEL org.opencontainers.image.vendor="ArcheTI" 123 | LABEL org.opencontainers.image.ref.name="%(tag)s" 124 | LABEL org.opencontainers.image.title="Odoo %(version)s" 125 | LABEL org.opencontainers.image.description="Full featured odoo image that make odoo deployment fun and secure." 126 | 127 | ENTRYPOINT ["/entrypoint.py"] 128 | 129 | cmd ["odoo"] 130 | -------------------------------------------------------------------------------- /versions.toml: -------------------------------------------------------------------------------- 1 | [defaults] 2 | gid = 1000 3 | uid = 1000 4 | pip_packages = "" 5 | apt_packages = "" 6 | template = "Dockerfile.template" 7 | config = "odoo.conf" 8 | entrypoint = "entrypoint.py" 9 | filename = "odoo_${ODOO_VERSION}.${ODOO_RELEASE}" 10 | version_path = "${ODOO_VERSION}" 11 | module_name = "odoo" 12 | base_image = "ubuntu:bionic" 13 | os_release = "bionic" 14 | python_version = "python3.6" 15 | release = "" 16 | 17 | wkhtmltox_repo = "https://github.com/wkhtmltopdf/wkhtmltopdf" 18 | wkhtmltox_version = "0.12.5" 19 | wkhtmltox_revision = "-1" 20 | 21 | # ARM support 22 | # wkhtmltox_repo = "https://github.com/wkhtmltopdf/packaging" 23 | #wkhtmltox_version = "0.12.6-1" 24 | #wkhtmltox_revision = "" 25 | 26 | odoo_repo = "https://github.com/odoo/odoo.git" 27 | ref = "" 28 | 29 | [odoo."8.0"] 30 | version = "8.0" 31 | release = "20171001" 32 | template = "Dockerfile27.template" 33 | module_name = "openerp" 34 | pip_packages = "pathlib2" 35 | 36 | [odoo."9.0"] 37 | version = "9.0c" 38 | version_path = "9.0" 39 | release = "20190401" 40 | template = "Dockerfile27.template" 41 | apt_packages = "ruby-sass libjpeg-dev" 42 | module_name = "openerp" 43 | pip_packages = "pathlib2" 44 | 45 | [odoo."10.0"] 46 | version = "10.0" 47 | release = "20200313" 48 | template = "Dockerfile27.template" 49 | apt_packages = "ruby-sass" 50 | pip_packages = "pathlib2" 51 | 52 | [odoo."11.0"] 53 | version = "11.0" 54 | release = "20201204" 55 | apt_packages = "ruby-sass" 56 | 57 | [odoo."12.0"] 58 | version = "12.0" 59 | release = "20200623" 60 | 61 | [odoo."13.0"] 62 | version = "13.0" 63 | release = "20200623" 64 | 65 | [odoo."14.0"] 66 | version = "14.0" 67 | release = "20210212" 68 | #filename = "odoo_14.0alpha1.${ODOO_RELEASE}" 69 | # version_path = "master" 70 | 71 | [odoo."10.0-nightly"] 72 | version = "10.0" 73 | template = "Dockerfile27.template" 74 | apt_packages = "ruby-sass" 75 | 76 | [odoo."11.0-nightly"] 77 | version = "11.0" 78 | apt_packages = "ruby-sass" 79 | 80 | [odoo."12.0-nightly"] 81 | version = "12.0" 82 | 83 | [odoo."13.0-nightly"] 84 | version = "13.0" 85 | 86 | [odoo."14.0-nightly"] 87 | version = "14.0" 88 | base_image = "ubuntu:focal" 89 | os_release = "focal" 90 | python_version = "python3.8" 91 | 92 | [odoo."14.0-tiny"] 93 | version = "14.0" 94 | release = "20201009" 95 | #filename = "odoo_14.0alpha1.${ODOO_RELEASE}" 96 | template = "DockerfileTiny.template" 97 | #version_path = "master" 98 | 99 | [odoo."14.3"] 100 | version = "14.3" 101 | release = "20210402" 102 | filename = "odoo_14.3alpha1.${ODOO_RELEASE}" 103 | version_path = "master" 104 | 105 | [odoo."15.0-nightly"] 106 | version = "15.0" 107 | base_image = "ubuntu:focal" 108 | os_release = "focal" 109 | python_version = "python3.8" 110 | --------------------------------------------------------------------------------