diff options
Diffstat (limited to 'nixos')
20 files changed, 374 insertions, 840 deletions
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml index 7d046d39de7..da046e62bfb 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml @@ -20,6 +20,12 @@ PHP now defaults to PHP 8.0, updated from 7.4. </para> </listitem> + <listitem> + <para> + kOps now defaults to 1.21.0, which uses containerd as the + default runtime. + </para> + </listitem> </itemizedlist> </section> <section xml:id="sec-release-21.11-new-services"> @@ -65,6 +71,13 @@ be able to access programmers supported by flashrom. </para> </listitem> + <listitem> + <para> + <link xlink:href="https://vikunja.io">vikunja</link>, a to-do + list app. Available as + <link linkend="opt-services.vikunja.enable">services.vikunja</link>. + </para> + </listitem> </itemizedlist> </section> <section xml:id="sec-release-21.11-incompatibilities"> diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md index 96854ffdd74..5ae9a5ccfd7 100644 --- a/nixos/doc/manual/release-notes/rl-2111.section.md +++ b/nixos/doc/manual/release-notes/rl-2111.section.md @@ -7,6 +7,7 @@ In addition to numerous new and upgraded packages, this release has the followin ## Highlights {#sec-release-21.11-highlights} - PHP now defaults to PHP 8.0, updated from 7.4. +- kOps now defaults to 1.21.0, which uses containerd as the default runtime. ## New Services {#sec-release-21.11-new-services} @@ -20,6 +21,8 @@ In addition to numerous new and upgraded packages, this release has the followin - Users of flashrom should migrate to [programs.flashrom.enable](options.html#opt-programs.flashrom.enable) and add themselves to the `flashrom` group to be able to access programmers supported by flashrom. +- [vikunja](https://vikunja.io), a to-do list app. Available as [services.vikunja](#opt-services.vikunja.enable). + ## Backward Incompatibilities {#sec-release-21.11-incompatibilities} - The `staticjinja` package has been upgraded from 1.0.4 to 3.0.1 diff --git a/nixos/modules/installer/tools/nixos-option/CMakeLists.txt b/nixos/modules/installer/tools/nixos-option/CMakeLists.txt deleted file mode 100644 index e5834598c4f..00000000000 --- a/nixos/modules/installer/tools/nixos-option/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -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 index 72eec3a3836..061460f38a3 100644 --- a/nixos/modules/installer/tools/nixos-option/default.nix +++ b/nixos/modules/installer/tools/nixos-option/default.nix @@ -1,11 +1 @@ -{lib, stdenv, boost, cmake, pkg-config, nix, ... }: -stdenv.mkDerivation rec { - name = "nixos-option"; - src = ./.; - nativeBuildInputs = [ cmake pkg-config ]; - buildInputs = [ boost nix ]; - meta = with lib; { - license = licenses.lgpl2Plus; - maintainers = with maintainers; [ chkno ]; - }; -} +{ pkgs, ... }: pkgs.nixos-option diff --git a/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc deleted file mode 100644 index 875c07da639..00000000000 --- a/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc +++ /dev/null @@ -1,83 +0,0 @@ -// 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 deleted file mode 100644 index 2274e9a0f85..00000000000 --- a/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.hh +++ /dev/null @@ -1,9 +0,0 @@ -#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 deleted file mode 100644 index f779d82edbd..00000000000 --- a/nixos/modules/installer/tools/nixos-option/nixos-option.cc +++ /dev/null @@ -1,643 +0,0 @@ -#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 & actualType = v.attrs->find(ctx.underscoreType); - if (actualType == v.attrs->end()) { - return false; - } - try { - Value evaluatedType = evaluateValue(ctx, *actualType->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)); - } -} - -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"); -} - -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. -struct FindAlongOptionPathRet -{ - Value option; - std::string path; -}; -FindAlongOptionPathRet findAlongOptionPath(Context & ctx, const std::string & path) -{ - Strings tokens = parseAttrPath(path); - Value v = ctx.optionsRoot; - std::string processedPath; - 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)) { - auto subOptions = getSubOptions(ctx, v); - if (lastAttribute && subOptions.attrs->empty()) { - break; - } - v = subOptions; - // 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; - } - processedPath = appendPath(processedPath, attr); - } catch (OptionPathError & e) { - throw OptionPathError("At '%s' in path '%s': %s", attr, path, e.msg()); - } - } - return {v, processedPath}; -} - -// Calls f on all the option names at or below the option described by `path`. -// Note that "the option described by `path`" is not trivial -- if path describes a value inside an aggregate -// option (such as users.users.root), the *option* described by that path is one path component shorter -// (eg: users.users), which results in f being called on sibling-paths (eg: users.users.nixbld1). If f -// doesn't want these, it must do its own filtering. -void mapOptions(const std::function<void(const std::string & path)> & f, Context & ctx, const std::string & path) -{ - auto root = findAlongOptionPath(ctx, path); - 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.option, root.path); -} - -// 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; - if (!forbiddenRecursionName(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"; -} - -// Replace with std::starts_with when C++20 is available -bool starts_with(const std::string & s, const std::string & prefix) -{ - return s.size() >= prefix.size() && - std::equal(s.begin(), std::next(s.begin(), prefix.size()), prefix.begin(), prefix.end()); -} - -void printRecursive(Context & ctx, Out & out, const std::string & path) -{ - mapOptions( - [&ctx, &out, &path](const std::string & optionPath) { - mapConfigValuesInOption( - [&ctx, &out, &path](const std::string & configPath, std::variant<Value, std::exception_ptr> v) { - if (starts_with(configPath, path)) { - printConfigValue(ctx, out, configPath, v); - } - }, - optionPath, ctx); - }, - ctx, path); -} - -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"; - } - } -} - -void printOne(Context & ctx, Out & out, const std::string & path) -{ - try { - auto result = findAlongOptionPath(ctx, path); - Value & option = result.option; - option = evaluateValue(ctx, option); - if (path != result.path) { - out << "Note: showing " << result.path << " instead of " << path << "\n"; - } - if (isOption(ctx, option)) { - printOption(ctx, out, result.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 recursive = 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 == "-r" || *arg == "--recursive") { - recursive = 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); - - auto print = recursive ? printRecursive : printOne; - if (args.empty()) { - print(ctx, out, ""); - } - for (const auto & arg : args) { - print(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 1dc0578daca..f79ed3493df 100644 --- a/nixos/modules/installer/tools/tools.nix +++ b/nixos/modules/installer/tools/tools.nix @@ -42,7 +42,7 @@ let nixos-option = if lib.versionAtLeast (lib.getVersion config.nix.package) "2.4pre" then null - else pkgs.callPackage ./nixos-option { }; + else pkgs.nixos-option; nixos-version = makeProg { name = "nixos-version"; diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 980e7027c98..f510f395161 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -388,6 +388,7 @@ ./services/hardware/bluetooth.nix ./services/hardware/bolt.nix ./services/hardware/brltty.nix + ./services/hardware/ddccontrol.nix ./services/hardware/fancontrol.nix ./services/hardware/freefall.nix ./services/hardware/fwupd.nix @@ -968,6 +969,7 @@ ./services/web-apps/trilium.nix ./services/web-apps/selfoss.nix ./services/web-apps/shiori.nix + ./services/web-apps/vikunja.nix ./services/web-apps/virtlyst.nix ./services/web-apps/wiki-js.nix ./services/web-apps/whitebophir.nix diff --git a/nixos/modules/services/hardware/ddccontrol.nix b/nixos/modules/services/hardware/ddccontrol.nix new file mode 100644 index 00000000000..766bf12ee9f --- /dev/null +++ b/nixos/modules/services/hardware/ddccontrol.nix @@ -0,0 +1,36 @@ +{ config +, lib +, pkgs +, ... +}: + +let + cfg = config.services.ddccontrol; +in + +{ + ###### interface + + options = { + services.ddccontrol = { + enable = lib.mkEnableOption "ddccontrol for controlling displays"; + }; + }; + + ###### implementation + + config = lib.mkIf cfg.enable { + # Give users access to the "gddccontrol" tool + environment.systemPackages = [ + pkgs.ddccontrol + ]; + + services.dbus.packages = [ + pkgs.ddccontrol + ]; + + systemd.packages = [ + pkgs.ddccontrol + ]; + }; +} diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix index 8fe689ef3db..3be247ffb24 100644 --- a/nixos/modules/services/monitoring/prometheus/default.nix +++ b/nixos/modules/services/monitoring/prometheus/default.nix @@ -323,15 +323,13 @@ let HTTP username ''; }; - password = mkOption { - type = types.str; - description = '' - HTTP password - ''; - }; + password = mkOpt types.str "HTTP password"; + password_file = mkOpt types.str "HTTP password file"; }; }) '' - Optional http login credentials for metrics scraping. + Sets the `Authorization` header on every scrape request with the + configured username and password. + password and password_file are mutually exclusive. ''; bearer_token = mkOpt types.str '' diff --git a/nixos/modules/services/networking/bind.nix b/nixos/modules/services/networking/bind.nix index 20eef2c3455..33da4071638 100644 --- a/nixos/modules/services/networking/bind.nix +++ b/nixos/modules/services/networking/bind.nix @@ -6,6 +6,8 @@ let cfg = config.services.bind; + bindPkg = config.services.bind.package; + bindUser = "named"; bindZoneCoerce = list: builtins.listToAttrs (lib.forEach list (zone: { name = zone.name; value = zone; })); @@ -104,6 +106,14 @@ in enable = mkEnableOption "BIND domain name server"; + + package = mkOption { + type = types.package; + default = pkgs.bind; + defaultText = "pkgs.bind"; + description = "The BIND package to use."; + }; + cacheNetworks = mkOption { default = [ "127.0.0.0/24" ]; type = types.listOf types.str; @@ -225,7 +235,7 @@ in preStart = '' mkdir -m 0755 -p /etc/bind if ! [ -f "/etc/bind/rndc.key" ]; then - ${pkgs.bind.out}/sbin/rndc-confgen -c /etc/bind/rndc.key -u ${bindUser} -a -A hmac-sha256 2>/dev/null + ${bindPkg.out}/sbin/rndc-confgen -c /etc/bind/rndc.key -u ${bindUser} -a -A hmac-sha256 2>/dev/null fi ${pkgs.coreutils}/bin/mkdir -p /run/named @@ -233,9 +243,9 @@ in ''; serviceConfig = { - ExecStart = "${pkgs.bind.out}/sbin/named -u ${bindUser} ${optionalString cfg.ipv4Only "-4"} -c ${cfg.configFile} -f"; - ExecReload = "${pkgs.bind.out}/sbin/rndc -k '/etc/bind/rndc.key' reload"; - ExecStop = "${pkgs.bind.out}/sbin/rndc -k '/etc/bind/rndc.key' stop"; + ExecStart = "${bindPkg.out}/sbin/named -u ${bindUser} ${optionalString cfg.ipv4Only "-4"} -c ${cfg.configFile} -f"; + ExecReload = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' reload"; + ExecStop = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' stop"; }; unitConfig.Documentation = "man:named(8)"; diff --git a/nixos/modules/services/networking/smartdns.nix b/nixos/modules/services/networking/smartdns.nix index f1888af7041..f84c727f034 100644 --- a/nixos/modules/services/networking/smartdns.nix +++ b/nixos/modules/services/networking/smartdns.nix @@ -54,6 +54,7 @@ in { systemd.packages = [ pkgs.smartdns ]; systemd.services.smartdns.wantedBy = [ "multi-user.target" ]; + systemd.services.smartdns.restartTriggers = [ confFile ]; environment.etc."smartdns/smartdns.conf".source = confFile; environment.etc."default/smartdns".source = "${pkgs.smartdns}/etc/default/smartdns"; diff --git a/nixos/modules/services/web-apps/vikunja.nix b/nixos/modules/services/web-apps/vikunja.nix new file mode 100644 index 00000000000..b0b6eb6df17 --- /dev/null +++ b/nixos/modules/services/web-apps/vikunja.nix @@ -0,0 +1,145 @@ +{ pkgs, lib, config, ... }: + +with lib; + +let + cfg = config.services.vikunja; + format = pkgs.formats.yaml {}; + configFile = format.generate "config.yaml" cfg.settings; + useMysql = cfg.database.type == "mysql"; + usePostgresql = cfg.database.type == "postgres"; +in { + options.services.vikunja = with lib; { + enable = mkEnableOption "vikunja service"; + package-api = mkOption { + default = pkgs.vikunja-api; + type = types.package; + defaultText = "pkgs.vikunja-api"; + description = "vikunja-api derivation to use."; + }; + package-frontend = mkOption { + default = pkgs.vikunja-frontend; + type = types.package; + defaultText = "pkgs.vikunja-frontend"; + description = "vikunja-frontend derivation to use."; + }; + environmentFiles = mkOption { + type = types.listOf types.path; + default = [ ]; + description = '' + List of environment files set in the vikunja systemd service. + For example passwords should be set in one of these files. + ''; + }; + setupNginx = mkOption { + type = types.bool; + default = config.services.nginx.enable; + defaultText = "config.services.nginx.enable"; + description = '' + Whether to setup NGINX. + Further nginx configuration can be done by changing + <option>services.nginx.virtualHosts.<frontendHostname></option>. + This does not enable TLS or ACME by default. To enable this, set the + <option>services.nginx.virtualHosts.<frontendHostname>.enableACME</option> to + <literal>true</literal> and if appropriate do the same for + <option>services.nginx.virtualHosts.<frontendHostname>.forceSSL</option>. + ''; + }; + frontendScheme = mkOption { + type = types.enum [ "http" "https" ]; + description = '' + Whether the site is available via http or https. + This does not configure https or ACME in nginx! + ''; + }; + frontendHostname = mkOption { + type = types.str; + description = "The Hostname under which the frontend is running."; + }; + + settings = mkOption { + type = format.type; + default = {}; + description = '' + Vikunja configuration. Refer to + <link xlink:href="https://vikunja.io/docs/config-options/"/> + for details on supported values. + ''; + }; + database = { + type = mkOption { + type = types.enum [ "sqlite" "mysql" "postgres" ]; + example = "postgres"; + default = "sqlite"; + description = "Database engine to use."; + }; + host = mkOption { + type = types.str; + default = "localhost"; + description = "Database host address. Can also be a socket."; + }; + user = mkOption { + type = types.str; + default = "vikunja"; + description = "Database user."; + }; + database = mkOption { + type = types.str; + default = "vikunja"; + description = "Database name."; + }; + path = mkOption { + type = types.str; + default = "/var/lib/vikunja/vikunja.db"; + description = "Path to the sqlite3 database file."; + }; + }; + }; + config = lib.mkIf cfg.enable { + services.vikunja.settings = { + database = { + inherit (cfg.database) type host user database path; + }; + service = { + frontendurl = "${cfg.frontendScheme}://${cfg.frontendHostname}/"; + }; + files = { + basepath = "/var/lib/vikunja/files"; + }; + }; + + systemd.services.vikunja-api = { + description = "vikunja-api"; + after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service"; + wantedBy = [ "multi-user.target" ]; + path = [ cfg.package-api ]; + restartTriggers = [ configFile ]; + + serviceConfig = { + Type = "simple"; + DynamicUser = true; + StateDirectory = "vikunja"; + ExecStart = "${cfg.package-api}/bin/vikunja"; + Restart = "always"; + EnvironmentFile = cfg.environmentFiles; + }; + }; + + services.nginx.virtualHosts."${cfg.frontendHostname}" = mkIf cfg.setupNginx { + locations = { + "/" = { + root = cfg.package-frontend; + tryFiles = "try_files $uri $uri/ /"; + }; + "~* ^/(api|dav|\\.well-known)/" = { + proxyPass = "http://localhost:3456"; + extraConfig = '' + client_max_body_size 20M; + ''; + }; + }; + }; + + environment.etc."vikunja/config.yaml".source = configFile; + }; +} diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 6be7b7e6846..abd8ab29cae 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -755,7 +755,7 @@ in default = []; example = [ "d /tmp 1777 root root 10d" ]; description = '' - Rules for creating and cleaning up temporary files + Rules for creation, deletion and cleaning of volatile and temporary files automatically. See <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry> for the exact format. diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 74160673214..2e4913dca47 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -450,6 +450,7 @@ in vaultwarden = handleTest ./vaultwarden.nix {}; vector = handleTest ./vector.nix {}; victoriametrics = handleTest ./victoriametrics.nix {}; + vikunja = handleTest ./vikunja.nix {}; virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {}; vscodium = handleTest ./vscodium.nix {}; wasabibackend = handleTest ./wasabibackend.nix {}; diff --git a/nixos/tests/chromium.nix b/nixos/tests/chromium.nix index 60ecf986d6e..d2a8f276f12 100644 --- a/nixos/tests/chromium.nix +++ b/nixos/tests/chromium.nix @@ -30,7 +30,10 @@ mapAttrs (channel: chromiumPkg: makeTest rec { machine.imports = [ ./common/user-account.nix ./common/x11.nix ]; machine.virtualisation.memorySize = 2047; machine.test-support.displayManager.auto.user = user; - machine.environment.systemPackages = [ chromiumPkg ]; + machine.environment = { + systemPackages = [ chromiumPkg ]; + variables."XAUTHORITY" = "/home/alice/.Xauthority"; + }; startupHTML = pkgs.writeText "chromium-startup.html" '' <!DOCTYPE html> @@ -63,17 +66,32 @@ mapAttrs (channel: chromiumPkg: makeTest rec { return "su - ${user} -c " + shlex.quote(cmd) - def get_browser_binary(): - """Returns the name of the browser binary.""" + def launch_browser(): + """Launches the web browser with the correct options.""" + # Determine the name of the binary: pname = "${getName chromiumPkg.name}" if pname.find("chromium") != -1: - return "chromium" # Same name for all channels and ungoogled-chromium - if pname == "google-chrome": - return "google-chrome-stable" - if pname == "google-chrome-dev": - return "google-chrome-unstable" - # For google-chrome-beta and as fallback: - return pname + binary = "chromium" # Same name for all channels and ungoogled-chromium + elif pname == "google-chrome": + binary = "google-chrome-stable" + elif pname == "google-chrome-dev": + binary = "google-chrome-unstable" + else: # For google-chrome-beta and as fallback: + binary = pname + # Add optional CLI options: + options = [] + major_version = "${versions.major (getVersion chromiumPkg.name)}" + if major_version > "91": + # To avoid a GPU crash: + options += ["--use-gl=angle", "--use-angle=swiftshader"] + options.append("file://${startupHTML}") + # Launch the process: + machine.succeed(ru(f'ulimit -c unlimited; {binary} {shlex.join(options)} & disown')) + if binary.startswith("google-chrome"): + # Need to click away the first window: + machine.wait_for_text("Make Google Chrome the default browser") + machine.screenshot("google_chrome_default_browser_prompt") + machine.send_key("ret") def create_new_win(): @@ -124,24 +142,32 @@ mapAttrs (channel: chromiumPkg: makeTest rec { @contextmanager - def test_new_win(description): + def test_new_win(description, url, window_name): create_new_win() + machine.wait_for_window("New Tab") + machine.send_chars(f"{url}\n") + machine.wait_for_window(window_name) + machine.screenshot(description) + machine.succeed( + ru( + "${xdo "copy-all" '' + key --delay 1000 Ctrl+a Ctrl+c + ''}" + ) + ) + clipboard = machine.succeed( + ru("${pkgs.xclip}/bin/xclip -o") + ) + print(f"{description} window content:\n{clipboard}") with machine.nested(description): - yield + yield clipboard # Close the newly created window: machine.send_key("ctrl-w") machine.wait_for_x() - url = "file://${startupHTML}" - machine.succeed(ru(f'ulimit -c unlimited; "{get_browser_binary()}" "{url}" & disown')) - - if get_browser_binary().startswith("google-chrome"): - # Need to click away the first window: - machine.wait_for_text("Make Google Chrome the default browser") - machine.screenshot("google_chrome_default_browser_prompt") - machine.send_key("ret") + launch_browser() machine.wait_for_text("startup done") machine.wait_until_succeeds( @@ -164,49 +190,7 @@ mapAttrs (channel: chromiumPkg: makeTest rec { machine.screenshot("startup_done") - with test_new_win("check sandbox"): - machine.succeed( - ru( - "${xdo "type-url" '' - search --sync --onlyvisible --name "New Tab" - windowfocus --sync - type --delay 1000 "chrome://sandbox" - ''}" - ) - ) - - machine.succeed( - ru( - "${xdo "submit-url" '' - search --sync --onlyvisible --name "New Tab" - windowfocus --sync - key --delay 1000 Return - ''}" - ) - ) - - machine.screenshot("sandbox_info") - - machine.succeed( - ru( - "${xdo "find-window" '' - search --sync --onlyvisible --name "Sandbox Status" - windowfocus --sync - ''}" - ) - ) - machine.succeed( - ru( - "${xdo "copy-sandbox-info" '' - key --delay 1000 Ctrl+a Ctrl+c - ''}" - ) - ) - - clipboard = machine.succeed( - ru("${pkgs.xclip}/bin/xclip -o") - ) - + with test_new_win("sandbox_info", "chrome://sandbox", "Sandbox Status") as clipboard: filters = [ "layer 1 sandbox.*namespace", "pid namespaces.*yes", @@ -253,6 +237,11 @@ mapAttrs (channel: chromiumPkg: makeTest rec { machine.screenshot("after_copy_from_chromium") + + with test_new_win("gpu_info", "chrome://gpu", "chrome://gpu"): + pass + + machine.shutdown() ''; }) channelMap diff --git a/nixos/tests/nginx-variants.nix b/nixos/tests/nginx-variants.nix index a535030663b..96a9a2c3b8c 100644 --- a/nixos/tests/nginx-variants.nix +++ b/nixos/tests/nginx-variants.nix @@ -29,5 +29,5 @@ builtins.listToAttrs ( }; } ) - [ "nginxStable" "nginxMainline" "nginxShibboleth" "openresty" "tengine" ] + [ "nginxStable" "nginxMainline" "nginxQuic" "nginxShibboleth" "openresty" "tengine" ] ) diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix index 69a9a6b2321..a33aca29fd2 100644 --- a/nixos/tests/prometheus-exporters.nix +++ b/nixos/tests/prometheus-exporters.nix @@ -454,15 +454,21 @@ let enable = true; lndTlsPath = "/var/lib/lnd/tls.cert"; lndMacaroonDir = "/var/lib/lnd"; + extraFlags = [ "--lnd.network=regtest" ]; }; metricProvider = { - systemd.services.prometheus-lnd-exporter.serviceConfig.DynamicUser = false; - services.bitcoind.main.enable = true; - services.bitcoind.main.extraConfig = '' - rpcauth=bitcoinrpc:e8fe33f797e698ac258c16c8d7aadfbe$872bdb8f4d787367c26bcfd75e6c23c4f19d44a69f5d1ad329e5adf3f82710f7 - bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332 - bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333 - ''; + virtualisation.memorySize = 1024; + systemd.services.prometheus-lnd-exporter.serviceConfig.RestartSec = 15; + systemd.services.prometheus-lnd-exporter.after = [ "lnd.service" ]; + services.bitcoind.regtest = { + enable = true; + extraConfig = '' + rpcauth=bitcoinrpc:e8fe33f797e698ac258c16c8d7aadfbe$872bdb8f4d787367c26bcfd75e6c23c4f19d44a69f5d1ad329e5adf3f82710f7 + zmqpubrawblock=tcp://127.0.0.1:28332 + zmqpubrawtx=tcp://127.0.0.1:28333 + ''; + extraCmdlineOptions = [ "-regtest" ]; + }; systemd.services.lnd = { serviceConfig.ExecStart = '' ${pkgs.lnd}/bin/lnd \ @@ -471,7 +477,7 @@ let --tlskeypath=/var/lib/lnd/tls.key \ --logdir=/var/log/lnd \ --bitcoin.active \ - --bitcoin.mainnet \ + --bitcoin.regtest \ --bitcoin.node=bitcoind \ --bitcoind.rpcuser=bitcoinrpc \ --bitcoind.rpcpass=hunter2 \ @@ -483,13 +489,31 @@ let wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; }; + # initialize wallet, creates macaroon needed by exporter + systemd.services.lnd.postStart = '' + ${pkgs.curl}/bin/curl \ + --retry 20 \ + --retry-delay 1 \ + --retry-connrefused \ + --cacert /var/lib/lnd/tls.cert \ + -X GET \ + https://localhost:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /tmp/seed + ${pkgs.curl}/bin/curl \ + --retry 20 \ + --retry-delay 1 \ + --retry-connrefused \ + --cacert /var/lib/lnd/tls.cert \ + -X POST \ + -d "{\"wallet_password\": \"asdfasdfasdf\", \"cipher_seed_mnemonic\": $(cat /tmp/seed | tr -d '\n')}" \ + https://localhost:8080/v1/initwallet + ''; }; exporterTest = '' wait_for_unit("lnd.service") wait_for_open_port(10009) wait_for_unit("prometheus-lnd-exporter.service") wait_for_open_port(9092) - succeed("curl -sSf localhost:9092/metrics | grep '^promhttp_metric_handler'") + succeed("curl -sSf localhost:9092/metrics | grep '^lnd_peer_count'") ''; }; diff --git a/nixos/tests/vikunja.nix b/nixos/tests/vikunja.nix new file mode 100644 index 00000000000..bd884b37f4f --- /dev/null +++ b/nixos/tests/vikunja.nix @@ -0,0 +1,65 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: { + name = "vikunja"; + + meta = with lib.maintainers; { + maintainers = [ em0lar ]; + }; + + nodes = { + vikunjaSqlite = { ... }: { + services.vikunja = { + enable = true; + database = { + type = "sqlite"; + }; + frontendScheme = "http"; + frontendHostname = "localhost"; + }; + services.nginx.enable = true; + }; + vikunjaPostgresql = { pkgs, ... }: { + services.vikunja = { + enable = true; + database = { + type = "postgres"; + user = "vikunja-api"; + database = "vikunja-api"; + host = "/run/postgresql"; + }; + frontendScheme = "http"; + frontendHostname = "localhost"; + }; + services.postgresql = { + enable = true; + ensureDatabases = [ "vikunja-api" ]; + ensureUsers = [ + { name = "vikunja-api"; + ensurePermissions = { "DATABASE \"vikunja-api\"" = "ALL PRIVILEGES"; }; + } + ]; + }; + services.nginx.enable = true; + }; + }; + + testScript = + '' + vikunjaSqlite.wait_for_unit("vikunja-api.service") + vikunjaSqlite.wait_for_open_port(3456) + vikunjaSqlite.succeed("curl --fail http://localhost:3456/api/v1/info") + + vikunjaSqlite.wait_for_unit("nginx.service") + vikunjaSqlite.wait_for_open_port(80) + vikunjaSqlite.succeed("curl --fail http://localhost/api/v1/info") + vikunjaSqlite.succeed("curl --fail http://localhost") + + vikunjaPostgresql.wait_for_unit("vikunja-api.service") + vikunjaPostgresql.wait_for_open_port(3456) + vikunjaPostgresql.succeed("curl --fail http://localhost:3456/api/v1/info") + + vikunjaPostgresql.wait_for_unit("nginx.service") + vikunjaPostgresql.wait_for_open_port(80) + vikunjaPostgresql.succeed("curl --fail http://localhost/api/v1/info") + vikunjaPostgresql.succeed("curl --fail http://localhost") + ''; +}) |