Jump to content

Cheatsheet: Difference between revisions

imported>DerickEddington
m (Fix command for getting patched sources.)
 
(14 intermediate revisions by 8 users not shown)
Line 1: Line 1:
{{Wide page}}
== A NixOS cheat sheet and comparison to Ubuntu ==
== A NixOS cheat sheet and comparison to Ubuntu ==
 
[[Ubuntu_vs._NixOS|Ubuntu vs. NixOS]] provides a table mapping of common administrative tasks and their commands in Ubuntu to similar capabilities in NixOS.
This is meant to give you basic ideas and get you unstuck. NixOS is very different from most distributions, a deeper understanding will be necessary sooner or later. Please follow the links to the manual pages or browse the Wiki for more in-depth NixOS tutorials.
 
The system-wide column is the equivalent of using <code>apt</code> under Ubuntu.
<!-- TODO''': Provide well-commented sample configuration.nix and ~/.nixpkgs/config.nix files with examples of common tasks. -->
<div class="table-responsive">
{| class="wikitable"
!|Task
!|Ubuntu
!|NixOS (system-wide and root)
!|NixOS (user) and Nix in general
|-
| colspan="5" style="text-align:center"| '''Basic concepts'''
|-
|
|
|This column will let you do everything you can with Ubuntu and more.
|This column just isn't possible in Ubuntu.
|-
|Who can install packages and who can run them?
|All packages are always system-wide and only root can install packages.
|Packages root installs are system-wide. It does so through /etc/nixos/configuration.nix. If root installs packages the same way users do, through ~/.nixpkgs/config.nix, they are also global. Root's default profile is the system-wide default profile.
|Users can install their own packages and have their own profiles (environments) through ~/.nixpkgs/config.nix
|-
|Package manager
|apt which is really running on top of dpkg, sometimes wrapped by UIs like aptitude.
|nix, but many system-wide operations are provided by nixos packages.
|Just nix without the involvement of nixos.
|-
|How do you select your official sources and major releases
|These are baked into the distribution (e.g. Ubuntu version X). Upgrades are hard and permanent.
|At any time you select from a collection of channels. They're system-wide when set by root. You can roll back changes or switch channels with ease.
|Channels are per-user if they're not set by root.
|-
|Where are packages installed?
|apt installs globally into /bin/, /usr/, etc.
|System-wide packages are in /run/current-system/sw/ (these are installed because of /etc/nixos/configuration.nix) and /nix/var/nix/profiles/default/bin/ (this is the profile managed by root). Note that the files are just symlinks to the real packages managed by nix /nix/store/.
|User packages are in ~/.nix-profile/. Note that the files are just symlinks to the real packages managed by nix in /nix/store/.
|-
|When changes take effect
|As soon as the command runs. Commands are not atomic and can leave your machine in a bad state.
|Most of the time you modify the configuration file and apply changes with nixos-rebuild switch
'''TODO''': How does one get nixos to do all the work for a switch and separate out the actual switching from fetching/building?
|Most of the time you apply changes with nix-env -i all
'''TODO''': How does one get nix to do all the work for a switch and separate out the actual switching from fetching/building?
|-
|Packages
|Uniformly referred to as packages
|Technically called "derivations" but everyone calls them packages.
|Technically called "derivations" but everyone calls them packages.
|-
| colspan="5" style="text-align:center"| '''Package management'''
|-
|-
|Install a package for all users
|<syntaxhighlight lang="console">$ sudo apt-get install emacs</syntaxhighlight>
|
1. Add to /etc/nixos/configuration.nix:
<syntaxhighlight lang="nix">
environment.systemPackages = with pkgs; [
  wget # let's assume wget was already present
  emacs
];</syntaxhighlight>
2. Run :
<syntaxhighlight lang="console">$ sudo nixos-rebuild switch</syntaxhighlight>
|<syntaxhighlight lang="console">$ nix-env -iA nixos.emacs</syntaxhighlight>
Or with collections, add the package to your ~/.nixpkgs/config.nix and run
<syntaxhighlight lang="console">$ nix-env -iA nixos.all</syntaxhighlight>
|-
|Install a package for a specific user only
|Not possible
|
1. Add to /etc/nixos/configuration.nix:
<syntaxhighlight lang="nix">
users.users.alice.packages = with pkgs; [ emacs ];</syntaxhighlight>
2. Run:
<syntaxhighlight lang="console">$ sudo nixos-rebuild switch</syntaxhighlight>
|
1. Add to ~/.nixpkgs/config.nix:
<syntaxhighlight lang="nix">users.users.alice.packages = with pkgs;[ emacs ];</syntaxhighlight>
2. Run:
<syntaxhighlight lang="console">$ nix-env -iA nixos.all</syntaxhighlight>
|-
|Install a service
|<syntaxhighlight lang="console">$ sudo apt install openssh-server</syntaxhighlight>
|
1. Add to /etc/nixos/configuration.nix:
<syntaxhighlight lang="nix">services.openssh.enable = true;</syntaxhighlight>
2. Run: <syntaxhighlight lang="console">$ sudo nixos-rebuild switch</syntaxhighlight>
|Not possible
|-
|Uninstall a package
|<syntaxhighlight lang="bash">sudo apt-get remove emacs</syntaxhighlight>
|remove from /etc/nixos/configuration.nix
<syntaxhighlight lang="console">$ sudo nixos-rebuild switch</syntaxhighlight>
|
<syntaxhighlight lang="console">$ nix-env --uninstall emacs</syntaxhighlight>
|-
|Uninstall a package removing its configuration
|<syntaxhighlight lang="console">$ sudo apt-get purge emacs</syntaxhighlight>
|All configuration is in configuration.nix
|
|-
|Update the list of packages
|<syntaxhighlight lang="console">$ sudo apt-get update</syntaxhighlight>
|<syntaxhighlight lang="console">$ sudo nix-channel --update</syntaxhighlight>
|<syntaxhighlight lang="console">$nix-channel --update</syntaxhighlight>
|-
|Upgrade packages
|<syntaxhighlight lang="console">$ sudo apt-get upgrade</syntaxhighlight>
|<syntaxhighlight lang="console">$ sudo nixos-rebuild switch</syntaxhighlight>
|<syntaxhighlight lang="console">$ nix-env -u</syntaxhighlight>
|-
|Check for broken dependencies
|<syntaxhighlight lang="console">$ sudo apt-get check</syntaxhighlight>
|<syntaxhighlight lang="console">$ nix-store --verify --check-contents</syntaxhighlight>
|unneeded!
|-
|List package dependencies
|<syntaxhighlight lang="console">$ apt-cache depends emacs</syntaxhighlight>
|Show the direct dependencies:
<syntaxhighlight lang="console">$ nix-store --query --requisites /run/current-system</syntaxhighlight>
or show a nested ASCII tree of dependencies:
<syntaxhighlight lang="console">$ nix-store -q --tree /nix/var/nix/profiles/system</syntaxhighlight>
(/run/current-system and /nix/var/nix/profiles/system are symbolic links that eventually end up at the same place.)
|<syntaxhighlight lang="console">$ nix-store --query --references\
  $(nix-instantiate '<nixpkgs>' -A emacs)</syntaxhighlight>
For installed packages:
<syntaxhighlight lang="console">$ nix-store --query --references $(which emacs)</syntaxhighlight>
|-
|List which packages depend on this one (reverse dependencies)
|<syntaxhighlight lang="console">$ apt-cache rdepends emacs</syntaxhighlight>
|
|For installed packages (only print reverse dependencies *which are already installed*):
<syntaxhighlight lang="console">$ nix-store --query --referrers $(which emacs)</syntaxhighlight>
|-
|Verify all installed packages
|<syntaxhighlight lang="console">$ debsums</syntaxhighlight>
|<syntaxhighlight lang="console">$ sudo nix-store --verify --check-contents</syntaxhighlight>
|<syntaxhighlight lang="console">$ nix-store --verify --check-contents</syntaxhighlight>
|-
|Fix packages with failed checksums
|Reinstall broken packages
|<syntaxhighlight lang="console">$ sudo nix-store --verify --check-contents --repair</syntaxhighlight>
|<syntaxhighlight lang="console">$ nix-store --verify --check-contents --repair</syntaxhighlight>
|-
|Select major version and stable/unstable
|Change sources.list and apt-get dist-upgrade. A an extremely infrequent and destructive operation. The nix variants are safe and easy to use.
|<syntaxhighlight lang="console">$ nix-channel --add\
  https://nixos.org/channels/nixpkgs-unstable <name></syntaxhighlight>
Add the unstable channel. At that address you will find names for other versions and variants. Name can be any string.
<syntaxhighlight lang="console">$ nix-channel --remove <name></syntaxhighlight>
To eliminate a channel.
<syntaxhighlight lang="console">$ nix-channel --list</syntaxhighlight>
To show all installed channel.
|When run by a user channels work locally, when run by root they're used as the system-wide channels.
|-
|Private package repository
|PPA
|Define your package tree as in the general column, and include it in configuration.nix, then list your packages in systemPackages to make them available system wide
|See [https://sandervanderburg.blogspot.de/2014/07/managing-private-nix-packages-outside.html]
|-
|Install a particular version of a package
|<syntaxhighlight lang="console">$ apt-get install package=version</syntaxhighlight>
|Although Nix on its own doesn't understand the concept of package versioning, you can install and play with older (or newer!) software via https://nixos.wiki/wiki/FAQ/Pinning_Nixpkgs with https://lazamar.co.uk/nix-versions.
 
 
For instance, to launch an older version of Vim you could use:
<syntaxhighlight lang="console">
$ nix-shell \
    -p vim \
    -I nixpkgs=\https://github.com/NixOS/nixpkgs/archive/4bba6650a6a5a2009e25bdeed8c1e871601a9bfb.tar.gz
</syntaxhighlight>
|
|-
| colspan="5" style="text-align:center"| '''Package configuration'''
|-
|Configure a package
|<syntaxhighlight lang="console">$ sudo dpkg-reconfigure <package></syntaxhighlight>
|Edit /etc/nixos/configuration.nix
|Edit ~/.nixpkgs/config.nix; '''TODO''': More details about how to edit
|-
|Global package configuration
|Modify configuration file in /etc/
|Edit /etc/nixos/configuration.nix
|
|-
|Find packages
|<syntaxhighlight lang="console">$ apt-cache search emacs</syntaxhighlight>
|<syntaxhighlight lang="console">$ nix-env -qaP '.*emacs.*'</syntaxhighlight> or <syntaxhighlight lang="console">$ nix search emacs</syntaxhighlight>
|<syntaxhighlight lang="console">$ nix-env -qaP '.*emacs.*'</syntaxhighlight> or <syntaxhighlight lang="console">$ nix search emacs</syntaxhighlight>
|-
|Show package description
|<syntaxhighlight lang="console">$ apt-cache show emacs</syntaxhighlight>
|<syntaxhighlight lang="console">$ nix-env -qa --description '.*emacs.*'</syntaxhighlight>
|<syntaxhighlight lang="console">$ nix-env -qa --description '.*emacs.*'</syntaxhighlight>
|-
|Show files installed by package
|<syntaxhighlight lang="console">$ dpkg -L emacs</syntaxhighlight>
|<syntaxhighlight lang="console">$ readlink -f $(which emacs)
/nix/store/ji06y4haijly0i0knmr986l2dajffv1p-emacs-24.4/bin/emacs-24.4</syntaxhighlight>
then
<syntaxhighlight lang="console">$du -a /nix/store/ji06y4haijly0i0knmr986l2dajffv1p-emacs-24.4</syntaxhighlight>
|<syntaxhighlight lang="console"></syntaxhighlight>
|-
|Show package for file
|<syntaxhighlight lang="console">$ dpkg -S /usr/bin/emacs</syntaxhighlight>
|follow the symlink or <syntaxhighlight lang="console">nix-locate /bin/emacs</syntaxhighlight> (requires <syntaxhighlight lang="console">nix-index</syntaxhighlight> package)
|(same)
|-
| colspan="5" style="text-align:center"| '''Services'''
|-
|Start a service
|<syntaxhighlight lang="console">$ sudo systemctl start apache</syntaxhighlight>
|<syntaxhighlight lang="console">$ sudo systemctl start apache</syntaxhighlight>
|
|-
|Stop a service
|<syntaxhighlight lang="console">$ sudo systemctl stop apache</syntaxhighlight>
|<syntaxhighlight lang="console">$ sudo systemctl stop apache</syntaxhighlight>
|
|-
| Enable a service
| <syntaxhighlight lang="console">$ sudo systemctl enable apache</syntaxhighlight>
| In /etc/nixos/configuration.nix, add <syntaxhighlight lang="console">services.tor.enable = true;</syntaxhighlight>, then run <syntaxhighlight lang="console">$ sudo nixos-rebuild switch</syntaxhighlight>
|-
| Disable a service
| <syntaxhighlight lang="console">$ sudo systemctl disable apache</syntaxhighlight>
| In /etc/nixos/configuration.nix, add <syntaxhighlight lang="console">services.tor.enable = false;</syntaxhighlight>, then run <syntaxhighlight lang="console">$ sudo nixos-rebuild switch</syntaxhighlight>
|-
|Where your log files live
|/var/log/
|System-wide packages /var/log/
|User packages ~/.nix-profile/var/log/
|-
|Adding a user
|<syntaxhighlight lang="console">$ sudo adduser alice</syntaxhighlight>
|Add <syntaxhighlight lang="nix">users.users.alice =
{ isNormalUser = true;
  home = "/home/alice";
  description = "Alice Foobar";
  extraGroups = [ "wheel" "networkmanager" ];
  openssh.authorizedKeys.keys =
      [ "ssh-dss AAAAB3Nza... alice@foobar" ];
};</syntaxhighlight> to /etc/nixos/configuration.nix and then call <syntaxhighlight lang="nix">nixos-rebuild switch</syntaxhighlight>
|
|-
| colspan="5" style="text-align:center"| '''Misc tasks'''
|-
|List binaries
|<syntaxhighlight lang="console">$ ls /usr/bin/</syntaxhighlight>
|<syntaxhighlight lang="console">$ ls /run/current-system/sw/bin &&\
ls /nix/var/nix/profiles/default/bin/</syntaxhighlight>
|<syntaxhighlight lang="console">$ ls ~/.nix-profile/bin</syntaxhighlight>
|-
|Get the current version number
|<syntaxhighlight lang="console">$ cat /etc/debian_version</syntaxhighlight>
|<syntaxhighlight lang="console">$ nixos-version</syntaxhighlight>
|
|-
|Get sources for a package
|<syntaxhighlight lang="bash">$ sudo apt-get source emacs</syntaxhighlight>
|
|In Debian, apt-get source gets both the patched upstream source and the recipe for the package. Those need two steps in Nix.
 
To find the package's attribute path: <syntaxhighlight lang="console">$ nix-env -qaP emacs</syntaxhighlight> or <syntaxhighlight lang="console">$ nox emacs</syntaxhighlight>
To download the source as specified by the package recipe: <syntaxhighlight lang="bash">nix-build '<nixpkgs>' -A emacs.src</syntaxhighlight>
The patched source is usually not a derivation itself, but can be produced for most packages with the following command: <syntaxhighlight lang="bash">nix-shell '<nixpkgs>' -A emacs\
--command 'unpackPhase; cd $sourceRoot; patchPhase'</syntaxhighlight>
|-
|Compile & install a package from source
|
|
|<syntaxhighlight lang="console">git clone foobar
cat >default.nix <<EOF
with import <nixpkgs> { };
lib.overrideDerivation foobar (oldAttrs : {
src = ./foobar;
})
EOF
nix-build</syntaxhighlight>
|-
|Install a binary package
|
|
|e.g. via [https://github.com/Mic92/nix-ld#nix-ld nix-ld]
|-
|Install a .deb
|<syntaxhighlight lang="console">$ sudo dpkg -i package.deb</syntaxhighlight>
|
|Install dpkg with Nix, then <syntaxhighlight lang="bash">dpkg -i package.deb</syntaxhighlight> While this is technically possible it will in all likelihood not work.
|}
 
</div>
 
=== Comparison of secret managing schemes ===
 
Manage [https://nixos.wiki/wiki/Comparison_of_secret_managing_schemes secrets] in your (system) configuration. That page tries to give an overview of different schemes that can be used and outlines the aims, requirements and implications of each.


== Working with the nix store ==
== Working with the nix store ==
Line 307: Line 8:
<syntaxHighlight lang="console">
<syntaxHighlight lang="console">
$ nix repl
$ nix repl
nix-repl> :l <nixpkgs>
nix-repl> :l <nixpkgs>  
Added 7486 variables.
Added 7486 variables.
nix-repl> "${xorg.libXtst}"
nix-repl> "${xorg.libXtst}"
"/nix/store/nlpnx21yjdjx2ii7ln4kcmbm0x1vy7w9-libXtst-1.2.3"
"/nix/store/nlpnx21yjdjx2ii7ln4kcmbm0x1vy7w9-libXtst-1.2.3"
nix-repl> :lf ./configuration.nix # as flakes way for a local file
# load nixos configuration from a nix file
$ nix repl --file '<nixpkgs/nixos>' -I nixos-config=./configuration.nix


$ nix-build '<nixpkgs>' --no-build-output -A xorg.libXtst
$ nix-build '<nixpkgs>' --no-build-output -A xorg.libXtst
Line 316: Line 23:
</syntaxHighlight>
</syntaxHighlight>


==== Adding files to the store ====
==== Get store path for a package from the Flake input ====
When packages are managed using [[Flakes]], store paths to them can be retrieved using <code>nix eval --inputs-from</code>, like this:<syntaxhighlight lang="shell">
$ nix eval --inputs-from "$flake_path" --raw "$input#$package"
</syntaxhighlight>For instance, when packages are managed using [[Home Manager]] using standard configuration, store path to the [[Git]] package can be retrieved using this command:<syntaxhighlight lang="shell">
$ nix eval --inputs-from ~/.config/home-manager --raw nixpkgs#git
</syntaxhighlight>


=== Add files to the store ===
It is sometimes necessary to add files to the store manually.
It is sometimes necessary to add files to the store manually.
This is particularly the case with packages that cannot be downloaded automatically,
This is particularly the case with packages that cannot be downloaded automatically,
Line 327: Line 40:
</syntaxHighlight>
</syntaxHighlight>


Unfortunately, `nix-store` will try to load the entire file into memory,
Unfortunately, <code>nix-store</code> will try to load the entire file into memory,
which will fail if the file size exceeds available memory.
which will fail if the file size exceeds available memory.
If we have root access, we can copy the file to the store ourselves:
If we have root access, we can copy the file to the store ourselves:
Line 348: Line 61:
</syntaxHighlight>
</syntaxHighlight>


=== Build nixos from nixpkgs repo ===
=== Build NixOS from nixpkgs repo ===


The following snippet will build the system from a git checkout:
The following snippet will build the system from a git checkout:
Line 356: Line 69:
</syntaxHighlight>
</syntaxHighlight>


This method can be used when testing nixos services for a pull request to nixpkgs.
This method can be used when testing NixOS services for a pull request to nixpkgs.


Building nixos from a git is an alternative to using nix channels and set up permanent following this [https://web.archive.org/web/20160327190212/http://anderspapitto.com/posts/2015-11-01-nixos-with-local-nixpkgs-checkout.html blog article].
Building NixOS from a git is an alternative to using nix channels and set up permanent following this [https://web.archive.org/web/20160327190212/http://anderspapitto.com/posts/2015-11-01-nixos-with-local-nixpkgs-checkout.html blog article].
It has a couple of advantages over nixpkgs as it allows back-porting of packages/changes to stable versions
It has a couple of advantages over nixpkgs as it allows back-porting of packages/changes to stable versions
as well as applying customization.
as well as applying customization.
Line 370: Line 83:
=== Evaluate a NixOS configuration without building ===
=== Evaluate a NixOS configuration without building ===


If you only want to evaluate `configuration.nix` without building (e.g. to syntax-check or see if you are using module options correctly), you can use:
If you only want to evaluate <code>configuration.nix</code> without building (e.g. to syntax-check or see if you are using module options correctly), you can use:


<syntaxHighlight lang="console">
<syntaxHighlight lang="console">
Line 376: Line 89:
</syntaxHighlight>
</syntaxHighlight>


This creates the `.drv` file that `nixos-rebuild build` would build.
This creates the <code>.drv</code> file that <code>nixos-rebuild build</code> would build.
 
=== Explore a NixOS configuration in the REPL ===
If you want to see what ''value'' a NixOS option takes without building, as opposed to merely checking that all options work, you can run:
<syntaxhighlight lang="console">
$ nix repl --file '<nixpkgs/nixos>'
Welcome to Nix 2.18.2. Type :? for help.
 
Loading installable ''...
Added 6 variables.
nix-repl> config.environment.shells  # for example
[ "/run/current-system/sw/bin/zsh" ... ]
 
# Equivalently, if starting from an existing REPL:
nix-repl> :l <nixpkgs/nixos>
Added 6 variables.
 
nix-repl> config.environment.shells
</syntaxhighlight>
 
This can be helpful if your configuration is spread across multiple modules, or if you import modules from external sources, or if NixOS has defaults and you want to know whether a default is being used or extended in your configuration, or a variety of other cases in which you might want the computer to tell you what the end result of all your Nixing is going to be before you switch to it.
 
You can do this with configuration files other than the one installed in <code>/etc/nixos</code>, too:
 
<pre>
nix-repl> :a import <nixpkgs/nixos> { configuration = /path/to/config.nix; }
</pre>


=== Manually switching a NixOS system to a certain version of system closure ===
=== Manually switching a NixOS system to a certain version of system closure ===
Line 429: Line 168:
where <code>-I nixpkgs=/path/to/nixpkgs</code> is optionally depending whether the vm should be build from git checkout or a channel.
where <code>-I nixpkgs=/path/to/nixpkgs</code> is optionally depending whether the vm should be build from git checkout or a channel.


On non-nixos (linux) systems the following command can be used instead:
On non-NixOS (linux) systems the following command can be used instead:


<syntaxHighlight lang="console">
<syntaxHighlight lang="console">
Line 449: Line 188:
</syntaxHighlight>
</syntaxHighlight>


Don't forget that by default nixos comes with a firewall enabled:
Don't forget that by default NixOS comes with a firewall enabled:


<syntaxHighlight lang="nix">
<syntaxHighlight lang="nix">
Line 459: Line 198:
=== Reuse a package as a build environment ===
=== Reuse a package as a build environment ===
As packages already contains all build dependencies, they can be reused to a build environment quickly.
As packages already contains all build dependencies, they can be reused to a build environment quickly.
In the following a setup for the cmake-based project [bcc](https://github.com/iovisor/bcc) is shown.
In the following a setup for the cmake-based project [https://github.com/iovisor/bcc bcc] is shown.
After obtaining the source:
After obtaining the source:


Line 477: Line 216:
</syntaxHighlight>
</syntaxHighlight>


To initiate the build environment run `nix-shell` in the project root directory
To initiate the build environment run <code>nix-shell</code> in the project root directory


<syntaxHighlight lang="console">
<syntaxHighlight lang="console">
Line 497: Line 236:
=== Evaluate packages for a different platform ===
=== Evaluate packages for a different platform ===


Sometimes you want to check whether a change to a package (such as adding a new dependency) would evaluate even on a different type of system. For example, you may want to check on `x68_64-linux` whether a package evaluates for `x68_64-darwin` or `aarch64-linux`.
Sometimes you want to check whether a change to a package (such as adding a new dependency) would evaluate even on a different type of system. For example, you may want to check on <code>x86_64-linux</code> whether a package evaluates for <code>x86_64-darwin</code> or <code>aarch64-linux</code>.


Use the `system` argument:
Use the <code>system</code> argument:


<syntaxHighlight lang="console">
<syntaxHighlight lang="console">
Line 529: Line 268:
</syntaxHighlight>
</syntaxHighlight>


and the following in `configuration.nix`:
and the following in <code>configuration.nix</code>:


<syntaxHighlight lang="nix">
<syntaxHighlight lang="nix">
Line 536: Line 275:
   allowUnfree = true;
   allowUnfree = true;


# Create an alias for the unstable channel
  # Create an alias for the unstable channel
 
  packageOverrides = pkgs: {
packageOverrides = pkgs: {
    unstable = import <nixos-unstable> { # pass the nixpkgs config to the unstable alias # to ensure `allowUnfree = true;` is propagated:
unstable = import <nixos-unstable> { # pass the nixpkgs config to the unstable alias # to ensure `allowUnfree = true;` is propagated:
      config = config.nixpkgs.config;
config = config.nixpkgs.config;
    };
};
  };
};
};
};
</syntaxHighlight>
</syntaxHighlight>
Line 624: Line 362:
== See also ==
== See also ==


- [[Garbage Collection]]
* [[Garbage Collection]]
- [[NFS#Nix_store_on_NFS|Nix store on NFS]]
* [[NFS#Nix_store_on_NFS|Nix store on NFS]]


[[Category:Cookbook]]
[[Category:Cookbook]]
[[Category:Software]]
[[Category:Software]]
6

edits