diff --git a/Dockerfile b/Dockerfile index e7c6a61e..2ee63698 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,13 @@ FROM ubuntu:14.04 -MAINTAINER Elico Corp +MAINTAINER Elico Corp + +# Define build constants +ENV GIT_BRANCH=7.0 \ + PG_VERSION=9.5 \ + BINARY_NAME=openerp-server + +# Set timezone to UTC +RUN ln -sf /usr/share/zoneinfo/Etc/UTC /etc/localtime # generate locales RUN locale-gen en_US.UTF-8 && update-locale @@ -11,17 +19,17 @@ RUN apt-key adv --keyserver keyserver.ubuntu.com \ --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8 # Add PostgreSQL's repository. It contains the most recent stable release -# of PostgreSQL, ``9.5``. -# install dependencies as distrib packages when system bindings are required -# some of them extend the basic odoo requirements for a better "apps" compatibility -# most dependencies are distributed as wheel packages at the next step +# of PostgreSQL. +# Install dependencies as distrib packages when system bindings are required. +# Some of them extend the basic Odoo requirements for a better "apps" +# compatibility. +# Most dependencies are distributed as PIP packages at the next step RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main" > \ /etc/apt/sources.list.d/pgdg.list && \ apt-get update && \ apt-get -yq install \ - adduser \ ghostscript \ - postgresql-client-9.5 \ + postgresql-client-$PG_VERSION \ python \ python-pip \ python-imaging \ @@ -59,27 +67,23 @@ ADD https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.1/wkhtmlto /opt/sources/wkhtmltox.deb RUN dpkg -i /opt/sources/wkhtmltox.deb -# Script to map the Odoo user with the host user (see FIXME inside) -ADD sources/target_user.sh /opt/sources/target_user.sh - # Startup script for custom setup ADD sources/startup.sh /opt/scripts/startup.sh -# create the odoo user -RUN adduser --home=/opt/odoo --disabled-password --gecos "" --shell=/bin/bash odoo +# Create the odoo user +RUN useradd --create-home --home-dir /opt/odoo --no-log-init odoo -# Switch to user odoo to create the folders mapped with volumes, else the corresponding -# folders will be created by root on the host +# Switch to user odoo to create the folders mapped with volumes, else the +# corresponding folders will be created by root on the host USER odoo # If the folders are created with "RUN mkdir" command, they will belong to root # instead of odoo! Hence the "RUN /bin/bash -c" trick. -RUN /bin/bash -c "mkdir -p /opt/odoo/{bin,etc,sources/odoo,additional_addons,data,ssh}" -RUN /bin/bash -c "mkdir -p /opt/odoo/var/{run,log,egg-cache}" +RUN /bin/bash -c "mkdir -p /opt/odoo/{etc,sources/odoo,additional_addons,data,ssh}" # Add Odoo OCB sources and remove .git folder in order to reduce image size WORKDIR /opt/odoo/sources -RUN git clone https://github.com/OCA/OCB.git -b 7.0 odoo && \ +RUN git clone https://github.com/OCA/OCB.git -b $GIT_BRANCH odoo && \ rm -rf odoo/.git ADD sources/odoo.conf /opt/odoo/etc/odoo.conf @@ -87,13 +91,12 @@ ADD auto_addons /opt/odoo/auto_addons User 0 -# Provide read/write access to group (for host user mapping). For some reason, the -# files added in a volume (e.g. odoo.conf) belong to root. This command imperatively -# needs to run before creating the volumes. +# Provide read/write access to odoo group (for host user mapping). This command +# must run before creating the volumes since they become readonly until the +# container is started. RUN chmod -R 775 /opt/odoo && chown -R odoo:odoo /opt/odoo VOLUME [ \ - "/opt/odoo/var", \ "/opt/odoo/etc", \ "/opt/odoo/additional_addons", \ "/opt/odoo/data", \ @@ -101,10 +104,26 @@ VOLUME [ \ "/opt/scripts" \ ] -# Set the default entrypoint (non overridable) to run when starting the container -ADD bin /app/bin/ -ENTRYPOINT ["/app/bin/boot"] -CMD ["help"] +# Use README for the help & man commands +ADD README.md /usr/share/man/man.txt +# Remove anchors and links to anchors to improve readability +RUN sed -i '/^ /dev/null + +# Use dumb-init as init system to launch the boot script +ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64.deb /opt/sources/dumb-init.deb +RUN dpkg -i /opt/sources/dumb-init.deb +ADD bin/boot /usr/bin/boot +ENTRYPOINT [ "/usr/bin/dumb-init", "/usr/bin/boot" ] +CMD [ "help" ] # Expose the odoo port (for linked containers) EXPOSE 8069 diff --git a/README.md b/README.md index ed13c3e9..9106eb83 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,675 @@ -# odoo-docker -Docker files to build dockers based on: -* standard Odoo code -* OCB code - -This work is original based on the docker from http://www.xcg-consulting.fr and available here: -https://hub.docker.com/r/xcgd/odoo/ - -# Roadmap -* Instead of having a predefined list of odoo.conf parameters available as ENV variables, allow - to change any of them dynamically. - 1. Suggested solution: - * pattern for ENV variable: "ODOO_CONF_" + parameter + "=" + value, e.g.: - * ODOO_CONF_dbfilter=^stable_.* - * ODOO_CONF_db_user=odoo - * ODOO_CONF_limit_memory_soft=671088640 - * then, in the boot script: - * loop over each parameter of odoo.conf - * check if there's an ENV variable "ODOO_CONF_" + parameter - * if so, replace the parameter in odoo.conf - 2. Other suggestion: - * remove all optional parameters from odoo.conf template, only keep the mandatory/important - ones (e.g. admin_passwd, db_user, etc.) - * replace those parameters using the current function (or using solution #1) - * either: - 1. append a user defined odoo.conf to the one in the image (add this user defined - odoo.conf by Dockerfile or by volume) - 2. or use ENV variables to customize parameters (I'm more in favor of that solution) \ No newline at end of file +# elicocorp/odoo +Simple yet powerful [Odoo][odoo] image for [Docker][dk] based on [OCB][ocb] +code and maintained by [Elico Corporation][ec]. + + [odoo]: https://www.odoo.com/ + [dk]: https://www.docker.com/ + [ocb]: https://github.com/OCA/OCB "Odoo Community Backports" + [ec]: https://www.elico-corp.com/ + + +## Table of Contents +- [Usage](#usage) + - [Run the image](#run_image) + - [Compose example](#compose_example) +- [Security](#security) +- [Data persistency](#data_persistency) +- [Host user mapping](#host_user_mapping) + - [Default host user mapping in Docker](#default_hum) + - [Host user mapping and volumes](#hum_and_volumes) + - [Impact](#impact) + - [Solution](#solution) +- [Odoo configuration file](#odoo_conf) +- [Additional Odoo modules](#additional_addons) + - [Automatically fetch Git repositories](#git_fetch) + - [Fetch multiple independent repositories](#fetch_multiple_repos) + - [Fetch private GitHub repositories](#git_ssh) +- [Run a bash script at startup](#startup_script) +- [How to extend this image](#extend_image) +- [Roadmap](#roadmap) +- [Bug Tracker](#bug_tracker) +- [Credits](#credits) + - [Contributors](#contributors) + - [Maintainer](#maintainer) + + [toc]: #toc "Table of Contents" + + +## Usage[^][toc] +In order to use this image, a recent version of Docker must be installed on the +host. For more information about Docker Engine, see the +[official documentation][dk-doc]. + + [dk-doc]: https://docs.docker.com/engine/ + + +### Run the image[^][toc] +Running this image without specifying any command will display this help +message: + + $ docker run elicocorp/odoo:10.0 + +To display the user manual, run the image with the command `man`. Redirecting +`stdout` to `less` is highly recommended: + + $ docker run elicocorp/odoo:10.0 man | less + +To start Odoo, run the image with the command `start`: + + $ docker run elicocorp/odoo:10.0 start + +The easiest way to use this image is to run it along with a [PostgreSQL][pg] +image. By default, Odoo is configured to connect with a PostgreSQL host named +`db`. + + [pg]: https://hub.docker.com/_/postgres/ + +**Note:** The [PostgreSQL image][ec-pg] of Elico Corp can be used as well. + + [ec-pg]: https://hub.docker.com/r/elicocorp/postgres/ + + +### Compose example[^][toc] +Below is an example of a simple `docker-compose.yml` to use this image. For +more information about Compose, see the [official documentation][dc-doc]. + + [dc-doc]: https://docs.docker.com/compose/ + + version: '3.3' + services: + + postgres: + image: postgres:9.5 + environment: + - POSTGRES_USER=odoo + network_mode: bridge + + odoo: + image: elicocorp/odoo:10.0 + command: start + ports: + - 127.0.0.1:8069:8069 + links: + - postgres:db + environment: + - ODOO_DB_USER=odoo + network_mode: bridge + +Once this file is created, simply move to the corresponding folder and run the +following command to start Odoo: + + $ docker-compose up + +**Note 1:** With this configuration, Odoo will be accessible at the following +URL *only* from the local host: + +It is possible to publish it following one of these options: + +1. map a reverse-proxy to `127.0.0.1:8069` +2. remove the `127.0.0.1:` prefix in order to publish the port `8069` outside +the local host + +**Note 2:** With this configuration: + +1. Odoo is running without master password +2. `odoo` PostgreSQL user is a superuser who doesn't require any password + +See [Security](#security) section for more info. + +**Note 3:** With this configuration, all the data will be lost once the +[containers][dk-con] are stopped. + + [dk-con]: https://www.docker.com/what-container + "What is a Container | Docker" + +See [Data persistency](#data_persistency) section for more info. + + +## Security[^][toc] +In order to improve the security, it is recommended to: + +1. set a master password for Odoo using `ODOO_ADMIN_PASSWD` +2. start PostgreSQL with a different superuser (e.g. `postgres`) +3. give the superuser a password using `POSTGRES_PASSWORD` +4. create a separate PostgreSQL user for Odoo (e.g. `odoo`) with his own +password and specify it using `ODOO_DB_PASSWORD` + +**Note:** Run below SQL queries with PostgreSQL superuser to create the `odoo` +user: + + CREATE user odoo WITH password 'strong_pg_odoo_password'; + ALTER user odoo WITH createdb; + +The `docker-compose.yml` should look like: + + version: '3.3' + services: + + postgres: + image: postgres:9.5 + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=strong_pg_superuser_password + network_mode: bridge + + odoo: + image: elicocorp/odoo:10.0 + command: start + ports: + - 127.0.0.1:8069:8069 + links: + - postgres:db + environment: + - ODOO_ADMIN_PASSWD=strong_odoo_master_password + - ODOO_DB_USER=odoo + - ODOO_DB_PASSWORD=strong_pg_odoo_password + network_mode: bridge + +**Note:** If Odoo is behind a reverse proxy, it is also suggested to change the +port published by the container (though this port is actually not opened to the +outside). For instance: + + ports: + - 127.0.0.1:12345:8069 + + +## Data persistency[^][toc] +As soon as the containers are removed, all the modifications (e.g. database, +attachments, etc.) will be lost. There are 2 main [volumes][dk-vol] that must +be made persistent in order to preserve the data: + + [dk-vol]: https://docs.docker.com/engine/admin/volumes/volumes/ + "Use volumes | Docker Documentation" + +1. the PostgreSQL database in `/var/lib/postgresql/data` +2. the Odoo filestore in `/opt/odoo/data/filestore` + +Optionally, it is also possible to map the Odoo sessions folder in +`/opt/odoo/data/sessions` + +In the following example, these volumes are mapped under the folder `volumes` +which is in the same folder as the `docker-compose.yml`. This command will +create the corresponding folders: + + mkdir -p ./volumes/{postgres,odoo/filestore,odoo/sessions} + +The `docker-compose.yml` should look like: + + version: '3.3' + services: + + postgres: + image: postgres:9.5 + volumes: + - ./volumes/postgres:/var/lib/postgresql/data + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=strong_pg_superuser_password + network_mode: bridge + + odoo: + image: elicocorp/odoo:10.0 + command: start + ports: + - 127.0.0.1:8069:8069 + links: + - postgres:db + volumes: + - ./volumes/odoo/filestore:/opt/odoo/data/filestore + - ./volumes/odoo/sessions:/opt/odoo/data/sessions + environment: + - ODOO_ADMIN_PASSWD=strong_odoo_master_password + - ODOO_DB_USER=odoo + - ODOO_DB_PASSWORD=strong_pg_odoo_password + network_mode: bridge + +**Note:** With this configuration, all the data created in the volumes will +belong to the user whose UID matches the user running inside the container. + +See Host user mapping section for more info. + + +## Host user mapping[^][toc] + + +### Default host user mapping in Docker[^][toc] +Each Docker image defines its own [users][dk-user]. Users only exist inside the +running container. + + [dk-user]: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#user + +For instance: + +* in `elicocorp/odoo` image, the default user that will run the Odoo process is +`odoo` with UID `1000` +* in `postgres` image, the default user that will run the PostgreSQL process is +`postgres` with UID `999` + +Whenever those users are used inside the container, Docker will actually use +the corresponding user on the host running Docker. The mapping is made on the +UID, not on the user name. + +If the user `elico` with UID `1000` exists on the host, when running the Odoo +image with the default user, the Odoo process executed by the `odoo` user +inside the container will actually be executed by the host user `elico`. + +**Note:** The users don't have to actually exist on the host for the container +to use them. Anonymous users with the corresponding UID will be created +automatically. + +If the with UID `999` doesn't exist on the host, when running the PostgreSQL +image with the default user, the PostgreSQL process executed by the `postgres` +user inside the container will actually be executed by the anonymous host user +with UID `999`. + + +### Host user mapping and volumes[^][toc] +When the user inside the container owns files that belong to a volume, the +corresponding files in the folder mapped to the volume on the host will +actually belong to the corresponding user on the host. + +Following the previous example: + +* in the Odoo container, the files created by the `odoo` user in the folder +`/opt/odoo/data/filestore` will be stored on the host in the folder +`./volumes/odoo/filestore` and belong to the host user `elico` +* in the PostgreSQL container, the files created by the `postgres` user in the +folder `/var/lib/postgresql/data` will be stored on the host in the folder +`./volumes/postgres` and belong to the anonymous host user with UID `999` + + +### Impact[^][toc] +When having `root` privileges on the host, the default host user mapping +behavior is usually not a big issue. The main impact is that the files mapped +with a volume might belong to users that don't have anything to do with the +corresponding Docker services. + +In the previous example: + +* the host user `elico` will be able to read the content of the Odoo filestore +* the anonymous host user with UID `999` will be able to read the PostgreSQL +database files + +It is possible to avoid this by creating host users with the corresponding UIDs +in order to control which host user owns the files in a volume. + +However, a user with limited system privileges (e.g. no `sudo`) will have a +bigger issue. The typical use case is a user with limited system privileges +that maps a volume inside his home folder. He would expect to own all the files +under his home folder, which won't be the case. + +Following the previous example, if the host user `seb` with UID `1001` starts +the image from his home folder: + +* the files in `./volumes/odoo/filestore` will belong to the host user `elico` +* the files in `./volumes/postgres` will belong to the anonymous host user with +UID `999` + +The host user `seb` will not be able to access those files even though they are +located under his own home folder. This can lead to very annoying situations +where a user would require the system administrator to help him delete files +under his own home folder. + + +### Solution[^][toc] +Each Docker image has its own way to deal with host user mapping: + +* for PostgreSQL, see the [official documentation][pg] (section "Arbitrary +--user Notes") +* for this image, use the environment variable `TARGET_UID` as described below + +First, the host user needs to find out his UID: + + $ echo $UID + +Then, simply assign this UID to the environment variable `TARGET_UID`. + +After starting the Docker containers, all the files created in the volumes will +belong to the corresponding host user. + +The `docker-compose.yml` should look like: + + version: '3.3' + services: + + postgres: + image: postgres:9.5 + volumes: + - ./volumes/postgres:/var/lib/postgresql/data + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=strong_pg_superuser_password + - /etc/passwd:/etc/passwd:ro + user: 1001:1001 + network_mode: bridge + + odoo: + image: elicocorp/odoo:10.0 + command: start + ports: + - 127.0.0.1:8069:8069 + links: + - postgres:db + volumes: + - ./volumes/odoo/filestore:/opt/odoo/data/filestore + - ./volumes/odoo/sessions:/opt/odoo/data/sessions + environment: + - TARGET_UID=1001 + - ODOO_ADMIN_PASSWD=strong_odoo_master_password + - ODOO_DB_USER=odoo + - ODOO_DB_PASSWORD=strong_pg_odoo_password + network_mode: bridge + +**Note:** For a more dynamic UID mapping, you can use Compose +[variable substitution][dk-var]. Simply export the environment variable `UID` +before starting the container and replace the `UID` with `$UID` in the +`docker-compose.yml`. + + [dk-var]: https://docs.docker.com/compose/compose-file/#variable-substitution + + +## Odoo configuration file[^][toc] +The configuration file is generated automatically at startup. Any available +Odoo parameter can be provided as an environment variable, prefixed by `ODOO_`. + +**Note:** As a convention, it is preferrable to use only big caps but this is +not mandatory. The parameters will be converted to small caps in the +configuration file. + +In the previous `docker-compose.yml` examples, the following Odoo parameters +have already been defined: + +* `admin_passwd`: environment variable `ODOO_ADMIN_PASSWD` +* `db_user`: environment variable `ODOO_DB_USER` +* `db_password`: environment variable `ODOO_DB_PASSWORD` + +For a complete list of Odoo parameters, see the [documentation][od-par]. + + [od-par]: https://www.odoo.com/documentation/10.0/reference/cmdline.html + +It is also possible to use a custom Odoo configuration file. The most common +ways are: + +1. [`ADD`][dk-add] the configuration file in `/opt/odoo/etc/odoo.conf` using a +[`Dockerfile`][dkf] +2. Map the `/opt/odoo/etc/odoo.conf` using a volume + + [dk-add]: https://docs.docker.com/engine/reference/builder/#add + [dkf]: https://docs.docker.com/engine/reference/builder/ + "Dockerfile reference | Docker Documentation" + + +## Additional Odoo modules[^][toc] +This image allows to load additional Odoo modules through the volume +`/opt/odoo/additional_addons`. When adding modules manually in that folder, the +Odoo parameter `addons_path` must be defined accordingly: + + addons_path = /opt/odoo/additional_addons,/opt/odoo/sources/odoo/addons + +**Note:** The previous configuration assumes that all the modules are at the +root of the folder `/opt/odoo/additional_addons`. Depending on the folder +structure, the parameter might need to be adapted. + + +### Automatically fetch Git repositories[^][toc] + +This image is able to automatically fetch (e.g. `git clone`) a [Git][git] +repository containing a set of modules. It is based on the +[cross repository dependency management][cross-repo-dep] system introduced by +the [OCA][oca]. + + [git]: https://git-scm.com + [cross-repo-dep]: https://github.com/OCA/maintainer-quality-tools/pull/159 + [oca]: https://github.com/OCA/ "Odoo Community Association" + +Basically, this image is able to recursively fetch Git repositories in the +`/opt/odoo/additional_addons` volume. Once all the repositories have been +fetched, the `addons_path` parameter will be generated automatically. + +The cross repository dependency is based on the +[`oca_dependencies.txt`][oca-dep] syntax. + + [oca-dep]: https://github.com/OCA/maintainer-quality-tools/blob/master/sample_files/oca_dependencies.txt + +**Note:** This image integrates a Git repositories cache system. If some of the +repositories already exist in the volume (e.g. when restarting the container), +the container will pull (e.g. `git pull`) them instead of cloning them, which +allows for much faster boot. + +The easiest way to clone a Git repository is to set the environment variable +`ADDONS_REPO` with the URL of the repository. + +For instance, in order to fetch the [OCA Project][oca-project] Git repository, +as well as all the Git repositories it depends on, you can use the following +`docker-compose.yml`: + + [oca-project]: https://github.com/OCA/project + "Odoo Project Management and Services Company Addons" + + version: '3.3' + services: + + postgres: + image: postgres:9.5 + volumes: + - ./volumes/postgres:/var/lib/postgresql/data + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=strong_pg_superuser_password + - /etc/passwd:/etc/passwd:ro + user: 1001:1001 + network_mode: bridge + + odoo: + image: elicocorp/odoo:10.0 + command: start + ports: + - 127.0.0.1:8069:8069 + links: + - postgres:db + volumes: + - ./volumes/odoo/addons:/opt/odoo/additional_addons + - ./volumes/odoo/filestore:/opt/odoo/data/filestore + - ./volumes/odoo/sessions:/opt/odoo/data/sessions + environment: + - ADDONS_REPO=https://github.com/OCA/project.git + - TARGET_UID=1001 + - ODOO_ADMIN_PASSWD=strong_odoo_master_password + - ODOO_DB_USER=odoo + - ODOO_DB_PASSWORD=strong_pg_odoo_password + network_mode: bridge + +**Note:** After the repositories have been fetched, it might not be required to +pull them every time the container is restarted. In that case, simply set the +environment variable `FETCH_OCA_DEPENDENCIES` to `False` (default value is +`True`) in order to boot much faster, e.g.: + + environment: + - FETCH_OCA_DEPENDENCIES=False + + +### Fetch multiple independent repositories[^][toc] +It might be necessary to fetch more than one Git repository (and the +repositories it depends on). In that case, instead of using the `ADDONS_REPO` +environment variable, simply create one `oca_dependencies.txt` file and put it +at the root of the `/opt/odoo/additional_addons` volume. + +For instance, if you want to fetch the +[OCA account payment modules][oca-account-payment] repository along with the +OCA project repository, put the following `oca_dependencies.txt` in the +`/opt/odoo/additional_addons` volume: + + [oca-account-payment]: https://github.com/OCA/account-payment + + # list the OCA project dependencies, one per line + # add a github url if you need a forked version + project https://github.com/OCA/project.git + account-payment https://github.com/OCA/account-payment.git + + +### Fetch private GitHub repositories[^][toc] +This image is able to pull multiple private GitHub repositories when provided a +valid SSH key that has read access to these repositories. The URL for GitHub +SSH authentication is available under the "Clone with SSH" option. + +Simply put the SSH private key whose name must be `id_rsa` in the volume +`/opt/odoo/ssh`. Since Odoo doesn't need to write in that volume, you can use +the `ro` option to mount a [read-only volume][dk-ro-vol]. + + [dk-ro-vol]: https://docs.docker.com/engine/admin/volumes/volumes/#use-a-read-only-volume + +The `docker-compose.yml` should look like: + + version: '3.3' + services: + + postgres: + image: postgres:9.5 + volumes: + - ./volumes/postgres:/var/lib/postgresql/data + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=strong_pg_superuser_password + - /etc/passwd:/etc/passwd:ro + user: 1001:1001 + network_mode: bridge + + odoo: + image: elicocorp/odoo:10.0 + command: start + ports: + - 127.0.0.1:8069:8069 + links: + - postgres:db + volumes: + - ./volumes/odoo/addons:/opt/odoo/additional_addons + - ./volumes/odoo/filestore:/opt/odoo/data/filestore + - ./volumes/odoo/sessions:/opt/odoo/data/sessions + - ./volumes/odoo/ssh:/opt/odoo/ssh:ro + environment: + - ADDONS_REPO=git@github.com:Elico-Corp/odoo-private-addons.git + - TARGET_UID=1001 + - ODOO_ADMIN_PASSWD=strong_odoo_master_password + - ODOO_DB_USER=odoo + - ODOO_DB_PASSWORD=strong_pg_odoo_password + network_mode: bridge + +**Note:** If the host user has a valid SSH key under the `.ssh` folder of his +home folder, he can map his `.ssh` folder instead, e.g.: + + volumes: + - ~/.ssh:/opt/odoo/ssh:ro + + +## Run a bash script at startup[^][toc] +In some cases, it might be useful to run some commands in the container before +starting Odoo. After the Odoo target user has been created, the container will +execute a [bash][bash] script with the container user `root`. + + [bash]: https://www.gnu.org/software/bash/ "GNU Bash" + +The script is located at `/opt/scripts/startup.sh` and can be mapped with a +volume or added via a `Dockerfile`. + + +## How to extend this image[^][toc] +This image comes with all the dependencies required to run the standard version +of Odoo. However, some additionnal modules might require an extra setup. + +While the startup script is a way to achieve this, the changes it operates in +the OS of the container are not persistent. Such setup would be performed every +time the container is restarted, which could induce long delay in the boot +process. + +In order to make those changes persistent, simply create a child Docker image +by extending this image. + +The below example shows how to install the dependencies `captcha` and +`simplecrypt` for the Odoo v8 module [`website_captcha_nogoogle`][wcn]. + + [wcn]: https://github.com/Elico-Corp/odoo-addons/tree/8.0/website_captcha_nogoogle + +This is how the `Dockerfile` would look like: + + FROM elicocorp/odoo:8.0 + MAINTAINER Elico Corp + RUN pip install --upgrade cffi + RUN pip install captcha simple-crypt recaptcha-client + RUN pip install --upgrade pillow + +Save it as `./build/odoo/Dockerfile`. Then, in `docker-compose.yml`, replace +the `image` instruction with a `build` instruction, e.g.: + + odoo: + build: ./build/odoo + +When starting the container with `docker-compose up`, Docker will first build +the image. In order to re-build the `odoo` image, use: + + $ docker-compose build odoo + +**Note:** Extend an image is an extremely versatile feature of Docker. The only +limit is your imagination! For instance, check out the Elico Corp Odoo Docker +image [localized for China][odoo-china]. + + [odoo-china]: https://github.com/Elico-Corp/odoo-docker-china + + +## Roadmap[^][toc] + +* Current list of PIP requirements is maintenained in this repository. It +should use the `https://github.com/OCA/OCB/blob/10.0/requirements.txt` instead. +To be noted that this file is not available in v7. +* Use the standard Odoo code rather than the OCB +* Use the code of `maintainer-quality-tools` to pull the `oca_dependencies.txt` + + +## Bug Tracker[^][toc] +Bugs are tracked on [GitHub Issues][gh-issues]. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smash it by providing detailed and welcomed feedback. + + [gh-issues]: https://github.com/Elico-Corp/elico_odoo/issues + + +## Credits[^][toc] + + +### Contributors[^][toc] + +* Sebastien Maillard +* Eric Caudal +* Noah Wang + +This image is a fork of [XCG Consulting][xcg] Odoo Docker image available +[here][xcgd]. + + [xcg]: https://www.xcg-consulting.fr/ + [xcgd]: https://hub.docker.com/r/xcgd/odoo/ + + +### Maintainer[^][toc] + +[![Elico Corp][ec-logo]][ec] + + [ec-logo]: https://www.elico-corp.com/logo.png + +This project is maintained by Elico Corporation. + +Elico Corp is an innovative actor in China, Hong-Kong and Singapore servicing +well known international companies and as well as local mid-sized businesses. +Since 2010, our seasoned Sino-European consultants have been providing full +range Odoo services: + +* Business consultancy for Gap analysis, BPM, operational work-flows review. +* Ready-to-use ERP packages aimed at starting businesses. +* Odoo implementation for manufacturing, international trading, service industry + and e-commerce. +* Connectors and integration with 3rd party software (Magento, Taobao, Coswin, + Joomla, Prestashop, Tradevine etc...). +* Odoo Support services such as developments, training, maintenance and hosting. + +Our headquarters are located in Shanghai with branch in Singapore servicing +customers from all over Asia Pacific. + +Contact information: Sales diff --git a/auto_addons/addons.py b/auto_addons/addons.py index 91945f8d..cbf8f31d 100644 --- a/auto_addons/addons.py +++ b/auto_addons/addons.py @@ -23,26 +23,13 @@ class Repo(object): """ - oca_dependencies.txt + Fetch Git repositories recursively. - For public repo: - - oca-repo -> https://github.com/OCA/oca-repo (default branch) - oca-repo 8.0 -> https://github.com/OCA/oca-repo (branch: 8.0) - organization/public-repo -> https://github.com/organization/public-repo (default branch) - organization/public-repo 8.0 -> https://github.com/organization/public-repo (branch: 8.0) - https://github.com/organization/public-repo -> https://github.com/organization/public-repo (default branch) - https://github.com/organization/public-repo 8.0 -> https://github.com/organization/public-repo (branch: 8.0) - public_repo_rename https://github.com/organization/public-repo -> https://github.com/organization/public-repo (default branch) - public_repo_rename https://github.com/organization/public-repo 8.0 -> https://github.com/organization/public-repo (branch: 8.0) - - For private repo: - - git@github.com:Elico-Corp/private-repo - git@github.com:Elico-Corp/private-repo 8.0 - private_repo_rename git@github.com:Elico-Corp/private-repo - private_repo_rename git@github.com:Elico-Corp/private-repo 8.0 + Based on the OCA cross repository dependency management system: + https://github.com/OCA/maintainer-quality-tools/pull/159 + Following the oca_dependencies.txt syntax: + https://github.com/OCA/maintainer-quality-tools/blob/master/sample_files/oca_dependencies.txt """ def __init__(self, remote_url, parent=None): if parent: @@ -194,13 +181,11 @@ def download(self, parent=None, is_loop=False, fetch_dep=True): return if os.path.exists(self.path): if fetch_dep: - print('PULL: %s %s' % (self.path, self.branch)) cmd = self.update_cmd call(cmd) else: self.fetch_branch_name() else: - print('CLONE: %s %s' % (self.path, self.branch)) result = call(self.download_cmd) if result != 0: if parent and parent.parent: @@ -271,7 +256,6 @@ def main(): if remote_url: # Only one master repo to download - print('remote_url: %s ' % remote_url) Repo(remote_url).download(fetch_dep=fetch_dep) else: # List of repos is defined in oca_dependencies.txt at the root of @@ -281,7 +265,6 @@ def main(): l = line.strip('\n').strip() if l.startswith('#') or not l: continue - print('remote_url: %s ' % l) Repo(l).download(fetch_dep=fetch_dep) write_addons_path() diff --git a/auto_addons/no_addons.sh b/auto_addons/no_addons.sh deleted file mode 100644 index 8a4ca07a..00000000 --- a/auto_addons/no_addons.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -# -# FIXME the below code should be in boot but for an unknown reason, the -# instruction `grep` fails to run in boot, when it works fine here. -# If we remove "^" from the regex, `grep` will run fine in boot! -# Both are bash scripts, but: -# - boot is run by Docker as an entrypoint -# - target_user.sh is run using bash command -# -log_src='['${0##*/}']' - -grep -P "^addons_path\s*=" $1 -if [ $? -ne 0 ]; then - echo "addons_path = /opt/odoo/sources/odoo/addons" >> $1 -fi \ No newline at end of file diff --git a/bin/boot b/bin/boot index b1cdc739..335332a7 100755 --- a/bin/boot +++ b/bin/boot @@ -1,127 +1,201 @@ #!/bin/bash # # This script is designed to be run inside the container -# - -# fail hard and fast even on pipelines -set -eo pipefail log_src='['${0##*/}']' function help { - set +e - cat bin/help.txt - set -e + cat /usr/share/man/help.txt } -function login { - echo $log_src[`date +%F.%H:%M:%S`]' Running bash' - set +e - if [[ ! -e $1 && $1 = "root" ]]; then - /bin/bash - else - sudo su - odoo +function man { + cat /usr/share/man/man.txt +} + +function _ensure_odoo_user_owns_volume { + # Make sure the folder exists + if [ -d "$1" ]; then + # Check if the volume has been mounted read-only + mount_type=$( cat /proc/mounts | grep "\s$1\s" | \ + awk '{print tolower(substr($4,0,3))}' ) + + if [ "$mount_type" != 'ro' ]; then + # Set target user as owner + chown "$odoo_user":"$odoo_user" "$1" + else + echo $log_src[`date +%F.%H:%M:%S`]' Read-only volume:' "$1" + fi fi - SERVICE_PID=$! - set -e } -function _update_odoo_conf_param { - if [ "$2" ]; then - echo $log_src[`date +%F.%H:%M:%S`]' Replacing parameter '$1 - sudo -i -u \#$TARGET_ID sed -i "s/$1/$2/g" $odoo_conf_file +function _ensure_odoo_user_owns_volumes { + _ensure_odoo_user_owns_volume /opt/odoo/etc + _ensure_odoo_user_owns_volume /opt/odoo/additional_addons + _ensure_odoo_user_owns_volume /opt/odoo/data + _ensure_odoo_user_owns_volume /opt/odoo/data/filestore + _ensure_odoo_user_owns_volume /opt/odoo/data/sessions + _ensure_odoo_user_owns_volume /opt/odoo/data/addons + _ensure_odoo_user_owns_volume /opt/odoo/ssh +} + +function _update_odoo_param { + # Check if the conf already contains that parameter + grep -q "^$1\s*=" "$odoo_conf_file" + found="$?" + + if [ "$found" -eq 0 ]; then + # Substitute the value + sudo -i -u "$odoo_user" sed -i \ + "s/^$1\s*=.*/$1 = $2/g" "$odoo_conf_file" + else + # Append the parameter (hide tee output to stdout) + echo "$1 = $2" | \ + sudo -i -u "$odoo_user" tee -a "$odoo_conf_file" > /dev/null fi } -function start { - echo $log_src[`date +%F.%H:%M:%S`]' Checking special requirements...' - bash /opt/scripts/startup.sh +function _update_odoo_conf_params { + # Loop over all the "ODOO_" ENV variables (see `<<<` after `done`) + while read -r env_var; do + # Remove "ODOO_" from ENV variable and convert to lowercase + odoo_param=${env_var:5} + odoo_param=${odoo_param,,} - echo $log_src[`date +%F.%H:%M:%S`]' Updating Odoo conf...' - _update_odoo_conf_param '_ADMIN_PASSWORD_' $ADMIN_PASSWORD - _update_odoo_conf_param '_DB_USER_' $DB_USER - _update_odoo_conf_param '_DB_PASSWORD_' $DB_PASSWORD - _update_odoo_conf_param '_DB_FILTER_' $DB_FILTER - echo $log_src[`date +%F.%H:%M:%S`]' Update Odoo conf done' + # Get the value of the corresponding ENV variable and escape slashes + val=${!env_var} + val=$( echo "$val" | sed 's/\//\\\//g' ) + + _update_odoo_param "$odoo_param" "$val" + + # Unset the environment variable for security purpose + unset "$env_var" + done <<< "$( printenv | grep '^ODOO_' | sed 's/=.*//g' )" +} + +function _setup_ssh_key { + # Create SSH config folder in $HOME folder of Odoo target user + ssh_folder=$( getent passwd "$odoo_user" | cut -d: -f6 )/.ssh + sudo -i -u "$odoo_user" mkdir "$ssh_folder" + + # Copy SSH private key from /opt/odoo/ssh + sudo -i -u "$odoo_user" cp /opt/odoo/ssh/id_rsa "$ssh_folder" + + echo $log_src[`date +%F.%H:%M:%S`]' Scanning GitHub key...' + # Hide ssh-keyscan stderr output since it's actually log message + ssh-keyscan github.com 2> /dev/null | \ + sudo -i -u "$odoo_user" tee "$ssh_folder/known_hosts" > /dev/null + + # Bind SSH key to GitHub host + echo "host github.com + HostName github.com + User git + IdentityFile $ssh_folder/id_rsa" | \ + sudo -i -u "$odoo_user" tee "$ssh_folder/config" > /dev/null + + # Secure SSH key + chmod 400 "$ssh_folder/id_rsa" +} - echo $log_src[`date +%F.%H:%M:%S`]' Check if need to download addons repo...' +function _download_addons { + # 2 reasons to download extra addons: + # 1) ENV variable ADDONS_REPO is defined + # 2) There's a file called `oca_dependencies.txt` at the root of extra addons folder if [ "$ADDONS_REPO" -o -a /opt/odoo/additional_addons/oca_dependencies.txt ]; then - echo $log_src[`date +%F.%H:%M:%S`]' Git config for target user' - sudo -i -u \#$TARGET_ID git config --global user.email "contact@elico-corp.com" - sudo -i -u \#$TARGET_ID git config --global user.name "Elico Corp - Odoo Docker" + # Git config for target user + sudo -i -u "$odoo_user" git config --global user.email "webmaster@elico-corp.com" + sudo -i -u "$odoo_user" git config --global user.name "Elico Corp - Odoo Docker" - echo $log_src[`date +%F.%H:%M:%S`]' Checking if need to setup SSH key...' + # Setup SSH key if [ -a /opt/odoo/ssh/id_rsa ]; then - echo $log_src[`date +%F.%H:%M:%S`]' Setting up SSH key...' - SSH_FOLDER=$( getent passwd $TARGET_ID | cut -d: -f6 )/.ssh - echo $log_src[`date +%F.%H:%M:%S`]' Creating SSH folder = '$SSH_FOLDER - mkdir $SSH_FOLDER - echo $log_src[`date +%F.%H:%M:%S`]' Copy SSH private key from /opt/odoo/ssh' - cp /opt/odoo/ssh/id_rsa $SSH_FOLDER - echo $log_src[`date +%F.%H:%M:%S`]' Scanning GitHub key...' - ssh-keyscan github.com > $SSH_FOLDER/known_hosts - echo $log_src[`date +%F.%H:%M:%S`]' Setup SSH config...' - echo "host github.com" > $SSH_FOLDER/config - echo " HostName github.com" >> $SSH_FOLDER/config - echo " User git" >> $SSH_FOLDER/config - echo " IdentityFile $SSH_FOLDER/id_rsa" >> $SSH_FOLDER/config - echo $log_src[`date +%F.%H:%M:%S`]' Securing SSH folder...' - chown -R $TARGET_ID:$TARGET_ID $SSH_FOLDER - chmod 400 $SSH_FOLDER/id_rsa - echo $log_src[`date +%F.%H:%M:%S`]' Setup SSH key done' + _setup_ssh_key fi + # Always fetch dependencies unless specified if [[ -z "$FETCH_OCA_DEPENDENCIES" ]]; then - echo $log_src[`date +%F.%H:%M:%S`]' Fetch dependencies undefined, set to True' FETCH_OCA_DEPENDENCIES=True fi echo $log_src[`date +%F.%H:%M:%S`]' Downloading additional addons...' - sudo -i -u \#$TARGET_ID python /opt/odoo/auto_addons/addons.py $FETCH_OCA_DEPENDENCIES $ADDONS_REPO + sudo -i -u "$odoo_user" python /opt/odoo/auto_addons/addons.py \ + "$FETCH_OCA_DEPENDENCIES" "$ADDONS_REPO" else - echo $log_src[`date +%F.%H:%M:%S`]' No additional addons to download' - sudo -i -u \#$TARGET_ID bash /opt/odoo/auto_addons/no_addons.sh $odoo_conf_file + # No additional addons to download + grep -q '^addons_path\s*=' "$odoo_conf_file" + found="$?" + + if [ "$found" -ne 0 ]; then + # Set addons path if it doesn't exist + echo 'addons_path = /opt/odoo/sources/odoo/addons' | \ + sudo -i -u "$odoo_user" tee -a "$odoo_conf_file" > /dev/null + fi fi +} - echo $log_src[`date +%F.%H:%M:%S`]' Running odoo...' - set +e - if [ ! -e $1 ]; then - echo $log_src[`date +%F.%H:%M:%S`]' ...with additional args: $*' - fi - sudo -i -u \#$TARGET_ID /usr/bin/python \ - /opt/odoo/sources/odoo/openerp-server \ - -c $odoo_conf_file \ - $* +function _host_user_mapping { + # Name of the target Odoo user + TARGET_USER_NAME='target-odoo-user' - SERVICE_PID=$! - set -e -} + # Check whether target user exists or not + exists=$( getent passwd "$TARGET_UID" | wc -l ) -# smart shutdown on SIGINT and SIGTERM -function on_exit() { - kill -TERM $SERVICE_PID - wait $SERVICE_PID 2>/dev/null - exit 0 + # Create target user + if [ "$exists" == "0" ]; then + echo $log_src[`date +%F.%H:%M:%S`]' Creating target Odoo user...' + odoo_user="$TARGET_USER_NAME" + adduser --uid "$TARGET_UID" --disabled-login --gecos "" --quiet \ + "$odoo_user" + + # Add target user to odoo group so that he can read/write the content + # of /opt/odoo + usermod -a -G odoo "$odoo_user" + else + # Target user already exists in the following cases: + # 1) Mapping with the same UID as odoo, OK + # 2) Target user has already been created (e.g. container has been + # restarted), OK + # 3) Mapping with another existing user (e.g. root, etc.), not OK + odoo_user_id=$( id -u "$odoo_user" ) + target_uid_name=$( getent passwd "$TARGET_UID" | cut -d: -f1 ) + + if [ "$TARGET_UID" -ne "$odoo_user_id" ] && \ + [ "$TARGET_USER_NAME" != "$target_uid_name" ]; then + echo $log_src[`date +%F.%H:%M:%S`]' ERROR: Cannot create target' \ + 'user as target UID already exists.' + exit 1 + fi + fi } -trap on_exit INT TERM -echo $log_src[`date +%F.%H:%M:%S`]' Search for target user...' -bash /opt/sources/target_user.sh +function start { + # Host user mapping + odoo_user='odoo' + if [ "$TARGET_UID" ]; then + _host_user_mapping + fi + + # If the folders mapped to the volumes didn't exist, Docker has created + # them with root instead of the target Odoo user. Making sure to give back + # the ownership to the corresponding host user. + _ensure_odoo_user_owns_volumes -if [[ -z "$TARGET_ID" ]]; then - echo $log_src[`date +%F.%H:%M:%S`]' No target user, falling back to odoo' - TARGET_ID=$( id -u odoo ) -fi + echo $log_src[`date +%F.%H:%M:%S`]' Checking special requirements...' + bash /opt/scripts/startup.sh -echo $log_src[`date +%F.%H:%M:%S`]' Search conf file...' -if [[ -z "$odoo_conf_file" ]]; then + echo $log_src[`date +%F.%H:%M:%S`]' Updating Odoo conf...' odoo_conf_file="/opt/odoo/etc/odoo.conf" -fi + _update_odoo_conf_params + + # Check if need to download addons repo + _download_addons + + echo $log_src[`date +%F.%H:%M:%S`]' Running odoo...' + if [ ! -e $1 ]; then + echo $log_src[`date +%F.%H:%M:%S`]' ...with additional args:' $* + fi + sudo -i -u "$odoo_user" /usr/bin/python \ + "/opt/odoo/sources/odoo/$BINARY_NAME" -c "$odoo_conf_file" $* +} -echo $log_src[`date +%F.%H:%M:%S`]' Running command...' -for arg in "$*" -do - $arg -done +# Run command +$* -wait \ No newline at end of file diff --git a/sources/odoo.conf b/sources/odoo.conf index 6bf75fa8..8c4769c8 100644 --- a/sources/odoo.conf +++ b/sources/odoo.conf @@ -1,104 +1,7 @@ [options] -admin_passwd = _ADMIN_PASSWORD_ -; set the default timezone for openerp interface -timezone = Europe/Paris +timezone = Etc/UTC -db_maxconn = 64 -db_name = False -db_template = template1 -dbfilter = _DB_FILTER_ - -; set this to True if you have installed unaccent in your PostgreSQL -; and want to be able to search without accents -unaccent = True - -; These options are really important and depend on the type of -; deployment you want to have so read the doc carefully and -; set these values accordingly -workers = 0 -max_cron_threads = 2 -limit_memory_hard = 2147483648 -limit_memory_soft = 671088640 -limit_request = 4096 -limit_time_cpu = 120 -limit_time_real = 180 - -; ----------------------------------------------------------------- -; Below options are less critical but you are welcome to tweak them -; ----------------------------------------------------------------- - -csv_internal_sep = , - -debug_mode = False -demo = {} - -email_from = False -import_partial = - -list_db = True - -log_handler = [':INFO'] -log_level = info -logfile = None - -login_message = False - -logrotate = True - -netrpc = False -netrpc_interface = -netrpc_port = 8070 - -osv_memory_age_limit = 1.0 -osv_memory_count_limit = False - -pg_path = None -pidfile = None - -proxy_mode = False -reportgz = False - -secure_cert_file = server.cert -secure_pkey_file = server.pkey - -server_wide_modules = None - -smtp_password = False -smtp_port = 25 -smtp_server = localhost -smtp_ssl = False -smtp_user = False - -static_http_document_root = None -static_http_enable = False -static_http_url_prefix = None - -syslog = False - -test_commit = False -test_enable = False -test_file = False -test_report_directory = False - -translate_modules = ['all'] -without_demo = False - -xmlrpc = True -xmlrpc_interface = -xmlrpc_port = 8069 -xmlrpcs = True -xmlrpcs_interface = -xmlrpcs_port = 8071 - -; DO NOT CHANGE THIS... It is setup accordingly to internal docker image layout -; READ the example run script to understand how it works - -; do not change this +# Specific setup for Docker db_host = db -; if False then Odoo will fallback to 5432 db_port = False - -; the PostgreSQL connection role Odoo will use to connect to its databases -db_user = _DB_USER_ -db_password = _DB_PASSWORD_ diff --git a/sources/pip-req.txt b/sources/pip-req.txt index 2cd602c8..64fc0407 100644 --- a/sources/pip-req.txt +++ b/sources/pip-req.txt @@ -4,6 +4,7 @@ cffi==0.8.6 cryptography==0.6.1 decorator==3.4.0 docutils==0.12 +erppeek feedparser==5.1.3 gdata==2.0.18 Genshi==0.7 diff --git a/sources/target_user.sh b/sources/target_user.sh deleted file mode 100644 index 683e61d2..00000000 --- a/sources/target_user.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -# -# FIXME the below code should be in boot but for an unknown reason, the -# instruction `getent passwd` fails to run in boot, when it works fine here. -# Both are bash scripts, but: -# - boot is run by Docker as an entrypoint -# - target_user.sh is run using bash command -# -log_src='['${0##*/}']' - -# Check if there's a target user to run Odoo -if [ "$TARGET_ID" ]; then - echo $log_src[`date +%F.%H:%M:%S`]' TARGET_ID = '$TARGET_ID - - echo $log_src[`date +%F.%H:%M:%S`]' Check if target user exists...' - EXISTS=$( getent passwd $TARGET_ID | wc -l ) - echo $log_src[`date +%F.%H:%M:%S`]' EXISTS = '$EXISTS - - # Create target user - if [ $EXISTS == "0" ]; then - echo $log_src[`date +%F.%H:%M:%S`]' Create target user...' - adduser --uid $TARGET_ID --disabled-password --gecos "" --shell=/bin/bash target-user - - # Add target user to odoo group so that he can read/write the content of /opt/odoo - echo $log_src[`date +%F.%H:%M:%S`]' Add target user to odoo group...' - usermod -a -G odoo target-user - # If the user already exists, check if it's the same as odoo - else - echo $log_src[`date +%F.%H:%M:%S`]' Target user already exists, make sure it is odoo...' - ODOO_ID=$( id -u odoo ) - if [ $TARGET_ID -ne $ODOO_ID ]; then - echo $log_src[`date +%F.%H:%M:%S`]' The target user is not the same as odoo, exiting' - exit 1 - fi - fi -fi