jordansissel/fpm · error · FPM::InvalidPackageConfiguration

Invalid systemd unit file extension: #{extname}. Expected on

Error message

Invalid systemd unit file extension: #{extname}. Expected one of: #{possible_extensions_str}

What it means

Each file passed via --deb-systemd is checked by extension. Files with no extension get '.service' appended; files with an extension must be one of the recognized systemd unit types (.service, .socket, .device, .mount, .automount, .swap, .target, .path, .timer). Any other extension raises FPM::InvalidPackageConfiguration listing the allowed set.

Source

Thrown at lib/fpm/package/deb.rb:574

        ".scope",
    ]

    attributes[:deb_systemd] = []
    attributes.fetch(:deb_systemd_list, []).each do |systemd|
      name = File.basename(systemd)
      extname = File.extname(name)

      name_with_extension = if extname.empty?
                              "#{name}.service"
                            elsif systemd_file_extensions.include?(extname)
                              name
                            else
                              # Mutating the array is fine as we raise directly after, and the array will be re-initialised next time
                              # this method is called. If this branch is changed in the future so as not to diverge, care should be
                              # taken to ensure that this mutated version of the array is only used for generating the error message.
                              systemd_file_extensions[-1] = systemd_file_extensions[-1].prepend("or ")
                              possible_extensions_str = systemd_file_extensions.join(", ")
                              raise FPM::InvalidPackageConfiguration,
                                "Invalid systemd unit file extension: #{extname}. Expected one of: #{possible_extensions_str}"
                            end

      dest_systemd = staging_path(File.join(attributes[:deb_systemd_path], "#{name_with_extension}"))
      mkdir_p(File.dirname(dest_systemd))
      FileUtils.cp(systemd, dest_systemd)
      File.chmod(0644, dest_systemd)

      attributes[:deb_systemd] << name_with_extension
    end

    if script?(:before_upgrade) or script?(:after_upgrade) or attributes[:deb_systemd].any?
      puts "Adding action files"
      if script?(:before_install) or script?(:before_upgrade)
        scripts[:before_install] = template("deb/preinst_upgrade.sh.erb").result(binding)
      end
      if script?(:before_remove) or not attributes[:deb_systemd].empty?
        scripts[:before_remove] = template("deb/prerm_upgrade.sh.erb").result(binding)

View on GitHub (pinned to b6d77ba72a)

Solutions

  1. Render templates first and pass the final unit file with a valid extension (foo.service)
  2. Rename files that are genuinely units but carry a wrong extension
  3. Drop the extension entirely so fpm appends .service (--deb-systemd myapp yields myapp.service)
  4. Remove non-unit files from the --deb-systemd list and ship them via -s dir input instead

Example fix

# before
fpm -s dir -t deb -n foo --deb-systemd myapp.service.in .
# -> Invalid systemd unit file extension: .in

# after
erb myapp.service.in > myapp.service
fpm -s dir -t deb -n foo --deb-systemd myapp.service .
Defensive patterns

Strategy: validation

Validate before calling

VALID_EXT = %w[.service .socket .device .mount .automount .swap .target .path .timer]

systemd_units.each do |path|
  ext = File.extname(path)
  next if ext.empty? || VALID_EXT.include?(ext)
  abort "#{path}: not a systemd unit extension (#{VALID_EXT.join(' ')})"
end

Type guard

def valid_systemd_unit?(path)
  ext = File.extname(path)
  ext.empty? || %w[.service .socket .device .mount .automount .swap .target .path .timer].include?(ext)
end

Try / catch

begin
  pkg.output(out)
rescue FPM::InvalidPackageConfiguration => e
  raise unless e.message =~ /Invalid systemd unit file extension/
  pkg.attributes[:deb_systemd] = pkg.attributes[:deb_systemd].select { |u| valid_systemd_unit?(u) }
  retry
end

Prevention

When it happens

Trigger: Passing --deb-systemd with a file like myapp.init, unit.txt, or myapp.service.in: the extension is present but not in the whitelist, so fpm refuses rather than shipping a file systemd would ignore. Only exact extensions from the list are accepted.

Common situations: SysV-to-systemd migration scripts passing .init or .sh files; template files (.service.erb/.service.in) passed directly instead of rendered output; typos like .servcie; documentation files accidentally included in the unit list.

Related errors


AI-assisted analysis of jordansissel/fpm@b6d77ba72a (2026-08-21). Data as JSON: /api/errors/81fd4a7c02c0ebb8. Report an issue: GitHub.