4

I've prepared a build box following this instructions, I've search around and did a hello world rpm build, but still not sure what's the best way to go about packaging a bunch of js files (nodejs app). I'd like to :

  1. deploy the files to a location
  2. run # npm install
  3. run a command to start node listener/daemon.

I've done a very simple spec file that looks like this:

Name:           nodejsapp
Version:        1.0
Release:        0
Summary:        API for a Platform

Group:          Group
License:        License
URL:            somewebsite
Distribution:   Linux CentOS
Source0:        %{name}-%{version}.tar.gz

%description
CDR Platform is awesome product.


%prep

%install
install -m 755 -d $RPM_BUILD_ROOT/opt/cdr/api

%clean
rm -rf $RPM_BUILD_ROOT


%files
%defattr(-,root,root,-)

the above builds but nothing happens when I install! I'm not sure I fully understand how rpmbuild does business.

So in this case which macro I need to use? Do I need to create a file list under %files? Do I need to gzip files before running rpmbuild? or can I gzip them during the rpmbuild?

Any help is appreciated.

Community
  • 1
  • 1
nolimit
  • 814
  • 10
  • 19
  • 1
    You should not have your rpm start any services though. It can set them up to be started by whatever mechanism should start it going forward but it should not, generally, start the service itself. – Etan Reisner Mar 03 '15 at 22:07
  • @EtanReisner thank you, yes I did read this document too, being new to to this I'm not sure where to start? since I don't compile code, do i need %build %install .. etc the ones related to building code? – nolimit Mar 03 '15 at 22:25
  • 1
    You don't need all of those sections. In fact, technically you don't need *any* of them. In practice you want at least `%prep` to extract your sources/etc. and `%install` to copy files to the build root. Then `%files` lists the files (under the buildroot) to be included in the rpm. – Etan Reisner Mar 03 '15 at 23:15
  • @EtanReisner are you saying I need to list every single file included in the package? they're too many!! – nolimit Mar 04 '15 at 01:46
  • @EtanReisner So I should write a shell command to copy files listed in the %files section? I'm not sure that makes sense? why do I need to list them then? – nolimit Mar 04 '15 at 01:48
  • You can list entire directory trees if you own the entire contents. So `/usr/share/my_app` will own everything under that tree. And yes, you copy all the files/directories of your application to the buildroot in `%install` and then list the files/directories in `%files`. – Etan Reisner Mar 04 '15 at 01:50
  • @EtanReisner tell me if that make sense: gzip the directory - put it in sources folder - %prep will unzip - %install will copy everything %prep unziped ...?? – nolimit Mar 04 '15 at 01:59
  • @EtanReisner is there a valid reason why you said we should not start any services in an rpm install? – nolimit Mar 05 '15 at 15:20
  • Because it isn't general practice, doesn't allow configuration of the service before it starts and as such may expose an unprotected service to the network unexpectedly, will start the service during system installation (if installed during kickstart) when it almost certainly isn't required or desirable (or even functional)... etc. – Etan Reisner Mar 05 '15 at 15:22
  • @EtanReisner in this case nodejs (prerequisite) is installed and configured, I'd be just start the listener or the daemon that manages the listener. Do you still feel that I should stick to the general practice? – nolimit Mar 05 '15 at 16:30
  • I don't do node so I don't know that I understand the distinction you are making but in general yes you probably should. Especially if starting the whatever is a simple command (you could put that in the `%post` output if you wanted to even). – Etan Reisner Mar 05 '15 at 16:35

1 Answers1

8

The guidelines for packaging nodejs apps for the Fedora EPEL repo can be found here. They are a good reference when packaging your own nodejs apps.

You don't mention what version or which distribution you are using, but I am going to assume it is a recent distribtion, so the service you want to start will be running under systemd. The Fedora systemd packaging guidlines can be found here.

I am also assuming you are using github to manage the code and it is a pure JS application.

For nodejs module/app that has no dependencies or only has dependencies on nodejs modules already packaged in the EPEL the following SPEC file (saved as SPECS/nodejs-myapp.spec) will work.

%define debug_package %{nil}
#
# automatically generate requires and provides from package.json
#
%{?nodejs_find_provides_and_requires}

#
# filter out any false provides created due to dependencies with native components
#
%{?nodejs_default_filter}

#
# name of zip file containing source without .zip extension
#
%define modname myapp

Summary: A nodejs app with a systemd daemon
Name:    nodejs-%{modname}
Group:   Applications/Tools
Version: 0.1
Release: 1
License: Unlicense
URL:     https://github.com/myuser/myapp
Source0: %{modname}-%{version}.zip
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildArch: noarch
ExclusiveArch: %{nodejs_arches} noarch
BuildRequires: nodejs-packaging
BuildRequires: systemd

%description
A nodejs app that installs as a systemd service

%prep
%setup -q -n %{modname}-%{version}

%build

%{__rm} -f .gitignore
#
# Fedora guidlines do not allow bundled modules
# use nodejs_symlink_deps in the install section to generate
# links based on package.json contents
#
%{__rm} -rf node_modules

%install
rm -rf $RPM_BUILD_ROOT

