This is the minimal CMakeLists.txt for building an executable named myproject
from main.cpp
:
cmake_minimum_required (VERSION 2.8.11) project (MyProject) add_executable (myproject main.cpp)
This is the minimal CMakeLists.txt for building an executable named myproject
from main.cpp
:
cmake_minimum_required (VERSION 2.8.11) project (MyProject) add_executable (myproject main.cpp)
In a previous post I detailed how to install Redmine on Linux using the excellent Bitnami docker image.
This post will teach you how to easily make an online backup of your Redmine installation. Note that automating the backup is not within the scope of this post.
We assume that the redmine is installed as shown in my previous post in /var/lib/redmine
. and that you want to backup to my.backup.server:~/redmine-backup/
using rsync
.
This is pretty easy, as the data is all in just one directory. You can sync it using
rsync --checksum -Pavz /var/lib/redmine/redmine_data my.backup.server:~/redmine-backup/
Note that old versions of files in redmine_data
will be overwritten, however files that are deleted locally will not be deleted on the backup server. To me, this seems like a good compromise between the ability to recover deleted files and the used storage space.
This part is slightly more complicated, since we need to access the MariaDB server running in a different container. Important note: The container ID can change so it is not sufficient to just find the container ID once and then use it. You need to determine the appropriate ID each time you do a backup. See below on instructions how to do that.
Full command:
docker exec -it $(docker container ls | grep redmine_mariadb_1 | cut -d' ' -f1) mysqldump -uroot bitnami_redmine | xz -e9 -zc - > redmine-database-dump-$(date -I).sql.xz
Let’s break it down:
docker exec -it (container ID) (command)
: Run a command on a running docker container.docker container ls | grep redmine_mariadb_1 | cut -d' ' -f1
: Get the ID (first field of the output cut -d' ' -f1
) of the running docker container named redmine_mariadb_1
mysqldump -uroot bitnami_redmine
: This is run on the docker container and dumps the Redmine Database as SQL to stdout. No password is neccessary since the Bitnami MariaDB image allows access without any password.xz -e9 -zc -
: Takes the data from mysqldump from stdin (-
), compresses it using maximum compression settings (-e9 -z
) and writes the compressed data to stdout.> redmine-database-dump-$(date -I).sql.xz
: Writes the compressed data from xz into a file called redmine-database-dump-(current date).sql.xz
in the current directory.The resulting file is called e.g. redmine-database-dump-2019-02-01.sql.xz
and it’s placed in the current directory. Ensure that you run the command in a suitable directory. Run it in /tmp
if you don’t know which directory might be suitable.
Now we can rsync
it to the server:
rsync --checksum -Pavz redmine-backup-*.sql.xz my.backup.server:~/redmine-backup/
Since the filename contains the current data, this approach will not overwrite old daily backups of the database, so you can restore your database very flexibly.
In a previous post I detailed how to install Redmine on Linux using the excellent Bitnami docker image.
This post shows you how to install a custom theme like A1 (which I used successfully for more than 5 years) if you use the bitnami Docker image. We will assume that you installed redmine in /var/lib/redmine
and your systemd service is called redmine
.
Note: If you get any permission denied errors, try running the same command using sudo
.
First, we need to create the themes directory.
sudo mkdir /var/lib/redmine/themes
The first thing we need to do is to copy the current (default) themes to that directory, since Redmine won’t be able to start up if the default theme isn’t available in the correct version.
In order to do this, we must first ensure that your container is running:
sudo systemctl start redmine
Now we can find out the container ID of the running redmine container:
uli:/var/lib/redmine$ docker container ps | grep redmine ae4de10d0b41 bitnami/redmine:latest "/app-entrypoint.sh …" 30 minutes ago Up 30 minutes 0.0.0.0:3718->3000/tcp redmine_redmine_1 c231d11c48e9 bitnami/mariadb:latest "/entrypoint.sh /run…" 30 minutes ago Up 30 minutes 3306/tcp redmine_mariadb_1
From these lines, you need to select the line that says redmine_redmine_1
at the end. The one that lists redmine_mariadb_1
at the end is the database container and we don’t need that one for this task.
From that line, copy the first column – this is the container ID – e.g. ae4de10d0b41
in this example.
Now we can copy the default theme folder:
docker cp ae4de10d0b41:/opt/bitnami/redmine/public/themes /var/lib/redmine/themes
Now copy your custom theme (e.g. the a1
folder) to /var/lib/redmine/themes
.
The next step is to fix the permissions. The bitnami container uses the user with UID 1001, so we need to change the owner to that. Repeat this every time you changed something in the themes directory:
sudo chown -R 1001:1001 /var/lib/redmine/themes
At this point we need to edit the docker-compose config (in /var/lib/redmine/docker-compose.yml
) to mount /var/lib/redmine/themes
in the correct directory. This is pretty easy: Just add - '/var/lib/redmine-szalata/themes:/opt/bitnami/redmine/public/themes'
to the volumes
section of the redmine
container.
The finished config file will look like this:
version: '2' services: mariadb: image: 'bitnami/mariadb:latest' environment: - ALLOW_EMPTY_PASSWORD=yes volumes: - '/var/lib/redmine/mariadb_data:/bitnami' redmine: image: 'bitnami/redmine:latest' environment: - REDMINE_USERNAME=admin - REDMINE_PASSWORD=redmineadmin - [email protected] - SMTP_HOST=smtp.gmail.com - SMTP_PORT=25 - [email protected] - SMTP_PASSWORD=yourGmailPassword ports: - '3718:3000' volumes: - '/var/lib/redmine/redmine_data:/bitnami' - '/var/lib/redmine/themes:/opt/bitnami/redmine/public/themes' depends_on: - mariadb
Now you can restart Redmine:
sudo systemctl restart redmine
and set your new theme by selecting it in Administration -> Settings -> Display.
Note: If you are on Windows, you can not install scipy using pip! Follow this guide instead: https://www.scipy.org/install.html. This blog post is only for Linux-based systems!
When building some of my libraries on Travis, I encountered this error during
sudo pip3 install numpy scipy --upgrade
numpy.distutils.system_info.NotFoundError: No lapack/blas resources
Install lapack and blas:
sudo apt-get -y install liblapack-dev libblas-dev
In most cases you will then get this error message:
error: library dfftpack has Fortran sources but no Fortran compiler found
Fix that by
sudo apt-get install -y gfortran
In Travis, you can do it like this in .travis.yml
:
before_install: - sudo apt-get -y install liblapack-dev libblas-dev gfortran
Similarly to my previous article about installing Redmine via docker behind a reverse proxy, this article details. Since I am running an instance of Redmine and an instance of Gitlab on the same virtual server, plus tens of other services.
While the Gitlab CE docker container is nicely preconfigured for standalone use on a dedicated VPS, running it behind a reverse proxy is not supported and will lead to a multitude of error messages – in effect, requiring lots of extra work to get up and running.
Note that we will not setup GitLab for SSH access. This is possible using this setup, but usually makes more trouble than it is worth. See this article on how to store git https passwords so you don’t have to enter your password every time.
# Install prerequisites sudo apt-get update sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common
# Add docker's package signing key curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - # Add repository sudo add-apt-repository -y "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" # Install latest stable docker stable version sudo apt-get update sudo apt-get -y install docker-ce
# Install docker-compose sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod a+x /usr/local/bin/docker-compose # Add current user to the docker group sudo usermod -a -G docker $USER # Enable & start docker service sudo systemctl enable docker sudo systemctl start docker
After running this shell script, log out & login from the system in order for the docker group to be added to the current user.
We will install Gitlab in /var/lib/gitlab
which will host the data directories and the docker-compose
script. You can use any directory if you use it consistently in all the configs (most importantly, docker-compose.yml
and the systemd
service).
# Create directories sudo mkdir /var/lib/gitlab
Next, we’ll create /var/lib/gitlab/docker-compose.yml
.
There’s a couple of things you need to change here:
gitlab_rails['gitlab_email_from']
and gitlab_rails['gitlab_email_display_name']
to whatever sender address & name you want emails to be sent fromgitlab_rails['smtp_address']
, gitlab_rails['smtp_port']
, gitlab_rails['smtp_user_name']
, gitlab_rails['smtp_password']
& gitlab_rails['smtp_domain']
) to a valid SMTP server. In rare cases you also have to change the other gitlab_rails['smtp_...']
settings.gitlab.mydomain.de
to your domain.'9080:80'
means that Gitlab will be mapped to port 9080
on the local PC. This port is chosen somewhat arbitarily – as we will run Gitlab behind an nginx reverse proxy, the port does not need to be any port in particular (as long as you use the same port everywhere), but it may not be used by anything else. You can use any port here, provided that it’s not used for anything else. Leave 80
as-is and only change 9080
if required.gitlab: image: 'gitlab/gitlab-ce:latest' restart: always hostname: 'gitlab.mydomain.de' environment: GITLAB_OMNIBUS_CONFIG: | external_url 'https://gitlab.mydomain.de' letsencrypt['enabled'] = false # Email gitlab_rails['gitlab_email_enabled'] = true gitlab_rails['gitlab_email_from'] = '[email protected]' gitlab_rails['gitlab_email_display_name'] = 'My GitLab' # SMTP gitlab_rails['smtp_enable'] = true gitlab_rails['smtp_address'] = "mail.mydomain.de" gitlab_rails['smtp_port'] = 25 gitlab_rails['smtp_user_name'] = "[email protected]" gitlab_rails['smtp_password'] = "yourSMTPPassword" gitlab_rails['smtp_domain'] = "mydomain.de" gitlab_rails['smtp_authentication'] = "login" gitlab_rails['smtp_enable_starttls_auto'] = true gitlab_rails['smtp_tls'] = true gitlab_rails['smtp_openssl_verify_mode'] = 'none' # Reverse proxy nginx config nginx['listen_port'] = 80 nginx['listen_https'] = false nginx['proxy_set_headers'] = { "X-Forwarded-Proto" => "https", "X-Forwarded-Ssl" => "on", "Host" => "gitlab.mydomain.de", "X-Real-IP" => "$$remote_addr", "X-Forwarded-For" => "$$proxy_add_x_forwarded_for", "Upgrade" => "$$http_upgrade", "Connection" => "$$connection_upgrade" } ports: - '9080:80' volumes: - './config:/etc/gitlab' - './logs:/var/log/gitlab' - './data:/var/opt/gitlab'
Next, we’ll configure the systemd service in /etc/systemd/system/gitlab.service
.
Set User=...
to your preferred user in the [Service]
section. That user needs to be a member of the docker
group. Also check if the WorkingDirectory=...
is correct.
[Unit] Description=Gitlab Requires=docker.service After=docker.service [Service] Restart=always User=root Group=docker WorkingDirectory=/var/lib/gitlab # Shutdown container (if running) when unit is stopped ExecStartPre=/usr/local/bin/docker-compose -f docker-compose.yml down -v # Start container when unit is started ExecStart=/usr/local/bin/docker-compose -f /docker-compose.yml up # Stop container when unit is stopped ExecStop=/usr/local/bin/docker-compose -f docker-compose.yml down -v [Install] WantedBy=multi-user.target
After creating the file, we can enable and start the gitlab service:
sudo systemctl enable gitlab sudo systemctl start gitlab
The output of sudo systemctl start gitlab
should be empty. In case it is
Job for gitlab.service failed because the control process exited with error code. See "systemctl status gitlab.service" and "journalctl -xe" for details.
you can debug the issue using journalctl -xe
and journalctl -e
The first startup usually takes about 10 minutes, so grab at least one cup of coffee. You can follow the progress using journalctl -xefu gitlab
. Once you see lines like
Dec 17 17:28:04 instance-1 docker-compose[4087]: gitlab_1 | {"method":"GET","path":"/-/metrics","format":"html","controller":"MetricsController","action":"index","status":200,"duration":28.82,"view":22.82,"db":0.97,"time":"2018-12-17T17:28:03.252Z","params":[],"remote_ip":null,"user_id":null,"username":null,"ua":null}
the startup is finished.
Now you can check if GitLab is running using
wget -O- http://localhost:9080/
(if you changed the port config before, you need to use your custom port in the URL).
If it worked, it will show a debug message output. Since gitlab will automatically redirect you to your domain (gitlab.mydomain.de
in this example) you should see something like
--2018-12-17 17:28:32-- http://localhost:9080/ Resolving localhost (localhost)... 127.0.0.1 Connecting to localhost (localhost)|127.0.0.1|:9080... connected. HTTP request sent, awaiting response... 302 Found Location: https://gitlab.gridbox.de/users/sign_in [following] --2018-12-17 17:28:32-- https://gitlab.mydomain.de/users/sign_in Resolving gitlab.gridbox.de (gitlab.mydomain.de)... 35.198.165.121 Connecting to gitlab.gridbox.de (gitlab.mydomain.de)|35.198.165.121|:443... failed: Connection refused.
Since we have not setup nginx as a reverse proxy yet, it’s totally fine that it’s saying connection refused. The redirection worked if you see the output listed above.
We’ll use nginx to proxy the requests from a certain domain (Using Apache, if you use it already, is also possible but it is outside the scope of this tutorial to tell you how to do that). Install it using
sudo apt -y install nginx
First, you’ll need a domain name with DNS being configured. For this example, we’ll assume that your domain name is gitlab.mydomain.de
! You need to change it to your domain name!
First, we’ll create the config file in /etc/nginx/sites-enabled/gitlab.conf
. Remember to replace gitlab.mydomain.de
by your domain name! If you use a port different from 9080, replace that as ewll.
map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { server_name gitlab.mydomain.de; access_log /var/log/nginx/gitlab.access_log; error_log /var/log/nginx/gitlab.error_log info; location / { proxy_pass http://127.0.0.1:9080; # docker container listens here proxy_read_timeout 3600s; proxy_http_version 1.1; # Websocket connection proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } listen 80; }
Now run sudo nginx -t
to test if there are any errors in the config file. If everything is alright, you’ll see
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
Once you have fixed all errors, if any, run sudo service nginx reload
to apply the configuration.
We need to setup a Let’s Encrypt SSL certificate before we can check if Gitlab is working:
First we need to install certbot
and the certbot nginx plugin in order to create & install the certificate in nginx:
sudo apt -y install python3-certbot python3-certbot-nginx
Fortunately certbot automates most of the process of installing & configuring SSL and the certificate. Run
sudo certbot --nginx
It will ask you to enter your Email address and agree to the terms of service and if you want to receive the EFF newsletter.
After that, certbot will ask you to select the correct domain name:
Which names would you like to activate HTTPS for? ------------------------------------------------------------------------------- 1: gitlab.mydomain.de ------------------------------------------------------------------------------- Select the appropriate numbers separated by commas and/or spaces, or leave input blank to select all options shown (Enter 'c' to cancel):
In this case, there is only one domain name (there will be more if you have more domains active on nginx!).
Therefore, enter 1
and press enter. certbot
will now generate the certificate. In case of success you will see an output including a line like
Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/gitlab.mydomain.de.conf
Now it will ask you whether to redirect all requests to HTTPS automatically:
Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access. ------------------------------------------------------------------------------- 1: No redirect - Make no further changes to the webserver configuration. 2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for new sites, or if you're confident your site works on HTTPS. You can undo this change by editing your web server's configuration. ------------------------------------------------------------------------------- Select the appropriate number [1-2] then [enter] (press 'c' to cancel):
Choose Redirect
here: Type 2
and press enter. Now you can login to GitLab and finish the installation.
You need to renew the certificate every 3 months for it to stay valid, and run sudo service nginx reload
afterwards to use the new certificate. If you fail to do this, users will see certificate expired error messages and won’t be able to access Gitlab easily! See this post for details on how to mostly automate this process!
Now you can open a browser and have a first look at your new GitLab installation:
Set the new password and then login with the username root and your newly set password.
After that, open the admin area at the top by clicking at the wrench icon in the purple navigation bar at the top.
At the navigation bar at the left, click on Settings (it’s at the bottom – you need to scroll down) and then click on General.
Click the Expand button to the right of Visibility and access controls. Scroll down until you see Enabled Git access protocols and select Only HTTP(S) in the combo box.
Then click the green Save changes button.
Since we have now disabled SSH access (which we didn’t set up in the first place), you can now use GitLab. A good place to start is to create a new project and try checking it out. See this article on how to store git https passwords so you don’t have to enter your git password every time.
Note: If GitLab doesn’t send emails, check config/gitlab.rb
, search for smtp
and if neccessary fix the SMTP settings there. After that, sudo systemctl stop gitlab && sudo systemctl start gitlab
Having used Redmine for almost 10 years for a multitude of projects and with widely varying team sizes, here are my practical tips on how you should use it for your project:
Create one super-project of which all projects are subprojects. This allows you to use all project management features like Gantt, Milestones etc in the super-project.
Example project structure
By opening the ticket list for the super-project, you see all the tickets of all sub-projects – and therefore have more tools at your disposal for high-level management of the company. For example, you could create milestones for business development in the super-project that depend on tasks in some R&D sub-project.
People often make the mistake of making a Redmine project public – maybe because that’s the default setting for new projects.
However, anyone that knows the project URL, e.g. https://redmine.mydomain.com/projects/test/
can see ALL THE DATA in the project by default, even though the project isn’t listed in the search bar project list
Solution: Make all projects non-public: As admin, go to the project, go to the Settings tab, uncheck the Public checkbox and click Save. Unless, of course, you actually want all the data to be publically available via Redmine…
Companies that grew organically to their current size often tend to have separate systems that fulfill mostly the same purpose but are mostly not interconnected – but are mostly used by separate subgroups of the personnel.
Typical example: (Project) management uses Redmine while the developers use Gitlab for source-code-oriented bug management and only interface to Redmine to communicate with the project managers.
While this constellation will work reasonably well even over long time periods, it inevitably leads to an unhealthy spread of information and, as a consequence, inefficiency in looking for information. Some low-level-development discussion or documentation will end up in Redmine whereas some project-management-related discussions inevitably end up in Gitlab.
If at all possible, try to encourage the use of one system and discourage the use of the other for any purpose that overlaps. Try to actively work with people opposed to that policy to find solutions on how to adapt their known process in the new system.
Most Redmine setups are notoriously hard to use on a smartphone – the UI is not really optimized for mobile use.
In order to facilitate the use on mobile or other devices, one easy way is to allow the user to answer to notification emails, with their response being posted as an update to the ticket. While this is somewhat hard to set up, a couple of hours by a skilled admin or expert will most certainly get you a couple of years of maintenance-free updates by incoming emails.
Note that this does not cover the use case of creating new tickets or otherwise interfacing with Redmine, but in practice, people that are currently on mobile-only will either just update existing tickets or whatever they intend to do is sufficiently important that they will deal with the suboptimal mobile UI that Redmine currently provides.
While there are a few Redmine mobile apps out there and it is certainly worth a try at some point, I have not had great success with them in past projects.
Many companies lack a structured and searchable way of collecting unstructured information. What about the SSH command the admin uses to connect to some server? Everyone who has worked in R&D knows that perfect-documentation-land is pure utopia and while actually writing top-down documentation for all the tasks at hand might work for some time, it is mostly only used to describe what to do whereas details about how to do it are quite easily omitted.
Moreover, documentation is often not in sync with the real world, especially if the people who originally wrote it have moved on to other projects or even other jobs. Don’t make the mistake of assuming that you have any way of enforcing the discipline to write & keep in sync technical documentation down to the command- or component-level over a long term. Initial success regarding that matter is far worse in predicting the long-term success of documentation projects than most project managers would admit – and it’s debatable if good-but-horribly-outdated-information is of any use in a multi-year project.
If you don’t use the Redmine wiki for any other process, try to push people towards using it for documenting what they did, even if it’s only a prototype command line script or something that doesn’t work out later (you can delete it if it serves no purpose anymore).
If, however, you use the wiki for other documentation, just reserve a certain section, e.g. any page accessible from a special Documentation page, for structured information and create pages for different types of unstructured information
Everyone who has worked in the technology industry knows that some project managers appear to have the uncanny ability to always have one more Urgent task up their sleeve, no matter how many you have already fixed. Moreover, there are so many Urgent and Immediate tasks that no-one really knows what these words mean any more – especially since you can’t quite remember the last time you fixed a Low-priority task.
When you introduce the use of an advanced issue tracking tool, you have one more change in breaking out of that vicious circle. It’s a productive policy to enforce a rule that project managers must only have one Immediate and three Urgent tasks open at any one time. This enables developers to actually prioritize and avoid the risk of getting yelled at by some manager that didn’t use the tools at hand properly, in effect imposing wrong or unclear constraints on the developers.
In practical projects, one phenomenon that often happens in conjunction with issue trackers is ticket orphanisation. Here’s how it happens.
Unless you are one of the lucky few that manages to close and properly & periodically handle all tickets over a long period of time, the small lapse of not having one assigned for a short period of time exponentially increases the likelihood of a ticket not being properly finished.
Here’s how it should work:
In other words, there must be someone assigned at any one time. While this does not automatically solve issues of people leaving the project, or being on holiday or sick, this is how it should work:
Independently of the aforementioned decisions:
A good way of conceptualizing sub-tickets is as local mini-milestones. While some tickets might have a scope larger than a typical milestone, sub-tickets should primarily focus on conveying relationship information to developers, while milestones should primarily focus on conveying information to mgmt.
Given that process, anyone should work primarily on the My tickets list, not so much on the project tickets list. The project tickets list is primarily for the mgmt to get an overview and developers should only use it to get some extra information or cross-reference some tasks.
The most efficient (& most obvious) way of using Redmine is to work through the My tickets list starting from the high priority tasks. Remember to regularly check if any higher priority tasks popped up.
One of the main tasks of the project manager is to ensure that the process I described automatically leads to people working on milestones, with some high-priority tasks outside the milestone interspersed in between. Proper communication to the developers ensures that while everyone is working based on the aforementioned scheme, the common goal of reaching the milestone is clearly defined in the minds of the developers.
A typical failure mode of software and hardware project management is that management thinks top-down and fails to enforce a process that thinks all the way to the bottom.
Regarding issue trackers, this systematic problem is often manifested in there being only coarse macro-tasks that, even if properly assigned to developers, don’t allow anyone to accurately assess the current state of the project (e.g. because there is no way of assessing a 0%…100% progress of an unclearly defined wide-scope task) and often lead to developers not giving regular updates.
Depending on the management style, irregular updates regarding specific parts of the development often lead to procrastination of tasks on the side of developers, greatly reducing their effectivity.
All of those failure modes can be avoided by using smaller, more strictly-defined micro-tasks. While this solution is obvious from a conceptual standpoint, the practical implementation is most often somewhere between utterly disastrous and not-viable-in-the-long term. The most common issues are:
However, most of these concerns are caused by a combination of inadequate processes and fundamental misconceptions about micro-tasks:
Since management of larger companies often operates under the assumption that every subordinate shall be exchangeable to a certain extent and that concept often prevails throughout the hierarchy, some find it hard to swallow that some tasks might be split into multiple micro-tasks by one developer whereas another one will just treat it monolithically based on differences in their projected timespan. Yet given even a large expected deviance from the projected time a task will take to the actual time spent on it (plus the fact that it’s much easier to judge the duration of a short task as compared to a long one) this remains without any practical consequence given that the min-forced-split time of 60 minutes is so small compared to the duration of the entire project. All remaining issues need to be handled by communicating between management and developers anyway, such as telling the developer that he should have split this or that task after repeatedly failing to do so.
In addition to facilitating effective & automatic information flow between management and the developers, micro-tasks in combination with effective supervision by development heads or management tend to avoid many cases of getting stuck: A typical behavior for developers is to continue working on a technically challenging task, even if they are totally stuck dealing with the current issue.
My recommendation is what I call the 15-minute rule: Once you can’t get measurable progress on a problem within 15 minutes, you should try another task, or (if not possible) approach the issue from a different angle.
While this leaves significant room for interpretation and even besides that should be treated more like a heuristic than a rule, self-applying this rule has helped many inexperienced developers.
Micro-tasks deal with this issue on a project setting by allowing the management or the supervisory developer (depending on the project size) to periodically check all the currently active tasks with very short cycles times of less than an hour: If there has been no progress on a micro-task within one or two hours, one can expect that either the developer hasn’t split up the task properly or he is stuck and might be able to profit from assistance by the usually more experiences supervisor.
Without micro-tasks the timespan in which one can expect a feedback or otherwise can assume that it’s stuck not only varies a lot depending on the individual task, but is also extremely long – sometimes even weeks or months. This increases the likelihood that a lot of time is being lost due to long periods of developer being stuck on a task without measureable progress. In effect, micro-tasks add to the supervisory developer’s toolbox by providing means to intervene before much valuable time is lost, while avoiding the work environment being perceived as surveillance-focused by the developers.
The way Redmine is set up in many companies effectively makes people, especially non-tech oriented people, unlikely to use Redmine to document their issues.
For security reasons, Redmine is often only available over a virtual private network. Regarding usability, this has the effect that often a user has to either log in through multiple layers of security (login to VPN, login to Redmine).
Moreover, often the VPN client requires a special setup that might not always.
One particular case that I recall is a company where all project mgmt tools were accessible only over VPN – and my VPN account could only handle one user at a time. Therefore, if I switched from my Desktop to my Notebook, the VPN couldn’t connect and therefore I was unable to use Redmine.
Security is usually handled sufficiently well by recurring reviews by admins and/or external specialists – these reviews should also cover your website. My main recommendation regarding Redmine is to copy the project URLs (e.g. https://redmine.gridbox.de/projects/test
) when you’re logged in and opening them in Incognito mode (where you’re not logged in). If you see the login form, everything is alright for this project. If you see the project overview page, you have set the project to Public mode – see above for how to fix it.
In general, security should not be conceptualized as an optimization goal (as it is often done by technology-affine people) but more as a tradeoff between certain types of risk and usability. In that context, usability can be modeled as the likelihood that people will use a tool or process voluntarily – or, equivalently, the amount of effort you have to put in so that a certain group of people uses a certain tool or process to a given extent.
This implies that any educated discussion about security will need to define some model of the risk that is being talked about, and the people that. In many cases, the mistake being made especially by technologically affine people is to base the usability model on their own aptitude & capability of using extremely suboptimal tools effectively, while sometimes their risk model is somewhat influenced by a desire to better than the current state-of-the-market. While both these properties are fundamentally desirable in almost every aspect, practical businesses will have to make the fundamental security-usability tradeoff at some point, explicitly or implicitly, whether they like it or now.
Often, Redmine admins strip users of all but the most obviously required privileges for all the subprojects. This means that users often can’t edit descriptions, can’t close or reopen issues. Even if they can contact the admin to e.g. re-open an erroneously closed issue, users will not use it effectively based on some level of fear of doing something they can’t revert themselves – and the social shame of having to contact the admin about it, even it the admin does not seem to judge them for their behavior.
The only action I recommend to not allow is to delete (as opposed to close) an issue as this is very hard to recover from. There are certain projects, of course, where certain (low level) users factually shouldn’t be able to do anything (e.g. marketing might just be able to read R&D info but not contribute to it), but in many projects, restrictions apply to all projects and inhibit effective use especially by non-tech personnel in projects with more that ~3 active participants.
If one actively enforces a specific format, length or any other policy for creating or updating tickets or documentation, this will not only create a significant entry barrier to new users (who often will refrain from using project management tools altogether except for the level that is actively enforced) but also create a hard wall of separation between the veteran users (who know the rules, or, as some would say, define the interpretation of the rules) and everyone else – with everyone else being more or less afraid of not being able to follow the rules and therefore refrain from using it beyond the minimal enforced level.
Additionally, if you impose policies on the format of some documentation don’t forget to consider the case that some relevant information might not fit into a predefined scheme. If you are too aggressive on enforcing the policy, important information might be lost as it is dropped from updates. Therefore, you should always include an additional information field in any predefined format, even if it’s often misused.
Example: A company enforces that development tickets must only be closed by git commits. Yet they failed to consider that many bugs are either unintentionally fixed by a secondary ticket without the other developer being aware of the primary ticket – or are based on a misconception and are therefore not really a fixable bug.
The ostensibly good intention of being able to map all tickets to git commits might, therefore, result in either a lot of tickets not being closed at all, or handled in some other far-from-ideal way as people are looking to a workaround for the policy. The corporate process of fixing the policies once such cases are known and documented are often both too slow and too complex for the user to use compared to a quick workaround. However, in the long term, the unknown status or a large portion of your bug tracker will almost certainly come and haunt you!
Think of it this way: Your Bugtracker might suffer the same fate as many of the classical internet forums ubiquitous since the early 2000s: If you ask a question, and you’ll only get an answer like
Use the search function ! What you are looking for has been answered SO MANY TIMES on this forum!
by some veteran user, this will likely be the last question you ask voluntarily on said forum.
Why is that? Because not only isn’t it as obviously easy to use the search function and actually get some meaningful results out of it as said veteran implies (because the new user usually doesn’t know the right question to ask, i.e. what terms to look for), but said veteran could have just as easily used the 10 seconds of his time to get you one or two of the seemingly ubiquitous answer-to-your-question-links to at least get you started.
One project I recall temporarily had the policy that every ticket shall have a due date set to at most 4 weeks in the future, in order to be able to be reminded it via Gantt etc. This didn’t work out, as constant needless update emails to extend the due date (e.g. for a postponed issue) annoyed all the users. Also,
My recommendation is to use due dates only where due dates are functionally implied and, at the same time, functionally required by the project.
Example: If the software needs to be delivered in 6 months, maybe the core feature implementation tasks should be due in 3 months.
In general, it is advisable to use milestones instead of due dates to keep a lean development process and still be able to keep close track of the progress. If you e.g. set due dates for tickets belonging to the next spring, you could just as well have created a milestone for said sprint, and therefore keep close track of the progress while removing the often unrealistic dangling sword of due dates from the mind of the developer.
Think of it this way: The most effective way to motivate people in said context is for everyone to work towards a common goal (milestone) instead of everyone fearing to not hit and get yelled at. Don’t come up with reasoning like We don't yell at my company
. Even if there is no explicitly negative reaction whatsoever, subconcious reactions to the negative motivation attempt will not make your goal easier to reach – or your project easier to manage.
If you make tools difficult to use for people, people will not use the tools – or use them only to the extent that you actively enforce. Since you should have better things to do than enforcing the use of tools, try your best to don’t make them hard to use – and stop coming up with reasons why you seem to make it harder to be used.
In practice, it is often sufficient to avoid punishing behavior that is incompatible with some policy but instead try to encourage & reward improvement. You could, for example, treat badly written issues with a lower priority until they are improved, but never make the mistake of trying to educate the users by dismissing them.
Often when you introduce new users to Redmine or you want to test out new features, a demo project can come in very handy. It allows you to test out and show features to users without real-world consequences even if you screw up something. Give all users permanent access to the demo project – so they can try out things as well if required.
Note: Also see this followup post on how to use custom themes in this setup and this followup post on how to backup Redmine using this setup.
This tutorial shows you step-by-step the easiest method of setting up a fresh redmine installation I have found so far. The commands have been tested on Ubuntu 18.04, but they should work with minimal modification on other DEB-based distributions
Please follow the instructions in How to install docker and docker-compose on Ubuntu in 30 seconds
We will install redmine in /var/lib/redmine
which will host the data directories and the docker-compose
script.
# Create directories sudo mkdir /var/lib/redmine sudo mkdir -p /var/lib/redmine/redmine_data /var/lib/redmine/mariadb_data # Set correct permissions for the directories sudo chown -R $USER:docker /var/lib/redmine sudo chown -R 1001:1001 /var/lib/redmine/redmine_data /var/lib/redmine/mariadb_data
Next, we’ll create /var/lib/redmine/docker-compose.yml
.
There’s a couple of things you need to change here:
REDMINE_EMAIL
to the email of the admin user you want to use (usually that is your email!)SMTP_HOST
, SMTP_PORT
, SMTP_USER
and SMTP_PASSWORD
) to a valid SMTP server. SMTP_TLS
defaults to true – in the rare case that'3718:3000'
means that Redmine will be mapped to port 3718 on the local PC. This port is chosen somewhat arbitarily – as we will run redmine behind an nginx reverse proxy, the port does not need to be any port in particular (as long as you use the same port everywhere), but it may not be used by anything else. You can use any port here, provided that it’s not used for anything else. Leave 3000
as-is and only change 3718
if required.Note that you do not need to change REDMINE_PASSWORD
– when you login for the first time, redmine will force you to change the password anyway.
version: '2' services: mariadb: image: 'bitnami/mariadb:latest' environment: - ALLOW_EMPTY_PASSWORD=yes volumes: - '/var/lib/redmine/mariadb_data:/bitnami' redmine: image: 'bitnami/redmine:latest' environment: - REDMINE_USERNAME=admin - REDMINE_PASSWORD=redmineadmin - [email protected] - SMTP_HOST=smtp.gmail.com - SMTP_PORT=25 - [email protected] - SMTP_PASSWORD=yourGmailPassword ports: - '3718:3000' volumes: - '/var/lib/redmine/redmine_data:/bitnami' depends_on: - mariadb
Next, we’ll configure the systemd service in /etc/systemd/system/redmine.service
.
Set User=...
to your current user in the [Service]
section.
[Unit] Description=Redmine Requires=docker.service After=docker.service [Service] Restart=always User=uli Group=docker # Shutdown container (if running) when unit is stopped ExecStartPre=/usr/local/bin/docker-compose -f /var/lib/redmine/docker-compose.yml down -v # Start container when unit is started ExecStart=/usr/local/bin/docker-compose -f /var/lib/redmine/docker-compose.yml up # Stop container when unit is stopped ExecStop=/usr/local/bin/docker-compose -f /var/lib/redmine/docker-compose.yml down -v [Install] WantedBy=multi-user.target
After creating the file, we can enable and start the redmine service:
sudo systemctl enable redmine sudo systemctl start redmine
The output of sudo systemctl start redmine
should be empty. In case it is
Job for redmine.service failed because the control process exited with error code. See "systemctl status redmine.service" and "journalctl -xe" for details.
debug the issue using journalctl -xe
and journalctl -e
The first startup usually takes about 3 minutes, so grab a cup of coffee.
Now you can check if redmine is running using
wget -qO- http://localhost:3718/
(if you changed the port config before, you need to use your custom port in the URL).
If it worked, it will show a large HTML output, ending with
[...] <div id="footer"> <div class="bgl"><div class="bgr"> Powered by <a href="https://www.redmine.org/">Redmine</a> © 2006-2018 Jean-Philippe Lang </div></div> </div> </div> </div> </body> </html>
If the output is empty, try wget -O- http://localhost:3718/
to see the error message
We’ll use nginx to proxy the requests from a certain domain (Using Apache, if you use it already, is also possible but it is outside the scope of this tutorial to tell you how to do that). Install it using
sudo apt -y install nginx
First, you’ll need a domain name with DNS being configured. For this example, we’ll assume that your domain name is redmine.techoverflow.net
! You need to change it to your domain name!
First, we’ll create the config file in /etc/nginx/sites-enabled/redmine.conf
. Remember to replace redmine.techoverflow.net
by your domain name! If you use a port different from 3718, replace that as ewll.
server { listen 80; server_name redmine.techoverflow.net; access_log /var/log/nginx/redmine.access_log; error_log /var/log/nginx/redmine.error_log info; location / { proxy_pass http://127.0.0.1:3718; # docker-compose forwarded proxy_read_timeout 3600s; proxy_http_version 1.1; } }
Now run sudo nginx -t
to test if there are any errors in the config file. If everything is alright, you’ll see
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
Once you have fixed all errors, if any, run sudo service nginx reload
to apply the configuration.
Test the setup by navigating your domain name in the browser. You should see the redmine interface:
First we need to install certbot
and the certbot nginx plugin in order to create & install the certificate in nginx:
sudo apt -y install python3-certbot python3-certbot-nginx
Fortunately certbot automates most of the process of installing & configuring SSL and the certificate. Run
sudo certbot --nginx
It will ask you to enter your Email address and agree to the terms of service and if you want to receive the EFF newsletter.
After that, certbot will ask you to select the correct domain name:
Which names would you like to activate HTTPS for? ------------------------------------------------------------------------------- 1: redmine.techoverflow.net ------------------------------------------------------------------------------- Select the appropriate numbers separated by commas and/or spaces, or leave input blank to select all options shown (Enter 'c' to cancel):
In this case, there is only one domain name (there will be more if you have more domains active on nginx!).
Therefore, enter 1
and press enter. certbot will now generate the certificate. In case of success you will see
Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/redmine.techoverflow.net.conf
Now it will ask you whether to redirect all requests to HTTPS automatically:
Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access. ------------------------------------------------------------------------------- 1: No redirect - Make no further changes to the webserver configuration. 2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for new sites, or if you're confident your site works on HTTPS. You can undo this change by editing your web server's configuration. ------------------------------------------------------------------------------- Select the appropriate number [1-2] then [enter] (press 'c' to cancel):
Choose Redirect
here: Type 2
and press enter. Now you can login to redmine and finish the installation.
You need to renew the certificate every 3 months for it to stay valid, and run sudo service nginx reload
afterwards to use the new certificate. If you fail to do this, users will see certificate expired error messages and won’t be able to access Redmine easily! See this post for details on how to mostly automate this process!
Go to your domain name (if you have followed the instructions above, it should automatically redirect you to HTTPS). Click Login at the top right and login with the username admin
and the default password redmineadmin
. Upon first login, it will require you to change the password to a new – and more secure password.
I won’t describe in detail how to setup Redmine for your project. However there’s two things you should take care of immediately after the first login:
Host name and path
to your domain name, e.g. redmine.techoverflow.net
. Set Protocol
to HTTPS
. You can also set a custom name for your Redmine installation under Application Title
Emission email address
(usually you would use [email protected]
here, but you might want to use your SMTP username for some SMTP providers like GMail)Send a test email
which will send a test email to the current redmine user’s email adress. Unless you have changed it, the default is the address configured in REDMINE_EMAIL
in /var/lib/redmine/docker-compose.yml
.In case the email does not work, change SMTP_...=...
in /var/lib/redmine/docker-compose.yml
but you also have to change it in /var/lib/redmine/redmine_data/redmine/conf/configuration.yml
! After doing the changes, restart redmine by
sudo systemctl restart redmine
which will use the new configuration from the config file.
ufw
is a simple Firewall for Ubuntu. Use sudo apt install ufw
to install it and sudo ufw enable
to activate it. The default configuration will allow SSH but it will block other ports, including port 3718 or any other custom port you might have used.
In order to enable it, use
sudo ufw enable sudo ufw allow ssh sudo ufw allow http sudo ufw allow https
Remember to add any ports you need to have open to the list as well. See the ufw docs for more information.
Every time you clone a git repository or push/pull, you have to enter a username and a password (e.g. for GitHub or your GitLab installation).
Instead, you want git to store the password so you only have to enter it once.
Configure the git credential helper to use a plaintext store instead of the default cache:
git config --global credential.helper store
NOTE: This approach will store your passwords in a plaintext file, so depending on your setup this might be a security risk.
You have initialized a git repository in a folder using
git init
Now that you have made some commits, you want to use
git push
but you get the following error message:
fatal: No configured push destination. Either specify the URL from the command-line or configure a remote repository using git remote add <name> <url> and then push using the remote name git push <name>
As you initialized your repository using git init
, git does not know which server to contact when you use git push
.
Therefore, we’ll have to add a server (called remote in git terminology) to the repository:
git remote add origin [email protected]:yourusername/yourrepository.git
Remember to replace [email protected]:yourusername/yourrepository.git
with the correct URL for your repository. Valid example URLs include:
https://github.com/ulikoehler/UliEngineering.git
[email protected]:ulikoehler/UliEngineering.git
This adds a server (remote add
) named origin
with the URL [email protected]:yourusername/yourrepository.git
.
The URL (last argument) depends on the server you use, for GitHub, you can get the URL (HTTPS or SSH, both will work) by clicking the green Clone or Download button.
Now you can push your existing data to the server. git push
by itself won’t work for the first time, because git doesn’t know automatically that you want to push to origin
. Therefore we have to tell it using --set-upstream
that future git push
commands shall automatically push to origin
:
git push --set-upstream origin master
If this command lists an error, you likely used the wrong URL for the repository or you don’t use the correct credentials (username/password, SSH key etc).
From now on, you can just use
git push
every time you’ve made a commit in order to push it to the server.
Note: origin
is no special name, it’s just the name git uses for the server when you git clone
a repository. Therefore it’s the standard name for your main server to push to. Similarly, git uses master
as the default branch name.
You’re trying to compile something (e.g. using GCC) on Ubuntu, but you get an error message similar to this one:
/usr/bin/ld: error: cannot open crt1.o: No such file or directory /usr/bin/ld: error: cannot open crti.o: No such file or directory /usr/bin/ld: error: cannot open crtn.o: No such file or directory
You want to compile and install libc++ (sometimes also named libcxx), but CMake complains with this error message
CMake Error at cmake/Modules/MacroEnsureOutOfSourceBuild.cmake:7 (message):
libcxx requires an out of source build. Please create a separate</em>
build directory and run 'cmake /path/to/libcxx [options]' there.
Call Stack (most recent call first):
CMakeLists.txt:24 (MACRO_ENSURE_OUT_OF_SOURCE_BUILD)
CMake Error at cmake/Modules/MacroEnsureOutOfSourceBuild.cmake:8 (message):
In-source builds are not allowed.
CMake would overwrite the makefiles distributed with Compiler-RT.
Please create a directory and run cmake from there, passing the path
to this source directory as the last argument.
This process created the file `CMakeCache.txt' and the directory `CMakeFiles'.
Please delete them.
Call Stack (most recent call first):
CMakeLists.txt:24 (MACRO_ENSURE_OUT_OF_SOURCE_BUILD)
You want to use git-svn
to clone a SVN repository, but you don’t want to clone the entire history (which can be quite slow) but only the latest revision.
You want to find out what the last revision number of a remote subversion repository is without cloning it (e.g. because cloning takes a looong time with subversion).