Skip to content

caddy: add suport for compiling Caddy with plugins#358586

Merged
thiagokokada merged 3 commits intoNixOS:masterfrom
vincentbernat:feature/caddy-plugins
Dec 26, 2024
Merged

caddy: add suport for compiling Caddy with plugins#358586
thiagokokada merged 3 commits intoNixOS:masterfrom
vincentbernat:feature/caddy-plugins

Conversation

@vincentbernat
Copy link
Member

@vincentbernat vincentbernat commented Nov 23, 2024

This adds a withPlugins function to Caddy package.

services.caddy = {
  enable = true;
  package = pkgs.caddy.withPlugins {
    plugins = [ "github.com/caddy-dns/powerdns@v1.0.1" ];
    hash = "sha256-F/jqR4iEsklJFycTjSaW8B/V3iTGqqGOzwYBUXxRKrc=";
  };
};

Fix: #14671

This is an alternative to #317881 and it relies on xcaddy. Looking at #317881 (comment), I am still missing tests. I am unsure if this is a build test (in checkPhase) or a NixOS test. The remaining requirements should be OK (notably use of xcaddy and FOD).

The release notes are missing, I'll add them once this gets a chance to be accepted.

Things done

  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • For non-Linux: Is sandboxing enabled in nix.conf? (See Nix manual)
    • sandbox = relaxed
    • sandbox = true
  • Tested, as applicable:
  • Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD". Note: all changes have to be committed, also see nixpkgs-review usage
  • Tested basic functionality of all binary files (usually in ./result/bin/)
  • 25.05 Release Notes (or backporting 24.11 and 25.05 Release notes)
    • (Package updates) Added a release notes entry if the change is major or breaking
    • (Module updates) Added a release notes entry if the change is significant
    • (Module addition) Added a release notes entry if adding a new NixOS module
  • Fits CONTRIBUTING.md.

Add a 👍 reaction to pull requests you find important.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick question, should we make the build fail if a plugin is provided without the version string?

For example:

pkgs.caddy.withPlugins {
  plugins = [ "github.com/caddy-dns/powerdns@v1.0.1" "github.com/caddy-dns/cloudflare" ];
  hash = "sha256-AoW35l7QkXunjBzZ43IlyU3UkVXw2D4eyc1jx8xpT0U=";
}

After testing this on my darwin machine, I have the following:

$ /nix/store/icp2z20hpf2ps7g4n5rzqdkg5qsjp38z-caddy-2.8.4/bin/caddy build-info
...
dep	github.com/caddy-dns/cloudflare	v0.0.0-20240703190432-89f16b99c18e
dep	github.com/caddy-dns/powerdns	v1.0.1	
...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think I can add an assertion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added an assertion (using the first non-versioned plugin as an example).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nit, imo pluginsHash makes the build log a bit too long, maybe caddy-src-with-plugins is good enough here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's there to ensure a cached build is not used when adding/removing a plugin. This was one of the request in #317881 (comment).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have switched to md5 to reduce the length a bit. We could also use a subset of the hash if it's still too long.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if using md5 is a good idea, IIRC it's considered outdated and insecure (citation needed)?

I took a look into the comment you linked, it seems like it'd be a good idea to include a test to make sure specified plugins are properly installed. I came up with the following but don't have time to dig deeper (feel free to take whatever you need):

diff --git a/pkgs/by-name/ca/caddy/package.nix b/pkgs/by-name/ca/caddy/package.nix
index eea6894ce328..c052da5ef290 100644
--- a/pkgs/by-name/ca/caddy/package.nix
+++ b/pkgs/by-name/ca/caddy/package.nix
@@ -116,6 +116,31 @@ buildGoModule {
             outputHash = hash;
             outputHashAlgo = "sha256";
           };
+
+          doInstallCheck = true;
+          installCheckPhase = ''
+            runHook preInstallCheck
+
+            build_info="$($out/bin/caddy build-info)"
+
+            for plugin in ''${plugins[@]}; do
+              # this won't work :(
+              echo $plugin
+              url=$(echo "$plugin" | cut -d'@' -f1)
+              version=$(echo "$plugin" | cut -d'@' -f2)
+              echo $url
+              echo $version
+
+              if echo "$build_info" | grep -q "$url[[:space:]]*$version"; then
+                echo "$plugin found in build-info"
+              else
+                echo "$plugin not found in build-info" >&2
+                exit 1
+              fi
+            done
+
+            runHook postInstallCheck
+          '';
       });
   };

For testing:

nom-build --expr 'with import ./. { }; caddy.withPlugins { plugins = [ "github.com/caddy-dns/powerdns@v1.0.1" "github.com/caddy-dns/cloudflare@v0.0.0-20240703190432-89f16b99c18e" ]; hash = "sha256-AoW35l7QkXunjBzZ43IlyU3UkVXw2D4eyc1jx8xpT0U="; }'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added a postInstallCheck as you suggest.