#
# copy in the module source
#
%{__install} -d  $RPM_BUILD_ROOT%{nodejs_sitelib}
%{__cp} -r $RPM_BUILD_DIR/%{modname}-%{version} $RPM_BUILD_ROOT%{nodejs_sitelib}/%{modname}
#
# link the daemon binaries into sbindir
#
%{__install} -d  $RPM_BUILD_ROOT%{_sbindir}
%{__ln_s} %{nodejs_sitelib}/%{modname}/bin/myappd $RPM_BUILD_ROOT%{_sbindir}/myappd

#
# link in any dependent nodejs modules referenced in package.json
#
%nodejs_symlink_deps

#
# documents will be handled by %doc, so remove them from buildroot
#
%{__rm} -rf $RPM_BUILD_ROOT%{nodejs_sitelib}/%{modname}{CHANGELOG.md,LICENSE.md,README.md,docs}

#
# Create a systemd unit file
#
cat << __EOF > $RPM_BUILD_ROOT%{_unitdir}/myappd.service
[Unit]
Description=MyApp provides the best API
Documentation=man:myapp.service(8)

[Service]
Type=simple
ExecStart=/usr/sbin/myappd

[Install]
WantedBy=multi-user.target
__EOF

%clean
rm -rf $RPM_BUILD_ROOT

%files
%defattr(-,root,root)
%doc CHANGELOG.md LICENSE.md README.md docs
%{nodejs_sitelib}/%{modname}
%{_sbindir}/myappd
%{_unitdir}/myappd.service

%changelog
* Sun Mar 22 2015 BitByteDog
- Initial spec file for myapp

This spec file will also cope with nodejs modules that have dependencies that are already packaged as part of the EPEL. The auto requires/provides feature of RPM uses the package.json file in the module to create the necessary requires dependencies in the final RPM package.

The source zip should be downloaded from github into the SOURCES directory using a command such as:

wget -O SOURCES/myapp-0.1.zip https://github.com/myuser/myapp/archive/v0.1.zip

The RPM is built with the command:

rpmbuild -ba SPECS/nodejs-myapp.spec

The systemd unit file created is very simple, read the link provided plus the associated systemd documentation to customize to your requirements. You will need to enable and start the service with the following commands:

systemctl enable myappd.service
systemctl start myappd.service

It will automatically start after that.

If you want to include bundled modules in your JS app, it can be done using npm. First add the tags:

BuildRequires: npm
AutoReq: no
AutoProv: no

To suppress auto requires/provides generation. Then remove the line

%{__rm} -rf node_modules

from the %build section. Also change the line

%nodejs_symlink_deps

in the %install for the lines

#
# npm will bundle in all dependent modules from the npm registry
# (bundling is OK for private packages but not for EPEL packages)
#
npm -g -q --production --prefix="${RPM_BUILD_ROOT}%{_prefix}" install

All modules bundled in the app and all dependencies listed in package.json will be bundled into your app, making it a standalone app.

Explanation of npm usage in the above command

  • npm install is executed in the package/application directory and works on the package contained in that directory.
  • -g install the package in the current working directory as a global package
  • -q print warnings and errors only
  • --production don't copy devDependencies
  • --prefix= install in prefix directory

The effect is to copy the nodejs app in the local directory to ${RPM_BUILD_ROOT}%{_prefix} ready for packaging.

BitByteDog
  • 3,074
  • 2
  • 26
  • 39
  • is there a requirement to install nodejs app in user/bin directory? or is it just a best practice? I'm installing it in /opt/myapp – nolimit Mar 27 '15 at 21:17
  • 1
    /opt/myapp is a fine (and correct) place to put nodejs. The reason is that there is an EPEL package nodejs that installs into /usr. Installing your nodejs over the top or in place of the EPEL package could cause conflicts or have unintended side effects as all EPEL nodejs apps installed will now be using your version of node. – BitByteDog Mar 28 '15 at 05:11
  • Thanks @BitByteDog I've noticed that this is talking about packaging a node extension that will be available in npm registry and the work I'm doing is an api for a non public application. I'm not sure if these guidelines sill applies? do I really want to make it that complicated (linking node modules, auto provides, filter out auto provides... etc) when I could just deploy to the file system then run my daemon (I use forever). What's your thoughts on that? – nolimit Mar 30 '15 at 15:23
  • rpmbuild is a great tool for packaging projects - I use it for JS, C, C++, Java projects because RPMS are easy to handle and powerful. If you don't want RPM to generate auto provides/requires use the "AutoReq: no" and "AutoProv: no" tags. NPM is only being used to copy files from the current directory to the PREFIX directory - it can be done with cp. I used "npm" because it understands the structure of nodejs programs and modules. With cp you would have to manually create the target file structure in the PREFIX directory. I have expanded on the usage of NPM in the answer. – BitByteDog Mar 31 '15 at 03:11
  • If you application has dependencies on the OS, or you need to run installation/removal scripts, then an RPM is a good idea. I assumed you were using NPM because most apps use one or two modules and NPM manages nodejs dependencies very well. If you are writing a simple app, with no dependencies, then just copy it to /opt/myapp/bin/myapp and chmod a+x /opt/myapp/bin/myapp. – BitByteDog Mar 31 '15 at 04:24
  • I did use rpmbuild, and my nodejs app does have dependencies that I handle in "Requires" tag in spec file. I've setup my grunt build to "npm install" before I package the app. I think these guidelines are not applicable to what I'm doing. – nolimit Mar 31 '15 at 15:07