diff --git a/manifests/unit.pp b/manifests/unit.pp
new file mode 100644
index 0000000000000000000000000000000000000000..a4560d4671daf01f5a49933a78b73cc8ed43fe65
--- /dev/null
+++ b/manifests/unit.pp
@@ -0,0 +1,123 @@
+# Copyright © 2021   Thomas Bellman, Linköping, Sweden
+# Licensed under the GNU LGPL v3+; see the README file for more information.
+
+
+/*
+ * Manage systemd units.
+ *
+ * This actually manages a config file in /etc/systemd/system/<service>.
+ * The name parameter must be on the format
+ *
+ *     UNITNAME "." TYPE
+ *
+ * where
+ *
+ *  - UNITNAME is the name of the service, e.g. "sshd" or "getty@".
+ *  - TYPE is the unit type, e.g. "service", "socket", et.c; see
+ *    systemd.unit(5).
+ *
+ * The content of the unit file can be specified in one of three ways
+ *  - the 'options' parameter, specifying the content as a hash of hashes,
+ *  - the 'content' parameter, specifying the content as a string,
+ *  - the 'source' parameter, specifying a file or Puppet URL to copy.
+ */
+define systemd::unit(
+	# Hash of hash of options to put in the unit file.  The keys in the
+	# top level hash are the sections in the config file, e.g. "Unit",
+	# "Service", "Socket".  The inner hashes are mappings from option
+	# names to values.
+	#
+	# This parameter is mutually exclusive with the 'source' and
+	# 'content' parameters.
+	#
+	# If the value of an option is a (possibly nested) list, one option
+	# line will be generated for each element in the list.  This is to
+	# support multi-value options, e.g. "ExecStart" in a service config,
+	# or "ListenStream" in a socket config.
+	#
+	# If the option name starts with a minus sign ("-"), an extra line
+	# setting that option to the empty string is prepended, in order to
+	# reset that option to the empty list, as systemd would otherwise
+	# just append the new value(s) to the existing list.
+	#
+	$options=undef,
+
+	# String to put in the unit file.  This is equivalent to the
+	# 'content' parameter on the 'file' resource type.
+	#
+	# This parameter is mutally exclusive with the 'source' and
+	# 'options' parameters.
+	#
+	$content=undef,
+
+	# Path or puppet: URL to a file to copy into the unit file.  This is
+	# equivalent to the 'source' parameter on the 'file' resource type.
+	#
+	# This parameter is mutally exclusive with the 'options' and
+	# 'content' parameters.
+	#
+	$source=undef,
+
+	# List of comment lines to add to the start of the parameter file.
+	# The list may be nested, and individual "lines" are allowed to
+	# contain newlines, turning them into multiple comment lines.
+	# This is only used if the 'options' parameter is used to specify
+	# the content of the unit file.
+	#
+	$comment=[],
+
+	# One of 'present' of 'absent'.
+	#
+	$ensure='present',
+)
+{
+    include systemd::vars
+    include systemd::daemon_reload
+
+    if ($name !~ /^([^\/.]+)\.([^\/.]+)$/)
+    {
+	fail("Systemd::Unit[${title}]:",
+	     " Name not on format <service>.<type>, ``${name}''")
+    }
+    if (($source != undef and $content == undef and $options == undef) or
+	($source == undef and $content != undef and $options == undef) or
+	($source == undef and $content == undef and $options != undef) or
+	($ensure == 'absent'))
+    {
+	# OK
+    } else {
+	fail("Systemd::Unit[${title}]:",
+	     "Exactly one of 'source', 'content' and 'options' must be used")
+    }
+
+    $systemd_resource_type = 'Unit'
+    $filepath = "${systemd::vars::etcdir}/${name}"
+
+    case $ensure
+    {
+	'present': {
+	    $xcontent = $options ? {
+		undef   => $content,
+		default => template('systemd/unitfile.erb'),
+	    }
+	    file {
+		$filepath:
+		    ensure => file,
+		    content => $xcontent, source => $source,
+		    owner => 'root', group => 'root', mode => '0444',
+		    notify => Class['systemd::daemon_reload'];
+	    }
+	}
+	'absent': {
+	    file {
+		$filepath:
+		    ensure => absent,
+		    notify => Class['systemd::daemon_reload'];
+	    }
+	}
+	default: {
+	    fail("Systemd::Unit[${title}]:",
+		 " Bad parameter ensure, ``${ensure}''")
+	}
+    }
+}
diff --git a/manifests/unit_options.pp b/manifests/unit_options.pp
index 6989b482c1300bcf6488397810c9f1e1d17e1fbe..1fb646b59e4ff4988b3d793514d1bc11c5ec3a87 100644
--- a/manifests/unit_options.pp
+++ b/manifests/unit_options.pp
@@ -70,6 +70,7 @@ define systemd::unit_options(
 	     " ``${name}''")
     }
 
+    $systemd_resource_type = 'Unit_options'
     $unitdir = "${systemd::vars::etcdir}/${unitname}.${unittype}.d"
     $filepath = "${unitdir}/${filename}.conf"
 
@@ -84,7 +85,7 @@ define systemd::unit_options(
 	    file {
 		$filepath:
 		    ensure => file,
-		    content => template('systemd/unit_options.conf.erb'),
+		    content => template('systemd/unitfile.erb'),
 		    owner => 'root', group => 'root', mode => '0444',
 		    notify => Class['systemd::daemon_reload'];
 	    }
diff --git a/templates/unit_options.conf.erb b/templates/unitfile.erb
similarity index 89%
rename from templates/unit_options.conf.erb
rename to templates/unitfile.erb
index 8cdf01b84d58c9241731233af8e9ea5aae26ac52..c3b32213d39219a383caa156e0e112d59a80a0b1 100644
--- a/templates/unit_options.conf.erb
+++ b/templates/unitfile.erb
@@ -22,4 +22,4 @@
 <%       end -%>
 <%    end %>
 <% end -%>
-# Managed by Puppet:  Systemd::Unit_options[<%= @title %>]
+# Managed by Puppet:  Systemd::<%= @systemd_resource_type %>[<%= @title %>]