I created my first FreeBSD port recently. I found that FreeBSD didn't have a port for [GoCD](https://www.gocd.org/), which is a continuous integration and continuous deployment (CI/CD) system. This was a great opportunity to learn how to build a FreeBSD port while also contributing back to the community. Edit: This post was mentioned in [BSD Now episode 294](https://www.bsdnow.tv/294). # Initial Setup I created a FreeBSD build environment in a Digital Ocean droplet. Installed a few things I needed, $ sudo pkg update $ sudo pkg install -y bash vim-tiny tmux openjdk8 ruby ruby24-gems rubygem-rake Since GoCD requires Java and Ruby I had to install those with ``pkg``. Otherwise, they will be built with the ports system, which is very slow. Others are for my convenience. I highly recommend you create a ``tmux`` session for your work, especially when the build environment is remote. $ tmux attach -t gocd || tmux new-session -s gocd $ sudo su # become root to make it easier to work with ports $ bash # Many steps assume you're using bash; modify them as needed # Setup Ports $ cd /usr/ports $ portsnap fetch && portsnap extract && portsnap update # Tweaks $ echo DEVELOPER=yes | tee -a /etc/make.conf # https://www.freebsd.org/doc/en/books/porters-handbook/quick-porting.html $ export DISTDIR=/usr/ports/distfiles # https://www.freebsd.org/doc/en/books/porters-handbook/quick-porting.html # Port Init $ mkdir -p /usr/ports/devel/gocd-server/files $ cd /usr/ports/devel/gocd-server $ touch Makefile pkg-descr pkg-plist files/gocd-server.in # pkg-descr Edit _pkg-descr_ with the following contents. GoCD is an open source Continuous Integration (CI) and Continuous Delivery (CD) system sponsored by ThoughtWorks Inc. It is built with Java. WWW: https://www.gocd.org/ # Makefile (fetch) Edit _Makefile_ to have the following contents. This is the first round of edits which will allow us to fetch the source tarball. We'll continue to edit this file later in this post, adding more content as we need it for the related step. At the time of writing, GoCD was at version 19.2.0-8641. # $FreeBSD$ PORTNAME=gocd-server DISTVERSION=19.2.0-8641 CATEGORIES=devel java MASTER_SITES=https://download.gocd.org/binaries/${DISTVERSION}/generic/ DISTFILES=go-server-${DISTVERSION}.zip MAINTAINER=me@example.com COMMENT=An open-source Continuous Integration and Continuous Delivery system LICENSE=APACHE20 .include This Makefile can _fetch_ the source code. $ make fetch # distinfo We are also able to create the _distinfo_ file with the _Makefile_ we have. $ make makesum # Makefile (extract) Edit _Makefile_ to be able to extract files from the source distribution. # $FreeBSD$ PORTNAME=gocd-server DISTVERSION=19.2.0-8641 CATEGORIES=devel java MASTER_SITES=https://download.gocd.org/binaries/${DISTVERSION}/generic/ DISTFILES=go-server-${DISTVERSION}.zip MAINTAINER=me@example.com COMMENT=An open-source Continuous Integration and Continuous Delivery system LICENSE=APACHE20 DISTNAME=go-server-19.2.0 EXTRACT_ONLY=go-server-${DISTVERSION}.zip EXTRACT_CMD=${UNZIP_NATIVE_CMD} ${DISTDIR}/${EXTRACT_ONLY} .include This Makefile can _extract_ the source code. $ make extract # GID and UID Search _/usr/ports/GIDs_ and _/usr/ports/UIDs_ for matching _free_ GID and UID and use them. $ grep free /usr/ports/GIDs | head $ grep free /usr/ports/UIDs | head I used GID 237 since it was free. You will not necessarily use these IDs when you submit your port to the project. But it doesn't matter. For now, all we care about is using available IDs. Edit _/usr/ports/GIDs_ to claim the GID for group gocd. $ sed -i.bak 's!# free: 237!gocd:*:237:!g' /usr/ports/GIDs Edit _/usr/ports/UIDs_ to claim the UID for user gocd. $ sed -i.bak 's!# free: 237!gocd:*:237:237::0:0:GoCD:/usr/local/gocd-server:/bin/sh!g' /usr/ports/UIDs # pkg-plist Edit _pkg-plist_ with the following contents, %%JAVAJARDIR%%/go.jar @dir(%%GOCD_SERVER_USER%%,%%GOCD_SERVER_GROUP%%,) gocd-server # files/gocd-server.in Edit _files/gocd-server.in_ to create an rc script with the following contents, #!/bin/sh # $FreeBSD$ # # PROVIDE: gocdserver # REQUIRE: LOGIN # KEYWORD: shutdown # # Configuration settings for gocdserver in /etc/rc.conf: # # gocd_server_enable (bool): # Set to "NO" by default. # Set it to "YES" to enable gocd # # gocd_server_config_dir (str) # Set to "%%GOCD_SERVER_CONFIG_DIR%%" by default. # Path where config files are stored. # # gocd_server_java_home (str): # Set to "%%JAVA_HOME%%" by default. # Set the Java virtual machine to run gocd # # gocd_server_java_opts (str): # Set to "" by default. # Java VM args to use. # # gocd_server_user (str): # Set to "%%GOCD_SERVER_USER%%" by default. # User to run gocd as. # # gocd_server_group (str): # Set to "%%GOCD_SERVER_GROUP%%" by default. # Group for data file ownership. # # gocd_server_log_file (str): # Set to "%%GOCD_SERVER_LOG_FILE%%" by default. # Log file location. # # gocd_server_memory (str): # Set to "%%GOCD_SERVER_MEM%%" by default. # Minimal memory to reserve. # # gocd_server_max_memory (str): # Set to "%%GOCD_SERVER_MAX_MEM%%" by default. # Maximum memory to use. # # gocd_server_max_metaspace (str): # Set to "%%GOCD_SERVER_MAX_METASPACE%%" by default. # Maximum memory for Java's Metaspace. # # gocd_server_port (str): # Set to "%%GOCD_SERVER_PORT%%" by default. # Port to use. # # gocd_server_ssl_port (str): # Set to "%%GOCD_SERVER_SSL_PORT%%" by default. # SSL/TLS port to use. # # gocd_server_user_lang (str): # Set to "%%GOCD_SERVER_USER_LANG%%" by default. # User language. # # gocd_server_user_country (str): # Set to "%%GOCD_SERVER_USER_COUNTRY%%" by default. # User country. # # gocd_server_listen_host (str): # Set to "%%GOCD_SERVER_LISTEN_HOST%%" by default. # Host address which GoCD server should bind to. # . /etc/rc.subr name=gocdserver desc="GoCD Continuous Integration and Continuous Delivery system" rcvar=gocd_server_enable load_rc_config "${name}" : ${gocd_server_enable:=NO} : ${gocd_server_config_dir="%%GOCD_SERVER_CONFIG_DIR%%"} : ${gocd_server_java_home="%%JAVA_HOME%%"} : ${gocd_server_user="%%GOCD_SERVER_USER%%"} : ${gocd_server_group="%%GOCD_SERVER_GROUP%%"} : ${gocd_server_log_file="%%GOCD_SERVER_LOG_FILE%%"} : ${gocd_server_memory="%%GOCD_SERVER_MEM%%"} : ${gocd_server_max_memory="%%GOCD_SERVER_MAX_MEM%%"} : ${gocd_server_max_metaspace="%%GOCD_SERVER_MAX_METASPACE%%"} : ${gocd_server_port="%%GOCD_SERVER_PORT%%"} : ${gocd_server_ssl_port="%%GOCD_SERVER_SSL_PORT%%"} : ${gocd_server_user_lang="%%GOCD_SERVER_USER_LANG%%"} : ${gocd_server_user_country="%%GOCD_SERVER_USER_COUNTRY%%"} : ${gocd_server_listen_host="%%GOCD_SERVER_LISTEN_HOST%%"} : ${gocd_server_args="-server -Dgocd.redirect.stdout.to.file=${gocd_server_log_file} -Djava.io.tmpdir=%%TMPDIR%% -Djava.security.egd=file:/dev/./urandom -Xms${gocd_server_memory} -Xmx${gocd_server_max_memory} -XX:MaxMetaspaceSize=${gocd_server_max_metaspace} -Duser.language=${gocd_server_user_lang} -Djruby.rack.request.size.threshold.bytes=30000000 -Duser.country=${gocd_server_user_country} -Dcruise.config.dir=${gocd_server_config_dir} -Dcruise.config.file=${gocd_server_config_dir}/cruise-config.xml -Dcruise.server.port=${gocd_server_port} -Dcruise.server.ssl.port=${gocd_server_ssl_port}"} if [ ! -z "${gocd_server_listen_host}" ]; then gocd_server_args="${gocd_server_args} -Dcruise.listen.host=${gocd_server_listen_host}" fi pidfile=/var/run/gocd-server/gocd.pid command=/usr/sbin/daemon java_cmd="${gocd_server_java_home}/bin/java" procname="${java_cmd}" command_args="-p ${pidfile} ${java_cmd} ${gocd_server_java_opts} -jar %%JAVAJARDIR%%/go.jar ${gocd_server_args} >> ${gocd_server_log_file} 2>&1" required_files="${java_cmd}" start_precmd=gocd_server_prestart start_cmd=gocd_server_start gocd_server_prestart() { if [ ! -f "${gocd_server_log_file}" ]; then install -o "${gocd_server_user}" -g "${gocd_server_group}" -m 640 /dev/null "${gocd_server_log_file}" fi if [ ! -d "/var/run/gocd-server" ]; then install -d -o "${gocd_server_user}" -g "${gocd_server_group}" -m 750 "/var/run/gocd-server" fi } gocd_server_start() { check_startmsgs && echo "Starting ${name}." GO_SERVER_LOG_DIR=$(dirname "${gocd_server_log_file}") su -l "${gocd_server_user}" -c "exec ${command} ${command_args} ${rc_arg}" } run_rc_command "$1" # Makefile (stage, package, install) Make final edits to _Makefile_ to be able to stage, package, and install. # $FreeBSD$ PORTNAME=gocd-server DISTVERSION=19.2.0-8641 CATEGORIES=devel java MASTER_SITES=https://download.gocd.org/binaries/${DISTVERSION}/generic/ DISTFILES=go-server-${DISTVERSION}.zip MAINTAINER=me@example.com COMMENT=An open-source Continuous Integration and Continuous Delivery system LICENSE=APACHE20 DISTNAME=go-server-19.2.0 EXTRACT_ONLY=go-server-${DISTVERSION}.zip EXTRACT_CMD=${UNZIP_NATIVE_CMD} ${DISTDIR}/${EXTRACT_ONLY} NO_BUILD=yes USE_JAVA=yes USE_RUBY=yes JAVA_VERSION=1.8+ NO_ARCH= TMPDIR?=/tmp GOCD_SERVER_HOME?=${PREFIX}/gocd-server GOCD_SERVER_CONFIG_DIR?=${LOCALBASE}/etc/gocd-server GOCD_SERVER_LOG_FILE?=/var/log/gocd-server.log GOCD_SERVER_MEM?=512m GOCD_SERVER_MAX_MEM?=1g GOCD_SERVER_MAX_METASPACE?=400m GOCD_SERVER_PORT?=8153 GOCD_SERVER_SSL_PORT?=8154 GOCD_SERVER_USER_LANG?=en GOCD_SERVER_USER_COUNTRY?=US GOCD_SERVER_USER?=gocd GOCD_SERVER_GROUP?=gocd GOCD_SERVER_LISTEN_HOST?= .if ${GOCD_SERVER_USER} == "gocd" USERS=gocd .endif .if ${GOCD_SERVER_GROUP} == "gocd" GROUPS=gocd .endif SUB_LIST+=GOCD_SERVER_CONFIG_DIR=${GOCD_SERVER_CONFIG_DIR} \ GOCD_SERVER_LOG_FILE=${GOCD_SERVER_LOG_FILE} \ GOCD_SERVER_MEM=${GOCD_SERVER_MEM} \ GOCD_SERVER_MAX_MEM=${GOCD_SERVER_MAX_MEM} \ GOCD_SERVER_MAX_METASPACE=${GOCD_SERVER_MAX_METASPACE} \ GOCD_SERVER_PORT=${GOCD_SERVER_PORT} \ GOCD_SERVER_SSL_PORT=${GOCD_SERVER_SSL_PORT} \ GOCD_SERVER_USER_LANG=${GOCD_SERVER_USER_LANG} \ GOCD_SERVER_USER_COUNTRY=${GOCD_SERVER_USER_COUNTRY} \ GOCD_SERVER_USER=${GOCD_SERVER_USER} \ GOCD_SERVER_GROUP=${GOCD_SERVER_GROUP} \ GOCD_SERVER_LISTEN_HOST=${GOCD_SERVER_LISTEN_HOST} \ JAVA_HOME=${JAVA_HOME} \ JAVAJARDIR=${JAVAJARDIR} \ TMPDIR=${TMPDIR} PLIST_SUB+=GOCD_SERVER_USER=${GOCD_SERVER_USER} \ GOCD_SERVER_GROUP=${GOCD_SERVER_GROUP} \ JAVAJARDIR=${JAVAJARDIR} do-install: ${MKDIR} ${STAGEDIR}${JAVAJARDIR} ${STAGEDIR}${GOCD_SERVER_HOME} ${INSTALL_DATA} ${INSTALL_WRKSRC}/go.jar ${STAGEDIR}${JAVAJARDIR} .include Test everything is staging ok with, $ make stage Test a package can be created with, $ make package Finally, install the port, $ make install # Source Code The complete source code is available under MIT license from my [gocd-server](https://github.com/aikchar/gocd-server/) git repo. # TODO - Log files are not being created in /var/log/ but instead in /usr/local/gocd-server/logs which needs to be fixed - Submit this port to the FreeBSD project