Compare commits

..

1 Commits

Author SHA1 Message Date
Jan Tojnar
460226e12c nixos/ibus: Do not force ibus input method module on GTK
The environment variable is meant for debugging and GTK should already choose the correct input method module automatically. For example, GTK 4 will use the `ibus` module on X11 and `wayland` module on Wayland.

It was introduced in f222abea44.

Other distros like [Ubuntu back in 2020](https://discourse.ubuntu.com/t/ibus-no-more-gtk-im-module-ibus/17727) recognised that setting this was not beneficial and instead lead to more issues, e.g. crashes in ibus.
2026-09-28 13:39:38 +02:00
1961 changed files with 63574 additions and 73637 deletions

View File

@@ -130,7 +130,7 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { default: checkCommitMessages } = await import('${{ github.workspace }}/trusted/ci/github-script/lint-commits.ts')
const { default: checkCommitMessages } = await import('${{ github.workspace }}/trusted/ci/github-script/lint-commits.js')
await checkCommitMessages({
github,

View File

@@ -38,8 +38,8 @@ jobs:
TARGET_SHA: ${{ inputs.targetSha }}
with:
script: |
const { classify } = await import('${{ github.workspace }}/ci/github-script/supportedBranches.ts')
const { default: supportedSystems } = await import('${{ github.workspace }}/ci/github-script/supportedSystems.ts')
const { classify } = await import('${{ github.workspace }}/ci/github-script/supportedBranches.js')
const { default: supportedSystems } = await import('${{ github.workspace }}/ci/github-script/supportedSystems.js')
const baseBranch = (
context.payload.merge_group?.base_ref ??

View File

@@ -64,8 +64,8 @@ jobs:
'.github/workflows/test.yml',
'ci/github-script/package.json',
'ci/github-script/package-lock.json',
'ci/github-script/supportedBranches.ts',
'ci/github-script/supportedSystems.ts',
'ci/github-script/supportedBranches.js',
'ci/github-script/supportedSystems.js',
'ci/pinned.json',
'pkgs/top-level/release-supported-systems.json',
].includes(file))) core.setOutput('merge-group', true)
@@ -82,8 +82,8 @@ jobs:
'ci/github-script/bot.js',
'ci/github-script/check-target-branch.ts',
'ci/github-script/commits.ts',
'ci/github-script/get-pr-commit-details.ts',
'ci/github-script/lint-commits.ts',
'ci/github-script/get-pr-commit-details.js',
'ci/github-script/lint-commits.js',
'ci/github-script/manual-file-edits.ts',
'ci/github-script/merge.js',
'ci/github-script/package.json',
@@ -91,9 +91,9 @@ jobs:
'ci/github-script/prepare.js',
'ci/github-script/reminders.ts',
'ci/github-script/reviewers.js',
'ci/github-script/reviews.ts',
'ci/github-script/supportedBranches.ts',
'ci/github-script/supportedSystems.ts',
'ci/github-script/reviews.js',
'ci/github-script/supportedBranches.js',
'ci/github-script/supportedSystems.js',
'ci/github-script/withRateLimit.js',
'ci/pinned.json',
'pkgs/top-level/release-supported-systems.json',

View File

@@ -444,9 +444,9 @@ nixos/tests/forgejo.nix @adamcstephens @bendlas @christoph-heiss @
/doc/hooks/zig.section.md @RossComputerGuy
# Buildbot
nixos/modules/services/continuous-integration/buildbot @Mic92
nixos/tests/buildbot.nix @Mic92
pkgs/development/tools/continuous-integration/buildbot @Mic92
nixos/modules/services/continuous-integration/buildbot @Mic92 @zowoq
nixos/tests/buildbot.nix @Mic92 @zowoq
pkgs/development/tools/continuous-integration/buildbot @Mic92 @zowoq
# Pretix
pkgs/by-name/pr/pretix/ @mweinelt

View File

@@ -104,7 +104,7 @@ For the purposes of CI, branches in the NixOS/nixpkgs repository are classified
Some branches also have a version component, which is either `unstable` or `YY.MM`.
`ci/github-script/supportedBranches.ts` is a script imported by CI to classify the base and head branches of a Pull Request.
`ci/github-script/supportedBranches.js` is a script imported by CI to classify the base and head branches of a Pull Request.
This classification will then be used to skip certain jobs.
This script can also be run locally to print basic test cases.

View File

@@ -1,6 +1,7 @@
{ lib, ... }:
rec {
inherit (lib) uniqueStrings;
# Borrowed from https://github.com/NixOS/nixpkgs/pull/355616
uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list);
/*
Converts a `packagePlatformPath` into a `packagePlatformAttr`

View File

@@ -4,7 +4,7 @@ import path from 'node:path'
import { DefaultArtifactClient } from '@actions/artifact'
import { handleMerge } from './merge.js'
import { handleReviewers } from './reviewers.js'
import { classify } from './supportedBranches.ts'
import { classify } from './supportedBranches.js'
import withRateLimit from './withRateLimit.js'
export default async ({ github, context, core, dry }) => {

View File

@@ -1,4 +1,4 @@
import { classify, split } from './supportedBranches.ts'
import { classify, split } from './supportedBranches.js'
type TargetBranchPolicyFacts = {
base: string

View File

@@ -6,8 +6,8 @@ import {
evaluateTargetBranchPolicy,
getTargetBranchPolicy,
} from './check-target-branch-policy.ts'
import { dismissReviews, postReview } from './reviews.ts'
import { split } from './supportedBranches.ts'
import { dismissReviews, postReview } from './reviews.js'
import { split } from './supportedBranches.js'
// TODO: should this be combined with the branch checks in prepare.js?
// They do seem quite similar, but this needs to run after eval,

View File

@@ -2,8 +2,8 @@ import { execFileSync } from 'node:child_process'
import type * as actionsCore from '@actions/core'
import type { context as actionsContext } from '@actions/github'
import type { GitHub } from '@actions/github/lib/utils'
import { dismissReviews, postReview } from './reviews.ts'
import { classify } from './supportedBranches.ts'
import { dismissReviews, postReview } from './reviews.js'
import { classify } from './supportedBranches.js'
import withRateLimit from './withRateLimit.js'
const dirname = import.meta.dirname

View File

@@ -3,23 +3,26 @@ import { promisify } from 'node:util'
const execFile = promisify(nodeExecFile)
export type Commit = {
subject: string
sha: string
author: { name: string; email: string }
committer: { name: string; email: string }
changedPaths: string[]
changedPathSegments: Set<string>
}
/**
* @typedef {{
* subject: string,
* sha: string,
* author: { name: string, email: string },
* committer: { name: string, email: string}
* changedPaths: string[],
* changedPathSegments: Set<string>,
* }} Commit
*/
interface RunGitProps {
args: string[]
core: typeof import('@actions/core')
quiet?: boolean
repoPath?: string
}
async function runGit({ args, repoPath, core, quiet }: RunGitProps) {
/**
* @param {{
* args: string[]
* core: typeof import('@actions/core'),
* quiet?: boolean,
* repoPath?: string,
* }} RunGitProps
*/
async function runGit({ args, repoPath, core, quiet }) {
if (repoPath) {
args = ['-C', repoPath, ...args]
}
@@ -31,29 +34,21 @@ async function runGit({ args, repoPath, core, quiet }: RunGitProps) {
return await execFile('git', args)
}
interface GetCommitMessagesForPRProps {
core: typeof import('@actions/core')
pr: Awaited<
ReturnType<
InstanceType<
typeof import('@actions/github/lib/utils').GitHub
>['rest']['pulls']['get']
>
>['data']
repoPath?: string
}
/**
* Gets the SHA, subject and changed files for each commit in the given PR.
*
* Don't use GitHub API at all: the "list commits on PR" endpoint has a limit
* of 250 commits and doesn't return the changed files.
*
* @param {{
* core: typeof import('@actions/core'),
* pr: Awaited<ReturnType<InstanceType<typeof import('@actions/github/lib/utils').GitHub>["rest"]["pulls"]["get"]>>["data"]
* repoPath?: string,
* }} GetCommitMessagesForPRProps
*
* @returns {Promise<Commit[]>}
*/
export async function getCommitDetailsForPR({
core,
pr,
repoPath,
}: GetCommitMessagesForPRProps): Promise<Commit[]> {
export async function getCommitDetailsForPR({ core, pr, repoPath }) {
await runGit({
args: ['fetch', `--depth=1`, 'origin', pr.base.sha],
repoPath,

View File

@@ -1,23 +1,17 @@
import { type Commit, getCommitDetailsForPR } from './get-pr-commit-details.ts'
import { classify } from './supportedBranches.ts'
import { getCommitDetailsForPR } from './get-pr-commit-details.js'
import { classify } from './supportedBranches.js'
type GitHub = InstanceType<typeof import('@actions/github/lib/utils').GitHub>
type Context = typeof import('@actions/github').context
type Core = typeof import('@actions/core')
/** @typedef {import('./get-pr-commit-details.js').Commit} Commit */
interface LintCommitsProps {
github: GitHub
context: Context
core: Core
repoPath?: string
}
export default async function lintCommits({
github,
context,
core,
repoPath,
}: LintCommitsProps) {
/**
* @param {{
* github: InstanceType<typeof import('@actions/github/lib/utils').GitHub>,
* context: typeof import('@actions/github').context,
* core: typeof import('@actions/core'),
* repoPath?: string,
* }} LintCommitsProps
*/
export default async function lintCommits({ github, context, core, repoPath }) {
// This check should only be run when we have the pull_request context.
const pull_number = context.payload.pull_request?.number
if (!pull_number) {
@@ -59,15 +53,13 @@ export default async function lintCommits({
await checkCommitMetadata({ commits, core })
}
interface CheckCommitMessagesProps {
commits: Commit[]
core: Core
}
async function checkCommitMessages({
commits,
core,
}: CheckCommitMessagesProps) {
/**
* @param {{
* commits: Commit[],
* core: typeof import('@actions/core'),
* }} CheckCommitMessagesProps
*/
async function checkCommitMessages({ commits, core }) {
const failures = new Set()
const conventionalCommitTypes = [
@@ -88,13 +80,10 @@ async function checkCommitMessages({
]
/**
* @param types e.g. ["fix", "feat"]
* @param sha commit hash
* @param {string[]} types e.g. ["fix", "feat"]
* @param {string?} sha commit hash
*/
function makeConventionalCommitRegex(
types: string[],
sha: string | null = null,
) {
function makeConventionalCommitRegex(types, sha = null) {
core.info(
`${
sha
@@ -177,15 +166,17 @@ async function checkCommitMessages({
}
}
interface CheckGitFieldsProps {
commits: Commit[]
core: Core
}
async function checkCommitMetadata({ commits, core }: CheckGitFieldsProps) {
/**
* @param {{
* commits: Commit[],
* core: typeof import('@actions/core'),
* }} CheckGitFieldsProps
*/
async function checkCommitMetadata({ commits, core }) {
const failures = new Set()
const isEmail = (s: string) => /^.+@.*$/.test(s)
/** @type {(s: string) => boolean} */
const isEmail = (s) => /^.+@.*$/.test(s)
for (const commit of commits) {
if (!commit.author.name) {

View File

@@ -1,6 +1,6 @@
import { getCommitDetailsForPR } from './get-pr-commit-details.ts'
import { dismissReviews, postReview } from './reviews.ts'
import { classify } from './supportedBranches.ts'
import { getCommitDetailsForPR } from './get-pr-commit-details.js'
import { dismissReviews, postReview } from './reviews.js'
import { classify } from './supportedBranches.js'
export default async function checkManualFileEdits({
github,

View File

@@ -1,5 +1,5 @@
// @ts-nocheck
import { classify } from './supportedBranches.ts'
import { classify } from './supportedBranches.js'
function runChecklist({
committers,

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
import { dismissReviews, postReview } from './reviews.ts'
import { classify } from './supportedBranches.ts'
import supportedSystems from './supportedSystems.ts'
import { dismissReviews, postReview } from './reviews.js'
import { classify } from './supportedBranches.js'
import supportedSystems from './supportedSystems.js'
const reviewKey = 'prepare'
@@ -66,7 +66,7 @@ export default async ({ github, context, core, dry }) => {
// commits between that base and head is the real base. We can query for this via GitHub's
// REST API. There can be multiple candidates for the real base with the same number of
// commits. In this case we pick the "best" candidate by a fixed ordering of branches,
// as defined in ci/github-script/supportedBranches.ts.
// as defined in ci/github-script/supportedBranches.js.
//
// These requests take a while, when comparing against the wrong release - they need
// to look at way more than 10k commits in that case. Thus, we try to minimize the

View File

@@ -3,9 +3,9 @@ import path from 'node:path'
import type * as actionsCore from '@actions/core'
import type { context as actionsContext } from '@actions/github'
import type { GitHub } from '@actions/github/lib/utils'
import { getCommitDetailsForPR } from './get-pr-commit-details.ts'
import { dismissReviews, postReview } from './reviews.ts'
import { classify } from './supportedBranches.ts'
import { getCommitDetailsForPR } from './get-pr-commit-details.js'
import { dismissReviews, postReview } from './reviews.js'
import { classify } from './supportedBranches.js'
/**
* Reminders to post as a non-blocking review when a pull request touches

View File

@@ -1,6 +1,6 @@
Thanks for contributing to the documentation
Make sure you follow the [documentation style guide](https://github.com/NixOS/nixpkgs/blob/master/doc/styleguide.md), most notably:
Make sure you follow the [documentation styleguide](https://github.com/NixOS/nixpkgs/blob/master/doc/styleguide.md), most notably:
- Show, don't tell: lead with a minimal working example; explanation follows the code.
- No meta-commentary: don't write "This section explains how to…", just do it.

View File

@@ -13,28 +13,30 @@ const reviewUsers = [
'manual-edit',
]
type GitHub = InstanceType<typeof import('@actions/github/lib/utils').GitHub>
type Context = typeof import('@actions/github').context
type Review = Awaited<
ReturnType<GitHub['rest']['pulls']['listReviews']>
>['data'][number]
type ReviewWithNonNullUser = Review & { user: NonNullable<Review['user']> }
interface DismissReviewsProps {
github: GitHub
context: Context
core: typeof import('@actions/core')
dry: boolean
reviewKey?: string
}
/**
* @typedef {InstanceType<typeof import('@actions/github/lib/utils').GitHub>} GitHub
* @typedef {typeof import('@actions/github').context} Context
*
* @typedef {Awaited<ReturnType<GitHub['rest']['pulls']['listReviews']>>['data'][number]} Review
* @typedef {Review & { user: NonNullable<Review['user']> }} ReviewWithNonNullUser
*/
/**
* @param {{
* github: GitHub,
* context: Context,
* core: typeof import('@actions/core'),
* dry: boolean,
* reviewKey?: string,
* }} DismissReviewsProps
*/
export async function dismissReviews({
github,
context,
core,
dry,
reviewKey,
}: DismissReviewsProps) {
}) {
const pull_number = context.payload.pull_request?.number
if (!pull_number) {
core.warning('dismissReviews called outside of pull_request context')
@@ -45,29 +47,23 @@ export async function dismissReviews({
return
}
const allReviews: Review[] = await github.paginate(
github.rest.pulls.listReviews,
{
...context.repo,
pull_number,
},
)
const allReviews = await github.paginate(github.rest.pulls.listReviews, {
...context.repo,
pull_number,
})
const reviews = allReviews
.filter((review): review is ReviewWithNonNullUser => !!review.user)
.filter(
const reviews = /** @type {ReviewWithNonNullUser[]} */ (
allReviews.filter(
(review) =>
review.user &&
review.state !== 'DISMISSED' &&
review.user.login.endsWith('[bot]') &&
reviewUsers.some((substr) => review.user?.login.includes(substr)),
)
)
const reviewsByUser = reviews.reduce(
(prev, curr) => {
if (!curr.user) {
return prev
}
if (!(curr.user.login in prev)) {
prev[curr.user.login] = []
}
@@ -76,7 +72,7 @@ export async function dismissReviews({
return prev
},
{} as Record<string, ReviewWithNonNullUser[]>,
/** @type {Record<string, ReviewWithNonNullUser[]> } */ ({}),
)
const commentRegex = new RegExp(
@@ -90,8 +86,8 @@ export async function dismissReviews({
)
let reviewsToMinimize = reviews
const reviewsToDismiss: ReviewWithNonNullUser[] = []
const reviewsToResolve: ReviewWithNonNullUser[] = []
const /** @type {ReviewWithNonNullUser[]} */ reviewsToDismiss = []
const /** @type {ReviewWithNonNullUser[]} */ reviewsToResolve = []
if (reviewKey && reviews.every((review) => commentRegex.test(review.body))) {
reviewsToMinimize = reviews.filter((review) =>
@@ -169,16 +165,17 @@ export async function dismissReviews({
])
}
interface PostReviewProps {
github: GitHub
context: Context
core: typeof import('@actions/core')
dry: boolean
body: string
event: keyof typeof eventToState
reviewKey: string
}
/**
* @param {{
* github: GitHub,
* context: Context,
* core: typeof import('@actions/core'),
* dry: boolean,
* body: string,
* event: keyof typeof eventToState,
* reviewKey: string,
* }} PostReviewProps
*/
export async function postReview({
github,
context,
@@ -187,7 +184,7 @@ export async function postReview({
body,
event = 'REQUEST_CHANGES',
reviewKey,
}: PostReviewProps) {
}) {
const pull_number = context.payload.pull_request?.number
if (!pull_number) {
core.warning('postReview called outside of pull_request context')
@@ -213,7 +210,8 @@ export async function postReview({
reviewUsers.some((substr) => review.user?.login.includes(substr)),
)
let pendingReview: null | Review
/** @type {null | Review} */
let pendingReview
const matchingReviews = reviews.filter((review) =>
reviewKeyRegex.test(review.body),
)

View File

@@ -101,7 +101,7 @@ program
.argument('<repo>', 'Name of the GitHub repository to run on (Example: nixpkgs)')
.argument('<pr>', 'Number of the Pull Request to run on')
.action(async (owner, repo, pr, options) => {
const checkCommitMessages = (await import('./lint-commits.ts')).default
const checkCommitMessages = (await import('./lint-commits.js')).default
await run(checkCommitMessages, owner, repo, pr, options)
})

View File

@@ -2,12 +2,11 @@
/*
#!nix-shell -i node -p nodejs
*/
// @ts-nocheck
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
type BranchType = 'channel' | 'development' | 'primary' | 'secondary'
const typeConfig: Record<string, BranchType[]> = {
const typeConfig = {
master: ['development', 'primary'],
release: ['development', 'primary'],
staging: ['development', 'secondary'],
@@ -20,7 +19,7 @@ const typeConfig: Record<string, BranchType[]> = {
// "order" ranks the development branches by how likely they are the intended base branch
// when they are an otherwise equally good fit according to ci/github-script/prepare.js.
const orderConfig: Record<string, number> = {
const orderConfig = {
master: 0,
release: 1,
staging: 2,
@@ -29,30 +28,15 @@ const orderConfig: Record<string, number> = {
'staging-next': 4,
}
type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
type Version = `${Digit}${Digit}.${Digit}${Digit}` | 'unstable'
interface SplitResult {
prefix: string
version: Version
suffix?: string
function split(branch) {
return {
...branch.match(
/(?<prefix>.+?)(-(?<version>\d{2}\.\d{2}|unstable)(?:-(?<suffix>.*))?)?$/,
).groups,
}
}
function split(branch: string) {
const groups = branch.match(
/(?<prefix>.+?)(-(?<version>\d{2}\.\d{2}|unstable)(?:-(?<suffix>.*))?)?$/,
)!.groups!
return groups as unknown as SplitResult
}
interface BranchClassification {
branch: string
order: number
stable: boolean
type: BranchType[]
version: Version
}
function classify(branch: string): BranchClassification {
function classify(branch) {
const { prefix, version } = split(branch)
return {
branch,
@@ -71,7 +55,7 @@ if (
fileURLToPath(import.meta.url) === resolve(process.argv[1])
) {
console.log('split(branch)')
function testSplit(branch: string) {
function testSplit(branch) {
console.log(branch, split(branch))
}
testSplit('master')
@@ -88,7 +72,7 @@ if (
console.log('')
console.log('classify(branch)')
function testClassify(branch: string) {
function testClassify(branch) {
console.log(branch, classify(branch))
}
testClassify('master')

View File

@@ -0,0 +1,11 @@
// @ts-nocheck
export default async ({ github, context, targetSha }) => {
const { content, encoding } = (
await github.rest.repos.getContent({
...context.repo,
path: 'pkgs/top-level/release-supported-systems.json',
ref: targetSha,
})
).data
return JSON.parse(Buffer.from(content, encoding).toString())
}

View File

@@ -1,30 +0,0 @@
interface SupportedSystemsProps {
github: InstanceType<typeof import('@actions/github/lib/utils').GitHub>
context: typeof import('@actions/github').context
targetSha: string
}
export default async ({
github,
context,
targetSha,
}: SupportedSystemsProps) => {
const contentObject = (
await github.rest.repos.getContent({
...context.repo,
path: 'pkgs/top-level/release-supported-systems.json',
ref: targetSha,
})
).data
if ('type' in contentObject && contentObject.type === 'file') {
const { content, encoding } = contentObject
return JSON.parse(
Buffer.from(content, encoding as BufferEncoding).toString(),
)
} else {
throw new Error(
'Fetched pkgs/top-level/release-supported-systems.json is not a file',
)
}
}

View File

@@ -7,26 +7,19 @@ This directory houses the source files for the Nixpkgs manual.
> We are actively restructuring our documentation to be more beginner friendly.
>
When writing new docs use **Progressive Disclosure:**
When writing new docs use **Progressive Disclosure**
- Start simple, pick up beginners.
- Use **examples** first to show how to get something done.
- Keep **explanation** lean.
Start simple, pick up beginners.
Use **examples** first to show how to get something done. Keep **Explanation** lean.
Use our [style guide](./styleguide.md) for more in depth guidance on writing good documentation.
Use our [styleguide](./styleguide.md) for more in depth guidance on writing good documentation.
Documentation about Nixpkgs belongs here.
This includes getting started guides and onboarding guides for *using* Nixpkgs and the language frameworks it ships.
Documentation about Nixpkgs belongs here, this includes 'getting-started'-guides and 'onboarding-guides' for *using* Nixpkgs and the language frameworks it ships.
Write **guides** task-first: lead with a working example, then explain in prose.
Write **reference** as the specification of functions and attributes.
We are actively working to generate reference documentation from the [doc-comments](https://github.com/NixOS/rfcs/blob/master/rfcs/0145-doc-strings.md) present in code, which also lets you view it locally with the `:doc` command in `nix repl`, e.g.:
```
nix-repl> :l <nixpkgs>
nix-repl> :doc lib.mapAttrsToList
```
We are actively working to generate reference documentation from the [doc-comments](https://github.com/NixOS/rfcs/blob/master/rfcs/0145-doc-strings.md) present in code, which also lets you view it locally with `:doc` in `nix repl`.
See [Document structure](#document-structure) for a structural template.
@@ -49,23 +42,23 @@ If the build succeeds, the manual will be in `./result/share/doc/nixpkgs/manual.
### Development environment
To reduce repetition, consider using tools from the documentation development environment:
To reduce repetition, consider using tools from the provided development environment:
Load it from the Nixpkgs documentation directory with
```ShellSession
$ cd /path/to/nixpkgs/doc
$ nix-shell
```
To load the documentation development environment automatically when entering that directory:
To load the development utilities automatically when entering that directory, [set up `nix-direnv`](https://nix.dev/guides/recipes/direnv).
1. Install [`nix-direnv`](https://search.nixos.org/packages?channel=unstable&query=nix-direnv#show=nix-direnv)
1. Set up direnv in the documentation directory:
Make sure that your local files aren't added to Git history by adding the following lines to `.git/info/exclude` at the root of the Nixpkgs repository:
```ShellSession
$ cd doc
$ echo "use nix" > .envrc
$ direnv allow
```
```
/**/.envrc
/**/.direnv
```
#### Live preview
@@ -140,12 +133,14 @@ A few markups for other kinds of literals are also available:
- `` {env}`XDG_DATA_DIRS` ``
- `` {file}`/etc/passwd` ``
- `` {option}`networking.useDHCP` ``
- `` {var}`pkgs` ``
The values will be formatted as inline `<code>` elements.
- `` {var}`/etc/passwd` ``
These literal kinds are used mostly in NixOS option documentation.
This syntax is taken from [MyST](https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#roles-an-in-line-extension-point).
Though, the feature originates from [reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html#role-manpage) with slightly different syntax.
They are handled by `myst_role` defined per renderer. <!-- reverse references in code -->
#### Admonitions
Set off from the text to bring attention to something.
@@ -168,7 +163,7 @@ The following are supported:
- `example`
Example admonitions require a title to work.
If you don't provide one, the manual won't build.
If you don't provide one, the manual won't be built.
```markdown
::: {.example #ex-showing-an-example}
@@ -184,11 +179,11 @@ Text for the example.
For defining a group of terms:
```markdown
Pear
: Green or yellow bulbous fruit
pear
: green or yellow bulbous fruit
Watermelon
: Green fruit with red flesh
watermelon
: green fruit with red flesh
```
## Commit conventions
@@ -220,7 +215,7 @@ When needed, each convention explains why it exists, so you can make a decision
Note that these conventions are about the **structure** of the manual (and its source files), not about the content that goes in it.
You, as the writer of documentation, are still in charge of its content.
**For prose style, see the [documentation style guide](./styleguide.md).**
**For prose style, see the [documentation styleguide](./styleguide.md).**
### Document structure
@@ -290,7 +285,7 @@ When changing existing content, update formatting if possible, but avoid excessi
### Examples first
Put examples before detailed explanations (see the [style guide](./styleguide.md) for the rationale).
Put examples before detailed explanations (see the [styleguide](./styleguide.md) for the rationale).
Use this structure for each documented item:

View File

@@ -5,7 +5,7 @@ Create a `shell.nix` with the following:
```nix
# shell.nix
let
nixpkgs = fetchTarball "https://channels.nixos.org/nixos-unstable/nixexprs.tar.zst";
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz";
pkgs = import nixpkgs { };
in
pkgs.mkShell {
@@ -25,7 +25,7 @@ nix-shell
This activates your `shell.nix` and you should see:
```sh
unpacking 'https://channels.nixos.org/nixos-unstable/nixexprs.tar.zst' into the Git cache...
unpacking 'https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz' into the Git cache...
Welcome in your nix shell
```

View File

@@ -53,7 +53,7 @@ Pin Nixpkgs and call the package from `default.nix`:
```nix
# default.nix
let
nixpkgs = fetchTarball "https://channels.nixos.org/nixos-unstable/nixexprs.tar.zst";
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz";
pkgs = import nixpkgs { };
in
pkgs.callPackage ./package.nix { }

View File

@@ -3,7 +3,7 @@
This hook defaults a variety of environment variables known
to control thread counts to 1. Many of these otherwise default
to `$(nproc)`, which causes massive overloads on build machines
if nix build jobs and build cores are already tuned to fully use
if nix build jobs and build cores are already tuned to fully utilize
compute capacity of a builder without additional parallelism.
Currently sets the following environment variables:

View File

@@ -5,7 +5,7 @@
COSMIC (Computer Operating System Main Interface Components) is a desktop environment developed by
System76, primarily for the Pop!_OS Linux distribution. Applications in the COSMIC ecosystem are
written in Rust and use libcosmic, which builds on the Iced GUI framework. This section explains
how to package and integrate COSMIC applications within Nix.
how to properly package and integrate COSMIC applications within Nix.
### libcosmicAppHook {#ssec-cosmic-libcosmic-app-hook}
@@ -17,7 +17,7 @@ and wrapping applications based on libcosmic. It handles many common requirement
- Managing Vergen environment variables for build-time information
- Setting up Rust linker flags for specific libraries
Add the hook to your package's `nativeBuildInputs`:
To use the hook, simply add it to your package's `nativeBuildInputs`:
```nix
{
@@ -61,9 +61,8 @@ rustPlatform.buildRustPackage {
}
```
> [!Note]
> `cosmic-settings` is a separate application and not a part of the libcosmic settings
> system itself. It's included by default in `libcosmicAppHook` only to provide these fallback theme
Note that `cosmic-settings` is a separate application and not a part of the libcosmic settings
system itself. It's included by default in `libcosmicAppHook` only to provide these fallback theme
settings.
### Icons {#ssec-cosmic-icons}

View File

@@ -63,7 +63,7 @@ For instance, `sqlite-lua` needs `g:sqlite_clib_path` to be set to work. Nixpkgs
- `wrapperArgs`: Extra arguments forwarded to the `makeWrapper` call.
- `wrapRc`: Nix, not being able to write in your `$HOME`, loads the
generated Neovim configuration via the `$VIMINIT` environment variable, i.e. : `export VIMINIT='lua dofile("/nix/store/…-init.lua")'`. This has side effects like preventing Neovim from sourcing your `init.lua` in `$XDG_CONFIG_HOME/nvim` (see bullet 7 of [`:help startup`](https://neovim.io/doc/user/starting.html#startup) in Neovim). Disable it if you want to generate your own wrapper. You can still reuse the generated vimscript init code via `neovim.passthru.initRc`.
- `plugins`: A list of plugins to add to the wrapper. If a plugin is not available in nixpkgs, you can [package it yourself](#what-if-your-favourite-vim-plugin-isnt-already-packaged).
- `plugins`: A list of plugins to add to the wrapper.
- `extraLuaPackages`: A function passed on to `lua.withPackages`.
- `extraPython3Packages`: A function passed on to `python3.withPackages`.
- `withPython3`, `withNodeJs`, `withRuby`, `withPerl` control when to enable neovim

View File

@@ -11,86 +11,47 @@ nix-shell -p swift --run 'swiftc -' <<< 'print("Hello world!")'
The `swift` package also provides the `swift` command, with some caveats:
- Swift Package Manager (SwiftPM) is packaged separately as `swiftpm`.
If you need functionality like `swift build`, `swift run`, `swift test`, you must also add the `swiftpm` package to your closure.
- On Darwin, the `swift repl` command requires an Xcode installation.
This is because it uses the system LLDB debugserver, which has special entitlements.
- Swift Package Manager (SwiftPM) is packaged separately as `swiftpm`. If you
need functionality like `swift build`, `swift run`, `swift test`, you must
also add the `swiftpm` package to your closure.
- On Darwin, the `swift repl` command requires an Xcode installation. This is
because it uses the system LLDB debugserver, which has special entitlements.
## Module search paths {#ssec-swift-module-search-paths}
The Swift compiler executables are patched to find the C and C++ standard libraries associated with its target platform, but they are not wrapped.
They will not find your application’s dependencies automatically in the Nix store.
Your build system is expected to handle this for you.
Like other toolchains in Nixpkgs, the Swift compiler executables are wrapped
to help Swift find your application's dependencies in the Nix store. These
wrappers scan the `buildInputs` of your package derivation for specific
directories where Swift modules are placed by convention, and automatically
add those directories to the Swift compiler search paths.
SwiftPM provides a hook that scans the `buildInputs` of your package derivation for specific directories where the Swift modules are placed by convention.
These directories are added automatically to `swiftpmFlags` when the hook runs.
Swift in Nixpkgs follows a few conventions when installing dependencies:
Swift follows different conventions depending on the platform. The wrappers
look for the following directories:
- Libraries (both shared and static) are installed to `lib`.
This differs from upstream packaging, but it matches how other langauges are packaged in Nixpkgs.
This allows Swift packages to take advantage of existing tooling that expects libraries to be installed in this standard location.
- Modules are installed to `lib/swift/<platform>` where `<platform>` is the Swift platform for your host platform (e.g., `lib/swift/macosx` or `lib/swift/linux`).
Note that Linux modules may be installed in a directory specific to the target architecture(e.g., `lib/swift/linux/x86_64`), but this is uncommon.
Upstream Swift appears to be moving away from this convention.
- On Darwin platforms: `lib/swift/macosx`
(If not targeting macOS, replace `macosx` with the Xcode platform name.)
- On other platforms: `lib/swift/linux/x86_64`
(Where `linux` and `x86_64` are from lowercase `uname -sm`.)
- For convenience, Nixpkgs also adds `lib/swift` to the search path.
This can save a bit of work packaging Swift modules, because many Nix builds
will produce output for just one target anyway.
## Core libraries {#ssec-swift-core-libraries}
The `swift` package contains a complete toolchain with the Swift stdlib, Dispatch, Foundation, XCTest, and Swift Testing.
These packages do not need to be added to `buildInputs` when packaging applications.
The Swift compiler will find them automatically in the `swift` toolchain.
In addition to the standard library, the Swift toolchain contains some
additional 'core libraries' that, on Apple platforms, are normally distributed
as part of the OS or Xcode. These are packaged separately in Nixpkgs and can
be found (for use in `buildInputs`) as:
If you do need to use these packages outside of the Swift toolchain, they are available in the following packages:
- `swiftPackages.stdlib` contains the Swift stdlib and backdeployment dylibs.
- `swiftPackages.swift-corelibs-libdispatch` contains the Dispatch framework.
- `swiftPackages.swift-corelibs-foundation` contains the Foundation framework.
- `swiftPackages.swift-corelibs-xctest` and `swiftPackages.swift-testing` contain the XCTest and Swift Testing frameworks respectively.
Note: On Darwin, the Swift stdlib has been removed from the SDK.
The Swift toolchain contains the stubs and modules required to build Swift applications with the following exceptions:
- Swift Differentiation is shipped as a dylib in Nixpkgs because it is no longer shipped with the OS (as of macOS 26.4).
This allows packages using Swift Differentiation to work regardless of OS version.
- The Span back-deployment dylib is shipped with the stdlib.
- This is expected because back-deployment dylibs are normally shipped with the toolchain.
- FoundationMacros is built and shipped as a dylib in `swiftPackages.swift-foundation` and included in the toolchain.
Macros are actually compiler plugins executed at build time.
Without this, FoundationMacros would not work on Darwin.
- `swiftPackages.Dispatch`
- `swiftPackages.Foundation`
- `swiftPackages.XCTest`
## Packaging with SwiftPM {#ssec-swift-packaging-with-swiftpm}
Nixpkgs includes two ways to package dependencies for Swift applications: `fetchSwiftPMDeps` and `swiftpm2nix`.
While `swiftpm2nix` is not deprecated, using `fetchSwiftPMDeps` is preferred because it is easier to use and does not (usually) require shipping extra files with your package.
### Packaging with `fetchSwiftPMDeps` {#ssec-swift-packaging-with-fetch-swiftpm-deps}
Swift provides a fetcher that will download all of your dependencies based on the `Package.resolved` shipped by your package.
If your package does not ship one, you will have to generate it yourself and provide it with your package.
Otherwise, set `swiftpmDeps` as follows:
```nix
{
swiftpmDeps = fetchSwiftPMDeps {
inherit src;
hash = "sha256-1KfyrQXE1HaO9WsuskzgiiEZxM/oelp40Jwzr8xJEL4=";
};
}
```
The `src` attribute is required as is the `hash`.
The first time you build your package, you will need to set `hash` to an empty value by using `lib.fakeHash` to get the hash for your dependencies.
The following optional attributes can also be used:
- `name`: Sets the name of the vendored dependencies fixed-output derivation.
You can also use `pname` and `version` to set the `name`.
This is often easier because you can inherit them from `finalAttrs`.
- `sourceRoot`: Sets the path where `Package.swift` and `Package.resolved` can be found if they are not in their default, top-level location.
- `patches`: Can be used to apply patches to your project before the dependencies are vendored.
This is useful to update `Package.swift` or `Package.resolved`.
- `postPatch`: Can be used to perform extra steps after patching.
You can copy a custom `Package.resolved` in `postPatch`.
### Packaging with `swiftpm2nix` {#ssec-swift-packaging-with-swiftpm2nix}
Nixpkgs includes a small helper `swiftpm2nix` that can fetch your SwiftPM
dependencies for you, when you need to write a Nix expression to package your
application.
The first step is to run the generator:
@@ -104,8 +65,8 @@ swift package resolve
swiftpm2nix
```
This produces some files in a directory `nix`, which will be part of your Nix expression.
The next step is to write that expression:
This produces some files in a directory `nix`, which will be part of your Nix
expression. The next step is to write that expression:
```nix
{
@@ -165,13 +126,45 @@ stdenv.mkDerivation (finalAttrs: {
})
```
#### Patching dependencies {#ssec-swiftpm-patching-dependencies}
### Custom build flags {#ssec-swiftpm-custom-build-flags}
In some cases, it may be necessary to patch a SwiftPM dependency.
SwiftPM dependencies are located in `.build/checkouts`, but the `swiftpm2nix` helper provides these as symlinks to read-only `/nix/store` paths.
To patch them, we need to make them writable.
If you'd like to build a different configuration than `release`:
A special function `swiftpmMakeMutable` is available to replace the symlink with a writable copy:
```nix
{ swiftpmBuildConfig = "debug"; }
```
It is also possible to provide additional flags to `swift build`:
```nix
{ swiftpmFlags = [ "--disable-dead-strip" ]; }
```
The default `buildPhase` already passes `-j` for parallel building.
If these two customization options are insufficient, provide your own
`buildPhase` that invokes `swift build`.
### Running tests {#ssec-swiftpm-running-tests}
Including `swiftpm` in your `nativeBuildInputs` also provides a default
`checkPhase`, but it must be enabled with:
```nix
{ doCheck = true; }
```
This essentially runs: `swift test -c release`
### Patching dependencies {#ssec-swiftpm-patching-dependencies}
In some cases, it may be necessary to patch a SwiftPM dependency. SwiftPM
dependencies are located in `.build/checkouts`, but the `swiftpm2nix` helper
provides these as symlinks to read-only `/nix/store` paths. To patch
them, we need to make them writable.
A special function `swiftpmMakeMutable` is available to replace the symlink
with a writable copy:
```nix
{
@@ -190,76 +183,21 @@ A special function `swiftpmMakeMutable` is available to replace the symlink with
}
```
### Custom build flags {#ssec-swiftpm-custom-build-flags}
If you'd like to build a different configuration than `release`:
```nix
{ swiftpmBuildConfig = "debug"; }
```
It is also possible to provide additional flags to `swift build`:
```nix
{ swiftpmFlags = [ "--disable-dead-strip" ]; }
```
The default `buildPhase` already passes `-j` for parallel building.
If these two customization options are insufficient, provide your own `buildPhase` that invokes `swift build`.
### Running tests {#ssec-swiftpm-running-tests}
Including `swiftpm` in your `nativeBuildInputs` also provides a default `checkPhase`, but it must be enabled with:
```nix
{ doCheck = true; }
```
This essentially runs: `swift test -c release`
### Installing packages {#ssec-swiftpm-install-phase}
SwiftPM provides a default install phase that installs any products specified in your package’s `Package.swift`.
If your package does not specify any products, which is not uncommon, you will have to manually install them to `out`.
To disable the SwiftPM install phase, include the following in your derivation:
```nix
{ dontUseSwiftpmInstall = true; }
```
## Hooks {#ssec-swift-hooks}
Swift provides the following hooks to automate builds and unpack dependencies:
- `swiftpmHook`: Propagated by `swiftpm`.
Also propagates `swiftpmUnpackHook`.
Provides build, install, and check phases. It also adds any dependencies found in `buildInputs` to `swiftpmFlags`.
- `swiftpmUnpackHook`: Sets up `workspace-state.json` and links vendored dependencies to the top-level `Packages` directory in the build environment.
Swift also provides a hook with the toolchain to replace rpath references to the toolchain with references to the stdlib package.
This hook is used automatically by the `swift` package.
This avoids pulling the entire toolchain into the closure of your package.
## Considerations for custom build tools {#ssec-swift-considerations-for-custom-build-tools}
### Linking the standard library {#ssec-swift-linking-the-standard-library}
The Swift stdlib is packaged separately as `swiftPackages.stdlib`.
The shared and static libraries are installed to `lib`.
Most tooling in Nixpkgs should find them automatically when linking.
The stdlib provides a hook to change any rpaths pointing to the toolchain to point to the stdlib instead.
The `swift` package has a separate `lib` output containing just the Swift
standard library, to prevent Swift applications needing a dependency on the
full Swift compiler at runtime. Linking with the Nixpkgs Swift toolchain
already ensures binaries correctly reference the `lib` output.
The stdlib modules are installed to `lib/swift/<platform>` in the `dev` output of the stdlib package.
These are symlinked together into the `swift` toolchain.
If your build tools locate the modules relative to the `swift` compiler executable, it should do the right thing automatically.
Sometimes, Swift is used only to compile part of a mixed codebase, and the
link step is manual. Custom build tools often locate the standard library
relative to the `swift` compiler executable, and while the result will work,
when this path ends up in the binary, it will have the Swift compiler as an
unintended dependency.
### Accessing properties of the Swift platform {#ssec-swift-platform-properties}
The architecture, platform, and triple used by Swift is available as attributes on the build/host/targetPlatform for the `stdenv`.
- `stdenv.<platform>.swift.platform`: The Swift platform (e.g., `macosx` for macOS, `linux` for Linux, etc).
- `stdenv.<platform>.swift.arch`: The Swift architecture (e.g., `arm64` for Darwin or `aarch64` for Linux, `x86_64`, etc).
- `stdenv.<platform>.swift.triple`: The triple used by Swift.
This is the same as `stdenv.<platform>.config` except on Darwin.
On Darwin, it uses the OS name instead of `darwin` and includes the deployment target (e.g., `arm64-apple-macosx14.0`).
In this case, you should investigate how your build process discovers the
standard library, and override the path. The correct path will be something
like: `"${swift.swift.lib}/${swift.swiftModuleSubdir}"`

View File

@@ -1981,9 +1981,6 @@
"sec-darwin-troubleshooting-xcodebuild-absolute-paths": [
"index.html#sec-darwin-troubleshooting-xcodebuild-absolute-paths"
],
"sec-darwin-missing-macros": [
"index.html#sec-darwin-missing-macros"
],
"sec-darwin-troubleshooting-libiconv": [
"index.html#sec-darwin-troubleshooting-libiconv"
],
@@ -4596,26 +4593,14 @@
"ssec-swift-packaging-with-swiftpm": [
"index.html#ssec-swift-packaging-with-swiftpm"
],
"ssec-swift-packaging-with-fetch-swiftpm-deps": [
"index.html#ssec-swift-packaging-with-fetch-swiftpm-deps"
],
"ssec-swift-packaging-with-swiftpm2nix": [
"index.html#ssec-swift-packaging-with-swiftpm2nix"
],
"ssec-swiftpm-patching-dependencies": [
"index.html#ssec-swiftpm-patching-dependencies"
],
"ssec-swiftpm-custom-build-flags": [
"index.html#ssec-swiftpm-custom-build-flags"
],
"ssec-swiftpm-running-tests": [
"index.html#ssec-swiftpm-running-tests"
],
"ssec-swiftpm-install-phase": [
"index.html#ssec-swiftpm-install-phase"
],
"ssec-swift-hooks": [
"index.html#ssec-swift-hooks"
"ssec-swiftpm-patching-dependencies": [
"index.html#ssec-swiftpm-patching-dependencies"
],
"ssec-swift-considerations-for-custom-build-tools": [
"index.html#ssec-swift-considerations-for-custom-build-tools"
@@ -4623,9 +4608,6 @@
"ssec-swift-linking-the-standard-library": [
"index.html#ssec-swift-linking-the-standard-library"
],
"ssec-swift-platform-properties": [
"index.html#ssec-swift-platform-properties"
],
"sec-language-tcl": [
"index.html#sec-language-tcl"
],

View File

@@ -16,8 +16,6 @@
+nixpkgs.url = "https://channels.nixos.org/nixos-26.05/nixexprs.tar.zst";
```
- GCC has been updated from GCC 15 to GCC 16. This introduces some backwards-incompatible changes. Refer to the [upstream porting guide](https://gcc.gnu.org/gcc-16/porting_to.html) for details.
- Emacs has been updated to 31.
This introduces some backwards‐incompatible changes; see the NEWS for details.
NEWS can be viewed from Emacs by typing `C-h n`, or by clicking `Help->Emacs News` from the menu bar.
@@ -38,10 +36,6 @@
- `zabbix.<package>` now defaults to version 7.4. If you want to keep using Zabbix 6.0, use `pkgs.zabbix60.<package>`.
Note that Zabbix 6.0 is in limited support, and will be deprecated on February 28, 2027. Consider upgrading.
- `zabbix-agent2-plugin-postgresql` is now moved to `zabbix{60,70,74}.plugins.postgresql`.
- Official Zabbix plugins (ember-plus, mongodb, and mssql) have been added under `zabbix{60,70,74}.plugins.<plugin>`.
- `perlPackages.NetOAuth` has been updated from 0.28 to 0.33.
Callers that verify messages must now set `allowed_signature_methods` per message or configure `@Net::OAuth::ALLOWED_SIGNATURE_METHODS`; `verify` otherwise throws an exception.
See the [upstream changelog](https://metacpan.org/dist/Net-OAuth/changes) for details.
@@ -123,9 +117,6 @@
- `himalaya` has been updated from `v1.2.0` to `v2.0.0`, which introduces breaking changes. See the [release notes](https://github.com/pimalaya/himalaya/releases/tag/v2.0.0) and the [migration guide](https://github.com/pimalaya/himalaya/blob/master/MIGRATION.md).
- `proton-cli` has been updated from `2.2.3` to `3.4.0`, and installs its command as `proton`, with `proton-cli` kept beside it as a symlink.
`3.0.0` reworked the command line - `--output` is now the response format, secrets are no longer accepted as flag values, and several subcommands moved - so scripts need a review against the [upstream changelog](https://github.com/roman-16/proton-cli/blob/main/CHANGELOG.md).
- `tengine` has been removed as it has seen seriously delayed responses to security vulnerabilities.
- `jellyfin` has been upgraded to major version 12, which contains breaking changes. See the [upstream blog post](https://jellyfin.org/posts/jellyfin-release-12.0) for more information on how to safely upgrade.
@@ -180,8 +171,6 @@
- `replaceVarsWith` now enables `strictDeps` and `__structuredAttrs` and passing these attributes to the function is no longer allowed.
By extension, `replaceVars` now also enables `strictDeps` and `__structuredAttrs`.
- `nginx` / `nginxStable` is now built without the `rtmp` nginx module by default. You can enable it again using `nginx.override { modules = [ pkgs.nginxModules.rtmp ]; }`
- `buildFHSEnvChroot` has been removed after deprecation in 23.05.
- `leafnode` has been removed, as it was an unmaintained alpha-release of leafnode 2 and has a dependency on the EOL PRCE-library. Consider using `leafnode1` instead, which is still maintained.
@@ -204,11 +193,6 @@
- `librest` providing 0.7 ABI was removed. `librest_1_0` providing 1.0 ABI was renamed to `librest` and `librest_1_0` was kept as an alias.
- `secretspec-ffi` has been renamed to `libsecretspec` and updated to 0.21.0.
The old package attribute remains an alias, but native consumers must rebuild
against the new `libsecretspec` library and pkg-config module. The separate
`libsecretspec-resolver` package provides a C client for `secretspec serve`.
- `luaPackages.lrexlib-pcre` has been removed as part of the process to fully migrate from the end-of-life PRCE library to PCRE2. `luaPackages.lrexlib-pcre2` and multiple other versions of lrexlib can be used instead.
- `hostapd` was upgraded to version 2.12+, which moves move supported, basic, and beacon transmission rate configuration to be at BSS level instead of per-radio for all BSSs. Refer to the [upstream example config](https://git.w1.fi/cgit/hostap/plain/hostapd/hostapd.conf) for details.
@@ -241,16 +225,6 @@
- `nim-2_0` & `nim-2_2` and respective aliases have been removed; please migrate to `nim` or `nim-unwrapped` (nim 2.2.10).
- `domoticz` has been updated from `2024.7` to `2026.x`, breaking third party applications and scripts using the old RType calls. Review the [release notes](https://github.com/domoticz/domoticz/blob/2026.2/History.txt#L398) for more information.
- `swift` is no longer wrapped.
The `NIX_SWIFTFLAGS_COMPILE` variable is no longer supported.
If you need to pass custom flags to the Swift compiler, you must add them via your package’s build system.
The default target version used by `swiftc` on Darwin is the operating system major version.
This value may be overridden by the build system (e.g., SwiftPM defaults to 10.13 instead).
See the Swift documentation in Nixpkgs for details.
- `swiftpm` is no longer wrapped to include Git to fetch dependencies.
Users with Git-based dependencies will need to add `git` to their dev shells or include it in their environment if they weren’t already.
- `vimacs` has been removed, as it has not been maintained in 10 years and was built for an old version of vim (6.0).
- The deprecated `appimageTools.extractType1`, `appimageTools.extractType2`, and `appimageTools.wrapType1` aliases now emit warnings. Use `appimageTools.extract` and `appimageTools.wrapType2` instead.
@@ -287,27 +261,18 @@
- `nextpnr` introduced support for the nexus and gatemate architectures. Building support for each individual architecture can be configured using the package parameters.
- `mastodon` has been updated to 4.7. The [4.7.0 release notes](https://github.com/mastodon/mastodon/releases/tag/v4.7.0) mention some unusually long running migrations.
- Emacs loads the `early-default` library after `early-init.el`.
Users can add `early-init.el` via `emacs.pkgs.withPackages`
by packaging `early-init.el` into a library named `early-default`.
To prevent loading the `early-default` library,
set `inhibit-early-default-init` in `early-init.el`.
- Ceph has a vulnerability in old generated CephX keys.
The project recommends to rotate old keys.
This is a manual process, see https://ceph.io/en/news/blog/2026/v20-2-4-v19-2-6-combo-released
- `services.ceph` enabled the generation of Ceph log files at `/var/log/ceph/`.
They were missing before because Ceph omitted logs when this directory was missing.
Ceph logs can grow large, so you may want to configure rotation of these logs.
- Firefox wrapper now accepts an optional `appDataDir` argument, which sets `MOZ_APP_DATA` to relocate Firefox application data. This is especially useful on macOS 27 and later, where wrapped Firefox applications may be denied access to profiles in traditional application data directory.
- Swift has been upgraded to Swift 6.2.4 from Swift 5.10.1.
The Swift packaging has been rewritten.
## Nixpkgs Library {#sec-nixpkgs-release-26.11-lib}
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
@@ -318,9 +283,6 @@
- `typescript` 7.0.2 now uses the Golang implementation. The [announcement document](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/) has information on what was changed.
- `macaulay2` no longer installs Emacs files.
Users can now get the files from an Emacs lisp package, like `emacs.pkgs.withPackages (epkgs: [ epkgs.m2 ])`.
- `navidrome`'s plugin infrastructure has significantly changed. `buildNavidromePlugin` is renamed to `buildNavidromeGoPlugin` to allow for other language types. Plugins must now be sourced from `pkgsCross.wasi32.navidromePlugins.<name>`.
- `navidromePlugins.apple-music` now uses a `bundleName` attribute which sets the plugin's name to match the plugin's documentation for easier use. You will need to update your Agent from `apple-music-plugin` to `apple-music` as noted in [their docs](https://github.com/navidrome/apple-music-plugin#installation).

View File

@@ -121,8 +121,7 @@ Generally, only the last SDK release for a major version is packaged.
|---------------|-------------|------------------------------|
| 15.0–15.4 | 14.4 | `apple-sdk_14` / `apple-sdk` |
| 16.0 | 15.0 | `apple-sdk_15` |
| 26.0 | 26.0 | `apple-sdk_26` |
| 27.0+ | 27.0+ | `apple-sdk_27`, etc |
| 26.0+ | 26.0+ | `apple-sdk_26`, etc |
#### Darwin Default SDK versions {#sec-darwin-troubleshooting-darwin-defaults}
@@ -193,13 +192,6 @@ stdenv.mkDerivation {
}
```
### Macro library not available {#sec-darwin-missing-macros}
Some frameworks provide macros that are only shipped with Xcode.
For example, the AppleIntelligence framework, Swift Data, and SwiftUI (as of the 27.0 SDK).
A non-free package making these available will be added at a later date.
Until then, they are unfortunately not available in Nixpkgs.
#### How to use libiconv on Darwin {#sec-darwin-troubleshooting-libiconv}
The libiconv package is included in the SDK by default along with libresolv and libsbuf.

View File

@@ -1,4 +1,4 @@
# Style guide
# Styleguide
Use this page as a reference and style guide for our internal and external documentation.
@@ -22,7 +22,7 @@ Write for someone who knows a great deal — up to but not including this projec
If specific knowledge is required, mention it at the start of the page.
### Show, don't tell
### Show, Don't Tell
The fastest path to understanding is a working example.
People learn by doing, not by reading about doing.
@@ -34,7 +34,7 @@ People learn by doing, not by reading about doing.
- Cover edge cases or variations
- Link to further information instead of including it
### Grammar and style
### Grammar and Style
**Sentence structure:**
@@ -54,7 +54,7 @@ Users care about *detecting hardware*, not *the tool that does it*.
> This command detects your hardware and saves the configuration.
### Content organization
### Content Organization
Lead with value. State what the reader will accomplish before explaining how.
@@ -83,23 +83,21 @@ Use **progressive disclosure**. Introduce concepts only when needed.
3. Explain concepts if needed
4. Provide advanced options separately or link to the reference
### No meta-commentary
### No Meta-commentary
Don't describe what the documentation does. Just do it.
**Don't:**
> This section explains how to configure networking.
> The following guide walks you through setting up a web server.
**Do:**
> Configure networking by setting:
> Set up a web server:
### Code examples
### Code Examples
**Keep examples focused:**
@@ -132,7 +130,7 @@ Paste code examples directly and without further alteration.
}
```
### Lead with practical examples
### Lead with Practical Examples
Don't front-load theory. Readers want to accomplish something first, then understand why it works.
@@ -168,7 +166,7 @@ Users learn the NixOS module system by seeing patterns first.
- Link deeper concepts instead of inlining them
- Link to `nix.dev` for optional learning
### General rules
### General Rules
- Abbreviate keys like `ssh-ed25519 AAAAC3NzaC…`
- Abbreviate IP addresses like `192.168.XXX.XXX`
@@ -202,7 +200,7 @@ Use sentence case. A reader scanning only headings should understand the page.
> Configure networking
> Add a user to the system
### Imperative mood, voice, and person
### Imperative Mood, Voice, and Person
Use imperative mood for instructions. Address the reader as "you", not "the user". Use active voice; in other words, make the subject do the action.
@@ -232,7 +230,7 @@ Use present tense for descriptions. Future tense makes documentation feel tentat
> This creates a new folder.
> Running this command installs the package.
### Be confident
### Be Confident
State facts. Don't hedge with "should," "might," "typically," or "usually" unless the behavior genuinely varies.
@@ -246,7 +244,7 @@ State facts. Don't hedge with "should," "might," "typically," or "usually" unles
> This creates the configuration file.
> The service starts automatically.
### Avoid nominalizations
### Avoid Nominalizations
A nominalization is a verb turned into a noun, often by adding *-tion*, *-meant*, or *-ance* (e.g. "explanation", "selection"). The fix: find the hidden verb and use it directly.
@@ -260,7 +258,7 @@ A nominalization is a verb turned into a noun, often by adding *-tion*, *-meant*
> Select from the list.
> Explain the error.
### Plain words
### Plain Words
Technical precision for technical terms; plain language for everything else.
@@ -272,7 +270,7 @@ Technical precision for technical terms; plain language for everything else.
- "set up" not "establish"
- "find out" not "ascertain"
### Filler words and weak phrases
### Filler Words and Weak Phrases
Cut words and phrases that add length without meaning.
@@ -298,7 +296,7 @@ Delete on sight:
Every word must earn its place.
### Writing procedures
### Writing Procedures
One instruction per sentence. Don't pack multiple actions into one sentence.
@@ -322,7 +320,7 @@ Don't bury the negative. Key limitations should be prominent, not a footnote aft
> This service does not support multiple instances.
### Consistent terminology
### Consistent Terminology
Pick a term and stick to it. Don't swap synonyms to avoid repetition. In technical documentation, repetition is clarity.
@@ -361,7 +359,7 @@ Only link when the destination is directly relevant, not for generic background
> See `[database schema](url)` for the full table structure.
### UI language
### UI Language
Match UI element names exactly: wording, casing, and spacing (even if a label seems oddly worded).

View File

@@ -22,7 +22,7 @@ import <nixpkgs> {
}
```
Note that we set `linker` to `lld`. This is because LLVM has its own linker, called "lld". By setting it, we use Clang and lld within this new instance of Nixpkgs. There is a shorthand method for building everything with LLVM: `pkgsLLVM`. This is easier to use with `nix-build` (or `nix build`):
Note that we set `linker` to `lld`. This is because LLVM has its own linker, called "lld". By setting it, we utilize Clang and lld within this new instance of Nixpkgs. There is a shorthand method for building everything with LLVM: `pkgsLLVM`. This is easier to use with `nix-build` (or `nix build`):
```bash
nix-build -A pkgsLLVM.hello

View File

@@ -105,48 +105,27 @@ There are several ways to tweak how Nix handles a package which has been marked
$ export NIXPKGS_ALLOW_UNFREE=1
```
- To allow specific unfree packages, add their names to your Nixpkgs configuration file:
- It is possible to permanently allow individual unfree packages, while still blocking unfree packages by default using the `allowUnfreePredicate` configuration option in the user configuration file.
This option is a function which accepts a package as a parameter, and returns a boolean. The following example configuration accepts a package and always returns false:
```nix
{ allowUnfreePredicate = (pkg: false); }
```
For a more useful example, try the following. This configuration only allows unfree packages named roon-server and Visual Studio Code:
```nix
{
allowUnfreePackages = [
"fence"
"roon-server"
"vscode"
];
allowUnfreePredicate =
pkg:
builtins.elem (lib.getName pkg) [
"roon-server"
"vscode"
];
}
```
`allowUnfreePackages` permits the listed unfree packages.
In NixOS modules, lists set through `nixpkgs.config.allowUnfreePackages` merge additively across modules. This allows you to declare your unfree exceptions in the same modules that triggered them.
To allow unfree packages programmatically:
```nix
{ lib, ... }:
{
allowUnfreePredicate = pkg: lib.hasPrefix "roon" (lib.getName pkg);
}
```
This permits packages such as `roon-bridge` and `roon-server`.
To combine the list and predicate, set both options:
```nix
{ lib, ... }:
{
allowUnfreePackages = [
"fence"
"vscode"
];
allowUnfreePredicate = pkg: lib.hasPrefix "roon" (lib.getName pkg);
}
```
This permits unfree packages that match either option.
- It is also possible to allow and block licenses that are specifically acceptable or not acceptable, using `allowlistedLicenses` and `blocklistedLicenses`, respectively.
The following example configuration allowlists the licenses `amd` and `wtfpl`:

View File

@@ -699,7 +699,20 @@ rec {
*/
filterAttrsRecursive =
pred: set:
mapAttrs (_: v: if isAttrs v then filterAttrsRecursive pred v else v) (filterAttrs pred set);
listToAttrs (
concatMap (
name:
let
v = set.${name};
in
if pred name v then
[
(nameValuePair name (if isAttrs v then filterAttrsRecursive pred v else v))
]
else
[ ]
) (attrNames set)
);
/**
Like [`lib.lists.foldl'`](#function-library-lib.lists.foldl-prime) but for attribute sets.
@@ -1515,7 +1528,12 @@ rec {
*/
zipAttrsWithNames =
names: f: sets:
genAttrs names (name: f name (catAttrs name sets));
listToAttrs (
map (name: {
inherit name;
value = f name (catAttrs name sets);
}) names
);
/**
Merge sets of attributes and use the function `f` to merge attribute values.

View File

@@ -341,8 +341,9 @@ rec {
f: g: final: prev:
let
fApplied = f final prev;
prev' = prev // fApplied;
in
fApplied // g final (prev // fApplied);
fApplied // g final prev';
/**
Composes a list of [`overlays`](#chap-overlays) and returns a single overlay function that combines them.
@@ -408,7 +409,7 @@ rec {
```
:::
*/
composeManyExtensions = lib.foldr composeExtensions (final: prev: { });
composeManyExtensions = lib.foldr (x: y: composeExtensions x y) (final: prev: { });
/**
Create an overridable, recursive attribute set. For example:
@@ -509,16 +510,13 @@ rec {
:::
*/
toExtension =
let
inherit (lib) isFunction;
in
f:
if isFunction f then
if lib.isFunction f then
final: prev:
let
fPrev = f prev;
in
if isFunction fPrev then
if lib.isFunction fPrev then
# f is (final: prev: { ... })
f final prev
else

View File

@@ -703,6 +703,11 @@ lib.mapAttrs mkLicense (
url = "https://www.schristiancollins.com/generaluser.php"; # license included in sources
};
gfl = {
fullName = "GUST Font License";
url = "https://www.gust.org.pl/projects/e-foundry/licenses/GUST-FONT-LICENSE.txt";
};
gfsl = {
fullName = "GUST Font Source License";
url = "https://www.gust.org.pl/projects/e-foundry/licenses/GUST-FONT-SOURCE-LICENSE.txt";

View File

@@ -1595,76 +1595,17 @@ let
*/
mkDefinition = args@{ file, value, ... }: args // { _type = "definition"; };
/**
Labels a definition with a priority.
See the documentation of `filterOverrides` for the interpretation of the priority value.
Nesting this function usually leads to an invalid definition.
`mkDefault`, `mkOptionDefault`, and `mkForce` partially apply `mkOverride` with common priorities used in the NixOS module system.
# Inputs
`priority`
: A numeric value representing the precedence.
See the documentation of `filterOverrides` for the interpretation of this value.
`content`
: The definition to be labeled with a given priority.
# Examples
:::{.example}
## `lib.modules.mkOverride` usage example
```nix
mkOverride 1000 "hello, world!"
=> { _type = "override"; content = "hello, world!"; priority = 1000; }
```
```nix
(lib.evalModules {
modules = [
{ options.foo = lib.mkOption { }; }
{ config.foo = lib.mkOverride 20 1; }
{ config.foo = lib.mkOverride 10 2; }
];
}).config
=> { foo = 2; }
```
:::
*/
mkOverride = priority: content: {
_type = "override";
inherit priority content;
};
/**
Labels a definition with the priority of option declaration defaults.
*/
mkOptionDefault = mkOverride 1500;
/**
Labels a definition with the priority used in config sections of non-user modules to set a default.
*/
mkDefault = mkOverride 1000;
mkOptionDefault = mkOverride 1500; # priority of option defaults
mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default
defaultOverridePriority = 100;
/**
Labels a definition with the priority used in image media profiles.
Image media profiles can be derived by inclusion into host config, hence needing to override host config, but do allow users to `mkForce`.
*/
mkImageMediaOverride = mkOverride 60;
/**
Labels a definition with a high priority (low value).
*/
mkImageMediaOverride = mkOverride 60; # image media profiles can be derived by inclusion into host config, hence needing to override host config, but do allow user to mkForce
mkForce = mkOverride 50;
/**
Labels a definition with used by {command}`nixos-rebuild build-vm`.
*/
mkVMOverride = mkOverride 10;
mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’
mkFixStrictness = warn "lib.mkFixStrictness has no effect and will be removed. It returns its argument unmodified, so you can just remove any calls." id;

View File

@@ -719,26 +719,6 @@ let
else
null;
};
swift = {
arch = final.uname.processor;
platform =
if final.isMacOS then
"macosx"
else if final.isiOS then
"iphoneos"
else if final.isLinux then
"linux"
else if final.isWindows then
"windows"
else
null;
triple =
if final.isDarwin then
# FIXME: Can this be done a better way?
"${final.swift.arch}-${final.parsed.vendor.name}-${final.swift.platform}${final.darwinMinVersion}"
else
final.config;
};
};
in
# Platforms elaborated by pre-26.11 Nixpkgs will include the `linux-kernel` attr,

View File

@@ -445,7 +445,8 @@
"id": 4020424,
"maintainers": {
"Mic92": 96200,
"kalbasit": 87115
"kalbasit": 87115,
"katexochen": 49727155
},
"members": {
"mfrw": 4929861,

View File

@@ -251,7 +251,7 @@
};
_365tuwe = {
name = "Uwe Schlifkowitz";
email = "uwe.schlifkowitz@secunet.com";
email = "supertuwe@gmail.com";
github = "365tuwe";
githubId = 10263091;
};
@@ -460,7 +460,6 @@
name = "aaravrav";
github = "aaravrav";
githubId = 37036762;
matrix = "@hepara:matrix.org";
};
aarnphm = {
email = "contact@aarnphm.xyz";
@@ -5091,12 +5090,6 @@
githubId = 1689801;
name = "Mikhail Chekan";
};
chemonke = {
email = "nixpkgs@chemonke.ch";
github = "chemonke";
githubId = 183837749;
name = "Curdin Bosshart";
};
chen = {
email = "i@cuichen.cc";
github = "cu1ch3n";
@@ -9520,12 +9513,6 @@
github = "fkautz";
githubId = 135706;
};
fkokosinski = {
name = "Filip Kokosiński";
email = "filip@kokosinski.me";
github = "fkokosinski";
githubId = 19800410;
};
fkomarek = {
name = "Filip Komárek";
github = "filip2cz";
@@ -10818,12 +10805,6 @@
githubId = 273582;
name = "greg";
};
gregl83 = {
email = "general+nixpkgs@gregorylanglais.com";
github = "gregl83";
githubId = 1258023;
name = "gregory langlais";
};
gregshuflin = {
email = "greg@everdayimshuflin.com";
github = "neunenak";
@@ -11532,13 +11513,6 @@
githubId = 58676303;
name = "hhydraa";
};
hideyosh1 = {
email = "penelope.zhong@proton.me";
keys = [ { fingerprint = "01E9 0D3E 815F 84CA 1003 E7D7 2F75 2D18 C2C1 7AF8"; } ];
name = "Penelope Zhong";
github = "hideyosh1";
githubId = 64223175;
};
higebu = {
name = "Yuya Kusakabe";
email = "yuya.kusakabe@gmail.com";
@@ -12169,12 +12143,6 @@
githubId = 71074737;
name = "Simon Wick";
};
ilovelinux = {
email = "nix+nixpkgs@ilovelinux.dev";
github = "ilovelinux";
githubId = 9268789;
name = "Antonio Spadaro";
};
ilya-epifanov = {
email = "mail@ilya.network";
github = "ilya-epifanov";
@@ -12669,12 +12637,6 @@
github = "j0hax";
githubId = 3802620;
};
j0schu = {
name = "Jonas";
email = "Joschu2015@t-online.de";
github = "J0schu";
githubId = 56407950;
};
j0xaf = {
email = "j0xaf@j0xaf.de";
name = "Jörn Gersdorf";
@@ -13191,13 +13153,6 @@
githubId = 2377;
name = "Jonathan del Strother";
};
jderrac = {
email = "jeremy@derrac.fr";
github = "jderrac";
githubId = 1788613;
name = "Jérémy Derrac";
keys = [ { fingerprint = "7B18 DA58 169F AEB8 6826 D1D6 BED4 91C6 40AB 31DD"; } ];
};
jdev082 = {
email = "jdev0894@gmail.com";
github = "jdev082";
@@ -13668,12 +13623,6 @@
githubId = 474643;
name = "José Miguel Martínez Carrasco";
};
jm5905938 = {
email = "jm5905938@gmail.com";
github = "jm5905938";
githubId = 187073435;
name = "Aveline Noir";
};
jmagnusj = {
email = "jmagnusj@gmail.com";
github = "magnusjonsson";
@@ -14138,12 +14087,6 @@
github = "jooooscha";
githubId = 57965027;
};
joseg313 = {
name = "Jose Garcia";
email = "501jag3@gmail.com";
github = "joseg313";
githubId = 215610619;
};
josephschmitt = {
name = "Joseph Schmitt";
email = "dev@joe.sh";
@@ -15364,13 +15307,6 @@
githubId = 231780064;
name = "Klea";
};
kleiner3 = {
name = "kleiner3";
email = "nixos@dasriley.de";
github = "kleiner3";
githubId = 49880817;
matrix = "@riley:catgirl.industries";
};
klntsky = {
email = "klntsky@gmail.com";
name = "Vladimir Kalnitsky";
@@ -17645,12 +17581,6 @@
githubId = 85435692;
name = "Maxwell Berg";
};
Mahdi-zarei = {
email = "mahdi.zrei@gmail.com";
github = "Mahdi-zarei";
githubId = 80265960;
name = "Mahdi";
};
mahe = {
email = "matthias.mh.herrmann@gmail.com";
github = "2chilled";
@@ -18798,13 +18728,6 @@
github = "mfairley";
githubId = 4374785;
};
mfocko = {
name = "Matej Focko";
github = "mfocko";
githubId = 8149784;
email = "me@mfocko.xyz";
matrix = "@mfocko:fedora.im";
};
mfossen = {
email = "msfossen@gmail.com";
github = "mfossen";
@@ -20171,12 +20094,6 @@
githubId = 52401682;
name = "myul";
};
Myxogastria0808 = {
email = "r.rstudio.c@gmail.com";
github = "Myxogastria0808";
githubId = 78744619;
name = "Yuki Osada";
};
myypo = {
email = "nikirsmcgl@gmail.com";
github = "myypo";
@@ -26049,12 +25966,6 @@
githubId = 11632726;
name = "Arijit Basu";
};
saylesss88 = {
email = "saylesss87@proton.me";
github = "saylesss88";
githubId = 209646716;
name = "T. Sawyer";
};
sb0 = {
email = "sb@m-labs.hk";
github = "sbourdeauducq";
@@ -26770,11 +26681,6 @@
github = "shimunn";
githubId = 41011289;
};
shinbunbun = {
name = "shinbunbun";
github = "shinbunbun";
githubId = 34409044;
};
shiphan = {
email = "timlin940511@gmail.com";
name = "Shiphan";
@@ -29072,13 +28978,6 @@
github = "thelissimus";
githubId = 70096720;
};
thelolcoder2007 = {
name = "thelolcoder2007";
github = "thelolcoder2007";
githubId = 52106896;
matrix = "@erents:dapperepoging.nl";
keys = [ { fingerprint = "E374 815F C754 462B 1C34 3562 FDC3 99DE 8F7E 200B"; } ];
};
themadbit = {
name = "Mark Tanui";
email = "marktanui75@gmail.com";
@@ -31672,10 +31571,10 @@
];
};
wrench-exile-legacy = {
email = "hello@wrenchd.dev";
email = "user@wrench-exile-legacy.site";
github = "wrench-exile-legacy";
githubId = 280737824;
name = "wrenchd";
name = "wrench";
};
wrmilling = {
name = "Winston R. Milling";

View File

@@ -1,39 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash
#!nix-shell -p jq git
# shellcheck shell=bash
#
# Usage: eval-pkg-sets.sh [extra flags for nix-* commands ...]
#
# Must be executed in a git checkout of Nixpkgs.
set -euo pipefail
NIXPKGS="$(git rev-parse --show-toplevel)"
PKGSETS="$(nix-env --readonly-mode --json --drv-path -f "$NIXPKGS" -qaP -A haskell.compiler "$@" \
| jq -r 'to_entries | unique_by(.value.drvPath) .[] .key | sub("^haskell.compiler";"haskell.packages")')"
trap 'exit 1' SIGINT SIGTERM
set +e
badsets=""
for set in $PKGSETS; do
# Confirm an equivalent package set to haskell.compiler.$entry exists and is usable
if ! nix-instantiate --readonly-mode -A "$set.ghc" "$@" > /dev/null 2>&1; then
echo "Skipping $set... ($set.ghc does not evaluate)"
else
echo "Evaluating $set..."
if ! nix-env --readonly-mode -f "$NIXPKGS" -qaP --drv-path -A "$set" "$@" > /dev/null; then
badsets+="$set "
fi
fi
done
if [ -n "$badsets" ]; then
echo "Found potential eval issues in the following sets:" >&2
# shellcheck disable=SC2086
printf '%s\n' $badsets
exit 1
fi

View File

@@ -6,7 +6,7 @@
# Attention: For unknown reasons, the script can't be easily cancelled and needs to be killed manually if it shouldn't run to completion.
use std/log
use std log
let broken_config = "pkgs/development/haskell-modules/configuration-hackage2nix/broken.yaml"

View File

@@ -33,7 +33,7 @@ fi
# Stackage solver to use, LTS or Nightly
# (should be capitalized like the display name)
SOLVER=Nightly
SOLVER=LTS
# Stackage solver version, if any. Use latest if empty
VERSION=
TMP_TEMPLATE=update-stackage.XXXXXXX
@@ -105,7 +105,6 @@ sed -r \
-e '/ hledger-ui /d' \
-e '/ hledger-web /d' \
-e '/ spacecookie /d' \
-e '/ hnix-store-core /d' \
< "${tmpfile_new}" >> $stackage_config
# Explanations:
# cabal2nix, distribution-nixpkgs, jailbreak-cabal, language-nix: These are our packages and we know what we are doing.

View File

@@ -108,6 +108,7 @@ with lib.maintainers;
members = [
lopsided98
mic92
zowoq
];
scope = "Maintain Buildbot CI framework";
shortName = "Buildbot";
@@ -750,7 +751,6 @@ with lib.maintainers;
swift = {
members = [
reckenrode
samasaur
stephank
];

View File

@@ -207,8 +207,6 @@
- The `jetty_11` package has been removed as it reached end of life. Use `jetty_12` instead.
- The postsrsd module now supports integrating with Postfix as a milter. The [](#opt-services.postsrsd.configurePostfix) option has become an enum to reflect the different integration options. Boolean values are deprecated and will be removed in NixOS 27.05. The previous default `true` is equivalent to `socketmap`.
- The Mullvad VPN service now has a separate toggle to enable the Mullvad VPN graphical user interface. If you have previously used Mullvad on a desktop by setting `services.mullvad-vpn.package` to `pkgs.mullvad-vpn`, you should now **unset that option**, and enable `services.mullvad-vpn.gui.enable`. The VPN will not work if `services.mullvad-vpn.package` is set to `pkgs.mullvad-vpn`, as `pkgs.mullvad-vpn` no longer contains the Mullvad Daemon; please ensure that `services.mullvad-vpn.package` is set to `pkgs.mullvad`, regardless if you plan to enable the graphical user interface or not.
- TUI command of `tracexec` now allocates a pseudo terminal by default. Use `--no-tty` to run without one and redirect the tracee's stdin, stdout, and
@@ -304,11 +302,9 @@
- `chatgpt` has been retargeted to OpenAI's new ChatGPT desktop app, while the previous app has been renamed to `chatgpt-classic`.
- NetBox was updated to `>= 4.7.0`. Have a look at the breaking changes
of the [4.6 release](https://github.com/netbox-community/netbox/releases/tag/v4.6.0)
and the [4.7 release](https://github.com/netbox-community/netbox/releases/tag/v4.7.0),
make the required changes to your configuration and database, if needed,
before you upgrade to NixOS 26.11.
- NetBox was updated to `>= 4.6.8`. Have a look at the breaking changes
of the [4.6 release](https://github.com/netbox-community/netbox/releases/tag/v4.6.0),
make the required changes to your database, if needed, then upgrade by setting `services.netbox.package = pkgs.netbox_4_6;` in your configuration.
- The COSMIC desktop module now enables by default `system76-power` and `system76-scheduler` following upstream recommended packages. The previous power managment service can be enabled back by setting `services.power-profiles-daemon.enable = true`.
@@ -334,41 +330,6 @@
- The `shell_interact()` function on interactive runs of NixOS VM tests has been deprecated. Use the SSH backdoor instead.
- The {option}`programs.fish.shellFunctions` option can now be used to create custom fish functions in a structured manner, as opposed to concatenating strings with {option}`program.fish.interactiveShellInit`.
:::{.example}
# Migrating fish functions to `programs.fish.shellFunctions`
Custom fish functions have historically been defined like so:
```nix
{
programs.fish.interactiveShellInit = ''
function backup --argument filename --description "Creates a backup copy of a file in the current directory."
cp $filename $filename.bak
end
'';
}
```
The above example can be migrated via the following structured code block:
```nix
{
programs.fish.shellFunctions = {
backup = {
modifiers = {
description = "Creates a backup copy of a file in the current directory.";
argument = "filename";
};
body = ''
cp $filename $filename.bak
'';
};
};
}
```
:::
- NixOS VM tests now prefer to express durations and timeouts as `datetime.timedelta` values instead of bare numbers. Methods such as `machine.wait_until_succeeds`, `machine.sleep`, `retry`, and `polling_condition` now accept a `timedelta` (e.g., `machine.wait_for_unit("sshd.service", timeout=datetime.timedelta(minutes=1))`). Passing an `int`/`float` as seconds still works but now emits a deprecation warning. Argument names that explicitly defined units were preserved but have had `timedelta` equivalents introduced (`timeout_seconds` → `timeout`, `secs` → `duration`, `seconds_interval` → `interval`).
- `darwin.linux-builder-vz` has been added: a variant of `darwin.linux-builder` that runs the builder guest on Apple's Virtualization.framework via the new `vzvm` package, translating `x86_64-linux` builds with Rosetta instead of emulating them. Apple silicon hosts only. As part of this, the `nixos/modules/profiles/nix-builder-vm.nix` profile has been split into the backend-neutral `nixos/modules/profiles/nix-builder.nix` and a QEMU-specific part. Existing imports of `nix-builder-vm.nix` keep working unchanged.
@@ -395,8 +356,6 @@ The above example can be migrated via the following structured code block:
- `boot.loader.systemd-boot` gained support for [Automatic Boot Assessment](https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT/) via the new [`boot.loader.systemd-boot.bootCounting`](#opt-boot.loader.systemd-boot.bootCounting.enable) options, allowing automatic detection of and recovery from bad NixOS generations. As part of this change, boot loader entries on the ESP/XBOOTLDR partition are now named `nixos-<content-hash>.conf` instead of `nixos-generation-<n>.conf`; existing entries are migrated automatically on the next `nixos-rebuild boot`/`switch`.
- The Wordpress module now supports auto database migrations using wp-cli, which gets triggered after every package version update.
- `services.nginx` gained a [`lua`](#opt-services.nginx.lua.enable) option to enable Lua scripting via OpenResty's lua-nginx-module on a stock nginx, configuring `lua_package_path`/`lua_package_cpath` from the packages listed in [`services.nginx.lua.extraPackages`](#opt-services.nginx.lua.extraPackages). Use this to add Lua to a regular nginx; for the full OpenResty platform (libraries that rely on its bundled lualib, such as `lua-resty-openidc`), set `services.nginx.package` to `pkgs.openresty` instead — the option configures the Lua search path for it too.
- `services.nginx.virtualHosts.<name>.locations.<name>` gained a new `useGrpcErrorPages` option. If enabled, it sets up error pages that are valid gRPC messages. This is useful if you proxy gRPC and want to emit errors from nginx, for example when adding authentication on top.

View File

@@ -335,7 +335,7 @@ class BaseMachine(ABC):
...
@abstractmethod
def wait_for_shutdown(self, timeout: dt.timedelta | None = None) -> None:
def wait_for_shutdown(self) -> None:
"""Wait for the machine to power off. This does *not* initiate a shutdown;
that's usually done via `shutdown()`.
"""
@@ -1061,7 +1061,7 @@ class QemuMachine(BaseMachine):
break
self.send_console(char.decode())
def wait_for_shutdown(self, timeout: dt.timedelta | None = None) -> None:
def wait_for_shutdown(self) -> None:
"""
Wait for the VM to power off. This does *not* initiate a shutdown;
that's usually done via `shutdown()`.
@@ -1072,9 +1072,7 @@ class QemuMachine(BaseMachine):
with self.nested("waiting for the VM to power off"):
sys.stdout.flush()
assert self.process
self.process.wait(
timeout=timeout.total_seconds() if timeout is not None else None
)
self.process.wait()
self.pid = None
self.booted = False
@@ -1905,7 +1903,7 @@ class NspawnMachine(BaseMachine):
self.systemctl("poweroff")
self.wait_for_shutdown()
def wait_for_shutdown(self, timeout: dt.timedelta | None = None) -> None:
def wait_for_shutdown(self) -> None:
"""
Wait for the container to power off. This does *not* initiate a shutdown;
that's usually done via `shutdown()`.
@@ -1914,9 +1912,7 @@ class NspawnMachine(BaseMachine):
return
with self.nested("waiting for the container to power off"):
self.process.wait(
timeout=timeout.total_seconds() if timeout is not None else None
)
self.process.wait()
self.process = None

View File

@@ -42,7 +42,7 @@ in
};
};
settings.nix-path = mkOption {
nixPath = mkOption {
type = types.listOf types.str;
default =
if cfg.channel.enable then
@@ -80,11 +80,8 @@ in
};
};
imports = [
(lib.mkRenamedOptionModule [ "nix" "nixPath" ] [ "nix" "settings" "nix-path" ])
];
config = mkIf cfg.enable {
environment.extraInit = mkIf cfg.channel.enable ''
if [ -e "$HOME/.nix-defexpr/channels" ]; then
export NIX_PATH="$HOME/.nix-defexpr/channels''${NIX_PATH:+:$NIX_PATH}"
@@ -98,7 +95,7 @@ in
# NIX_PATH has a non-empty default according to Nix docs, so we don't unset
# it when empty.
environment.sessionVariables = {
NIX_PATH = cfg.settings.nix-path;
NIX_PATH = cfg.nixPath;
};
systemd.tmpfiles.rules = lib.mkIf cfg.channel.enable [

View File

@@ -63,7 +63,7 @@ in
default = false;
description = ''
Use the Wayland input method frontend.
This doesn't set `GTK_IM_MODULE` and `QT_IM_MODULE` environment variables.
This doesn't set `QT_IM_MODULE` environment variable.
See [Using Fcitx 5 on Wayland](https://fcitx-im.org/wiki/Using_Fcitx_5_on_Wayland#GTK_IM_MODULE).
'';
};
@@ -90,7 +90,6 @@ in
XMODIFIERS = "@im=ibus";
}
// lib.optionalAttrs (!cfg.waylandFrontend) {
GTK_IM_MODULE = "ibus";
QT_IM_MODULE = "ibus";
};

View File

@@ -72,20 +72,6 @@ $ nixos-version --configuration-revision
aa314ebd1592f6cdd53cb5bba8bcae97d9323de8
.Ed
.
.It Fl -kernel-version
Show the kernel version, e.g.
.Bd -literal -offset indent
$ nixos-version --kernel-version
7.2.5
.Ed
.
.It Fl -specialisations
Show specialisations, separated by spaces, if available, e.g.
.Bd -literal -offset indent
$ nixos-version --specialisations
foo bar
.Ed
.
.It Fl -json
Print a JSON representation of the versions of NixOS and the top-level
configuration flake.

View File

@@ -20,23 +20,8 @@ case "$1" in
fi
echo "@configurationRevision@"
;;
--kernel-version)
if [[ "@kernelVersion@" =~ "@" ]]; then
echo "$0: kernel version is unknown" >&2
exit 1
fi
echo "@kernelVersion@"
;;
--specialisations)
specialisations=@specialisations@
if [[ -z "$specialisations" ]]; then
echo "$0: no specialisations found" >&2
exit 1
fi
printf '%s\n' "$specialisations"
;;
--json)
cat <<'EOF'
cat <<EOF
@json@
EOF
;;

View File

@@ -53,27 +53,13 @@ let
nixos-version = makeProg {
name = "nixos-version";
src = ./nixos-version.sh;
replacements = rec {
replacements = {
inherit (pkgs) runtimeShell;
inherit (config.system.nixos) version codeName revision;
inherit (config.system) configurationRevision;
kernelVersion =
if config.boot.kernel.enable then
# modDirVersion returns 6.18.54-xanmod1 instead of 6.18.54
config.boot.kernelPackages.kernel.modDirVersion or config.boot.kernelPackages.kernel.version
else
null;
specialisations = lib.escapeShellArg (
lib.concatStringsSep " " (lib.attrNames config.specialisation)
);
json = builtins.toJSON (
{
nixosVersion = config.system.nixos.version;
specialisations = lib.attrNames config.specialisation;
}
// lib.optionalAttrs (kernelVersion != null) {
inherit kernelVersion;
}
// lib.optionalAttrs (config.system.nixos.revision != null) {
nixpkgsRevision = config.system.nixos.revision;
@@ -306,7 +292,7 @@ in
{
options.system.tools.${name}.enable = lib.mkEnableOption "${name} script" // {
default = config.nix.enable && !config.system.disableInstallerTools;
defaultText = lib.literalExpression "config.nix.enable && !config.system.disableInstallerTools";
defaultText = "config.nix.enable && !config.system.disableInstallerTools";
};
config = lib.mkIf config.system.tools.${name}.enable {

View File

@@ -102,7 +102,7 @@ in
# because we would need some kind of evil shim taking the *calling* flake's self path,
# perhaps, to ever make that work (in order to know where the Nix expr for the system came
# from and how to call it).
nix.settings.nix-path = lib.mkDefault (
nix.nixPath = lib.mkDefault (
[ "nixpkgs=flake:nixpkgs" ]
++ lib.optional config.nix.channel.enable "/nix/var/nix/profiles/per-user/root/channels"
);

View File

@@ -1258,6 +1258,7 @@
./services/networking/gnunet.nix
./services/networking/go-autoconfig.nix
./services/networking/go-camo.nix
./services/networking/go-neb.nix
./services/networking/go-shadowsocks2.nix
./services/networking/gobgpd.nix
./services/networking/godns.nix

View File

@@ -61,10 +61,11 @@ in
#!${pkgs.runtimeShell}
# Import environment variables
${cfg.extraSessionCommands}
# Start dwl, then set up the systemd user environment once dwl
# has actually set WAYLAND_DISPLAY (see dwl(1) -s), instead of
# importing it before dwl exists.
exec ${lib.getExe cfg.package} -s "systemctl --user import-environment DISPLAY WAYLAND_DISPLAY; systemctl --user start dwl-session.target"
# Setup systemd user environment
systemctl --user import-environment DISPLAY WAYLAND_DISPLAY
systemctl --user start dwl-session.target
# Start dwl
exec ${lib.getExe cfg.package}
'';
mode = "0755"; # Make it executable
};

View File

@@ -33,13 +33,7 @@ in
config = lib.mkIf cfg.enable (
lib.mkMerge [
{
environment.systemPackages = [
cfg.package
];
# Needed to add the freedesktop sound theme
# It's only a runtime dependency for noctalia, so it's not made a package dependency.
xdg.sounds.enable = true;
environment.systemPackages = [ cfg.package ];
systemd.user.services.noctalia = lib.mkIf cfg.systemd.enable {
description = "Noctalia Wayland desktop shell";

View File

@@ -486,10 +486,6 @@ in
See https://www.isc.org/blogs/isc-dhcp-eol/ for details.
Please switch to a different implementation like kea or dnsmasq.
'')
(lib.mkRemovedOptionModule [ "services" "go-neb" ] ''
The Go-NEB project was discontinued by Matrix.org and archived in June
2023. Use matrix-hookshot or another maintained Matrix bot instead.
'')
(mkRemovedOptionModule [ "services" "gsignond" ] ''
The corresponding package was unmaintained, abandoned upstream, used outdated library and thus removed from nixpkgs.
'')

View File

@@ -51,10 +51,7 @@ in
sockets.pwupdd.wantedBy = lib.optional config.users.mutableUsers "sockets.target"; # immutable users do not need password updating
sockets.newidmapd.wantedBy = [ "sockets.target" ];
services."pwupdd@".environment.PWUPDD_OPTS = lib.escapeShellArgs cfg.extraArgs;
services."pwaccessd".environment = {
LD_LIBRARY_PATH = config.system.nssModules.path;
PWACCESSD_OPTS = lib.escapeShellArgs cfg.extraArgs;
};
services."pwaccessd".environment.PWACCESSD_OPTS = lib.escapeShellArgs cfg.extraArgs;
};
environment.systemPackages = [ cfg.package ];

View File

@@ -48,7 +48,6 @@
# Accounts daemon looks for dbus interfaces in $XDG_DATA_DIRS/accountsservice
environment.XDG_DATA_DIRS = "${config.system.path}/share";
environment.LD_LIBRARY_PATH = config.system.nssModules.path;
}
(

View File

@@ -90,6 +90,8 @@ let
}) cfg.sieve.pipeBins
);
yesOrNo = v: if v then "yes" else "no";
toOption =
i: n: v:
"${i}${toString n} = ${v}";
@@ -101,7 +103,7 @@ let
if isInt v then
toString v
else if isBool v then
lib.boolToYesNo v
yesOrNo v
else if isString v then
v
else if isPath v || isDerivation v then

View File

@@ -6,6 +6,8 @@
}:
let
concatMapLines = f: l: lib.concatStringsSep "\n" (map f l);
cfg = config.services.mlmmj;
stateDir = "/var/lib/mlmmj";
spoolDir = "/var/spool/mlmmj";
@@ -139,10 +141,10 @@ in
];
};
extraAliases = lib.concatMapStringsSep "\n" (alias cfg.listDomain) cfg.mailLists;
extraAliases = concatMapLines (alias cfg.listDomain) cfg.mailLists;
virtual = lib.concatMapStringsSep "\n" (virtual cfg.listDomain) cfg.mailLists;
transport = lib.concatMapStringsSep "\n" (transport cfg.listDomain) cfg.mailLists;
virtual = concatMapLines (virtual cfg.listDomain) cfg.mailLists;
transport = concatMapLines (transport cfg.listDomain) cfg.mailLists;
};
environment.systemPackages = [ pkgs.mlmmj ];
@@ -163,7 +165,7 @@ in
ExecStart = "${pkgs.mlmmj}/bin/mlmmj-maintd -F -d ${spoolDir}/${cfg.listDomain}";
};
preStart = ''
${lib.concatMapStringsSep "\n" (createList cfg.listDomain) cfg.mailLists}
${concatMapLines (createList cfg.listDomain) cfg.mailLists}
${lib.getExe' config.services.postfix.package "postmap"} /etc/postfix/virtual
${lib.getExe' config.services.postfix.package "postmap"} /etc/postfix/transport
'';

View File

@@ -244,6 +244,8 @@ in
"noroot"
"noroot-locked"
];
RuntimeDirectory = "postfix-tlspol";
RuntimeDirectoryMode = "1750";
WorkingDirectory = "/var/cache/postfix-tlspol";
UMask = "0077";
};

View File

@@ -46,14 +46,6 @@ let
configFile = pkgs.writeText "postsrsd.conf" (
renderAttr (lib.filterAttrsRecursive (_: v: v != null) cfg.settings)
);
postfixIntegration =
if cfg.configurePostfix == true then
"socketmap"
else if cfg.configurePostfix == false then
"none"
else
cfg.configurePostfix;
in
{
imports = [
@@ -130,15 +122,6 @@ in
'';
};
milter = lib.mkOption {
type = with lib.types; nullOr (strMatching "^(unix|inet):.+");
default = "unix:/run/postsrsd/milter";
example = "inet:localhost:9997";
description = ''
Milter listener configuration in `unix:/path/to/socket` or `inet:host:port` format.
'';
};
secrets-file = lib.mkOption {
type = lib.types.str;
default = "\${CREDENTIALS_DIRECTORY}/secrets-file";
@@ -185,11 +168,11 @@ in
};
socketmap = lib.mkOption {
type = with lib.types; nullOr (strMatching "^(unix|inet):.+");
default = "unix:/run/postsrsd/socketmap";
type = lib.types.strMatching "^(unix|inet):.+";
default = "unix:/run/postsrsd/socket";
example = "inet:localhost:10003";
description = ''
Socketmap listener configuration in `unix:/path/to/socket` or `inet:host:port` format.
Listener configuration in socket map format native to Postfix configuration.
'';
};
@@ -229,23 +212,10 @@ in
};
configurePostfix = lib.mkOption {
type = lib.types.enum [
true
false
"none"
"socketmap"
"milter"
];
default = "socketmap";
example = "milter";
type = lib.types.bool;
default = true;
description = ''
Whether and how to integrate postsrsd into the local Postfix instance.
::: {.caution}
Boolean values are deprecated and retained for backwards
compatibility. `true` is equivalent to `socketmap`, and `false` is
equivalent to `none`.
:::
Whether to configure the required settings to use postsrsd in the local Postfix instance.
'';
};
@@ -264,41 +234,17 @@ in
};
config = lib.mkMerge [
{
warnings = lib.optionals (cfg.enable && isBool cfg.configurePostfix) [
''
Boolean values are deprecated for `services.postsrsd.configurePostfix` and will be rejected in NixOS 27.05.
Use `none`, `socketmap`, or `milter` instead. `true` is equivalent to `socketmap` and `false` is equivalent to `none`.
''
];
}
(lib.mkIf (cfg.enable && postfixIntegration != "none" && config.services.postfix.enable) {
assertions = [
{
assertion = postfixIntegration == "milter" -> cfg.settings.milter != null;
message = "Configuring Postfix `smtpd_milters` requires `services.postsrsd.settings.milter` to be set.";
}
{
assertion = postfixIntegration == "socketmap" -> cfg.settings.socketmap != null;
message = "Configuring Postfix canonical maps requires `services.postsrsd.settings.socketmap` to be set.";
}
];
services.postfix.settings.main =
lib.optionalAttrs (postfixIntegration == "socketmap") {
# https://github.com/roehling/postsrsd#configuration
sender_canonical_maps = "socketmap:${cfg.settings.socketmap}:forward";
sender_canonical_classes = "envelope_sender";
recipient_canonical_maps = "socketmap:${cfg.settings.socketmap}:reverse";
recipient_canonical_classes = [
"envelope_recipient"
"header_recipient"
];
}
// lib.optionalAttrs (postfixIntegration == "milter") {
# https://github.com/roehling/postsrsd/tree/main#milter-support
smtpd_milters = [ cfg.settings.milter ];
};
(lib.mkIf (cfg.enable && cfg.configurePostfix && config.services.postfix.enable) {
services.postfix.settings.main = {
# https://github.com/roehling/postsrsd#configuration
sender_canonical_maps = "socketmap:${cfg.settings.socketmap}:forward";
sender_canonical_classes = "envelope_sender";
recipient_canonical_maps = "socketmap:${cfg.settings.socketmap}:reverse";
recipient_canonical_classes = [
"envelope_recipient"
"header_recipient"
];
};
users.users.postfix.extraGroups = [ cfg.group ];
})

View File

@@ -20,16 +20,11 @@ let
rawHomeserverUrl = cfg.homeserverUrl;
pantalaimon = {
use = cfg.pantalaimon.enable;
}
// lib.optionalAttrs cfg.pantalaimon.enable {
inherit (cfg.pantalaimon) username;
use = cfg.pantalaimon.enable;
password = "@PANTALAIMON_PASSWORD@"; # will be replaced in "generateConfig"
};
encryption = {
inherit (cfg.settings.encryption) username;
password = "@ENCRYPTION_PASSWORD@"; # will be replaced in "generateConfig"
};
};
moduleConfigFile = pkgs.writeText "module-config.yaml" (
@@ -77,9 +72,6 @@ let
${lib.optionalString (cfg.pantalaimon.passwordFile != null) ''
${pkgs.replace-secret}/bin/replace-secret '@PANTALAIMON_PASSWORD@' '${cfg.pantalaimon.passwordFile}' ${cfg.dataPath}/config/default.yaml
''}
${lib.optionalString (cfg.encryption.passwordFile != null) ''
${pkgs.replace-secret}/bin/replace-secret '@ENCRYPTION_PASSWORD@' '${cfg.encryption.passwordFile}' ${cfg.dataPath}/config/default.yaml
''}
''
);
in
@@ -106,14 +98,6 @@ in
'';
};
encryption.passwordFile = lib.mkOption {
type = with lib.types; nullOr path;
default = null;
description = ''
File containing the matrix password for the `mjolnir` user.
'';
};
pantalaimon = lib.mkOption {
description = ''
`pantalaimon` options (enables E2E Encryption support).
@@ -202,22 +186,17 @@ in
config = lib.mkIf config.services.mjolnir.enable {
assertions = [
{
assertion = !(cfg.settings.encryption.use && cfg.encryption.passwordFile == null);
message = "encryption.passwordFile must be specified when native encryption is used.";
}
{
assertion = !(cfg.pantalaimon.enable && cfg.pantalaimon.passwordFile == null);
message = "pantalaimon.passwordFile must be specified when pantalaimon is enabled.";
message = "Specify pantalaimon.passwordFile";
}
{
assertion = cfg.accessTokenFile == null -> cfg.pantalaimon.enable || cfg.settings.encryption.use;
message = "Do not specify accessTokenFile when using native encryption or pantalaimon";
assertion = !(cfg.pantalaimon.enable && cfg.accessTokenFile != null);
message = "Do not specify accessTokenFile when using pantalaimon";
}
{
assertion =
!(!cfg.pantalaimon.enable && !cfg.settings.encryption.use && cfg.accessTokenFile == null);
message = "Specify accessTokenFile when not using pantalaimon or native encryption.";
assertion = !(!cfg.pantalaimon.enable && cfg.accessTokenFile == null);
message = "Specify accessTokenFile when not using pantalaimon";
}
];

View File

@@ -1,6 +1,6 @@
# CLIProxyAPI {#module-services-cliproxyapi}
[CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) exposes OAuth-based subscription CLIs (Claude Code, Codex, Grok, Antigravity, Kimi, Devin, Meta) behind OpenAI/Gemini/Anthropic-compatible HTTP APIs.
[CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) exposes OAuth-based subscription CLIs (Claude Code, Codex, Gemini, Qwen, Grok, Antigravity) behind OpenAI/Gemini/Anthropic-compatible HTTP APIs.
Enable it with:
@@ -10,11 +10,11 @@ Enable it with:
}
```
The service runs as a dedicated `cliproxyapi` user and keeps its configuration and OAuth tokens under `/var/lib/cliproxyapi`. The configuration file is regenerated from [](#opt-services.cliproxyapi.settings) at startup, which overwrites any changes made through the management API.
The service runs as a dedicated `cliproxyapi` user and keeps its configuration and OAuth tokens under `/var/lib/cliproxyapi`.
## Authentication {#module-services-cliproxyapi-authentication}
Provider logins use OAuth and must land in the service's `oauth.auth-dir` (`/var/lib/cliproxyapi`), which is owned by the `cliproxyapi` user. Either of the approaches below writes the token with the correct ownership, and the running service picks it up without a restart.
Provider logins use OAuth and must land in the service's `auth-dir` (`/var/lib/cliproxyapi`), which is owned by the `cliproxyapi` user. Either of the approaches below writes the token with the correct ownership, and the running service picks it up without a restart.
### Management API {#module-services-cliproxyapi-authentication-management-api}
@@ -22,31 +22,19 @@ Set a management key in [](#opt-services.cliproxyapi.settings):
```nix
{
services.cliproxyapi.settings.management.secret-key._secret = "/run/secrets/cliproxyapi-mgmt-key";
services.cliproxyapi.settings.remote-management.secret-key._secret =
"/run/secrets/cliproxyapi-mgmt-key";
}
```
Request a login URL and open it in a browser:
Then request an authentication URL for the desired provider and open it in a browser:
```bash
curl -H "Authorization: Bearer <management-key>" \
"http://127.0.0.1:8317/v8/management/oauth/auth-url?provider=claude"
http://127.0.0.1:8317/v0/management/anthropic-auth-url
```
Other values for `provider` are `codex`, `antigravity`, `kimi`, `kimi-ai`, `xai`, `devin` and `meta`. `kimi`, `kimi-ai`, `xai` and `meta` use a device code, so the login finishes once it is approved in the browser.
For `claude`, `codex` and `antigravity`, the browser ends up on a `localhost` page that fails to load. Send that URL to the daemon to finish the login:
```bash
curl -H "Authorization: Bearer <management-key>" \
-H "Content-Type: application/json" \
-d '{"redirect_url": "<url>"}' \
http://127.0.0.1:8317/v8/management/oauth/callback
```
Alternatively, add `is_webui=true` to the login URL request, and the daemon will listen on the callback port and finish the login itself.
To check on a login, query `/v8/management/oauth/status?state=<state>` with the `state` from the login URL response. It returns `wait` while the login is pending, `ok` once the token is saved and `error` if it failed.
The daemon completes the OAuth flow itself and stores the token in its `auth-dir`. Authentication endpoints are available for the `anthropic`, `codex`, `xai`, `antigravity`, and `kimi` providers.
### Command-line login {#module-services-cliproxyapi-authentication-cli}
@@ -64,4 +52,4 @@ Then run the login as the service user, pointing at the managed configuration:
sudo -u cliproxyapi cliproxyapi -config /var/lib/cliproxyapi/config.yaml --claude-login
```
Other providers have their own flags, such as `--codex-login` or `--xai-login`; see `cliproxyapi -help`. On a headless host, add `-no-browser` to print the login URL. The Claude, Codex, Antigravity and Devin logins then ask you to paste the `localhost` URL you were redirected to.
Other providers use their matching flags, for example `--codex-login` or `--xai-login`. On a headless host, pass `-no-browser` to print the OAuth URL instead of launching a browser.

View File

@@ -10,9 +10,14 @@ let
format = pkgs.formats.yaml { };
stateDir = "/var/lib/cliproxyapi";
configPath = "${stateDir}/config.yaml";
settings = {
auth-dir = stateDir;
}
// cfg.settings;
secretsReplacement = utils.genJqSecretsReplacement {
loadCredential = true;
} cfg.settings configPath;
} settings configPath;
port = cfg.settings.port or 8317;
in
{
options.services.cliproxyapi = {
@@ -21,30 +26,14 @@ in
package = lib.mkPackageOption pkgs "cliproxyapi" { };
settings = lib.mkOption {
type = lib.types.submodule {
freeformType = format.type;
options = {
server.port = lib.mkOption {
type = lib.types.port;
default = 8317;
description = "Port on which CLIProxyAPI listens.";
};
oauth.auth-dir = lib.mkOption {
type = lib.types.str;
default = stateDir;
description = "Directory where OAuth tokens are stored.";
};
};
};
type = format.type;
default = { };
example = lib.literalExpression ''
{
server = {
host = "127.0.0.1";
port = 8317;
};
access.api-keys = [ { _secret = "/run/secrets/cliproxyapi-api-key"; } ];
management.secret-key._secret = "/run/secrets/cliproxyapi-management-key";
host = "127.0.0.1";
port = 8317;
api-keys = [ { _secret = "/run/secrets/cliproxyapi-api-key"; } ];
remote-management.secret-key._secret = "/run/secrets/cliproxyapi-management-key";
}
'';
description = ''
@@ -65,7 +54,7 @@ in
openFirewall = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to open the firewall for {option}`services.cliproxyapi.settings.server.port`.";
description = "Whether to open the firewall for the specified port.";
};
user = lib.mkOption {
@@ -153,7 +142,7 @@ in
};
networking.firewall = lib.mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.settings.server.port ];
allowedTCPPorts = [ port ];
};
};

View File

@@ -38,8 +38,12 @@ let
PAPERLESS_REDIS = "unix://${redisServer.unixSocket}";
}
// lib.optionalAttrs (cfg.settings.PAPERLESS_AI_ENABLED or true) {
NLTK_DATA = cfg.package.nltkDataDir;
TIKTOKEN_CACHE_DIR = cfg.package.tiktokenCacheDir;
}
// lib.optionalAttrs (cfg.settings.PAPERLESS_ENABLE_NLTK or true) {
PAPERLESS_NLTK_DIR = cfg.package.nltkDataDir;
}
// lib.optionalAttrs (cfg.openMPThreadingWorkaround) {
OMP_NUM_THREADS = "1";
}
@@ -713,9 +717,7 @@ in
"d '${cfg.exporter.directory}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
];
services.paperless.exporter.settings = lib.mapAttrs (
_: v: lib.mkDefault v
) options.services.paperless.exporter.settings.default;
services.paperless.exporter.settings = options.services.paperless.exporter.settings.default;
systemd.services.paperless-exporter = {
startAt = lib.defaultTo [ ] cfg.exporter.onCalendar;

View File

@@ -6,76 +6,6 @@
}:
let
cfg = config.services.beszel.agent;
hasVideoDriver = driver: builtins.elem driver config.services.xserver.videoDrivers;
# Collector names must match `isValidCollectorSource` in upstream's agent/gpu.go.
# macmon and powermetrics are macOS-only and omitted here.
gpuCollectors = {
# read sysfs directly, need no package or device access
"amd_sysfs" = { };
"intel_sysfs" = { };
"intel_gpu_top" = {
package = lib.getBin pkgs.intel-gpu-tools;
deviceAllow = [ "char-drm rw" ];
capabilities = [ "CAP_PERFMON" ];
# perf_event_open is in @debug, not @system-service
systemCalls = [ "perf_event_open" ];
};
"nvidia-smi" = {
package = lib.getBin config.hardware.nvidia.package;
deviceAllow = [ "char-nvidia* rw" ];
};
"nvml" = {
deviceAllow = [ "char-nvidia* rw" ];
};
"nvtop" = {
package = lib.getBin pkgs.nvtopPackages.full;
deviceAllow = [
"char-nvidia* rw"
"char-drm rw"
];
};
"rocm-smi" = {
package = lib.getBin pkgs.rocmPackages.rocm-smi;
deviceAllow = [
"char-drm rw"
"char-kfd rw"
];
};
};
activeCollectors = lib.optionals (!cfg.environment.SKIP_GPU) cfg.environment.GPU_COLLECTOR;
collectorAttrs =
attr: lib.unique (lib.concatMap (name: gpuCollectors.${name}.${attr} or [ ]) activeCollectors);
gpuPackages = map (name: gpuCollectors.${name}.package) (
lib.filter (name: gpuCollectors.${name} ? package) activeCollectors
);
gpuNeedsDevices = collectorAttrs "deviceAllow" != [ ];
# capabilities granted under PrivateUsers are void on the host, see
# systemd.exec(5), so these collectors also need the user namespace disabled
gpuNeedsCapabilities = collectorAttrs "capabilities" != [ ];
# Any explicit DeviceAllow turns DevicePolicy=auto into an allow-list, so the GPU
# devices are omitted when smartmon relies on full /dev access.
deviceAllowList =
lib.optionals (cfg.smartmon.enable && cfg.smartmon.deviceAllow != [ ]) (
map (device: "${device} r") cfg.smartmon.deviceAllow
)
++ lib.optionals (!cfg.smartmon.enable || cfg.smartmon.deviceAllow != [ ]) (
collectorAttrs "deviceAllow" ++ lib.optionals config.boot.zfs.enabled [ "/dev/zfs rw" ]
);
serviceCapabilities =
lib.optionals cfg.smartmon.enable [
"CAP_SYS_RAWIO"
"CAP_SYS_ADMIN"
]
++ collectorAttrs "capabilities";
in
{
meta.maintainers = with lib.maintainers; [
@@ -130,45 +60,6 @@ in
Enabling this option will skip systemd tracking and its setup in NixOS.
'';
};
SKIP_GPU = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to disable GPU monitoring.
Enabling this option will skip GPU tracking.
'';
};
GPU_COLLECTOR = lib.mkOption {
# upstream takes a comma-separated string, which used to be passed through as is
type =
with lib.types;
coercedTo str (value: map lib.trim (lib.splitString "," value)) (
listOf (enum (lib.attrNames gpuCollectors))
);
default =
lib.optionals (hasVideoDriver "nvidia") [ "nvidia-smi" ]
++ lib.optionals (hasVideoDriver "amdgpu") [ "amd_sysfs" ]
++ lib.optionals (hasVideoDriver "intel") [ "intel_sysfs" ];
defaultText = lib.literalMD ''
derived from {option}`services.xserver.videoDrivers`
'';
example = [
"nvidia-smi"
"intel_gpu_top"
];
description = ''
GPU collectors to use, in priority order. Overrides the agent's
auto-detection; the packages needed by the selected collectors are added
to the service path. If empty, the agent auto-detects available
collectors. `rocm-smi` is deprecated upstream in favour of `amd_sysfs`,
and `intel_gpu_top` is not used on the xe driver, where `intel_sysfs` is
preferred.
Access to GPU device nodes is only granted for the collectors listed
here, so a collector provided through
{option}`services.beszel.agent.extraPath` has to be listed as well.
'';
};
};
};
default = { };
@@ -238,22 +129,22 @@ in
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
# drop empty lists so an unset GPU_COLLECTOR keeps upstream auto-detection
environment = lib.mapAttrs (
_: value:
if lib.isBool value then
(lib.boolToString value)
else if lib.isList value then
lib.concatStringsSep "," value
else
value
) (lib.filterAttrs (_: value: value != [ ]) (cfg.environment // { DATA_DIR = cfg.dataDir; }));
_: value: if lib.isBool value then (lib.boolToString value) else value
) (cfg.environment // { DATA_DIR = cfg.dataDir; });
path =
cfg.extraPath
++ lib.optionals cfg.smartmon.enable [ cfg.smartmon.package ]
++ lib.optionals config.boot.zfs.enabled [ config.boot.zfs.package ]
++ gpuPackages;
++ lib.optionals (builtins.elem "nvidia" config.services.xserver.videoDrivers) [
(lib.getBin config.hardware.nvidia.package)
]
++ lib.optionals (builtins.elem "amdgpu" config.services.xserver.videoDrivers) [
(lib.getBin pkgs.rocmPackages.rocm-smi)
]
++ lib.optionals (builtins.elem "intel" config.services.xserver.videoDrivers) [
(lib.getBin pkgs.intel-gpu-tools)
];
serviceConfig = {
ExecStart = ''
@@ -274,22 +165,26 @@ in
DynamicUser = true;
User = "beszel-agent";
# Capabilities needed for SMART monitoring and GPU performance counters
AmbientCapabilities = serviceCapabilities;
CapabilityBoundingSet = serviceCapabilities;
# Capabilities needed for SMART monitoring
AmbientCapabilities = lib.mkIf cfg.smartmon.enable [
"CAP_SYS_RAWIO"
"CAP_SYS_ADMIN"
];
CapabilityBoundingSet = lib.mkIf cfg.smartmon.enable [
"CAP_SYS_RAWIO"
"CAP_SYS_ADMIN"
];
DeviceAllow = lib.mkIf (deviceAllowList != [ ]) deviceAllowList;
# Device access for SMART monitoring
DeviceAllow = lib.mkIf (cfg.smartmon.enable && cfg.smartmon.deviceAllow != [ ]) (
map (device: "${device} r") cfg.smartmon.deviceAllow
);
LockPersonality = true;
NoNewPrivileges = !cfg.smartmon.enable;
PrivateDevices = !cfg.smartmon.enable && !gpuNeedsDevices;
PrivateDevices = !cfg.smartmon.enable;
PrivateTmp = true;
# zfs commands fail inside a user namespace since zfs 2.2, see syncoid.nix
PrivateUsers =
!cfg.smartmon.enable
&& !config.boot.zfs.enabled
&& !cfg.environment.SKIP_SYSTEMD
&& !gpuNeedsCapabilities;
PrivateUsers = !cfg.smartmon.enable && !cfg.environment.SKIP_SYSTEMD;
ProtectClock = true;
ProtectControlGroups = "strict";
ProtectHome = "read-only";
@@ -304,7 +199,7 @@ in
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallErrorNumber = "EPERM";
SystemCallFilter = [ "@system-service" ] ++ collectorAttrs "systemCalls";
SystemCallFilter = [ "@system-service" ];
Type = "simple";
UMask = 27;
};

View File

@@ -95,8 +95,8 @@ in
DynamicUser = true;
StateDirectory = "glpi-agent";
CapabilityBoundingSet = [ "CAP_DAC_READ_SEARCH" ];
AmbientCapabilities = [ "CAP_DAC_READ_SEARCH" ];
CapabilityBoundingSet = [ "CAP_SYS_ADMIN" ];
AmbientCapabilities = [ "CAP_SYS_ADMIN" ];
LimitCORE = 0;
LimitNOFILE = 65535;
@@ -104,7 +104,7 @@ in
MemorySwapMax = 0;
MemoryZSwapMax = 0;
PrivateTmp = true;
ProcSubset = "all";
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;

View File

@@ -32,14 +32,10 @@ let
inherit (package) phpPackage;
phpOptions = toKeyValue cfg.phpOptions;
preferLocalBuild = true;
strictDeps = true;
__structuredAttrs = true;
passAsFile = [ "phpOptions" ];
}
''
(
cat $phpPackage/etc/php.ini
printf "%s" "$phpOptions"
) > $out
cat $phpPackage/etc/php.ini $phpOptionsPath > $out
'';
artisanWrapper = pkgs.writeShellScriptBin "librenms-artisan" ''

View File

@@ -113,7 +113,20 @@ let
filterAttrsListRecursive =
pred: x:
if isAttrs x then
mapAttrs (_: filterAttrsListRecursive pred) (filterAttrs pred x)
listToAttrs (
concatMap (
name:
let
v = x.${name};
in
if pred name v then
[
(nameValuePair name (filterAttrsListRecursive pred v))
]
else
[ ]
) (attrNames x)
)
else if isList x then
map (filterAttrsListRecursive pred) x
else

View File

@@ -11,10 +11,6 @@ let
configFile = settingsFormat.generate "config.toml" cfg.extraConfig;
in
{
meta = {
inherit (pkgs.telegraf.meta) maintainers;
};
###### interface
options = {
services.telegraf = {

View File

@@ -240,8 +240,6 @@ in
"AF_INET"
"AF_INET6"
]
# AF_UNIX to be able to connect to e.g. /dev/log
++ lib.optionals (cfg.settings.log.file or "" == "syslog") [ "AF_UNIX" ]
++ lib.optionals cfg.allowDHCP [ "AF_PACKET" ];
RestrictNamespaces = true;
RestrictRealtime = true;

View File

@@ -21,6 +21,8 @@ let
(listOf settingType)
];
genAttrs' = names: f: lib.listToAttrs (map f names);
regexEscape =
let
# taken from https://github.com/python/cpython/blob/05cb728d68a278d11466f9a6c8258d914135c96c/Lib/re.py#L251-L266
@@ -298,7 +300,7 @@ in
lib.mapAttrsToList (name: cfg: {
${cfg.nginx.virtualHost} = {
locations =
(lib.genAttrs' [ "cgit.css" "cgit.js" "cgit.png" "favicon.ico" "robots.txt" ] (
(genAttrs' [ "cgit.css" "cgit.js" "cgit.png" "favicon.ico" "robots.txt" ] (
fileName:
lib.nameValuePair "= ${stripLocation cfg}/${fileName}" {
alias = lib.mkDefault "${cfg.package}/cgit/${fileName}";

View File

@@ -7,6 +7,7 @@
let
cfg = config.services.cloudflare-ddns;
boolToString = b: if b then "true" else "false";
formatList = l: lib.concatStringsSep "," l;
in
{
@@ -264,7 +265,7 @@ in
let
toEnv = name: value: "${name}=\"${toString value}\"";
toEnvList = name: value: "${name}=\"${formatList value}\"";
toEnvBool = name: value: "${name}=\"${lib.boolToString value}\"";
toEnvBool = name: value: "${name}=\"${boolToString value}\"";
toEnvMaybe =
pred: name: value:
lib.optionalString pred (toEnv name value);

View File

@@ -13,6 +13,7 @@ let
mkEnableOption
mkIf
mkOption
mkOverride
mkPackageOption
nameValuePair
recursiveUpdate
@@ -350,11 +351,13 @@ in
fedimintdName: cfg:
(nameValuePair cfg.nginx.fqdn (
lib.mkMerge [
(lib.mapAttrsRecursive (_: lib.mkDefault) cfg.nginx.config)
cfg.nginx.config
{
enableACME = true;
forceSSL = true;
# Note: we want by default to enable OpenSSL, but it seems anything 100 and above is
# overridden by default value from vhost-options.nix
enableACME = mkOverride 99 true;
forceSSL = mkOverride 99 true;
locations.${cfg.nginx.path_ws} = {
proxyPass = "http://127.0.0.1:${toString cfg.api_ws.port}/";
proxyWebsockets = true;

View File

@@ -0,0 +1,10 @@
{ lib, ... }:
{
imports = [
(lib.mkRemovedOptionModule [ "services" "go-neb" ] ''
The Go-NEB project was discontinued by Matrix.org and archived in June
2023. Use matrix-hookshot or another maintained Matrix bot instead.
'')
];
}

View File

@@ -20,24 +20,6 @@ in
default = null;
description = "Portal to discover targets on";
};
discoverType = mkOption {
description = ''
Target discovery type.
Change this if you want to discover your targes via an iSNS server
or use the targets provided via firmware settings.
See {manpage}`iscsiadm(8)`.
'';
default = "sendtargets";
example = "sendtargets";
type = enum [
"st"
"sendtargets"
"isns"
"fw"
];
};
name = mkOption {
type = str;
description = "Name of this iscsi initiator";
@@ -99,7 +81,7 @@ in
wantedBy = [ "remote-fs.target" ];
serviceConfig.ExecStartPre =
mkIf (cfg.discoverPortal != null)
"${cfg.package}/bin/iscsiadm --mode discoverydb --type ${cfg.discoverType} --portal ${escapeShellArg cfg.discoverPortal} --discover";
"${cfg.package}/bin/iscsiadm --mode discoverydb --type sendtargets --portal ${escapeShellArg cfg.discoverPortal} --discover";
};
environment.systemPackages = [ cfg.package ];

View File

@@ -43,23 +43,6 @@ in
type = nullOr str;
};
discoverType = mkOption {
description = ''
Target discovery type.
Change this if you want to discover your targes via an iSNS server
or use the targets provided via firmware settings.
See {manpage}`iscsiadm(8)`.
'';
default = "sendtargets";
example = "sendtargets";
type = enum [
"st"
"sendtargets"
"isns"
"fw"
];
};
target = mkOption {
description = ''
Name of the iSCSI target to boot from.
@@ -185,7 +168,7 @@ in
iscsid --foreground --no-pid-file --debug ${toString cfg.logLevel} &
iscsiadm --mode discoverydb \
--type ${cfg.discoverType} \
--type sendtargets \
--discover \
--portal ${escapeShellArg cfg.discoverPortal} \
--debug ${toString cfg.logLevel}

View File

@@ -292,7 +292,7 @@ in
assertions = lib.mapAttrsToList (netName: netCfg: {
# IFNAMSIZ caps network device names to 16 chars (including NULL terminator).
# Without this check, users might end up with a truncated interface name.
assertion = netCfg.tun.disable || builtins.stringLength netCfg.tun.device <= 15;
assertion = !netCfg.tun.disable && builtins.stringLength netCfg.tun.device <= 15;
message = ''
Network device names can't be longer than 15 chars.
`config.services.nebula.networks.${netName}.tun.device` is set to "${netCfg.tun.device}" which is above the limit.

View File

@@ -85,10 +85,12 @@ rec {
else
f (path ++ [ name ]) name value;
in
concatMapAttrs g set;
mapAttrs'' g set;
in
recurse [ ] set;
mapAttrs'' = f: set: foldl' (a: b: a // b) { } (mapAttrsToList f set);
# Extract the options from the given set of parameters.
paramsToOptions = ps: mapParamsRecursive (_path: name: param: { ${name} = param.option; }) ps;

View File

@@ -16,6 +16,10 @@ let
}:
attrsOfAttrs:
let
# map function to string for each key val
mapAttrsToStringsSep =
sep: mapFn: attrs:
lib.concatStringsSep sep (lib.mapAttrsToList mapFn attrs);
mkSection =
sectName: sectValues:
''
@@ -25,7 +29,7 @@ let
+ "}";
in
# map input to ini sections
lib.concatMapAttrsStringSep "\n" mkSection attrsOfAttrs;
mapAttrsToStringsSep "\n" mkSection attrsOfAttrs;
configFile = pkgs.writeText "manticore.conf" (
toSphinx {

View File

@@ -405,9 +405,7 @@ in
extraConfig = nginxAuthRequest + ''
types {
video/mp4 mp4;
image/jpeg jpg jpeg;
image/png png;
image/webp webp;
image/jpeg jpg;
}
expires 7d;
@@ -495,6 +493,19 @@ in
}
'';
};
# frontend uses this to fetch the version
"/api/go2rtc/api" = {
proxyPass = "http://frigate-go2rtc/api";
recommendedProxySettings = true;
extraConfig =
nginxAuthRequest
+ nginxProxySettings
+ ''
limit_except GET {
deny all;
}
'';
};
# integrationn uses this to add webrtc candidate
"/api/go2rtc/webrtc" = {
proxyPass = "http://frigate-go2rtc/api/webrtc";
@@ -530,7 +541,6 @@ in
expires off;
proxy_cache frigate_api_cache;
proxy_cache_key "$scheme$proxy_host$request_uri|$role|$groups|$user";
proxy_cache_lock on;
proxy_cache_use_stale updating;
proxy_cache_valid 200 5s;
@@ -553,13 +563,6 @@ in
${nginxProxySettings}
}
location /api/logout {
auth_request off;
rewrite ^/api(/.*)$ $1 break;
proxy_pass http://frigate-api;
${nginxProxySettings}
}
location /api/auth/first_time_login {
auth_request off;
limit_except GET {
@@ -744,6 +747,7 @@ in
]
++ optionals (!stdenv.hostPlatform.isAarch64) [
# not available on aarch64-linux
intel-gpu-tools
rocmPackages.rocminfo
];
serviceConfig = {
@@ -771,10 +775,11 @@ in
Group = "frigate";
SupplementaryGroups = [ "render" ] ++ optionals withCoral [ "coral" ];
# No capabilities
CapabilityBoundingSet = [ "" ];
AmbientCapabilities = optionals (elem cfg.vaapiDriver [
"i965"
"iHD"
]) [ "CAP_PERFMON" ]; # for intel_gpu_top
# Allow delegating access
UMask = "0027";
StateDirectory = "frigate";
@@ -792,53 +797,9 @@ in
# Sockets/IPC
RuntimeDirectory = "frigate";
RemoveIPC = true;
# Reduce visible process scope to cgroup
ProtectProc = "invisible";
# Allow wide /proc inspection, e.g. for cpuinfo
ProcSubset = "all";
# Protect various system locations/interfaces
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectSystem = "strict";
# No JIT compilation
MemoryDenyWriteExecute = true;
# No ABI personality changes
LockPersonality = true;
# Only IP/Unix sockets
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
# Deny namespace creation
RestrictNamespaces = true;
# No privilege escalation
NoNewPrivileges = true;
RestrictSUIDSGID = true;
# No realtime schedulign
RestrictRealtime = true;
# Restrict allowed syscalls
SystemCallFilter = [
"@system-service"
"~@privileged"
];
SystemCallArchitectures = "native";
SystemCallErrorNumber = "EPERM";
};
};

View File

@@ -1167,16 +1167,17 @@ in
};
config = mkIf cfg.enable {
assertions = [
{
assertion =
assertions =
optionals
(
cfg.config.":pleroma".":media_proxy".enabled
-> cfg.config.":pleroma".":media_proxy".base_url != null;
message = ''
`services.akkoma.config.":pleroma".":media_proxy".base_url` must be set to a URL with a different host component (domain name) than the web endpoint when the media proxy is enabled.
'';
}
];
&& cfg.config.":pleroma".":media_proxy".base_url == null
)
[
''
`services.akkoma.config.":pleroma".":media_proxy".base_url` must be set when the media proxy is enabled.
''
];
warnings =
optionals (with config.security; cfg.installWrapper && (!sudo.enable) && (!sudo-rs.enable))
[

View File

@@ -202,7 +202,7 @@ in
(mkIf cfg.playwrightSupport {
changedetection-io-playwright = {
image = "docker.io/browserless/chrome";
image = "browserless/chrome";
environment = {
SCREEN_WIDTH = "1920";
SCREEN_HEIGHT = "1024";

View File

@@ -116,7 +116,7 @@ in
services.phpfpm.pools.engelsystem = {
user = "engelsystem";
settings = lib.mapAttrs (_: v: lib.mkDefault v) {
settings = {
"listen.owner" = config.services.nginx.user;
"pm" = "dynamic";
"pm.max_children" = 32;

View File

@@ -43,7 +43,7 @@ in
type = types.submodule { freeformType = types.attrsOf (types.nullOr types.str); };
defaultText = lib.literalExpression ''
{
HBOX_STORAGE_CONN_STRING = "file:///var/lib/homebox?no_tmp_dir=1";
HBOX_STORAGE_CONN_STRING = "file:///var/lib/homebox";
HBOX_STORAGE_PREFIX_PATH = "data";
HBOX_DATABASE_DRIVER = "sqlite3";
HBOX_DATABASE_SQLITE_PATH = "/var/lib/homebox/data/homebox.db?_pragma=busy_timeout=999&_pragma=journal_mode=WAL&_fk=1";
@@ -51,6 +51,7 @@ in
HBOX_OPTIONS_GITHUB_RELEASE_CHECK = "false";
HBOX_MODE = "production";
HOME = "/var/lib/homebox";
TMPDIR = "/var/lib/homebox/tmp";
}
'';
description = ''
@@ -124,9 +125,7 @@ in
services.homebox.settings = lib.mkMerge [
(lib.mapAttrs (_: mkDefault) {
# We cannot use a tempdir as homebox wants to rename the file, which does not work across filesystem boundaries
# also see: https://github.com/google/go-cloud/issues/3294 and https://pkg.go.dev/gocloud.dev/blob/fileblob#URLOpener
HBOX_STORAGE_CONN_STRING = "file:///var/lib/homebox?no_tmp_dir=1";
HBOX_STORAGE_CONN_STRING = "file:///var/lib/homebox";
HBOX_STORAGE_PREFIX_PATH = "data";
HBOX_DATABASE_DRIVER = "sqlite3";
HBOX_DATABASE_SQLITE_PATH = "/var/lib/homebox/data/homebox.db?_pragma=busy_timeout=999&_pragma=journal_mode=WAL&_fk=1";
@@ -135,8 +134,10 @@ in
HBOX_MODE = "production";
# Fix this startup issue:
# failed to create modcache index dir: mkdir /var/empty/.cache: read-only file system
# TODO: remove once https://github.com/golang/tools/commit/03cb4551c662c0e078502fe5f317ca4114b89cd8 is available
HOME = "/var/lib/homebox";
# Fix uploading/saving attachments/images:
# [...] rename /tmp/ced4804c80b1ed1f6e88060f6d829db421e6dbf3a189715265900b5d6b0243ed.1889b3d16ab36e22.tmp /var/lib/homebox/data/5f42f81b-e9ad-4495-b6a6-9e9f704db30e/documents/ced4804c80b1ed1f6e88060f6d829db421e6dbf3a189715265900b5d6b0243ed: invalid cross-device link" [...]
TMPDIR = "/var/lib/homebox/tmp";
})
(mkIf cfg.database.createLocally {

View File

@@ -434,10 +434,10 @@ in
package = lib.mkOption {
type = types.package;
default =
if lib.versionAtLeast config.system.stateVersion "26.11" then pkgs.netbox_4_7 else pkgs.netbox_4_5;
if lib.versionAtLeast config.system.stateVersion "26.11" then pkgs.netbox_4_6 else pkgs.netbox_4_5;
defaultText = lib.literalExpression ''
if lib.versionAtLeast config.system.stateVersion "26.11" then
pkgs.netbox_4_7
pkgs.netbox_4_6
else
pkgs.netbox_4_5;
'';
@@ -563,15 +563,6 @@ in
config = lib.mkIf cfg.enable (
lib.mkMerge [
{
assertions = [
{
assertion =
cfg.postgresql.createLocally
-> lib.versionAtLeast config.services.postgresql.finalPackage.version "15";
message = "NetBox requires PostgreSQL >= 15. Please read the NixOS manual to upgrade your PostgreSQL version.";
}
];
services.netbox.plugins = lib.mkIf enableLDAP (ps: [ ps.django-auth-ldap ]);
services.redis.servers.netbox.enable = cfg.redis.createLocally;
@@ -742,6 +733,26 @@ in
PrivateTmp = true;
};
};
netbox-housekeeping = defaultUnitConfig // {
description = "NetBox housekeeping job";
wantedBy = [ "multi-user.target" ];
after = [
"network-online.target"
"netbox.service"
];
wants = [ "network-online.target" ];
serviceConfig = defaultServiceConfig // {
Type = "oneshot";
ExecStart = toString [
(lib.getExe finalPackage)
"housekeeping"
];
};
};
};
systemd.timers.netbox-housekeeping = {

View File

@@ -12,13 +12,6 @@ let
scheme = if cfg.ssl.enable then "https" else "http";
configFile = settingsFormat.generate "rundeck-config.properties" cfg.settings;
jaasLoginModuleClass =
if lib.versionAtLeast cfg.package.version "6" then
"org.rundeck.jaas.PropertyFileLoginModule"
else
"org.eclipse.jetty.jaas.spi.PropertyFileLoginModule";
frameworkFile = settingsFormat.generate "framework.properties" cfg.frameworkSettings;
realmFile = pkgs.writeText "realm.properties" ''
@@ -140,43 +133,7 @@ in
aclPolicies = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = {
"admin.aclpolicy" = ''
description: Admin, all access.
context:
project: '.*'
for:
resource:
- allow: '*'
adhoc:
- allow: '*'
job:
- allow: '*'
node:
- allow: '*'
runner:
- allow: '*'
by:
group: admin
---
description: Admin, all access.
context:
application: 'rundeck'
for:
resource:
- allow: '*'
project:
- allow: '*'
project_acl:
- allow: '*'
storage:
- allow: '*'
by:
group: admin
'';
};
default = { };
description = "ACL policies for Rundeck, where the attribute name is the filename and the value is the policy content";
example = lib.literalExpression ''
{
@@ -536,9 +493,9 @@ in
group = cfg.group;
text = ''
RDpropertyfilelogin {
${jaasLoginModuleClass} required
org.eclipse.jetty.jaas.spi.PropertyFileLoginModule required
debug="true"
file="${cfg.configDir}/realm.properties";
file="/etc/rundeck/realm.properties";
};
'';
};
@@ -674,7 +631,9 @@ in
replaceSecret "@SERVER_UUID@" "${cfg.dataDir}/.uuid" "${cfg.configDir}/framework.properties"
)}
install -C -m 0640 ${cfg.configDir}/framework.properties ${cfg.dataDir}/etc/framework.properties
if [ -f ${cfg.dataDir}/etc/framework.properties ]; then
install -m 0640 ${cfg.configDir}/framework.properties ${cfg.dataDir}/etc/framework.properties
fi
${lib.concatStringsSep "\n" (
lib.mapAttrsToList (

View File

@@ -555,33 +555,7 @@ in
before = [ "phpfpm-wordpress-${hostName}.service" ];
after = optional cfg.database.createLocally "mysql.service";
script = secretsScript (stateDir hostName);
serviceConfig = {
Type = "oneshot";
User = user;
Group = webserver.group;
};
})
) eachSite)
(mapAttrs' (
hostName: cfg:
(nameValuePair "wordpress-migrate-database-${hostName}" {
wantedBy = [ "multi-user.target" ];
after = [
"phpfpm-wordpress-${hostName}.service"
]
++ optional cfg.database.createLocally "mysql.service";
script = ''
# Auto migrate database after version update
versionFile="${stateDir hostName}/src-version"
version=$(cat "$versionFile" 2>/dev/null || echo 0)
if [[ $version != 0 && $version != ${cfg.package.version} ]]; then
echo "Executing database migration"
${lib.getExe pkgs.wp-cli} --path="${cfg.finalPackage}/share/wordpress" \
--skip-plugins --skip-themes core update-db
fi
echo ${cfg.package.version} > "$versionFile"
'';
serviceConfig = {
Type = "oneshot";
User = user;

View File

@@ -9,10 +9,6 @@ let
cfg = config.boot.kexec;
in
{
meta = {
inherit (pkgs.kexec-tools.meta) maintainers;
};
options.boot.kexec = {
enable = lib.mkEnableOption "kexec" // {
default = lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.kexec-tools;

View File

@@ -34,7 +34,6 @@ in
thin_repair = "${pkgs."thin-provisioning-tools"}/bin/thin_repair";
thin_metadata_size = "${pkgs."thin-provisioning-tools"}/bin/thin_metadata_size";
stratis-min = "${pkgs.stratisd}/bin/stratis-min";
cryptsetup = "${pkgs.cryptsetup}/bin/cryptsetup";
};
services = lib.attrsets.mapAttrs' (mountPoint: fileSystem: {
name = "stratis-setup-${fileSystem.stratis.poolUuid}";

View File

@@ -22,14 +22,6 @@
};
};
syslogConf = {
services.adguardhome = {
enable = true;
settings.log.file = "syslog";
};
};
declarativeConf = {
services.adguardhome = {
enable = true;
@@ -135,12 +127,6 @@
schemaVersionBefore23.wait_for_unit("adguardhome.service")
schemaVersionBefore23.wait_for_open_port(3000)
with subtest("Logging to syslog test"):
# AdGuard is expected to fail when it cannot connect to syslog
# hence its sufficient to look whether the service starts at all
syslogConf.wait_for_unit("adguardhome.service")
syslogConf.wait_for_open_port(3000)
with subtest("Declarative config test, DNS will be reachable"):
declarativeConf.wait_for_unit("adguardhome.service")
declarativeConf.wait_for_open_port(53)

View File

@@ -402,10 +402,22 @@ in
ceph-multi-node-bluestore-cephfs = runTestOn [ "aarch64-linux" "x86_64-linux" ] (
import ./ceph-multi-node-bluestore.nix { withCephfs = true; }
);
ceph-multi-node-deprecated-filestore = runTestOn [
"aarch64-linux"
"x86_64-linux"
] ./ceph-multi-node-deprecated-filestore.nix;
ceph-single-node-bluestore = runTestOn [
"aarch64-linux"
"x86_64-linux"
] ./ceph-single-node-bluestore.nix;
ceph-single-node-bluestore-dmcrypt = runTestOn [
"aarch64-linux"
"x86_64-linux"
] ./ceph-single-node-bluestore-dmcrypt.nix;
ceph-single-node-deprecated-filestore = runTestOn [
"aarch64-linux"
"x86_64-linux"
] ./ceph-single-node-deprecated-filestore.nix;
certmgr = import ./certmgr.nix { inherit pkgs runTest; };
cfssl = runTestOn [ "aarch64-linux" "x86_64-linux" ] ./cfssl.nix;
cgit = runTest ./cgit.nix;
@@ -647,6 +659,10 @@ in
imports = [ ./firefox.nix ];
_module.args.firefoxPackage = pkgs.firefox;
};
firefox-beta = runTest {
imports = [ ./firefox.nix ];
_module.args.firefoxPackage = pkgs.firefox-beta;
};
firefox-devedition = runTest {
imports = [ ./firefox.nix ];
_module.args.firefoxPackage = pkgs.firefox-devedition;
@@ -656,6 +672,10 @@ in
imports = [ ./firefox.nix ];
_module.args.firefoxPackage = pkgs.firefox-esr;
};
firefox-esr-140 = runTest {
imports = [ ./firefox.nix ];
_module.args.firefoxPackage = pkgs.firefox-esr-140;
};
firefox-esr-153 = runTest {
imports = [ ./firefox.nix ];
_module.args.firefoxPackage = pkgs.firefox-esr-153;
@@ -1262,7 +1282,6 @@ in
nginx-modsecurity = runTest ./nginx-modsecurity.nix;
nginx-moreheaders = runTest ./nginx-moreheaders.nix;
nginx-njs = runTest ./nginx-njs.nix;
nginx-otel = runTest ./nginx-otel.nix;
nginx-proxyprotocol = runTest ./nginx-proxyprotocol/default.nix;
nginx-pubhtml = runTest ./nginx-pubhtml.nix;
nginx-redirectcode = runTest ./nginx-redirectcode.nix;
@@ -1303,9 +1322,6 @@ in
nixos-rebuild-target-host = runTest {
imports = [ ./nixos-rebuild-target-host.nix ];
};
nixos-rebuild-target-host-interrupted = runTest {
imports = [ ./nixos-rebuild-target-host-interrupted.nix ];
};
nixpkgs = pkgs.callPackage ../modules/misc/nixpkgs/test.nix { inherit evalMinimalConfig; };
nixpkgs-config-allow-unfree =
pkgs.callPackage ../modules/misc/nixpkgs/test-nixpkgs-config-allow-unfree.nix

View File

@@ -59,50 +59,6 @@
openFirewall = true;
};
};
# Only inspected by the test script, never activated: the VM has no GPU,
# but the generated units can still be checked.
specialisation."gpu-sysfs".configuration = {
services.beszel.agent = {
enable = true;
environment.GPU_COLLECTOR = [ "amd_sysfs" ];
};
};
specialisation."gpu-devices".configuration = {
services.beszel.agent = {
enable = true;
environment.GPU_COLLECTOR = [ "intel_gpu_top" ];
};
};
specialisation."gpu-smartmon".configuration = {
services.beszel.agent = {
enable = true;
# upstream's comma-separated form is accepted as well
environment.GPU_COLLECTOR = "intel_gpu_top";
smartmon = {
enable = true;
deviceAllow = [ "/dev/nvme0" ];
};
};
};
specialisation."zfs".configuration = {
networking.hostId = "8425e349";
boot.supportedFilesystems = [ "zfs" ];
services.beszel.agent.enable = true;
};
specialisation."gpu-skipped".configuration = {
services.beszel.agent = {
enable = true;
environment = {
SKIP_GPU = true;
GPU_COLLECTOR = [ "intel_gpu_top" ];
};
};
};
};
};
@@ -111,13 +67,6 @@
let
hubCfg = nodes.hubHost.services.beszel.hub;
agentCfg = nodes.agentHost.specialisation."agent".configuration.services.beszel.agent;
# /run/current-system points at the "agent" specialisation after the switch,
# so the units are read from the store directly.
gpuUnit =
name:
"${
nodes.agentHost.specialisation.${name}.configuration.system.build.toplevel
}/etc/systemd/system/beszel-agent.service";
in
''
import json
@@ -166,41 +115,5 @@
agentHost.wait_for_unit("beszel-agent.service")
agentHost.wait_until_succeeds("journalctl -eu beszel-agent --grep 'SSH connection established'")
agentHost.wait_until_succeeds(f'curl -H \'Authorization: {user["token"]}\' -f ${agentCfg.environment.HUB_URL}/api/collections/systems/records | jq -e \'.items[].status == "up"\' ')
with subtest("Agent stays sandboxed without a GPU"):
agentHost.succeed("systemctl show beszel-agent -p PrivateDevices --value | grep -qx yes")
agentHost.succeed("systemctl show beszel-agent -p PrivateUsers --value | grep -qx yes")
with subtest("GPU collectors shape the unit"):
# sysfs-only collector keeps the sandbox
sysfs = agentHost.succeed("cat ${gpuUnit "gpu-sysfs"}")
assert "PrivateDevices=true" in sysfs, sysfs
assert "PrivateUsers=true" in sysfs, sysfs
assert "intel-gpu-tools" not in sysfs, sysfs
# device-based collector gets its devices as an allow-list, and
# CAP_PERFMON/perf_event_open with the user namespace disabled
devices = agentHost.succeed("cat ${gpuUnit "gpu-devices"}")
assert "PrivateDevices=false" in devices, devices
assert "PrivateUsers=false" in devices, devices
assert "DeviceAllow=char-drm rw" in devices, devices
assert "CAP_PERFMON" in devices, devices
assert "perf_event_open" in devices, devices
# GPU devices must survive smartmon's DeviceAllow list
smartmon = agentHost.succeed("cat ${gpuUnit "gpu-smartmon"}")
assert "DeviceAllow=/dev/nvme0 r" in smartmon, smartmon
assert "DeviceAllow=char-drm rw" in smartmon, smartmon
# zfs only gets /dev/zfs, but needs the host user namespace
zfs = agentHost.succeed("cat ${gpuUnit "zfs"}")
assert "PrivateDevices=true" in zfs, zfs
assert "DeviceAllow=/dev/zfs rw" in zfs, zfs
assert "PrivateUsers=false" in zfs, zfs
# SKIP_GPU wins over an explicitly configured collector
skipped = agentHost.succeed("cat ${gpuUnit "gpu-skipped"}")
assert "PrivateDevices=true" in skipped, skipped
assert "intel-gpu-tools" not in skipped, skipped
'';
}

View File

@@ -25,16 +25,19 @@ let
osd0 = {
name = "0";
ip = "192.168.1.2";
key = "AQBCEJNa3s8nHRAANvdsr93KqzBznuIWm2gOGg==";
uuid = "55ba2294-3e24-478f-bee0-9dca4c231dd9";
};
osd1 = {
name = "1";
ip = "192.168.1.3";
key = "AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ==";
uuid = "5e97a838-85b6-43b0-8950-cb56d554d1e5";
};
osd2 = {
name = "2";
ip = "192.168.1.4";
key = "AQAdyhZeIaUlARAAGRoidDAmS6Vkp546UFEf5w==";
uuid = "ea999274-13d0-4dd5-9af9-ad25a324f72f";
};
# Client that mounts CephFS using the in-kernel client.
@@ -55,14 +58,6 @@ let
monHost = cfg.monA.ip;
monInitialMembers = cfg.monA.name;
};
extraConfig = {
log_to_syslog = "false";
log_to_file = "false";
log_to_stderr = "true";
debug_rocksdb = "1/5";
debug_mgr = "1/5";
mon_host = "v2:${cfg.monA.ip}:3300 v1:${cfg.monA.ip}:6789";
};
}
// daemonConfig;
@@ -86,7 +81,6 @@ let
bash
sudo
ceph
cryptsetup
netcat
];
@@ -151,11 +145,6 @@ let
enable = true;
daemons = [ cfg.monA.name ];
};
# TODO: move this to a separate machine
rgw = {
enable = true;
daemons = [ cfg.monA.name ];
};
}
# The MDS daemon (which provides CephFS) is only configured in the CephFS
# variant of this test.
@@ -220,11 +209,6 @@ let
vlans = [ 1 ];
};
# Ceph 20.2.4 introduced the aes256k cipher for authentication.
# Linux started supporting these in kernel version 7.0.
# Remove this line at the earliest convenience (i.e. when tests are run by 7.0 or higher by default).
boot.kernelPackages = pkgs.linuxPackages_latest;
networking = networkConfig;
environment.systemPackages = with pkgs; [
@@ -301,8 +285,6 @@ let
# Based on the "manual deployment" approach from:
# https://docs.ceph.com/en/tentacle/install/manual-deployment/
baseScript = ''
import json
start_all()
monA.wait_for_unit("network.target")
@@ -315,15 +297,14 @@ let
"sudo -u ceph ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'",
"sudo -u ceph ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *' --cap mgr 'allow *'",
"sudo -u ceph ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring",
# Creating the mon with v2 (and a legacy v1) address right away removes the need for running `enable-msgr2` later on.
# It is also makes the test more consistent by fixing the address to a known value instead of letting it derive the address.
"monmaptool --create --addv ${cfg.monA.name} '[v2:${cfg.monA.ip}:3300,v1:${cfg.monA.ip}:6789]' --auth-allowed-ciphers aes256k --auth-preferred-cipher aes256k --auth-service-cipher aes256k --fsid ${cfg.clusterId} /tmp/monmap",
"monmaptool --create --add ${cfg.monA.name} ${cfg.monA.ip} --fsid ${cfg.clusterId} /tmp/monmap",
"sudo -u ceph ceph-mon --mkfs -i ${cfg.monA.name} --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring",
"sudo -u ceph mkdir -p /var/lib/ceph/mgr/ceph-${cfg.monA.name}/",
"sudo -u ceph touch /var/lib/ceph/mon/ceph-${cfg.monA.name}/done",
"systemctl start ceph-mon-${cfg.monA.name}",
)
monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
monA.succeed("ceph mon enable-msgr2")
monA.succeed("ceph config set mon auth_allow_insecure_global_id_reclaim false")
# Can't check ceph status until a mon is up
@@ -339,63 +320,59 @@ let
monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
# Send the bootstrap-osd keyring to the OSD machines.
monA.succeed("ceph auth get client.bootstrap-osd -o /etc/ceph/ceph.client.bootstrap-osd.keyring")
monA.succeed("cp /etc/ceph/ceph.client.bootstrap-osd.keyring /tmp/shared")
# Send the admin keyring to the OSD machines.
monA.succeed("cp /etc/ceph/ceph.client.admin.keyring /tmp/shared")
osd0.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph")
osd1.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph")
osd2.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph")
# Bootstrap the BlueStore OSDs.
#
# The steps for this are roughly the same for all OSDs:
# 1. get the bootstrap-osd keyring
# 2. prepare the osd via ceph-volume lvm, the second line contains the OSD specific configuration
# 3. deactivate it to unmount the tmpfs
# 4. activate it without a tmpfs for persistent data
# 5. sync, so the osd has at least one consistent state saved
# 6. start it
# osd.0: plain
osd0.succeed(
"mkdir -p /var/lib/ceph/bootstrap-osd",
"cp /tmp/shared/ceph.client.bootstrap-osd.keyring /var/lib/ceph/bootstrap-osd/ceph.keyring",
"ceph-volume lvm prepare --objectstore bluestore --no-systemd --osd-id ${cfg.osd0.name} --osd-fsid ${cfg.osd0.uuid} "
"--data /dev/vdb",
"ceph-volume lvm deactivate ${cfg.osd0.name} ${cfg.osd0.uuid}",
"ceph-volume lvm activate --no-tmpfs --no-systemd ${cfg.osd0.name} ${cfg.osd0.uuid}",
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd0.name}",
"echo bluestore > /var/lib/ceph/osd/ceph-${cfg.osd0.name}/type",
"ln -sf /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd0.name}/block",
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd0.name}/keyring --name osd.${cfg.osd0.name} --add-key ${cfg.osd0.key}",
'echo \'{"cephx_secret": "${cfg.osd0.key}"}\' | ceph osd new ${cfg.osd0.uuid} -i -',
)
osd1.succeed(
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd1.name}",
"echo bluestore > /var/lib/ceph/osd/ceph-${cfg.osd1.name}/type",
"ln -sf /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd1.name}/block",
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd1.name}/keyring --name osd.${cfg.osd1.name} --add-key ${cfg.osd1.key}",
'echo \'{"cephx_secret": "${cfg.osd1.key}"}\' | ceph osd new ${cfg.osd1.uuid} -i -',
)
osd2.succeed(
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd2.name}",
"echo bluestore > /var/lib/ceph/osd/ceph-${cfg.osd2.name}/type",
"ln -sf /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd2.name}/block",
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd2.name}/keyring --name osd.${cfg.osd2.name} --add-key ${cfg.osd2.key}",
'echo \'{"cephx_secret": "${cfg.osd2.key}"}\' | ceph osd new ${cfg.osd2.uuid} -i -',
)
# We `sync` so that the config survives the forced crashes below.
osd0.succeed(
"ceph-osd -i ${cfg.osd0.name} --mkfs --osd-uuid ${cfg.osd0.uuid}",
"chown -R ceph:ceph /var/lib/ceph/osd",
"sync",
"systemctl start ceph-osd-${cfg.osd0.name}",
)
# osd.1: plain
osd1.succeed(
"mkdir -p /var/lib/ceph/bootstrap-osd",
"cp /tmp/shared/ceph.client.bootstrap-osd.keyring /var/lib/ceph/bootstrap-osd/ceph.keyring",
"ceph-volume lvm prepare --objectstore bluestore --no-systemd --osd-id ${cfg.osd1.name} --osd-fsid ${cfg.osd1.uuid} "
"--data /dev/vdb --dmcrypt",
"ceph-volume lvm deactivate ${cfg.osd1.name} ${cfg.osd1.uuid}",
"ceph-volume lvm activate --no-tmpfs --no-systemd ${cfg.osd1.name} ${cfg.osd1.uuid}",
"ceph-osd -i ${cfg.osd1.name} --mkfs --osd-uuid ${cfg.osd1.uuid}",
"chown -R ceph:ceph /var/lib/ceph/osd",
"sync",
"systemctl start ceph-osd-${cfg.osd1.name}",
)
# osd.2: plain
osd2.succeed(
"mkdir -p /var/lib/ceph/bootstrap-osd",
"cp /tmp/shared/ceph.client.bootstrap-osd.keyring /var/lib/ceph/bootstrap-osd/ceph.keyring",
"ceph-volume lvm prepare --objectstore bluestore --no-systemd --osd-fsid ${cfg.osd2.uuid} --osd-id ${cfg.osd2.name} "
"--data /dev/vdb",
"ceph-volume lvm deactivate ${cfg.osd2.name} ${cfg.osd2.uuid}",
"ceph-volume lvm activate --no-tmpfs --no-systemd ${cfg.osd2.name} ${cfg.osd2.uuid}",
"ceph-osd -i ${cfg.osd2.name} --mkfs --osd-uuid ${cfg.osd2.uuid}",
"chown -R ceph:ceph /var/lib/ceph/osd",
"sync",
"systemctl start ceph-osd-${cfg.osd2.name}",
)
monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
monA.succeed(
# Autoscaling will cause PGs to be peering, causing the tests to become flakey.
"ceph osd pool set noautoscale",
"ceph osd pool create multi-node-test 32 32",
"ceph osd pool ls | grep 'multi-node-test'",
@@ -412,7 +389,6 @@ let
"ceph osd pool ls | grep 'multi-node-other-test'",
)
monA.succeed("ceph osd pool set multi-node-other-test size 2")
# TODO: actually write to the pool using rados directly
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
monA.wait_until_succeeds("! ceph -s | grep -e 'unknown' -e 'pgs inactive'")
monA.fail(
@@ -420,100 +396,23 @@ let
"ceph osd pool delete multi-node-other-test multi-node-other-test --yes-i-really-really-mean-it",
)
# Bootstrap RGW
monA.succeed(
"sudo -u ceph mkdir -p /var/lib/ceph/radosgw/ceph-${cfg.monA.name}",
"ceph auth get-or-create client.${cfg.monA.name} osd 'allow rwx' mon 'allow rw' > /var/lib/ceph/radosgw/ceph-${cfg.monA.name}/keyring",
"chown ceph:ceph /var/lib/ceph/radosgw/ceph-${cfg.monA.name}/keyring",
"systemctl start ceph-rgw-${cfg.monA.name}",
)
monA.wait_for_unit("ceph-rgw-${cfg.monA.name}")
monA.wait_for_open_port(7480)
# Enable the dashboard and recheck health
monA.succeed(
"ceph mgr module enable dashboard",
"ceph config set mgr mgr/dashboard/ssl false",
# default is 8080 but it's better to be explicit
"ceph config set mgr mgr/dashboard/server_port 8080",
)
# The dashboard does not listen on localhost:
# `server_addr` defaults to the wildcard address, but the dashboard module
# resolves that to the active mgr's own IP and binds only to it,
# so loopback is never bound.
# See https://github.com/ceph/ceph/blob/v20.2.2/src/pybind/mgr/dashboard/module.py#L213-L214
# Therefore address the dashboard via the mgr's IP instead of localhost.
dashboard = "http://${cfg.monA.ip}:8080"
monA.wait_for_open_port(8080, addr="${cfg.monA.ip}")
monA.wait_until_succeeds(f"curl -s --fail {dashboard}")
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
# Initialize dashboard creds.
# In a the query below, we test the Dashboard's `/api/rgw/daemon`,
# which needs that the dashboard can talk to RGW.
# `set-rgw-credentials` needs a running RGW daemon.
monA.succeed(
"echo 'foo bar baz qux' > /tmp/dashboard_pw",
"ceph dashboard ac-user-create admin -i /tmp/dashboard_pw administrator",
"ceph dashboard set-rgw-credentials",
"sync",
)
# Get dashboard auth token
auth_payload = json.dumps({"username": "admin", "password": "foo bar baz qux"})
auth_response = json.loads(monA.succeed(
f"curl --fail -s -X POST -H 'Accept: application/vnd.ceph.api.v1.0+json' -H 'Content-Type: application/json' -d '{auth_payload}' {dashboard}/api/auth",
))
token = auth_response["token"]
# Check cluster health via dashboard API
health = json.loads(monA.succeed(
f"curl --fail -s -H 'Accept: application/vnd.ceph.api.v1.0+json' -H 'Authorization: Bearer {token}' {dashboard}/api/health/minimal",
))
assert health["health"]["status"] == "HEALTH_OK"
# List daemons via REST API.
# This also requires a running RGW daemon, as it asserts on the first one.
rgw_daemons = json.loads(monA.succeed(
f"curl --fail -s -H 'Accept: application/vnd.ceph.api.v1.0+json' -H 'Authorization: Bearer {token}' {dashboard}/api/rgw/daemon",
))
assert rgw_daemons[0]["id"] == "${cfg.monA.name}"
# Shut down ceph on all machines in a very unpolite way
monA.crash()
osd0.crash()
osd1.crash()
osd2.crash()
# Start the mon first and mark the OSDs as down.
# Since the heartbeats are pretty high by default, the OSDs would otherwise be marked as up still.
# However we do not want to lower the heartbeats since this might cause flakey tests.
monA.start()
monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
monA.wait_until_succeeds("ceph osd down all")
# Then start the OSDs as normal.
# Start it up
osd0.start()
osd1.start()
osd2.start()
# Ensure they are all up.
osd0.wait_for_unit("network.target")
osd1.wait_for_unit("network.target")
osd2.wait_for_unit("network.target")
monA.start()
# FIXME: dmcrypt OSDs currently do not work out of the box.
# For a potential long-term fix see: https://github.com/NixOS/nixpkgs/pull/512912#discussion_r3140295546
osd1.succeed(
"ceph-volume lvm activate --no-tmpfs --no-systemd ${cfg.osd1.name} ${cfg.osd1.uuid}",
"systemctl start ceph-osd-${cfg.osd1.name}",
)
# Test the cluster state thoroughly.
# Ensure the cluster comes back up again.
monA.wait_until_succeeds("ceph -s | grep 'mon: 1 daemons'")
monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
# Verify the recovery.
@@ -545,50 +444,45 @@ let
# Create a CephFS.
monA.succeed(
"ceph fs volume create testing",
"ceph osd pool set cephfs.testing.data pg_num 32",
"ceph osd pool set cephfs.testing.meta pg_num 32",
"ceph osd pool create cephfs-data 32 32",
"ceph osd pool create cephfs-metadata 32 32",
"ceph fs new cephfs cephfs-metadata cephfs-data",
)
# Wait for the MDS to claim the filesystem and become active.
monA.wait_until_succeeds("ceph fs status testing | grep -e 'active'", timeout=60)
monA.wait_until_succeeds("ceph fs status cephfs | grep -e 'active'", timeout=60)
# Create a subvolume, issue credentials, then distribute those credentials.
# Distribute the admin keyring (and a plain secret file for the kernel
# client) to both client machines, so that they can authenticate.
monA.succeed(
"ceph fs subvolumegroup create testing group",
"ceph fs subvolume create testing subvolume --group_name group",
"ceph fs subvolume authorize testing subvolume kclient group",
"ceph fs subvolume authorize testing subvolume fuseclient group",
"ceph auth get client.kclient -o /tmp/shared/ceph.client.kclient.keyring",
"ceph auth get client.fuseclient -o /tmp/shared/ceph.client.fuseclient.keyring",
"cp /etc/ceph/ceph.client.admin.keyring /tmp/shared",
"ceph-authtool -p /etc/ceph/ceph.client.admin.keyring > /tmp/shared/admin.secret",
)
kclient.succeed("cp /tmp/shared/ceph.client.kclient.keyring /etc/ceph")
fuseclient.succeed("cp /tmp/shared/ceph.client.fuseclient.keyring /etc/ceph")
# Get the volume path generated by Ceph.
volume_path = monA.succeed("ceph fs subvolume getpath testing subvolume group | tee /dev/stderr").strip()
kclient.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph")
fuseclient.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph")
kclient.succeed("cp /tmp/shared/admin.secret /etc/ceph/admin.secret")
# Mount CephFS on the kernel client.
# We force the messenger v2 protocol via "ms_mode=secure"; the cluster
# has msgr2 enabled (the monmap is created with a v2 address above) and the legacy v1
# has msgr2 enabled (see "ceph mon enable-msgr2" above) and the legacy v1
# protocol apparently does not reconnect reliably after the servers are restarted.
# The msgr2 monitor listens on port 3300 (instead of legacy v1 port 6789),
# so we have to point the device string at that port explicitly.
# `recover_session=clean` makes the kernel client automatically reconnect
# (discarding its stale session) after the whole cluster has been down,
# which would otherwise leave the mount blocklisted and hanging.
# which would otherwise leave the mount blocklisted and hanging forever.
# Real CephFS use may not prefer hanging `recover_session=clean`, and
# prefer manual de-blocklisting to avoid any failed OS syscalls,
# but for this test, discarding stale sessions is good enough.
kclient.succeed("mkdir -p /mnt/cephfs")
kclient.wait_until_succeeds(
f"mount -t ceph kclient@.testing={volume_path} /mnt/cephfs -o ms_mode=secure,recover_session=clean"
"mount -t ceph ${cfg.monA.ip}:3300:/ /mnt/cephfs -o name=admin,secretfile=/etc/ceph/admin.secret,ms_mode=secure,recover_session=clean"
)
kclient.succeed("mountpoint /mnt/cephfs")
# Mount CephFS on the FUSE client using ceph-fuse.
fuseclient.succeed("mkdir -p /mnt/cephfs")
fuseclient.wait_until_succeeds(
f"ceph-fuse --id fuseclient -m ${cfg.monA.ip}:3300 -r {volume_path} /mnt/cephfs"
"ceph-fuse --id admin -m ${cfg.monA.ip}:6789 /mnt/cephfs"
)
fuseclient.succeed("mountpoint /mnt/cephfs")
@@ -616,40 +510,24 @@ let
osd1.crash()
osd2.crash()
# Start the mon first and mark the OSDs as down.
# Since the heartbeats are pretty high by default, the OSDs would otherwise be marked as up still.
# However we do not want to lower the heartbeats since this might cause flakey tests.
monA.start()
monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
monA.wait_until_succeeds("ceph osd down all")
# Then start the OSDs as normal.
# Start it up
osd0.start()
osd1.start()
osd2.start()
# Ensure they are all up.
osd0.wait_for_unit("network.target")
osd1.wait_for_unit("network.target")
osd2.wait_for_unit("network.target")
# FIXME: dmcrypt OSDs currently do not work out of the box.
# For a potential long-term fix see: https://github.com/NixOS/nixpkgs/pull/512912#discussion_r3140295546
osd1.succeed(
"ceph-volume lvm activate --no-tmpfs --no-systemd ${cfg.osd1.name} ${cfg.osd1.uuid}",
"systemctl start ceph-osd-${cfg.osd1.name}",
)
monA.start()
# Ensure the cluster comes back up again.
# See the note above on why this uses `wait_until_succeeds`.
monA.wait_until_succeeds("ceph -s | grep 'mon: 1 daemons'")
monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'", timeout=60)
# Ensure the MDS/CephFS comes back up again, too.
monA.wait_for_unit("ceph-mds-${cfg.monA.name}")
monA.wait_until_succeeds("ceph fs status testing | grep -e 'active'", timeout=60)
monA.wait_until_succeeds("ceph fs status cephfs | grep -e 'active'", timeout=60)
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
# The clients kept running across the outage, so their CephFS mounts

View File

@@ -0,0 +1,291 @@
# Tests the legacy FileStore OSD backend.
{ lib, ... }:
let
cfg = {
clusterId = "066ae264-2a5d-4729-8001-6ad265f50b03";
monA = {
name = "a";
ip = "192.168.1.1";
};
osd0 = {
name = "0";
ip = "192.168.1.2";
key = "AQBCEJNa3s8nHRAANvdsr93KqzBznuIWm2gOGg==";
uuid = "55ba2294-3e24-478f-bee0-9dca4c231dd9";
};
osd1 = {
name = "1";
ip = "192.168.1.3";
key = "AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ==";
uuid = "5e97a838-85b6-43b0-8950-cb56d554d1e5";
};
osd2 = {
name = "2";
ip = "192.168.1.4";
key = "AQAdyhZeIaUlARAAGRoidDAmS6Vkp546UFEf5w==";
uuid = "ea999274-13d0-4dd5-9af9-ad25a324f72f";
};
};
generateCephConfig =
{ daemonConfig }:
{
enable = true;
global = {
fsid = cfg.clusterId;
monHost = cfg.monA.ip;
monInitialMembers = cfg.monA.name;
};
}
// daemonConfig;
generateHost =
{ cephConfig, networkConfig }:
{ pkgs, ... }:
{
virtualisation = {
emptyDiskImages = [ 20480 ];
vlans = [ 1 ];
};
networking = networkConfig;
environment.systemPackages = with pkgs; [
bash
sudo
ceph
xfsprogs
netcat
];
boot.kernelModules = [ "xfs" ];
services.ceph = cephConfig;
};
networkMonA = {
dhcpcd.enable = false;
interfaces.eth1.ipv4.addresses = lib.mkOverride 0 [
{
address = cfg.monA.ip;
prefixLength = 24;
}
];
firewall = {
allowedTCPPorts = [
6789
3300
];
allowedTCPPortRanges = [
{
from = 6800;
to = 7300;
}
];
};
};
cephConfigMonA = generateCephConfig {
daemonConfig = {
mon = {
enable = true;
daemons = [ cfg.monA.name ];
};
mgr = {
enable = true;
daemons = [ cfg.monA.name ];
};
};
};
networkOsd = osd: {
dhcpcd.enable = false;
interfaces.eth1.ipv4.addresses = lib.mkOverride 0 [
{
address = osd.ip;
prefixLength = 24;
}
];
firewall = {
allowedTCPPortRanges = [
{
from = 6800;
to = 7300;
}
];
};
};
cephConfigOsd =
osd:
generateCephConfig {
daemonConfig = {
osd = {
enable = true;
daemons = [ osd.name ];
};
};
};
# Following deployment is based on the manual deployment described here:
# https://docs.ceph.com/docs/master/install/manual-deployment/
# For other ways to deploy a ceph cluster, look at the documentation at
# https://docs.ceph.com/docs/master/
testscript =
{ ... }:
''
start_all()
monA.wait_for_unit("network.target")
osd0.wait_for_unit("network.target")
osd1.wait_for_unit("network.target")
osd2.wait_for_unit("network.target")
# Bootstrap ceph-mon daemon
monA.succeed(
"sudo -u ceph ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'",
"sudo -u ceph ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *' --cap mgr 'allow *'",
"sudo -u ceph ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring",
"monmaptool --create --add ${cfg.monA.name} ${cfg.monA.ip} --fsid ${cfg.clusterId} /tmp/monmap",
"sudo -u ceph ceph-mon --mkfs -i ${cfg.monA.name} --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring",
"sudo -u ceph mkdir -p /var/lib/ceph/mgr/ceph-${cfg.monA.name}/",
"sudo -u ceph touch /var/lib/ceph/mon/ceph-${cfg.monA.name}/done",
"systemctl start ceph-mon-${cfg.monA.name}",
)
monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
monA.succeed("ceph mon enable-msgr2")
monA.succeed("ceph config set mon auth_allow_insecure_global_id_reclaim false")
# Can't check ceph status until a mon is up
monA.succeed("ceph -s | grep 'mon: 1 daemons'")
# Start the ceph-mgr daemon, it has no deps and hardly any setup
monA.succeed(
"ceph auth get-or-create mgr.${cfg.monA.name} mon 'allow profile mgr' osd 'allow *' mds 'allow *' > /var/lib/ceph/mgr/ceph-${cfg.monA.name}/keyring",
"sync", # to ensure shell redirection above is durable
"systemctl start ceph-mgr-${cfg.monA.name}",
)
monA.wait_for_unit("ceph-mgr-a")
monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
# Send the admin keyring to the OSD machines
monA.succeed("cp /etc/ceph/ceph.client.admin.keyring /tmp/shared")
osd0.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph")
osd1.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph")
osd2.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph")
# Bootstrap OSDs
osd0.succeed(
"mkfs.xfs /dev/vdb",
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd0.name}",
"mount /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd0.name}",
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd0.name}/keyring --name osd.${cfg.osd0.name} --add-key ${cfg.osd0.key}",
'echo \'{"cephx_secret": "${cfg.osd0.key}"}\' | ceph osd new ${cfg.osd0.uuid} -i -',
)
osd1.succeed(
"mkfs.xfs /dev/vdb",
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd1.name}",
"mount /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd1.name}",
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd1.name}/keyring --name osd.${cfg.osd1.name} --add-key ${cfg.osd1.key}",
'echo \'{"cephx_secret": "${cfg.osd1.key}"}\' | ceph osd new ${cfg.osd1.uuid} -i -',
)
osd2.succeed(
"mkfs.xfs /dev/vdb",
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd2.name}",
"mount /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd2.name}",
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd2.name}/keyring --name osd.${cfg.osd2.name} --add-key ${cfg.osd2.key}",
'echo \'{"cephx_secret": "${cfg.osd2.key}"}\' | ceph osd new ${cfg.osd2.uuid} -i -',
)
# We `sync` so that the config survives the forced crashes below.
osd0.succeed(
"ceph-osd -i ${cfg.osd0.name} --mkfs --osd-uuid ${cfg.osd0.uuid}",
"chown -R ceph:ceph /var/lib/ceph/osd",
"sync",
"systemctl start ceph-osd-${cfg.osd0.name}",
)
osd1.succeed(
"ceph-osd -i ${cfg.osd1.name} --mkfs --osd-uuid ${cfg.osd1.uuid}",
"chown -R ceph:ceph /var/lib/ceph/osd",
"sync",
"systemctl start ceph-osd-${cfg.osd1.name}",
)
osd2.succeed(
"ceph-osd -i ${cfg.osd2.name} --mkfs --osd-uuid ${cfg.osd2.uuid}",
"chown -R ceph:ceph /var/lib/ceph/osd",
"sync",
"systemctl start ceph-osd-${cfg.osd2.name}",
)
monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
monA.succeed(
"ceph osd pool create multi-node-test 32 32",
"ceph osd pool ls | grep 'multi-node-test'",
# We need to enable an application on the pool, otherwise it will
# stay unhealthy in state POOL_APP_NOT_ENABLED.
# Creating a CephFS would do this automatically, but we haven't done that here.
# See: https://docs.ceph.com/en/reef/rados/operations/pools/#associating-a-pool-with-an-application
# We use the custom application name "nixos-test" for this.
"ceph osd pool application enable multi-node-test nixos-test",
"ceph osd pool rename multi-node-test multi-node-other-test",
"ceph osd pool ls | grep 'multi-node-other-test'",
)
monA.wait_until_succeeds("ceph -s | grep '2 pools, 33 pgs'")
monA.succeed("ceph osd pool set multi-node-other-test size 2")
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
monA.wait_until_succeeds("ceph -s | grep '33 active+clean'")
monA.fail(
"ceph osd pool ls | grep 'multi-node-test'",
"ceph osd pool delete multi-node-other-test multi-node-other-test --yes-i-really-really-mean-it",
)
# Shut down ceph on all machines in a very unpolite way
monA.crash()
osd0.crash()
osd1.crash()
osd2.crash()
# Start it up
osd0.start()
osd1.start()
osd2.start()
monA.start()
# Ensure the cluster comes back up again
monA.succeed("ceph -s | grep 'mon: 1 daemons'")
monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
'';
in
{
name = "basic-multi-node-ceph-cluster-deprecated-filestore";
meta = with lib.maintainers; {
maintainers = [ lejonet ];
};
nodes = {
monA = generateHost {
cephConfig = cephConfigMonA;
networkConfig = networkMonA;
};
osd0 = generateHost {
cephConfig = cephConfigOsd cfg.osd0;
networkConfig = networkOsd cfg.osd0;
};
osd1 = generateHost {
cephConfig = cephConfigOsd cfg.osd1;
networkConfig = networkOsd cfg.osd1;
};
osd2 = generateHost {
cephConfig = cephConfigOsd cfg.osd2;
networkConfig = networkOsd cfg.osd2;
};
};
testScript = testscript;
}

View File

@@ -0,0 +1,269 @@
{ lib, ... }:
let
# the single node ipv6 address
ip = "2001:db8:ffff::";
# the global ceph cluster id
cluster = "54465b37-b9d8-4539-a1f9-dd33c75ee45a";
# the fsids of OSDs
osd-fsid-map = {
"0" = "1c1b7ea9-06bf-4d30-9a01-37ac3a0254aa";
"1" = "bd5a6f49-69d5-428c-ac25-a99f0c44375c";
"2" = "c90de6c7-86c6-41da-9694-e794096dfc5c";
};
in
{
name = "basic-single-node-ceph-cluster-bluestore-dmcrypt";
meta.maintainers = with lib.maintainers; [
benaryorg
nh2
];
nodes.ceph =
{
lib,
pkgs,
config,
...
}:
{
# disks for bluestore
virtualisation.emptyDiskImages = [
20480
20480
20480
];
# networking setup (no external connectivity required, only local IPv6)
networking.useDHCP = false;
systemd.network = {
enable = true;
wait-online.extraArgs = [
"-i"
"lo"
];
networks = {
"40-loopback" = {
enable = true;
name = "lo";
DHCP = "no";
addresses = [ { Address = "${ip}/128"; } ];
};
};
};
# do not start the ceph target by default so we can format the disks first
systemd.targets.ceph.wantedBy = lib.mkForce [ ];
# add the packages to systemPackages so the testscript doesn't run into any unexpected issues
# this shouldn't be required on production systems which have their required packages in the unit paths only
# but it helps in case one needs to actually run the tooling anyway
environment.systemPackages = with pkgs; [
ceph
cryptsetup
lvm2
];
services.ceph = {
enable = true;
client.enable = true;
extraConfig = {
public_addr = ip;
cluster_addr = ip;
# ipv6
ms_bind_ipv4 = "false";
ms_bind_ipv6 = "true";
# msgr2 settings
ms_cluster_mode = "secure";
ms_service_mode = "secure";
ms_client_mode = "secure";
ms_mon_cluster_mode = "secure";
ms_mon_service_mode = "secure";
ms_mon_client_mode = "secure";
# less default modules, cuts down on memory and startup time in the tests
mgr_initial_modules = "";
# distribute by OSD, not by host, as per https://docs.ceph.com/en/reef/cephadm/install/#single-host
osd_crush_chooseleaf_type = "0";
};
client.extraConfig."mon.0" = {
host = "ceph";
mon_addr = "v2:[${ip}]:3300";
public_addr = "v2:[${ip}]:3300";
};
global = {
fsid = cluster;
clusterNetwork = "${ip}/64";
publicNetwork = "${ip}/64";
monInitialMembers = "0";
};
mon = {
enable = true;
daemons = [ "0" ];
};
osd = {
enable = true;
daemons = builtins.attrNames osd-fsid-map;
};
mgr = {
enable = true;
daemons = [ "ceph" ];
};
};
systemd.services =
let
osd-name = id: "ceph-osd-${id}";
osd-pre-start = id: [
"!${config.services.ceph.osd.package.out}/bin/ceph-volume lvm activate --bluestore ${id} ${osd-fsid-map.${id}} --no-systemd"
"${config.services.ceph.osd.package.lib}/libexec/ceph/ceph-osd-prestart.sh --id ${id} --cluster ${config.services.ceph.global.clusterName}"
];
osd-post-stop = id: [
"!${config.services.ceph.osd.package.out}/bin/ceph-volume lvm deactivate ${id}"
];
map-osd = id: {
name = osd-name id;
value = {
serviceConfig.ExecStartPre = lib.mkForce (osd-pre-start id);
serviceConfig.ExecStopPost = osd-post-stop id;
unitConfig.ConditionPathExists = lib.mkForce [ ];
unitConfig.StartLimitBurst = lib.mkForce 4;
path = with pkgs; [
util-linux
lvm2
cryptsetup
];
};
};
in
lib.pipe config.services.ceph.osd.daemons [
(map map-osd)
builtins.listToAttrs
];
};
testScript = ''
start_all()
ceph.wait_for_unit("default.target")
# Bootstrap ceph-mon daemon
ceph.succeed(
"mkdir -p /var/lib/ceph/bootstrap-osd",
"ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'",
"ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *' --cap mgr 'allow *'",
"ceph-authtool --create-keyring /var/lib/ceph/bootstrap-osd/ceph.keyring --gen-key -n client.bootstrap-osd --cap mon 'profile bootstrap-osd' --cap mgr 'allow r'",
"ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring",
"ceph-authtool /tmp/ceph.mon.keyring --import-keyring /var/lib/ceph/bootstrap-osd/ceph.keyring",
"monmaptool --create --fsid ${cluster} --addv 0 'v2:[${ip}]:3300/0' --clobber /tmp/ceph.initial-monmap",
"mkdir -p /var/lib/ceph/mon/ceph-0",
"ceph-mon --mkfs -i 0 --monmap /tmp/ceph.initial-monmap --keyring /tmp/ceph.mon.keyring",
"chown ceph:ceph -R /tmp/ceph.mon.keyring /var/lib/ceph",
"systemctl start ceph-mon-0.service",
)
ceph.wait_for_unit("ceph-mon-0.service")
# should the mon not start or bind for some reason this gives us a better error message than the config commands running into a timeout
ceph.wait_for_open_port(3300, "${ip}")
ceph.succeed(
# required for HEALTH_OK
"ceph config set mon auth_allow_insecure_global_id_reclaim false",
# IPv6
"ceph config set global ms_bind_ipv4 false",
"ceph config set global ms_bind_ipv6 true",
# the new (secure) protocol
"ceph config set global ms_bind_msgr1 false",
"ceph config set global ms_bind_msgr2 true",
# just a small little thing
"ceph config set mon mon_compact_on_start true",
)
# Can't check ceph status until a mon is up
ceph.succeed("ceph -s | grep 'mon: 1 daemons'")
# Bootstrap OSDs (do this before starting the mgr because cryptsetup and the mgr both eat a lot of memory)
ceph.succeed(
# this will automatically do what's required for LVM, cryptsetup, and stores all the data in Ceph's internal databases
"ceph-volume lvm prepare --bluestore --data /dev/vdb --dmcrypt --no-systemd --osd-id 0 --osd-fsid ${osd-fsid-map."0"}",
"ceph-volume lvm prepare --bluestore --data /dev/vdc --dmcrypt --no-systemd --osd-id 1 --osd-fsid ${osd-fsid-map."1"}",
"ceph-volume lvm prepare --bluestore --data /dev/vdd --dmcrypt --no-systemd --osd-id 2 --osd-fsid ${osd-fsid-map."2"}",
"sudo ceph-volume lvm deactivate 0",
"sudo ceph-volume lvm deactivate 1",
"sudo ceph-volume lvm deactivate 2",
"chown -R ceph:ceph /var/lib/ceph",
)
# Start OSDs (again, argon2id eats memory, so this happens before starting the mgr)
ceph.succeed(
"systemctl start ceph-osd-0.service",
"systemctl start ceph-osd-1.service",
"systemctl start ceph-osd-2.service",
)
ceph.wait_until_succeeds("ceph -s | grep 'quorum 0'")
ceph.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
# Start the ceph-mgr daemon, after copying in the keyring
ceph.succeed(
"mkdir -p /var/lib/ceph/mgr/ceph-ceph/",
"ceph auth get-or-create -o /var/lib/ceph/mgr/ceph-ceph/keyring mgr.ceph mon 'allow profile mgr' osd 'allow *' mds 'allow *'",
"chown -R ceph:ceph /var/lib/ceph/mgr/ceph-ceph/",
"systemctl start ceph-mgr-ceph.service",
)
ceph.wait_for_unit("ceph-mgr-ceph")
ceph.wait_until_succeeds("ceph -s | grep 'quorum 0'")
ceph.wait_until_succeeds("ceph -s | grep 'mgr: ceph(active,'")
ceph.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
ceph.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
# test the actual storage
ceph.succeed(
"ceph osd pool create single-node-test 32 32",
"ceph osd pool ls | grep 'single-node-test'",
# We need to enable an application on the pool, otherwise it will
# stay unhealthy in state POOL_APP_NOT_ENABLED.
# Creating a CephFS would do this automatically, but we haven't done that here.
# See: https://docs.ceph.com/en/reef/rados/operations/pools/#associating-a-pool-with-an-application
# We use the custom application name "nixos-test" for this.
"ceph osd pool application enable single-node-test nixos-test",
"ceph osd pool rename single-node-test single-node-other-test",
"ceph osd pool ls | grep 'single-node-other-test'",
)
ceph.wait_until_succeeds("ceph -s | grep '2 pools, 33 pgs'")
ceph.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
ceph.wait_until_succeeds("ceph -s | grep '33 active+clean'")
ceph.fail(
# the old pool should be gone
"ceph osd pool ls | grep 'multi-node-test'",
# deleting the pool should fail without setting mon_allow_pool_delete
"ceph osd pool delete single-node-other-test single-node-other-test --yes-i-really-really-mean-it",
)
# rebooting gets rid of any potential tmpfs mounts or device-mapper devices
ceph.shutdown()
ceph.start()
ceph.wait_for_unit("default.target")
# Start it up (again OSDs first due to memory constraints of cryptsetup and mgr)
ceph.systemctl("start ceph-mon-0.service")
ceph.wait_for_unit("ceph-mon-0")
ceph.systemctl("start ceph-osd-0.service")
ceph.wait_for_unit("ceph-osd-0")
ceph.systemctl("start ceph-osd-1.service")
ceph.wait_for_unit("ceph-osd-1")
ceph.systemctl("start ceph-osd-2.service")
ceph.wait_for_unit("ceph-osd-2")
ceph.systemctl("start ceph-mgr-ceph.service")
ceph.wait_for_unit("ceph-mgr-ceph")
# Ensure the cluster comes back up again
ceph.succeed("ceph -s | grep 'mon: 1 daemons'")
ceph.wait_until_succeeds("ceph -s | grep 'quorum 0'")
ceph.wait_until_succeeds("ceph osd stat | grep -E '3 osds: 3 up[^,]*, 3 in'")
ceph.wait_until_succeeds("ceph -s | grep 'mgr: ceph(active,'")
ceph.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
'';
}

View File

@@ -9,14 +9,17 @@ let
};
osd0 = {
name = "0";
key = "AQBCEJNa3s8nHRAANvdsr93KqzBznuIWm2gOGg==";
uuid = "55ba2294-3e24-478f-bee0-9dca4c231dd9";
};
osd1 = {
name = "1";
key = "AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ==";
uuid = "5e97a838-85b6-43b0-8950-cb56d554d1e5";
};
osd2 = {
name = "2";
key = "AQAdyhZeIaUlARAAGRoidDAmS6Vkp546UFEf5w==";
uuid = "ea999274-13d0-4dd5-9af9-ad25a324f72f";
};
};
@@ -48,11 +51,6 @@ let
vlans = [ 1 ];
};
# Ceph 20.2.4 introduced the aes256k cipher for authentication.
# Linux started supporting these in kernel version 7.0.
# Remove this line at the earliest convenience (i.e. when tests are run by 7.0 or higher by default).
boot.kernelPackages = pkgs.linuxPackages_latest;
networking = networkConfig;
environment.systemPackages = with pkgs; [
@@ -117,18 +115,13 @@ let
"sudo -u ceph ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'",
"sudo -u ceph ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *' --cap mgr 'allow *'",
"sudo -u ceph ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring",
# Create the monmap with both a msgr2 (v2) and a legacy (v1) address.
# Using plain `--add` yields a v1-only monmap, which leaves the cluster
# in HEALTH_WARN with MON_MSGR2_NOT_ENABLED. Running `ceph mon
# enable-msgr2` afterwards is not enough: it rewrites the monmap (a
# subsequent `ceph mon dump` does show the v2 address), but the health
# check keeps reporting the mon as v1-only indefinitely.
"monmaptool --create --addv ${cfg.monA.name} '[v2:${cfg.monA.ip}:3300,v1:${cfg.monA.ip}:6789]' --fsid ${cfg.clusterId} /tmp/monmap",
"monmaptool --create --add ${cfg.monA.name} ${cfg.monA.ip} --fsid ${cfg.clusterId} /tmp/monmap",
"sudo -u ceph ceph-mon --mkfs -i ${cfg.monA.name} --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring",
"sudo -u ceph touch /var/lib/ceph/mon/ceph-${cfg.monA.name}/done",
"systemctl start ceph-mon-${cfg.monA.name}",
)
monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
monA.succeed("ceph mon enable-msgr2")
monA.succeed("ceph config set mon auth_allow_insecure_global_id_reclaim false")
# Can't check ceph status until a mon is up
@@ -155,24 +148,14 @@ let
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd2.name}",
"echo bluestore > /var/lib/ceph/osd/ceph-${cfg.osd2.name}/type",
"ln -sf /dev/vdd /var/lib/ceph/osd/ceph-${cfg.osd2.name}/block",
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd0.name}/keyring --name osd.${cfg.osd0.name} --gen-key",
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd1.name}/keyring --name osd.${cfg.osd1.name} --gen-key",
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd2.name}/keyring --name osd.${cfg.osd2.name} --gen-key",
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd0.name}/keyring --name osd.${cfg.osd0.name} --add-key ${cfg.osd0.key}",
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd1.name}/keyring --name osd.${cfg.osd1.name} --add-key ${cfg.osd1.key}",
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd2.name}/keyring --name osd.${cfg.osd2.name} --add-key ${cfg.osd2.key}",
'echo \'{"cephx_secret": "${cfg.osd0.key}"}\' | ceph osd new ${cfg.osd0.uuid} -i -',
'echo \'{"cephx_secret": "${cfg.osd1.key}"}\' | ceph osd new ${cfg.osd1.uuid} -i -',
'echo \'{"cephx_secret": "${cfg.osd2.key}"}\' | ceph osd new ${cfg.osd2.uuid} -i -',
)
# Register the OSDs with the generated keys read back from their keyrings.
for osd_name, osd_uuid in [
("${cfg.osd0.name}", "${cfg.osd0.uuid}"),
("${cfg.osd1.name}", "${cfg.osd1.uuid}"),
("${cfg.osd2.name}", "${cfg.osd2.uuid}"),
]:
key = monA.succeed(
f"ceph-authtool --print-key /var/lib/ceph/osd/ceph-{osd_name}/keyring --name osd.{osd_name}"
).strip()
monA.succeed(
f"echo '{{\"cephx_secret\": \"{key}\"}}' | ceph osd new {osd_uuid} -i -"
)
# Initialize the OSDs with regular filestore
monA.succeed(
"ceph-osd -i ${cfg.osd0.name} --mkfs --osd-uuid ${cfg.osd0.uuid}",

Some files were not shown because too many files have changed in this diff Show More