As for the MD5, this is not meant to be secure, just a safety to ensure the user does not forget to update the hash when modifying plugins. A mechanism like this was requested in the original PR.

@ofborg ofborg bot added 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin. 10.rebuild-linux: 0 This PR does not cause any packages to rebuild on Linux. labels Nov 24, 2024
@stepbrobd
Copy link
Member

LGTM!

Tested with couple more plugins and everything works fine on aarch64-darwin and x86_64-linux

@stepbrobd stepbrobd added the 12.approvals: 1 This PR was reviewed and approved by one person. label Nov 29, 2024
@stepbrobd
Copy link
Member

cc @NickCao

Copy link
Member

@NickCao NickCao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from the nitpick, looks pretty good!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
unpackPhase = "true";
dontUnpack = true;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Thanks.

@BatteredBunny
Copy link
Contributor

fyi theres a typo in the commit message

@vincentbernat vincentbernat force-pushed the feature/caddy-plugins branch 2 times, most recently from e1c0a97 to 56bc3a5 Compare December 1, 2024 07:03
@github-actions github-actions bot added 6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: documentation This PR adds or changes documentation 8.has: changelog This PR adds or changes release notes labels Dec 1, 2024
@vincentbernat
Copy link
Member Author

I have also updated the release notes.

@github-actions github-actions bot added 10.rebuild-linux: 1-10 This PR causes between 1 and 10 packages to rebuild on Linux. and removed 10.rebuild-linux: 0 This PR does not cause any packages to rebuild on Linux. labels Dec 1, 2024
@wegank wegank removed the 12.approvals: 1 This PR was reviewed and approved by one person. label Dec 1, 2024
@stepbrobd stepbrobd added the 12.approvals: 1 This PR was reviewed and approved by one person. label Dec 3, 2024
@vincentbernat
Copy link
Member Author

I have added git as a native build input, as it may be required for modules not present on proxy.golang.org. There was a comment about that. goModule also uses git as a native build input.

@wegank wegank removed the 12.approvals: 1 This PR was reviewed and approved by one person. label Dec 3, 2024
Copy link
Contributor

@kusold kusold Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran into an issue with this check when plugin versions are specified as git-shas.

When given the input:

    plugins = [
      "github.com/caddy-dns/cloudflare@89f16b99c18ef49c8bb470a82f895bce01cbaece"
      "github.com/dulli/caddy-wol@c0d58507c9037191aa9622d531a001db619dd543"
    ];
    hash = "sha256-pKiL3bGcFVcD7r67mY9RtAEHDJc+gq3vz8DkZYssfb4=";

caddy build-info contains:

...
dep     github.com/caddy-dns/cloudflare v0.0.0-20240703190432-89f16b99c18e
...
dep     github.com/dulli/caddy-wol      v1.0.1-0.20240903185854-c0d58507c903
...

This causes this check to fail because the versions were substituted. If I update my input to the versions, it works:

plugins = [
      "github.com/caddy-dns/cloudflare@v0.0.0-20240703190432-89f16b99c18e"
      "github.com/dulli/caddy-wol@v1.0.1-0.20240903185854-c0d58507c903"
    ];
    hash = "sha256-pKiL3bGcFVcD7r67mY9RtAEHDJc+gq3vz8DkZYssfb4=";

Do you need to check for the version, or is matching the module sufficient?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO checking module + version is better, and you can get the format it wants beforehand with go get and check the go.mod file

@thegatesdev
Copy link

Is there intent to support plugins like FrankenPHP?
FrankenPHP requires PHP to be present at compile- and runtime and uses CGO.

