diff options
Diffstat (limited to 'nixos/modules')
23 files changed, 1371 insertions, 544 deletions
diff --git a/nixos/modules/hardware/brillo.nix b/nixos/modules/hardware/brillo.nix new file mode 100644 index 000000000000..e970c9480998 --- /dev/null +++ b/nixos/modules/hardware/brillo.nix @@ -0,0 +1,22 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.hardware.brillo; +in +{ + options = { + hardware.brillo = { + enable = mkEnableOption '' + Enable brillo in userspace. + This will allow brightness control from users in the video group. + ''; + }; + }; + + + config = mkIf cfg.enable { + services.udev.packages = [ pkgs.brillo ]; + environment.systemPackages = [ pkgs.brillo ]; + }; +} diff --git a/nixos/modules/installer/tools/nixos-option.sh b/nixos/modules/installer/tools/nixos-option.sh deleted file mode 100644 index 4560e9c7403a..000000000000 --- a/nixos/modules/installer/tools/nixos-option.sh +++ /dev/null @@ -1,327 +0,0 @@ -#! @shell@ -e - -# FIXME: rewrite this in a more suitable language. - -usage () { - exec man nixos-option - exit 1 -} - -##################### -# Process Arguments # -##################### - -xml=false -verbose=false -nixPath="" - -option="" -exit_code=0 - -argfun="" -for arg; do - if test -z "$argfun"; then - case $arg in - -*) - sarg="$arg" - longarg="" - while test "$sarg" != "-"; do - case $sarg in - --*) longarg=$arg; sarg="--";; - -I) argfun="include_nixpath";; - -*) usage;; - esac - # remove the first letter option - sarg="-${sarg#??}" - done - ;; - *) longarg=$arg;; - esac - for larg in $longarg; do - case $larg in - --xml) xml=true;; - --verbose) verbose=true;; - --help) usage;; - -*) usage;; - *) if test -z "$option"; then - option="$larg" - else - usage - fi;; - esac - done - else - case $argfun in - set_*) - var=$(echo $argfun | sed 's,^set_,,') - eval $var=$arg - ;; - include_nixpath) - nixPath="-I $arg $nixPath" - ;; - esac - argfun="" - fi -done - -if $verbose; then - set -x -else - set +x -fi - -############################# -# Process the configuration # -############################# - -evalNix(){ - # disable `-e` flag, it's possible that the evaluation of `nix-instantiate` fails (e.g. due to broken pkgs) - set +e - result=$(nix-instantiate ${nixPath:+$nixPath} - --eval-only "$@" 2>&1) - exit_code=$? - set -e - - if test $exit_code -eq 0; then - sed '/^warning: Nix search path/d' <<EOF -$result -EOF - return 0; - else - sed -n ' - /^error/ { s/, at (string):[0-9]*:[0-9]*//; p; }; - /^warning: Nix search path/ { p; }; -' >&2 <<EOF -$result -EOF - exit_code=1 - fi -} - -header="let - nixos = import <nixpkgs/nixos> {}; - nixpkgs = import <nixpkgs> {}; -in with nixpkgs.lib; -" - -# This function is used for converting the option definition path given by -# the user into accessors for reaching the definition and the declaration -# corresponding to this option. -generateAccessors(){ - if result=$(evalNix --strict --show-trace <<EOF -$header - -let - path = "${option:+$option}"; - pathList = splitString "." path; - - walkOptions = attrsNames: result: - if attrsNames == [] then - result - else - let name = head attrsNames; rest = tail attrsNames; in - if isOption result.options then - walkOptions rest { - options = result.options.type.getSubOptions ""; - opt = ''(\${result.opt}.type.getSubOptions "")''; - cfg = ''\${result.cfg}."\${name}"''; - } - else - walkOptions rest { - options = result.options.\${name}; - opt = ''\${result.opt}."\${name}"''; - cfg = ''\${result.cfg}."\${name}"''; - } - ; - - walkResult = (if path == "" then x: x else walkOptions pathList) { - options = nixos.options; - opt = ''nixos.options''; - cfg = ''nixos.config''; - }; - -in - ''let option = \${walkResult.opt}; config = \${walkResult.cfg}; in'' -EOF -) - then - echo $result - else - # In case of error we want to ignore the error message roduced by the - # script above, as it is iterating over each attribute, which does not - # produce a nice error message. The following code is a fallback - # solution which is cause a nicer error message in the next - # evaluation. - echo "\"let option = nixos.options${option:+.$option}; config = nixos.config${option:+.$option}; in\"" - fi -} - -header="$header -$(eval echo $(generateAccessors)) -" - -evalAttr(){ - local prefix="$1" - local strict="$2" - local suffix="$3" - - # If strict is set, then set it to "true". - test -n "$strict" && strict=true - - evalNix ${strict:+--strict} <<EOF -$header - -let - value = $prefix${suffix:+.$suffix}; - strict = ${strict:-false}; - cleanOutput = x: with nixpkgs.lib; - if isDerivation x then x.outPath - else if isFunction x then "<CODE>" - else if strict then - if isAttrs x then mapAttrs (n: cleanOutput) x - else if isList x then map cleanOutput x - else x - else x; -in - cleanOutput value -EOF -} - -evalOpt(){ - evalAttr "option" "" "$@" -} - -evalCfg(){ - local strict="$1" - evalAttr "config" "$strict" -} - -findSources(){ - local suffix=$1 - evalNix --strict <<EOF -$header - -option.$suffix -EOF -} - -# Given a result from nix-instantiate, recover the list of attributes it -# contains. -attrNames() { - local attributeset=$1 - # sed is used to replace un-printable subset by 0s, and to remove most of - # the inner-attribute set, which reduce the likelyhood to encounter badly - # pre-processed input. - echo "builtins.attrNames $attributeset" | \ - sed 's,<[A-Z]*>,0,g; :inner; s/{[^\{\}]*};/0;/g; t inner;' | \ - evalNix --strict -} - -# map a simple list which contains strings or paths. -nixMap() { - local fun="$1" - local list="$2" - local elem - for elem in $list; do - test $elem = '[' -o $elem = ']' && continue; - $fun $elem - done -} - -# This duplicates the work made below, but it is useful for processing -# the output of nixos-option with other tools such as nixos-gui. -if $xml; then - evalNix --xml --no-location <<EOF -$header - -let - sources = builtins.map (f: f.source); - opt = option; - cfg = config; -in - -with nixpkgs.lib; - -let - optStrict = v: - let - traverse = x : - if isAttrs x then - if x ? outPath then true - else all id (mapAttrsFlatten (n: traverseNoAttrs) x) - else traverseNoAttrs x; - traverseNoAttrs = x: - # do not continue in attribute sets - if isAttrs x then true - else if isList x then all id (map traverse x) - else true; - in assert traverse v; v; -in - -if isOption opt then - optStrict ({} - // optionalAttrs (opt ? default) { inherit (opt) default; } - // optionalAttrs (opt ? example) { inherit (opt) example; } - // optionalAttrs (opt ? description) { inherit (opt) description; } - // optionalAttrs (opt ? type) { typename = opt.type.description; } - // optionalAttrs (opt ? options) { inherit (opt) options; } - // { - # to disambiguate the xml output. - _isOption = true; - declarations = sources opt.declarations; - definitions = sources opt.definitions; - value = cfg; - }) -else - opt -EOF - exit $? -fi - -if test "$(evalOpt "_type" 2> /dev/null)" = '"option"'; then - echo "Value:" - evalCfg 1 - - echo - - echo "Default:" - if default=$(evalOpt "default" - 2> /dev/null); then - echo "$default" - else - echo "<None>" - fi - echo - if example=$(evalOpt "example" - 2> /dev/null); then - echo "Example:" - echo "$example" - echo - fi - echo "Description:" - echo - echo $(evalOpt "description") - - echo $desc; - - printPath () { echo " $1"; } - - echo "Declared by:" - nixMap printPath "$(findSources "declarations")" - echo - echo "Defined by:" - nixMap printPath "$(findSources "files")" - echo - -else - # echo 1>&2 "Warning: This value is not an option." - - result=$(evalCfg "") - if [ ! -z "$result" ]; then - names=$(attrNames "$result" 2> /dev/null) - echo 1>&2 "This attribute set contains:" - escapeQuotes () { eval echo "$1"; } - nixMap escapeQuotes "$names" - else - echo 1>&2 "An error occurred while looking for attribute names. Are you sure that '$option' exists?" - fi -fi - -exit $exit_code diff --git a/nixos/modules/installer/tools/nixos-option/CMakeLists.txt b/nixos/modules/installer/tools/nixos-option/CMakeLists.txt new file mode 100644 index 000000000000..e5834598c4fd --- /dev/null +++ b/nixos/modules/installer/tools/nixos-option/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required (VERSION 2.6) +project (nixos-option) + +add_executable(nixos-option nixos-option.cc libnix-copy-paste.cc) +target_link_libraries(nixos-option PRIVATE -lnixmain -lnixexpr -lnixstore -lnixutil) +target_compile_features(nixos-option PRIVATE cxx_std_17) + +install (TARGETS nixos-option DESTINATION bin) diff --git a/nixos/modules/installer/tools/nixos-option/default.nix b/nixos/modules/installer/tools/nixos-option/default.nix new file mode 100644 index 000000000000..753fd92c7bbf --- /dev/null +++ b/nixos/modules/installer/tools/nixos-option/default.nix @@ -0,0 +1,11 @@ +{lib, stdenv, boost, cmake, pkgconfig, nix, ... }: +stdenv.mkDerivation rec { + name = "nixos-option"; + src = ./.; + nativeBuildInputs = [ cmake pkgconfig ]; + buildInputs = [ boost nix ]; + meta = { + license = stdenv.lib.licenses.lgpl2Plus; + maintainers = with lib.maintainers; [ chkno ]; + }; +} diff --git a/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc new file mode 100644 index 000000000000..875c07da6399 --- /dev/null +++ b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc @@ -0,0 +1,83 @@ +// These are useful methods inside the nix library that ought to be exported. +// Since they are not, copy/paste them here. +// TODO: Delete these and use the ones in the library as they become available. + +#include <nix/config.h> // for nix/globals.hh's reference to SYSTEM + +#include "libnix-copy-paste.hh" +#include <boost/format/alt_sstream.hpp> // for basic_altstringbuf... +#include <boost/format/alt_sstream_impl.hpp> // for basic_altstringbuf... +#include <boost/format/format_class.hpp> // for basic_format +#include <boost/format/format_fwd.hpp> // for format +#include <boost/format/format_implementation.hpp> // for basic_format::basi... +#include <boost/optional/optional.hpp> // for get_pointer +#include <iostream> // for operator<<, basic_... +#include <nix/types.hh> // for Strings, Error +#include <string> // for string, basic_string + +using boost::format; +using nix::Error; +using nix::Strings; +using std::string; + +// From nix/src/libexpr/attr-path.cc +Strings parseAttrPath(const string & s) +{ + Strings res; + string cur; + string::const_iterator i = s.begin(); + while (i != s.end()) { + if (*i == '.') { + res.push_back(cur); + cur.clear(); + } else if (*i == '"') { + ++i; + while (1) { + if (i == s.end()) + throw Error(format("missing closing quote in selection path '%1%'") % s); + if (*i == '"') + break; + cur.push_back(*i++); + } + } else + cur.push_back(*i); + ++i; + } + if (!cur.empty()) + res.push_back(cur); + return res; +} + +// From nix/src/nix/repl.cc +bool isVarName(const string & s) +{ + if (s.size() == 0) + return false; + char c = s[0]; + if ((c >= '0' && c <= '9') || c == '-' || c == '\'') + return false; + for (auto & i : s) + if (!((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || i == '_' || i == '-' || + i == '\'')) + return false; + return true; +} + +// From nix/src/nix/repl.cc +std::ostream & printStringValue(std::ostream & str, const char * string) +{ + str << "\""; + for (const char * i = string; *i; i++) + if (*i == '\"' || *i == '\\') + str << "\\" << *i; + else if (*i == '\n') + str << "\\n"; + else if (*i == '\r') + str << "\\r"; + else if (*i == '\t') + str << "\\t"; + else + str << *i; + str << "\""; + return str; +} diff --git a/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.hh b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.hh new file mode 100644 index 000000000000..2274e9a0f853 --- /dev/null +++ b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.hh @@ -0,0 +1,9 @@ +#pragma once + +#include <iostream> +#include <nix/types.hh> +#include <string> + +nix::Strings parseAttrPath(const std::string & s); +bool isVarName(const std::string & s); +std::ostream & printStringValue(std::ostream & str, const char * string); diff --git a/nixos/modules/installer/tools/nixos-option/nixos-option.cc b/nixos/modules/installer/tools/nixos-option/nixos-option.cc new file mode 100644 index 000000000000..9b92dc829cd1 --- /dev/null +++ b/nixos/modules/installer/tools/nixos-option/nixos-option.cc @@ -0,0 +1,618 @@ +#include <nix/config.h> // for nix/globals.hh's reference to SYSTEM + +#include <exception> // for exception_ptr, current_exception +#include <functional> // for function +#include <iostream> // for operator<<, basic_ostream, ostrin... +#include <iterator> // for next +#include <list> // for _List_iterator +#include <memory> // for allocator, unique_ptr, make_unique +#include <new> // for operator new +#include <nix/args.hh> // for argvToStrings, UsageError +#include <nix/attr-path.hh> // for findAlongAttrPath +#include <nix/attr-set.hh> // for Attr, Bindings, Bindings::iterator +#include <nix/common-eval-args.hh> // for MixEvalArgs +#include <nix/eval-inline.hh> // for EvalState::forceValue +#include <nix/eval.hh> // for EvalState, initGC, operator<< +#include <nix/globals.hh> // for initPlugins, Settings, settings +#include <nix/nixexpr.hh> // for Pos +#include <nix/shared.hh> // for getArg, LegacyArgs, printVersion +#include <nix/store-api.hh> // for openStore +#include <nix/symbol-table.hh> // for Symbol, SymbolTable +#include <nix/types.hh> // for Error, Path, Strings, PathSet +#include <nix/util.hh> // for absPath, baseNameOf +#include <nix/value.hh> // for Value, Value::(anonymous), Value:... +#include <string> // for string, operator+, operator== +#include <utility> // for move +#include <variant> // for get, holds_alternative, variant +#include <vector> // for vector<>::iterator, vector + +#include "libnix-copy-paste.hh" + +using nix::absPath; +using nix::Bindings; +using nix::Error; +using nix::EvalError; +using nix::EvalState; +using nix::Path; +using nix::PathSet; +using nix::Strings; +using nix::Symbol; +using nix::tAttrs; +using nix::ThrownError; +using nix::tLambda; +using nix::tString; +using nix::UsageError; +using nix::Value; + +// An ostream wrapper to handle nested indentation +class Out +{ + public: + class Separator + {}; + const static Separator sep; + enum LinePolicy + { + ONE_LINE, + MULTI_LINE + }; + explicit Out(std::ostream & ostream) : ostream(ostream), policy(ONE_LINE), writeSinceSep(true) {} + Out(Out & o, const std::string & start, const std::string & end, LinePolicy policy); + Out(Out & o, const std::string & start, const std::string & end, int count) + : Out(o, start, end, count < 2 ? ONE_LINE : MULTI_LINE) + {} + Out(const Out &) = delete; + Out(Out &&) = default; + Out & operator=(const Out &) = delete; + Out & operator=(Out &&) = delete; + ~Out() { ostream << end; } + + private: + std::ostream & ostream; + std::string indentation; + std::string end; + LinePolicy policy; + bool writeSinceSep; + template <typename T> friend Out & operator<<(Out & o, T thing); +}; + +template <typename T> Out & operator<<(Out & o, T thing) +{ + if (!o.writeSinceSep && o.policy == Out::MULTI_LINE) { + o.ostream << o.indentation; + } + o.writeSinceSep = true; + o.ostream << thing; + return o; +} + +template <> Out & operator<<<Out::Separator>(Out & o, Out::Separator /* thing */) +{ + o.ostream << (o.policy == Out::ONE_LINE ? " " : "\n"); + o.writeSinceSep = false; + return o; +} + +Out::Out(Out & o, const std::string & start, const std::string & end, LinePolicy policy) + : ostream(o.ostream), indentation(policy == ONE_LINE ? o.indentation : o.indentation + " "), + end(policy == ONE_LINE ? end : o.indentation + end), policy(policy), writeSinceSep(true) +{ + o << start; + *this << Out::sep; +} + +// Stuff needed for evaluation +struct Context +{ + Context(EvalState & state, Bindings & autoArgs, Value optionsRoot, Value configRoot) + : state(state), autoArgs(autoArgs), optionsRoot(optionsRoot), configRoot(configRoot), + underscoreType(state.symbols.create("_type")) + {} + EvalState & state; + Bindings & autoArgs; + Value optionsRoot; + Value configRoot; + Symbol underscoreType; +}; + +Value evaluateValue(Context & ctx, Value & v) +{ + ctx.state.forceValue(v); + if (ctx.autoArgs.empty()) { + return v; + } + Value called{}; + ctx.state.autoCallFunction(ctx.autoArgs, v, called); + return called; +} + +bool isOption(Context & ctx, const Value & v) +{ + if (v.type != tAttrs) { + return false; + } + const auto & atualType = v.attrs->find(ctx.underscoreType); + if (atualType == v.attrs->end()) { + return false; + } + try { + Value evaluatedType = evaluateValue(ctx, *atualType->value); + if (evaluatedType.type != tString) { + return false; + } + return static_cast<std::string>(evaluatedType.string.s) == "option"; + } catch (Error &) { + return false; + } +} + +// Add quotes to a component of a path. +// These are needed for paths like: +// fileSystems."/".fsType +// systemd.units."dbus.service".text +std::string quoteAttribute(const std::string & attribute) +{ + if (isVarName(attribute)) { + return attribute; + } + std::ostringstream buf; + printStringValue(buf, attribute.c_str()); + return buf.str(); +} + +const std::string appendPath(const std::string & prefix, const std::string & suffix) +{ + if (prefix.empty()) { + return quoteAttribute(suffix); + } + return prefix + "." + quoteAttribute(suffix); +} + +bool forbiddenRecursionName(std::string name) { return (!name.empty() && name[0] == '_') || name == "haskellPackages"; } + +void recurse(const std::function<bool(const std::string & path, std::variant<Value, std::exception_ptr>)> & f, + Context & ctx, Value v, const std::string & path) +{ + std::variant<Value, std::exception_ptr> evaluated; + try { + evaluated = evaluateValue(ctx, v); + } catch (Error &) { + evaluated = std::current_exception(); + } + if (!f(path, evaluated)) { + return; + } + if (std::holds_alternative<std::exception_ptr>(evaluated)) { + return; + } + const Value & evaluated_value = std::get<Value>(evaluated); + if (evaluated_value.type != tAttrs) { + return; + } + for (const auto & child : evaluated_value.attrs->lexicographicOrder()) { + if (forbiddenRecursionName(child->name)) { + continue; + } + recurse(f, ctx, *child->value, appendPath(path, child->name)); + } +} + +// Calls f on all the option names +void mapOptions(const std::function<void(const std::string & path)> & f, Context & ctx, Value root) +{ + recurse( + [f, &ctx](const std::string & path, std::variant<Value, std::exception_ptr> v) { + bool isOpt = std::holds_alternative<std::exception_ptr>(v) || isOption(ctx, std::get<Value>(v)); + if (isOpt) { + f(path); + } + return !isOpt; + }, + ctx, root, ""); +} + +// Calls f on all the config values inside one option. +// Simple options have one config value inside, like sound.enable = true. +// Compound options have multiple config values. For example, the option +// "users.users" has about 1000 config values inside it: +// users.users.avahi.createHome = false; +// users.users.avahi.cryptHomeLuks = null; +// users.users.avahi.description = "`avahi-daemon' privilege separation user"; +// ... +// users.users.avahi.openssh.authorizedKeys.keyFiles = [ ]; +// users.users.avahi.openssh.authorizedKeys.keys = [ ]; +// ... +// users.users.avahi.uid = 10; +// users.users.avahi.useDefaultShell = false; +// users.users.cups.createHome = false; +// ... +// users.users.cups.useDefaultShell = false; +// users.users.gdm = ... ... ... +// users.users.messagebus = ... .. ... +// users.users.nixbld1 = ... .. ... +// ... +// users.users.systemd-timesync = ... .. ... +void mapConfigValuesInOption( + const std::function<void(const std::string & path, std::variant<Value, std::exception_ptr> v)> & f, + const std::string & path, Context & ctx) +{ + Value * option; + try { + option = findAlongAttrPath(ctx.state, path, ctx.autoArgs, ctx.configRoot); + } catch (Error &) { + f(path, std::current_exception()); + return; + } + recurse( + [f, ctx](const std::string & path, std::variant<Value, std::exception_ptr> v) { + bool leaf = std::holds_alternative<std::exception_ptr>(v) || std::get<Value>(v).type != tAttrs || + ctx.state.isDerivation(std::get<Value>(v)); + if (!leaf) { + return true; // Keep digging + } + f(path, v); + return false; + }, + ctx, *option, path); +} + +std::string describeError(const Error & e) { return "«error: " + e.msg() + "»"; } + +void describeDerivation(Context & ctx, Out & out, Value v) +{ + // Copy-pasted from nix/src/nix/repl.cc :( + Bindings::iterator i = v.attrs->find(ctx.state.sDrvPath); + PathSet pathset; + try { + Path drvPath = i != v.attrs->end() ? ctx.state.coerceToPath(*i->pos, *i->value, pathset) : "???"; + out << "«derivation " << drvPath << "»"; + } catch (Error & e) { + out << describeError(e); + } +} + +Value parseAndEval(EvalState & state, const std::string & expression, const std::string & path) +{ + Value v{}; + state.eval(state.parseExprFromString(expression, absPath(path)), v); + return v; +} + +void printValue(Context & ctx, Out & out, std::variant<Value, std::exception_ptr> maybeValue, const std::string & path); + +void printList(Context & ctx, Out & out, Value & v) +{ + Out listOut(out, "[", "]", v.listSize()); + for (unsigned int n = 0; n < v.listSize(); ++n) { + printValue(ctx, listOut, *v.listElems()[n], ""); + listOut << Out::sep; + } +} + +void printAttrs(Context & ctx, Out & out, Value & v, const std::string & path) +{ + Out attrsOut(out, "{", "}", v.attrs->size()); + for (const auto & a : v.attrs->lexicographicOrder()) { + std::string name = a->name; + attrsOut << name << " = "; + printValue(ctx, attrsOut, *a->value, appendPath(path, name)); + attrsOut << ";" << Out::sep; + } +} + +void multiLineStringEscape(Out & out, const std::string & s) +{ + int i; + for (i = 1; i < s.size(); i++) { + if (s[i - 1] == '$' && s[i] == '{') { + out << "''${"; + i++; + } else if (s[i - 1] == '\'' && s[i] == '\'') { + out << "'''"; + i++; + } else { + out << s[i - 1]; + } + } + if (i == s.size()) { + out << s[i - 1]; + } +} + +void printMultiLineString(Out & out, const Value & v) +{ + std::string s = v.string.s; + Out strOut(out, "''", "''", Out::MULTI_LINE); + std::string::size_type begin = 0; + while (begin < s.size()) { + std::string::size_type end = s.find('\n', begin); + if (end == std::string::npos) { + multiLineStringEscape(strOut, s.substr(begin, s.size() - begin)); + break; + } + multiLineStringEscape(strOut, s.substr(begin, end - begin)); + strOut << Out::sep; + begin = end + 1; + } +} + +void printValue(Context & ctx, Out & out, std::variant<Value, std::exception_ptr> maybeValue, const std::string & path) +{ + try { + if (auto ex = std::get_if<std::exception_ptr>(&maybeValue)) { + std::rethrow_exception(*ex); + } + Value v = evaluateValue(ctx, std::get<Value>(maybeValue)); + if (ctx.state.isDerivation(v)) { + describeDerivation(ctx, out, v); + } else if (v.isList()) { + printList(ctx, out, v); + } else if (v.type == tAttrs) { + printAttrs(ctx, out, v, path); + } else if (v.type == tString && std::string(v.string.s).find('\n') != std::string::npos) { + printMultiLineString(out, v); + } else { + ctx.state.forceValueDeep(v); + out << v; + } + } catch (ThrownError & e) { + if (e.msg() == "The option `" + path + "' is used but not defined.") { + // 93% of errors are this, and just letting this message through would be + // misleading. These values may or may not actually be "used" in the + // config. The thing throwing the error message assumes that if anything + // ever looks at this value, it is a "use" of this value. But here in + // nixos-option, we are looking at this value only to print it. + // In order to avoid implying that this undefined value is actually + // referenced, eat the underlying error message and emit "«not defined»". + out << "«not defined»"; + } else { + out << describeError(e); + } + } catch (Error & e) { + out << describeError(e); + } +} + +void printConfigValue(Context & ctx, Out & out, const std::string & path, std::variant<Value, std::exception_ptr> v) +{ + out << path << " = "; + printValue(ctx, out, std::move(v), path); + out << ";\n"; +} + +void printAll(Context & ctx, Out & out) +{ + mapOptions( + [&ctx, &out](const std::string & optionPath) { + mapConfigValuesInOption( + [&ctx, &out](const std::string & configPath, std::variant<Value, std::exception_ptr> v) { + printConfigValue(ctx, out, configPath, v); + }, + optionPath, ctx); + }, + ctx, ctx.optionsRoot); +} + +void printAttr(Context & ctx, Out & out, const std::string & path, Value & root) +{ + try { + printValue(ctx, out, *findAlongAttrPath(ctx.state, path, ctx.autoArgs, root), path); + } catch (Error & e) { + out << describeError(e); + } +} + +bool hasExample(Context & ctx, Value & option) +{ + try { + findAlongAttrPath(ctx.state, "example", ctx.autoArgs, option); + return true; + } catch (Error &) { + return false; + } +} + +void printOption(Context & ctx, Out & out, const std::string & path, Value & option) +{ + out << "Value:\n"; + printAttr(ctx, out, path, ctx.configRoot); + + out << "\n\nDefault:\n"; + printAttr(ctx, out, "default", option); + + out << "\n\nType:\n"; + printAttr(ctx, out, "type.description", option); + + if (hasExample(ctx, option)) { + out << "\n\nExample:\n"; + printAttr(ctx, out, "example", option); + } + + out << "\n\nDescription:\n"; + printAttr(ctx, out, "description", option); + + out << "\n\nDeclared by:\n"; + printAttr(ctx, out, "declarations", option); + + out << "\n\nDefined by:\n"; + printAttr(ctx, out, "files", option); + out << "\n"; +} + +void printListing(Out & out, Value & v) +{ + out << "This attribute set contains:\n"; + for (const auto & a : v.attrs->lexicographicOrder()) { + std::string name = a->name; + if (!name.empty() && name[0] != '_') { + out << name << "\n"; + } + } +} + +bool optionTypeIs(Context & ctx, Value & v, const std::string & soughtType) +{ + try { + const auto & typeLookup = v.attrs->find(ctx.state.sType); + if (typeLookup == v.attrs->end()) { + return false; + } + Value type = evaluateValue(ctx, *typeLookup->value); + if (type.type != tAttrs) { + return false; + } + const auto & nameLookup = type.attrs->find(ctx.state.sName); + if (nameLookup == type.attrs->end()) { + return false; + } + Value name = evaluateValue(ctx, *nameLookup->value); + if (name.type != tString) { + return false; + } + return name.string.s == soughtType; + } catch (Error &) { + return false; + } +} + +bool isAggregateOptionType(Context & ctx, Value & v) +{ + return optionTypeIs(ctx, v, "attrsOf") || optionTypeIs(ctx, v, "listOf") || optionTypeIs(ctx, v, "loaOf"); +} + +MakeError(OptionPathError, EvalError); + +Value getSubOptions(Context & ctx, Value & option) +{ + Value getSubOptions = evaluateValue(ctx, *findAlongAttrPath(ctx.state, "type.getSubOptions", ctx.autoArgs, option)); + if (getSubOptions.type != tLambda) { + throw OptionPathError("Option's type.getSubOptions isn't a function"); + } + Value emptyString{}; + nix::mkString(emptyString, ""); + Value v; + ctx.state.callFunction(getSubOptions, emptyString, v, nix::Pos{}); + return v; +} + +// Carefully walk an option path, looking for sub-options when a path walks past +// an option value. +Value findAlongOptionPath(Context & ctx, const std::string & path) +{ + Strings tokens = parseAttrPath(path); + Value v = ctx.optionsRoot; + for (auto i = tokens.begin(); i != tokens.end(); i++) { + const auto & attr = *i; + try { + bool lastAttribute = std::next(i) == tokens.end(); + v = evaluateValue(ctx, v); + if (attr.empty()) { + throw OptionPathError("empty attribute name"); + } + if (isOption(ctx, v) && optionTypeIs(ctx, v, "submodule")) { + v = getSubOptions(ctx, v); + } + if (isOption(ctx, v) && isAggregateOptionType(ctx, v) && !lastAttribute) { + v = getSubOptions(ctx, v); + // Note that we've consumed attr, but didn't actually use it. This is the path component that's looked + // up in the list or attribute set that doesn't name an option -- the "root" in "users.users.root.name". + } else if (v.type != tAttrs) { + throw OptionPathError("Value is %s while a set was expected", showType(v)); + } else { + const auto & next = v.attrs->find(ctx.state.symbols.create(attr)); + if (next == v.attrs->end()) { + throw OptionPathError("Attribute not found", attr, path); + } + v = *next->value; + } + } catch (OptionPathError & e) { + throw OptionPathError("At '%s' in path '%s': %s", attr, path, e.msg()); + } + } + return v; +} + +void printOne(Context & ctx, Out & out, const std::string & path) +{ + try { + Value option = findAlongOptionPath(ctx, path); + option = evaluateValue(ctx, option); + if (isOption(ctx, option)) { + printOption(ctx, out, path, option); + } else { + printListing(out, option); + } + } catch (Error & e) { + std::cerr << "error: " << e.msg() + << "\nAn error occurred while looking for attribute names. Are " + "you sure that '" + << path << "' exists?\n"; + } +} + +int main(int argc, char ** argv) +{ + bool all = false; + std::string path = "."; + std::string optionsExpr = "(import <nixpkgs/nixos> {}).options"; + std::string configExpr = "(import <nixpkgs/nixos> {}).config"; + std::vector<std::string> args; + + struct MyArgs : nix::LegacyArgs, nix::MixEvalArgs + { + using nix::LegacyArgs::LegacyArgs; + }; + + MyArgs myArgs(nix::baseNameOf(argv[0]), [&](Strings::iterator & arg, const Strings::iterator & end) { + if (*arg == "--help") { + nix::showManPage("nixos-option"); + } else if (*arg == "--version") { + nix::printVersion("nixos-option"); + } else if (*arg == "--all") { + all = true; + } else if (*arg == "--path") { + path = nix::getArg(*arg, arg, end); + } else if (*arg == "--options_expr") { + optionsExpr = nix::getArg(*arg, arg, end); + } else if (*arg == "--config_expr") { + configExpr = nix::getArg(*arg, arg, end); + } else if (!arg->empty() && arg->at(0) == '-') { + return false; + } else { + args.push_back(*arg); + } + return true; + }); + + myArgs.parseCmdline(nix::argvToStrings(argc, argv)); + + nix::initPlugins(); + nix::initGC(); + nix::settings.readOnlyMode = true; + auto store = nix::openStore(); + auto state = std::make_unique<EvalState>(myArgs.searchPath, store); + + Value optionsRoot = parseAndEval(*state, optionsExpr, path); + Value configRoot = parseAndEval(*state, configExpr, path); + + Context ctx{*state, *myArgs.getAutoArgs(*state), optionsRoot, configRoot}; + Out out(std::cout); + + if (all) { + if (!args.empty()) { + throw UsageError("--all cannot be used with arguments"); + } + printAll(ctx, out); + } else { + if (args.empty()) { + printOne(ctx, out, ""); + } + for (const auto & arg : args) { + printOne(ctx, out, arg); + } + } + + ctx.state.printStats(); + + return 0; +} diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix index 052e7fdd4fc1..e4db39b5c810 100644 --- a/nixos/modules/installer/tools/tools.nix +++ b/nixos/modules/installer/tools/tools.nix @@ -41,10 +41,7 @@ let inherit (config.system.nixos-generate-config) configuration; }; - nixos-option = makeProg { - name = "nixos-option"; - src = ./nixos-option.sh; - }; + nixos-option = pkgs.callPackage ./nixos-option { }; nixos-version = makeProg { name = "nixos-version"; diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index df6e4dc1336a..24912c27245c 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -44,6 +44,7 @@ ./hardware/all-firmware.nix ./hardware/bladeRF.nix ./hardware/brightnessctl.nix + ./hardware/brillo.nix ./hardware/ckb-next.nix ./hardware/cpu/amd-microcode.nix ./hardware/cpu/intel-microcode.nix @@ -619,7 +620,6 @@ ./services/networking/iodine.nix ./services/networking/iperf3.nix ./services/networking/ircd-hybrid/default.nix - ./services/networking/jormungandr.nix ./services/networking/iwd.nix ./services/networking/keepalived/default.nix ./services/networking/keybase.nix @@ -812,8 +812,10 @@ ./services/web-apps/nexus.nix ./services/web-apps/pgpkeyserver-lite.nix ./services/web-apps/matomo.nix + ./services/web-apps/moinmoin.nix ./services/web-apps/restya-board.nix ./services/web-apps/tt-rss.nix + ./services/web-apps/trac.nix ./services/web-apps/selfoss.nix ./services/web-apps/shiori.nix ./services/web-apps/virtlyst.nix @@ -864,6 +866,7 @@ ./services/x11/hardware/multitouch.nix ./services/x11/hardware/synaptics.nix ./services/x11/hardware/wacom.nix + ./services/x11/hardware/digimend.nix ./services/x11/hardware/cmt.nix ./services/x11/gdk-pixbuf.nix ./services/x11/redshift.nix diff --git a/nixos/modules/programs/ssh.nix b/nixos/modules/programs/ssh.nix index 733b8f7636fd..703975fd06c9 100644 --- a/nixos/modules/programs/ssh.nix +++ b/nixos/modules/programs/ssh.nix @@ -115,6 +115,16 @@ in ''; }; + agentPKCS11Whitelist = mkOption { + type = types.nullOr types.str; + default = null; + example = "\${pkgs.opensc}/lib/opensc-pkcs11.so"; + description = '' + A pattern-list of acceptable paths for PKCS#11 shared libraries + that may be used with the -s option to ssh-add. + ''; + }; + package = mkOption { type = types.package; default = pkgs.openssh; @@ -241,6 +251,7 @@ in ExecStart = "${cfg.package}/bin/ssh-agent " + optionalString (cfg.agentTimeout != null) ("-t ${cfg.agentTimeout} ") + + optionalString (cfg.agentPKCS11Whitelist != null) ("-P ${cfg.agentPKCS11Whitelist} ") "-a %t/ssh-agent"; StandardOutput = "null"; Type = "forking"; diff --git a/nixos/modules/security/pam_mount.nix b/nixos/modules/security/pam_mount.nix index 8b131c54a2a5..75f58462d13d 100644 --- a/nixos/modules/security/pam_mount.nix +++ b/nixos/modules/security/pam_mount.nix @@ -50,9 +50,6 @@ in <pam_mount> <debug enable="0" /> - ${concatStrings (map userVolumeEntry (attrValues extraUserVolumes))} - ${concatStringsSep "\n" cfg.extraVolumes} - <!-- if activated, requires ofl from hxtools to be present --> <logout wait="0" hup="no" term="no" kill="no" /> <!-- set PATH variable for pam_mount module --> @@ -64,6 +61,9 @@ in <cryptmount>${pkgs.pam_mount}/bin/mount.crypt %(VOLUME) %(MNTPT)</cryptmount> <cryptumount>${pkgs.pam_mount}/bin/umount.crypt %(MNTPT)</cryptumount> <pmvarrun>${pkgs.pam_mount}/bin/pmvarrun -u %(USER) -o %(OPERATION)</pmvarrun> + + ${concatStrings (map userVolumeEntry (attrValues extraUserVolumes))} + ${concatStringsSep "\n" cfg.extraVolumes} </pam_mount> ''; }]; diff --git a/nixos/modules/services/audio/mpd.nix b/nixos/modules/services/audio/mpd.nix index 0df8f9688d25..56dc858b6405 100644 --- a/nixos/modules/services/audio/mpd.nix +++ b/nixos/modules/services/audio/mpd.nix @@ -181,6 +181,7 @@ in { ProtectKernelModules = true; RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK"; RestrictNamespaces = true; + Restart = "always"; }; }; diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix index 0f4eb2ccfcad..50661b873f64 100644 --- a/nixos/modules/services/misc/matrix-synapse.nix +++ b/nixos/modules/services/misc/matrix-synapse.nix @@ -407,6 +407,9 @@ in { "192.168.0.0/16" "100.64.0.0/10" "169.254.0.0/16" + "::1/128" + "fe80::/64" + "fc00::/7" ]; description = '' List of IP address CIDR ranges that the URL preview spider is denied diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix index 7d976db96300..3ffde8e9bce2 100644 --- a/nixos/modules/services/monitoring/netdata.nix +++ b/nixos/modules/services/monitoring/netdata.nix @@ -138,7 +138,7 @@ in { description = "Real time performance monitoring"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; - path = (with pkgs; [ gawk curl ]) ++ lib.optional cfg.python.enable + path = (with pkgs; [ curl gawk which ]) ++ lib.optional cfg.python.enable (pkgs.python3.withPackages cfg.python.extraPackages); serviceConfig = { Environment="PYTHONPATH=${pkgs.netdata}/libexec/netdata/python.d/python_modules"; diff --git a/nixos/modules/services/networking/jormungandr.nix b/nixos/modules/services/networking/jormungandr.nix deleted file mode 100644 index 152cceb4bf91..000000000000 --- a/nixos/modules/services/networking/jormungandr.nix +++ /dev/null @@ -1,102 +0,0 @@ -{ config, lib, pkgs, ... }: - -let - cfg = config.services.jormungandr; - - inherit (lib) mkEnableOption mkIf mkOption; - inherit (lib) optionalString types; - - dataDir = "/var/lib/jormungandr"; - - # Default settings so far, as the service matures we will - # move these out as separate settings - configSettings = { - storage = dataDir; - p2p = { - public_address = "/ip4/127.0.0.1/tcp/8299"; - topics_of_interest = { - messages = "high"; - blocks = "high"; - }; - }; - rest = { - listen = "127.0.0.1:8607"; - }; - }; - - configFile = if cfg.configFile == null then - pkgs.writeText "jormungandr.yaml" (builtins.toJSON configSettings) - else cfg.configFile; - -in { - - options = { - - services.jormungandr = { - enable = mkEnableOption "jormungandr service"; - - configFile = mkOption { - type = types.nullOr types.path; - default = null; - example = "/var/lib/jormungandr/node.yaml"; - description = '' - The path of the jormungandr blockchain configuration file in YAML format. - If no file is specified, a file is generated using the other options. - ''; - }; - - secretFile = mkOption { - type = types.nullOr types.path; - default = null; - example = "/etc/secret/jormungandr.yaml"; - description = '' - The path of the jormungandr blockchain secret node configuration file in - YAML format. Do not store this in nix store! - ''; - }; - - genesisBlockHash = mkOption { - type = types.nullOr types.str; - default = null; - example = "d70495af81ae8600aca3e642b2427327cb6001ec4d7a0037e96a00dabed163f9"; - description = '' - Set the genesis block hash (the hash of the block0) so we can retrieve - the genesis block (and the blockchain configuration) from the existing - storage or from the network. - ''; - }; - - genesisBlockFile = mkOption { - type = types.nullOr types.path; - default = null; - example = "/var/lib/jormungandr/block-0.bin"; - description = '' - The path of the genesis block file if we are hosting it locally. - ''; - }; - - }; - }; - - config = mkIf cfg.enable { - - systemd.services.jormungandr = { - description = "jormungandr server"; - wantedBy = [ "multi-user.target" ]; - after = [ "network-online.target" ]; - environment = { - RUST_BACKTRACE = "full"; - }; - serviceConfig = { - DynamicUser = true; - StateDirectory = baseNameOf dataDir; - ExecStart = '' - ${pkgs.jormungandr}/bin/jormungandr --config ${configFile} \ - ${optionalString (cfg.secretFile != null) " --secret ${cfg.secretFile}"} \ - ${optionalString (cfg.genesisBlockHash != null) " --genesis-block-hash ${cfg.genesisBlockHash}"} \ - ${optionalString (cfg.genesisBlockFile != null) " --genesis-block ${cfg.genesisBlockFile}"} - ''; - }; - }; - }; -} diff --git a/nixos/modules/services/networking/nat.nix b/nixos/modules/services/networking/nat.nix index 89d8590093dd..5681bda51cb4 100644 --- a/nixos/modules/services/networking/nat.nix +++ b/nixos/modules/services/networking/nat.nix @@ -29,7 +29,7 @@ let iptables -w -t nat -N nixos-nat-post # We can't match on incoming interface in POSTROUTING, so - # mark packets coming from the external interfaces. + # mark packets coming from the internal interfaces. ${concatMapStrings (iface: '' iptables -w -t nat -A nixos-nat-pre \ -i '${iface}' -j MARK --set-mark 1 diff --git a/nixos/modules/services/security/vault.nix b/nixos/modules/services/security/vault.nix index d5962ba9af90..b0ab8fadcbec 100644 --- a/nixos/modules/services/security/vault.nix +++ b/nixos/modules/services/security/vault.nix @@ -119,9 +119,8 @@ in }; users.groups.vault.gid = config.ids.gids.vault; - systemd.tmpfiles.rules = optional (cfg.storagePath != null) [ - "d '${cfg.storagePath}' 0700 vault vault - -" - ]; + systemd.tmpfiles.rules = optional (cfg.storagePath != null) + "d '${cfg.storagePath}' 0700 vault vault - -"; systemd.services.vault = { description = "Vault server daemon"; diff --git a/nixos/modules/services/web-apps/moinmoin.nix b/nixos/modules/services/web-apps/moinmoin.nix new file mode 100644 index 000000000000..0fee64be0bb2 --- /dev/null +++ b/nixos/modules/services/web-apps/moinmoin.nix @@ -0,0 +1,303 @@ +{ config, lib, pkgs, ... }: +with lib; + +let + cfg = config.services.moinmoin; + python = pkgs.python27; + pkg = python.pkgs.moinmoin; + dataDir = "/var/lib/moin"; + usingGunicorn = cfg.webServer == "nginx-gunicorn" || cfg.webServer == "gunicorn"; + usingNginx = cfg.webServer == "nginx-gunicorn"; + user = "moin"; + group = "moin"; + + uLit = s: ''u"${s}"''; + indentLines = n: str: concatMapStrings (line: "${fixedWidthString n " " " "}${line}\n") (splitString "\n" str); + + moinCliWrapper = wikiIdent: pkgs.writeShellScriptBin "moin-${wikiIdent}" '' + ${pkgs.su}/bin/su -s ${pkgs.runtimeShell} -c "${pkg}/bin/moin --config-dir=/var/lib/moin/${wikiIdent}/config $*" ${user} + ''; + + wikiConfig = wikiIdent: w: '' + # -*- coding: utf-8 -*- + + from MoinMoin.config import multiconfig, url_prefix_static + + class Config(multiconfig.DefaultConfig): + ${optionalString (w.webLocation != "/") '' + url_prefix_static = '${w.webLocation}' + url_prefix_static + ''} + + sitename = u'${w.siteName}' + page_front_page = u'${w.frontPage}' + + data_dir = '${dataDir}/${wikiIdent}/data' + data_underlay_dir = '${dataDir}/${wikiIdent}/underlay' + + language_default = u'${w.languageDefault}' + ${optionalString (w.superUsers != []) '' + superuser = [${concatMapStringsSep ", " uLit w.superUsers}] + ''} + + ${indentLines 4 w.extraConfig} + ''; + wikiConfigFile = name: wiki: pkgs.writeText "${name}.py" (wikiConfig name wiki); + +in +{ + options.services.moinmoin = with types; { + enable = mkEnableOption "MoinMoin Wiki Engine"; + + webServer = mkOption { + type = enum [ "nginx-gunicorn" "gunicorn" "none" ]; + default = "nginx-gunicorn"; + example = "none"; + description = '' + Which web server to use to serve the wiki. + Use <literal>none</literal> if you want to configure this yourself. + ''; + }; + + gunicorn.workers = mkOption { + type = ints.positive; + default = 3; + example = 10; + description = '' + The number of worker processes for handling requests. + ''; + }; + + wikis = mkOption { + type = attrsOf (submodule ({ name, ... }: { + options = { + siteName = mkOption { + type = str; + default = "Untitled Wiki"; + example = "ExampleWiki"; + description = '' + Short description of your wiki site, displayed below the logo on each page, and + used in RSS documents as the channel title. + ''; + }; + + webHost = mkOption { + type = str; + description = "Host part of the wiki URL. If undefined, the name of the attribute set will be used."; + example = "wiki.example.org"; + }; + + webLocation = mkOption { + type = str; + default = "/"; + example = "/moin"; + description = "Location part of the wiki URL."; + }; + + frontPage = mkOption { + type = str; + default = "LanguageSetup"; + example = "FrontPage"; + description = '' + Front page name. Set this to something like <literal>FrontPage</literal> once languages are + configured. + ''; + }; + + superUsers = mkOption { + type = listOf str; + default = []; + example = [ "elvis" ]; + description = '' + List of trusted user names with wiki system administration super powers. + + Please note that accounts for these users need to be created using the <command>moin</command> command-line utility, e.g.: + <command>moin-<replaceable>WIKINAME</replaceable> account create --name=<replaceable>NAME</replaceable> --email=<replaceable>EMAIL</replaceable> --password=<replaceable>PASSWORD</replaceable></command>. + ''; + }; + + languageDefault = mkOption { + type = str; + default = "en"; + example = "de"; + description = "The ISO-639-1 name of the main wiki language. Languages that MoinMoin does not support are ignored."; + }; + + extraConfig = mkOption { + type = lines; + default = ""; + example = '' + show_hosts = True + search_results_per_page = 100 + acl_rights_default = u"Known:read,write,delete,revert All:read" + logo_string = u"<h2>\U0001f639</h2>" + theme_default = u"modernized" + + user_checkbox_defaults = {'show_page_trail': 0, 'edit_on_doubleclick': 0} + navi_bar = [u'SomePage'] + multiconfig.DefaultConfig.navi_bar + actions_excluded = multiconfig.DefaultConfig.actions_excluded + ['newaccount'] + + mail_smarthost = "mail.example.org" + mail_from = u"Example.Org Wiki <wiki@example.org>" + ''; + description = '' + Additional configuration to be appended verbatim to this wiki's config. + + See <link xlink:href='http://moinmo.in/HelpOnConfiguration' /> for documentation. + ''; + }; + + }; + config = { + webHost = mkDefault name; + }; + })); + example = literalExample '' + { + "mywiki" = { + siteName = "Example Wiki"; + webHost = "wiki.example.org"; + superUsers = [ "admin" ]; + frontPage = "Index"; + extraConfig = "page_category_regex = ur'(?P<all>(Category|Kategorie)(?P<key>(?!Template)\S+))'" + }; + } + ''; + description = '' + Configurations of the individual wikis. Attribute names must be valid Python + identifiers of the form <literal>[A-Za-z_][A-Za-z0-9_]*</literal>. + + For every attribute <replaceable>WIKINAME</replaceable>, a helper script + moin-<replaceable>WIKINAME</replaceable> is created which runs the + <command>moin</command> command under the <literal>moin</literal> user (to avoid + file ownership issues) and with the right configuration directory passed to it. + ''; + }; + }; + + config = mkIf cfg.enable { + assertions = forEach (attrNames cfg.wikis) (wname: + { assertion = builtins.match "[A-Za-z_][A-Za-z0-9_]*" wname != null; + message = "${wname} is not valid Python identifier"; + } + ); + + users.users = { + moin = { + description = "MoinMoin wiki"; + home = dataDir; + group = group; + isSystemUser = true; + }; + }; + + users.groups = { + moin = { + members = mkIf usingNginx [ config.services.nginx.user ]; + }; + }; + + environment.systemPackages = [ pkg ] ++ map moinCliWrapper (attrNames cfg.wikis); + + systemd.services = mkIf usingGunicorn + (flip mapAttrs' cfg.wikis (wikiIdent: wiki: + nameValuePair "moin-${wikiIdent}" + { + description = "MoinMoin wiki ${wikiIdent} - gunicorn process"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + restartIfChanged = true; + restartTriggers = [ (wikiConfigFile wikiIdent wiki) ]; + + environment = let + penv = python.buildEnv.override { + # setuptools: https://github.com/benoitc/gunicorn/issues/1716 + extraLibs = [ python.pkgs.gevent python.pkgs.setuptools pkg ]; + }; + in { + PYTHONPATH = "${dataDir}/${wikiIdent}/config:${penv}/${python.sitePackages}"; + }; + + preStart = '' + umask 0007 + rm -rf ${dataDir}/${wikiIdent}/underlay + cp -r ${pkg}/share/moin/underlay ${dataDir}/${wikiIdent}/ + chmod -R u+w ${dataDir}/${wikiIdent}/underlay + ''; + + serviceConfig = { + User = user; + Group = group; + WorkingDirectory = "${dataDir}/${wikiIdent}"; + ExecStart = ''${python.pkgs.gunicorn}/bin/gunicorn moin_wsgi \ + --name gunicorn-${wikiIdent} \ + --workers ${toString cfg.gunicorn.workers} \ + --worker-class gevent \ + --bind unix:/run/moin/${wikiIdent}/gunicorn.sock + ''; + + Restart = "on-failure"; + RestartSec = "2s"; + StartLimitIntervalSec = "30s"; + + StateDirectory = "moin/${wikiIdent}"; + StateDirectoryMode = "0750"; + RuntimeDirectory = "moin/${wikiIdent}"; + RuntimeDirectoryMode = "0750"; + + NoNewPrivileges = true; + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + PrivateNetwork = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + }; + } + )); + + services.nginx = mkIf usingNginx { + enable = true; + virtualHosts = flip mapAttrs' cfg.wikis (name: w: nameValuePair w.webHost { + forceSSL = mkDefault true; + enableACME = mkDefault true; + locations."${w.webLocation}" = { + extraConfig = '' + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + + proxy_pass http://unix:/run/moin/${name}/gunicorn.sock; + ''; + }; + }); + }; + + systemd.tmpfiles.rules = [ + "d /run/moin 0750 ${user} ${group} - -" + "d ${dataDir} 0550 ${user} ${group} - -" + ] + ++ (concatLists (flip mapAttrsToList cfg.wikis (wikiIdent: wiki: [ + "d ${dataDir}/${wikiIdent} 0750 ${user} ${group} - -" + "d ${dataDir}/${wikiIdent}/config 0550 ${user} ${group} - -" + "L+ ${dataDir}/${wikiIdent}/config/wikiconfig.py - - - - ${wikiConfigFile wikiIdent wiki}" + # needed in order to pass module name to gunicorn + "L+ ${dataDir}/${wikiIdent}/config/moin_wsgi.py - - - - ${pkg}/share/moin/server/moin.wsgi" + # seed data files + "C ${dataDir}/${wikiIdent}/data 0770 ${user} ${group} - ${pkg}/share/moin/data" + # fix nix store permissions + "Z ${dataDir}/${wikiIdent}/data 0770 ${user} ${group} - -" + ]))); + }; + + meta.maintainers = with lib.maintainers; [ b42 ]; +} diff --git a/nixos/modules/services/web-apps/trac.nix b/nixos/modules/services/web-apps/trac.nix new file mode 100644 index 000000000000..207fb857438a --- /dev/null +++ b/nixos/modules/services/web-apps/trac.nix @@ -0,0 +1,79 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.trac; + + inherit (lib) mkEnableOption mkIf mkOption types; + +in { + + options = { + + services.trac = { + enable = mkEnableOption "Trac service"; + + listen = { + ip = mkOption { + type = types.str; + default = "0.0.0.0"; + description = '' + IP address that Trac should listen on. + ''; + }; + + port = mkOption { + type = types.port; + default = 8000; + description = '' + Listen port for Trac. + ''; + }; + }; + + dataDir = mkOption { + default = "/var/lib/trac"; + type = types.path; + description = '' + The directory for storing the Trac data. + ''; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Open ports in the firewall for Trac. + ''; + }; + }; + + }; + + config = mkIf cfg.enable { + + systemd.services.trac = { + description = "Trac server"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + DynamicUser = true; + StateDirectory = baseNameOf cfg.dataDir; + ExecStart = '' + ${pkgs.trac}/bin/tracd -s \ + -b ${toString cfg.listen.ip} \ + -p ${toString cfg.listen.port} \ + ${cfg.dataDir} + ''; + }; + preStart = '' + if [ ! -e ${cfg.dataDir}/VERSION ]; then + ${pkgs.trac}/bin/trac-admin ${cfg.dataDir} initenv Trac "sqlite:db/trac.db" + fi + ''; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.listen.port ]; + }; + + }; +} diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix index 99304d0e48ae..3c5918baa533 100644 --- a/nixos/modules/services/web-servers/apache-httpd/default.nix +++ b/nixos/modules/services/web-servers/apache-httpd/default.nix @@ -6,6 +6,8 @@ let mainCfg = config.services.httpd; + runtimeDir = "/run/httpd"; + httpd = mainCfg.package.out; httpdConf = mainCfg.configFile; @@ -27,41 +29,26 @@ let listenToString = l: "${l.ip}:${toString l.port}"; - extraModules = attrByPath ["extraModules"] [] mainCfg; - extraForeignModules = filter isAttrs extraModules; - extraApacheModules = filter isString extraModules; - allHosts = [mainCfg] ++ mainCfg.virtualHosts; enableSSL = any (vhost: vhost.enableSSL) allHosts; - - # Names of modules from ${httpd}/modules that we want to load. - apacheModules = - [ # HTTP authentication mechanisms: basic and digest. - "auth_basic" "auth_digest" - - # Authentication: is the user who he claims to be? - "authn_file" "authn_dbm" "authn_anon" "authn_core" - - # Authorization: is the user allowed access? - "authz_user" "authz_groupfile" "authz_host" "authz_core" - - # Other modules. - "ext_filter" "include" "log_config" "env" "mime_magic" - "cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif" - "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs" - "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling" - "userdir" "alias" "rewrite" "proxy" "proxy_http" - "unixd" "cache" "cache_disk" "slotmem_shm" "socache_shmcb" + # NOTE: generally speaking order of modules is very important + modules = + [ # required apache modules our httpd service cannot run without + "authn_core" "authz_core" + "log_config" + "mime" "autoindex" "negotiation" "dir" + "alias" "rewrite" + "unixd" "slotmem_shm" "socache_shmcb" "mpm_${mainCfg.multiProcessingModule}" - - # For compatibility with old configurations, the new module mod_access_compat is provided. - "access_compat" ] ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ]) ++ optional enableSSL "ssl" - ++ extraApacheModules; + ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; } + ++ optional mainCfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; } + ++ optional mainCfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; } + ++ mainCfg.extraModules; allDenied = "Require all denied"; @@ -85,20 +72,22 @@ let browserHacks = '' - BrowserMatch "Mozilla/2" nokeepalive - BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0 - BrowserMatch "RealPlayer 4\.0" force-response-1.0 - BrowserMatch "Java/1\.0" force-response-1.0 - BrowserMatch "JDK/1\.0" force-response-1.0 - BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully - BrowserMatch "^WebDrive" redirect-carefully - BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully - BrowserMatch "^gnome-vfs" redirect-carefully + <IfModule mod_setenvif.c> + BrowserMatch "Mozilla/2" nokeepalive + BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0 + BrowserMatch "RealPlayer 4\.0" force-response-1.0 + BrowserMatch "Java/1\.0" force-response-1.0 + BrowserMatch "JDK/1\.0" force-response-1.0 + BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully + BrowserMatch "^WebDrive" redirect-carefully + BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully + BrowserMatch "^gnome-vfs" redirect-carefully + </IfModule> ''; sslConf = '' - SSLSessionCache shmcb:${mainCfg.stateDir}/ssl_scache(512000) + SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000) Mutex posixsem @@ -239,13 +228,13 @@ let ServerRoot ${httpd} - DefaultRuntimeDir ${mainCfg.stateDir}/runtime + DefaultRuntimeDir ${runtimeDir}/runtime - PidFile ${mainCfg.stateDir}/httpd.pid + PidFile ${runtimeDir}/httpd.pid ${optionalString (mainCfg.multiProcessingModule != "prefork") '' # mod_cgid requires this. - ScriptSock ${mainCfg.stateDir}/cgisock + ScriptSock ${runtimeDir}/cgisock ''} <IfModule prefork.c> @@ -264,13 +253,12 @@ let Group ${mainCfg.group} ${let - load = {name, path}: "LoadModule ${name}_module ${path}\n"; - allModules = map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules - ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; } - ++ optional mainCfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; } - ++ optional mainCfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; } - ++ extraForeignModules; - in concatMapStrings load (unique allModules) + mkModule = module: + if isString module then { name = module; path = "${httpd}/modules/mod_${module}.so"; } + else if isAttrs module then { inherit (module) name path; } + else throw "Expecting either a string or attribute set including a name and path."; + in + concatMapStringsSep "\n" (module: "LoadModule ${module.name}_module ${module.path}") (unique (map mkModule modules)) } AddHandler type-map var @@ -337,6 +325,7 @@ in imports = [ (mkRemovedOptionModule [ "services" "httpd" "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.") + (mkRemovedOptionModule [ "services" "httpd" "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.") ]; ###### interface @@ -384,7 +373,12 @@ in extraModules = mkOption { type = types.listOf types.unspecified; default = []; - example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.so"; } ]''; + example = literalExample '' + [ + "proxy_connect" + { name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; } + ] + ''; description = '' Additional Apache modules to be used. These can be specified as a string in the case of modules distributed @@ -431,16 +425,6 @@ in ''; }; - stateDir = mkOption { - type = types.path; - default = "/run/httpd"; - description = '' - Directory for Apache's transient runtime state (such as PID - files). It is created automatically. Note that the default, - <filename>/run/httpd</filename>, is deleted at boot time. - ''; - }; - virtualHosts = mkOption { type = types.listOf (types.submodule ( { options = import ./per-server-options.nix { @@ -595,6 +579,28 @@ in date.timezone = "${config.time.timeZone}" ''; + services.httpd.extraModules = mkBefore [ + # HTTP authentication mechanisms: basic and digest. + "auth_basic" "auth_digest" + + # Authentication: is the user who he claims to be? + "authn_file" "authn_dbm" "authn_anon" + + # Authorization: is the user allowed access? + "authz_user" "authz_groupfile" "authz_host" + + # Other modules. + "ext_filter" "include" "env" "mime_magic" + "cern_meta" "expires" "headers" "usertrack" "setenvif" + "dav" "status" "asis" "info" "dav_fs" + "vhost_alias" "imagemap" "actions" "speling" + "proxy" "proxy_http" + "cache" "cache_disk" + + # For compatibility with old configurations, the new module mod_access_compat is provided. + "access_compat" + ]; + systemd.services.httpd = { description = "Apache HTTPD"; @@ -611,12 +617,6 @@ in preStart = '' - mkdir -m 0750 -p ${mainCfg.stateDir} - [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir} - - mkdir -m 0750 -p "${mainCfg.stateDir}/runtime" - [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime" - mkdir -m 0700 -p ${mainCfg.logDir} # Get rid of old semaphores. These tend to accumulate across @@ -630,10 +630,13 @@ in serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}"; serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop"; serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful"; + serviceConfig.Group = mainCfg.group; serviceConfig.Type = "forking"; - serviceConfig.PIDFile = "${mainCfg.stateDir}/httpd.pid"; + serviceConfig.PIDFile = "${runtimeDir}/httpd.pid"; serviceConfig.Restart = "always"; serviceConfig.RestartSec = "5s"; + serviceConfig.RuntimeDirectory = "httpd httpd/runtime"; + serviceConfig.RuntimeDirectoryMode = "0750"; }; }; diff --git a/nixos/modules/services/x11/hardware/digimend.nix b/nixos/modules/services/x11/hardware/digimend.nix new file mode 100644 index 000000000000..a9f5640905aa --- /dev/null +++ b/nixos/modules/services/x11/hardware/digimend.nix @@ -0,0 +1,43 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.xserver.digimend; + + pkg = config.boot.kernelPackages.digimend; + +in + +{ + + options = { + + services.xserver.digimend = { + + enable = mkOption { + default = false; + description = '' + Whether to enable the digimend drivers for Huion/XP-Pen/etc. tablets. + ''; + }; + + }; + + }; + + + config = mkIf cfg.enable { + + # digimend drivers use xsetwacom and wacom X11 drivers + services.xserver.wacom.enable = true; + + boot.extraModulePackages = [ pkg ]; + + environment.etc."X11/xorg.conf.d/50-digimend.conf".source = + "${pkg}/usr/share/X11/xorg.conf.d/50-digimend.conf"; + + }; + +} diff --git a/nixos/modules/services/x11/hardware/libinput.nix b/nixos/modules/services/x11/hardware/libinput.nix index bd289976532b..4a25232383d3 100644 --- a/nixos/modules/services/x11/hardware/libinput.nix +++ b/nixos/modules/services/x11/hardware/libinput.nix @@ -122,7 +122,7 @@ in { description = '' Specify the scrolling method: <literal>twofinger</literal>, <literal>edge</literal>, - or <literal>none</literal> + <literal>button</literal>, or <literal>none</literal> ''; }; diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix index ed3431554be4..e313d2b411bb 100644 --- a/nixos/modules/virtualisation/qemu-vm.nix +++ b/nixos/modules/virtualisation/qemu-vm.nix @@ -23,24 +23,56 @@ let cfg = config.virtualisation; - qemuGraphics = lib.optionalString (!cfg.graphics) "-nographic"; - consoles = lib.concatMapStringsSep " " (c: "console=${c}") cfg.qemu.consoles; - # XXX: This is very ugly and in the future we really should use attribute - # sets to build ALL of the QEMU flags instead of this mixed mess of Nix - # expressions and shell script stuff. - mkDiskIfaceDriveFlag = idx: driveArgs: let - inherit (cfg.qemu) diskInterface; - # The drive identifier created by incrementing the index by one using the - # shell. - drvId = "drive$((${idx} + 1))"; - # NOTE: DO NOT shell escape, because this may contain shell variables. - commonArgs = "index=${idx},id=${drvId},${driveArgs}"; - isSCSI = diskInterface == "scsi"; - devArgs = "${diskInterface}-hd,drive=${drvId}"; - args = "-drive ${commonArgs},if=none -device lsi53c895a -device ${devArgs}"; - in if isSCSI then args else "-drive ${commonArgs},if=${diskInterface}"; + driveOpts = { ... }: { + + options = { + + file = mkOption { + type = types.str; + description = "The file image used for this drive."; + }; + + driveExtraOpts = mkOption { + type = types.attrsOf types.str; + default = {}; + description = "Extra options passed to drive flag."; + }; + + deviceExtraOpts = mkOption { + type = types.attrsOf types.str; + default = {}; + description = "Extra options passed to device flag."; + }; + + }; + + }; + + driveCmdline = idx: { file, driveExtraOpts, deviceExtraOpts, ... }: + let + drvId = "drive${toString idx}"; + mkKeyValue = generators.mkKeyValueDefault {} "="; + mkOpts = opts: concatStringsSep "," (mapAttrsToList mkKeyValue opts); + driveOpts = mkOpts (driveExtraOpts // { + index = idx; + id = drvId; + "if" = "none"; + inherit file; + }); + deviceOpts = mkOpts (deviceExtraOpts // { + drive = drvId; + }); + device = + if cfg.qemu.diskInterface == "scsi" then + "-device lsi53c895a -device scsi-hd,${deviceOpts}" + else + "-device virtio-blk-pci,${deviceOpts}"; + in + "-drive ${driveOpts} ${device}"; + + drivesCmdLine = drives: concatStringsSep " " (imap1 driveCmdline drives); # Shell script to start the VM. startVM = @@ -77,13 +109,11 @@ let ''} cd $TMPDIR - idx=2 - extraDisks="" + idx=0 ${flip concatMapStrings cfg.emptyDiskImages (size: '' if ! test -e "empty$idx.qcow2"; then ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M" fi - extraDisks="$extraDisks ${mkDiskIfaceDriveFlag "$idx" "file=$(pwd)/empty$idx.qcow2,werror=report"}" idx=$((idx + 1)) '')} @@ -97,21 +127,7 @@ let -virtfs local,path=/nix/store,security_model=none,mount_tag=store \ -virtfs local,path=$TMPDIR/xchg,security_model=none,mount_tag=xchg \ -virtfs local,path=''${SHARED_DIR:-$TMPDIR/xchg},security_model=none,mount_tag=shared \ - ${if cfg.useBootLoader then '' - ${mkDiskIfaceDriveFlag "0" "file=$NIX_DISK_IMAGE,cache=writeback,werror=report"} \ - ${mkDiskIfaceDriveFlag "1" "file=$TMPDIR/disk.img,media=disk"} \ - ${if cfg.useEFIBoot then '' - -pflash $TMPDIR/bios.bin \ - '' else '' - ''} - '' else '' - ${mkDiskIfaceDriveFlag "0" "file=$NIX_DISK_IMAGE,cache=writeback,werror=report"} \ - -kernel ${config.system.build.toplevel}/kernel \ - -initrd ${config.system.build.toplevel}/initrd \ - -append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS" \ - ''} \ - $extraDisks \ - ${qemuGraphics} \ + ${drivesCmdLine config.virtualisation.qemu.drives} \ ${toString config.virtualisation.qemu.options} \ $QEMU_OPTS \ "$@" @@ -367,6 +383,12 @@ in ''; }; + drives = + mkOption { + type = types.listOf (types.submodule driveOpts); + description = "Drives passed to qemu."; + }; + diskInterface = mkOption { default = "virtio"; @@ -476,8 +498,49 @@ in # FIXME: Consolidate this one day. virtualisation.qemu.options = mkMerge [ - (mkIf (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [ "-vga std" "-usb" "-device usb-tablet,bus=usb-bus.0" ]) - (mkIf (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [ "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet" ]) + (mkIf (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [ + "-vga std" "-usb" "-device usb-tablet,bus=usb-bus.0" + ]) + (mkIf (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [ + "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet" + ]) + (mkIf (!cfg.useBootLoader) [ + "-kernel ${config.system.build.toplevel}/kernel" + "-initrd ${config.system.build.toplevel}/initrd" + ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"'' + ]) + (mkIf cfg.useEFIBoot [ + "-pflash $TMPDIR/bios.bin" + ]) + (mkIf (!cfg.graphics) [ + "-nographic" + ]) + ]; + + virtualisation.qemu.drives = mkMerge [ + (mkIf cfg.useBootLoader [ + { + file = "$NIX_DISK_IMAGE"; + driveExtraOpts.cache = "writeback"; + driveExtraOpts.werror = "report"; + } + { + file = "$TMPDIR/disk.img"; + driveExtraOpts.media = "disk"; + deviceExtraOpts.bootindex = "1"; + } + ]) + (mkIf (!cfg.useBootLoader) [ + { + file = "$NIX_DISK_IMAGE"; + driveExtraOpts.cache = "writeback"; + driveExtraOpts.werror = "report"; + } + ]) + (imap0 (idx: _: { + file = "$(pwd)/empty${toString idx}.qcow2"; + driveExtraOpts.werror = "report"; + }) cfg.emptyDiskImages) ]; # Mount the host filesystem via 9P, and bind-mount the Nix store |