line¶
line manages one line in a file that something else owns, such as a
setting in the configuration file a package installs, and leaves the rest
of the file alone. When the file would change, plan shows the change as a
diff of the file, and apply writes it in one rename, keeping the file's
mode and owner.
Use file when sadame should own the whole file; use line when
the package should keep owning it and you only need one setting.
New in 0.2.0
Minimal example¶
line "export EDITOR=vi", path: "/etc/profile"
Options¶
| Option | Type | Default | Description |
|---|---|---|---|
path |
String | required | Absolute path of the file to edit. |
line |
String | the name | The line, without a newline. Give it when the name is a description, or when the same line goes into two files. |
match |
Regexp, or a String with one | none | Lines to replace. Every line that matches is replaced by the line; when none matches and the line is not in the file, it is appended. |
state |
:present or :absent |
:present |
:absent removes the line, and every line that matches match. |
verify |
String containing %{path} |
none | A command that checks the new content before it replaces the file, such as "sshd -t -f %{path}". If it fails, the file is not changed. |
sensitive |
true or false |
false |
Hide the diff in output and JSON, for lines with secrets. |
notifies |
[:action, "type[name]"], optionally with :immediately, or a list of them |
none | Actions to run on other resources when this one changes. See Notifications. |
subscribes |
[:action, "type[name]"], optionally with :immediately, or a list of them |
none | Actions to run on this resource when another one changes. |
tags |
Symbol, String, or an Array of them | none | Tags for running part of a recipe with --tags and --skip-tags. See Tags. |
Without match, the line is appended when no line in the file is exactly
the same. Lines are compared without their newline; spaces count.
The file must exist when the change is applied: line edits files, it
does not create them. On a machine where the file comes from a package that
the same recipe installs, plan shows the line being added to a file that
"does not exist yet", and apply edits the file the package created.
Scenarios¶
# The SSH server's configuration belongs to its package. Each line resource
# changes one line and leaves the rest of the file as it was shipped, and
# sshd -t checks the edited file before it replaces the real one.
package({ "arch" => "openssh" }.fetch(platform.family, "openssh-server"))
# sshd -t needs host keys and, on Debian and Ubuntu, /run/sshd. Most
# packages leave both to the service's first start.
exec "ssh-keygen -A", creates: "/etc/ssh/ssh_host_ed25519_key"
directory "/run/sshd", mode: "0755" if platform.family == "debian"
# Replaces the line that sets the option, commented out or not; appends it
# when no line does.
line "PermitRootLogin no",
path: "/etc/ssh/sshd_config",
match: /^#?\s*PermitRootLogin\s/,
verify: "sshd -t -f %{path}"
line "PasswordAuthentication no",
path: "/etc/ssh/sshd_config",
match: /^#?\s*PasswordAuthentication\s/,
verify: "sshd -t -f %{path}"
# Removes the line if it is there. Debian ships it; sshd's default is "no".
line "no X11 forwarding",
path: "/etc/ssh/sshd_config",
line: "X11Forwarding yes",
state: :absent,
verify: "sshd -t -f %{path}"
Hardening the SSH server's configuration¶
The plan shows each edit as a diff of sshd_config. With match, the
commented-out default line becomes the setting, so the file still reads in
the order the package wrote it:
$ sudo sadame plan examples/resources/line
sadame 0.5.1 plan on local (Debian GNU/Linux 12 (bookworm))
+ directory[/run/sshd]
state: absent -> present
mode: 0755
~ line[PermitRootLogin no]
--- /etc/ssh/sshd_config
+++ /etc/ssh/sshd_config (desired)
@@ -30,7 +30,7 @@
# Authentication:
#LoginGraceTime 2m
-#PermitRootLogin prohibit-password
+PermitRootLogin no
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10
~ line[PasswordAuthentication no]
--- /etc/ssh/sshd_config
+++ /etc/ssh/sshd_config (desired)
@@ -54,7 +54,7 @@
#IgnoreRhosts yes
# To disable tunneled clear text passwords, change to no here!
-#PasswordAuthentication yes
+PasswordAuthentication no
#PermitEmptyPasswords no
# Change to yes to enable challenge-response passwords (beware issues with
~ line[no X11 forwarding]
--- /etc/ssh/sshd_config
+++ /etc/ssh/sshd_config (desired)
@@ -87,7 +87,6 @@
#AllowAgentForwarding yes
#AllowTcpForwarding yes
#GatewayPorts no
-X11Forwarding yes
#X11DisplayOffset 10
#X11UseLocalhost yes
#PermitTTY yes
Plan: 1 to create, 3 to update, 0 to remove, 0 to run, 2 unchanged.
$ sudo sadame apply examples/resources/line
sadame 0.5.1 apply on local (Debian GNU/Linux 12 (bookworm))
unchanged package[openssh-server]
unchanged exec[ssh-keygen -A]
changed directory[/run/sshd]
state: absent -> present
mode: 0755
changed line[PermitRootLogin no]
--- /etc/ssh/sshd_config
+++ /etc/ssh/sshd_config (desired)
@@ -30,7 +30,7 @@
# Authentication:
#LoginGraceTime 2m
-#PermitRootLogin prohibit-password
+PermitRootLogin no
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10
changed line[PasswordAuthentication no]
--- /etc/ssh/sshd_config
+++ /etc/ssh/sshd_config (desired)
@@ -54,7 +54,7 @@
#IgnoreRhosts yes
# To disable tunneled clear text passwords, change to no here!
-#PasswordAuthentication yes
+PasswordAuthentication no
#PermitEmptyPasswords no
# Change to yes to enable challenge-response passwords (beware issues with
changed line[no X11 forwarding]
--- /etc/ssh/sshd_config
+++ /etc/ssh/sshd_config (desired)
@@ -87,7 +87,6 @@
#AllowAgentForwarding yes
#AllowTcpForwarding yes
#GatewayPorts no
-X11Forwarding yes
#X11DisplayOffset 10
#X11UseLocalhost yes
#PermitTTY yes
Summary
changed 4 directory[/run/sshd]
line[PermitRootLogin no]
line[PasswordAuthentication no]
line[no X11 forwarding]
unchanged 2
failed 0
skipped 0
notified 0
Finished in 0.57s.
$ sudo sadame plan examples/resources/line
sadame 0.5.1 plan on local (Debian GNU/Linux 12 (bookworm))
Plan: 0 to create, 0 to update, 0 to remove, 0 to run, 6 unchanged.
The edits do not restart the SSH server. Add
notifies: [:restart, "service[ssh]"] (sshd on Rocky Linux and Arch)
when they should take effect right away.
Writing match¶
match is a regular expression for one line, without its newline. Make it
match every form the setting can take in the file, commented out or not,
with or without spaces, so that the line replaces the setting instead of
being appended after it:
| You want | match |
|---|---|
Port 2222 where the file has #Port 22 |
/^#?\s*Port\s/ |
vm.swappiness = 10 where the file may have vm.swappiness=60 |
/^#?\s*vm\.swappiness\s*=/ |
include_dir = 'conf.d' in postgresql.conf |
/^#?\s*include_dir\s*=/ |
When more than one line matches, every one of them is replaced, so the file ends up with the same line twice rather than two different values.
Common mistakes¶
- Managing the same file with
fileandline. Thefileresource writes the whole content on every run andlinechanges it again, so neither ever reports "unchanged". Use one or the other. - A
matchthat misses the active setting. If the file hasPermitRootLogin yesandmatchis/^#PermitRootLogin/, nothing matches, soPermitRootLogin nois appended at the end, and sshd keeps the first value it reads:yes. Cover both forms:/^#?\s*PermitRootLogin\s/. - Expecting
lineto create the file. It fails with "does not exist". Create the file with file, or install the package that ships it, earlier in the recipe.