Comment on lines 81 to 87
let
pluginsSorted = builtins.sort builtins.lessThan plugins;
pluginsList = lib.concatMapStrings (plugin: "${plugin}-") pluginsSorted;
pluginsHash = builtins.hashString "md5" pluginsList;
pluginsWithoutVersion = builtins.filter (p: !lib.hasInfix "@" p) pluginsSorted;
in
assert lib.assertMsg (builtins.length pluginsWithoutVersion == 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another non-blocking nit, but in general builtins should be avoided because sometimes we have polyfills for older versions of Nix inside lib, so in general we should always prefer using lib.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, except for builtins.hashString as there does not seem there is an equivalent.

@wegank wegank added the 12.approvals: 2 This PR was reviewed and approved by two persons. label Dec 26, 2024
@thiagokokada thiagokokada merged commit 9ceb117 into NixOS:master Dec 26, 2024
4 of 5 checks passed
@oxalica
Copy link
Contributor

oxalica commented Dec 27, 2024

It does not work well with a commit-hash version. I'm using https://github.com/mholt/caddy-webdav which does not have a version tag, and it results in an unhelpful build error.

caddy.withPlugins {
  plugins = [
    "github.com/mholt/caddy-webdav@42168ba04c9dc2cd228ab8c453dbab27654e52e6"
  ];
  hash = "";
}
caddy> Running phase: installCheckPhase
caddy> Plugins not found: github.com/mholt/caddy-webdav@42168ba04c9dc2cd228ab8c453dbab27654e52e6

Thanks to @kusold 's #358586 (comment), I need to fill in the magic v0.0.0-{date}-{shortrev} to make it build. (Sorry if it's in any obvious format but I'm not a Go dev)

If this is intentional, could we improve the documentation to inform how to generate a valid version string for commit-hash-only plugins? Or if it's possible, could we provide these information directly in build error messages?

@stepbrobd
Copy link
Member

stepbrobd commented Dec 27, 2024

I need to fill in the magic v0.0.0-{date}-{shortrev} to make it build

What I usually do is create an empty project and run go get on the plugin to get the full string:

$ go mod init temp
$ go get github.com/mholt/caddy-webdav
$ grep 'caddy-webdav' go.mod
        github.com/mholt/caddy-webdav v0.0.0-20241008162340-42168ba04c9d // indirect

I'll make a new PR to update the docs and perhaps a script to automatically generate those strings for untagged plugins

@oxalica
Copy link
Contributor

oxalica commented Jan 13, 2025

Is it possible to override the caddy source when using withPlugin? It seems (caddy.overrideAttrs (old: { patches = [ .. ]; })).withPlugin { .. } will not applying the patch at all. Overriding src or version to use a different version also has no effect.

I tried (caddy.withPlugin { .. }).overrideAttrs (old: { patches = [ .. ]; }). It does try to apply the patch, but fails because the source layout is significantly different than upstream.

@stepbrobd
Copy link
Member

stepbrobd commented Jan 14, 2025

Oof this is a hard one

withPlugin overrides the caddy source by using the one generated by xcaddy, i.e. if you call withPlugins, the src will be replaced (from the full caddy source) to only one main.go and vendored dependencies (where the full caddy source will be moved to)

If you want to apply the patches, try changing the patch paths to /vendor/github.com/caddyserver/caddy/v2/.... In other words, instead of apply patches directly on (caddy.withPlugins {...}).src, apply them to "${(caddy.withPlugins {...}).src}/vendor/github.com/caddyserver/caddy/v2/"

To illustrate:

$ # im in nixpkgs
$ nix repl -f .
Type :? for help.
Loading installable ''...
Added 23515 variables.
nix-repl> :b (caddy.withPlugins {
          plugins = [
                "github.com/caddy-dns/cloudflare@v0.0.0-20240703190432-89f16b99c18e"
          ];
          hash = "sha256-JoujVXRXjKUam1Ej3/zKVvF0nX97dUizmISjy3M3Kr8=";
          }).src

This derivation produced the following outputs:
  out -> /nix/store/48vdfpdaz04wcdqvymx7cx29vsav5pxj-caddy-src-with-plugins-0bbf432f07a600413ee833233180f51d-2.9.0
nix-repl> :b caddy.src

This derivation produced the following outputs:
  out -> /nix/store/vwmnpjxxfq1ffc5a82c0q24p0c3sdl3h-source
$ # withPlugins caddy src:
$ > ls -l /nix/store/48vdfpdaz04wcdqvymx7cx29vsav5pxj-caddy-src-with-plugins-0bbf432f07a600413ee833233180f51d-2.9.0/vendor/github.com/caddyserver/caddy/v2
total 608
-r--r--r--   1 root  nixbld    399 Dec 31  1969 AUTHORS
-r--r--r--   1 root  nixbld  11358 Dec 31  1969 LICENSE
-r--r--r--   1 root  nixbld  12002 Dec 31  1969 README.md
-r--r--r--   1 root  nixbld  45621 Dec 31  1969 admin.go
-r--r--r--   1 root  nixbld  33278 Dec 31  1969 caddy.go
...
$ # caddy.src:
$ ls -l /nix/store/vwmnpjxxfq1ffc5a82c0q24p0c3sdl3h-source
total 880
-r--r--r--   1 root  nixbld    399 Dec 31  1969 AUTHORS
-r--r--r--   1 root  nixbld  11358 Dec 31  1969 LICENSE
-r--r--r--   1 root  nixbld  12002 Dec 31  1969 README.md
-r--r--r--   1 root  nixbld  45621 Dec 31  1969 admin.go
-r--r--r--   1 root  nixbld   5790 Dec 31  1969 admin_test.go
-r--r--r--   1 root  nixbld  33278 Dec 31  1969 caddy.go
...

Edit:

If you want to use a specific version of caddy, you might have to patch xcaddy as well, since the vendored caddy source is pinned in xcaddy's go.mod 🤣

@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/tailscale-caddy-certificates-not-working/61228/6

@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/using-nix-to-replace-xcaddy-to-build-caddy-webserver/11765/3

@Jonas-Sander
Copy link
Contributor

Please add this to the docs!
I wasted hours, because I didn't know about this way.

@aaravrav
Copy link
Contributor

How can I replace a caddy module with my own? This is implemented in xcaddy's --with flag

@stepbrobd
Copy link
Member

How can I replace a caddy module with my own? This is implemented in xcaddy's --with flag

use this patch for now, should be merged fairly soon #433072

@sokai
Copy link

sokai commented Sep 3, 2025

@stepbrobd´: Thanks a lot for your/this implementation + dedication!
May I ask you, if/how it's possible to use the GOPROXY env var during the building process? – I tried a lot, with no luck, to use my Athens proxy, that works with go install $MODULE$ but not with nixos-rebuild).
KR

@stepbrobd
Copy link
Member

@stepbrobd´: Thanks a lot for your/this implementation + dedication! May I ask you, if/how it's possible to use the GOPROXY env var during the building process? – I tried a lot, with no luck, to use my Athens proxy, that works with go install $MODULE$ but not with nixos-rebuild). KR

My immediate thought is using overrideAttrs to inject GOPROXY into the build env var... Have you tried that yet?

@sokai
Copy link

sokai commented Sep 3, 2025

@stepbrobd Thanks for your fast reply! :)

