Tag Archives: linux

How to package the TeamCity build agent for Debian/Ubuntu

This post brought to you by Thumbtack Engineering and the letters C and I.

How to package the TeamCity build agent for Debian/Ubuntu

We’ll use fpm to package the TeamCity 9 build agent for Debian 8 (“Jessie”) or Ubuntu Linux, including a systemd service file to start it automatically.

First, read the TeamCity 9 docs for “Setting up and Running Additional Build Agents”.

Get the TeamCity build agent

Download TeamCity 9 from JetBrains. I’m going to be using TeamCity 9.0.3. You can also get a version of the agent matched to your TeamCity install by going to the Agents tab and clicking “Install Build Agents” in the upper right.

Install fpm if you don’t have it already.

Unzip TeamCity-9.0.3.tar.gz and find the buildAgent directory. This is where all of the agent’s code and config files live. Since TeamCity wasn’t written with the FHS in mind, we’ll put it in /opt/TeamCity/buildAgent.

Make the agent scripts executable

Run chmod +x buildAgent/bin/*.sh. You won’t need to run them on the system you’re using for packaging, but fpm preserves the executable flag when it creates a package.

Create a systemd service file

Create a file named teamcity-build-agent.service and put this in it.

[Unit]
Description=TeamCity build agent

[Service]
User=teamcity
ExecStart=/opt/TeamCity/buildAgent/bin/agent.sh run

[Install]
WantedBy=multi-user.target

The run argument keeps the startup script from forking so that systemd can manage the process itself. The User section should be changed to reflect whatever user you plan on running TeamCity as. (See the systemd.exec man page for more process control options.)

This file will eventually be copied to /lib/systemd/system/teamcity-build-agent.service, because systemd is too cool for /etc.

Build a package with fpm

fpm has some usage examples on its wiki. We’re going to use the -s dir input type to take a directory and the -t deb output type to generate a Debian .deb package.

fpm
-s dir
-t deb
--name teamcity-build-agent
--version 9.0.3
--architecture all
--depends java6-runtime-headless
buildAgent=/opt/TeamCity/
teamcity-build-agent.service=/lib/systemd/system/teamcity-build-agent.service

This fpm invocation tells it thje package name and version, that the agent doesn’t care what architecture you’re using but requires Java 6, and that everything should go under /opt/TeamCity, except for the service file, which goes where systemd is expecting it.

You should now have a file named teamcity-build-agent_9.0.3_all.deb.

Create a user

Before installing the package on your target machine, run sudo adduser teamcity --system --group --home /opt/TeamCity, or add something equivalent to your configuration manager if that’s where you manage users. (I’ll be using Puppet to manage users on production machines that will have this package). The --group option creates a teamcity group along with the teamcity user, so that you can then add other users to the teamcity group if they need to look at TeamCity builds or configuration.

For extra credit, you could create and destroy the user in preinstall/postremove scripts.

Install the package

Copy the file to your target machine and run sudo dpkg -i teamcity-build-agent_9.0.3_all.deb.

Configure the package

Copy /opt/TeamCity/buildAgent/conf/buildAgent.dist.properties to /opt/TeamCity/buildAgent/conf/buildAgent.properties. Edit serverUrl to point it at your TeamCity server. You may also want to set name to something recognizable, or the agent will default to your hostname. If you’re using Puppet, this is a good file to templatize.

Create writable directories

You’ll need to create the directories named in bin/agent.sh and conf/buildAgent.properties, including at least logs, work, temp, and system, as well as update and backup directories that I’ve only seen in log output. They should be writable by the teamcity user.

sudo install -d -o teamcity -g teamcity -m ug=rwx /opt/TeamCity/buildAgent/logs
sudo install -d -o teamcity -g teamcity -m ug=rwx /opt/TeamCity/buildAgent/work
sudo install -d -o teamcity -g teamcity -m ug=rwx /opt/TeamCity/buildAgent/temp
sudo install -d -o teamcity -g teamcity -m ug=rwx /opt/TeamCity/buildAgent/system
sudo install -d -o teamcity -g teamcity -m ug=rwx /opt/TeamCity/buildAgent/update
sudo install -d -o teamcity -g teamcity -m ug=rwx /opt/TeamCity/buildAgent/backup

Alternatively, you can map them to more FHS-friendly locations in /tmp and /var. logs is used by agent.sh for both logging and a PID file, but that can be changed by setting the LOG_DIR and PID_FILE environment variables in the service file.

More extra credit: there’s no easy way to specify file ownership in an fpm-generated .deb package. This is a known issue. This is not a limitation of the .deb format itself, which, broadly speaking, is a pair of tarballs. Solve this problem and your package can create those writable directories itself.

Start the agent

Run sudo systemctl start teamcity-build-agent. You can also run sudo systemctl status teamcity-build-agent to see if it started successfully.

Talking in both directions

Communication between a TeamCity server and a TeamCity agent is two-way. Agent→server connections can and should use HTTPS, but server→agent connections don’t yet support encryption.

You’ll need to make sure the agent can reach the server (outbound on on whatever port you used in the serverUrl section of the agent config file), and the server can likewise reach the agent (inbound on the port specified in ownPort and the IP address specified in ownAddress, if you’ve bound your agent to a specific IP). The inbound port is 9090 by default, and again, it’s not encrypted, so you’ll need to use a VPN, SSH tunnel, or equivalent between the server and agent.

Advertisements
Tagged , , , , , ,

Unicorn Support Would Be Easier

Unicode has two ways to represent composite characters like accented Roman letters. If you want to write a ü (that should render as a lowercase u with umlaut/diaeresis/two dots over it, if your browser is any good), you can represent it as the single Unicode codepoint LATIN SMALL LETTER U WITH DIAERESIS (U+00FC), or you can represent it as LATIN SMALL LETTER U (U+0075) followed by COMBINING DIAERESIS (U+0308). Since these are, in fact, the same letter, Unicode defines normalization forms for things like comparing and sorting strings. In the NFC form, all composite characters that have a single codepoint representation are represented as that single codepoint; in the NFD form, they are all decomposed into multiple codepoint representations. If you pick a normalization form and stick with it, then you can ensure that ü is always equal to ü no matter which representation it came in with.

If you don’t do normalization, then annoying things happen. The setup is as follows: I’ve got a server machine running Debian 5 Linux, serving files from an XFS filesystem through Samba, talking to a client Mac running OS X 10.5 (Leopard) and mounting the share using mount_smbfs(8). Some files and folders have accented letters in their names. Now, they display fine in Finder directory listings. Problem is, when you click on them, they disappear!

It took some poking around in Wireshark to figure out what was going on. I noticed that the file names being received from the server in directory listings were not the same as the ones that were later being requested: specifically, characters like our friend ü were decomposed in the listings, but composed in the requests. Samba would then report the requested files as missing.

I’m honestly not sure who to blame here. It could be OS X’s fault for not keeping track of received filenames somewhere so it could send out the same ones it got. It could be Samba’s fault for not normalizing incoming requests before looking up files when it’s set to use Unicode. It could even be XFS’s fault: XFS filenames are byte strings and can include any bytes other than the ASCII codes for / and NUL. The Mac OS filesystem HFS+ uses a normalization form that is (almost) NFD, so filenames are stored on disk in one format only, and other representations are illegal. XFS does not appear to do any processing of filenames. It’s actually possible to create two files named “ü” in the same directory, or at least two filenames that display as “ü” when interpreted as UTF-8 byte strings, decoded to Unicode code points, and displayed by something that can handle Unicode characters.

Fortunately, there is a workaround. If Unicode filenames are converted to NFC on disk on the server, they appear to survive the round trip to the Finder and back just fine. Python’s unicodedata module has a normalize() function that makes this pretty painless.

***

update: here’s some working sample code to NFC-ize everything in a folder hierarchy.

import os
import unicodedata

for root, dirs, files in os.walk(u'/path/to/base', topdown=False):
    for entry in files:
        nfc = unicodedata.normalize('NFC', entry)
        if entry != nfc:
            os.rename(
                os.path.join(root, entry),
                os.path.join(root, nfc))
            print os.path.join(root, nfc)
    rootparent, rootentry = os.path.split(root)
    nfc = unicodedata.normalize('NFC', rootentry)
    if rootentry != nfc:
        os.rename(root, os.path.join(rootparent, nfc))
        print os.path.join(rootparent, nfc)
Tagged , , , ,