using overrideAttrs to inject GOPROXY into the build env var

Yes, think so …

Current/simple setup

caddyWithPlugins = pkgs.caddy.withPlugins {
  plugins = [
    […]
  ];
  hash = "sha256-9TR2vFgrxupy8O4i+a05+/Fj2D4sQCw42q2odX22fD0=";
};

overrideAttrs setup

caddyWithPlugins = (pkgs.caddy.overrideAttrs (
  finalAttrs: prevAttrs: {
    env.GOPROXY = "http://$MYATHENSPROXYHOSTNAME:3000,https://proxy.golang.org,direct";
  }
)).withPlugins {
  plugins = [
    […]
  ];
  hash = "sha256-9TR2vFgrxupy8O4i+a05+/Fj2D4sQCw42q2odX22fD0=";
};

With both setups $GOPROXY isn't used … :/

@stepbrobd
Copy link
Member

sorry about the late reply, i was at nixcon last week. the problem is with where GOPROXY is used:

the above snippet in your reply overrides the withPlugins' build environment variable, where all the dependencies are already fetched by xcaddy (check the with plugins implementation, we are overriding caddy's source with xcaddy's build env)

the correct place to do the override is at (caddy.withPlugins { ... }).src).overrideAttrs (...). for example:

$ nix build -L --impure --expr '
  ((import ./. {}).caddy.withPlugins {
    plugins = [ "github.com/mholt/caddy-webdav@v0.0.0-20241008162340-42168ba04c9d" ];
    hash = "sha256-ymuWQ6ic2MacS3JiGZ5Nu/wq2Iz53TOhxrylRIz3CQc=";
  }).overrideAttrs (prev: {
    src = prev.src.overrideAttrs (_: { env.GOPROXY = "sup"; });
  })
'
...
/nix/store/rgmygksbfyy75iappxpinv0y8lxfq35x-go-1.24.6/bin/go mod init caddy
       > go: creating new go.mod: module caddy
       > go: to add module requirements and sums:
       >    go mod tidy
       > 2025/09/09 12:00:02 [INFO] Pinning versions
       > 2025/09/09 12:00:02 [INFO] exec (timeout=0s): /nix/store/rgmygksbfyy75iappxpinv0y8lxfq35x-go-1.24.6/bin/go get -v github.com/caddyserver/caddy/v2@v2.10.2
       > go: github.com/caddyserver/caddy/v2@v2.10.2: invalid proxy URL missing scheme: sup
       > 2025/09/09 12:00:02 [FATAL] exit status 1
...

shoud give you what you want

@stepbrobd
Copy link
Member

for future reference, if you have a question on using caddy withPlugins, or for some reason it doesn't work for your use case, please open a new issue and ping the maintainers there instead of replying to this old PR. thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: changelog This PR adds or changes release notes 8.has: documentation This PR adds or changes documentation 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin. 10.rebuild-linux: 1-10 This PR causes between 1 and 10 packages to rebuild on Linux. 12.approvals: 2 This PR was reviewed and approved by two persons.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

caddy: add all addons